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

发布于 2024-07-14 07:46:31 字数 306 浏览 4 评论 0原文

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

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

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

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:

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?

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

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

发布评论

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

评论(14

柳若烟 2024-07-21 07:46:31

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

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

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

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

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);
余罪 2024-07-21 07:46:31

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

XPathVisualizer 是免费的,易于使用。

替代文本

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

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

XPathVisualizer is free, easy to use.

alt text

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

ぇ气 2024-07-21 07:46:31

对于任何寻求快速破解解决方案的人来说,尤其是在您了解 XML 并且不需要担心名称空间等的情况下,您可以通过简单地绕过这个烦人的小“功能”将文件读取为字符串并替换攻击性属性:

XmlDocument doc = new XmlDocument();
string fileData = File.ReadAllText(fileName);
fileData = fileData.Replace(" xmlns=\"", " whocares=\"");
using (StringReader sr = new StringReader(fileData))
{
   doc.Load(sr);
}

XmlNodeList nodeList = doc.SelectNodes("project/property");

我发现这比处理单个文件时需要默认名称空间前缀的所有其他无意义的操作更容易。 希望这可以帮助。

For anyone looking for a quick hack solution, especially in those cases where you know the XML and don't need to worry about namespaces and all that, you can get around this annoying little "feature" by simply reading the file to a string and replacing the offensive attribute:

XmlDocument doc = new XmlDocument();
string fileData = File.ReadAllText(fileName);
fileData = fileData.Replace(" xmlns=\"", " whocares=\"");
using (StringReader sr = new StringReader(fileData))
{
   doc.Load(sr);
}

XmlNodeList nodeList = doc.SelectNodes("project/property");

I find this easier than all the other non-sense requiring a prefix for a default namespace when I'm dealing with a single file. Hope this helps.

秋凉 2024-07-21 07:46:31

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

  • 提供您自己的 XmlNamespaceManager

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

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

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

请参阅 XmlNamespaceManager 和 UndefinedXsltContext 不'处理默认命名空间以获取更多信息。

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

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

  • provide your own XmlNamespaceManager

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

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.

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

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

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.

白色秋天 2024-07-21 07:46:31

您可以使用 XPath 语句而不使用 XmlNamespaceManager,如下所示:

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

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

重点是使用:

namespace-uri() = ''

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

You can use XPath statement without using XmlNamespaceManager like this:

...
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:

namespace-uri() = ''

which will found element with default namespace without using prefixes.

终难愈 2024-07-21 07:46:31

我的回答扩展了布兰登之前的回答。 我使用他的示例创建了一个扩展方法,如下所示:

static public class XmlDocumentExt
{
    static public XmlNamespaceManager GetPopulatedNamespaceMgr(this System.Xml.XmlDocument xd)
    {
        XmlNamespaceManager nmsp = new XmlNamespaceManager(xd.NameTable);
        XPathNavigator nav = xd.DocumentElement.CreateNavigator();
        foreach (KeyValuePair<string,string> kvp in nav.GetNamespacesInScope(XmlNamespaceScope.All))
        {
            string sKey = kvp.Key;
            if (sKey == "")
            {
                sKey = "default";
            }
            nmsp.AddNamespace(sKey, kvp.Value);
        }

        return nmsp;
    }
}

然后在我的 XML 解析代码中,我只添加一行:

XmlDocument xdCandidate = new XmlDocument();
xdCandidate.Load(sCandidateFile);
XmlNamespaceManager nmsp = xdCandidate.GetPopulatedNamespaceMgr();  // 1-line addition
XmlElement xeScoreData = (XmlElement)xdCandidate.SelectSingleNode("default:ScoreData", nmsp);

我真的很喜欢这个方法,因为它在从源 XML 文件加载命名空间方面是完全动态的,而且它并没有完全忽视 XML 命名空间的概念,因此它可以与需要多个命名空间来消除冲突的 XML 一起使用。

My answer extends the previous answer by Brandon. I used his example to create an extension method as follows:

static public class XmlDocumentExt
{
    static public XmlNamespaceManager GetPopulatedNamespaceMgr(this System.Xml.XmlDocument xd)
    {
        XmlNamespaceManager nmsp = new XmlNamespaceManager(xd.NameTable);
        XPathNavigator nav = xd.DocumentElement.CreateNavigator();
        foreach (KeyValuePair<string,string> kvp in nav.GetNamespacesInScope(XmlNamespaceScope.All))
        {
            string sKey = kvp.Key;
            if (sKey == "")
            {
                sKey = "default";
            }
            nmsp.AddNamespace(sKey, kvp.Value);
        }

        return nmsp;
    }
}

Then in my XML parsing code, I just add a single line:

XmlDocument xdCandidate = new XmlDocument();
xdCandidate.Load(sCandidateFile);
XmlNamespaceManager nmsp = xdCandidate.GetPopulatedNamespaceMgr();  // 1-line addition
XmlElement xeScoreData = (XmlElement)xdCandidate.SelectSingleNode("default:ScoreData", nmsp);

I really like this method because it is completely dynamic in terms of loading the namespaces from the source XML file, and it doesn't completely disregard the concept of XML namespaces so this can be used with XML that requires multiple namespaces for deconfliction.

是伱的 2024-07-21 07:46:31

我遇到了类似的问题,默认命名空间为空。 在此示例 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>

我尝试使用在 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;
  }

我将其范围缩小到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;
  }

经过多次绞尽脑汁和谷歌搜索(这让我来到这里),我决定直接在我的 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 );
    }
  }

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

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:

<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:

  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:

  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:

  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.

时光无声 2024-07-21 07:46:31

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

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

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);
一腔孤↑勇 2024-07-21 07:46:31

就我而言,添加前缀是不切实际的。 太多的 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;
    }
}

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

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

希望这有帮助

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.

    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

抚你发端 2024-07-21 07:46:31

我使用了上面 SpikeDog 描述的虽然简单但有用的方法。 它运行得很好,直到我向它添加一个使用管道组合多个路径的 xpath 表达式。

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

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

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.

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);
                });
}
萤火眠眠 2024-07-21 07:46:31

或者,如果有人像我一样应该使用 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);

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

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);
陌上青苔 2024-07-21 07:46:31

1] 如果您的 XML 文件在命名空间中没有任何前缀:

<bookstore xmlns="http://www.contoso.com/books">
…
</bookstore>

您有以下解决方法:

XmlTextReader reader = new XmlTextReader(@"C:\Temp\books.xml");
// ignore the namespace as there is a single default namespace:
reader.Namespaces = false;
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XPathNodeIterator nodes = navigator.Select("//book");

2] 如果您的 XML 文件在命名空间中具有前缀:

<bookstore xmlns:ns="http://www.contoso.com/books">
…
</bookstore>

使用此方法:

XmlTextReader reader = new XmlTextReader(@"C:\Temp\books.xml");
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XPathNodeIterator nodes = navigator.Select("//book");

当然,如果需要,您可以使用命名空间管理:

XmlTextReader reader = new XmlTextReader(@"C:\Temp\books.xml");
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(reader.NameTable);
nsmgr.AddNamespace("ns", "http://www.contoso.com/book");
XPathNodeIterator nodes = navigator.Select("//book", nsmgr);

我认为这是使代码在大多数情况下工作的最简单方法。

我希望这有助于解决这个微软问题......

1] If you have a XML file without any prefix in the namespace:

<bookstore xmlns="http://www.contoso.com/books">
…
</bookstore>

you have this workaround:

XmlTextReader reader = new XmlTextReader(@"C:\Temp\books.xml");
// ignore the namespace as there is a single default namespace:
reader.Namespaces = false;
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XPathNodeIterator nodes = navigator.Select("//book");

2] If you have a XML file with a prefix in the namespace:

<bookstore xmlns:ns="http://www.contoso.com/books">
…
</bookstore>

Use this:

XmlTextReader reader = new XmlTextReader(@"C:\Temp\books.xml");
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XPathNodeIterator nodes = navigator.Select("//book");

Of course, you can use a namespace manage if needed:

XmlTextReader reader = new XmlTextReader(@"C:\Temp\books.xml");
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(reader.NameTable);
nsmgr.AddNamespace("ns", "http://www.contoso.com/book");
XPathNodeIterator nodes = navigator.Select("//book", nsmgr);

I think that it's the easiest way to make the code working in the most cases.

I hope this help to solve this Microsoft issue…

单身情人 2024-07-21 07:46:31

这个问题至今仍困扰着我。 我现在已经做了一些测试,所以希望我能帮助你。

这是来自 Microsoft 的来源,是问题的关键

重要的一段在这里:

XPath 将空前缀视为空命名空间。 换句话说,只有映射到名称空间的前缀才能在 XPath 查询中使用。 这意味着,如果要查询 XML 文档中的命名空间,即使它是默认命名空间,也需要为其定义前缀。

本质上,您必须记住 XPath 解析器使用命名空间 URI - 其设计是前缀可互换。 就是这样,在编程时,您可以分配我们想要的任何前缀 - 只要 URI 匹配即可。

为了清楚起见,通过示例:

示例 A:

<data xmlns:nsa="http://example.com/ns"><nsa:a>World</nsa:a></data>

它具有 NULL 默认 URI(xmlns= 未定义)。 因此 /data/nsa:a 返回“World”。

示例 B:

<data xmlns:nsa="http://example.com/ns" xmlns="https://standardns/"><nsa:a>World</nsa:a></data>

此文档具有命名的默认前缀 https://standardns/。 因此,使用 /data/nsa:a 执行 XPathNavigator.Execute 不会返回任何结果。 MS 认为 data 的 XML namespace uri 应该为 NULL,而 data 的命名空间 URI 实际上是“https://standardns/” 。 本质上,XPath 正在寻找 /NULL:data/nsa:a - 尽管这不起作用,因为您不能将 NULL URI 作为“NULL”作为前缀引用。 NULL 前缀是所有 XPath 中的默认前缀 - 因此出现了问题。

我们如何解决这个问题?

XmlNamespaceManager result = new XmlNamespaceManager(xDoc.NameTable);
result.AddNamespace("DEFAULT", "https://standardns/");
result.AddNamespace("nsa", "http://example.com/ns");

这样,我们现在可以将 a 引用为 /DEFAULT:data/nsa:a

示例 C:

<data><a xmlns="https://standardns/">World</a></data>

在此示例中,data 位于空命名空间。 a 位于默认命名空间“https://standardns/”中。 根据 Microsoft 的说法,/data/a 不应该工作,因为 a 位于 NS https://standardns/data 中 位于命名空间 NULL 中。 因此 被隐藏(除非通过执行奇怪的“忽略名称空间”黑客)并且无法按原样选择。 这本质上是根本原因 - 您不应该能够选择两者都没有前缀的“a”和“data”,因为这会假设它们位于同一名称空间中,但事实并非如此!

我们如何解决这个问题?

XmlNamespaceManager result = new XmlNamespaceManager(xDoc.NameTable);
result.AddNamespace("DEFAULT", "https://standardns/");

这样,我们现在可以将 a 引用为 /data/DEFAULT:a,因为 data 是从 NULL 命名空间中选择的,而 a 是从新前缀“DEFAULT”中选择的。 此示例中重要的是命名空间前缀不需要保持不变。 在代码中引用具有不同前缀的 URI 命名空间是完全可以接受的,就像您正在处理的文档中写入的内容一样。

希望这可以帮助一些人!

This one still keeps bugging me. I've done some testing now, so hopefully I can help you with this.

This is the source from Microsoft, which is the key to the problem

The important paragraph is here:

XPath treats the empty prefix as the null namespace. In other words, only prefixes mapped to namespaces can be used in XPath queries. This means that if you want to query against a namespace in an XML document, even if it is the default namespace, you need to define a prefix for it.

In essence, you have to remember the XPath parser uses the Namespace URI - with the design that the prefix is interchangeable. This is so, when programming, you can assign whatever prefix we want - as long as the URI matches.

For clarity with examples:

Example A:

<data xmlns:nsa="http://example.com/ns"><nsa:a>World</nsa:a></data>

This has a NULL default URI (xmlns= is not defined). Because of this /data/nsa:a returns "World".

Example B:

<data xmlns:nsa="http://example.com/ns" xmlns="https://standardns/"><nsa:a>World</nsa:a></data>

This document has a named default prefix https://standardns/. XPathNavigator.Execute with /data/nsa:a therefore returns no results. MS considers that the XML namespace uri for data should be NULL, and the namespace URI for data is actually "https://standardns/". Essentially XPath is looking for /NULL:data/nsa:a - although this won't work, as you can't refer to the NULL URI as "NULL" as a prefix. NULL prefix is the default in all XPath - hence the issue.

How do we solve this?

XmlNamespaceManager result = new XmlNamespaceManager(xDoc.NameTable);
result.AddNamespace("DEFAULT", "https://standardns/");
result.AddNamespace("nsa", "http://example.com/ns");

In this way, we can now refer to a as /DEFAULT:data/nsa:a

Example C:

<data><a xmlns="https://standardns/">World</a></data>

In this example data is in the NULL namespace. a is in the default namespace "https://standardns/". /data/a should not work, according to Microsoft, because a is in the NS https://standardns/ and data is in the namespace NULL. <a> is therefore hidden (except by doing weird "ignore the namespace" hacks) and cannot be selected upon as-is. This is essentially the root cause - you should not be able to select "a" and "data" with no prefixes for both, as this would assume that they were in the same namespace, and they aren't!

How do we solve this?

XmlNamespaceManager result = new XmlNamespaceManager(xDoc.NameTable);
result.AddNamespace("DEFAULT", "https://standardns/");

In this way, we can now refer to a as /data/DEFAULT:a as data is selected from the NULL namespace, and a is selected from the new prefix "DEFAULT". The important thing in this example is that the namespace prefix does not need to remain the same. It's perfectly acceptable to refer to a URI namespace with a different prefix in your code, as to what is written in the document you are processing.

Hope this helps some people!

夏末染殇 2024-07-21 07:46:31

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

这是使用 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);

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.

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