Reputation: 2153
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?
Upvotes: 0
Views: 68
Reputation: 2153
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
Upvotes: 0
Reputation: 178061
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>
Upvotes: 0
Reputation: 57121
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).
Upvotes: 1