PHP outputing nested childnodes - php

I am trying to output a xml file to an array thats then outputted to screen. The xml file loads I know it loads because as I can output entry > Id but I can not access its child nodes. I need the data located in.
content > s:organisationSummay
content > s:organisationSummay > s:address
content > s:organisationSummay > s:geographicCoordinates
how would I access the the data located in s:organisationSummay ,s:address, s:geographicCoordinates so I can getElementsByTagName for each items in that child node.
$doc2 = new DOMDocument();
$url = 'http://v1.syndication.nhschoices.nhs.uk/organisations/'.$_POST['ServiceType'].'/postcode/'.$_POST['PostCode'].'.xml?apikey=??&range=50';
echo $url;
$doc2->load($url);
$arrFeeds = array();
foreach ($doc2->getElementsByTagName('entry') as $node)
{
echo $node->getElementsByTagName($content->'s:name');
$itemRSS = array (
'PracticeName' => $organisationSummary->getElementsByTagName('s:name')->item(0)->nodeValue
);
array_push($arrFeeds, $itemRSS);
}
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns:s="http://syndication.nhschoices.nhs.uk/services" xmlns="http://www.w3.org/2005/Atom">
<title type="text">NHS Choices - GP Practices Near Postcode - ls1- Within 50km</title>
<id>http://v1.syndication.nhschoices.nhs.uk/organisations/gppractices/postcode/bd164jt?range=50</id>
<rights type="text">© Crown Copyright 2009</rights>
<updated>2012-07-06T10:24:46+01:00</updated>
<category term="Search"/>
<logo>http://www.nhs.uk/nhscwebservices/documents/logo1.jpg</logo>
<author>
<name>NHS Choices</name>
<uri>http://www.nhs.uk</uri>
<email>webservices#nhschoices.nhs.uk</email>
</author>
<link rel="self" type="application/xml" title="NHS Choices - GP Practices Near Postcode - ;ls1 - Within 50km" href="http://v1.syndication.nhschoices.nhs.uk/organisations/gppractices/postcode/ls1?apikey=??&range=50"/>
<link rel="first" type="application/xml" title="first" length="1000" href="http://v1.syndication.nhschoices.nhs.uk/organisations/gppractices/postcode/ls1?apikey=??&range=50&page=1"/>
<link rel="next" type="application/xml" title="next" length="1000" href="http://v1.syndication.nhschoices.nhs.uk/organisations/gppractices/postcode/Ls1?apikey=??&range=50&page=2"/>
<link rel="last" type="application/xml" title="last" length="1000" href="http://v1.syndication.nhschoices.nhs.uk/organisations/gppractices/postcode/LS1?apikey=??&range=50&page=10"/>
<link rel="alternate" title="NHS Choices - Find and choose services - GP Practices" href="http://www.nhs.uk/ServiceDirectories/pages/ServiceSearch.aspx?ServiceType=GP"/>
<s:SearchCoords>439300,411100</s:SearchCoords>
<entry>
<id>http://v1.syndication.nhschoices.nhs.uk/organisations/gppractices/1</id>
<title type="text">Medical Practice</title>
<updated>2012-07-06T09:24:46Z</updated>
<link rel="self" title="Medical Practice" href="http://v1.syndication.nhschoices.nhs.uk/organisations/gppractices/1?apikey=??"/>
<link rel="alternate" title="Medical Practice" href="http://www.nhs.uk/ServiceDirectories/Pages/GP.aspx?pid=1"/>
<content type="application/xml">
<s:organisationSummary>
<s:name>Medical Practice</s:name>
<s:address>
<s:addressLine>Health Care Centre</s:addressLine>
<s:addressLine>2</s:addressLine>
<s:addressLine>Town</s:addressLine>
<s:addressLine>Yorkshire</s:addressLine>
<s:postcode>?</s:postcode>
</s:address>
<s:contact type="General">
<s:telephone>5558383</s:telephone>
</s:contact>
<s:geographicCoordinates>
<s:northing>438880</s:northing>
<s:easting>411444</s:easting>
<s:longitude>-1.82821202227791</s:longitude>
<s:latitude>53.996218047559</s:latitude>
</s:geographicCoordinates>
<s:Distance>0.5</s:Distance>
</s:organisationSummary>
</content>
</entry>
<entry>
<id>http://v1.syndication.nhschoices.nhs.uk/organisations/gppractices/2</id>
<title type="text">Surgery</title>
<updated>2012-07-06T09:24:46Z</updated>
<link rel="self" title="Surgery" href="http://v1.syndication.nhschoices.nhs.uk/organisations/gppractices/1?apikey=??"/>
<link rel="alternate" title="Surgery" href="http://www.nhs.uk/ServiceDirectories/Pages/GP.aspx?pid=2"/>
<content type="application/xml">
<s:organisationSummary>
<s:name>Surgery</s:name>
<s:address>
<s:addressLine>Healthcare Centre</s:addressLine>
<s:addressLine>Kings</s:addressLine>
<s:addressLine>Town</s:addressLine>
<s:postcode>?</s:postcode>
</s:address>
<s:contact type="General">
<s:telephone>555555</s:telephone>
<s:email>Email</s:email>
</s:contact>
<s:geographicCoordinates>
<s:northing>78421</s:northing>
<s:easting>484100</s:easting>
<s:longitude>-1.828987402220691</s:longitude>
<s:latitude>53.987218047559</s:latitude>
</s:geographicCoordinates>
<s:Distance>0.5</s:Distance>
</s:organisationSummary>
</content>
</entry>
</feed>

This is a namespaced document, so you need to use the proper namespace methods, e.g. DOMDocument::getElementsByTagNameNS.
In addition, there is so much wrong with your loop that I suspect you're either not including all the code or you really misunderstand how DOMDocument works.
$NS = array(
's' => "http://syndication.nhschoices.nhs.uk/services",
'atom' => "http://www.w3.org/2005/Atom",
);
$entries = array();
foreach ($doc2->getElementsByTagNameNS($NS['s'], 'organisationSummary') as $node)
{
$entries[] = array(
'name' => trim($node->getElementsByTagNameNS($NS['s'], 'name')->item(0)->textContent),
'address' => keyByElementName($node->getElementsByTagNameNS($NS['s'], 'address')->item(0)),
'geographicCoordinates' => keyByElementName($node->getElementsByTagNameNS($NS['s'], 'geographicCoordinates')->item(0)),
);
}
function keyByElementName(DOMNode $node)
{
$elem = array();
foreach ($node->childNodes as $child) {
if ($child->nodeType===XML_ELEMENT_NODE) {
$elem[$child->localName] = trim($child->textContent);
}
}
return $elem;
}
However, consider using DOMXPath or SimpleXML, as these will be easier than dom traversal.

Related

Is there a cleaner way to achieve this XML to JSON transformation of NHS data

I am consuming location and address data from a public NHS API, it is coming in in an XML format and I have transformed it to JSON for use within my webapp.
My code is working and producing the desired result, however I cannot help but cringe at the amount of nested foreach loops inside this code.
Is there a cleaner way to achieve the same result that will be more performant?
I have tried using simplexml_load_string and many variations and it refuses to output/parse the content of a nested element containing s:organisationSummary data.
Here is the PHP class
class XmlElement {
public $name;
public $attributes;
public $content;
public $children;
}
class GpService
{
protected $apiKey;
public function __construct()
{
$this->apiKey = env('NHS_API_KEY');
}
public function xmlToObject($xml) {
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
xml_parse_into_struct($parser, $xml, $tags);
xml_parser_free($parser);
$elements = array(); // the currently filling [child] XmlElement array
$stack = array();
foreach ($tags as $tag) {
$index = count($elements);
if ($tag['type'] == "complete" || $tag['type'] == "open") {
$elements[$index] = new XmlElement;
$elements[$index]->name = $tag['tag'];
$elements[$index]->attributes = (isset($tag['attributes']))?$tag['attributes']:null;
$elements[$index]->content = (isset($tag['value']))?$tag['value']:null;
if ($tag['type'] == "open") { // push
$elements[$index]->children = array();
$stack[count($stack)] = &$elements;
$elements = &$elements[$index]->children;
}
}
if ($tag['type'] == "close") { // pop
$elements = &$stack[count($stack) - 1];
unset($stack[count($stack) - 1]);
}
}
return $elements[0]; // the single top-level element
}
public function searchByPostcode($postcode)
{
$postcodeRequest = new \GuzzleHttp\Client();
$postcodeGet = $postcodeRequest->request('GET', 'https://api.nhs.uk/data/gppractices/postcode/' . $postcode . '/?distance=15', [
'headers' => [
'subscription-key' => $this->apiKey,
]
]);
//Convert the XML string into an SimpleXMLElement object.
$xmlObject = $this->xmlToObject($postcodeGet->getBody());
$outputObject = [];
// Absolutely insane loops to get data into the format I would like
foreach($xmlObject->children as $key => $entry)
{
if($entry->name === 'entry')
{
foreach($entry->children as $value)
{
if($value->name === 'content')
{
foreach($value->children as $objectProperty)
{
foreach($objectProperty->children as $dataItem)
{
if(is_array($dataItem->children))
{
foreach($dataItem->children as $childKey => $childDataItem)
{
if($childDataItem->name == 's:addressLine')
{
$childDataItem->name = $childDataItem->name . $childKey;
}
$outputObject[$key][str_replace('s:','',strtolower($childDataItem->name))] = $childDataItem->content;
}
}
else
{
$outputObject[$key][str_replace('s:','',strtolower($dataItem->name))] = $dataItem->content;
}
}
}
}
}
}
}
return $outputObject;
}
}
Here is the XML Data:
<feed xmlns:s="https://api.nhs.uk/data/services" xmlns="https://www.w3.org/2005/Atom">
<title type="text">NHS website - GP Practices Near Postcode - LE2 1DA - Within 15km</title>
<id>https://api.nhs.uk/data/gppractices/postcode/le21da?range=15</id>
<rights type="text">© Crown Copyright 2009</rights>
<updated>2019-08-22T15:04:20+01:00</updated>
<category term="Search"/>
<logo>https://www.nhs.uk/nhscwebservices/documents/logo1.jpg</logo>
<author>
<name>NHS website</name>
<uri>https://www.nhs.uk</uri>
<email>webservices#nhschoices.nhs.uk</email>
</author>
<link rel="self" type="application/xml" title="NHS website - GP Practices Near Postcode - LE2 1DA - Within 15km" href="https://api.nhs.uk/data/gppractices/postcode/LE21DA&range=15"/>
<link rel="first" type="application/xml" title="first" length="1000" href="https://api.nhs.uk/data/gppractices/postcode/LE21DA&range=15&page=1"/>
<link rel="next" type="application/xml" title="next" length="1000" href="https://api.nhs.uk/data/gppractices/postcode/LE21DA&range=15&page=2"/>
<link rel="last" type="application/xml" title="last" length="1000" href="https://api.nhs.uk/data/gppractices/postcode/LE21DA&range=15&page=10"/>
<tracking xmlns="https://api.nhs.uk/data/services"><img style="border: 0; width: 1px; height: 1px;" alt="" src="https://statse.webtrendslive.com/dcs2221tai1ckz5huxw0mfq86_1m2w/njs.gif?dcsuri=/organisations%2fgppractices%2fpostcode%2fLE21DA&amp;wt.cg_s=OrganisationData&amp;wt.cg_n=syndication&synduserid=7a6ea2a0-8dec-11e9-8e02-f5191ebd3281&syndreviewdate=22-08-2019T14:04&synduserid=7a6ea2a0-8dec-11e9-8e02-f5191ebd3281&syndreviewdate=22-08-2019T14:04"/></tracking>
<s:SearchCoords>52.6299,-1.11502</s:SearchCoords>
<entry>
<id>https://api.nhs.uk/data/gppractices/4863</id>
<title type="text">Highfields Surgery (R Wadhwa)</title>
<updated>2019-08-22T14:04:20Z</updated>
<link rel="self" title="Highfields Surgery (R Wadhwa)" href="https://api.nhs.uk/data/gppractices/4863"/>
<link rel="alternate" title="Highfields Surgery (R Wadhwa)" href="https://www.nhs.uk/Services/GP/Overview/DefaultView.aspx?id=42231"/>
<content type="application/xml">
<s:organisationSummary>
<s:name>Highfields Surgery (R Wadhwa)</s:name>
<s:odsCode>C82116</s:odsCode>
<s:address>
<s:addressLine>25 Severn Street</s:addressLine>
<s:addressLine>Leicester</s:addressLine>
<s:addressLine>Leicestershire</s:addressLine>
<s:postcode>LE2 0NN</s:postcode>
</s:address>
<s:contact type="General">
<s:telephone>01162543253</s:telephone>
</s:contact>
<s:geographicCoordinates>
<s:longitude>-1.11859357357025</s:longitude>
<s:latitude>52.6293983459473</s:latitude>
</s:geographicCoordinates>
<s:Distance>0.247038430918239</s:Distance>
</s:organisationSummary>
</content>
</entry>
<entry>
<id>https://api.nhs.uk/data/gppractices/4901</id>
<title type="text">Dr R Kapur & Partners</title>
<updated>2019-08-22T14:04:20Z</updated>
<link rel="self" title="Dr R Kapur & Partners" href="https://api.nhs.uk/data/gppractices/4901"/>
<link rel="alternate" title="Dr R Kapur & Partners" href="https://www.nhs.uk/Services/GP/Overview/DefaultView.aspx?id=36753"/>
<content type="application/xml">
<s:organisationSummary>
<s:name>Dr R Kapur & Partners</s:name>
<s:odsCode>C82659</s:odsCode>
<s:address>
<s:addressLine>St Peters Health Centre</s:addressLine>
<s:addressLine>Sparkenhoe Street</s:addressLine>
<s:addressLine>Leicester</s:addressLine>
<s:addressLine>Leicestershire</s:addressLine>
<s:postcode>LE2 0TA</s:postcode>
</s:address>
<s:contact type="General">
<s:telephone>01162951258</s:telephone>
</s:contact>
<s:geographicCoordinates>
<s:longitude>-1.11907768249512</s:longitude>
<s:latitude>52.6310386657715</s:latitude>
</s:geographicCoordinates>
<s:Distance>0.30219913005026</s:Distance>
</s:organisationSummary>
</content>
</entry>
<entry>
<id>https://api.nhs.uk/data/gppractices/4827</id>
<title type="text">Shefa Medical Practice</title>
<updated>2019-08-22T14:04:20Z</updated>
<link rel="self" title="Shefa Medical Practice" href="https://api.nhs.uk/data/gppractices/4827"/>
<link rel="alternate" title="Shefa Medical Practice" href="https://www.nhs.uk/Services/GP/Overview/DefaultView.aspx?id=38960"/>
<content type="application/xml">
<s:organisationSummary>
<s:name>Shefa Medical Practice</s:name>
<s:odsCode>C82080</s:odsCode>
<s:address>
<s:addressLine>St Peters Health Centre</s:addressLine>
<s:addressLine>Sparkenhoe Street</s:addressLine>
<s:addressLine>Leicester</s:addressLine>
<s:addressLine>Leicestershire</s:addressLine>
<s:postcode>LE2 0TA</s:postcode>
</s:address>
<s:contact type="General">
<s:telephone>01162957835</s:telephone>
</s:contact>
<s:geographicCoordinates>
<s:longitude>-1.11907768249512</s:longitude>
<s:latitude>52.6310386657715</s:latitude>
</s:geographicCoordinates>
<s:Distance>0.30219913005026</s:Distance>
</s:organisationSummary>
</content>
</entry>
<entry>
<id>https://api.nhs.uk/data/gppractices/4902</id>
<title type="text">St Peter's Health Centre - Mansingh & Partner</title>
<updated>2019-08-22T14:04:20Z</updated>
<link rel="self" title="St Peter's Health Centre - Mansingh & Partner" href="https://api.nhs.uk/data/gppractices/4902"/>
<link rel="alternate" title="St Peter's Health Centre - Mansingh & Partner" href="https://www.nhs.uk/Services/GP/Overview/DefaultView.aspx?id=35950"/>
<content type="application/xml">
<s:organisationSummary>
<s:name>St Peter's Health Centre - Mansingh & Partner</s:name>
<s:odsCode>C82660</s:odsCode>
<s:address>
<s:addressLine>Sparkenhoe Street</s:addressLine>
<s:addressLine>Leicester</s:addressLine>
<s:addressLine>Leicestershire</s:addressLine>
<s:postcode>LE2 0TA</s:postcode>
</s:address>
<s:contact type="General">
<s:telephone>01162957827</s:telephone>
</s:contact>
<s:geographicCoordinates>
<s:longitude>-1.11907768249512</s:longitude>
<s:latitude>52.6310386657715</s:latitude>
</s:geographicCoordinates>
<s:Distance>0.30219913005026</s:Distance>
</s:organisationSummary>
</content>
</entry>
<entry>
<id>https://api.nhs.uk/data/gppractices/4848</id>
<title type="text">Al-Waqas Medical Centre</title>
<updated>2019-08-22T14:04:20Z</updated>
<link rel="self" title="Al-Waqas Medical Centre" href="https://api.nhs.uk/data/gppractices/4848"/>
<link rel="alternate" title="Al-Waqas Medical Centre" href="https://www.nhs.uk/Services/GP/Overview/DefaultView.aspx?id=43636"/>
<content type="application/xml">
<s:organisationSummary>
<s:name>Al-Waqas Medical Centre</s:name>
<s:odsCode>C82099</s:odsCode>
<s:address>
<s:addressLine>AL-Waqas Medical Practice</s:addressLine>
<s:addressLine>91 St Peters Road</s:addressLine>
<s:addressLine>Leicester</s:addressLine>
<s:addressLine>Leicestershire</s:addressLine>
<s:postcode>LE2 1DJ</s:postcode>
</s:address>
<s:contact type="General">
<s:telephone>01162543003</s:telephone>
</s:contact>
<s:geographicCoordinates>
<s:longitude>-1.10840618610382</s:longitude>
<s:latitude>52.6290435791016</s:latitude>
</s:geographicCoordinates>
<s:Distance>0.458084198394662</s:Distance>
</s:organisationSummary>
</content>
</entry>
<entry>
<id>https://api.nhs.uk/data/gppractices/4889</id>
<title type="text">Community Health Centre</title>
<updated>2019-08-22T14:04:20Z</updated>
<link rel="self" title="Community Health Centre" href="https://api.nhs.uk/data/gppractices/4889"/>
<link rel="alternate" title="Community Health Centre" href="https://www.nhs.uk/Services/GP/Overview/DefaultView.aspx?id=42802"/>
<content type="application/xml">
<s:organisationSummary>
<s:name>Community Health Centre</s:name>
<s:odsCode>C82643</s:odsCode>
<s:address>
<s:addressLine>Melbourne Centre</s:addressLine>
<s:addressLine>Melbourne Road</s:addressLine>
<s:addressLine>Leicester</s:addressLine>
<s:addressLine>Leicestershire</s:addressLine>
<s:postcode>LE2 0GU</s:postcode>
</s:address>
<s:contact type="General">
<s:telephone>01162622946</s:telephone>
</s:contact>
<s:geographicCoordinates>
<s:longitude>-1.11336529254913</s:longitude>
<s:latitude>52.6338653564453</s:latitude>
</s:geographicCoordinates>
<s:Distance>0.462480666213577</s:Distance>
</s:organisationSummary>
</content>
</entry>
<entry>
<id>https://api.nhs.uk/data/gppractices/4801</id>
<title type="text">Sayeed Medical Centre</title>
<updated>2019-08-22T14:04:20Z</updated>
<link rel="self" title="Sayeed Medical Centre" href="https://api.nhs.uk/data/gppractices/4801"/>
<link rel="alternate" title="Sayeed Medical Centre" href="https://www.nhs.uk/Services/GP/Overview/DefaultView.aspx?id=42819"/>
<content type="application/xml">
<s:organisationSummary>
<s:name>Sayeed Medical Centre</s:name>
<s:odsCode>C82060</s:odsCode>
<s:address>
<s:addressLine>352-354 East Park Road</s:addressLine>
<s:addressLine>Leicester</s:addressLine>
<s:addressLine>Leicestershire</s:addressLine>
<s:postcode>LE5 5AY</s:postcode>
</s:address>
<s:contact type="General">
<s:telephone>01163232030</s:telephone>
</s:contact>
<s:geographicCoordinates>
<s:longitude>-1.10658276081085</s:longitude>
<s:latitude>52.630802154541</s:latitude>
</s:geographicCoordinates>
<s:Distance>0.581034496166074</s:Distance>
</s:organisationSummary>
</content>
</entry>
<entry>
<id>https://api.nhs.uk/data/gppractices/4759</id>
<title type="text">Melbourne Street Surgery</title>
<updated>2019-08-22T14:04:20Z</updated>
<link rel="self" title="Melbourne Street Surgery" href="https://api.nhs.uk/data/gppractices/4759"/>
<link rel="alternate" title="Melbourne Street Surgery" href="https://www.nhs.uk/Services/GP/Overview/DefaultView.aspx?id=36620"/>
<content type="application/xml">
<s:organisationSummary>
<s:name>Melbourne Street Surgery</s:name>
<s:odsCode>C82031</s:odsCode>
<s:address>
<s:addressLine>56 Melbourne Street</s:addressLine>
<s:addressLine>Leicester</s:addressLine>
<s:addressLine>Leicestershire</s:addressLine>
<s:postcode>LE2 0AS</s:postcode>
</s:address>
<s:contact type="General">
<s:telephone>01162536299</s:telephone>
</s:contact>
<s:geographicCoordinates>
<s:longitude>-1.11437559127808</s:longitude>
<s:latitude>52.636344909668</s:latitude>
</s:geographicCoordinates>
<s:Distance>0.717122615984056</s:Distance>
</s:organisationSummary>
</content>
</entry>
<entry>
<id>https://api.nhs.uk/data/gppractices/4770</id>
<title type="text">East Park Medical Centre - R P Pandya</title>
<updated>2019-08-22T14:04:20Z</updated>
<link rel="self" title="East Park Medical Centre - R P Pandya" href="https://api.nhs.uk/data/gppractices/4770"/>
<link rel="alternate" title="East Park Medical Centre - R P Pandya" href="https://www.nhs.uk/Services/GP/Overview/DefaultView.aspx?id=40574"/>
<content type="application/xml">
<s:organisationSummary>
<s:name>East Park Medical Centre - R P Pandya</s:name>
<s:odsCode>C82037</s:odsCode>
<s:address>
<s:addressLine>264-266 East Park Road</s:addressLine>
<s:addressLine>Leicester</s:addressLine>
<s:addressLine>Leicestershire</s:addressLine>
<s:postcode>LE5 5FD</s:postcode>
</s:address>
<s:contact type="General">
<s:telephone>01162736330</s:telephone>
</s:contact>
<s:geographicCoordinates>
<s:longitude>-1.10444343090057</s:longitude>
<s:latitude>52.633544921875</s:latitude>
</s:geographicCoordinates>
<s:Distance>0.822717143851754</s:Distance>
</s:organisationSummary>
</content>
</entry>
<entry>
<id>https://api.nhs.uk/data/gppractices/4834</id>
<title type="text">Evington Medical Centre</title>
<updated>2019-08-22T14:04:20Z</updated>
<link rel="self" title="Evington Medical Centre" href="https://api.nhs.uk/data/gppractices/4834"/>
<link rel="alternate" title="Evington Medical Centre" href="https://www.nhs.uk/Services/GP/Overview/DefaultView.aspx?id=36363"/>
<content type="application/xml">
<s:organisationSummary>
<s:name>Evington Medical Centre</s:name>
<s:odsCode>C82088</s:odsCode>
<s:address>
<s:addressLine>2 - 6 Halsbury Street</s:addressLine>
<s:addressLine>Leicester</s:addressLine>
<s:addressLine>Leicestershire</s:addressLine>
<s:postcode>LE2 1QA</s:postcode>
</s:address>
<s:contact type="General">
<s:telephone>0116 3190343</s:telephone>
</s:contact>
<s:geographicCoordinates>
<s:longitude>-1.10676002502441</s:longitude>
<s:latitude>52.624267578125</s:latitude>
</s:geographicCoordinates>
<s:Distance>0.834829502560211</s:Distance>
</s:organisationSummary>
</content>
</entry>
</feed>
This is the output I am expecting:
{
"success":true,
"data":{
"13":{
"name":"Highfields Surgery (R Wadhwa)",
"odscode":"C82116",
"addressline0":"25 Severn Street",
"addressline1":"Leicester",
"addressline2":"Leicestershire",
"postcode":"LE2 0NN",
"telephone":"01162543253",
"longitude":"-1.11859357357025",
"latitude":"52.6293983459473",
"distance":"0.247038430918239"
},
"14":{
"name":"Dr R Kapur & Partners",
"odscode":"C82659",
"addressline0":"St Peters Health Centre",
"addressline1":"Sparkenhoe Street",
"addressline2":"Leicester",
"addressline3":"Leicestershire",
"postcode":"LE2 0TA",
"telephone":"01162951258",
"longitude":"-1.11907768249512",
"latitude":"52.6310386657715",
"distance":"0.30219913005026"
},
"15":{
"name":"Shefa Medical Practice",
"odscode":"C82080",
"addressline0":"St Peters Health Centre",
"addressline1":"Sparkenhoe Street",
"addressline2":"Leicester",
"addressline3":"Leicestershire",
"postcode":"LE2 0TA",
"telephone":"01162957835",
"longitude":"-1.11907768249512",
"latitude":"52.6310386657715",
"distance":"0.30219913005026"
},
"16":{
"name":"St Peter's Health Centre - Mansingh & Partner",
"odscode":"C82660",
"addressline0":"Sparkenhoe Street",
"addressline1":"Leicester",
"addressline2":"Leicestershire",
"postcode":"LE2 0TA",
"telephone":"01162957827",
"longitude":"-1.11907768249512",
"latitude":"52.6310386657715",
"distance":"0.30219913005026"
},
"17":{
"name":"Al-Waqas Medical Centre",
"odscode":"C82099",
"addressline0":"AL-Waqas Medical Practice",
"addressline1":"91 St Peters Road",
"addressline2":"Leicester",
"addressline3":"Leicestershire",
"postcode":"LE2 1DJ",
"telephone":"01162543003",
"longitude":"-1.10840618610382",
"latitude":"52.6290435791016",
"distance":"0.458084198394662"
},
"18":{
"name":"Community Health Centre",
"odscode":"C82643",
"addressline0":"Melbourne Centre",
"addressline1":"Melbourne Road",
"addressline2":"Leicester",
"addressline3":"Leicestershire",
"postcode":"LE2 0GU",
"telephone":"01162622946",
"longitude":"-1.11336529254913",
"latitude":"52.6338653564453",
"distance":"0.462480666213577"
},
"19":{
"name":"Sayeed Medical Centre",
"odscode":"C82060",
"addressline0":"352-354 East Park Road",
"addressline1":"Leicester",
"addressline2":"Leicestershire",
"postcode":"LE5 5AY",
"telephone":"01163232030",
"longitude":"-1.10658276081085",
"latitude":"52.630802154541",
"distance":"0.581034496166074"
},
"20":{
"name":"Melbourne Street Surgery",
"odscode":"C82031",
"addressline0":"56 Melbourne Street",
"addressline1":"Leicester",
"addressline2":"Leicestershire",
"postcode":"LE2 0AS",
"telephone":"01162536299",
"longitude":"-1.11437559127808",
"latitude":"52.636344909668",
"distance":"0.717122615984056"
},
"21":{
"name":"East Park Medical Centre - R P Pandya",
"odscode":"C82037",
"addressline0":"264-266 East Park Road",
"addressline1":"Leicester",
"addressline2":"Leicestershire",
"postcode":"LE5 5FD",
"telephone":"01162736330",
"longitude":"-1.10444343090057",
"latitude":"52.633544921875",
"distance":"0.822717143851754"
},
"22":{
"name":"Evington Medical Centre",
"odscode":"C82088",
"addressline0":"2 - 6 Halsbury Street",
"addressline1":"Leicester",
"addressline2":"Leicestershire",
"postcode":"LE2 1QA",
"telephone":"0116 3190343",
"longitude":"-1.10676002502441",
"latitude":"52.624267578125",
"distance":"0.834829502560211"
}
}
}
You can use DOM and XPath expressions. All the data is inside the {https://api.nhs.uk/data/services}organisationSummary nodes.
The following example register the prefix service for the namespace https://api.nhs.uk/data/services (That is what the s in your example resolves to). Then it fetches any {https://api.nhs.uk/data/services}organisationSummary node using the //service:organisationSummary expression.
For each each organisation it builds up an $item variable using expressions for scalar values. A loop is used for the {https://api.nhs.uk/data/services}addressLine nodes.
// create the DOM document and load the XML
$document = new DOMDocument();
$document->loadXML($xml);
// create an Xpath instance for the document - register namespace
$xpath = new DOMXpath($document);
$xpath->registerNamespace('service', 'https://api.nhs.uk/data/services');
$json = [
"success" => true,
// force object for data - it will be an array otherwise
"data" => new stdClass()
];
// iterate over all organisationSummary nodes
foreach ($xpath->evaluate('//service:organisationSummary') as $index => $organisation) {
// fetch single values
$item = [
"name" => $xpath->evaluate('string(service:name)', $organisation),
"odscode" => $xpath->evaluate('string(service:odsCode)', $organisation),
"postcode" => $xpath->evaluate('string(service:address/service:postcode)', $organisation),
"telephone" => $xpath->evaluate('string(service:contact/service:telephone)', $organisation),
"longitude" => $xpath->evaluate('string(service:geographicCoordinates/service:longitude)', $organisation),
"latitude" => $xpath->evaluate('string(service:geographicCoordinates/service:latitude)', $organisation),
"distance" => $xpath->evaluate('string(service:Distance)', $organisation)
];
// add the addressLine values
foreach ($xpath->evaluate('service:address/service:addressLine', $organisation) as $lineIndex => $line) {
$item['addressline'.$lineIndex] = $line->textContent;
}
// add the $item
$json['data']->{$index} = $item;
}
echo json_encode($json, JSON_PRETTY_PRINT);
Output:
{
"success": true,
"data": {
"0": {
"name": "Highfields Surgery (R Wadhwa)",
"odscode": "C82116",
"postcode": "LE2 0NN",
"telephone": "01162543253",
"longitude": "-1.11859357357025",
"latitude": "52.6293983459473",
"distance": "0.247038430918239",
"addressline0": "25 Severn Street",
"addressline1": "Leicester",
"addressline2": "Leicestershire"
},
"1": {
"name": "Dr R Kapur & Partners",
"odscode": "C82659",
"postcode": "LE2 0TA",
"telephone": "01162951258",
"longitude": "-1.11907768249512",
"latitude": "52.6310386657715",
"distance": "0.30219913005026",
"addressline0": "St Peters Health Centre",
"addressline1": "Sparkenhoe Street",
"addressline2": "Leicester",
"addressline3": "Leicestershire"
},
...
You need to use proper methods to traverse the XML document, rather than looking at every single element and checking its name.
Your two best options are DOM methods and XPath. The former will be familiar to you if you've worked with front-end JavaScript code (e.g. document.getElementsByTagName or document.getElementById.) The latter has a steeper learning curve but is far more powerful. (XSLT is also a good option for transforming XML, but I'm not very familiar with it.)
Step one though, is to use the proper XML library. The XML functions you're using are very low level. I'd recommend using PHP's DomDocument extension instead. Let's dive in!
<?php
$xml = file_get_contents("nhs.xml");
// assume XML document is stored in a variable called $xml
$dom = new DomDocument;
$dom->loadXml($xml);
// thankfully we can ignore namespaces!
$summaries = $dom->getElementsByTagName("organisationSummary");
// go through each <organisationSummary> element
foreach ($summaries as $os) {
$entry_data = [
// note we're using $os and not $dom now, so we get child elements of this particular element
"name" => $os->getElementsByTagName("name")[0]->textContent,
"odscode" => $os->getElementsByTagName("odsCode")[0]->textContent,
"addressline0" => $os->getElementsByTagName("addressLine")[0]->textContent,
// provide a default empty string in case there's no further lines
"addressline1" => $os->getElementsByTagName("addressLine")[1]->textContent ?? "",
"addressline2" => $os->getElementsByTagName("addressLine")[2]->textContent ?? "",
"addressline3" => $os->getElementsByTagName("addressLine")[3]->textContent ?? "",
"postcode" => $os->getElementsByTagName("postcode")[0]->textContent,
"telephone" => $os->getElementsByTagName("telephone")[0]->textContent,
"longitude" => $os->getElementsByTagName("longitude")[0]->textContent,
"latitude" => $os->getElementsByTagName("latitude")[0]->textContent,
"distance" => $os->getElementsByTagName("Distance")[0]->textContent,
];
$json_data[] = $entry_data;
}
if (count($json_data)) {
$output = ["success" => true, "data" => $json_data];
} else {
$output = ["success" => false];
}
echo json_encode($output, JSON_PRETTY_PRINT);

How to fetch exact parameter from XML string with PHP?

This is part of XML document:
<entry>
<author>
<name>Dunnock_D</name>
<uri>http://www.flickr.com/people/dunnock_d/</uri>
</author>
<link rel="license" type="text/html" href="https://creativecommons.org/licenses/by-nc/2.0/deed.en" />
<link rel="enclosure" type="image/jpeg" href="http://farm8.staticflickr.com/7548/26820724620_1d221c3187_b.jpg" />
</entry>
My code:
$xml = simplexml_load_string($result);
foreach ($xml->entry as $pixinfo) {
echo $pixinfo->link[1]['href'];
}
The problem is there can be one or more link strings and I need only particular with rel="enclosure" attribute.
What is the easiest way without extra IF and loops?
Thank you!
For that you can use DOMXPath, more specifically the query function. Let's say your $result variable contains the following:
<?xml version='1.0' encoding='UTF-8'?>
<entries>
<entry>
<author>
<name>Dunnock_D</name>
<uri>http://www.flickr.com/people/dunnock_d/</uri>
</author>
<link rel="license" type="text/html" href="https://creativecommons.org/licenses/by-nc/2.0/deed.en" />
<link rel="enclosure" type="image/jpeg" href="http://farm8.staticflickr.com/7548/26820724620_1d221c3187_b.jpg" />
</entry>
<entry>
<author>
<name>Dunnock_D</name>
<uri>http://www.flickr.com/people/dunnock_d/</uri>
</author>
<link rel="license" type="text/html" href="https://creativecommons.org/licenses/by-nc/2.0/deed.en" />
<link rel="enclosure" type="image/jpeg" href="http://farm8.staticflickr.com/7548/26820724620_1d221c3187_b.jpg" />
</entry>
<entry>
<author>
<name>Dunnock_D</name>
<uri>http://www.flickr.com/people/dunnock_d/</uri>
</author>
<link rel="license" type="text/html" href="https://creativecommons.org/licenses/by-nc/2.0/deed.en" />
<link rel="enclosure" type="image/jpeg" href="http://farm8.staticflickr.com/7548/26820724620_1d221c3187_b.jpg" />
</entry>
</entries>
I know the entries are repeated, but it's only for demo purposes. The code to get only the enclosure links would be:
$doc = new DOMDocument();
$doc->preserveWhiteSpace = false;
$doc->loadXML($result);
$xpath = new DOMXpath($doc);
$entries = $xpath->query('//entries/entry');
foreach ($entries as $entry) {
$link = $xpath->query('link[#rel="enclosure"]', $entry)->item(0);
$href = $link->getAttribute('href');
echo "{$href}\n";
}
You are using simplexml. Just use "attributes()" function: http://php.net/manual/pt_BR/simplexmlelement.attributes.php
Or you can access directly:
foreach ($xml->entry as $pixinfo) {
if($pixinfo->link[1]['rel'] == 'enclosure') {
echo $pixinfo->link[1]['href'];
}
}
The solution is Xpath.
With SimpleXML you can fetch the attribute node and cast the generated SimpleXMLElement into a string. You should make sure that you got an element before you cast it. SimpleXMLElement::xpath() will always return an array of SimpleXMLElement objects.
$entry = new SimpleXMLElement($xml);
$enclosures = $entry->xpath('link[#rel="enclosure"]/#href');
if (count($enclosures) > 0) {
var_dump((string)$enclosures[0]);
}
Output:
string(63) "http://farm8.staticflickr.com/7548/26820724620_1d221c3187_b.jpg"
With DOM the bootstrap is slightly larger, but you can fetch the href attribute directly as a string:
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
var_dump(
$xpath->evaluate('string(/entry/link[#rel="enclosure"]/#href)')
);
This will return an empty string if the expression does not match.

Google Analytics and XML namespace issue

I have looked through several posts on how to parse the namespace of a Google Analytics api feed (XML file) in PHP. They all say I need to have this bit of code:
$properties = $item->children('http://schemas.google.com/analytics/2009');
(or something similar, all involving the URL "http://schemas.google.com/analytics/2009")
The problem is, however, that I then get an error:
Fatal error: Call to a member function children() on a non-object
It seems as though the schemas.google.com doesn't exist, but I can't find where I need to be pointing to... any ideas?
Here's a snippet of the XML file I'm working with:
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:dxp="http://schemas.google.com/analytics/2009" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/">
<id>https://www.googleapis.com/analytics/v2.4/data?ids=ga:42418300&dimensions=ga:pagePath&metrics=ga:pageviews,ga:uniquePageviews&sort=-ga:pageviews&start-date=2013-12-01&end-date=2014-01-01&max-results=10</id>
<updated>2014-01-03T22:21:31.057Z</updated>
<title type="text">Google Analytics Data for View (Profile) xxxxxxx</title>
<link rel="self" type="application/atom+xml" href="https://www.googleapis.com/analytics/v2.4/data?ids=ga:42418300&dimensions=ga:pagePath&metrics=ga:pageviews,ga:uniquePageviews&sort=-ga:pageviews&start-date=2013-12-01&end-date=2014-01-01&max-results=10"/>
<link rel="next" type="application/atom+xml" href="https://www.googleapis.com/analytics/v2.4/data?ids=ga:42418300&dimensions=ga:pagePath&metrics=ga:pageviews,ga:uniquePageviews&sort=-ga:pageviews&start-date=2013-12-01&end-date=2014-01-01&start-index=11&max-results=10"/>
<author>
<name>Google Analytics</name>
</author>
<generator>Google Analytics</generator>
<openSearch:totalResults>4826</openSearch:totalResults>
<openSearch:startIndex>1</openSearch:startIndex>
<openSearch:itemsPerPage>10</openSearch:itemsPerPage>
<dxp:aggregates>
<dxp:metric name="ga:pageviews" type="integer" value="166656"/>
<dxp:metric name="ga:uniquePageviews" type="integer" value="132895"/>
</dxp:aggregates>
<dxp:containsSampledData>false</dxp:containsSampledData>
<dxp:dataSource>
<dxp:property name="ga:profileId" value="xxxxxxxx"/>
<dxp:property name="ga:webPropertyId" value="UA-xxxxxxxx-1"/>
<dxp:property name="ga:accountName" value="Provost"/>
<dxp:tableId>ga:42418300</dxp:tableId>
<dxp:tableName>WebSite</dxp:tableName>
</dxp:dataSource>
<dxp:endDate>2014-01-01</dxp:endDate>
<dxp:startDate>2013-12-01</dxp:startDate>
<entry>
<id>https://www.googleapis.com/analytics/v2.4/data?ids=ga:42418300&ga:pagePath=/&start-date=2013-12-01&end-date=2014-01-01</id>
<updated>2014-01-03T22:21:31.057Z</updated>
<title type="text">ga:pagePath=/</title>
<link rel="alternate" type="text/html" href="http://www.google.com/analytics"/>
<dxp:dimension name="ga:pagePath" value="/"/>
<dxp:metric name="ga:pageviews" type="integer" value="38197"/>
<dxp:metric name="ga:uniquePageviews" type="integer" value="29385"/>
</entry>
<entry>
<id>https://www.googleapis.com/analytics/v2.4/data?ids=ga:42418300&ga:pagePath=/page2/&start-date=2013-12-01&end-date=2014-01-01</id>
<updated>2014-01-03T22:21:31.057Z</updated>
<title type="text">ga:pagePath=/page2/</title>
<link rel="alternate" type="text/html" href="http://www.google.com/analytics"/>
<dxp:dimension name="ga:pagePath" value="/page2/"/>
<dxp:metric name="ga:pageviews" type="integer" value="13964"/>
<dxp:metric name="ga:uniquePageviews" type="integer" value="10974"/>
</entry>
Here's my PHP so far:
<?php
//ini_set('auto_detect_line_endings',TRUE);
$xml = simplexml_load_file("/ga-feed.xml");
$namespaces = $xml->getNamespaces(true);
foreach ($xml->entry as $key => $value) {
$value->registerXPathNamespace('dxp', 'http://schemas.google.com/analytics/2009');
echo $value->xpath('dxp:metric[pageviews]') . "<br />\n";
}
?>
Eventually, I need to be able run some calculations with pageviews and unique pageviews (show top 4 sites, a 5th "Other" would be a combination of all the other sites) against the overall pageviews/unique pageviews. Am I at least going in a semi-correct direction?
Update: In my foreach, I removed "feed" so it just says $xml->entry as $key, and now it will display a list of text:
Array
Array
Array
Array
Array
Array
Array
Array
Array
Array
Not quite what I'm looking for... but progress? lol

Problem getting XPath working in PHP

I've been trying to access the NHS API using different methods to read in the XML.
Here is a snippet of the XML:
<feed xmlns:s="http://syndication.nhschoices.nhs.uk/services" xmlns="http://www.w3.org/2005/Atom">
<title type="text">NHS Choices - GP Practices Near Postcode - W1T4LB - Within 5km</title>
<entry>
<id>http://v1.syndication.nhschoices.nhs.uk/organisations/gppractices/27369</id>
<title type="text">Fitzrovia Medical Centre</title>
<updated>2011-08-20T22:47:39Z</updated>
<link rel="self" title="Fitzrovia Medical Centre" href="http://v1.syndication.nhschoices.nhs.uk/organisations/gppractices/27369?apikey="/>
<link rel="alternate" title="Fitzrovia Medical Centre" href="http://www.nhs.uk/ServiceDirectories/Pages/GP.aspx?pid=303A92EF-EC8D-496B-B9CD-E6D836D13BA2"/>
<content type="application/xml">
<s:organisationSummary>
<s:name>Fitzrovia Medical Centre</s:name>
<s:address>
<s:addressLine>31 Fitzroy Square</s:addressLine>
<s:addressLine>London</s:addressLine>
<s:postcode>W1T6EU</s:postcode>
</s:address>
<s:contact type="General">
<s:telephone>020 7387 5798</s:telephone>
</s:contact>
<s:geographicCoordinates>
<s:northing>182000</s:northing>
<s:easting>529000</s:easting>
<s:longitude>-0.140267259415255</s:longitude>
<s:latitude>51.5224357586293</s:latitude>
</s:geographicCoordinates>
<s:Distance>0.360555127546399</s:Distance>
</s:organisationSummary>
</content>
</entry>
</feed>
I've been using this PHP to access it:
<?php
$feedURL = 'http://v1.syndication.nhschoices.nhs.uk/organisations/gppractices/postcode/W1T4LB.xml?apikey=&range=5';
$raw = file_get_contents($feedURL);
$dom = new DOMDocument();
$dom->loadXML($raw);
$xp = new DOMXPath($dom);
$result = $xp->query("//entry"); // select all entry nodes
print $result->item(0)->nodeValue;
?>
Problem is I have no results, the $raw data is present, but the $dom never gets filled with the string XML. This means the XPath won't work.
Also... for bonus points: how do I access the <s:Name> tag using XPath in this instance??
Appreciate the help as always.
Edit:
Here is the resulting PHP that worked fine, thanks to #Andrej L
<?php
$feedURL = 'http://v1.syndication.nhschoices.nhs.uk/organisations/gppractices/postcode/W1T4LB.xml?apikey=&range=5';
$xml = simplexml_load_file($feedURL);
$xml->registerXPathNamespace('s', 'http://syndication.nhschoices.nhs.uk/services');
$result = $xml->xpath('//s:name');
foreach ($result as $title)
{
print $title . '<br />';
}
?>
I think it's better to use SimpleXml library. see http://www.php.net/manual/en/book.simplexml.php
Register namespace using http://www.php.net/manual/en/simplexmlelement.registerxpathnamespace.php
Use xpath method. See http://www.php.net/manual/en/simplexmlelement.xpath.php

Traversing XML in PHP

I have the following XML code that I'm trying to parse, but I'm sure of how to traverse some of the data in PHP:
<entry>
<id>http://data.treasury.gov:8001/Feed.svc/DailyTreasuryYieldCurveRateData(5360)</id>
<title type="text"></title>
<updated>2011-06-09T20:15:18Z</updated>
<author>
<name />
</author>
<link rel="edit" title="DailyTreasuryYieldCurveRateDatum" href="DailyTreasuryYieldCurveRateData(5360)" />
<category term="TreasuryDataWarehouseModel.DailyTreasuryYieldCurveRateDatum" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<content type="application/xml">
<m:properties>
<d:Id m:type="Edm.Int32">5360</d:Id>
<d:NEW_DATE m:type="Edm.DateTime">2011-06-01T00:00:00</d:NEW_DATE>
<d:BC_1MONTH m:type="Edm.Double">0.04</d:BC_1MONTH>
<d:BC_3MONTH m:type="Edm.Double">0.05</d:BC_3MONTH>
<d:BC_6MONTH m:type="Edm.Double">0.11</d:BC_6MONTH>
<d:BC_1YEAR m:type="Edm.Double">0.18</d:BC_1YEAR>
<d:BC_2YEAR m:type="Edm.Double">0.44</d:BC_2YEAR>
<d:BC_3YEAR m:type="Edm.Double">0.74</d:BC_3YEAR>
<d:BC_5YEAR m:type="Edm.Double">1.6</d:BC_5YEAR>
<d:BC_7YEAR m:type="Edm.Double">2.28</d:BC_7YEAR>
<d:BC_10YEAR m:type="Edm.Double">2.96</d:BC_10YEAR>
<d:BC_20YEAR m:type="Edm.Double">3.83</d:BC_20YEAR>
<d:BC_30YEAR m:type="Edm.Double">4.15</d:BC_30YEAR>
<d:BC_30YEARDISPLAY m:type="Edm.Double">4.15</d:BC_30YEARDISPLAY>
</m:properties>
</content>
</entry>
I can only get so far as
entry->content
As the following throws an error for having a colon:
entry->content->m:properties
How do I access what's inside content such as d:NEW_DATE?
In SimpleXML you can use the children('prefix', true) and attributes('prefix', true) functions to access namespaced content.
entry->content->children('m', true)->properties
or to access d:NEW_DATE
entry->content->children('m', true)->properties->children('d', true)->NEW_DATE
or one step further to access the m:type attribute
entry->content->children('m', true)->properties->children('d', true)->NEW_DATE->attributes('m', true)->type
You can use the SimpleXml's functions
SimpleXML
But my fav class is DOMDocument

Categories