php sources here, and others...

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

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