AzuraCast/src/Xml/Reader.php

132 lines
3.2 KiB
PHP

<?php
/**
* A customized implementation of the Zend/Laminas Config XML reader.
*/
declare(strict_types=1);
namespace App\Xml;
use RuntimeException;
use XMLReader;
use const LIBXML_XINCLUDE;
/**
* XML config reader.
*/
class Reader
{
/**
* Nodes to handle as plain text.
*/
protected static array $textNodes = [
XMLReader::TEXT,
XMLReader::CDATA,
XMLReader::WHITESPACE,
XMLReader::SIGNIFICANT_WHITESPACE,
];
public static function fromString(string $string): array|string|bool
{
if (empty($string)) {
return [];
}
/** @var XMLReader|false $reader */
$reader = XMLReader::XML($string, null, LIBXML_XINCLUDE);
if (false === $reader) {
return false;
}
set_error_handler(
function ($error, $message = '') {
throw new RuntimeException(
sprintf('Error reading XML string: %s', $message),
$error
);
},
E_WARNING
);
$return = self::processNextElement($reader);
restore_error_handler();
$reader->close();
return $return;
}
protected static function processNextElement(XMLReader $reader): string|array
{
$children = [];
$text = '';
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT) {
if ($reader->depth === 0) {
return self::processNextElement($reader);
}
$attributes = self::getAttributes($reader);
$name = $reader->name;
if ($reader->isEmptyElement) {
$child = [];
} else {
$child = self::processNextElement($reader);
}
if ($attributes) {
if (is_string($child)) {
$child = ['_' => $child];
}
if (!is_array($child)) {
$child = [];
}
$child = array_merge($child, $attributes);
}
if (isset($children[$name])) {
if (!is_array($children[$name]) || !array_key_exists(0, $children[$name])) {
$children[$name] = [$children[$name]];
}
$children[$name][] = $child;
} else {
$children[$name] = $child;
}
} elseif ($reader->nodeType === XMLReader::END_ELEMENT) {
break;
} elseif (in_array($reader->nodeType, self::$textNodes)) {
$text .= $reader->value;
}
}
return $children ?: $text;
}
/**
* Get all attributes on the current node.
*
* @return string[]
*/
protected static function getAttributes(XMLReader $reader): array
{
$attributes = [];
if ($reader->hasAttributes) {
while ($reader->moveToNextAttribute()) {
$attributes['@' . $reader->localName] = $reader->value;
}
$reader->moveToElement();
}
return $attributes;
}
}