Wrap Dom Element in Another Dom Element in PHP

Wrap DOM element in another DOM element in PHP

Why bother with re-creating the node? Why not just replace the node? (If I understand what you're trying to do)...

if($spancount == 0){
$element = $doc->createElement('span');
$element->setAttribute('style','color:#ffffff;');
$tag->parentNode->replaceChild($element, $tag);
$element->apendChild($tag);
}

Edit Whoops, it looks like you're trying to wrap everything under $tag in the span... Try this instead:

if($spancount == 0){
$element = $doc->createElement('span');
$element->setAttribute('style','color:#ffffff;');
foreach ($tag->childNodes as $child) {
$tag->removeChild($child);
$element->appendChild($child);
}
$tag->appendChild($child);
}

Edit2 Based on your results, it looks like that foreach is not completing because of the node removal... Try replacing the foreach with this:

while ($tag->childNodes->length > 0) {
$child = $tag->childNodes->item(0);
$tag->removeChild($child);
$element->appendChild($child);
}

How can I wrap two chosen elements with a div using PHP's DOMDocument?

You can use the appendChild() method to move an existing node. So:

  • Create a new div
  • Use appendChild on the new div to make your chosen elements a child of this div

How to conditionally wrap together elements using the DOM API?

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]");

Wrap all images with a div using DOMDocument

I never used DOMdocument before, but I think you mean something like this:

$html = <<<EOF
<html>
<head>
<title>:( -> :)</title>
</head>
<body>
<img src="www.com" />
<div class="random">
<img src="www.ru" />
</div>
</body>
</html>
EOF;

$dom = new DOMDocument();
$dom->loadHTML($html);

//Create new wrapper div
$new_div = $dom->createElement('div');
$new_div->setAttribute('class','wrapper');

//Find all images
$images = $dom->getElementsByTagName('img');

//Iterate though images
foreach ($images AS $image) {
//Clone our created div
$new_div_clone = $new_div->cloneNode();
//Replace image with this wrapper div
$image->parentNode->replaceChild($new_div_clone,$image);
//Append this image to wrapper div
$new_div_clone->appendChild($image);
}

PHP DOMDocument, wrap all elements without node with p

The text is technically already inside a "text node", but this will wrap all unwrapped text nodes with paragraph nodes:

<?php

$html = <<<'END'
<div>
<p>This is some text inside a text-node</p>
This is text without any node and should be wrapped with a text-node
</div>
END;

$doc = new \DOMDocument();
$doc->loadHTML($html, LIBXML_HTML_NOIMPLIED);

$xpath = new \DOMXPath($doc);
$nodes = $xpath->query('//text()[not(ancestor::p)][normalize-space()]');

foreach ($nodes as $node) {
$p = $doc->createElement('p', htmlspecialchars(trim($node->textContent)));
$node->parentNode->replaceChild($p, $node);
}

print $doc->saveHTML($doc->documentElement);

// <div>
// <p>This is some text inside a text-node</p>
// <p>This is text without any node and should be wrapped with a text-node</p>
// </div>

The key is to select all the non-empty text nodes without p ancestors, using the //text()[not(ancestor::p)][normalize-space()] XPath query.

Using DOMDocument to wrap all nodes between header tags in div

My initial thoughts were based around manipulating the output buffer as I had failed to read the opening paragraph properly and the following function was used as a callback to ob_start.

You might notice the use of $tags towards the start of the function and followed by the rather complicted $query - the $tags is used later to aid populating found nodes and to ensure we stop when the next node found is in that $tags array - rather than write the pattern and maintain this array independently I figured it would be more flexible like this.

Essentially it works like this: Construct a query pattern from the supplied $tags array and use that to query the HTML DOM. If there are matching nodes, iterate through the collection and add the found node( a header ) to an array. Then iterate over the siblings of the found node and add these to the same new array. Before the loop moves to the next node in the collection save this array before repeating the process. Once all the discovered nodes have been processed it is time to create the container DIV elements ensuring that all children are once again populated.

<?php
#https://stackoverflow.com/questions/59234379/using-domdocument-to-wrap-all-nodes-between-header-tags-in-div/59235431#59235431

function wrapcallback( $buffer ){
global $use_output_buffer;

$delimiter='#';
$tags=array('h1','h2','h3','h4','h5','h6');
$query=implode('|', explode( $delimiter, sprintf( '//%s', implode( sprintf( '%s//', $delimiter ), $tags ) ) ) );
$keepers=array();
$parents=array();

libxml_use_internal_errors( true );
$dom=new DOMDocument;
$dom->validateOnParse=false;
$dom->recover=true;
$dom->strictErrorChecking=false;
$dom->preserveWhiteSpace=true;
$dom->loadHTML( $buffer );
$errors = libxml_get_errors();
libxml_clear_errors();

$xp=new DOMXPath( $dom );
$col=$xp->query( $query );

if( $col->length > 0 ){
foreach( $col as $node ){

$parents[]=$node->parentNode;
$nodes=array( $node );

while( $node = $node->nextSibling ){
if( in_array( $node->nodeName, $tags ) )break;
if( $node->nodeType==XML_ELEMENT_NODE )$nodes[]=$node;
}
$keepers[]=$nodes;
}
}

foreach( $keepers as $index => $obj ){
$div=$dom->createElement('div');
$parents[ $index ]->appendChild( $div );
foreach( $obj as $child )$div->appendChild( $child );
}

$keepers = $parents = $xp = $div = null;
echo $dom->saveHTML();
};

$html="
<!DOCTYPE html>
<html lang='en'>
<head>
<title>It's a Christmas Wrapper!</title>
<style>
body{
background:url( https://storage.needpix.com/rsynced_images/christmas-wallpaper-1480711266Vyi.jpg );
background-repeat:repeat;
color:white;
}

</style>
</head>
<body>

<h1>Section 1</h1>
<p>Paragraph Text</p>
<p>Paragraph Text</p>

<h2>Section 2</h2>
<p>Paragraph Text</p>
<img src='/images/laracroft.png' />
<p>Further Paragraph Text</p>

<h1>Section 3</h1>
<p>Paragraph Text</p>
<p>Paragraph Text</p>

</body>
</html>";

wrapcallback( $html );

?>

Wrapping With Divs PHP Dom

The code below wraps each <h2> and its .episode siblings in .season container

   $page = '<div class="block">
<h2>Season 1</h2>
<div class="episode"><a href="s1ep1.com">Episode 1</a></div>
<div class="episode"><a href="s1ep2.com">Episode 2</a></div>
<h2>Season 2</h2>
<div class="episode"><a href="s2ep1.com">Episode 1</a></div>
<div class="episode"><a href="s2ep1.com">Episode 2</a></div>
</div>';

$dom = new DOMDocument();

$origVal = libxml_use_internal_errors(true);
@$dom->loadHTML($page);
libxml_clear_errors();
libxml_use_internal_errors($origVal);

//create a tmeplate 'season' div
$season = $dom->createElement('div');
$season->setAttribute('class', 'season');

//get all '.block' divs using xpath
$xpath = new DOMXPath($dom);
$divs = $xpath->query("//*[@class='block']");

$clones = array();
$clone = '';

foreach($divs as $currDiv) {

//check if the 'block' contains any <h2> elemnts, if not, skip this block
if(!count($currDiv->getElementsByTagName('h2'))) {
continue;
}

foreach($currDiv->childNodes as $child) {

if(in_array($child->nodeName, array(
'#text',
'#comment'
))
) {
//ignore white space (and text content), and comments in 'block' div
continue;
}

if($child->nodeName == 'h2') {
if($clone) {
//save all clones of 'season' template div in an array for further use
$clones[] = $clone;
}

$clone = $season->cloneNode(true);
}

//this is the tricky part. If we do not append a clone of original div, then it actually moves the div to $clone. This changes HTML structure and disrupts the current loop
//so we append the clones of child to the 'season' div
if($child->nodeName == 'h2' || $child->getAttribute('class') == 'episode') {
$clone->appendChild($child->cloneNode(true));
}
}
$clones[] = $clone;

//remove all children of current 'block' div
while($currDiv->childNodes->length) {
$currDiv->removeChild($currDiv->firstChild);
}

//isnert all 'season' nodes in it
foreach($clones as $c) {
$currDiv->appendChild($c);
}
}

echo $dom->saveHTML();


Related Topics



Leave a reply



Submit