ParserTest.php (16876B)
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\Yaml\Tests; 13 14 use Symfony\Component\Yaml\Yaml; 15 use Symfony\Component\Yaml\Parser; 16 17 class ParserTest extends \PHPUnit_Framework_TestCase 18 { 19 protected $parser; 20 21 protected function setUp() 22 { 23 $this->parser = new Parser(); 24 } 25 26 protected function tearDown() 27 { 28 $this->parser = null; 29 } 30 31 /** 32 * @dataProvider getDataFormSpecifications 33 */ 34 public function testSpecifications($file, $expected, $yaml, $comment) 35 { 36 $this->assertEquals($expected, var_export($this->parser->parse($yaml), true), $comment); 37 } 38 39 public function getDataFormSpecifications() 40 { 41 $parser = new Parser(); 42 $path = __DIR__.'/Fixtures'; 43 44 $tests = array(); 45 $files = $parser->parse(file_get_contents($path.'/index.yml')); 46 foreach ($files as $file) { 47 $yamls = file_get_contents($path.'/'.$file.'.yml'); 48 49 // split YAMLs documents 50 foreach (preg_split('/^---( %YAML\:1\.0)?/m', $yamls) as $yaml) { 51 if (!$yaml) { 52 continue; 53 } 54 55 $test = $parser->parse($yaml); 56 if (isset($test['todo']) && $test['todo']) { 57 // TODO 58 } else { 59 eval('$expected = '.trim($test['php']).';'); 60 61 $tests[] = array($file, var_export($expected, true), $test['yaml'], $test['test']); 62 } 63 } 64 } 65 66 return $tests; 67 } 68 69 public function testTabsInYaml() 70 { 71 // test tabs in YAML 72 $yamls = array( 73 "foo:\n bar", 74 "foo:\n bar", 75 "foo:\n bar", 76 "foo:\n bar", 77 ); 78 79 foreach ($yamls as $yaml) { 80 try { 81 $content = $this->parser->parse($yaml); 82 83 $this->fail('YAML files must not contain tabs'); 84 } catch (\Exception $e) { 85 $this->assertInstanceOf('\Exception', $e, 'YAML files must not contain tabs'); 86 $this->assertEquals('A YAML file cannot contain tabs as indentation at line 2 (near "'.strpbrk($yaml, "\t").'").', $e->getMessage(), 'YAML files must not contain tabs'); 87 } 88 } 89 } 90 91 public function testEndOfTheDocumentMarker() 92 { 93 $yaml = <<<EOF 94 --- %YAML:1.0 95 foo 96 ... 97 EOF; 98 99 $this->assertEquals('foo', $this->parser->parse($yaml)); 100 } 101 102 public function getBlockChompingTests() 103 { 104 $tests = array(); 105 106 $yaml = <<<'EOF' 107 foo: |- 108 one 109 two 110 bar: |- 111 one 112 two 113 114 EOF; 115 $expected = array( 116 'foo' => "one\ntwo", 117 'bar' => "one\ntwo", 118 ); 119 $tests['Literal block chomping strip with single trailing newline'] = array($expected, $yaml); 120 121 $yaml = <<<'EOF' 122 foo: |- 123 one 124 two 125 126 bar: |- 127 one 128 two 129 130 131 EOF; 132 $expected = array( 133 'foo' => "one\ntwo", 134 'bar' => "one\ntwo", 135 ); 136 $tests['Literal block chomping strip with multiple trailing newlines'] = array($expected, $yaml); 137 138 $yaml = <<<'EOF' 139 {} 140 141 142 EOF; 143 $expected = array(); 144 $tests['Literal block chomping strip with multiple trailing newlines after a 1-liner'] = array($expected, $yaml); 145 146 $yaml = <<<'EOF' 147 foo: |- 148 one 149 two 150 bar: |- 151 one 152 two 153 EOF; 154 $expected = array( 155 'foo' => "one\ntwo", 156 'bar' => "one\ntwo", 157 ); 158 $tests['Literal block chomping strip without trailing newline'] = array($expected, $yaml); 159 160 $yaml = <<<'EOF' 161 foo: | 162 one 163 two 164 bar: | 165 one 166 two 167 168 EOF; 169 $expected = array( 170 'foo' => "one\ntwo\n", 171 'bar' => "one\ntwo\n", 172 ); 173 $tests['Literal block chomping clip with single trailing newline'] = array($expected, $yaml); 174 175 $yaml = <<<'EOF' 176 foo: | 177 one 178 two 179 180 bar: | 181 one 182 two 183 184 185 EOF; 186 $expected = array( 187 'foo' => "one\ntwo\n", 188 'bar' => "one\ntwo\n", 189 ); 190 $tests['Literal block chomping clip with multiple trailing newlines'] = array($expected, $yaml); 191 192 $yaml = <<<'EOF' 193 foo: | 194 one 195 two 196 bar: | 197 one 198 two 199 EOF; 200 $expected = array( 201 'foo' => "one\ntwo\n", 202 'bar' => "one\ntwo", 203 ); 204 $tests['Literal block chomping clip without trailing newline'] = array($expected, $yaml); 205 206 $yaml = <<<'EOF' 207 foo: |+ 208 one 209 two 210 bar: |+ 211 one 212 two 213 214 EOF; 215 $expected = array( 216 'foo' => "one\ntwo\n", 217 'bar' => "one\ntwo\n", 218 ); 219 $tests['Literal block chomping keep with single trailing newline'] = array($expected, $yaml); 220 221 $yaml = <<<'EOF' 222 foo: |+ 223 one 224 two 225 226 bar: |+ 227 one 228 two 229 230 231 EOF; 232 $expected = array( 233 'foo' => "one\ntwo\n\n", 234 'bar' => "one\ntwo\n\n", 235 ); 236 $tests['Literal block chomping keep with multiple trailing newlines'] = array($expected, $yaml); 237 238 $yaml = <<<'EOF' 239 foo: |+ 240 one 241 two 242 bar: |+ 243 one 244 two 245 EOF; 246 $expected = array( 247 'foo' => "one\ntwo\n", 248 'bar' => "one\ntwo", 249 ); 250 $tests['Literal block chomping keep without trailing newline'] = array($expected, $yaml); 251 252 $yaml = <<<'EOF' 253 foo: >- 254 one 255 two 256 bar: >- 257 one 258 two 259 260 EOF; 261 $expected = array( 262 'foo' => 'one two', 263 'bar' => 'one two', 264 ); 265 $tests['Folded block chomping strip with single trailing newline'] = array($expected, $yaml); 266 267 $yaml = <<<'EOF' 268 foo: >- 269 one 270 two 271 272 bar: >- 273 one 274 two 275 276 277 EOF; 278 $expected = array( 279 'foo' => 'one two', 280 'bar' => 'one two', 281 ); 282 $tests['Folded block chomping strip with multiple trailing newlines'] = array($expected, $yaml); 283 284 $yaml = <<<'EOF' 285 foo: >- 286 one 287 two 288 bar: >- 289 one 290 two 291 EOF; 292 $expected = array( 293 'foo' => 'one two', 294 'bar' => 'one two', 295 ); 296 $tests['Folded block chomping strip without trailing newline'] = array($expected, $yaml); 297 298 $yaml = <<<'EOF' 299 foo: > 300 one 301 two 302 bar: > 303 one 304 two 305 306 EOF; 307 $expected = array( 308 'foo' => "one two\n", 309 'bar' => "one two\n", 310 ); 311 $tests['Folded block chomping clip with single trailing newline'] = array($expected, $yaml); 312 313 $yaml = <<<'EOF' 314 foo: > 315 one 316 two 317 318 bar: > 319 one 320 two 321 322 323 EOF; 324 $expected = array( 325 'foo' => "one two\n", 326 'bar' => "one two\n", 327 ); 328 $tests['Folded block chomping clip with multiple trailing newlines'] = array($expected, $yaml); 329 330 $yaml = <<<'EOF' 331 foo: > 332 one 333 two 334 bar: > 335 one 336 two 337 EOF; 338 $expected = array( 339 'foo' => "one two\n", 340 'bar' => 'one two', 341 ); 342 $tests['Folded block chomping clip without trailing newline'] = array($expected, $yaml); 343 344 $yaml = <<<'EOF' 345 foo: >+ 346 one 347 two 348 bar: >+ 349 one 350 two 351 352 EOF; 353 $expected = array( 354 'foo' => "one two\n", 355 'bar' => "one two\n", 356 ); 357 $tests['Folded block chomping keep with single trailing newline'] = array($expected, $yaml); 358 359 $yaml = <<<'EOF' 360 foo: >+ 361 one 362 two 363 364 bar: >+ 365 one 366 two 367 368 369 EOF; 370 $expected = array( 371 'foo' => "one two\n\n", 372 'bar' => "one two\n\n", 373 ); 374 $tests['Folded block chomping keep with multiple trailing newlines'] = array($expected, $yaml); 375 376 $yaml = <<<'EOF' 377 foo: >+ 378 one 379 two 380 bar: >+ 381 one 382 two 383 EOF; 384 $expected = array( 385 'foo' => "one two\n", 386 'bar' => 'one two', 387 ); 388 $tests['Folded block chomping keep without trailing newline'] = array($expected, $yaml); 389 390 return $tests; 391 } 392 393 /** 394 * @dataProvider getBlockChompingTests 395 */ 396 public function testBlockChomping($expected, $yaml) 397 { 398 $this->assertSame($expected, $this->parser->parse($yaml)); 399 } 400 401 /** 402 * Regression test for issue #7989. 403 * 404 * @see https://github.com/symfony/symfony/issues/7989 405 */ 406 public function testBlockLiteralWithLeadingNewlines() 407 { 408 $yaml = <<<'EOF' 409 foo: |- 410 411 412 bar 413 414 EOF; 415 $expected = array( 416 'foo' => "\n\nbar", 417 ); 418 419 $this->assertSame($expected, $this->parser->parse($yaml)); 420 } 421 422 public function testObjectSupportEnabled() 423 { 424 $input = <<<EOF 425 foo: !!php/object:O:30:"Symfony\Component\Yaml\Tests\B":1:{s:1:"b";s:3:"foo";} 426 bar: 1 427 EOF; 428 $this->assertEquals(array('foo' => new B(), 'bar' => 1), $this->parser->parse($input, false, true), '->parse() is able to parse objects'); 429 } 430 431 public function testObjectSupportDisabledButNoExceptions() 432 { 433 $input = <<<EOF 434 foo: !!php/object:O:30:"Symfony\Tests\Component\Yaml\B":1:{s:1:"b";s:3:"foo";} 435 bar: 1 436 EOF; 437 438 $this->assertEquals(array('foo' => null, 'bar' => 1), $this->parser->parse($input), '->parse() does not parse objects'); 439 } 440 441 /** 442 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 443 */ 444 public function testObjectsSupportDisabledWithExceptions() 445 { 446 $this->parser->parse('foo: !!php/object:O:30:"Symfony\Tests\Component\Yaml\B":1:{s:1:"b";s:3:"foo";}', true, false); 447 } 448 449 /** 450 * @requires extension iconv 451 */ 452 public function testNonUtf8Exception() 453 { 454 $yamls = array( 455 iconv('UTF-8', 'ISO-8859-1', "foo: 'äöüß'"), 456 iconv('UTF-8', 'ISO-8859-15', "euro: '€'"), 457 iconv('UTF-8', 'CP1252', "cp1252: '©ÉÇáñ'"), 458 ); 459 460 foreach ($yamls as $yaml) { 461 try { 462 $this->parser->parse($yaml); 463 464 $this->fail('charsets other than UTF-8 are rejected.'); 465 } catch (\Exception $e) { 466 $this->assertInstanceOf('Symfony\Component\Yaml\Exception\ParseException', $e, 'charsets other than UTF-8 are rejected.'); 467 } 468 } 469 } 470 471 /** 472 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 473 */ 474 public function testUnindentedCollectionException() 475 { 476 $yaml = <<<EOF 477 478 collection: 479 -item1 480 -item2 481 -item3 482 483 EOF; 484 485 $this->parser->parse($yaml); 486 } 487 488 /** 489 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 490 */ 491 public function testShortcutKeyUnindentedCollectionException() 492 { 493 $yaml = <<<EOF 494 495 collection: 496 - key: foo 497 foo: bar 498 499 EOF; 500 501 $this->parser->parse($yaml); 502 } 503 504 /** 505 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 506 * @expectedExceptionMessage Multiple documents are not supported. 507 */ 508 public function testMultipleDocumentsNotSupportedException() 509 { 510 Yaml::parse(<<<EOL 511 # Ranking of 1998 home runs 512 --- 513 - Mark McGwire 514 - Sammy Sosa 515 - Ken Griffey 516 517 # Team ranking 518 --- 519 - Chicago Cubs 520 - St Louis Cardinals 521 EOL 522 ); 523 } 524 525 /** 526 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 527 */ 528 public function testSequenceInAMapping() 529 { 530 Yaml::parse(<<<EOF 531 yaml: 532 hash: me 533 - array stuff 534 EOF 535 ); 536 } 537 538 /** 539 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 540 */ 541 public function testMappingInASequence() 542 { 543 Yaml::parse(<<<EOF 544 yaml: 545 - array stuff 546 hash: me 547 EOF 548 ); 549 } 550 551 /** 552 * @expectedException \Symfony\Component\Yaml\Exception\ParseException 553 * @expectedExceptionMessage missing colon 554 */ 555 public function testScalarInSequence() 556 { 557 Yaml::parse(<<<EOF 558 foo: 559 - bar 560 "missing colon" 561 foo: bar 562 EOF 563 ); 564 } 565 566 /** 567 * > It is an error for two equal keys to appear in the same mapping node. 568 * > In such a case the YAML processor may continue, ignoring the second 569 * > `key: value` pair and issuing an appropriate warning. This strategy 570 * > preserves a consistent information model for one-pass and random access 571 * > applications. 572 * 573 * @see http://yaml.org/spec/1.2/spec.html#id2759572 574 * @see http://yaml.org/spec/1.1/#id932806 575 * 576 * @covers \Symfony\Component\Yaml\Parser::parse 577 */ 578 public function testMappingDuplicateKeyBlock() 579 { 580 $input = <<<EOD 581 parent: 582 child: first 583 child: duplicate 584 parent: 585 child: duplicate 586 child: duplicate 587 EOD; 588 $expected = array( 589 'parent' => array( 590 'child' => 'first', 591 ), 592 ); 593 $this->assertSame($expected, Yaml::parse($input)); 594 } 595 596 /** 597 * @covers \Symfony\Component\Yaml\Inline::parseMapping 598 */ 599 public function testMappingDuplicateKeyFlow() 600 { 601 $input = <<<EOD 602 parent: { child: first, child: duplicate } 603 parent: { child: duplicate, child: duplicate } 604 EOD; 605 $expected = array( 606 'parent' => array( 607 'child' => 'first', 608 ), 609 ); 610 $this->assertSame($expected, Yaml::parse($input)); 611 } 612 613 public function testEmptyValue() 614 { 615 $input = <<<EOF 616 hash: 617 EOF; 618 619 $this->assertEquals(array('hash' => null), Yaml::parse($input)); 620 } 621 622 public function testCommentAtTheRootIndent() 623 { 624 $this->assertEquals(array( 625 'services' => array( 626 'app.foo_service' => array( 627 'class' => 'Foo', 628 ), 629 'app/bar_service' => array( 630 'class' => 'Bar', 631 ), 632 ), 633 ), Yaml::parse(<<<EOF 634 # comment 1 635 services: 636 # comment 2 637 # comment 3 638 app.foo_service: 639 class: Foo 640 # comment 4 641 # comment 5 642 app/bar_service: 643 class: Bar 644 EOF 645 )); 646 } 647 648 public function testStringBlockWithComments() 649 { 650 $this->assertEquals(array('content' => <<<EOT 651 # comment 1 652 header 653 654 # comment 2 655 <body> 656 <h1>title</h1> 657 </body> 658 659 footer # comment3 660 EOT 661 ), Yaml::parse(<<<EOF 662 content: | 663 # comment 1 664 header 665 666 # comment 2 667 <body> 668 <h1>title</h1> 669 </body> 670 671 footer # comment3 672 EOF 673 )); 674 } 675 676 public function testFoldedStringBlockWithComments() 677 { 678 $this->assertEquals(array(array('content' => <<<EOT 679 # comment 1 680 header 681 682 # comment 2 683 <body> 684 <h1>title</h1> 685 </body> 686 687 footer # comment3 688 EOT 689 )), Yaml::parse(<<<EOF 690 - 691 content: | 692 # comment 1 693 header 694 695 # comment 2 696 <body> 697 <h1>title</h1> 698 </body> 699 700 footer # comment3 701 EOF 702 )); 703 } 704 705 public function testNestedFoldedStringBlockWithComments() 706 { 707 $this->assertEquals(array(array( 708 'title' => 'some title', 709 'content' => <<<EOT 710 # comment 1 711 header 712 713 # comment 2 714 <body> 715 <h1>title</h1> 716 </body> 717 718 footer # comment3 719 EOT 720 )), Yaml::parse(<<<EOF 721 - 722 title: some title 723 content: | 724 # comment 1 725 header 726 727 # comment 2 728 <body> 729 <h1>title</h1> 730 </body> 731 732 footer # comment3 733 EOF 734 )); 735 } 736 737 public function testReferenceResolvingInInlineStrings() 738 { 739 $this->assertEquals(array( 740 'var' => 'var-value', 741 'scalar' => 'var-value', 742 'list' => array('var-value'), 743 'list_in_list' => array(array('var-value')), 744 'map_in_list' => array(array('key' => 'var-value')), 745 'embedded_mapping' => array(array('key' => 'var-value')), 746 'map' => array('key' => 'var-value'), 747 'list_in_map' => array('key' => array('var-value')), 748 'map_in_map' => array('foo' => array('bar' => 'var-value')), 749 ), Yaml::parse(<<<EOF 750 var: &var var-value 751 scalar: *var 752 list: [ *var ] 753 list_in_list: [[ *var ]] 754 map_in_list: [ { key: *var } ] 755 embedded_mapping: [ key: *var ] 756 map: { key: *var } 757 list_in_map: { key: [*var] } 758 map_in_map: { foo: { bar: *var } } 759 EOF 760 )); 761 } 762 763 public function testYamlDirective() 764 { 765 $yaml = <<<EOF 766 %YAML 1.2 767 --- 768 foo: 1 769 bar: 2 770 EOF; 771 $this->assertEquals(array('foo' => 1, 'bar' => 2), $this->parser->parse($yaml)); 772 } 773 774 public function testFloatKeys() 775 { 776 $yaml = <<<EOF 777 foo: 778 1.2: "bar" 779 1.3: "baz" 780 EOF; 781 782 $expected = array( 783 'foo' => array( 784 '1.2' => 'bar', 785 '1.3' => 'baz', 786 ), 787 ); 788 789 $this->assertEquals($expected, $this->parser->parse($yaml)); 790 } 791 } 792 793 class B 794 { 795 public $b = 'foo'; 796 }