为什么需要XmlNamespaceManager?

至less在.Net Framework中,为了处理命名空间(或相当笨拙和冗长的[local-name()=...必须使用XmlNamespaceManager [local-name()=... XPath谓词/函数/无论)执行XPath查询时。 我明白为什么命名空间是必要的或者至less是有益的,但为什么这么复杂呢?

为了查询一个简单的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我的第一个烦恼 – 如果我理解正确 – 是仅仅添加一个名称空间引用到父/根标记(不pipe是否用作子节点标记的一部分),如下所示:

 <?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 ”)来find一个甚至不使用前缀的节点。 这有什么意义? 什么是错误的(概念上)与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) 

…为什么在这种情况下,我需要(概念上)一个名称空间pipe理器?

**编辑成以下评论**

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

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

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

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

基本点(就像上面的Kev所指出的那样)是命名空间URI是命名空间的重要部分,而不是命名空间的前缀,前缀是“任意的方便”

至于为什么你需要一个命名空间pipe理器,而不是有一些神奇的工作,它使用文档,我可以想到两个原因。

原因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返回什么? 如果没有某种外部命名空间pipe理器,你会expression吗?

原因2

它允许您为任何等效的文档重复使用相同的XPathexpression式,而无需了解任何有关正在使用的命名空间前缀的知识。

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

DOC1:

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

DOC2:

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

为了在没有命名空间pipe理器的情况下实现这个后面的目标,你必须检查每个文档,为每个文档build立一个自定义的XPathexpression式。

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

 <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> 

如果在命名空间pipe理器中有一个映射,那么“ ccc:root/ccc:element ”查询将匹配两个实例。

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

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

据我所知,没有什么好的理由,如果你有这样的文档,你需要手动定义一个XmlNamespaceManager来获取abc -prefixed节点:

 <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是否已经在父节点中指定。 我可能是错的,如果是这样,我会欢迎评论这个答案,所以我可以更新它。

不过, 这个博客似乎证实了我的怀疑。 它基本上说,你需要手动定义一个XmlNamespaceManager并手动迭代xmlns:属性,将每个属性添加到名称空间pipe理器。 不知道为什么微软无法自动做到这一点。

下面是我基于该博客文章创build的一个方法,基于源XmlDocumentxmlns:属性自动生成XmlNamespaceManager

 /// <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; } 

我就这样使用它:

 XPathNavigator xNav = xmlDoc.CreateNavigator(); XPathNodeIterator xIter = xNav.Select("//abc:NodeC", createNsMgrForDocument(xmlDoc)); 

我回答第1点:

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

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

不再处于“空”的名字空间。 您仍然需要一些方法来使用XPath来引用这些节点,因此您可以创build一个前缀来引用它们,即使它是“组成的”。

回答点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> 。 命名空间前缀对于人类来说是一个任意的方便,所以当我们inputXML或者必须读取它时,我们不必这样做:

 <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> 

在searchXML文档时,不会通过友好前缀search,而是通过名称空间URI进行search,因此您必须通过使用XmlNamespaceManager传入的名称空间表来告诉XPath有关名称空间。

您需要将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()只需要XPathexpression式的前缀和名称空间URI之间的连接。

这个线程帮助我更清楚地理解命名空间的问题。 谢谢。 当我看到Jez的代码时 ,我试了一下,因为它看起来像比我编程的更好的解决scheme。 但是,我发现它有一些缺点。 正如所写的,它只能看到根节点(但名称空间可以在任何地方列出),并且不处理默认名称空间。 我试图通过修改他的代码来解决这些问题,但无济于事。

这是我的版本的function。 它使用正则expression式来查找整个文件中的命名空间映射; 使用默认命名空间,给他们任意前缀'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; }