4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\Serializer\Tests\Encoder;
14 use PHPUnit\Framework\TestCase;
15 use Symfony\Component\Serializer\Tests\Fixtures\Dummy;
16 use Symfony\Component\Serializer\Tests\Fixtures\NormalizableTraversableDummy;
17 use Symfony\Component\Serializer\Tests\Fixtures\ScalarDummy;
18 use Symfony\Component\Serializer\Encoder\XmlEncoder;
19 use Symfony\Component\Serializer\Serializer;
20 use Symfony\Component\Serializer\Normalizer\CustomNormalizer;
21 use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
23 class XmlEncoderTest extends TestCase
30 private $exampleDateTimeString = '2017-02-19T15:16:08+0300';
32 protected function setUp()
34 $this->encoder = new XmlEncoder();
35 $serializer = new Serializer(array(new CustomNormalizer()), array('xml' => new XmlEncoder()));
36 $this->encoder->setSerializer($serializer);
39 public function testEncodeScalar()
41 $obj = new ScalarDummy();
44 $expected = '<?xml version="1.0"?>'."\n".
45 '<response>foo</response>'."\n";
47 $this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
50 public function testSetRootNodeName()
52 $obj = new ScalarDummy();
55 $this->encoder->setRootNodeName('test');
56 $expected = '<?xml version="1.0"?>'."\n".
57 '<test>foo</test>'."\n";
59 $this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
63 * @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
64 * @expectedExceptionMessage Document types are not allowed.
66 public function testDocTypeIsNotAllowed()
68 $this->encoder->decode('<?xml version="1.0"?><!DOCTYPE foo><foo></foo>', 'foo');
71 public function testAttributes()
73 $obj = new ScalarDummy();
84 'Bar' => array(1, 2, 3),
87 $expected = '<?xml version="1.0"?>'."\n".
89 '<foo-bar id="1" name="Bar"/>'.
90 '<Foo Type="test"><Bar>Test</Bar></Foo>'.
91 '<föo_bär>a</föo_bär>'.
97 $this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
100 public function testElementNameValid()
102 $obj = new ScalarDummy();
103 $obj->xmlFoo = array(
109 $expected = '<?xml version="1.0"?>'."\n".
111 '<foo-bar>a</foo-bar>'.
112 '<foo_bar>a</foo_bar>'.
113 '<föo_bär>a</föo_bär>'.
116 $this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
119 public function testEncodeSimpleXML()
121 $xml = simplexml_load_string('<firstname>Peter</firstname>');
122 $array = array('person' => $xml);
124 $expected = '<?xml version="1.0"?>'."\n".
125 '<response><person><firstname>Peter</firstname></person></response>'."\n";
127 $this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
130 public function testEncodeXmlAttributes()
132 $xml = simplexml_load_string('<firstname>Peter</firstname>');
133 $array = array('person' => $xml);
135 $expected = '<?xml version="1.1" encoding="utf-8" standalone="yes"?>'."\n".
136 '<response><person><firstname>Peter</firstname></person></response>'."\n";
139 'xml_version' => '1.1',
140 'xml_encoding' => 'utf-8',
141 'xml_standalone' => true,
144 $this->assertSame($expected, $this->encoder->encode($array, 'xml', $context));
147 public function testContext()
149 $array = array('person' => array('name' => 'George Abitbol'));
151 <?xml version="1.0"?>
154 <name>George Abitbol</name>
161 'xml_format_output' => true,
164 $this->assertSame($expected, $this->encoder->encode($array, 'xml', $context));
167 public function testEncodeScalarRootAttributes()
174 $expected = '<?xml version="1.0"?>'."\n".
175 '<response gender="m">Paul</response>'."\n";
177 $this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
180 public function testEncodeRootAttributes()
183 'firstname' => 'Paul',
187 $expected = '<?xml version="1.0"?>'."\n".
188 '<response gender="m"><firstname>Paul</firstname></response>'."\n";
190 $this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
193 public function testEncodeCdataWrapping()
196 'firstname' => 'Paul <or Me>',
199 $expected = '<?xml version="1.0"?>'."\n".
200 '<response><firstname><![CDATA[Paul <or Me>]]></firstname></response>'."\n";
202 $this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
205 public function testEncodeScalarWithAttribute()
208 'person' => array('@gender' => 'M', '#' => 'Peter'),
211 $expected = '<?xml version="1.0"?>'."\n".
212 '<response><person gender="M">Peter</person></response>'."\n";
214 $this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
217 public function testDecodeScalar()
219 $source = '<?xml version="1.0"?>'."\n".
220 '<response>foo</response>'."\n";
222 $this->assertEquals('foo', $this->encoder->decode($source, 'xml'));
225 public function testDecodeBigDigitAttributes()
228 <?xml version="1.0"?>
229 <document index="182077241760011681341821060401202210011000045913000000017100">Name</document>
232 $this->assertSame(array('@index' => 182077241760011681341821060401202210011000045913000000017100, '#' => 'Name'), $this->encoder->decode($source, 'xml'));
235 public function testDecodeNegativeIntAttribute()
238 <?xml version="1.0"?>
239 <document index="-1234">Name</document>
242 $this->assertSame(array('@index' => -1234, '#' => 'Name'), $this->encoder->decode($source, 'xml'));
245 public function testDecodeFloatAttribute()
248 <?xml version="1.0"?>
249 <document index="-12.11">Name</document>
252 $this->assertSame(array('@index' => -12.11, '#' => 'Name'), $this->encoder->decode($source, 'xml'));
255 public function testDecodeNegativeFloatAttribute()
258 <?xml version="1.0"?>
259 <document index="-12.11">Name</document>
262 $this->assertSame(array('@index' => -12.11, '#' => 'Name'), $this->encoder->decode($source, 'xml'));
265 public function testEncode()
267 $source = $this->getXmlSource();
268 $obj = $this->getObject();
270 $this->assertEquals($source, $this->encoder->encode($obj, 'xml'));
273 public function testEncodeWithNamespace()
275 $source = $this->getNamespacedXmlSource();
276 $array = $this->getNamespacedArray();
278 $this->assertEquals($source, $this->encoder->encode($array, 'xml'));
281 public function testEncodeSerializerXmlRootNodeNameOption()
283 $options = array('xml_root_node_name' => 'test');
284 $this->encoder = new XmlEncoder();
285 $serializer = new Serializer(array(), array('xml' => new XmlEncoder()));
286 $this->encoder->setSerializer($serializer);
289 'person' => array('@gender' => 'M', '#' => 'Peter'),
292 $expected = '<?xml version="1.0"?>'."\n".
293 '<test><person gender="M">Peter</person></test>'."\n";
295 $this->assertEquals($expected, $serializer->serialize($array, 'xml', $options));
298 public function testEncodeTraversableWhenNormalizable()
300 $this->encoder = new XmlEncoder();
301 $serializer = new Serializer(array(new CustomNormalizer()), array('xml' => new XmlEncoder()));
302 $this->encoder->setSerializer($serializer);
305 <?xml version="1.0"?>
306 <response><foo>normalizedFoo</foo><bar>normalizedBar</bar></response>
310 $this->assertEquals($expected, $serializer->serialize(new NormalizableTraversableDummy(), 'xml'));
313 public function testDecode()
315 $source = $this->getXmlSource();
316 $obj = $this->getObject();
318 $this->assertEquals(get_object_vars($obj), $this->encoder->decode($source, 'xml'));
321 public function testDecodeCdataWrapping()
324 'firstname' => 'Paul <or Me>',
327 $xml = '<?xml version="1.0"?>'."\n".
328 '<response><firstname><![CDATA[Paul <or Me>]]></firstname></response>'."\n";
330 $this->assertEquals($expected, $this->encoder->decode($xml, 'xml'));
333 public function testDecodeCdataWrappingAndWhitespace()
336 'firstname' => 'Paul <or Me>',
339 $xml = '<?xml version="1.0"?>'."\n".
340 '<response><firstname>'."\n".
341 '<![CDATA[Paul <or Me>]]></firstname></response>'."\n";
343 $this->assertEquals($expected, $this->encoder->decode($xml, 'xml'));
346 public function testDecodeWithNamespace()
348 $source = $this->getNamespacedXmlSource();
349 $array = $this->getNamespacedArray();
351 $this->assertEquals($array, $this->encoder->decode($source, 'xml'));
354 public function testDecodeScalarWithAttribute()
356 $source = '<?xml version="1.0"?>'."\n".
357 '<response><person gender="M">Peter</person></response>'."\n";
360 'person' => array('@gender' => 'M', '#' => 'Peter'),
363 $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
366 public function testDecodeScalarRootAttributes()
368 $source = '<?xml version="1.0"?>'."\n".
369 '<person gender="M">Peter</person>'."\n";
376 $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
379 public function testDecodeRootAttributes()
381 $source = '<?xml version="1.0"?>'."\n".
382 '<person gender="M"><firstname>Peter</firstname><lastname>Mac Calloway</lastname></person>'."\n";
385 'firstname' => 'Peter',
386 'lastname' => 'Mac Calloway',
390 $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
393 public function testDecodeArray()
395 $source = '<?xml version="1.0"?>'."\n".
398 '<person><firstname>Benjamin</firstname><lastname>Alexandre</lastname></person>'.
399 '<person><firstname>Damien</firstname><lastname>Clay</lastname></person>'.
404 'people' => array('person' => array(
405 array('firstname' => 'Benjamin', 'lastname' => 'Alexandre'),
406 array('firstname' => 'Damien', 'lastname' => 'Clay'),
410 $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
413 public function testDecodeXMLWithProcessInstruction()
416 <?xml version="1.0"?>
417 <?xml-stylesheet type="text/xsl" href="/xsl/xmlverbatimwrapper.xsl"?>
418 <?display table-view?>
419 <?sort alpha-ascending?>
422 <?textinfo whitespace is allowed ?>
428 <item key="A B">bar</item>
430 <title>title1</title>
432 <?item ignore-title ?>
434 <title>title2</title>
444 <?instruction <value> ?>
446 $obj = $this->getObject();
448 $this->assertEquals(get_object_vars($obj), $this->encoder->decode($source, 'xml'));
451 public function testDecodeIgnoreWhiteSpace()
454 <?xml version="1.0"?>
457 <firstname>Benjamin</firstname>
458 <lastname>Alexandre</lastname>
461 <firstname>Damien</firstname>
462 <lastname>Clay</lastname>
466 $expected = array('person' => array(
467 array('firstname' => 'Benjamin', 'lastname' => 'Alexandre'),
468 array('firstname' => 'Damien', 'lastname' => 'Clay'),
471 $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
474 public function testDecodeWithoutItemHash()
476 $obj = new ScalarDummy();
477 $obj->xmlFoo = array(
480 'item' => array('@key' => 'key', 'key-val' => 'val'),
487 'Bar' => array(1, 2, 3),
493 'key' => array('@key' => 'key', 'key-val' => 'val'),
500 'Bar' => array(1, 2, 3),
503 $xml = $this->encoder->encode($obj, 'xml');
504 $this->assertEquals($expected, $this->encoder->decode($xml, 'xml'));
508 * @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
510 public function testDecodeInvalidXml()
512 $this->encoder->decode('<?xml version="1.0"?><invalid><xml>', 'xml');
516 * @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
518 public function testPreventsComplexExternalEntities()
520 $this->encoder->decode('<?xml version="1.0"?><!DOCTYPE scan[<!ENTITY test SYSTEM "php://filter/read=convert.base64-encode/resource=XmlEncoderTest.php">]><scan>&test;</scan>', 'xml');
523 public function testDecodeEmptyXml()
525 if (method_exists($this, 'expectException')) {
526 $this->expectException('Symfony\Component\Serializer\Exception\UnexpectedValueException');
527 $this->expectExceptionMessage('Invalid XML data, it can not be empty.');
529 $this->setExpectedException('Symfony\Component\Serializer\Exception\UnexpectedValueException', 'Invalid XML data, it can not be empty.');
531 $this->encoder->decode(' ', 'xml');
534 protected function getXmlSource()
536 return '<?xml version="1.0"?>'."\n".
539 '<bar>a</bar><bar>b</bar>'.
540 '<baz><key>val</key><key2>val</key2><item key="A B">bar</item>'.
541 '<item><title>title1</title></item><item><title>title2</title></item>'.
542 '<Barry><FooBar id="1"><Baz>Ed</Baz></FooBar></Barry></baz>'.
547 protected function getNamespacedXmlSource()
549 return '<?xml version="1.0"?>'."\n".
550 '<response 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">'.
552 '<app:foo>foo</app:foo>'.
553 '<yt:bar>a</yt:bar><yt:bar>b</yt:bar>'.
554 '<media:baz><media:key>val</media:key><media:key2>val</media:key2><item key="A B">bar</item>'.
555 '<item><title>title1</title></item><item><title>title2</title></item>'.
556 '<Barry size="large"><FooBar gd:id="1"><Baz>Ed</Baz></FooBar></Barry></media:baz>'.
560 protected function getNamespacedArray()
563 '@xmlns' => 'http://www.w3.org/2005/Atom',
564 '@xmlns:app' => 'http://www.w3.org/2007/app',
565 '@xmlns:media' => 'http://search.yahoo.com/mrss/',
566 '@xmlns:gd' => 'http://schemas.google.com/g/2005',
567 '@xmlns:yt' => 'http://gdata.youtube.com/schemas/2007',
570 'yt:bar' => array('a', 'b'),
571 'media:baz' => array(
572 'media:key' => 'val',
573 'media:key2' => 'val',
594 protected function getObject()
598 $obj->bar = array('a', 'b');
599 $obj->baz = array('key' => 'val', 'key2' => 'val', 'A B' => 'bar', 'item' => array(array('title' => 'title1'), array('title' => 'title2')), 'Barry' => array('FooBar' => array('Baz' => 'Ed', '@id' => 1)));
605 public function testEncodeXmlWithBoolValue()
607 $expectedXml = <<<'XML'
608 <?xml version="1.0"?>
609 <response><foo>1</foo><bar>0</bar></response>
613 $actualXml = $this->encoder->encode(array('foo' => true, 'bar' => false), 'xml');
615 $this->assertEquals($expectedXml, $actualXml);
618 public function testEncodeXmlWithDateTimeObjectValue()
620 $xmlEncoder = $this->createXmlEncoderWithDateTimeNormalizer();
622 $actualXml = $xmlEncoder->encode(array('dateTime' => new \DateTime($this->exampleDateTimeString)), 'xml');
624 $this->assertEquals($this->createXmlWithDateTime(), $actualXml);
627 public function testEncodeXmlWithDateTimeObjectField()
629 $xmlEncoder = $this->createXmlEncoderWithDateTimeNormalizer();
631 $actualXml = $xmlEncoder->encode(array('foo' => array('@dateTime' => new \DateTime($this->exampleDateTimeString))), 'xml');
633 $this->assertEquals($this->createXmlWithDateTimeField(), $actualXml);
639 private function createXmlEncoderWithDateTimeNormalizer()
641 $encoder = new XmlEncoder();
642 $serializer = new Serializer(array($this->createMockDateTimeNormalizer()), array('xml' => new XmlEncoder()));
643 $encoder->setSerializer($serializer);
649 * @return \PHPUnit_Framework_MockObject_MockObject|NormalizerInterface
651 private function createMockDateTimeNormalizer()
653 $mock = $this->getMockBuilder('\Symfony\Component\Serializer\Normalizer\CustomNormalizer')->getMock();
656 ->expects($this->once())
657 ->method('normalize')
658 ->with(new \DateTime($this->exampleDateTimeString), 'xml', array())
659 ->willReturn($this->exampleDateTimeString);
662 ->expects($this->once())
663 ->method('supportsNormalization')
664 ->with(new \DateTime($this->exampleDateTimeString), 'xml')
673 private function createXmlWithDateTime()
675 return sprintf('<?xml version="1.0"?>
676 <response><dateTime>%s</dateTime></response>
677 ', $this->exampleDateTimeString);
683 private function createXmlWithDateTimeField()
685 return sprintf('<?xml version="1.0"?>
686 <response><foo dateTime="%s"/></response>
687 ', $this->exampleDateTimeString);