为什么需要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的一个方法,基于源XmlDocument
的xmlns:
属性自动生成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; }