Passed
Push — master ( b84cd1...8da634 )
by Brian
02:40
created

ArrayToXml::convert()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 11
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 6
crap 1
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
     * @param bool $xmlStandalone
37
     *
38
     * @throws \DOMException
39
     */
40 3
    public function __construct(
41
        array $content,
42
        $rootElement = 'root',
43
        $elementCase = 'snake',
44
        $xmlVersion = '1.0',
45
        $xmlEncoding = 'UTF-8',
46
        $xmlStandalone = false
47
    ) {
48 3
        $this->domDocument = new DOMDocument($xmlVersion, $xmlEncoding);
49
50 3
        if ($xmlStandalone) {
51 1
            $this->domDocument->xmlStandalone = true;
52
        }
53
54 3
        $this->elementCase = $elementCase;
55
56 3
        if ($this->isArrayAllKeySequential($content) && ! empty($content)) {
57
            throw new DOMException('Invalid Character Error');
58
        }
59
60 3
        $root = $this->createRootElement($rootElement);
61
62 3
        $this->domDocument->appendChild($root);
63
64 3
        $this->convertElement($root, $content);
65
    }
66
67
    /**
68
     * Convert the given array to an xml string.
69
     *
70
     * @param string[] $arr
71
     * @param string $rootElementName
72
     * @param string $elementCase
73
     * @param string $xmlVersion
74
     * @param string $xmlEncoding
75
     * @param bool $xmlStandalone
76
     *
77
     * @return string
78
     */
79 3
    public static function convert(
80
        array $arr,
81
        $rootElementName = 'root',
82
        $elementCase = 'snake',
83
        $xmlVersion = '1.0',
84
        $xmlEncoding = 'UTF-8',
85
        $xmlStandalone = false
86
    ) {
87 3
        $converter = new static($arr, $rootElementName, $elementCase, $xmlVersion, $xmlEncoding, $xmlStandalone);
88
89 3
        return $converter->toXml();
90
    }
91
92
    /**
93
     * Return as XML.
94
     *
95
     * @return string
96
     */
97 3
    public function toXml()
98
    {
99 3
        return $this->domDocument->saveXML();
100
    }
101
102
    /**
103
     * Return as DOM object.
104
     *
105
     * @return \DOMDocument
106
     */
107
    public function toDom()
108
    {
109
        return $this->domDocument;
110
    }
111
112
    /**
113
     * Add node.
114
     *
115
     * @param string $key
116
     * @param array $value
117
     */
118 3
    protected function addNode(DOMElement $domElement, $key, $value): void
119
    {
120 3
        $key = Str::{$this->elementCase}($key);
121 3
        $child = $this->domDocument->createElement($key);
122 3
        $domElement->appendChild($child);
123 3
        $this->convertElement($child, $value);
124
    }
125
126
    /**
127
     * Add collection node.
128
     *
129
     * @param array $value
130
     */
131
    protected function addCollectionNode(DOMElement $domElement, $value): void
132
    {
133
        if (0 === $domElement->childNodes->length && 0 === $domElement->attributes->length) {
134
            $this->convertElement($domElement, $value);
135
136
            return;
137
        }
138
139
        $child = new DOMElement($domElement->tagName);
140
        $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

140
        $domElement->parentNode->/** @scrutinizer ignore-call */ 
141
                                 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...
141
        $this->convertElement($child, $value);
142
    }
143
144
    /**
145
     * Add sequential node.
146
     *
147
     * @param array $value
148
     */
149
    protected function addSequentialNode(DOMElement $domElement, $value): void
150
    {
151
        if (empty($domElement->nodeValue)) {
152
            $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

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