PHP: Get specific value from simplexml array - php

I am pretty new to PHP an XML and hope you can help me with this.
Searching the forum didn't help me yet to find an answer to my specific issue.
I have a PHP page with a simplexml array that looks like the following, just longer:
SimpleXMLElement Object
(
[textID] => Array
(
[0] => SimpleXMLElement Object
(
[textID] => 1
[content] => Text1
)
[1] => SimpleXMLElement Object
(
[textID] => 2
[content] => Text2
)
[2] => SimpleXMLElement Object
(
[textID] => 3
[content] => Text3
)
)
)
Now I am trying to echo out a specific value from this array by referring to its ID which is an integer.
The only way I get this working is the following but this just goes by the order within the array, not by the actual ID:
<?php echo $objTexts->textID[1]->content; ?>
Can someone tell me what I am missing here ?
Thanks, Tim

SimpleXML has no way of knowing that the textID identifies which node is which - it is just another element in the XML.
Based on your sample output, your XML is a little confusing as you have multiple elements called textID which each have a single child, also called textID, which has a different meaning. Nonetheless, what you want to do can be achieved either by looping through all the outer textID elements and testing the value of their inner textID element:
foreach ( $objTexts->textID as $item )
{
if ( $item->textID == '2' )
{
...
}
}
Or, you could use XPath, which is a fairly simple query language for XML, and is supported within SimpleXML in the form of the ->xpath() method. In your case, you want to find a textID node which contains a textID child with a particular value, so the code would look something like this:
// ->xpath always returns a plain PHP array - not a SimpleXML object
$xpath_results = $objTexts->xpath('//textID[textID=2]');
// If you're certain you only want the first result:
echo $xpath_results[0]->content;
// If you might want multiple matches
foreach ( $xpath_results as $item )
{
...
}

Related

PHP XML to Array

EDIT: The reason I used json_decode is to get it from XML to an array.
So here is my dilemma:
I access an API that returns xml, I then convert it to an array as follows:
public function convertXMLtoJSON($xml){
return json_decode(json_encode(simplexml_load_string($xml), true), true);
}
When I have a single item (for example:) [SINGLE ITEM XML]
<LineItem>
<field>hello</field>
</LineItem>
It gets displayed like: [SINGLE ITEM ARRAY]
[LineItem] => Array(
[field] => hello
)
now if I have: [MULTI ITEM XML]
<LineItem>
<field>hello</field>
</LineItem>
<LineItem>
<field>hello part 2</field>
</LineItem>
I get: [MULTI ITEM ARRAY]
[LineItem] => Array(
[0] => Array (
[field] => hello
)
[1] => Array (
[field] => hello part 2
)
)
I need the first example to be in an array, not just by itself because differentiating between the two is just going to be way too time consuming.
As I understand your specific question, you want everything to be formatted like the multi array, even if there is only a single result.
I would handle this by checking the array for a count of 1 after it is returned. Let us suppose that you have an array called $xmlArray that has a single result in the format you describe.
if (count($xmlArray) == 1) {
$multiArray['LineItem'][] = $xmlArray['LineItem'];
}
This will convert the $xmlArray with a single element into the multi-line array format you have described in your question.

Using xPath to access values of simpleXML

I have a XML object result from my database containing settings.
I am trying to access the values for a particular settingName:
SimpleXMLElement Object
(
[settings] => Array
(
[0] => SimpleXMLElement Object
(
[settingName] => Test
[settingDescription] => Testing
[requireValue] => 1
[localeID] => 14
[status] => 1
[value] => 66
[settingID] => 5
)
[1] => SimpleXMLElement Object
(
[settingName] => Home Page Stats
[settingDescription] => Show the Top 5 Teammate / Teamleader stats?
[requireValue] => 0
[localeID] => 14
[status] => 0
[value] => SimpleXMLElement Object
(
)
[settingID] => 3
)
)
)
I tried using xPath and have this so far:
$value = $fetchSettings->xpath("//settingName[text()='Test']/../value");
which returns:
Array ( [0] => SimpleXMLElement Object ( [0] => 66 ) )
How can I get the actual value and not just another array/object?
The end result will just be 66 for the example above.
SimpleXMLElement::xpath() returns a plain PHP array of "search results"; the first result will always be index 0 if any results were found.
Each "search result" is a SimpleXMLElement object, which has a magic __toString() method for getting the direct text content of a node (including CDATA, but including text inside child nodes, etc). The simplest way to call it is with (string)$my_element; (int)$my_element will also invoke it, then convert the result to an integer.
So:
$xpath_results = $fetchSettings->xpath("//settingName[text()='Test']/../value");
if ( count($xpath_results) > 0 ) {
$value = (string)$xpath_results[0];
}
Alternatively, the DOMXPath class can return results other than element and attribute nodes, due to the DOM's richer object model. For instance, you can have an XPath expression ending //text() to refer to the text content of a node, rather than the node itself (SimpleXML will do the search, but give you an element object anyway).
The downside is it's rather more verbose to use, but luckily you can mix and match the two sets of functions (using dom_import_simplexml() and its counterpart) as they have the same underlying representation:
// WARNING: Untested code. Please comment or edit if you find a bug!
$fetchSettings_dom = dom_import_simplexml($fetchSettings);
$xpath = new DOMXPath($fetchSettings_dom->ownerDocument);
$value = $xpath->evaluate(
"//settingName[text()='Test']/../value/text()",
$fetchSettings_dom
);
Because every element in a XML-file can appear as multiple times the parser always returns an array. If you are sure, that it is only a single item you can use current()
echo (string) current($value);
Note, that I cast the SimpleXMLElement to a string (see http://php.net/manual/simplexmlelement.tostring.php ) to get the actual value.
Use DomXPath class instead.
http://php.net/manual/en/domxpath.evaluate.php
The sample from php.net is just equivalent what you'd like to achieve:
<?php
$doc = new DOMDocument;
$doc->load('book.xml');
$xpath = new DOMXPath($doc);
$tbody = $doc->getElementsByTagName('tbody')->item(0);
// our query is relative to the tbody node
$query = 'count(row/entry[. = "en"])';
$entries = $xpath->evaluate($query, $tbody);
echo "There are $entries english books\n";
In this way, you can get values straight from the XML.

XML array to comma separated list

I have an array list of tags from xml file and im using xml file to add some posts to wordpress. So i need to convert xml array to comma separated list for tags to import in my posts.
This is an xml file example
<tags>
<tag>tag1</tag>
<tag>tag2</tag>
<tag>tag3</tag>
</tags>
So when i call this file in my .php file and use print_r to get the output i get this
SimpleXMLElement Object (
[tag] => Array (
[0] => SimpleXMLElement Object ( )
[1] => SimpleXMLElement Object ( )
[2] => SimpleXMLElement Object ( )
[3] => SimpleXMLElement Object ( )
)
)
So 0 1 2 3 are tags and values are stored in SimpleXMLElement Object ( ) i read it's normal to not get values when using print_r but i know the values are there.
Now i need to convert this as a list at the end to get this result
$post_tags = tag1, tag2, tag3;
so that i can use $post_tags in my function.
Assuming your object (the one on which you called print_r() is $xml), you would loop over $xml->tag and append each child object's contents to an array. Finally, implode the array to a string.
// An array to hold it temporarily
$post_tags_arr = array();
foreach ($xml->tag as $t) {
// Cast it to a string to get the text value back
$post_tags_arr[] = (string)$t;
}
// Implode the array to a string
$post_tags = implode(', ', $post_tags_arr);
echo $post_tags;
You indicate that you need a list for $post_tags, but your syntax above is ambiguous as to whether you wanted a string or an array. If you wanted an array, you have it in $post_tags_arr prior to the implode().
If you feel like being clever, and the <tag> nodes reside at the same level and have no children, you can simply cast them as a regular array, which will result in their string values.
// Cast it to an array in one go:
$post_tags_arr = (array)$xml->tag;
print_r($post_tags_arr);
Array
(
[0] => tag1
[1] => tag2
[2] => tag3
)

efficent way to drill down through xml feed multiple times

Ive found myself drilling down through xml feeds quite allot, they are almost identical feeds apart from a couple of array names.
Ideally id want to make a sort of function that i can call, but i have no idea how to do it with this sort of data
//DRILLING DOWN TO THE PRICE ATTRIBUTE FOR EACH FEED & MAKING IT A WORKING VAR
$wh_odds = $wh_xml->response->will->class->type->market->participant;
$wh_odds_attrib = $wh_odds->attributes();
$wh_odds_attrib['odds'];
$lad_odds = $lad_xml->response->lad->class->type->market->participant;
$lad_odds_attrib = $lad_odds->attributes();
$lad_odds_attrib['odds'];
as you can see they are essentially the very similar, but im not quite sure how i can streamline the process of setting a working var without writing 3 lines each time.
The function you're probably looking for is called SimpleXMLElement::xpath().
XPath is a language of it's own designed to pick stuff out of XML files. In your case, the xpath expression to get the odds attribute of all these elements is:
response/*/class/type/market/participant/#odds
You can also replace the * with the concrete element name or allow multiple names there and what not.
$odds = $lad_xml->xpath('response/*/class/type/market/participant/#odds');
Different to your code, this has all attribute elements inside the array (you have the attribute's parent element inside the variable). An example outcome (considering two of such elements) would be:
Array
(
[0] => SimpleXMLElement Object
(
[#attributes] => Array
(
[odds] => a
)
)
[1] => SimpleXMLElement Object
(
[#attributes] => Array
(
[odds] => a
)
)
)
You can turn that into strings as well easily:
$odds_strings = array_map('strval', $odds);
print_r($odds_strings);
Array
(
[0] => a
[1] => a
)
Xpath is especially useful if you say, you want to get all participant element's odds attributes:
//participant/#odds
You don't need to explicitly specify each of the parent element names.
I hope this is helpful.
You can do it like this:
function getAttrib ($xmlObj, $attrName) {
$wh_odds = $xmlObj->response->$attrName->class->type->market->participant;
$wh_odds_attrib = $wh_odds->attributes();
return $wh_odds_attrib['odds'];
}
getAttrib ($wh_xml, "will");
hope that helps.

Can I use SimpleXML & Xpath to directly select an Elements Attribute?

i.e. - i want to return a string "yellow" using something like xpath expression "//banana/#color" and the following example xml...
<fruits>
<kiwi color="green" texture="hairy"/>
<banana color="yellow" texture="waxy"/>
</fruits>
$fruits = simplexml_load_string(
'<fruits>
<kiwi color="green" texture="hairy"/>
<banana color="yellow" texture="waxy"/>
</fruits>');
print_r($fruits->xpath('//banana/#color'));
produces
Array
(
[0] => SimpleXMLElement Object
(
[#attributes] => Array
(
[color] => yellow
)
)
)
whereas i would prefer something like...
Array
(
[0] => SimpleXMLElement Object
(
[0] => yellow
)
)
...so that i don't need to write a special case into the application i'm writing.
thanks very much! :)
I just gave your test a shot because i was curious and I found that it does actually produce the string value yellow when converted to string.
$fruits = simplexml_load_string(
'<fruits>
<kiwi color="green" texture="hairy"/>
<banana color="yellow" texture="waxy"/>
</fruits>');
$found = $fruits->xpath('//banana/#color');
echo $found[0];
It would seem this is just how SimpleXmlElement attribute nodes are represented. So you can use this as (string) $found[0] if you are not printing/echoing it directly.
Of course if your depending on the value remaining a SimpleXMLElement then that could be an issue I suppose. But i would think just remembering to cast as string when you go to use the node later would still be doable.
IF you really need a detailed interface for Nodes that supports an Attribute as a node then you may want to just switch to DOMDocument. You code will get more verbose, but the implementation is more clear.

Categories