Accessing a specific XML-node with PHP using SimpleXML - php

lately i ran into a problem using simplexml.
What i want to do is to get a value of a nested node that occurs multiple times.
The xml looks somewhat like this:
<response>
<album id="123">
[...]
<duration>
<value format="seconds">2576</value>
<value format="mm:ss">42:56</value>
<value format="hh:mm:ss">00:42:56</value>
<value format="xs:duration">PT42M56S</value>
</duration>
[...]
</album>
</response>
Specifically i need the value of the the <value format="hh:mm:ss"> node.
So I have a reference to the object that looks somewhat like this:
$this->webservice->album->duration->value;
Now, if i var_dump this the result will be:
object(SimpleXMLElement)#117 (5) {
["#attributes"]=> array(1) {
["format"]=> string(7) "seconds"
}
[0]=> string(4) "2576"
[1]=> string(5) "42:56"
[2]=> string(8) "00:42:56"
[3]=> string(8) "PT42M56S"
}
I do not understand this output, since it takes the format-attribute of the first node (seconds) and continues with the node-values in the array, while ignoring the format-attribute of the following nodes completely.
Furthermore, if i do the following:
$this->webservice->album->duration->value[2];
it results in:
object(SimpleXMLElement)#108 (1) {
["#attributes"]=> array(1) {
["format"]=> string(8) "hh:mm:ss"
}
}
where i haven't got a value to address at all.
I tried to use xpath instead too in the following way:
$this->webservice->album->duration->xpath('value[#format="hh:mm:ss"]');
which results in:
array(1) {
[0]=> object(SimpleXMLElement)#116 (1) {
["#attributes"]=> array(1) {
["format"]=> string(8) "hh:mm:ss"
}
}
}
So my question is:
What am I doing wrong? xD
Thanks in advance for any helpful advice :)

Your mistake is in trusting var_dump too completely, rather than trying to use the elements based on the examples in the manual.
On your first attempt, you accessed $duration_node->value; this can be used in a few different ways:
if you iterate over it with foreach($duration_node->value as $value_node), you get each of the <value> elements in turn
you can access specific elements by index, e.g. $duration_node->value[2] for the 3rd element
if you treat it as a single element, SimpleXML assumes you want the first element, i.e. echo $duration_node->value is the same as echo $duration_node->value[0]
Your second example worked fine - it found the <value> element with an attribute format="hh:mm:ss". The xpath() method always returns an array, so you need to check that it's not empty then look at the first element.
Once you have the right element, accessing its text content is as simple as casting it to a string ((string)$foo), or passing it to something that always needs a string (e.g. echo).
So this would work:
$xpath_results = $this->webservice->album->duration->xpath('value[#format="hh:mm:ss"]');
if ( count($xpath_results) != 0 ) {
$value = (string)$xpath_results[0];
}
As would this:
foreach ( $this->webservice->album->duration->value as $value_node ) {
if ( $value_node['format'] == 'hh:mm:ss' ) {
$value = (string)$value_node;
break;
}
}

Related

How do I print Attribute Values of an object(stdClass) in PHP

I'm having a difficult time understanding how to print out an attribute value of an object. The particular example I am working from is this:
object(SimpleXMLElement)#1 (1) {
["links"]=>
object(SimpleXMLElement)#4 (2) {
["#attributes"]=>
array(3) {
["total-matched"]=>
string(2) "31"
["records-returned"]=>
string(2) "10"
["page-number"]=>
string(1) "3"
}
I want to print the value of the links total-matched (which is 31). I've tried this: echo $object->links->total-matched; but I only get the value of 0.
How can I do this?
$object->links->total-matched evaluates as $object->link->total - matched (- is minus, I suppose you should see warning about using unknown constant - turn on error reporting). To access attributes with names like this you can do following: $object->links->{'total-matched'} although in this case, since it's SimpleXML attribute, I think you need to get attributes array:
$attr = $object->links->attributes();
echo $attr['total-matched'];

SimpleXMLElement get non-attribute values

I have the following SimpleXMLElement:
object(SimpleXMLElement)#10 (3) {
["#attributes"]=> array(3) {
["id"]=> string(8) "18022352"
["name"]=> string(14) "The Salmon Man"
["slug"]=> string(14) "the-salmon-man"
}
["bids"]=> object(SimpleXMLElement)#11 (1) {
["price"]=> array(1) {
[0]=> object(SimpleXMLElement)#13 (1) {
["#attributes"]=> array(4) {
["decimal"]=> string(4) "9.60"
["percent"]=> string(5) "10.42"
["backers_stake"]=> string(5) "40.36"
["liability"]=> string(6) "347.00"
}
}
}
}
["offers"]=> object(SimpleXMLElement)#12 (1) {
["price"]=> array(1) {
[0]=> object(SimpleXMLElement)#15 (1) {
["#attributes"]=> array(4) {
["decimal"]=> string(4) "9.20"
["percent"]=> string(5) "10.87"
["backers_stake"]=> string(5) "85.35"
["liability"]=> string(5) "10.41"
}
}
}
}
}
Why does this work:
$horse[0]['name']
but this doesn't:
$horse[0]['bids'] // also tried $horse['bids'] and other ways
I can get the values like below but I was hoping to search the smaller object:
$xml->xpath("//odds/event[#id='$matchid']/market[#slug='to-win']/contract[#name='$pony']/bids"); // $pony == $horse[0]['name']
It's often easier to look at the XML itself, rather than a print_r of the SimpleXML object. In this case, you have something like this:
<horse id="18022352" name="The Salmon Man" slug="the-salmon-man">
<bids>
<price decimal="9.60" percent="10.42" backers_stake="40.36" liability="347.00" />
</bids>
<offers>
<price decimal="9.60" percent="10.42" backers_stake="40.36" liability="347.00" />
</offers>
</horse>
The bids item is an element, and as discussed in the SimpleXML Basic Usage guide, you access child elements with ->foo notation, whereas attributes use ['foo'] notation.
So if $horse is that <horse> element, you need:
$name = $horse['name'];
$bids = $horse->bids;
Note that this is equivalent to explicitly asking for the first child called bids; both forms will work regardless of whether there is actually more than one identically name element:
$bids = $horse->bids[0];
Now there are presumably one or more <price> elements in practice, so you might want to loop over, and then echo each decimal attribute. That would look like this:
foreach ( $horse->bids->price as $price ) {
echo $price['decimal'];
}
Again, this loop will work fine with only one <price>, it will just just loop once. If there is only ever one price, or you only care about the first one, you could just write:
echo $horse->bids->price['decimal'];
Which is equivalent to:
echo $horse->bids[0]->price[0]['decimal'];
Or of course either of these:
echo $horse->bids[0]->price['decimal'];
echo $horse->bids->price[0]['decimal'];
The number of different ways of getting to the same place is one reason I don't recommend relying too much on print_r output. Another is that it sometimes can't display everything that's in the XML, which doesn't mean that data isn't available if you ask for it.

How can I extract the value from this SimpleXml?

How can I extract the value from this SimpleXml? I keep getting an empty array, and I don't know what I'm doing wrong.
I just want to extract the string "Familial GI Stromal Tumor With Loss of Heterozygosity and Amplification of Mutant KIT.".
object(SimpleXMLElement)#13 (2) {
["#attributes"]=>
array(2) {
["Name"]=>
string(5) "Title"
["Type"]=>
string(6) "String"
}
[0]=>
string(86) "Familial GI Stromal Tumor With Loss of Heterozygosity and Amplification of Mutant KIT."
}
SimpleXMLElement has a __toString() method. For the element you showed in your question, you should be able to just echo the string content.
echo $yourElement;
or if you want it in a variable, you can call __toString() explicitly
$someVar = $yourElement->__toString();
or trigger it by treating the element as a string;
$someVar = "$yourElement";
Just convert it into a string.
var_dump((string) $element);
Anyway, it depends on your xpath.
Thanks for the help, everyone. Turns out I figured it out. I simply had to access it from the array.
$title = $pub->xpath('Item[#Name="Title"]')[0];

Accessing CDATA values in a simplexmliterator

I have a simple xml structure:
<?xml version="1.0"?>
<fmxmlsnippet type="FMObjectList">
<Step enable="True" id="141" name="Set Variable">
<Value>
<Calculation><![CDATA[Get(RecordID)]]></Calculation>
</Value>
<Repetition>
<Calculation><![CDATA[1]]></Calculation>
</Repetition>
<Name>$currentRecord</Name>
</Step>
</fmxmlsnippet>
What I'm trying to do is pull out the attribute values for the Step node as well as the CDATA values form the Calculation child node for the Value and Repetition nodes.
I can get the step attribute values without an issue:
$iterator = new SimpleXMLIterator($xml);
for($iterator->rewind() ; $iterator->valid() ; $iterator->next()){
$stepId = $iterator->current()->attributes()->id->__toString();
$stepName = $iterator->current()->attributes()->name->__toString();
$stepEnable= $iterator->current()->attributes()->enable->__toString();
}
But I'm having a problem getting the CDATA. I've tried numerous methods of getting it without success.
I noticed at one point that when I die and var_dump the result of $iterator->current() in my for loop the CDATA doesn't even look like it is recognized:
object(SimpleXMLIterator)#3 (4) {
["#attributes"]=>
array(3) {
["enable"]=>
string(4) "True"
["id"]=>
string(3) "141"
["name"]=>
string(12) "Set Variable"
}
["Value"]=>
object(SimpleXMLIterator)#4 (1) {
["Calculation"]=>
object(SimpleXMLIterator)#6 (0) {
}
}
["Repetition"]=>
object(SimpleXMLIterator)#5 (1) {
["Calculation"]=>
object(SimpleXMLIterator)#6 (0) {
}
}
["Name"]=>
string(14) "$currentRecord"
}
It's like the content of the Calculation nodes is empty.
Is there a way of getting the CDATA?
No, that var_dump() is just misleading, it shows its empty but its it is there. Simply cast it then assign:
for($iterator->rewind() ; $iterator->valid() ; $iterator->next()){
$stepId = $iterator->current()->attributes()->id->__toString();
$stepName = $iterator->current()->attributes()->name->__toString();
$stepEnable= $iterator->current()->attributes()->enable->__toString();
$calculation = (string) $iterator->current()->Value->Calculation;
$repitition = (string) $iterator->current()->Repetition->Calculation;
echo $calculation . '<br/>' . $repitition;
}
Sample Output

Accessing a simplexml element within a simplexml element

I think theres something more going on here but it looks like certain data available in the raw xml is unavailable to me after it gets set in the simplexml element via my soap call.
Here's the raw information I get back from the soap call (retrieved w/ soapUI), Please note the e_mail element in particular.
<GetSubscriberData_ByDrupal_IdResult><![CDATA[
<SubscriberDataRoot>
<SubscriberData>
<city>MEDIA</city>
<state>PA</state>
<zip>19063-4112</zip>
<country>USA</country>
<phone>1231231244</phone>
<e_mail>some_email#somewhere.com</e_mail>
</SubscriberData>
</SubscriberDataRoot>]]>
</GetSubscriberData_ByDrupal_IdResult>
and here is the SimpleXMLElement that I var_dump'd after it was yielded from my SoapClient call.
object(SimpleXMLElement)#26 (1) {
["SubscriberData"]=>
object(SimpleXMLElement)#33 (29) {
["city"]=>
string(5) "MEDIA"
["state"]=>
string(2) "PA"
["zip"]=>
string(10) "19063-4112"
["country"]=>
string(3) "USA"
["phone"]=>
string(10) "1231231234"
["e_mail"]=>
object(SimpleXMLElement)#36 (0) {
}
}
}
I can access most of the data as expected with something akin to $soap_data->SubscriberData->city however notable the e_mail element is not available directly, its another SimpleXMLElement. I've tried iterating over it, using asXML, __toString, casting to a (string)..
(for instance...)
php> var_dump($acct->SubscriberData->e_mail);
object(SimpleXMLElement)#38 (0) {
}
php> var_dump($acct->SubscriberData->e_mail->asXML());
string(17) "<e_mail></e_mail>"
php> var_dump($acct->SubscriberData->e_mail->__toString());
string(0) ""
php> var_dump((string) $acct->SubscriberData->e_mail);
string(0) ""
but I can't access it like any of the other string valued elements. I want to be able to get the string value such as $email = $acct->SubscriberData->e_mail like the rest of the elements. Thanks in advance.
I'm not sure I can test/reproduce your object structure without your soap stuff, but if I took the raw XML and tried to process it, I'd have to process the CDATA block separately.
$GetSubscriberData_ByDrupal_IdResult = simplexml_load_string($xmlSource);
$SubscriberDataRoot = simplexml_load_string((string) $GetSubscriberData_ByDrupal_IdResult);
$email = (string) $SubscriberDataRoot->SubscriberData->e_mail;
echo $email; // assigned to var just for demonstration

Categories