原文地址 http://www.cnblogs.com/qiantuwuliang/archive/2011/06/11/2078329.html
概述
正則表達式是 做文本解析工作必不可少的技能。如Web服務器日志分析,網頁前端開發等。很多高級文本編輯器都支持正則表達式的一個子集,熟練掌握正則表達式,經常能夠 使你的一些工作事半功倍。例如統計代碼行數,只需一個正則就搞定。嵌套Html標簽的匹配是正則表達式應用中一個比較難的話題,因為它涉及到的正則語法比 較多,也比較難。因此也就更有研究的價值。
思路
任何復雜的正則表達式都是由簡單的子表達 式組成的,要想寫出復雜的正則來,一方面需要有化繁為簡的功底,另外一方面,我們需要從正則引擎的角度去思考問題。關於正則引擎的原理,推薦 《Mastering Regular Expression》中文名叫《精通正則表達式》。挺不錯的一本書。
OK,先確定我們要解決的問題——從一段Html文本中找出特定id的標簽的innerHTML。
這里面最大的難點就是,Html標簽是支持嵌套的,怎么能夠找到指定標簽相對應的閉合標簽呢?
我們可以這樣想,先匹配最前面的起始標簽,假設是div吧(<div),然后一旦遇到嵌套div,就“壓入堆棧”,然后一遇到div結束標簽了,就“彈出堆棧”。如果遇到結束標簽的時候,堆棧里面已經沒有東西了,那么匹配結束,此結束標簽為正確的閉合標簽。
我之所以能夠這樣去思考,是因為我了解過正則的特性,我知道正則中的平衡組能夠實現我剛才說的“堆棧”操作。所以,如果我們要編寫復雜正則表達式,需要對正則的一些高級特性至少有所了解,這樣我們思考問題才有個方向。
實現
這里假設我們要匹配的文本是一段合法的Html文本。下面這段Html代碼是從我的博客上拷貝下來的,作為我們的測試文本。我們要匹配的就是footer這個div的innerHTML,同時把標簽名也捕獲下來。
< a id ="gotop" href ="#" onclick ="MGJS.goTop();return false;" > Top </ a >
< a id ="powered" href ="http://wordpress.org/" > WordPress </ a >
< div id ="copyright" >
Copyright © 2009 簡單生活 —— Kevin Yang的博客 </ div >
< div id ="themeinfo" >
Theme by < a href ="http://www.neoease.com/" > mg12 </ a > .
Valid < a href ="http://validator.w3.org/check?uri=referer" > XHTML 1.1 </ a >
and < a href ="http://jigsaw.w3.org/css-validator/" > CSS 3 </ a > .
</ div >
</ div >
這里我們需要借助Expresso工具來構建和測試編寫的正則表達式。
匹配起始標簽
起始標簽特征很好提取,以尖括號打頭,然后跟着一連串英文字母,然后一大串屬性中(非尖括號字符)匹配id(不區分大小寫)=footer。需要注意的是,footer可以被雙引號或者單引號包裹,也可以什么都不加。正則如下:
上面的正則表達式需要做幾點說明:
1. <尖括號在正則中算是一個特殊字符,在顯式捕獲分組中用它將分組名括起來。但是因為開頭的尖括號在此上下文下並不會出現解析歧義,因此加不加轉義符效果是一樣的。
2. (?<GroupName>RegEx)格式定義一個命名分組,我們在上面定義了一個HtmlTag的標簽分組,用來存放匹配到的Html標簽名。Quote分組是用來給后面的匹配使用的。
3. (?(GroupName)Then|Else)是條件語句,表示當捕獲到GroupName分組時執行Then匹配,否則執行Else匹配。上面的正則 中,我們先嘗試匹配footer字符串左邊的引號,並將其存入LeftQuote分組中,然后在footer右側進行條件解析,如果之前匹配到 LeftQuote分組,那么右側也應該批評LeftQuote分組。這樣一來,我們就能精確匹配id的各種情況了。
匹配閉合標簽
在成功匹配到起始標簽之后,后面的Html文本可以分為三種情況:
A. 匹配到嵌套div起始標簽<div,這個時候,需要將其捕獲到Nested分組。
B. 匹配到嵌套div起始標簽的閉合標簽,這個時候,需要將之前的Nested分組釋放
C. 其他任意文本。注意,需要使用.*?方式關閉貪婪匹配,否則最后的閉合標簽可能會過度匹配
使 用(RegEx1|RegEx2|RegEx3)*這種方式,可以將幾個條件以或的形式組合起來,然后再取若干次匹配結果,最終再匹配閉合標簽。其中 (?<-Nested>)是表示釋放之前捕獲的Nested分組。確切的語法是(?<N-M>)即使用N分組替換掉M分組,如果 N分組沒有指定或不存在,則釋放M分組。
update:前面過於側重分析了,最后沒有給出一個完整的正則真是抱歉。
上面這個正則能夠匹配任意id=footer的html標簽。
需要注意,此正則表達式需要設置SingleLine=true,這樣點號才可以把換行符也匹配進去。
對於domoxz 的問題,如果要匹配p標簽,那么只需將上述的正則中的HtmlTag替換成p即可。
另加一個 實戰需求: 如果想得到 匹配任意id=footer的html標簽 的 innerHTML,則正則改成如下即可:
C#參考代碼:
if (m.Count > 0 )
{
foreach (Match subm in m)
{
this .tp4rtbResult.Text += c.ToString() + " . " + subm.Groups[ 0 ].Value.Replace( " amp; " , "" ) + " \r\n+++++++++++++++++++++++++++++++++\r\n " ;
}
}
else
{
this .tp4rtbResult.Text += " 匹配失敗... " + " \r\n+++++++++++++++++++++++++++++++++\r\n " ;
}