XML命名空间和XPath

8

我有一个应用程序,需要加载XML文档并根据XPath输出节点。

假设我有以下这样的文档:

<aaa>
  ...[many nodes here]...
  <bbb>text</bbb>
  ...[many nodes here]...
  <bbb>text</bbb>
  ...[many nodes here]...
</aaa>

使用XPath //bbb,目前一切都很好。选择doc.SelectNodes("//bbb");会返回所需节点列表。然后有人上传了一个只有一个节点<myfancynamespace:foo/>和根标记中的额外命名空间的文档,所有的东西都崩溃了。
为什么?//bbbmyfancynamespace无关,理论上甚至应该使用//myfancynamespace:foo更好,因为没有歧义,但是表达式返回0个结果,就是这样。
这种行为是否有解决方法?
我确实有一个文档的命名空间管理器,并将其传递给XPath查询。但是我不知道命名空间和前缀,因此无法在查询之前添加它们。
我必须在执行任何选择之前预解析文档以填充命名空间管理器吗?这种行为为什么存在,这根本没有意义。
编辑:
我正在使用:XmlDocumentXmlNamespaceManager 编辑2:
XmlDocument doc = new XmlDocument();
doc.XmlResolver = null;
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
//I wish I could:
//nsmgr.AddNamespace("magic", "http://magicnamespaceuri/
//...
doc.LoadXML(usersuppliedxml);
XmlNodeList nodes = doc.SelectNodes(usersuppliedxpath, nsmgr);//usersuppliedxpath -> "//bbb"

//nodes.Count should be > 0, but with namespaced document they are 0

编辑3: 发现了一篇文章,描述了一个实际场景的问题,并提出了一个解决方法,但不是很美观的解决方法: http://codeclimber.net.nz/archive/2008/01/09/How-to-query-a-XPath-doc-that-has-a-default.aspx

似乎去掉xmlns是解决问题的方法...


你能添加相关的代码片段吗?(例如实例化XmlDocument、XPath等) - Jeff Swensen
好的,已编辑帖子,请查看Edit2。 - Coder
@程序员:你说的意思是,对于给定的过程,意外的输入会导致意外的输出。那就是验证的用例。 - user357812
当我说“你还没有向我们展示输入的XML长什么样子”时,我的意思是指导致问题的那个。 - LarsH
这个问答解决了我的问题。然而,在搜索时我不知道命名空间是一个错误的概念。我的问题是我的xpath搜索根本没有返回任何结果。 - Brian Leeming
显示剩余2条评论
4个回答

14

你没有完全理解XML命名空间的重点。

但如果你确实需要在使用未知命名空间的文档上执行XPath,并且你确实不在意它,那么你需要剥离它并重新加载文档。XPath无法以与命名空间无关的方式工作,除非你想在选择器中的每个点使用local-name()函数。

private XmlDocument StripNamespace(XmlDocument doc)
{
    if (doc.DocumentElement.NamespaceURI.Length > 0)
    {
        doc.DocumentElement.SetAttribute("xmlns", "");
        // must serialize and reload for this to take effect
        XmlDocument newDoc = new XmlDocument();
        newDoc.LoadXml(doc.OuterXml);
        return newDoc;
    }
    else
    {
        return doc;
    }
}

这超级有用。我在自己的一些项目中实现了 StripNamespaces() 方法,但这比我的方法优雅得多。我完全会借鉴这个。 :) - DWRoelands
去除命名空间是最简单的方法,否则我需要创建一个用户前缀,而用户可能不知道这一点。感谢您的提示。 - Coder
2
需要指出的是,此函数并不会剥离文档中的所有命名空间;它似乎旨在从任何命名空间(默认或其他)中的文档的最外层元素中删除任何默认命名空间声明。这有点奇怪,但如果整个文档都在默认命名空间中,并且在文档中没有较低级别的默认命名空间声明,那么它将实现您想要的效果。 - LarsH
1
@LarsH,没错。我特别是在使用它来处理符合该描述的xhtml文档。 - harpo
1
@DWRoelands,这个程序是由pluralsight提供的,你可以在那里找到更多关于这种技术的讨论(我完全承认这是一种hack)。我应该提到这一点,但我在我的副本中留下了归属。 :) - harpo

6

<myfancynamespace:foo/>不一定与<foo/>相同。

命名空间确实很重要。但我能理解你的挫败感,因为它们通常会破坏代码,各种实现(C#,Java等) tend倾向于以不同的方式输出它。

我建议您更改XPath以允许接受所有命名空间。例如,而不是

//bbb 

将其定义为

//*[local-name()='bbb']

那应该就没问题了。

用户输入XPath,因此我假设如果他输入了“//foo”,那么他期望从默认命名空间中选择“foo”,这不一定是“myfancynamespace”,但如果他输入“//ns1:foo”,则应选择“ns1”命名空间的“foo”,而不管该命名空间的实际URI是什么。感觉这是一个简单的情景... - Coder
2
如果用户输入XPath,则必须了解命名空间及其影响。这是XML中最不为人所知的功能,因此我可以看出您可能会遇到一些用户问题,但让他们了解local-name(),他们应该能够很快掌握它。 - Aliostad
1
@Aliostad: +1 “如果用户输入XPath,则必须了解命名空间”。我同意。 - user357812
1
所以我假设如果他输入了“//foo”,那么他期望从默认命名空间中得到“foo” - 是的,你说得对,但是这个表达式意味着XPath环境中默认命名空间中的foo,而你似乎在考虑XML文档(在元素“foo”处)的默认命名空间。这些可以不同很令人困惑,但是如果您考虑从许多不同来源验证XML文档,您将看到它们必须是不同的原因。 - LarsH
这个答案对于没有命名空间的小型文档非常有用,而且很可能适用于90%的用户。 即使需要命名空间,如果有一定的结构,有时也可以忽略它们。 - Martin Clemens Bloch
显示剩余2条评论

0
你应该更详细地描述你想要做什么。以你提问的方式,根本没有意义。命名空间只是名称的一部分。没有多余的东西,也没有少的东西。所以你的问题就像是在询问一个XPath查询,以获取所有以“x”结尾的标签。这不是XML的设计初衷,但如果你有奇怪的理由这样做:请随意遍历所有节点并自行实现。对于你请求的功能也同样适用。

给定随机的XML文档DOC,在默认命名空间中选择所有节点“bbb”。或者使用XPath查询“//bbb”,不考虑命名空间。 - Coder
@程序员: “默认命名空间”不会是选择<myfancynamespace:foo/>的命名空间... - user357812
@Coder, 在之前的评论中,你提出了两个不同(不兼容)的规范。后者有意义,并且听起来你知道如何做到这一点。但“给定随机的XML文档DOC,在默认命名空间中选择所有节点“bbb” ”意味着您的应用程序的行为将会改变,取决于声明为默认值的那个命名空间!这完全破坏了命名空间的语义。命名空间声明和前缀被指定为透明的。只有每个元素的名称和命名空间URI确定其身份。 - LarsH
@Coder - 在我之前的评论中,我假设你所说的“默认命名空间”是指每个bbb元素的XML文档的默认命名空间。如果你指的是XPath环境的默认命名空间,则忽略我之前的大部分评论。 - LarsH

0
你可以使用LINQ XML类,例如XDocument。它们极大地简化了命名空间的处理。

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