Tuesday, February 16, 2010

Convert XML to Array and Array to XML in PHP

This class library provides two XML related functions.  One to convert an XML Tree into a PHP array and another to convert a complex PHP object into XML.



The first is the xml_to_array function witch will read through an entire xml tree and convert it into a single multi-dimensional array.  That is each node in the root node will be added as items in the array and all children and properties will be added to the respective items.  This is done in a completely recursive manner so that nodes within nodes within nodes will be fully read and created as arrays within arrays within arrays.  The exact format of the generated array is dependent on the options selected.

The default options will merge all the properties and children directly into the node but this may need to be disabled if you have children with the same name as properties or if you need to distinguish between the two types.

Here is a sample XML file, from a svn dump.
svn log --non-interactive --xml
<?xml version="1.0"?>
<log>
<logentry
   revision="114">
<author>pynej</author>
<date>2010-01-11T14:20:42.200771Z</date>
<msg>Removed smarty 2.0 code.
</msg>
</logentry>
<logentry
   revision="113">
<author>pynej</author>
<date>2008-08-08T05:14:31.046815Z</date>
<msg>* Changed videos to search recursively.  This may be configured by an extra variable in the future.</msg>
</logentry>
</log>

Calling the conversion with the default options would look like so.
print_r(xmlutils::xml_to_array($xml));
Array
(
    [name] => log
    [0] => Array
        (
            [name] => logentry
            [revision] => 114
            [author] => pynej
            [date] => 2010-01-11T14:20:42.200771Z
            [msg] => Removed smarty 2.0 code.
        )

    [1] => Array
        (
            [name] => logentry
            [revision] => 113
            [author] => pynej
            [date] => 2008-08-08T05:14:31.046815Z
            [msg] => * Changed videos to search recursively.  This may be configured by an extra variable in the future.
        )
)

Where as the call with no options generates the more detailed but less user friendly output.
print_r(xmlutils::xml_to_array($xml), 0);
Array
(
    [name] => log
    [children] => Array
        (
            [0] => Array
                (
                    [name] => logentry
                    [attributes] => Array
                        (
                            [revision] => 114
                        )
                    [children] => Array
                        (
                            [0] => Array
                                (
                                    [name] => author
                                    [value] => pynej
                                )
                            [1] => Array
                                (
                                    [name] => date
                                    [value] => 2010-01-11T14:20:42.200771Z
                                )
                            [2] => Array
                                (
                                    [name] => msg
                                    [value] => Removed smarty 2.0 code.
                                )
                        )
                )

            [1] => Array
                (
                    [name] => logentry
                    [attributes] => Array
                        (
                            [revision] => 113
                        )
                    [children] => Array
                        (
                            [0] => Array
                                (
                                    [name] => author
                                    [value] => pynej
                                )
                            [1] => Array
                                (
                                    [name] => date
                                    [value] => 2008-08-08T05:14:31.046815Z
                                )
                            [2] => Array
                                (
                                    [name] => msg
                                    [value] => * Changed videos to search recursively.  This may be configured by an extra variable in the future.
                                )
                        )
                )
        )
) 

A more useful format might be to retail the attribute/children breakdown but processes the rest.
print_r(xmlutils::xml_to_array($xml), xmlutils::XML_MERGE_ATTRIBUTES | xmlutils::XML_VALUE_PAIRS | xmlutils::XML_MERGE_VALUES);
Array
(
    [name] => log
    [children] => Array
        (
            [0] => Array
                (
                    [name] => logentry
                    [revision] => 114
                    [children] => Array
                        (
                            [author] => pynej
                            [date] => 2010-01-11T14:20:42.200771Z
                            [msg] => Removed smarty 2.0 code.
                        )
                )

            [1] => Array
                (
                    [name] => logentry
                    [revision] => 113
                    [children] => Array
                        (
                            [author] => pynej
                            [date] => 2008-08-08T05:14:31.046815Z
                            [msg] => * Changed videos to search recursively.  This may be configured by an extra variable in the future.
                        )
                )
        )
) 



The second function array_to_xml will to the inverse of this and will generate a XML Tree from a complex anonymous array object.  This object can contain any amount of data and will be recursively traversed and added to the tree.

Also note that XML Tree's cant have shared nodes or references so if any exist in the source object the data in them will be duplicated in the XML Tree.  There is no way to preserve these references with this tool.  Also note that no recursion checks are done so an object with circular recursion will lock up the process. 

The following array converted to XML will look like so.
print_r($logList);
Array
(
    [name] => log
    [0] => Array
        (
            [name] => logentry
            [revision] => 114
            [author] => pynej
            [date] => 2010-01-11T14:20:42.200771Z
            [msg] => Removed smarty 2.0 code.
        )

    [1] => Array
        (
            [name] => logentry
            [revision] => 113
            [author] => pynej
            [date] => 2008-08-08T05:14:31.046815Z
            [msg] => Array
                (
                    [0] => * Added links to the debug section to view the contents of ajax calls.
                    [1] => * Added query->get_md5 to calculate the md5 hash of a query result.
        )
)

print_r(xmlutils::array_to_xml($logList));
<?xml version="1.0" encoding="utf-8"?>
<data>
 <name>log</name>
 <data-item>
  <name>logentry</name>
  <revision>114</revision>
  <author>pynej</author>
  <date>2010-01-11T14:20:42.200771Z</date>
  <msg>Removed smarty 2.0 code.</msg>
 </data-item>
 <data-item>
  <name>logentry</name>
  <revision>113</revision>
  <author>pynej</author>
  <date>2008-08-08T05:14:31.046815Z</date>
  <msg>
    <msg-item>* Added links to the debug section to view the contents of ajax calls.</msg-item>
    <msg-item>* Added query->get_md5 to calculate the md5 hash of a query result.</msg-item>
  </msg>
 </data-item>
</data>




Source Code:
xmlutils.php
/**
 * This class contains mothods to convert a xml tree into a complex array with nested properties and to convert a complex array object into an xml tree.
 *
 * @package xml-utils
 * @author Jeremy Pyne <jeremy.pyne@gmail.com>
 * @license CC:BY/NC/SA  http://creativecommons.org/licenses/by-nc-sa/3.0/
 * @lastupdate February 16 2010
 * @version 1.5
 */
final class xmlutils
{
       /**
         * Add a levels attributes directly to the levels node instead of into an attributes array.
         *
         */
        const XML_MERGE_ATTRIBUTES = 1;
        /**
         * Merge the attribures of a level into the parent level that they belong to.
         *
         */
        const XML_MERGE_VALUES = 2;
        /**
         * Add a levels children directly to the levels node instead of into a children array.
         *
         */
        const XML_MERGE_CHIILDREN = 4;
        /**
         * Process value as a lone entry under their level and ignore the other attributes and children.
         *
         */
        const XML_VALUE_PAIRS = 8;
        /**
         * Split the value of a node into an array on newlines.
         *
         */
        const XML_SPLIT_VALUES = 16;
        /**
         * If a value is an array with a single item, just use the item.
         *
         */
        const XML_SPLIT_SHIFT = 32;

        /**
         * This function will convert an XML tree into a multi-dimentional array.
         *
         * @param SimpleXMLElement $xml
         * @param bitfield $ops
         * @return array
         */
        public static function xml_to_array($xml, $ops=63) {
                // Store the name of this level.
                $level = array();
                $level["name"] = $xml->getName();

                // Grab the value of this level.
                $value = trim((string)$xml);

                // If we have a value, process it.
                if($value) {
                        // Split the value into an array on newlines.
                        if($ops & self::XML_SPLIT_VALUES)
                                $value = explode("\n", $value);

                        // If the value is an array with one item, remove the array.
                        if($ops & self::XML_SPLIT_SHIFT)
                                if(sizeof($value) == 1)
                                        $value = array_shift($value);

                        // Store the value of this level.
                        $level["value"] = $value;
                }

                // If this level had a value just return the name/value as an array.
                if($ops & self::XML_VALUE_PAIRS && array_key_exists("value", $level))
                        return array($level["name"] => $level["value"]);

                // Loop through each atribute of this level.
                foreach($xml->attributes() as $attribute) {
                        // Add each attribure directly to this level in the array.
                        if($ops & self::XML_MERGE_ATTRIBUTES)
                                $level[$attribute->getName()] = (string)$attribute;
                        // Add all the attributes to an attributes array under this level in the array.
                        else
                                $level["attributes"][$attribute->getName()] = (string)$attribute;
                }

                // Loop through each child of this level.
                foreach($xml->children() as $children) {
                        // Get an array of this childs data.
                        $child = self::xml_to_array($children, $ops);

                        if($ops & self::XML_MERGE_VALUES) {
                                // Add each child directly to this level  or to the children array of this level in the array.
                                if(sizeof($child) == 1) {
                                        // If there is only one child then merge it up.
                                        if($ops & self::XML_MERGE_CHIILDREN)
                                                $level[array_shift(array_keys($child))] = $child[array_shift(array_keys($child))];
                                        else
                                                $level["children"][array_shift(array_keys($child))] = $child[array_shift(array_keys($child))];
                                } elseif(array_key_exists("value", $child)) {
                                        // If there is a value key then merge it up.
                                        if($ops & self::XML_MERGE_CHIILDREN)
                                                $level[$child["name"]] = $child["value"];
                                        else
                                                $level["children"][$child["name"]] = $child["value"];
                                } elseif(array_key_exists("children", $child)) {
                                        // If there are children, then merge them up.
                                        if($ops & self::XML_MERGE_CHIILDREN)
                                                $level[] = $child;
                                        else
                                                $level["children"][] = $child;
                                } else {
                                        // Otherwise just assigne yourself.
                                        if($ops & self::XML_MERGE_CHIILDREN)
                                                $level[] = $child;
                                        else
                                                $level["children"][] = $child;
                                }
                        } else {
                                $level["children"][] = $child;
                        }
                }


                return $level;
        }

        /**
        * The main function for converting to an XML document.
        * Pass in a multi dimensional array and this recrusively loops through and builds up an XML document.
        *
        * @param array $data
        * @param string $rootNodeName - what you want the root node to be - defaultsto data.
        * @param SimpleXMLElement $xml - should only be used recursively
        * @return string XML
        */
        public static function array_to_xml($data, $rootNodeName = 'data', $xml=null, $parentXml=null)
        {

                // turn off compatibility mode as simple xml throws a wobbly if you don't.
                if (ini_get('zend.ze1_compatibility_mode') == 1)
                {
                        ini_set ('zend.ze1_compatibility_mode', 0);
                }
                //if ($rootNodeName == false) {
                //      $xml = simplexml_load_string("<s/>");
                //}
                if ($xml == null)
                {
                       $xml = simplexml_load_string("<?xml version='1.0' encoding='utf-8'?><$rootNodeName />");
                }

                // loop through the data passed in.
                foreach($data as $key => $value)
                {
                        // Create a name for this item based off the attribute name or if this is a item in an array then the parent nodes name.
                        $nodeName = is_numeric($key) ? $rootNodeName . '-item' : $key;
                        $nodeName = preg_replace('/[^a-z1-9_-]/i', '', $nodeName);

                        // If this item is an array then we will be recursine to the logic is more complex.
                        if (is_array($value)) {
                                // If this node is part of an array we have to proccess is specialy.
                                if (is_numeric($key)) {
                                        // Another exception if this is teh root node and is an array.  In this case we don't have a parent node to use so we must use the current node and not update the reference. 
                                        if($parentXml == null) {
                                                $childXml = $xml->addChild($nodeName);
                                                self::array_to_xml($value, $nodeName, $childXml, $xml);
                                        // If this is a array node then we want to add the item under the parent node instead of out current node. Also we have to update $xml to reflect the change.
                                        } else {
                                                $xml = $parentXml->addChild($nodeName);
                                                self::array_to_xml($value, $nodeName, $xml, $parentXml);
                                        }
                                } else {
                                        // For a normal attribute node just add it to the parent node.
                                        $childXml = $xml->addChild($nodeName);
                                        self::array_to_xml($value, $nodeName, $childXml, $xml);
                                }
                        // If not then it is a simple value and can be directly appended to the XML tree.
                        } else {
                                $value = htmlentities($value);
                                $xml->addChild($nodeName, $value);
                        }
                }

                // Pass back as string or simple xml object.
                return $xml->asXML();
        }

21 comments:

Jeremy Cushing said...

I need something like this for quite some time.

Cheers, Jeremy Cushing

nerik said...

Sorry,

but it seems like the posted source code results uncompleted.

If I run, first I get:


Parse error: syntax error, unexpected ';', expecting T_FUNCTION in C:\Program Files\o2app\efleet\htdocs\xmlutils.php on line 190

Then (if I add the final brace before php end tag) I get:

Fatal error: Call to a member function getName() on a non-object in C:\Program Files\o2app\efleet\htdocs\xmlutils.php on line 54

I think something in the code displayed is missing...

Can you help me please ?

Jeremy Pyne said...

Yes its just the closing { that was missing. As for the error your seeing i guess it doesn't have the best error checking. I'm not sure how you were calling the xml_to_array method but you nee to pass in a SimpleXMLElement object for it to convert. the getName method that is called is a method on this SimpleXMLElement object.

nerik said...

You're right.

I solved.

Thanks!

Reserve Free Lancer said...

this is working properly but when i am sending the child node as

transaction_10 its getting transaction_1

transaction_20 its getting transaction_2

transaction_30 its getting transaction_3


-----------------------------------

Reserve Free Lancer said...

this is working properly but when i am sending the child node as

transaction_10 its getting transaction_1

transaction_20 its getting transaction_2

transaction_30 its getting transaction_3

Jeremy Pyne said...

Hrm, I haven't seen that problem before. If you want to send be a sample file/sample code that demonstrates this problem and I can take a look.

Reserve Free Lancer said...

Thanks alot i fixed that issues

Ethan said...

I'm trying to be able to go back and forth between an array and XML multiple times. But as I try your different examples, the data keeps changing and I can never return the data to it's original form. I copied your example xml into example.xml. I put your code in xmlutils.php and fixed the } issue. Here's my example php:

include('xmlutils.php');

$xmlStr = file_get_contents('example.xml');
$xmlObj = simplexml_load_string($xmlStr);
//$newarray=xmlutils::xml_to_array($xmlObj, xmlutils::XML_MERGE_ATTRIBUTES | xmlutils::XML_VALUE_PAIRS | xmlutils::XML_MERGE_VALUES);
$newarray=xmlutils::xml_to_array($xmlObj, 0);


$xmlStr2=xmlutils::array_to_xml($newarray);
$xmlObj2= simplexml_load_string($xmlStr2);
//$newarray2=xmlutils::xml_to_array($xmlObj2, xmlutils::XML_MERGE_ATTRIBUTES | xmlutils::XML_VALUE_PAIRS | xmlutils::XML_MERGE_VALUES);
$newarray2=xmlutils::xml_to_array($xmlObj2, 0);

print_r($newarray);
echo "\n\n again \n\n";
print_r($newarray2);

Jeremy Pyne said...

Ethan I didn't write these methods to me cyclical like you are trying to sue them. I actually had them in two different places and combined them here for their functionality. To do what you want will require some rewriting of the logic of the decoding? logic to more closely replicate the original object.

Ethan said...

Thank you for your response. I did like the functionality you provided with these and find it's one of the better examples I found in my searches. I did find another example that fit my needs from another programmer. But to make it work, he formatted the array in a specialized way. So while it won't work for just any multidimensional array as yours does, it does fit my needs for now. Thank you, Ethan.

lauren said...

This is a small library to Convert XML to Array and Array to XML in PHP.this type of functionality is required for interacting other language application by using xml as a common interface used by both applications
digital signature sharepoint

myacy said...

Hi, I have a sitemap. php and rss in training I need sitemap.XML, how they manage to make it pass? office@galati-anunturi.ro

RvW said...

is it possible if I have an xml that looks as below:



pynej
2010-01-11T14:20:42.200771Z
Removed smarty 2.0 code.



pynej
2008-08-08T05:14:31.046815Z
* Changed videos to search recursively. This may be configured by an extra variable in the future.



i get an array that looks as below:

Array
(
[name] => log
[0] => Array
(
[name] => logentry
[revision] => 114
[author] => pynej
[date] => 2010-01-11T14:20:42.200771Z
[date-format] => nl
[msg] => Removed smarty 2.0 code.
)

[1] => Array
(
[name] => logentry
[revision] => 113
[author] => pynej
[date] => 2008-08-08T05:14:31.046815Z
[date-format] => nl
[msg] => * Changed videos to search recursively. This may be configured by an extra variable in the future.
)

)

RvW said...

I can not post xml pity. the point is that if I have a format = "nl" in the date ellement i want to get it out is shown in the array. is that possible? I would greatly facilitated!

Jeremy Pyne said...

In the attributes proccesing section you could ass a bit of code/option to move the attributes to a sibbling node of the current element.

// Loop through each atribute of this level.
foreach($xml->attributes() as $attribute) {

if($attribute->getName() == "format")
$parentLevel[$parent->getName() ' . "_" . $attribute->getName()] = (string)$attribute;
...
}

Sathya G said...

I have seen that all will say the same thing repeatedly. But in your blog, I had a chance to get some useful and unique information. I would like to suggest your blog in my dude circle. please keep on updates. Hope it might be much useful for us. keep on updating.
PHP Training in Chennai

vignesjose said...

I'm very happy for this blog site my comment post. Selenium Training in Tambaram We create this blog content is really interesting and use valuable information news.Well job, it's grateful knowledge informative.
Selenium Training in Chennai | Selenium Course in Chennai

karthik anu said...


Being new to the blogging world I feel like there is still so much to learn. Your tips helped to clarify a few things for me as well as giving..
iOS Training in Chennai
Android Training in Chennai
php Training in Chennai

Jayarajan K said...

People have to grab lot of skills and knowledge about big data, analytics. sexiest job of the century. high paying jobs.


online aptitude training

learn core java online

MBA in marketing management

MBA in event management

Big data analytics training

annamalai university distance education mba

Big data for beginners

Analytics courses


Priya Kannan said...

Inspiring writings and I greatly admired what you have to say , I hope you continue to provide new ideas for us all and greetings success always for you..Keep update more information..
PHP Training in Chennai