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
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);
}
}
I am storing an xml response in array format. Now i need to get the node attribute values like HotelLocation, hotelCode etc from the array. Below is the format how my array looks like :
Array
(
[0] => Array
(
[media] => <SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/"><SOAP:Body><hotel:HotelMediaLinksRsp TraceId="trace" TransactionId="69B008C27F00000109CA96A4918F80C6" ResponseTime="77" xmlns:common_v25_0="http://www.travelport.com/schema/common_v25_0" xmlns:hotel="http://www.travelport.com/schema/hotel_v25_0"><hotel:HotelPropertyWithMediaItems><hotel:HotelProperty HotelChain="CP" HotelCode="36588" HotelLocation="DXB" Name="CROWNE PLAZA FESTIVAL CITY" VendorLocationKey="1tTgCxLKRUuwf3+sFMCwFg==" HotelTransportation="Limo" ReserveRequirement="Other" ParticipationLevel="Enhanced Best Available Rate participant" Availability="Available" FeaturedProperty="true" NetTransCommissionInd="C"><hotel:PropertyAddress><hotel:Address>DUBAI-FESTIVAL CITY</hotel:Address></hotel:PropertyAddress></hotel:HotelProperty></hotel:HotelPropertyWithMediaItems></hotel:HotelMediaLinksRsp></SOAP:Body></SOAP:Envelope>
)
)
(Php)
First you need to access that part of your array.
If it is stored as $MyResponse you would access [0] using $MyResponse[0].
I would then turn the XML into an array to make it easier to access like so. See more on SimpleXML here.
$xml = new SimpleXMLElement($MyResponse[0]);
Then you can access the individual elements like an array.
For Example..
echo $xml->a->b->b['name'];
I'm beginning to use simplexml and I don't know what I'm doing wrong .. :(
I want to extract a xml from a url, I'm using the following code:
$xml = new SimpleXMLElement(file_get_contents($url));
And... print_r shows other object SimpleXML inside my array...It is correct? How can I read it?
SimpleXMLElement Object
(
[action] => QUERY
[response] => SUCCESS
[responsedata] => SimpleXMLElement Object
(
)
)
Thanks a lot,
SimpleXMLElement::__construct provides an optional (3rd) argument called data_is_url for cases like yours.
By default, data_is_url is FALSE. Use TRUE to specify that data is a path or URL to an XML document instead of string data.
You should try something like:
$xml = new SimpleXMLElement($url, 0, true);
I have some code that pulls HTML from an external source:
$doc = new DOMDocument();
#$doc->loadHTML($html);
$xml = #simplexml_import_dom($doc); // just to make xpath more simple
$images = $xml->xpath('//img');
$sources = array();
Then, if I add all of the sources with this code:
foreach ($images as $i) {
array_push($sources, $i['src']);
}
echo "<pre>";
print_r($sources);
die();
I get this result:
Array
(
[0] => SimpleXMLElement Object
(
[0] => /images/someimage.gif
)
[1] => SimpleXMLElement Object
(
[0] => /images/en/someother.jpg
)
....
)
But when I use this code:
foreach ($images as $i) {
$sources[] = (string)$i['src'];
}
I get this result (which is what is desired):
Array
(
[0] => /images/someimage.gif
[1] => /images/en/someother.jpg
...
)
What is causing this difference?
What is so different about array_push()?
Thanks,
EDIT: While I realize the answers match what I am asking (I've awarded), I more wanted to know why whether using array_push or other notation adds the SimpleXMLElement Object and not a string when both arent casted. I knew when explicitly casting to a string I'd get a string. See follow up question here:Why aren't these values being added to my array as strings?
The difference is not caused by array_push() -- but by the type-cast you are using in the second case.
In your first loop, you are using :
array_push($sources, $i['src']);
Which means you are adding SimpleXMLElement objects to your array.
While, in the second loop, you are using :
$sources[] = (string)$i['src'];
Which means (thanks to the cast to string), that you are adding strings to your array -- and not SimpleXMLElement objects anymore.
As a reference : relevant section of the manual : Type Casting.
Sorry, just noticed better answers above, but the regex itself is still valid.
Are you trying to get all images in HTML markup?
I know you are using PHP, but you can convert use this C# example of where to go:
List<string> links = new List<string>();
if (!string.IsNullOrEmpty(htmlSource))
{
string regexImgSrc = #"<img[^>]*?src\s*=\s*[""']?([^'"" >]+?)[ '""][^>]*?>";
MatchCollection matchesImgSrc = Regex.Matches(htmlSource, regexImgSrc, RegexOptions.IgnoreCase | RegexOptions.Singleline);
foreach (Match m in matchesImgSrc)
{
string href = m.Groups[1].Value;
links.Add(href);
}
}
In your first example, you should:
array_push($sources, (string) $i['src']);
Your second example gives an array of strings because you are converting the SimpleXMLElements to strings using the (string) cast. In your first example you are not, so you get an array of SimpleXMLElements instead.
I have a multi-dimensional multi-object array from a simplexml_import_dom() function call.
A slice of one section of the array:
[Price] => SimpleXMLElement Object
(
[Prices] => Array
(
[0] => SimpleXMLElement Object
(
[#attributes] => Array
(
[MType] => A
[PType] => R
This is causing me quite a bit of problems when trying to read nested objects. I have tried to loop through the array using multiple get_object_vars() but because the depth and location of the nested objects is continually changing I haven't been able to yield desirable results.
Does PHP contain a function that I haven't been able to find to convert a multi-dimensional multi-object array to a standard multi-dimensional array? Or has anyone solved this problem before?
Thanks for your help!
The question is often asked, but there's a fundamental issue that has to be addressed on a case-by-case basis when converting a XML tree to an array, which makes an one-size-fits-all method impossible: how do you differentiate nodes from attributes?
For instance, how would you convert this XML to an array:
<node val="attr"><val>child</val></node>
Also, a node can have any number of children with the same name, which you can't emulate with an associative array.
Long story short, you'll have to cook up your own solution. Judging from your output, it would look something like this:
$arr = array();
foreach ($Price->Prices as $Prices)
{
$tmp = array();
foreach ($Prices->attributes() as $k => $v)
{
$tmp[$k] = (string) $v;
}
$arr[] = $tmp;
}
If it's not what you're looking for, please edit your question and add an example of the source document (XML) as well as the expected result (the array.)
Are you aware that these objects have functions that you can use? Try the following:
foreach ($simpleXmlObject->children() as $element) {
echo $element->getName();
}