I am having some trouble trying to loop through an XML document. The XML looks like this:
<data>
<weather>
<hourly>
<time>0</time>
<tempC>17</tempC>
<tempF>62</tempF>
<windspeedMiles>24</windspeedMiles>
<windspeedKmph>39</windspeedKmph>
</hourly>
<hourly>
<time>3</time>
<tempC>16</tempC>
<tempF>60</tempF>
<windspeedMiles>22</windspeedMiles>
<windspeedKmph>35</windspeedKmph>
</hourly>
</weather>
<weather>
<hourly>
<time>0</time>
<tempC>17</tempC>
<tempF>62</tempF>
<windspeedMiles>24</windspeedMiles>
<windspeedKmph>39</windspeedKmph>
</hourly>
<hourly>
<time>3</time>
<tempC>16</tempC>
<tempF>60</tempF>
<windspeedMiles>22</windspeedMiles>
<windspeedKmph>35</windspeedKmph>
</hourly>
</weather>
</data>
My code (below) whilst it loops through all 'weather' nodes, it only picks out the first 'hourly' child node and completely skips the second. Would someone be able to help me as if I am honest, I do not know enough about looping to fix it and its driving me nuts! Grr.
Here is my PHP code which loads an XML document from online and then formats the XML results into div tags and obviously loops through the XML but as I said only loops through the first 'hourly' node of each 'weather' node.
<?php
// load SimpleXML
$data = new SimpleXMLElement('myOnlineXMLdocument.xml', null, true);
echo <<<EOF
<div class="observationRow">
<div class="observationTitleSmall"><br>Time</div>
<div class="observationTitleSmall"><br>Temp C</div>
<div class="observationTitleSmall"><br>Temp F</div>
<div class="observationTitleSmall"><br>Wind Speed MPH</div>
<div class="observationTitleSmall"><br>Wind Speed KMPH</div>
</div>
EOF;
foreach($data as $weather) // loop through our hours
{
echo <<<EOF
<div>
<div class="observationCellSmall"><br>{$weather->time}</div>
<div class="observationCellSmall"><br>{$weather->tempC}</div>
<div class="observationCellSmall"><br>{$weather->tempF}</div>
<div class="observationCellSmall"><br>{$weather->hourly->windspeedMiles}</div>
<div class="observationCellSmall"><br>{$weather->hourly->windspeedKmph}</div>
EOF;
}
echo '</div>';
?>
EDITED CODE:
$str = "";
foreach($data->weather as $weather)
{
foreach ($weather->hourly as $hour)
{
$str .= "
<div>";
if ($hour->time == "0") {
$str .= "
<div class='observationCellSmall'><br>$weather->date</div>
<div class='observationCellSmall'><br>$weather->maxtempC</div>
<div class='observationCellSmall'><br>$weather->mintempC</div>";
}
$str .= "
<div class='observationCellSmall'><br>$hour->time</div>
<div class='observationCellSmall'><br>$hour->tempC</div>
<div class='observationCellSmall'><br>$hour->tempF</div>
<div class='observationCellSmall'><br>$hour->windspeedMiles</div>
<div class='observationCellSmall'><br>$hour->windspeedKmph</div>
</div>
";
}
}
echo $str;
Using a slenderized version of your XML feed, that generates this:
<div>
<div class='observationCellSmall'><br>2013-08-19</div>
<div class='observationCellSmall'><br>17</div>
<div class='observationCellSmall'><br>15</div>
<div class='observationCellSmall'><br>0</div>
<div class='observationCellSmall'><br>15</div>
<div class='observationCellSmall'><br>59</div>
<div class='observationCellSmall'><br>11</div>
<div class='observationCellSmall'><br>18</div>
</div>
<div>
<div class='observationCellSmall'><br>300</div>
<div class='observationCellSmall'><br>15</div>
<div class='observationCellSmall'><br>59</div>
<div class='observationCellSmall'><br>13</div>
<div class='observationCellSmall'><br>21</div>
</div>
<div>
<div class='observationCellSmall'><br>2013-08-20</div>
<div class='observationCellSmall'><br>21</div>
<div class='observationCellSmall'><br>16</div>
<div class='observationCellSmall'><br>0</div>
<div class='observationCellSmall'><br>17</div>
<div class='observationCellSmall'><br>62</div>
<div class='observationCellSmall'><br>11</div>
<div class='observationCellSmall'><br>18</div>
</div>
<div>
<div class='observationCellSmall'><br>300</div>
<div class='observationCellSmall'><br>16</div>
<div class='observationCellSmall'><br>61</div>
<div class='observationCellSmall'><br>10</div>
<div class='observationCellSmall'><br>17</div>
</div>
You need a nested loop. One to loop over the weathers, and and another to loop over the hourlies.
foreach($data->weather as $weather) {
foreach($weather->hourly as $hourly) {
// code here
}
}
I don't remember the simplexml API 100% off my head, if that doesn't work you might need to use ->getChildren() or something to make it iterable.
Either that, or use xpath and nab the hourlies directly: /data/weather/hourly.
Related
I inherited the following piece of PHP code, that removes elements from the DOM before pushing the content into a page. We only want to show the first 5 elements to not have a too long page
Assuming the code retrieves an HTML fragment structured like this:
<div class='year'>2019</div>
<div class='record'>Record A</div>
<div class='record'>Record B</div>
<div class='year'>2018</div>
<div class='record'>Record C</div>
<div class='record'>Record D</div>
<div class='record'>Record E</div>
<div class='year'>2017</div>
<div class='record'>Record F</div>
<div class='year'>2016</div>
<div class='record'>Record G</div>
Now, the below piece of code removes all the extra records:
$dom = new DOMDocument();
// be sure to load the encoding
$dom->loadHTML('<?xml encoding="utf-8" ?>' . $tmp);
// let's use XPath
$finder = new DomXPath($dom);
// set the limit
$limit = 5; $cnt = 0;
// and remove unwanted elements
foreach($finder->query("//*[contains(#class, 'record')]") as $elm ) {
if ($cnt >= $limit)
$elm->parentNode->removeChild($elm);
$cnt++;
}
// finally, echo
echo $dom->saveHTML($dom->documentElement);
Logically, I end up having the following HTML:
<div class='year'>2019</div>
<div class='record'>Record A</div>
<div class='record'>Record B</div>
<div class='year'>2018</div>
<div class='record'>Record C</div>
<div class='record'>Record D</div>
<div class='record'>Record E</div>
<div class='year'>2017</div>
<div class='year'>2016</div>
How could I identify all the elements having the class year and having the next sibling also having this class and delete it? (here that would get the 2017 element)
Then I believe it would only be a matter of checking if the last element has the class year and remove it.
Or is there a cleaner way to achieve that?
You can add an extra foreach after the current one...
foreach($finder->query("//div[#class='year']/following-sibling::div[1][#class='year']")
as $elm ) {
$elm->parentNode->removeChild($elm);
}
The XPath here is looking for a <div class="year"> element and then only looking at the next <div> tag for the same thing (following-sibling::div[1] limits it to just the next div tag after the current one).
Here is a plain JS method in case you want to do this on the client instead
const recs = document.querySelectorAll(".record");
const divs = document.querySelectorAll("div");
const lastRec = recs[4];
let found = false;
divs.forEach(div => {
div.classList.toggle("hide",found)
if (div === lastRec) found = true
})
.hide { display:none}
<div class='year'>2019</div>
<div class='record'>Record A</div>
<div class='record'>Record B</div>
<div class='year'>2018</div>
<div class='record'>Record C</div>
<div class='record'>Record D</div>
<div class='record'>Record E</div>
<div class='year'>2017</div>
<div class='record'>Record F</div>
<div class='year'>2016</div>
<div class='record'>Record G</div>
I finally ended up using the following code:
$dom = new DOMDocument();
// be sure to load the encoding
$dom->loadHTML('<?xml encoding="utf-8" ?>' . $tmp);
// let's use XPath
$finder = new DomXPath($dom);
foreach($finder->query("(//*[contains(#class, 'record')])[5]/following-sibling::*") as $elm) {
$elm->parentNode->removeChild($elm);
}
// finally, echo
echo $dom->saveHTML($dom->documentElement);
it allowed me to achieve my goal in 1 pass without using nested loops
Suppose we have this input:
<div wrap>1</div>
<div>2</div>
<div wrap>3</div>
<div wrap>4</div>
<div wrap>5</div>
The required output should be:
<div class="wrapper">
<div wrap>1</div>
</div>
<div>2</div>
<div class="wrapper">
<div wrap>3</div>
<div wrap>4</div>
<div wrap>5</div>
</div>
Also, suppose that these elements are direct children of the body element and there can be other unrelated element or text nodes before or after them.
Notice how consecutive elements are grouped inside a single wrapper and not individually wrapped.
How would you handle body's DOMNodeList and insert the wrappers in the correct place?
Following the conversation (comments) about wrapping only direct children of the body element,
For this input:
<body>
<div wrap>1
<div wrap>1.1</div>
</div>
<div>2</div>
<div wrap>3</div>
<div wrap>4</div>
<div wrap>5</div>
</body>
The required output should be:
<body>
<div class="wrapper">
<div wrap>1
<div wrap>1.1</div>
<!–– ignored ––>.
</div>
</div>
<div>2</div>
<div class="wrapper">
<div wrap>3</div>
<div wrap>4</div>
<div wrap>5</div>
</div>
</body>
Notice how elements that are not direct descendants of the body element are totally ignored.
It's been interesting to write and would be good to see other solutions, but here is my attempt anyway.
I've added comments in the code rather than describing the method here as I think the comments make it easier to understand...
// Test HTML
$startHTML = '<div wrap>1</div>
<div>2</div>
<div wrap>3</div>
<div wrap>4</div>
<div wrap>5</div>';
$doc = new DOMDocument();
$doc->loadHTML($startHTML);
$xp = new DOMXPath($doc);
// Find any div tag with a wrap attribute which doesn't have an immediately preceeding
// tag with a wrap attribute, (or the first node which means it won't have a preceeding
// element anyway)
$wrapList = $xp->query("//div[#wrap='' and preceding-sibling::*[1][not(#wrap)]
or position() = 1]");
// Iterate over each of the first in the list of wrapped nodes
foreach ( $wrapList as $wrap ) {
// Create new wrapper
$wrapper = $doc->createElement("div");
$class = $doc->createAttribute("class");
$class->value = "wrapper";
$wrapper->appendChild($class);
// Copy subsequent wrap nodes (if any)
$nextNode = $wrap->nextSibling;
while ( $nextNode ) {
$next = $nextNode;
$nextNode = $nextNode->nextSibling;
// If it's an element (and not a text node etc)
if ( $next->nodeType == XML_ELEMENT_NODE ) {
// If it also has a wrap attribute - copy it
if ($next->hasAttribute("wrap") ) {
$wrapper->appendChild($next);
}
// If no attribute, then finished copying
else {
break;
}
}
}
// Replace first wrap node with new wrapper
$wrap->parentNode->replaceChild($wrapper, $wrap);
// Move the wrap node into the wrapper
$wrapper->insertBefore($wrap, $wrapper->firstChild);
}
echo $doc->saveHTML();
As it's using HTML, the end result is all wrapped in the standard tags as well, but the output (formatted) is...
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<body>
<div class="wrapper">
<div wrap>1</div>
</div>
<div>2</div>
<div class="wrapper">
<div wrap>3</div>
<div wrap>4</div>
<div wrap>5</div>
</div>
</body>
</html>
Edit:
If you only want it to apply to direct descendants of the <body> tag, then update the XPath expression to include it as part of the criteria...
$wrapList = $xp->query("//body/div[#wrap='' and preceding-sibling::*[1][not(#wrap)]
or position() = 1]");
I have following HTML code and I need make results from db, but I don't know how. There are three static divs. I don't know how to end static div and how to recognize columns. Can you help me please? Please see structure of html code with numbers of results. Thank for help.
<div class="static-div">
<div class="first-div">1</div>
<div class="second-div">4</div>
<div class="third-div">7</div>
<div class="first-div">10</div>
<div class="second-div">13</div>
<div class="third-div">16</div>
<div class="first-div">19</div>
<div class="second-div">22</div>
<div class="third-div">25</div>
</div>
<div class="static-div">
<div class="first-div">2</div>
<div class="second-div">5</div>
<div class="third-div">8</div>
<div class="first-div">11</div>
<div class="second-div">14</div>
<div class="third-div">17</div>
<div class="first-div">20</div>
<div class="second-div">23</div>
<div class="third-div">26</div>
</div>
<div class="static-div">
<div class="first-div">3</div>
<div class="second-div">6</div>
<div class="third-div">9</div>
<div class="first-div">12</div>
<div class="second-div">15</div>
<div class="third-div">18</div>
<div class="first-div">21</div>
<div class="second-div">24</div>
<div class="third-div">27</div>
</div>
You can achieve this output by using Arrays.
we will create three arrays for given three static divs.
try the following code.
The variable $i is not for loop iteration so if you are using $i for iteration please replace the following $i with other name.
$i=1;
$first_arr=array();
$sec_arr=array();
$third_arr=array();
while(database loop condition)
{
if($i==1)
{
$val=' <div class="first-div">'.$row['column'].'</div>';//$val is a variable from database query result
array_push($first_arr,$val);
$i++;
}
elseif($i==2)
{
$val=' <div class="second-div">'.$row['column'].'</div>';//$val is a variable from database query result
array_push($sec_arr,$val);
$i++;
}
elseif($i==3)
{
$val=' <div class="second-div">'.$row['column'].'</div>';//$val is a variable from database query result
array_push($sec_arr,$val);
$i=1;
}
}
$first_div=implode('',$first_arr);
$sec_div=implode('',$sec_arr);
$third_div=implode('',$third_arr);
echo '<div class="static-div">'.$first_div.'</div>
<div class="static-div">'.$sec_div.'</div>
<div class="static-div">'.$third_div.'</div>';
I'm not sure what I'm doing wrong but I'm getting the right nodeValue for what I want. It's just not updating when the php script is done. Here's the code:
$dom = new DOMDocument();
//suppress HTML5 and other errors
libxml_use_internal_errors(true);
$dom->loadHTMLFile($pageURL);
libxml_use_internal_errors(false);
$xpath = new DOMXPath($dom);
$divContent = $xpath->query("//*[#id='resultStats']/p")->item(0);
$newText = new DOMText("100 results");
var_dump($divContent->nodeValue); //returns old test value "400 results" which is correct
$divContent->removeChild($divContent->firstChild);
$divContent->appendChild($newText);
var_dump($divContent->localName); //"p" because i got it from <p> in resultStats
var_dump($divContent->textContent); //"100 results"
var_dump($divContent->nodeValue); //"100 results"
more of the HTML that is around it
<div class="container">
<div class="row">
<div class="resultStats span3 offset1" id="resultStats">
<p>400 results found.</p>
</div>
</div>
<div class="row">
<div class="span12">
<div class="row">
<div class="span6 offset1">
<?php
if (isset($_POST['q'])) {
//code from above that is executing every time from tests
}
?>
</div>
<div class="span5">
span5
</div>
</div>
</div>
</div>
I'm not sure what I'm doing wrong. If I do dom->save it rewrites everything (even php code) so I don't think that's a good idea.
I don't understand why you're using DOMDocument for this. Can't you just do this:
<div class="container">
<div class="row">
<div class="resultStats span3 offset1" id="resultStats">
<?php
// get new result count somehow in $resultCount
echo '<p>'.$resultCount.' results found</p>';
?>
</div>
</div>
I have the following HTML:
[...]
<div class="row clearfix">
<div class="col1">Data</div>
<div class="col2">Data</div>
<div class="col3">Data</div>
<div class="col4">Data</div>
<div class="col5">Data</div>
<div class="col6">Data</div>
<div class="col7">Data</div>
<div class="col8">Data</div>
</div><!--// row-->
<div class="row clearfix otherClass">
<div class="col1">Data</div>
<div class="col2">Data</div>
<div class="col3">Data</div>
<div class="col4">Data</div>
<div class="col5">Data</div>
<div class="col6">Data</div>
<div class="col7">Data</div>
<div class="col8">Data</div>
</div><!--// row-->
<div class="row clearfix thirdClass">
<div class="col1">Data</div>
<div class="col2">Data</div>
<div class="col3">Data</div>
<div class="col4">Data</div>
<div class="col5">Data</div>
<div class="col6">Data</div>
<div class="col7">Data</div>
<div class="col8">Data</div>
</div><!--// row-->
[...]
I want to get all of these divs out of the HTML, they all start with "row clearfix" as class, but can have more data to it.
After that I want to be able to handle each col separetely, so get the value of col1, col2, col3 ect.
I have written this code, but am stuck now. Can someone help me out?
$oDom = new DOMDocument();
$oDom->loadHtml($a_sHTML);
$oDomXpath = new DOMXpath($oDom);
$oDomObject = $oDomXpath->query('//div[#class="row clearfix"]');
foreach ($oDomObject as $oObject) {
var_dump($oObject->query('//div[#class="col1"]')->nodeValue);
}
UPDATE *Solution*
Thanks to the replies below, I got it working with the following code:
$oDom = new DOMDocument();
#$oDom->loadHtml($a_sHTML);
$oDomXpath = new DOMXpath($oDom);
$oDomObject = $oDomXpath->query('//div[contains(#class,"row") and contains(#class,"clearfix")]');
foreach ($oDomObject as $oObject) {
foreach($oObject->childNodes as $col)
{
if ($col->hasAttributes())
{
var_dump($col->getAttribute('class') . " == " . trim($col->nodeValue));
}
}
}
To match the outer divs I think that what you need is
//div[starts-with(#class,"row clearfix")]
or
//div[contains(#class,"row clearfix")]
or
//div[contains(#class,"row") and contains(#class,"clearfix")]
I'd go for the last one because the class names could be in any order.
I am not 100% sure what you want to do with the inner div, but you could get them with something like this:
div[starts-with(#class,"col")]