深柳堂

饭否消息解析之从minidom到xpath

抛板砖,引白玉:为何不用xpath,什么是xpath?

最近拾起了以前的小项目,在完善上篇文章发布后,“那个谁”的回复让我很感兴趣。他问,“为什么不用xpath?”

xpath是什么东东?我反问。反问之前,当然少不了先google一番,以免……那个啥。

首先映入眼帘的是w3c ,对xpath的介绍如下:

XPath is a language for addressing parts of an XML document, designed to be used by both XSLT and XPointer.

直译为中文就是,

XPath 是一种语言,用于在XML文档中定位各部分内容,可由XSLT或XPointer调用。

还搜索到xpath的教程,在这里。草草看过,当时并未着意。

虽如此,但是python里的minidom模块,也有此功效呀。为什么非要使用xpath呢?尤其是考虑到在python中还需要额外安装,不如minidom之放之四海而皆可运行。

跟那个谁再交流,意见仍是“力荐”。还推荐我细读教程,并在firefox里使用XPath Checker插件。

于是就照办了。

发硎新试,其快可知

一试XPath Checker,果然石破天惊。选中部分网页文字后,在右键菜单中选”View Xpath”,立即显示出该节点的XPath路径。层次清晰,定位精准。只是我对其语法尚未了了。于是细读教程,边学边用;半小时后,已经能够运用到之前写的饭否信息抓取程序上。虽然写代码还有些吃力,但是思路很清晰,不会纠缠于细节中无法脱身。

那个谁还提议,一般的html文档不是标准的xml文档,因此用xpath解析时,最好格式化一下。

我也注意到这个问题了。从饭否html中取出的有用内容,只占全文的一小部分;额外的部分白白拖慢速度,增强析取难度。

经过实验,我将原代码改进如下:

1. 仍用原来的minidom模块下载、分析文档,只取<ol>与</ol>之间的部分。这部分保存成字符串格式,备用。只取需要的那部分,使结构清晰,层次浅显。

2. 使用xpath来解析上一步取出的字串。

到现在,/,//,@,[],=,等等,每个符号都从原来的meaningless变成helpful,在我的工具箱中有了合适的位置,随取随用,十分方便。我已经成了xpath的受益者。现在才觉得学习xpath真是很有趣、有用。

目前还有个小问题,无法使用纯粹的xpath语法解决。问题描述如下:

xpath只能解析实体内容,不能”囫囵吞枣”地解析。例如:

<li>
	<a href='http://a.com'>hello world</a>
</li>

在view xpath 下,使用/li/a,得到的是

<a href='http://a.com'>hello world</a>

全部内容;

但是在python下,使用

?View Code PYTHON
method=doc.xpath(u'''string(/li/a)''')

虽然,也能通过/li/a/@href得到’http://a.com’的内容。

却只能得到hello world。xpath把所有的<>之内的东西给消灭掉了。很诡异。

遇到这种情况,如果我想得到整条的信息,就使用list.childNodes[index-1].firstChild.toxml()[22:-7]这种变通方式。不过,之前的doc = Parse(str(list.toxml()))我觉得用得挺好,是自己的一个”创举”,在程序中再度使用一下传统的xml解析方式,也无可厚非。当然,如果能够在xpath下把上述所有的事情都处理掉,是最好的。

经过了一点点的修补、改进,最终的饭否消息程序如下(核心代码部分):

?View Code PYTHON
    def __getMsgByPage__(self,page):        
 
        url="http://fanfou.com/"+self.user+"/p."+str(page)
        node = minidom.parse(urllib2.urlopen(url))
        list = node.getElementsByTagName("ol")[0]
        doc = Parse(str(list.toxml()))
        cu=self.sql.cursor()
        max=doc.xpath(u'''count(/ol/li)''')
        max=int(max)+1
        if max==1:
            return 0
        max=int(max)
        for index in range(1,max):
            method=doc.xpath(u'''string(/ol/li[%d]//span[@class='method'])''' % index)[2:]	
            method=method.replace(' ','')
            if method=="彩信":
                time=doc.xpath(u'''string(/ol/li[%d]//span[@class="time"]/@title)'''\
                        % index)
                uuid=doc.xpath(u'''string(/ol/li[%d]//a[@class='photo']/@href)'''\
                        % index) [-11:]
            else:
                time=doc.xpath(u'''string(/ol/li[%d]//a[@class='time']/@title)'''\
                        % index) 
                uuid=doc.xpath(u'''string(/ol/li[%d]//a[@class='time']/@href)''' % index)[-11:]
 
            content = list.childNodes[index-1].firstChild.toxml()[22:-7]
            # content, uuid, time, method are now available for further use.

最关键的代码,只有几行而已。省掉了原来长篇累牍的coding。效率也错,我将自己近3000条饭否消息批量下载,共150余页,历时86秒。饭否服务器也很给面子,中途没有封锁我。

总结一下:Xpath很适合在xml中定位各部分内容,定位精准,描述性极佳,是xml中的搜索利器。经常做xml解析的,不妨尝试一把。

个人感言

从纯手工正则表达式解析,到使用minidom解析,再到使用xpath,看似弯路,其实蛮有收获。从自己事必躬亲精确控制每一个细节(用手工作),再到借助工具实现一部分功能(手脑并用),再到完全用合适的工具来处理全部事情(用脑工作),似乎正是良性的发展路径。自豪地说,由于我已经使用过纯手工正则表达式的解析,即使现有的工具不适合我,我进可攻,退可守;我知道解析的细节,现有的工具(好看的封装而已嘛)骗不了我,即使它包装得再好,还是正则表达式在作引擎(曾经读过python处理xml的相关库文件的python代码,感谢开源);从追求实现(it works!)到追求卓越的实现(the excellent solution),也是进步的必然。我不是说使用正则式就低级——我从来没有说过诸如此类的话,不论是对正则表达式,还是对正则表达式的使用者;事实上,正则表达式一直是我的箧中飞刃;我爱正则表达式!——只是说,不同的工具在合适的场合,有不同的效用。不单要知道某种工具的缺点以便能够避其短,更重要的是要知道它的优点以便扬其长。这样才能从容地调兵遣将,手下无不可用之工具。

相关链接:

19

两本关于正则表达式的PDF电子书

1. Regular Expression HOWTO

作者

A.M. Kuchling(amk@amk.ca)。

简要介绍
本书主要介绍如何在python下使用re模块的正则表达式来处理问题。
本电子书是英文的,没有中文版。不过,内容比较简单,正则式和python的初学者可以看看。

不足
(只针对书本身,不针对内容)虽然是pdf,虽然有目录,但是点击目录不能跳转。由于本书只有短短25页,一会就能看完。所以,还是将就看吧。

文件
21页,pdf尺寸130kb。MD5:0a223f1ad3f932c8a0a7494120c10b05。

下载地址(如果您是通过FEED读本文,请移步到原始地址下载):

  Regular Expression HOWTO (130.8 KiB, 248 hits)

另:关于python下使用正则式,还有两个链接可供参考,在这里:
python正则表达式链接

2. 正则表达式指南

作者
张子阳,(博客http://jimmyzhang.cnblogs.com)。

简要介绍
本电子书是中文版的,有概念有解释有例子,适合偏爱中文的初学者阅读。
本书提到了一款正则式测试工具,RegexTester,由于现在在ubuntu下,尚未安装wine,这款软件有时间再测试。先略过不谈。

不足之处
对正则表达式的语言没有明确指出,或许作者的意图是写适合各种语言的正则表达式的教程吧。以笔者之见,初级程度的正则表达式区别不大,而深究起来,各种正则表达式的语言、工具各有其不同风格。本书忽略了这些特点,或许是为了不让初学者心存芥蒂,先了解正则表达式的概念。

文件
45页,pdf,362kb。MD5:77e36c693bc2d3248fb0dcfd70250037。

下载地址(如果您是通过FEED读本文,请移步到原始地址下载):

  RegexTester (72.2 KiB, 198 hits)

  Regular Expression Tutorial (362.8 KiB, 427 hits)

19

饭否消息析取之regex vs xml

页内导航:

批量导出饭否程序的方法很多,但是基本思路都是先将该网页保存到本地,然后将有用的饭否消息析取出来。本文不讨论如何下载饭否网页了(使用迅雷、wget、curl等),重点讨论对于下载到本地的网页,如何将有用的饭否消息析取出来。

小插曲:能否只用官方的API来获取全部饭否消息?

您或许会提议为什么不使用饭否自身的API。是的,饭否的API更快捷方便,兼容性很强。只是,饭否官方只提供下载前20条饭否消息的API。如果纯粹使用饭否官方API来下载全部饭否消息的方法也不是没有,只是很邪恶:

while (true) 
{ 
    download 20 messages via API; 
    store them; 
    delete this 20 messages via API; 
}

一边下载一边删除,确实总能得到全部消息。删除了前面的20条,能保证后面的20条以新消息的面目出现。这在理论上是行得通的。但是我们需要的是英雄Heroes里Peter那样无损的复制方式,而不是Sylar那样的残忍的剪切方式,呵呵。既然官方的API有限制,我们就自己动手了。请继续阅读本文。

饭否消息结构

打开一个饭否消息网页的源代码,例如本人的
http://fanfou.com/regex/p.1(其实http://fanfou.com/regex是http://fanfou.com/regex/p.1的快捷方式。这里使用完整的路径,以便体现其一般性。),观察可见,有用的饭否消息在这个框架里面:(代码较长,阅读请点击展开)

 
<ol>
    <li>
        <span class="content">
            代码非抄不能懂也。</span>
        <span class="stamp">
            <a class="time" title="2008-10-03 12:07" href="/statuses/QD6qHiqUbeE">
                2008-10-03 12:07</a>
            <span class="method">
                通过 <a href="http://del.icio.us/fanfou/API%E5%BA%94%E7%94%A8" target="_blank">
                    API</a>
            </span>
        </span>
    </li>
 
    <li>
        <span class="content">
            向自由的身心致敬! - 早嗷嗷也盼~晚安安也盼~望穿安安双眼~~怎知道今日里打土匪进深山自己的队伍来哎到嗷~面安前安呐啊啊啊啊啊~~~ <a href="http://fanfou.com/linkto/aHR0cDovL3d3dy5kb3ViYW4uY29tL2V2ZW50LzEwMjczNDg3Lw" target="_blank">
                http://www.douban.com/event/10273487/</a>
        </span>
        <span class="stamp">
            <a class="time" title="2008-10-06 14:07" href="/share/bd96z1U-gHw">
                2008-10-06 14:07</a>
            <span class="method">
                通过<a href="http://help.fanfou.com/share_button.html" target="_blank">
                    饭否分享</a>
            </span>
        </span>
    </li>
 
    <li>
        <span class="content">
            <a class="photo" href="http://fanfou.com/photo/8JsezhHM_VU">
                <img src="http://photo.fanfou.com/m0/00/19/e2_36807.jpg" alt="caixinceshi - no description" />
            </a>
            上传了新照片:caixinceshi - no description</span>
        <span class="stamp">
            <span class="time" title="2008-10-03 11:33">
                2008-10-03 11:33</span>
            <span class="method">
                通过<a href="http://help.fanfou.com/mobile_mms.html" target="_blank">
                    彩信</a>
            </span>
        </span>
    </li>
    #更多的<li>...</li>条目,每页最多20条。
</ol>

Tips:在分析饭否源代码时,饭否消息全在一行,不便于阅读。您可以拷贝所需要的代码(注意前后结构的匹配呼应)到vim中,执行:%s/>/>\r/g(将每个>后面加上一个换行符),再按ggvG全选,按=格式代码,所有的代码就成了漂亮的缩进格式,便于阅读了。


使用regex解析饭否消息

下面是使用regex来解析饭否消息的代码(直接拷贝自本人原来的perl抓饭程序。)

my $ffmsg=qr{
    <li>
        <span class="content">
            (.*?)
        </span>
        <span class="stamp">
            <a href="/(?:statuses|share)/([-_a-zA-Z0-9]{11})" class="time" title="([-: 0-9]{16})">
                [^<]+
            </a> 
            <span class="method">
                通过(网页|(?:\s*<[^>]+>)[^<]+(?:</a>))
            </span>
        </span>
    </li>
' }xi;

可以看出,使用正则表达式,能够比较真实地再现原网页代码的风貌。有几处小地方需要说明一下:

  • 在第一组小括号里,我使用了([^<]+?)来捕获消息正文(一条完整的消息可以分为:消息正文;发送时间;消息uuid例如QD6qHiqUbeE,发送方法,类型(彩信还是文本))。最初是使用.*?的。但是这样不精确,有时候两条消息竟然混合在一起。而([^<]+?)捕获的是从当前位置开始至下一个<之前的所有内容。或许您会问,这不怕受到消息正文中可能出现的<的影响吗?答案是:不会受到影响。因为饭否会把所有的<以及其实有可能影响解析的字符,都转换成<的形式了,因此它不影响解析。同时,使用精确的正则表达式有助于提高效率,让不匹配的正则式尽早失败。
  • (?:statuses|share)。这条正则表达式是用来捕获饭否的uuid。它不但能捕获以普通方法发布的消息(网页、短信、手机、API、IM工具等),还能捕获由“饭否分享”工具发布的消息。我不是很喜欢饭否分享这个工具。(或许改天有时间写篇文章,揭露它的缺点?)之所以把“饭否分享”消息和普通消息分开来说,是因为两者的结构是不一样的。
  • 通过(网页|(?:\s*<[^>]+>)[^<]+(?:))这条正则式,既用了捕获型括号,又用了非捕获型括号。使用后者,能有效地避免程序太复杂,便于按序号引用($1,$2等,如果越多则越混乱,修改正则式后,更是乱成一团遭),还能节省内存(如果程序中捕获了太多的内容,而不及时释放,或许会占尽资源。毕竟不是只捕获几十字节。要考虑到饭否用户或许有近十万条的饭否消息。指的是苹果流冰这样的“万玻南痨话”)
  • xi选项:x是为了使用忽略空白字符和允许注释;i选项是忽略大小写。

使用正则表达式来析取饭否消息文本,需要考虑的细节很多。一处不细致,程序运行起来就会给你难看。饭否彩信的格式就略过不分析了。道理相同,点到为止。


使用xml解析饭否消息

再来看一下在python下,使用xml来解析饭否消息。注:该程序参考了ppippyfan程序。
 

?View Code PYTHON
from xml.dom import minidom, Node #引人解析工具:xml小马驹! 
node = minidom.parse(urllib2.urlopen("http://fanfou.com/zhasm/p.1")) 
#抓取页面http://fanfou.com/zhasm/p.1 的全部内容到变量node中 
l = node.getElementsByTagName("ol")[0] 
#将饭否消息部分内容保存到变量l中 
for c in range(0, number): 
    # 时间 
    if l.childNodes[c].hasAttribute("class"): 
        continue 
    content = ( 
            #时间: 
            l.childNodes[c].childNodes[5].\ 
            firstChild.getAttribute("title"), 
            #消息正文 :
            childNodes[c].childNodes[0].toxml()[22:-7], 
            #uuid 
            l.childNodes[c].childNodes[5].firstChild.\ 
            getAttribute("href")[10:]
            )

在xml文件中,前后呼应的标签,成了鲜活的特征,这些特征可以被xml解析函数很容易地辨识出来,并提取出所需内容。

  • childNodes[c].childNodes[0].toxml()[22:-7]:这条语句的意思是,对于每一条饭否消息(childNodes[c]),其消息内容的第一个节点(childNodes[0]),截取其第23字节到倒数第7字节的内容。它是指哪一段呢?其实就是每一对<span class=”content”>…</span>之间点号所示的内容。
  • 每条消息的发送时间、正文、uuid,保存在tuple中。

取得了内容之后,至于之后的煎炒烹炸,就悉听尊便了。

值得一提的是,本人在大量下载饭否消息时,不止一次遇到过饭否页面无法访问的情况。问了饭否郭万怀,答曰为了减轻服务器负载,每个IP地址下每分钟允许访问100个页面。超过此数就会自动屏蔽。我测试的结果是少于100页。比较靠谱的间隔是,每析取一页,sleep(15)。是有些慢了。没办法。当然,也有人说,执行本人以前写的抓饭程序,一次下载几百页,并没有遇到当机情况。那我只能说是您的RP高、运气好了。


两者比较

 

个人认为,xml与regex相比,有如下特点:

  • 通用性:xml具有通用性,不单单能解析饭否消息,其它符合规则的html文本,同样能够较少地改动代码,即可解析;而正则表达式则具有专用性,不能放之四海而皆准。当饭否的界面、框架有微调时,估计使用正则表达式解析的工具首先倒下。
  • 可读性:有人说perl是只写语言,regex尤甚。这是在说perl或regex代码在编写时性之所至,酣畅淋漓,执行也很高效。只是,如果代码格式混乱且无注释文档的话,隔数日、数月再读,仿佛读天书一般。而使用xml库来解析的python语言,则由于代码格式整齐,库函数见名知意,因而具有较强的可读性。这样说,总体是这样。不过我们可以尽可能把代码(即使是perl或regex的)写的整齐已读,尤其是考虑到perl支持/x选项。
  • 效率:良好编译的正则式,其执行效率应该优于xml解析。但是,使用xml能够节省编程时间;使用正则式牺牲一部分的编程时间,理论上能提高一点点效率。有兴趣的读者可以编写一段程序,循环个成千上万次,比较一下平均时间。

写到这里,对照金庸先生在《鹿鼎记》第五章:“金戈运启驱除会,玉匣书留想象间”两种武功的比较,颇有意味:
“大慈大悲千叶手”招式太多,记起来麻烦。而“八卦游龙掌”只有八八六十四式,但反复变化,尽可敌得住千叶手。那么哪一门功夫厉害些?这两门都是上乘掌法,说不上哪一门功夫厉害。谁的功夫深,用得巧妙,谁就胜了。

以本文来看,regex就相当于是大慈大悲千叶手了,需要留意的细节太多;xml方式呢,就相当于只有八八六十四式的“八卦游龙掌”。两种工具都很有用。

呼吁官方提供更多功能

离题了。这里顺便发发牢骚而已,与xml、regex无关。我不止一次地在饭否和本人blog中抱怨,使用上面这足粗笨的方法下载、解析,是最无奈的应用。最便捷的方式,应该是官方提供批量导出程序,只要执行一条数据库查询导出即可实现我们辛辛苦苦半天才能以变通的方式实现的功能。或许是饭否官方的人员都在忙着增强和美化海内吧,饭否自生自长,长时间没有更新,任凭jiwai.de、zuosa等推出一项又一项的新功能。 

扩展

本文的思路,对twitter同样适用。但是twitter越来越慢了。有一段时间好像还不支持查看历史页面。


相关阅读

验证码:BANG1F79A9FAD20225BEA7FE397AXIANGUO e8da37692b5b030cbefb9956e3bdb9cc

19

《精通正则表达式》视频教程提供下载

偶然从网上找到该教程,下载后觉得不错,可以作为《精通正则表达式》的番外篇,共同学习。

关于此视频的讲师:

此视频的讲师为余晟先生。余先生是抓虾网高级顾问。毕业于东北师范大学,主修计算机,辅修中文。现居北京。曾任高级程序员、技术经理;从事过大量文本解析和数据抽取的工作。对程序语言、算法、数据库和敏捷开发都有兴趣,译有《精通正则表达式》(第3版)一书。

关于此视频

此视频分为5讲,每讲30分钟左右,内容深入浅出,适合以下受众:

  • 对正则式感兴趣的人;
  • 对正则式不感兴趣的人;
  • 正则式初学者,想入门;
  • 正则式有所成者,想提高。

当然,如果能静下心来,通读《精通正则表达式》原书,并亲自动手尝试,效果更为显著。

目录及下载:

视频文件(avi格式)已经使用7-zip压缩,使其总尺寸从784Mb减小到74.7Mb。您需要使用支持7-zip的解压软件才能打开。

上传空间在mediafire。在网络封锁日益严峻的大环境下,您或许需要使用代那个理才能访问。我不敢保证此文件址长时有效。需要下载的请抓紧时间。

章节 内容 大小(Mb) 链接
第一讲 15.5 点此下载
第二讲 13.1 点此下载
第三讲 16.6 点此下载
第四讲 15.3 点此下载
第五讲 13.9 点此下载
源代码 16.9Kb 点此下载

2008.11.04更新:
由于mediafire不出意料被AND(不幸言中),上面的文件也无法下载。好在还有纳米盘。请访问牛腩的学习空间去使用纳米盘下载。上面除了我这里提到的5部分《精通正则表达式》vedio以外,还有两段“实战正则表达式”,还不赶紧去看看?

19

为project babel加上geshi语法高亮显示

注:本文使用的正则表达式为php风格。

从v2ex到M6,我一直喜欢project babel(PB)。自己也尝试在本地或服务器上架设PB系统。遇到的一个问题是:PB只支持有限的ubb代码,当我想在PB上贴源码时,没有语法高亮很不美观。

管理员对此的答复是,简约是最好的,不必非得使用72号红色字体加上一堆感叹号才能让文字有力度。当然了,这样不给脑残以任何自证的机会,当然是不错。可是因为怕脑残搞乱而消减功能,就有些类似于怕一个男人淫乱而将全天下男人葵花宝典掉一样了。

代码语法高亮,最著名的模块莫过于geshi。我使用过php写过几行代码,语法什么的都需要现查手册;对css、form之类也不是很熟悉。因此让我自己改造PB的话,一时摸不到头绪。于是在M6上发了求教贴。有热心人sara回复如下:

有个缺点,就是一个帖子里同一种代码只能用一次,希望有高手能改进一下。
比如php代码:[php]your code[/php]

  1. 在v2excore.php加geshi的路径。
  2. 在utilities.php加
    preg_match('/\[php\]/i', $text, $_m_php_open);
    preg_match('/\[\/php\]/i', $text, $_m_php_close);
    if ((count($_m_php_open) == 1) && (count($_m_php_open) == count($_m_php_close))) {
    list($a, $b) = explode('[php]', $text);
    list($c, $d) = explode('[/php]', $b);
    $c = str_replace('&lt;', '<', $c);
    $c = str_replace('&gt;', '>', $c);
    $text = $a . '<div class="codes php">' . geshi_highlight($c, 'php', '', 'true') . '</div>' . $d;
    }

    加在

    $text = preg_replace($p, $r, $text);

    的后面。

我读了这段代码之后,发现它并没有大段的CSS内容,这我就放心多了。于是细读之,遇到问题就查手册。

这段代码翻译成自然语言,就是:

  1. 查找文本中[php]的次数$_m_php_open,以及[/php]的次数$_m_php_close;查找时不分大小写。
  2. 次数$_m_php_open与$_m_php_close是否相等且等于1。如果不满足条件,就不处理了。
    只有两者相等且为1时,记录下[php]与[/php]之间的内容$c,把里面所有的”<”、”>”都换成”<"、">“形式。
  3. 把需要处理的代码部分$c使用geshi_highlight的相关语法(这里是PHP)进行格式化。

既如此,那我就可以自已实现了。

刚才使用

preg_match('/\[php\]/i', $text, $_m_php_open);
preg_match('/\[\/php\]/i', $text, $_m_php_close);

寻找配对的标签,不是一种好的方法。因为它的通用性不强,不便于前后配对。较好的解决方法是:

$text=preg_replace_callback("#\[(asp|bash|c|cpp|csharp|css|delphi|div|dos|dot|ini|java|java5|javascript|latex|lisp|luamatlab|mysql|pascal|perl|php|python|qbasic|rails|reg|ruby|scheme|sql|tcl|text|vb|vbnet|xml)\](.*?)\[/\\1\]#ix","geshi_replace",$text);

使用正则表达式,第一组括号捕获了语言种类标签,第二组捕获了程序内容。选项i表示不分大小写,s表示点号可以匹配任意字符(包括换行符)。

之所以使用callback方式的正则匹配,是因为替换的内容多而复杂。该callback函数是这样定义的:

function geshi_replace($matches)  
{
    //matches[1]=lang
    //matches[2]=code
    $code=$matches[2];
    $lang=$matches[1];
    $code = str_replace('&lt;', '<', $code);
    $code = str_replace('&gt;', '>', $code);
    $str = '<div class="code">'. geshi_highlight($code, $lang,'','true') .'</div>';
    $str = str_replace("<br />","",$str);
    return $str;
}

程序与原来给出的大同小异。只是还有一行

$str = str_replace("<br />","",$str);

是新加上去的。这是因为geshi_highlight输出的文本,总是包含”<br /><br />”,因此显示出来的高亮代码行距太大,这里给它修剪一下,就可以了。

总结一下如何在PB6.0中加入geshi:

1. 上传geshi到babel目录;
2. 在v2excore.php加geshi的路径,让pb能找到geshi.php。
3. 在Utilities.php文件下,function format_ubb之前加上如下function:

      function geshi_replace($matches)
      {
      //matches[1]=lang
      //matches[2]=code
      $code=$matches[2];
      $lang=$matches[1];
      $code = str_replace('<', '<', $code);
      $code = str_replace('>', '>', $code);
      $str = '<div class="code">'. geshi_highlight($code, $lang,'','true') .'</div>';
      $str = str_replace("<br />","",$str);
      return $str;
      }

4. 在function format_ubb中的

      $text = preg_replace($p, $r, $text);

之后插入一行代码:

      $text=preg_replace_callback("#\[(asp|bash|c|cpp-qt|cpp|csharp|css|delphi|diff|div|dos|dot|eiffel|fortran|freebasic|genero|gml|groovy|haskell|html4strict|idl|ini|inno|io|java|java5|javascript|latex|lisp|lua|m68k|matlab|mirc|mpasm|mysql|nsis|objc|ocaml-brief|ocaml|oobas|oracle8|pascal|per|perl|php-brief|php|plsql|python|qbasic|rails|reg|robots|ruby|sas|scheme|sdlbasic|smalltalk|smarty|sql|tcl|text|thinbasic|tsql|vb|vbnet|vhdl|visualfoxpro|winbatch|xml)\](.*?)\[/\\1\]#is","geshi_replace",$text);

大功告成。
使用方法:

  1. 在PB贴子的编辑框中使用[lang]…[/lang]标签。
  2. lang标签不可嵌套。
  3. lang的可选方案以geshi支持为限,大致包括:asm, asp , bash, c, cpp-qt, cpp, csharp, css, delphi, diff, div, dos, dot, eiffel, fortran, freebasic, genero, gml, groovy, haskell, html4strict, idl, ini, inno, io, java, java5, javascript, latex, lisp, lua, m68k, matlab, mirc, mpasm, mysql, nsis, objc, ocaml-brief, ocaml, oobas, oracle8, pascal, per, perl, php-brief, php, plsql, python, qbasic, rails, reg, robots, ruby, sas, scheme, sdlbasic, smalltalk, smarty, sql, tcl, text, thinbasic, tsql, vb, vbnet, vhdl, visualfoxpro, winbatch, xml。呵呵。

结论:

  1. 十分感谢sara
  2. 正则表达式一如既往地有用、好用。
  3. 有些问题看上去很难。可是只要打开缺口,剩下的就不成问题;就如慕容复语:“这几步棋我也想得出来。万事起头难,便是第一着怪棋,无论如何想不出。”
19
  • 通配天下

    • 正则
        正则式gtalk群:regex[at]zhasm[dot]com
        站长gtalk:rex[at]zhasm[dot]com
      --------------------------------------------------------------
      ◆匹配文本,管它字母数字一次多次懒惰贪婪行首行尾,火眼金睛疏不漏  
      ◆替换字串,任尔局部全局单行多行前瞻回顾大写小写,偷梁换柱度陈仓
      --------------------------------------------------------------