Issues (41)

src/Xml/FromArrayBuilder.php (1 issue)

Labels
Severity
1
<?php
2
3
/** @noinspection PhpComposerExtensionStubsInspection */
4
5
namespace Nip\Utility\Xml;
6
7
use DOMDocument;
8
use DOMText;
9
use SebastianBergmann\CodeCoverage\XmlException;
0 ignored issues
show
The type SebastianBergmann\CodeCoverage\XmlException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use SimpleXMLElement;
11
12
/**
13
 * Class FromArrayBuilder
14
 * @package Nip\Utility\Xml
15
 */
16
class FromArrayBuilder
17
{
18
    protected $input;
19
20
    /**
21
     * @var DOMDocument
22
     */
23
    protected $dom;
24
    protected $options = [];
25
26
27
    /**
28
     * @param          $input
29
     * @param   array  $options
30
     *
31
     * @return DOMDocument|SimpleXMLElement
32
     */
33 1
    public static function build($input, array $options = [])
34
    {
35 1
        if (is_object($input) && method_exists($input, 'toArray') && is_callable([$input, 'toArray'])) {
36
            $input = $input->toArray();
37
        }
38 1
        if (!is_array($input) || count($input) !== 1) {
39
            throw new XmlException('Invalid input.');
40
        }
41 1
        $key = key($input);
42 1
        if (is_int($key)) {
43
            throw new XmlException('The key of input must be alphanumeric');
44
        }
45
46 1
        return (new static($input, $options))->buildXml();
47
    }
48
49
    /**
50
     * FromArrayBuilder constructor.
51
     *
52
     * @param          $input
53
     * @param   array  $options
54
     */
55 1
    protected function __construct($input, array $options = [])
56
    {
57 1
        $this->input = $input;
58
59
        $defaults = [
60 1
            'format'   => 'tags',
61 1
            'version'  => '1.0',
62 1
            'encoding' => mb_internal_encoding(),
63 1
            'return'   => 'simplexml',
64
            'pretty'   => false,
65
        ];
66 1
        $options  += $defaults;
67
68 1
        $this->options = $options;
69
70 1
        $this->dom = new DOMDocument($options['version'], $options['encoding']);
71 1
        if ($options['pretty']) {
72
            $this->dom->formatOutput = true;
73
        }
74 1
    }
75
76
    /**
77
     * @return SimpleXMLElement|DOMDocument
78
     */
79 1
    protected function buildXml()
80
    {
81 1
        $this->addData($this->dom, $this->dom, $this->input, $this->options['format']);
82
83 1
        $return = strtolower($this->options['return']);
84 1
        if ($return === 'simplexml' || $return === 'simplexmlelement') {
85 1
            return new SimpleXMLElement($this->dom->saveXML());
86
        }
87
88
        return $this->dom;
89
    }
90
91
    /**
92
     * Recursive method to create childs from array
93
     *
94
     * @param   \DOMDocument              $dom     Handler to DOMDocument
95
     * @param   \DOMDocument|\DOMElement  $node    Handler to DOMElement (child)
96
     * @param   array                     $data    Array of data to append to the $node.
97
     * @param   string                    $format  Either 'attributes' or 'tags'. This determines where nested keys go.
98
     *
99
     * @return void
100
     * @throws XmlException
101
     */
102 1
    protected function addData(DOMDocument $dom, $node, &$data, $format): void
103
    {
104 1
        if (empty($data) || !is_array($data)) {
105
            return;
106
        }
107 1
        foreach ($data as $key => $value) {
108 1
            if (!is_string($key)) {
109
                throw new XmlException('Invalid array');
110
            }
111 1
            $this->addDataItem($dom, $node, $key, $value, $format);
112
        }
113 1
    }
114
115
    /**
116
     * @param   DOMDocument  $dom
117
     * @param                $node
118
     * @param                $key
119
     * @param                $value
120
     * @param                $format
121
     */
122 1
    protected function addDataItem(DOMDocument $dom, $node, $key, $value, $format): void
123
    {
124 1
        if (is_object($value) && method_exists($value, 'toArray') && is_callable([$value, 'toArray'])) {
125
            $value = $value->toArray();
126
        }
127
128 1
        if (!is_array($value)) {
129 1
            if (is_bool($value)) {
130
                $value = (int)$value;
131 1
            } elseif ($value === null) {
132
                $value = '';
133
            }
134 1
            $isNamespace = strpos($key, 'xmlns:');
135 1
            if ($isNamespace !== false) {
136
                /** @psalm-suppress PossiblyUndefinedMethod */
137
                $node->setAttributeNS('http://www.w3.org/2000/xmlns/', $key, (string)$value);
138
                return;
139
            }
140 1
            if ($key[0] !== '@' && $format === 'tags') {
141 1
                if (!is_numeric($value)) {
142
                    // Escape special characters
143
                    // https://www.w3.org/TR/REC-xml/#syntax
144
                    // https://bugs.php.net/bug.php?id=36795
145 1
                    $child = $dom->createElement($key, '');
146 1
                    $child->appendChild(new DOMText((string)$value));
147
                } else {
148 1
                    $child = $dom->createElement($key, (string)$value);
149
                }
150 1
                $node->appendChild($child);
151
            } else {
152
                if ($key[0] === '@') {
153
                    $key = substr($key, 1);
154
                }
155
                $attribute = $dom->createAttribute($key);
156
                $attribute->appendChild($dom->createTextNode((string)$value));
157 1
                $node->appendChild($attribute);
158
            }
159
        } else {
160 1
            if ($key[0] === '@') {
161
                throw new XmlException('Invalid array');
162
            }
163 1
            if (is_numeric(implode('', array_keys($value)))) {
164
                // List
165
                foreach ($value as $item) {
166
                    $itemData          = compact('dom', 'node', 'key', 'format');
167
                    $itemData['value'] = $item;
168
                    $this->createChild($itemData);
169
                }
170
            } else {
171
                // Struct
172 1
                $this->createChild(compact('dom', 'node', 'key', 'value', 'format'));
173
            }
174
        }
175 1
    }
176
177
    /**
178
     * Helper to _fromArray(). It will create childs of arrays
179
     *
180
     * @param   array  $data  Array with information to create childs
181
     *
182
     * @return void
183
     */
184 1
    protected function createChild(array $data): void
185
    {
186
        $data += [
187 1
            'dom'    => null,
188
            'node'   => null,
189
            'key'    => null,
190
            'value'  => null,
191
            'format' => null,
192
        ];
193
194 1
        $value  = $data['value'];
195 1
        $dom    = $data['dom'];
196 1
        $key    = $data['key'];
197 1
        $format = $data['format'];
198 1
        $node   = $data['node'];
199
200 1
        $childNS = $childValue = null;
201 1
        if (is_object($value) && method_exists($value, 'toArray') && is_callable([$value, 'toArray'])) {
202
            $value = $value->toArray();
203
        }
204 1
        if (is_array($value)) {
205 1
            if (isset($value['@'])) {
206
                $childValue = (string)$value['@'];
207
                unset($value['@']);
208
            }
209 1
            if (isset($value['xmlns:'])) {
210
                $childNS = $value['xmlns:'];
211 1
                unset($value['xmlns:']);
212
            }
213
        } elseif (!empty($value) || $value === 0 || $value === '0') {
214
            $childValue = (string)$value;
215
        }
216
217 1
        $child = $dom->createElement($key);
218 1
        if ($childValue !== null) {
219
            $child->appendChild($dom->createTextNode($childValue));
220
        }
221 1
        if ($childNS) {
222
            $child->setAttribute('xmlns', $childNS);
223
        }
224
225 1
        $this->addData($dom, $child, $value, $format);
226 1
        $node->appendChild($child);
227 1
    }
228
}
229