C# 使用XmlDocument和XPath - 无法选择带命名空间的节点(返回null)

4

我希望做的事情本应该很简单,但我却遇到了很大的麻烦。我尝试了来自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");

但是无论我在xpath中输入什么,它总是返回null,无论我选择带有子节点、值或不带这些的内容,都会返回节点为null,列表计数为0。我可以使用GetElementsByTagName()来获取节点,如示例所示,它将返回正确的节点,但我想用xpath选择适当的字段来完成它。我还尝试使用XElement和Linq,但仍然没有成功。这个XML有什么奇怪的地方吗?我相信这一定是一些简单的问题,但我已经苦苦挣扎了很长时间。
2个回答

6
你没有处理文档中存在的命名空间。具体来说,是高级元素:
<ABRSearchByABNResponse xmlns="http://abr.business.gov.au/ABRXMLSearch/">

ABRSearchByABNResponse及其所有子元素(除非被另一个xmlns覆盖)放入命名空间http://abr.business.gov.au/ABRXMLSearch/。为了浏览这些节点(而不使用GetElementsByTagNamelocal-name()等技巧),您需要使用XmlNamespaceManager注册命名空间,如下所示。 xmlns别名不一定需要与原始文档中使用的别名匹配,但最好这样做:

XmlDocument

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

最近,可以使用 XDocument 发挥 LINQ 的能力,使得在命名空间中工作变得更加容易(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);

顺便提一下,我注意到你使用“var”而不是特定的类型。这是最佳实践还是你的个人偏好?我不怎么编程,所以会接受那些有经验的人的任何建议。 - Chris H
var 是强类型隐式类型,几乎所有的函数式语言都使用它。使用它还是不使用它是一个伟大的宗教争议。现在几乎所有的代码都使用 var - 你可以在 IDE 中悬停在变量上以查看其类型。这将把重点放在给变量一个合适的名称上(我还没有真正做到 - 害羞)。 - StuartLC
我指的是几乎“所有我的代码”。并不想影响你。 - StuartLC

1
你需要在DocumentElement上调用SelectSingleNode和SelectNodes方法,而不是在文档本身上调用。
例如:
XmlNode xnode = xdoc.DocumentElement.SelectSingleNode("//response");

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接