Is it possible to sort a simplexml object but keep the object as a simplexml object instead of casting to an array.
There doesnt appear to be a way to do this - and if casted to an array it seems very messy to transform the array(s) back to a simplexml object.
So -
Is it possible to sort a simplexml object by an attribute etc.
For example (sort by the position attribute node):
<test_nodes>
<node position="1"></node>
<node position="2"></node>
<node position="3"></node>
</test_nodes>
If not then what is the most painless and efficient way to cast an array to a simple xml object
Related
I have an xpath query on a simplexml element like so:
$theSimpleXmlObject->xpath('//path/to/*/node');
it returns an array of results which is great but it is an array of simplexml objects and i just want a flat array.
Is this possible?
Ideally i would have a simple array returned with the matching node values instead of having to iterate over the results to prepare the array.
Try in this way to get Array
$xml = json_decode(json_encode((array) simplexml_load_string($string)), 1);
When you cast xml to array it will cast only "first level", so we can made it by convert it in JavaScript Object Notation and decode it again to array. Serialization will not help, becouse it keep data-structure.
I've seen this problem a few times, and would like a definitive answer.
Given this structure in xml:
<ByteListSet>
<Byte Order="0">14</Byte>
</ByteListSet>
I am not able to access the attribute 'order'. var_dump (unsurprisingly) does not show any attributes for ByteListSet. Indeed, foreach iteration does not produce a #attributes item.
However, the following structure:
<ByteListSet>
<Byte Order="0"><Value>3729</Value></Byte>
</ByteListSet>
Results properly in ByteListSet having a child Byte that is a SimpleXmlObject which has #attributes.
I would assume that SimpleXML is indeed picking up the #attributes from the first case, but where is it keeping them? The trouble is that in the former structure, ByteListSet produces this on var_dump of ->children():
object(SimpleXMLElement)[25]
public 'Byte' => string '14' (length=2)
if I get_object_vars() on it and var_dump each, I simply get:
string '14' (length=2)
Indicating that Byte is not being returned to me as an xml object, but just as a string; as a property of the ByteList object above it.
Order="0" is there somewhere, but I don't have access to it. How do I get to it? NOTE: ->attributes() returns, as you would expect, a blank array.
(We do not control this schema, so it can't be restructured.)
You wrote:
Given this structure in xml:
<ByteListSet>
<Byte Order="0">14</Byte>
</ByteListSet>
I am not able to access the attribute 'order'.
Sure, because the attribute order does not exist. XML is case-sensitive, the attribute is named Order with uppercase O at the beginning.
Using the right attribute name allows you to access the value as outline in Example #5 in the basic examples of the SimpleXML extension you can find in the manual:
$ByteListSet = simplexml_load_string(<<<XML
<ByteListSet>
<Byte Order="0">14</Byte>
</ByteListSet>
XML
);
echo $ByteListSet->Byte['Order']; # 0
Please keep in mind that what you see in var_dump or print_r is not always what you get. This is espeically true for Simplexml (compare: How to get values of xml elements?; SimpleXML and print_r() - why is this empty?), however, also for other objects in PHP that make use of ArrayAccess, __toString() or IteratorIterator / IteratorAggregate.
Please let me know if that answers your question. You were not very specific which existing solutions you have not understood so far, so I answered your question, but I'm not 100% if I hit the nail.
I have a nice function that converts a (simple, duh) xml to an array, and to do so it uses the SimpleXmlIterator class.
It works quite well, but I'd like to make it accept not only xml source strings or SimpleXmlIterator objects, but SimpleXmlElements too since I use them way more often that iterators (that I use just in that function, actually).
What I did so far was
$iter = new SimpleXmlIterator($xml->asXML());
but to me it's like passing through Tokyo to get from Paris to London. After all, SimpleXmlIterator extends SimpleXmlElement, so is there a better way to convert a SimpleXmlElement object to a SimpleXmlIterator?
You can use SimpleXMLIterator at the beginning by setting the second parameter in http://php.net/manual/de/function.simplexml-load-file.php
$yourSimpleXMLIteratorObject = simplexml_load_file("your_file.xml","SimpleXMLIterator");
all Objects resulting, are now SimpleXMLIterator-Objects
Why not parse all your XML into SimpleXMLIterator objects to begin with if you plan to use them as such?
As far as I know there is not an easy way to convert/cast from SimpleXMLElement to SimpleXMLIterator. These classes simply wrap a libxml resource so you would think there would be a way to obtain a resource handle and pass it into SimpleXML* constructors, but if there is it's news to me.
How do I parse "id" from the following event
<event id="100990">
<title>myTitle</title>
</event>
I parse the title like this:
$xml->event[0]->title
Use the attributes method of SimpleXML
$id_attribute = (string)$xml->event[0]->attributes()->id;
You access attributes just like you would access elements of an associative array:
$xml_node['id'] // The value of the attribute `id` of the node `$xml_node`
The returned value is an object (with a __toString method), so you may want to typecast the returned value.
This is exactly what you need: http://www.php.net/manual/en/simplexmlelement.attributes.php
Calling addChild with an empty string as the value (or even with whitespace) seems to cause a redundant SimpleXml node to be added inside the node instead of adding just the node with no value.
Here's a quick demo of what happens:
[description] => !4jh5jh1uio4jh5ij14j34io5j!
And here's with an empty string:
[description] => SimpleXMLElement Object ( [0] => )
The workaround I'm using at the moment is pretty horrible - I'm doing a str_replace on the final JSON to replace !4jh5jh1uio4jh5ij14j34io5j! with an empty string. Yuck. Perhaps the only answer at this point is 'submit a bug report to simplexml'...
Does anyone have a better solution?
I think I figured out what is going on. Given code like this:
$xml = new SimpleXMLElement('<xml></xml>');
$xml->addChild('node','value');
print_r($xml);
$xml = new SimpleXMLElement('<xml></xml>');
$xml->addChild('node','');
print_r($xml);
$xml = new SimpleXMLElement('<xml></xml>');
$xml->addChild('node');
print_r($xml);
The output is this:
SimpleXMLElement Object
(
[node] => value
)
SimpleXMLElement Object
(
[node] => SimpleXMLElement Object
(
[0] =>
)
)
SimpleXMLElement Object
(
[node] => SimpleXMLElement Object
(
)
)
So, to make it so that in case #2 the empty element isn't created (i.e. if you don't know if the second argument is going to be an empty string or not), you could just do something like this:
$mystery_string = '';
$xml = new SimpleXMLElement('<xml></xml>');
if (preg_match('#\S#', $mystery_string)) // Checks for non-whitespace character
$xml->addChild('node', $mystery_string);
else
$xml->addChild('node');
print_r($xml);
echo "\nOr in JSON:\n";
echo json_encode($xml);
To output:
SimpleXMLElement Object
(
[node] => SimpleXMLElement Object
(
)
)
Or in JSON:
{"node":{}}
Is that what you want?
Personally, I never use SimpleXML, and not only because of this sort of weird behavior -- it is still under major development and in PHP5 is missing like 2/3 of the methods you need to do DOM manipulation (like deleteChild, replaceChild etc).
I use DOMDocument (which is standardized, fast and feature-complete, since it's an interface to libxml2).
With SimpleXML, what you get if you use print_r(), or var_dump(), serialize(), or similar, does not correspond to what is stored internally in the object. It is a 'magical' object which overloads the way PHP interates its contents.
You get the true representation of the element with AsXML() only.
When something like print_r() iterates over a SimpleXML element or you access its properties using the -> operator, you get a munged version of the object. This munged version allows you to do things like "echo $xml->surname" or $xml->names[1] as if it really had these as properties, but is separate to the true XML contained within: in the munged representation elements are not necessarily in order, and elements whose names are PHP reserved words (like "var") aren't presented as properties, but can be accessed with code like $xml["var"] - as if the object is an associative array. Where multiple sibling elements have the same name they are presented like arrays. I guess an empty string is also presented like an array for some reason. However, when output using AsXML() you get the real representation.
Maybe I'm not understanding the question right but, it seems to me that when you use the addChild method, you're required to have a string as an argument for the name of the node regardless of what content is in the node. The value (second argument) is optional and can be left blank to add and empty node.
Let me know if that helps.
I've created an Xml library to which extends the simpleXml object to include all of the functionally that is present in the DOMDocument but is missing an interface from SimpleXml (as the two functions interact with the same underlying libxml2 object --by reference). It also has niceties such as AsArray() or AsJson() to output your object in one of those formats.
I've just updated the library to work as you expect when outputting JSON. You can do the following:
$xml = new bXml('<xml></xml>');
$xml->addChild('node', '');
$json_w_root = $xml->asJson(); // is { 'xml': {'node':'' } }
$json = $xml->children()->asJson(); // is { 'node' : '' } as expected.
The library is hosted on google code at http://code.google.com/p/blibrary/