(轉)c# .net 使用正則表達式匹配嵌套Html標簽


原文地址 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,同時把標簽名也捕獲下來。

復制代碼
< div  style ="background-color:gray;"  id ="footer" >
    
< id ="gotop"  href ="#"  onclick ="MGJS.goTop();return false;" > Top </ a >
    
< id ="powered"  href ="http://wordpress.org/" > WordPress </ a >
    
< div  id ="copyright" >
        Copyright 
&copy;  2009 簡單生活 —— Kevin Yang的博客     </ div >
    
< div  id ="themeinfo" >
        Theme by 
< href ="http://www.neoease.com/" > mg12 </ a > .
 Valid 
< href ="http://validator.w3.org/check?uri=referer" > XHTML 1.1 </ a >
        and 
< href ="http://jigsaw.w3.org/css-validator/" > CSS 3 </ a > .
    
</ div >  
</ div >  
復制代碼
 
        

這里我們需要借助Expresso工具來構建和測試編寫的正則表達式。

匹配起始標簽

起始標簽特征很好提取,以尖括號打頭,然后跟着一連串英文字母,然后一大串屬性中(非尖括號字符)匹配id(不區分大小寫)=footer。需要注意的是,footer可以被雙引號或者單引號包裹,也可以什么都不加。正則如下:

< ( ?< HtmlTag > [\w] + )[ ^> ] * \s[iI][dD] = ( ?< Quote > [ " ']?)footer(?(Quote)\k<Quote>)[ "' ]?[^>]*>

上面的正則表達式需要做幾點說明:

1. <尖括號在正則中算是一個特殊字符,在顯式捕獲分組中用它將分組名括起來。但是因為開頭的尖括號在此上下文下並不會出現解析歧義,因此加不加轉義符效果是一樣的。

2. (?<GroupName>RegEx)格式定義一個命名分組,我們在上面定義了一個HtmlTag的標簽分組,用來存放匹配到的Html標簽名。Quote分組是用來給后面的匹配使用的。

3. (?(GroupName)Then|Else)是條件語句,表示當捕獲到GroupName分組時執行Then匹配,否則執行Else匹配。上面的正則 中,我們先嘗試匹配footer字符串左邊的引號,並將其存入LeftQuote分組中,然后在footer右側進行條件解析,如果之前匹配到 LeftQuote分組,那么右側也應該批評LeftQuote分組。這樣一來,我們就能精確匹配id的各種情況了。

匹配閉合標簽

(( ?< Nested >< \k < HtmlTag > [ ^> ] *> ) |</ \k < HtmlTag >> ( ?<- Nested > ) | . *? ) *</ \k < HtmlTag >>

在成功匹配到起始標簽之后,后面的Html文本可以分為三種情況:

A. 匹配到嵌套div起始標簽<div,這個時候,需要將其捕獲到Nested分組。

B. 匹配到嵌套div起始標簽的閉合標簽,這個時候,需要將之前的Nested分組釋放

C. 其他任意文本。注意,需要使用.*?方式關閉貪婪匹配,否則最后的閉合標簽可能會過度匹配

使 用(RegEx1|RegEx2|RegEx3)*這種方式,可以將幾個條件以或的形式組合起來,然后再取若干次匹配結果,最終再匹配閉合標簽。其中 (?<-Nested>)是表示釋放之前捕獲的Nested分組。確切的語法是(?<N-M>)即使用N分組替換掉M分組,如果 N分組沒有指定或不存在,則釋放M分組。

update:前面過於側重分析了,最后沒有給出一個完整的正則真是抱歉。

< ( ?< HtmlTag > [\w] + )[ ^> ] * \s[iI][dD] = ( ?< Quote > [ " ']?)footer(?(Quote)\k<Quote>)[ "' ]?[^>]*>((?<Nested><\k<HtmlTag>[^>]*>)|</\k<HtmlTag>>(?<-Nested>)|.*?)*</\k<HtmlTag>>

上面這個正則能夠匹配任意id=footer的html標簽。

需要注意,此正則表達式需要設置SingleLine=true,這樣點號才可以把換行符也匹配進去。 

對於domoxz 的問題,如果要匹配p標簽,那么只需將上述的正則中的HtmlTag替換成p即可。

另加一個 實戰需求: 如果想得到  匹配任意id=footer的html標簽 的 innerHTML,則正則改成如下即可:

< ( ?< HtmlTag > [\w] + )[ ^> ] * \s[iI][dD] = ( ?< Quote > [ " ']?)footer(?(Quote)\k<Quote>)[ "' ]?[^>]*>(((?<Nested><\k<HtmlTag>[^>]*>)|</\k<HtmlTag>>(?<-Nested>)|.*?)*)</\k<HtmlTag>>

 C#參考代碼:

復制代碼
MatchCollection m  =  Regex.Matches( this .tp4rtbCont.Text,  this .tp4txtRegx.Text.Trim(), RegexOptions.IgnoreCase  |  RegexOptions.Multiline | RegexOptions.Singleline);


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 " ;
}
復制代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM