我有一个主题问题的解决方案,但这是一个破解,我想知道是否有更好的方法。
下面是一个示例XML文件和一个PHP CLI脚本,该脚本执行作为参数给定的xpath查询。对于这个测试用例,命令行是:
./xpeg "//MainType[@ID=123]"
最奇怪的是这条线,没有它我的方法就不起作用:
$result->loadXML($result->saveXML($result));
据我所知,这只是重新解析修改后的XML,在我看来这应该没有必要。
有没有更好的方法可以在PHP中对此XML执行xpath查询?
XML(注意默认名称空间的绑定):
<?xml version="1.0" encoding="utf-8"?>
<MyRoot
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.example.com/data http://www.example.com/data/MyRoot.xsd"
xmlns="http://www.example.com/data">
<MainType ID="192" comment="Bob's site">
<Price>$0.20</Price>
<TheUrl><![CDATA[http://www.example.com/path1/]]></TheUrl>
<Validated>N</Validated>
</MainType>
<MainType ID="123" comment="Test site">
<Price>$99.95</Price>
<TheUrl><![CDATA[http://www.example.com/path2]]></TheUrl>
<Validated>N</Validated>
</MainType>
<MainType ID="922" comment="Health Insurance">
<Price>$600.00</Price>
<TheUrl><![CDATA[http://www.example.com/eg/xyz.php]]></TheUrl>
<Validated>N</Validated>
</MainType>
<MainType ID="389" comment="Used Cars">
<Price>$5000.00</Price>
<TheUrl><![CDATA[http://www.example.com/tata.php]]></TheUrl>
<Validated>N</Validated>
</MainType>
</MyRoot>
PHP CLI脚本:
#!/usr/bin/php-cli
<?php
$xml = file_get_contents("xpeg.xml");
$domdoc = new DOMDocument();
$domdoc->loadXML($xml);
// remove the default namespace binding
$e = $domdoc->documentElement;
$e->removeAttributeNS($e->getAttributeNode("xmlns")->nodeValue,"");
// hack hack, cough cough, hack hack
$domdoc->loadXML($domdoc->saveXML($domdoc));
$xpath = new DOMXpath($domdoc);
$str = trim($argv[1]);
$result = $xpath->query($str);
if ($result !== FALSE) {
dump_dom_levels($result);
}
else {
echo "error'n";
}
// The following function isn't really part of the
// question. It simply provides a concise summary of
// the result.
function dump_dom_levels($node, $level = 0) {
$class = get_class($node);
if ($class == "DOMNodeList") {
echo "Level $level ($class): $node->length items'n";
foreach ($node as $child_node) {
dump_dom_levels($child_node, $level+1);
}
}
else {
$nChildren = 0;
foreach ($node->childNodes as $child_node) {
if ($child_node->hasChildNodes()) {
$nChildren++;
}
}
if ($nChildren) {
echo "Level $level ($class): $nChildren children'n";
}
foreach ($node->childNodes as $child_node) {
if ($child_node->hasChildNodes()) {
dump_dom_levels($child_node, $level+1);
}
}
}
}
?>
解决方案是使用命名空间,而不是去掉它。
$result = new DOMDocument();
$result->loadXML($xml);
$xpath = new DOMXpath($result);
$xpath->registerNamespace("x", trim($argv[2]));
$str = trim($argv[1]);
$result = $xpath->query($str);
并在命令行中这样调用它(注意XPath表达式中的x:
)
./xpeg "//x:MainType[@ID=123]" "http://www.example.com/data"
你可以通过使其更加闪亮
- 自己查找默认名称空间(通过查看文档元素的名称空间属性)
- 在命令行上支持多个命名空间,并在
$xpath->query()
之前将它们全部注册 - 支持
xyz=http//namespace.uri/
形式的参数以创建自定义命名空间前缀
底线是:在XPath中,当您真正指的是//namespace:foo
时,您不能查询//foo
。这些是根本不同的,因此选择不同的节点。XML可以定义默认名称空间(因此可以在文档中删除显式名称空间使用),这并不意味着可以在XPath中删除名称空间使用。
只是出于好奇,如果删除这一行会发生什么?
$e->removeAttributeNS($e->getAttributeNode("xmlns")->nodeValue,"");
在我看来,这是最有可能导致需要你破解的原因。您基本上是删除xmlns="http://www.example.com/data"
部分,然后重新构建DOMDocument。您是否考虑过简单地使用字符串函数来删除该名称空间?
$pieces = explode('xmlns="', $xml);
$xml = $pieces[0] . substr($pieces[1], strpos($pieces[1], '"') + 1);
然后继续你的路?它甚至可能会更快。
考虑到XPath语言的当前状态,我觉得Tomalek提供了最好的答案:将前缀与默认名称空间相关联,并为所有标记名加前缀。这是我打算在当前应用程序中使用的解决方案。
当这不可能或不实用时,比我的破解更好的解决方案是调用一个与重新扫描相同的方法(希望更有效):DOMDocument::normalizeDocument()。该方法的行为"就像您保存并加载了文档,将文档置于‘正常’形式。"
作为一种变体,您可以使用xpath掩码:
//*[local-name(.) = 'MainType'][@ID='123']