PHP XMLReader dealing with missing element - php

I'm using XMLReader to parse a file which i don't control. The file is missing some elements, example below. Where book 2 is missing an info element, i still want to add this to the array. I realise i could use a combination of XMLReader and SimpleXML... but i'd like to see if this is possible?
<book>
<title>book 1</title>
<info>book 1 info</info>
</book>
<book>
<title>book 2</title>
</book>
<book>
<title>book 3</title>
<info>book 3 info</info>
</book>
I have the following XMLReader function:
<?php
function parseXML($args){
extract($args);
$xml = new XMLReader();
$xml->open($file) or die('Cannot open file');
while($xml->read()){
switch($xml->nodeType){
case(XMLREADER::ELEMENT):{
if(in_array($xml->localName, $nodes, true)){
$lName = $xml->localName;
$val = $xml->readString();
$xml->read();
$child[$lName] = trim($val);
if($lName == end($nodes)){
$arr[] = $child;
}
}
}
break;
}
}
$xml->close($file);
return $arr;
}
$settings = array(
'file' => 'books.xml',
'nodes' => array('name', 'info')
);
$books = parseXML($settings);
var_dump($books);
?>
which produces:
Array
(
[0] => Array
(
[name] => book 1
[info] => book 1 info
)
[1] => Array
(
[name] => book 3
[info] => book 3 info
)
)

You could rethink your logic a little. The snippet below builds up a $book array for each <book> element, starting each time with default values (an empty string) in case a <name> or <info> does not exist for the book.
As <name> and <info> elements are reached, their contents are added to the current $book array.
When reaching a </book>, the $book array is added to the main array of $books.
Building $books
function parseXML($args){
extract($args);
$xml = new XMLReader();
$xml->open($file) or die('Cannot open file');
$books = array();
while ($xml->read()) {
// <book> - start a new child array with default values
if ($xml->nodeType === XMLReader::ELEMENT
&& $xml->localName === 'book') {
$book = array_fill_keys($nodes, '');
// </book> - add the child array to main array
} elseif ($xml->nodeType === XMLReader::END_ELEMENT
&& $xml->localName === 'book') {
$books[] = $book;
// <info> or <title> - add to child array
} elseif ($xml->nodeType === XMLReader::ELEMENT
&& in_array($xml->localName, $nodes, TRUE)) {
$name = $xml->localName;
$val = $xml->readString();
$book[$name] = trim($val);
}
}
$xml->close();
return $books;
}
Resulting array
$books = array(
array('title' => 'book 1', 'info' => 'book 1 info'),
array('title' => 'book 2', 'info' => ''),
array('title' => 'book 3', 'info' => 'book 3 info'),
);

Related

adding node in xml file via php

I have a php array and I need to convert it into xml file but I cant get the format I want.
Array:
Array
(
[1] => Array
(
[2000307] => Array
(
[eventid] => 2000307
[eveseq] => 100
[fee] => 200
)
[2000310] => Array
(
[eventid] => 2000310
[eveseq] =>101
[fee] => 300
)
)
)
convert array to xml:
$xml = new SimpleXMLElement('<Event/>');
event_to_xm($array, $xml);
function event_to_xml($array, &$xml) {
$array = json_decode($array,TRUE);
foreach($array as $key => $value) {
foreach($value as $id => $index) {
foreach($index as $title => $result) {
if(is_array($result)) {
if(!is_numeric($title)){
$subnode = $xml->addChild($title);
array_to_xml($result, $xml);
} else {
array_to_xml($result, $xml);
}
} else {
$xml->addChild($title, htmlspecialchars($result));
}
}
}
}
}
event.xml:
<?xml version="1.0"?>
<Event>
<eventid>2000307</eventid>
<eveseq>100</eveseq>
<fee>zz</fee>
<eventid>2000310</eventid>
<eveseq>101</eveseq>
<fee>0</fee>
</Event>
What I expect is that it will create a cd tag when a new array begin:
xml:
<?xml version="1.0"?>
<Event>
<cd>
<eventid>2000307</eventid>
<eveseq>100</eveseq>
<fee>200</fee>
</cd>
<cd>
<eventid>2000310</eventid>
<eveseq>101</eveseq>
<fee>300</fee>
</cd>
</Event>
What I tried:
I tried to direct add a attribute but I encounter this error Call to a member function addChild() on null
$xml=new SimpleXMLElement("event.xml", 0, TRUE);
$child = $xml->event[0]->addChild("cd");
I would take a somewhat different approach - first, use DOMDocument instead of SimpleXML, and, second, use xpath and a fragment to insert elements into the document. Something like this:
$events = array(
"2000307" => array(
"eventid" => 2000307,
"eveseq" => 100,
"fee" => 200,
),
"2000310" => array(
"eventid" => 20003010,
"eveseq" => 101,
"fee" => 300,
)
);
#create a blank document with a root element
$xml = <<<'XML'
<?xml version="1.0"?>
<Event></Event>
XML;
$document = new DOMDocument();
$document->loadXML($xml);
$xpath = new DOMXpath($document);
$expr = "//Event";
#indicate where to insert the new elements
$dest = $xpath->query($expr);
foreach($events as $event) {
$fragment = $document->createDocumentFragment();
#this is the new element to be inserted
$instance = " <cd>
<eventid>{$event['eventid']}</eventid>
<eveseq>{$event['eveseq']}</eveseq>
<fee>{$event['fee']}</fee>
</cd>\n";
$fragment->appendXML($instance);
$dest[0]->appendChild($fragment);
}
$document->formatOutput = TRUE;
echo $document->saveXML();
Output should be your expected output.
You can iterate array data and construct new simplexml object with new 'cd' child. Try like this:
$data = array (
"2000307" => array(
"eventid" => 2000307,
"eveseq" => 100,
"fee" => 200,
),
"2000310" => array(
"eventid" => 20003010,
"eveseq" => 101,
"fee" => 300,
)
);
function to_xml(SimpleXMLElement $object, array $data)
{
foreach ($data as $key => $value) {
if (is_array($value)) {
$new_object = $object->addChild('cd');
to_xml($new_object, $value);
} else {
$object->addChild($key, $value);
}
}
}
$xml = new SimpleXMLElement('<event/>');
to_xml($xml, $data);
print $xml->asXML();
I found a way of doing it and without iterate array data
$xml = new SimpleXMLElement('<Event/>');
event_to_xm($array, $xml);
function event_to_xml($array, &$xml) {
$array = json_decode($array,TRUE);
foreach($array as $key => $value) {
foreach($value as $id => $index) {
if(is_array($value)){
$neweve = $xml->addChild('cd');
}
foreach($index as $title => $result) {
if(is_array($result)) {
$subnode = $xml->addChild($title);
event_to_xml($subnode, $xml);
} else {
$neweve->addChild($title, htmlspecialchars($result));
}
}
}
}
}

how to pass XML setAttribute in php foreach

I want to generate XML for an array which has many field and i want to set that fields in a single XML element as attributes of that element below is my php code.
<?php
$rs=array();//values come in $rs from database, it has many fields
$doc = new DOMDocument();
$doc->formatOutput = true;
$root = $doc->createElement( "slides" );
$doc->appendChild( $root );
$firstchild = $doc->createElement( "device" );
$fs=$doc->appendChild( $firstchild );
foreach( $rs as $key=>$value ){
$fs->setAttribute($key,$value);
}
$xml_string = $doc->saveXML();
echo $xml_string;
But this is not working for me, it gives me an error like:
DOMElement:setAttribute() expects parameter 2 to be string,array given
My $rs array structure is like:
Array
(
[0] => Array
(
[id] => 1
[name] => dfd
[width] => 2
[height] => 1
[resolution] =>
[space] =>
)
)
, i want output like:
<slides>
<device id="12" name="mydevice" color="red" .....and so on></device>
</slides>
You did some mistakes, the main of them that you appendchild element to Document but not to parent
$doc = new DOMDocument();
$doc->formatOutput = true;
$root = $doc->createElement( "slides" );
$doc->appendChild( $root );
$fs = $doc->createElement( "device" );
$root->appendChild( $fs );
foreach( $rs as $key=>$value ){
$fs->setAttribute($key,$value);
}
$xml_string = $doc->saveXML();
echo $xml_string;
working example
You need to create each attribute and assign it to the correct node, something like this:
foreach( $rs as $key => $value ){
$attrib=$doc->createAttribute($key);
$attrib->nodeValue=$value;
$fs->appendChild( $attrib );
}
The $rs variable is an array of the records with an array of the fields as an element. So you need two nested loops.
The outer loop iterates the records and creates an device element node for each record. The inner loop iterates the fields and adds the attributes.
$rs = [
[
'id' => 1,
'name' => 'dfd',
'width' => 2,
'height' => 1,
'resolution' => '',
'space' => ''
]
];
$document = new DOMDocument();
$slides = $document->appendChild(
$document->createElement("slides")
);
foreach ($rs as $record) {
$device = $slides->appendChild(
$document->createElement("device")
);
foreach($record as $key => $value){
$device->setAttribute($key,$value);
}
}
$document->formatOutput = true;
echo $document->saveXML();
Output:
<?xml version="1.0"?>
<slides>
<device id="1" name="dfd" width="2" height="1" resolution="" space=""/>
</slides>

Get value from XML Array in PHP

I'm trying to get 1 value from my XML Array.
This is the code:
<?php
function objectsIntoArray($arrObjData, $arrSkipIndices = array())
{
$arrData = array();
// if input is object, convert into array
if (is_object($arrObjData)) {
$arrObjData = get_object_vars($arrObjData);
}
if (is_array($arrObjData)) {
foreach ($arrObjData as $index => $value) {
if (is_object($value) || is_array($value)) {
$value = objectsIntoArray($value, $arrSkipIndices); // recursive call
}
if (in_array($index, $arrSkipIndices)) {
continue;
}
$arrData[$index] = $value;
}
}
return $arrData;
}
?>
Result:
<?php
$xmlUrl = "http://radiourl:port/status.xsl"; // XML feed file/URL
$xmlStr = file_get_contents($xmlUrl);
$xmlObj = simplexml_load_string($xmlStr);
$arrXml = objectsIntoArray($xmlObj);
print_r($arrXml);
?>
The result of print_r($arrXml); is this:
Array (
[Mount-Point] => /listen.mp3
[Stream-Title] => VibboStream
[Stream-Description] => name
[Content-Type] => audio/mpeg
[Mount-started] => 14/Jun/2016:04:28:49 -0500
[Bitrate] => 128
[Current-Listeners] => 1
[Peak-Listeners] => 3
[Stream-Genre] => Various
[Stream-URL] => http://url [ice-bitrate] => 128
[icy-info] => ice-samplerate=44100;ice-bitrate=128;ice-channels=2
[Current-Song] => Artist - Title
)
So what I'm trying to get is the part [Current-Song] => Artist - Title.
When I echo it I'd like to only see Artist - Title
Can someone help me with this?
You already have the xml object. Just use it.
$artistTitle = $xmlObj->{'Current-Song'};
And just use it from there.
More info on that curly brace syntax here

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

XMl parsing query in php

I'm parsing xml nodes and I'm doing something wrong but I don't know what.
I have this xml:
$xml=" <root>
<info>
<company/>
<user_id>43</user_id>
<currency>EUR</currency>
</info>
<products>
<product>
<id>1336</id>
<pn>NX.RZNAA.003</pn>
<stock>1.00</stock>
</product>
<product>
<id>1337</id>
<pn>NX.RZNAA.004</pn>
<stock>4.00</stock>
<item_number>5</item_number>
</product>
</products>
</root>";
As you can see I have two "product" nodes with child nodes. But in first node "product" I have one node less then in second. In second the node "item_number" is added (it's optional node, if it has value it is in xml otherwise not) . When I'm parsing this nodes my parser returns value from second nodes "product" even if I'm on first node.
Anyone know what is the problem here?
Here is my code:
$xmlDoc = new DOMDocument();
$xmlDoc->load($xml) ;
$xpath = new DOMXPath($xmlDoc);
$tagForCount="count(//".$arrayPolja[0].")";
$x=$xmlDoc->getElementsByTagName("product");
$xpath = new DomXpath($xmlDoc);
$count = 3;
$arrayPolja[0] = "id";
$arrayPolja[1] = "pn";
$arrayPolja[2] = "stock";
$arrayPolja[3] = "item_number";
$r=0;
foreach ($x as $product) {
$i=0;
while ($i<=$count)
{
$polje=$arrayPolja[$i];
$values[$i]=$xpath->query($polje, $product)->item($r)->textContent;
$i++;
}
$r++;
}
For one, the second iteration in the loop is overriding the values of your array "$values" which is why you'd only be seeing the values from the second product node (if you are inspecting the "$values" array which is what I'm assuming you are doing).
Try this:
$xmlDoc = new DOMDocument();
$xmlDoc->load($xml);
$xpath = new DOMXPath($xmlDoc);
$x = $xmlDoc->getElementsByTagName("product");
$array = array('id','pn','stock','item_number');
$values = array();
foreach ($x as $product) {
$data = array();
// node name here
$data['node'] = $product->nodeName;
foreach ($array as $v){
$obj = $xpath->query($v, $product)->item(0);
if (gettype($obj) == 'object'){
$data[$v] = $obj->textContent;
}
}
$values[] = $data;
}
echo '<pre>' . print_r($values, true). '</pre>';
That should produce this:
Array
(
[0] => Array
(
[node] => product
[id] => 1336
[pn] => NX.RZNAA.003
[stock] => 1.00
)
[1] => Array
(
[node] => product
[id] => 1337
[pn] => NX.RZNAA.004
[stock] => 4.00
[item_number] => 5
)

Categories