HTML manipulation with PHP

While it would be nice that WordPress plugins to always offer a way to overwrite their template files, that’s not the reality.

This is especially frustrating when working with libraries like Alpine.js, which makes use of directives.

Parenthesis: A directive is just a HTML attribute that has a special meaning for the JS library, for example:

<div x-bind:class="{ '--open': isOpen }"></div>

Without Alpine.js the x-bind:class is a meaningless attribute that doesn't do anything.

DomDocument and DOMXPath

When there is no direct way of modifying the HTML, I tend to reach for the DomDocument class.

With DomDocument you can parse HTML code and remove parts of it, add new elements or attributes.

If you never used DomDocument before, here's how it typically looks and works:

$normalizedHtml = mb_convert_encoding($htmlContentThatComesFromSomewhere, 'HTML-ENTITIES', 'UTF-8');

// New up the objects and pass the HTML that you have
$dom = new DOMDocument();
$dom->loadHTML($normalizedHtml);

$xPath = new DOMXPath($dom);
// Find all <sup> tags
$supTags = $xPath->query('//sup');

foreach ($supTags as $node) {
    // Get whatever that is in the <sup> tag
    $nodeContent = $node->nodeValue;

    // Create a new <a> tag
    $anchor = $dom->createElement('a');
    $anchor->setAttribute('href', '#');
    // Add the directives that are just attributes with certain value
    $anchor->setAttribute('x-data', '{}');
    $anchor->setAttribute('x-on:click', '$dispatch("open-footnotes")');
    // Set contents of the <a> tag to be the same as the <sup> tag
    $anchor->nodeValue = $nodeContent;

    // Replace the <sup> tag content with the <a> tag
    $node->nodeValue = '';
    $node->appendChild($anchor);
}

// Save the modification
$modifiedHtml = $dom->saveHTML();

This turns a HTML like this:

<p>Lorem ipsizzle<sup>1</sup> dolizzle sit break it down...</p>

into this:

<p>Lorem ipsizzle<sup><a href="#" x-data="{}" x-on:click="$dispatch('open-footnotes')">1</a></sup> dolizzle sit break it down...</p>

By the way, there are many wrapper packages that offer the same functionality and more with nicer API.

It's also handy to know that tools for converting CSS selector to XPath queries exist. This is because XPath queries quickly get more verbose than you expect. Selecting a plain HTML tag is straightforward, but selecting an element that has the has-blue-color class looks like this:

$xPath->query('.//\*\[contains(concat(" ",normalize-space(@class)," ")," has-blue-color ")\]');