6

我有一个解决主题问题的方法,但这是一个 hack,我想知道是否有更好的方法来做到这一点。

下面是一个示例 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);
      }
    }
  }
}
?>
4

4 回答 4

13

解决方案是使用命名空间,而不是摆脱它。

$result = new DOMDocument();
$result->loadXML($xml);

$xpath = new DOMXpath($result);
$xpath->registerNamespace("x", trim($argv[2]));

$str = trim($argv[1]);
$result = $xpath->query($str);

并在命令行上调用它(注意x:XPath 表达式中的)

./xpeg "//x:MainType[@ID=123]" "http://www.example.com/data"

您可以通过以下方式使其更加闪亮

  • 自己找出默认命名空间(通过查看文档元素的命名空间属性)
  • 在命令行上支持多个命名空间并在之前将它们全部注册$xpath->query()
  • xyz=http//namespace.uri/以创建自定义命名空间前缀的形式支持参数

底线是:在 XPath 中,您无法查询//foo真正的意思是//namespace:foo. 这些是根本不同的,因此选择不同的节点。XML 可以定义默认命名空间(因此可以在文档中删除显式命名空间使用)这一事实并不意味着您可以在 XPath 中删除命名空间使用。

于 2011-06-25T03:14:00.437 回答
1

只是出于好奇,如果删除此行会发生什么?

$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);

然后继续上路?它甚至可能最终变得更快。

于 2011-06-25T02:56:30.610 回答
0

鉴于 XPath 语言的当前状态,我觉得 Tomalek 提供了最好的答案:将前缀与默认命名空间相关联,并为所有标记名称添加前缀。这就是我打算在当前应用程序中使用的解决方案。

当这不可能或不可行时,比我的 hack 更好的解决方案是调用一种与重新扫描执行相同操作的方法(希望更有效):DOMDocument::normalizeDocument()。该方法的行为“就像您保存然后加载文档一样,将文档置于‘正常’形式。”</p>

于 2011-06-29T04:43:46.673 回答
0

作为变体,您也可以使用 xpath 掩码:

//*[local-name(.) = 'MainType'][@ID='123']
于 2017-03-03T12:04:26.253 回答