Version 1
[yaffs-website] / vendor / symfony / serializer / Tests / Encoder / XmlEncoderTest.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Symfony\Component\Serializer\Tests\Encoder;
13
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;
22
23 class XmlEncoderTest extends TestCase
24 {
25     /**
26      * @var XmlEncoder
27      */
28     private $encoder;
29
30     private $exampleDateTimeString = '2017-02-19T15:16:08+0300';
31
32     protected function setUp()
33     {
34         $this->encoder = new XmlEncoder();
35         $serializer = new Serializer(array(new CustomNormalizer()), array('xml' => new XmlEncoder()));
36         $this->encoder->setSerializer($serializer);
37     }
38
39     public function testEncodeScalar()
40     {
41         $obj = new ScalarDummy();
42         $obj->xmlFoo = 'foo';
43
44         $expected = '<?xml version="1.0"?>'."\n".
45             '<response>foo</response>'."\n";
46
47         $this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
48     }
49
50     public function testSetRootNodeName()
51     {
52         $obj = new ScalarDummy();
53         $obj->xmlFoo = 'foo';
54
55         $this->encoder->setRootNodeName('test');
56         $expected = '<?xml version="1.0"?>'."\n".
57             '<test>foo</test>'."\n";
58
59         $this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
60     }
61
62     /**
63      * @expectedException        \Symfony\Component\Serializer\Exception\UnexpectedValueException
64      * @expectedExceptionMessage Document types are not allowed.
65      */
66     public function testDocTypeIsNotAllowed()
67     {
68         $this->encoder->decode('<?xml version="1.0"?><!DOCTYPE foo><foo></foo>', 'foo');
69     }
70
71     public function testAttributes()
72     {
73         $obj = new ScalarDummy();
74         $obj->xmlFoo = array(
75             'foo-bar' => array(
76                 '@id' => 1,
77                 '@name' => 'Bar',
78             ),
79             'Foo' => array(
80                 'Bar' => 'Test',
81                 '@Type' => 'test',
82             ),
83             'föo_bär' => 'a',
84             'Bar' => array(1, 2, 3),
85             'a' => 'b',
86         );
87         $expected = '<?xml version="1.0"?>'."\n".
88             '<response>'.
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>'.
92             '<Bar>1</Bar>'.
93             '<Bar>2</Bar>'.
94             '<Bar>3</Bar>'.
95             '<a>b</a>'.
96             '</response>'."\n";
97         $this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
98     }
99
100     public function testElementNameValid()
101     {
102         $obj = new ScalarDummy();
103         $obj->xmlFoo = array(
104             'foo-bar' => 'a',
105             'foo_bar' => 'a',
106             'föo_bär' => 'a',
107         );
108
109         $expected = '<?xml version="1.0"?>'."\n".
110             '<response>'.
111             '<foo-bar>a</foo-bar>'.
112             '<foo_bar>a</foo_bar>'.
113             '<föo_bär>a</föo_bär>'.
114             '</response>'."\n";
115
116         $this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
117     }
118
119     public function testEncodeSimpleXML()
120     {
121         $xml = simplexml_load_string('<firstname>Peter</firstname>');
122         $array = array('person' => $xml);
123
124         $expected = '<?xml version="1.0"?>'."\n".
125             '<response><person><firstname>Peter</firstname></person></response>'."\n";
126
127         $this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
128     }
129
130     public function testEncodeXmlAttributes()
131     {
132         $xml = simplexml_load_string('<firstname>Peter</firstname>');
133         $array = array('person' => $xml);
134
135         $expected = '<?xml version="1.1" encoding="utf-8" standalone="yes"?>'."\n".
136             '<response><person><firstname>Peter</firstname></person></response>'."\n";
137
138         $context = array(
139             'xml_version' => '1.1',
140             'xml_encoding' => 'utf-8',
141             'xml_standalone' => true,
142         );
143
144         $this->assertSame($expected, $this->encoder->encode($array, 'xml', $context));
145     }
146
147     public function testContext()
148     {
149         $array = array('person' => array('name' => 'George Abitbol'));
150         $expected = <<<'XML'
151 <?xml version="1.0"?>
152 <response>
153   <person>
154     <name>George Abitbol</name>
155   </person>
156 </response>
157
158 XML;
159
160         $context = array(
161             'xml_format_output' => true,
162         );
163
164         $this->assertSame($expected, $this->encoder->encode($array, 'xml', $context));
165     }
166
167     public function testEncodeScalarRootAttributes()
168     {
169         $array = array(
170           '#' => 'Paul',
171           '@gender' => 'm',
172         );
173
174         $expected = '<?xml version="1.0"?>'."\n".
175             '<response gender="m">Paul</response>'."\n";
176
177         $this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
178     }
179
180     public function testEncodeRootAttributes()
181     {
182         $array = array(
183           'firstname' => 'Paul',
184           '@gender' => 'm',
185         );
186
187         $expected = '<?xml version="1.0"?>'."\n".
188             '<response gender="m"><firstname>Paul</firstname></response>'."\n";
189
190         $this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
191     }
192
193     public function testEncodeCdataWrapping()
194     {
195         $array = array(
196           'firstname' => 'Paul <or Me>',
197         );
198
199         $expected = '<?xml version="1.0"?>'."\n".
200             '<response><firstname><![CDATA[Paul <or Me>]]></firstname></response>'."\n";
201
202         $this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
203     }
204
205     public function testEncodeScalarWithAttribute()
206     {
207         $array = array(
208             'person' => array('@gender' => 'M', '#' => 'Peter'),
209         );
210
211         $expected = '<?xml version="1.0"?>'."\n".
212             '<response><person gender="M">Peter</person></response>'."\n";
213
214         $this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
215     }
216
217     public function testDecodeScalar()
218     {
219         $source = '<?xml version="1.0"?>'."\n".
220             '<response>foo</response>'."\n";
221
222         $this->assertEquals('foo', $this->encoder->decode($source, 'xml'));
223     }
224
225     public function testEncode()
226     {
227         $source = $this->getXmlSource();
228         $obj = $this->getObject();
229
230         $this->assertEquals($source, $this->encoder->encode($obj, 'xml'));
231     }
232
233     public function testEncodeWithNamespace()
234     {
235         $source = $this->getNamespacedXmlSource();
236         $array = $this->getNamespacedArray();
237
238         $this->assertEquals($source, $this->encoder->encode($array, 'xml'));
239     }
240
241     public function testEncodeSerializerXmlRootNodeNameOption()
242     {
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);
247
248         $array = array(
249             'person' => array('@gender' => 'M', '#' => 'Peter'),
250         );
251
252         $expected = '<?xml version="1.0"?>'."\n".
253             '<test><person gender="M">Peter</person></test>'."\n";
254
255         $this->assertEquals($expected, $serializer->serialize($array, 'xml', $options));
256     }
257
258     public function testEncodeTraversableWhenNormalizable()
259     {
260         $this->encoder = new XmlEncoder();
261         $serializer = new Serializer(array(new CustomNormalizer()), array('xml' => new XmlEncoder()));
262         $this->encoder->setSerializer($serializer);
263
264         $expected = <<<'XML'
265 <?xml version="1.0"?>
266 <response><foo>normalizedFoo</foo><bar>normalizedBar</bar></response>
267
268 XML;
269
270         $this->assertEquals($expected, $serializer->serialize(new NormalizableTraversableDummy(), 'xml'));
271     }
272
273     public function testDecode()
274     {
275         $source = $this->getXmlSource();
276         $obj = $this->getObject();
277
278         $this->assertEquals(get_object_vars($obj), $this->encoder->decode($source, 'xml'));
279     }
280
281     public function testDecodeCdataWrapping()
282     {
283         $expected = array(
284             'firstname' => 'Paul <or Me>',
285         );
286
287         $xml = '<?xml version="1.0"?>'."\n".
288             '<response><firstname><![CDATA[Paul <or Me>]]></firstname></response>'."\n";
289
290         $this->assertEquals($expected, $this->encoder->decode($xml, 'xml'));
291     }
292
293     public function testDecodeCdataWrappingAndWhitespace()
294     {
295         $expected = array(
296             'firstname' => 'Paul <or Me>',
297         );
298
299         $xml = '<?xml version="1.0"?>'."\n".
300             '<response><firstname>'."\n".
301                 '<![CDATA[Paul <or Me>]]></firstname></response>'."\n";
302
303         $this->assertEquals($expected, $this->encoder->decode($xml, 'xml'));
304     }
305
306     public function testDecodeWithNamespace()
307     {
308         $source = $this->getNamespacedXmlSource();
309         $array = $this->getNamespacedArray();
310
311         $this->assertEquals($array, $this->encoder->decode($source, 'xml'));
312     }
313
314     public function testDecodeScalarWithAttribute()
315     {
316         $source = '<?xml version="1.0"?>'."\n".
317             '<response><person gender="M">Peter</person></response>'."\n";
318
319         $expected = array(
320             'person' => array('@gender' => 'M', '#' => 'Peter'),
321         );
322
323         $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
324     }
325
326     public function testDecodeScalarRootAttributes()
327     {
328         $source = '<?xml version="1.0"?>'."\n".
329             '<person gender="M">Peter</person>'."\n";
330
331         $expected = array(
332             '#' => 'Peter',
333             '@gender' => 'M',
334         );
335
336         $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
337     }
338
339     public function testDecodeRootAttributes()
340     {
341         $source = '<?xml version="1.0"?>'."\n".
342             '<person gender="M"><firstname>Peter</firstname><lastname>Mac Calloway</lastname></person>'."\n";
343
344         $expected = array(
345             'firstname' => 'Peter',
346             'lastname' => 'Mac Calloway',
347             '@gender' => 'M',
348         );
349
350         $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
351     }
352
353     public function testDecodeArray()
354     {
355         $source = '<?xml version="1.0"?>'."\n".
356             '<response>'.
357             '<people>'.
358             '<person><firstname>Benjamin</firstname><lastname>Alexandre</lastname></person>'.
359             '<person><firstname>Damien</firstname><lastname>Clay</lastname></person>'.
360             '</people>'.
361             '</response>'."\n";
362
363         $expected = array(
364             'people' => array('person' => array(
365                 array('firstname' => 'Benjamin', 'lastname' => 'Alexandre'),
366                 array('firstname' => 'Damien', 'lastname' => 'Clay'),
367             )),
368         );
369
370         $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
371     }
372
373     public function testDecodeXMLWithProcessInstruction()
374     {
375         $source = <<<'XML'
376 <?xml version="1.0"?>
377 <?xml-stylesheet type="text/xsl" href="/xsl/xmlverbatimwrapper.xsl"?>
378     <?display table-view?>
379     <?sort alpha-ascending?>
380     <response>
381         <foo>foo</foo>
382         <?textinfo whitespace is allowed ?>
383         <bar>a</bar>
384         <bar>b</bar>
385         <baz>
386             <key>val</key>
387             <key2>val</key2>
388             <item key="A B">bar</item>
389             <item>
390                 <title>title1</title>
391             </item>
392             <?item ignore-title ?>
393             <item>
394                 <title>title2</title>
395             </item>
396             <Barry>
397                 <FooBar id="1">
398                     <Baz>Ed</Baz>
399                 </FooBar>
400             </Barry>
401         </baz>
402         <qux>1</qux>
403     </response>
404     <?instruction <value> ?>
405 XML;
406         $obj = $this->getObject();
407
408         $this->assertEquals(get_object_vars($obj), $this->encoder->decode($source, 'xml'));
409     }
410
411     public function testDecodeIgnoreWhiteSpace()
412     {
413         $source = <<<'XML'
414 <?xml version="1.0"?>
415 <people>
416     <person>
417         <firstname>Benjamin</firstname>
418         <lastname>Alexandre</lastname>
419     </person>
420     <person>
421         <firstname>Damien</firstname>
422         <lastname>Clay</lastname>
423     </person>
424 </people>
425 XML;
426         $expected = array('person' => array(
427             array('firstname' => 'Benjamin', 'lastname' => 'Alexandre'),
428             array('firstname' => 'Damien', 'lastname' => 'Clay'),
429         ));
430
431         $this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
432     }
433
434     public function testDecodeWithoutItemHash()
435     {
436         $obj = new ScalarDummy();
437         $obj->xmlFoo = array(
438             'foo-bar' => array(
439                 '@key' => 'value',
440                 'item' => array('@key' => 'key', 'key-val' => 'val'),
441             ),
442             'Foo' => array(
443                 'Bar' => 'Test',
444                 '@Type' => 'test',
445             ),
446             'föo_bär' => 'a',
447             'Bar' => array(1, 2, 3),
448             'a' => 'b',
449         );
450         $expected = array(
451             'foo-bar' => array(
452                 '@key' => 'value',
453                 'key' => array('@key' => 'key', 'key-val' => 'val'),
454             ),
455             'Foo' => array(
456                 'Bar' => 'Test',
457                 '@Type' => 'test',
458             ),
459             'föo_bär' => 'a',
460             'Bar' => array(1, 2, 3),
461             'a' => 'b',
462         );
463         $xml = $this->encoder->encode($obj, 'xml');
464         $this->assertEquals($expected, $this->encoder->decode($xml, 'xml'));
465     }
466
467     /**
468      * @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
469      */
470     public function testDecodeInvalidXml()
471     {
472         $this->encoder->decode('<?xml version="1.0"?><invalid><xml>', 'xml');
473     }
474
475     /**
476      * @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
477      */
478     public function testPreventsComplexExternalEntities()
479     {
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');
481     }
482
483     public function testDecodeEmptyXml()
484     {
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.');
488         } else {
489             $this->setExpectedException('Symfony\Component\Serializer\Exception\UnexpectedValueException', 'Invalid XML data, it can not be empty.');
490         }
491         $this->encoder->decode(' ', 'xml');
492     }
493
494     protected function getXmlSource()
495     {
496         return '<?xml version="1.0"?>'."\n".
497             '<response>'.
498             '<foo>foo</foo>'.
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>'.
503             '<qux>1</qux>'.
504             '</response>'."\n";
505     }
506
507     protected function getNamespacedXmlSource()
508     {
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">'.
511             '<qux>1</qux>'.
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>'.
517             '</response>'."\n";
518     }
519
520     protected function getNamespacedArray()
521     {
522         return array(
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',
528             'qux' => '1',
529             'app:foo' => 'foo',
530             'yt:bar' => array('a', 'b'),
531             'media:baz' => array(
532                 'media:key' => 'val',
533                 'media:key2' => 'val',
534                 'A B' => 'bar',
535                 'item' => array(
536                     array(
537                         'title' => 'title1',
538                     ),
539                     array(
540                         'title' => 'title2',
541                     ),
542                 ),
543                 'Barry' => array(
544                     '@size' => 'large',
545                     'FooBar' => array(
546                         'Baz' => 'Ed',
547                         '@gd:id' => 1,
548                     ),
549                 ),
550             ),
551         );
552     }
553
554     protected function getObject()
555     {
556         $obj = new Dummy();
557         $obj->foo = 'foo';
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)));
560         $obj->qux = '1';
561
562         return $obj;
563     }
564
565     public function testEncodeXmlWithBoolValue()
566     {
567         $expectedXml = <<<'XML'
568 <?xml version="1.0"?>
569 <response><foo>1</foo><bar>0</bar></response>
570
571 XML;
572
573         $actualXml = $this->encoder->encode(array('foo' => true, 'bar' => false), 'xml');
574
575         $this->assertEquals($expectedXml, $actualXml);
576     }
577
578     public function testEncodeXmlWithDateTimeObjectValue()
579     {
580         $xmlEncoder = $this->createXmlEncoderWithDateTimeNormalizer();
581
582         $actualXml = $xmlEncoder->encode(array('dateTime' => new \DateTime($this->exampleDateTimeString)), 'xml');
583
584         $this->assertEquals($this->createXmlWithDateTime(), $actualXml);
585     }
586
587     public function testEncodeXmlWithDateTimeObjectField()
588     {
589         $xmlEncoder = $this->createXmlEncoderWithDateTimeNormalizer();
590
591         $actualXml = $xmlEncoder->encode(array('foo' => array('@dateTime' => new \DateTime($this->exampleDateTimeString))), 'xml');
592
593         $this->assertEquals($this->createXmlWithDateTimeField(), $actualXml);
594     }
595
596     /**
597      * @return XmlEncoder
598      */
599     private function createXmlEncoderWithDateTimeNormalizer()
600     {
601         $encoder = new XmlEncoder();
602         $serializer = new Serializer(array($this->createMockDateTimeNormalizer()), array('xml' => new XmlEncoder()));
603         $encoder->setSerializer($serializer);
604
605         return $encoder;
606     }
607
608     /**
609      * @return \PHPUnit_Framework_MockObject_MockObject|NormalizerInterface
610      */
611     private function createMockDateTimeNormalizer()
612     {
613         $mock = $this->getMockBuilder('\Symfony\Component\Serializer\Normalizer\CustomNormalizer')->getMock();
614
615         $mock
616             ->expects($this->once())
617             ->method('normalize')
618             ->with(new \DateTime($this->exampleDateTimeString), 'xml', array())
619             ->willReturn($this->exampleDateTimeString);
620
621         $mock
622             ->expects($this->once())
623             ->method('supportsNormalization')
624             ->with(new \DateTime($this->exampleDateTimeString), 'xml')
625             ->willReturn(true);
626
627         return $mock;
628     }
629
630     /**
631      * @return string
632      */
633     private function createXmlWithDateTime()
634     {
635         return sprintf('<?xml version="1.0"?>
636 <response><dateTime>%s</dateTime></response>
637 ', $this->exampleDateTimeString);
638     }
639
640     /**
641      * @return string
642      */
643     private function createXmlWithDateTimeField()
644     {
645         return sprintf('<?xml version="1.0"?>
646 <response><foo dateTime="%s"/></response>
647 ', $this->exampleDateTimeString);
648     }
649 }