Получить xpath узла xml внутри рекурсивной функции - PullRequest
3 голосов
/ 22 февраля 2010

Допустим, у меня есть код для рекурсивного перебора 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 каждого узла, чтобы я мог сохранить его для сопоставления с другим кодом?

Ответы [ 5 ]

5 голосов
/ 22 февраля 2010

Очевидный способ сделать это - передать 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 по пути кажется странной. Возможно, вы работаете над неправильным решением совершенно другой проблемы.

3 голосов
/ 22 февраля 2010

Вы можете передать третьему параметру xmlRecurse $ xpath (с текущим представлением узла xPath) и добавить представление 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++.']');
  }
}
2 голосов
/ 06 марта 2012
$domNode = dom_import_simplexml($node);
$xpath = $domNode->getNodePath();

Вам нужен PHP 5> = 5.2.0, чтобы это работало.

2 голосов
/ 22 февраля 2010

Я думаю, что с SimpleXML вы можете сделать это только так, как указали другие: путем повторения пути к узлу в качестве строкового аргумента.

С DOMDocument вы можете использовать свойство $node->parentNode, чтобы отсканировать обратно к элементу документа и создать его для произвольного узла (например, если у вас была ссылка на узел и вы хотите узнать, где в дереве он был без предварительное знание того, как вы попали в этот узел).

1 голос
/ 22 февраля 2010

В продолжение идеи 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;
}

Я бы не рекомендовал его для рассматриваемого случая (отображение всего документа, а не одного данного узла), но это может быть полезно для дальнейшего использования.

...