• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

un-zero-un / Isocontent / 19885920020

03 Dec 2025 07:29AM UTC coverage: 94.505% (+0.8%) from 93.75%
19885920020

push

gha

344 of 364 relevant lines covered (94.51%)

130.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.11
/src/Renderer/HTMLRenderer.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Isocontent\Renderer;
6

7
use Isocontent\AST\BlockNode;
8
use Isocontent\AST\Node;
9
use Isocontent\AST\NodeList;
10
use Isocontent\AST\TextNode;
11
use Isocontent\Specs\BlockArgumentMatch;
12
use Isocontent\Specs\BlockTypeMatch;
13
use Isocontent\Specs\Specification;
14

15
final class HTMLRenderer implements Renderer
16
{
17
    /**
18
     * @var list<array{Specification, string}>
19
     */
20
    private array $tags;
21

22
    /**
23
     * @param list<array{Specification, string}> $tags
24
     */
25
    public function __construct(?array $tags = null)
304✔
26
    {
27
        $this->tags = $tags ?? [
304✔
28
            [new BlockTypeMatch('paragraph'), 'p'],
304✔
29
            [new BlockTypeMatch('inline_text'), 'span'],
304✔
30
            [new BlockTypeMatch('emphasis'), 'em'],
304✔
31
            [new BlockTypeMatch('strong'), 'strong'],
304✔
32
            [new BlockTypeMatch('generic'), 'span'],
304✔
33
            [(new BlockTypeMatch('list'))->and(new BlockArgumentMatch('ordered', false)), 'ul'],
304✔
34
            [(new BlockTypeMatch('list'))->and(new BlockArgumentMatch('ordered', true)), 'ol'],
304✔
35
            [new BlockTypeMatch('list_item'), 'li'],
304✔
36
            [(new BlockTypeMatch('title'))->and(new BlockArgumentMatch('level', 1)), 'h1'],
304✔
37
            [(new BlockTypeMatch('title'))->and(new BlockArgumentMatch('level', 2)), 'h2'],
304✔
38
            [(new BlockTypeMatch('title'))->and(new BlockArgumentMatch('level', 3)), 'h3'],
304✔
39
            [(new BlockTypeMatch('title'))->and(new BlockArgumentMatch('level', 4)), 'h4'],
304✔
40
            [(new BlockTypeMatch('title'))->and(new BlockArgumentMatch('level', 5)), 'h5'],
304✔
41
            [(new BlockTypeMatch('title'))->and(new BlockArgumentMatch('level', 6)), 'h6'],
304✔
42
            [new BlockTypeMatch('quote'), 'blockquote'],
304✔
43
            [new BlockTypeMatch('new_line'), 'br'],
304✔
44
            [new BlockTypeMatch('link'), 'a'],
304✔
45
            [new BlockTypeMatch('stripped'), 'del'],
304✔
46
            [new BlockTypeMatch('separator'), 'hr'],
304✔
47
            [new BlockTypeMatch('subscript'), 'sub'],
304✔
48
            [new BlockTypeMatch('superscript'), 'sup'],
304✔
49
            [new BlockTypeMatch('code'), 'code'],
304✔
50
        ];
304✔
51
    }
52

53
    #[\Override]
184✔
54
    public function render(NodeList $ast): string
55
    {
56
        return array_reduce(
184✔
57
            $ast->nodes,
184✔
58
            function (string $memo, Node $node) {
184✔
59
                if ($node instanceof TextNode) {
184✔
60
                    return $memo.htmlentities($node->getValue());
184✔
61
                }
62

63
                if ($node instanceof BlockNode) {
176✔
64
                    return $memo.$this->renderBlockNode($node);
176✔
65
                }
66

67
                throw new \RuntimeException('Unsupported node type: '.get_class($node));
×
68
            },
184✔
69
            ''
184✔
70
        );
184✔
71
    }
72

73
    #[\Override]
88✔
74
    public function supportsFormat(string $format): bool
75
    {
76
        return 'html' === $format;
88✔
77
    }
78

79
    private function renderBlockNode(BlockNode $blockNode): string
176✔
80
    {
81
        $tagName = 'span';
176✔
82
        foreach ($this->tags as $tag) {
176✔
83
            if ($tag[0]->isSatisfiedBy($blockNode)) {
176✔
84
                $tagName = $tag[1];
176✔
85
            }
86
        }
87

88
        if (null === $blockNode->getChildren()) {
176✔
89
            return strtr('<:tagName: />', [':tagName:' => $tagName]);
32✔
90
        }
91

92
        return strtr(
176✔
93
            '<:tagName:>:content:</:tagName:>',
176✔
94
            [
176✔
95
                ':tagName:' => $tagName,
176✔
96
                ':content:' => $this->render($blockNode->getChildren()),
176✔
97
            ]
176✔
98
        );
176✔
99
    }
100
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc