匹配规则是 Proxomitron 最复杂的部分。刚开始接触时会很费解 - 尤其是如果你从没有使用过模式匹配语言。尽管如此也不要灰心。每个简单的规则都会帮助你理解得更加深入一些。每次进步一点点,很快就会成为你的第二天性。为了让你适应,就先从一些基础的 HTML 匹配的小技巧开始。
注意: 这部分要求读者懂一点点 HTML - 如果你不懂,网上有很多非常好的教程。另外如果你不打算自己写规则,那么你可以完全无视本页面。
格式化你的匹配规则
复杂的匹配规则通常会非常难以读懂。但是,为了让这些规则更容易辨认,匹配表达式和替换文本部分都可以随意换行。这些换行不会对过滤造成任何影响。如果想要在过滤中加入换行符,使用 "\n"。
另外,由于空格永远匹配 true,你也可以在匹配表达式中用空格分隔元素。只是小心,它们会消耗它们发现的空格。
一些普通信息
在设计新的规则时,哪怕过滤的不全面,也不要过滤过多。永远从简单的开始 - 然后再视需要加入更复杂的匹配。这样,当一个规则突然不匹配时,你会知道是哪部分的问题。
使用消息窗口来检查规则是否匹配,特别是使用消息窗口里的 "HTML debug info" 选项 (或在 URL 的主机名前输入 "dbug..") 来以源文件查看页面,以知道你的过滤规则都过滤了什么。这两个是非常有用的工具。另一个更有用的是新的匹配测试窗口,你可以用它检查一个规则是如何改变 HTML 代码的。
剪切和复制 HTML
通常,要设计一个新规则时,最好先把你需要的 HTML 代码剪切并复制到匹配表达式部分。值得注意的是行尾 - 由于匹配表达式部分忽略断行,一段像这样的 HTML 代码...
<br>
<p>
和 "<br><p>"(没有空格)是一样的。但是在实际匹配时就会造成问题了,因为实际的 HTML 代码有一个必须匹配的换行符。解决方法是在每行的开始或结束加上一个空格,例如 "<br> (空格) <p>"(如果它们之间没有的话)。记住一个空格可以匹配任何空格符包括换行符。
禁用标签或标签元素
由于浏览器会忽略任何它们无法解析的标签和属性,禁用标签或标签属性的一个 "快速而卑鄙" 但是非常有效率的方法就是重命名。当一个属性被许多不同的标签使用时,这种方法非常好。以 "onload" 为例,这个属性会自动运行一段 Javascript 代码。虽然通常都放在 "<body ... >" 标签里,但也有可能在别的标签里出现。要阻止这段代码,你可以使用...
Matching: | onload= |
Replace: | LoadOff= |
它会把一个标签...
<body background="bak.gif" onload="window.open(myadd);" >
变成...
<body background="bak.gif" LoadOff="window.open(myadd);" >
很简单吧!但是这也会有点冒险,因为 "onload=" 也可能在标签属性之外以普通文本的形式出现,虽然实际使用中这种情况很少发生(等于符号减少了这种情况的发生)。就算真的出现了,也不算什么大问题,因为你知道 LoadOff 是什么。
一石二鸟
这是一个用同一个规则改变开始和结束标签的简单技巧。这个技巧在默认规则的 "Blink to Bold" 里有使用。我们用这个规则把 "<blink>" 转变为 "<b>" ,并把 "</blink>" 转变为 "</b>" - 让我们看看这是如何完成的...
Matching: | <\1blink> |
Replace: | <\1b> |
通过使用 "\1" 元字符,这个规则既会匹配开始标签: "<blink>" 也会匹配结束标签: "</blink>"。另外,"\1" 会捕获结束标签的斜杠并在替换文本部分添加上去。还有一个更安全,但略微复杂的规则...
Matching: | < ( / | )\1 blink> |
Replace: | <\1b> |
你知道为什么吗?如果不知道的话,读一读本页的 "空白匹配" 一节。
捕获标签的内容
通常你只希望改变标签的一个属性,但保留别的属性。这里我们可以使用位置变量 "\0-9"。以下的例子可以用来禁用网页的背景。
Matching: | <body \1 background=\w \2 > |
Replace: | <body \1 \2> |
当位置变量的前面不是括号 ( ... ) 时,位置变量和 "*" 作用类似。这里,"\1" 捕获任何在 background 属性前面的内容,而 "\2" 捕获 background 属性后面的内容。在替换文本部分,我们略去 background 属性,但你也可以在这里放你喜欢的背景。
给标签添加一个新的属性
这是一个给标签添加一个属性的简单方法。虽然 "合适" 的方法应该是在属性已经存在的情况下替换该属性,不存在的情况下才添加,不过这会比较难一些。一般来说总是添加会比较简单一些。我们只需要保证浏览器会使用我们添加的属性而不是原先存在的属性。例如,给所有的 "<img ... >" 表天添加一个边框,你可以使用...
Matching: | <img \1 > |
Replace: | <img border=1 \1 border=1> |
为什么要加上两边 border=1?理想情况下,当浏览器发现重复的属性时,它会使用第一个属性而无视剩下的,这也是 Netscape 的做法。但 IE 的做法却完全相反(很吃惊吧)。在前后都加上属性的话则可以在 Netscape 和 IE 下都正常工作。在 Proxomitron 的规则里,保证规则跨浏览器并不像设计网页那么重要。因为你知道你自己常用什么浏览器,所以你完全可以只针对你的浏览器来写规则。
捕获特定的标签属性值
标签的属性值常常会比较难匹配。以 "<a href=... >" 为例。"href" 表示 URL,但 URL 两边可能是引号,双引号,或是没有引号。这就是 $AVQ(...)(属性值和引号)匹配命令方便的原因。它会匹配所有的值,包括它找到的引号。例如,如果你想要把 URL 捕获进 \1 变量,你可以使用...
<a * href=$AVQ(\1) * >
还记得当 "\1" 或其他的 \(数字) 跟在括号后面时,它捕获括号里的所有文本吗?如果这里你只想捕获包含特定文本的属性值,可以使用括号 (...) 和 $AV(...) 命令...
<a * href=( $AV(*(banner|advert|cgi)*) )\1 * >
这只会匹配包含单词 "banner", "advert", 或 "cgi" 的 URL。我们现在已经有了一个 "banner blaster" 类的规则雏形了。$AV(...) 和 $AVQ 一样,只不过它不会匹配任何引号,所以你也不必检查是否包含引号。尽管如此,由于我们仍然希望捕获包含引号的整个值,我们可以括住它然后在后面写上 \1。这样我们就成功将整个属性值包括引号捕获进 \1 了。
空白匹配
有时你会希望无论一个特定值存不存在,你的表达式都能成功匹配。你可以使用这条规则... "( something | )"
这会先检查单词 "something",但如果没有找到这个单词,这条表达式仍然能成功匹配。为什么?注意这段表达式里有一个 "OR" 函数,在它和右括号之间没有任何字符。这创造了一个空表达式,并且空表达式永远返回 true 并且不消耗字符。将这段理解为 - 匹配 "something" 或什么都不匹配.
注意如果你的表达式写成 "(|something)",单词 "something" 就永远不会被匹配了!因为 OR 是从左到右处理的,空白表达式会永远在单词 "something" 先被检测并匹配。
一个很实用的例子是 ( " | ) * ( " | ) ,它可以检测任何被或不被引号包围的东西。
这是一个更复杂一些的例子,如果 "<img ... >" 标签存在 "border" 值,则捕获该值,并存放进变量 \1
<img ( * (border=\w)\1 | ) * >
注意星号的位置,例如 "<img*(border=\w|)\1*>" 可能并不会像你想象的那样工作。"<img " 后紧接着的第一个单词如果不是 "border",副表达式 OR 函数后面的空白匹配仍然会成功匹配!那么当 "border" 在后面出现的时候,它会被第二个星号匹配,因为第一次匹配已经错过了。
使用 "AND" 捕获多个标签属性
使用 AND "&",你可以捕获一组特定的标签属性而无视它们的前后顺序。例如,如果你想要重写 "<img ... >" 标签来包含你自己的一张图片,但你想要保留原先的 "width" 和 "height" 值,你可以使用...
Matching: | <img ( (*(height=\w)\1*| ) & (*(width=\w)\2*| ) ) > |
Replace: | <img src="file://d|/my_pictures/shonen.gif" \1 \2 > |
注意 height 被捕获进变量 \1,而 width被捕获进变量 \2。通过使用之前描述过的 "空白匹配" 语法,即使没有 width 或 height 值,表达式仍然会成功匹配。这种情况下对应的 \n 值会是空白。
使用 "智能" 引号
大多数时候,"\w" 就可以成功捕获标签属性。但有时你会还需要一些其他的东西,例如 "<img ... >" 标签的 "alt" 属性通常会包含空格,像是... alt="this is some text" or alt='also some text'. 要捕获这类东西时可以使用双引号...
Matching: | alt=( " * " )\1 |
伴随着 Javascript 的使用,通常你会遇到更复杂的情况。以下是一个常见的 "引号包含引号" 的例子...
onmouseover="javascript:window.open( ' mywindow.html ' ); "
要想捕获这个值会比较困难,因为它也可以被写成...
onmouseover= ' javascript:window.open( " mywindow.html " ); '
这时单引号的好处就体现出来了。当它跟着一个双引号时,它会找到那个双引号所对应的结束引号...
Matching: | onmouseover=( " * ' )\1 |
不管是双引号单引号还是嵌套引号,这句统统都匹配!但是,这种情况下其实还有更好的解决方式 - 记得 $AVQ( ) 命令吗?它也可以处理各种各样的引号(甚至没有引号)并且比用引号捕获属性值来得更方便...
Matching: | onmouseover=$AVQ(\1) |
使用文件 URL 来引入你自己的东西
"文件 URL" 就是一个指向你硬盘上的某个文件,而不是 Internet 上的某个地址的 URL。浏览器使用文件 URL 来查看脱机存放的网页,但它们也可以用来方便地往页面插入你自己的图片,网页,甚至是 Javascript 代码。
Proxomitron 现在可以更容易地往规则的替换文本部分插入一个文件 URL。首先将光标指向你希望插入 URL 的地方,右键,从菜单里选择 "Insert file URL" 。一个选择文件对话框会打开,让你选择你要插入的文件。
这是一个例子,使用文件 URL 的 "background replacer" 规则
Matching: | <body ( \1 background=\w | ) \2 > |
Replace: | <body \1 background="file://c|/pictures/background.gif" \2 > |
注意匹配表达式在 ")" 和 "\2" 之间有一个空白 - 忘记这个空白其实是很常见的错误!这会导致 \2 匹配 "( ... )" 里的内容,而不是在 background 属性之后的所有内容。
往每个访问的网页插入 Javascript 或其他
这是一个真正用来控制网页的技巧。JavaScript 是非常强力的工具 - 前提是由合适的人使用... 现在 Proxomitron 就提供了这么个机会!要往访问的每个网页插入 Javascript 或是其他东西,先要匹配一个你知道每个页面都会有的标签 - "<html>", "<head>" 或 "<body>" 都是很好的选择。例如,一个阻止弹出 Javascript 错误消息的规则。对于 Netscpe 浏览器我们需要在所有页面都执行以下 Javascript 代码 "<script> this.onerror=null; </script>" 规则如下...
Matching: | <html> |
Replace: | <html>\n<script> onerror=null; </script>$STOP( ) |
这个规则会直接在 <html> 标签后面插入代码。注意那个 "\n" - 会在 <html> 标签后和<script>标签之间产生一个换行符。虽然这并不是必要的,但是可以网页源代码更容易阅读。另外,当使用这个技巧时,最好勾选 "Allow for multiple matches"。这会允许任何其他使用类似此技巧的规则也能正常工作。
还要注意 $STOP( ) 命令。这会为页面的剩余部分关闭此过滤规则,保证我们的脚本只被插入一次 (如果你使用多重匹配的话,此命令就显得更为重要了)。
像上面这样的小脚本可以直接放在替换文本部分,但是如果是大一点的脚本,就会显得有点麻烦了。一个更好的解决方法是使用 "<script ...>" 标签包含一个 "文件 URL",指向我们真实的脚本文件。例如...
<html>\n<script src= "file://c|/scripts/myscript.js" >
一个更好的往每个访问的网页插入东西的方法
我先说上面的那个方法因为这在你要往网页的特定区域插入东西时会很有用(像是 <body ... > 标签的后面)。但是,如果你只想把东西放在页面的开始处或结尾处,还有一个更简单的方法....
匹配元字符有两个特殊的值 <start> 和 <end> - 它们是用来往网页的开始或结尾处插入东西的。它们容易使用,而且由于不需要搜索,非常高效。使用 <start> 的话,上面的例子可以写成...
Matching: | <start> |
Replace: | <script> onerror=null; </script> |
这用于 Javascript(和重写 JavaScript 函数 - 见下)效果非常好。而且也不用管是否打开了 "Allow for multiple matches"!
重写 JavaScript 函数
在 Netscape 和 Internet Explorer 4.0+ 里,有一个非常有效的技巧可以用来重写 JavaScripts。任何 JavaScript 函数 - 甚至是内建的 - 都可以被自定义成我们想要的形式。例如我们想要去除烦人的 "alert( ... )" 和 "confirm( ... )" 对话框,我们可以在网儿开始插入下面的脚本(使用上面说的 <start>)....
function confirm( ){ return(1); }
现在任何脚本想要调用 alert 或 confirm 对话框时,都会调用我们的函数。通过返回 "1",我们还让脚本里的 confirm 对话框以为我们点击的是 "yes"!
这是非常强大的概念 - 虽然例子里的函数几乎没做什么事,但其实我们可以使用更复杂的替换函数做一些其他完全不同的事情。这几乎是完全没有什么限制的。
由于这种方法非常有效,Proxomitron 的默认规则也用了很多这种技巧。一个缺点就是,它在绝大多数版本的 Netscape 和 IE 4.0+ 的浏览器里都可以正常工作 - 但是在使用 Microsoft JScript 的 IE 3.x 没法工作。Proxomitron 为 IE 3.0 用户提供了另外的规则组合,用搜索和替换达到相同的目的。
如何使用递归匹配
递归匹配是指一个规则匹配它本身的结果。通常我们都不希望这种事情的发生,特别是有时候会导致 无限递归 - 规则会反复无止境地匹配它本身。但是,使用正确的话,这也是一种很强大的技巧。例如 - 如果你想去除任何 "<script ..." 和 "</script>" 标签产生的弹出窗口。由于 JavaScript 使用 "open(...)" 命令弹出一个新的窗口,可以像这样设计规则...
Matching: | <script \1 open \( * \) \2 </script> |
Replace: | <script \1 \2 </script> |
(事实上,这里最好也要限制 范围 ,我们会稍候讨论到。同时也注意用 "\" 编码括号)。这在当该段 Javascript 代码里 有且只有一个 open 命令时正常,但如果有超过一个,那么只有第一个会被去除。解决方法?两种。第一,勾选 "Allow for multiple matches" 来让规则匹配它本身的结果。第二,我们可以将替换文本变成...
Replace: | \n<script \1 \2 </script> |
为什么?为了防止意外的无限递归,Proxomitron 的匹配引擎总是在所有规则都被检查过之后向前移动一个字符。意思是在之前的匹配中,如果不勾选多重匹配,我们的规则在下一次检查文本时就只能看到 "script ..." 而不是 "<script ..."。利用这一点,我们只要在替换文本的开始加入一个换行符。虽然在最终输出中这会在 "<script ..." 标签的前面产生一个空行,但浏览器会无视这个空行的。这样规则在下一次检查文本时就会看到 "<script ..."。你也可以用一个空格代替空行符 "\n",或者是其他任何不会影响浏览器正常功能的字符,反正只要是能把整个结果往前推一个字符就可以了。
一旦所有的 "open(...)" 命令都被移除,规则就不会再匹配成功,所以这不会导致无限递归。
事实上,虽然这个技巧很有用,但其实还有更好(也更安全)的方法...
如何 不 使用递归匹配
Proxomitron 也有一个功能叫作 "替换堆栈"。它使用 \# 变量收集 多个。也许最简单的理解这个功能的方法就是把 \# 想象成从 \0 一直用到 \9 并能以同样的顺序返回值的一种方法。区别就是如果在循环里被调用或被调用多次,值会一直堆叠在原先的值的结尾而不是替换。通过这点我们可以进行多次匹配而不必使用递归...
Match: | <script (\#.open \( * \))+ \# </script> |
Replace: | <script \@ </script> |
这里我们把 "+" 放在括号后面,创建一个循环匹配: "(...)+",它循环一直到无法找到任何匹配。在这个例子里这个循环匹配会一直搜索 ".open()" 命令。任何在单词 "open" 前面的都会先被第一个 \# 匹配,然后反复下去,不停地把下一个 "open" 前面的代码放进堆栈 \#。一直到找不到任何 "open" 后,剩下的部分就会被放进第二个堆栈 \#。
最终除了 "open" 命令,我们捕获了两个 script 标签里的所有代码。在替换文本部分我们只要调用 \@ 释放堆栈 \# 里储存的所有东西。这和递归匹配有相同的效果,但更有效率,也减少了陷入无限循环的可能性。
一些关于范围的东西
为了简单起见,刚才的例子并没有用到网页过滤的边界设置。Bounds 可以用来控制在搜索匹配时 Proxomitron 的匹配引擎可以扫描多远(参见网页过滤规则编辑器可以获得更多详细解释)。
在 bounds 部分你通常只要给规则定义搜索的开始点和结束点。仍然是上面的例子,下面的规则...
Matching: | <script \1 open \( * \) \2 </script> |
Replace: | <script \1 \2 </script> |
在加上 bounds 后会是...
Bounds: | <script\s*</script> |
Byte Limit: | 4096 |
Matching: | \1 open \( * \) \2 |
Replace: | \1 \2 |
注意我们只是把开始和结束文本移到了 bounds 部分。byte limit 是 Proxomitron 搜索的最大字符限制。另外我们在匹配表达式里使用 \1 和 \2 来捕获 bounds 匹配的开始和结束 - 包括 <script 和 </script>。所以我们就不用在替换文本里再写一遍了。
用 bounds 时需要注意的事情是: Match: 部分必须完全匹配与 Bounds: 部分一致的文本
所以,如果 bounds: 匹配的是从 "<HEAD>" 到 "</HEAD>" 部分,匹配表达式也必须完全匹配这段文本而不能是这段文本中的某一段!要做到这样很简单。就像上面的例子,只要把开始和结束都捕获进变量,再在替换文本部分调用就行了。
差不多就是这么多...
这些就是我能想到的所有技巧了。如果你有什么好的想法可以告诉我。记住你也可以看看 Proxomitron 自带的一些规则!如果你要写新的规则,它们会是很好的入门教材 - 通常你会发现有些规则和你要做的比较类似。
