Algoritmo para renderizar sistema de bloques HTML

Estructuras para almacenar datos de contenido HTML y algoritmo para renderizar el código web a partir de árbol de nodos

Algoritmo para renderizar sistema de bloques HTML

De árbol de nodos HTML a tabla

Algoritmo para renderizar sistema de bloques HTML

id parent value type weight
1 null Hello world! h1 100
2 null null container 100
3 null
<h1>Hello world!</h1>
<div>
    <div class="text">
        <p>lorem ipsum dolor</p>
        <p>sit amet consectetur</p>
        <p><img src="https://picsum.photos/1280/720"></p>
    </div> 
    <ul>
        <li>lorem ipsum dolor</li>
        <li>sit amet consectetur</li>
    </ul>
</div>
code-plain 100
4 2 null text 100
5 2 null ul 100
6 5 lorem ipsum dolor item 100
7 5 sit amet consectetur item 100
8 4 lorem ipsum dolor item 90
9 4 sit amet consectetur item 100
10 4 https://picsum.photos/1280/720 img 100

Representación de tabla en PHP

$items = [
    [
        "id" => "1",
        "parent" => null,
        "value" => "Hello world!",
        "weight" => 100,
        "type" => "h1",
        "children" => [],
    ],
    [
        "id" => "2",
        "parent" => null,
        "value" => null,
        "weight" => 100,
        "type" => "container",
        "children" => [],
    ],
    [
        "id" => "3",
        "parent" => null,
        "value" => '<h1>Hello world!</h1>
        <div>
            <div class="text">
                <p>lorem ipsum dolor</p>
                <p>sit amet consectetur</p>
                <p><img src="https://picsum.photos/1280/720"></p>
            </div> 
            <ul>
                <li>lorem ipsum dolor</li>
                <li>sit amet consectetur</li>
            </ul>
        </div>',
        "weight" => 100,
        "type" => "code-plain",
        "children" => [],
    ],
    [
        "id" => "4",
        "parent" => "2",
        "value" => null,
        "weight" => 100,
        "type" => "text",
        "children" => [],
    ],
    [
        "id" => "5",
        "parent" => "2",
        "value" => null,
        "weight" => 100,
        "type" => "ul",
        "children" => [],
    ],
    [
        "id" => "6",
        "parent" => "5",
        "value" => "lorem ipsum dolor",
        "weight" => 100,
        "type" => "item",
        "children" => [],
    ],
    [
        "id" => "7",
        "parent" => "5",
        "value" => "sit amet consectetur",
        "weight" => 100,
        "type" => "item",
        "children" => [],
    ],
    [
        "id" => "8",
        "parent" => "4",
        "value" => "lorem ipsum dolor",
        "weight" => 90,
        "type" => "item",
        "children" => [],
    ],
    [
        "id" => "9",
        "parent" => "4",
        "value" => "sit amet consectetur",
        "weight" => 100,
        "type" => "item",
        "children" => [],
    ],
    [
        "id" => "10",
        "parent" => "4",
        "value" => "https://picsum.photos/1280/720",
        "weight" => 100,
        "type" => "img",
        "children" => [],
    ],
];

Código del algoritmo en PHP

Clase base para renderizar bloques de contenido

class HyperItemsRenderDefault implements \Stringable
{
    protected mixed $value;
    protected array $children;

    public function __construct(
        mixed $value = null,
        array $children = []
    ) {
        $this->value = $value;
        $this->children = $children;
    }

    public function render(): string
    {
        return json_encode([
            'value' => $this->value,
            'children' => $this->children,
        ]);
    }

    public function __toString(): string
    {
        return $this->render();
    }
}

Clases derivadas para cada bloque HTML


class HyperItemsRenderForContainer extends HyperItemsRenderDefault
{
    public function render(): string
    {
        $content = implode('', $this->children);
        return "<div>$content</div>";
    }
}

class HyperItemsRenderForText extends HyperItemsRenderDefault
{
    public function render(): string
    {
        $content = array_reduce($this->children, fn($carry, $item) => $carry . "<p>$item</p>");

        return "<div>$content</div>";
    }
}

class HyperItemsRenderForImg extends HyperItemsRenderDefault
{
    public function render(): string
    {
        return "<img src=\"{$this->value}\">";
    }
}
class HyperItemsRenderForH1 extends HyperItemsRenderDefault
{
    public function render(): string
    {
        return "<h1>{$this->value}</h1>";
    }
}
class HyperItemsRenderForItem extends HyperItemsRenderDefault
{
    public function render(): string
    {
        return $this->value;
    }
}

class HyperItemsRenderForUl extends HyperItemsRenderDefault
{
    public function render(): string
    {
        $content = array_reduce($this->children, fn($carry, $item) => $carry . "<li>$item</li>");

        return "<ul>$content</ul>";
    }
}

class HyperItemsRenderForPlainCode extends HyperItemsRenderDefault
{
    public function render(): string
    {
        $value = htmlspecialchars($this->value);
        return "<pre><code>$value</code></pre>";
    }
}

Mapeo de clases y tipos de bloque de contenido

$RenderClasses = [
    'default' => HyperItemsRenderDefault::class,
    'container' => HyperItemsRenderForContainer::class,
    'text' => HyperItemsRenderForText::class,
    'img' => HyperItemsRenderForImg::class,
    'h1' => HyperItemsRenderForH1::class,
    'item' => HyperItemsRenderForItem::class,
    'ul' => HyperItemsRenderForUl::class,
    'code-plain' => HyperItemsRenderForPlainCode::class,
];

Algoritmo para convertir de tabla de datos a árbol de nodos

$nodesMap = []; // Nodes in Map structure
$nodesTree = []; // Nodes in Tree structure

// Generate Map structure
foreach ($items as $k => $item) {
    $nodesMap[$item['id']] = &$items[$k];
}

// Nesting nodes and generate tree structure
foreach ($nodesMap as $k => $item) {
    if (!is_null($item['parent'])) {
        $nodesMap[$item['parent']]['children'][] = &$nodesMap[$k];
    } else {
        $nodesTree[] = &$nodesMap[$k];
    }
}

Algoritmo para asignar la lógica de renderizado y poder imprimir HTML final

// Instantiate the correct Render Object for each node
foreach ($nodesMap as $k => $item) {
    $render = $RenderClasses[$item['type']] ?? null;
    if (!isset($render)) {
        $render = $RenderClasses['default'];
    }
    $nodesMap[$k] = new $render($item['value'], $item['children']);
}

$HTMLrender = array_reduce($nodesTree, fn($carry, $item) => $carry . $item); // Final HTML

echo $HTMLrender;

Código HTML resultante

<h1>Hello world!</h1>
<div>
    <div>
        <p>lorem ipsum dolor</p>
        <p>sit amet consectetur</p>
        <p><img src="https://picsum.photos/1280/720" /></p>
    </div>
    <ul>
        <li>lorem ipsum dolor</li>
        <li>sit amet consectetur</li>
    </ul>
</div>
<pre><code>&lt;h1&gt;Hello world!&lt;/h1&gt;
&lt;div&gt;
    &lt;div class="text"&gt;
        &lt;p&gt;lorem ipsum dolor&lt;/p&gt;
        &lt;p&gt;sit amet consectetur&lt;/p&gt;
        &lt;p&gt;&lt;img src="https://picsum.photos/1280/720"&gt;&lt;/p&gt;
    &lt;/div&gt; 
    &lt;ul&gt;
        &lt;li&gt;lorem ipsum dolor&lt;/li&gt;
        &lt;li&gt;sit amet consectetur&lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</code></pre>

Hello world!

lorem ipsum dolor

sit amet consectetur

  • lorem ipsum dolor
  • sit amet consectetur
<h1>Hello world!</h1>
<div>
    <div class="text">
        <p>lorem ipsum dolor</p>
        <p>sit amet consectetur</p>
        <p><img src="https://picsum.photos/1280/720"></p>
    </div> 
    <ul>
        <li>lorem ipsum dolor</li>
        <li>sit amet consectetur</li>
    </ul>
</div>