2010/07/28

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

本文所使用的 XML 檔案 (ns.xml) 如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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 的處理往往都需事先知道後, 才能在程式中設定.
例如以下的程式:
1
2
3
4
5
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.
    程式參考如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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 後的查詢方式: 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//查詢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);
}

沒有留言: