1. 什么是零寬斷言
有時候在使用正則表達式做匹配的時候,我們希望匹配一個字符串,這個字符串的前面或后面需要是特定的內容,但我們又不想要前面或后面的這個特定的內容,這時候就需要零寬斷言的幫助了。所謂零寬斷言,簡單來說就是匹配一個位置,這個位置滿足某個正則,但是不納入匹配結果的,所以叫“零寬”,而且這個位置的前面或后面需要滿足某種正則。
2、不同的零寬斷言
零寬斷言:正向和反向兩類,每類又分為:預測先行和回顧后發;
正預測先行:簡稱正向先行斷言,語法:(?=exp),它斷言此位置的后面能匹配表達式exp,但不包含此位置;
如:a(?=\d),返回匹配字符串中以數字為結尾的a字符。
正回顧后發:簡稱正向后發斷言,語法:(?<=exp),它斷言此位置的前面能匹配表達式exp;
如:(?<=\d)a,返回匹配字符串中以數字為開頭的a字符。
負預測先行:簡稱反向先行斷言,語法:(?!exp),它斷言此位置的后面不能匹配表達式exp;
如:a(?!\d),返回不匹配字符串中以數字結尾的a字符。
負回顧后發:簡稱反向后發斷言,語法:(?<!exp),它斷言此位置的前面不能匹配表達式exp;
如:a(?<!exp)a,返回不匹配字符串中以數字開頭的a字符。
3、零寬斷言的實踐與總結
示例:提取<div>Hello World</div>中Hello World
目標字符串:Hello World
根據以上所說,當我們需要提取字符串的時候,可以用斷言,思路如下:
首先,目標字符串是hello world,那么它可以歸納為 .* ;
其次,目標字符串前面有<div>,既然是前面有,那么根據四種斷言的含義,容易得出用正向后發斷言(?<=exp),將它放在目標字符串前面,得到(?<=<div>).*,進一步可以將div歸納為[a-zA-Z]+,從而得到(?<=<[a-zA-Z]+>).*;
最后,目標字符串后面有</div>,既然是后面有,那么根據四種斷言的含義,容易得出用正向先行斷言(?=exp),將它放在目標字符串后面,從而得到(?<=<[a-zA-Z]+>).*(?=</[a-zA-Z]+>);
進一步的,我們發現前后兩個斷言中都有[a-zA-Z]+,可以使用分組來避免書寫重復的內容:(?<=<([a-zA-Z]+)>).*(?=</\1>),當然也可以使用命名分組,這里就不展開了。
說到這里,我歸納出了幾句書寫斷言的口訣:
前面有,正向后發(?<=exp),放前面;
后面有,正向先行(?=exp),放后面;
前面無,反向后發(?<!exp),放前面;
后面無,反向先行(?!exp),放后面。
請記住,這個前面和后面是針對目標字符串,也就是你要提取出來的字符串而言的。
4、實戰:
4.1、正向先行(?=exp):獲取字符串中以ing結尾的字符:
4.2、正向后發(?<=exp):獲取字符串中以do開頭的單詞后半部分:
4.3、反向先行(?!exp):匹配出字符串中不是以ing結尾的單詞:
此處有雷,如果字符串變成“do run going hing”你再試試看,此處有待解決。
4.4、反向后發(?<!exp):匹配字符串中不以do開頭的單詞:
此處有雷,同上
總結:主要原因是因為反向斷言不支持匹配不定長的表達式;
4.5、正向先行后發斷言結合應用:字符串時ip地址,獲取整數部分