正規表示式 詳細講解平衡組

2021-06-09 03:28:25 字數 4927 閱讀 5038

要讀懂這篇文章的精髓,你最好要有一點正則匹配原理的基礎。比如".*?"匹配文字內容"asp163",稍懂正規表示式的人都知道可以匹配,但是你知道他的匹配過程嗎?如果你不太清楚,那麼下面的內容,對你來說可能不太適合,或許,看的太吃力且無法領悟平衡組的用法。因此,我建議你先了解正規表示式nfa引擎的匹配原理。想要整理乙份易懂易描述的話,的確要費些時間,但不知道這篇內容會不會達到我預期的效果。慢慢完善吧~(注:這是我2023年寫的,現在拿過來,有時間將自己做為讀者來看本篇文章,修改有問題的地方,並增加些例項,盡量做到通俗易懂。)

如果想要匹配可巢狀的層次性結構的話,就得使用平衡組了。舉個例子吧,如何把「xx aa> yy」這樣的字串裡,最長的尖括號內的內容捕獲出來?

這裡需要用到以下的語法構造:

(?)把捕獲的內容命名為group,並壓入堆疊

(?<-group>)從堆疊上彈出最後壓入堆疊的名為group的捕獲內容,如果堆疊本來為空,則本分組的匹配失敗

(?(group)yes|no)如果堆疊上存在以名為group的捕獲內容的話,繼續匹配yes部分的表示式,否則繼續匹配no部分

(?!)順序否定環視,由於沒有字尾表示式,試圖匹配總是失敗

如果你不是乙個程式設計師(或者你是乙個對堆疊的概念不熟的程式設計師),你就這樣理解上面的三種語法吧:第乙個就是在黑板上寫乙個(或再寫乙個)"group",第二個就是從黑板上擦掉乙個"group",第三個就是看黑板上寫的還有沒有"group",如果有就繼續匹配yes部分,否則就匹配no部分。

我們需要做的是每碰到了左括號,就在黑板上寫乙個"group",每碰到乙個右括號,就擦掉乙個,到了最後就看看黑板上還有沒有-如果有那就證明左括號比右括號多,那匹配就應該失敗(為了能看得更清楚一點,我用了(?'group')的語法): 

<                 #最外層的左括號

[^<>]* #最外層的左括號後面的不是括號的內容

( (

(?'open'<) #碰到了左括號,在黑板上寫乙個"open"

[^<>>]* #匹配左括號後面的不是括號的內容)+(

(?'-open'>) #碰到了右括號,擦掉乙個"open"

[^<>]* #匹配右括號後面不是括號的內容

)+)*

(?(open)(?!)) #在遇到最外層的右括號前面,判斷黑板上還有沒有沒擦掉的"open";如果有,則匹配失敗

> #最外層的右括號

看了上面的介紹,你明白了嗎?在我未理解正規表示式匹配原理之前,看上面對於平衡組的介紹,似懂非懂,且只能當做模板記住,而不能靈活運用。因此查閱大量有關正則方面的資料,這裡尤其感謝lxcnn的技術文件及《精通正規表示式》這本書,讓我對正規表示式有了更深入、更系統的理解,因此,在它們的基礎之上,我就結合自己的學習經歷做個小結,一來做為學習筆記存檔,另外,如果能解決你的疑惑,也是件讓人高興的事。

我先暫不分析上面的**,先講解一下關於平衡組相關的概念及知識。

平衡組,故名思義,平衡即對稱,主要是結合幾種正則語法規則,提供對配對出現的巢狀結構的匹配。平衡組有狹義與廣義兩種定義,狹義平衡組指(?expression)語法,而廣義平衡組並不是固定的語法規則,而是幾種語法規則的綜合運用,我們平時所說的平衡組通常指的是廣義平衡組。本文中如無特殊說明,平衡組這種簡寫指的是廣義平衡組。

平衡組的匹配原理可以用堆疊來解釋,先舉個例子,再根據例子進行解釋。

源字串:a+(b*(c+d))/e+f-(g/(h-i))*j

正規表示式:\(((?\()|(?<-open>\))|[^()])*(?(open)(?!))\)

需求說明:匹配成對出現的()中的內容

輸出:(b*(c+d)) 和 (g/(h-i))

我將上面正規表示式**分行寫,並加上注釋,這樣看起來有層次,而且方便 

\(               #普通字元「(」

( #分組構造,用來限定量詞「*」修飾範圍

(?\() #命名捕獲組,遇到開括弧「open」計數加1

| #分支結構

(?<-open>\)) #狹義平衡組,遇到閉括弧「open」計數減1

| #分支結構

[^()]+ #非括弧的其它任意字元

)* #以上子串出現0次或任意多次

(?(open)(?!)) #判斷是否還有「open」,有則說明不配對,什麼都不匹配

\) #普通閉括弧

對於乙個巢狀結構而言,開始和結束標記都是確定的,對於本例開始為「(」,結束為「)」,那麼接下來就是考察中間的結構,中間的字元可以劃分為三類,一類是「(」,一類是「)」,其餘的就是除這兩個字元以外的任意字元。

1、先找到第乙個「(」,作為匹配的開始。即上面的第1行,匹配了:a+(b*(c+d))/e+f-(g/(h-i))*j(紅色顯示部分)

2、在第1步以後,每匹配到乙個「(」,就入棧乙個open捕獲組,計數加1

3、在第1步以後,每匹配到乙個「)」,就出棧最近入棧的open捕獲組,計數減1

也就是講,上面的第一行正則「\(」匹配了:a+(b*(c+d))/e+f-(g/(h-i))*j(紅色顯示部分)

然後,匹配到c前面的「(」,此時,計數加1;繼續匹配,匹配到d後面的「)」,計算減1;——注意嘍:此時堆疊中的計數是0,正則還是會向前繼續匹配的,但是,如果匹配到「)」的話,比如,這個例子中d))(紅色顯示的括號)——引擎此時將控制權交給(?(open)(?!)),判斷堆疊中是否為0,如果為0,則執行匹配「no」分支,由於這個條件判斷結構中沒有「no」分支,所以什麼都不做,把控制權交給接下來的「\)

這個正規表示式「\)」可匹配接下來的),即b))(紅色顯示的括號)

4、後面的(?(open)(?!))用來保證堆疊中open捕獲組計數是否為0,也就是「(」和「)」是配對出現的

5、最後的「)」,作為匹配的結束

首先匹配第乙個「(」,然後一直匹配,直到出現以下兩種情況之一時,把控制權交給(?(open)(?!))

a)堆疊中open計數已為0,此時再遇到「)

b)匹配到字串結束符

這時控制權交給(?(open)(?!)),判斷open是否有匹配,由於此時計數為0,沒有匹配,那麼就匹配「no」分支,由於這個條件判斷結構中沒有「no」分支,所以什麼都不做,把控制權交給接下來的「\)」

如果上面遇到的是情況a),那麼此時「\)」可以匹配接下來的「)」,匹配成功;

如果上面遇到的是情況b),那麼此時會進行回溯,直到「\)」匹配成功為止,否則報告整個表示式匹配失敗。

由於.net中的狹義平衡組「(?expression)」結構,可以動態的對堆疊中捕獲組進行計數,匹配到乙個開始標記,入棧,計數加1,匹配到乙個結束標記,出棧,計數減1,最後再判斷堆疊中是否還有open,有則說明開始和結束標記不配對出現,不匹配,進行回溯或報告匹配失敗;如果沒有,則說明開始和結束標記配對出現,繼續進行後面子表示式的匹配。

需要對「(?!)」進行一下說明,它屬於順序否定環視,完整的語法是「(?!expression)」。由於這裡的「expression」不存在,表示這裡不是乙個位置,所以試圖嘗試匹配總是失敗的,作用就是在open不配對出現時,報告匹配失敗。

snhame

f

以上為部分的html**.現在我們的問題是要提取出其的標籤並將其刪除掉,以往我們慣用的方法都是直接去取,像[\s\s]+?\,不過問題出來了,我們提取到的不是我們想要的內容,而是

snhame
原因也很簡單,它和離他最近的標籤匹配上了,不過它不知道這個標籤不是它的-_-,是不是就是?符號的原因呢,我們去掉讓他無限制貪婪,可這下問題更大了,什麼亂七八糟的東東它都匹配到了

snhame

f

這個結果也不是我們想要的。那麼我就用「平衡組」來解決吧。

]*>((?]*>)+|(?<-mm>)|[\s\s])*?(?(mm)(?!))
匹配的結果是

snhame

f

這正是我們想要的

注意,我開始寫成這樣的方式

]*>((?]*>)+|(?<-mm>)|[\s\s])*(?(mm)(?!))
匹配的結果是

snhame

f

以下**只是做為乙個問題**

文字內容:e+f(-(g/(h-i))*j

正規表示式:

\(

( (?\()

|(?<-mm>\))|.

)*?(?(mm)(?!))

\)

匹配的結果是:(-(g/(h-i))

正規表示式詳細講解

什麼是正規表示式?正規表示式通常被用來檢索 替換那些符合某個規則的文字,使用一系列特殊字元模式,來表示某一類字串。元字元 元字元含義 將下乙個字元標記符 或乙個向後引用 或乙個八進位制轉義符 匹配輸入字行首 匹配輸入行尾 匹配前面的子表示式任意次 匹配前面的子表示式一次或多次 大於等於1次 匹配前面...

詳細講解 正規表示式

正規表示式,又稱規則表示式。英語 regular expression,在 中常簡寫為regex regexp或re 電腦科學的乙個概念。正規表示式通常被用來檢索 替換那些符合某個模式 規則 的文字。許多程式語言都支援利用正規表示式進行字串操作。例如,在perl中就內建了乙個功能強大的正規表示式引擎...

正規表示式之分組捕獲 條件表示式 平衡組

其實我是想說平衡組,但我並是想寫一篇給零基礎的朋友們閱讀,我希望你是對正則有所了解,對平衡組不太了解的朋友閱讀。如果已經了解,看看也無妨。多一點思路。分組捕獲 就是用括號把要匹配的內容擴起來 命名分組捕獲 或 name 就是在分組捕獲的基礎上,增加?分組名 或?分組名 為了鞏固印象,舉個例子 axa...