diff --git a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php index a88fc05c8cb65..de685edabbf41 100644 --- a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php @@ -216,7 +216,7 @@ final protected function isElementNameValid($name) { return $name && false === strpos($name, ' ') && - preg_match('#^[\pL_][\pL0-9._-]*$#ui', $name); + preg_match('#^[\pL_][\pL0-9._:-]*$#ui', $name); } /** @@ -234,42 +234,81 @@ private function parseXml(\SimpleXmlElement $node) $data['@'.$attrkey] = (string) $attr; } } - foreach ($node->children() as $key => $subnode) { - if ($subnode->count()) { - $value = $this->parseXml($subnode); - } elseif ($subnode->attributes()) { - $value = array(); - foreach ($subnode->attributes() as $attrkey => $attr) { - $value['@'.$attrkey] = (string) $attr; - } - $value['#'] = (string) $subnode; - } else { - $value = (string) $subnode; - } - if ($key === 'item') { - if (isset($value['@key'])) { - if (isset($value['#'])) { - $data[$value['@key']] = $value['#']; - } else { - $data[$value['@key']] = $value; + // Parse documents with namespaces + if ($namespaces = $node->getDocNamespaces()) { + foreach ($namespaces as $prefix => $namespace) { + // Build strings for node namespace prefixes and root node xmlns attribute names + $xmlns = 'xmlns'; + if (!empty($prefix)) { + $xmlns = $xmlns.':'.$prefix; + $prefix = $prefix.':'; + } + // Add the namespaces as attributes to the root node + if (!count($node->xpath("parent::*"))) { + $data['@'.$xmlns] = (string) $namespace; + } + // Parse attributes in namespace + if ($node->attributes($namespace)) { + foreach ($node->attributes($namespace) as $attrkey => $attr) { + $data['@'.$prefix.$attrkey] = (string) $attr; } - } else { - $data['item'][] = $value; } - } elseif (array_key_exists($key, $data) || $key == "entry") { - if ((false === is_array($data[$key])) || (false === isset($data[$key][0]))) { - $data[$key] = array($data[$key]); + // Parse children in namespace + foreach ($node->children($namespace) as $key => $subnode) { + $this->parseXmlSubnode($data, $subnode, $prefix.$key); } - $data[$key][] = $value; - } else { - $data[$key] = $value; + } + } else { // XML doc has no (root) namespaces + // Parse children + foreach ($node->children() as $key => $subnode) { + $this->parseXmlSubnode($data, $subnode, $key); } } return $data; } + /** + * Parse the input SimpleXmlElement into an array. + * + * @param array $data array to fill + * @param \SimpleXMLElement $subnode xml to parse + * @param string $key name of the subnode including any namespace prefix + */ + private function parseXmlSubnode(&$data, \SimpleXMLElement $subnode, $key) + { + if ($subnode->count()) { + $value = $this->parseXml($subnode); + } elseif ($subnode->attributes()) { + $value = array(); + foreach ($subnode->attributes() as $attrkey => $attr) { + $value['@'.$attrkey] = (string) $attr; + } + $value['#'] = (string) $subnode; + } else { + $value = (string) $subnode; + } + if ($key === 'item') { + if (isset($value['@key'])) { + if (isset($value['#'])) { + $data[(string) $value['@key']] = $value['#']; + } else { + $data[(string) $value['@key']] = $value; + } + } else { + $data['item'][] = $value; + } + } elseif (array_key_exists($key, $data) || $key == "entry") { + if ((false === is_array($data[$key])) || (false === isset($data[$key][0]))) { + $data[$key] = array($data[$key]); + } + $data[$key][] = $value; + } else { + $data[$key] = $value; + } + } + /** * Parse the data and convert it to DOMElements * diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php index 04489abb004f9..ebd6dd9d49ece 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php @@ -201,6 +201,14 @@ public function testEncode() $this->assertEquals($source, $this->encoder->encode($obj, 'xml')); } + public function testEncodeWithNamespace() + { + $source = $this->getNamespacedXmlSource(); + $array = $this->getNamespacedArray(); + + $this->assertEquals($source, $this->encoder->encode($array, 'xml')); + } + public function testEncodeSerializerXmlRootNodeNameOption() { $options = array('xml_root_node_name' => 'test'); @@ -226,6 +234,14 @@ public function testDecode() $this->assertEquals(get_object_vars($obj), $this->encoder->decode($source, 'xml')); } + public function testDecodeWithNamespace() + { + $source = $this->getNamespacedXmlSource(); + $array = $this->getNamespacedArray(); + + $this->assertEquals($array, $this->encoder->decode($source, 'xml')); + } + public function testDecodeScalarWithAttribute() { $source = ''."\n". @@ -350,6 +366,53 @@ protected function getXmlSource() ''."\n"; } + protected function getNamespacedXmlSource() + { + return ''."\n". + ''. + '1'. + 'foo'. + 'ab'. + 'valvalbar'. + 'Codestin Search AppCodestin Search App'. + 'Ed'. + ''."\n"; + } + + protected function getNamespacedArray() + { + return array( + '@xmlns' => 'http://www.w3.org/2005/Atom', + '@xmlns:app' => 'http://www.w3.org/2007/app', + '@xmlns:media' => 'http://search.yahoo.com/mrss/', + '@xmlns:gd' => 'http://schemas.google.com/g/2005', + '@xmlns:yt' => 'http://gdata.youtube.com/schemas/2007', + 'qux' => "1", + 'app:foo' => "foo", + 'yt:bar' => array("a", "b"), + 'media:baz' => array( + 'media:key' => "val", + 'media:key2' => "val", + 'A B' => "bar", + 'item' => array( + array( + 'title' => 'title1', + ), + array( + 'title' => 'title2', + ) + ), + 'Barry' => array( + '@size' => 'large', + 'FooBar' => array( + '@gd:id' => '1', + 'Baz' => 'Ed', + ), + ), + ), + ); + } + protected function getObject() { $obj = new Dummy;