当XML具有命名空间时,如何使用XPath动态查询XDocument

本文关键字:XPath 何使用 动态 查询 XDocument XML 命名空间 | 更新日期: 2023-09-27 18:28:00

我一直在使用这个绝对XPath扩展来获取XDocument内部的节点的XPath字符串。随后,XPath可以使用xdoc.XPathSelectElement(xpath)查询该节点。

然而,这对于使用名称空间的XML文档来说是失败的,如以下所示:

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://service.svsxml.svs.com" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <soapenv:Header>
        <wsse:Security soapenv:mustUnderstand="1">
            <wsse:UsernameToken>
                <wsse:Username>Pizza</wsse:Username>
                <wsse:Password>Pie</wsse:Password>
            </wsse:UsernameToken>
        </wsse:Security>
    </soapenv:Header>
</soapenv:Envelope>

Username节点上调用GetAbsoluteXPath给出:

/Envelope/Header[1]/Security[1]/UsernameToken[1]/Username[1]

但是,使用XPathSelectElement和上面的XPath查询XDocument会产生null,因为没有指定命名空间。

根据我查找的答案,解决方案是添加一个NamespaceManager并手动编辑路径。但是,我的路径是动态生成的(我事先不知道XML文档的名称空间或结构),所以手动调整字符串不是一种选择。

我的问题是:

  • 有没有一种方法可以完全忽略名称空间来使用XPath进行查询

类似名称问题的答案是按本地名称而不是XPath进行查询。然而,当多个节点的名称为LocalName(这在我分析的XML中经常出现)时,这将失败,因为按名称搜索只会完全丢弃XPath的特殊性。

为了澄清:我事先不知道XML文档是什么样子的,XPath和名称空间是在运行时确定的,而不是在编译时确定的。因此,手动添加仅适用于此特定示例的字符串通常不会起作用。

当XML具有命名空间时,如何使用XPath动态查询XDocument

下面的代码是xml-linq,将使用一个节点。对于多个节点,您必须添加附加搜索信息才能获得您要查找的确切节点。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
    class Program
    {
        const string FILENAME = @"c:'temp'test.xml";
        static void Main(string[] args)
        {
            XDocument doc = XDocument.Load(FILENAME);
            XElement envelope = (XElement)doc.FirstNode;
            XNamespace wsse =  envelope.GetNamespaceOfPrefix("wsse");
            string username = envelope.Descendants(wsse + "Username").FirstOrDefault().Value;
        }
    }
}
​

我有同样的要求,并找到了这样的解决方案:

XDocument doc = ...
var nsmngr = new XmlNamespaceManager(doc.CreateReader().NameTable);
foreach (var attribute in doc.Descendants().Attributes().Where(a => a.IsNamespaceDeclaration))
{
    nsmngr.AddNamespace(attribute.Name.LocalName, attribute.Value);
}
// then use the namespacemanager when querying the document
var someXPath = "/Envelope/Header[1]/Security[1]/UsernameToken[1]/Username[1]";
doc.XPathEvaluate(someXPath, nsmngr)