Passed
Branch master (31b9e5)
by Tomáš
01:44
created

BaseNode::toArray()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 10
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Inspirum\XML\Builder;
6
7
use DOMDocument;
8
use DOMDocumentFragment;
9
use DOMElement;
10
use DOMException;
11
use DOMNode;
12
use Inspirum\XML\Exception\Handler;
13
use Inspirum\XML\Formatter\Config;
14
use Inspirum\XML\Formatter\Formatter;
15
use Throwable;
16
use function is_array;
17
use function strpos;
18
19
abstract class BaseNode implements Node
20
{
21 68
    protected function __construct(
22
        private DOMDocument $document,
23
        private ?DOMNode $node,
24
        private NamespaceRegistry $namespaceRegistry,
25
    ) {
26
    }
27
28 44
    protected function createNode(DOMNode $element): Node
29
    {
30 44
        return new DefaultNode($this->document, $element, $this->namespaceRegistry);
31
    }
32
33 11
    public function getDocument(): DOMDocument
34
    {
35 11
        return $this->document;
36
    }
37
38 6
    public function getNode(): ?DOMNode
39
    {
40 6
        return $this->node;
41
    }
42
43
    /**
44
     * @inheritDoc
45
     */
46 36
    public function addElement(string $name, array $attributes = []): Node
47
    {
48 36
        return $this->addTextElement($name, null, $attributes);
49
    }
50
51
    /**
52
     * @inheritDoc
53
     */
54 40
    public function addTextElement(string $name, mixed $value, array $attributes = [], bool $forcedEscape = false): Node
55
    {
56 40
        $element = $this->createFullDOMElement($name, $value, $attributes, $forcedEscape);
57
58 34
        $this->appendChild($element);
59
60 34
        return $this->createNode($element);
61
    }
62
63 6
    public function append(Node $element): void
64
    {
65 6
        if ($element->getNode() !== null) {
66 6
            $this->appendChild($element->getNode());
67
        }
68
    }
69
70
    /**
71
     * @inheritDoc
72
     */
73 2
    public function createElement(string $name, array $attributes = []): Node
74
    {
75 2
        return $this->createTextElement($name, null, $attributes);
76
    }
77
78
    /**
79
     * @inheritDoc
80
     */
81 10
    public function createTextElement(string $name, mixed $value, array $attributes = [], bool $forcedEscape = false): Node
82
    {
83 10
        $element = $this->createFullDOMElement($name, $value, $attributes, $forcedEscape);
84
85 10
        return $this->createNode($element);
86
    }
87
88 4
    public function addXMLData(string $content): ?Node
89
    {
90 4
        if ($content === '') {
91 1
            return null;
92
        }
93
94 3
        $element = $this->createDOMFragment($content);
95
96 3
        $this->appendChild($element);
97
98 3
        return $this->createNode($element);
99
    }
100
101
    /**
102
     * Create new DOM element.
103
     *
104
     * @param array<string,string> $attributes
105
     */
106 50
    private function createFullDOMElement(string $name, mixed $value, array $attributes, bool $forcedEscape): DOMElement
107
    {
108 50
        $this->registerNamespaces($attributes);
109
110 47
        $element = $this->createDOMElementNS($name);
111
112 44
        $this->setDOMElementValue($element, $value, $forcedEscape);
113
114 44
        foreach ($attributes as $attributeName => $attributeValue) {
115 26
            $this->setDOMAttributeNS($element, $attributeName, $attributeValue);
116
        }
117
118 44
        return $element;
119
    }
120
121
    /**
122
     * Create new DOM fragment element
123
     */
124 3
    private function createDOMFragment(string $content): DOMDocumentFragment
125
    {
126 3
        $element = $this->document->createDocumentFragment();
127
128 3
        $element->appendXML($content);
129
130 3
        return $element;
131
    }
132
133
    /**
134
     * Create new DOM element with namespace if exists
135
     */
136 47
    private function createDOMElementNS(string $name, ?string $value = null): DOMElement
137
    {
138 47
        $prefix = Formatter::getNamespacePrefix($name);
139 44
        $value  = Formatter::encodeValue($value);
140
141 44
        if ($prefix !== null && $this->namespaceRegistry->hasNamespace($prefix)) {
142 8
            return $this->document->createElementNS($this->namespaceRegistry->getNamespace($prefix), $name, (string) $value);
143
        }
144
145 40
        return $this->document->createElement($name, (string) $value);
146
    }
147
148
    /**
149
     * Set node value to element
150
     */
151 44
    private function setDOMElementValue(DOMElement $element, mixed $value, bool $forcedEscape): void
152
    {
153 44
        $value = Formatter::encodeValue($value);
154
155 44
        if ($value === '' || $value === null) {
156 38
            return;
157
        }
158
159
        try {
160 32
            if (strpos($value, '&') !== false || $forcedEscape) {
161 1
                throw new DOMException('DOMDocument::createElement(): unterminated entity reference');
162
            }
163
164 32
            $element->nodeValue = $value;
165 1
        } catch (Throwable) {
166 1
            $cdata = $this->document->createCDATASection($value);
167 1
            $element->appendChild($cdata);
168
        }
169
    }
170
171
    /**
172
     * Create new DOM attribute with namespace if exists
173
     *
174
     * @return void
175
     */
176 26
    private function setDOMAttributeNS(DOMElement $element, string $name, mixed $value): void
177
    {
178 26
        $prefix = Formatter::getNamespacePrefix($name);
179 26
        $value  = Formatter::encodeValue($value);
180
181 26
        if ($prefix === 'xmlns') {
182 14
            $element->setAttributeNS('http://www.w3.org/2000/xmlns/', $name, (string) $value);
183 23
        } elseif ($prefix !== null && $this->namespaceRegistry->hasNamespace($prefix)) {
184 8
            $element->setAttributeNS($this->namespaceRegistry->getNamespace($prefix), $name, (string) $value);
185
        } else {
186 17
            $element->setAttribute($name, (string) $value);
187
        }
188
    }
189
190
    /**
191
     * Append child to parent node.
192
     */
193 40
    private function appendChild(DOMNode $element): void
194
    {
195 40
        $node = $this->node ?? $this->document;
196 40
        $node->appendChild($element);
197
    }
198
199
    /**
200
     * Register xmlns namespace URLs
201
     *
202
     * @param array<string,string> $attributes
203
     */
204 50
    private function registerNamespaces(array $attributes): void
205
    {
206 50
        foreach ($attributes as $attributeName => $attributeValue) {
207 29
            [$prefix, $namespaceLocalName] = Formatter::parseQualifiedName($attributeName);
208
209 29
            if ($prefix === 'xmlns' && $namespaceLocalName !== null) {
210 14
                $this->namespaceRegistry->registerNamespace($namespaceLocalName, $attributeValue);
211
            }
212
        }
213
    }
214
215 7
    public function getTextContent(): ?string
216
    {
217 7
        $node = $this->node ?? $this->document;
218
219 7
        return $node->textContent;
220
    }
221
222 26
    public function toString(bool $formatOutput = false): string
223
    {
224 26
        return Handler::withErrorHandlerForDOMDocument(function () use ($formatOutput): string {
225 26
            $this->document->formatOutput = $formatOutput;
226
227 26
            $xml = $this->document->saveXML($this->node);
228 26
            if ($xml === false) {
229 1
                throw new DOMException('\DOMDocument::saveXML() method failed');
230
            }
231
232 25
            return $xml;
233
        });
234
    }
235
236
    /**
237
     * Convert to string
238
     *
239
     * @return string
240
     */
241 1
    public function __toString(): string
242
    {
243 1
        return $this->toString();
244
    }
245
246
    /**
247
     * @inheritDoc
248
     */
249 13
    public function toArray(?Config $config = null): array
250
    {
251 13
        $result = Formatter::nodeToArray($this->node ?? $this->document, $config ?? new Config());
252
253 13
        if (is_array($result) === false) {
254 2
            $result = [$result];
255
        }
256
257 13
        return $result;
258
    }
259
260
    /**
261
     * @inheritDoc
262
     */
263 1
    public function jsonSerialize(): array
264
    {
265 1
        return $this->toArray();
266
    }
267
}
268