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

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.      * @return \DOMDocument
  37.      *
  38.      * @throws XmlParsingException When parsing of XML file returns error
  39.      * @throws InvalidXmlException When parsing of XML with schema or callable produces any errors unrelated to the XML parsing itself
  40.      * @throws \RuntimeException   When DOM extension is missing
  41.      */
  42.     public static function parse($content$schemaOrCallable null)
  43.     {
  44.         if (!\extension_loaded('dom')) {
  45.             throw new \RuntimeException('Extension DOM is required.');
  46.         }
  47.         $internalErrors libxml_use_internal_errors(true);
  48.         $disableEntities libxml_disable_entity_loader(true);
  49.         libxml_clear_errors();
  50.         $dom = new \DOMDocument();
  51.         $dom->validateOnParse true;
  52.         if (!$dom->loadXML($contentLIBXML_NONET | (\defined('LIBXML_COMPACT') ? LIBXML_COMPACT 0))) {
  53.             libxml_disable_entity_loader($disableEntities);
  54.             throw new XmlParsingException(implode("\n", static::getXmlErrors($internalErrors)));
  55.         }
  56.         $dom->normalizeDocument();
  57.         libxml_use_internal_errors($internalErrors);
  58.         libxml_disable_entity_loader($disableEntities);
  59.         foreach ($dom->childNodes as $child) {
  60.             if (XML_DOCUMENT_TYPE_NODE === $child->nodeType) {
  61.                 throw new XmlParsingException('Document types are not allowed.');
  62.             }
  63.         }
  64.         if (null !== $schemaOrCallable) {
  65.             $internalErrors libxml_use_internal_errors(true);
  66.             libxml_clear_errors();
  67.             $e null;
  68.             if (\is_callable($schemaOrCallable)) {
  69.                 try {
  70.                     $valid \call_user_func($schemaOrCallable$dom$internalErrors);
  71.                 } catch (\Exception $e) {
  72.                     $valid false;
  73.                 }
  74.             } elseif (!\is_array($schemaOrCallable) && is_file((string) $schemaOrCallable)) {
  75.                 $schemaSource file_get_contents((string) $schemaOrCallable);
  76.                 $valid = @$dom->schemaValidateSource($schemaSource);
  77.             } else {
  78.                 libxml_use_internal_errors($internalErrors);
  79.                 throw new XmlParsingException('The schemaOrCallable argument has to be a valid path to XSD file or callable.');
  80.             }
  81.             if (!$valid) {
  82.                 $messages = static::getXmlErrors($internalErrors);
  83.                 if (empty($messages)) {
  84.                     throw new InvalidXmlException('The XML is not valid.'0$e);
  85.                 }
  86.                 throw new XmlParsingException(implode("\n"$messages), 0$e);
  87.             }
  88.         }
  89.         libxml_clear_errors();
  90.         libxml_use_internal_errors($internalErrors);
  91.         return $dom;
  92.     }
  93.     /**
  94.      * Loads an XML file.
  95.      *
  96.      * @param string               $file             An XML file path
  97.      * @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation
  98.      *
  99.      * @return \DOMDocument
  100.      *
  101.      * @throws \InvalidArgumentException When loading of XML file returns error
  102.      * @throws XmlParsingException       When XML parsing returns any errors
  103.      * @throws \RuntimeException         When DOM extension is missing
  104.      */
  105.     public static function loadFile($file$schemaOrCallable null)
  106.     {
  107.         $content = @file_get_contents($file);
  108.         if ('' === trim($content)) {
  109.             throw new \InvalidArgumentException(sprintf('File %s does not contain valid XML, it is empty.'$file));
  110.         }
  111.         try {
  112.             return static::parse($content$schemaOrCallable);
  113.         } catch (InvalidXmlException $e) {
  114.             throw new XmlParsingException(sprintf('The XML file "%s" is not valid.'$file), 0$e->getPrevious());
  115.         }
  116.     }
  117.     /**
  118.      * Converts a \DOMElement object to a PHP array.
  119.      *
  120.      * The following rules applies during the conversion:
  121.      *
  122.      *  * Each tag is converted to a key value or an array
  123.      *    if there is more than one "value"
  124.      *
  125.      *  * The content of a tag is set under a "value" key (<foo>bar</foo>)
  126.      *    if the tag also has some nested tags
  127.      *
  128.      *  * The attributes are converted to keys (<foo foo="bar"/>)
  129.      *
  130.      *  * The nested-tags are converted to keys (<foo><foo>bar</foo></foo>)
  131.      *
  132.      * @param \DOMElement $element     A \DOMElement instance
  133.      * @param bool        $checkPrefix Check prefix in an element or an attribute name
  134.      *
  135.      * @return mixed
  136.      */
  137.     public static function convertDomElementToArray(\DOMElement $element$checkPrefix true)
  138.     {
  139.         $prefix = (string) $element->prefix;
  140.         $empty true;
  141.         $config = [];
  142.         foreach ($element->attributes as $name => $node) {
  143.             if ($checkPrefix && !\in_array((string) $node->prefix, [''$prefix], true)) {
  144.                 continue;
  145.             }
  146.             $config[$name] = static::phpize($node->value);
  147.             $empty false;
  148.         }
  149.         $nodeValue false;
  150.         foreach ($element->childNodes as $node) {
  151.             if ($node instanceof \DOMText) {
  152.                 if ('' !== trim($node->nodeValue)) {
  153.                     $nodeValue trim($node->nodeValue);
  154.                     $empty false;
  155.                 }
  156.             } elseif ($checkPrefix && $prefix != (string) $node->prefix) {
  157.                 continue;
  158.             } elseif (!$node instanceof \DOMComment) {
  159.                 $value = static::convertDomElementToArray($node$checkPrefix);
  160.                 $key $node->localName;
  161.                 if (isset($config[$key])) {
  162.                     if (!\is_array($config[$key]) || !\is_int(key($config[$key]))) {
  163.                         $config[$key] = [$config[$key]];
  164.                     }
  165.                     $config[$key][] = $value;
  166.                 } else {
  167.                     $config[$key] = $value;
  168.                 }
  169.                 $empty false;
  170.             }
  171.         }
  172.         if (false !== $nodeValue) {
  173.             $value = static::phpize($nodeValue);
  174.             if (\count($config)) {
  175.                 $config['value'] = $value;
  176.             } else {
  177.                 $config $value;
  178.             }
  179.         }
  180.         return !$empty $config null;
  181.     }
  182.     /**
  183.      * Converts an xml value to a PHP type.
  184.      *
  185.      * @param mixed $value
  186.      *
  187.      * @return mixed
  188.      */
  189.     public static function phpize($value)
  190.     {
  191.         $value = (string) $value;
  192.         $lowercaseValue strtolower($value);
  193.         switch (true) {
  194.             case 'null' === $lowercaseValue:
  195.                 return null;
  196.             case ctype_digit($value):
  197.                 $raw $value;
  198.                 $cast = (int) $value;
  199.                 return '0' == $value[0] ? octdec($value) : (((string) $raw === (string) $cast) ? $cast $raw);
  200.             case isset($value[1]) && '-' === $value[0] && ctype_digit(substr($value1)):
  201.                 $raw $value;
  202.                 $cast = (int) $value;
  203.                 return '0' == $value[1] ? octdec($value) : (((string) $raw === (string) $cast) ? $cast $raw);
  204.             case 'true' === $lowercaseValue:
  205.                 return true;
  206.             case 'false' === $lowercaseValue:
  207.                 return false;
  208.             case isset($value[1]) && '0b' == $value[0].$value[1] && preg_match('/^0b[01]*$/'$value):
  209.                 return bindec($value);
  210.             case is_numeric($value):
  211.                 return '0x' === $value[0].$value[1] ? hexdec($value) : (float) $value;
  212.             case preg_match('/^0x[0-9a-f]++$/i'$value):
  213.                 return hexdec($value);
  214.             case preg_match('/^[+-]?[0-9]+(\.[0-9]+)?$/'$value):
  215.                 return (float) $value;
  216.             default:
  217.                 return $value;
  218.         }
  219.     }
  220.     protected static function getXmlErrors($internalErrors)
  221.     {
  222.         $errors = [];
  223.         foreach (libxml_get_errors() as $error) {
  224.             $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
  225.                 LIBXML_ERR_WARNING == $error->level 'WARNING' 'ERROR',
  226.                 $error->code,
  227.                 trim($error->message),
  228.                 $error->file ?: 'n/a',
  229.                 $error->line,
  230.                 $error->column
  231.             );
  232.         }
  233.         libxml_clear_errors();
  234.         libxml_use_internal_errors($internalErrors);
  235.         return $errors;
  236.     }
  237. }