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

Formatter::decodeValue()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 7
c 1
b 0
f 0
dl 0
loc 15
ccs 8
cts 8
cp 1
rs 10
cc 4
nc 4
nop 1
crap 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Inspirum\XML\Formatter;
6
7
use DOMNode;
8
use InvalidArgumentException;
9
use function array_keys;
10
use function array_pad;
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 preg_match;
17
use function sprintf;
18
use function trim;
19
use const XML_CDATA_SECTION_NODE;
20
use const XML_TEXT_NODE;
21
22
final class Formatter
23
{
24
    /**
25
     * Parse node name to namespace prefix and local name
26
     *
27
     * @return array{0: string|null, 1: string}
28
     */
29 50
    public static function parseQualifiedName(string $name): array
30
    {
31 50
        static::validateElementName($name);
32
33 47
        return array_pad(explode(':', $name, 2), -2, null);
34
    }
35
36
    /**
37
     * Get local name from node name
38
     */
39 5
    public static function getLocalName(string $name): string
40
    {
41 5
        return static::parseQualifiedName($name)[1];
42
    }
43
44
    /**
45
     * Get namespace prefix from node name
46
     */
47 47
    public static function getNamespacePrefix(string $name): ?string
48
    {
49 47
        return static::parseQualifiedName($name)[0];
50
    }
51
52
    /**
53
     * Validate element name
54
     *
55
     * @throws \InvalidArgumentException
56
     */
57 50
    protected static function validateElementName(string $value): void
58
    {
59 50
        $regex = '/^[a-zA-Z][a-zA-Z0-9_]*(:[a-zA-Z][a-zA-Z0-9_]*)?$/';
60
61 50
        if (preg_match($regex, $value) !== 1) {
62 6
            throw new InvalidArgumentException(
63 6
                sprintf('Element name or namespace prefix [%s] has invalid value', $value)
64
            );
65
        }
66
    }
67
68
    /**
69
     * Normalize value.
70
     */
71 44
    public static function encodeValue(mixed $value): ?string
72
    {
73 44
        if ($value === null) {
74 44
            return null;
75
        }
76
77 40
        if (is_bool($value)) {
78 10
            $value = $value ? 'true' : 'false';
79
        }
80
81 40
        return (string) $value;
82
    }
83
84
    /**
85
     * Normalize value.
86
     *
87
     * @param mixed $value
88
     *
89
     * @return mixed
90
     */
91 1
    public static function decodeValue(mixed $value): mixed
92
    {
93 1
        if (is_numeric($value)) {
94 1
            return $value + 0;
95
        }
96
97 1
        if (in_array($value, ['true', 'True'])) {
98 1
            return true;
99
        }
100
101 1
        if (in_array($value, ['false', 'False'])) {
102 1
            return false;
103
        }
104
105 1
        return $value;
106
    }
107
108
    /**
109
     * Convert DOM node to array
110
     *
111
     * @param \DOMNode                       $node
112
     * @param \Inspirum\XML\Formatter\Config $config
113
     *
114
     * @return mixed
115
     */
116 13
    public static function nodeToArray(DOMNode $node, Config $config): mixed
117
    {
118 13
        $result = [
119 13
            $config->attributesName => [],
120 13
            $config->valueName      => null,
121 13
            $config->nodesName      => [],
122
        ];
123
124 13
        if ($node->hasAttributes()) {
125
            /** @var \DOMAttr $attribute */
126 8
            foreach ($node->attributes ?? [] as $attribute) {
127 8
                $result[$config->attributesName][$attribute->nodeName] = $config->autoCast
128 1
                    ? self::decodeValue($attribute->nodeValue)
129 7
                    : $attribute->nodeValue;
130
            }
131
        }
132
133 13
        if ($node->hasChildNodes()) {
134
            /** @var \DOMNode $child */
135 11
            foreach ($node->childNodes ?? [] as $child) {
136 11
                if (in_array($child->nodeType, [XML_TEXT_NODE, XML_CDATA_SECTION_NODE])) {
137 11
                    if (trim((string) $child->nodeValue) !== '') {
138 11
                        $result[$config->valueName] = $config->autoCast
139 1
                            ? self::decodeValue($child->nodeValue)
140 10
                            : $child->nodeValue;
141
                    }
142
143 11
                    continue;
144
                }
145
146 11
                $result[$config->nodesName][$child->nodeName][] = self::nodeToArray($child, $config);
147
            }
148
        }
149
150 13
        if ($config->fullResponse) {
151 1
            return $result;
152
        }
153
154 12
        if (count($result[$config->nodesName]) === 0 && count($result[$config->attributesName]) === 0) {
1 ignored issue
show
Bug introduced by
It seems like $result[$config->nodesName] can also be of type null; however, parameter $value of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

154
        if (count(/** @scrutinizer ignore-type */ $result[$config->nodesName]) === 0 && count($result[$config->attributesName]) === 0) {
Loading history...
155 10
            return $result[$config->valueName];
156
        }
157
158 11
        return self::simplifyArray($result, $config, $node);
159
    }
160
161
    /**
162
     * Remove unnecessary data
163
     *
164
     * @param array<int|string,mixed> $result
165
     *
166
     * @return array<int|string,mixed>
167
     */
168 11
    private static function simplifyArray(array $result, Config $config, DOMNode $node): array
169
    {
170 11
        $simpleResult = $result[$config->nodesName];
171 11
        foreach ($simpleResult as $nodeName => $values) {
172
            if (
173 10
                in_array($nodeName, $config->alwaysArray) === false
174 10
                && in_array($node->nodeName . '.' . $nodeName, $config->alwaysArray) === false
175 10
                && array_keys($values) === [0]
176
            ) {
177 9
                $simpleResult[$nodeName] = $values[0];
178
            }
179
        }
180
181 11
        if (count($result[$config->attributesName]) > 0) {
182 7
            $simpleResult[$config->attributesName] = $result[$config->attributesName];
183
        }
184
185 11
        if ($result[$config->valueName] !== null) {
186 4
            $simpleResult[$config->valueName] = $result[$config->valueName];
187
        }
188
189 11
        return $simpleResult;
190
    }
191
}
192