<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)')
);
//BrowseNode
//BrowseNode[not(Ancestors)]
//BrowseNode[not(Ancestors)]/Name
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.