E. Jaep
E. Jaep

Reputation: 2153

How to remove elements from the DOM if they are consecutive

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

Answers (3)

E. Jaep
E. Jaep

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

mplungjan
mplungjan

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

Nigel Ren
Nigel Ren

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

Related Questions