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(); |
|
|
|
|
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) |
|
|
|
|
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
|
|
|
|
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.