Convert spaces between PRE tags, via DOM parser

前端 未结 2 849
小蘑菇
小蘑菇 2021-01-15 22:15

Regex was my original idea as a solution, although it soon became apparent a DOM parser would be more appropriate... I\'d like to convert spaces to   b

2条回答
  •  爱一瞬间的悲伤
    2021-01-15 22:24

    This is somewhat tricky when you want to insert   Entities without DOM converting the ampersand to & entities because Entities are nodes and spaces are just character data. Here is how to do it:

    $dom = new DOMDocument;
    $dom->loadHtml($html);
    $xp = new DOMXPath($dom);
    foreach ($xp->query('//text()[ancestor::pre]') as $textNode)
    {
        $remaining = $textNode;
        while (($nextSpace = strpos($remaining->wholeText, ' ')) !== FALSE) {
            $remaining = $remaining->splitText($nextSpace);
            $remaining->nodeValue = substr($remaining->nodeValue, 1);
            $remaining->parentNode->insertBefore(
                $dom->createEntityReference('nbsp'),
                $remaining
            );
        }
    }
    

    Fetching all the pre elements and working with their nodeValues doesnt work here because the nodeValue attribute would contain the combined DOMText values of all the children, e.g. it would include the nodeValue of the span childs. Setting the nodeValue on the pre element would delete those.

    So instead of fetching the pre nodes, we fetch all the DOMText nodes that have a pre element parent somewhere up on their axis:

    DOMElement pre
        DOMText "abc 123"         <-- picking this
        DOMElement span
           DOMText "abc 123"      <-- and this one
    DOMElement
        DOMText "123 123"         <-- and this one
    

    We then go through each of those DOMText nodes and split them into separate DOMText nodes at each space. We remove the space and insert a nbsp Entity node before the split node, so in the end you get a tree like

    DOMElement pre
        DOMText "abc"
        DOMEntity nbsp
        DOMText "123"
        DOMElement span
           DOMText "abc"
           DOMEntity nbsp
           DOMText "123"
    DOMElement
        DOMText "123"
        DOMEntity nbsp
        DOMText "123"
    

    Because we only worked with the DOMText nodes, any DOMElements are left untouched and so it will preserve the span elements inside the pre element.

    Caveat:

    Your snippet is not valid because it doesnt have a root element. When using loadHTML, libxml will add any missing structure to the DOM, which means you will get your snippet including a DOCTYPE, html and body tag back.

    If you want the original snippet back, you'd have to getElementsByTagName the body node and fetch all the children to get the innerHTML. Unfortunately, there is no innerHTML function or property in PHP's DOM implementation, so we have to do that manually:

    $innerHtml = '';
    foreach ($dom->getElementsByTagName('body')->item(0)->childNodes as $child) {
        $tmp_doc = new DOMDocument();
        $tmp_doc->appendChild($tmp_doc->importNode($child,true));
        $innerHtml .= $tmp_doc->saveHTML();
    }
    echo $innerHtml;
    

    Also see

    • How to get innerHTML of DOMNode?
    • DOMDocument in php
    • https://stackoverflow.com/search?q=user%3A208809+dom

提交回复
热议问题