1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Spatie\ArrayToXml; |
4
|
|
|
|
5
|
|
|
use DOMElement; |
6
|
|
|
use DOMDocument; |
7
|
|
|
use DOMException; |
8
|
|
|
|
9
|
|
|
class ArrayToXml |
10
|
|
|
{ |
11
|
|
|
/** |
12
|
|
|
* The root DOM Document. |
13
|
|
|
* |
14
|
|
|
* @var DOMDocument |
15
|
|
|
*/ |
16
|
|
|
protected $document; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Set to enable replacing space with underscore. |
20
|
|
|
* |
21
|
|
|
* @var bool |
22
|
|
|
*/ |
23
|
|
|
protected $replaceSpacesByUnderScoresInKeyNames = true; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Construct a new instance. |
27
|
|
|
* |
28
|
|
|
* @param string[] $array |
29
|
|
|
* @param string|array $rootElement |
30
|
|
|
* @param bool $replaceSpacesByUnderScoresInKeyNames |
31
|
|
|
* @param string $xmlEncoding |
32
|
|
|
* @param string $xmlVersion |
33
|
|
|
* |
34
|
|
|
* @throws DOMException |
35
|
|
|
*/ |
36
|
|
|
public function __construct(array $array, $rootElement = '', $replaceSpacesByUnderScoresInKeyNames = true, $xmlEncoding = null, $xmlVersion = '1.0') |
37
|
|
|
{ |
38
|
|
|
$this->document = new DOMDocument($xmlVersion, $xmlEncoding); |
39
|
|
|
$this->replaceSpacesByUnderScoresInKeyNames = $replaceSpacesByUnderScoresInKeyNames; |
40
|
|
|
|
41
|
|
|
if ($this->isArrayAllKeySequential($array) && ! empty($array)) { |
42
|
|
|
throw new DOMException('Invalid Character Error'); |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
$root = $this->createRootElement($rootElement); |
46
|
|
|
|
47
|
|
|
$this->document->appendChild($root); |
48
|
|
|
|
49
|
|
|
$this->convertElement($root, $array); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Convert the given array to an xml string. |
54
|
|
|
* |
55
|
|
|
* @param string[] $array |
56
|
|
|
* @param string $rootElementName |
57
|
|
|
* @param bool $replaceSpacesByUnderScoresInKeyNames |
58
|
|
|
* @param string $xmlEncoding |
59
|
|
|
* @param string $xmlVersion |
60
|
|
|
* |
61
|
|
|
* @return string |
62
|
|
|
*/ |
63
|
|
|
public static function convert(array $array, $rootElementName = '', $replaceSpacesByUnderScoresInKeyNames = true, $xmlEncoding = null, $xmlVersion = '1.0') |
64
|
|
|
{ |
65
|
|
|
$converter = new static($array, $rootElementName, $replaceSpacesByUnderScoresInKeyNames, $xmlEncoding, $xmlVersion); |
66
|
|
|
|
67
|
|
|
return $converter->toXml(); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Return as XML. |
72
|
|
|
* |
73
|
|
|
* @return string |
74
|
|
|
*/ |
75
|
|
|
public function toXml() |
76
|
|
|
{ |
77
|
|
|
return $this->document->saveXML(); |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Return as DOM object. |
82
|
|
|
* |
83
|
|
|
* @return DOMDocument |
84
|
|
|
*/ |
85
|
|
|
public function toDom() |
86
|
|
|
{ |
87
|
|
|
return $this->document; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Parse individual element. |
92
|
|
|
* |
93
|
|
|
* @param DOMElement $element |
94
|
|
|
* @param string|string[] $value |
95
|
|
|
*/ |
96
|
|
|
private function convertElement(DOMElement $element, $value) |
97
|
|
|
{ |
98
|
|
|
$sequential = $this->isArrayAllKeySequential($value); |
99
|
|
|
|
100
|
|
|
if (! is_array($value)) { |
101
|
|
|
$element->nodeValue = htmlspecialchars($value); |
102
|
|
|
|
103
|
|
|
return; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
foreach ($value as $key => $data) { |
107
|
|
|
if (! $sequential) { |
108
|
|
|
if (($key === '_attributes') || ($key === '@attributes')) { |
109
|
|
|
$this->addAttributes($element, $data); |
110
|
|
|
} elseif ((($key === '_value') || ($key === '@value')) && is_string($data)) { |
111
|
|
|
$element->nodeValue = htmlspecialchars($data); |
112
|
|
|
} elseif ((($key === '_cdata') || ($key === '@cdata')) && is_string($data)) { |
113
|
|
|
$element->appendChild($this->document->createCDATASection($data)); |
114
|
|
|
} else { |
115
|
|
|
$this->addNode($element, $key, $data); |
116
|
|
|
} |
117
|
|
|
} elseif (is_array($data)) { |
118
|
|
|
$this->addCollectionNode($element, $data); |
119
|
|
|
} else { |
120
|
|
|
$this->addSequentialNode($element, $data); |
121
|
|
|
} |
122
|
|
|
} |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* Add node. |
127
|
|
|
* |
128
|
|
|
* @param DOMElement $element |
129
|
|
|
* @param string $key |
130
|
|
|
* @param string|string[] $value |
131
|
|
|
*/ |
132
|
|
|
protected function addNode(DOMElement $element, $key, $value) |
133
|
|
|
{ |
134
|
|
|
if ($this->replaceSpacesByUnderScoresInKeyNames) { |
135
|
|
|
$key = str_replace(' ', '_', $key); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
$child = $this->document->createElement($key); |
139
|
|
|
$element->appendChild($child); |
140
|
|
|
$this->convertElement($child, $value); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Add collection node. |
145
|
|
|
* |
146
|
|
|
* @param DOMElement $element |
147
|
|
|
* @param string|string[] $value |
148
|
|
|
* |
149
|
|
|
* @internal param string $key |
150
|
|
|
*/ |
151
|
|
|
protected function addCollectionNode(DOMElement $element, $value) |
152
|
|
|
{ |
153
|
|
|
if ($element->childNodes->length === 0 && $element->attributes->length === 0) { |
|
|
|
|
154
|
|
|
$this->convertElement($element, $value); |
155
|
|
|
|
156
|
|
|
return; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
$child = $element->cloneNode(); |
160
|
|
|
$element->parentNode->appendChild($child); |
161
|
|
|
$this->convertElement($child, $value); |
|
|
|
|
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Add sequential node. |
166
|
|
|
* |
167
|
|
|
* @param DOMElement $element |
168
|
|
|
* @param string|string[] $value |
169
|
|
|
* |
170
|
|
|
* @internal param string $key |
171
|
|
|
*/ |
172
|
|
|
protected function addSequentialNode(DOMElement $element, $value) |
173
|
|
|
{ |
174
|
|
|
if (empty($element->nodeValue)) { |
175
|
|
|
$element->nodeValue = htmlspecialchars($value); |
176
|
|
|
|
177
|
|
|
return; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
$child = $element->cloneNode(); |
181
|
|
|
$child->nodeValue = htmlspecialchars($value); |
182
|
|
|
$element->parentNode->appendChild($child); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* Check if array are all sequential. |
187
|
|
|
* |
188
|
|
|
* @param array|string $value |
189
|
|
|
* |
190
|
|
|
* @return bool |
191
|
|
|
*/ |
192
|
|
|
protected function isArrayAllKeySequential($value) |
193
|
|
|
{ |
194
|
|
|
if (! is_array($value)) { |
195
|
|
|
return false; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
if (count($value) <= 0) { |
199
|
|
|
return true; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
return array_unique(array_map('is_int', array_keys($value))) === [true]; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* Add attributes. |
207
|
|
|
* |
208
|
|
|
* @param DOMElement $element |
209
|
|
|
* @param string[] $data |
210
|
|
|
*/ |
211
|
|
|
protected function addAttributes($element, $data) |
212
|
|
|
{ |
213
|
|
|
foreach ($data as $attrKey => $attrVal) { |
214
|
|
|
$element->setAttribute($attrKey, $attrVal); |
215
|
|
|
} |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Create the root element. |
220
|
|
|
* |
221
|
|
|
* @param string|array $rootElement |
222
|
|
|
* @return DOMElement |
223
|
|
|
*/ |
224
|
|
|
protected function createRootElement($rootElement) |
225
|
|
|
{ |
226
|
|
|
if (is_string($rootElement)) { |
227
|
|
|
$rootElementName = $rootElement ?: 'root'; |
228
|
|
|
|
229
|
|
|
return $this->document->createElement($rootElementName); |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
$rootElementName = $rootElement['rootElementName'] ?? 'root'; |
233
|
|
|
|
234
|
|
|
$element = $this->document->createElement($rootElementName); |
235
|
|
|
|
236
|
|
|
foreach ($rootElement as $key => $value) { |
237
|
|
|
if ($key !== '_attributes' && $key !== '@attributes') { |
238
|
|
|
continue; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
$this->addAttributes($element, $rootElement[$key]); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
return $element; |
245
|
|
|
} |
246
|
|
|
} |
247
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.