簡體   English   中英

使用默認命名空間綁定的XML上的XML xpath查詢

[英]PHP xpath query on XML with default namespace binding

我有一個主題問題的解決方案,但它是一個黑客,我想知道是否有更好的方法來做到這一點。

下面是一個示例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"

你可以讓它更閃亮

  • 自己找出默認命名空間(通過查看文檔元素的namespace屬性)
  • 在命令行上支持多個命名空間並在$xpath->query()之前注冊它們
  • xyz=http//namespace.uri/的形式支持參數以創建自定義名稱空間前綴

底線是:在XPath中,當你真正的意思是//namespace:foo時,你無法查詢//foo //namespace: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掩碼:

//*[local-name(.) = 'MainType'][@ID='123']

鑒於XPath語言的當前狀態,我覺得Tomalek提供了最佳答案:將前綴與默認命名空間相關聯,並為所有標記名稱添加前綴。 這是我打算在我當前的應用程序中使用的解決方案。

當這不可行或不實用時,比我的黑客更好的解決方案是調用一個與重新掃描(希望更有效)相同的方法: DOMDocument :: normalizeDocument() 該方法表現為“就像您保存並加載文檔一樣,將文檔置於'正常'形式。”

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM