| [ Index ] |
PHP Cross Reference of YOURLS |
[Summary view] [Print] [Text view]
1 <?php 2 3 namespace Spatie\ArrayToXml; 4 5 use Closure; 6 use DOMDocument; 7 use DOMElement; 8 use DOMException; 9 use Exception; 10 11 class ArrayToXml 12 { 13 protected DOMDocument $document; 14 protected DOMElement $rootNode; 15 16 protected bool $replaceSpacesByUnderScoresInKeyNames = true; 17 18 protected bool $addXmlDeclaration = true; 19 20 protected string $numericTagNamePrefix = 'numeric_'; 21 22 protected array $options = ['convertNullToXsiNil' => false, 'convertBoolToString' => false]; 23 24 public function __construct( 25 array $array, 26 string | array $rootElement = '', 27 bool $replaceSpacesByUnderScoresInKeyNames = true, 28 string | null $xmlEncoding = null, 29 string $xmlVersion = '1.0', 30 array $domProperties = [], 31 bool | null $xmlStandalone = null, 32 bool $addXmlDeclaration = true, 33 array | null $options = ['convertNullToXsiNil' => false, 'convertBoolToString' => false] 34 ) { 35 $this->document = new DOMDocument($xmlVersion, $xmlEncoding ?? ''); 36 37 if (! is_null($xmlStandalone)) { 38 $this->document->xmlStandalone = $xmlStandalone; 39 } 40 41 if (! empty($domProperties)) { 42 $this->setDomProperties($domProperties); 43 } 44 45 $this->addXmlDeclaration = $addXmlDeclaration; 46 47 $this->options = array_merge($this->options, $options); 48 49 $this->replaceSpacesByUnderScoresInKeyNames = $replaceSpacesByUnderScoresInKeyNames; 50 51 if (! empty($array) && $this->isArrayAllKeySequential($array)) { 52 throw new DOMException('Invalid Character Error'); 53 } 54 55 $this->rootNode = $this->createRootElement($rootElement); 56 57 $this->document->appendChild($this->rootNode); 58 59 $this->convertElement($this->rootNode, $array); 60 } 61 62 public function setNumericTagNamePrefix(string $prefix): void 63 { 64 $this->numericTagNamePrefix = $prefix; 65 } 66 67 public static function convert( 68 array $array, 69 $rootElement = '', 70 bool $replaceSpacesByUnderScoresInKeyNames = true, 71 ?string $xmlEncoding = null, 72 string $xmlVersion = '1.0', 73 array $domProperties = [], 74 ?bool $xmlStandalone = null, 75 bool $addXmlDeclaration = true, 76 array $options = ['convertNullToXsiNil' => false] 77 ): string { 78 $converter = new static( 79 $array, 80 $rootElement, 81 $replaceSpacesByUnderScoresInKeyNames, 82 $xmlEncoding, 83 $xmlVersion, 84 $domProperties, 85 $xmlStandalone, 86 $addXmlDeclaration, 87 $options 88 ); 89 90 return $converter->toXml(); 91 } 92 93 public function toXml($options = 0): string 94 { 95 return $this->addXmlDeclaration 96 ? $this->document->saveXML(options: $options) 97 : $this->document->saveXML($this->document->documentElement, $options); 98 } 99 100 public function toDom(): DOMDocument 101 { 102 return $this->document; 103 } 104 105 protected function ensureValidDomProperties(array $domProperties): void 106 { 107 foreach ($domProperties as $key => $value) { 108 if (! property_exists($this->document, $key)) { 109 throw new Exception("{$key} is not a valid property of DOMDocument"); 110 } 111 } 112 } 113 114 public function setDomProperties(array $domProperties): self 115 { 116 $this->ensureValidDomProperties($domProperties); 117 118 foreach ($domProperties as $key => $value) { 119 $this->document->{$key} = $value; 120 } 121 122 return $this; 123 } 124 125 public function prettify(): self 126 { 127 $this->document->preserveWhiteSpace = false; 128 $this->document->formatOutput = true; 129 130 return $this; 131 } 132 133 public function dropXmlDeclaration(): self 134 { 135 $this->addXmlDeclaration = false; 136 137 return $this; 138 } 139 140 public function addProcessingInstruction(string $target, string $data): self 141 { 142 $elements = $this->document->getElementsByTagName('*'); 143 144 $rootElement = $elements->count() > 0 ? $elements->item(0) : null; 145 146 $processingInstruction = $this->document->createProcessingInstruction($target, $data); 147 148 $this->document->insertBefore($processingInstruction, $rootElement); 149 150 return $this; 151 } 152 153 protected function convertElement(DOMElement $element, mixed $value): void 154 { 155 if ($value instanceof Closure) { 156 $value = $value(); 157 } 158 159 $sequential = $this->isArrayAllKeySequential($value); 160 161 if (! is_array($value)) { 162 $value = htmlspecialchars($value ?? ''); 163 164 $value = $this->removeControlCharacters($value); 165 166 $element->nodeValue = $value; 167 168 return; 169 } 170 171 foreach ($value as $key => $data) { 172 if (! $sequential) { 173 if (($key === '_attributes') || ($key === '@attributes')) { 174 $this->addAttributes($element, $data); 175 } elseif ((($key === '_value') || ($key === '@value')) && is_scalar($data)) { 176 $element->nodeValue = htmlspecialchars($data); 177 } elseif ((($key === '_cdata') || ($key === '@cdata')) && is_scalar($data)) { 178 $element->appendChild($this->document->createCDATASection($data)); 179 } elseif ((($key === '_mixed') || ($key === '@mixed')) && is_scalar($data)) { 180 $fragment = $this->document->createDocumentFragment(); 181 $fragment->appendXML($data); 182 $element->appendChild($fragment); 183 } elseif ($key === '__numeric') { 184 $this->addNumericNode($element, $data); 185 } elseif (str_starts_with($key, '__custom:')) { 186 $this->addNode($element, str_replace('\:', ':', preg_split('/(?<!\\\):/', $key)[1]), $data); 187 } elseif (str_starts_with($key, '_comment') || str_starts_with($key, '@comment')) { 188 if ($data !== null && $data !== '') { 189 $element->appendChild(new \DOMComment($data)); 190 } 191 } else { 192 $this->addNode($element, $key, $data); 193 } 194 } elseif (is_array($data)) { 195 $this->addCollectionNode($element, $data); 196 } else { 197 $this->addSequentialNode($element, $data); 198 } 199 } 200 } 201 202 protected function addNumericNode(DOMElement $element, mixed $value): void 203 { 204 foreach ($value as $key => $item) { 205 $this->convertElement($element, [$this->numericTagNamePrefix.$key => $item]); 206 } 207 } 208 209 protected function addNode(DOMElement $element, string $key, mixed $value): void 210 { 211 if ($this->replaceSpacesByUnderScoresInKeyNames) { 212 $key = str_replace(' ', '_', $key); 213 } 214 215 $child = $this->document->createElement($key); 216 217 $this->addNodeTypeAttribute($child, $value); 218 219 $element->appendChild($child); 220 221 $value = $this->convertNodeValue($child, $value); 222 223 $this->convertElement($child, $value); 224 } 225 226 protected function convertNodeValue(DOMElement $element, mixed $value): mixed 227 { 228 if ($this->options['convertBoolToString'] && is_bool($value)) { 229 $value = $value ? 'true' : 'false'; 230 } 231 232 return $value; 233 } 234 235 protected function addNodeTypeAttribute(DOMElement $element, mixed $value): void 236 { 237 if ($this->options['convertNullToXsiNil'] && is_null($value)) { 238 if (! $this->rootNode->hasAttribute('xmlns:xsi')) { 239 $this->rootNode->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); 240 } 241 242 $element->setAttribute('xsi:nil', 'true'); 243 } 244 } 245 246 protected function addCollectionNode(DOMElement $element, mixed $value): void 247 { 248 if ($element->childNodes->length === 0 && $element->attributes->length === 0) { 249 $this->convertElement($element, $value); 250 251 return; 252 } 253 254 $child = $this->document->createElement($element->tagName); 255 $element->parentNode->appendChild($child); 256 $this->convertElement($child, $value); 257 } 258 259 protected function addSequentialNode(DOMElement $element, mixed $value): void 260 { 261 if (empty($element->nodeValue) && ! is_numeric($element->nodeValue)) { 262 $element->nodeValue = htmlspecialchars($value); 263 264 return; 265 } 266 267 $child = $this->document->createElement($element->tagName); 268 $child->nodeValue = htmlspecialchars($value); 269 $element->parentNode->appendChild($child); 270 } 271 272 protected function isArrayAllKeySequential(array | string | null $value): bool 273 { 274 if (! is_array($value)) { 275 return false; 276 } 277 278 if (count($value) <= 0) { 279 return true; 280 } 281 282 if (\key($value) === '__numeric') { 283 return false; 284 } 285 286 return array_unique(array_map('is_int', array_keys($value))) === [true]; 287 } 288 289 protected function addAttributes(DOMElement $element, array $data): void 290 { 291 foreach ($data as $attrKey => $attrVal) { 292 $element->setAttribute($attrKey, $attrVal ?? ''); 293 } 294 } 295 296 protected function createRootElement(string|array $rootElement): DOMElement 297 { 298 if (is_string($rootElement)) { 299 $rootElementName = $rootElement ?: 'root'; 300 301 return $this->document->createElement($rootElementName); 302 } 303 304 $rootElementName = $rootElement['rootElementName'] ?? 'root'; 305 306 $element = $this->document->createElement($rootElementName); 307 308 foreach ($rootElement as $key => $value) { 309 if ($key !== '_attributes' && $key !== '@attributes') { 310 continue; 311 } 312 313 $this->addAttributes($element, $value); 314 } 315 316 return $element; 317 } 318 319 protected function removeControlCharacters(string $value): string 320 { 321 return preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/', '', $value); 322 } 323 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Tue Jan 6 05:10:29 2026 | Cross-referenced by PHPXref 0.7.1 |