PHP recursive function to convert multidimensional array to xml - php

I need to convert an array of indefinite depth to an xml string. I thought a recursive function would be better to this since the depth of the array is not unknown and is mostly of 3 levels. Here is the recursive function that I have come up so far
function array2xml($data, $key = ''){
if(!is_array($data)){
return "<".$key.">".$data."</".$key.">";
}
else{
foreach($data as $key => $value){
if(!is_array($value)){
return array2xml($value, $key);
}
else{
return "<".$key.">".array2xml($value)."</".$key.">";
}
}
}
This is the inital call but it returns only the first element in the array. For instance,
echo array2xml([
'students' => [
'names' => [
'frank' => '12',
'jason' => '13',
'beth' => '14'
],
'groups' => [
'yellow' => '165',
'green' => '98'
]
]
]);
Returns this output
<students><names><frank>12</frank></names></students>
Would appreciate it if someone could fix this recursive function so that the elements in the array are printed like this
<students>
<names>
<frankDiaz>12</frank>
<jasonVaaz>13</jason>
<bethDoe>14</beth>
</names>
<groups>
<yellow>165</yellow>
</groups>

The problem is that you you use return in your function's foreach loop and therefore break out of the loop/function prematurely...
Of course you don't factor in formatting etc. either; but that's a minor point.
Update notes
The function no longer uses echo to output the code instead it is returned as a string which can be assigned to a variable or printed itself
Added camelCase names as updated in the question
Code
$array = [
'students' => [
'names' => [
'frankDiaz' => '12',
'jasonVaaz' => '13',
'bethDoe' => '14'
],
'groups' => [
'yellow' => '165',
'green' => '98'
]
]
];
function array2xml($array, $tabs = 0) {
$xml = "";
foreach ($array as $key => $arr) {
if (is_array($arr)) {
$xml .= str_repeat(" ", $tabs) . "<$key>\n";
$xml .= array2xml($arr, $tabs + 1);
$xml .= str_repeat(" ", $tabs) . "</$key>\n";
} else {
$xml .= str_repeat(" ", $tabs) . "<$key>$arr</$key>\n";
}
}
return $xml;
}
echo array2xml($array);
Output:
<students>
<names>
<frankDiaz>12</frankDiaz>
<jasonVaaz>13</jasonVaaz>
<bethDoe>14</bethDoe>
</names>
<groups>
<yellow>165</yellow>
<green>98</green>
</groups>
</students>

A variation on #Steven's answer above, but creating actual xml with simplexml:
$xml_string = "";
function array2xml($array) {
global $xml_string;
foreach ($array as $key => $arr) {
if (is_array($arr)) {
$xml_string .= "<$key>\n";
array2xml($arr);
}
else {
$xml_string .= "<$key>". $arr;
}
$xml_string .= "</$key>\n";
}
}
array2xml($array);
$final = simplexml_load_string($xml_string);
echo $final->asXML();

the output still works as an XML, just not as easily human readable. To get things on new lines try inputting a carriage return line feed after every element.

Related

SimpleXMLElement expand to repeated values in array

Please help me.. I spent a lot of time on it and I think I ran out of ideas.
I have that code for function array_to_xml:
function array_to_xml( $data, &$xml_data ) {
foreach( $data as $key => $value ) {
if (!empty($value)) {
if( is_array($value)) {
if (!empty($value["#attributes"])) {
$subnode = $xml_data->addChild($key, $value["#value"]);
foreach ($value["#attributes"] as $key1 => $val1) {
$subnode->addAttribute($key1, $val1);
}
} else if ($key == "#value") {
foreach ($value as $attr => $attrVal) {
$subnode = $xml_data->addChild("$attr", $attrVal);
array_to_xml($attrVal, $subnode);
}
} else {
if (!empty($value) and $key != "kontakt") {
$subnode = $xml_data->addChild($key);
array_to_xml($value, $subnode);
} else if (!empty($value) and $key == "kontakt") {
$subnode = $xml_data->addChild($key);
array_to_xml($value, $subnode);
};
}
} else {
$xml_data->addChild("$key",$value);
}
}
}
}
When it hits the "contact" attribute, it creates the following structure:
<kontakt>
<1 typ="email">abc#abc.pl</1>
<2 typ="telefon">3123453141</2>
</kontakt>
How should the function be changed so that its result would be the structure shown below?
<kontakt typ="email">abc#abc.pl</kontakt>
<kontakt typ="telefon">3123453141</kontakt>
The array:
"kontakt"=>[
1=>[
'#value' => 'abc#abc.pl',
'#attributes' => ['typ' => 'email'],
],
2=>[
'#value' => '3123453141',
'#attributes' => ['typ' => 'telefon'],
],
],
That's the only thing I'm missing for happiness ;)
something like that (i´ve rewritten your function):
<?php
function array_to_xml( $data, &$xml_data ) {
foreach( $data as $key => $value ) {
foreach($value as $innerkey => $innervalue) {
$v = $innervalue['#value'];
$t = "";
if(isset($innervalue['#attributes']['typ'])) {
$t = $innervalue['#attributes']['typ'];
}
$kontakt = $xml_data->addChild('kontakt', $v);
$kontakt->addAttribute('typ', $t);
}
}
}
$data = array("kontakt"=>[
1=>[
'#value' => 'abc#abc.pl',
'#attributes' => ['typ' => 'email'],
],
2=>[
'#value' => '3123453141',
'#attributes' => ['typ' => 'telefon'],
],
]
);
$xml_data = new SimpleXMLElement('<test></test>') ;
array_to_xml($data,$xml_data);
echo $xml_data->asXML();
results in
<?xml version="1.0"?>
<test><kontakt typ="email">abc#abc.pl</kontakt><kontakt typ="telefon">3123453141</kontakt></test>
playground: https://3v4l.org/Qo9uaU#v7.0.33
<?php
//the array
$data = array(
"kontakt"=>[
1=>[
'#value' => 'abc#abc.pl',
'#attributes' => ['typ' => 'email'],
],
2=>[
'#value' => '3123453141',
'#attributes' => ['typ' => 'telefon'],
],
]);
//call to function
$xml = array_to_xml($data);
show($xml);
//function to convert the array to xml
function array_to_xml( $data) {
$xml = '';
foreach( $data as $key => $value ) {
foreach($value as $key1 => $value1){
$xml .= '<kontakt typ="'.$value1['#attributes']['typ'].'">';
$xml .= $value1['#value'].'</kontakt>'.PHP_EOL;
}
}
return $xml;
}
//function to show it in xml form
function show($data){
echo '<pre>';
echo nl2br(str_replace('<', '<', $data));
echo '</pre>';
}
?>
May this will help you. You can configure in case of multiple array. The above function will work in case of the provided string.
Before writing the recursive function it can help to line-out which cases there are in the data. I found three, and all what is needed to identify them is the key that is always string (for the recursive traversal):
key is #value -> the elements' node-value
key is #attributes -> the elements' attributes key/value map
key is the name of child elements -> list of children to add
Only the third case traverses into child nodes.
function array_to_xml(array $array, SimpleXMLElement $xml): void
{
foreach ($array as $key => $value) {
if ($key === '#value') {
$xml[0] = $value;
} elseif ($key === '#attributes') {
foreach ($value as $name => $item) {
$xml->addAttribute($name, $item);
}
} else {
foreach ($value as $item) {
array_to_xml($item, $xml->addChild($key));
}
}
}
}
It is not only far less code, the main difference is that the function operates on the element itself; it doesn't fetch the $key only to add it later, in the else case it adds it directly and passes the new child with the array.
The recursion is not scattered at mutliple places, but at a single one.
SimpleXMLElement itself can also be used to give each node actual method(s) and these automatically propagate over the whole document. Here an ad-hoc example:
(new class ('<test/>') extends SimpleXMLElement {
public function add(array $array): self
{
foreach ($array as $key => $value) match ($key) {
'#value' => $this[0] = $value,
'#attributes' => array_map($this->addAttribute(...), array_keys($value), $value),
default => array_map(fn ($child) => $this->addChild($key)?->add($child), $value),
};
return $this;
}
})->add($array)->asXML('php://output');
// <?xml version="1.0"?>
// <test><kontakt typ="email">abc#abc.pl</kontakt><kontakt typ="telefon">3123453141</kontakt></test>
The result is the same, one benefit could the that you don't need to think that much passing $xml around.

how to convert std Object to simpleXMLelement Object in PHP [duplicate]

How can I convert an array to a SimpleXML object in PHP?
Here is php 5.2 code which will convert array of any depth to xml document:
Array
(
['total_stud']=> 500
[0] => Array
(
[student] => Array
(
[id] => 1
[name] => abc
[address] => Array
(
[city]=>Pune
[zip]=>411006
)
)
)
[1] => Array
(
[student] => Array
(
[id] => 2
[name] => xyz
[address] => Array
(
[city]=>Mumbai
[zip]=>400906
)
)
)
)
generated XML would be as:
<?xml version="1.0"?>
<student_info>
<total_stud>500</total_stud>
<student>
<id>1</id>
<name>abc</name>
<address>
<city>Pune</city>
<zip>411006</zip>
</address>
</student>
<student>
<id>1</id>
<name>abc</name>
<address>
<city>Mumbai</city>
<zip>400906</zip>
</address>
</student>
</student_info>
PHP snippet
<?php
// function defination to convert array to xml
function array_to_xml( $data, &$xml_data ) {
foreach( $data as $key => $value ) {
if( is_array($value) ) {
if( is_numeric($key) ){
$key = 'item'.$key; //dealing with <0/>..<n/> issues
}
$subnode = $xml_data->addChild($key);
array_to_xml($value, $subnode);
} else {
$xml_data->addChild("$key",htmlspecialchars("$value"));
}
}
}
// initializing or creating array
$data = array('total_stud' => 500);
// creating object of SimpleXMLElement
$xml_data = new SimpleXMLElement('<?xml version="1.0"?><data></data>');
// function call to convert array to xml
array_to_xml($data,$xml_data);
//saving generated xml file;
$result = $xml_data->asXML('/file/path/name.xml');
?>
Documentation on SimpleXMLElement::asXML used in this snippet
a short one:
<?php
$test_array = array (
'bla' => 'blub',
'foo' => 'bar',
'another_array' => array (
'stack' => 'overflow',
),
);
$xml = new SimpleXMLElement('<root/>');
array_walk_recursive($test_array, array ($xml, 'addChild'));
print $xml->asXML();
results in
<?xml version="1.0"?>
<root>
<blub>bla</blub>
<bar>foo</bar>
<overflow>stack</overflow>
</root>
keys and values are swapped - you could fix that with array_flip() before the array_walk. array_walk_recursive requires PHP 5. you could use array_walk instead, but you won't get 'stack' => 'overflow' in the xml then.
The answers provided here only convert array to XML with nodes, you are not able to set attributes. I have written a php function that allows you to convert an array to php and also set attributes for particular nodes in the xml. The downside here is you have to construct an array in a particular way with few conventions (only if you want to use attributes)
The following example will allow you to set attributes in XML too.
The source can be found here:
https://github.com/digitickets/lalit/blob/master/src/Array2XML.php
<?php
$books = array(
'#attributes' => array(
'type' => 'fiction'
),
'book' => array(
array(
'#attributes' => array(
'author' => 'George Orwell'
),
'title' => '1984'
),
array(
'#attributes' => array(
'author' => 'Isaac Asimov'
),
'title' => 'Foundation',
'price' => '$15.61'
),
array(
'#attributes' => array(
'author' => 'Robert A Heinlein'
),
'title' => 'Stranger in a Strange Land',
'price' => array(
'#attributes' => array(
'discount' => '10%'
),
'#value' => '$18.00'
)
)
)
);
/* creates
<books type="fiction">
<book author="George Orwell">
<title>1984</title>
</book>
<book author="Isaac Asimov">
<title>Foundation</title>
<price>$15.61</price>
</book>
<book author="Robert A Heinlein">
<title>Stranger in a Strange Land</title>
<price discount="10%">$18.00</price>
</book>
</books>
*/
?>
I found all of the answers to use too much code. Here is an easy way to do it:
function to_xml(SimpleXMLElement $object, array $data)
{
foreach ($data as $key => $value) {
if (is_array($value)) {
$new_object = $object->addChild($key);
to_xml($new_object, $value);
} else {
// if the key is an integer, it needs text with it to actually work.
if ($key != 0 && $key == (int) $key) {
$key = "key_$key";
}
$object->addChild($key, $value);
}
}
}
Then it's a simple matter of sending the array into the function, which uses recursion, so it will handle a multi-dimensional array:
$xml = new SimpleXMLElement('<rootTag/>');
to_xml($xml, $my_array);
Now $xml contains a beautiful XML object based on your array exactly how you wrote it.
print $xml->asXML();
<?php
function array_to_xml(array $arr, SimpleXMLElement $xml)
{
foreach ($arr as $k => $v) {
is_array($v)
? array_to_xml($v, $xml->addChild($k))
: $xml->addChild($k, $v);
}
return $xml;
}
$test_array = array (
'bla' => 'blub',
'foo' => 'bar',
'another_array' => array (
'stack' => 'overflow',
),
);
echo array_to_xml($test_array, new SimpleXMLElement('<root/>'))->asXML();
From PHP 5.4
function array2xml($data, $root = null){
$xml = new SimpleXMLElement($root ? '<' . $root . '/>' : '<root/>');
array_walk_recursive($data, function($value, $key)use($xml){
$xml->addChild($key, $value);
});
return $xml->asXML();
}
Another improvement:
/**
* Converts an array to XML
*
* #param array $array
* #param SimpleXMLElement $xml
* #param string $child_name
*
* #return SimpleXMLElement $xml
*/
public function arrayToXML($array, SimpleXMLElement $xml, $child_name)
{
foreach ($array as $k => $v) {
if(is_array($v)) {
(is_int($k)) ? $this->arrayToXML($v, $xml->addChild($child_name), $v) : $this->arrayToXML($v, $xml->addChild(strtolower($k)), $child_name);
} else {
(is_int($k)) ? $xml->addChild($child_name, $v) : $xml->addChild(strtolower($k), $v);
}
}
return $xml->asXML();
}
Usage:
$this->arrayToXML($array, new SimpleXMLElement('<root/>'), 'child_name_to_replace_numeric_integers');
Here is my entry, simple and clean..
function array2xml($array, $xml = false){
if($xml === false){
$xml = new SimpleXMLElement('<root/>');
}
foreach($array as $key => $value){
if(is_array($value)){
array2xml($value, $xml->addChild($key));
}else{
$xml->addChild($key, $value);
}
}
return $xml->asXML();
}
header('Content-type: text/xml');
print array2xml($array);
So anyway... I took onokazu's code (thanks!) and added the ability to have repeated tags in XML, it also supports attributes, hope someone finds it useful!
<?php
function array_to_xml(array $arr, SimpleXMLElement $xml) {
foreach ($arr as $k => $v) {
$attrArr = array();
$kArray = explode(' ',$k);
$tag = array_shift($kArray);
if (count($kArray) > 0) {
foreach($kArray as $attrValue) {
$attrArr[] = explode('=',$attrValue);
}
}
if (is_array($v)) {
if (is_numeric($k)) {
array_to_xml($v, $xml);
} else {
$child = $xml->addChild($tag);
if (isset($attrArr)) {
foreach($attrArr as $attrArrV) {
$child->addAttribute($attrArrV[0],$attrArrV[1]);
}
}
array_to_xml($v, $child);
}
} else {
$child = $xml->addChild($tag, $v);
if (isset($attrArr)) {
foreach($attrArr as $attrArrV) {
$child->addAttribute($attrArrV[0],$attrArrV[1]);
}
}
}
}
return $xml;
}
$test_array = array (
'bla' => 'blub',
'foo' => 'bar',
'another_array' => array (
array('stack' => 'overflow'),
array('stack' => 'overflow'),
array('stack' => 'overflow'),
),
'foo attribute1=value1 attribute2=value2' => 'bar',
);
$xml = array_to_xml($test_array, new SimpleXMLElement('<root/>'))->asXML();
echo "$xml\n";
$dom = new DOMDocument;
$dom->preserveWhiteSpace = FALSE;
$dom->loadXML($xml);
$dom->formatOutput = TRUE;
echo $dom->saveXml();
?>
I wanted a code that will take all the elements inside an array and treat them as attributes, and all arrays as sub elements.
So for something like
array (
'row1' => array ('head_element' =>array("prop1"=>"some value","prop2"=>array("empty"))),
"row2"=> array ("stack"=>"overflow","overflow"=>"overflow")
);
I would get something like this
<?xml version="1.0" encoding="utf-8"?>
<someRoot>
<row1>
<head_element prop1="some value">
<prop2 0="empty"/>
</head_element>
</row1>
<row2 stack="overflow" overflow="stack"/>
</someRoot>
To achive this the code is below, but be very careful, it is recursive and may actually cause a stackoverflow :)
function addElements(&$xml,$array)
{
$params=array();
foreach($array as $k=>$v)
{
if(is_array($v))
addElements($xml->addChild($k), $v);
else $xml->addAttribute($k,$v);
}
}
function xml_encode($array)
{
if(!is_array($array))
trigger_error("Type missmatch xml_encode",E_USER_ERROR);
$xml=new SimpleXMLElement('<?xml version=\'1.0\' encoding=\'utf-8\'?><'.key($array).'/>');
addElements($xml,$array[key($array)]);
return $xml->asXML();
}
You may want to add checks for length of the array so that some element get set inside the data part and not as an attribute.
I use a couple of functions that I wrote a while back to generate the xml to pass back and forth from PHP and jQuery etc...
Neither use any additional frameworks just purely generates a string that can then be used with SimpleXML (or other framework)...
If it's useful to anyone, please use it :)
function generateXML($tag_in,$value_in="",$attribute_in=""){
$return = "";
$attributes_out = "";
if (is_array($attribute_in)){
if (count($attribute_in) != 0){
foreach($attribute_in as $k=>$v):
$attributes_out .= " ".$k."=\"".$v."\"";
endforeach;
}
}
return "<".$tag_in."".$attributes_out.((trim($value_in) == "") ? "/>" : ">".$value_in."</".$tag_in.">" );
}
function arrayToXML($array_in){
$return = "";
$attributes = array();
foreach($array_in as $k=>$v):
if ($k[0] == "#"){
// attribute...
$attributes[str_replace("#","",$k)] = $v;
} else {
if (is_array($v)){
$return .= generateXML($k,arrayToXML($v),$attributes);
$attributes = array();
} else if (is_bool($v)) {
$return .= generateXML($k,(($v==true)? "true" : "false"),$attributes);
$attributes = array();
} else {
$return .= generateXML($k,$v,$attributes);
$attributes = array();
}
}
endforeach;
return $return;
}
Love to all :)
You could use the XMLParser that I have been working on.
$xml = XMLParser::encode(array(
'bla' => 'blub',
'foo' => 'bar',
'another_array' => array (
'stack' => 'overflow',
)
));
// #$xml instanceof SimpleXMLElement
echo $xml->asXML();
Would result in:
<?xml version="1.0"?>
<root>
<bla>blub</bla>
<foo>bar</foo>
<another_array>
<stack>overflow</stack>
</another_array>
</root>
Based on everything else here, handles numerical indices + attributes via prefixing with #, and could inject xml to existing nodes:
Code
function simple_xmlify($arr, SimpleXMLElement $root = null, $el = 'x') {
// based on, among others http://stackoverflow.com/a/1397164/1037948
if(!isset($root) || null == $root) $root = new SimpleXMLElement('<' . $el . '/>');
if(is_array($arr)) {
foreach($arr as $k => $v) {
// special: attributes
if(is_string($k) && $k[0] == '#') $root->addAttribute(substr($k, 1),$v);
// normal: append
else simple_xmlify($v, $root->addChild(
// fix 'invalid xml name' by prefixing numeric keys
is_numeric($k) ? 'n' . $k : $k)
);
}
} else {
$root[0] = $arr;
}
return $root;
}//-- fn simple_xmlify
Usage
// lazy declaration via "queryparam"
$args = 'hello=4&var[]=first&var[]=second&foo=1234&var[5]=fifth&var[sub][]=sub1&var[sub][]=sub2&var[sub][]=sub3&var[#name]=the-name&var[#attr2]=something-else&var[sub][#x]=4.356&var[sub][#y]=-9.2252';
$q = array();
parse_str($val, $q);
$xml = simple_xmlify($q); // dump $xml, or...
$result = get_formatted_xml($xml); // see below
Result
<?xml version="1.0"?>
<x>
<hello>4</hello>
<var name="the-name" attr2="something-else">
<n0>first</n0>
<n1>second</n1>
<n5>fifth</n5>
<sub x="4.356" y="-9.2252">
<n0>sub1</n0>
<n1>sub2</n1>
<n2>sub3</n2>
</sub>
</var>
<foo>1234</foo>
</x>
Bonus: Formatting XML
function get_formatted_xml(SimpleXMLElement $xml, $domver = null, $preserveWhitespace = true, $formatOutput = true) {
// http://stackoverflow.com/questions/1191167/format-output-of-simplexml-asxml
// create new wrapper, so we can get formatting options
$dom = new DOMDocument($domver);
$dom->preserveWhiteSpace = $preserveWhitespace;
$dom->formatOutput = $formatOutput;
// now import the xml (converted to dom format)
/*
$ix = dom_import_simplexml($xml);
$ix = $dom->importNode($ix, true);
$dom->appendChild($ix);
*/
$dom->loadXML($xml->asXML());
// print
return $dom->saveXML();
}//-- fn get_formatted_xml
Here's a function that did the trick for me:
Just call it with something like
echo arrayToXml("response",$arrayIWantToConvert);
function arrayToXml($thisNodeName,$input){
if(is_numeric($thisNodeName))
throw new Exception("cannot parse into xml. remainder :".print_r($input,true));
if(!(is_array($input) || is_object($input))){
return "<$thisNodeName>$input</$thisNodeName>";
}
else{
$newNode="<$thisNodeName>";
foreach($input as $key=>$value){
if(is_numeric($key))
$key=substr($thisNodeName,0,strlen($thisNodeName)-1);
$newNode.=arrayToXml3($key,$value);
}
$newNode.="</$thisNodeName>";
return $newNode;
}
}
I found this solution similar to the original problem
<?php
$test_array = array (
'bla' => 'blub',
'foo' => 'bar',
'another_array' => array (
'stack' => 'overflow',
),
);
class NoSimpleXMLElement extends SimpleXMLElement {
public function addChild($name,$value) {
parent::addChild($value,$name);
}
}
$xml = new NoSimpleXMLElement('<root/>');
array_walk_recursive($test_array, array ($xml, 'addChild'));
print $xml->asXML();
Most of the above answers are correct. However, I came up with this answer which solves the array_walk_recursive compatibility issue and also the numerical keys problem. It also passed all the tests I made:
function arrayToXML(Array $array, SimpleXMLElement &$xml) {
foreach($array as $key => $value) {
// None array
if (!is_array($value)) {
(is_numeric($key)) ? $xml->addChild("item$key", $value) : $xml->addChild($key, $value);
continue;
}
// Array
$xmlChild = (is_numeric($key)) ? $xml->addChild("item$key") : $xml->addChild($key);
arrayToXML($value, $xmlChild);
}
}
I have also added a test class for this which you may find useful:
class ArrayToXmlTest extends PHPUnit_Framework_TestCase {
public function setUp(){ }
public function tearDown(){ }
public function testFuncExists() {
$this->assertTrue(function_exists('arrayToXML'));
}
public function testFuncReturnsXml() {
$array = array(
'name' => 'ardi',
'last_name' => 'eshghi',
'age' => 31,
'tel' => '0785323435'
);
$xmlEl = new SimpleXMLElement('<root/>');
arrayToXml($array, $xmlEl);
$this->assertTrue($xmlEl instanceOf SimpleXMLElement);
}
public function testAssocArrayToXml() {
$array = array(
'name' => 'ardi',
'last_name' => 'eshghi',
'age' => 31,
'tel' => '0785323435'
);
$expectedXmlEl = new SimpleXMLElement('<root/>');
$expectedXmlEl->addChild('name', $array['name']);
$expectedXmlEl->addChild('last_name', $array['last_name']);
$expectedXmlEl->addChild('age', $array['age']);
$expectedXmlEl->addChild('tel', $array['tel']);
$actualXmlEl = new SimpleXMLElement('<root/>');
arrayToXml($array, $actualXmlEl);
$this->assertEquals($expectedXmlEl->asXML(), $actualXmlEl->asXML());
}
public function testNoneAssocArrayToXml() {
$array = array(
'ardi',
'eshghi',
31,
'0785323435'
);
// Expected xml value
$expectedXmlEl = new SimpleXMLElement('<root/>');
foreach($array as $key => $value)
$expectedXmlEl->addChild("item$key", $value);
// What the function produces
$actualXmlEl = new SimpleXMLElement('<root/>');
arrayToXml($array, $actualXmlEl);
$this->assertEquals($expectedXmlEl->asXML(), $actualXmlEl->asXML());
}
public function testNestedMixArrayToXml() {
$testArray = array(
"goal",
"nice",
"funny" => array(
'name' => 'ardi',
'tel' =>'07415517499',
"vary",
"fields" => array(
'small',
'email' => 'ardi.eshghi#gmail.com'
),
'good old days'
),
"notes" => "come on lads lets enjoy this",
"cast" => array(
'Tom Cruise',
'Thomas Muller' => array('age' => 24)
)
);
// Expected xml value
$expectedXmlEl = new SimpleXMLElement('<root/>');
$expectedXmlEl->addChild('item0', $testArray[0]);
$expectedXmlEl->addChild('item1', $testArray[1]);
$childEl = $expectedXmlEl->addChild('funny');
$childEl->addChild("name", $testArray['funny']['name']);
$childEl->addChild("tel", $testArray['funny']['tel']);
$childEl->addChild("item0", "vary");
$childChildEl = $childEl->addChild("fields");
$childChildEl->addChild('item0', 'small');
$childChildEl->addChild('email', $testArray['funny']['fields']['email']);
$childEl->addChild("item1", 'good old days');
$expectedXmlEl->addChild('notes', $testArray['notes']);
$childEl2 = $expectedXmlEl->addChild('cast');
$childEl2->addChild('item0', 'Tom Cruise');
$childChildEl2 = $childEl2->addChild('Thomas Muller');
$childChildEl2->addChild('age', $testArray['cast']['Thomas Muller']['age']);
// What the function produces
$actualXmlEl = new SimpleXMLElement('<root/>');
arrayToXml($testArray, $actualXmlEl);
$this->assertEquals($expectedXmlEl->asXML(), $actualXmlEl->asXML());
}
}
other solution:
$marray=array(....);
$options = array(
"encoding" => "UTF-8",
"output_type" => "xml",
"version" => "simple",
"escaping" => array("non-ascii, on-print, markup")
);
$xmlres = xmlrpc_encode_request('root', $marray, $options);
print($xmlres);
IF the array is associative and keyed correctly, it would probably be easier to turn it into xml first. Something like:
function array2xml ($array_item) {
$xml = '';
foreach($array_item as $element => $value)
{
if (is_array($value))
{
$xml .= "<$element>".array2xml($value)."</$element>";
}
elseif($value == '')
{
$xml .= "<$element />";
}
else
{
$xml .= "<$element>".htmlentities($value)."</$element>";
}
}
return $xml;
}
$simple_xml = simplexml_load_string(array2xml($assoc_array));
The other route would be to create your basic xml first, like
$simple_xml = simplexml_load_string("<array></array>");
and then for each part of your array, use something similar to my text creating loop and instead use the simplexml functions "addChild" for each node of the array.
I'll try that out later and update this post with both versions.
Just a edit on a function above, when a key is numeric, add a prefix "key_"
// initializing or creating array
$student_info = array(your array data);
// creating object of SimpleXMLElement
$xml_student_info = new SimpleXMLElement("<?xml version=\"1.0\"?><student_info></student_info>");
// function call to convert array to xml
array_to_xml($student,$xml_student_info);
//saving generated xml file
$xml_student_info->asXML('file path and name');
function array_to_xml($student_info, &$xml_student_info) {
foreach($student_info as $key => $value) {
if(is_array($value)) {
if(!is_numeric($key)){
$subnode = $xml_student_info->addChild("$key");
array_to_xml($value, $subnode);
}
else{
$subnode = $xml_student_info->addChild("key_$key");
array_to_xml($value, $subnode);
}
}
else {
if(!is_numeric($key)){
$xml_student_info->addChild("$key","$value");
}else{
$xml_student_info->addChild("key_$key","$value");
}
}
}
}
You can Use the following function in you code directly,
function artoxml($arr, $i=1,$flag=false){
$sp = "";
for($j=0;$j<=$i;$j++){
$sp.=" ";
}
foreach($arr as $key=>$val){
echo "$sp<".$key.">";
if($i==1) echo "\n";
if(is_array($val)){
if(!$flag){echo"\n";}
artoxml($val,$i+5);
echo "$sp</".$key.">\n";
}else{
echo "$val"."</".$key.">\n";
}
}
}
Call the function with first argument as your array and the second argument must be 1, this will be increased for perfect indentation, and third must be true.
for example, if the array variable to be converted is $array1 then,
calling would be, the calling function should be encapsulated with <pre> tag.
artoxml($array1,1,true);
Please see the page source after executing the file, because the < and > symbols won't be displayed in a html page.
function toXML($data, $obj = false, $dom) {
$is_first_level = false;
if($obj === false) {
$dom = new DomDocument('1.0');
$obj = $dom;
$is_first_level = true;
}
if(is_array($data)) {
foreach($data as $key => $item) {
$this->toXML($item, $obj->appendChild($dom->createElement($key)), $dom);
}
}else {
$obj->appendChild($dom->createTextNode($data));
}
if($is_first_level) {
$obj->formatOutput = true;
return $obj->saveXML();
}
return $obj;
}
function array2xml(array $data, SimpleXMLElement $object = null, $oldNodeName = 'item')
{
if (is_null($object)) $object = new SimpleXMLElement('<root/>');
$isNumbered = true;
$idx = 0;
foreach ($data as $key => $x)
if (is_string($key) || ($idx++ != $key + 0))
$isNumbered = false;
foreach ($data as $key => $value)
{
$attribute = preg_match('/^[0-9]/', $key . '') ? $key : null;
$key = (is_string($key) && !preg_match('/^[0-9]/', $key . '')) ? $key : preg_replace('/s$/', '', $oldNodeName);
if (is_array($value))
{
$new_object = $object->addChild($key);
if (!$isNumbered && !is_null($attribute)) $new_object->addAttribute('id', $attribute);
array2xml($value, $new_object, $key);
}
else
{
if (is_bool($value)) $value = $value ? 'true' : 'false';
$node = $object->addChild($key, htmlspecialchars($value));
if (!$isNumbered && !is_null($attribute) && !isset($node->attributes()->id))
$node->addAttribute('id', $attribute);
}
}
return $object;
}
This function returns for example a list of <obj>...</obj><obj>...</obj> XML tags for numeric indexes.
Input:
array(
'people' => array(
'dog',
'cat',
'life' => array(
'gum',
'shoe',
),
'fish',
),
array('yeah'),
)
Output:
<root>
<people>
<people>dog</people>
<people>cat</people>
<life>
<life>gum</life>
<life>shoe</life>
</life>
<people>fish</people>
<people>
<people>yeah</people>
</people>
</people>
</root>
This should satisfy all common needs. Maybe you may change the 3rd line to:
$key = is_string($key) ? $key : $oldNodeName . '_' . $key;
or if you are working with plurals ending with s:
$key = is_string($key) ? $key : preg_replace('/s$/', '', $oldNodeName);
With FluidXML you can generate, starting from a PHP Array, an XML for SimpleXML with... just two lines of code.
$fluidxml = fluidxml($array);
$simplexml = simplexml_import_dom($fluidxml->dom());
An example array could be
$array = [ 'doc' => [
'fruit' => 'orange',
'cake' => [
'#id' => '123',
'#' => 'tiramisu' ],
[ 'pasta' => 'matriciana' ],
[ 'pasta' => 'boscaiola' ]
] ];
https://github.com/servo-php/fluidxml
You may use xmlrpc_encode to create a xml from array if a verbose xml is not a problem.
www.php.net/xmlrpc_encode
be careful the xml created differs in case you use associative and/or numeric keys
<?php
// /params/param/value/struct/member
// there is a tag "member" for each element
// "member" contains a tag "name". its value is the associative key
$xml1 = xmlrpc_encode(array('a'=>'b','c'=>'d'));
$simplexml1 = simplexml_load_string($xml1);
print_r($xml1);
print_r($simplexml1);
// /params/param/value/array/data
// there is a tag "data" for each element
// "data" doesn't contain the tag "name"
$xml2 = xmlrpc_encode(array('a','b'));
$simplexml2 = simplexml_load_string($xml2);
print_r($xml2);
print_r($simplexml2);
?>
function array2xml($array, $xml = false){
if($xml === false){
$xml = new SimpleXMLElement('<?xml version=\'1.0\' encoding=\'utf-8\'?><'.key($array).'/>');
$array = $array[key($array)];
}
foreach($array as $key => $value){
if(is_array($value)){
$this->array2xml($value, $xml->addChild($key));
}else{
$xml->addChild($key, $value);
}
}
return $xml->asXML();
}
My answer, cobbling together others' answers. This should correct for the failure to compensate for numeric keys:
function array_to_xml($array, $root, $element) {
$xml = new SimpleXMLElement("<{$root}/>");
foreach ($array as $value) {
$elem = $xml->addChild($element);
xml_recurse_child($elem, $value);
}
return $xml;
}
function xml_recurse_child(&$node, $child) {
foreach ($child as $key=>$value) {
if(is_array($value)) {
foreach ($value as $k => $v) {
if(is_numeric($k)){
xml_recurse_child($node, array($key => $v));
}
else {
$subnode = $node->addChild($key);
xml_recurse_child($subnode, $value);
}
}
}
else {
$node->addChild($key, $value);
}
}
}
The array_to_xml() function presumes that the array is made up of numeric keys first. If your array had an initial element, you would drop the foreach() and $elem statements from the array_to_xml() function and just pass $xml instead.
I would have commented the second most voted answer, because it doesn't preserve structure and generates bad xml if there is numerically indexed inner arrays.
I developed my own version based on it, because I needed simple converter between json and xml regardless of the structure of data. My version preserves numeric key information and structure of the original array. It creates elements for the numerically indexed values by wrapping values to value -named elements with key-attribute that contains numerical key.
For example
array('test' => array(0 => 'some value', 1 => 'other'))
converts to
<test><value key="0">some value</value><value key="1">other</value></test>
My version of array_to_xml -function (hope it helps somebody :)
function array_to_xml($arr, &$xml) {
foreach($arr as $key => $value) {
if(is_array($value)) {
if(!is_numeric($key)){
$subnode = $xml->addChild("$key");
} else {
$subnode = $xml->addChild("value");
$subnode->addAttribute('key', $key);
}
array_to_xml($value, $subnode);
}
else {
if (is_numeric($key)) {
$xml->addChild("value", $value)->addAttribute('key', $key);
} else {
$xml->addChild("$key",$value);
}
}
}
}
Whole XML structure is defined in $data Array:
function array2Xml($data, $xml = null)
{
if (is_null($xml)) {
$xml = simplexml_load_string('<' . key($data) . '/>');
$data = current($data);
$return = true;
}
if (is_array($data)) {
foreach ($data as $name => $value) {
array2Xml($value, is_numeric($name) ? $xml : $xml->addChild($name));
}
} else {
$xml->{0} = $data;
}
if (!empty($return)) {
return $xml->asXML();
}
}
If you work in magento and you have this type of associative array
$test_array = array (
'0' => array (
'category_id' => '582',
'name' => 'Surat',
'parent_id' => '565',
'child_id' => '567',
'active' => '1',
'level' => '6',
'position' => '17'
),
'1' => array (
'category_id' => '567',
'name' => 'test',
'parent_id' => '0',
'child_id' => '576',
'active' => '0',
'level' => '0',
'position' => '18'
),
);
then this is best to convert associative array to xml format.Use this code in controller file.
$this->loadLayout(false);
//header ("content-type: text/xml");
$this->getResponse()->setHeader('Content-Type','text/xml');
$this->renderLayout();
$clArr2xml = new arr2xml($test_array, 'utf-8', 'listdata');
$output = $clArr2xml->get_xml();
print $output;
class arr2xml
{
var $array = array();
var $xml = '';
var $root_name = '';
var $charset = '';
public function __construct($array, $charset = 'utf-8', $root_name = 'root')
{
header ("content-type: text/xml");
$this->array = $array;
$this->root_name = $root_name;
$this->charset = $charset;
if (is_array($array) && count($array) > 0) {
$this->struct_xml($array);
} else {
$this->xml .= "no data";
}
}
public function struct_xml($array)
{
foreach ($array as $k => $v) {
if (is_array($v)) {
$tag = ereg_replace('^[0-9]{1,}', 'item', $k); // replace numeric key in array to 'data'
$this->xml .= "<$tag>";
$this->struct_xml($v);
$this->xml .= "</$tag>";
} else {
$tag = ereg_replace('^[0-9]{1,}', 'item', $k); // replace numeric key in array to 'data'
$this->xml .= "<$tag><![CDATA[$v]]></$tag>";
}
}
}
public function get_xml()
{
$header = "<?xml version=\"1.0\" encoding=\"" . $this->charset . "\"?><" . $this->root_name . ">";
$footer = "</" . $this->root_name . ">";
return $header . $this->xml . $footer;
}
}
I hope it helps to all.
// Structered array for XML convertion.
$data_array = array(
array(
'#xml_tag' => 'a',
'#xml_value' => '',
'#tag_attributes' => array(
array(
'name' => 'a_attr_name',
'value' => 'a_attr_value',
),
),
'#subnode' => array(
array(
'#xml_tag' => 'aa',
'#xml_value' => 'aa_value',
'#tag_attributes' => array(
array(
'name' => 'aa_attr_name',
'value' => 'aa_attr_value',
),
),
'#subnode' => FALSE,
),
),
),
array(
'#xml_tag' => 'b',
'#xml_value' => 'b_value',
'#tag_attributes' => FALSE,
'#subnode' => FALSE,
),
array(
'#xml_tag' => 'c',
'#xml_value' => 'c_value',
'#tag_attributes' => array(
array(
'name' => 'c_attr_name',
'value' => 'c_attr_value',
),
array(
'name' => 'c_attr_name_1',
'value' => 'c_attr_value_1',
),
),
'#subnode' => array(
array(
'#xml_tag' => 'ca',
'#xml_value' => 'ca_value',
'#tag_attributes' => FALSE,
'#subnode' => array(
array(
'#xml_tag' => 'caa',
'#xml_value' => 'caa_value',
'#tag_attributes' => array(
array(
'name' => 'caa_attr_name',
'value' => 'caa_attr_value',
),
),
'#subnode' => FALSE,
),
),
),
),
),
);
// creating object of SimpleXMLElement
$xml_object = new SimpleXMLElement('<?xml version=\"1.0\"?><student_info></student_info>');
// function call to convert array to xml
array_to_xml($data_array, $xml_object);
// saving generated xml file
$xml_object->asXML('/tmp/test.xml');
/**
* Converts an structured PHP array to XML.
*
* #param Array $data_array
* The array data for converting into XML.
* #param Object $xml_object
* The SimpleXMLElement Object
*
* #see https://gist.github.com/drupalista-br/9230016
*
*/
function array_to_xml($data_array, &$xml_object) {
foreach($data_array as $node) {
$subnode = $xml_object->addChild($node['#xml_tag'], $node['#xml_value']);
if ($node['#tag_attributes']) {
foreach ($node['#tag_attributes'] as $tag_attributes) {
$subnode->addAttribute($tag_attributes['name'], $tag_attributes['value']);
}
}
if ($node['#subnode']) {
array_to_xml($node['#subnode'], $subnode);
}
}
}

PHP: Convert Multidimensional Array to String

I am trying to convert a multidimensional array into a string.
Till now I have been able to convert a pipe delimited string into an array.
Such as:
group|key|value
group|key_second|value
Will render into the following array:
$x = array(
'group' => array(
'key' => 'value',
'key_second' => 'value'
),
);
However, now I want it to be the other way around, where a multidimensional array is provided and I want to convert it to a pipe delimited string just like in the first code example.
Any ideas how to do this ?
PS: Please do note that the array can dynamically have any depth.
For example:
$x['group']['sub_group']['category']['key'] = 'value'
Translates to
group|sub_group|category|key|value
I have created my own function:
This should have no problem handling even big arrays
function array_to_pipe($array, $delimeter = '|', $parents = array(), $recursive = false)
{
$result = '';
foreach ($array as $key => $value) {
$group = $parents;
array_push($group, $key);
// check if value is an array
if (is_array($value)) {
if ($merge = array_to_pipe($value, $delimeter, $group, true)) {
$result = $result . $merge;
}
continue;
}
// check if parent is defined
if (!empty($parents)) {
$result = $result . PHP_EOL . implode($delimeter, $group) . $delimeter . $value;
continue;
}
$result = $result . PHP_EOL . $key . $delimeter . $value;
}
// somehow the function outputs a new line at the beginning, we fix that
// by removing the first new line character
if (!$recursive) {
$result = substr($result, 1);
}
return $result;
}
Demo provided here http://ideone.com/j6nThF
You can also do this using a loop like this:
$x = array(
'group' => array(
'key' => 'value',
'key_second' => 'value'
)
);
$yourstring ="";
foreach ($x as $key => $value)
{
foreach ($x[$key] as $key2 => $value2)
{
$yourstring .= $key.'|'.$key2.'|'.$x[$key][$key2]."<BR />";
}
}
echo $yourstring;
Here is a working DEMO
This code should do the thing.
You needed a recursive function to do this. But be careful not to pass object or a huge array into it, as this method is very memory consuming.
function reconvert($array,$del,$path=array()){
$string="";
foreach($array as $key=>$val){
if(is_string($val) || is_numeric($val)){
$string.=implode($del,$path).$del.$key.$del.$val."\n";
} else if(is_bool($val)){
$string.=implode($del,$path).$del.$key.$del.($val?"True":"False")."\n";
} else if(is_null($val)){
$string.=implode($del,$path).$del.$key.$del."NULL\n";
}else if(is_array($val)=='array') {
$path[]=$key;
$string.=reconvert($val,$del,$path);
array_pop($path);
} else {
throw new Exception($key." has type ".gettype($val).' which is not a printable value.');
}
}
return $string;
}
DEMO: http://ideone.com/89yLLo
You can do it by
Look at serialize and unserialize.
Look at json_encode and json_decode
Look at implode
And Possible duplicate of Multidimensional Array to String
You can do this if you specifically want a string :
$x = array(
'group' => array(
'key' => 'value',
'key_second' => 'value'
),
'group2' => array(
'key2' => 'value',
'key_second2' => 'value'
),
);
$str='';
foreach ($x as $key=>$value)
{
if($str=='')
$str.=$key;
else
$str.="|$key";
foreach ($value as $key1=>$value1)
$str.="|$key1|$value1";
}
echo $str; //it will print group|key|value|key_second|value|group2|key2|value|key_second2|value

Outputting Array Keys of Multi-Dimensional Array

I'm trying to write a function that takes a multi-dimensional array as input and outputs a multi-line string of keys like the following
['key']['subkey']
['key']['another_subkey']
['key']['another_subkey']['subkey_under_subkey']
['key']['yet_another_subkey']
['another_key']['etc']
Here is my attempt. It has problems when you get to the second level.
function get_array_keys_as_string($array){
$output = "";
foreach($array as $k => $v){
if(is_array($v)){
$string = get_array_keys_as_string($v);
$prepend = "['$k']";
$string = $prepend.str_replace("\n","\n".$prepend, $string);
$output .= $string;
}
else{
$output .= "['$k']\n";
}
}
return $output;
}
I know I need a recursive function, but so far my attempts have come up short.
To get the exact output you asked for use the following:
$arr = array(
"key" => array(
"subkey" => 1,
"another_subkey" => array(
"subkey_under_subkey" => 1
),
"yet_another_subkey" => 1
),
"another_key" => array(
"etc" => 1
)
);
function print_keys_recursive($array, $path = false) {
foreach($array as $key=>$value) {
if(!is_array($value)) {
echo $path."[".$key."]<br/>";
} else {
if($path) {
echo $path."[".$key."]<br/>";
}
print_keys_recursive($value, $path."[".$key."]");
}
}
return;
}
print_keys_recursive($arr);
Output:
[key][subkey]
[key][another_subkey]
[key][another_subkey][subkey_under_subkey]
[key][yet_another_subkey]
[another_key][etc]
Not sure how you want the output since you have not provided an example array, just the result, but here is an example based on the following array,
$array = array(
"key" => array(
"subkey" => 1,
"another_subkey" => array("2", "subkey_under_subkey" => 3),
"yet_another_subkey" => 4
),
"another_key" => array("etc"),
"last_key" => 0
);
Using the following function,
function recursive_keys($arr, $history = NULL)
{
foreach ($arr as $key => $value)
{
if (is_array($value))
recursive_keys($value, $history."['".$key."']");
else
echo $history."['".$key."']\n";
}
}
Output of recursive_keys($array) is,
['key']['subkey']
['key']['another_subkey']['0']
['key']['another_subkey']['subkey_under_subkey']
['key']['yet_another_subkey']
['another_key']['0']
['last_key']
Try this
function arrayMultiKeys($array,$depth = 0){
foreach($array as $k=>$v){
echo "['".$k."']";
if(is_array($v)){
arrayMultiKeys($v,$depth + 1);
}
if($depth == 0 ){
echo "<br>";
}
}
}

PHP array to XML with attributes

In CakePHP I know the XML-class in cake.libs. What I like on this easy to use class is the fact that you can convert an array to xml with attributes.
On a current project I'm working with Zend Framework and I am missing this nice class.
Does anybody know how I can convert an PHP-array to XML with attributes easily?
I tried this one but unfortunately its not easy to handle result arrays from database cause you have to define the attributes inside the array.
Maybe I have missed something in ZF? Or does anybody know how to adapt the CakePHP class to ZF?
Many thanks for any helpful hints.
This function will allow you to create XML document with simple PHP array variable. Nodes are just stacked onto each other and naming it 'Attribute_XXX' will add atribute XXX to that parent node.
function to_xml(SimpleXMLElement $object, array $data)
{
$attr = "Attribute_";
foreach ($data as $key => $value) {
if (is_array($value)) {
$new_object = $object->addChild($key);
to_xml($new_object, $value);
} else {
if(strpos($key, $attr) !== false){
$object->addAttribute(substr($key, strlen($attr)), $value);
}else{
$object->addChild($key, $value);
}
}
}
}
Usage:
$my_array = array (
'TagN1' => array (
'TagInsideOfIt' => array (
'Atrribute_IDuser' => 'anything',
'RegularTag' => 'whatever',
'Address' => array(
'Attribute_ID' => '111',
'Attribute_key' => 'aaa',
'Company' => 'Google Inc.'
)
)
)
);
$xml = new SimpleXMLElement('<root/>');
to_xml($xml, $my_array);
Header('Content-type: text/xml');
print($xml->asXML());
You can get some answers at this URL How to convert array to SimpleXML
An alternative/fast and easy approach would be to convert array data into JSON, that would be faster as well using Zend library. Converting array into JSON is very easy as using follows functions.
Zend_Json::encode() and Zend_Json_Encoder::encode()
Tanks, a using the code of #jezda159 and i modified for me, this is my code:
the array:
$elements_array = array(
"node" => array(
"subnode" => array(
"[attribute]" => "valueOfAttribute",
"[second-attribute]" => "valueOfAttribute",
"second-subnode" => array(
"[attribute]" => "valueOfAttribute",
"[second-attribute]" => "valueOfAttribute",
),
"ListOfElements" => array(
"one",
"two",
"tree"
)
)
),
);
The code:
function array_to_xml($data, $object){
foreach ($data as $key => $value) {
$keyname = is_numeric( $key ) ? "item".$key : $key;
if (is_array($value)) {
$new_object = $object->addChild($keyname);
array_to_xml($value, $new_object);
} else {
preg_match("#\[([a-z0-9-_]+)\]#i", $keyname, $attr);
if( count($attr) ){
$object->addAttribute($attr[1], $value);
}else{
$object->addChild($keyname, $value);
}
}
}
}
$xml_user_info = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8" ?><Elements></Elements>');
//function call to convert array to xml
array_to_xml($elements_array,$xml_user_info);
header('Content-type: text/xml');
//show generated xml file
echo $xml_user_info->asXML();
The result:
<?xml version="1.0" encoding="UTF-8"?>
<Elements>
<node>
<subnode attribute="valueOfAttribute" second-attribute="valueOfAttribute">
<second-subnode attribute="valueOfAttribute" second-attribute="valueOfAttribute"/>
<ListOfElements>
<item0>one</item0>
<item1>two</item1>
<item2>tree</item2>
</ListOfElements>
</subnode>
</node>
</Elements>

Categories