美文网首页
正则表达式(二)

正则表达式(二)

作者: IsaacHHH | 来源:发表于2018-03-22 14:31 被阅读13次

    我们想象一下这样的场景:需要用正则表达式去标记要匹配的文本位置,而不仅仅是去查找文本本身。这就需要我们正则中的另一个知识:前后查找。

    从需求中去理解"前后查找"

    所谓的前后查找,在进步一用大白话来说就是:我要查找某个位置的前面(或后面)的内容。比如:https://baidu.com,我们需要查找:前面的,也就是https这几个字。

    或许对于上面这个例子,不用前后查找也能实现。比如用"文本匹配"、子表达式以及利用开始^、结束$符都可以。但是很显然是比较耗时、繁琐的。比如下面这个例子:

    <body>
      <h1>我是H1标签</h1>
    </boy>
    

    对于上面的例子中,我们想要获取到标题标签里的内容, 注意:仅仅是要获取内容,而不获取标签本身。

    或许我们可以这么写:
    <h1>.*<\/h1>
    或许再完善一些,可以这么写:
    <[hH]([1-6])>.*<\/[hH]\1>
    我们会发现,这样虽然可以获取到标题标签和内容,但是我们不想要<h1>——<h6>这些元素标签,我们只需要标签里的内容。虽然我们可以在进一步对返回的匹配结果进行处理,但是相对于前后查找显然是比较繁琐的。

    有了实际场景需求后,可以去尝试用前后查找的方法去解决。


    前后查找实际应用

    前后前后,肯定会有前有后,也就是会有向前查找向后查找

    向前查找

    基本语法:?=

    先来看几个例子

    1. 找出URL中的使用的是什么协议。
    let str1 = 'https://baidu.com';
    let str2 = 'http://qq.com';
    let str3 = 'ftp://taobao.com';
    
    // 正则表达式
    let regexp = /.+(?=:)/;
    str1.match(regexp); // https
    str2.match(regexp); // http
    str3.match(regexp); // ftp
    

    这里,我们在js中使用match方法去找出协议名称,可以很容易的匹配到。

    对于这段正则/.+(?=:), 其中?=正是向前查找的语法。对于使用()包裹起来的是字表达时,这个我们应该了解,也就是说,在子表达式中, 我们通过向前查找的语法?=告诉正则, 我们只匹配:前面的内容。

    用一些专业术语来说, 我们这里?=:的意思是:只要找到:就行,但是不消费它,并且返回:前面的内容。

    如果我们不用向后查找?=试一下:
    正则:.+:
    使用上面的正则运行一下你会发现,冒号:也被包含在匹配结果里了,显然,这不是我们想要的。

    向后查找

    基本语法: ?<=

    我们发现,向后查找相较于向前查找,在语法上,知识少了一个箭头:<。在概念上,理解了前面的向前查找,向后查应该也会理解。

    所谓的向后查找,就是查找某个字符位置的后面内容。

    先来看几个例子

    还是拿前面的向查找的的匹配URL协议的例子。只不过我们现在把需求改一下,改成获取URL地址,但是不包含协议头和冒号

    修改后的正则: (?<=:).+
    这样,我们就会获取到协议后面的网址。也就是说会获取:后面的内容(虽然带上了两个斜线//)

    当然,这里我们可以直接调用JS中的location对象中的host(或者hostname)属性,包括协议名称也可以拿到,但是我们这里说的是正则😁。


    解决问题(前后查找同时用)

    前面通过两个简单的例子,我们理解了什么是向后查找和向前查找。前后查找就是匹配一个标识的前后内容,可以将这个标识理解成界定符(或分隔符), 但是不消费这个界定符。

    理解了之后,我们开始着手解决刚开始我们遇到的场景:去找出标题标签中的内容。
    在开始之前,我们要知道,前后查找可以同时用,也就是说向后查找和向前查找可以同时用
    而这里我们要查找出来标签里的文本内容,换句话说,就是要查找<h1></h1>之间的内容。
    再进一步理解,就是我们要<h1>进行向后查找,对<h1/>进行向前查找

    文本:

    <body>
      <h1>我是H1</h1>
    </body>
    

    正则:(?<=<h1>).*(?=<\/h1>)

    这里我们通过两个子表达式来解决了前面的问题,两个子表达式就像两个定界符,分别在两边卡住了我们需要的内容。解决思路前面我们也已经解释了,就是前后查找同时用。
    当然,这个正则有一些局限性,只能匹配<h1>标签,我们可以进一步完善:

    完善后的正则:
    (?<=[hH]([1-6])>).*(?=<\/[hH]\1>)
    这里我们可以成功匹配<h1><h6>标签之间的内容,并且不论标签是否大小写,还有一点就是:利用了回溯引用,前后标签不匹配的我们会忽略。关于回溯引用,我的另外一篇文章中有写到。


    负向前(后)查找

    这里所说的负向前查找负向后查找,和前面我们所说的向前查找和向后查找是相对的。通俗点说,这里的就是取非的意思,也就是对前后查找取非。

    这里的取非,和我们正则中的另外一个取非字符^不同,这里的取非,并没有使用^字符。

    负向前查找

    负向前查找会向前查找与给定模式不匹配的内容。

    基本语法: ?!

    我们来看一个例子

    123(?!com) //匹配后面不带com的123
    可以匹配: 123cn
    不能匹配:123com
    

    负向后查找

    负向后查找会向后查找与给定模式不匹配的内容

    基本语法:?<!

    (?<!com)123 // 匹配前面不带com的123
    可以匹配: abc123
    不可匹配: com123
    

    我们来看一个例子

    文本:

    I paid $30 for 100 apples,
    50 pranges, and 60 pears,
    I saved $5 on this order.
    

    我们需要做的是:找出价格,但是不包含$符号。这里我们使用向后查找
    正则(?<=\$)\d+
    这样,我们会查找出价格,在这里就是305

    如果我们要查找出来数量,这里就要使用到负向后查找
    正则\b(?<!\$)\d+
    我们此时会返回1005060

    另外我们这里使用\b元字符来定义了单词边界,是为了防止出现查找到的内容包含一些非单词的内容。你可以尝试下去掉\b元字符再去匹配下,会发现返回的结果中包含了$30中的0,因为这个0完全符合规则,因为0前面不是$。

    当然了,如果不使用负向前查找,也可以使用[^$]\d+,但是需要注意的是,返回的每个结果前面会包含一个空格。


    总结

    这里的前后查找又称零宽断言
    有四种基本的前后查找操作符:

    • ?=exp: 向前查找, 找出exp表达式位置前面的内容
    • ?!exp: 负向前查找,找出内容后面不等于exp表达式的内容
    • ?<=exp: 向后查找,找出exp表达式位置后面的内容
    • ?<!exp: 负向后查找,找出内容前面不等于exp的内容

    所谓的前后查找,就是将我们充当定界符的东西放在=后面,这样就不会消费这个定界符,返回的结果只是这个界定符前面(或后面)的内容。

    同时,我们可以将前后查找组合使用。

    相关文章

      网友评论

          本文标题:正则表达式(二)

          本文链接:https://www.haomeiwen.com/subject/rxtyqftx.html