2 namespace Consolidation\OutputFormatters\Transformations;
4 use Consolidation\OutputFormatters\Options\FormatterOptions;
5 use Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface;
6 use Consolidation\OutputFormatters\StructuredData\Xml\XmlSchema;
9 * Simplify a DOMDocument to an array.
11 class DomToArraySimplifier implements SimplifyToArrayInterface
13 public function __construct()
18 * @param ReflectionClass $dataType
20 public function canSimplify(\ReflectionClass $dataType)
23 $dataType->isSubclassOf('\Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface') ||
24 $dataType->isSubclassOf('DOMDocument') ||
25 ($dataType->getName() == 'DOMDocument');
28 public function simplifyToArray($structuredData, FormatterOptions $options)
30 if ($structuredData instanceof DomDataInterface) {
31 $structuredData = $structuredData->getDomData();
33 if ($structuredData instanceof \DOMDocument) {
34 // $schema = $options->getXmlSchema();
35 $simplified = $this->elementToArray($structuredData);
36 $structuredData = array_shift($simplified);
38 return $structuredData;
42 * Recursively convert the provided DOM element into a php array.
44 * @param \DOMNode $element
47 protected function elementToArray(\DOMNode $element)
49 if ($element->nodeType == XML_TEXT_NODE) {
50 return $element->nodeValue;
52 $attributes = $this->getNodeAttributes($element);
53 $children = $this->getNodeChildren($element);
55 return array_merge($attributes, $children);
59 * Get all of the attributes of the provided element.
61 * @param \DOMNode $element
64 protected function getNodeAttributes($element)
66 if (empty($element->attributes)) {
70 foreach ($element->attributes as $key => $attribute) {
71 $attributes[$key] = $attribute->nodeValue;
77 * Get all of the children of the provided element, with simplification.
79 * @param \DOMNode $element
82 protected function getNodeChildren($element)
84 if (empty($element->childNodes)) {
87 $uniformChildrenName = $this->hasUniformChildren($element);
89 if (in_array($element->nodeName, ["{$uniformChildrenName}s", "{$uniformChildrenName}es"])) {
90 $result = $this->getUniformChildren($element->nodeName, $element);
92 $result = $this->getUniqueChildren($element->nodeName, $element);
94 return array_filter($result);
98 * Get the data from the children of the provided node in preliminary
101 * @param \DOMNode $element
104 protected function getNodeChildrenData($element)
107 foreach ($element->childNodes as $key => $value) {
108 $children[$key] = $this->elementToArray($value);
114 * Determine whether the children of the provided element are uniform.
115 * @see getUniformChildren(), below.
117 * @param \DOMNode $element
120 protected function hasUniformChildren($element)
123 foreach ($element->childNodes as $key => $value) {
124 $name = $value->nodeName;
128 if ($last && ($name != $last)) {
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.
145 * @param string $parentKey
146 * @param \DOMNode $element
149 protected function getUniformChildren($parentKey, $element)
151 $children = $this->getNodeChildrenData($element);
152 $simplifiedChildren = [];
153 foreach ($children as $key => $value) {
154 if ($this->valueCanBeSimplified($value)) {
155 $value = array_shift($value);
157 $id = $this->getIdOfValue($value);
159 $simplifiedChildren[$parentKey][$id] = $value;
161 $simplifiedChildren[$parentKey][] = $value;
164 return $simplifiedChildren;
168 * Determine whether the provided value has additional unnecessary
169 * nesting. {"color": "red"} is converted to "red". No other
170 * simplification is done.
172 * @param \DOMNode $value
175 protected function valueCanBeSimplified($value)
177 if (!is_array($value)) {
180 if (count($value) != 1) {
183 $data = array_shift($value);
184 return is_string($data);
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
193 protected function getIdOfValue($value)
195 if (!is_array($value)) {
198 if (array_key_exists('id', $value)) {
199 return trim($value['id'], '-');
201 if (array_key_exists('name', $value)) {
202 return trim($value['name'], '-');
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.
213 * @param string $parentKey
214 * @param \DOMNode $element
217 protected function getUniqueChildren($parentKey, $element)
219 $children = $this->getNodeChildrenData($element);
220 if ((count($children) == 1) && (is_string($children[0]))) {
221 return [$element->nodeName => $children[0]];
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);
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.");
233 $simplifiedChildren[$key] = $value;
235 return $simplifiedChildren;