本节书摘来自异步社区《重构HTML:改善Web应用的设计(修订版)》一书中的第2章,第2.4节,作者: 【美】Elliotte Rusty Harold 更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.4 正则表达式
手工检查或者改动一个网站的每一个文件,即使是一个小型网站的少量文件,也是一件十分乏味和费劲的事情。让计算机搜索错误,可以的话同时自动修复错误,这显然是更高效的做法。很多工具都支持这种方式,包括诸如grep、egrep和sed等命令行工具,诸如jEdit、BBEdit、TextPad和PSPad等文本编辑器,当然也包括诸如Java、Perl和PHP等编程语言。所有这些工具都提供了一门专门的搜索语法规则,被称为正则表达式。尽管在不同的工具中它的表现略有不同,但基本语法几乎是一致的。
为达到说明的目的,本节中我将使用jEdit文本编辑器作为搜索和替换工具。选择它的原因在于它提供非常多适合你的特性,GUI也非常合理,而且还是开源的。由于它是用Java编写的,所以本质上说能够运行于任何平台上。
然而,这里我展示的技术当然不会只局限于一个编辑器上。我在工作中常用到的是界面稍为优美的BBEdit,但它只能运行在Mac上。当然还有很多其他的选择,如果你喜欢某一种工具,那就用它吧。其实你需要的是:
- 完整支持正则表达式的搜索和替换;
- 递归搜索目录的能力;
- 过滤搜索文件的能力;
- 能够显示变更且不需手工去验证的工具;
- 自动识别不同的字符编码和行尾结束惯例(line-ending conventions)。
满足上述条件的工具应该就够用了。
2.4.1 搜索
正则表达式的第一个目的是查找所有可能是错误的东西。比如,最近我发现在一个文件中,我老是把2006拼错为20066,而且这个错误可能不止一处,所以我需要通过搜索该字符串来检查这个错误。
在jEdit中,你可以使用Directory菜单中的Search/Search执行一个多文件搜索。选中这个菜单项后出现了如图2-6所示的对话框,图中示例有所调整。
- 需要搜索的字符串(即目标字符串)在第一个输入框中。
- 用于替换目标字符串的字符串在第二个输入框中。这里我只查找而不替换,所以不用输入替换字符串。
- Directory单选按钮表示是否搜索多文件。你可以只在当前文件甚至是当前选中的文本中进行搜索。
- 将Filter设置为.html表示搜索只以.html为后缀的文件。你可以修改此处以搜索不同类型或不同部分的文件。比如,我常常只需搜索那些被命名为news2000.html、news2001.html、news2002.html等旧信息文件。在这种情况下,我需要把Filter设置为news2.html。当然我也可以通过重写过滤器的正则表达式,比如newsdddd.html,搜索到包括news1999.html等更旧的文件。
- 我还指定了需要搜索文件所在的本地目录,本例中是/Users/elharo/Cafe au Lait/javafaq。
- 选中Search subdirectories复选框,否则,jEdit不会搜索javafaq的下一层目录,而只是javafaq这一层。
- 选中Keep dialog复选框,这在搜索完成后还能保持对话框的打开状态。
- 选中Ignore case复选框,这允许正则表达式忽略字母大小写。对你来说,- 大部分情况下都是要忽略大小写的。
- 选中Regular expressions复选框,如果你只是查找一个常量字符串,就没有必要选中此项。但是,大部分的搜索都会比单纯的字符串搜索复杂。
- 选中HyperSearch,这会出现一个显示所有匹配结果的窗口,而不只是下一项的匹配结果。
幸好这个特定的问题看起来已经解决掉了,但我还发现了另一个更严重的问题。由于一些未知因素,我在其中一个网站中的链接中使用了双等号,如下所示:
<a href=="../../index.html">Cafe au Lait</a>
因此,链接无论在哪儿都是失效的。第一步是要找出涉及此问题的所有文件。在这个例子里,错误的字符串是常量,并且在正确的文本中出现的可能性也很小,所以非常容易进行搜索。图2-7的HyperSearch结果显示,这个问题在476个文件中出现了4475次。
在错误不多的情况下,你只用单击每个错误以打开文件,并手工修复就行了。有时这也是必需的,甚至是最简单的解决方案。但是在错误量成千上万时,你就有必要使用工具来修复了。在上面的例子中,解决方法是不言自明的:在Replace with文本框中输入href=,再单击Replace all按钮就行了。
执行此类操作时必须十分小心,否则小错也会酿成大祸。错误的搜索和替换可能是错误问题的根源。在执行整站的操作之前,你应该先在少量的文件上进行搜索和替换,对你的正则表达式测试一番。
更重要的是务必在网站的备份副本上进行操作,在每次更改后务必运行测试套件,并在文件被改动的情况下当场核对,以保证正确性。如果发生了某些错误,编辑器的撤销功能将会非常有用。但并不是所有的编辑器都能有一个足够大的缓冲区(buffer)支持多重撤销,所以处理不了上千次的改动。假如编辑器不支持多重撤销,马上删除正在编辑的副本并用一个原始的副本进行替换,以防止搜索出错。跟其他复杂的代码一样,有时你不得不多花点时间对正则表达式进行排错。
2.4.2 搜索模式
通常你不关心搜索的具体内容,但必须了解它的通用模式。举例来说,要查找刚过去的几年的公元表示法,实际上是要搜索以200开头的四位数字。查找name=value这种形式的属性对时,并不能保证是name=value、name='value'还是name="value"格式。搜索所有不管是否有属性的
起始标签。这些都是使用正则表达式的绝佳场合。
在正则表达式中,特定的字符和模式可以表示其他的字符集合。比如,d表示任意的数字。因此要搜索2000年到2009年中的任意一年,可以使用200d这样的正则表达式,它能匹配2000、2001、2002等一直到2009这些年份。
但是200d这个正则表达式也会匹配12000、200032、12320056或其他可能不是年份的字符串。(准确地说,它匹配符合200d的字符串子串,而不是整个字符串)。因此,你可能希望指明需要匹配前后都有空白③的字符串。元字符s匹配空白,所以现在我们可以将正则表达式重写为s200ds,这样才可以实现只匹配看起来是本世纪头十年中年份的字符串。
当然这还是不能保证匹配到的字符串形式就一定是年份,它也有可能是价格、人数、分数、电影标题等。你需要看一下匹配列表,检查是否是你想要的结果。尤其是在如此简单的情况下,误报的情况需要加以注意。当然,更进一步地完善正则表达式可以避免误报,或者手工删除偶然的匹配,都是可行的。
这里有更多的方法。比如我们在这个搜索中可以使用b200db。元字符b匹配单词的开头或结尾,完全不会选中任何字符。这就可以避免需要选中单词前后的空白,也可以识别在句子末尾的诸如“This is 2008.”中的年份。但是它不能区别是英文句号还是数学中的小数点,所以也会匹配到“in 中的2005。
你可以简单地使用或操作符|来分隔年份,比如:
2000|2001|2002|2003|2004|2005|2006|2007|2008|2009
但单词分界问题还是避免不了。
有时你得暂时停止使用搜索。特别是由CMS、模板页面或者其他程序自动生成的内容时,搜索只能用来找错,定位程序在哪里产生了错误的标记。然后必须改正程序错误才能生成正确的标记。在这种情况下,你就不必太担心误报了,因为所有的更改都是手工执行的。这种搜索只找错不修错。
如果你没有停止使用搜索,并且继续替换,这需要谨慎行事。正则表达式比本书中的例子更棘手,涉及HTML就更不用说有多么狡猾了。不管怎么说,对于清理HTML它都是极有价值的工具。
注解
如果你并没有太多的正则表达式经验,请参考附录A中更多的例子。同时我推荐Jeffrey E. F. Friedl的Mastering Regular Expressions,3rd Edition(O’Reilly,2006)。