ChatGPT解决这个技术问题 Extra ChatGPT

为什么需要 XmlNamespaceManager?

我对 why 的看法有些枯燥 - 至少在 .Net Framework 中 - 必须使用 XmlNamespaceManager 来处理命名空间(或相当笨重和冗长的 { 2} XPath 谓词/函数/其他)在执行 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:我的第一个烦恼——如果我理解正确的话——仅仅是将命名空间引用添加到父/根标记(无论是否用作子节点标记的一部分),如下所示:

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

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

Dim nsmgr As New XmlNamespaceManager(doc.NameTable)
nsmgr.AddNamespace("ab", "http://example.com/xmlns/foo")
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://example.com/xmlns/foo" xmlns:feg="http://example.com/xmlns/bar">
   <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://example.com/xmlns/foo")
nsmgr.AddNamespace("feg", "http://example.com/xmlns/bar")
Dim desiredNode As XmlNode = doc.SelectSingleNode("//feg:nodeName", nsmgr)

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

******已编辑为以下评论****

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

当名称空间前缀 ("cde") 到名称空间 URI ("http://example.com/xmlns/foo") 的直接映射在源文档中明确说明时:

...<rootNode xmlns:cde="http://example.com/xmlns/foo"...

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

作为一个快速的附录,我承认在某些情况下,像 XMLNamespaceManager 之类的东西可能会让事情变得更容易,但我相信在上述情况下,它会使事情变得比实际情况更难。
我的主要困惑是为什么需要在 XML 文档和实现 XPath 查询的代码中指定前缀与命名空间的关系。如果根节点已经包含映射,为什么我必须对加载文档时已经解析的信息进行硬编码?此外,如果将来在文档中添加第三个命名空间,我是否不必更改和重新编译我的代码来声明第三个关系?
从上面删除: 简单地将命名空间前缀放在 XPath 查询中 -doc.SelectSingleNode("//feg:nodeName") - 并完成它有什么问题?对于人脑来说,这段代码片段的含义是否存在疑问? [PARAGRAPH] 换句话说,额外的代码行和无法从源 XML 文档和/或 XPath 明确派生的 XmlNamespaceManager 的实例化真正增加了对情况的理解询问?
从上面删除,继续当然,对于大多数 XML 文档和使用 XML 和 XPath 的情况,至少可以想象,如果不是很实际,简单地获取来自文档和查询的命名空间信息,而不是要求预先知道命名空间,或手动解析文档以确定 AddNamespace() 的参数?我忍不住想我一定遗漏了一些明显的东西,如果我是,请赐教!
+1 这个问题。我现在也有同样的想法。我的根节点有一堆 xmlns:abc="..." xmlns:def="..." 属性;为什么 XPathNodeIterator 不能找出与没有 XmlNamespaceManager<abc:SomeNode/> 之类的子节点相关联的命名空间?

C
Community

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

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

原因一

如果只允许向 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);

文档1:

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

文档2:

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

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


尽管原因 1 下的示例似乎是有效的 AFAIK,但我不得不想知道在现实世界中存在多少这样的案例,因为它看起来非常复杂。当然,使用单字母命名空间和节点名在一定程度上限制了可能性的数量,尽管我已经看到了一些将 2、3 和 4 字母缩写用作命名空间前缀的真实示例,但我还没有看到 1-理论和例子之外的字母前缀。基本上,我发现自己真的在寻找如何使用命名空间管理器来表达这些。
要回答您的原因 1 的问题:这取决于我想从数据中找到或过滤掉什么——对于如此复杂且同时无意义的节点名称和关系,这很难做到。但是,您的原因 1 提供了迄今为止最有见地和最清晰的答案......至于原因 2,我不确定提供的代码是否会执行,因为您的源使用命名空间,但您没有提供命名空间管理器 - 我我错在这?
在我的两个示例中,我都要求您考虑没有名称空间管理器的生活。据我所知,如果不求助于命名空间管理器,我在原因 1 中提出的问题是不可能回答的。我不是在问如何提取任何特定节点,而是在问您希望这些表达式返回哪些节点。
你是对的——原因 2 中的代码将需要一个命名空间管理器。我故意省略了名称空间管理器,因为您的问题的重点(据我了解)是您相信我们可以没有它生活 - 这表明了我们不能的情况。
原因 1 中您的问题的最终答案://z 应匹配 <z xmlns="mynamespace"><z xmlns="myOthernamespace">//a:z 将返回一个空集,//b:z 将匹配 <b:z xmlns:b="mynamespace"> 和 {7 } - 这背后的逻辑是没有指定命名空间管理器,并且没有“尝试从文档本身获取信息”命令,因此命名空间被视为任何其他属性,并且{ 8} 在我的脑海中变成像 - 这样的另一个有效字符,如果你知道你的数据,或者不在乎,查询一个节点应该不会那么痛苦
A
Adrian Zanescu

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

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

对比

  <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 中使用的文字前缀,只关心为查询文字定义的前缀并且命名空间值与文档的实际值匹配。即使使用的文档之间的前缀不同,这也需要具有常量查询表达式,并且它是一般情况下的正确实现。


T
Tom Hunter

据我所知,如果您有这样的文档,则没有充分的理由需要手动定义 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>

Microsoft 根本懒得写一些东西来检测已经在父节点中指定了 xmlns:abc。我可能是错的,如果是这样,我欢迎对此答案发表评论,以便我更新它。

但是,this blog post 似乎证实了我的怀疑。它基本上说您需要手动定义一个 XmlNamespaceManager 并手动遍历 xmlns: 属性,将每个属性添加到命名空间管理器。不知道为什么微软不能自动做到这一点。

下面是我基于该博客文章创建的一种方法,用于根据源 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));

welp,经过这么长时间后回到这个问题 - 它不仅仅是微软 - 我相信它在 XML 或 XPATH 规范中 - 在我使用过的其他非 MS 语言中它以类似的方式发生 - 不确定是否有一个确实为您提取名称空间,但是如何指定哪个范围(因为可以在任何范围内指定名称空间)... idunno - 我喜欢文字模式,其中 : 成为类似于数字的文字字符,字母或 - 因此 prfx:NodeName 被视为就像 prfxNodeNameprfx-NodeName - 一个简单的标识符......虽然不符合标准......叹息
K
Kev

我回答第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 您的名称空间。


虽然我认为没有概念上的理由要求某人在仍然只使用一个命名空间时确认非“空”命名空间,但为什么有必要使函数需要的不仅仅是一个标志,例如 doc.SelectSingleNode("//nodeName", NamespaceFlags.UseDocumentNamespace)
-- 也就是说,为什么要求程序员实例化一个单独的对象,预知(或解析和确定)文档中使用的命名空间,然后指定一个完全随机和人为的命名空间前缀来插入到 XPath 查询中?请原谅我的语气——我只是一头雾水。
@code - 这是因为在更复杂的文档(例如 RSS feeds)中,通常有多个名称空间在起作用。拥有一个特殊标志来处理该特定条件(根据您的示例,文档仅位于默认命名空间中)是一个糟糕的设计选择,并且会增加框架代码的额外复杂性。那么为什么不涵盖所有基础并要求代码的使用者传递一个 XmlNamespaceManager 来代替。
我相信您提供的示例 (RSS) 与我在原始问题中的 Mystery #2 相关(多个命名空间)。 XPath 查询和 RSS 文档本身包含查询节点所需的所有信息。我能想象的唯一需要 XmlNamespaceManager 的情况是有多个命名空间(“someplace.org”和“otherplace.net”)使用相同的前缀(都使用 xmlns:place 或类似的,但在不同的范围内)文档)。否则,文档和查询将提供产生所需结果所需的所有信息。
感谢您的耐心等待,但这似乎仍然无法回答我的问题。为什么需要使用多个 //feg:nodeName 来查找特定节点?在内部将 feg... 转换为 http://otherplace.net 应该相对简单,而无需我明确说明这种关系 - 它就在根节点中! (xmlns:feg="http://otherplace.net")。至少,我认为应该有一个像 XmlNamespaceManager.GetNSFromDocument(xdoc) 这样的辅助函数......如果答案只是他们没有(还)为你做这项工作,那么好吧!是这样吗?
C
Christian Schwarz

您需要将 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 之间的连接。


C
Community

这个线程帮助我更清楚地理解了命名空间的问题。谢谢。当我看到 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;
}

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅