vendor/symfony/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php
changeset 0 7f95f8617b0b
equal deleted inserted replaced
-1:000000000000 0:7f95f8617b0b
       
     1 <?php
       
     2 
       
     3 namespace Symfony\Component\Serializer\Encoder;
       
     4 
       
     5 use Symfony\Component\Serializer\SerializerInterface;
       
     6 use Symfony\Component\Serializer\Exception\UnexpectedValueException;
       
     7 
       
     8 /*
       
     9  * This file is part of the Symfony framework.
       
    10  *
       
    11  * (c) Fabien Potencier <fabien@symfony.com>
       
    12  *
       
    13  * This source file is subject to the MIT license that is bundled
       
    14  * with this source code in the file LICENSE.
       
    15  */
       
    16 
       
    17 /**
       
    18  * Encodes XML data
       
    19  *
       
    20  * @author Jordi Boggiano <j.boggiano@seld.be>
       
    21  * @author John Wards <jwards@whiteoctober.co.uk>
       
    22  * @author Fabian Vogler <fabian@equivalence.ch>
       
    23  */
       
    24 class XmlEncoder extends SerializerAwareEncoder implements EncoderInterface, DecoderInterface, NormalizationAwareInterface
       
    25 {
       
    26     private $dom;
       
    27     private $format;
       
    28     private $rootNodeName = 'response';
       
    29 
       
    30     /**
       
    31      * {@inheritdoc}
       
    32      */
       
    33     public function encode($data, $format)
       
    34     {
       
    35         if ($data instanceof \DOMDocument) {
       
    36             return $data->saveXML();
       
    37         }
       
    38 
       
    39         $this->dom = new \DOMDocument();
       
    40         $this->format = $format;
       
    41 
       
    42         if (null !== $data && !is_scalar($data)) {
       
    43             $root = $this->dom->createElement($this->rootNodeName);
       
    44             $this->dom->appendChild($root);
       
    45             $this->buildXml($root, $data);
       
    46         } else {
       
    47             $this->appendNode($this->dom, $data, $this->rootNodeName);
       
    48         }
       
    49 
       
    50         return $this->dom->saveXML();
       
    51     }
       
    52 
       
    53     /**
       
    54      * {@inheritdoc}
       
    55      */
       
    56     public function decode($data, $format)
       
    57     {
       
    58         $xml = simplexml_load_string($data);
       
    59         if (!$xml->count()) {
       
    60             if (!$xml->attributes()) {
       
    61                 return (string) $xml;
       
    62             }
       
    63             $data = array();
       
    64             foreach ($xml->attributes() as $attrkey => $attr) {
       
    65                 $data['@'.$attrkey] = (string) $attr;
       
    66             }
       
    67             $data['#'] = (string) $xml;
       
    68 
       
    69             return $data;
       
    70         }
       
    71 
       
    72         return $this->parseXml($xml);
       
    73     }
       
    74 
       
    75     /**
       
    76      * Sets the root node name
       
    77      * @param string $name root node name
       
    78      */
       
    79     public function setRootNodeName($name)
       
    80     {
       
    81         $this->rootNodeName = $name;
       
    82     }
       
    83 
       
    84     /**
       
    85      * Returns the root node name
       
    86      * @return string
       
    87      */
       
    88     public function getRootNodeName()
       
    89     {
       
    90         return $this->rootNodeName;
       
    91     }
       
    92 
       
    93     /**
       
    94      * @param DOMNode $node
       
    95      * @param string $val
       
    96      * @return Boolean
       
    97      */
       
    98     final protected function appendXMLString($node, $val)
       
    99     {
       
   100         if (strlen($val) > 0) {
       
   101             $frag = $this->dom->createDocumentFragment();
       
   102             $frag->appendXML($val);
       
   103             $node->appendChild($frag);
       
   104 
       
   105             return true;
       
   106         }
       
   107 
       
   108         return false;
       
   109     }
       
   110 
       
   111     /**
       
   112      * @param DOMNode $node
       
   113      * @param string $val
       
   114      * @return Boolean
       
   115      */
       
   116     final protected function appendText($node, $val)
       
   117     {
       
   118         $nodeText = $this->dom->createTextNode($val);
       
   119         $node->appendChild($nodeText);
       
   120 
       
   121         return true;
       
   122     }
       
   123 
       
   124     /**
       
   125      * @param DOMNode $node
       
   126      * @param string $val
       
   127      * @return Boolean
       
   128      */
       
   129     final protected function appendCData($node, $val)
       
   130     {
       
   131         $nodeText = $this->dom->createCDATASection($val);
       
   132         $node->appendChild($nodeText);
       
   133 
       
   134         return true;
       
   135     }
       
   136 
       
   137     /**
       
   138      * @param DOMNode $node
       
   139      * @param DOMDocumentFragment $fragment
       
   140      * @return Boolean
       
   141      */
       
   142     final protected function appendDocumentFragment($node, $fragment)
       
   143     {
       
   144         if ($fragment instanceof \DOMDocumentFragment) {
       
   145             $node->appendChild($fragment);
       
   146 
       
   147             return true;
       
   148         }
       
   149 
       
   150         return false;
       
   151     }
       
   152 
       
   153     /**
       
   154      * Checks the name is a valid xml element name
       
   155      * @param string $name
       
   156      * @return Boolean
       
   157      */
       
   158     final protected function isElementNameValid($name)
       
   159     {
       
   160         return $name &&
       
   161             false === strpos($name, ' ') &&
       
   162             preg_match('#^[\pL_][\pL0-9._-]*$#ui', $name);
       
   163     }
       
   164 
       
   165     /**
       
   166      * Parse the input SimpleXmlElement into an array
       
   167      *
       
   168      * @param SimpleXmlElement $node xml to parse
       
   169      * @return array
       
   170      */
       
   171     private function parseXml($node)
       
   172     {
       
   173         $data = array();
       
   174         if ($node->attributes()) {
       
   175             foreach ($node->attributes() as $attrkey => $attr) {
       
   176                 $data['@'.$attrkey] = (string) $attr;
       
   177             }
       
   178         }
       
   179         foreach ($node->children() as $key => $subnode) {
       
   180             if ($subnode->count()) {
       
   181                 $value = $this->parseXml($subnode);
       
   182             } elseif ($subnode->attributes()) {
       
   183                 $value = array();
       
   184                 foreach ($subnode->attributes() as $attrkey => $attr) {
       
   185                     $value['@'.$attrkey] = (string) $attr;
       
   186                 }
       
   187                 $value['#'] = (string) $subnode;
       
   188             } else {
       
   189                 $value = (string) $subnode;
       
   190             }
       
   191 
       
   192             if ($key === 'item') {
       
   193                 if (isset($value['@key'])) {
       
   194                     $data[(string)$value['@key']] = $value['#'];
       
   195                 } elseif (isset($data['item'])) {
       
   196                     $tmp = $data['item'];
       
   197                     unset($data['item']);
       
   198                     $data[] = $tmp;
       
   199                     $data[] = $value;
       
   200                 }
       
   201             } elseif (key_exists($key, $data)) {
       
   202                 if ((false === is_array($data[$key]))  || (false === isset($data[$key][0]))) {
       
   203                     $data[$key] = array($data[$key]);
       
   204                 }
       
   205                 $data[$key][] = $value;
       
   206             } else {
       
   207                 $data[$key] = $value;
       
   208             }
       
   209         }
       
   210 
       
   211         return $data;
       
   212     }
       
   213 
       
   214     /**
       
   215      * Parse the data and convert it to DOMElements
       
   216      *
       
   217      * @param DOMNode $parentNode
       
   218      * @param array|object $data data
       
   219      * @return Boolean
       
   220      */
       
   221     private function buildXml($parentNode, $data)
       
   222     {
       
   223         $append = true;
       
   224 
       
   225         if (is_array($data) || $data instanceof \Traversable) {
       
   226             foreach ($data as $key => $data) {
       
   227                 //Ah this is the magic @ attribute types.
       
   228                 if (0 === strpos($key, "@") && is_scalar($data) && $this->isElementNameValid($attributeName = substr($key,1))) {
       
   229                     $parentNode->setAttribute($attributeName, $data);
       
   230                 } elseif ($key === '#') {
       
   231                     $append = $this->selectNodeType($parentNode, $data);
       
   232                 } elseif (is_array($data) && false === is_numeric($key)) {
       
   233                     /**
       
   234                      * Is this array fully numeric keys?
       
   235                      */
       
   236                     if (ctype_digit(implode('', array_keys($data)))) {
       
   237                         /**
       
   238                          * Create nodes to append to $parentNode based on the $key of this array
       
   239                          * Produces <xml><item>0</item><item>1</item></xml>
       
   240                          * From array("item" => array(0,1));
       
   241                          */
       
   242                         foreach ($data as $subData) {
       
   243                             $append = $this->appendNode($parentNode, $subData, $key);
       
   244                         }
       
   245                     } else {
       
   246                         $append = $this->appendNode($parentNode, $data, $key);
       
   247                     }
       
   248                 } elseif (is_numeric($key) || !$this->isElementNameValid($key)) {
       
   249                     $append = $this->appendNode($parentNode, $data, "item", $key);
       
   250                 } else {
       
   251                     $append = $this->appendNode($parentNode, $data, $key);
       
   252                 }
       
   253             }
       
   254 
       
   255             return $append;
       
   256         }
       
   257         if (is_object($data)) {
       
   258             $data = $this->serializer->normalize($data, $this->format);
       
   259             if (null !== $data && !is_scalar($data)) {
       
   260                 return $this->buildXml($parentNode, $data);
       
   261             }
       
   262             // top level data object was normalized into a scalar
       
   263             if (!$parentNode->parentNode->parentNode) {
       
   264                 $root = $parentNode->parentNode;
       
   265                 $root->removeChild($parentNode);
       
   266 
       
   267                 return $this->appendNode($root, $data, $this->rootNodeName);
       
   268             }
       
   269 
       
   270             return $this->appendNode($parentNode, $data, 'data');
       
   271         }
       
   272         throw new UnexpectedValueException('An unexpected value could not be serialized: '.var_export($data, true));
       
   273     }
       
   274 
       
   275     /**
       
   276      * Selects the type of node to create and appends it to the parent.
       
   277      *
       
   278      * @param DOMNode      $parentNode
       
   279      * @param array|object $data
       
   280      * @param string       $nodename
       
   281      * @param string       $key
       
   282      * @return Boolean
       
   283      */
       
   284     private function appendNode($parentNode, $data, $nodeName, $key = null)
       
   285     {
       
   286         $node = $this->dom->createElement($nodeName);
       
   287         if (null !== $key) {
       
   288             $node->setAttribute('key', $key);
       
   289         }
       
   290         $appendNode = $this->selectNodeType($node, $data);
       
   291         // we may have decided not to append this node, either in error or if its $nodeName is not valid
       
   292         if ($appendNode) {
       
   293             $parentNode->appendChild($node);
       
   294         }
       
   295 
       
   296         return $appendNode;
       
   297     }
       
   298 
       
   299     /**
       
   300      * Tests the value being passed and decide what sort of element to create
       
   301      *
       
   302      * @param DOMNode $node
       
   303      * @param mixed $val
       
   304      * @return Boolean
       
   305      */
       
   306     private function selectNodeType($node, $val)
       
   307     {
       
   308         if (is_array($val)) {
       
   309             return $this->buildXml($node, $val);
       
   310         } elseif ($val instanceof \SimpleXMLElement) {
       
   311             $child = $this->dom->importNode(dom_import_simplexml($val), true);
       
   312             $node->appendChild($child);
       
   313         } elseif ($val instanceof \Traversable) {
       
   314             $this->buildXml($node, $val);
       
   315         } elseif (is_object($val)) {
       
   316             return $this->buildXml($node, $this->serializer->normalize($val, $this->format));
       
   317         } elseif (is_numeric($val)) {
       
   318             return $this->appendText($node, (string) $val);
       
   319         } elseif (is_string($val)) {
       
   320             return $this->appendCData($node, $val);
       
   321         } elseif (is_bool($val)) {
       
   322             return $this->appendText($node, (int) $val);
       
   323         } elseif ($val instanceof \DOMNode) {
       
   324             $child = $this->dom->importNode($val, true);
       
   325             $node->appendChild($child);
       
   326         }
       
   327 
       
   328         return true;
       
   329     }
       
   330 }