Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace League\CommonMark\Xml;
6
 
7
use League\CommonMark\Environment\EnvironmentInterface;
8
use League\CommonMark\Event\DocumentPreRenderEvent;
991 lars 9
use League\CommonMark\Exception\InvalidArgumentException;
148 lars 10
use League\CommonMark\Node\Block\Document;
11
use League\CommonMark\Node\Node;
12
use League\CommonMark\Node\StringContainerInterface;
13
use League\CommonMark\Output\RenderedContent;
14
use League\CommonMark\Output\RenderedContentInterface;
15
use League\CommonMark\Renderer\DocumentRendererInterface;
16
use League\CommonMark\Util\Xml;
17
 
18
final class XmlRenderer implements DocumentRendererInterface
19
{
20
    private const INDENTATION = '    ';
21
 
22
    private EnvironmentInterface $environment;
23
 
24
    private XmlNodeRendererInterface $fallbackRenderer;
25
 
26
    /** @var array<class-string, XmlNodeRendererInterface> */
27
    private array $rendererCache = [];
28
 
29
    public function __construct(EnvironmentInterface $environment)
30
    {
31
        $this->environment      = $environment;
32
        $this->fallbackRenderer = new FallbackNodeXmlRenderer();
33
    }
34
 
35
    public function renderDocument(Document $document): RenderedContentInterface
36
    {
37
        $this->environment->dispatch(new DocumentPreRenderEvent($document, 'xml'));
38
 
39
        $xml = '<?xml version="1.0" encoding="UTF-8"?>';
40
 
41
        $indent = 0;
42
        $walker = $document->walker();
43
        while ($event = $walker->next()) {
44
            $node = $event->getNode();
45
 
46
            $closeImmediately = ! $node->hasChildren();
47
            $selfClosing      = $closeImmediately && ! $node instanceof StringContainerInterface;
48
 
49
            $renderer = $this->findXmlRenderer($node);
50
            $tagName  = $renderer->getXmlTagName($node);
51
 
52
            if ($event->isEntering()) {
53
                $attrs = $renderer->getXmlAttributes($node);
54
 
55
                $xml .= "\n" . \str_repeat(self::INDENTATION, $indent);
56
                $xml .= self::tag($tagName, $attrs, $selfClosing);
57
 
58
                if ($node instanceof StringContainerInterface) {
59
                    $xml .= Xml::escape($node->getLiteral());
60
                }
61
 
62
                if ($closeImmediately && ! $selfClosing) {
63
                    $xml .= self::tag('/' . $tagName);
64
                }
65
 
66
                if (! $closeImmediately) {
67
                    $indent++;
68
                }
69
            } elseif (! $closeImmediately) {
70
                $indent--;
71
                $xml .= "\n" . \str_repeat(self::INDENTATION, $indent);
72
                $xml .= self::tag('/' . $tagName);
73
            }
74
        }
75
 
76
        return new RenderedContent($document, $xml . "\n");
77
    }
78
 
79
    /**
80
     * @param array<string, string|int|float|bool> $attrs
81
     */
82
    private static function tag(string $name, array $attrs = [], bool $selfClosing = \false): string
83
    {
84
        $result = '<' . $name;
85
        foreach ($attrs as $key => $value) {
86
            $result .= \sprintf(' %s="%s"', $key, self::convertAndEscape($value));
87
        }
88
 
89
        if ($selfClosing) {
90
            $result .= ' /';
91
        }
92
 
93
        $result .= '>';
94
 
95
        return $result;
96
    }
97
 
98
    /**
99
     * @param string|int|float|bool $value
100
     */
101
    private static function convertAndEscape($value): string
102
    {
103
        if (\is_string($value)) {
104
            return Xml::escape($value);
105
        }
106
 
107
        if (\is_int($value) || \is_float($value)) {
108
            return (string) $value;
109
        }
110
 
111
        if (\is_bool($value)) {
112
            return $value ? 'true' : 'false';
113
        }
114
 
115
        // @phpstan-ignore-next-line
991 lars 116
        throw new InvalidArgumentException('$value must be a string, int, float, or bool');
148 lars 117
    }
118
 
119
    private function findXmlRenderer(Node $node): XmlNodeRendererInterface
120
    {
121
        $class = \get_class($node);
122
 
123
        if (\array_key_exists($class, $this->rendererCache)) {
124
            return $this->rendererCache[$class];
125
        }
126
 
127
        foreach ($this->environment->getRenderersForClass($class) as $renderer) {
128
            if ($renderer instanceof XmlNodeRendererInterface) {
129
                return $this->rendererCache[$class] = $renderer;
130
            }
131
        }
132
 
133
        return $this->rendererCache[$class] = $this->fallbackRenderer;
134
    }
135
}