Passed
Push — master ( 352e15...b87a11 )
by Tomáš
12:18
created

Formatter::nodeToArrayRecursive()   C

Complexity

Conditions 15
Paths 12

Size

Total Lines 52
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 15

Importance

Changes 0
Metric Value
eloc 29
c 0
b 0
f 0
dl 0
loc 52
ccs 30
cts 30
cp 1
rs 5.9166
cc 15
nc 12
nop 3
crap 15

How to fix   Long Method    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 Inspirum\XML\Formatter;
6
7
use DOMNode;
8
use Stringable;
9
use function array_keys;
10
use function array_merge;
11
use function count;
12
use function in_array;
13
use function is_array;
14
use function is_bool;
15
use function is_numeric;
16
use function is_scalar;
17
use function ltrim;
18
use function rtrim;
19
use function sprintf;
20
use function str_starts_with;
21
use function trim;
22
use const XML_CDATA_SECTION_NODE;
23
use const XML_TEXT_NODE;
24
25
final class Formatter
26
{
27
    /**
28
     * Prefix namespaces with xmlns
29
     *
30
     * @param array<string,string> $namespaces
31
     *
32
     * @return array<string,string>
33
     */
34 8
    public static function namespacesToAttributes(array $namespaces): array
35
    {
36 8
        $attributes = [];
37
38 8
        foreach ($namespaces as $namespaceLocalName => $namespaceValue) {
39 8
            if (str_starts_with($namespaceLocalName, 'xmlns') === false) {
40 8
                $namespaceLocalName = rtrim(sprintf('xmlns:%s', $namespaceLocalName), ':');
41
            }
42
43 8
            $attributes[$namespaceLocalName] = $namespaceValue;
44
        }
45
46 8
        return $attributes;
47
    }
48
49
    /**
50
     * Normalize value.
51
     */
52 76
    public static function encodeValue(mixed $value): ?string
53
    {
54 76
        if ($value === null) {
55 76
            return null;
56
        }
57
58 72
        if (is_bool($value)) {
59 15
            $value = $value ? 'true' : 'false';
60
        }
61
62 72
        if (is_scalar($value) || $value instanceof Stringable) {
63 72
            return (string) $value;
64
        }
65
66 1
        return null;
67
    }
68
69
    /**
70
     * Normalize value.
71
     *
72
     * @param mixed $value
73
     *
74
     * @return mixed
75
     */
76 3
    public static function decodeValue(mixed $value): mixed
77
    {
78 3
        if (is_numeric($value)) {
79 3
            return $value + 0;
80
        }
81
82 3
        if (in_array($value, ['true', 'True'], true)) {
83 3
            return true;
84
        }
85
86 3
        if (in_array($value, ['false', 'False'], true)) {
87 2
            return false;
88
        }
89
90 3
        return $value;
91
    }
92
93
    /**
94
     * Convert DOM node to array
95
     *
96
     * @param \DOMNode $node
97
     * @param \Inspirum\XML\Formatter\Config $config
98
     *
99
     * @return mixed
100
     */
101 20
    public static function nodeToArray(DOMNode $node, Config $config): mixed
102
    {
103 20
        return self::nodeToArrayRecursive($node, $config, 1);
104
    }
105
106 20
    private static function nodeToArrayRecursive(DOMNode $node, Config $config, int $depth): mixed
107
    {
108 20
        $value = null;
109
        /** @var array<string,mixed> $attributes */
110 20
        $attributes = [];
111
        /** @var array<string,array<mixed>> $nodes */
112 20
        $nodes = [];
113
114 20
        if ($node->hasAttributes()) {
115
            /** @var \DOMAttr $attribute */
116 13
            foreach ($node->attributes ?? [] as $attribute) {
117 13
                $attributes[$attribute->nodeName] = $config->isAutoCast()
118 2
                    ? self::decodeValue($attribute->nodeValue)
119 11
                    : $attribute->nodeValue;
120
            }
121
        }
122
123 20
        if ($node->hasChildNodes()) {
124
            /** @var \DOMNode $child */
125 18
            foreach ($node->childNodes ?? [] as $child) {
126 18
                if (in_array($child->nodeType, [XML_TEXT_NODE, XML_CDATA_SECTION_NODE])) {
127 18
                    if (trim((string) $child->nodeValue) !== '') {
128 18
                        $value = $config->isAutoCast()
129 2
                            ? self::decodeValue($child->nodeValue)
130 16
                            : $child->nodeValue;
131
                    }
132
133 18
                    continue;
134
                }
135
136 18
                $childNodes = self::nodeToArrayRecursive($child, $config, $depth + 1);
137 18
                if ($config->isFlatten()) {
138 5
                    self::flattenArray($nodes, $config->isWithoutRoot() && $depth === 1 ? '' : $child->nodeName, $childNodes, $config);
139
                } else {
140 13
                    $nodes[$child->nodeName][] = $childNodes;
141
                }
142
            }
143
        }
144
145 20
        if ($config->isFullResponse()) {
146 1
            return [
147 1
                $config->getAttributesName() => $attributes,
148 1
                $config->getValueName()      => $value,
149 1
                $config->getNodesName()      => $nodes,
150 1
            ];
151
        }
152
153 19
        if (count($nodes) === 0 && count($attributes) === 0) {
154 17
            return $value;
155
        }
156
157 18
        return self::simplifyArray($attributes, $value, $nodes, $config, $node);
158
    }
159
160
    /**
161
     * Flatten node to one-dimensional array
162
     *
163
     * @param array<string,array<mixed>> $nodes
164
     */
165 5
    private static function flattenArray(array &$nodes, string $nodeNames, mixed $childNodes, Config $config): void
166
    {
167 5
        if (!is_array($childNodes)) {
168 5
            $nodes[$nodeNames][] = $childNodes;
169
170 5
            return;
171
        }
172
173 5
        foreach ($childNodes as $childNodeName => $childNodeValues) {
174 5
            if ($childNodeName === $config->getAttributesName() && is_array($childNodeValues)) {
175 5
                foreach ($childNodeValues as $attributeName => $attributeValue) {
176 5
                    $nodeKey           = sprintf('%s%s%s', $nodeNames, $config->getFlattenAttributes(), $attributeName);
177 5
                    $nodes[$nodeKey][] = $attributeValue;
178
                }
179 5
            } elseif ($childNodeName === $config->getValueName()) {
180 5
                $nodes[$nodeNames][] = $childNodeValues;
181
            } else {
182 5
                $nodeKey = ltrim(sprintf('%s%s%s', $nodeNames, $config->getFlattenNodes(), $childNodeName), $config->getFlattenNodes());
183 5
                if (is_array($childNodeValues)) {
184 5
                    $nodes[$nodeKey] = array_merge($nodes[$nodeKey] ?? [], $childNodeValues);
185
                } else {
186 4
                    $nodes[$nodeKey][] = $childNodeValues;
187
                }
188
            }
189
        }
190
    }
191
192
    /**
193
     * Remove unnecessary data
194
     *
195
     * @param array<string,mixed> $attributes
196
     * @param array<string,array<mixed>> $nodes
197
     *
198
     * @return array<int|string,mixed>
199
     */
200 18
    private static function simplifyArray(array $attributes, mixed $value, array $nodes, Config $config, DOMNode $node): array
201
    {
202 18
        $simpleResult = $nodes;
203 18
        foreach ($nodes as $nodeName => $values) {
204
            if (
205 17
                !$config->isAlwaysArray()
206 17
                && in_array($nodeName, $config->getAlwaysArrayNodeNames(), true) === false
207 17
                && in_array($node->nodeName . '.' . $nodeName, $config->getAlwaysArrayNodeNames(), true) === false
208 17
                && array_keys($values) === [0]
209
            ) {
210 14
                $simpleResult[$nodeName] = $values[0];
211
            }
212
        }
213
214 18
        if (count($attributes) > 0) {
215 12
            $simpleResult[$config->getAttributesName()] = $attributes;
216
        }
217
218 18
        if ($value !== null) {
219 9
            $simpleResult[$config->getValueName()] = $value;
220
        }
221
222 18
        return $simpleResult;
223
    }
224
}
225