DomToArraySimplifier::getNodeChildrenData()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
namespace Consolidation\OutputFormatters\Transformations;
3
4
use Consolidation\OutputFormatters\Options\FormatterOptions;
5
use Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface;
6
use Consolidation\OutputFormatters\StructuredData\Xml\XmlSchema;
7
8
/**
9
 * Simplify a DOMDocument to an array.
10
 */
11
class DomToArraySimplifier implements SimplifyToArrayInterface
12
{
13
    public function __construct()
14
    {
15
    }
16
17
    /**
18
     * @param ReflectionClass $dataType
19
     */
20
    public function canSimplify(\ReflectionClass $dataType)
21
    {
22
        return
23
            $dataType->isSubclassOf('\Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface') ||
24
            $dataType->isSubclassOf('DOMDocument') ||
25
            ($dataType->getName() == 'DOMDocument');
0 ignored issues
show
Bug introduced by
Consider using $dataType->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
26
    }
27
28
    public function simplifyToArray($structuredData, FormatterOptions $options)
29
    {
30
        if ($structuredData instanceof DomDataInterface) {
31
            $structuredData = $structuredData->getDomData();
32
        }
33
        if ($structuredData instanceof \DOMDocument) {
34
            // $schema = $options->getXmlSchema();
35
            $simplified = $this->elementToArray($structuredData);
36
            $structuredData = array_shift($simplified);
37
        }
38
        return $structuredData;
39
    }
40
41
    /**
42
     * Recursively convert the provided DOM element into a php array.
43
     *
44
     * @param \DOMNode $element
45
     * @return array
46
     */
47
    protected function elementToArray(\DOMNode $element)
48
    {
49
        if ($element->nodeType == XML_TEXT_NODE) {
50
            return $element->nodeValue;
51
        }
52
        $attributes = $this->getNodeAttributes($element);
53
        $children = $this->getNodeChildren($element);
54
55
        return array_merge($attributes, $children);
56
    }
57
58
    /**
59
     * Get all of the attributes of the provided element.
60
     *
61
     * @param \DOMNode $element
62
     * @return array
63
     */
64
    protected function getNodeAttributes($element)
65
    {
66
        if (empty($element->attributes)) {
67
            return [];
68
        }
69
        $attributes = [];
70
        foreach ($element->attributes as $key => $attribute) {
71
            $attributes[$key] = $attribute->nodeValue;
72
        }
73
        return $attributes;
74
    }
75
76
    /**
77
     * Get all of the children of the provided element, with simplification.
78
     *
79
     * @param \DOMNode $element
80
     * @return array
81
     */
82
    protected function getNodeChildren($element)
83
    {
84
        if (empty($element->childNodes)) {
85
            return [];
86
        }
87
        $uniformChildrenName = $this->hasUniformChildren($element);
88
        // Check for plurals.
89
        if (in_array($element->nodeName, ["{$uniformChildrenName}s", "{$uniformChildrenName}es"])) {
90
            $result = $this->getUniformChildren($element->nodeName, $element);
91
        } else {
92
            $result = $this->getUniqueChildren($element->nodeName, $element);
93
        }
94
        return array_filter($result);
95
    }
96
97
    /**
98
     * Get the data from the children of the provided node in preliminary
99
     * form.
100
     *
101
     * @param \DOMNode $element
102
     * @return array
103
     */
104
    protected function getNodeChildrenData($element)
105
    {
106
        $children = [];
107
        foreach ($element->childNodes as $key => $value) {
108
            $children[$key] = $this->elementToArray($value);
109
        }
110
        return $children;
111
    }
112
113
    /**
114
     * Determine whether the children of the provided element are uniform.
115
     * @see getUniformChildren(), below.
116
     *
117
     * @param \DOMNode $element
118
     * @return boolean
119
     */
120
    protected function hasUniformChildren($element)
121
    {
122
        $last = false;
123
        foreach ($element->childNodes as $key => $value) {
124
            $name = $value->nodeName;
125
            if (!$name) {
126
                return false;
127
            }
128
            if ($last && ($name != $last)) {
129
                return false;
130
            }
131
            $last = $name;
132
        }
133
        return $last;
134
    }
135
136
    /**
137
     * Convert the children of the provided DOM element into an array.
138
     * Here, 'uniform' means that all of the element names of the children
139
     * are identical, and further, the element name of the parent is the
140
     * plural form of the child names.  When the children are uniform in
141
     * this way, then the parent element name will be used as the key to
142
     * store the children in, and the child list will be returned as a
143
     * simple list with their (duplicate) element names omitted.
144
     *
145
     * @param string $parentKey
146
     * @param \DOMNode $element
147
     * @return array
148
     */
149
    protected function getUniformChildren($parentKey, $element)
150
    {
151
        $children = $this->getNodeChildrenData($element);
152
        $simplifiedChildren = [];
153
        foreach ($children as $key => $value) {
154
            if ($this->valueCanBeSimplified($value)) {
155
                $value = array_shift($value);
156
            }
157
            $id = $this->getIdOfValue($value);
158
            if ($id) {
159
                $simplifiedChildren[$parentKey][$id] = $value;
160
            } else {
161
                $simplifiedChildren[$parentKey][] = $value;
162
            }
163
        }
164
        return $simplifiedChildren;
165
    }
166
167
    /**
168
     * Determine whether the provided value has additional unnecessary
169
     * nesting.  {"color": "red"} is converted to "red". No other
170
     * simplification is done.
171
     *
172
     * @param \DOMNode $value
173
     * @return boolean
174
     */
175
    protected function valueCanBeSimplified($value)
176
    {
177
        if (!is_array($value)) {
178
            return false;
179
        }
180
        if (count($value) != 1) {
181
            return false;
182
        }
183
        $data = array_shift($value);
184
        return is_string($data);
185
    }
186
187
    /**
188
     * If the object has an 'id' or 'name' element, then use that
189
     * as the array key when storing this value in its parent.
190
     * @param mixed $value
191
     * @return string
192
     */
193
    protected function getIdOfValue($value)
194
    {
195
        if (!is_array($value)) {
196
            return false;
197
        }
198
        if (array_key_exists('id', $value)) {
199
            return trim($value['id'], '-');
200
        }
201
        if (array_key_exists('name', $value)) {
202
            return trim($value['name'], '-');
203
        }
204
    }
205
206
    /**
207
     * Convert the children of the provided DOM element into an array.
208
     * Here, 'unique' means that all of the element names of the children are
209
     * different.  Since the element names will become the key of the
210
     * associative array that is returned, so duplicates are not supported.
211
     * If there are any duplicates, then an exception will be thrown.
212
     *
213
     * @param string $parentKey
214
     * @param \DOMNode $element
215
     * @return array
216
     */
217
    protected function getUniqueChildren($parentKey, $element)
0 ignored issues
show
Unused Code introduced by
The parameter $parentKey is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
218
    {
219
        $children = $this->getNodeChildrenData($element);
220
        if ((count($children) == 1) && (is_string($children[0]))) {
221
            return [$element->nodeName => $children[0]];
222
        }
223
        $simplifiedChildren = [];
224
        foreach ($children as $key => $value) {
225
            if (is_numeric($key) && is_array($value) && (count($value) == 1)) {
226
                $valueKeys = array_keys($value);
227
                $key = $valueKeys[0];
228
                $value = array_shift($value);
229
            }
230
            if (array_key_exists($key, $simplifiedChildren)) {
231
                throw new \Exception("Cannot convert data from a DOM document to an array, because <$key> appears more than once, and is not wrapped in a <{$key}s> element.");
232
            }
233
            $simplifiedChildren[$key] = $value;
234
        }
235
        return $simplifiedChildren;
236
    }
237
}
238