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);
88 if ("{$uniformChildrenName}s" == $element->nodeName) {
89 $result = $this->getUniformChildren($element->nodeName, $element);
91 $result = $this->getUniqueChildren($element->nodeName, $element);
93 return array_filter($result);
97 * Get the data from the children of the provided node in preliminary
100 * @param \DOMNode $element
103 protected function getNodeChildrenData($element)
106 foreach ($element->childNodes as $key => $value) {
107 $children[$key] = $this->elementToArray($value);
113 * Determine whether the children of the provided element are uniform.
114 * @see getUniformChildren(), below.
116 * @param \DOMNode $element
119 protected function hasUniformChildren($element)
122 foreach ($element->childNodes as $key => $value) {
123 $name = $value->nodeName;
127 if ($last && ($name != $last)) {
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.
144 * @param string $parentKey
145 * @param \DOMNode $element
148 protected function getUniformChildren($parentKey, $element)
150 $children = $this->getNodeChildrenData($element);
151 $simplifiedChildren = [];
152 foreach ($children as $key => $value) {
153 if ($this->valueCanBeSimplified($value)) {
154 $value = array_shift($value);
156 $id = $this->getIdOfValue($value);
158 $simplifiedChildren[$parentKey][$id] = $value;
160 $simplifiedChildren[$parentKey][] = $value;
163 return $simplifiedChildren;
167 * Determine whether the provided value has additional unnecessary
168 * nesting. {"color": "red"} is converted to "red". No other
169 * simplification is done.
171 * @param \DOMNode $value
174 protected function valueCanBeSimplified($value)
176 if (!is_array($value)) {
179 if (count($value) != 1) {
182 $data = array_shift($value);
183 return is_string($data);
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
192 protected function getIdOfValue($value)
194 if (!is_array($value)) {
197 if (array_key_exists('id', $value)) {
198 return trim($value['id'], '-');
200 if (array_key_exists('name', $value)) {
201 return trim($value['name'], '-');
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.
212 * @param string $parentKey
213 * @param \DOMNode $element
216 protected function getUniqueChildren($parentKey, $element)
218 $children = $this->getNodeChildrenData($element);
219 if ((count($children) == 1) && (is_string($children[0]))) {
220 return [$element->nodeName => $children[0]];
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);
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.");
232 $simplifiedChildren[$key] = $value;
234 return $simplifiedChildren;