首页 > 解决方案 > C# 将 XPath 与 XmlDocument 一起使用 - 无法选择命名空间中的节点(返回 null)

问题描述

我正在尝试做一些应该很简单的事情,但我遇到了可怕的麻烦。我已经尝试过 StackOverflow 中多个类似问题的代码,但无济于事。我正在尝试从澳大利亚政府的 ABN 查询中获取各种信息。这是匿名返回 XML 值:

    <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
        <ABRSearchByABNResponse xmlns="http://abr.business.gov.au/ABRXMLSearch/">
            <ABRPayloadSearchResults>
                <request>
                    <identifierSearchRequest>
                        <authenticationGUID>00000000-0000-0000-0000-000000000000</authenticationGUID>
                        <identifierType>ABN</identifierType>
                        <identifierValue>00 000 000 000</identifierValue>
                        <history>N</history>
                    </identifierSearchRequest>
                </request>
                <response>
                    <usageStatement>The Registrar of the ABR monitors the quality of the information available on this website and updates the information regularly. However, neither the Registrar of the ABR nor the Commonwealth guarantee that the information available through this service (including search results) is accurate, up to date, complete or accept any liability arising from the use of or reliance upon this site.</usageStatement>
                    <dateRegisterLastUpdated>2017-01-01</dateRegisterLastUpdated>
                    <dateTimeRetrieved>2017-01-01T00:00:00.2016832+10:00</dateTimeRetrieved>
                    <businessEntity>
                        <recordLastUpdatedDate>2017-01-01</recordLastUpdatedDate>
                        <ABN>
                            <identifierValue>00000000000</identifierValue>
                            <isCurrentIndicator>Y</isCurrentIndicator>
                            <replacedFrom>0001-01-01</replacedFrom>
                        </ABN>
                        <entityStatus>
                            <entityStatusCode>Active</entityStatusCode>
                            <effectiveFrom>2017-01-01</effectiveFrom>
                            <effectiveTo>0001-01-01</effectiveTo>
                        </entityStatus>
                        <ASICNumber>000000000</ASICNumber>
                        <entityType>
                            <entityTypeCode>PRV</entityTypeCode>
                            <entityDescription>Australian Private Company</entityDescription>
                        </entityType>
                        <goodsAndServicesTax>
                            <effectiveFrom>2017-01-01</effectiveFrom>
                            <effectiveTo>0001-01-01</effectiveTo>
                        </goodsAndServicesTax>
                        <mainName>
                            <organisationName>COMPANY LTD</organisationName>
                            <effectiveFrom>2017-01-01</effectiveFrom>
                        </mainName>
                        <mainBusinessPhysicalAddress>
                            <stateCode>NSW</stateCode>
                            <postcode>0000</postcode>
                            <effectiveFrom>2017-01-01</effectiveFrom>
                            <effectiveTo>0001-01-01</effectiveTo>
                        </mainBusinessPhysicalAddress>
                    </businessEntity>
                </response>
            </ABRPayloadSearchResults>
        </ABRSearchByABNResponse>
    </soap:Body>
</soap:Envelope>

所以我想获取例如整个响应,xpath="//response"然后使用该节点中的各种 xpath 语句来获取<organisationName>("//mainName/organisationName") 和其他值。应该很简单吧?在 Notepad++ 中进行测试时,这些 xpath 语句似乎可以工作,但我在 Visual Studio 中使用此代码:

XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(ipxml);
XmlNode xnode = xdoc.SelectSingleNode("//response");
XmlNodeList xlist = xdoc.SelectNodes("//mainName/organisationName");
xlist = xdoc.GetElementsByTagName("mainName");

但它总是返回 null,无论我在 xpath 中放置什么,我都会得到节点的null返回值和列表的 0 计数,无论我选择带有子节点的东西,值与否。我可以使用GetElementsByTagName()返回正确节点的示例中的节点来获取节点,但我想“正确地”使用 xpath 选择正确的字段。

我也尝试过使用 XElement 和 Linq,但仍然没有运气。XML 有什么奇怪的地方吗?

我敢肯定它一定很简单,但我已经挣扎了很多年。

标签: c#xmlxpath

解决方案


您没有处理文档中存在的名称空间。具体来说,高级元素:

<ABRSearchByABNResponse xmlns="http://abr.business.gov.au/ABRXMLSearch/">

placeABRSearchByABNResponse及其所有子元素(除非被另一个元素覆盖xmlns)进入命名空间http://abr.business.gov.au/ABRXMLSearch/。为了导航到这些节点(不使用 hackGetElementsByTagName或 using local-name()),您需要使用 注册命名空间XmlNamespaceManager,就像这样。xmlns别名不一定需要与原始文档中使用的别名相匹配,但这样做是一个很好的约定:

xml文档

var xdoc = new XmlDocument();
var ns = new XmlNamespaceManager(xdoc.NameTable);
ns.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");
ns.AddNamespace("abr", "http://abr.business.gov.au/ABRXMLSearch/");

xdoc.LoadXml(ipxml);
// NB need to use the overload accepting a namespace
var xresponse = xdoc.SelectSingleNode("//abr:response", ns);
var xlist = xdoc.SelectNodes("//abr:mainName/abr:organisationName", ns);

XDocument

最近,LINQ 的强大功能可以与XDocument一起使用,这使得使用命名空间变得更加容易(Descendants在任何深度查找子节点)

var xdoc = XDocument.Parse(ipxml);
XNamespace soap = "http://schemas.xmlsoap.org/soap/envelope/";
XNamespace abr = "http://abr.business.gov.au/ABRXMLSearch/";

var xresponse = xdoc.Descendants(abr + "response");
var xlist = xdoc.Descendants(abr + "organisationName");

XDocument + XPath

您还可以在 Linq to Xml中使用 XPath,尤其是对于更复杂的表达式:

var xdoc = XDocument.Parse(ipxml);
var ns = new XmlNamespaceManager(new NameTable());
ns.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");
ns.AddNamespace("abr", "http://abr.business.gov.au/ABRXMLSearch/");

var xresponse = xdoc.XPathSelectElement("//abr:response", ns);
var xlist = xdoc.XPathSelectElement("//abr:mainName/abr:organisationName", ns);

推荐阅读