2010/07/28

[c#] Regular Expression 抓取/取代特定資料的說明

在網路上常見到一些想用 Regular Expression 取資料的問題.
有些解答看起來很複雜, 實際上只要一個一個 pattern 拆解, 就不難發現道理其實很簡單.
以下的例子將視情況進行補充:

  1. 抓取雙引號 ("..."), 大括號 ({...}) 等被特殊符號框住的字串:
    以雙引號為例, 參考以下的 Code: (不抓取空字串)
    string strTest = "<a href=\"http://www.w3schools.com/html/default.asp\" class=\"cLink\">HTML</a><br/>";
    //這是錯誤的寫法
    MatchCollection matches = Regex.Matches(strTest, "\".+\"", RegexOptions.IgnoreCase);
    foreach (Match match in matches)
    {
      Console.WriteLine(match.Value);
      //結果:"http://www.w3schools.com/html/default.asp" class="cLink"
    }
    //這是正確的寫法
    matches = Regex.Matches(strTest, "\"[^\"]+\"", RegexOptions.IgnoreCase);
    foreach (Match match in matches)
    {
      Console.WriteLine(match.Value);
      //結果:"http://www.w3schools.com/html/default.asp"
      //    "cLink"
    }
    //這也是正確的寫法
    matches = Regex.Matches(strTest, "\".+?\"", RegexOptions.IgnoreCase);
    foreach (Match match in matches)
    {
      Console.WriteLine(match.Value);
      //結果:"http://www.w3schools.com/html/default.asp"
      //    "cLink"
    }
    上述的例子說明如下:
    Step1: 設定起始的符號, 也就是給一個雙引號.
    Step2: 再來是設定結束的符號, 這部分一樣是給一個雙引號.
    Step3: 在中括號的開頭填入一個 '^', 表示要排除掉中括號裡所列的字元, 這邊要排除的字元就放結束符號, 表示除了結束字元外的字元我們都要吃. 
               在 '^' 符號後面接一個 Step2 的結束符號並在中括號外面給一個 '+', 表示開始與結束符號中至少要有一個字元, 若要允許空字串就給 '*'.
               最後一種寫法可參考: http://www.regular-expressions.info/reference.htmllazy 說明.
    同樣的道理, 如果要取大括號裡的字串, 表示式就是: "{[^}]+}" 或 '{.+?}'.
  2. 承1. 如果不想要 Match 後的字串包含開始與結束字元, 就可以使用 Regular Expression 中的 Group 概念, 或是用字串的 Trim, 例如: match.Value.Trim('\"') .
    例如以下的 Code:
    string strTest = "<a href=\"http://www.w3schools.com/html/default.asp\" "
    + "class=\"cLink\">HTML</a><br/>";
    //將雙引號中的字串放進一個名為item的Group中
    MatchCollection matches = Regex.Matches(strTest, 
      "\"(?<item>[^\"]+)\"", 
      RegexOptions.IgnoreCase);
    foreach (Match match in matches)
    {
      Console.WriteLine(match.Groups["item"].Value);
      //結果:http://www.w3schools.com/html/default.asp
      //    cLink
    }
    蠻簡單的做法, 只要將起始與結束符號中的 Pattern 放在 (?<item>)  裡, 然後用 match.Groups["item"].Value 去取值就可以.
    不過初步還是先用 match.Value 去確認有抓對, 再去思考要怎麼拆解成 Group.
  3. 在字串的取代方面, string.Replace(string oldValue, string newValue) 是一對一的取代, 如果要多對多或多對一的取代, 可以用 Regex.Replace.
    例如, 我們想把字串中所有的標籤 (<...>) 刪除掉, 可以用以下的 Code:
    string strTest = "<a href=\"http://www.w3schools.com/html/default.asp\" "
      +"class=\"cLink\">HTML</a><br/>";
    //利用"<[^>]+>", 找出所有的<...>
    Console.WriteLine(Regex.Replace(strTest, "<[^>]+>", string.Empty));            
    //結果:HTML
    看起來比 string.Replace 好用, 也比較彈性. 不過還是要注意要 Replace 掉的 Pattern 要寫對.
  4. Regex.Replace 還有一個用法, 就是透過 delegate 的方式做取代的後處理.
    這一般用在比對的 Pattern 寫得不是很精準的時候用, 透過後處理的方式決定要不要進行取代.
    例如, 如果我們想移除所有的 HTML 標籤, 但又想留住 <br> 這個標籤, 就可以參考以下的 Code:
    string strTest = "<a href=\"http://www.w3schools.com/html/default.asp\" "
    + "class=\"cLink\">HTML</a><br/>";
    string strResult = Regex.Replace(strTest, "<[^>]+>", 
      delegate(Match m) {
        //如果查到的字串是以"<br"為開頭, 就不取代, 而回傳原本比對到的字串
        if (m.Value.StartsWith("<br", StringComparison.OrdinalIgnoreCase))
        {
          return m.Value;
        }
        else
        {
          //除了<br>以外的字串,都換成空字串
          return string.Empty;
        }
      }
    );
    Console.WriteLine(strResult);            
    //結果:HTML<br/>
    這樣的寫法, 還可以應用在許多地方. 例如進行關鍵字的 highlight, 或是移除掉一些敏感字眼.
  5. (2009/06/26補充) 有時候, 礙於畫面好看或是資料的縮排, 會對一個很長的字串做指定長度的斷行.
    舉例來說, 有一長串文字, 我們希望每 10 個字換一行. 有人會撰寫如下的程式去處理:
    string strSource = "Windows 7已確定十月二十二日上市,為避免上市前電腦買氣遲滯,"
      + "微軟提供Vista到Windows 7免費升級方案。";
    string strResult = string.Empty;
    for (int i = 0; i < strSource.Length; i += 10)
    {
      strResult += strSource.Substring(i, strSource.Length < (i + 10) 
        ? strSource.Length - i : 10) + Environment.NewLine;
    }
    Console.WriteLine(strResult);
    (2009/09/16修改) 如上的作業也可以用 Regex.Replace() 完成:
    string strSource = "Windows 7已確定十月二十二日上市,為避免上市前電腦買氣遲滯,"
      + "微軟提供Vista到Windows 7免費升級方案。";
    Console.WriteLine(Regex.Replace(strSource, "(?!.{10}$).{10}", 
      delegate(Match m) { return m.Value + Environment.NewLine; }));
    //或是用到群組概念
    Console.WriteLine(Regex.Replace(strSource, "(?!.{10}$)(.{10})", 
      "$1" + Environment.NewLine));
    只要把 10 這個數字換成其它數字, 就能依指定長度斷行. 不過上例中文字也算長度 1, 所以若是中英夾雜的字串, 斷行後的長度看起來還是會不一致. (要注意)
Regular Expression 是一個很不人性的語言, 所以要多嘗試才能抓到一些概念, 算是一種蠻麻煩的學習.
不過如果懂得用, 是還蠻方便的.

1 則留言: