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