Skip to content

Commit 8e6233e

Browse files
committed
[Serializer] Using DOMElement instead of SimpleXmlElement in XmlEncoder to permit some behavior
1 parent 27d2b04 commit 8e6233e

File tree

2 files changed

+92
-23
lines changed

2 files changed

+92
-23
lines changed

src/Symfony/Component/Serializer/Encoder/XmlEncoder.php

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,13 @@ public function encode($data, $format)
5353
*/
5454
public function decode($data, $format)
5555
{
56-
$xml = simplexml_load_string($data);
57-
if (!$xml->count()) {
58-
return (string) $xml;
56+
$xml = \DOMDocument::loadXML($data);
57+
if (!$xml->documentElement->hasChildNodes()) {
58+
return "";
59+
} elseif ($xml->documentElement->childNodes->length == 1 && $xml->documentElement->firstChild instanceof \DOMText) {
60+
return trim((string)$xml->documentElement->firstChild->wholeText);
5961
}
60-
return $this->parseXml($xml);
62+
return $this->parseXml($xml->documentElement);
6163
}
6264

6365
/**
@@ -149,41 +151,62 @@ final protected function isElementNameValid($name)
149151
}
150152

151153
/**
152-
* Parse the input SimpleXmlElement into an array
154+
* Parse the input DOMElement into an array
153155
*
154-
* @param SimpleXmlElement $node xml to parse
156+
* @param DOMElement $node xml to parse
155157
* @return array
156158
*/
157159
private function parseXml($node)
158160
{
159161
$data = array();
160-
foreach ($node->children() as $key => $subnode) {
161-
if ($subnode->count()) {
162+
foreach ($node->childNodes as $subnode) {
163+
//When xml is "beautiful" (with tabs and newlines...), tabs and newline are considered as text but we do not want them
164+
if ($subnode instanceof DOMText && trim($subnode->wholeText) === "") {
165+
continue;
166+
}
167+
if (!$subnode->hasChildNodes()) {
168+
$value = "";
169+
} elseif ($subnode->childNodes->length == 1 && $subnode->firstChild instanceof \DOMText) {
170+
$value = trim((string)$subnode->firstChild->wholeText);
171+
} else {
162172
$value = $this->parseXml($subnode);
163-
if ($subnode->attributes()) {
164-
foreach ($subnode->attributes() as $attrkey => $attr) {
165-
$value['@'.$attrkey] = (string) $attr;
166-
}
173+
}
174+
175+
if ($subnode->hasAttributes()) {
176+
if (is_string($value) && $value !== "") {
177+
$value = array('#' => $value);
178+
} elseif (is_string($value)) {
179+
$value = array();
180+
}
181+
foreach($subnode->attributes as $attrKey => $attr) {
182+
$value['@'.$attrKey] = (string) $attr->value;
167183
}
168-
} else {
169-
$value = (string) $subnode;
170184
}
171-
if ($key === 'item') {
172-
if (isset($subnode['key'])) {
173-
$data[(string)$subnode['key']] = $value;
185+
186+
if ($subnode->tagName === 'item') {
187+
if (isset($value['@key'])) {
188+
$key = $value['@key'];
189+
$tmp = $value['#'];
190+
unset($value['@key']);
191+
unset($value['#']);
192+
if (!empty($value)) {
193+
$data[$key] = array_merge(array('#' => $tmp), $value);
194+
} else {
195+
$data[$key] = $tmp;
196+
}
174197
} elseif (isset($data['item'])) {
175198
$tmp = $data['item'];
176199
unset($data['item']);
177200
$data[] = $tmp;
178201
$data[] = $value;
179202
}
180-
} elseif (key_exists($key, $data)) {
181-
if (false === is_array($data[$key])) {
182-
$data[$key] = array($data[$key]);
203+
} elseif (key_exists($subnode->tagName, $data)) {
204+
if ((false === is_array($data[$subnode->tagName])) || (false === isset($data[$subnode->tagName][0]))) {
205+
$data[$subnode->tagName] = array($data[$subnode->tagName]);
183206
}
184-
$data[$key][] = $value;
207+
$data[$subnode->tagName][] = $value;
185208
} else {
186-
$data[$key] = $value;
209+
$data[$subnode->tagName] = $value;
187210
}
188211
}
189212
return $data;
@@ -205,6 +228,8 @@ private function buildXml($parentNode, $data)
205228
//Ah this is the magic @ attribute types.
206229
if (0 === strpos($key, "@") && is_scalar($data) && $this->isElementNameValid($attributeName = substr($key,1))) {
207230
$parentNode->setAttribute($attributeName, $data);
231+
} elseif ($key === '#') {
232+
$append = $this->selectNodeType($parentNode, $data);
208233
} elseif (is_array($data) && false === is_numeric($key)) {
209234
/**
210235
* Is this array fully numeric keys?

tests/Symfony/Tests/Component/Serializer/Encoder/XmlEncoderTest.php

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,18 @@ public function testEncodeSimpleXML()
111111

112112
$this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
113113
}
114+
115+
public function testEncodeScalarWithAttribute()
116+
{
117+
$array = array(
118+
'person' => array('@gender' => 'M', '#' => 'Peter'),
119+
);
120+
121+
$expected = '<?xml version="1.0"?>'."\n".
122+
'<response><person gender="M"><![CDATA[Peter]]></person></response>'."\n";
123+
124+
$this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
125+
}
114126

115127
public function testDecodeScalar()
116128
{
@@ -135,6 +147,38 @@ public function testDecode()
135147

136148
$this->assertEquals(get_object_vars($obj), $this->encoder->decode($source, 'xml'));
137149
}
150+
151+
public function testDecodeScalarWithAttribute()
152+
{
153+
$source = '<?xml version="1.0"?>'."\n".
154+
'<response><person gender="M">Peter</person></response>'."\n";
155+
156+
$expected = array(
157+
'person' => array('@gender' => 'M', '#' => 'Peter'),
158+
);
159+
160+
$this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
161+
}
162+
163+
public function testDecodeArray()
164+
{
165+
$source = '<?xml version="1.0"?>'."\n".
166+
'<response>'.
167+
'<people>'.
168+
'<person><firstname>Benjamin</firstname><lastname>Alexandre</lastname></person>'.
169+
'<person><firstname>Damien</firstname><lastname>Clay</lastname></person>'.
170+
'</people>'.
171+
'</response>'."\n";
172+
173+
$expected = array(
174+
'people' => array('person' => array(
175+
array('firstname' => 'Benjamin', 'lastname' => 'Alexandre'),
176+
array('firstname' => 'Damien', 'lastname' => 'Clay')
177+
))
178+
);
179+
180+
$this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
181+
}
138182

139183
protected function getXmlSource()
140184
{
@@ -153,7 +197,7 @@ protected function getObject()
153197
$obj = new Dummy;
154198
$obj->foo = 'foo';
155199
$obj->bar = array('a', 'b');
156-
$obj->baz = array('key' => 'val', 'key2' => 'val', 'A B' => 'bar', "Barry" => array('FooBar' => array("@id"=>1,"Baz"=>"Ed")));
200+
$obj->baz = array('key' => 'val', 'key2' => 'val', 'A B' => 'bar', "Barry" => array('FooBar' => array("Baz"=>"Ed", "@id"=>1)));
157201
$obj->qux = "1";
158202
return $obj;
159203
}

0 commit comments

Comments
 (0)