[ Index ]

PHP Cross Reference of YOURLS

title

Body

[close]

/includes/vendor/spatie/array-to-xml/src/ -> ArrayToXml.php (source)

   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  }


Generated: Tue Jan 6 05:10:29 2026 Cross-referenced by PHPXref 0.7.1