Deprecated: Constant E_STRICT is deprecated in /home/normanv/www/annuairepro/vendor/symfony/error-handler/ErrorHandler.php on line 58

Deprecated: Constant E_STRICT is deprecated in /home/normanv/www/annuairepro/vendor/symfony/error-handler/ErrorHandler.php on line 76
Symfony Profiler

vendor/symfony/serializer/Encoder/XmlEncoder.php line 354

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\Serializer\Encoder;
  11. use Symfony\Component\Serializer\Exception\BadMethodCallException;
  12. use Symfony\Component\Serializer\Exception\NotEncodableValueException;
  13. use Symfony\Component\Serializer\SerializerAwareInterface;
  14. use Symfony\Component\Serializer\SerializerAwareTrait;
  15. /**
  16.  * @author Jordi Boggiano <j.boggiano@seld.be>
  17.  * @author John Wards <jwards@whiteoctober.co.uk>
  18.  * @author Fabian Vogler <fabian@equivalence.ch>
  19.  * @author Kévin Dunglas <dunglas@gmail.com>
  20.  * @author Dany Maillard <danymaillard93b@gmail.com>
  21.  */
  22. class XmlEncoder implements EncoderInterfaceDecoderInterfaceNormalizationAwareInterfaceSerializerAwareInterface
  23. {
  24.     use SerializerAwareTrait;
  25.     public const FORMAT 'xml';
  26.     public const AS_COLLECTION 'as_collection';
  27.     /**
  28.      * An array of ignored XML node types while decoding, each one of the DOM Predefined XML_* constants.
  29.      */
  30.     public const DECODER_IGNORED_NODE_TYPES 'decoder_ignored_node_types';
  31.     /**
  32.      * An array of ignored XML node types while encoding, each one of the DOM Predefined XML_* constants.
  33.      */
  34.     public const ENCODER_IGNORED_NODE_TYPES 'encoder_ignored_node_types';
  35.     public const ENCODING 'xml_encoding';
  36.     public const FORMAT_OUTPUT 'xml_format_output';
  37.     /**
  38.      * A bit field of LIBXML_* constants.
  39.      */
  40.     public const LOAD_OPTIONS 'load_options';
  41.     public const REMOVE_EMPTY_TAGS 'remove_empty_tags';
  42.     public const ROOT_NODE_NAME 'xml_root_node_name';
  43.     public const STANDALONE 'xml_standalone';
  44.     public const TYPE_CAST_ATTRIBUTES 'xml_type_cast_attributes';
  45.     public const VERSION 'xml_version';
  46.     private $defaultContext = [
  47.         self::AS_COLLECTION => false,
  48.         self::DECODER_IGNORED_NODE_TYPES => [\XML_PI_NODE\XML_COMMENT_NODE],
  49.         self::ENCODER_IGNORED_NODE_TYPES => [],
  50.         self::LOAD_OPTIONS => \LIBXML_NONET \LIBXML_NOBLANKS,
  51.         self::REMOVE_EMPTY_TAGS => false,
  52.         self::ROOT_NODE_NAME => 'response',
  53.         self::TYPE_CAST_ATTRIBUTES => true,
  54.     ];
  55.     public function __construct(array $defaultContext = [])
  56.     {
  57.         $this->defaultContext array_merge($this->defaultContext$defaultContext);
  58.     }
  59.     /**
  60.      * {@inheritdoc}
  61.      */
  62.     public function encode($datastring $format, array $context = [])
  63.     {
  64.         $encoderIgnoredNodeTypes $context[self::ENCODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::ENCODER_IGNORED_NODE_TYPES];
  65.         $ignorePiNode \in_array(\XML_PI_NODE$encoderIgnoredNodeTypestrue);
  66.         if ($data instanceof \DOMDocument) {
  67.             return $data->saveXML($ignorePiNode $data->documentElement null);
  68.         }
  69.         $xmlRootNodeName $context[self::ROOT_NODE_NAME] ?? $this->defaultContext[self::ROOT_NODE_NAME];
  70.         $dom $this->createDomDocument($context);
  71.         if (null !== $data && !\is_scalar($data)) {
  72.             $root $dom->createElement($xmlRootNodeName);
  73.             $dom->appendChild($root);
  74.             $this->buildXml($root$data$format$context$xmlRootNodeName);
  75.         } else {
  76.             $this->appendNode($dom$data$format$context$xmlRootNodeName);
  77.         }
  78.         return $dom->saveXML($ignorePiNode $dom->documentElement null);
  79.     }
  80.     /**
  81.      * {@inheritdoc}
  82.      */
  83.     public function decode(string $datastring $format, array $context = [])
  84.     {
  85.         if ('' === trim($data)) {
  86.             throw new NotEncodableValueException('Invalid XML data, it cannot be empty.');
  87.         }
  88.         $internalErrors libxml_use_internal_errors(true);
  89.         if (\LIBXML_VERSION 20900) {
  90.             $disableEntities libxml_disable_entity_loader(true);
  91.         }
  92.         libxml_clear_errors();
  93.         $dom = new \DOMDocument();
  94.         $dom->loadXML($data$context[self::LOAD_OPTIONS] ?? $this->defaultContext[self::LOAD_OPTIONS]);
  95.         libxml_use_internal_errors($internalErrors);
  96.         if (\LIBXML_VERSION 20900) {
  97.             libxml_disable_entity_loader($disableEntities);
  98.         }
  99.         if ($error libxml_get_last_error()) {
  100.             libxml_clear_errors();
  101.             throw new NotEncodableValueException($error->message);
  102.         }
  103.         $rootNode null;
  104.         $decoderIgnoredNodeTypes $context[self::DECODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::DECODER_IGNORED_NODE_TYPES];
  105.         foreach ($dom->childNodes as $child) {
  106.             if (\in_array($child->nodeType$decoderIgnoredNodeTypestrue)) {
  107.                 continue;
  108.             }
  109.             if (\XML_DOCUMENT_TYPE_NODE === $child->nodeType) {
  110.                 throw new NotEncodableValueException('Document types are not allowed.');
  111.             }
  112.             if (!$rootNode) {
  113.                 $rootNode $child;
  114.             }
  115.         }
  116.         // todo: throw an exception if the root node name is not correctly configured (bc)
  117.         if ($rootNode->hasChildNodes()) {
  118.             $xpath = new \DOMXPath($dom);
  119.             $data = [];
  120.             foreach ($xpath->query('namespace::*'$dom->documentElement) as $nsNode) {
  121.                 $data['@'.$nsNode->nodeName] = $nsNode->nodeValue;
  122.             }
  123.             unset($data['@xmlns:xml']);
  124.             if (empty($data)) {
  125.                 return $this->parseXml($rootNode$context);
  126.             }
  127.             return array_merge($data, (array) $this->parseXml($rootNode$context));
  128.         }
  129.         if (!$rootNode->hasAttributes()) {
  130.             return $rootNode->nodeValue;
  131.         }
  132.         return array_merge($this->parseXmlAttributes($rootNode$context), ['#' => $rootNode->nodeValue]);
  133.     }
  134.     /**
  135.      * {@inheritdoc}
  136.      */
  137.     public function supportsEncoding(string $format)
  138.     {
  139.         return self::FORMAT === $format;
  140.     }
  141.     /**
  142.      * {@inheritdoc}
  143.      */
  144.     public function supportsDecoding(string $format)
  145.     {
  146.         return self::FORMAT === $format;
  147.     }
  148.     final protected function appendXMLString(\DOMNode $nodestring $val): bool
  149.     {
  150.         if ('' !== $val) {
  151.             $frag $node->ownerDocument->createDocumentFragment();
  152.             $frag->appendXML($val);
  153.             $node->appendChild($frag);
  154.             return true;
  155.         }
  156.         return false;
  157.     }
  158.     final protected function appendText(\DOMNode $nodestring $val): bool
  159.     {
  160.         $nodeText $node->ownerDocument->createTextNode($val);
  161.         $node->appendChild($nodeText);
  162.         return true;
  163.     }
  164.     final protected function appendCData(\DOMNode $nodestring $val): bool
  165.     {
  166.         $nodeText $node->ownerDocument->createCDATASection($val);
  167.         $node->appendChild($nodeText);
  168.         return true;
  169.     }
  170.     /**
  171.      * @param \DOMDocumentFragment $fragment
  172.      */
  173.     final protected function appendDocumentFragment(\DOMNode $node$fragment): bool
  174.     {
  175.         if ($fragment instanceof \DOMDocumentFragment) {
  176.             $node->appendChild($fragment);
  177.             return true;
  178.         }
  179.         return false;
  180.     }
  181.     final protected function appendComment(\DOMNode $nodestring $data): bool
  182.     {
  183.         $node->appendChild($node->ownerDocument->createComment($data));
  184.         return true;
  185.     }
  186.     /**
  187.      * Checks the name is a valid xml element name.
  188.      */
  189.     final protected function isElementNameValid(string $name): bool
  190.     {
  191.         return $name &&
  192.             !str_contains($name' ') &&
  193.             preg_match('#^[\pL_][\pL0-9._:-]*$#ui'$name);
  194.     }
  195.     /**
  196.      * Parse the input DOMNode into an array or a string.
  197.      *
  198.      * @return array|string
  199.      */
  200.     private function parseXml(\DOMNode $node, array $context = [])
  201.     {
  202.         $data $this->parseXmlAttributes($node$context);
  203.         $value $this->parseXmlValue($node$context);
  204.         if (!\count($data)) {
  205.             return $value;
  206.         }
  207.         if (!\is_array($value)) {
  208.             $data['#'] = $value;
  209.             return $data;
  210.         }
  211.         if (=== \count($value) && key($value)) {
  212.             $data[key($value)] = current($value);
  213.             return $data;
  214.         }
  215.         foreach ($value as $key => $val) {
  216.             $data[$key] = $val;
  217.         }
  218.         return $data;
  219.     }
  220.     /**
  221.      * Parse the input DOMNode attributes into an array.
  222.      */
  223.     private function parseXmlAttributes(\DOMNode $node, array $context = []): array
  224.     {
  225.         if (!$node->hasAttributes()) {
  226.             return [];
  227.         }
  228.         $data = [];
  229.         $typeCastAttributes = (bool) ($context[self::TYPE_CAST_ATTRIBUTES] ?? $this->defaultContext[self::TYPE_CAST_ATTRIBUTES]);
  230.         foreach ($node->attributes as $attr) {
  231.             if (!is_numeric($attr->nodeValue) || !$typeCastAttributes || (isset($attr->nodeValue[1]) && '0' === $attr->nodeValue[0] && '.' !== $attr->nodeValue[1])) {
  232.                 $data['@'.$attr->nodeName] = $attr->nodeValue;
  233.                 continue;
  234.             }
  235.             if (false !== $val filter_var($attr->nodeValue\FILTER_VALIDATE_INT)) {
  236.                 $data['@'.$attr->nodeName] = $val;
  237.                 continue;
  238.             }
  239.             $data['@'.$attr->nodeName] = (float) $attr->nodeValue;
  240.         }
  241.         return $data;
  242.     }
  243.     /**
  244.      * Parse the input DOMNode value (content and children) into an array or a string.
  245.      *
  246.      * @return array|string
  247.      */
  248.     private function parseXmlValue(\DOMNode $node, array $context = [])
  249.     {
  250.         if (!$node->hasChildNodes()) {
  251.             return $node->nodeValue;
  252.         }
  253.         if (=== $node->childNodes->length && \in_array($node->firstChild->nodeType, [\XML_TEXT_NODE\XML_CDATA_SECTION_NODE])) {
  254.             return $node->firstChild->nodeValue;
  255.         }
  256.         $value = [];
  257.         $decoderIgnoredNodeTypes $context[self::DECODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::DECODER_IGNORED_NODE_TYPES];
  258.         foreach ($node->childNodes as $subnode) {
  259.             if (\in_array($subnode->nodeType$decoderIgnoredNodeTypestrue)) {
  260.                 continue;
  261.             }
  262.             $val $this->parseXml($subnode$context);
  263.             if ('item' === $subnode->nodeName && isset($val['@key'])) {
  264.                 $value[$val['@key']] = $val['#'] ?? $val;
  265.             } else {
  266.                 $value[$subnode->nodeName][] = $val;
  267.             }
  268.         }
  269.         $asCollection $context[self::AS_COLLECTION] ?? $this->defaultContext[self::AS_COLLECTION];
  270.         foreach ($value as $key => $val) {
  271.             if (!$asCollection && \is_array($val) && === \count($val)) {
  272.                 $value[$key] = current($val);
  273.             }
  274.         }
  275.         return $value;
  276.     }
  277.     /**
  278.      * Parse the data and convert it to DOMElements.
  279.      *
  280.      * @param array|object $data
  281.      *
  282.      * @throws NotEncodableValueException
  283.      */
  284.     private function buildXml(\DOMNode $parentNode$datastring $format, array $contextstring $xmlRootNodeName null): bool
  285.     {
  286.         $append true;
  287.         $removeEmptyTags $context[self::REMOVE_EMPTY_TAGS] ?? $this->defaultContext[self::REMOVE_EMPTY_TAGS] ?? false;
  288.         $encoderIgnoredNodeTypes $context[self::ENCODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::ENCODER_IGNORED_NODE_TYPES];
  289.         if (\is_array($data) || ($data instanceof \Traversable && (null === $this->serializer || !$this->serializer->supportsNormalization($data$format)))) {
  290.             foreach ($data as $key => $data) {
  291.                 // Ah this is the magic @ attribute types.
  292.                 if (str_starts_with($key'@') && $this->isElementNameValid($attributeName substr($key1))) {
  293.                     if (!\is_scalar($data)) {
  294.                         $data $this->serializer->normalize($data$format$context);
  295.                     }
  296.                     if (\is_bool($data)) {
  297.                         $data = (int) $data;
  298.                     }
  299.                     $parentNode->setAttribute($attributeName$data);
  300.                 } elseif ('#' === $key) {
  301.                     $append $this->selectNodeType($parentNode$data$format$context);
  302.                 } elseif ('#comment' === $key) {
  303.                     if (!\in_array(\XML_COMMENT_NODE$encoderIgnoredNodeTypestrue)) {
  304.                         $append $this->appendComment($parentNode$data);
  305.                     }
  306.                 } elseif (\is_array($data) && false === is_numeric($key)) {
  307.                     // Is this array fully numeric keys?
  308.                     if (ctype_digit(implode(''array_keys($data)))) {
  309.                         /*
  310.                          * Create nodes to append to $parentNode based on the $key of this array
  311.                          * Produces <xml><item>0</item><item>1</item></xml>
  312.                          * From ["item" => [0,1]];.
  313.                          */
  314.                         foreach ($data as $subData) {
  315.                             $append $this->appendNode($parentNode$subData$format$context$key);
  316.                         }
  317.                     } else {
  318.                         $append $this->appendNode($parentNode$data$format$context$key);
  319.                     }
  320.                 } elseif (is_numeric($key) || !$this->isElementNameValid($key)) {
  321.                     $append $this->appendNode($parentNode$data$format$context'item'$key);
  322.                 } elseif (null !== $data || !$removeEmptyTags) {
  323.                     $append $this->appendNode($parentNode$data$format$context$key);
  324.                 }
  325.             }
  326.             return $append;
  327.         }
  328.         if (\is_object($data)) {
  329.             if (null === $this->serializer) {
  330.                 throw new BadMethodCallException(sprintf('The serializer needs to be set to allow "%s()" to be used with object data.'__METHOD__));
  331.             }
  332.             $data $this->serializer->normalize($data$format$context);
  333.             if (null !== $data && !\is_scalar($data)) {
  334.                 return $this->buildXml($parentNode$data$format$context$xmlRootNodeName);
  335.             }
  336.             // top level data object was normalized into a scalar
  337.             if (!$parentNode->parentNode->parentNode) {
  338.                 $root $parentNode->parentNode;
  339.                 $root->removeChild($parentNode);
  340.                 return $this->appendNode($root$data$format$context$xmlRootNodeName);
  341.             }
  342.             return $this->appendNode($parentNode$data$format$context'data');
  343.         }
  344.         throw new NotEncodableValueException('An unexpected value could not be serialized: '.(!\is_resource($data) ? var_export($datatrue) : sprintf('%s resource'get_resource_type($data))));
  345.     }
  346.     /**
  347.      * Selects the type of node to create and appends it to the parent.
  348.      *
  349.      * @param array|object $data
  350.      */
  351.     private function appendNode(\DOMNode $parentNode$datastring $format, array $contextstring $nodeNamestring $key null): bool
  352.     {
  353.         $dom $parentNode instanceof \DOMDocument $parentNode $parentNode->ownerDocument;
  354.         $node $dom->createElement($nodeName);
  355.         if (null !== $key) {
  356.             $node->setAttribute('key'$key);
  357.         }
  358.         $appendNode $this->selectNodeType($node$data$format$context);
  359.         // we may have decided not to append this node, either in error or if its $nodeName is not valid
  360.         if ($appendNode) {
  361.             $parentNode->appendChild($node);
  362.         }
  363.         return $appendNode;
  364.     }
  365.     /**
  366.      * Checks if a value contains any characters which would require CDATA wrapping.
  367.      */
  368.     private function needsCdataWrapping(string $val): bool
  369.     {
  370.         return preg_match('/[<>&]/'$val);
  371.     }
  372.     /**
  373.      * Tests the value being passed and decide what sort of element to create.
  374.      *
  375.      * @throws NotEncodableValueException
  376.      */
  377.     private function selectNodeType(\DOMNode $node$valstring $format, array $context): bool
  378.     {
  379.         if (\is_array($val)) {
  380.             return $this->buildXml($node$val$format$context);
  381.         } elseif ($val instanceof \SimpleXMLElement) {
  382.             $child $node->ownerDocument->importNode(dom_import_simplexml($val), true);
  383.             $node->appendChild($child);
  384.         } elseif ($val instanceof \Traversable) {
  385.             $this->buildXml($node$val$format$context);
  386.         } elseif ($val instanceof \DOMNode) {
  387.             $child $node->ownerDocument->importNode($valtrue);
  388.             $node->appendChild($child);
  389.         } elseif (\is_object($val)) {
  390.             if (null === $this->serializer) {
  391.                 throw new BadMethodCallException(sprintf('The serializer needs to be set to allow "%s()" to be used with object data.'__METHOD__));
  392.             }
  393.             return $this->selectNodeType($node$this->serializer->normalize($val$format$context), $format$context);
  394.         } elseif (is_numeric($val)) {
  395.             return $this->appendText($node, (string) $val);
  396.         } elseif (\is_string($val) && $this->needsCdataWrapping($val)) {
  397.             return $this->appendCData($node$val);
  398.         } elseif (\is_string($val)) {
  399.             return $this->appendText($node$val);
  400.         } elseif (\is_bool($val)) {
  401.             return $this->appendText($node, (int) $val);
  402.         }
  403.         return true;
  404.     }
  405.     /**
  406.      * Create a DOM document, taking serializer options into account.
  407.      */
  408.     private function createDomDocument(array $context): \DOMDocument
  409.     {
  410.         $document = new \DOMDocument();
  411.         // Set an attribute on the DOM document specifying, as part of the XML declaration,
  412.         $xmlOptions = [
  413.             // nicely formats output with indentation and extra space
  414.             self::FORMAT_OUTPUT => 'formatOutput',
  415.             // the version number of the document
  416.             self::VERSION => 'xmlVersion',
  417.             // the encoding of the document
  418.             self::ENCODING => 'encoding',
  419.             // whether the document is standalone
  420.             self::STANDALONE => 'xmlStandalone',
  421.         ];
  422.         foreach ($xmlOptions as $xmlOption => $documentProperty) {
  423.             if ($contextOption $context[$xmlOption] ?? $this->defaultContext[$xmlOption] ?? false) {
  424.                 $document->$documentProperty $contextOption;
  425.             }
  426.         }
  427.         return $document;
  428.     }
  429. }