36

什么是 XPath(在 C# API 到 XDocument.XPathSelectElements(xpath, nsman) 如果重要的话)从这个文档中查询所有 MyNodes?

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <MyNode xmlns="lcmp" attr="true">
    <subnode />
  </MyNode>
</configuration>
  • 我试过/configuration/MyNode这是错误的,因为它忽略了命名空间。
  • 我试过/configuration/lcmp:MyNode这是错误的,因为lcmp是 URI,而不是前缀。
  • 我试过/configuration/{lcmp}MyNode失败了,因为Additional information: '/configuration/{lcmp}MyNode' has an invalid token.

编辑:我不能mgr.AddNamespace("df", "lcmp");像一些回答者建议的那样使用。这要求 XML 解析程序提前知道我计划使用的所有名称空间。由于这适用于任何源文件,因此我不知道要为哪些命名空间手动添加前缀。看起来像是{my uri}XPath 语法,但微软没有费心去实现它……真的吗?

4

6 回答 6

38

configuration元素位于未命名的命名空间中,并且 MyNode 绑定到lcmp没有命名空间前缀的命名空间。

XPATH语句将允许您在MyNode没有声明lcmp命名空间或在 XPATH 中使用命名空间前缀的情况下对元素进行寻址:

/configuration/*[namespace-uri()='lcmp' and local-name()='MyNode']

它匹配任何属于其子元素的元素,configuration然后使用谓词过滤器namespace-uri()local-name()函数将其限制为该MyNode元素。

如果您不知道元素将使用哪个命名空间 uri,那么您可以使XPATH更通用并仅匹配local-name()

/configuration/*[local-name()='MyNode']

但是,您冒着匹配碰巧使用相同名称的不同词汇表(绑定到不同的命名空间uri)中的不同元素的风险。

于 2010-03-27T16:30:58.300 回答
12

您需要使用 XmlNamespaceManager,如下所示:

   XDocument doc = XDocument.Load(@"..\..\XMLFile1.xml");
   XmlNamespaceManager mgr = new XmlNamespaceManager(new NameTable());
   mgr.AddNamespace("df", "lcmp");
   foreach (XElement myNode in doc.XPathSelectElements("configuration/df:MyNode", mgr))
   {
       Console.WriteLine(myNode.Attribute("attr").Value);
   }
于 2010-03-26T18:05:20.470 回答
8

XPath (故意)不是为您希望对仅存在于 XML 文档中的某些未知名称空间使用相同 XPath 表达式的情况而设计的。您应该提前知道命名空间,向 XPath 处理器声明命名空间,并在表达式中使用该名称。Martin 和 Dan 的回答展示了如何在 C# 中执行此操作。

这种困难的原因在XML 命名空间规范中得到了最好的表达:

我们设想可扩展标记语言 (XML) 的应用,其中单个 XML 文档可能包含为多个软件模块定义和使用的元素和属性(这里称为“标记词汇表”)。这样做的一个动机是模块化:如果存在这样一个易于理解的标记词汇表并且有可用的有用软件,那么最好重用这个标记而不是重新发明它。

此类包含多个标记词汇的文档会带来识别和冲突的问题。软件模块需要能够识别它们被设计处理的元素和属性,即使面对在用于某些其他软件包的标记使用相同的元素名称或属性名称时发生的“冲突”。

这些考虑要求文档构造应该具有构造的名称,以避免来自不同标记词汇的名称之间的冲突。本规范描述了一种机制,即 XML 名称空间,它通过为元素和属性分配扩展名称来实现这一点。

也就是说,命名空间应该用来确保你知道你的文档在说什么:那个<head>元素是在谈论 XHTML 文档的序言还是 AnatomyML 文档中的某些人?您永远不会“假定”对名称空间不可知,这几乎是您应该在任何 XML 词汇表中定义的第一件事。

应该可以做你想做的事,但我不认为它可以在单个 XPath 表达式中完成。首先,您需要在文档中翻找并提取所有命名空间URI,然后将它们添加到命名空间管理器中,然后运行您想要的实际 XPath 表达式(并且您需要了解有关文档中命名空间分布的信息)点,或者你有很多表达式要运行)。我认为您可能最好使用 XPath 以外的东西(例如 DOM 或类似 SAX 的 API)来查找命名空间URI,但您也可以探索 XPath 命名空间轴(在 XPath 1.0 中),使用namespace-uri-from-QName函数(在 XPath 2.0 中)或使用像 Oleg's 这样的表达方式"configuration/*[local-name() = 'MyNode']". 无论如何,我认为最好的办法是尽量避免编写与命名空间无关的 XPath!为什么你不提前知道你的命名空间?你将如何避免匹配你不打算匹配的东西?

编辑 - 你知道 namespaceURI 吗?

所以事实证明,你的问题让我们所有人都感到困惑。显然您知道名称空间 URI,但您不知道 XML 文档中使用的名称空间前缀。实际上,在这种情况下,没有使用命名空间前缀,并且 URI 成为定义它的默认命名空间。要知道的关键是所选前缀(或缺少前缀)与您的 XPath 表达式(以及一般的 XML 解析)无关。当文档表示为文本时,前缀/xmlns 属性只是将节点与命名空间 URI 相关联的一种方式。您可能想看看这个答案,我在其中尝试澄清名称空间前缀。

您应该尝试以与解析器相同的方式来考虑 XML 文档——每个节点都有一个名称空间 URI 和一个本地名称。命名空间前缀/继承规则只是节省了多次输入 URI。写下来的一种方法是使用 Clark 表示法:也就是说,您编写 { http://www.example.com/namespace/example }LocalNodeName,但这种表示法通常只用于文档 - XPath 对这种表示法一无所知。

相反,XPath 使用自己的命名空间前缀。类似/ns1:root/ns2:node. 但这些与可能在原始 XML 文档中使用的任何前缀完全分开,并且没有任何关系。任何 XPath 实现都可以通过命名空间 URI 映射它自己的前缀。对于 C# 实现,您使用XmlNamespaceManager,在 Perl 中您提供哈希, xmllint 接受命令行参数...所以您需要做的就是为您知道的命名空间 URI 创建一些任意前缀,并在 XPath 表达式中使用此前缀。使用什么前缀并不重要,在 XML 中,您只关心 URI 和 localName 的组合。

另一件要记住的事情(这常常令人惊讶)是 XPath 不进行命名空间继承。无论命名空间是来自继承、xmlns 属性还是命名空间前缀,您都需要为每个具有命名空间的对象添加前缀。此外,尽管您应该始终考虑 URI 和 localNames,但也有一些方法可以从 XML 文档中访问前缀。很少需要使用这些。

于 2010-03-26T19:03:33.367 回答
4

下面是如何使命名空间对 XPathSelectElements 扩展方法中的 XPath 表达式可用的示例:

using System;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Xml;
namespace XPathExpt
{
 class Program
 {
   static void Main(string[] args)
   {
     XElement cfg = XElement.Parse(
       @"<configuration>
          <MyNode xmlns=""lcmp"" attr=""true"">
            <subnode />
          </MyNode>
         </configuration>");
     XmlNameTable nameTable = new NameTable();
     var nsMgr = new XmlNamespaceManager(nameTable);
     // Tell the namespace manager about the namespace
     // of interest (lcmp), and give it a prefix (pfx) that we'll
     // use to refer to it in XPath expressions. 
     // Note that the prefix choice is pretty arbitrary at 
     // this point.
     nsMgr.AddNamespace("pfx", "lcmp");
     foreach (var el in cfg.XPathSelectElements("//pfx:MyNode", nsMgr))
     {
         Console.WriteLine("Found element named {0}", el.Name);
     }
   }
 }
}
于 2010-03-26T18:06:11.577 回答
1

Xpath 2.0 + 库的示例:

using Wmhelp.XPath2;

doc.XPath2SelectElements("/*:configuration/*:MyNode");

看 :

.NET 的 XPath 和 XSLT 2.0?

于 2014-03-19T20:45:32.883 回答
1

我喜欢@mads-hansen,他的回答非常好,以至于我写了这些通用实用程序类成员:

    /// <summary>
    /// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query.
    /// </summary>
    /// <param name="childElementName">Name of the child element.</param>
    /// <returns></returns>
    public static string GetLocalNameXPathQuery(string childElementName)
    {
        return GetLocalNameXPathQuery(namespacePrefixOrUri: null, childElementName: childElementName, childAttributeName: null);
    }

    /// <summary>
    /// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query.
    /// </summary>
    /// <param name="namespacePrefixOrUri">The namespace prefix or URI.</param>
    /// <param name="childElementName">Name of the child element.</param>
    /// <returns></returns>
    public static string GetLocalNameXPathQuery(string namespacePrefixOrUri, string childElementName)
    {
        return GetLocalNameXPathQuery(namespacePrefixOrUri, childElementName, childAttributeName: null);
    }

    /// <summary>
    /// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query.
    /// </summary>
    /// <param name="namespacePrefixOrUri">The namespace prefix or URI.</param>
    /// <param name="childElementName">Name of the child element.</param>
    /// <param name="childAttributeName">Name of the child attribute.</param>
    /// <returns></returns>
    /// <remarks>
    /// This routine is useful when namespace-resolving is not desirable or available.
    /// </remarks>
    public static string GetLocalNameXPathQuery(string namespacePrefixOrUri, string childElementName, string childAttributeName)
    {
        if (string.IsNullOrEmpty(childElementName)) return null;

        if (string.IsNullOrEmpty(childAttributeName))
        {
            return string.IsNullOrEmpty(namespacePrefixOrUri) ?
                string.Format("./*[local-name()='{0}']", childElementName)
                :
                string.Format("./*[namespace-uri()='{0}' and local-name()='{1}']", namespacePrefixOrUri, childElementName);
        }
        else
        {
            return string.IsNullOrEmpty(namespacePrefixOrUri) ?
                string.Format("./*[local-name()='{0}']/@{1}", childElementName, childAttributeName)
                :
                string.Format("./*[namespace-uri()='{0}' and local-name()='{1}']/@{2}", namespacePrefixOrUri, childElementName, childAttributeName);
        }
    }
于 2015-11-18T23:13:56.230 回答