XML into Associative Array using PHP

Viewed 19319

Can anyone help with converting data from an XML document into an associative array? I'm running into issues given that the XML structure is sort of 3D and the array is more of a 2D structure (please forgive my lack of correct terminology throughout). The XML elements have attributes, children and grand-children (but I never know their names), so I figured I'd try to make the key in the array a concatenation of each child/attribute name and the value equal to, well, the value. Trouble is I need the attribute name and value as part of the concatenated array key to make it unique...

For example:

<Computer id="1">   
    <OS>
        <Name>Linux</Name>
        <Age>Older than me</Age>
    </OS>
</Computer>
<Computer id="2">
    <OS>
        <Name>Windows</Name>
        <Age>Not so much</Age>
    </OS>
</Computer>

Should ideally give:

[Computer-id-1-OS-Name] = 'Linux'
[Computer-id-1-OS-Age] = 'Older than me'
[Computer-id-2-OS-Name] = 'Windows'
[Computer-id-2-OS-Age] = 'Not so much'

But I'm getting this result:

[Computer-id] = '1'
[Computer-OS-Name] = 'Linux'
[Computer-OS-Age] = 'Older than me'
[Computer-id] = '2'
[Computer-OS-Name] = 'Windows'
[Computer-OS-Age] = 'Not so much'

So that the [Computer-id] key is not unique. I'm using a recursive function to read in the values, but I can't figure how to get the attribute name and attribute value into the name of the subordinate keys...(By the way there is a good reason for doing this seemingly illogical task!) Any help would be greatly appreciated...

Here is the function which 'flattens' the XML data after it has been read into a multi-dimensional array. I'm not sure I'm going about this the right way!

function flattenArray ($array, $baseName = NULL)
{
    reset($array);
    while (list ($key, $value) = each($array)) {
        $outKey = $key . "-";
        if (is_array($value)) {
            flattenArray($value, $baseName . $outKey);
        } else {
            $finalKey = $baseName . rtrim($outKey, '-');
            $finalValue = $value;
            echo "$finalKey = $finalValue\n";
        }
    }
}
6 Answers

I modified user655000's answer to be closer to how json_decode(json_encode($dom)) would format/return the data. I also made the initial array parameter optional, since it's just going to be empty anyway.

I couldn't use the decode(encode) method as there appears to be bugs in PHP's encode function, which resulted in the decode() returning null on some sample data. I tried a safer version of the encode function, but it ran out of memory.

There is a minor behavior difference. the decode(encode) method will discard any attributes (possibly children too) if there is nodeText. My method does not.

function readxml($xmlfile, $recursive = false){
    $ob = simplexml_load_file($xmlfile);
    //primary method
    $json = json_encode($ob);
    $array = json_decode($json, true);
    if(is_null($array)){//backup method
        $array = xml2assoc($ob);
    }
    return $array;
}

function xml2assoc($obj, &$arr = null) {
    $children = $obj->children();//->count(); 
    $nodes = [];
    foreach ( $children as $elementName => $node ) {
        if(!isset($nodes[$elementName])){
            $nodes[$elementName] = 0;
        }
        $nodes[$elementName]++;
    }
    $indexes = [];

    if($arr === null){
        $arr = [];
    }
    foreach ( $children as $elementName => $node ) {
        $temp = array();
        $grandchildren = $node->children()->count();
        
        //attributes        
        $attributes = $node->attributes();
        foreach ( $attributes as $attributeName => $attributeValue ) {
            $attribName = trim((string) $attributeName);
            $attribVal = trim((string) $attributeValue);
            $temp["@attributes"][$attribName] = $attribVal;
        }
        
        //text      
        $text = (string) $node;
        $text = trim($text);
        if (strlen($text) > 0) {
            if(count($temp) == 0 && $grandchildren == 0){
                $temp = $text;//discard the children/attribute data since there aren't any
            } else {
                $temp["NodeText"] = $text;//retain the children/attributes
            }
        }       
        
        //grandchildren
        if($temp || is_string($temp) || $grandchildren > 0 ){
            if( $nodes[$elementName] == 1 ){//only one of it's kind
                $arr[$elementName] = $temp;
                xml2assoc($node, $arr[$elementName]);
            } else {//has multiple nodes of the same kind
                if(isset($indexes[$elementName])){
                    $indexes[$elementName]++;
                } else {
                    $indexes[$elementName] = 0;
                }
                $index = $indexes[$elementName];
                $arr[$elementName][$index] = $temp;
                xml2assoc($node, $arr[$elementName][$index]);
            }
        }
    }
    return $arr;
}
Related