显示为星号的 DOMXPath 路径

DOMXPath paths showing up as asterisks

我有一个 XML 字符串,片段如下:

<?xml version="1.0"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
       xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <SendPurchases xmlns="urn:services.insurance">
            <Partner>
                <UserID>MyCompany</UserID>
                <Password>ABC123</Password>
            </Partner>
            <PurchasesRequest>
                <Total>100</Total> 
            </PurchasesRequest>
        </SendPurchases>
    </soap:Body>
</soap:Envelope>

我正在将 XML 转换为 DOMDocument 以进行操作 "easier":

$doc = new DOMDocument();
$doc->loadXML($xml);
$xpath = new DOMXPath($doc);

然后我想操纵某些值,最好使用它们的路径:

$elements = $xpath->query('/soap:Envelope/soap:Body/SendPurchases/Partner/UserID');

但是上面的查询没有返回任何结果。

事实上,当我遍历文档中的所有元素时:

foreach ($doc->getElementsByTagName('*') as $node) {
    echo $node->getNodePath() . "\n";
}

它returns是这样的:

/soap:Envelope/soap:Body
/soap:Envelope/soap:Body/*
/soap:Envelope/soap:Body/*/*[1]
/soap:Envelope/soap:Body/*/*[1]/*[1]
/soap:Envelope/soap:Body/*/*[1]/*[2]
/soap:Envelope/soap:Body/*/*[2]
/soap:Envelope/soap:Body/*/*[2]/*[1]

如您所见,<soap:Body> 中的所有元素都替换为星号和索引,而不是元素名称。

沿该路径查询有效,但对我来说维护起来并不容易,我更愿意改用元素名称。

感谢@Dekel 的提示。我需要注册两个命名空间,然后在查询的时候指定命名空间:

$xpath = new DOMXPath($doc);
$xpath->registerNamespace('soap', 'http://schemas.xmlsoap.org/soap/envelope/');
$xpath->registerNamespace('insr', 'urn:services.insurance');

$elements = $xpath->query('/soap:Envelope/soap:Body/insr:SendPurchases/insr:Partner/insr:UserID');

现在可以使用了。

另一种选择是use local-name():

$xml = '<?xml version="1.0"?>
    <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
           xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
        <soap:Body>
            <SendPurchases xmlns="urn:services.insurance">
                <Partner>
                    <UserID>MyCompany</UserID>
                    <Password>ABC123</Password>
                </Partner>
                <PurchasesRequest>
                    <Total>100</Total> 
                </PurchasesRequest>
            </SendPurchases>
        </soap:Body>
    </soap:Envelope>';
$doc = new DOMDocument();
$doc->loadXML($xml);
$xpath = new DOMXPath($doc);

$elements = $xpath->query("//*[local-name()='UserID']");

var_dump($elements->item(0)->nodeValue);
// string(9) "MyCompany"

我的快速而肮脏的解决方案:

$path = $oNode->getNodePath(); #eg. /*/*[2]/*
while ($oNode) {
    $path = preg_replace('~(.*/)[*]~', ''.$oNode->localName, $path, 1);
    $oNode = $oNode->parentNode;
}
$path; #eg. /root/node[2]/subnode