这篇文章针对JavaScript,正则表达式以及Firefox插件开发。
写这篇文章已经是预谋已久的事情了,但是好几天都没写,其主要原因还是太复杂了,但是其实说真正的复杂,也不算复杂,但是如果要真真正正的完全的实现这个需求还是有一定难度的,我基本上花了一个下午的时间去写一个正则表达式,写完之后才发现这个正则表达式如此之变态以至于回家之后我想改一下都读不懂自己的正则。看来就算对正则再熟悉的人,也会认为正则是火星文的。如果你不想看枯燥的过程,可以直接跳到下面很大一块的地方去拿结果。
好,先来说说需求,首先,大家都会看到这个链接http://www.jguoer.com,或者soundbbg@gmail.com。恩,不错,不过会有很多人想要订阅我的网站或者给我发邮件,当你们看到这段话的时候,而且又想给我发邮件的时候,你可以这样做:复制超链接,粘贴到浏览器,按回车键。这样就能到我的网站或者给我发链接。恩,不错,如果我做一个Firefox插件把网页里面的所有这种文本类型的链接都变成超链接,那不是很方便用户,而且肯定有不错的市场,恩,有好的想法。
咱们来简单的抽象一下。
需求,浏览器中类似URL的文本链接(http://www.jguoer.com)变成超文本链接(http://www.jguoer.com)。其实不需要当成是浏览器插件,就单纯的更简单的抽象就可以了。
1.最简单的需求
输入:http://www.jguoer.com
输出:<a href="http://www.jguoer.com">http://www.jguoer.com</a>。
嗯,看上去不那么复杂,很多人都会说,用正则表达式来匹配去做嘛,很简单的,OK,好,匹配超链接的正则表达式不难,如下所示(随便写的,未验证,只是作为一步一步的深入讲解)。
(((http)|(ftp)|(https))://)([a-zA-Z0-9]*)[.]([a-zA-Z0-9]*)[.]([a-zA-Z0-9]*)
好吧,上面的正则表达式还是很简单的,第一,我们不仅仅匹配了http头,还匹配了ftp和https。OK,这个不难,但是这个肯定不能满足我们的需求的,为什么呢,看看下面的抽象。
2.适应复杂的超链接需求
输入:http://www.jguoer.com/default.aspx?id=100
输出:<a href="http://www.jguoer.com/default.aspx?id=100">http://www.jguoer.com/default.aspx?id=100</a>
好吧,现在你看到了情况(1)里面的需求很明显不能满足,因为那个正则表达式不能匹配有后缀的超链接,所以,我们还得加上一点东西。
((((http)|(ftp)|(https))://)([a-zA-Z0-9]*)[.]([a-zA-Z0-9]*)[.]([a-zA-Z0-9]*)((/[0-9a-z_!~*'().;?:@&=+$,%#/-]+)|(/)|()))
好吧,这已经有点复杂了,但是还是不能满足我们的需求,为什么呢,再看看下面的抽象。
3.适应不带http等协议开头的超链接的需求
输入:www.jguoer.com
输出:<a href="http://www.jguoer.com">www.jguoer.com</a>
对于情况(1),(2)呢,很幸运,有http开头,所以我们可以很简单的匹配到一个超链接,但是如果是情况(3)的话,明明是超链接,为什么就不显示超链接呢。所以情况(1),(2)的正则表达式是很明显不能满足需求的,那么我们只能先匹配出所有XXX.XXX.XXX这样的情况,然后再进行判断。判断的代码我就不写了,其基本逻辑就是匹配到www.jguoer.com,然后匹配完之后就搜索其中前7-8个字符串是否符合http://,ftp://等情况,如果符合的话,那么在添加超链接的时候我们就加上http://或者ftp://之类的。
好了好了,咱们休息一下,是不是看晕了还不能理解,那好,那咱慢慢理解,如果你理解了上面的思路的话,我们再来看看是否已经满足了需求。嗯。。很可惜,还是不能满足需求,为什么呢?大家看看这个http://www.jguoer.com,好吧,我在超链接那里加粗了,那么HTML代码就是http://www.<b>jguoer</b>.com,这我们的(1),(2),(3)的正则表达式就匹配不了了。看看这个还是有缺陷的啊。不过好在我们改进一下正则表达式,在XXX.ABC.XXX的中间的域名的里面加上匹配<,>就行了。就是要匹配http://www.abc.com,www.abc.com,http://www.a<tag>b</tag>c.com和www.a<tag>b</tag>c.com。好吧,有点复杂,我就直接上正则吧。
(?:(?:(?:[^@:(){}`\'\"\\[\]\s]+:)?[^@:(){}`\'\"\\[\]\s]+@)?(www|ftp|irc|jabber)\.(?:[^`~!@#$%^&*()_=+\[{\]}\\|;:\’\",.\?\s]+\.)+[a-z]{2,6}(?:[\#?](?:[^\^\[\]{}|\\\’\"`\s]*[^!@\^()\[\]{}|\\:;\’\",.?<>`\s])?)?)
上面的正则是不是已经有点看不懂了?别担心,至少上面的满足了我们的需求,匹配了上面加粗的内容。好像貌似我们已经基本上完成了,但是等等,好像有些情况还没考虑进去啊。还有哪些啊?
4.适应HTML代码里面加粗的超链接的需求
比如soundbbg@gmail.com(邮件形式),还有192.168.0.1(IP形式),当然还会包括上面所有的加粗之类的情况了,如soundbbg@gmail.com,192.168.0.1。
好了好了,情况够复杂了,还能更复杂吗?可以,因为还有很多情况你没考虑到。
5.适应多种协议以及以上所有需求的需求
这里只是将http://,https://,ftp://协议考虑进去了,如果是电驴呢?ed2k://file:xxxx,ok,还有邮箱的有mailto:soundbbg@gmail.com,还有,还有。。
那这样你可能会惊呼,哇,那这样的正则表达式要有多复杂啊。对,其实逻辑不难,但是写起来和调试起来还是很复杂的,咱们看看最终版本的正则吧,如果不懂而又有同样需求的话,可以免费COPY:)。
?:(news:\/\/|nntp:\/\/|telnet:\/\/|irc:\/\/|mms:\/\/|ed2k:\/\/|file:\/\/|about:|mailto:|xmpp:|h…s:\/\/|f.p:\/\/|h.?.?p:\/\/)[^\^\[\]{}|\\\’\"<>`\s]*[^!@\^()\[\]{}|\\:;\’\",.?<>`\s])|(?:(?:(?:(?:[^@:(){}`\'\"\/\[\]\s]+:)?[^@:(){}`\'\"\/\[\]\s]+@)?(www|ftp|irc|jabber)\.(?:[^`~!@#$%^&*()_=+\[{\]}\\|;:\’\",.\/?\s]+\.)+[a-z]{2,6}(?:[\/#?](?:[^\^\[\]{}|\\\’\"`\s]*[^!@\^()\[\]{}|\\:;\’\",.?`\s])?)?)|(?:(?:[^@:<>(){}`\'\"\/\[\]\s]+@)?((?:(?:(?:(?:[0-1]?[0-9]?[0-9])|(?:2[0-4][0-9])|(?:25[0-5]))(?:\.(?:(?:[0-1]?[0-9]?[0-9])|(?:2[0-4][0-9])|(?:25[0-5]))){3})|(?:[A-Fa-f0-9:]{16,39}))|(?:(?:[^`~!@#$%^&*()_=+\[{\]}\\|;:\’\",<.>\/?\s]+\.)+[a-z]{2,6}))\/(?:[^\^\[\]{}|\\\’\"<>`\s]*[^!@\^()\[\]{}|\\:;\’\",.?<>`\s](?:[#?](?:[^\^\[\]{}|\\\’\"<>`\s]*[^!@\^()\[\]{}|\\:;\’\",.?<>`\s])?)?)?)|(?:[^@:<>(){}`\'\"\/\[\]\s]+:[^@:<>(){}`\'\"\/\[\]\s]+@((?:(?:(?:(?:[0-1]?[0-9]?[0-9])|(?:2[0-4][0-9])|(?:25[0-5]))(?:\.(?:(?:[0-1]?[0-9]?[0-9])|(?:2[0-4][0-9])|(?:25[0-5]))){3})|(?:[A-Fa-f0-9:]{16,39}))|(?:(?:[^`~!@#$%^&*()_=+\[{\]}\\|;:\’\",<.>\/?\s]+\.)+[a-z]{2,6}))(?:\/(?:(?:[^\^\[\]{}|\\\’\"<>`\s]*[^!@\^()\[\]{}|\\:;\’\",.?<>`\s])?)?)?(?:[#?](?:[^\^\[\]{}|\\\’\"<>`\s]*[^!@\^()\[\]{}|\\:;\’\",.?<>`\s])?)?))|([^@:<>(){}`\'\"\/\[\]\s]+@(?:(?:(?:[^`~!@#$%^&*()_=+\[{\]}\\|;:\’\",<.>\/?\s]+\.)+[a-z]{2,6})|(?:(?:(?:(?:(?:[0-1]?[0-9]?[0-9])|(?:2[0-4][0-9])|(?:25[0-5]))(?:\.(?:(?:[0-1]?[0-9]?[0-9])|(?:2[0-4][0-9])|(?:25[0-5]))){3})|(?:[A-Fa-f0-9:]{16,39}))))(?:[^\^*\[\]{}|\\\"<>\/`\s]+[^!@\^()\[\]{}|\\:;\’\",.?<>`\s])?)|([0-9]{1,3})[.]([0-9]{1,3})[.]([0-9]{1,3})[.]([0-9]{1,3})
好吧,这个正则是有点复杂了,不过确实管用。我们能匹配所有的可以作为超链接的内容了。
但是,好吧,我也不想写但是,但是确实有但是。我们就算匹配了,也不能完全实现我们替换的链接,我们这里仅仅只走了第一步–即匹配了超链接。
好,我们选择了http://www.abc.com,那么怎么变成<a href="http://www.abc.com">http://www.abc.com</a>呢?好吧,这是有点难,但是不是难点,所以这里就不过多讲解。这里只是提一下思路。
找到匹配的URL。
1.找到这个Match对象的前缀,如果是"或者包含http://,则说明这个是链接内(HTML标记内的链接),不能替换,否则替换。(看注释1)
2.然后找到Match对象的起始点,如果XXXXwww.abc.comXXX,这个时候不能直接用replace替换,而需要找到www的索引,然后用substring替换。(看注释2)
3.最后得到最终结果。
如果我每个都说的话,那就是2篇文章了。太麻烦了。好了,我们正确的找到了并替换了相应的超链接之后,就能够实现我们的需求了。这个变态的需求。下面我给大家一个文件,如果大家自己能写代码实现的话,那就真的理解了。大家可以锻炼锻炼。

输入的HTML效果图(超链接,IP和E-Mail)

输出的HTML(该变的都变成超链接了,注意,第四个本来是超链接的没有改动过)
下面是这个HTML的输入。
test.html (306.00 bytes)
注释1:使用正则表达式需要使用JS里面的正则表达式的Match对象并使用while循环匹配对象。
注释2:由于不能替换,所以我们必须要将替换的结果插入到替换的地方,所以必须使用注释1里面的Match对象的index属性进行调整。