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();
        }

33 comments:

Unknown said...

I need something like this for quite some time.

Cheers, Jeremy Cushing

Unknown 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 ?

PyneJ 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.

Unknown 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

PyneJ 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

Unknown 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);

PyneJ 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.

Unknown 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

MIha nicu 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!

PyneJ 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;
...
}

Unknown 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

Anonymous 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


merlin said...

Thanks for sharing this useful information.it provides insightful information.Worth reading.

Selenium Training in chennai | Selenium Training in annanagar | Selenium Training in omr | Selenium Training in porur | Selenium Training in tambaram | Selenium Training in velachery


Training for IT and Software Courses said...

This is excellent information. It is amazing and wonderful to visit your site.Thanks for sharing this information,this is useful to me.

Teradata Online Training

Teradata Classes Online

Teradata Training Online

Online Teradata Course

Teradata Course Online

Training for IT and Software Courses said...

Wow it is really wonderful and awesome thus it is very much useful for me to understand many concepts and helped me a lot.

Cloud Computing Online Training

Cloud Computing Classes Online

Cloud Computing Training Online

Online Cloud Computing Course

Cloud Computing Course Online

Training for IT and Software Courses said...

After reading your article I was amazed. I know that you explain it very well. And I hope that other readers will also experience how I feel after reading your article.

Machine Learning certification Online Training in bangalore

Machine Learning certification courses in bangalore

Machine Learning certification classes in bangalore

Machine Learning certification Online Training institute in bangalore

Machine Learning certification course syllabus

best Machine Learning certification Online Training

Machine Learning certification Online Training centers

deiva 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..
web designing training in chennai

web designing training in omr

digital marketing training in chennai

digital marketing training in omr

rpa training in chennai

rpa training in omr

tally training in chennai

tally training in omr

Jayalakshmi said...

This post is really a fastidious one it assists new web people, who are wishing for blogging.
angular js training in chennai

angular js training in tambaram

full stack training in chennai

full stack training in tambaram

php training in chennai

php training in tambaram

photoshop training in chennai

photoshop training in tambaram

jeni said...

This is the exact information I am been searching for, Thanks for sharing the required infos with the clear update and required points.
sap training in chennai

sap training in velachery

azure training in chennai

azure training in velachery

cyber security course in chennai

cyber security course in velachery

ethical hacking course in chennai

ethical hacking course in velachery

Unknown said...

excellent article....keep sharing..

tejaswani blogs said...

Excellent material with unique content, and it is really important to be aware of blog-based material.

1000 Social BookMarking Sites List

Ravi Varma said...

https://ravivarma.in/

Unknown said...

Thanks for Sharing
Best Interior Designers

Let2know said...

I'm grateful to you for sharing this plenty of valuable assessment. I found this asset most extreme helpful for me. much thanks to you a lot for better do whatever it takes. Edius Video Editing Software

Back linker said...

I could following to appreciation for the undertakings you have made recorded as a printed copy this leaflet. I'm trusting the thesame fine take diversion doings from you inside the thinking of as capably. thankful to you...
Bitdefender Antivirus Plus 2019 Crack

haseeb said...

Each and every year, when 14th August arrives, it fills our hearts with love for our nation and memories of all those who lost their lives to get freedom for ..Independence Day Of Pakistan Greetings

Maya said...


This blog is great and has a lot of useful information.
mIRC