Subversion-Projekte lars-tiefland.laravel_shop

Revision

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