Passed
Push — master ( 25ed05...407684 )
by Бабичев
05:15
created

XMLReader::addNode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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