I've been searching for a while on this and haven't had much luck. I've found plenty of resources showing how to echo data from dynamic XML, but I'm a PHP novice, and nothing I've written seems to grab and print exactly what I want, though from everything I've heard, it should be relatively easy. The source XML (located at 192.168.0.15:8080/requests/status.xml) is as follows:
<root>
<fullscreen>0</fullscreen>
<volume>97</volume>
<repeat>false</repeat>
<version>2.0.5 Twoflower</version>
<random>true</random>
<audiodelay>0</audiodelay>
<apiversion>3</apiversion>
<videoeffects>
<hue>0</hue>
<saturation>1</saturation>
<contrast>1</contrast>
<brightness>1</brightness>
<gamma>1</gamma>
</videoeffects>
<state>playing</state>
<loop>true</loop>
<time>37</time>
<position>0.22050105035305</position>
<rate>1</rate>
<length>168</length>
<subtitledelay>0</subtitledelay>
<equalizer/>
<information>
<category name="meta">
<info name="description">
000003EC 00000253 00000D98 000007C0 00009C57 00004E37 000068EB 00003DC5 00015F90 00011187
</info>
<info name="date">2003</info>
<info name="artwork_url"> file://brentonshp04/music%24/Music/Hackett%2C%20Steve/Guitar%20Noir%20%26%20There%20Are%20Many%20Sides%20to%20the%20Night%20Disc%202/Folder.jpg
</info>
<info name="artist">Steve Hackett</info>
<info name="publisher">Recall</info>
<info name="album">Guitar Noir & There Are Many Sides to the Night Disc 2
</info>
<info name="track_number">5</info>
<info name="title">Beja Flor [Live]</info>
<info name="genre">Rock</info>
<info name="filename">Beja Flor [Live]</info>
</category>
<category name="Stream 0">
<info name="Bitrate">128 kb/s</info>
<info name="Type">Audio</info>
<info name="Channels">Stereo</info>
<info name="Sample rate">44100 Hz</info>
<info name="Codec">MPEG Audio layer 1/2/3 (mpga)</info>
</category>
</information>
<stats>
<lostabuffers>0</lostabuffers>
<readpackets>568</readpackets>
<lostpictures>0</lostpictures>
<demuxreadbytes>580544</demuxreadbytes>
<demuxbitrate>0.015997290611267</demuxbitrate>
<playedabuffers>0</playedabuffers>
<demuxcorrupted>0</demuxcorrupted>
<sendbitrate>0</sendbitrate>
<sentbytes>0</sentbytes>
<displayedpictures>0</displayedpictures>
<demuxreadpackets>0</demuxreadpackets>
<sentpackets>0</sentpackets>
<inputbitrate>0.016695899888873</inputbitrate>
<demuxdiscontinuity>0</demuxdiscontinuity>
<averagedemuxbitrate>0</averagedemuxbitrate>
<decodedvideo>0</decodedvideo>
<averageinputbitrate>0</averageinputbitrate>
<readbytes>581844</readbytes>
<decodedaudio>0</decodedaudio>
</stats>
</root>
What I'm trying to write is a simple PHP script that echoes the artist's name (In this example Steve Hackett). Actually I'd like it to echo the artist, song and album, but I'm confident that if I'm shown how to retrieve one, I can figure out the rest on my own.
What little of my script which actually seems to work goes as follows. I've tried more than what's below, but I left out the bits that I know for a fact aren't working.
<?PHP
$file = file_get_contents('http://192.168.0.15:8080/requests/status.xml');
$sxe = new SimpleXMLElement($file);
foreach($sxe->...
echo "Artist: "...
?>
I think I need to use foreach and echo, but I can't figure out how to do it in a way that will print what's between those info brackets.
I'm sorry if I've left anything out. I'm not only new to PHP, but I'm new to StackOverflow too. I've referenced this site in other projects, and it's always been incredibly helpful, so thanks in advance for your patience and help!
////////Finished Working Script - Thanks to Stefano and all who helped!
<?PHP
$file = file_get_contents('http://192.168.0.15:8080/requests/status.xml');
$sxe = new SimpleXMLElement($file);
$artist_xpath = $sxe->xpath('//info[#name="artist"]');
$album_xpath = $sxe->xpath('//info[#name="album"]');
$title_xpath = $sxe->xpath('//info[#name="title"]');
$artist = (string) $artist_xpath[0];
$album = (string) $album_xpath[0];
$title = (string) $title_xpath[0];
echo "<B>Artist: </B>".$artist."</br>";
echo "<B>Title: </B>".$title."</br>";
echo "<B>Album: </B>".$album."</br>";
?>
Instead of using a for loop, you can obtain the same result with XPath:
// Extraction splitted across two lines for clarity
$artist_xpath = $sxe->xpath('//info[#name="artist"]');
$artist = (string) $artist_xpath[0];
echo $artist;
You will have to adjust the xpath expression (i.e. change #name=... appropriately), but you get the idea. Also notice that [0] is necessary because xpath will return an array of matches (and you only need the first) and the cast (string) is used to extract text contained in the node.
Besides, your XML is invalid and will be rejected by the parser because of the literal & appearing in the <info name="album"> tag.
If you look at your code again, you are missing a function that turns the first result of the xpath expression into a string of a SimpleXMLElement (casting).
One way to write this once is to extend from SimpleXMLElement:
class BetterXMLElement extends SimpleXMLElement
{
public function xpathString($expression) {
list($result) = $this->xpath($expression);
return (string) $result;
}
}
You then create the more specific SimpleXMLElement like you did use the less specific before:
$file = file_get_contents('http://192.168.0.15:8080/requests/status.xml');
$sxe = new BetterXMLElement($file);
And then you benefit in your following code:
$artist = $sxe->xpathString('//info[#name="artist"]');
$album = $sxe->xpathString('//info[#name="album"]');
$title = $sxe->xpathString('//info[#name="title"]');
echo "<B>Artist: </B>".$artist."</br>";
echo "<B>Title: </B>".$title."</br>";
echo "<B>Album: </B>".$album."</br>";
This spares you some repeated code. This means as well less places you can make an error in :)
Sure you can further on optimize this by allowing to pass an array of multiple xpath queries and returning all values named then. But that is something you need to write your own according to your specific needs. So use what you learn in programming to make programming more easy :)
If you want some more suggestions, here is another, very detailed example using DOMDocument, the sister-library of SimpleXML. It is quite advanced but might give you some good inspiration, I think something similar is possible with SimpleXML as well and this is probably what you're looking for in the end:
Extracting data from HTML using PHP and xPath
Related
I have done a bit of searching on this, but am just not sure I am searching for the right thing. Examples and things I have found have just confused me and possibly sent me in the wrong direction.
I am trying to figure out a php while statement, or if statement to return the results of XML output. The thing is the row/section I need may not always be the same number of results returned. For example there are ShoutCast streams, some have 1 mount point, and some have 3 mount points. Each mount point can have a different amount of listeners tuned in to that particular mount.
My Goal: To get the integer from all mount points returned in the XML, add them together to make a grand total of listeners.
The XML
<centovacast version="3.1.2" host="host.net">
<response type="success">
<message>Complete</message>
<data>
<status>
<mount>/stream</mount>
<sid>1</sid>
<listenercount>31</listenercount>
<genre>Blues</genre>
<url>http://www.websiteurl.com</url>
<title>Streams Name</title>
<currentsong>Artist Name - Track Name</currentsong>
<bitrate>128</bitrate>
<sourceconnected>1</sourceconnected>
<codec>audio/mpeg</codec>
<displayname>/stream</displayname>
<serverstate>1</serverstate>
<appstate>
<sctrans2>1</sctrans2>
</appstate>
<sourcestate>1</sourcestate>
<reseller/>
<useserver>1</useserver>
<ipaddress>11.11.111.111</ipaddress>
<port>8031</port>
<proxy>0</proxy>
<servertype>ShoutCast2</servertype>
<sourcetype>sctrans2</sourcetype>
</status>
<mountpoints>
<row>
<mount>/stream</mount>
<sid>1</sid>
<listenercount>31</listenercount>
<genre>Blues</genre>
<url>http://www.websiteurl.com</url>
<title>Stream Title Name</title>
<currentsong>Artist Name - Track Name</currentsong>
<bitrate>128</bitrate>
<sourceconnected>1</sourceconnected>
<codec>audio/mpeg</codec>
<displayname>/stream</displayname>
</row>
<row>
<mount>/live</mount>
<sid>2</sid>
<listenercount>0</listenercount>
<genre/>
<url/>
<title/>
<currentsong/>
<bitrate>0</bitrate>
<sourceconnected>0</sourceconnected>
<codec/>
<displayname>/live</displayname>
</row>
</mountpoints>
</data>
</response>
</centovacast>
So on the above I know how to pull the listeners for each mount individually using the following code.
$countlisteners->response->data->mountpoints->row[0]->listenercount;
That gives me the result for the first mount, and switching the 0 to a 1 gives me the second mount, so on and so forth.
What I need is for php that will count how many of those mounts exist, and assign each result to a variable I can then use to add together to get a grand total. Is there a way to do this?
What about doing something like this?
$countlisteners = simplexml_load_file('http://urltoxml.com');
foreach($countlisteners->response->data->mountpoints->row->listenercount as $result){
$total = $result;
echo $total;
}
You can use DOMDocument for extracting all mountpoint tags
<?php
$xml="Your xml document content here";
$dom = new DOMDocument;
$dom->loadXML($xml);
$books = $dom->getElementsByTagName('mountpoints');
foreach ($mountpoints as $mountpoints) {
echo $mountpoints->nodeValue;
//you can add your count variable here
//nodeValues can be assigned to varables
}
?>
I figured it out. So simplistic, yet hard to figure out.
$total = 0;
foreach($countlisteners->response->data->mountpoints->row as $result){
$total += $result->listenercount;
$items++;
}
echo $total;
You normally do that with Xpath. It's a query language for XML documents.
You're interested in all listenercount elements, the Xpath expression for these elements could be as simple as:
//listenercount
When you now use SimpleXML to parse the document, the following line of code gives you three SimpleXMLElements inside an array that represent the three values you want to create the sum of:
$array = simplexml_load_string($buffer)->xpath('//listenercount');
As you need the sum of the integer values of these three elements, it can be easily processed with array_map and array_sum:
$sum = array_sum(array_map('intval', $array));
And this gives you in $sum what you're looking for:
var_dump($sum); # int(62)
I hope this sheds you some light why it's often better to get the information you're looking for with an xpath query from the document instead of writing many lines of code to traverse the document "on your own".
The full example:
$buffer = <<<XML
<centovacast version="3.1.2" host="host.net">
<response type="success">
<message>Complete</message>
<data>
<status>
<mount>/stream</mount>
<sid>1</sid>
<listenercount>31</listenercount>
<genre>Blues</genre>
<url>http://www.websiteurl.com</url>
<title>Streams Name</title>
<currentsong>Artist Name - Track Name</currentsong>
<bitrate>128</bitrate>
<sourceconnected>1</sourceconnected>
<codec>audio/mpeg</codec>
<displayname>/stream</displayname>
<serverstate>1</serverstate>
<appstate>
<sctrans2>1</sctrans2>
</appstate>
<sourcestate>1</sourcestate>
<reseller/>
<useserver>1</useserver>
<ipaddress>11.11.111.111</ipaddress>
<port>8031</port>
<proxy>0</proxy>
<servertype>ShoutCast2</servertype>
<sourcetype>sctrans2</sourcetype>
</status>
<mountpoints>
<row>
<mount>/stream</mount>
<sid>1</sid>
<listenercount>31</listenercount>
<genre>Blues</genre>
<url>http://www.websiteurl.com</url>
<title>Stream Title Name</title>
<currentsong>Artist Name - Track Name</currentsong>
<bitrate>128</bitrate>
<sourceconnected>1</sourceconnected>
<codec>audio/mpeg</codec>
<displayname>/stream</displayname>
</row>
<row>
<mount>/live</mount>
<sid>2</sid>
<listenercount>0</listenercount>
<genre/>
<url/>
<title/>
<currentsong/>
<bitrate>0</bitrate>
<sourceconnected>0</sourceconnected>
<codec/>
<displayname>/live</displayname>
</row>
</mountpoints>
</data>
</response>
</centovacast>
XML;
$array = simplexml_load_string($buffer)->xpath('//listenercount');
$sum = array_sum(array_map('intval', $array));
var_dump($sum);
I am trying to call an XPath on a SimpleXMLElement, but I always get empty array.
This is the XML:
<Produkt>
.....
<Darstellung>
<Info Art="Kurztext" Wert="some info!"/>
<Info ... />
</Darstellung>
</Produkt>
I tried this:
$shortDescription = $Produkt->xpath('//Darstellung/Info[#Art="Kurztext"]/#Wert');
$configurableProduct['short_description'] = (string)$shortDescription[0];
And this:
$configurableProduct['short_description'] = $Produkt->xpath('//Darstellung/Info[#Art="Kurztext"]/#Wert');
And also without // in the beginning of the XPath expression or with /*. But when I dump, I see that the array returned from the xpath() function is always empty.
The problem is not in unregistered namespace, because the XML file is not using namespaces. I am kinda out of ideas already... (tried some more things in the syntax, but don't remember them already).
P.S. Yes, I am sure $Produkt is a SimpleXMLElement object and I have checked that.
this code
$xmlStr = '<Produkt>
<Darstellung>
<Info Art="Kurztext" Wert="some info!"/>
<Info />
</Darstellung>
</Produkt>';
$xml = simplexml_load_string($xmlStr);
$ret = $xml->xpath('//Darstellung/Info[#Art="Kurztext"]/#Wert');
var_dump((string)$ret[0]);
returns
string(10) "some info!"
Yes, $ret[0] is XMLObject, so you should cast it to string.
This is my XML file named: full.xml
I need your help. I need a PHP script that open "full.xml"
and only display all values of the nodes that have .email
Example of the Output I want:
sales#company1.com
sales#company2.com
sales#company3.com
Thanks! I will thank you so much!
EDIT
$Connect = simplexml_load_file("full.xml");
return $Connect->table[0]->*.email;
The design of your XML is not very smart. With this xpath expression, you select all nodes with .email at the end of their name:
$xml = simplexml_load_string($x); // assume XML in $x
$results = $xml->xpath("//*[substring(name(),string-length(name())-" . (strlen('.email') - 1) . ") = '.email']");
--> result is an array with the selected nodes.
BTW: if you have any chance of CHANGING the structure of the XML, AVOID combining information within node names like <company1.email>, but do it like this:
...
<companies>
<company id="1">
<email>info#company1.com</email>
<tel>+498988123456</tel>
<name>somename</name>
</company>
<company id="2">
<email>info#company2.com</email>
<tel>+498988123457</tel>
<name>someothername</name>
</company>
</companies>
....
It will be much easier to read and parse.
I'm trying to parse a jobs feed using PHP's SimpleXML. I've only used JSON before and am having problems getting the parser to work. Here's some sample data:
<shrs>
<rq url="http://api.simplyhired.com/a/jobs-api/xml_v2/q-comission">
<t>Comission Jobs</t>
<dt>2011-02-18T23:58:38Z</dt>
<si>0</si>
<rpd>10</rpd>
<tr>192</tr>
<tv>146</tv>
<em url=""/>
<h>
<kw pos="1"/>
</h>
</rq>
<rs>
<r>
<jt>Virtual Recruiter (IT) - Comission ...</jt>
<cn url="">Remedy Intelligent Staffing</cn>
<src url="http://api.simplyhired.com/a/job-details/view/jobkey-monster91949932/cjp-0/hits-192?aff_id=28700">Monster</src>
<ty>organic</ty>
<loc cty="Buffalo" st="NY" postal="14211" county="" region="" country="US">Buffalo, NY</loc>
<ls>2011-02-04T05:51:17Z</ls>
<dp>2011-02-04T05:51:17Z</dp>
<e>
Seeking a candidate with previous recruiting experience to work as a Virtual Recruiter for a large client in the IT industry.a Responsibilities: Will recruit, screen, interview, and place candidates for many openings throughout the US Will...
</e>
</r>
<r>
<jt>Virtual Loan Officer (Mortgage) draw vs comission</jt>
<cn url="">Netbranchology.com</cn>
<src url="http://api.simplyhired.com/a/job-details/view/jobkey-7114.353281/cjp-2/hits-192?aff_id=28700">netbranchology.com</src>
<ty>organic</ty>
<loc cty="Denver" st="CO" postal="80218" county="" region="" country="US">Denver, CO</loc>
<ls>2011-02-10T11:47:50Z</ls>
<dp>2011-01-26T11:36:18Z</dp>
<e>
Minimize your overhead by becoming a virtual loan officer... Our client, a Texas-based mortgage banker, has just launched an innovative new program that lets you work from anywhere to originate residential mortgage loans. No office is...
</e>
</r>
</rs>
</shrs>
[etc]
I'd like to retrieve the metadata in the tags into variables, and then loop through each job result under to process it. How can I do this with PHP? (I've been playing around with the SimpleXML functions so far)
Nodes are accessed as object properties, attributes use the array notation. foreach lets you iterate over nodes. You can get the content of a node by casting it as a string. (so if you use echo it's implied)
$shrs = simplexml_load_string($xml);
foreach ($shrs->rs->r as $r)
{
$jobTitle = $r->jt;
$city = $r->loc['cty'];
echo "There's an offer for $jobTitle in $city<br />\n";
}
Try SimpleXML: http://www.php.net/manual/en/book.simplexml.php
It will parse your XML into a nice object.
Edit: here's how to use it (assumes your xml is stored in the variable $xml):
$xmlObject = new SimpleXMLElement($xml);
// to retrieve "http://api.simplyhired.com/a/jobs-api/xml_v2/q-comission"
$url = $xmlObject->rq['url'];
// to retrieve "Comission Jobs"
$t = $xmlObject->rq->t;
...
Hope it helps.
I'm being given XML in the following format, and am parsing it with PHP's SimpleXML.
<?xml version="1.0" encoding="UTF-8"?>
<ManageMyBooking>
<BookingInfo>
<PartyDetails>
<Passenger>
<PaxNo>1</PaxNo>
<Title>Mrs</Title>
<Surname>Murphy</Surname>
</Passenger>
<Passenger>
<PaxNo>2</PaxNo>
<Title>Mr</Title>
<Surname>Murphy</Surname>
</Passenger>
<Passenger>
<PaxNo>3</PaxNo>
<Title>Miss</Title>
<Surname>Murphy</Surname>
</Passenger>
</PartyDetails>
<Accommodation>
<Units>
<Unit>
<UnitNo>1</UnitNo>
<UnitDesc>...</UnitDesc>
<PaxAssociated>1|2</PaxAssociated>
</Unit>
<Unit>
<UnitNo>2</UnitNo>
<UnitDesc>...</UnitDesc>
<PaxAssociated>3</PaxAssociated>
</Unit>
</Units>
</Accommodation>
</BookingInfo>
</ManageMyBooking>
I'm looping through the Units (Rooms) thus:
// $Accommodation is a SimpleXML Object defined earlier, and able to provide relevant info
<? foreach ($Accommodation->Units as $Units) {
foreach ($Units->Unit as $Unit) {
// (room/unit details echoed out here)
foreach ($Unit->xpath('//Passenger[contains(PaxAssociated,./PaxNo)]') as $RoomPax) { ?>
<?= $RoomPax->Title $RoomPax->Surname" ?><br />
<?= "$RoomPax->Title $RoomPax->Surname" ?><br />
<? }
}
} ?>
in an attempt to show the names off the Passengers (Pax) in each room.
But this xpath finds no-one, and the following gets everyone.
//Passenger[contains(PaxNo,./PaxAssociated)]
What's especially frustrating is that I've successfully used XPath elsewhere in the same PHP for a very similar purpose, with no problems.
Any help/advice/suggestions will be much appreciated.
Edit:
for completeness, and to answer a question from multiple people:
The following works elsewhere in the code, (though not 100% correctly given the possible matching on '22' vs '2'.
//Flight[contains(PaxAssociated,./PaxNo)]
This:
//Passenger[contains(PaxNo,./PaxAssociated)]
is: Find any <Passenger> with a child <PaxNo> who's value contains the value of the child <PaxAssociated>. It would only work with such a data structure (which you clearly don't have):
<ManageMyBooking>
<BookingInfo>
<PartyDetails>
<Passenger>
<PaxNo>1|2</PaxNo> <!-- note the exchanged value! -->
<PaxAssociated>1</PaxAssociated>
</Passenger>
</PartyDetails>
</BookingInfo>
</ManageMyBooking>
So this is wrong on multiple accounts. What you mean is probably a dynamic XPath expression, like this:
foreach ($Units->Unit as $Unit) {
$XPath = "//Passenger[contains('". $Unit->PaxAssociated . "', PaxNo)]";
foreach ($Unit->xpath($XPath) as $RoomPax) {
// ...
}
}
This works on first glance, but it is not fail-safe, because "22" contains "2" as well. So doing a contains() alone won't get you anywhere. Correct would be:
$XPath = "//Passenger[contains('|". $Unit->PaxAssociated ."|', concat('|', PaxNo, '|'))]";
This way you check "|22|" against "|2|", which would return false.