Search code examples
phppreg-replacedomdocumentpreg-replace-callback

How to realize a code conversion from nested DVIs to table including class to style?


Here's the situation: I want to change nested DIV elements to table elements, I want the classes to be set as inline style.

I now have an example array with the classes and the style attributes:

$classes    = array(
            'container' => 'width: 100%;padding-right: 15px;padding-left: 15px;margin-right: auto;margin-left: auto;',
            'card' => 'position: relative;display: -ms-flexbox;display: flex;-ms-flex-direction: column;flex-direction: column;min-width: 0;word-wrap: break-word;background-color: #fff;background-clip: border-box;border: 1px solid rgba(0, 0, 0, 0.125);border-radius: 0.25rem;',
        );

This code is then to be converted into the following:

<div class="container">
    01
    <div class="card">
        02
    </div>
</div>

This code is then to be created from this:

<table>
    <tr>
        <td class="container" style="width: 100%;padding-right: 15px;padding-left: 15px;margin-right: auto;margin-left: auto;">
            01
            <table>
                <tr>
                    <td class="card" style="position: relative;display: -ms-flexbox;display: flex;-ms-flex-direction: column;flex-direction: column;min-width: 0;word-wrap: break-word;background-color: #fff;background-clip: border-box;border: 1px solid rgba(0, 0, 0, 0.125);border-radius: 0.25rem;">
                        02
                    </td>                
                </tr>
            </table>
        </td>
    </tr>
</table>

Does anyone have any idea how I could do something like this? I've been experimenting with preg_replace_callback and DOMDocument since Friday. Unfortunately everything without success.


Solution

  • There's nothing particular. All you have to take care is:

    • to replace the nodes in reverse order
    • to use a deep copy of children nodes (using DOMNode::cloneNode with the parameter set to true)

    I assume you are working with parts of html (not a full document with <html> and <body> tags), but if it isn't the case you can easily change the code yourself:

    $cssClasses = [
        'container' => 'width: 100%;padding-right: 15px;padding-left: 15px;margin-right: auto;margin-left: auto;',
        'card' => 'position: relative;display: -ms-flexbox;display: flex;-ms-flex-direction: column;flex-direction: column;min-width: 0;word-wrap: break-word;background-color: #fff;background-clip: border-box;border: 1px solid rgba(0, 0, 0, 0.125);border-radius: 0.25rem;'
    ];
    
    $html = <<<'EOD'
    <div class="container">
        01
        <div class="card">
            02
        </div>
    </div>
    EOD;
    
    $dom = new DOMDocument;
    $dom->loadHTML($html);
    
    $divNL = $dom->getElementsByTagName('div');
    
    for ($i = $divNL->length - 1; $i >= 0; $i--) {
        $node = $divNL->item($i);
    
        $table = $dom->createElement('table');
        $tr = $dom->createElement('tr');
        $td = $dom->createElement('td');
        $table->appendChild($tr);
        $tr->appendChild($td);
    
        foreach ($node->attributes as $attr) {
            $td->appendChild($attr);
        }
    
        $styles = array_reduce(
            preg_split('~\s+~u', $td->getAttribute('class')),
            function($c, $i) use ($cssClasses) {
                return isset($cssClasses[$i]) ? $cssClasses[$i] . $c : $c;
            },
            $td->getAttribute('style')
        );
    
        if ( !empty($styles) )
            $td->setAttribute('style', $styles);
    
    
        foreach ($node->childNodes as $childNode) {
            $td->appendChild($childNode->cloneNode(true));
        }
    
        $node->parentNode->replaceChild($table, $node);
    }
    
    $result = '';
    
    foreach ($dom->getElementsByTagName('body')->item(0)->childNodes as $childNode) {
        $result .= $dom->saveHTML($childNode);
    }
    
    echo $result;
    

    demo