Passed
Push — render-xml ( 61b7f8 )
by Colin
02:12
created

XmlRenderer::renderDocument()   B

Complexity

Conditions 10
Paths 21

Size

Total Lines 42
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 10

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 25
dl 0
loc 42
ccs 26
cts 26
cp 1
rs 7.6666
c 1
b 0
f 0
cc 10
nc 21
nop 1
crap 10

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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