2010/07/28

[C#] 利用 Regex 或 XPath 查詢 xmlns, 並設定 XmlNamespaceManager

本文所使用的 XML 檔案 (ns.xml) 如下:
<?xml version="1.0" encoding="utf-8" ?>
<root>
  <h:table xmlns:h=http://www.w3.org/TR/html4/>
    <h:tr>
      <h:td>Apples</h:td>
      <h:td>Bananas</h:td>
    </h:tr>
  </h:table>
  <f:table xmlns:f="http://www.w3schools.com/furniture">
    <f:name>African Coffee Table</f:name>
    <f:width>80</f:width>
    <f:length>120</f:length>
  </f:table>
  <table>
    <name>Microsoft Visual C# 2008 Step by Step</name>
    <author>John Sharp</author>
  </table>
</root>

在 xml 的查詢中, Namespace 的處理往往都需事先知道後, 才能在程式中設定.
例如以下的程式:
XmlDocument doc = new XmlDocument(); 
doc.Load(@".\ns.xml"); 
XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable); 
mgr.AddNamespace("h", "http://www.w3.org/TR/html4/"); 
mgr.AddNamespace("f", http://www.w3schools.com/furniture);
如果 ns.xml 檔案的 namespace 多了一個, 或是對應的 url 路徑異動了, 程式大概就要修改, 然後重新上版.
因為我覺得這樣蠻麻煩的, 所以寫了以下兩個方式來做 namespace 的設定.
  1. 使用 Regular Expression, 找出所有 xmlns:<perefix>="<uri>" 的 pattern.
    程式參考如下:
    XmlDocument doc = new XmlDocument(); 
    doc.Load(@".\ns.xml"); 
    XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable); 
    Regex reg = new Regex("xmlns[:]?(?<prefix>.*?)\\s*=\\s*\"(?<uri>.*?)\"", 
      RegexOptions.IgnoreCase); 
    Match match = reg.Match(doc.OuterXml); 
    while (match.Success) 
    { 
        if (!mgr.HasNamespace(match.Groups["prefix"].Value)) 
        { 
            mgr.AddNamespace(match.Groups["prefix"].Value, 
              match.Groups["uri"].Value); 
        } 
        match = match.NextMatch(); 
    }
  2. 使用 SelectNodes(), 查出所有的 XmlDocument 中所有的 Node, 再針對屬性 Prefix 為 xmlns 的資料做設定.
    程式參考如下 :
  3. XmlDocument doc = new XmlDocument(); 
    doc.Load(@".\ns.xml"); 
    XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable); 
    XmlNodeList lstNodes = doc.SelectNodes("//child::*"); 
    foreach (XmlNode node in lstNodes) 
    { 
        foreach (XmlAttribute attr in node.Attributes) 
        { 
            if (attr.Prefix.Equals("xmlns")) 
            { 
                if (!mgr.HasNamespace(attr.LocalName)) 
                { 
                    mgr.AddNamespace(attr.LocalName, attr.Value); 
                } 
            } 
        } 
    }
第 2 種方式注意是使用 "//child::*", 如果使用 "/child::*" (差一個 '/'), 就無法查出所有的 Node.
另外, 要避免使用 xmlns=http://www.w3.org/TR/html4/, 這種預設 namespace 的用法, 以免後續的 XPath 查詢出問題.
補充一下設定 namespace 後的查詢方式: 
//查詢xmlns:h="http://www.w3.org/TR/html4/"的<h:table> 
XmlNodeList lstNode = doc.SelectNodes("//h:table", mgr); 
foreach (System.Xml.XmlNode node in lstNode) 
{ 
    Console.WriteLine(node.OuterXml); 
} 
//查詢xmlns:f="http://www.w3schools.com/furniture"的<f:table> 
lstNode = doc.SelectNodes("//f:table", mgr); 
foreach (System.Xml.XmlNode node in lstNode) 
{ 
    Console.WriteLine(node.OuterXml); 
} 
//查詢沒有設定NameSpace的<table> 
lstNode = doc.SelectNodes("//table", mgr); 
foreach (System.Xml.XmlNode node in lstNode) 
{ 
    Console.WriteLine(node.OuterXml); 
}

沒有留言: