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 testEncode()
227 $source = $this->getXmlSource();
228 $obj = $this->getObject();
230 $this->assertEquals($source, $this->encoder->encode($obj, 'xml'));
233 public function testEncodeWithNamespace()
235 $source = $this->getNamespacedXmlSource();
236 $array = $this->getNamespacedArray();
238 $this->assertEquals($source, $this->encoder->encode($array, 'xml'));
241 public function testEncodeSerializerXmlRootNodeNameOption()
243 $options = array('xml_root_node_name' => 'test');
244 $this->encoder = new XmlEncoder();
245 $serializer = new Serializer(array(), array('xml' => new XmlEncoder()));
246 $this->encoder->setSerializer($serializer);
249 'person' => array('@gender' => 'M', '#' => 'Peter'),
252 $expected = '<?xml version="1.0"?>'."\n".
253 '<test><person gender="M">Peter</person></test>'."\n";
255 $this->assertEquals($expected, $serializer->serialize($array, 'xml', $options));
258 public function testEncodeTraversableWhenNormalizable()
260 $this->encoder = new XmlEncoder();
261 $serializer = new Serializer(array(new CustomNormalizer()), array('xml' => new XmlEncoder()));
262 $this->encoder->setSerializer($serializer);
265 <?xml version="1.0"?>
266 <response><foo>normalizedFoo</foo><bar>normalizedBar</bar></response>
270 $this->assertEquals($expected, $serializer->serialize(new NormalizableTraversableDummy(), 'xml'));
273 public function testDecode()
275 $source = $this->getXmlSource();
276 $obj = $this->getObject();
278 $this->assertEquals(get_object_vars($obj), $this->encoder->decode($source, 'xml'));
281 public function testDecodeCdataWrapping()
284 'firstname' => 'Paul <or Me>',
287 $xml = '<?xml version="1.0"?>'."\n".
288 '<response><firstname><![CDATA[Paul <or Me>]]></firstname></response>'."\n";
290 $this->assertEquals($expected, $this->encoder->decode($xml, 'xml'));
293 public function testDecodeCdataWrappingAndWhitespace()
296 'firstname' => 'Paul <or Me>',
299 $xml = '<?xml version="1.0"?>'."\n".
300 '<response><firstname>'."\n".
301 '<![CDATA[Paul <or Me>]]></firstname></response>'."\n";
303 $this->assertEquals($expected, $this->encoder->decode($xml, 'xml'));
306 public function testDecodeWithNamespace()
308 $source = $this->getNamespacedXmlSource();
309 $array = $this->getNamespacedArray();
311 $this->assertEquals($array, $this->encoder->decode($source, 'xml'));
314 public function testDecodeScalarWithAttribute()
316 $source = '<?xml version="1.0"?>'."\n".
317 '<response><person gender="M">Peter</person></response>'."\n";
320 'person' => array('@gender' => 'M', '#' => 'Peter'),
323 $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
326 public function testDecodeScalarRootAttributes()
328 $source = '<?xml version="1.0"?>'."\n".
329 '<person gender="M">Peter</person>'."\n";
336 $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
339 public function testDecodeRootAttributes()
341 $source = '<?xml version="1.0"?>'."\n".
342 '<person gender="M"><firstname>Peter</firstname><lastname>Mac Calloway</lastname></person>'."\n";
345 'firstname' => 'Peter',
346 'lastname' => 'Mac Calloway',
350 $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
353 public function testDecodeArray()
355 $source = '<?xml version="1.0"?>'."\n".
358 '<person><firstname>Benjamin</firstname><lastname>Alexandre</lastname></person>'.
359 '<person><firstname>Damien</firstname><lastname>Clay</lastname></person>'.
364 'people' => array('person' => array(
365 array('firstname' => 'Benjamin', 'lastname' => 'Alexandre'),
366 array('firstname' => 'Damien', 'lastname' => 'Clay'),
370 $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
373 public function testDecodeXMLWithProcessInstruction()
376 <?xml version="1.0"?>
377 <?xml-stylesheet type="text/xsl" href="/xsl/xmlverbatimwrapper.xsl"?>
378 <?display table-view?>
379 <?sort alpha-ascending?>
382 <?textinfo whitespace is allowed ?>
388 <item key="A B">bar</item>
390 <title>title1</title>
392 <?item ignore-title ?>
394 <title>title2</title>
404 <?instruction <value> ?>
406 $obj = $this->getObject();
408 $this->assertEquals(get_object_vars($obj), $this->encoder->decode($source, 'xml'));
411 public function testDecodeIgnoreWhiteSpace()
414 <?xml version="1.0"?>
417 <firstname>Benjamin</firstname>
418 <lastname>Alexandre</lastname>
421 <firstname>Damien</firstname>
422 <lastname>Clay</lastname>
426 $expected = array('person' => array(
427 array('firstname' => 'Benjamin', 'lastname' => 'Alexandre'),
428 array('firstname' => 'Damien', 'lastname' => 'Clay'),
431 $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
434 public function testDecodeWithoutItemHash()
436 $obj = new ScalarDummy();
437 $obj->xmlFoo = array(
440 'item' => array('@key' => 'key', 'key-val' => 'val'),
447 'Bar' => array(1, 2, 3),
453 'key' => array('@key' => 'key', 'key-val' => 'val'),
460 'Bar' => array(1, 2, 3),
463 $xml = $this->encoder->encode($obj, 'xml');
464 $this->assertEquals($expected, $this->encoder->decode($xml, 'xml'));
468 * @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
470 public function testDecodeInvalidXml()
472 $this->encoder->decode('<?xml version="1.0"?><invalid><xml>', 'xml');
476 * @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
478 public function testPreventsComplexExternalEntities()
480 $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');
483 public function testDecodeEmptyXml()
485 if (method_exists($this, 'expectException')) {
486 $this->expectException('Symfony\Component\Serializer\Exception\UnexpectedValueException');
487 $this->expectExceptionMessage('Invalid XML data, it can not be empty.');
489 $this->setExpectedException('Symfony\Component\Serializer\Exception\UnexpectedValueException', 'Invalid XML data, it can not be empty.');
491 $this->encoder->decode(' ', 'xml');
494 protected function getXmlSource()
496 return '<?xml version="1.0"?>'."\n".
499 '<bar>a</bar><bar>b</bar>'.
500 '<baz><key>val</key><key2>val</key2><item key="A B">bar</item>'.
501 '<item><title>title1</title></item><item><title>title2</title></item>'.
502 '<Barry><FooBar id="1"><Baz>Ed</Baz></FooBar></Barry></baz>'.
507 protected function getNamespacedXmlSource()
509 return '<?xml version="1.0"?>'."\n".
510 '<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">'.
512 '<app:foo>foo</app:foo>'.
513 '<yt:bar>a</yt:bar><yt:bar>b</yt:bar>'.
514 '<media:baz><media:key>val</media:key><media:key2>val</media:key2><item key="A B">bar</item>'.
515 '<item><title>title1</title></item><item><title>title2</title></item>'.
516 '<Barry size="large"><FooBar gd:id="1"><Baz>Ed</Baz></FooBar></Barry></media:baz>'.
520 protected function getNamespacedArray()
523 '@xmlns' => 'http://www.w3.org/2005/Atom',
524 '@xmlns:app' => 'http://www.w3.org/2007/app',
525 '@xmlns:media' => 'http://search.yahoo.com/mrss/',
526 '@xmlns:gd' => 'http://schemas.google.com/g/2005',
527 '@xmlns:yt' => 'http://gdata.youtube.com/schemas/2007',
530 'yt:bar' => array('a', 'b'),
531 'media:baz' => array(
532 'media:key' => 'val',
533 'media:key2' => 'val',
554 protected function getObject()
558 $obj->bar = array('a', 'b');
559 $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)));
565 public function testEncodeXmlWithBoolValue()
567 $expectedXml = <<<'XML'
568 <?xml version="1.0"?>
569 <response><foo>1</foo><bar>0</bar></response>
573 $actualXml = $this->encoder->encode(array('foo' => true, 'bar' => false), 'xml');
575 $this->assertEquals($expectedXml, $actualXml);
578 public function testEncodeXmlWithDateTimeObjectValue()
580 $xmlEncoder = $this->createXmlEncoderWithDateTimeNormalizer();
582 $actualXml = $xmlEncoder->encode(array('dateTime' => new \DateTime($this->exampleDateTimeString)), 'xml');
584 $this->assertEquals($this->createXmlWithDateTime(), $actualXml);
587 public function testEncodeXmlWithDateTimeObjectField()
589 $xmlEncoder = $this->createXmlEncoderWithDateTimeNormalizer();
591 $actualXml = $xmlEncoder->encode(array('foo' => array('@dateTime' => new \DateTime($this->exampleDateTimeString))), 'xml');
593 $this->assertEquals($this->createXmlWithDateTimeField(), $actualXml);
599 private function createXmlEncoderWithDateTimeNormalizer()
601 $encoder = new XmlEncoder();
602 $serializer = new Serializer(array($this->createMockDateTimeNormalizer()), array('xml' => new XmlEncoder()));
603 $encoder->setSerializer($serializer);
609 * @return \PHPUnit_Framework_MockObject_MockObject|NormalizerInterface
611 private function createMockDateTimeNormalizer()
613 $mock = $this->getMockBuilder('\Symfony\Component\Serializer\Normalizer\CustomNormalizer')->getMock();
616 ->expects($this->once())
617 ->method('normalize')
618 ->with(new \DateTime($this->exampleDateTimeString), 'xml', array())
619 ->willReturn($this->exampleDateTimeString);
622 ->expects($this->once())
623 ->method('supportsNormalization')
624 ->with(new \DateTime($this->exampleDateTimeString), 'xml')
633 private function createXmlWithDateTime()
635 return sprintf('<?xml version="1.0"?>
636 <response><dateTime>%s</dateTime></response>
637 ', $this->exampleDateTimeString);
643 private function createXmlWithDateTimeField()
645 return sprintf('<?xml version="1.0"?>
646 <response><foo dateTime="%s"/></response>
647 ', $this->exampleDateTimeString);