获取递归函数中XML节点的XPath

3
假设我有一些代码可以递归地遍历XML文件,就像这样:
$xmlfile = new SimpleXMLElement('http://www.domain.com/file.xml',null,true);
xmlRecurse($xmlfile,0);
function xmlRecurse($xmlObj,$depth) {
  foreach($xmlObj->children() as $child) {
    echo str_repeat('-',$depth).">".$child->getName().": ".$subchild."\n";
    foreach($child->attributes() as $k=>$v){
        echo "Attrib".str_repeat('-',$depth).">".$k." = ".$v."\n";
    }
    xmlRecurse($child,$depth+1);
  }
}

如何计算每个节点的xpath,以便我可以将其存储以映射到其他代码?


计算XPath是什么意思? - Gordon
我是指为每个$child计算它的xpath - 例如/dwml/data/location/point/@latitude。 - robjmills
5个回答

4

这种做法显而易见的是将XPath作为第三个参数传递,并在深入挖掘时构建它。你必须考虑到拥有相同名称的兄弟节点,因此在迭代时必须跟踪具有与当前子节点相同名称的前置兄弟节点的数量。

工作示例:

function xmlRecurse($xmlObj,$depth=0,$xpath=null) {
  if (!isset($xpath)) {
    $xpath='/'.$xmlObj->getName().'/';
  }
  $position = array();

  foreach($xmlObj->children() as $child) {

    $name = $child->getName();
    if(isset($position[$name])) {
      ++$position[$name];
    }
    else {
      $position[$name]=1;
    }
    $path=$xpath.$name.'['.$position[$name].']';

    echo str_repeat('-',$depth).">".$name.": $path\n";
    foreach($child->attributes() as $k=>$v){
        echo "Attrib".str_repeat('-',$depth).">".$k." = ".$v."\n";
    }

    xmlRecurse($child,$depth+1,$path.'/');
  }
}

注意,整个文档映射并沿途存储XPath的想法似乎很奇怪。你可能正在错误地解决完全不同的问题。


很有趣。实际上我们正在考虑允许用户上传一个模板XML文件,并且需要存储每个节点/属性在我们系统内的映射关系。您能否推荐比XPath更好的方法?我真正喜欢它的是将路径存储为简单字符串这个想法。 - robjmills

2
$domNode = dom_import_simplexml($node);
$xpath = $domNode->getNodePath();

您需要PHP 5 >= 5.2.0才能使其正常工作。

2
您可以将名为$xpath的第三个参数传递给xmlRecurse,并在每次迭代中添加子级的xpath表示形式:
function xmlRecurse($xmlObj,$depth,$xpath) {
  $i=0;
  foreach($xmlObj->children() as $child) {
    echo str_repeat('-',$depth).">".$child->getName().": ".$subchild."\n";
    foreach($child->attributes() as $k=>$v){
        echo "Attrib".str_repeat('-',$depth).">".$k." = ".$v."\n";
    }
    xmlRecurse($child,$depth+1,$xpath.'/'.$child->getName().'['.$i++.']');
  }
}

当然,您也可以使用其属性构建当前子xPath表示。但是这样做,您必须将所有xpath字符串存储在数组中,以确保您没有添加重复项。 - Ololo
这是真的,我想知道是否有更直接的方法,不依赖于通过递归函数传递变量,类似于$child->current()或类似的东西。 - robjmills

2
使用SimpleXML,我认为你只能像其他人指出的那样,通过将节点路径作为字符串参数进行递归来完成。
使用DOMDocument,您可以使用$node->parentNode属性向后遍历到文档元素,并为任意节点构造它(例如,如果您有一个节点引用并想要发现它在树中的位置,而没有先前了解如何到达该节点)。

1

继续探讨MightyE关于回溯的想法:

function whereami($node)
{
    if ($node instanceof SimpleXMLElement)
    {
        $node = dom_import_simplexml($node);
    }
    elseif (!$node instanceof DOMNode)
    {
        die('Not a node?');
    }

    $q     = new DOMXPath($node->ownerDocument);
    $xpath = '';

    do
    {
        $position = 1 + $q->query('preceding-sibling::*[name()="' . $node->nodeName . '"]', $node)->length;
        $xpath    = '/' . $node->nodeName . '[' . $position . ']' . $xpath;
        $node     = $node->parentNode;
    }
    while (!$node instanceof DOMDocument);

    return $xpath;
}

我不建议在这种情况下使用它(映射整个文档,而不是单个给定节点),但将来可能会有用。

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