XML SelectNode()返回空值。为什么命名空间很重要?

3

我有一个获取根元素节点的代码:

xmlNodes = rootElement.SelectNodes("DefinitionName");

它没有返回存在的节点。在调试器中,我可以展开rootElement查找DefinitionName。显然问题在于文件定义了命名空间(请参见下面的XML)。MSDN说我必须像这样做才能获取节点返回:
注意:这与我的代码无关。这是来自MSDN的示例:
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("ab", "http://www.lucernepublishing.com");
XmlNodeList nodelist = doc.SelectNodes("//ab:book", nsmgr);

我有两个问题:

  1. 为什么命名空间很重要?如果我想要一个节点,而它存在,那就直接给我。
  2. 我的应用程序处理许多XML文件。我该如何指定命名空间(nsmgr.AddNamespace())?我需要先解析文件才能获取吗?

我感觉自己正在走一条漫长、充满焦虑的路。

这是XML:

    <?xml version="1.0" encoding="utf-8"?>
    <SessionStateInfo xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
    z:Id="1" z:Type="Company.Apps.MoreHere.Session.SessionStateInfo"
    z:Assembly="assembly info here"
    xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"
    xmlns="http://schemas.datacontract.org/2004/07/MoreHere.Session">
      <CoaterNumber>25</CoaterNumber>
      <DefinitionName z:Id="2">Two Line</DefinitionName>
      <EnableManualMode>true</EnableManualMode>

一个节点的标识不仅由其本地名称(在您的示例中为DefinitionName)确定,还由其命名空间URI和本地名称的组合确定。请注意,前缀本身并不重要,除了定义命名空间URI之外。因此,一般来说,a:x、b:x和x是不同的节点:x在全局NS中,而a:x和b:x可能在不同的NS中。例外情况是:前缀a和b可以引用相同的NS URI,或者由于默认NS生效,x实际上可以在NS中。但是,从您的帖子中,我不明白为什么DefinitionName会在www.lucernepublishing.com NS中。 - Dabbler
因此,元素/节点没有使用命名空间前缀并不重要。如果文件中定义了命名空间,则必须通过命名空间获取节点? - Bob Horn
1
如果节点位于命名空间中,则必须通过该命名空间进行访问。但是,说“如果文件中定义了一个命名空间”有点过于简单化,因为命名空间定义可以位于文档树的任何位置,影响某些节点但不影响其他节点。然而,我并没有看到DefinitionName实际上在命名空间中。 - Dabbler
1
啊,所以真正的XML确实有一个默认命名空间声明(xmlns =“…”),这会改变很多事情... - Ian Roberts
显示剩余5条评论
3个回答

5
<SessionStateInfo ....
    xmlns="http://schemas.datacontract.org/2004/07/MoreHere.Session">

这意味着该元素及其所有后代都在http://schemas.datacontract.org/2004/07/MoreHere.Session命名空间中。由于XPath中未命名的名称始终指向没有命名空间的元素,因此您需要将此URI绑定到前缀并在XPath中使用该前缀,即使文档中未使用前缀。
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("mhs", "http://schemas.datacontract.org/2004/07/MoreHere.Session");
xmlNodes = rootElement.SelectNodes("mhs:DefinitionName", nsmgr);

如果您知道要查找的元素始终具有相同的本地名称,但可能有或可能没有命名空间(或可能具有不同的命名空间),那么您可以使用XPath技巧,例如

rootElement.SelectNodes("*[local-name() = 'DefinitionName']");

+1 谢谢。这回答了我的第一个问题。但我不能在代码中硬编码命名空间,因为我事先不知道它。有些XML文档将有命名空间,而有些则没有。您能回答我的问题中的第二个观点/问题吗? - Bob Horn
@BobHorn 我已经添加了一个hack,如果必要的话你可以使用它,但是命名空间通常是有原因的 - 使用它们的目的是在命名空间A中命名为DefinitionName的元素不应该与命名空间B中命名为DefinitionName的元素相同对待... - Ian Roberts
令人惊讶的是,您会让节点驻留在命名空间中,然后在处理它们时声称您了解节点但不了解命名空间。因为如果您只关心它们的本地名称,尽管节点生活在不同的世界(可以这么说),这使得使用命名空间似乎是多余的。但是,如果这确实是您想要的,您可以通过调用Siraf建议的local-name()来忽略NS。 - Dabbler
这是一个部署应用程序。用户在部署过程中指定要更改的节点。应用程序本身不知道它将处理的任何文件;它只是根据用户输入更改节点。因此,可能缺少的一部分是我需要让用户指定选项命名空间? - Bob Horn
这是完全可能的。从纯XML(+命名空间)的角度来看,驻留在不同命名空间中的节点是完全无关的,无论它们的本地名称是否相等。你是否想基于它们的本地名称对它们应用相同的处理超出了XML范围,只能在业务逻辑层面上回答。 - Dabbler

2

这很重要,因为如果有一个命名空间附加在其中,“定义名称”就不够了。 想象一下,你得到了一个人员名单,所有人的名字都是John:

  • John Smith
  • John Jones
  • John Murphy

你所做的相当于询问“John”,而不是例如“John Smith”。


1

这不是问题的确切答案,但可能是使用XDocument的替代解决方案。

using System;
using System.Dynamic;
using System.Xml.Linq;
using Microsoft.CSharp.RuntimeBinder;
using System.Linq;

namespace ConsoleApplication8
{
    class Program
    {
        static void Main(string[] args)
        {
            XDocument document = XDocument.Load("SessionStateInfo.xml");
            XNamespace nameSpace = document.Root.GetDefaultNamespace();
            XElement node = document.Descendants(nameSpace + "DefinitionName").FirstOrDefault();   

            if (node != null)
            {
                Console.WriteLine("Cool! XDocument rocks! value: {0}", node.Value);
            }
            else
            {
                Console.WriteLine("Spoot! Didn't find it!");
            }
        }      
    }       
}

这似乎可以正常工作,无论是否指定默认命名空间。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接