【正則表達式系列】零寬斷言

前言

正則表達式中,有一個繞不過去的坎,那就是零寬斷言css

零寬斷言是一種零寬度的匹配,它匹配的內容不會保存到匹配結果中,也不會佔用index寬度,最終匹配的結果只是一個位置html

簡單的說,它用於查找在某些內容以前或以後的東西(但返回結果並不包括這些內容)正則表達式

JavaScript中只支持零寬先行斷言工具

簡介

零寬斷言分爲4學習

  • 正向零寬先行斷言(?=exp測試

    • 目標字符出現的位置的右邊必須匹配到exp這個表達式
  • 負向零寬先行斷言(?!exp.net

    • 目標字符出現的位置的右邊不能匹配到exp這個表達式
  • 正向零寬後發斷言(?<=expcode

    • 目標字符出現的位置的左邊必須匹配到exp這個表達式
  • 負向零寬後發斷言(?<!expregexp

    • 目標字符出現的位置的左邊不能匹配到exp這個表達式

注,關於先行後發,還有其它稱呼,譬如前瞻後瞻等,本文統一使用先行後發htm

JavaScript中的斷言

JavaScript語言內只支持零寬先行斷言(即只支持?=exp?!exp

因此本文中只會介紹零寬先行斷言

另外,能夠經過RegexBuddy 4等工具分析正則的匹配過程

大綱

  • 正向零寬先行斷言
  • 負向零寬先行斷言
  • 實戰演練

正向零寬先行斷言

示例1

var str="abcdefg";
var reg=/ab(?=cd)/;
str.match(reg); // ["ab", index: 0, input: "abcdefg"]
  • index = 0a匹配a成功,嘗試b匹配b成功
  • 繼續嘗試匹配,(?=cd)接管控制權
  • 接下來(?=cd)依次嘗試匹配cd成功
  • 此時全部的正則表達式匹配完畢,匹配成功,返回成功結果

示例2

var str="abcdefg";
var reg=/(?=cd)efg/;
str.match(reg); // null

想要達到的效果是匹配在cd後方的efg,可是這是零寬後發斷言纔有的效果(?<=exp),而JS中並不支持,此時使用先行斷言,實際效果爲

  • ?=cd獲取控制權,一直到index = 2時才匹配成功,接下來e獲取控制權
  • 惋惜因爲?=cd是零寬式的,所以匹配成功後,下一輪匹配依然從index = 2開始嘗試,此時c匹配e失敗,因而index挪到3
  • 接下來又是d, e, f, g匹配?=cd失敗,因而最終匹配失敗,返回null

示例3

var str="abcdefg";
var reg=/(?=cd)cdefg/;
str.match(reg); // ["cdefg", index: 2, input: "abcdefg"]

基於示例2的變形

  • index = 2時,?=cd匹配成功了,交給cdefg
  • index = 2,此時恰好c匹配,繼續吃進d,e,f,g也都匹配,因而匹配成功,因而返回成功結果

示例4

var str="abcdefg";
var reg=/ab(?=cd)cdefg/;
str.match(reg); // ["abcdefg", index: 0, input: "abcdefg"]

上述示例的綜合

  • index = 0時,左側的ab匹配成功
  • 繼續日後嘗試(index = 2處),?=cd也匹配成功
  • 接下來繼續從index = 2開始嘗試,c, d, e ,f , g依次匹配成功,因而匹配結束,返回成功結果(index = 0,由於沒有失敗,後續的嘗試都成功了)

注,零寬斷言返回的是位置而不是字符,零寬斷言匹配成功後,其他表達式會基於這個返回的位置繼續判斷

另外,請不要把先行斷言當成後發斷言來用

負向零寬先行斷言

示例1

var str="abcdefg";
var reg=/ab(?!cd)/;
str.match(reg); // null
  • ab匹配成功後,接下來cd匹配?!cd失敗
  • 接下來的b, c, d, e, f, g依次都匹配a失敗,因而最終匹配失敗,返回null

示例2

var str="abcdefg";
var reg=/ab(?!ab)cd/;
str.match(reg); // ["abcd", index: 0, input: "abcdefg"]
  • ab匹配成功後,接下來cd匹配?!ab成功
  • 因爲?!ab是零寬的,所以接下來仍然從index = 2處嘗試(也就是c繼續匹配c),所以匹配成功,接下來d也匹配d成功,全部表達式匹配完畢,最終返回成功結果(index = 0,由於沒有失敗)

實戰演練

接下來一些實戰練習,加深印象

找到文本中的全部ing單詞的前綴

需求說明

例如: I am reading in the dining room的匹配結果應該是readdin

代碼

var str="I am reading in the dining room";
var reg=/\w+(?=ing)/g;
str.match(reg); //  ["read", "din"]

說明

  • \w+匹配至少一個以上的單詞
  • ?=ing表明右側必須有ing,可是匹配的結果又不包含ing
  • g是全局匹配
  • 所以符合了需求,成功匹配

測試一個文件是不是.css後綴,但又不能是.min.css

需求說明

這道題曾屢次出如今各大平臺,基本都是依靠零寬斷言來檢測,例如:

test('a.min.css'); // false
test('b.css'); // true
test('c.mining.css'); // true

代碼

var reg=/^(?!.*\.min\.css$).+\.css$/;

reg.test('a.min.css'); // false
reg.test('.min.css'); // false
reg.test('.css'); // false
reg.test('min.css'); // true
reg.test('b.css'); // true
reg.test('c.mining.css'); // true

說明

因爲只考慮單個文件名的匹配,因此較簡單

  • 先用?!.*\.min\.css負向先行斷言試探文件名。這一步匹配完後,直接就排除了xxx.min.css了(因爲是*,因此.min.css也會匹配失敗)
  • 而後繼續用\w+.*\.css匹配xxx.css這種狀況
  • 因而就符合了要求,只匹配.css但不匹配.min.css
  • 另外,關於回溯步驟,因爲較多,這裏就不贅述了,感興趣的能夠用RegexBuddy等工具自行檢測

找到全部的.min.css文件的文件名

需求說明

例如: a.min.css;.min.css;.css;min.css;b.css;c.min.js;d.css;e.a.min.css(文件以;隔開)的匹配結果應該是ae.a

代碼

var str="a.min.css;.min.css;.css;min.css;b.css;c.min.js;d.css;e.a.min.css";
var reg=/\w+[^;]*(?=\.min\.css)/g;
str.match(reg); //  ["a", "e.a"]

說明

這類型表達式回溯次數不少,實際中能夠有更好的解決方案,好比先分割,再匹配

  • 左側的表達式\w+[ ^;]*確保了必須是一個正常的單詞開頭,而且不能包括;,因此直接排除了名字以.開頭或名字中包含;的狀況
  • 右側的零寬斷言?=\.min\.css確保名字右側必須有.min.css
  • 因而最終只剩下了ae.a符合狀況

找到全部.css文件的文件名,須要排除.min.css

需求說明

這道題基於上兩題的綜合與變形,增長了點難度(再也不是單個文件名匹配,而是字符串中的文件名提取)

例如: a.min.css;.min.css;.css;min.css;b.css;c.min.js;d.css;e.a.min.css;f.min.a.css(文件以;分割)的匹配結果應該是minbdf.min.a

代碼

var str = "a.min.css;.min.css;.css;min.css;b.css;c.min.js;d.css;e.a.min.css;f.min.a.css"; 
var reg1 = /[^;]+(?=\.css)/g;

var match1 = str.match(reg1); 

var reg2 = /\.min$/;
var match2 = [];

match1 && match1.map(function(item, index) {
    !reg2.test(item) && match2.push(item);
});

console.log(match1); // ["a.min", ".min", "min", "b", "d", "e.a.min", "f.min.a"]
console.log(match2); // ["min", "b", "d", "f.min.a"]

說明

好吧,我認可沒法只靠一個表達式實現這個功能(不知道在座的各位有誰能夠的...)

  • 第一步[ ^;]+(?=\.css)先匹配全部的.css後綴的名字
  • 第二步\.min$剔除以.min結尾的名字
  • 因而就只剩下了正常的結果
  • 或者先分割,而後再匹配(是.css但非.min.css)也行

PS:原本準備一步解決做爲壓軸的,可是嘗試了好久都未果,最終仍是拆分來實現的,之因此仍然放在最後,也算是給本身一個警醒

寫在最後的話

深刻研究後,才發現精通正則表達式真的很難,不少時候,你認爲的已經精通了只是一種假象。

所以,仍是放下身段,努力學習吧!

附錄

博客

初次發佈2017.07.26於我的博客

http://www.dailichun.com/2017/07/26/regularExpressionZeroWidthAssertion.html

參考資料

相關文章
相關標籤/搜索