4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\Config\Util;
14 use Symfony\Component\Config\Util\Exception\InvalidXmlException;
15 use Symfony\Component\Config\Util\Exception\XmlParsingException;
18 * XMLUtils is a bunch of utility methods to XML operations.
20 * This class contains static methods only and is not meant to be instantiated.
22 * @author Fabien Potencier <fabien@symfony.com>
23 * @author Martin Hasoň <martin.hason@gmail.com>
24 * @author Ole Rößner <ole@roessner.it>
29 * This class should not be instantiated.
31 private function __construct()
36 * Parses an XML string.
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
41 * @return \DOMDocument
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
47 public static function parse($content, $schemaOrCallable = null)
49 if (!\extension_loaded('dom')) {
50 throw new \RuntimeException('Extension DOM is required.');
53 $internalErrors = libxml_use_internal_errors(true);
54 $disableEntities = libxml_disable_entity_loader(true);
55 libxml_clear_errors();
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);
62 throw new XmlParsingException(implode("\n", static::getXmlErrors($internalErrors)));
65 $dom->normalizeDocument();
67 libxml_use_internal_errors($internalErrors);
68 libxml_disable_entity_loader($disableEntities);
70 foreach ($dom->childNodes as $child) {
71 if (XML_DOCUMENT_TYPE_NODE === $child->nodeType) {
72 throw new XmlParsingException('Document types are not allowed.');
76 if (null !== $schemaOrCallable) {
77 $internalErrors = libxml_use_internal_errors(true);
78 libxml_clear_errors();
81 if (\is_callable($schemaOrCallable)) {
83 $valid = \call_user_func($schemaOrCallable, $dom, $internalErrors);
84 } catch (\Exception $e) {
87 } elseif (!\is_array($schemaOrCallable) && is_file((string) $schemaOrCallable)) {
88 $schemaSource = file_get_contents((string) $schemaOrCallable);
89 $valid = @$dom->schemaValidateSource($schemaSource);
91 libxml_use_internal_errors($internalErrors);
93 throw new XmlParsingException('The schemaOrCallable argument has to be a valid path to XSD file or callable.');
97 $messages = static::getXmlErrors($internalErrors);
98 if (empty($messages)) {
99 throw new InvalidXmlException('The XML is not valid.', 0, $e);
101 throw new XmlParsingException(implode("\n", $messages), 0, $e);
105 libxml_clear_errors();
106 libxml_use_internal_errors($internalErrors);
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
117 * @return \DOMDocument
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
123 public static function loadFile($file, $schemaOrCallable = null)
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));
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());
138 * Converts a \DOMElement object to a PHP array.
140 * The following rules applies during the conversion:
142 * * Each tag is converted to a key value or an array
143 * if there is more than one "value"
145 * * The content of a tag is set under a "value" key (<foo>bar</foo>)
146 * if the tag also has some nested tags
148 * * The attributes are converted to keys (<foo foo="bar"/>)
150 * * The nested-tags are converted to keys (<foo><foo>bar</foo></foo>)
152 * @param \DOMElement $element A \DOMElement instance
153 * @param bool $checkPrefix Check prefix in an element or an attribute name
155 * @return array A PHP array
157 public static function convertDomElementToArray(\DOMElement $element, $checkPrefix = true)
159 $prefix = (string) $element->prefix;
162 foreach ($element->attributes as $name => $node) {
163 if ($checkPrefix && !\in_array((string) $node->prefix, array('', $prefix), true)) {
166 $config[$name] = static::phpize($node->value);
171 foreach ($element->childNodes as $node) {
172 if ($node instanceof \DOMText) {
173 if ('' !== trim($node->nodeValue)) {
174 $nodeValue = trim($node->nodeValue);
177 } elseif ($checkPrefix && $prefix != (string) $node->prefix) {
179 } elseif (!$node instanceof \DOMComment) {
180 $value = static::convertDomElementToArray($node, $checkPrefix);
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]);
187 $config[$key][] = $value;
189 $config[$key] = $value;
196 if (false !== $nodeValue) {
197 $value = static::phpize($nodeValue);
198 if (\count($config)) {
199 $config['value'] = $value;
205 return !$empty ? $config : null;
209 * Converts an xml value to a PHP type.
211 * @param mixed $value
215 public static function phpize($value)
217 $value = (string) $value;
218 $lowercaseValue = strtolower($value);
221 case 'null' === $lowercaseValue:
223 case ctype_digit($value):
225 $cast = (int) $value;
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)):
230 $cast = (int) $value;
232 return '0' == $value[1] ? octdec($value) : (((string) $raw === (string) $cast) ? $cast : $raw);
233 case 'true' === $lowercaseValue:
235 case 'false' === $lowercaseValue:
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;
250 protected static function getXmlErrors($internalErrors)
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',
257 trim($error->message),
258 $error->file ?: 'n/a',
264 libxml_clear_errors();
265 libxml_use_internal_errors($internalErrors);