vendor/symfony/config/Util/XmlUtils.php line 115

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Config\Util;
  11. use Symfony\Component\Config\Util\Exception\InvalidXmlException;
  12. use Symfony\Component\Config\Util\Exception\XmlParsingException;
  13. /**
  14.  * XMLUtils is a bunch of utility methods to XML operations.
  15.  *
  16.  * This class contains static methods only and is not meant to be instantiated.
  17.  *
  18.  * @author Fabien Potencier <fabien@symfony.com>
  19.  * @author Martin Hasoň <martin.hason@gmail.com>
  20.  * @author Ole Rößner <ole@roessner.it>
  21.  */
  22. class XmlUtils
  23. {
  24.     /**
  25.      * This class should not be instantiated.
  26.      */
  27.     private function __construct()
  28.     {
  29.     }
  30.     /**
  31.      * Parses an XML string.
  32.      *
  33.      * @param string               $content          An XML string
  34.      * @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation
  35.      *
  36.      * @throws XmlParsingException When parsing of XML file returns error
  37.      * @throws InvalidXmlException When parsing of XML with schema or callable produces any errors unrelated to the XML parsing itself
  38.      * @throws \RuntimeException   When DOM extension is missing
  39.      */
  40.     public static function parse(string $contentstring|callable $schemaOrCallable null): \DOMDocument
  41.     {
  42.         if (!\extension_loaded('dom')) {
  43.             throw new \LogicException('Extension DOM is required.');
  44.         }
  45.         $internalErrors libxml_use_internal_errors(true);
  46.         libxml_clear_errors();
  47.         $dom = new \DOMDocument();
  48.         $dom->validateOnParse true;
  49.         if (!$dom->loadXML($content\LIBXML_NONET \LIBXML_COMPACT)) {
  50.             throw new XmlParsingException(implode("\n", static::getXmlErrors($internalErrors)));
  51.         }
  52.         $dom->normalizeDocument();
  53.         libxml_use_internal_errors($internalErrors);
  54.         foreach ($dom->childNodes as $child) {
  55.             if (\XML_DOCUMENT_TYPE_NODE === $child->nodeType) {
  56.                 throw new XmlParsingException('Document types are not allowed.');
  57.             }
  58.         }
  59.         if (null !== $schemaOrCallable) {
  60.             $internalErrors libxml_use_internal_errors(true);
  61.             libxml_clear_errors();
  62.             $e null;
  63.             if (\is_callable($schemaOrCallable)) {
  64.                 try {
  65.                     $valid $schemaOrCallable($dom$internalErrors);
  66.                 } catch (\Exception $e) {
  67.                     $valid false;
  68.                 }
  69.             } elseif (is_file($schemaOrCallable)) {
  70.                 $schemaSource file_get_contents((string) $schemaOrCallable);
  71.                 $valid = @$dom->schemaValidateSource($schemaSource);
  72.             } else {
  73.                 libxml_use_internal_errors($internalErrors);
  74.                 throw new XmlParsingException(sprintf('Invalid XSD file: "%s".'$schemaOrCallable));
  75.             }
  76.             if (!$valid) {
  77.                 $messages = static::getXmlErrors($internalErrors);
  78.                 if (empty($messages)) {
  79.                     throw new InvalidXmlException('The XML is not valid.'0$e);
  80.                 }
  81.                 throw new XmlParsingException(implode("\n"$messages), 0$e);
  82.             }
  83.         }
  84.         libxml_clear_errors();
  85.         libxml_use_internal_errors($internalErrors);
  86.         return $dom;
  87.     }
  88.     /**
  89.      * Loads an XML file.
  90.      *
  91.      * @param string               $file             An XML file path
  92.      * @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation
  93.      *
  94.      * @throws \InvalidArgumentException When loading of XML file returns error
  95.      * @throws XmlParsingException       When XML parsing returns any errors
  96.      * @throws \RuntimeException         When DOM extension is missing
  97.      */
  98.     public static function loadFile(string $filestring|callable $schemaOrCallable null): \DOMDocument
  99.     {
  100.         if (!is_file($file)) {
  101.             throw new \InvalidArgumentException(sprintf('Resource "%s" is not a file.'$file));
  102.         }
  103.         if (!is_readable($file)) {
  104.             throw new \InvalidArgumentException(sprintf('File "%s" is not readable.'$file));
  105.         }
  106.         $content = @file_get_contents($file);
  107.         if ('' === trim($content)) {
  108.             throw new \InvalidArgumentException(sprintf('File "%s" does not contain valid XML, it is empty.'$file));
  109.         }
  110.         try {
  111.             return static::parse($content$schemaOrCallable);
  112.         } catch (InvalidXmlException $e) {
  113.             throw new XmlParsingException(sprintf('The XML file "%s" is not valid.'$file), 0$e->getPrevious());
  114.         }
  115.     }
  116.     /**
  117.      * Converts a \DOMElement object to a PHP array.
  118.      *
  119.      * The following rules applies during the conversion:
  120.      *
  121.      *  * Each tag is converted to a key value or an array
  122.      *    if there is more than one "value"
  123.      *
  124.      *  * The content of a tag is set under a "value" key (<foo>bar</foo>)
  125.      *    if the tag also has some nested tags
  126.      *
  127.      *  * The attributes are converted to keys (<foo foo="bar"/>)
  128.      *
  129.      *  * The nested-tags are converted to keys (<foo><foo>bar</foo></foo>)
  130.      *
  131.      * @param \DOMElement $element     A \DOMElement instance
  132.      * @param bool        $checkPrefix Check prefix in an element or an attribute name
  133.      */
  134.     public static function convertDomElementToArray(\DOMElement $elementbool $checkPrefix true): mixed
  135.     {
  136.         $prefix = (string) $element->prefix;
  137.         $empty true;
  138.         $config = [];
  139.         foreach ($element->attributes as $name => $node) {
  140.             if ($checkPrefix && !\in_array((string) $node->prefix, [''$prefix], true)) {
  141.                 continue;
  142.             }
  143.             $config[$name] = static::phpize($node->value);
  144.             $empty false;
  145.         }
  146.         $nodeValue false;
  147.         foreach ($element->childNodes as $node) {
  148.             if ($node instanceof \DOMText) {
  149.                 if ('' !== trim($node->nodeValue)) {
  150.                     $nodeValue trim($node->nodeValue);
  151.                     $empty false;
  152.                 }
  153.             } elseif ($checkPrefix && $prefix != (string) $node->prefix) {
  154.                 continue;
  155.             } elseif (!$node instanceof \DOMComment) {
  156.                 $value = static::convertDomElementToArray($node$checkPrefix);
  157.                 $key $node->localName;
  158.                 if (isset($config[$key])) {
  159.                     if (!\is_array($config[$key]) || !\is_int(key($config[$key]))) {
  160.                         $config[$key] = [$config[$key]];
  161.                     }
  162.                     $config[$key][] = $value;
  163.                 } else {
  164.                     $config[$key] = $value;
  165.                 }
  166.                 $empty false;
  167.             }
  168.         }
  169.         if (false !== $nodeValue) {
  170.             $value = static::phpize($nodeValue);
  171.             if (\count($config)) {
  172.                 $config['value'] = $value;
  173.             } else {
  174.                 $config $value;
  175.             }
  176.         }
  177.         return !$empty $config null;
  178.     }
  179.     /**
  180.      * Converts an xml value to a PHP type.
  181.      */
  182.     public static function phpize(string|\Stringable $value): mixed
  183.     {
  184.         $value = (string) $value;
  185.         $lowercaseValue strtolower($value);
  186.         switch (true) {
  187.             case 'null' === $lowercaseValue:
  188.                 return null;
  189.             case ctype_digit($value):
  190.             case isset($value[1]) && '-' === $value[0] && ctype_digit(substr($value1)):
  191.                 $raw $value;
  192.                 $cast = (int) $value;
  193.                 return self::isOctal($value) ? \intval($value8) : (($raw === (string) $cast) ? $cast $raw);
  194.             case 'true' === $lowercaseValue:
  195.                 return true;
  196.             case 'false' === $lowercaseValue:
  197.                 return false;
  198.             case isset($value[1]) && '0b' == $value[0].$value[1] && preg_match('/^0b[01]*$/'$value):
  199.                 return bindec($value);
  200.             case is_numeric($value):
  201.                 return '0x' === $value[0].$value[1] ? hexdec($value) : (float) $value;
  202.             case preg_match('/^0x[0-9a-f]++$/i'$value):
  203.                 return hexdec($value);
  204.             case preg_match('/^[+-]?[0-9]+(\.[0-9]+)?$/'$value):
  205.                 return (float) $value;
  206.             default:
  207.                 return $value;
  208.         }
  209.     }
  210.     protected static function getXmlErrors(bool $internalErrors)
  211.     {
  212.         $errors = [];
  213.         foreach (libxml_get_errors() as $error) {
  214.             $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
  215.                 \LIBXML_ERR_WARNING == $error->level 'WARNING' 'ERROR',
  216.                 $error->code,
  217.                 trim($error->message),
  218.                 $error->file ?: 'n/a',
  219.                 $error->line,
  220.                 $error->column
  221.             );
  222.         }
  223.         libxml_clear_errors();
  224.         libxml_use_internal_errors($internalErrors);
  225.         return $errors;
  226.     }
  227.     private static function isOctal(string $str): bool
  228.     {
  229.         if ('-' === $str[0]) {
  230.             $str substr($str1);
  231.         }
  232.         return $str === '0'.decoct(\intval($str8));
  233.     }
  234. }