)c# .net 使用Regex匹配嵌套Html標籤

來源:互聯網
上載者:User

原文地址 http://www.cnblogs.com/qiantuwuliang/archive/2011/06/11/2078329.html

概述

Regex是 做文本解析工作必不可少的技能。如Web伺服器日誌分析,網頁前端開發等。很多進階文字編輯器都支援Regex的一個子集,熟練掌握Regex,經常能夠 使你的一些工作事半功倍。例如統計程式碼數,只需一個正則就搞定。嵌套Html標籤的匹配是Regex應用中一個比較難的話題,因為它涉及到的正則文法比 較多,也比較難。因此也就更有研究的價值。

思路

任何複雜的Regex都是由簡單的子表達 式組成的,要想寫出複雜的正則來,一方面需要有化繁為簡的功底,另外一方面,我們需要從正則引擎的角度去思考問題。關於正則引擎的原理,推薦 《Mastering Regular Expression》中文名叫《精通Regex》。挺不錯的一本書。

OK,先確定我們要解決的問題——從一段Html文本中找出特定id的標籤的innerHTML

這裡面最大的痛點就是,Html標籤是支援嵌套的,怎麼能夠找到指定標籤相對應的閉合標籤呢?

我們可以這樣想,先匹配最前面的起始標籤,假設是div吧(<div),然後一旦遇到嵌套div,就“壓入堆棧”,然後一遇到div結束標籤了,就“彈出堆棧”。如果遇到結束標籤的時候,堆棧裡面已經沒有東西了,那麼匹配結束,此結束標籤為正確的閉合標籤

我之所以能夠這樣去思考,是因為我瞭解過正則的特性,我知道正則中的平衡組能夠實現我剛才說的“堆棧”操作。所以,如果我們要編寫複雜Regex,需要對正則的一些進階特性至少有所瞭解,這樣我們思考問題才有個方向。

實現

這裡假設我們要匹配的文本是一段合法的Html文本。下面這段Html代碼是從我的部落格上拷貝下來的,作為我們的測試文本。我們要匹配的就是footer這個div的innerHTML,同時把標籤名也捕獲下來。

<div style="background-color:gray;" id="footer">
    <a id="gotop" href="#" onclick="MGJS.goTop();return false;">Top</a>
    <a id="powered" href="http://wordpress.org/">WordPress</a>
    <div id="copyright">
        Copyright &copy; 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工具來構建和測試編寫的Regex。

匹配起始標籤

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

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

上面的Regex需要做幾點說明:

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標籤。

需要注意,此Regex需要設定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";
}

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.