nunomaduro/termwind

Table renderer

butschster opened this issue · 1 comments

It would be great to render tables from HTML

Something like this

<table style="box">
    <thead>
        <tr>
            <td>1</td>
            <td>2</td>
            <td>3</td>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><div class="width-5 px-4">4</div></td>
            <td rowspan="2">9</td>
            <td>6</td>
        </tr>
        <tr>
            <td class="width-10">7</td>
            <td colspan="2"><div>4</div></td>
        </tr>
    </tbody>
</table>

To this

┌───────────────┬───┬───┐
│ 1             │ 2 │ 3 │
├───────────────┼───┼───┤
│     4         │ 9 │ 6 │
│ 7             │   │ 4 │
└───────────────┴───┴───┘

I scetched a concept of TableRenderer

<?php
declare(strict_types=1);

namespace Termwind\Html;

use DOMNode;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Output\BufferedOutput;
use Termwind\Components\Element;
use Termwind\HtmlRenderer;
use function Termwind\div;

final class TableRenderer
{
    private Table $table;
    private BufferedOutput $output;
    private string $styles;

    public function __construct(DOMNode $node)
    {
        $this->output = new BufferedOutput();
        $this->table = new Table($this->output);

        $style = $node->getAttribute('style');
        $this->styles = $node->getAttribute('class');

        if (!empty($style)) {
            $this->table->setStyle($style);
        }

        $this->convert($node);
    }

    private function convert(DOMNode $node)
    {
        foreach ($node->childNodes as $child) {
            if ($child->nodeName === 'thead') {
                $this->parseHeader($child);
            }

            if ($child->nodeName === 'tbody') {
                $this->parseBody($child);
            }

            if ($child->nodeName === 'tr') {
                foreach ($this->parseRow($child) as $row) {
                    $this->table->addRow($row);
                }
            }
        }
    }

    private function parseHeader(DOMNode $node)
    {
        foreach ($node->childNodes as $child) {
            if ($child->nodeName === 'tr') {
                foreach ($this->parseRow($child) as $row) {
                    $this->table->setHeaders($row);
                }
            }
        }
    }

    private function parseBody(DOMNode $node)
    {
        foreach ($node->childNodes as $child) {
            if ($child->nodeName === 'tr') {
                foreach ($this->parseRow($child) as $row) {
                    $this->table->addRow($row);
                }
            }
        }
    }

    private function parseRow(DOMNode $node): \Iterator
    {
        $row = [];

        foreach ($node->childNodes as $child) {
            if ($child->nodeName === 'th' || $child->nodeName === 'td') {
                $row[] = new TableCell(
                    (string)(new HtmlRenderer())->parse($child->ownerDocument->saveXML($child)),
                    [
                        'colspan' => max((int)$child->getAttribute('colspan'), 1),
                        'rowspan' => max((int)$child->getAttribute('rowspan'), 1),
                    ]
                );
            }
        }

        if ($row !== []) {
            yield $row;
        }
    }

    public function render(): Element
    {
        $this->table->render();

        return div($this->output->fetch(), $this->styles);
    }
}

And changes for HtmlRenderer

    ...

    /**
     * Convert a tree of DOM nodes to a tree of termwind elements.
     */
    private function convert(DOMNode $node): Components\Element|string
    {
        ...

        if ($node->nodeName === 'table') {
            return (new TableRenderer($node))->render();
        }

        ...
    }
    ...

I hope my sketch will be a good starting point for better solution with ability to use background colors and text colors in a table cell.

Thank you for an amazing package. I want to use it in my project, but I need tables as well.

Feel free to make a pull request with your proposal, and a screenshot of what's the "actual" render on the console.