I would like to apply a function to every node in a SimpleXML object.
<api>
<stuff>ABC</stuff>
<things>
<thing>DEF</thing>
<thing>GHI</thing>
<thing>JKL</thing>
</things>
</api>
//function reverseText($str){};
<api>
<stuff>CBA</stuff>
<things>
<thing>FED</thing>
<thing>IHG</thing>
<thing>LKJ</thing>
</things>
</api>
How would I apply reverseText() to every node to get the second XML snippet?
Here the Standard PHP Library can come to the rescue.
One option is to use the (little known) SimpleXMLIterator
. It is one of several RecursiveIterator
s available in PHP and a RecursiveIteratorIterator
from the SPL can be used to loop over and change all of the elements' text.
$source = '
<api>
<stuff>ABC</stuff>
<things>
<thing>DEF</thing>
<thing>GHI</thing>
<thing>JKL</thing>
</things>
</api>
';
$xml = new SimpleXMLIterator($source);
$iterator = new RecursiveIteratorIterator($xml);
foreach ($iterator as $element) {
// Use array-style syntax to write new text to the element
$element[0] = strrev($element);
}
echo $xml->asXML();
The above example outputs the following:
<?xml version="1.0"?>
<api>
<stuff>CBA</stuff>
<things>
<thing>FED</thing>
<thing>IHG</thing>
<thing>LKJ</thing>
</things>
</api>
You can create an array of all nodes in the document with the SimpleXMLElement::xpath()
method.
Then you can use array_walk
on that array. However you don't want to reverse the string of every node, only of those elements which don't have any child elements.
$source = '
<api>
<stuff>ABC</stuff>
<things>
<thing>DEF</thing>
<thing>GHI</thing>
<thing>JKL</thing>
</things>
</api>
';
$xml = new SimpleXMLElement($source);
array_walk($xml->xpath('//*'), function(&$node) {
if (count($node)) return;
$node[0] = strrev($node);
});
echo $xml->asXML();
The above example outputs the following:
<?xml version="1.0"?>
<api>
<stuff>CBA</stuff>
<things>
<thing>FED</thing>
<thing>IHG</thing>
<thing>LKJ</thing>
</things>
</api>
The xpath query allows more control for example with namespaces.