phpdomdocumentdomxpathdom-node

How to insert HTML into a text node with DOMNode in PHP


I have a PHP script that uses DOMDocument and DOMXPath to find and replace merge codes in an HTML template. A simple example might be:

<html>
  <head>
    <title>Title</title>
  </head>
  <body>
    <p>Hello {greeting}!</p>
    <table><tr>
      <td>Details:</td>
      <td>{details}</td>
    </tr></table>
  </body>
</html>

The following code substituted fields based on an associative array where the key matches the merge field:

private function substituteFields (DOMNode $node, $fields)
{
    $x = new DOMXPath ($node->ownerDocument);
    foreach ($fields as $field => $value)
    {
        $query = $x->query (".//text()[contains(., '{" . $field . "}')]", $node);

        foreach ($query as $subnode)
        {
            $subnode->nodeValue = str_replace ("{" . $field . "}", $value, $subnode->nodeValue);
        }
    }
}

This is working well.

However, some merge codes will need HTML substituted into them:

$fields ['greeting'] = "Joe Soap";
$fields ['details'] = "<div class='details'>Details here</div>";

The substitution is happening, but the HTML is being escaped, which is probably a sensible idea in most cases.

Can I work around this?


Solution

  • I got around this a little clumsily, but it works for now. If there are better solutions, I'll happily modify my answer!

    Essentially, this looks for an opening tag "<" character within the substitution text. If it finds one, it invokes an HTML substitution method which I modified from this question, answer and comments.

    It has a limitation in that it can't substitute HTML mid-node. For example, the following will not work:

    <p>Here is a bit of {html_code}</p>
    

    But that can be made to work like this:

    <p>Here is a bit of <span>{html_code}</span></p>
    

    Here is the modified code:

    private function substituteFields (DOMNode $node, $fields)
    {
        $x = new DOMXPath ($node->ownerDocument);
        foreach ($fields as $field => $value)
        {
            $query = $x->query (".//text()[contains(., '{" . $field . "}')]", $node);
    
            foreach ($query as $subnode)
            {
                $replace = str_replace ("{" . $field . "}", $value, $subnode->nodeValue);
    
                if (substr ($replace, 0, 1) != "<")
                {
                    $subnode->nodeValue = $replace;
                }
                else
                {
                    $this->appendHTML ($subnode, $replace);
                }
            }
        }
    }
    
    private function appendHTML (DOMNode $parent, $source)
    {
        $tmpDoc = new DOMDocument();
        $tmpDoc->loadHTML ($source);
        foreach ($tmpDoc->getElementsByTagName ('body')->item (0)->childNodes as $node)
        {
            $importedNode = $parent->ownerDocument->importNode ($node, true);
            $parent->parentNode->replaceChild ($importedNode, $parent);
        }
    }