1<?php
2
3declare(strict_types=1);
4
5namespace JMS\Serializer\Tests\Serializer;
6
7use JMS\Serializer\Context;
8use JMS\Serializer\DeserializationContext;
9use JMS\Serializer\Exception\InvalidArgumentException;
10use JMS\Serializer\GraphNavigatorInterface;
11use JMS\Serializer\Handler\DateHandler;
12use JMS\Serializer\Handler\HandlerRegistryInterface;
13use JMS\Serializer\Metadata\StaticPropertyMetadata;
14use JMS\Serializer\SerializationContext;
15use JMS\Serializer\SerializerBuilder;
16use JMS\Serializer\Tests\Fixtures\AccessorSetter;
17use JMS\Serializer\Tests\Fixtures\Discriminator\ObjectWithXmlAttributeDiscriminatorChild;
18use JMS\Serializer\Tests\Fixtures\Discriminator\ObjectWithXmlAttributeDiscriminatorParent;
19use JMS\Serializer\Tests\Fixtures\Discriminator\ObjectWithXmlNamespaceAttributeDiscriminatorChild;
20use JMS\Serializer\Tests\Fixtures\Discriminator\ObjectWithXmlNamespaceAttributeDiscriminatorParent;
21use JMS\Serializer\Tests\Fixtures\Discriminator\ObjectWithXmlNamespaceDiscriminatorChild;
22use JMS\Serializer\Tests\Fixtures\Discriminator\ObjectWithXmlNamespaceDiscriminatorParent;
23use JMS\Serializer\Tests\Fixtures\Discriminator\ObjectWithXmlNotCDataDiscriminatorChild;
24use JMS\Serializer\Tests\Fixtures\Discriminator\ObjectWithXmlNotCDataDiscriminatorParent;
25use JMS\Serializer\Tests\Fixtures\Input;
26use JMS\Serializer\Tests\Fixtures\InvalidUsageOfXmlValue;
27use JMS\Serializer\Tests\Fixtures\ObjectWithNamespacesAndList;
28use JMS\Serializer\Tests\Fixtures\ObjectWithNamespacesAndNestedList;
29use JMS\Serializer\Tests\Fixtures\ObjectWithVirtualXmlProperties;
30use JMS\Serializer\Tests\Fixtures\ObjectWithXmlKeyValuePairs;
31use JMS\Serializer\Tests\Fixtures\ObjectWithXmlKeyValuePairsWithObjectType;
32use JMS\Serializer\Tests\Fixtures\ObjectWithXmlKeyValuePairsWithType;
33use JMS\Serializer\Tests\Fixtures\ObjectWithXmlNamespaces;
34use JMS\Serializer\Tests\Fixtures\ObjectWithXmlNamespacesAndObjectProperty;
35use JMS\Serializer\Tests\Fixtures\ObjectWithXmlNamespacesAndObjectPropertyAuthor;
36use JMS\Serializer\Tests\Fixtures\ObjectWithXmlNamespacesAndObjectPropertyVirtual;
37use JMS\Serializer\Tests\Fixtures\ObjectWithXmlRootNamespace;
38use JMS\Serializer\Tests\Fixtures\Person;
39use JMS\Serializer\Tests\Fixtures\PersonCollection;
40use JMS\Serializer\Tests\Fixtures\PersonLocation;
41use JMS\Serializer\Tests\Fixtures\SimpleClassObject;
42use JMS\Serializer\Tests\Fixtures\SimpleSubClassObject;
43use JMS\Serializer\Visitor\Factory\XmlDeserializationVisitorFactory;
44use JMS\Serializer\Visitor\Factory\XmlSerializationVisitorFactory;
45use JMS\Serializer\XmlSerializationVisitor;
46
47class XmlSerializationTest extends BaseSerializationTest
48{
49    /**
50     * @expectedException \JMS\Serializer\Exception\RuntimeException
51     */
52    public function testInvalidUsageOfXmlValue()
53    {
54        $obj = new InvalidUsageOfXmlValue();
55        $this->serialize($obj);
56    }
57
58    /**
59     * @dataProvider getXMLBooleans
60     */
61    public function testXMLBooleans($xmlBoolean, $boolean)
62    {
63        if ($this->hasDeserializer()) {
64            self::assertSame($boolean, $this->deserialize('<result>' . $xmlBoolean . '</result>', 'boolean'));
65        }
66    }
67
68    public function getXMLBooleans()
69    {
70        return [['true', true], ['false', false], ['1', true], ['0', false]];
71    }
72
73    public function testAccessorSetterDeserialization()
74    {
75        /** @var AccessorSetter $object */
76        $object = $this->deserialize(
77            '<?xml version="1.0"?>
78            <AccessorSetter>
79                <element attribute="attribute">element</element>
80                <collection>
81                    <entry>collectionEntry</entry>
82                </collection>
83            </AccessorSetter>',
84            'JMS\Serializer\Tests\Fixtures\AccessorSetter'
85        );
86
87        self::assertInstanceOf('stdClass', $object->getElement());
88        self::assertInstanceOf('JMS\Serializer\Tests\Fixtures\AccessorSetterElement', $object->getElement()->element);
89        self::assertEquals('attribute-different', $object->getElement()->element->getAttribute());
90        self::assertEquals('element-different', $object->getElement()->element->getElement());
91        self::assertEquals(['collectionEntry' => 'collectionEntry'], $object->getCollection());
92    }
93
94    public function testPropertyIsObjectWithAttributeAndValue()
95    {
96        $personCollection = new PersonLocation();
97        $person = new Person();
98        $person->name = 'Matthias Noback';
99        $person->age = 28;
100        $personCollection->person = $person;
101        $personCollection->location = 'The Netherlands';
102
103        self::assertEquals($this->getContent('person_location'), $this->serialize($personCollection));
104    }
105
106    public function testPropertyIsCollectionOfObjectsWithAttributeAndValue()
107    {
108        $personCollection = new PersonCollection();
109        $person = new Person();
110        $person->name = 'Matthias Noback';
111        $person->age = 28;
112        $personCollection->persons->add($person);
113        $personCollection->location = 'The Netherlands';
114
115        self::assertEquals($this->getContent('person_collection'), $this->serialize($personCollection));
116    }
117
118    /**
119     * @expectedException JMS\Serializer\Exception\InvalidArgumentException
120     * @expectedExceptionMessage The document type "<!DOCTYPE author [<!ENTITY foo SYSTEM "php://filter/read=convert.base64-encode/resource=XmlSerializationTest.php">]>" is not allowed. If it is safe, you may add it to the whitelist configuration.
121     */
122    public function testExternalEntitiesAreDisabledByDefault()
123    {
124        $this->deserialize('<?xml version="1.0"?>
125            <!DOCTYPE author [
126                <!ENTITY foo SYSTEM "php://filter/read=convert.base64-encode/resource=' . basename(__FILE__) . '">
127            ]>
128            <result>
129                &foo;
130            </result>', 'stdClass');
131    }
132
133    /**
134     * @expectedException JMS\Serializer\Exception\InvalidArgumentException
135     * @expectedExceptionMessage The document type "<!DOCTYPE foo>" is not allowed. If it is safe, you may add it to the whitelist configuration.
136     */
137    public function testDocumentTypesAreNotAllowed()
138    {
139        $this->deserialize('<?xml version="1.0"?><!DOCTYPE foo><foo></foo>', 'stdClass');
140    }
141
142    /**
143     * @doesNotPerformAssertions
144     */
145    public function testWhitelistedDocumentTypesAreAllowed()
146    {
147        $xmlVisitor = new XmlDeserializationVisitorFactory();
148
149        $xmlVisitor->setDoctypeWhitelist([
150            '<!DOCTYPE authorized SYSTEM "http://authorized_url.dtd">',
151            '<!DOCTYPE author [<!ENTITY foo SYSTEM "php://filter/read=convert.base64-encode/resource=' . basename(__FILE__) . '">]>',
152        ]);
153
154        $builder = SerializerBuilder::create();
155        $builder->setDeserializationVisitor('xml', $xmlVisitor);
156        $serializer = $builder->build();
157
158        $serializer->deserialize('<?xml version="1.0"?>
159            <!DOCTYPE authorized SYSTEM "http://authorized_url.dtd">
160            <foo></foo>', 'stdClass', 'xml');
161
162        $serializer->deserialize('<?xml version="1.0"?>
163            <!DOCTYPE author [
164                <!ENTITY foo SYSTEM "php://filter/read=convert.base64-encode/resource=' . basename(__FILE__) . '">
165            ]>
166            <foo></foo>', 'stdClass', 'xml');
167    }
168
169    public function testVirtualAttributes()
170    {
171        self::assertEquals(
172            $this->getContent('virtual_attributes'),
173            $this->serialize(new ObjectWithVirtualXmlProperties(), SerializationContext::create()->setGroups(['attributes']))
174        );
175    }
176
177    public function testVirtualValues()
178    {
179        self::assertEquals(
180            $this->getContent('virtual_values'),
181            $this->serialize(new ObjectWithVirtualXmlProperties(), SerializationContext::create()->setGroups(['values']))
182        );
183    }
184
185    public function testVirtualXmlList()
186    {
187        self::assertEquals(
188            $this->getContent('virtual_properties_list'),
189            $this->serialize(new ObjectWithVirtualXmlProperties(), SerializationContext::create()->setGroups(['list']))
190        );
191    }
192
193    public function testVirtualXmlMap()
194    {
195        self::assertEquals(
196            $this->getContent('virtual_properties_map'),
197            $this->serialize(new ObjectWithVirtualXmlProperties(), SerializationContext::create()->setGroups(['map']))
198        );
199    }
200
201    public function testUnserializeMissingArray()
202    {
203        $xml = '<result></result>';
204        $object = $this->serializer->deserialize($xml, 'JMS\Serializer\Tests\Fixtures\ObjectWithAbsentXmlListNode', 'xml');
205        self::assertEquals($object->absentAndNs, []);
206
207        $xml = '<result xmlns:x="http://www.example.com">
208                    <absent_and_ns>
209                        <x:entry>foo</x:entry>
210                    </absent_and_ns>
211                  </result>';
212        $object = $this->serializer->deserialize($xml, 'JMS\Serializer\Tests\Fixtures\ObjectWithAbsentXmlListNode', 'xml');
213        self::assertEquals($object->absentAndNs, ['foo']);
214    }
215
216    public function testObjectWithNamespacesAndList()
217    {
218        $object = new ObjectWithNamespacesAndList();
219        $object->name = 'name';
220        $object->nameAlternativeB = 'nameB';
221
222        $object->phones = ['111', '222'];
223        $object->addresses = ['A' => 'Street 1', 'B' => 'Street 2'];
224
225        $object->phonesAlternativeB = ['555', '666'];
226        $object->addressesAlternativeB = ['A' => 'Street 5', 'B' => 'Street 6'];
227
228        $object->phonesAlternativeC = ['777', '888'];
229        $object->addressesAlternativeC = ['A' => 'Street 7', 'B' => 'Street 8'];
230
231        $object->phonesAlternativeD = ['999', 'AAA'];
232        $object->addressesAlternativeD = ['A' => 'Street 9', 'B' => 'Street A'];
233
234        self::assertEquals(
235            $this->getContent('object_with_namespaces_and_list'),
236            $this->serialize($object, SerializationContext::create())
237        );
238        self::assertEquals(
239            $object,
240            $this->deserialize($this->getContent('object_with_namespaces_and_list'), get_class($object))
241        );
242    }
243
244    public function testObjectWithNamespaceAndNestedList()
245    {
246        $object = new ObjectWithNamespacesAndNestedList();
247        $personCollection = new PersonCollection();
248        $personA = new Person();
249        $personA->age = 11;
250        $personA->name = 'AAA';
251
252        $personB = new Person();
253        $personB->age = 22;
254        $personB->name = 'BBB';
255
256        $personCollection->persons->add($personA);
257        $personCollection->persons->add($personB);
258
259        $object->personCollection = $personCollection;
260
261        self::assertEquals(
262            $this->getContent('object_with_namespaces_and_nested_list'),
263            $this->serialize($object, SerializationContext::create())
264        );
265        self::assertEquals(
266            $object,
267            $this->deserialize($this->getContent('object_with_namespaces_and_nested_list'), get_class($object))
268        );
269    }
270
271    public function testArrayKeyValues()
272    {
273        self::assertEquals($this->getContent('array_key_values'), $this->serializer->serialize(new ObjectWithXmlKeyValuePairs(), 'xml'));
274    }
275
276    public function testDeserializeArrayKeyValues()
277    {
278        $xml = $this->getContent('array_key_values_with_type_1');
279        $result = $this->serializer->deserialize($xml, ObjectWithXmlKeyValuePairsWithType::class, 'xml');
280
281        self::assertInstanceOf(ObjectWithXmlKeyValuePairsWithType::class, $result);
282        self::assertEquals(ObjectWithXmlKeyValuePairsWithType::create1(), $result);
283
284        $xml2 = $this->getContent('array_key_values_with_type_2');
285        $result2 = $this->serializer->deserialize($xml2, ObjectWithXmlKeyValuePairsWithType::class, 'xml');
286
287        self::assertInstanceOf(ObjectWithXmlKeyValuePairsWithType::class, $result2);
288        self::assertEquals(ObjectWithXmlKeyValuePairsWithType::create2(), $result2);
289    }
290
291    public function testDeserializeTypedAndNestedArrayKeyValues()
292    {
293        $xml = $this->getContent('array_key_values_with_nested_type');
294        $result = $this->serializer->deserialize($xml, ObjectWithXmlKeyValuePairsWithObjectType::class, 'xml');
295
296        self::assertInstanceOf(ObjectWithXmlKeyValuePairsWithObjectType::class, $result);
297        self::assertEquals(ObjectWithXmlKeyValuePairsWithObjectType::create1(), $result);
298    }
299
300    /**
301     * @dataProvider getDateTime
302     * @group datetime
303     */
304    public function testDateTimeNoCData($key, $value, $type)
305    {
306        $builder = SerializerBuilder::create();
307        $builder->configureHandlers(static function (HandlerRegistryInterface $handlerRegistry) {
308            $handlerRegistry->registerSubscribingHandler(new DateHandler(\DateTime::ATOM, 'UTC', false));
309        });
310        $serializer = $builder->build();
311
312        self::assertEquals($this->getContent($key . '_no_cdata'), $serializer->serialize($value, $this->getFormat()));
313    }
314
315    /**
316     * @dataProvider getDateTimeImmutable
317     * @group datetime
318     */
319    public function testDateTimeImmutableNoCData($key, $value, $type)
320    {
321        $builder = SerializerBuilder::create();
322        $builder->configureHandlers(static function (HandlerRegistryInterface $handlerRegistry) {
323            $handlerRegistry->registerSubscribingHandler(new DateHandler(\DateTime::ATOM, 'UTC', false));
324        });
325        $serializer = $builder->build();
326
327        self::assertEquals($this->getContent($key . '_no_cdata'), $serializer->serialize($value, $this->getFormat()));
328    }
329
330    /**
331     * @expectedException JMS\Serializer\Exception\RuntimeException
332     * @expectedExceptionMessage Unsupported value type for XML attribute map. Expected array but got object
333     */
334    public function testXmlAttributeMapWithoutArray()
335    {
336        $attributes = new \ArrayObject(['type' => 'text']);
337
338        $this->serializer->serialize(new Input($attributes), $this->getFormat());
339    }
340
341    public function testObjectWithOnlyNamespacesAndList()
342    {
343        $object = new ObjectWithNamespacesAndList();
344
345        $object->phones = [];
346        $object->addresses = [];
347
348        $object->phonesAlternativeB = [];
349        $object->addressesAlternativeB = [];
350
351        $object->phonesAlternativeC = ['777', '888'];
352        $object->addressesAlternativeC = ['A' => 'Street 7', 'B' => 'Street 8'];
353
354        $object->phonesAlternativeD = [];
355        $object->addressesAlternativeD = [];
356
357        self::assertEquals(
358            $this->getContent('object_with_only_namespaces_and_list'),
359            $this->serialize($object, SerializationContext::create())
360        );
361
362        $deserialized = $this->deserialize($this->getContent('object_with_only_namespaces_and_list'), get_class($object));
363        self::assertEquals($object, $deserialized);
364    }
365
366    public function testDeserializingNull()
367    {
368        $this->markTestSkipped('Not supported in XML.');
369    }
370
371    public function testObjectWithXmlNamespaces()
372    {
373        $object = new ObjectWithXmlNamespaces('This is a nice title.', 'Foo Bar', new \DateTime('2011-07-30 00:00', new \DateTimeZone('UTC')), 'en');
374
375        $serialized = $this->serialize($object);
376        self::assertEquals($this->getContent('object_with_xml_namespaces'), $serialized);
377
378        $xml = simplexml_load_string($this->serialize($object));
379        $xml->registerXPathNamespace('ns1', 'http://purl.org/dc/elements/1.1/');
380        $xml->registerXPathNamespace('ns2', 'http://schemas.google.com/g/2005');
381        $xml->registerXPathNamespace('ns3', 'http://www.w3.org/2005/Atom');
382
383        self::assertEquals('2011-07-30T00:00:00+00:00', $this->xpathFirstToString($xml, './@created_at'));
384        self::assertEquals('e86ce85cdb1253e4fc6352f5cf297248bceec62b', $this->xpathFirstToString($xml, './@ns2:etag'));
385        self::assertEquals('en', $this->xpathFirstToString($xml, './@ns1:language'));
386        self::assertEquals('This is a nice title.', $this->xpathFirstToString($xml, './ns1:title'));
387        self::assertEquals('Foo Bar', $this->xpathFirstToString($xml, './ns3:author'));
388
389        $deserialized = $this->deserialize($this->getContent('object_with_xml_namespacesalias'), get_class($object));
390        self::assertEquals('2011-07-30T00:00:00+00:00', $this->getField($deserialized, 'createdAt')->format(\DateTime::ATOM));
391        self::assertAttributeEquals('This is a nice title.', 'title', $deserialized);
392        self::assertAttributeSame('e86ce85cdb1253e4fc6352f5cf297248bceec62b', 'etag', $deserialized);
393        self::assertAttributeSame('en', 'language', $deserialized);
394        self::assertAttributeEquals('Foo Bar', 'author', $deserialized);
395    }
396
397    public function testObjectWithXmlNamespacesAndBackReferencedNamespaces()
398    {
399        $author = new ObjectWithXmlNamespacesAndObjectPropertyAuthor('mr', 'smith');
400        $object = new ObjectWithXmlNamespacesAndObjectProperty('This is a nice title.', $author);
401
402        $serialized = $this->serialize($object);
403        self::assertEquals($this->getContent('object_with_xml_namespaces_and_object_property'), $serialized);
404    }
405
406    public function testObjectWithXmlNamespacesAndBackReferencedNamespacesWithListeners()
407    {
408        $author = new ObjectWithXmlNamespacesAndObjectPropertyAuthor('mr', 'smith');
409        $object = new ObjectWithXmlNamespacesAndObjectPropertyVirtual('This is a nice title.', new \stdClass());
410
411        $this->handlerRegistry->registerHandler(
412            GraphNavigatorInterface::DIRECTION_SERIALIZATION,
413            'ObjectWithXmlNamespacesAndObjectPropertyAuthorVirtual',
414            $this->getFormat(),
415            static function (XmlSerializationVisitor $visitor, $data, $type, Context $context) use ($author) {
416                $factory = $context->getMetadataFactory(get_class($author));
417                $classMetadata = $factory->getMetadataForClass(get_class($author));
418
419                $metadata = new StaticPropertyMetadata(get_class($author), 'foo', $author);
420                $metadata->xmlNamespace = $classMetadata->xmlRootNamespace;
421                $metadata->xmlNamespace = $classMetadata->xmlRootNamespace;
422
423                $visitor->visitProperty($metadata, $author);
424            }
425        );
426
427        $serialized = $this->serialize($object);
428        self::assertEquals($this->getContent('object_with_xml_namespaces_and_object_property_virtual'), $serialized);
429    }
430
431    public function testObjectWithXmlRootNamespace()
432    {
433        $object = new ObjectWithXmlRootNamespace('This is a nice title.', 'Foo Bar', new \DateTime('2011-07-30 00:00', new \DateTimeZone('UTC')), 'en');
434        self::assertEquals($this->getContent('object_with_xml_root_namespace'), $this->serialize($object));
435    }
436
437    public function testXmlNamespacesInheritance()
438    {
439        $object = new SimpleClassObject();
440        $object->foo = 'foo';
441        $object->bar = 'bar';
442        $object->moo = 'moo';
443
444        self::assertEquals($this->getContent('simple_class_object'), $this->serialize($object));
445
446        $childObject = new SimpleSubClassObject();
447        $childObject->foo = 'foo';
448        $childObject->bar = 'bar';
449        $childObject->moo = 'moo';
450        $childObject->baz = 'baz';
451        $childObject->qux = 'qux';
452
453        self::assertEquals($this->getContent('simple_subclass_object'), $this->serialize($childObject));
454    }
455
456    public function testWithoutFormatedOutputByXmlSerializationVisitor()
457    {
458        $xmlVisitor = new XmlSerializationVisitorFactory();
459        $xmlVisitor->setFormatOutput(false);
460
461        $builder = SerializerBuilder::create();
462        $builder->setSerializationVisitor('xml', $xmlVisitor);
463        $serializer = $builder->build();
464
465        $object = new SimpleClassObject();
466        $object->foo = 'foo';
467        $object->bar = 'bar';
468        $object->moo = 'moo';
469
470        $stringXml = $serializer->serialize($object, $this->getFormat());
471        self::assertXmlStringEqualsXmlString($this->getContent('simple_class_object_minified'), $stringXml);
472    }
473
474    public function testDiscriminatorAsXmlAttribute()
475    {
476        $xml = $this->serialize(new ObjectWithXmlAttributeDiscriminatorChild());
477        self::assertEquals($this->getContent('xml_discriminator_attribute'), $xml);
478        self::assertInstanceOf(
479            ObjectWithXmlAttributeDiscriminatorChild::class,
480            $this->deserialize(
481                $xml,
482                ObjectWithXmlAttributeDiscriminatorParent::class
483            )
484        );
485    }
486
487    public function testDiscriminatorAsNotCData()
488    {
489        $xml = $this->serialize(new ObjectWithXmlNotCDataDiscriminatorChild());
490        self::assertEquals($this->getContent('xml_discriminator_not_cdata'), $xml);
491        self::assertInstanceOf(
492            ObjectWithXmlNotCDataDiscriminatorChild::class,
493            $this->deserialize(
494                $xml,
495                ObjectWithXmlNotCDataDiscriminatorParent::class
496            )
497        );
498    }
499
500    public function testDiscriminatorWithNamespace()
501    {
502        $xml = $this->serialize(new ObjectWithXmlNamespaceDiscriminatorChild());
503        self::assertEquals($this->getContent('xml_discriminator_namespace'), $xml);
504
505        self::assertInstanceOf(
506            ObjectWithXmlNamespaceDiscriminatorChild::class,
507            $this->deserialize(
508                $xml,
509                ObjectWithXmlNamespaceDiscriminatorParent::class
510            )
511        );
512    }
513
514    public function testDiscriminatorAsXmlAttributeWithNamespace()
515    {
516        $xml = $this->serialize(new ObjectWithXmlNamespaceAttributeDiscriminatorChild());
517        self::assertEquals($this->getContent('xml_discriminator_namespace_attribute'), $xml);
518
519        self::assertInstanceOf(
520            ObjectWithXmlNamespaceAttributeDiscriminatorChild::class,
521            $this->deserialize(
522                $xml,
523                ObjectWithXmlNamespaceAttributeDiscriminatorParent::class
524            )
525        );
526    }
527
528    /**
529     * @expectedException \JMS\Serializer\Exception\XmlErrorException
530     */
531    public function testDeserializeEmptyString()
532    {
533        $this->deserialize('', 'stdClass');
534    }
535
536    public function testEvaluatesToNull()
537    {
538        $context = $this->getMockBuilder(DeserializationContext::class)->getMock();
539        $navigator = $this->getMockBuilder(GraphNavigatorInterface::class)->getMock();
540
541        $visitor = (new XmlDeserializationVisitorFactory())->getVisitor($navigator, $context);
542        $xsdNilAsTrueElement = simplexml_load_string('<empty xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>');
543        $xsdNilAsOneElement = simplexml_load_string('<empty xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="1"/>');
544
545        self::assertTrue($visitor->isNull($xsdNilAsTrueElement));
546        self::assertTrue($visitor->isNull($xsdNilAsOneElement));
547        self::assertTrue($visitor->isNull(null));
548    }
549
550    public function testDoubleEncoding()
551    {
552        $context = $this->getMockBuilder(DeserializationContext::class)->getMock();
553        $navigator = $this->getMockBuilder(GraphNavigatorInterface::class)->getMock();
554
555        $visitor = (new XmlSerializationVisitorFactory())->getVisitor($navigator, $context);
556
557        // Setting locale with comma fractional separator
558        $locale = setlocale(LC_ALL, 0);
559        if (!setlocale(LC_ALL, 'ru_RU.UTF-8')) {
560            $this->markTestIncomplete('Required locale not available');
561        }
562
563        self::assertEquals('0.0', $visitor->visitDouble(0, [])->data);
564        self::assertEquals('1.0', $visitor->visitDouble(1, [])->data);
565        self::assertEquals('1.1', $visitor->visitDouble(1.1, [])->data);
566        self::assertEquals('1.123456789', $visitor->visitDouble(1.123456789, [])->data);
567
568        // Switching locale back
569        setlocale(LC_ALL, $locale);
570    }
571
572    private function xpathFirstToString(\SimpleXMLElement $xml, $xpath)
573    {
574        $nodes = $xml->xpath($xpath);
575        return (string) reset($nodes);
576    }
577
578    /**
579     * @param string $key
580     */
581    protected function getContent($key)
582    {
583        if (!file_exists($file = __DIR__ . '/xml/' . $key . '.xml')) {
584            throw new InvalidArgumentException(sprintf('The key "%s" is not supported.', $key));
585        }
586
587        return file_get_contents($file);
588    }
589
590    protected function getFormat()
591    {
592        return 'xml';
593    }
594}
595