Converting a SimpleXML object to an array
If you have been following my Twitter, you would of heard me complaining about converting a SimpleXML object into an array. I am still having that problem, so if you can get it working correctly (my test so far below), I would be greatly appreciative. If you have never used the SimpleXML object, it can be quite awesome when actually reading an XML document - but once it comes to converting it to something else, it comes straight from the darkest depths of hell. Every property of the object, is also a SimpleXML object, so on and so forth. Each property/object has a method children(), which returns more properties, or attributes() which returns attributes; weirdly enough, children() also return attributes. Furthermore, you can't just echo the object out to get a value, you have to turn it into a string. You can see where this can get quite difficult and confusing, as it always spits out data your not expecting.
After countless hours, I was able to get it to properly convert to an array... about 95% of the time... while keeping attributes and parent/children hierarchy. The only scenario where it doesn't convert properly, is when you have nodes within a node that has attributes (which is kind of rare in my opinion). Here's a small little example:
// Works just fine
<root>
<node foo="bar">I'm a node!</node>
</root>
// Does not work
<root>
<node foo="bar">
<childNode>I'm here to make your life miserable!</childNode>
<childNode>Me too!</childNode>
</node>
</root>
Besides that little instance, I am able to properly turn an XML document with attributes, and multiple nodes with the same name, all into a perfectly replicated array. Here is the code I wrote to achieve such an amazing task (sarcasm).
/**
* Convert a SimpleXML object into an array (last resort).
*
* @access public
* @param object $xml
* @param boolean $root - Should we append the root node into the array
* @return array
*/
public function xmlToArray($xml, $root = true) {
if (!$xml->children()) {
return (string)$xml;
}
$array = array();
foreach ($xml->children() as $element => $node) {
$totalElement = count($xml->{$element});
if (!isset($array[$element])) {
$array[$element] = "";
}
// Has attributes
if ($attributes = $node->attributes()) {
$data = array(
'attributes' => array(),
'value' => (count($node) > 0) ? xmlToArray($node, false) : (string)$node
// 'value' => (string)$node (old code)
);
foreach ($attributes as $attr => $value) {
$data['attributes'][$attr] = (string)$value;
}
if ($totalElement > 1) {
$array[$element][] = $data;
} else {
$array[$element] = $data;
}
// Just a value
} else {
if ($totalElement > 1) {
$array[$element][] = xmlToArray($node, false);
} else {
$array[$element] = xmlToArray($node, false);
}
}
}
if ($root) {
return array($xml->getName() => $array);
} else {
return $array;
}
}
I know exactly where the problem resides also. Its the value index of the $data array. (The little bastard below).
$data = array('attributes' => array(), 'value' => (string)$node);
// Should be
$data = array('attributes' => array(), 'value' => xmlToArray($node, false));
A simple fix right? Nope! When you do that, it totally breaks... for some reason. The first line of the function (!$xml->children()) gets passed since the element passed does have children, since it has attributes; now I can never understand why attributes count as children when you have attributes(). I tried many different conditionals to get it working, I tried unsetting the attributes (but can't determine the property), and all these other routes... but to no avail. But I digress, seeing as how it works 95% of the time, and the case it doesn't work isn't used that much. However, if you can figure it out, I will be in your debt forever.