Getting element names/values with XML/Xpath/PHP - php

I have an XML schema that looks as follows:
<xml>
<user id="1">
<first_name>Bill</first_name>
<last_name>Steve</last_name>
<phone_numbers>
<work>xxx-xxx-xxxx</work>
<home>xxx-xxx-xxxx</home>
</phone_numbers>
</user>
<user id="2">
........
</user>
</xml>
Im working on parsing all of this information into PHP using DOM. Ex.
$userInfo = $doc->getElementsByTagName( "user" );
foreach($userInfo as $row)
{
$first_name = $row->getElementsByTagName("first_name");
}
When I try to nest this to select the phone numbers however I get an error. I've tried using XPath to select the phone numbers with equal problems. I tried something along the lines of
$userInfo = $doc->getElementsByTagName( "user" );
foreach($userInfo as $row)
{
$phoneInfo = $row->getElementsByTagName("phone_numbers");
foreach($phoneInfo as $row2)
{
$work = $row2->getElementsByTagName("work");
}
}
Im curious if Im doing something fundamentally wrong, or how to get this going. I've been tearing my hair out for a few hours now.

You can't get the value directly from a DOMNodeList Object, try this :
$userInfo = $doc->getElementsByTagName( "user" );
foreach($userInfo as $row)
{
$phoneInfo = $row->getElementsByTagName("phone_numbers");
foreach($phoneInfo as $row2)
{
// get the value from the first child
$work = $row2->getElementsByTagName("work")->item(0)->nodeValue;
$home = $row2->getElementsByTagName("home")->item(0)->nodeValue;
}
}

Well, you could switch it to SimpleXml which makes this type of parsing easier:
$userInfo = $doc->getElementsByTagName( "user" );
foreach ($userInfo as $user) {
$node = simplexml_import_dom($user);
$id = (string) $node['id'];
$first = (string) $node->first_name;
$last = (string) $node->last_name;
$workPhone = (string) $node->phone_numbers->work;
$homePhone = (string) $node->phone_numbers->home;
}
Now, in DomDocument, you could do this by using DomXpath:
$userInfo = $doc->getElementsByTagName( "user" );
$xpath = new DomXpath($doc);
foreach ($userInfo as $user) {
$id = $user->getAttribute('id');
$first = $xpath->query('//first_name', $user)->item(0)->textContent;
$last = $xpath->query('//last_name', $user)->item(0)->textContent;
$work = $xpath->query('//phone_numbers/work', $user)->item(0)->textContent;
$home = $xpath->query('//phone_numbers/home', $user)->item(0)->textContent;
}
Note that the above code (both parts) require that the format is exactly that. If you have conditionals, you might want to change it to something like this (the firstname conditional only):
$userInfo = $doc->getElementsByTagName( "user" );
$xpath = new DomXpath($doc);
foreach ($userInfo as $user) {
$id = $user->getAttribute('id');
$firstQuery = $xpath->query('//first_name', $user);
if ($firstQuery->length > 0) {
$first = $firstQuery->item(0)->textContent;
} else {
$first = '';
}
}

Related

How to create looped XML file from HTML in PHP?

I would like to be able to create an XML file from some of the content of a html page. I have tried intensively but seem to miss something.
I have created two arrays, I have setup a DOMdocument and I have prepared to save an XML file on the server... I have tried to make tons of different foreach loops all over the place - but it won't work.
Here is my code:
<?php
$page = file_get_contents('http://www.halfmen.dk/!hmhb8/score.php');
$doc = new DOMDocument();
$doc->loadHTML($page);
$score = $doc->getElementsByTagName('div');
$keyarray = array();
$teamarray = array();
foreach ($score as $value) {
if ($value->getAttribute('class') == 'xml') {
$keyarray[] = $value->firstChild->nodeValue;
$teamarray[] = $value->firstChild->nextSibling->nodeValue;
}
}
print_r($keyarray);
print_r($teamarray);
$doc = new DOMDocument('1.0','utf-8');
$doc->formatOutput = true;
$droot = $doc->createElement('ROOT');
$droot = $doc->appendChild($droot);
$dsection = $doc->createElement('SECTION');
$dsection = $droot->appendChild($dsection);
$dkey = $doc->createElement('KEY');
$dkey = $dsection->appendChild($dkey);
$dteam = $doc->createElement('TEAM');
$dteam = $dsection->appendChild($dteam);
$dkeytext = $doc->createTextNode($keyarray);
$dkeytext = $dkey->appendChild($dkeytext);
$dteamtext = $doc->createTextNode($teamarray);
$dteamtext = $dteam->appendChild($dteamtext);
echo $doc->save('xml/test.xml');
?>
I really like simplicity, thank you.
You need to add each item in one at a time rather than as an array, which is why I build the XML for each div tag rather than as a second pass. I've had to assume that your XML is structured the way I've done it, but this may help you.
$page = file_get_contents('http://www.halfmen.dk/!hmhb8/score.php');
$doc = new DOMDocument();
$doc->loadHTML($page);
$score = $doc->getElementsByTagName('div');
$doc = new DOMDocument('1.0','utf-8');
$doc->formatOutput = true;
$droot = $doc->createElement('ROOT');
$droot = $doc->appendChild($droot);
foreach ($score as $value) {
if ($value->getAttribute('class') == 'xml') {
$dsection = $doc->createElement('SECTION');
$dsection = $droot->appendChild($dsection);
$dkey = $doc->createElement('KEY', $value->firstChild->nodeValue);
$dkey = $dsection->appendChild($dkey);
$dteam = $doc->createElement('TEAM', $value->firstChild->nextSibling->nodeValue);
$dteam = $dsection->appendChild($dteam);
}
}

XML Obtain multiple values

I have the below XML file. There are 4 rows constantly repeated for different websites.
These are _URL _Away _Home _Draw. Each of these is prepended by the website. I need to compare all of the _Away rows to find the highest value, but there may sometimes be 1 of these rows and other times there can be as many as 32. What I would like to know is, is there a way to get these by defining the end of the string without having to explicitly declare the entire string for each website?
<XMLSOCCER.COM>
<Odds>
<Id>1547</Id>
<_10Bet_Home_Home>1.31</_10Bet_Home_Home>
<_10Bet_Home_Url>http://en.10bet.com</_10Bet_Home_Url>
<_10Bet_Home_Away>8.50</_10Bet_Home_Away>
<_10Bet_Home_Draw>5.40</_10Bet_Home_Draw>
<Bet_At_Home_Home>1.25</Bet_At_Home_Home>
<Bet_At_Home_Url>http://www.bet-at-home.com/</Bet_At_Home_Url>
<Bet_At_Home_Away>10.00</Bet_At_Home_Away>
<Bet_At_Home_Draw>5.75</Bet_At_Home_Draw>
<Bet365_Url>http://www.bet365.com/</Bet365_Url>
<Bet365_Home>1.30</Bet365_Home>
<Bet365_Away>9.00</Bet365_Away>
<Bet365_Draw>5.50</Bet365_Draw>
<BetVictor_Home>1.30</BetVictor_Home>
<BetVictor_Url>http://www.betvictor.com/</BetVictor_Url>
<BetVictor_Away>9.00</BetVictor_Away>
<BetVictor_Draw>5.40</BetVictor_Draw>
<Bwin_Home>1.28</Bwin_Home>
</Odds>
</XMLSOCCER.COM>
You can use XPath to fetch all nodes ending with _Away. Here's a code snippet that accomplishes what you want:
<?php
$xml = <<<XML
<XMLSOCCER.COM>
<Odds>
<Id>1547</Id>
<_10Bet_Home_Home>1.31</_10Bet_Home_Home>
<_10Bet_Home_Url>http://en.10bet.com</_10Bet_Home_Url>
<_10Bet_Home_Away>8.50</_10Bet_Home_Away>
<_10Bet_Home_Draw>5.40</_10Bet_Home_Draw>
<Bet_At_Home_Home>1.25</Bet_At_Home_Home>
<Bet_At_Home_Url>http://www.bet-at-home.com/</Bet_At_Home_Url>
<Bet_At_Home_Away>10.00</Bet_At_Home_Away>
<Bet_At_Home_Draw>5.75</Bet_At_Home_Draw>
<Bet365_Url>http://www.bet365.com/</Bet365_Url>
<Bet365_Home>1.30</Bet365_Home>
<Bet365_Away>9.00</Bet365_Away>
<Bet365_Draw>5.50</Bet365_Draw>
<BetVictor_Home>1.30</BetVictor_Home>
<BetVictor_Url>http://www.betvictor.com/</BetVictor_Url>
<BetVictor_Away>9.00</BetVictor_Away>
<BetVictor_Draw>5.40</BetVictor_Draw>
<Bwin_Home>1.28</Bwin_Home>
</Odds>
</XMLSOCCER.COM>
XML;
$sxe = new SimpleXMLElement($xml);
$nodesEndingWithAway = $sxe->xpath('//*[substring(name(),string-length(name())-3) = "Away"]');
$highestValue = 0;
$nodeName = '';
foreach ($nodesEndingWithAway as $node) {
if ((float) $node > $highestValue) {
$highestValue = (float) $node;
$nodeName = $node->getName();
}
}
echo "Highest value is {$highestValue} from node {$nodeName}.\n";
Output:
Highest value is 10 from node Bet_At_Home_Away.
Note: I think it would be possible to accomplish it with a single XPath expression without the need to process the nodes with the foreach.
You can do this with XPath.
$doc = new DOMDocument();
$doc->load($filename);
$xpath = new DOMXPath($doc);
$elements = $xpath->query('/XMLSOCCER.COM/Odds/*[substring(name(),string-length(name())-3) = "Away"]');
$maxValue = 0;
foreach ($elements as $element) {
$value = floatval($element->nodeValue);
$maxValue = max($maxValue, $value);
}
EDIT: very compressed:
$maxbid = max(array_map('floatval', $xml->xpath("//*[substring(name(),string-length(name())-" . (strlen($search) - 1) . ") = '$search']")));
in several steps:
use simplexml and xpath:
$search = "_Away";
$xml = simplexml_load_string($x);
$results = $xml->xpath("//*[substring(name(),string-length(name())-" . (strlen($search) - 1) . ") = '$search']");
Loop through your results:
foreach ($results as $result) echo "$result <br />";
Print highest result:
echo "highest: " . number_format(max(array_map('floatval', $results)), 2, '.', ',');
See it working: http://codepad.viper-7.com/iEpGz9

How do I rename XML values using php?

How do I rename a value in xml using PHP? This is what I've got so far:
<?php
$q = $_GET["q"];
$q = stripslashes($q);
$q = explode('|^', $q);
$old = $q[0];
$dom = new DOMDocument;
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->Load("test.xml");
$xpath = new DOMXPath($dom);
$query1 = 'channel/item[title="' . $old . '"]/title';
$entries = $xpath->query($query1);
foreach ($entries as $entry)
{
$oldchapter = $entry->parentNode->removeChild($entry);
$item = $dom->getElementsByTagName('item');
foreach ($item as $items)
{
$title = $dom->createElement('title', $q[1]);
$items->appendChild($title);
}
}
$dom->save("test.xml");
Basically, what it does is take two titles from a url, the old existing title, and the one the user wants to change it to (so like this oldtitle|^newtitle), and puts them into an array.
What I've tried doing is removing the existing old title, and then making a new title with, using the new title value from the url, but it doesn't seem to be working. Where am I going wrong, or is there an easier way of doing this?
The way to do this is with DOMNode::replaceChild(). The majority of your code is correct, you've just slightly over-complicated some of the DOM stuff.
Try this:
<?php
$q = $_GET["q"];
$q = stripslashes($q);
$q = explode('|^', $q);
$old = $q[0];
$dom = new DOMDocument;
// Do this *before* loading the document
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->Load("test.xml");
$xpath = new DOMXPath($dom);
$query1 = 'channel/item[title="' . $old . '"]/title';
$entries = $xpath->query($query1);
// This is all you need to do in the loop
foreach ($entries as $oldTitle) {
$newTitle = $dom->createElement('title', $q[1]);
$entry->parentNode->replaceChild($newTitle, $oldTitle);
}
$dom->save("test.xml");

Xpath to parse xml and input in mysql

I'm trying to use xpath in conjunction with DOMDocument to try and parse my xml and insert into a table.
All my variables are inserting correctly other than $halftimescore - why is this?
Here is my code:
<?php
define('INCLUDE_CHECK',true);
require 'db.class.php';
$dom = new DOMDocument();
$dom ->load('main.xml');
$xpath = new DOMXPath($dom);
$queryResult = $xpath->query('//live/Match/Results/Result[#name="HT"]');
foreach($queryResult as $resulty) {
$halftimescore=$resulty->getAttribute("value");
}
$Match = $dom->getElementsByTagName("Match");
foreach ($Match as $match) {
$matchid = $match->getAttribute("id");
$home = $match->getElementsByTagName("Home");
$hometeam = $home->item(0)->getAttribute("name");
$homeid = $home->item(0)->getAttribute("id");
$away = $match->getElementsByTagName("Away");
$awayid = $away->item(0)->getAttribute("id");
$awayteam = $away->item(0)->getAttribute("name");
$leaguename = $match->getElementsByTagName("league");
$league = $leaguename->item(0)->nodeValue;
$leagueid = $leaguename->item(0)->getAttribute("id");
foreach ($match->getElementsByTagName('Result') as $result) {
$resulttype = $result->getAttribute("name");
$score = $result->getAttribute("value");
$scoreid = $result->getAttribute("value");
}
mysql_query("
INSERT INTO blabla
(home_team, match_id, ht_score, away_team)
VALUES
('".$hometeam."', '".$matchid."', '".$halftimescore."', '".$awayteam."')
");
}
Because you populated $halftimescore outside the main loop, in a loop of its own, it will only have one value (the last value) because each iteration overwrites the previous.
What you need to do instead is run the XPath query within the main loop, with a base node of the current node, like this:
// ...
$xpath = new DOMXPath($dom);
/*
Remove these lines from here...
$queryResult = $xpath->query('//live/Match/Results/Result[#name="HT"]');
foreach($queryResult as $resulty) {
$halftimescore=$resulty->getAttribute("value");
}
*/
$Match = $dom->getElementsByTagName("Match");
foreach ($Match as $match) {
// and do the query here instead:
$result = $xpath->query('./Results/Result[#name="HT"]', $match);
if ($result->length < 1) {
// handle this error - the node was not found
}
$halftimescore = $result->item(0)->getAttribute("value");
// ...

php domDocument variables

I have the following code at the moment:
$ip = '195.72.186.157';
$xmlDoc = new DOMDocument();
$xmlDoc->loadXML(file_get_contents('http://www.geoffmeierhans.com/services/geo-locator/locate/?ip='.$ip.'&output=xml'));
foreach($xmlDoc->getElementsByTagName('city') as $link) {
$links = array('text' => $link->nodeValue);
}
$city = $links['text'];
echo $city;
Is there a better way to get the city variable? Since there is only one tag called city a loop isn't really needed but I can't get it to work any other way
Well, you can use the length parameter to DomNodeList (what's returned by the getElementsByTagName call.
If you want only the first result:
$nodes = $xmlDoc->getElementsByTagName('city');
if ($nodes->length > 0) {
$city = $nodes->item(0)->nodeValue;
} else {
$city = ''; // There is no city element
}

Categories