xml xpath does not return node value - php

I have a test file where I'm trying to parse an xml string using SimpleXML's xpath method.
When I try to access a nodes values directly using xpath I get empty output, but when I use xpath to grab the elements and then loop through them it works fine.
When I look at the documentation, it seems like my syntax should work. Is there something I'm missing?
<?php
$xmlstring = '<?xml version="1.0" encoding="iso-8859-1"?>
<users>
<user>
<firstname>Sheila</firstname>
<surname>Green</surname>
<address>2 Good St</address>
<city>Campbelltown</city>
<country>Australia</country>
<contact>
<phone type="mobile">1234 1234</phone>
<url>http://example.com</url>
<email>pamela#example.com</email>
</contact>
</user>
<user>
<firstname>Bruce</firstname>
<surname>Smith</surname>
<address>1 Yakka St</address>
<city>Meekatharra</city>
<country>Australia</country>
<contact>
<phone type="landline">4444 4444</phone>
<url>http://yakka.example.com</url>
<email>bruce#yakka.example.com</email>
</contact>
</user>
</users>';
// Start parsing
if(!$xml = simplexml_load_string($xmlstring)){
echo "Error loading string ";
} else {
echo "<pre>";
// Print all firstname values directly from xpath
// This outputs the elements, but the values are blank
print_r($xml->xpath("/users/user/firstname"));
// Set a variable with all of the user elements and then loop through and print firstname values
// This DOES output the values
$users = $xml->xpath("/users/user");
foreach($users as $user){
echo $user->firstname;
}
// Find all firstname values by tag
// This does not output the values
print_r($xml->xpath("//firstname"));
echo "</pre>";
}

As per the manual http://uk1.php.net/manual/en/simplexmlelement.xpath.php
The xpath method searches the SimpleXML node for children matching the XPath path.
In your first and third examples, you are being returned objects containing an array of the node's value, rather than the node itself. So you aren't going to be able to do e.g.
$results = $xml->xpath("//firstname");
foreach ($results as $result) {
echo $result->firstname;
}
Instead you can just echo out the value directly. Well, almost directly (they are still simplexml objects after all)...
$results = $xml->xpath("//firstname");
foreach ($results as $result) {
echo $result->__toString();
}

Related

Setting Parent Node Variable in SimpleXML and XPath

I am working with PHP and SimpleXML/XPath, and I'm just wondering how to set a certain parent (with a certain attribute value) equal to a variable, which I could use in a 'foreach'?
I'm currently getting this error:
Notice: Array to string conversion
and this output
Array
Thanks for any leads.
Here is the php code:
<?php
$url = "test_b.xml";
$xml = simplexml_load_file($url);
$xml_report_abbrev_b = $xml->xpath('//poster[#name="U-Verify"]')[0];
if($xml_report_abbrev_b){
foreach($xml_report_abbrev_b as $node_a) {
echo '<h1>'.$node_a->xpath('/full_image/#url').'</h1>';
}
} else {
echo 'XPath query failed';
}
?>
Here's the xml:
<data>
<poster name="U-Verify" id="uverify">
<full_image url="u-verify.jpg"/>
<full_other url=""/>
</poster>
<poster name="Minimum" id="min">
<full_image url="min.jpg"/>
<full_other url="spa_min.jpg"/>
</poster>
</data>
Using SimpleXML's element access and attribute access directly instead of using the XPath query will make the code simpler and perform better.
Your code could be reduced to...
$xml_report_abbrev_b = $xml->xpath('//poster[#name="U-Verify"]');
if($xml_report_abbrev_b){
echo '<h1>'.$xml_report_abbrev_b[0]->full_image['url'].'</h1>';
} else {
echo 'XPath query failed';
}
Note the way the echo line says - with the <poster> element you found from the XPath expression, use the <full_image> element and fetch the url attribute.
I also moved the [0] into the if because if the XPath didn't find a value, this produced an error as there isn't any data to get a value from.
This outputs...
<h1>u-verify.jpg</h1>

Parsing XML API data result from Plesk

I try to get domain name from Plesk API result XML data as below:
<packet version="1.6.7.0">
<site-alias>
<get>
<result>
<status>ok</status>
<info>
<name>example.com</name>
</info>
</result>
<result>
<status>ok</status>
<info>
<name>domain.net</name>
</info>
</result>
</get>
</site-alias>
</packet>
using
$xml= simplexml_load_string($response);
echo $xml['site-alias']['get']['result'][0]['name'];
if there is more better way to do so, please advice, thanks.
There are multiple ways to get the values using SimpleXMLElement.
To get only 1 value, you could for example do it like this:
// Get 1 using SimpleXMLElement
echo $xml->{"site-alias"}->get->result->info->name;
// Get 1 using xpath
echo $xml->xpath('/packet/site-alias/get/result[1]/info/name')[0];
To get multiple values you could do it like this:
// Get multiple using SimpleXMLElement
$items = $xml->{"site-alias"}->get->result;
foreach ($items as $item) {
echo $item->info->name . "<br>";
}
// Get multiple using xpath
$items = $xml->xpath('/packet/site-alias/get/result/info/name');
foreach ($items as $item) {
echo $item . "<br>";
}
The SimpleXMLElement has a method __toString so you can echo the string content.
The xpath method returns an array, so you can get the first value using index 0.
A short way to get your value could be:
echo $xml->xpath('//name')[0];
Demo

SimpleXML: trouble with parent with attributes

Need help with updating some simplexml code I did along time ago. The XML file I'm parsing from is formatted in a new way, but I can't figure out how to navigate it.
Example of old XML format:
<?xml version="1.0" encoding="UTF-8"?>
<pf version="1.0">
<pinfo>
<pid><![CDATA[test1 pid]]></pid>
<picture><![CDATA[http://test1.image]]></picture>
</pinfo>
<pinfo>
<pid><![CDATA[test2 pid]]></pid>
<picture><![CDATA[http://test2.image]]></picture>
</pinfo>
</pf>
and then the new XML format (note "category name" added):
<?xml version="1.0" encoding="UTF-8"?>
<pf version="1.2">
<category name="Cname1">
<pinfo>
<pid><![CDATA[test1 pid]]></pid>
<picture><![CDATA[http://test1.image]]></picture>
</pinfo>
</category>
<category name="Cname2">
<pinfo>
<pid><![CDATA[test2 pid]]></pid>
<picture><![CDATA[http://test2.image]]></picture>
</pinfo>
</category>
</pf>
And below the old code for parsing that doesn't work since the addition of "category name" in the XML:
$pinfo = new SimpleXMLElement($_SERVER['DOCUMENT_ROOT'].'/xml/file.xml', null, true);
foreach($pinfo as $resource)
{
$Profile_id = $resource->pid;
$Image_url = $resource->picture;
// and then some echo´ing of the collected data inside the loop
}
What do I need to add or do completely different? I tried with xpath,children and sorting by attributes but no luck - SimpleXML has always been a mystery to me :)
You were iterating over all <pinfo> elements located in the root element previously:
foreach ($pinfo as $resource)
Now all <pinfo> elements have moved from the root element into the <category> elements. You now need to query those elements first:
foreach ($pinfo->xpath('/*/category/pinfo') as $resource)
The now wrong named variable $pinfo is standing a bit in the way so it better do some more changes:
$xml = new SimpleXMLElement($_SERVER['DOCUMENT_ROOT'].'/xml/file.xml', null, true);
$pinfos = $xml->xpath('/*/category/pinfo');
foreach ($pinfos as $pinfo) {
$Profile_id = $pinfo->pid;
$Image_url = $pinfo->picture;
// ... and then some echo´ing of the collected data inside the loop
}
The category elements exist as their own array when you load the XML file. The XML you are used to parsing is contained within. All you need to do is wrap your current code with another foreach. Other than that there isn't much to change.
foreach($pinfo as $category)
{
foreach($category as $resource)
{
$Profile_id = $resource->pid;
$Image_url = $resource->picture;
// and then some echo´ing of the collected data inside the loop
}
}

How can I normalize/check a json object output it can run through a foreach loop (php)

Maybe I just need to get some sleep, but I cannot figure this one out :( ... My problem is that the json output I have changes when it contains a single order vs multiple orders.
Here is an example json output of a single order:
{"Ack":"Success","OrderArray":{"Order":{"OrderID":"165921181012"}},"OrdersPerPage":"10","PageNumber":"1","ReturnedOrderCountActual":"1"}
Here is an example json output of multiple orders:
{"Ack":"Success","OrderArray":{"Order":[{"OrderID":"165921181012","OrderStatus":"Completed"},{"OrderID":"151330738592-1109250612005","OrderStatus":"Completed"},{"OrderID":"380931137567-501668037025","OrderStatus":"Completed"}]},"OrdersPerPage":"10","PageNumber":"1","ReturnedOrderCountActual":"3"}
The difference being the [ ]
Right now my code is as follows:
$json_o = json_decode($json);
foreach ($json_o->OrderArray->Order as $orderkey=>$o) { echo $o->OrderID; }
My first thought was to write an if statement to handle single orders Edit**, I can detect the difference between the single orders vs multiple orders using is_array / is_object. But, is there a way to add the [ ] (force a single element array) around json "OrderID" so that the foreach loop would run even if only one order exists?
If you are also generating the JSON yourself, normalise the format at that point. 'Order' should always be an array of orders, even if it only contains a single element. That, or set some other flag which signifies whether it contains only a single order or multiple orders.
If you're only consuming the JSON and have no choice:
if (!isset($json_o->OrderArray[0])) {
$json_o->OrderArray = array($json_o->OrderArray);
}
foreach ($json_O->OrderArray as $order) ...
So I found another question that dealt with a similar issue. php-convert-xml-to-json-group-when-there-is-one-child With this you can customize the json encode and add brackets where it is needed. The example below adds the array around the "status" element.
<?php
/**
* PHP convert XML to JSON group when there is one child
* #link https://stackoverflow.com/q/16935560/367456
*/
$bufferXml = <<<STRING
<?xml version="1.0" encoding="UTF-8"?>
<searchResult>
<status>
<userName1>johndoe</userName1>
</status>
<users>
<user>
<userName>johndoe</userName>
</user>
<user>
<userName>johndoe1</userName>
<fullName>John Doe</fullName>
</user>
<user>
<userName>johndoe2</userName>
</user>
<user>
<userName>johndoe3</userName>
<fullName>John Doe Mother</fullName>
</user>
<user>
<userName>johndoe4</userName>
</user>
</users>
</searchResult>
STRING;
class XML2JsonSearchResult extends SimpleXMLElement implements JsonSerializable
{
public function jsonSerialize()
{
$name = $this->getName();
if ($name == 'status') {
$value = (array)$this;
return [$value];
}
return $this;
}
}
$converter = new XML2JsonSearchResult($bufferXml);
echo "<pre>";
echo json_encode($converter, JSON_PRETTY_PRINT);
echo "</pre>";

simplexml, returning multiple items with the same tag

I have the following XML file loaded into php simplexml.
<adf>
<prospect>
<customer>
<name part="first">Bob</name>
<name part="last">Smith</name>
</customer>
</prospect>
</adf>
using
$customers = new SimpleXMLElement($xmlstring);
This will return "Bob" but how do I return the last name?
echo $customers->prospect[0]->customer->contact->name;
You can access the different <name> elements by number, using array-style syntax.
$names = $customers->prospect[0]->customer->name;
echo $names[0]; // Bob
echo $names[1]; // Smith
In fact, you're already doing it for the <prospect> element!
See also Basic SimpleXML Usage in the manual.
If you want to select elements based on some criteria, then XPath is the tool to use.
$customer = $customers->prospect[0]->customer;
$last_names = $customer->xpath('name[#part="last"]'); // always returns an array
echo $last_names[0]; // Smith

Categories