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\Encoder\XmlEncoder;
16 use Symfony\Component\Serializer\Normalizer\CustomNormalizer;
17 use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
18 use Symfony\Component\Serializer\Serializer;
19 use Symfony\Component\Serializer\Tests\Fixtures\Dummy;
20 use Symfony\Component\Serializer\Tests\Fixtures\NormalizableTraversableDummy;
21 use Symfony\Component\Serializer\Tests\Fixtures\ScalarDummy;
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 testEncodeRemovingEmptyTags()
149 $array = array('person' => array('firstname' => 'Peter', 'lastname' => null));
151 $expected = '<?xml version="1.0"?>'."\n".
152 '<response><person><firstname>Peter</firstname></person></response>'."\n";
154 $context = array('remove_empty_tags' => true);
156 $this->assertSame($expected, $this->encoder->encode($array, 'xml', $context));
159 public function testEncodeNotRemovingEmptyTags()
161 $array = array('person' => array('firstname' => 'Peter', 'lastname' => null));
163 $expected = '<?xml version="1.0"?>'."\n".
164 '<response><person><firstname>Peter</firstname><lastname/></person></response>'."\n";
166 $this->assertSame($expected, $this->encoder->encode($array, 'xml'));
169 public function testContext()
171 $array = array('person' => array('name' => 'George Abitbol'));
173 <?xml version="1.0"?>
176 <name>George Abitbol</name>
183 'xml_format_output' => true,
186 $this->assertSame($expected, $this->encoder->encode($array, 'xml', $context));
189 public function testEncodeScalarRootAttributes()
196 $expected = '<?xml version="1.0"?>'."\n".
197 '<response gender="m">Paul</response>'."\n";
199 $this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
202 public function testEncodeRootAttributes()
205 'firstname' => 'Paul',
209 $expected = '<?xml version="1.0"?>'."\n".
210 '<response gender="m"><firstname>Paul</firstname></response>'."\n";
212 $this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
215 public function testEncodeCdataWrapping()
218 'firstname' => 'Paul <or Me>',
221 $expected = '<?xml version="1.0"?>'."\n".
222 '<response><firstname><![CDATA[Paul <or Me>]]></firstname></response>'."\n";
224 $this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
227 public function testEncodeScalarWithAttribute()
230 'person' => array('@gender' => 'M', '#' => 'Peter'),
233 $expected = '<?xml version="1.0"?>'."\n".
234 '<response><person gender="M">Peter</person></response>'."\n";
236 $this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
239 public function testDecodeScalar()
241 $source = '<?xml version="1.0"?>'."\n".
242 '<response>foo</response>'."\n";
244 $this->assertEquals('foo', $this->encoder->decode($source, 'xml'));
247 public function testDecodeBigDigitAttributes()
250 <?xml version="1.0"?>
251 <document index="182077241760011681341821060401202210011000045913000000017100">Name</document>
254 $this->assertSame(array('@index' => 182077241760011681341821060401202210011000045913000000017100, '#' => 'Name'), $this->encoder->decode($source, 'xml'));
257 public function testDecodeNegativeIntAttribute()
260 <?xml version="1.0"?>
261 <document index="-1234">Name</document>
264 $this->assertSame(array('@index' => -1234, '#' => 'Name'), $this->encoder->decode($source, 'xml'));
267 public function testDecodeFloatAttribute()
270 <?xml version="1.0"?>
271 <document index="-12.11">Name</document>
274 $this->assertSame(array('@index' => -12.11, '#' => 'Name'), $this->encoder->decode($source, 'xml'));
277 public function testDecodeNegativeFloatAttribute()
280 <?xml version="1.0"?>
281 <document index="-12.11">Name</document>
284 $this->assertSame(array('@index' => -12.11, '#' => 'Name'), $this->encoder->decode($source, 'xml'));
287 public function testNoTypeCastAttribute()
290 <?xml version="1.0"?>
291 <document a="018" b="-12.11">
292 <node a="018" b="-12.11"/>
296 $data = $this->encoder->decode($source, 'xml', array('xml_type_cast_attributes' => false));
306 $this->assertSame($expected, $data);
309 public function testEncode()
311 $source = $this->getXmlSource();
312 $obj = $this->getObject();
314 $this->assertEquals($source, $this->encoder->encode($obj, 'xml'));
317 public function testEncodeWithNamespace()
319 $source = $this->getNamespacedXmlSource();
320 $array = $this->getNamespacedArray();
322 $this->assertEquals($source, $this->encoder->encode($array, 'xml'));
325 public function testEncodeSerializerXmlRootNodeNameOption()
327 $options = array('xml_root_node_name' => 'test');
328 $this->encoder = new XmlEncoder();
329 $serializer = new Serializer(array(), array('xml' => new XmlEncoder()));
330 $this->encoder->setSerializer($serializer);
333 'person' => array('@gender' => 'M', '#' => 'Peter'),
336 $expected = '<?xml version="1.0"?>'."\n".
337 '<test><person gender="M">Peter</person></test>'."\n";
339 $this->assertEquals($expected, $serializer->serialize($array, 'xml', $options));
342 public function testEncodeTraversableWhenNormalizable()
344 $this->encoder = new XmlEncoder();
345 $serializer = new Serializer(array(new CustomNormalizer()), array('xml' => new XmlEncoder()));
346 $this->encoder->setSerializer($serializer);
349 <?xml version="1.0"?>
350 <response><foo>normalizedFoo</foo><bar>normalizedBar</bar></response>
354 $this->assertEquals($expected, $serializer->serialize(new NormalizableTraversableDummy(), 'xml'));
357 public function testDecode()
359 $source = $this->getXmlSource();
360 $obj = $this->getObject();
362 $this->assertEquals(get_object_vars($obj), $this->encoder->decode($source, 'xml'));
365 public function testDecodeCdataWrapping()
368 'firstname' => 'Paul <or Me>',
371 $xml = '<?xml version="1.0"?>'."\n".
372 '<response><firstname><![CDATA[Paul <or Me>]]></firstname></response>'."\n";
374 $this->assertEquals($expected, $this->encoder->decode($xml, 'xml'));
377 public function testDecodeCdataWrappingAndWhitespace()
380 'firstname' => 'Paul <or Me>',
383 $xml = '<?xml version="1.0"?>'."\n".
384 '<response><firstname>'."\n".
385 '<![CDATA[Paul <or Me>]]></firstname></response>'."\n";
387 $this->assertEquals($expected, $this->encoder->decode($xml, 'xml'));
390 public function testDecodeWithNamespace()
392 $source = $this->getNamespacedXmlSource();
393 $array = $this->getNamespacedArray();
395 $this->assertEquals($array, $this->encoder->decode($source, 'xml'));
398 public function testDecodeScalarWithAttribute()
400 $source = '<?xml version="1.0"?>'."\n".
401 '<response><person gender="M">Peter</person></response>'."\n";
404 'person' => array('@gender' => 'M', '#' => 'Peter'),
407 $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
410 public function testDecodeScalarRootAttributes()
412 $source = '<?xml version="1.0"?>'."\n".
413 '<person gender="M">Peter</person>'."\n";
420 $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
423 public function testDecodeRootAttributes()
425 $source = '<?xml version="1.0"?>'."\n".
426 '<person gender="M"><firstname>Peter</firstname><lastname>Mac Calloway</lastname></person>'."\n";
429 'firstname' => 'Peter',
430 'lastname' => 'Mac Calloway',
434 $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
437 public function testDecodeArray()
439 $source = '<?xml version="1.0"?>'."\n".
442 '<person><firstname>Benjamin</firstname><lastname>Alexandre</lastname></person>'.
443 '<person><firstname>Damien</firstname><lastname>Clay</lastname></person>'.
448 'people' => array('person' => array(
449 array('firstname' => 'Benjamin', 'lastname' => 'Alexandre'),
450 array('firstname' => 'Damien', 'lastname' => 'Clay'),
454 $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
457 public function testDecodeXMLWithProcessInstruction()
460 <?xml version="1.0"?>
461 <?xml-stylesheet type="text/xsl" href="/xsl/xmlverbatimwrapper.xsl"?>
462 <?display table-view?>
463 <?sort alpha-ascending?>
466 <?textinfo whitespace is allowed ?>
472 <item key="A B">bar</item>
474 <title>title1</title>
476 <?item ignore-title ?>
478 <title>title2</title>
488 <?instruction <value> ?>
490 $obj = $this->getObject();
492 $this->assertEquals(get_object_vars($obj), $this->encoder->decode($source, 'xml'));
495 public function testDecodeIgnoreWhiteSpace()
498 <?xml version="1.0"?>
501 <firstname>Benjamin</firstname>
502 <lastname>Alexandre</lastname>
505 <firstname>Damien</firstname>
506 <lastname>Clay</lastname>
510 $expected = array('person' => array(
511 array('firstname' => 'Benjamin', 'lastname' => 'Alexandre'),
512 array('firstname' => 'Damien', 'lastname' => 'Clay'),
515 $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
518 public function testDecodeWithoutItemHash()
520 $obj = new ScalarDummy();
521 $obj->xmlFoo = array(
524 'item' => array('@key' => 'key', 'key-val' => 'val'),
531 'Bar' => array(1, 2, 3),
537 'key' => array('@key' => 'key', 'key-val' => 'val'),
544 'Bar' => array(1, 2, 3),
547 $xml = $this->encoder->encode($obj, 'xml');
548 $this->assertEquals($expected, $this->encoder->decode($xml, 'xml'));
552 * @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
554 public function testDecodeInvalidXml()
556 $this->encoder->decode('<?xml version="1.0"?><invalid><xml>', 'xml');
560 * @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
562 public function testPreventsComplexExternalEntities()
564 $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');
567 public function testDecodeEmptyXml()
569 if (method_exists($this, 'expectException')) {
570 $this->expectException('Symfony\Component\Serializer\Exception\UnexpectedValueException');
571 $this->expectExceptionMessage('Invalid XML data, it can not be empty.');
573 $this->setExpectedException('Symfony\Component\Serializer\Exception\UnexpectedValueException', 'Invalid XML data, it can not be empty.');
575 $this->encoder->decode(' ', 'xml');
578 protected function getXmlSource()
580 return '<?xml version="1.0"?>'."\n".
583 '<bar>a</bar><bar>b</bar>'.
584 '<baz><key>val</key><key2>val</key2><item key="A B">bar</item>'.
585 '<item><title>title1</title></item><item><title>title2</title></item>'.
586 '<Barry><FooBar id="1"><Baz>Ed</Baz></FooBar></Barry></baz>'.
591 protected function getNamespacedXmlSource()
593 return '<?xml version="1.0"?>'."\n".
594 '<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">'.
596 '<app:foo>foo</app:foo>'.
597 '<yt:bar>a</yt:bar><yt:bar>b</yt:bar>'.
598 '<media:baz><media:key>val</media:key><media:key2>val</media:key2><item key="A B">bar</item>'.
599 '<item><title>title1</title></item><item><title>title2</title></item>'.
600 '<Barry size="large"><FooBar gd:id="1"><Baz>Ed</Baz></FooBar></Barry></media:baz>'.
604 protected function getNamespacedArray()
607 '@xmlns' => 'http://www.w3.org/2005/Atom',
608 '@xmlns:app' => 'http://www.w3.org/2007/app',
609 '@xmlns:media' => 'http://search.yahoo.com/mrss/',
610 '@xmlns:gd' => 'http://schemas.google.com/g/2005',
611 '@xmlns:yt' => 'http://gdata.youtube.com/schemas/2007',
614 'yt:bar' => array('a', 'b'),
615 'media:baz' => array(
616 'media:key' => 'val',
617 'media:key2' => 'val',
638 protected function getObject()
642 $obj->bar = array('a', 'b');
643 $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)));
649 public function testEncodeXmlWithBoolValue()
651 $expectedXml = <<<'XML'
652 <?xml version="1.0"?>
653 <response><foo>1</foo><bar>0</bar></response>
657 $actualXml = $this->encoder->encode(array('foo' => true, 'bar' => false), 'xml');
659 $this->assertEquals($expectedXml, $actualXml);
662 public function testEncodeXmlWithDateTimeObjectValue()
664 $xmlEncoder = $this->createXmlEncoderWithDateTimeNormalizer();
666 $actualXml = $xmlEncoder->encode(array('dateTime' => new \DateTime($this->exampleDateTimeString)), 'xml');
668 $this->assertEquals($this->createXmlWithDateTime(), $actualXml);
671 public function testEncodeXmlWithDateTimeObjectField()
673 $xmlEncoder = $this->createXmlEncoderWithDateTimeNormalizer();
675 $actualXml = $xmlEncoder->encode(array('foo' => array('@dateTime' => new \DateTime($this->exampleDateTimeString))), 'xml');
677 $this->assertEquals($this->createXmlWithDateTimeField(), $actualXml);
683 private function createXmlEncoderWithDateTimeNormalizer()
685 $encoder = new XmlEncoder();
686 $serializer = new Serializer(array($this->createMockDateTimeNormalizer()), array('xml' => new XmlEncoder()));
687 $encoder->setSerializer($serializer);
693 * @return \PHPUnit_Framework_MockObject_MockObject|NormalizerInterface
695 private function createMockDateTimeNormalizer()
697 $mock = $this->getMockBuilder('\Symfony\Component\Serializer\Normalizer\CustomNormalizer')->getMock();
700 ->expects($this->once())
701 ->method('normalize')
702 ->with(new \DateTime($this->exampleDateTimeString), 'xml', array())
703 ->willReturn($this->exampleDateTimeString);
706 ->expects($this->once())
707 ->method('supportsNormalization')
708 ->with(new \DateTime($this->exampleDateTimeString), 'xml')
717 private function createXmlWithDateTime()
719 return sprintf('<?xml version="1.0"?>
720 <response><dateTime>%s</dateTime></response>
721 ', $this->exampleDateTimeString);
727 private function createXmlWithDateTimeField()
729 return sprintf('<?xml version="1.0"?>
730 <response><foo dateTime="%s"/></response>
731 ', $this->exampleDateTimeString);