Tuesday, 6 February 2007

PHP: XML to Array and backwards

Here the XML with PHP solution: XML->Array and Array->XML.
Work with it as with usual array.

Format XML->Array
_c - children
_v - value
_a - attributes

This is 1.1 :)

Example #1 (1.xml):


<ddd>
<onemore dd="55">
<tt>333</tt>
<tt ss="s1">555</tt>
<tt>777</tt>
</onemore>
<two>sdf rr</two>
</ddd>



The code:

$xml=xml2ary(file_get_contents('1.xml'));
print_r($xml);


Here is the Array result:

Array
(
[ddd] => Array (
[_c] => Array (
[onemore] => Array (
[_a] => Array (
[dd] => 55
)
[_c] => Array (
[tt] => Array (
[0] => Array (
[_v] => 333
)
[1] => Array (
[_a] => Array (
[ss] => s1
)
[_v] => 555
)
[2] => Array (
[_v] => 777
)
)
)
)
[two] => Array (
[_v] => sdf rr
)
)
)
)


Example #2: (1.xml as described before)

$xml=xml2ary(file_get_contents('1.xml'));
$xml['ddd']['_c']['twomore']=$xml['ddd']['_c']['onemore'];
$xml['ddd']['_c']['twomore']['_c']['tt'][0]['_v']='hello';
echo ary2xml($xml);


Will output result:

<ddd>
<onemore dd="55">
<tt>hello</tt>
<tt ss="s1">555</tt>
<tt>777</tt>
</onemore>
<two>sdf rr</two>
<twomore dd="55">
<tt>hello</tt>
<tt ss="s1">555</tt>
<tt>777</tt>
</twomore>
</ddd>



THE Sources: :)

/*
Working with XML. Usage:
$xml=xml2ary(file_get_contents('1.xml'));
$link=&$xml['ddd']['_c'];
$link['twomore']=$link['onemore'];
// ins2ary(); // dot not insert a link, and arrays with links inside!
echo ary2xml($xml);
*/

// XML to Array
function xml2ary(&$string) {
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_parse_into_struct($parser, $string, $vals, $index);
xml_parser_free($parser);

$mnary=array();
$ary=&$mnary;
foreach ($vals as $r) {
$t=$r['tag'];
if ($r['type']=='open') {
if (isset($ary[$t])) {
if (isset($ary[$t][0])) $ary[$t][]=array(); else $ary[$t]=array($ary[$t], array());
$cv=&$ary[$t][count($ary[$t])-1];
} else $cv=&$ary[$t];
if (isset($r['attributes'])) {foreach ($r['attributes'] as $k=>$v) $cv['_a'][$k]=$v;}
$cv['_c']=array();
$cv['_c']['_p']=&$ary;
$ary=&$cv['_c'];

} elseif ($r['type']=='complete') {
if (isset($ary[$t])) { // same as open
if (isset($ary[$t][0])) $ary[$t][]=array(); else $ary[$t]=array($ary[$t], array());
$cv=&$ary[$t][count($ary[$t])-1];
} else $cv=&$ary[$t];
if (isset($r['attributes'])) {foreach ($r['attributes'] as $k=>$v) $cv['_a'][$k]=$v;}
$cv['_v']=(isset($r['value']) ? $r['value'] : '');

} elseif ($r['type']=='close') {
$ary=&$ary['_p'];
}
}

_del_p($mnary);
return $mnary;
}

// _Internal: Remove recursion in result array
function _del_p(&$ary) {
foreach ($ary as $k=>$v) {
if ($k==='_p') unset($ary[$k]);
elseif (is_array($ary[$k])) _del_p($ary[$k]);
}
}

// Array to XML
function ary2xml($cary, $d=0, $forcetag='') {
$res=array();
foreach ($cary as $tag=>$r) {
if (isset($r[0])) {
$res[]=ary2xml($r, $d, $tag);
} else {
if ($forcetag) $tag=$forcetag;
$sp=str_repeat("\t", $d);
$res[]="$sp<$tag";
if (isset($r['_a'])) {foreach ($r['_a'] as $at=>$av) $res[]=" $at=\"$av\"";}
$res[]=">".((isset($r['_c'])) ? "\n" : '');
if (isset($r['_c'])) $res[]=ary2xml($r['_c'], $d+1);
elseif (isset($r['_v'])) $res[]=$r['_v'];
$res[]=(isset($r['_c']) ? $sp : '')."</$tag>\n";
}

}
return implode('', $res);
}

// Insert element into array
function ins2ary(&$ary, $element, $pos) {
$ar1=array_slice($ary, 0, $pos); $ar1[]=$element;
$ary=array_merge($ar1, array_slice($ary, $pos));
}

60 comments:

Justin said...

Thanks!
You solved my problem

Din gamle far said...

Just what I needed! :)

Anonymous said...

Perhaps you should put a if(!function_exists()).. around the internal declaration of _del_p. If you use xml2ary more than once, PHP complains.

leoSr said...

To anonymous:
Thanks for your idea about _del_p. Bug is fixed now:)

Kyle said...

nice job!

Tomek said...

Hi,

great function!

I'm trying to use it to read from an external xml file. The xml file is actually an url (google maps). Somehow I can't get it working.

I first declare the url this way:
$xml_location = "'http://maps.google.com/maps/geo?q=$location&output=xml&key=ABQIAAAAJfKsQWO6RF10dFkeiMCQ-RTuc1leHq4ZyxUzjHJLRPnNfWTYLhSmhVnunLB95kDrffV4E0R-m4xnUw'";

Then I place that variable into the
function this way:
$xml=xml2ary(file_get_contents($xml_location));

I get the following error:
Warning: file_get_contents('http://maps.google.com/maps/geo?q=nederland&output=xml&key=ABQIAAAAJfKsQWO6RF10dFkeiMCQ-RTuc1leHq4ZyxUzjHJLRPnNfWTYLhSmhVnunLB95kDrffV4E0R-m4xnUw') [function.file-get-contents]: failed to open stream: No such file or directory in /home/sites/site19/web/maps/map.php on line 158

When I put the contents of $xml_location directly into the function. For example:
$xml=xml2ary(file_get_contents('http://maps.google.com/maps/geo?q=nederland&output=xml&key=ABQIAAAAJfKsQWO6RF10dFkeiMCQ-RTuc1leHq4ZyxUzjHJLRPnNfWTYLhSmhVnunLB95kDrffV4E0R-m4xnUw'));

It does work...

What's going wrong over here?

Mauricio said...

Great piece of code! Thanks!
Wouldn't be a good idea adding support for html content on ary2xml function? I made a small mod for your code, adding the CDATA tag between the value (couldn't paste the code)

just my 2 cents, thanks ;)

leoSr said...

To Tomek: You got an error "No such file or directory" becouse of $xml_location = "'http..'" (superfluous single quotes)

To Mauricio: please send me by email your code about CDATA, and I'll add it into this post.

Eqhes said...

Hi all!

the Mauricio's CDATA hack is there: http://forums.kayako.com/showthread.php?t=11269

Search "// small hack ;)" over the page; above this text is the hack.


Bye!!

deo said...

You're a lifesaver. Quite a brilliant little script. EXACTLY what I needed.

Ederson said...

GREAT! Thanks! ;-)

Sapar said...

Hola, bro!

A nice piece of work.

1. ary2xml() returns xml without end tag. Is it a feature?
2. May be it is better to have it with a header which states that is xml script&

Of course, I made these changes and using it. If anybody needs this function with above features included, just let me know at sapargali at gmail dot com.

Andrei said...

Thank you ! This is a nice script that solved a part of my problem.

lionlancer said...

Thanks a lot for the BRILLIANT script!!! :D

You save me from hours of headaches! ^__^

Anonymous said...

Thanks for saving me hours of work!

James Little said...

awesome script, thanks! Nice logical structure and saved me a lot of time :)

Anonymous said...

greate script... but how do i escape or other html tags from beeing arrayed? :D

Anonymous said...

Excellent script ... thanks for saving many hours of work

Anonymous said...

very cool script, thanks - learning this stuff and this is very useful.

Anonymous said...

PERFECT!!! I have been looking around FOREVER for a nice xml=>array class/functions out there. I modified your program to be class-based, but other than that, this is SAAAAWEEEEET!!! I love the fact that it standardizes everything...like if you have a subarray, it automatically sets it up to have a 0 index, rather than waiting until there are multiple children before making it. That is easily the sweetest thing that I have ever seen!!!!!!

georg said...

Thanks a lot

Anonymous said...

Plug&play ... really!! Thks.

Anonymous said...

I changed:
elseif (isset($r['_v'])) $res[]=$r['_v']
to:
elseif (isset($r['_v'])) $res[]=htmlspecialchars($r['_v'])
.

Now values like "hal&lo" will go well.

Agustino.O said...

Excelent, thanks a lot.
Hay to wigle with it a little, but it served it's purpose.
Only script around that did this thing.
Of course when you have acces to PHP5 there's simpleXML.

dyreknyc said...

Hey.. this is a super tool. Quick question for you. I would like all attributes to have a numerical index. Is this possible? Example:

[ddd]
[onemore dd="55"]
[tt]333[/tt]
[tt ss="s1"]555[/tt]
[tt]777[/tt]
[/onemore]
[two]sdf rr[/two]
[/ddd]
(switched < to [, to post)
becomes this:

Array
(
[ddd] => Array (
[0] => Array (
[_c] => Array (
[onemore] => Array (
[0] => Array (
[_a] => Array (
[dd] => 55
)
[_c] => Array (
[tt] => Array (
[0] => Array (
[_v] => 333
)
...

any ideas?
This way I can use a uniform parse on all depths of the array, instead of checking if key [0] exists, and so on... THanks

CD said...

Brilliant! Worked flawlessly! Can I buy you a Guiness?

James Star said...

Wikkked, I spent 1 week looking for xml -> array and array -> xml... checked out SPL, simpleXML but just came across this script and it works sweeet as... Q: How do you save the xml? e.g. saveXML()?? Thanks, James from NZ.

Todd said...

Very awesome!

If you have time, could you show an example of someone selecting the values of the tt element where the ss attribute equals s1?

This doesn't seem to work:
$xml['ddd']['_c']['onemore']['_c']['tt']['_a']['ss']['s1']['_v']

Mavrick said...

@Todd

try: $xml['ddd']['_c']['onemore']['_c']['tt']['_a']['ss']['_v']['s1']

_c, _v, _a looks like it comes before the result

gprod said...

Great! thanks a lot as I'v been hitting my head against the wall for a good week now.
At last a simple function to extract xml into a array and hich support multiples tags with the same name and supports attributes as well.
Really saved my day

Anonymous said...

I'm confused how to actually traverse this multidimensional array.

if I have:
$xml = xml2ary(file_get_contents($url));

then my xml gets stored as an array successfully.

But how do I traverse it? I tried a for loop, but it's not working. I don't know the values that are in the array and all the examples you have assume that you know the keys. Can you help me with this?

Nick said...

Wow, Great Work. This helped me tremendously at the last minutes. Thanks for your great work.

Anonymous said...

Google checkout is using a modified version of this in their PHP code.

Ali said...

mapping is interesting but it's better to omit the _v and put the value (if any) in _c. because an element can't have both value and children at the same time. when using the value code must check if the _c is a value or an array!!!

Anonymous said...

Thanks a million for this. Been struggling for a while until I found your solution - solved the problem in an afternoon.

Mariano said...

Great job in those functions they are just what i needed. Thanks!

Hasie said...

hi gentlepeople,

don't want to spoil the party here, but this somewhat larger xml isn't parsed beyond the 1st child:

here it is:
http://www.socon.nl/bigfile.xml

has anybody advise on this?

Harry

Smart-Coder said...

Great Job !!!!!!!!

Great Coder !!!!!!!

Great Mind :)

Anonymous said...

Thank you very much for giving this for free !!

Tejas said...

Hi,
Good Work.

When i am trying
ary2xml()it not returns xml code..
i do print_r(ary2xml($xml)); just getting same array again..

I want same xml again & save it ti same pllace..

Thanks in advance

Matteo Terruzzi said...

Wonderful!

Anonymous said...

This function rocks. Been using this function perfectly, but in my last project I'm using it with an exported XML file (multitabs) from Excel and it gives me a weird error or well no error, it just stops working :/

The exact problem is that when parsing it shows what should be a value( _v) as a child (_c) which then messes up the entire array of course and at that time the function ends all arrays and thus stops parsing. Seeing as this is midway the first tab, I'm a little confused, there's nothing wrong with the text in that value and it has parsed most of the others before it perfectly.

I'm a little confused why it would do that, it has worked perfectly in the past. I double checked the xml file, but that's all solid. Any known bug that might cause this??

Anonymous said...

While this works reasonably well in procedural scripts, it's useless for OOP apps. How about upgrading and presenting us with an awesome state-of-the-art OOP-class?
keep up the good work
mr.spots

Anup Khandelwal said...

Nice Script. But not able to parse big XML? Why so?

Rupert Sharp said...

This was so useful, thanks very much.

Dataxtream said...

2 years still relevant and useful. Thx.

TuxSax said...

Anup, parsing big XML file may require you to change your php.ini resource limits settings, to allow php to allocate more memory, I've set it to 128M and it worked.
BTW, great script, I finally got what I was looking for ages to do, so simple and effective!!!
Thanks a bunch!

Anonymous said...

Fantastic - have tried a number of different XML->Array converters and this is the only one that's worked perfectly. Thanks!

e_i_pi said...

Great code, I've been hunting around for ages for code to do this. Perfect, absolutely perfect, many thanks

dolce said...

simply amazing! thanks so much :)

Anonymous said...

Really great job.
thanks a lot, very effective and easy to understandable script is this.
This one i will say the best script i have seen ever.

Anonymous said...

WOW this is a fantastic script! I have spent HOURS trying to work this info and here it is. I wish I had tried this first. THANK YOU SO MUCH!

Anonymous said...

Just wanted to say, I was searching for XML to Array scripts and having a horrible time - they all had some fatal flaw (failed to deal with attributes, required installing binary extensions to PHP that are not commonly available on shared hosts, etc.).

This is the only one that worked straight out of the box and that extracts all of the useful information from the XML file. Great job, and thanks for contributing!

Robert Schifreen said...

Many thanks for this. Works very well. Though there's one anomaly which took me a while to work out. If a branch of the XML has multiple entries, the code creates an array, but otherwise it only creates a single variable. So my code for retrieving something from $xml needed to be slightly different, without an array subscript, in this case. Might be useful if it created arrays every time, even with just a single element.

Anonymous said...

Possibly the most interesting thing I have read all week...


http://lobinsurance.info

Anonymous said...

thanks for this tips

Anonymous said...

Thanks for the function. I modified it a little so that it parses an XML file instead of a string.

You can do this by changing the argument to ask for a URL, then add the following lines right after the {:

$handle = fopen($file, "r");
$string = fread($handle);

Anonymous said...

Great! It works

Ed Greenberg said...

Very nice function. Many thanks.

Anonymous said...

Thanks for the script.

Only find one bug. If there is a & sign in a tag xml & array script will stop working anybody a tweak/bugfix for this.