XML node and values reversed using SimpleXMLElement and array_walk() - php

I am trying to build an XML element from an array in PHP using an example that was posted elsewhere on this site. The XML string is being created as expected however the nodes and their values are reversed. For Example:
$params = array(
"email" => 'me#gmail.com'
,"pass" => '123456'
,"pass-conf" => '123456'
);
$xml = new SimpleXMLElement('<root/>');
array_walk_recursive($params, array($xml, 'addChild'));
echo $xml->asXML();
Now what I am expecting to be returned is:
<?xml version="1.0"?>
<root>
<email>me#gmail.com</email>
<pass>123456</pass>
<pass-conf>123456</pass-conf>
</root>
However, what I keep getting is the node names as values and values as node names:
<?xml version="1.0"?>
<root>
<me#gmail.com>email</me#gmail.com>
<123456>pass</123456>
<123456>pass-conf</123456>
</root>
I have tested switching the key with the value in the $params array, but that seems like a lazy hack to me. I believe the issue lies within my callback in array_walk_recursive, but I'm not sure how exactly to make it work. I am open to recommendations on better ways to convert a PHP array to XML. I just tried this because it seemed simple and not convoluted. (haha..)

The problem with your code is that array_walk_recursive supplies the callback with the arguments value then key (in that order).
SimpleXMLElement::addChild accepts the arguments name then value (in that order).
Here's a less convoluted solution
foreach ($params as $key => $value) {
$xml->addChild($key, $value);
}
https://3v4l.org/oOHSb

Related

Differences in setting value in SimpleXML and foreach loop

In answering a previous question I found the following behaviour which I can't understand. The following code shows the issue...
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
$data = <<< XML
<?xml version="1.0" standalone="yes"?>
<Base>
<Data>
<Value></Value>
</Data>
</Base>
XML;
$xml = simplexml_load_string($data);
foreach ( $xml->Data->Value as $value ) {
$value = 1;
}
echo $xml->asXML().PHP_EOL;
foreach ( $xml->Data as $value ) {
$value->Value = 1;
}
echo $xml->asXML().PHP_EOL;
I would expect the output at each point to be the same, but the output is...
<?xml version="1.0" standalone="yes"?>
<Base>
<Data>
<Value/>
</Data>
</Base>
<?xml version="1.0" standalone="yes"?>
<Base>
<Data>
<Value>1</Value>
</Data>
</Base>
So this seems to indicate that the first loop which directly accesses the <Value> element, doesn't set the value and yet the second loop which indirectly accesses it works OK.
What is the difference?
The difference is nothing to do with the loops, or with references, but with what exactly = means in each case.
The first version can be simplified to this:
$value = $xml->Data->Value;
$value = 1;
This is a straight-forward assignment to a variable, first of one value, and then of another. There's no interaction between the old value and the new one, so $xml is not changed.
The second case can be written like this:
$data = $xml->Data;
$data->Value = 1;
// Or just $xml->Data->Value = 1;
Here, we are assigning not to a normal variable, but to an object property, and the trick is that the object can intercept that assignment, and do something special with it. In this case, it triggers SimpleXML to send the value to the libxml representation of the XML document in memory. It is as though you had run a method call like $data->setValueOfChild('Value', 1);.
Note that if we instead wrote this:
$value =& $xml->Data->Value;
$value = 1;
Now the first assignment sets $value to be a reference, and the second assigns 1 to that reference. This is enough to write the value to an actual object property, but does not trigger the interception SimpleXML needs.
However, there is one additional trick we can use in this particular case: as well as intercepting property access, the SimpleXMLElement class intercepts array access so that you can write $foo->NameThatOccursMoreThanOnce[3] and $some_element['Attribute']. So it turns out we can write this:
$value = $xml->Data->Value;
$value[0] = 1;
Here, $value is a SimpleXMLElement object, which can intercept the $value[0] = 1 as something like $value->setValueOfItem(0, 1).
In this case, the object holds the collection of all elements called <Value> from inside the <Data> element; but conveniently, even if the object has already been narrowed down to one item, [0] just refers back to the same element, so this works too:
$value = $xml->Data->Value[0];
$value[0] = 1;
Finally, a quick note that your own objects can implement this magic behaviour too! The property access can be implemented using the __get, __set, and __unset magic methods, and the array access can be implemented using the ArrayAccess interface.

SimpleXMLElement foreach child Array/Value

I have the following code which works but seems like the incorrect way to implement this. Disregard the "...." that is all extra stuff we need not be concerned by. The issue I was having was that Sup was an array some of the time and other times it was just a value (or so print_r claimed, I thought/hoped it would have just been a one element array).
$users is a simpleXMLElement.
foreach($users as $user) {
if ($user->InstSup->Sup[1] == '') {
foreach($user->InstSup as $affid) {
....
} else {
foreach($user->InstSup->Sup as $affid) {
Here are the varying instances...
<Users>
<User>
<InstSup><Sup>1</Sup></InstSup>
</User>
<User>
<InstSup><Sup>2</Sup><Sup>3</Sup><Sup>4</Sup><Sup>5</Sup></InstSup>
</User>
</Users>
Thanks.
First of all, don't trust the output of print_r (or var_dump) when you deal with SimpleXMLElements. It's not showing the whole picture, better take a look at the XML as-is, for example with the asXML() method.
Now to the problem you've got. When you want to have just the list (so to speak an "array") of the <Sup> elements that are children of <InstSup>, you better query the document with Xpath. It's fairly straight forward and gives you the array you want:
$users = new SimpleXMLElement($buffer);
$sups = $users->xpath('/Users/User/InstSup/Sup');
foreach ($sups as $index => $sup) {
printf("#%d: %s (%s)\n", $index, $sup, $sup->asXML());
}
This creates the following output:
#0: 1 (<Sup>1</Sup>)
#1: 2 (<Sup>2</Sup>)
#2: 3 (<Sup>3</Sup>)
#3: 4 (<Sup>4</Sup>)
#4: 5 (<Sup>5</Sup>)
And this is the $buffer to complete the example:
$buffer = <<<XML
<Users>
<User>
<InstSup><Sup>1</Sup></InstSup>
</User>
<User>
<InstSup><Sup>2</Sup><Sup>3</Sup><Sup>4</Sup><Sup>5</Sup></InstSup>
</User>
</Users>
XML;
As the line-up in the output shows, even though the <Sup> elements are inside (same-named but) different parent elements, the XPath query expression
/Users/User/InstSup/Sup
returns all the elements in that path from the document.
So hopefully you now better understand that it's not only that print_r is not that useful because it doesn't show the whole picture, but also by understanding how the document has it's nodes ordered, you can even more easily query the data with an Xpath expression.

How to get name of very first tag of XML with php's SimpleXML?

I am parsing XML strings using simplexml_load_string(), but I noticed that i don't get the name of the very first tag.
For example, I have these two xml strings:
$s = '<?xml version="1.0" encoding="UTF-8"?>
<ParentTypeABC>
<chidren1>
<children2>1000</children2>
</chidren1>
</ParentTypeABC>
';
$t = '<?xml version="1.0" encoding="UTF-8"?>
<ParentTypeDEF>
<chidren1>
<children2>1000</children2>
</chidren1>
</ParentTypeDEF>
';
NOTICE that they are nearly identical, the only difference being that one has the first node as <ParentTypeABC> and the other as <ParentTypeDEF>
then I just convert them to SimpleXML objects:
$o = simplexml_load_string($s);
$p = simplexml_load_string($t);
but then i have two equal objects, none of them having the "top" node's name appearing, either ParentTypeABC or ParentTypeDEF (I examine the objects using print_r()):
// with top node "ParentTypeABC"
SimpleXMLElement Object
(
[chidren1] => SimpleXMLElement Object
(
[children2] => 1000
)
)
// with top node "ParentTypeDEF"
SimpleXMLElement Object
(
[chidren1] => SimpleXMLElement Object
(
[children2] => 1000
)
)
So how I am supposed to know the top node's name? If I parse unknown XMLs and I need to know what's the top node name, what can I do?
Is there an option in simplexml_load_string() I could use?
I know there are MANY ways to parse XML's with PHP, but I'd like it to be as simple as posible, and to get a simple object or array I could navigate easily.
I made a simple example here to fiddle with.
SimpleXML has a getName() method.
echo $xml->getName();
This should return the name of the respective node, no matter if root or not.
http://php.net/manual/en/simplexmlelement.getname.php

PHP SimpleXML Element parsing issue

I've come across a weird but apparently valid XML string that I'm being returned by an API. I've been parsing XML with SimpleXML because it's really easy to pass it to a function and convert it into a handy array.
The following is parsed incorrectly by SimpleXML:
<?xml version="1.0" standalone="yes"?>
<Response>
<CustomsID>010912-1
<IsApproved>NO</IsApproved>
<ErrorMsg>Electronic refunds...</ErrorMsg>
</CustomsID>
</Response>
Simple XML results in:
SimpleXMLElement Object ( [CustomsID] => 010912-1 )
Is there a way to parse this in XML? Or another XML library that returns an object that reflects the XML structure?
That is an odd response with the text along with other nodes. If you manually traverse it (not as an array, but as an object) you should be able to get inside:
<?php
$xml = '<?xml version="1.0" standalone="yes"?>
<Response>
<CustomsID>010912-1
<IsApproved>NO</IsApproved>
<ErrorMsg>Electronic refunds...</ErrorMsg>
</CustomsID>
</Response>';
$sObj = new SimpleXMLElement( $xml );
var_dump( $sObj->CustomsID );
exit;
?>
Results in second object:
object(SimpleXMLElement)#2 (2) {
["IsApproved"]=>
string(2) "NO"
["ErrorMsg"]=>
string(21) "Electronic refunds..."
}
You already parse the XML with SimpleXML. I guess you want to parse it into a handy array which you not further define.
The problem with the XML you have is that it's structure is not very distinct. In case it does not change much, you can convert it into an array using a SimpleXMLIterator instead of a SimpleXMLElement:
$it = new SimpleXMLIterator($xml);
$mode = RecursiveIteratorIterator::SELF_FIRST;
$rit = new RecursiveIteratorIterator($it, $mode);
$array = array_map('trim', iterator_to_array($rit));
print_r($array);
For the XML-string in question this gives:
Array
(
[CustomsID] => 010912-1
[IsApproved] => NO
[ErrorMsg] => Electronic refunds...
)
See as well the online demo and How to parse and process HTML/XML with PHP?.

XML to array, xml2ary bug

I am using this function http://mysrc.blogspot.it/2007/02/php-xml-to-array-and-backwards.html
to parse an XML to an Array. Very great function. But the strange thing is that
if I have the following 2 xml files:
<response>
<company prop1=1>
</company>
<company prop1=2>
</company>
</response>
<response>
<company prop1=1>
</company>
</response>
I got different result. For the first case, I got an array of two elements:
Array(
int(0) => _a => Array(...)
int(1) => _a => Array(...)
)
but for the second case I got
Array (
_a => Array(...)
)
which is not an array with indexes as the first case. This complicates parsing.
Does anybody have any idea how to modify the code?
Regards.
Let's say you do something like
$result = xml2ary($xml);
Try adding this line after your call to xml2ary():
$result = is_int(reset(array_keys($result))) ? $result : array($result);
This checks if the first key of the result array is an integer (which means that the xml2ary function returned multiple results. If not, it automatically wraps the $result variable in an array(), so that you have the same response format even when only one XML item is parsed.
Try using the PHP simplexml class:
http://php.net/manual/en/book.simplexml.php
It's the best way to parse XML with PHP

Categories