正则表达式匹配规则
Jan 30th
原文:How will my regular expression match?
作者:hv
译文:正则表达式的匹配过程
译者:rex
rex译注:在《Programming Perl》第三版第五章,讲了正则匹配的六条规则,深入,透彻,但是不易理解。可以将本文当作是该章的简化版。另外,一定的正则式调试经验也有助于理解正则式的匹配过程。可以参考《Programming Perl》第五章“正则表达式编译器”一节,或阅读这篇文章《Debugging Regular Expressions》。建议阅读例程序时,先默想输出结果,然后再上机测试,最后再看本文的答案。
经常见到关于正则表达式“偏好”的讨论,即,正则表达式引擎“喜欢”更长的、更短的,还是最左端的匹配。
事实非常简单:正则表达式引擎总是返回它所匹配到的第一个结果,如果你理解了正则表达式尝试匹配的顺序,你就再也不会对找到哪一个匹配而困惑了。
下面是一些简单规则(次序无关),阐释正则引擎尝试匹配的顺序。
1.从最左端的字符开始
引擎从目标字串的第一个字符开始,对正则模式进行匹配测试。只有当在第一个字符位置测试完整个正则模式且匹配失败时,引擎才会移动到目标字串的下一个字符,重新开始匹配测试。这样依次测试,直到字串的结束位置。
例子:
1 2 | ($match) = "The longest word" =~ /(\w+)/; print $match; |
其输出结果是“The”(请先自行猜测,再使用鼠标选定,查看结果;下同。),原因是匹配是从字串的最左端开始的。这个单词之后,还有一个更长的单词可以匹配,但是正则引擎却对其视而不见:只要从字串开始处进行匹配且成功,它就返回这个结果,匹配过程就结束了。
Rex:如果正则式是以$结尾的,例如abc$,该正则在试图匹配字串“xyzabc”是否会首先跳到最后一个字符开始比较呢?我的理解是,就常规的正则引擎的正常的匹配过程而言,虽然有$符,但是该$符与普通字符一样,不会特殊处理,整条正则式会在x,y,z处失败3次,然后在a处再开始匹配测试并最终成功。$不会加速匹配过程,只是保证了最终匹配位置。但是不排除具体的正则引擎,会对定位锚点进行优化,从而实现early exit,即只要不符合某一条件,就提前退出,从而减少真正的比较次数,提升效率。看一下abc$ 的perl代码:
2. 最左边的备选项优先
如果正则模式中含备选项,那么首先测试左边的选项。如果最左侧的备选项成功匹配,那么就继续使用剩余的正则表达式剩余的字串进行匹配测试;只有在最左边的备选项不能匹配时才依次尝试下一个备选项。
例子:
1 2 | ($match) = "Mrs Smith" =~ /(Mr|Mrs)/; print $match; |
其输出结果是“Mr”,因为最左端的正则备选项匹配成功,在本例中也就意味着整个正则表达式匹配成功。此时第二个备选项就被完全无视了。
3. 贪婪模式
对于任何常规的量词(即? * + {1,3}),引擎首先会尝试允许范围内最大数量的重复次数(如果实际文本不足,则尽可能多地重复),然后继续使用剩余的正则表达式剩余的字串进行匹配测试。只有当剩余的正则匹配失败时,引擎才不情愿地“吐出”一次重复的文本,尝试进行后续的匹配,直到匹配成功,或到达允许重复的最小次数。
例子:
1 2 | ($match) = "/foo/bar" =~ m[.*/(.*)]; print $match; |
其输出结果是”bar“。匹配过程是:
.*先匹配完整个字串/foo/bar;此时发现.*之后还需要匹配一个/,此时只好回退字串,.*此时是/foo/ba,然后是/foo/b,/foo/,/foo。此时,后续的正则式得到了/,得以继续匹配并最终成功,返回它认为正确的结果。看图:
Rex: 如果我们想把下面的英文字串
At this point the remaining pattern matches, so this is the match returned提取出最后一个单词和其它部分,应该怎样写正则呢?(.*)(\w+)$吗?这样的话,$2中只有d这个字母。正确的写法包括:
.*\b(\w+)$ # .*先用完整个字符,再一个字符一个字符地回退,直到遇到一个单词边界,然后开始找\w+。
4. 懒惰模式
在量词之后加上问号?(即?? *? +? {1,3}?),就由贪婪模式变为懒惰模式,匹配的过程也随之改变:引擎先尝试允许范围内最少次数的重复,随即使用后续的正则对后续的字串作匹配测试。只有当后续的正则匹配失败时,引擎才懒洋洋地再吃进一次重复,再进行后续匹配测试,直到匹配成功,或到达所允许的匹配次数上限。
例子:
1 2 | ($match) = "foo/bar/baz" =~ m[.*?/(.*)]; print $match; |
显而易见,输出结果是bar/baz。匹配过程中,.*?先匹配空字串”",然后是”f”,”fo”,”foo”,此时匹配成功,并返回相应结果。
Rex:继续思考上节的问题,另一种将At this point the remaining pattern matches, so this is the match returned中最后一个单词与其余部分分开的正则式还可以是:
.*?\w+$
5. 正则式各部分的匹配次序:从左到右
上面一些例子中的暗含前提是正则模式之间的匹配次序是从左到右。因此在“贪婪模式”例子中有两个.*,基于本条规则,只有当第一个.*的条件满足之后,它才会考虑后续的正则表达式该如何匹配。
6. 嵌套
其实这只是“从左到右”规则的引申。参见下面的例子:
($outer, $inner) = "/foo/bar" =~ m[(/(.+))*]; print $inner;
其输出结果是“foo/bar”。正则引擎先遇到外层的量词,意识到该部分正则要尽可能多地重复,然后开始尝试匹配内层的正则。嵌于内层的量词是贪婪的,因此也会尽可能多地重复,它抓到的字串是foo/bar这部分。此时,外层量词没有机会再重复一次匹配了,但是没有关系——整个正则表达式已经成功匹配,因此返回相应结果。
本文概括了绝大部分常规正则表达式的匹配规则。其余不常见的情况,仍需留心(例如锚点,pos(),以及各种各样的零宽断言)。同时,正则表达式优化器有时会让引擎略过某些步骤,但前提条件是目标匹配结果不变。
Rex注:将何伟平先生译的《Programming Perl》中的正则匹配规则也抄录在这里。只列提纲,需要详细内容的请自行翻书。
- 规则1:引擎试图尽可能地匹配字符串的左边,这样整个正则表达式按照规则2匹配。
引擎从字符串的第一个字符开始,然后从那里开始尝试匹配整个模式。只有引擎在到达字符串的终点之前先到达模式的终点才是整个模式的匹配。如果匹配,引擎马上退出——它不会继续寻找“更好的”匹配,即便模式可能以几种不同的方式匹配也如此。- 规则2:当引擎碰到一个候选集合时(用|符号分隔),不管是在顶层还是在当前“群集”层次中,它都从左向右尝试这些候选项,并且在第一个可以成功完成整个模式匹配的候选项处停止。
- 规则3:如果根据规则4和5(这样整个正则表达式就可以满足了),某个候选项里顺序列出的每个项都匹配,那么这个候选项就是匹配项。
(rex注:列出的每个项,觉得这里说“子项”,层次更清晰。)
- 规则4:如果一个断言不出猎在当前位置匹配,引擎将回溯到规则3并重新以不同的选择试验高强弱顺序的项。
- 规则5:一个量化的原子只有在其本身匹配了其量词许可的次数才算匹配。(原子本身是按照规则6匹配的)。
- 规则6:每个原子都根据它的类型指定的语义来匹配。如果该原子 不匹配(或者它匹配而模式的其余部分不匹配),那么引擎将回溯到规则5,然后尝试该原子 的数量的下一选择。
笔记:如何写出高效率的正则表达式
Nov 30th
如果纯粹是为了挑战自己的正则水平,用来实现一些特效(例如使用正则表达式计算质数、解线性方程),效率不是问题;如果所写的正则表达式只是为了满足一两次、几十次的运行,优化与否区别也不太大。但是,如果所写的正则表达式会百万次、千万次地运行,效率就是很大的问题了。我这里总结了几条提升正则表达式运行效率的经验(工作中学到的,看书学来的,自己的体会),贴在这里。如果您有其它的经验而这里没有提及,欢迎赐教。
为行文方便,先定义两个概念。
- 误匹配:指正则表达式所匹配的内容范围超出了所需要范围,有些文本明明不符合要求,但是被所写的正则式“击中了”。例如,如果使用\d{11}来匹配11位的手机号,\d{11}不单能匹配正确的手机号,它还会匹配98765432100这样的明显不是手机号的字符串。我们把这样的匹配称之为误匹配。
- 漏匹配:指正则表达式所匹配的内容所规定的范围太狭窄,有些文本确实是所需要的,但是所写的正则没有将这种情况囊括在内。例如,使用\d{18}来匹配18位的身份证号码,就会漏掉结尾是字母X的情况。
写出一条正则表达式,既可能只出现误匹配(条件写得极宽松,其范围大于目标文本),也可能只出现漏匹配(只描述了目标文本中多种情况种的一种),还可能既有误匹配又有漏匹配。例如,使用\w+\.com来匹配.com结尾的域名,既会误匹配abc_.com这样的字串(合法的域名中不含下划线,\w包含了下划线这种情况),又会漏掉ab-c.com这样的域名(合法域名中可以含中划线,但是\w不匹配中划线)。
精准的正则表达式意味着既无误匹配且无漏匹配。当然,现实中存在这样的情况:只能看到有限数量的文本,根据这些文本写规则,但是这些规则将会用到海量的文本中。这种情况下,尽可能地(如果不是完全地)消除误匹配以及漏匹配,并提升运行效率,就是我们的目标。本文所提出的经验,主要是针对这种情况。
- 掌握语法细节。正则表达式在各种语言中,其语法大致相同,细节各有千秋。明确所使用语言的正则的语法的细节,是写出正确、高效正则表达式的基础。例如,perl中与\w等效的匹配范围是[a-zA-Z0-9_];perl正则式不支持肯定逆序环视中使用可变的重复(variable repetition inside lookbehind,例如(?<=.*)abc),但是.Net语法是支持这一特性的;又如,JavaScript连逆序环视(Lookbehind,如(?<=ab)c)都不支持,而perl和python是支持的。《精通正则表达式》第3章《正则表达式的特性和流派概览》明确地列出了各大派系正则的异同,这篇文章也简要地列出了几种常用语言、工具中正则的比较。对于具体使用者而言,至少应该详细了解正在使用的那种工作语言里正则的语法细节。
- 先粗后精,先加后减。使用正则表达式语法对于目标文本进行描述和界定,可以像画素描一样,先大致勾勒出框架,再逐步在局步实现细节。仍举刚才的手机号的例子,先界定\d{11},总不会错;再细化为1[358]\d{9},就向前迈了一大步(至于第二位是不是3、5、8,这里无意深究,只举这样一个例子,说明逐步细化的过程)。这样做的目的是先消除漏匹配(刚开始先尽可能多地匹配,做加法),然后再一点一点地消除误匹配(做减法)。这样有先有后,在考虑时才不易出错,从而向“不误不漏”这个目标迈进。
- 留有余地。所能看到的文本sample是有限的,而待匹配检验的文本是海量的,暂时不可见的。对于这样的情况,在写正则表达式时要跳出所能见到的文本的圈子,开拓思路,作出“战略性前瞻”。例如,经常收到这样的垃圾短信:“发*票”、“发#漂”。如果要写规则屏蔽这样烦人的垃圾短信,不但要能写出可以匹配当前文本的正则表达式 发[*#](?:票|漂),还要能够想到 发.(?:票|漂|飘)之类可能出现的“变种”。这在具体的领域或许会有针对性的规则,不多言。这样做的目的是消除漏匹配,延长正则表达式的生命周期。
- 明确。具体说来,就是谨慎用点号这样的元字符,尽可能不用星号和加号这样的任意量词。只要能确定范围的,例如\w,就不要用点号;只要能够预测重复次数的,就不要用任意量词。例如,写析取twitter消息的脚本,假设一条消息的xml正文部分结构是<span class=”msg”>…</span>且正文中无尖括号,那么<span class=”msg”>[^<]{1,480}</span>这种写法的思路要好于<span class=”msg”>.*</span>,原因有二:一是使用[^<],它保证了文本的范围不会超出下一个小于号所在的位置;二是明确长度范围,{1,480},其依据是一条twitter消息大致能的字符长度范围。当然,480这个长度是否正确还可推敲,但是这种思路是值得借鉴的。说得狠一点,“滥用点号、星号和加号是不环保、不负责任的做法”。
- 不要让稻草压死骆驼。每使用一个普通括号()而不是非捕获型括号(?:…),就会保留一部分内存等着你再次访问。这样的正则表达式、无限次地运行次数,无异于一根根稻草的堆加,终于能将骆驼压死。养成合理使用(?:…)括号的习惯。
- 宁简勿繁。将一条复杂的正则表达式拆分为两条或多条简单的正则表达式,编程难度会降低,运行效率会提升。例如用来消除行首和行尾空白字符的正则表达式s/^\s+|\s+$//g;,其运行效率理论上要低于s/^\s+//g; s/\s+$//g; 。这个例子出自《精通正则表达式》第五章,书中对它的评论是“它几乎总是最快的,而且显然最容易理解”。既快又容易理解,何乐而不为?工作中我们还有其它的理由要将C==(A|B)这样的正则表达式拆为A和B两条表达式分别执行。例如,虽然A和B这两种情况只要有一种能够击中所需要的文本模式就会成功匹配,但是如果只要有一条子表达式(例如A)会产生误匹配,那么不论其它的子表达式(例如B)效率如何之高,范围如何精准,C的总体精准度也会因A而受到影响。
- 巧妙定位。有时候,我们需要匹配的the,是作为单词的the(两边有空格),而不是作为单词一部分的t-h-e的有序排列(例如together中的the)。在适当的时候用上^,$,\b等等定位锚点,能有效提升找到成功匹配、淘汰不成功匹配的效率。
总结完发现,《精通正则表达式》的第5章、第6章已经以更为有条理的方式总结出了常用的优化方法。不过,泛泛地读过的印象是肤浅的,过后即忘的;而真正若有所悟时在书上得到了系统地印证,这种感觉才是真的爽。
[转]理解正则表达式
Aug 30th
rex注:本文原作者孟岩,原文转自孟岩CSDN博客。本文为《程序员》07年3月号《七种武器》专题所做。rex以前只是知道如何使用正则表达式而已,在读MRE时,读到NFA、DFA其实都是一头雾水,不知所云。现在抓紧时间恶补基础知识,学习了些编译原理,这才有些明白。如今再看从源头讲正则表达式的文章,就心有戚戚了,呵呵。搜到好文章一篇,与大家分享。
在程序员日常工作中,数据处理占据了相当的比重。而在所有的数据之中,文本又占据了相当的比重。文本能够被人理解,具有良好的透明性,利于系统的开发、测试和维护。然而,易于被人理解的文本数据,机器处理起来就不一定都那么容易。文本数据复杂多变,特定性强,甚至是千奇百怪。因此,文本处理程序可谓生存环境恶劣。一般来说,文本处理程序都是特定于应用的,一个项目有一个项目的要求,彼此之间很难抽出共同点,代码很难复用,往往是“一次编码,一次运行,到处补丁”。其程序结构散乱丑陋,谈不上有什么“艺术性”,基本上与“模式”、“架构”什么的无缘。在这里,从容雅致、温文尔雅派不上用场,要想生存就必须以暴制暴。
事实上,几十年的实践证明,除了正则表达式和更高级的parser技术,在这样一场街头斗殴中别无利器。而其中,尤以正则表达式最为常用。所以,对于今天的程序员来说,熟练使用正则表达式着实应该是一种必不可少的基本功。然而现实情况却是,知道的人很多,善于应用的人却很少,而能够洞悉其原理,理智而高效地应用它的人则少之又少。大多数开发者被它的外表吓倒,不敢也不耐烦深入了解其原理。事实上,正则表达式背后的原理并不复杂,只要耐心学习,积极实践,理解正则表达式并不困难。下面列举的一些条款,来自我本人学习和时间经验的不完全总结。由于水平和篇幅所限,只能浮光掠影,不足和谬误之处,希望得到有识之士的指教。
1. 了解正则表达式的历史
正则表达式萌芽于1940年代的神经生理学研究,由著名数学家Stephen Kleene第一个正式描述。具体地说,Kleene归纳了前述的神经生理学研究,在一篇题为《正则集代数》的论文中定义了“正则集”,并在其上定义了一个代数系统,并且引入了一种记号系统来描述正则集,这种记号系统被他称为“正则表达式”。在理论数学的圈子里被研究了几十年之后,1968年,后来发明了UNIX系统的Ken Thompson第一个把正则表达式用于计算机领域,开发了qed和grep两个实用文本处理工具,取得了巨大成功。在此后十几年里,一大批一流计算机科学家和黑客对正则表达式进行了密集的研究和实践。在1980年代早期,UNIX运动的两个中心贝尔实验室和加州大学伯克利分校分别围绕grep工具对正则表达式引擎进行了研究和实现。与之同时,编译器“龙书”的作者Alfred Aho开发了Egrep工具,大大扩展和增强了正则表达式的功能。此后,他又与《C程序设计语言》的作者Brian Kernighan等三人一起发明了流行的awk文本编辑语言。到了1986年,正则表达式迎来了一次飞跃。先是C语言顶级黑客Henry Spencer以源代码形式发布了一个用C语言写成的正则表达式程序库(当时还不叫open source),从而把正则表达式的奥妙带入寻常百姓家,然后是技术怪杰Larry Wall横空出世,发布了Perl语言的第一个版本。自那以后,Perl一直是正则表达式的旗手,可以说,今天正则表达式的标准和地位是由Perl塑造的。Perl 5.x发布以后,正则表达式进入了稳定成熟期,其强大能力已经征服了几乎所有主流语言平台,成为每个专业开发者都必须掌握的基本工具。
2. 掌握一门正则表达式语言
使用正则表达式有两种方法,一种是通过程序库,另一种是通过内置了正则表达式引擎的语言本身。前者的代表是Java、.NET、C/C++、Python,后者的代表则是Perl、Ruby、JavaScript和一些新兴语言,如Groovy等。如果学习正则表达式的目标仅仅是应付日常应用,则通过程序库使用就可以。但只有掌握一门正则表达式语言,才能够将正则表达式变成编程的直觉本能,达到较高的水准。不但如此,正则表达式语言也能够在实践中提供更高的开发和执行效率。因此,有心者应当掌握一门正则表达式语言。
3. 理解DFA和NFA
正则表达式引擎分成两类,一类称为DFA(确定性有穷自动机),另一类称为NFA(非确定性有穷自动机)。两类引擎要顺利工作,都必须有一个正则式和一个文本串,一个捏在手里,一个吃下去。DFA捏着文本串去比较正则式,看到一个子正则式,就把可能的匹配串全标注出来,然后再看正则式的下一个部分,根据新的匹配结果更新标注。而NFA是捏着正则式去比文本,吃掉一个字符,就把它跟正则式比较,匹配就记下来:“某年某月某日在某处匹配上了!”,然后接着往下干。一旦不匹配,就把刚吃的这个字符吐出来,一个个的吐,直到回到上一次匹配的地方。
DFA与NFA机制上的不同带来5个影响:
1. DFA对于文本串里的每一个字符只需扫描一次,比较快,但特性较少;NFA要翻来覆去吃字符、吐字符,速度慢,但是特性丰富,所以反而应用广泛,当今主要的正则表达式引擎,如Perl、Ruby、Python的re模块、Java和.NET的regex库,都是NFA的。
2. 只有NFA才支持lazy和backreference等特性;
3. NFA急于邀功请赏,所以最左子正则式优先匹配成功,因此偶尔会错过最佳匹配结果;DFA则是“最长的左子正则式优先匹配成功”。
4. NFA缺省采用greedy量词(见item 4);
5. NFA可能会陷入递归调用的陷阱而表现得性能极差。
我这里举一个例子来说明第3个影响。
例如用正则式/perl|perlman/来匹配文本 ‘perlman book’。如果是NFA,则以正则式为导向,手里捏着正则式,眼睛看着文本,一个字符一个字符的吃,吃完 ‘perl’ 以后,跟第一个子正则式/perl/已经匹配上了,于是记录在案,往下再看,吃进一个 ‘m’,这下糟了,跟子式/perl/不匹配了,于是把m吐出来,向上汇报说成功匹配 ‘perl’,不再关心其他,也不尝试后面那个子正则式/perlman/,自然也就看不到那个更好的答案了。
如果是DFA,它是以文本为导向,手里捏着文本,眼睛看着正则式,一口一口的吃。吃到/p/,就在手里的 ‘p’ 上打一个钩,记上一笔,说这个字符已经匹配上了,然后往下吃。当看到 /perl/ 之后,DFA不会停,会尝试再吃一口。这时候,第一个子正则式已经山穷水尽了,没得吃了,于是就甩掉它,去吃第二个子正则式的/m/。这一吃好了,因为又匹配上了,于是接着往下吃。直到把正则式吃完,心满意足往上报告说成功匹配了 ‘perlman’。
由此可知,要让NFA正确工作,应该使用 /perlman|perl/ 模式。
通过以上例子,可以理解为什么NFA是最左子式匹配,而DFA是最长左子式匹配。实际上,如果仔细分析,关于NFA和DFA的不同之处,都可以找出道理。而明白这些道理,对于有效应用正则表达式是非常有意义的。
4. 理解greedy和lazy量词
由于日常遇到的正则表达式引擎全都是NFA,所以缺省都采用greedy量词。Greedy量词的意思不难理解,就是对于/.*/、/\w+/这样的“重复n”次的模式,以贪婪方式进行,尽可能匹配更多字符,直到不得以罢手为止。
举一个例子,以 /<.*>/ 模式匹配 ‘<book> <title> Perl Hacks </title> </book>\t’文本,匹配结果不是 ‘<book>’,而是 ‘<book> <title> Perl Hacks </title> </book>’。原因就在于NFA引擎以贪婪方式执行“重复n次”的命令。让我们来仔细分析一下这个过程。
条款3指出,NFA的模型是以正则式为导向,拿着正则式吃文本。在上面的例子里,当它拿着/.*/这个正则式去吃文本的时候,缺省情况下它就这么一路吃下去,即使碰到 ‘>’字符也不罢手——既然 /./ 是匹配任意字符, ‘>’ 当然也可以匹配!所以就尽管吃下去,直到吃完遇到结尾(包括\t字符)也不觉得有什么不对。这个时候它突然发现,在正则表达式最后还有一个 />/,于是慌了神,知道吃多了,于是就开始一个字符一个字符的往回吐,直到吐出倒数第二个字符 ‘>’,完成了与正则式的匹配,才长舒一口气,向上汇报,匹配字符串从第一个字符 ‘<’ 开始,到倒数第二个字符 ‘>’结束,即’<book> <title> Perl Hacks </title> </book>’。
Greedy量词的行为有时确实是用户所需要的,有时则不是。比如在这个例子里,用户可能实际上想得到的是 ‘book’ 串。怎么办呢?这时候lazy量词就派上用场了。把模式改为/<.*?>/就可以得到 ‘book’。这个加在 ‘*’号后面的 ‘?’ 把greedy量词行为变成lazy量词行为,从而由尽量多吃变为尽量少吃,只要吃到一个 ‘>’立刻停止。
问号在正则表达式里用途最广泛,这里是很重要的一个用途。
5. 理解backtracking
在条款4的基础上解释backtracking就很容易了。当NFA发现自己吃多了,一个一个往回吐,边吐边找匹配,这个过程叫做backtracking。由于存在这个过程,在NFA匹配过程中,特别是在编写不合理的正则式匹配过程中,文本被反复扫描,效率损失是不小的。明白这个道理,对于写出高效的正则表达式很有帮助。
wordpress UTF8 中文字数统计插件
Jan 2nd
最近想在博客中实现这样的功能:“本文字数XXX,继续阅读…”。在网上找了一款Word Count Plugin for WordPress,作者是 Murray Williams,可惜它只能统计英文单词数,却不能统计中文字数。我下载了源码,自己动手修改,实现了想要的功能。修改过程中涉及了PHP语言中如何使用正则表达式来匹配中文,于是我把过程写这在里。
在Excel VBA中使用正则表达式
Dec 25th
[译]从文本中析取有效URL链接
Nov 7th
原文作者是Jan Goyvaerts(Regex Guru),原页面链接是Detecting URLs in a Block of Text,
翻译者:rex,译者博客(http://iregex.org)。
rex注:URL是Uniform Resource Locator的缩写(wiki),中文叫作统一资源定位符(百科),解释如下:Internet上的每一个网页都具有一个唯一的名称标识,通常称之为URL地址,这种地址可以是本地磁盘,也可以是局域网上的某一台计算机,更多的是Internet上的站点。简单地说,URL就是Web地址,俗称“网址”。
饭否消息解析之从minidom到xpath
Oct 14th
抛板砖,引白玉:为何不用xpath,什么是xpath?
最近拾起了以前的小项目,在完善上篇文章发布后,“那个谁”的回复让我很感兴趣。他问,“为什么不用xpath?”
xpath是什么东东?我反问。反问之前,当然少不了先google一番,以免……那个啥。
Read the rest of this entry »
饭否消息析取之regex vs xml
Oct 8th
页内导航:
批量导出饭否程序的方法很多,但是基本思路都是先将该网页保存到本地,然后将有用的饭否消息析取出来。本文不讨论如何下载饭否网页了(使用迅雷、wget、curl等),重点讨论对于下载到本地的网页,如何将有用的饭否消息析取出来。
探索匹配中文的正则表达式
Aug 24th
按:本文使用的RegexBuddy为3.1.0(完全)版,并非最新版3.1.1(截至2008.08.23)。需要该版本的请在这篇文章后留言。
注:参考www.regular-expressions.info的风格,更新了本模板的style.css文件,加入了与正则式代码相关的格式:
- 正则式格式举例:[a-z]+@[a-z]+?\.[a-z]+
- 匹配格式举例:pig@animals.com和chicken@birds.com
- 普通文本格式举例:这是一些普通文本。hello regex world. pig@animals.com和chicken@birds.com
《精通正则表达式》视频教程提供下载
Aug 17th
偶然从网上找到该教程,下载后觉得不错,可以作为《精通正则表达式》的番外篇,共同学习。
关于此视频的讲师:
此视频的讲师为余晟先生。余先生是抓虾网高级顾问。毕业于东北师范大学,主修计算机,辅修中文。现居北京。曾任高级程序员、技术经理;从事过大量文本解析和数据抽取的工作。对程序语言、算法、数据库和敏捷开发都有兴趣,译有《精通正则表达式》(第3版)一书。
关于此视频
此视频分为5讲,每讲30分钟左右,内容深入浅出,适合以下受众:
- 对正则式感兴趣的人;
- 对正则式不感兴趣的人;
- 正则式初学者,想入门;
- 正则式有所成者,想提高。
当然,如果能静下心来,通读《精通正则表达式》原书,并亲自动手尝试,效果更为显著。 Read the rest of this entry »
发布7款与搜索相关的cheat-sheet
Jun 12th
正则表达式袖珍小抄
以前曾经发布在我的另一款blog上,由于与正则表达式相关,特在此处重新发布。该小抄的英文原始文档在这里,下面的链接是我翻译过的(中英对照)。希望能给经常使用正则表达式的朋友带来便利。
Google Cheat Sheet
在这里其实有个简化版的google cheat sheet,网页版的。下面提供的,更为全面,排版也更为华丽,共有6款,列表如下:
- EXTREME GOOGLING.pdf
- Google Cheat Sheet.pdf
- SEO_Web_Developer_Cheat_Sheet.pdf
- google-url-parameters.pdf
- google_cheat_sheet_03.pdf
- googlecheatsheet_02.pdf
请点击下表中的蓝色文件名开始下载:
为project babel加上geshi语法高亮显示
Jun 8th
注:本文使用的正则表达式为php风格。
从v2ex到M6,我一直喜欢project babel(PB)。自己也尝试在本地或服务器上架设PB系统。遇到的一个问题是:PB只支持有限的ubb代码,当我想在PB上贴源码时,没有语法高亮很不美观。
管理员对此的答复是,简约是最好的,不必非得使用72号红色字体加上一堆感叹号才能让文字有力度。当然了,这样不给脑残以任何自证的机会,当然是不错。可是因为怕脑残搞乱而消减功能,就有些类似于怕一个男人淫乱而将全天下男人葵花宝典掉一样了。
代码语法高亮,最著名的模块莫过于geshi。我使用过php写过几行代码,语法什么的都需要现查手册;对css、form之类也不是很熟悉。因此让我自己改造PB的话,一时摸不到头绪。于是在M6上发了求教贴。有热心人sara回复如下:
有个缺点,就是一个帖子里同一种代码只能用一次,希望有高手能改进一下。
比如php代码:[php]your code[/php]
- 在v2excore.php加geshi的路径。
- 在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('<', '<', $c); $c = str_replace('>', '>', $c); $text = $a . '<div class="codes php">' . geshi_highlight($c, 'php', '', 'true') . '</div>' . $d; }
加在
$text = preg_replace($p, $r, $text);
的后面。
我读了这段代码之后,发现它并没有大段的CSS内容,这我就放心多了。于是细读之,遇到问题就查手册。
这段代码翻译成自然语言,就是:
- 查找文本中[php]的次数$_m_php_open,以及[/php]的次数$_m_php_close;查找时不分大小写。
- 次数$_m_php_open与$_m_php_close是否相等且等于1。如果不满足条件,就不处理了。
只有两者相等且为1时,记录下[php]与[/php]之间的内容$c,把里面所有的”<”、”>”都换成”<"、">“形式。 - 把需要处理的代码部分$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('<', '<', $code); $code = str_replace('>', '>', $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);
大功告成。
使用方法:
- 在PB贴子的编辑框中使用[lang]…[/lang]标签。
- lang标签不可嵌套。
- 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。呵呵。
结论:
匹配中文的正则表达式
Jun 2nd
以前在编写linux下的scim郑码码表时,就跟正则式的中文匹配问题打过交道。当时总结了这样一条经验,utf8编码格式下,中文正则式应该这样书写:
[\x80-\xff]{3}
当然,这与语言无关。在perl与python中,都是一样的。
现在,这条正则式又派上用场了。正在编写的一个小程序MiniBlogs Updater中,需要计算用户所输入的文字字数。因为中英文字符编码长度不一,如果直接使用python中的len()函数,它计算的是该字串的实际长度,一个中文字并非等同于一个英文字母的。因此,需要把中文字当成英文字母来处理。
我写了这样一条语句来处理:
length=len(re.sub('[\x80-\xff]{3}','a',msg))
它的意思是,把所有的中文都替换成英文字母a,然后再统计字数。(只是统计而已,不修改源字串。)这条语句在windows下utf8文件中能够正常工作。
再分享两则与匹配中文的正则表达式有用的链接:
python正则表达式链接
May 26th
最近迷上了python,对它的三重引号赞不绝口,原来在Perl中一直困扰我的utf8字串问题,在python中得到圆满解决。我指的是一直在写的fanfou应用程序中,发送私信的编码问题。调用饭否API向饭否发送普通消息没有问题,因为它兼容utf8与gb2312;而发送私时,却只允许使用utf8编码。最见效的例子是发“联通”两个字。
闲话打住,切入正题,说一说python中的正则式。推荐两个网址:
- Python正则表达式操作指南,由A.M. Kuchling(amk@amk.ca)原创,由FireHare翻译,发布在ubuntu.org.cn上。该文档可以当作手册来查阅。
- Dive into python中的正则式在线文档,发布在啄木鸟上。该文档深入浅出,以例子入手,适合当作自学教材。
在《mastering regular expressions》一书中perl与php都拿出整整一章来讲解,唯独没有python的单独章节。好在既然已经知道了正则式的大概,剩下的只是查语法就是了,上面的第一个链接足矣。
另外再引用一下《mastering regular expressions》中的原话:
I thought I knew regular expressions until I read Mastering Regular Expressions. Now I do.
神往精通正则表达式的境界。





Comments