[跟我学正则表达式] 8. 使用后向引用

本文深入探讨了正则表达式的高级应用,包括匹配HTML标签、重复单词检测、文本格式化及大小写转换等技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

来自:http://searun.iteye.com/blog/389186

<HTML>
<BODY>
    <DIV>
        <H1>Welcome to my Homepage</H1>
        Content is divided into two sections: <BR>
        <H2>ColdFusion</H2>
        Information about Macromedia ColdFusion.
        <H2>Wireless</H2>
        Information about Bluetooth, 802.11, and more.
    </DIV>
</BODY>
<script>
    var t = document.getElementsByTagName("DIV");
    var myRe = /<[hH]1>.*<\/[hH]1>/g;
    var str = t[0].innerHTML;
    var myArray;
    while ((myArray = myRe.exec(str)) !== null) {
        var msg = "Found " + myArray[0] + ".  ";
        msg += "Next match starts at " + myRe.lastIndex;
        console.log(msg);//Found <h1>Welcome to my Homepage</h1>.  Next match starts at 34 
    }
</script>
</HTML>

分析

这里的模式“<[hH]1>.*</[hH]1>”匹配了第一个段落(从 <H1> 到 </H1> ),同样可以匹配 <h1> ( HTML 不是大小写敏感的)。但是什么模式可以匹配所有的六种段落(六种中的任何一种都是合法的)?

 

一种方案是将前面的 1 换成数字区间,如下面所示:

<HTML>
<BODY>
    <DIV>
        <H1>Welcome to my Homepage</H1>
        Content is divided into two sections: <BR>
        <H2>ColdFusion</H2>
        Information about Macromedia ColdFusion.
        <H2>Wireless</H2>
        Information about Bluetooth, 802.11, and more.
    </DIV>
</BODY>
<script>
    var t = document.getElementsByTagName("DIV");
    var myRe = /<[hH][1-6]>.*?<\/[hH][1-6]>/g;
    var str = t[0].innerHTML;
    var myArray;
    while ((myArray = myRe.exec(str)) !== null) {
        var msg = "Found " + myArray[0] + ".  ";
        msg += "Next match starts at " + myRe.lastIndex;
        console.log(msg);
    }
    /*
Found <h1>Welcome to my Homepage</h1>.  Next match starts at 34
Found <h2>ColdFusion</h2>.  Next match starts at 101
Found <h2>Wireless</h2>.  Next match starts at 164 
    */
</script>
</HTML>

分析

这看起来可以工作:“<[hH][1-6]>”可以匹配任何开始的段落标签(在例子中包括<H1>和<H2>),而“<[hH][1-6]>”可以匹配所有的结束标签(在例子中为< / H1>和< / H2>)。

 

注意:我们这里使用了“.*?”而不是“.* ”。正如在第五章解释的一样,如“ * ”的量词是贪婪的,所以模式“<[hH][1-6]>.*</[hH][1-6]>”将匹配从第二行中的 <H1> 直到第六行中的 </H2> 。可以使用非贪婪量词“ .*? ”来解决这个问题。

 

我说的是可能,而不是可以,因为这个特定的例子中即使是使用贪婪量词也是可以解决问题的。因为这里的元字符“ .”不能匹配换行符,而在这个例子中,段落标签都是位于单独的行中。但是这里使用非贪婪的量词匹配符是没有副作用的,最好使用安全的模式。

<HTML>
<BODY>
    <DIV>
        <H1>Welcome to my Homepage</H1>
        Content is divided into two sections: <BR>
        <H2>ColdFusion</H2>
        Information about Macromedia ColdFusion.
        <H2>Wireless</H2>
        Information about Bluetooth, 802.11, and more.
    </DIV>
</BODY>
<script>
    var t = document.getElementsByTagName("DIV");
    var myRe = /<[hH][1-6]>.*<\/[hH][1-6]>/g;
    var str = t[0].innerHTML;
    var myArray;
    while ((myArray = myRe.exec(str)) !== null) {
        var msg = "Found " + myArray[0] + ".  ";
        msg += "Next match starts at " + myRe.lastIndex;
        console.log(msg);
    }
    /*
Found <h1>Welcome to my Homepage</h1>.  Next match starts at 34
Found <h2>ColdFusion</h2>.  Next match starts at 101
Found <h2>Wireless</h2>.  Next match starts at 164 
    */
</script>
</HTML>

成功了吗?还没有。看看下面的例子(使用了同样的模式):

<script>
    var t = document.getElementsByTagName("DIV");
    var myRe = /<[hH]([1-6])>.*?<\/[hH][1-6]>/g;
    var str = 
        "<DIV>\n" +
        "<H1>Welcome to my Homepage</H1>" +
        "Content is divided into two sections:<BR>" +
        "<H2>ColdFusion</H2>" +
        "Information about Macromedia ColdFusion." +
        "<H2>Wireless</H2>" +
        "Information about Bluetooth, 802.11, and more." +
        "<H2>This is not valid HTML</H3>" +
    "</DIV>";
    var myArray;
    while ((myArray = myRe.exec(str)) !== null) {
        var msg = "Found " + myArray[0] + ".  ";
        msg += "Next match starts at " + myRe.lastIndex;
        console.log(msg);
    }
    /*
Found <H1>Welcome to my Homepage</H1>.  Next match starts at 37
Found <H2>ColdFusion</H2>.  Next match starts at 97
Found <H2>Wireless</H2>.  Next match starts at 154
Found <H2>This is not valid HTML</H3>.  Next match starts at 231     
    */
</script>
</HTML>

注意:上面的再也不能直接获取html中的内容了,因为浏览器会自动填充上<h2>,其实前面的例子中也是这样,大写都编程了小写了

分析

采用 <H2> 开始而采用 </H3> 的段落标题标签是非法的,但是现在的模式可以匹配。

 

这里的问题在于匹配的第二个部分(匹配结束的标签)没有办法知道匹配第一部分的知识(匹配开始的标签)。这也是为什么后向引用重要的原因。

使用后向引用匹配

在后面将会重新来看上面的例子。现在我们来看一个简单的例子,一个如果不使用后向引用就不能解决问题的例子。

 

假设现在有一段文本,你希望找到所有重复的单词。很显然,在搜索单词的第二次出现的时候,必须首先知道此单词。后向引用允许正则表达式模式参照前面的匹配内容(在这个例子中,就是第一次匹配的单词)。

理解这个特性的最好方式就是看看它的使用。下面的文本中包含了三组需要定位的重复单词:

<HTML>
<BODY>
    <DIV>
        This is a block of of text,
        several words here are are
        repeated, and and they
        should not be.
    </DIV>
</BODY>
<script>
    var t = document.getElementsByTagName("DIV");
    var myRe = /[ ]+(\w+)[ ]+\1/g;
    var str = t[0].innerHTML;
    var myArray;
    while ((myArray = myRe.exec(str)) !== null) {
        var msg = "Found " + myArray[0] + ".  ";
        msg += "Next match starts at " + myRe.lastIndex;
        console.log(msg);
    }
    /*
Found  of of.  Next match starts at 24
Found  are are.  Next match starts at 59
Found  and and.  Next match starts at 79  
     */
</script>
</HTML>

分析

此模式可以工作,但是为什么可以工作呢?“[ ]+ ”匹配一个或者更多空格,“\w+”匹配一个或者更多的文字数字式字符,而“[ ]+”则用来匹配尾部的空格。但是注意到这里的“ \w+ ”加上了括号使其成为子表达式。此子表达式并不是用于重复匹配,而且本例中也不需要重复。这里的子表达式仅仅是对表达式进行分组,标记此子表达式供以后使用。模式的最后部分是“ \1 ”,这是对子表达式的后向引用,所以当“ \w+ ”匹配了单词 of ,“ \1 ”也将匹配of ,当“ \w+ ”匹配了单词 and ,“ \1 ”也将匹配 and 。

 

注意:术语后向应用是因为这些实体将引用以前的子表达式。

但是“ \1 ”的实际含义是什么呢?它匹配模式中第一个子表达式。同理,“ \2 ”将匹配第二个子表达式,“ \3 ”将匹配第四个,依此类推。“[ ]+(\w+)[ ]+\1”因此将可以匹配所有重复出现的单词。

提示:你可以将后向应用理解成变量。

现在你已经看到了后向引用的用法,再来看看前面的 HTML 例子。使用后向引用,可以创建一个模式用来匹配开始标签和结束标签(忽略所有不匹配的标签对)。下面是这个例子:

<script>
    var t = document.getElementsByTagName("DIV");
    var myRe = /<[hH]([1-6])>.*?<\/[hH]\1>/g;
    var str = 
        "<DIV>\n" +
        "<H1>Welcome to my Homepage</H1>" +
        "Content is divided into two sections:<BR>" +
        "<H2>ColdFusion</H2>" +
        "Information about Macromedia ColdFusion." +
        "<H2>Wireless</H2>" +
        "Information about Bluetooth, 802.11, and more." +
        "<H2>This is not valid HTML</H3>" +
    "</DIV>";
    var myArray;
    while ((myArray = myRe.exec(str)) !== null) {
        var msg = "Found " + myArray[0] + ".  ";
        msg += "Next match starts at " + myRe.lastIndex;
        console.log(msg);
    }
    /*
Found <H1>Welcome to my Homepage</H1>.  Next match starts at 37
Found <H2>ColdFusion</H2>.  Next match starts at 97
Found <H2>Wireless</H2>.  Next match starts at 154 
    */
</script>
</HTML>

分析

同样的,在这里找到了三个匹配:一个<H1> 对和两个 <H2>对。就像以前一样,“<[hH]([1-6])>”将匹配任何的段落标签。但是和以前不一样的是,这里的“ [1-6] ”使用了小括号括起来成为了子表达式。这样,匹配结束标签的模式可以通过“</[hH]\1>”中的“ \1 ”来引用此子表达式。“ ([1-6]) ”是一个可以匹配数字 1 到 6 的子表达式,“\1 ”因此可以匹配相同的数字。在这种情况下,“<H2>This is not valid HTML</H3>”将不能匹配。

 

笔记:非常遗憾的是,后向引用语法在不同的正则表达式实现中是不一样的。 JavaScript 中使用 \ 来表示后向引用(除了 $ 使用时的替换操作),Macromedia ColdFusion 和vi也是这样。 Perl 语言使用的是 $ (所以 $1 表示这里的 \1)。 .NET 正则表达式支持返回一个包含匹配名为 Groups 属性的对象,所以 C# 中的match.Groups[1]将引用第一个匹配,Visual Basic .NET中的match.Groups(1)将引用第一个匹配。 PHP 通过名为 $matches 的数组返回此信息,所以$matches[1] 引用第一个匹配(尽管可以通过标志来改变)。在 JAVA 和 Python 语言中则返回包含一个数组名为 group的匹配对象。

具体的正则表达式实现相关信息可以参看附录 1 :流行应用和语言中的正则表达式。

注意:后向引用只能够引用子表达式(需要使用小括号括起来)。

提示:引用的匹配一般是从 1 开始。在大多数的实现中,匹配 0 可以用来引用整个表达式。

笔记:正如你所看到的,子表达式是通过相对位置来引用的: \1 引用第一个, \5 引用第五个,等等。尽管获得了广泛的支持,这个语法有着一个严重的问题:移动或者修改子表达式(也因此改变了子表达式的顺序)将会破坏模式,增加或者删除子表达式将会带来更大的问题。为了能够克服这个缺点,现在有些新的正则表达式实现支持命名引用,也就是说为每个可能引用的子表达式给定一个唯一的名称,在以后可以通过此名称来引用(而不是相对位置)。命名引用在本书中并没有包含,因为这还不是一个广泛支持的特性,而且支持此特性的正则表达式实现的语法都很不一样。尽管如此,如果你使用的应用或者语言支持命名引用的话(如 .NET ),最好是利用这种特性的好处。

 

执行替换操作

到现在为止我们所看到的正则表达式都是进行搜索——在一段文本中定位单词。事实上,可能确实大部分的正则表达式都是用来进行搜索。但是这不是正则表达式可以做的所有事情——正则表达式还可以用来执行替换操作。举个例子,将 CA 替换成California和将MI替换成Michigan 并不是正则表达式需要完成的工作。尽管使用正则表达式也是合法的,但是没有必要这么做。事实上,在这里如果使用简单的字符串操作函数的话过程将会变得更加容易。

正则表达式只有在使用后向引用的时候才变得有竞争性。下面是在第五章中使用过的例子:

<script>
    var t = document.getElementsByTagName("DIV");
    var myRe = /\w+[\w\.]*@[\w\.]+\.\w+/g;
    var str = "Hello, ben@forta.com is my email address.";
    var myArray;
    while ((myArray = myRe.exec(str)) !== null) {
        var msg = "Found " + myArray[0] + ".  ";
        msg += "Next match starts at " + myRe.lastIndex;
        console.log(msg);
    }
    /*
Found ben@forta.com.  Next match starts at 20 
    */
</script>
</HTML>

分析

这个模式匹配了一段文本中的邮件地址(已经在第五章中进行了解释)。

 

但是如果你现在希望将所有的邮件地址修改为可点击的该怎么做?在 HTML 语言中可以使用“<A HREF="mailto:user@address.com">user@address.com</A >” 来创建一个可点击的邮件地址。是否可以通过正则表达式改变为可点击的邮件地址?事实上,是而且非常简单(当你知道如何使用后向引用的时候)。

<script>
    var t = document.getElementsByTagName("DIV");var str = "Hello, ben@forta.com is my email address.";
    var replaceStr = str.replace(/(\w+[\w\.]*@[\w\.]+\.\w+)/,"<A HREF=\"mailto:$1\">$1</A >");
    console.log(replaceStr);
    /*
Hello, <A HREF="mailto:ben@forta.com">ben@forta.com</A > is my email address.  
    */
</script>
</HTML>

分析

在替换操作中,将使用两个正则表达式:其中一个指定搜索模式,第二个指定需要匹配的文本。后向引用可能会跨越模式,所以在第一个模式中匹配的子表达式将会用于第二个模式中。“(\w+[\w\.]*@[\w\.]+\.\w+)”和前面的模式是一样的(定位邮件地址),但是这里指定了一个子表达式。这样匹配的文本将可以用于替换模式。“<A HREF="mailto:$1">$1</A > ” 使用了匹配的子表达式两次——第一个用为 HREF 的属性(定义 mailto: ),后面则定义了可点击文本。所以,“ ben@forta.com ”将变为“<A HREF=" mailto:ben@forta.com">ben@forta.com</A >”,这也是我们所需要的。

 

注意:前面已经提到过,你可能需要根据实现来修改后向引用。在这里,JavaScript用户将使用 $ 来替代前面的 \ 。而对于ColdFusion 用户则可以使用 \ 来执行搜索和替换操作。

提示:就像在这个例子中看到的,一个子表达式可以通过后向引用根据需要简单的引用多次。

让我们再来看一个例子。用户信息保存在数据库中,其中电话号码使用“313-555-1234”的格式保存。现在,需要重新格式化电话号码为“(313) 555-1234”。下面是这个例子:

<script>
    var t = document.getElementsByTagName("DIV");
    var str = "313-555-1234\n" +
                        "248-555-9999\n" +
                        "810-555-9000";
    var replaceStr = str.replace(/(\d{3})(-)(\d{3})(-)(\d{4})/g,"($1) $3-$5");
    console.log(replaceStr);
    /*
(313) 555-1234
(248) 555-9999
(810) 555-9000   
    */
</script>
</HTML>

分析

同样的,这里使用了两个正则表达式模式。第一个看起来很复杂,实际上是比较简单的。“(\d{3})(-)(\d{3})(-)(\d{4})”匹配了一个电话号码,并分成了五个子表达式(五个部分)。“(\d{3})”匹配刚开始的三个数字并作为第一个子表达式,“ (-) ”匹配“ - ”并作为第二个子表达式,依此类推。最后的结果是将此电话号码分为了五个部分(每个部分都是一个子表达式):区域码,连字符,号码前三个数字,连字符,号码后四个数字。这四个部分可以根据需要单独引用,所以“($1) $3-$5”只是使用了其中的三个子表达式,而忽略了另外的两个,因此“313-555-1234”改变为了“(313) 555-1234”。

 

提示:在对文本重新格式化的时候,一般来说会将文本变为许多小的子表达式,这可以更好地控制文本。

 

改变大小写

有些正则表达式实现还支持通过表 8.1 中的元字符来改变大小写。

 

元字符

描述

\E

终止\L 或者 \U 的转换

\l

将接下来的字符改为小写

\L

将接下来的所有字符改为小写直到遇到 \E

\u

将接下来的字符改为大写

\U

将接下来的所有字符改为大写直到遇到 \E

表8.1. 改变大小写的元字符

 

\l 和 \u 放置在字符或者子表达式之前用来转换接下来的字符大小写。 \L 和 \U 用来转会接下来的所有字符大小写直到遇到 \E 。下面是一个简单的例子,将 <H1> 标签对中的文字改为大写:

 //以下内容不知道在javascript中如何使用,如果有人知道请赐教

文本
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
<H2>This is not valid HTML</H3>
</BODY>

正则表达式
(<[Hh]1>)(.*?)(</[Hh]1>)

替换
$1\U$2\E$3

结果
<BODY>
<H1>WELCOME TO MY HOMEPAGE</H1>
Content is divided into two sections:<BR>
<H2>ColdFusion</H2>
Information about Macromedia ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth, 802.11, and more.
<H2>This is not valid HTML</H3>
</BODY>

分析

此模式“(<[Hh]1>)(.*?)(</[Hh]1>)”将段落标签内容分解成三个部分:开始标签、文本和结束标签。第二个模式中将这些内容放在了一起: $1 包含了开始标签,“ \U$2\E”转换了第二个子表达式到其大写形式, $3 中包含了结束标签。

 

 

//以上内容不知道在javascript中如何使用,如果有人知道请赐教

 

javascript中如何使用上面的元字符,实在不明白,下面是另一种解决方法:

<script>
    var t = document.getElementsByTagName("DIV");
    var str = "<BODY>\n" + "<h1>Welcome to my Homepage</h1>\n"
            + "Content is divided into two sections:<BR>\n"
            + "<h2>ColdFusion</h2>\n"
            + "Information about Macromedia ColdFusion.\n"
            + "<h2>Wireless</h2>\n"
            + "Information about Bluetooth, 802.11, and more.\n"
            + "<h2>This is not valid HTML</h3>\n" + "</BODY>";
    var replaceStr = str.replace(/(<[Hh][1-6]>)(.*?)(<\/[Hh][1-6]>)/g,
        function($1,$2,$3,$4) { 
            //如果实在不明白为什么要这样做,可以注掉下面的测试代码
            /*
            console.log("========");
            console.log($1);
            console.log($2);
            console.log($3);
            console.log($4);
            console.log("============") 
            */
            return $2+$3.toUpperCase()+$4;
        }
    );
    console.log(replaceStr);
    /*
<BODY>
<h1>WELCOME TO MY HOMEPAGE</h1>
Content is divided into two sections:<BR>
<h2>COLDFUSION</h2>
Information about Macromedia ColdFusion.
<h2>WIRELESS</h2>
Information about Bluetooth, 802.11, and more.
<h2>THIS IS NOT VALID HTML</h3>
</BODY> 
     */
</script>
</HTML>

加g和不加g的区别

<HTML>
<BODY>
    <DIV>
        <H1>Welcome to my Homepage</H1>
        Content is divided into two sections: <BR>
        <H2>ColdFusion</H2>
        Information about Macromedia ColdFusion.
        <H2>Wireless</H2>
        Information about Bluetooth, 802.11, and more.
    </DIV>
</BODY>
<script>
    var t = document.getElementsByTagName("DIV");
    var myRe = /<[hH]1>.*<\/[hH]1>/;//这里没有g
    var str = t[0].innerHTML;
    var myArray;
    while ((myArray = myRe.exec(str)) !== null) {
        var msg = "Found " + myArray[0] + ".  ";
        msg += "Next match starts at " + myRe.lastIndex;
        console.log(msg);
    }
</script>
</HTML>

运行结果:

 

 

 

 

 

 

转载于:https://www.cnblogs.com/ghgyj/p/3315884.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值