14

How to Change innerHTML of a php DOMElement ?

iavian
  • 197
  • 1
  • 1
  • 5
  • You can't, directly. You'll have to have an intermediary function to take a string and turn it into an array of DOMElements. You could write it yourself, although I'm sure something like that exists on the web. – Sasha Chedygov May 06 '10 at 03:06
  • There is no innerHTML property in php DOMElement class. Try looking here: http://php.net/manual/en/class.domelement.php and here: http://www.php.net/manual/en/class.domtext.php – Babiker May 06 '10 at 03:39
  • If you stroll by, feel free to vote to **reopen** as this is *not* a duplicate of the linked question. – Walf Jun 21 '21 at 02:22

10 Answers10

14

Another solution:

1) create new DOMDocumentFragment from the HTML string to be inserted; 2) remove old content of our element by deleting its child nodes; 3) append DOMDocumentFragment to our element.

function setInnerHTML($element, $html)
{
    $fragment = $element->ownerDocument->createDocumentFragment();
    $fragment->appendXML($html);
    while ($element->hasChildNodes())
        $element->removeChild($element->firstChild);
    $element->appendChild($fragment);
}

Alternatively, we can replace our element with its clean copy and then append DOMDocumentFragment to this clone.

function setInnerHTML($element, $html)
{
    $fragment = $element->ownerDocument->createDocumentFragment();
    $fragment->appendXML($html);
    $clone = $element->cloneNode(); // Get element copy without children
    $clone->appendChild($fragment);
    $element->parentNode->replaceChild($clone, $element);
}

Test:

$doc = new DOMDocument();
$doc->loadXML('<div><span style="color: green">Old HTML</span></div>');
$div = $doc->getElementsByTagName('div')->item(0);
echo $doc->saveHTML();

setInnerHTML($div, '<p style="color: red">New HTML</p>');
echo $doc->saveHTML();

// Output:
// <div><span style="color: green">Old HTML</span></div>
// <div><p style="color: red">New HTML</p></div>
Guest
  • 141
  • 1
  • 3
  • 1
    While this code snippet may solve the question, [including an explanation](//meta.stackexchange.com/questions/114762/explaining-entirely-code-based-answers) really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. Please also try not to crowd your code with explanatory comments, this reduces the readability of both the code and the explanations! – Jonathan Lam Aug 07 '16 at 15:11
  • This doesn't work properly if the content is not a well-formed XML fragment (so tags like `
    ` don't auto-close). This leads to an empty document fragment. For all other cases, it works just fine.
    – Keyacom Mar 26 '23 at 16:36
8

I needed to do this for a project recently and ended up with an extension to DOMElement: http://www.keyvan.net/2010/07/javascript-like-innerhtml-access-in-php/

Here's an example showing how it's used:

<?php
require_once 'JSLikeHTMLElement.php';
$doc = new DOMDocument();
$doc->registerNodeClass('DOMElement', 'JSLikeHTMLElement');
$doc->loadHTML('<div><p>Para 1</p><p>Para 2</p></div>');
$elem = $doc->getElementsByTagName('div')->item(0);

// print innerHTML
echo $elem->innerHTML; // prints '<p>Para 1</p><p>Para 2</p>'

// set innerHTML
$elem->innerHTML = '<a href="http://fivefilters.org">FF</a>';

// print document (with our changes)
echo $doc->saveXML();
?>
Keyvan
  • 502
  • 5
  • 14
3

I ended up making this function using a few functions from other people on this page. I changed the one from Joanna Goch the way that Peter Brand says mostly, and also added some code from Guest and from other places.

This function does not use an extension, and does not use appendXML (which is very picky and breaks even if it sees one BR tag that is not closed) and seems to be working good.

function set_inner_html( $element, $content ) {
    $DOM_inner_HTML = new DOMDocument();
    $internal_errors = libxml_use_internal_errors( true );
    $DOM_inner_HTML->loadHTML( mb_convert_encoding( $content, 'HTML-ENTITIES', 'UTF-8' ) );
    libxml_use_internal_errors( $internal_errors );
    $content_node = $DOM_inner_HTML->getElementsByTagName('body')->item(0);
    $content_node = $element->ownerDocument->importNode( $content_node, true );
    while ( $element->hasChildNodes() ) {
        $element->removeChild( $element->firstChild );
    }
    $element->appendChild( $content_node );
}
Nikolay
  • 359
  • 3
  • 7
2

I think the best thing you can do is come up with a function that will take the DOMElement that you want to change the InnerHTML of, copy it, and replace it.

In very rough PHP:

function replaceElement($el, $newInnerHTML) {
    $newElement = $myDomDocument->createElement($el->nodeName, $newInnerHTML);
    $el->parentNode->insertBefore($newElement, $el);
    $el->parentNode->removeChild($el);

    return $newElement;
}

This doesn't take into account attributes and nested structures, but I think this will get you on your way.

Greg
  • 21,235
  • 17
  • 84
  • 107
Michael T. Smith
  • 568
  • 3
  • 22
1

It seems that appendXML doesn't work always - for example if you try to append XML with 3 levels. Here is the function I wrote that always work (you want to set $content as innerHTML to $element):

function setInnerHTML($DOM, $element, $content) {
    $DOMInnerHTML = new DOMDocument();
    $DOMInnerHTML->loadHTML($content);
    $contentNode = $DOMInnerHTML->getElementsByTagName('body')->item(0)->firstChild;
    $contentNode = $DOM->importNode($contentNode, true);
    $element->appendChild($contentNode);
    return $elementNode;
}
  • 1
    The variable `$elementNode` returned is undefined - shouldn't that be `$contentNode`? You could simplify the function by using `$element->ownerDocument` instead of having to pass in `$DOM`. This imports only the first child of whatever $content contains. To import the entire fragment, remove the `->firstChild' on line 4. – Peter Brand Jan 01 '16 at 11:25
  • This doesn't support multibyte encodings, unless you replace `$content` in `$DOMInnerHTML->loadHTML($content)` with `mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8')` (so the `mbstring` extension is required). But there's a bigger problem: the fact it implicitly wraps in a `

    ` element (unless there is already a root element).

    – Keyacom Mar 26 '23 at 17:03
0

Here is a replace by class function I just wrote:

It will replace the innerHtml of a class. You can also specify the node type eg. div/p/a etc.

function replaceInnerHtmlByClass($html, $replace=null, $class=null, $nodeType=null){
  if(!$nodeType){ $nodeType = '*'; }
  $dom = new DOMDocument();
  $dom->loadHTML($html);
  $xpath = new DOMXPath($dom);
  $nodes = $xpath->query("//{$nodeType}[contains(concat(' ', normalize-space(@class), ' '), '$class')]");
  foreach($nodes as $node) {
    while($node->childNodes->length){
      $node->removeChild($node->firstChild);
    }
    $fragment = $dom->createDocumentFragment();
    $fragment->appendXML($replace);
    $node->appendChild($fragment);
  }
  return $dom->saveHTML($dom->documentElement);
}

Here is another function I wrote to remove nodes with a specific class but preserving the inner html.

Setting replace to true will discard the inner html.

Setting replace to any other content will replace the inner html with the provided content.

function stripTagsByClass($html, $class=null, $nodeType=null, $replace=false){
  if(!$nodeType){ $nodeType = '*'; }
  $dom = new DOMDocument();
  $dom->loadHTML($html);
  $xpath = new DOMXPath($dom);
  $nodes = $xpath->query("//{$nodeType}[contains(concat(' ', normalize-space(@class), ' '), '$class')]");
  foreach($nodes as $node) {
    $innerHTML = '';
    $children = $node->childNodes;
    foreach($children as $child) {
      $tmp = new DOMDocument();
      $tmp->appendChild($tmp->importNode($child,true));       
      $innerHTML .= $tmp->saveHTML();
    }
    $fragment = $dom->createDocumentFragment();
    if($replace !== null && $replace !== false){
      if($replace === true){ $replace = ''; }
      $innerHTML = $replace;
    }
    $fragment->appendXML($innerHTML);
    $node->parentNode->replaceChild($fragment, $node);
  }
  return $dom->saveHTML($dom->documentElement);
}

Theses functions can easily be adapted to use other attributes as the selector.

I only needed it to evaluate the class attribute.

Dieter Gribnitz
  • 5,062
  • 2
  • 41
  • 38
0

Have a look at this library PHP Simple HTML DOM Parser http://simplehtmldom.sourceforge.net/

It looks pretty straightforward. You can change innertextproperty of your elements. It might help.

Jull
  • 271
  • 2
  • 5
  • 15
  • 1
    PHP, not JavaScript... PHP's implementation of DOM does not have an `innerHTML` property. – Sasha Chedygov May 06 '10 at 03:04
  • 1
    yes, my fault. but php5 HTML DOM parser has 'innertext'. http://simplehtmldom.sourceforge.net/ – Jull May 06 '10 at 03:11
  • That's not the library the OP was referring to. See: http://php.net/manual/en/book.dom.php But you could edit your answer and link to the library you found and see if the OP wants to switch. – Sasha Chedygov May 06 '10 at 03:54
0

Developing on from Joanna Goch's answer, this function will insert either a text node or an HTML fragment:

function nodeFromContent($node, $content) {
    //creates a text node, or dom node if content contains html 
    $lt = strpos($content, '<');
    $gt = strrpos($content, '>');
    if (!($lt === false || $gt === false) && $gt > $lt) {
        //< followed by > means potentially contains HTML
        $DOMInnerHTML = new DOMDocument();
        $DOMInnerHTML->loadHTML($content);
        $contentNode = $DOMInnerHTML->getElementsByTagName('body')->item(0);
        $newNode = $node->ownerDocument->importNode($contentNode, true);
    } else {
        $newNode = $node->ownerDocument->createTextNode($content); 
    }
    return $newNode;
}

usage

$newNode = nodeFromContent($node, $content);
$node->parentNode->insertBefore($newNode, $node);
//or $node->appendChild($newNode) depending on what you require
Peter Brand
  • 576
  • 6
  • 20
-1

here is how you do it:

$doc = new DOMDocument('');
$label = $doc->createElement('label');

$label->appendChild($doc->createTextNode('test'));
$li->appendChild($label);

echo $doc->saveHTML();
Christopher Pelayo
  • 792
  • 11
  • 30
-1
function setInnerHTML($DOM, $element, $innerHTML) {
  $node = $DOM->createTextNode($innerHTML);
  $element->appendChild($node);
}
Tobi
  • 1
  • 1