Completed
Push — master ( 16c231...cc821a )
by Greg
10s
created

DomToArraySimplifier::getIdOfValue()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 12
rs 9.2
cc 4
eloc 7
nc 4
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');
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();
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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
        if ("{$uniformChildrenName}s" == $element->nodeName) {
89
            $result = $this->getUniformChildren($element->nodeName, $element);
90
        } else {
91
            $result = $this->getUniqueChildren($element->nodeName, $element);
92
        }
93
        return array_filter($result);
94
    }
95
96
    /**
97
     * Get the data from the children of the provided node in preliminary
98
     * form.
99
     *
100
     * @param \DOMNode $element
101
     * @return array
102
     */
103
    protected function getNodeChildrenData($element)
104
    {
105
        $children = [];
106
        foreach ($element->childNodes as $key => $value) {
107
            $children[$key] = $this->elementToArray($value);
108
        }
109
        return $children;
110
    }
111
112
    /**
113
     * Determine whether the children of the provided element are uniform.
114
     * @see getUniformChildren(), below.
115
     *
116
     * @param \DOMNode $element
117
     * @return boolean
118
     */
119
    protected function hasUniformChildren($element)
120
    {
121
        $last = false;
122
        foreach ($element->childNodes as $key => $value) {
123
            $name = $value->nodeName;
124
            if (!$name) {
125
                return false;
126
            }
127
            if ($last && ($name != $last)) {
128
                return false;
129
            }
130
            $last = $name;
131
        }
132
        return $last;
133
    }
134
135
    /**
136
     * Convert the children of the provided DOM element into an array.
137
     * Here, 'uniform' means that all of the element names of the children
138
     * are identical, and further, the element name of the parent is the
139
     * plural form of the child names.  When the children are uniform in
140
     * this way, then the parent element name will be used as the key to
141
     * store the children in, and the child list will be returned as a
142
     * simple list with their (duplicate) element names omitted.
143
     *
144
     * @param string $parentKey
145
     * @param \DOMNode $element
146
     * @return array
147
     */
148
    protected function getUniformChildren($parentKey, $element)
149
    {
150
        $children = $this->getNodeChildrenData($element);
151
        $simplifiedChildren = [];
152
        foreach ($children as $key => $value) {
153
            if ($this->valueCanBeSimplified($value)) {
154
                $value = array_shift($value);
155
            }
156
            $id = $this->getIdOfValue($value);
157
            if ($id) {
158
                $simplifiedChildren[$parentKey][$id] = $value;
159
            } else {
160
                $simplifiedChildren[$parentKey][] = $value;
161
            }
162
        }
163
        return $simplifiedChildren;
164
    }
165
166
    /**
167
     * Determine whether the provided value has additional unnecessary
168
     * nesting.  {"color": "red"} is converted to "red". No other
169
     * simplification is done.
170
     *
171
     * @param \DOMNode $value
172
     * @return boolean
173
     */
174
    protected function valueCanBeSimplified($value)
175
    {
176
        if (!is_array($value)) {
177
            return false;
178
        }
179
        if (count($value) != 1) {
180
            return false;
181
        }
182
        $data = array_shift($value);
183
        return is_string($data);
184
    }
185
186
    /**
187
     * If the object has an 'id' or 'name' element, then use that
188
     * as the array key when storing this value in its parent.
189
     * @param mixed $value
190
     * @return string
191
     */
192
    protected function getIdOfValue($value)
193
    {
194
        if (!is_array($value)) {
195
            return false;
196
        }
197
        if (array_key_exists('id', $value)) {
198
            return trim($value['id'], '-');
199
        }
200
        if (array_key_exists('name', $value)) {
201
            return trim($value['name'], '-');
202
        }
203
    }
204
205
    /**
206
     * Convert the children of the provided DOM element into an array.
207
     * Here, 'unique' means that all of the element names of the children are
208
     * different.  Since the element names will become the key of the
209
     * associative array that is returned, so duplicates are not supported.
210
     * If there are any duplicates, then an exception will be thrown.
211
     *
212
     * @param string $parentKey
213
     * @param \DOMNode $element
214
     * @return array
215
     */
216
    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...
217
    {
218
        $children = $this->getNodeChildrenData($element);
219
        if ((count($children) == 1) && (is_string($children[0]))) {
220
            return [$element->nodeName => $children[0]];
221
        }
222
        $simplifiedChildren = [];
223
        foreach ($children as $key => $value) {
224
            if (is_numeric($key) && is_array($value) && (count($value) == 1)) {
225
                $valueKeys = array_keys($value);
226
                $key = $valueKeys[0];
227
                $value = array_shift($value);
228
            }
229
            if (array_key_exists($key, $simplifiedChildren)) {
230
                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.");
231
            }
232
            $simplifiedChildren[$key] = $value;
233
        }
234
        return $simplifiedChildren;
235
    }
236
}
237