Passed
Push — master ( 68bace...6d1614 )
by Brian
02:33 queued 12s
created

ArrayToXml   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 234
Duplicated Lines 0 %

Test Coverage

Coverage 52.11%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 37
eloc 65
c 2
b 0
f 0
dl 0
loc 234
ccs 37
cts 71
cp 0.5211
rs 9.44

11 Methods

Rating   Name   Duplication   Size   Complexity  
A addSequentialNode() 0 11 2
B createRootElement() 0 21 7
A addCollectionNode() 0 11 3
A addAttributes() 0 4 2
A addNode() 0 6 1
A toDom() 0 3 1
A isArrayAllKeySequential() 0 11 3
A convert() 0 10 1
A toXml() 0 3 1
C convertElement() 0 25 13
A __construct() 0 20 3
1
<?php
2
3
namespace Bmatovu\LaravelXml\Support;
4
5
use DOMDocument;
6
use DOMElement;
7
use DOMException;
8
use Illuminate\Support\Str;
9
10
/**
11
 * @see https://github.com/spatie/array-to-xml
12
 */
13
class ArrayToXml
14
{
15
    /**
16
     * The DOM Document.
17
     *
18
     * @var \DOMDocument
19
     */
20
    protected $domDocument;
21
22
    /**
23
     * Set to enable replacing space with underscore.
24
     *
25
     * @var string
26
     */
27
    protected $elementCase = 'snake';
28
29
    /**
30
     * Construct a new instance.
31
     *
32
     * @param string $rootElement
33
     * @param string $elementCase
34
     * @param string $xmlVersion
35
     * @param string $xmlEncoding
36
     *
37
     * @throws \DOMException
38
     */
39 2
    public function __construct(
40
        array $content,
41
        $rootElement = 'root',
42
        $elementCase = 'snake',
43
        $xmlVersion = '1.0',
44
        $xmlEncoding = 'UTF-8'
45
    ) {
46 2
        $this->domDocument = new DOMDocument($xmlVersion, $xmlEncoding);
47
48 2
        $this->elementCase = $elementCase;
49
50 2
        if ($this->isArrayAllKeySequential($content) && ! empty($content)) {
51
            throw new DOMException('Invalid Character Error');
52
        }
53
54 2
        $root = $this->createRootElement($rootElement);
55
56 2
        $this->domDocument->appendChild($root);
57
58 2
        $this->convertElement($root, $content);
59
    }
60
61
    /**
62
     * Convert the given array to an xml string.
63
     *
64
     * @param string[] $arr
65
     * @param string $rootElementName
66
     * @param string $elementCase
67
     * @param string $xmlVersion
68
     * @param string $xmlEncoding
69
     *
70
     * @return string
71
     */
72 2
    public static function convert(
73
        array $arr,
74
        $rootElementName = 'root',
75
        $elementCase = 'snake',
76
        $xmlVersion = '1.0',
77
        $xmlEncoding = 'UTF-8'
78
    ) {
79 2
        $converter = new static($arr, $rootElementName, $elementCase, $xmlVersion, $xmlEncoding);
80
81 2
        return $converter->toXml();
82
    }
83
84
    /**
85
     * Return as XML.
86
     *
87
     * @return string
88
     */
89 2
    public function toXml()
90
    {
91 2
        return $this->domDocument->saveXML();
92
    }
93
94
    /**
95
     * Return as DOM object.
96
     *
97
     * @return \DOMDocument
98
     */
99
    public function toDom()
100
    {
101
        return $this->domDocument;
102
    }
103
104
    /**
105
     * Add node.
106
     *
107
     * @param string $key
108
     * @param array $value
109
     */
110 2
    protected function addNode(DOMElement $domElement, $key, $value): void
111
    {
112 2
        $key = Str::{$this->elementCase}($key);
113 2
        $child = $this->domDocument->createElement($key);
114 2
        $domElement->appendChild($child);
115 2
        $this->convertElement($child, $value);
116
    }
117
118
    /**
119
     * Add collection node.
120
     *
121
     * @param array $value
122
     */
123
    protected function addCollectionNode(DOMElement $domElement, $value): void
124
    {
125
        if (0 === $domElement->childNodes->length && 0 === $domElement->attributes->length) {
126
            $this->convertElement($domElement, $value);
127
128
            return;
129
        }
130
131
        $child = new DOMElement($domElement->tagName);
132
        $domElement->parentNode->appendChild($child);
0 ignored issues
show
Bug introduced by
The method appendChild() does not exist on null. ( Ignorable by Annotation )

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

132
        $domElement->parentNode->/** @scrutinizer ignore-call */ 
133
                                 appendChild($child);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
133
        $this->convertElement($child, $value);
134
    }
135
136
    /**
137
     * Add sequential node.
138
     *
139
     * @param array $value
140
     */
141
    protected function addSequentialNode(DOMElement $domElement, $value): void
142
    {
143
        if (empty($domElement->nodeValue)) {
144
            $domElement->nodeValue = htmlspecialchars($value);
0 ignored issues
show
Bug introduced by
$value of type array is incompatible with the type string expected by parameter $string of htmlspecialchars(). ( Ignorable by Annotation )

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

144
            $domElement->nodeValue = htmlspecialchars(/** @scrutinizer ignore-type */ $value);
Loading history...
145
146
            return;
147
        }
148
149
        $child = new DOMElement($domElement->tagName);
150
        $child->nodeValue = htmlspecialchars($value);
151
        $domElement->parentNode->appendChild($child);
152
    }
153
154
    /**
155
     * Check if all array keys are sequential.
156
     *
157
     * @param array|string $value
158
     *
159
     * @return bool
160
     */
161 2
    protected function isArrayAllKeySequential($value)
162
    {
163 2
        if (! \is_array($value)) {
164 2
            return false;
165
        }
166
167 2
        if (0 === \count($value)) {
168
            return true;
169
        }
170
171 2
        return array_unique(array_map('is_int', array_keys($value))) === [true];
172
    }
173
174
    /**
175
     * Add attributes.
176
     *
177
     * @param \DOMElement $domElement
178
     * @param string[] $data
179
     */
180
    protected function addAttributes($domElement, $data): void
181
    {
182
        foreach ($data as $attrKey => $attrVal) {
183
            $domElement->setAttribute($attrKey, $attrVal);
184
        }
185
    }
186
187
    /**
188
     * Create the root element.
189
     *
190
     * @param array|string $rootElement
191
     *
192
     * @return \DOMElement
193
     */
194 2
    protected function createRootElement($rootElement)
195
    {
196 2
        if (\is_string($rootElement)) {
197 2
            $rootElementName = $rootElement ?: 'root';
198
199 2
            return $this->domDocument->createElement($rootElementName);
200
        }
201
202
        $rootElementName = isset($rootElement['rootElementName']) ? $rootElement['rootElementName'] : 'root';
203
204
        $domElement = $this->domDocument->createElement($rootElementName);
205
206
        foreach ($rootElement as $key => $value) {
207
            if ('_attributes' !== $key && '@attributes' !== $key) {
208
                continue;
209
            }
210
211
            $this->addAttributes($domElement, $rootElement[$key]);
212
        }
213
214
        return $domElement;
215
    }
216
217
    /**
218
     * Parse individual element.
219
     *
220
     * @param array|string $value
221
     */
222 2
    protected function convertElement(DOMElement $domElement, $value): void
223
    {
224 2
        $sequential = $this->isArrayAllKeySequential($value);
225
226 2
        if (! \is_array($value)) {
227 2
            $domElement->nodeValue = htmlspecialchars($value);
228
229 2
            return;
230
        }
231
232 2
        foreach ($value as $key => $data) {
233 2
            if (! $sequential) {
234 2
                if (('_attributes' === $key) || ('@attributes' === $key)) {
235
                    $this->addAttributes($domElement, $data);
236 2
                } elseif ((('_value' === $key) || ('@value' === $key)) && \is_string($data)) {
237
                    $domElement->nodeValue = htmlspecialchars($data);
238 2
                } elseif ((('_cdata' === $key) || ('@cdata' === $key)) && \is_string($data)) {
239
                    $domElement->appendChild($this->domDocument->createCDATASection($data));
240
                } else {
241 2
                    $this->addNode($domElement, $key, $data);
242
                }
243
            } elseif (\is_array($data)) {
244
                $this->addCollectionNode($domElement, $data);
245
            } else {
246
                $this->addSequentialNode($domElement, $data);
247
            }
248
        }
249
    }
250
}
251