Passed
Push — master ( 407684...48d2f8 )
by Бабичев
03:42
created

XMLReader::asObject()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Bavix\XMLReader;
4
5
use Bavix\Foundation\SharedInstance;
6
use Bavix\Helpers\File;
7
use Bavix\Helpers\Arr;
8
use Bavix\Exceptions;
9
use DOMElement;
10
11
class XMLReader
12
{
13
14
    use SharedInstance;
15
16
    /**
17
     * @var array
18
     */
19
    protected $copyright = [
20
        'created-with' => 'https://github.com/bavix/xml'
21
    ];
22
23
    /**
24
     * @var \DOMDocument
25
     */
26
    protected $document;
27
28
    /**
29
     * @return \DOMDocument
30
     */
31 1
    protected function document()
32
    {
33 1
        if (!$this->document)
34
        {
35 1
            $this->document                = new \DOMDocument('1.0', 'utf-8');
36 1
            $this->document->formatOutput  = true;
37 1
            $this->copyright['created-at'] = \date('Y-m-d H:i:s');
38
        }
39
40 1
        return $this->document;
41
    }
42
43
    /**
44
     * @param string $name
45
     *
46
     * @return \DOMElement
47
     */
48 1
    protected function element($name)
49
    {
50 1
        return $this->document()->createElement($name);
51
    }
52
53
    /**
54
     * @param \SimpleXMLElement $element
55
     * @param string            $property
56
     *
57
     * @return array
58
     */
59 1
    protected function _property(\SimpleXMLElement $element, $property)
60
    {
61 1
        $output = [];
62
63 1
        if (method_exists($element, $property))
64
        {
65
66 1
            $properties = $element->$property();
67
68 1
            if ($properties)
69
            {
70 1
                $output['@' . $property] = is_array($properties) ?
71
                    $properties :
72 1
                    $this->_asArray($properties);
73
74 1
                if (empty($output['@' . $property]))
75
                {
76
                    Arr::remove($output, '@' . $property);
77
                }
78
            }
79
80
        }
81
82 1
        return $output;
83
    }
84
85
    /**
86
     * @param \SimpleXMLElement $element
87
     *
88
     * @return array|string
89
     *
90
     * @codeCoverageIgnore
91
     */
92
    protected function _asData(\SimpleXMLElement $element)
93
    {
94
        $output = $this->_property($element, 'attributes');
95
96
        if (!$element->count())
97
        {
98
            $output['@value'] = (string)$element;
99
100
            if (!isset($output['@attributes']))
101
            {
102
                $output = $output['@value'];
103
            }
104
        }
105
106
        return $output;
107
    }
108
109
    /**
110
     * @param \SimpleXMLElement $element
111
     *
112
     * @return array|string
113
     *
114
     * @codeCoverageIgnore
115
     */
116
    protected function _asArray(\SimpleXMLElement $element)
117
    {
118
        $output = $this->_asData($element);
119
120
        if (!$element->count())
121
        {
122
            return $output;
123
        }
124
125
        return $this->_pushArray($output, $element);
0 ignored issues
show
Bug introduced by
It seems like $output can also be of type string; however, parameter $output of Bavix\XMLReader\XMLReader::_pushArray() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

125
        return $this->_pushArray(/** @scrutinizer ignore-type */ $output, $element);
Loading history...
126
    }
127
128
    /**
129
     * @param array             $output
130
     * @param \SimpleXMLElement $element
131
     *
132
     * @return array
133
     *
134
     * @codeCoverageIgnore
135
     */
136
    protected function _pushArray(array &$output, \SimpleXMLElement $element)
137
    {
138
        $first = [];
139
140
        /**
141
         * @var \SimpleXMLElement $item
142
         */
143
        foreach ($element as $key => $item)
144
        {
145
            if (!isset($output[$key]))
146
            {
147
                $first[$key]  = true;
148
                $output[$key] = $this->_asArray($item);
149
                continue;
150
            }
151
152
            if (!empty($first[$key]))
153
            {
154
                $output[$key] = [$output[$key]];
155
            }
156
157
            $output[$key][] = $this->_asArray($item);
158
            $first[$key]    = false;
159
        }
160
161
        return $output;
162
    }
163
164
    /**
165
     * @param string|\DOMNode $mixed
166
     *
167
     * @return \SimpleXMLElement
168
     *
169
     * @codeCoverageIgnore
170
     */
171
    protected function _simpleXml($mixed)
172
    {
173
        if ($mixed instanceof \DOMNode)
174
        {
175
            return \simplexml_import_dom($mixed);
176
        }
177
178
        if (File::isFile($mixed))
179
        {
180
            return \simplexml_load_file($mixed);
181
        }
182
183
        return \simplexml_load_string($mixed);
184
    }
185
186
    /**
187
     * @param string|\DOMNode $mixed
188
     *
189
     * @return array
190
     */
191 1
    public function asArray($mixed)
192
    {
193 1
        $data = $this->_simpleXml($mixed);
194
195 1
        return $data ?
196 1
            $this->_asArray($data) :
197 1
            null;
198
    }
199
200
    /**
201
     * @return \DOMDocument
202
     */
203
    public function asObject()
204
    {
205
        return clone $this->document();
206
    }
207
208
    /**
209
     * @param array|\Traversable $storage
210
     *
211
     * @return array
212
     *
213
     * @codeCoverageIgnore
214
     */
215
    protected function _convertStorage($storage)
216
    {
217
        if ($storage instanceof \Traversable)
218
        {
219
            return \iterator_to_array($storage);
220
        }
221
222
        return $storage;
223
    }
224
225
    /**
226
     * @param array|\Traversable $storage
227
     * @param string             $name
228
     *
229
     * @return string
230
     */
231 1
    public function asXML($storage, $name = 'bavix')
232
    {
233 1
        $element = $this->element($name);
234
235 1
        $this->addAttributes($element, $this->copyright);
236 1
        $this->document()->appendChild($element);
237 1
        $this->convert($element, $this->_convertStorage($storage));
238 1
        $xml = $this->document()->saveXML();
239
240 1
        $this->document = null;
241
242 1
        return $xml;
243
    }
244
245
    /**
246
     * @param DOMElement $element
247
     * @param mixed      $storage
248
     *
249
     * @throws Exceptions\Blank
250
     *
251
     * @codeCoverageIgnore
252
     */
253
    protected function convert(DOMElement $element, $storage)
254
    {
255
        if (!is_array($storage))
256
        {
257
            $element->nodeValue = htmlspecialchars($storage);
258
259
            return;
260
        }
261
262
        if (empty($storage))
263
        {
264
            throw new Exceptions\Blank('Array is empty');
265
        }
266
267
        $isInt      = Arr::map(Arr::getKeys($storage), '\is_int');
268
        $sequential = !Arr::in($isInt, false);
269
270
        foreach ($storage as $key => $data)
271
        {
272
            if ($sequential)
273
            {
274
                $this->sequential($element, $data);
275
                continue;
276
            }
277
278
            $this->addNodeWithKey($key, $element, $data);
279
        }
280
    }
281
282
    /**
283
     * @param string     $key
284
     * @param DOMElement $element
285
     * @param mixed      $storage
286
     *
287
     * @codeCoverageIgnore
288
     */
289
    protected function addNodeWithKey($key, DOMElement $element, $storage)
290
    {
291
        if ($key === '@attributes')
292
        {
293
            $this->addAttributes($element, $storage);
294
        }
295
        else if ($key === '@value' && \is_string($storage))
296
        {
297
            $element->nodeValue = \htmlspecialchars($storage);
298
        }
299
        else
300
        {
301
            $this->addNode($element, $key, $storage);
302
        }
303
    }
304
305
    /**
306
     * @param DOMElement $element
307
     * @param mixed      $storage
308
     *
309
     * @codeCoverageIgnore
310
     */
311
    protected function sequential(DOMElement $element, $storage)
312
    {
313
        if (is_array($storage))
314
        {
315
            $this->addCollectionNode($element, $storage);
316
317
            return;
318
        }
319
320
        $this->addSequentialNode($element, $storage);
321
    }
322
323
    /**
324
     * @param DOMElement $element
325
     * @param string     $key
326
     * @param mixed      $value
327
     *
328
     * @throws Exceptions\Blank
329
     *
330
     * @codeCoverageIgnore
331
     */
332
    protected function addNode(DOMElement $element, $key, $value)
333
    {
334
        $key   = \str_replace(' ', '-', $key);
335
        $child = $this->document()->createElement($key);
336
        $element->appendChild($child);
337
        $this->convert($child, $value);
338
    }
339
340
    /**
341
     * @param DOMElement $element
342
     * @param mixed      $value
343
     *
344
     * @throws Exceptions\Blank
345
     *
346
     * @codeCoverageIgnore
347
     */
348
    protected function addCollectionNode(DOMElement $element, $value)
349
    {
350
        if ($element->childNodes->length === 0)
351
        {
352
            $this->convert($element, $value);
353
354
            return;
355
        }
356
357
        /**
358
         * @var $child DOMElement
359
         */
360
        $child = $this->document()->createElement($element->nodeName);
361
//        $child = $element->cloneNode();
362
        $element->parentNode->appendChild($child);
363
        $this->convert($child, $value);
364
    }
365
366
    /**
367
     * @param DOMElement $element
368
     * @param mixed      $value
369
     *
370
     * @codeCoverageIgnore
371
     */
372
    protected function addSequentialNode(DOMElement $element, $value)
373
    {
374
        if (empty($element->nodeValue))
375
        {
376
            $element->nodeValue = \htmlspecialchars($value);
377
378
            return;
379
        }
380
381
        $child = $this->document()->createElement($element->nodeName);
382
//        $child = $element->cloneNode();
383
        $child->nodeValue = \htmlspecialchars($value);
384
        $element->parentNode->appendChild($child);
385
    }
386
387
    /**
388
     * @param DOMElement         $element
389
     * @param array|\Traversable $storage
390
     *
391
     * @codeCoverageIgnore
392
     */
393
    protected function addAttributes(DOMElement $element, $storage)
394
    {
395
        foreach ($storage as $attrKey => $attrVal)
396
        {
397
            $element->setAttribute($attrKey, $attrVal);
398
        }
399
    }
400
401
}
402