如何在没有前缀的默认命名空间中使用 XPath?

发布于 2024-08-26 07:43:57 字数 797 浏览 5 评论 0原文

用于从此文档查询所有 MyNode 的 XPath(在 C# API 中为 XDocument.XPathSelectElements(xpath, nsman),如果重要的话)是什么?

<?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 但失败了,因为 其他信息:'/configuration/{lcmp}MyNode' 有一个无效的令牌。

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

What is the XPath (in C# API to XDocument.XPathSelectElements(xpath, nsman) if it matters) to query all MyNodes from this document?

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <MyNode xmlns="lcmp" attr="true">
    <subnode />
  </MyNode>
</configuration>
  • I tried /configuration/MyNode which is wrong because it ignores the namespace.
  • I tried /configuration/lcmp:MyNode which is wrong because lcmp is the URI, not the prefix.
  • I tried /configuration/{lcmp}MyNode which failed because Additional information: '/configuration/{lcmp}MyNode' has an invalid token.

EDIT: I can't use mgr.AddNamespace("df", "lcmp"); as some of the answerers have suggested. That requires that the XML parsing program know all the namespaces I plan to use ahead of time. Since this is meant to be applicable to any source file, I don't know which namespaces to manually add prefixes for. It seems like {my uri} is the XPath syntax, but Microsoft didn't bother implementing that... true?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

不一样的天空 2024-09-02 07:43:57

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

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

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

它与任何元素是 configuration 的子元素,然后使用带有 namespace-uri()local-name() 函数将其限制为 MyNode 元素。

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

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

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

The configuration element is in the unnamed namespace, and the MyNode is bound to the lcmp namespace without a namespace prefix.

This XPATH statement will allow you to address the MyNode element without having declared the lcmp namespace or use a namespace prefix in your XPATH:

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

It matches any element that is a child of configuration and then uses a predicate filer with namespace-uri() and local-name() functions to restrict it to the MyNode element.

If you don't know which namespace-uri's will be used for the elements, then you can make the XPATH more generic and just match on the local-name():

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

However, you run the risk of matching different elements in different vocabularies(bound to different namespace-uri's) that happen to use the same name.

妖妓 2024-09-02 07:43:57

您需要使用 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);
   }

You need to use an XmlNamespaceManager as follows:

   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);
   }
生生漫 2024-09-02 07:43:57

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

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

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

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

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

也就是说,命名空间应该用于确保您知道文档正在谈论什么: 元素是在谈论 XHTML 文档的前导码还是在 AnatomyML 文档中谈论某人的头?您永远“不应该”对命名空间一无所知,而且它几乎是您应该在任何 XML 词汇表中定义的第一件事。

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

编辑 - 你知道namespaceURI吗?

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

您应该尝试以解析器思考 XML 文档的方式思考它 - 每个节点都有一个名称空间 URI 和一个本地名称。命名空间前缀/继承规则只是节省了多次键入 URI 的时间。记下此内容的一种方法是使用克拉克表示法:也就是说,您编写 {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 文档访问前缀。很少需要使用这些。

XPath is (deliberately) not designed for for the case where you want to use the same XPath expression for some unknown namespaces that only live in the XML document. You are expected to know the namespace ahead of time, declare the namespace to the XPath processor, and use the name in your expression. The answers by Martin and Dan show how to do this in C#.

The reason for this difficulty is best expressed in the XML namespaces spec:

We envision applications of Extensible Markup Language (XML) where a single XML document may contain elements and attributes (here referred to as a "markup vocabulary") that are defined for and used by multiple software modules. One motivation for this is modularity: if such a markup vocabulary exists which is well-understood and for which there is useful software available, it is better to re-use this markup rather than re-invent it.

Such documents, containing multiple markup vocabularies, pose problems of recognition and collision. Software modules need to be able to recognize the elements and attributes which they are designed to process, even in the face of "collisions" occurring when markup intended for some other software package uses the same element name or attribute name.

These considerations require that document constructs should have names constructed so as to avoid clashes between names from different markup vocabularies. This specification describes a mechanism, XML namespaces, which accomplishes this by assigning expanded names to elements and attributes.

That is, namespaces are supposed to be used to make sure you know what your document is talking about: is that <head> element talking about the preamble to an XHTML document or somebodies head in an AnatomyML document? You are never "supposed" to be agnostic about the namespace and it's pretty much the first thing you ought to define in any XML vocabulary.

It should be possible to do what you want, but I don't think it can be done in a single XPath expression. First of all you need to rummage around in the document and extract all the namespaceURIs, then add these to the namespace manager and then run the actual XPath expression you want (and you need to know something about the distribution of namespaces in the document at this point, or you have a lot of expressions to run). I think you are probably best using something other than XPath (e.g. a DOM or SAX-like API) to find the namespaceURIs, but you could also explore the XPath namespace-axis (in XPath 1.0), use the namespace-uri-from-QName function (in XPath 2.0) or use expressions like Oleg's "configuration/*[local-name() = 'MyNode']". Anyway, I think your best bet is to try and avoid writing namespace agnostic XPath! Why do you not know your namespace ahead of time? How are you going to avoid matching things you don't intend to match?

Edit - you know the namespaceURI?

So it turns out that your question confused us all. Apparently you know the namespace URI, but you don't know the namespace prefix that's used in the XML document. Indeed, in this case no namespace prefix is used and the URI becomes the default namspace where it is defined. The key thing to know is that the chosen prefix (or lack of a prefix) is irrelevant to your XPath expression (and XML parsing in general). The prefix / xmlns attribute is just one way to associate a node with a namespace URI when the document is expressed as text. You may want to take a look at this answer, where I try and clarify namespace prefixes.

You should try to think of the XML document in the same way the parser thinks of it - each node has a namespace URI and a local name. The namespace prefix / inheritance rules just saves typing the URI out lots of times. One way to write this down is in Clark notation: that is, you write {http://www.example.com/namespace/example}LocalNodeName, but this notation is usually just used for documentation - XPath knows nothing about this notation.

Instead, XPath uses its own namespace prefixes.Something like /ns1:root/ns2:node. But these are completely separate from and nothing to do with any prefixes that may be used in the original XML document. Any XPath implementation will have a way to map it's own prefixes with namespace URIs. For the C# implementation you use an XmlNamespaceManager, in Perl you provide a hash, xmllint takes command line arguments... So all you need to do is create some arbitrary prefix for the namespace URI you know, and use this prefix in the XPath expression. It doesn't matter what prefix you use, in XML you just care about the combination of the URI and the localName.

The other thing to remember (it's often a surprise) is that XPath doesn't do namespace inheritance. You need to add a prefix for every that has a namespace, irrespective of whether the namespace comes from inheritance, an xmlns attribute, or a namespace prefix. Also, although you should always think in terms of URIs and localNames, there are also ways to access the prefix from an XML document. It's rare to have to use these.

你穿错了嫁妆 2024-09-02 07:43:57

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

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);
     }
   }
 }
}

Here's an example of how to make the namespace available to the XPath expression in the
XPathSelectElements extension method:

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);
     }
   }
 }
}
百善笑为先 2024-09-02 07:43:57

Xpath 2.0 + 库的示例:

using Wmhelp.XPath2;

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

请参阅:

适用于 .NET 的 XPath 和 XSLT 2.0?

Example with Xpath 2.0 + a library :

using Wmhelp.XPath2;

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

See :

XPath and XSLT 2.0 for .NET?

蘑菇王子 2024-09-02 07:43:57

我很喜欢@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);
        }
    }

I like @mads-hansen, his answer, so well that I wrote these general-purpose utility-class members:

    /// <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);
        }
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文