Passed
Push — master ( 86abb8...57f290 )
by Tomáš
12:38
created

Formatter   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 182
Duplicated Lines 0 %

Test Coverage

Coverage 98.61%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 36
eloc 62
c 1
b 0
f 0
dl 0
loc 182
ccs 71
cts 72
cp 0.9861
rs 9.52

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getNamespacePrefix() 0 3 1
A encodeValue() 0 15 6
A decodeValue() 0 15 4
B simplifyArray() 0 22 7
A getLocalName() 0 3 1
A validateElementName() 0 7 2
A parseQualifiedName() 0 9 3
C nodeToArray() 0 47 12
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Inspirum\XML\Formatter;
6
7
use DOMNode;
8
use InvalidArgumentException;
9
use Stringable;
10
use function array_keys;
11
use function count;
12
use function explode;
13
use function in_array;
14
use function is_bool;
15
use function is_numeric;
16
use function is_scalar;
17
use function preg_match;
18
use function sprintf;
19
use function trim;
20
use const XML_CDATA_SECTION_NODE;
21
use const XML_TEXT_NODE;
22
23
final class Formatter
24
{
25
    /**
26
     * Parse node name to namespace prefix and local name
27
     *
28
     * @return array{0: string|null, 1: string}
29
     */
30 50
    public static function parseQualifiedName(string $name): array
31
    {
32 50
        self::validateElementName($name);
33
34 47
        $parsed = explode(':', $name, 2);
35
36 47
        return [
37 47
            count($parsed) === 2 ? $parsed[0] : null,
38 47
            count($parsed) === 2 ? $parsed[1] : $parsed[0],
39 47
        ];
40
    }
41
42
    /**
43
     * Get local name from node name
44
     */
45 5
    public static function getLocalName(string $name): string
46
    {
47 5
        return self::parseQualifiedName($name)[1];
48
    }
49
50
    /**
51
     * Get namespace prefix from node name
52
     */
53 47
    public static function getNamespacePrefix(string $name): ?string
54
    {
55 47
        return self::parseQualifiedName($name)[0];
56
    }
57
58
    /**
59
     * Validate element name
60
     *
61
     * @throws \InvalidArgumentException
62
     */
63 50
    protected static function validateElementName(string $value): void
64
    {
65 50
        $regex = '/^[a-zA-Z][a-zA-Z0-9_]*(:[a-zA-Z][a-zA-Z0-9_]*)?$/';
66
67 50
        if (preg_match($regex, $value) !== 1) {
68 6
            throw new InvalidArgumentException(
69 6
                sprintf('Element name or namespace prefix [%s] has invalid value', $value),
70 6
            );
71
        }
72
    }
73
74
    /**
75
     * Normalize value.
76
     */
77 44
    public static function encodeValue(mixed $value): ?string
78
    {
79 44
        if ($value === null) {
80 44
            return null;
81
        }
82
83 40
        if (is_bool($value)) {
84 10
            $value = $value ? 'true' : 'false';
85
        }
86
87 40
        if (is_scalar($value) || $value instanceof Stringable) {
88 40
            return (string) $value;
89
        }
90
91
        return null;
92
    }
93
94
    /**
95
     * Normalize value.
96
     *
97
     * @param mixed $value
98
     *
99
     * @return mixed
100
     */
101 1
    public static function decodeValue(mixed $value): mixed
102
    {
103 1
        if (is_numeric($value)) {
104 1
            return $value + 0;
105
        }
106
107 1
        if (in_array($value, ['true', 'True'], true)) {
108 1
            return true;
109
        }
110
111 1
        if (in_array($value, ['false', 'False'], true)) {
112 1
            return false;
113
        }
114
115 1
        return $value;
116
    }
117
118
    /**
119
     * Convert DOM node to array
120
     *
121
     * @param \DOMNode                       $node
122
     * @param \Inspirum\XML\Formatter\Config $config
123
     *
124
     * @return mixed
125
     */
126 13
    public static function nodeToArray(DOMNode $node, Config $config): mixed
127
    {
128 13
        $value = null;
129
        /** @var array<string, mixed> $attributes */
130 13
        $attributes = [];
131
        /** @var array<string, array<mixed>> $nodes */
132 13
        $nodes = [];
133
134 13
        if ($node->hasAttributes()) {
135
            /** @var \DOMAttr $attribute */
136 8
            foreach ($node->attributes ?? [] as $attribute) {
137 8
                $attributes[$attribute->nodeName] = $config->autoCast
138 1
                    ? self::decodeValue($attribute->nodeValue)
139 7
                    : $attribute->nodeValue;
140
            }
141
        }
142
143 13
        if ($node->hasChildNodes()) {
144
            /** @var \DOMNode $child */
145 11
            foreach ($node->childNodes ?? [] as $child) {
146 11
                if (in_array($child->nodeType, [XML_TEXT_NODE, XML_CDATA_SECTION_NODE])) {
147 11
                    if (trim((string) $child->nodeValue) !== '') {
148 11
                        $value = $config->autoCast
149 1
                            ? self::decodeValue($child->nodeValue)
150 10
                            : $child->nodeValue;
151
                    }
152
153 11
                    continue;
154
                }
155
156 11
                $nodes[$child->nodeName][] = self::nodeToArray($child, $config);
157
            }
158
        }
159
160 13
        if ($config->fullResponse) {
161 1
            return [
162 1
                $config->attributesName => $attributes,
163 1
                $config->valueName      => $value,
164 1
                $config->nodesName      => $nodes,
165 1
            ];
166
        }
167
168 12
        if (count($nodes) === 0 && count($attributes) === 0) {
169 10
            return $value;
170
        }
171
172 11
        return self::simplifyArray($attributes, $value, $nodes, $config, $node);
173
    }
174
175
    /**
176
     * Remove unnecessary data
177
     *
178
     * @param array<string,mixed>        $attributes
179
     * @param array<string,array<mixed>> $nodes
180
     *
181
     * @return array<int|string,mixed>
182
     */
183 11
    private static function simplifyArray(array $attributes, mixed $value, array $nodes, Config $config, DOMNode $node): array
184
    {
185 11
        $simpleResult = $nodes;
186 11
        foreach ($nodes as $nodeName => $values) {
187
            if (
188 10
                in_array($nodeName, $config->alwaysArray, true) === false
189 10
                && in_array($node->nodeName . '.' . $nodeName, $config->alwaysArray, true) === false
190 10
                && array_keys($values) === [0]
191
            ) {
192 9
                $simpleResult[$nodeName] = $values[0];
193
            }
194
        }
195
196 11
        if (count($attributes) > 0) {
197 7
            $simpleResult[$config->attributesName] = $attributes;
198
        }
199
200 11
        if ($value !== null) {
201 4
            $simpleResult[$config->valueName] = $value;
202
        }
203
204 11
        return $simpleResult;
205
    }
206
}
207