在 C# 中使用带有默认命名空间的 Xpath

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/585812/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 09:15:01  来源:igfitidea点击:

Using Xpath With Default Namespace in C#

c#xmlxpathnamespacesxpathnavigator

提问by macleojw

I've got an XML document with a default namespace. I'm using a XPathNavigator to select a set of nodes using Xpath as follows:

我有一个带有默认命名空间的 XML 文档。我正在使用 XPathNavigator 使用 Xpath 选择一组节点,如下所示:

XmlElement myXML = ...;  
XPathNavigator navigator = myXML.CreateNavigator();
XPathNodeIterator result = navigator.Select("/outerelement/innerelement");

I am not getting any results back: I'm assuming this is because I am not specifying the namespace. How can I include the namespace in my select?

我没有得到任何结果:我假设这是因为我没有指定命名空间。如何在我的选择中包含命名空间?

采纳答案by Marc Gravell

First - you don't need a navigator; SelectNodes / SelectSingleNode should suffice.

首先 - 你不需要导航器;SelectNodes / SelectSingleNode 应该足够了。

You may, however, need a namespace-manager - for example:

但是,您可能需要一个命名空间管理器——例如:

XmlElement el = ...; //TODO
XmlNamespaceManager nsmgr = new XmlNamespaceManager(
    el.OwnerDocument.NameTable);
nsmgr.AddNamespace("x", el.OwnerDocument.DocumentElement.NamespaceURI);
var nodes = el.SelectNodes(@"/x:outerelement/x:innerelement", nsmgr);

回答by Cerebrus

In this case, it is probably namespace resolution which is the cause of the problem, but it is also possible that your XPath expression is not correct in itself. You may want to evaluate it first.

在这种情况下,问题的原因可能是名称空间解析,但也可能是您的 XPath 表达式本身不正确。您可能想先对其进行评估。

Here is the code using an XPathNavigator.

这是使用 XPathNavigator 的代码。

//xNav is the created XPathNavigator.
XmlNamespaceManager mgr = New XmlNamespaceManager(xNav.NameTable);
mgr.AddNamespace("prefix", "http://tempuri.org/");

XPathNodeIterator result = xNav.Select("/prefix:outerelement/prefix:innerelement", mgr);

回答by SO User

In case the namespaces differ for outerelement and innerelement

如果外部元素和内部元素的命名空间不同

XmlNamespaceManager manager = new XmlNamespaceManager(myXmlDocument.NameTable);
                            manager.AddNamespace("o", "namespaceforOuterElement");
                            manager.AddNamespace("i", "namespaceforInnerElement");
string xpath = @"/o:outerelement/i:innerelement"
// For single node value selection
XPathExpression xPathExpression = navigator.Compile(xpath );
string reportID = myXmlDocument.SelectSingleNode(xPathExpression.Expression, manager).InnerText;

// For multiple node selection
XmlNodeList myNodeList= myXmlDocument.SelectNodes(xpath, manager);

回答by Cheeso

You might want to try an XPath Visualizer tool to help you through.

您可能想尝试使用 XPath Visualizer 工具来帮助您完成。

XPathVisualizeris free, easy to use.

XPathVisualizer是免费的,易于使用。

alt text

替代文字

IMPORTANT: If you are using Windows 7/8 and don't see File, Edit and Help Menu items, please press ALT key.

重要提示:如果您使用的是 Windows 7/8 并且没有看到文件、编辑和帮助菜单项,请按 ALT 键。

回答by SpikeDog

In my case adding a prefix wasn't practical. Too much of the xml or xpath were determined at runtime. Eventually I extended the methds on XmlNode. This hasn't been optimised for performance and it probably doesn't handle every case but it's working for me so far.

在我的情况下,添加前缀是不切实际的。太多的 xml 或 xpath 是在运行时确定的。最终我扩展了 XmlNode 上的方法。这还没有针对性能进行优化,它可能无法处理所有情况,但到目前为止它对我有用。

    public static class XmlExtenders
{

    public static XmlNode SelectFirstNode(this XmlNode node, string xPath)
    {
        const string prefix = "pfx";
        XmlNamespaceManager nsmgr = GetNsmgr(node, prefix);
        string prefixedPath = GetPrefixedPath(xPath, prefix);
        return node.SelectSingleNode(prefixedPath, nsmgr);
    }

    public static XmlNodeList SelectAllNodes(this XmlNode node, string xPath)
    {
        const string prefix = "pfx";
        XmlNamespaceManager nsmgr = GetNsmgr(node, prefix);
        string prefixedPath = GetPrefixedPath(xPath, prefix);
        return node.SelectNodes(prefixedPath, nsmgr);
    }

    public static XmlNamespaceManager GetNsmgr(XmlNode node, string prefix)
    {
        string namespaceUri;
        XmlNameTable nameTable;
        if (node is XmlDocument)
        {
            nameTable = ((XmlDocument) node).NameTable;
            namespaceUri = ((XmlDocument) node).DocumentElement.NamespaceURI;
        }
        else
        {
            nameTable = node.OwnerDocument.NameTable;
            namespaceUri = node.NamespaceURI;
        }
        XmlNamespaceManager nsmgr = new XmlNamespaceManager(nameTable);
        nsmgr.AddNamespace(prefix, namespaceUri);
        return nsmgr;
    }

    public static string GetPrefixedPath(string xPath, string prefix)
    {
        char[] validLeadCharacters = "@/".ToCharArray();
        char[] quoteChars = "\'\"".ToCharArray();

        List<string> pathParts = xPath.Split("/".ToCharArray()).ToList();
        string result = string.Join("/",
                                    pathParts.Select(
                                        x =>
                                        (string.IsNullOrEmpty(x) ||
                                         x.IndexOfAny(validLeadCharacters) == 0 ||
                                         (x.IndexOf(':') > 0 &&
                                          (x.IndexOfAny(quoteChars) < 0 || x.IndexOfAny(quoteChars) > x.IndexOf(':'))))
                                            ? x
                                            : prefix + ":" + x).ToArray());
        return result;
    }
}

Then in your code just use something like

然后在你的代码中使用类似的东西

        XmlDocument document = new XmlDocument();
        document.Load(pathToFile);
        XmlNode node = document.SelectFirstNode("/rootTag/subTag");

Hope this helps

希望这可以帮助

回答by Tomek Szpakowicz

When using XPath in .NET (via a navigator or SelectNodes/SelectSingleNode) on XML with namespaces you need to:

在 .NET 中使用 XPath 时(通过导航器或 SelectNodes/SelectSingleNode)在带有命名空间的 XML 上,您需要:

  • provide your own XmlNamespaceManager

  • andexplicitly prefix all elements in XPath expression, which are in namespace.

  • 提供您自己的 XmlNamespaceManager

  • 在 XPath 表达式中为命名空间中的所有元素显式添加前缀。

The latter is (paraphrased from MS source linked below): because XPath 1.0 ignores default namespace specifications (xmlns="some_namespace"). So when you use element name without prefix it assumes null namespace.

后者是(从下面链接的 MS 源中转述):因为 XPath 1.0 忽略默认命名空间规范 (xmlns="some_namespace")。因此,当您使用不带前缀的元素名称时,它假定命名空间为空。

That's why .NET implementation of XPath ignores namespace with prefix String.Empty in XmlNamespaceManager and allways uses null namespace.

这就是为什么 XPath 的 .NET 实现会忽略 XmlNamespaceManager 中带有前缀 String.Empty 的命名空间并始终使用空命名空间。

See XmlNamespaceManager and UndefinedXsltContext don't handle default namespacefor more information.

有关详细信息,请参阅XmlNamespaceManager 和 UndefinedXsltContext 不处理默认命名空间

I find this "feature" very inconvenient because you cannot make old XPath namespace-aware by simply adding default namespace declaration, but that's how it works.

我发现这个“功能”非常不方便,因为您不能通过简单地添加默认命名空间声明来使旧的 XPath 命名空间感知,但这就是它的工作原理。

回答by Dan

I used the hacky-but-useful approach described by SpikeDog above. It worked very well until I threw an xpath expression at it that used pipes to combine multiple paths.

我使用了上面 SpikeDog 描述的 hacky-but-useful 方法。它工作得很好,直到我向它抛出一个 xpath 表达式,它使用管道来组合多个路径。

So I rewrote it using regular expressions, and thought I'd share:

所以我用正则表达式重写了它,并认为我会分享:

public string HackXPath(string xpath_, string prefix_)
{
    return System.Text.RegularExpressions.Regex.Replace(xpath_, @"(^(?![A-Za-z0-9\-\.]+::)|[A-Za-z0-9\-\.]+::|[@|/|\[])(?'Expression'[A-Za-z][A-Za-z0-9\-\.]*)", x =>
                {
                    int expressionIndex = x.Groups["Expression"].Index - x.Index;
                    string before = x.Value.Substring(0, expressionIndex);
                    string after = x.Value.Substring(expressionIndex, x.Value.Length - expressionIndex);
                    return String.Format("{0}{1}:{2}", before, prefix_, after);
                });
}

回答by Brandon

I encountered a similar problem with a blank default namespace. In this example XML, I have a mix of elements with namespace prefixes, and a single element (DataBlock) without:

我遇到了一个与空白默认命名空间类似的问题。在这个示例 XML 中,我混合了带有命名空间前缀的元素,以及一个没有以下内容的元素 (DataBlock):

<src:SRCExample xmlns="urn:some:stuff:here" xmlns:src="www.test.com/src" xmlns:a="www.test.com/a" xmlns:b="www.test.com/b">
 <DataBlock>
  <a:DocID>
   <a:IdID>7</a:IdID>
  </a:DocID>
  <b:Supplimental>
   <b:Data1>Value</b:Data1>
   <b:Data2/>
   <b:Extra1>
    <b:More1>Value</b:More1>
   </b:Extra1>
  </b:Supplimental>
 </DataBlock>
</src:SRCExample>

I attempted to use an XPath that worked in XPath Visualizer, but did not work in my code:

我尝试使用在 XPath Visualizer 中工作的 XPath,但在我的代码中不起作用:

  XmlDocument doc = new XmlDocument();
  doc.Load( textBox1.Text );
  XPathNavigator nav = doc.DocumentElement.CreateNavigator();
  XmlNamespaceManager nsman = new XmlNamespaceManager( nav.NameTable );
  foreach ( KeyValuePair<string, string> nskvp in nav.GetNamespacesInScope( XmlNamespaceScope.All ) ) {
    nsman.AddNamespace( nskvp.Key, nskvp.Value );
  }

  XPathNodeIterator nodes;

  XPathExpression failingexpr = XPathExpression.Compile( "/src:SRCExample/DataBlock/a:DocID/a:IdID" );
  failingexpr.SetContext( nsman );
  nodes = nav.Select( failingexpr );
  while ( nodes.MoveNext() ) {
    string testvalue = nodes.Current.Value;
  }

I narrowed it down to the "DataBlock" element of the XPath, but couldn't make it work except by simply wildcarding the DataBlock element:

我将它缩小到 XPath 的“DataBlock”元素,但无法使其工作,除非简单地通配 DataBlock 元素:

  XPathExpression workingexpr = XPathExpression.Compile( "/src:SRCExample/*/a:DocID/a:IdID" );
  failingexpr.SetContext( nsman );
  nodes = nav.Select( failingexpr );
  while ( nodes.MoveNext() ) {
    string testvalue = nodes.Current.Value;
  }

After much headscratching and googling (which landed me here) I decided to tackle the default namespace directly in my XmlNamespaceManager loader by changing it to:

经过大量的头疼和谷歌搜索(这让我来到这里),我决定直接在我的 XmlNamespaceManager 加载器中处理默认命名空间,将其更改为:

  foreach ( KeyValuePair<string, string> nskvp in nav.GetNamespacesInScope( XmlNamespaceScope.All ) ) {
    nsman.AddNamespace( nskvp.Key, nskvp.Value );
    if ( nskvp.Key == "" ) {
      nsman.AddNamespace( "default", nskvp.Value );
    }
  }

So now "default" and "" point to the same namespace. Once I did this, the XPath "/src:SRCExample/default:DataBlock/a:DocID/a:IdID" returned my results just like I wanted. Hopefully this helps to clarify the issue for others.

所以现在“default”和“”指向同一个命名空间。一旦我这样做了,XPath "/src:SRCExample/default:DataBlock/a:DocID/a:IdID" 就像我想要的那样返回我的结果。希望这有助于为其他人澄清问题。

回答by Bartosz W?gielewski

You can use XPath statement without using XmlNamespaceManager like this:

您可以像这样使用 XPath 语句而不使用 XmlNamespaceManager:

...
navigator.Select("//*[ local-name() = 'innerelement' and namespace-uri() = '' ]")
...

That is a simple way of selecting element within XML with default namespace definied.

The point is to use:

这是在定义了默认命名空间的 XML 中选择元素的简单方法。

重点是使用:

namespace-uri() = ''

which will found element with default namespace without using prefixes.

它将在不使用前缀的情况下找到具有默认命名空间的元素。

回答by Zak

Or, if anyone should be using an XPathDocument, like me:

或者,如果有人应该像我一样使用 XPathDocument:

XPathDocument xdoc = new XPathDocument(file);
XPathNavigator nav = xdoc.CreateNavigator();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(nav.NameTable);
nsmgr.AddNamespace("y", "http://schemas.microsoft.com/developer/msbuild/2003");
XPathNodeIterator nodeIter = nav.Select("//y:PropertyGroup", nsmgr);