为什么XmlNamespaceManager是必需的?

时间:2011-08-24 15:28:43

标签: .net xpath xml-namespaces selectsinglenode

我对为什么 - 至少在.Net Framework中 - 有点干涩 - 有必要使用XmlNamespaceManager来处理命名空间(或者执行XPath查询时,相当笨重且冗长[local-name()=... XPath谓词/函数/等等。我了解为什么命名空间是必要的或至少是有益的,但为什么它是如此复杂?

为了查询简单的XML文档(没有命名空间)......

<?xml version="1.0" encoding="ISO-8859-1"?>
<rootNode>
   <nodeName>Some Text Here</nodeName>
</rootNode>

...可以使用类似doc.SelectSingleNode("//nodeName")(匹配<nodeName>Some Text Here</nodeName>

的内容

神秘#1 我的第一个烦恼 - 如果我理解正确 - 只是添加一个命名空间引用到父/ root标记(无论是否用作部分一个子节点标签或不像这样:

<?xml version="1.0" encoding="ISO-8859-1"?>
<rootNode xmlns="http://someplace.org">
   <nodeName>Some Text Here</nodeName>
</rootNode>

...需要几行额外的代码来获得相同的结果:

Dim nsmgr As New XmlNamespaceManager(doc.NameTable)
nsmgr.AddNamespace("ab", "http://s+omeplace.org")
Dim desiredNode As XmlNode = doc.SelectSingleNode("//ab:nodeName", nsmgr)

...基本上梦想一个不存在的前缀(“ab”)来找到一个甚至不使用前缀的节点。 这有什么意义? doc.SelectSingleNode("//nodeName")有什么问题(概念上)?

神秘#2 :所以,假设你有一个使用前缀的XML文档:

<?xml version="1.0" encoding="ISO-8859-1"?>
<rootNode xmlns:cde="http://someplace.org" xmlns:feg="http://otherplace.net">
   <cde:nodeName>Some Text Here</cde:nodeName>
   <feg:nodeName>Some Other Value</feg:nodeName>
   <feg:otherName>Yet Another Value</feg:otherName>
</rootNode>

...如果我理解正确,您必须将两个名称空间添加到XmlNamespaceManager,以便对单个节点进行查询...

Dim nsmgr As New XmlNamespaceManager(doc.NameTable)
nsmgr.AddNamespace("cde", "http://someplace.org")
nsmgr.AddNamespace("feg", "http://otherplace.net")
Dim desiredNode As XmlNode = doc.SelectSingleNode("//feg:nodeName", nsmgr)

...为什么,在这种情况下,我是否需要(概念上)命名空间管理器?

**已删除以下评论**

编辑已添加: 我修改和改进的问题基于XmlNamespaceManager的明显冗余,我认为是大多数情况,并使用命名空间管理器来指定前缀到URI的映射:

当源文档中明确声明了名称空间前缀(“cde”)到名称空间URI(“http://someplace.org”)的直接映射时:

...<rootNode xmlns:cde="http://someplace.org"...

程序员在进行查询之前重新创建映射的概念需求是什么?

6 个答案:

答案 0 :(得分:18)

基本点(由Kev, above指出)是命名空间URI是命名空间的重要部分,而不是命名空间前缀,前缀是“任意方便”

至于为什么你需要一个命名空间管理器,而不是使用该文档可以解决这个问题,我可以想到两个原因。

原因1

如果允许只将命名空间声明添加到documentElement,就像在示例中那样,selectSingleNode只使用定义的内容确实是微不足道的。

但是,您可以在文档中的任何元素上定义名称空间前缀,并且名称空间前缀不会唯一地绑定到文档中的任何给定名称空间。请考虑以下示例

<w xmlns:a="mynamespace">
  <a:x>
    <y xmlns:a="myOthernamespace">
      <z xmlns="mynamespace">
      <b:z xmlns:b="mynamespace">
      <z xmlns="myOthernamespace">
      <b:z xmlns:b="myOthernamespace">
    </y>
  </a:x>
</w>

在此示例中,您希望//z//a:z//b:z返回什么?如果没有某种外部命名空间管理器,你会如何表达这一点?

原因2

它允许您为任何等效文档重用相同的XPath表达式,而无需了解有关正在使用的名称空间前缀的任何信息。

myXPathExpression = "//z:y"
doc1.selectSingleNode(myXPathExpression);
doc2.selectSingleNode(myXPathExpression);

DOC1:

<x>
  <z:y xmlns:z="mynamespace" />
</x>

DOC2:

<x xmlns"mynamespace">
  <y>
</x>

为了在没有命名空间管理器的情况下实现后一个目标,您必须检查每个文档,为每个文档构建自定义XPath表达式。

答案 1 :(得分:12)

据我所知,如果您有这样的文档,则没有充分的理由需要手动定义XmlNamespaceManager来获取abc - 前缀节点:

<itemContainer xmlns:abc="http://abc.com" xmlns:def="http://def.com">
    <abc:nodeA>...</abc:nodeA>
    <def:nodeB>...</def:nodeB>
    <abc:nodeC>...</abc:nodeC>
</itemContainer>

微软根本无法写一些东西来检测父节点中已经指定了xmlns:abc。我可能是错的,如果是的话,我欢迎对这个答案发表评论,以便我可以更新它。

然而,this blog post似乎证实了我的怀疑。它基本上表示您需要手动定义XmlNamespaceManager并手动迭代xmlns:属性,将每个属性添加到命名空间管理器。 Dunno为什么微软不能自动执行此操作。

以下是我根据该博客文章创建的一种方法,可根据来源XmlNamespaceManager的{​​{1}}属性自动生成xmlns:

XmlDocument

我这样使用它:

/// <summary>
/// Creates an XmlNamespaceManager based on a source XmlDocument's name table, and prepopulates its namespaces with any 'xmlns:' attributes of the root node.
/// </summary>
/// <param name="sourceDocument">The source XML document to create the XmlNamespaceManager for.</param>
/// <returns>The created XmlNamespaceManager.</returns>
private XmlNamespaceManager createNsMgrForDocument(XmlDocument sourceDocument)
{
    XmlNamespaceManager nsMgr = new XmlNamespaceManager(sourceDocument.NameTable);

    foreach (XmlAttribute attr in sourceDocument.SelectSingleNode("/*").Attributes)
    {
        if (attr.Prefix == "xmlns")
        {
            nsMgr.AddNamespace(attr.LocalName, attr.Value);
        }
    }

    return nsMgr;
}

答案 2 :(得分:12)

原因很简单。您在XPath查询中使用的前缀与xml文档中声明的前缀之间没有必需的连接。举一个例子,下面的xmls在语义上是等价的:

<aaa:root xmlns:aaa="http://someplace.org">
 <aaa:element>text</aaa:element>
</aaa:root>

VS

  <bbb:root xmlns:bbb="http://someplace.org">
     <bbb:element>text</bbb:element>
  </bbb:root>

ccc:root/ccc:element”查询将匹配两个实例,前提是命名空间管理器中有映射。

nsmgr.AddNamespace("ccc", "http://someplace.org")

.NET实现不关心xml中使用的文字前缀,只是为查询文字定义了前缀,并且命名空间值与doc的实际值匹配。这需要具有常量查询表达式,即使前缀在消费文档之间有所不同,并且它是一般情况下的正确实现。

答案 3 :(得分:4)

我回答第1点:

为XML文档设置默认命名空间仍然意味着节点,即使没有名称空间前缀,即:。

<rootNode xmlns="http://someplace.org">
   <nodeName>Some Text Here</nodeName>
</rootNode>

不再位于“空”命名空间中。您仍然需要某种方法来使用XPath引用这些节点,因此您创建一个引用它们的前缀,即使它是“组成”的。

回答第2点:

<rootNode xmlns:cde="http://someplace.org" xmlns:feg="http://otherplace.net">
   <cde:nodeName>Some Text Here</cde:nodeName>
   <feg:nodeName>Some Other Value</feg:nodeName>
   <feg:otherName>Yet Another Value</feg:otherName>
</rootNode>

在实例文档的内部,驻留在命名空间中的节点与其节点名称及其长命名空间名称一起存储,它被称为(在W3C用语中)扩展名称

例如,<cde:nodeName>基本上存储为<http://someplace.org:nodeName>。名称空间前缀对于人类来说是一种随意的方便,因此当我们输入XML或必须阅读它时,我们不必这样做:

<rootNode>
   <http://someplace.org:nodeName>Some Text Here</http://someplace.org:nodeName>
   <http://otherplace.net:nodeName>Some Other Value</http://otherplace.net:nodeName>
   <http://otherplace.net:otherName>Yet Another Value</http://otherplace.net:otherName>
</rootNode>

当搜索XML文档时,它不会被友好前缀搜索,它们的搜索是通过名称空间URI完成的,因此您必须通过使用XmlNamespaceManager传入的名称空间表告诉XPath您的名称空间。

答案 4 :(得分:3)

您需要将URI /前缀对注册到XmlNamespaceManager实例,以便让SelectSingleNode()知道您所指的特定“nodeName”节点 - 来自“http:// someplace”的节点.org“或来自”http://otherplace.net“的那个。

请注意,在执行XPath查询时,具体前缀名称无关紧要。我相信这也有效:

Dim nsmgr As New XmlNamespaceManager(doc.NameTable)
nsmgr.AddNamespace("any", "http://someplace.org")
nsmgr.AddNamespace("thing", "http://otherplace.net")
Dim desiredNode As XmlNode = doc.SelectSingleNode("//thing:nodeName", nsmgr)

SelectSingleNode()只需要来自XPath表达式的前缀和名称空间URI之间的连接。

答案 5 :(得分:3)

这个帖子帮助我更清楚地理解命名空间的问题。谢谢。当我看到Jez's code时,我尝试了它,因为它看起来比我编程的解决方案更好。不过,我发现了它的一些缺点。如上所述,它只在根节点中查找(但名称空间可以在任何地方列出。),并且它不处理默认名称空间。我试图通过修改他的代码解决这些问题,但无济于事。

这是我的该功能版本。它使用正则表达式来查找整个文件中的命名空间映射;使用默认命名空间,为它们提供任意前缀'ns';并处理同一命名空间的多次出现。

private XmlNamespaceManager CreateNamespaceManagerForDocument(XmlDocument document)
{
    var nsMgr = new XmlNamespaceManager(document.NameTable);

    // Find and remember each xmlns attribute, assigning the 'ns' prefix to default namespaces.
    var nameSpaces = new Dictionary<string, string>();
    foreach (Match match in new Regex(@"xmlns:?(.*?)=([\x22\x27])(.+?)\2").Matches(document.OuterXml))
        nameSpaces[match.Groups[1].Value + ":" + match.Groups[3].Value] = match.Groups[1].Value == "" ? "ns" : match.Groups[1].Value;

    // Go through the dictionary, and number non-unique prefixes before adding them to the namespace manager.
    var prefixCounts = new Dictionary<string, int>();
    foreach (var namespaceItem in nameSpaces)
    {
        var prefix = namespaceItem.Value;
        var namespaceURI = namespaceItem.Key.Split(':')[1];
        if (prefixCounts.ContainsKey(prefix)) 
            prefixCounts[prefix]++; 
        else 
            prefixCounts[prefix] = 0;
        nsMgr.AddNamespace(prefix + prefixCounts[prefix].ToString("#;;"), namespaceURI);
    }
    return nsMgr;
}