Skip to content

Commit

Permalink
Implement #1087 - "empty" XML namespace
Browse files Browse the repository at this point in the history
This is a basic implementation of `xmlns=""` handling.
  • Loading branch information
discordier committed Jun 13, 2019
1 parent 48be7ea commit 7ab1eba
Show file tree
Hide file tree
Showing 8 changed files with 53 additions and 4 deletions.
8 changes: 8 additions & 0 deletions src/XmlDeserializationVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,14 @@ public function visitProperty(PropertyMetadata $metadata, $data)
if (!$node->count()) {
throw new NotAcceptableException();
}
} elseif ('' === $metadata->xmlNamespace) {
// See #1087 - element must be like: <element xmlns="" /> - https://www.w3.org/TR/REC-xml-names/#iri-use
// Use of an empty string in a namespace declaration turns it into an "undeclaration".
$nodes = $data->xpath('./' . $name);
if (empty($nodes)) {
throw new NotAcceptableException();
}
$node = reset($nodes);
} else {
$namespaces = $data->getDocNamespaces();
if (isset($namespaces[''])) {
Expand Down
21 changes: 21 additions & 0 deletions src/XmlSerializationVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,15 @@ private function addNamespaceAttributes(ClassMetadata $metadata, \DOMElement $el

private function createElement(string $tagName, ?string $namespace = null): \DOMElement
{
// See #1087 - element must be like: <element xmlns="" /> - https://www.w3.org/TR/REC-xml-names/#iri-use
// Use of an empty string in a namespace declaration turns it into an "undeclaration".
if ('' === $namespace) {
// If we have a default namespace, we need to create namespaced.
if ($this->parentHasNonEmptyDefaultNs()) {
return $this->document->createElementNS($namespace, $tagName);
}
return $this->document->createElement($tagName);
}
if (null === $namespace) {
return $this->document->createElement($tagName);
}
Expand Down Expand Up @@ -491,4 +500,16 @@ private function getClassDefaultNamespace(ClassMetadata $metadata): ?string
{
return $metadata->xmlNamespaces[''] ?? null;
}

private function parentHasNonEmptyDefaultNs()
{
$element = $this->currentNode;
while ($element) {
if ((null !== $uri = $element->lookupNamespaceUri(null)) && ('' !== $uri)) {
return true;
}
$element = $element->parentNode;
}
return false;
}
}
9 changes: 8 additions & 1 deletion tests/Fixtures/ObjectWithXmlNamespaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,19 @@ class ObjectWithXmlNamespaces
*/
private $language;

public function __construct($title, $author, \DateTime $createdAt, $language)
/**
* @Type("string")
* @XmlElement(namespace="")
*/
private $emptyNsElement;

public function __construct($title, $author, \DateTime $createdAt, $language, $emptyNsElement)
{
$this->title = $title;
$this->author = $author;
$this->createdAt = $createdAt;
$this->language = $language;
$this->emptyNsElement = $emptyNsElement;
$this->etag = sha1($this->createdAt->format(\DateTime::ATOM));
}
}
10 changes: 9 additions & 1 deletion tests/Fixtures/ObjectWithXmlRootNamespace.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use JMS\Serializer\Annotation\Type;
use JMS\Serializer\Annotation\XmlAttribute;
use JMS\Serializer\Annotation\XmlElement;
use JMS\Serializer\Annotation\XmlRoot;

/**
Expand Down Expand Up @@ -41,12 +42,19 @@ class ObjectWithXmlRootNamespace
*/
private $language;

public function __construct($title, $author, \DateTime $createdAt, $language)
/**
* @Type("string")
* @XmlElement(namespace="")
*/
private $emptyNsElement;

public function __construct($title, $author, \DateTime $createdAt, $language, $emptyNsElement)
{
$this->title = $title;
$this->author = $author;
$this->createdAt = $createdAt;
$this->language = $language;
$this->emptyNsElement = $emptyNsElement;
$this->etag = sha1($this->createdAt->format(\DateTime::ATOM));
}
}
6 changes: 4 additions & 2 deletions tests/Serializer/XmlSerializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ public function testDeserializingNull()

public function testObjectWithXmlNamespaces()
{
$object = new ObjectWithXmlNamespaces('This is a nice title.', 'Foo Bar', new \DateTime('2011-07-30 00:00', new \DateTimeZone('UTC')), 'en');
$object = new ObjectWithXmlNamespaces('This is a nice title.', 'Foo Bar', new \DateTime('2011-07-30 00:00', new \DateTimeZone('UTC')), 'en', 'value for empty namespace property');

$serialized = $this->serialize($object);
self::assertEquals($this->getContent('object_with_xml_namespaces'), $serialized);
Expand All @@ -385,13 +385,15 @@ public function testObjectWithXmlNamespaces()
self::assertEquals('en', $this->xpathFirstToString($xml, './@ns1:language'));
self::assertEquals('This is a nice title.', $this->xpathFirstToString($xml, './ns1:title'));
self::assertEquals('Foo Bar', $this->xpathFirstToString($xml, './ns3:author'));
self::assertEquals('value for empty namespace property', $this->xpathFirstToString($xml, './empty_ns_element'));

$deserialized = $this->deserialize($this->getContent('object_with_xml_namespacesalias'), get_class($object));
self::assertEquals('2011-07-30T00:00:00+00:00', $this->getField($deserialized, 'createdAt')->format(\DateTime::ATOM));
self::assertAttributeEquals('This is a nice title.', 'title', $deserialized);
self::assertAttributeSame('e86ce85cdb1253e4fc6352f5cf297248bceec62b', 'etag', $deserialized);
self::assertAttributeSame('en', 'language', $deserialized);
self::assertAttributeEquals('Foo Bar', 'author', $deserialized);
self::assertEquals('value for empty namespace property', $this->getField($deserialized, 'emptyNsElement'));
}

public function testObjectWithXmlNamespacesAndBackReferencedNamespaces()
Expand Down Expand Up @@ -430,7 +432,7 @@ static function (XmlSerializationVisitor $visitor, $data, $type, Context $contex

public function testObjectWithXmlRootNamespace()
{
$object = new ObjectWithXmlRootNamespace('This is a nice title.', 'Foo Bar', new \DateTime('2011-07-30 00:00', new \DateTimeZone('UTC')), 'en');
$object = new ObjectWithXmlRootNamespace('This is a nice title.', 'Foo Bar', new \DateTime('2011-07-30 00:00', new \DateTimeZone('UTC')), 'en', 'value for empty namespace property');
self::assertEquals($this->getContent('object_with_xml_root_namespace'), $this->serialize($object));
}

Expand Down
1 change: 1 addition & 0 deletions tests/Serializer/xml/object_with_xml_namespaces.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
<ex:test-object xmlns:ex="http://example.com/namespace" xmlns:gd="http://schemas.google.com/g/2005" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:ns-fde543a0="http://purl.org/dc/elements/1.1/" created_at="2011-07-30T00:00:00+00:00" gd:etag="e86ce85cdb1253e4fc6352f5cf297248bceec62b" ns-fde543a0:language="en">
<ns-fde543a0:title xmlns:ns-fde543a0="http://purl.org/dc/elements/1.1/"><![CDATA[This is a nice title.]]></ns-fde543a0:title>
<atom:author><![CDATA[Foo Bar]]></atom:author>
<empty_ns_element><![CDATA[value for empty namespace property]]></empty_ns_element>
</ex:test-object>
1 change: 1 addition & 0 deletions tests/Serializer/xml/object_with_xml_namespacesalias.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
<test-object xmlns="http://example.com/namespace" xmlns:name1="http://schemas.google.com/g/2005" xmlns:name2="http://www.w3.org/2005/Atom" xmlns:name3="http://purl.org/dc/elements/1.1/" created_at="2011-07-30T00:00:00+00:00" name1:etag="e86ce85cdb1253e4fc6352f5cf297248bceec62b" name3:language="en">
<name3:title><![CDATA[This is a nice title.]]></name3:title>
<name2:author><![CDATA[Foo Bar]]></name2:author>
<empty_ns_element xmlns=""><![CDATA[value for empty namespace property]]></empty_ns_element>
</test-object>
1 change: 1 addition & 0 deletions tests/Serializer/xml/object_with_xml_root_namespace.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
<test-object xmlns="http://example.com/namespace" created_at="2011-07-30T00:00:00+00:00" etag="e86ce85cdb1253e4fc6352f5cf297248bceec62b" language="en">
<title><![CDATA[This is a nice title.]]></title>
<author><![CDATA[Foo Bar]]></author>
<empty_ns_element xmlns=""><![CDATA[value for empty namespace property]]></empty_ns_element>
</test-object>

0 comments on commit 7ab1eba

Please sign in to comment.