I have a simple XML structure like, that when parse with simplexml_load_string generates this:
SimpleXMLElement Object
(
[#attributes] => Array
(
[token] => rs2rglql9c8ztem
)
[attachments] => SimpleXMLElement Object
(
[attachment] => 112979696
)
)
the XML structure:
<uploads token="vwl3u75llktsdzi">
<attachments>
<attachment>123456789</attachment>
</attachments>
</uploads>
I can get to the only actually important value "123456789" through iteration but that is a faf. Is there a way I can access it directly, ideally using the names of the elements.
I need to able to get attributes to ideally.
The simplest way to store the textual node value of a SimpleXMLElement in its own variable is to cast the element to a string:
$xml = simplexml_load_string($str);
$var = (string) $xml->attachments->attachment;
echo $var;
UPDATE
In accordance with the further question in your comment, the SimpleXMLElement::attributesdocs method will also return a SimpleXMLElement object which can be accessed in the same manner as the above solution. Consider:
$str = '<uploads token="vwl3u75llktsdzi">
<attachments>
<attachment myattr="attribute value">123456789</attachment>
</attachments>
</uploads>';
$xml = simplexml_load_string($str);
$attr = (string) $xml->attachments->attachment->attributes()->myattr;
echo $attr; // outputs: attribute value
Yes you can through the {} sytax here it goes:
$xmlString = '<uploads token="vwl3u75llktsdzi">
<attachments>
<attachment myattr="attribute value">123456789</attachment>
</attachments>
</uploads>';
$xml = simplexml_load_string($xmlString);
echo "attachment attribute: " . $xml->{"attachments"}->attributes()["myattr"] " \n";
echo " uploads attribute: " . $xml->{"uploads"}->attributes()["token"] . "\n";
you can replace "attachments" with $myVar or something
remember attributes() returns an associative array so you can get the keys with php array_keys() or do a foreach cycle.
Related
I got a Google Shopping feed like this (extract):
<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">
...
<g:id><![CDATA[Blah]]></g:id>
<title><![CDATA[Blah]]></title>
<description><![CDATA[Blah]]></description>
<g:product_type><![CDATA[Blah]]></g:product_type>
Now, SimpleXML can read the "title" and "description" tags but it can't read the tags with "g:" prefix.
There are solutions on stackoverflow for this specific case, using the "children" function.
But I don't only want to read Google Shopping XMLs, I need it to be undependend from structure or namespace, I don't know anything about the file (I recursively loop through the nodes as an multidimensional array).
Is there a way to do it with SimpleXML? I could replace the colons, but I want to be able to store the array and reassemble the XML (in this case specifically for Google Shopping) so I do not want to lose information.
You want to use SimpleXMLElement to extract data from XML and convert it into an array.
This is generally possible but comes with some caveats. Before XML Namespaces your XML comes with CDATA. For XML to array conversion with Simplexml you need to convert CDATA to text when you load the XML string. This is done with the LIBXML_NOCDATA flag. Example:
$xml = simplexml_load_string($buffer, null, LIBXML_NOCDATA);
print_r($xml); // print_r shows how SimpleXMLElement does array conversion
This gives you the following output:
SimpleXMLElement Object
(
[#attributes] => Array
(
[version] => 2.0
)
[title] => Blah
[description] => Blah
)
As you can already see, there is no nice form to present the attributes in an array, therefore Simplexml by convention puts these into the #attributes key.
The other problem you have is to handle those multiple XML namespaces. In the previous example no specific namespace was used. That is the default namespace. When you convert a SimpleXMLElement to an array, the namespace of the SimpleXMLElement is used. As none was explicitly specified, the default namespace has been taken.
But if you specify a namespace when you create the array, that namespace is taken.
Example:
$xml = simplexml_load_string($buffer, null, LIBXML_NOCDATA, "http://base.google.com/ns/1.0");
print_r($xml);
This gives you the following output:
SimpleXMLElement Object
(
[id] => Blah
[product_type] => Blah
)
As you can see, this time the namespace that has been specified when the SimpleXMLElement was created is used in the array conversion: http://base.google.com/ns/1.0.
As you write you want to take all namespaces from the document into account, you need to obtain those first - including the default one:
$xml = simplexml_load_string($buffer, null, LIBXML_NOCDATA);
$namespaces = [null] + $xml->getDocNamespaces(true);
Then you can iterate over all namespaces and recursively merge them into the same array shown below:
$array = [];
foreach ($namespaces as $namespace) {
$xml = simplexml_load_string($buffer, null, LIBXML_NOCDATA, $namespace);
$array = array_merge_recursive($array, (array) $xml);
}
print_r($array);
This then finally should create and output the array of your choice:
Array
(
[#attributes] => Array
(
[version] => 2.0
)
[title] => Blah
[description] => Blah
[id] => Blah
[product_type] => Blah
)
As you can see, this is perfectly possible with SimpleXMLElement. However it's important you understand how SimpleXMLElement converts into an array (or serializes to JSON which does follow the same rules). To simulate the SimpleXMLElement-to-array conversion, you can make use of print_r for a quick output.
Note that not all XML constructs can be equally well converted into an array. That's not specifically a limitation of Simplexml but lies in the nature of which structures XML can represent and which structures an array can represent.
Therefore it is most often better to keep the XML inside an object like SimpleXMLElement (or DOMDocument) to access and deal with the data - and not with an array.
However it's perfectly fine to convert data into an array as long as you know what you do and you don't need to write much code to access members deeper down the tree in the structure. Otherwise SimpleXMLElement is to be favored over an array because it allows dedicated access not only to many of the XML feature but also querying like a database with the SimpleXMLElement::xpath method. You would need to write many lines of own code to access data inside the XML tree that comfortable on an array.
To get the best of both worlds, you can extend SimpleXMLElement for your specific conversion needs:
$buffer = <<<BUFFER
<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">
...
<g:id><![CDATA[Blah]]></g:id>
<title><![CDATA[Blah]]></title>
<description><![CDATA[Blah]]></description>
<g:product_type><![CDATA[Blah]]></g:product_type>
</rss>
BUFFER;
$feed = new Feed($buffer, LIBXML_NOCDATA);
print_r($feed->toArray());
Which does output:
Array
(
[#attributes] => stdClass Object
(
[version] => 2.0
)
[title] => Blah
[description] => Blah
[id] => Blah
[product_type] => Blah
[#text] => ...
)
For the underlying implementation:
class Feed extends SimpleXMLElement implements JsonSerializable
{
public function jsonSerialize()
{
$array = array();
// json encode attributes if any.
if ($attributes = $this->attributes()) {
$array['#attributes'] = iterator_to_array($attributes);
}
$namespaces = [null] + $this->getDocNamespaces(true);
// json encode child elements if any. group on duplicate names as an array.
foreach ($namespaces as $namespace) {
foreach ($this->children($namespace) as $name => $element) {
if (isset($array[$name])) {
if (!is_array($array[$name])) {
$array[$name] = [$array[$name]];
}
$array[$name][] = $element;
} else {
$array[$name] = $element;
}
}
}
// json encode non-whitespace element simplexml text values.
$text = trim($this);
if (strlen($text)) {
if ($array) {
$array['#text'] = $text;
} else {
$array = $text;
}
}
// return empty elements as NULL (self-closing or empty tags)
if (!$array) {
$array = NULL;
}
return $array;
}
public function toArray() {
return (array) json_decode(json_encode($this));
}
}
Which is an adoption with namespaces of the Changing JSON Encoding Rules example given in SimpleXML and JSON Encode in PHP – Part III and End.
The answer given by hakre was well written and exactly what I was looking for, especially the Feed class he provided at the end. But it was incomplete in a couple of ways, so I modified his class to be more generically useful and wanted to share the changes:
One of the most important issues which was missed in the original is that Attributes may also have namespaces, and without taking that into account, you are quite likely to miss attributes on elements.
The other bit which is important is that when converting to an array, if you have something which may contain elements of the same name but different namespaces, there is no way to tell which namespace the element was from. (Yes, it's a really rare situation... but I ran into it with a government standard based on NIEM...) So I added a static option which will cause the namespace prefix to be added to all keys in the final array that belong to a namespace. To use it, set
Feed::$withPrefix = true; before calling toArray()
Finally, more for my own preferences, I added an option to toArray() to return the final array as associative instead of using objects.
Here's the updated class:
class Feed extends \SimpleXMLElement implements \JsonSerializable
{
public static $withPrefix = false;
public function jsonSerialize()
{
$array = array();
$attributes = array();
$namespaces = [null] + $this->getDocNamespaces(true);
// json encode child elements if any. group on duplicate names as an array.
foreach ($namespaces as $prefix => $namespace) {
foreach ($this->attributes($namespace) as $name => $attribute) {
if (static::$withPrefix && !empty($namespace)) {
$name = $prefix . ":" . $name;
}
$attributes[$name] = $attribute;
}
foreach ($this->children($namespace) as $name => $element) {
if (static::$withPrefix && !empty($namespace)) {
$name = $prefix . ":" . $name;
}
if (isset($array[$name])) {
if (!is_array($array[$name])) {
$array[$name] = [$array[$name]];
}
$array[$name][] = $element;
} else {
$array[$name] = $element;
}
}
}
if (!empty($attributes)) {
$array['#attributes'] = $attributes;
}
// json encode non-whitespace element simplexml text values.
$text = trim($this);
if (strlen($text)) {
if ($array) {
$array['#text'] = $text;
} else {
$array = $text;
}
}
// return empty elements as NULL (self-closing or empty tags)
if (!$array) {
$array = NULL;
}
return $array;
}
public function toArray($assoc=false) {
return (array) json_decode(json_encode($this), $assoc);
}
}
SimpleXMLElement Object
(
[0] => CEM
)
There is a SimpleXMLElement Object like this. I tried accessing it with $object->0 and $object->{0}. But it is giving a php error. How do we access it with out changing it to another format.
From your print_r output it might not be really obvious:
SimpleXMLElement Object
(
[0] => CEM
)
This is just a single XML element containing the string CEM, for example:
<xml>CEM</xml>
You obtain that value by casting to string (see Basic SimpleXML usageDocs):
(string) $object;
When you have a SimpleXMLElement object and you're unsure what it represents, it's easier to use echo $object->asXML(); to output the XML and analyze it than using print_r alone because these elements have a lot of magic that you need to know about to read the print_r output properly.
An example from above:
<?php
$object = new SimpleXMLElement('<xml>CEM</xml>');
print_r($object);
echo "\n", $object->asXML(), "\n";
echo 'Content: ', $object, "\n"; // echo does cast to string automatically
Output:
SimpleXMLElement Object
(
[0] => CEM
)
<?xml version="1.0"?>
<xml>CEM</xml>
Content: CEM
(Online Demo)
In your case, it appears as you do
print_r($object) and $object it's an array
so what you are viewing is not an XML objet but an array enclosed in the display object of print_r()
any way, to access a simpleXML object you can use the {} sytax here it goes:
$xmlString = '<uploads token="vwl3u75llktsdzi">
<attachments>
<attachment myattr="attribute value">123456789</attachment>
</attachments>
</uploads>';
$xml = simplexml_load_string($xmlString);
echo "attachment attribute: " . $xml->{"attachments"}->attributes()["myattr"] " \n";
echo " uploads attribute: " . $xml->{"uploads"}->attributes()["token"] . "\n";
you can replace "attachments" with $myVar or something
remember attributes() returns an associative array so you can access data with square braces or through array_keys() or do a foreach cycle.
In your specific case may be just
echo $object[0]; // returns string "CEM"
I understand that SimpleXML is far more efficient than DOMDocument. Any advice on how I would reform the below into a SimpleXML version?
<?php
$doc = new DOMDocument();
$doc->load( 'feedpage.xml' );
$Main = $doc->getElementsByTagName( "varOne" );
foreach( $Main as $varOne )
{
$VarTwo = $varOne->getElementsByTagName( "VarTwo" );
$VarTwo = $VarTwo->item(0)->nodeValue;
$VarThree = $varOne->getElementsByTagName( "VarThree" );
$VarThree = $VarThree->item(0)->nodeValue;
$VarFour = $varOne->getElementsByTagName( "VarFour" );
$VarFour = $VarFour->item(0)->nodeValue;
$VarFive = $varOne->getElementsByTagName( "VarFive" );
$VarFive = $VarFive->item(0)->nodeValue;
echo "$VarTwo - $VarThree - $VarTFour - ETC\n";
echo "<img src=\"$VarFive\" />";
echo "Link";
}
?>
Start with something like this:
$doc = simplexml_load_file("feedpage.xml");
Then (since I don't know what your XML file looks like), try:
echo "<pre>".print_r($doc,true)."</pre>";
to see exactly how the resulting object is laid out. From there, you should be able to pick out the pieces you need to build what you want.
Edit:
If your output is:
SimpleXMLElement Object (
[varOne] => SimpleXMLElement Object (
[varOne] => Title
[varTwo] => A description
[VarThree] => A Link
[VarFour] => An Image
)
)
You could do this to access the properties of each one:
foreach($doc as $row) {
$title = $row->varOne;
$description = $row->varTwo;
// etc.
}
So with the foreach loop, you can go through each main item and access each item's properties.
And if you want to put code in comments, you can use the backtick (`) to surround your text, but it doesn't work well for code blocks (like the one you wanted to post). Best for variables or other short bits.
FINAL EDIT:
Let's take this example object:
SimpleXMLElement Object (
[varOne] => SimpleXMLElement Object (
[varOne] => "Title"
[varTwo] => "Description"
[varThree] => "Link"
[varFour] => "Image"
[varFive] => SimpleXMLElement Object (
[varA] => "something"
[varB] => SimpleXMLElement Object (
[varX] => "a string"
)
[varC] => "another thing"
)
)
)
Let's say the whole thing is contained in a variable $obj. Now let's say we wanted what's in varX. We'd access it like this:
echo $obj->varOne->varFive->varB->varX;
Beyond this, I don't know what else to tell you. You need to closely examine the objects you have and determine how they are structured. Extrapolate what you've learned here and apply it to your situation.
I am currently attempting to extract values from an array that was generated by an SIMPLEXML/XPATH Query. I have had no success with my attempts if anyone can take look would be greatly appreciated.
I am looking just to extract ReclaimDate. I have tried a few functions and some of the advice on this post with no luck.
Array
(
[0] => Array
(
[0] => SimpleXMLElement Object
(
[#attributes] => Array
(
[ReclaimDate] => 05/15/2008
[ReclaimPrice] => 555555555
[_Owner] => ownername
)
)
)
If I just had to take a stab, I'd agree that what #Frank Farmer said should work:
// if $myVar is what you print_r'ed
echo (string)$myVar[0][0]['ReclaimDate'];
or this
echo (string)$myVar[0][0]->attributes('ReclaimDate');
http://www.php.net/manual/en/simplexml.examples-basic.php#example-4587
This is resolved thanks to Frank Farmer and Dan Beam.
This worked : echo (string)$check_reo[0][0]['ReclaimDate']
For anyone that is looking to use SimpleXML and XPATH to extract and write some basic logic from an XML file this is what worked for me.
$xmlstring = <<<XML <?xml version='1.0' standalone='yes'?> <YOURXMLGOESHERE>TEST</YOURXMLGOESHERE> XML;
$xpathcount = simplexml_load_string($xmlstring); // Load XML for XPATH Node Counts
$doc = new DOMDocument(); // Create new DOM Instance for Parsing
$xpathcountstr = $xpathcount->asXML(); // Xpath Query
$doc->loadXML($xpathcountstr); // Load Query Results
$xpathquery = array($xpathcount->xpath("//XMLNODEA[1]/XMLNODEB/*[name()='KEYWORDTOCHECKIFXMLCEXISTS']"));
print_r ($xpathquery) // CHECK Array that is returned from the XPATH query
`Array
(
[0] => Array
(
[0] => SimpleXMLElement Object
(
[#attributes] => Array
(
[ReclaimDate] => 05/15/2008
[ReclaimPrice] => 555555555
[_Owner] => ownername
)
)
) // Array RETURNED`
echo (string)$xpathquery[0][0]['ReclaimDate'] // EXTRACT THE VALUE FROM THE ARRAY COLUMN;
This site helped me receive a better understanding on how XPATH can search XML very easily with a lot more features than what I had previously known.
http://zvon.org/xxl/XPathTutorial/Output/examples.html
Here is the simple XML Function that worked for me
$xmlread = simplexml_load_string($xmlstring, "simple_xml_extended");
`class simple_xml_extended extends SimpleXMLElement { // Read XML and get attribute
public function Attribute($name){
foreach($this->Attributes() as $key=>$val) {
if($key == $name)
return (string)$val;
}
}
}`
Here is the Function action when extracting Single Values with an attribute based XML results
$GETVAR1 = $xmlread->XMLNODE1->XMLNODE2->XMLNODE3->XMLNODE4->XMLNODE5[0]->Attribute('XMLNODE5ATTRIBUTE');
This might not be the most efficient or best method, but its what ended working our for me. Hope this helps someone out who is still unclear about SIMPLEXML and XPATH.
An additional link for further insight: http://www.phpfreaks.com/tutorial/handling-xml-data
I have some json object that I decoded, and one of the attributes starts with an "#" and I can't access the element with php because it throws an error.
[offers] => stdClass Object
(
[#attributes] => stdClass Object
(
[id] => levaka0B8a
)
)
How would I go about accessing attributes?
You can access it by a string:
echo $obj->{'#attributes'}->id; // levaka0B8a
Or a variable:
$name = '#attributes';
echo $obj->$name->id;
For more information on how variables are defined and used, see the following docs:
Variable Basics - Useful for learning what can be accessed as a variable without needing to use strings.
Variable Variables - How we used the variable to act as the name for another variable. This can be dangerous so tread carefully
You could do this:
$object->{'#attributes'}
Try to use,
$objSimpleXml->attributes()->id
Sample Code to Refer
<?php
$string = <<<XML
<a>
<foo name="one" game="lonely">1</foo>
</a>
XML;
$xml = simplexml_load_string($string);
var_dump( $xml );
foreach($xml->foo[0]->attributes() as $a => $b) {
echo $a,'="',$b,"\"\n";
}
?>
direct access is below from ircmaxwell or Richard Tuin, however you can decode JSON with second param true and recive array insted what could be an easier to access