xpath: getting child node by searching parent attribute - php

I have a KML file for google maps that I want to edit. Here's a stripped-down version:
<?xml version='1.0' encoding='UTF-8'?>
<kml xmlns='http://www.opengis.net/kml/2.2'>
<Document>
<Placemark>
<name>00</name>
<snippet></snippet>
<description><![CDATA[]]></description>
<styleUrl>#Style0-polygon-3-map</styleUrl>
<ExtendedData>
<Data name='Two-Digit Districts'>
<value>AK-00</value>
</Data>
<Data name='Standard Districts'>
<value>AK-AL</value>
</Data>
<Data name='At Large as District 1'>
<value>AK-1</value>
</Data>
<Data name='Full District Name'>
<value>Alaska At Large</value>
</Data>
<Data name=''>
<value>AK</value>
</Data>
</ExtendedData>
</PlaceMark>
</Document>
</kml>
In my php code, I'm digging into each placemark node using SimpleXML:
foreach ($kml->Document->Placemark as $placemark) {
$extendeddata = $placemark->ExtendedData;
}
I need to get the <Data> node where the name attribute matches "At Large as District 1". In the XML tools plugin for NPP, when I take just the <ExtendedData>...</ExtendedData> node, this query returns the node I want: //Data[#name='At Large as District 1']. However, when I try that same query in PHP:
$targetnode = $extendeddata->xpath("//Data[#name='At Large as District 1']")
I get an empty array. I don't understand why what should be a perfectly valid xpath query isn't returning any information. Am I missing something?

There is an ending tag mismatch.
Replace </PlaceMark> with </Placemark>
Code
You have to register with a namespace in order to do an xpath in this case.
$extendeddata = simplexml_load_string( $str );
$extendeddata->registerXPathNamespace( 'ns', 'http://www.opengis.net/kml/2.2' );
$targetnode = $extendeddata->xpath("//ns:Data[#name='At Large as District 1']");
Output
Array
(
[0] => SimpleXMLElement Object
(
[#attributes] => Array
(
[name] => At Large as District 1
)
[value] => AK-1
)
)
Hope this helps.

Related

Search for and replace values in a KML file

I'm very new to php so go easy on me.
I'm trying to search through a KML file for latitude and longitude values, then replace them with user inputted lat/long values. The problem I'm having is actually searching through the KML file to find the specific lat/long values to be replaced.
KML:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<Folder>
<name>Folder-Name</name>
<open>1</open>
<gx:Tour>
<name class="name">Tour-Name</name>
<gx:Playlist>
<gx:FlyTo>
<LookAt>
<gx:horizFov>100</gx:horizFov>
<longitude class="lookat-long">33.33333</longitude>
<latitude class="lookat-lat">-111.11111</latitude>
<altitude>0</altitude>
<heading>0</heading>
<tilt>60</tilt>
<range>100</range>
<altitudeMode>relativeToGround</altitudeMode>
</LookAt>
</gx:FlyTo>
</gx:Playlist>
</gx:Tour>
</Folder>
</kml>
I need to replace "33.33333" and "-111.11111" with user inputted values. I have tried using SimpleXML, but it does not recognize the gx: part of the tag, since that is KML specific, and not part of XML. So when I try this code:
<?php
$xml = simplexml_load_file('my_kml_file');
print_r($xml)
?>
I get this output:
SimpleXMLElement Object ( [Folder] => SimpleXMLElement Object ( [name] => Temporary Places [open] => 0 ) )
It just stops at <open> because it doesn't recognize the rest. I have spent hours upon hours trying to figure out how to best do this and I just can't. Please help.
You can use the xpath method to get the namespaced node like so:
$xml = simplexml_load_file('my_kml_file');
$xml->xpath('//gx:Tour/gx:Playlist/gx:FlyTo')[0]->LookAt->longitude = 'newValue';
$xml->xpath('//gx:Tour/gx:Playlist/gx:FlyTo')[0]->LookAt->latitude = 'newValue';
print_r($xml->asXml());
Output:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<Folder>
<name>Folder-Name</name>
<open>1</open>
<gx:Tour>
<name class="name">Tour-Name</name>
<gx:Playlist>
<gx:FlyTo>
<LookAt>
<gx:horizFov>100</gx:horizFov>
<longitude class="lookat-long">newValue</longitude>
<latitude class="lookat-lat">newValue</latitude>
<altitude>0</altitude>
<heading>0</heading>
<tilt>60</tilt>
<range>100</range>
<altitudeMode>relativeToGround</altitudeMode>
</LookAt>
</gx:FlyTo>
</gx:Playlist>
</gx:Tour>
</Folder>
</kml>

Display data within XML tags in PHP

The XML that's received is below. How can I get the values ('AI', '3', '20.78'...) and display them in PHP?
The values are always returned in that order but the length can vary.
<?xml version="1.0" encoding="UTF-8"?>
<Data>
<Item Type="AI" Chan="3" Value="20.78" Manual="OFF" Min="0.00" Max="100.00" Units="degC" Name="Workshop Temp" />
</Data>
Any ideas would be much appreciated!
You might find the documentation of SimpleXMLElement::attributes useful,
SimpleXMLElement::attributes — Identifies an element's attributes
Return Values
Returns a SimpleXMLElement object that can be iterated over to loop through the attributes on the tag.
Returns NULL if called on a SimpleXMLElement object that already represents an attribute and not a tag.
here is how you should use it:
$str = <<< XML
<?xml version="1.0" encoding="UTF-8"?>
<Data>
<Item Type="AI" Chan="3" Value="20.78" Manual="OFF" Min="0.00" Max="100.00" Units="degC" Name="Workshop Temp" />
</Data>
XML;
$xml = simplexml_load_string($str);
foreach($xml->Item[0]->attributes() as $key => $att) {
echo $att."\n";
}

Losing tags and data when parsing XML string with Simple XML

I have run into a minor problem. I am using Zoho CRM API and it returns me an XML in a format like this:
<response uri="/crm/private/xml/Contacts/getRecords">
<result>
<Contacts>
<row no="1">
<FL val="Contact Owner">
<![CDATA[ Kristo Vaher ]]>
</FL>
<FL val="Lead Source">
<![CDATA[ Partner ]]>
</FL>
</row>
</Contacts>
</result>
</response>
When I create an XML object through simplexml_load_string() then it will give me most of that XML in the new object, but it won't give me the 'inner' string of FL tags (the CDATA elements), the data that actually interests me.
My new SimpleXML object only has data like:
[1] => SimpleXMLElement Object
(
[#attributes] => Array
(
[val] => Contact Owner
)
)
My best guess is that this is because the XML should not really be built this way, I've read somewhere that your cannot have inner content in XML tag if it has attributes and vice versa (is this correct?).
What are my alternatives? Writing a parser myself is not really an option.
Thanks!
To get attributes:
foreach ($value->attributes() as $key => $val){
// get all attributes
}
To get data:
echo (string) $load->result->Contacts->row->FL[0];

PHP & XML Read element by attribute value

$xml = '<?xml version="1.0"?>
<entries>
<response>
<category>client</category>
<action>Greeting</action>
<code>1000</code>
<msg>Your Connection with API Server is Successful</msg>
<resData>
<data name="svDate">2010-10-10 02:27:14</data>
</resData>
</response>
<response>
<category>client</category>
<action>Login</action>
<code>1000</code>
<msg>Command completed successfully</msg>
<value>L116:no value</value>
</response>
<response>
<category>domain</category>
<action>InfoDomain</action>
<code>1000</code>
<msg>Command completed successfully</msg>
<value>L125:no value</value>
<resData>
<data name="domain">google.com</data>
<data name="crDate">2004-12-16</data>
<data name="exDate">2013-12-16</data>
</resData>
</response>
</entries>';
$xml = simplexml_load_string($xml);
$domain = $xml->response[2]->resData[0]->data[0];
$crdate = $xml->response[2]->resData[0]->data[1];
$exdate = $xml->response[2]->resData[0]->data[2];
With the above code i can get the values.
But how can i get the values by attribute value?
For example i want to get the values with something like this:
$domain = $xml->response[2]->resData[0]->data["domain"];
$crdate = $xml->response[2]->resData[0]->data["crdate"];
$exdate = $xml->response[2]->resData[0]->data["exdate"];
One more question.
If i have two elements with the same name?
For example i would like to parse the dns. How could i do it?
The xml code is like this:
<?xml version="1.0"?>
<entries>
<response>
<category>client</category>
<action>Greeting</action>
<code>1000</code>
<msg>Your Connection with API Server is Successful</msg>
<resData>
<data name="svDate">2010-10-10 02:27:14</data>
</resData>
</response>
<response>
<category>client</category>
<action>Login</action>
<code>1000</code>
<msg>Command completed successfully</msg>
<value>L116:no value</value>
</response>
<response>
<category>domain</category>
<action>InfoDomain</action>
<code>1000</code>
<msg>Command completed successfully</msg>
<value>L125:no value</value>
<resData>
<data name="domain">google.com</data>
<data name="crDate">2004-12-16</data>
<data name="exDate">2013-12-16</data>
<data name="dns">ns1.google.com</data>
<data name="dns">ns2.google.com</data>
</resData>
</response>
</entries>
As you can see the ns1 and ns2 have the same name. name="dns".
How can i parse each one in a different variable?
Thank you!
With the element["attribute"] syntax, attribute is the name of an attribute on the element. It is not the value of some randomly chosen attribute belonging to an element.
The example below creates an array containing a mapping for the data elements of name attribute value to text value.
$data = array();
foreach ($xml->response[2]->resData->data as $d) {
$data[strtolower($d['name'])] = (string) $d;
}
// Now you can access the values via $data['domain'], $data['crdate'], etc.
Nick, your code expects XML structured like:
<resData>
<data domain="google.com" crdate="2004-12-16" exdate="2013-12-16" />
</resData>
Edit due to question change
In a marvelous dose of eating my own words, due to the change in the question an XPath approach would be more appropriate (don't you love OPs who do that?).
You can easily get an array of the name="dns" elements with a basic XPath expression.
$xml = simplexml_load_string($xml);
$dns = $xml->xpath('response[category="domain"]/resData/data[#name="dns"]');
You can use XPath to to do queries against your XML, e.g.
$entries = simplexml_load_string($xml);
$dataElements = $entries->xpath('/entries/response/resData/data[#name="dns"]');
foreach ($dataElements as $dataElement) {
echo $dataElement;
}
The above XPath finds all <data> elements with a name attribute of "dns" that are direct children of the given element hierarchy, e.g.
<entries>
…
<response>
…
<resData>
…
<data name="dns">
IMO this is easier and more appropriate than having the extra step of copying over the values into an array which would disconnect it from the actual DOM tree and which you would have to repeat for all the elements you want to map this way. XPath is built-in. You just query and get the result.
Because $dataElements is an array, you can also access the elements in it individually with
echo $dataElements[0]; // prints "ns1.google.com"
echo $dataElements[1]; // prints "ns2.google.com"
Note that the $dataElements array actually contains SimpleXmlElements connected to the main document. Any changes you do to them will also be reflected in the main document.

Getting XML attributes with PHP

Hey guys, I have looked all around for help but found nothing sadly. I am trying to use PHP to grab the contents of an XML file and pull the data from an attribute. I have checked other tutorials and tried but none worked.
Here is the XML file:
<eveapi version="2">
<currentTime>2011-04-03 03:55:59</currentTime>
<result>
<rowset name="notifications" key="notificationID" columns="notificationID,typeID,senderID,sentDate,read">
<row notificationID="339040500" typeID="75" senderID="1000137" sentDate="2011-04-03 03:53:00" read="0" />
</rowset>
</result>
<cachedUntil>2011-04-03 04:25:59</cachedUntil>
</eveapi>
I am trying to pull the data from 'typeID' & 'sentDate'. Also, this XML file may containt multiple tags.
All help is welcomed, thanks!
You can use the following code :-
$xmlstr = '<eveapi version="2">
<currentTime>2011-04-03 03:55:59</currentTime>
<result>
<rowset name="notifications" key="notificationID" columns="notificationID,typeID,senderID,sentDate,read">
<row notificationID="339040500" typeID="75" senderID="1000137" sentDate="2011-04-03 03:53:00" read="0" />
</rowset>
</result>
<cachedUntil>2011-04-03 04:25:59</cachedUntil>
</eveapi>
';
$xml = simplexml_load_string($xmlstr);
print_r ($xml->result->rowset->row['typeID']);
Note: If you have multiple row in rowset then in object, the row will be as collection of array. In that case you have to access the typeID like bellow -
print_r ($xml->result->rowset->row[0]['typeID']);
Putting flame retardant suit on...
$string = '<eveapi version="2">
<currentTime>2011-04-03 03:55:59</currentTime>
<result>
<rowset name="notifications" key="notificationID" columns="notificationID,typeID,senderID,sentDate,read">
<row notificationID="339040500" typeID="75" senderID="1000137" sentDate="2011-04-03 03:53:00" read="0" />
</rowset>
</result>
<cachedUntil>2011-04-03 04:25:59</cachedUntil>
</eveapi>';
preg_match_all('#([\S]+)="(.*?)"#is', $string, $matches);
unset($matches[0]);
$xml = array_combine($matches[1], $matches[2]);
print_r($xml);
=
Array
(
[version] => 2
[name] => notifications
[key] => notificationID
[columns] => notificationID,typeID,senderID,sentDate,read
[notificationID] => 339040500
[typeID] => 75
[senderID] => 1000137
[sentDate] => 2011-04-03 03:53:00
[read] => 0
)

Categories