204

I want to convert below XML to PHP array. Any suggestions on how I can do this?

<aaaa Version="1.0">
   <bbb>
     <cccc>
       <dddd Id="id:pass" />
       <eeee name="hearaman" age="24" />
     </cccc>
   </bbb>
</aaaa>
Syscall
  • 18,131
  • 10
  • 32
  • 49
Hearaman
  • 8,117
  • 13
  • 38
  • 56
  • 3
    also, how is that question different form your other question? http://stackoverflow.com/questions/6578084/how-to-convert-this-xml-request-into-array-in-php – Gordon Jul 05 '11 at 07:04
  • 3
    Few things are as obnoxious as an OP accepting the wrong answer to their own question. – John Oct 20 '17 at 09:42

10 Answers10

498

easy!

$xml = simplexml_load_string($xmlstring, "SimpleXMLElement", LIBXML_NOCDATA);
$json = json_encode($xml);
$array = json_decode($json,TRUE);
MartyIX
  • 26,515
  • 27
  • 129
  • 197
user1398287
  • 4,995
  • 5
  • 19
  • 25
  • 1
    Thanks! After looking through a bunch of functions everywhere, nothing structured the parsed XML in a sane way... this is simple and works beautifully! – Brad Jan 28 '14 at 22:19
  • 31
    You might run into trouble at CDATA sections (returning always null). As a solution try $xml = simplexml_load_string( $xmlstring , null , LIBXML_NOCDATA ); $json = json_encode($xml); $array = json_decode($json,TRUE); (see http://stackoverflow.com/a/2970701/413531) //e damn.. is there a way to add new lines in a comment? – Hirnhamster May 26 '14 at 09:57
  • 1
    @Hirnhamster: If you use that flag, you most often can just cast to array: `$array = (array) simplexml_load_string($xmlstring, null , LIBXML_NOCDATA);` - no need for decode a JSON encode. In case you need more options: http://stackoverflow.com/a/25095553/367456 – hakre Aug 02 '14 at 13:48
  • 4
    We do the exact same thing but with simplexml_load_file and it work fine. Thanks – Thermech Sep 03 '14 at 13:46
  • 2
    What the second parameter(TRUE) is for? – Mansour Fahad Sep 19 '14 at 18:50
  • 4
    @MansourFahad In [json_decode](http://us3.php.net/manual/en/function.json-decode.php) you can use the optional second parameter as `TRUE` (normally defaults to `FALSE`) to convert the JSON input to an associative array. – Jake Bathman Sep 26 '14 at 19:20
  • 17
    @Ismael Miguel too much code? Just because you put all those functions on one line doesn't mean you're using less code. It might look more compact but it comes at the expense of readability. – Jage Mar 05 '15 at 21:44
  • @Jage Don't forget the performance win. You don't have to be storing it into another variable. PHP passes it eficiently to the function. – Ismael Miguel Mar 05 '15 at 23:05
  • @IsmaelMiguel The performance gain by doing it that way is negligible. You're talking micro-optimization at the expense of readability and maintainability. I'd sooner have other devs be able to read my code than save them a small handful of clock cycles. – maiorano84 Oct 29 '15 at 13:59
  • @maiorano84 You are right, it is a micro-optimization. One that can avoid a ton of copies remaining in memory due to assigning the data to other functions. You keey a XML object, a JSON representation and then an array, when you could only have a XML object and the array. – Ismael Miguel Oct 29 '15 at 15:09
  • 1
    @IsmaelMiguel You seem to be suggesting that PHP's internal garbage collection is insufficient in handling such a trivial task like this. Do you have any benchmarks to back up your claim that there would be a "ton" of copies remaining in memory just from creating variables to maintain readability, and produce a significant enough overhead that memory becomes an issue? Because if not, I'm not sure what you're trying to argue here except that you somehow believe that less lines = performant, which is laughably false. – maiorano84 Oct 29 '15 at 15:55
  • @maiorano84 Yes, it is as false as it can get. I am talking about a huge string that will be left behind. I'm not saying that the GB won't be enough. I'm saying that you will have 2 copies of the same string, because you assigned the original to a variable and you pass the copy to the function. – Ismael Miguel Oct 30 '15 at 21:28
  • 1
    If your xml has namespaces, do `simplexml_load_string($xmlstring, "SimpleXMLElement", LIBXML_NOCDATA, "ns3", true);` ns3 is an example of a perfix. if it's a prefix, the next argument needs to be true. If the fourth argument is not a namespace, but a URI, the 5th parameter is not needed, since it's defaulted to false. If any of these are improper, the result will be an empty string. – ahnbizcad Mar 24 '17 at 16:44
  • 2
    simplexml_load_string drops attributes on elements with only text node - so this doesn't work for me. – Eaten by a Grue Sep 21 '17 at 16:54
  • This will work for some sets of XML (the one OP posted is okay). However, it will also lose attributes and whole nodes in some situations. For example, if you have an "array" of elements (a collection of elements with the same name) then any attributes they have will not be in the final array. I'm finding this even on PHP 7.2; it's a problem that is probably here to stay. – Jason Mar 22 '18 at 11:47
  • Don't try to convert into an array, by the use of json_encode. It does not work with namespaces! – Sven Jun 26 '21 at 18:17
141

Another option is the SimpleXML extension (I believe it comes standard with most php installs.)

http://php.net/manual/en/book.simplexml.php

The syntax looks something like this for your example

$xml = new SimpleXMLElement($xmlString);
echo $xml->bbb->cccc->dddd['Id'];
echo $xml->bbb->cccc->eeee['name'];
// or...........
foreach ($xml->bbb->cccc as $element) {
  foreach($element as $key => $val) {
   echo "{$key}: {$val}";
  }
}
Hearaman
  • 8,117
  • 13
  • 38
  • 56
Sam Dufel
  • 17,084
  • 3
  • 46
  • 49
  • 90
    To be fair, this does not exactly answer the question how to get an array. – sieppl Nov 05 '13 at 08:03
  • SimpleXML suck when parsing this xml : https://www.amazon.in/rss/bestsellers/shoes?tag=dealslama-21 Even print_r don't tell object contains actually. – Ravi Soni Jan 24 '15 at 07:39
  • use var_dump, you'll see the xml structure as keys inside the object. – Magus Oct 03 '16 at 16:49
  • 3
    I have some `[CDATA[TEXT]]` inside some elements and they are not parsing with this. It parses it as a `SimpleXMLElement Object`. Any workaround for that? – masterFly Oct 26 '16 at 04:20
  • This doesn’t answer the question – Bilaal Rashid Apr 14 '19 at 16:11
  • I believe when I put this answer in 8 years ago, I was attempting to highlight that you can access the contents of a parsed XML document using SimpleXML in the same manner as you would if it was in a plain array. – Sam Dufel Apr 18 '19 at 23:35
51

Converting an XML string ($buffer) into a simplified array ignoring attributes and grouping child-elements with the same names:

function XML2Array(SimpleXMLElement $parent)
{
    $array = array();

    foreach ($parent as $name => $element) {
        ($node = & $array[$name])
            && (1 === count($node) ? $node = array($node) : 1)
            && $node = & $node[];

        $node = $element->count() ? XML2Array($element) : trim($element);
    }

    return $array;
}

$xml   = simplexml_load_string($buffer);
$array = XML2Array($xml);
$array = array($xml->getName() => $array);

Result:

Array
(
    [aaaa] => Array
        (
            [bbb] => Array
                (
                    [cccc] => Array
                        (
                            [dddd] => 
                            [eeee] => 
                        )

                )

        )

)

If you also want to have the attributes, they are available via JSON encoding/decoding of SimpleXMLElement. This is often the most easy quick'n'dirty solution:

$xml   = simplexml_load_string($buffer);
$array = json_decode(json_encode((array) $xml), true);
$array = array($xml->getName() => $array);

Result:

Array
(
    [aaaa] => Array
        (
            [@attributes] => Array
                (
                    [Version] => 1.0
                )

            [bbb] => Array
                (
                    [cccc] => Array
                        (
                            [dddd] => Array
                                (
                                    [@attributes] => Array
                                        (
                                            [Id] => id:pass
                                        )

                                )

                            [eeee] => Array
                                (
                                    [@attributes] => Array
                                        (
                                            [name] => hearaman
                                            [age] => 24
                                        )

                                )

                        )

                )

        )

)

Take note that all these methods only work in the namespace of the XML document.

ntzm
  • 4,541
  • 2
  • 30
  • 35
hakre
  • 184,866
  • 48
  • 414
  • 792
  • Related: [Json Encode or Serialize an XML](http://stackoverflow.com/a/18553535/367456) – hakre Sep 06 '13 at 10:20
  • In PHP 7 I had to add this: `&& (is_countable($node) && 1 === count($node) ? $node = array($node) : 1)`, however I got an error in the next line: `[] operator not supported for strings`. – andreshg112 Dec 16 '19 at 21:48
  • @andreshg112: I'm unable to reproduce (works stable PHP 5.3.0 - 7.4.0), the behavior has not changed since ages, please compare against hundreds of different PHP versions: https://3v4l.org/l4nQN – hakre Dec 16 '19 at 23:58
  • maybe it is because of my KML file (it's an XML). I cannot share it. I already imported it but I had to do it another way. – andreshg112 Dec 26 '19 at 13:12
  • probably you are concerned about XML namespaces. The example is only for the parts w/o a namespace (or the default one, I sometimes mix this). – hakre Dec 28 '19 at 18:38
40
$array = json_decode(json_encode((array)simplexml_load_string($xml)),true);
ntzm
  • 4,541
  • 2
  • 30
  • 35
Fawad Ghafoor
  • 5,529
  • 6
  • 40
  • 53
  • 1
    if you cast to array, you dont need `json_encode` and `json_decode`. – Ismael Miguel Jan 28 '14 at 23:20
  • 12
    @Ismael in theory, casting to array should be enough. In practice we need to cast all leaf nodes too, which are also objects. A naive cast leaves the leaves as SimpleXML objects. json_encode casts recursively saving a lot of legwork. – Peter Mellett May 01 '14 at 11:00
  • 2
    If you do not have text values into your `$array` variable, it is perhaps because of CDATA. To solve it, load your XML with: `new SimpleXMLElement($xml, LIBXML_NOCDATA)`. – Jonathan Petitcolas Jul 25 '14 at 09:24
  • 1
    ps. $xml = str_replace(array(''),'',$xml); – user956584 Jan 15 '15 at 11:51
  • 1
    It doesn't work like that. That code wouldn't convert even a simple XML like this one `Hello!`. Run the code and you'll see that the **** `c` attribute is lost! Please check the full code here if you don't want any bad surprises https://github.com/gaarf/XML-string-to-PHP-array/blob/master/xmlstr_to_array.php or see my answer below http://stackoverflow.com/a/30234924/828366 – Francesco Casula May 14 '15 at 10:21
16

The method used in the accepted answer drop attributes when encountering child elements with only a text node. For example:

$xml = '<container><element attribute="123">abcd</element></container>';
print_r(json_decode(json_encode(simplexml_load_string($xml, "SimpleXMLElement", LIBXML_NOCDATA)),1));

Array
(
    [element] => abcd
)

My solution (and I wish I could give credit here because I'm sure I adapted this from something):

function XMLtoArray($xml) {
    $previous_value = libxml_use_internal_errors(true);
    $dom = new DOMDocument('1.0', 'UTF-8');
    $dom->preserveWhiteSpace = false; 
    $dom->loadXml($xml);
    libxml_use_internal_errors($previous_value);
    if (libxml_get_errors()) {
        return [];
    }
    return DOMtoArray($dom);
}

function DOMtoArray($root) {
    $result = array();

    if ($root->hasAttributes()) {
        $attrs = $root->attributes;
        foreach ($attrs as $attr) {
            $result['@attributes'][$attr->name] = $attr->value;
        }
    }

    if ($root->hasChildNodes()) {
        $children = $root->childNodes;
        if ($children->length == 1) {
            $child = $children->item(0);
            if (in_array($child->nodeType,[XML_TEXT_NODE,XML_CDATA_SECTION_NODE])) {
                $result['_value'] = $child->nodeValue;
                return count($result) == 1
                    ? $result['_value']
                    : $result;
            }

        }
        $groups = array();
        foreach ($children as $child) {
            if (!isset($result[$child->nodeName])) {
                $result[$child->nodeName] = DOMtoArray($child);
            } else {
                if (!isset($groups[$child->nodeName])) {
                    $result[$child->nodeName] = array($result[$child->nodeName]);
                    $groups[$child->nodeName] = 1;
                }
                $result[$child->nodeName][] = DOMtoArray($child);
            }
        }
    }
    return $result;
}

$xml = '
    <aaaa Version="1.0">
       <bbb>
         <cccc>
           <dddd id="123" />
           <eeee name="john" age="24" />
           <ffff type="employee">Supervisor</ffff>
         </cccc>
       </bbb>
    </aaaa>
';
print_r(XMLtoArray($xml));

Array
(
    [aaaa] => Array
        (
            [@attributes] => Array
                (
                    [Version] => 1.0
                )

            [bbb] => Array
                (
                    [cccc] => Array
                        (
                            [dddd] => Array
                                (
                                    [@attributes] => Array
                                        (
                                            [id] => 123
                                        )

                                )

                            [eeee] => Array
                                (
                                    [@attributes] => Array
                                        (
                                            [name] => john
                                            [age] => 24
                                        )

                                )

                            [ffff] => Array
                                (
                                    [@attributes] => Array
                                        (
                                            [type] => employee
                                        )

                                    [_value] => Supervisor
                                )

                        )

                )

        )

)
Eaten by a Grue
  • 19,208
  • 10
  • 77
  • 99
  • 2
    This is the only answer I've found which handled node attributes as well as arrays. Very easy to understand too. – Brainware Aug 19 '20 at 02:36
  • Wonderful. You saved me at least 18 yerars of debugging! – Mad Marvin Jan 28 '21 at 20:15
  • This answer also fails to account for nodes that that contain text as well as XML children. I have spent hours looking now. I am starting to believe there is no XML solution anywhere that can properly parse all XML, and keep everything! – InterLinked Nov 25 '21 at 01:43
10

See https://github.com/gaarf/XML-string-to-PHP-array/blob/master/xmlstr_to_array.php

<?php
/**
  * convert xml string to php array - useful to get a serializable value
  *
  * @param string $xmlstr
  * @return array
  *
  * @author Adrien aka Gaarf & contributors
  * @see http://gaarf.info/2009/08/13/xml-string-to-php-array/
*/
function xmlstr_to_array($xmlstr) {
  $doc = new DOMDocument();
  $doc->loadXML($xmlstr);
  $root = $doc->documentElement;
  $output = domnode_to_array($root);
  $output['@root'] = $root->tagName;
  return $output;
}
function domnode_to_array($node) {
  $output = array();
  switch ($node->nodeType) {
    case XML_CDATA_SECTION_NODE:
    case XML_TEXT_NODE:
      $output = trim($node->textContent);
    break;
    case XML_ELEMENT_NODE:
      for ($i=0, $m=$node->childNodes->length; $i<$m; $i++) {
        $child = $node->childNodes->item($i);
        $v = domnode_to_array($child);
        if(isset($child->tagName)) {
          $t = $child->tagName;
          if(!isset($output[$t])) {
            $output[$t] = array();
          }
          $output[$t][] = $v;
        }
        elseif($v || $v === '0') {
          $output = (string) $v;
        }
      }
      if($node->attributes->length && !is_array($output)) { //Has attributes but isn't an array
        $output = array('@content'=>$output); //Change output into an array.
      }
      if(is_array($output)) {
        if($node->attributes->length) {
          $a = array();
          foreach($node->attributes as $attrName => $attrNode) {
            $a[$attrName] = (string) $attrNode->value;
          }
          $output['@attributes'] = $a;
        }
        foreach ($output as $t => $v) {
          if(is_array($v) && count($v)==1 && $t!='@attributes') {
            $output[$t] = $v[0];
          }
        }
      }
    break;
  }
  return $output;
}
Francesco Casula
  • 24,611
  • 13
  • 128
  • 130
9

Surprised no one mentioned xml_parse_into_struct:

$simple = "<para><note>simple note</note></para>";
$p = xml_parser_create();
xml_parse_into_struct($p, $simple, $vals, $index);
xml_parser_free($p);
echo "Index array\n";
print_r($index);
echo "\nVals array\n";
print_r($vals);
eozzy
  • 62,241
  • 100
  • 265
  • 409
  • Sometimes I wonder what the developer who created the PHP XML implementation was thinking when xml_parse_into_struct was designed ... – Anibal Sanchez Jan 08 '20 at 10:28
5

XML To Array

More Details Visit https://github.com/sapankumarmohanty/lamp/blob/master/Crate-XML-2-Array

//Convert XML to array and SOAP XML to array

function xml2array($contents, $get_attributes = 1, $priority = 'tag')
    {
        if (!$contents) return array();
        if (!function_exists('xml_parser_create')) {
            // print "'xml_parser_create()' function not found!";
            return array();
        }
        // Get the XML parser of PHP - PHP must have this module for the parser to work
        $parser = xml_parser_create('');
        xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, "UTF-8"); // http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
        xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
        xml_parse_into_struct($parser, trim($contents) , $xml_values);
        xml_parser_free($parser);
        if (!$xml_values) return; //Hmm...
        // Initializations
        $xml_array = array();
        $parents = array();
        $opened_tags = array();
        $arr = array();
        $current = & $xml_array; //Refference
        // Go through the tags.
        $repeated_tag_index = array(); //Multiple tags with same name will be turned into an array
        foreach($xml_values as $data) {
            unset($attributes, $value); //Remove existing values, or there will be trouble
            // This command will extract these variables into the foreach scope
            // tag(string), type(string), level(int), attributes(array).
            extract($data); //We could use the array by itself, but this cooler.
            $result = array();
            $attributes_data = array();
            if (isset($value)) {
                if ($priority == 'tag') $result = $value;
                else $result['value'] = $value; //Put the value in a assoc array if we are in the 'Attribute' mode
            }
            // Set the attributes too.
            if (isset($attributes) and $get_attributes) {
                foreach($attributes as $attr => $val) {                                   
                                    if ( $attr == 'ResStatus' ) {
                                        $current[$attr][] = $val;
                                    }
                    if ($priority == 'tag') $attributes_data[$attr] = $val;
                    else $result['attr'][$attr] = $val; //Set all the attributes in a array called 'attr'
                }
            }
            // See tag status and do the needed.
                        //echo"<br/> Type:".$type;
            if ($type == "open") { //The starting of the tag '<tag>'
                $parent[$level - 1] = & $current;
                if (!is_array($current) or (!in_array($tag, array_keys($current)))) { //Insert New tag
                    $current[$tag] = $result;
                    if ($attributes_data) $current[$tag . '_attr'] = $attributes_data;
                                        //print_r($current[$tag . '_attr']);
                    $repeated_tag_index[$tag . '_' . $level] = 1;
                    $current = & $current[$tag];
                }
                else { //There was another element with the same tag name
                    if (isset($current[$tag][0])) { //If there is a 0th element it is already an array
                        $current[$tag][$repeated_tag_index[$tag . '_' . $level]] = $result;
                        $repeated_tag_index[$tag . '_' . $level]++;
                    }
                    else { //This section will make the value an array if multiple tags with the same name appear together
                        $current[$tag] = array(
                            $current[$tag],
                            $result
                        ); //This will combine the existing item and the new item together to make an array
                        $repeated_tag_index[$tag . '_' . $level] = 2;
                        if (isset($current[$tag . '_attr'])) { //The attribute of the last(0th) tag must be moved as well
                            $current[$tag]['0_attr'] = $current[$tag . '_attr'];
                            unset($current[$tag . '_attr']);
                        }
                    }
                    $last_item_index = $repeated_tag_index[$tag . '_' . $level] - 1;
                    $current = & $current[$tag][$last_item_index];
                }
            }
            elseif ($type == "complete") { //Tags that ends in 1 line '<tag />'
                // See if the key is already taken.
                if (!isset($current[$tag])) { //New Key
                    $current[$tag] = $result;
                    $repeated_tag_index[$tag . '_' . $level] = 1;
                    if ($priority == 'tag' and $attributes_data) $current[$tag . '_attr'] = $attributes_data;
                }
                else { //If taken, put all things inside a list(array)
                    if (isset($current[$tag][0]) and is_array($current[$tag])) { //If it is already an array...
                        // ...push the new element into that array.
                        $current[$tag][$repeated_tag_index[$tag . '_' . $level]] = $result;
                        if ($priority == 'tag' and $get_attributes and $attributes_data) {
                            $current[$tag][$repeated_tag_index[$tag . '_' . $level] . '_attr'] = $attributes_data;
                        }
                        $repeated_tag_index[$tag . '_' . $level]++;
                    }
                    else { //If it is not an array...
                        $current[$tag] = array(
                            $current[$tag],
                            $result
                        ); //...Make it an array using using the existing value and the new value
                        $repeated_tag_index[$tag . '_' . $level] = 1;
                        if ($priority == 'tag' and $get_attributes) {
                            if (isset($current[$tag . '_attr'])) { //The attribute of the last(0th) tag must be moved as well
                                $current[$tag]['0_attr'] = $current[$tag . '_attr'];
                                unset($current[$tag . '_attr']);
                            }
                            if ($attributes_data) {
                                $current[$tag][$repeated_tag_index[$tag . '_' . $level] . '_attr'] = $attributes_data;
                            }
                        }
                        $repeated_tag_index[$tag . '_' . $level]++; //0 and 1 index is already taken
                    }
                }
            }
            elseif ($type == 'close') { //End of tag '</tag>'
                $current = & $parent[$level - 1];
            }
        }
        return ($xml_array);
    }
    
    // Let's call the this above function xml2array
    
    xml2array($xmlContent, $get_attributes = 3, $priority = 'tag'); // it will work 100% if not ping me @skype: sapan.mohannty
    
//  Enjoy coding
Community
  • 1
  • 1
htngapi
  • 345
  • 3
  • 7
1

I liked this question and some answers was helpful to me, but i need to convert the xml to one domination array, so i will post my solution maybe someone need it later:

<?php
$xml = json_decode(json_encode((array)simplexml_load_string($xml)),1);
$finalItem = getChild($xml);
var_dump($finalItem);

function getChild($xml, $finalItem = []){
    foreach($xml as $key=>$value){
        if(!is_array($value)){
            $finalItem[$key] = $value;
        }else{
            $finalItem = getChild($value, $finalItem);
        }
    }
    return $finalItem;
}
?>  
Mohammad Alabed
  • 809
  • 6
  • 17
1

Two lines of code (https://www.php.net/manual/en/book.simplexml.php#113485)

$xml = new SimpleXMLElement("<your><xml><string>ok</string></xml></your>");
$array = (array)$xml;