Completed
Pull Request — master (#47)
by Greg
02:01
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
            }
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