如何获得最深的XML元素

<Item>
<BrowseNodes>
    <BrowseNode>
        <BrowseNodeId>3024254031</BrowseNodeId>
        <Name>Tunnelzelte</Name>
        <Ancestors>
            <BrowseNode>
                <BrowseNodeId>3024250031</BrowseNodeId>
                <Name>Zelte</Name>
                <Ancestors>
                    <BrowseNode>
                        <BrowseNodeId>16435151</BrowseNodeId>
                        <Name>Camping & Outdoor</Name>
                        <Ancestors>
                            <BrowseNode>
                                <BrowseNodeId>16435121</BrowseNodeId>
                                <Name>Kategorien</Name>
                                <IsCategoryRoot>1</IsCategoryRoot>
                                <Ancestors>
                                    <BrowseNode>
                                        <BrowseNodeId>16435051</BrowseNodeId>
                                        <Name>Sport & Freizeit</Name>
                                    </BrowseNode>
                                </Ancestors>
                            </BrowseNode>
                        </Ancestors>
                    </BrowseNode>
                ´</Ancestors>
            </BrowseNode>
        </Ancestors>
    </BrowseNode>
</BrowseNodes>

Hi,

I try to get the deepest "Name" element and vlaue (Sport & Freizeit in this case), but it does not work with xpath():

$temp=$item->xpath("//Name[last()]");

I guess I don't understand how xpath() works..

Does anybody has a hint? The number of "BrowseNode" elements is flexible, so I can't work with a fix path.

THX!

You have to wrap the //Name tag and then get the last item.

(//Name)[last()]

The "deepest" element could be described as one that has no Ancestors child node.

$document = new DOMDocument();
$document->loadXML($xml);
$xpath = new DOMXpath($document);

var_dump(
  $xpath->evaluate('string(//BrowseNode[not(Ancestors)]/Name)')
);
  • Fetch any "BrowseNode"
    //BrowseNode
  • ... without an "Ancestors" child
    //BrowseNode[not(Ancestors)]
  • ... fetch its "Name" child
    //BrowseNode[not(Ancestors)]/Name
  • ... cast the first fetched node into string
    string(//BrowseNode[not(Ancestors)]/Name)

If the structure is allowed to have multiple BrowseNode branches the problem can not be solved with Xpath alone. In this case you will have to fetch the nodes into an array and sort it.

$document = new DOMDocument();
$document->loadXML($xml);
$xpath = new DOMXpath($document);

$leafs = array_map(
  function(DOMElement $node) use ($xpath) {
      // fetch name and level
      return [
          'name' => $xpath->evaluate('string(Name)', $node),
          'level' => (int)$xpath->evaluate('count(ancestor::BrowseNode)', $node),
          // keep the node for additional Xpath expressions
          'node' => $node
      ];  
  },
  // get BrowseNode elements without Ancestors
  iterator_to_array(
      $xpath->evaluate('//BrowseNode[not(Ancestors)]')
  )
);

// sort array by level descending
usort(
    $leafs,
    function($a, $b) {
        // compare level
        $result = -($a['level'] <=> $b['level']);
        // same level -> compare name
        return ($result !== 0) ? $result : strnatcmp($a['name'], $b['name']); 
    }
);

// let's output the leafs to see what has happened
foreach ($leafs as $leaf) {
    var_dump([$leaf['name'], $leaf['level']]);
}

By keeping the node in the leaf array you can use additional Xpath expressions to fetch related data. Like all ancestor names:

// get the first 
if (count($leafs) > 0) {
    $leaf = $leafs[0];
    var_dump(
        array_map(
            function(DOMElement $node) {
                return $node->textContent;
            },
            // fetch Name of ancestors
            iterator_to_array(
                $xpath->evaluate('ancestor::BrowseNode/Name', $leaf['node'])
            )
        )
    );
}

Thanks to everybody! I'm still trying to find a solution. Until now I get only empty arrays. I tried your ideas.

Someone had the same problem:

Get Last XML Child from Tree of All Same Name Children

If I find a solution I will post it.