ParserTest.php 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Yaml\Tests;
  11. use Symfony\Component\Yaml\Yaml;
  12. use Symfony\Component\Yaml\Parser;
  13. class ParserTest extends \PHPUnit_Framework_TestCase
  14. {
  15. protected $parser;
  16. protected function setUp()
  17. {
  18. $this->parser = new Parser();
  19. }
  20. protected function tearDown()
  21. {
  22. $this->parser = null;
  23. }
  24. /**
  25. * @dataProvider getDataFormSpecifications
  26. */
  27. public function testSpecifications($file, $expected, $yaml, $comment, $deprecated)
  28. {
  29. $deprecations = array();
  30. if ($deprecated) {
  31. set_error_handler(function ($type, $msg) use (&$deprecations) {
  32. if (E_USER_DEPRECATED !== $type) {
  33. restore_error_handler();
  34. return call_user_func_array('PHPUnit_Util_ErrorHandler::handleError', func_get_args());
  35. }
  36. $deprecations[] = $msg;
  37. });
  38. }
  39. $this->assertEquals($expected, var_export($this->parser->parse($yaml), true), $comment);
  40. if ($deprecated) {
  41. restore_error_handler();
  42. $this->assertCount(1, $deprecations);
  43. $this->assertContains('Using the comma as a group separator for floats is deprecated since version 3.2 and will be removed in 4.0.', $deprecations[0]);
  44. }
  45. }
  46. public function getDataFormSpecifications()
  47. {
  48. $parser = new Parser();
  49. $path = __DIR__.'/Fixtures';
  50. $tests = array();
  51. $files = $parser->parse(file_get_contents($path.'/index.yml'));
  52. foreach ($files as $file) {
  53. $yamls = file_get_contents($path.'/'.$file.'.yml');
  54. // split YAMLs documents
  55. foreach (preg_split('/^---( %YAML\:1\.0)?/m', $yamls) as $yaml) {
  56. if (!$yaml) {
  57. continue;
  58. }
  59. $test = $parser->parse($yaml);
  60. if (isset($test['todo']) && $test['todo']) {
  61. // TODO
  62. } else {
  63. eval('$expected = '.trim($test['php']).';');
  64. $tests[] = array($file, var_export($expected, true), $test['yaml'], $test['test'], isset($test['deprecated']) ? $test['deprecated'] : false);
  65. }
  66. }
  67. }
  68. return $tests;
  69. }
  70. public function testTabsInYaml()
  71. {
  72. // test tabs in YAML
  73. $yamls = array(
  74. "foo:\n bar",
  75. "foo:\n bar",
  76. "foo:\n bar",
  77. "foo:\n bar",
  78. );
  79. foreach ($yamls as $yaml) {
  80. try {
  81. $content = $this->parser->parse($yaml);
  82. $this->fail('YAML files must not contain tabs');
  83. } catch (\Exception $e) {
  84. $this->assertInstanceOf('\Exception', $e, 'YAML files must not contain tabs');
  85. $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');
  86. }
  87. }
  88. }
  89. public function testEndOfTheDocumentMarker()
  90. {
  91. $yaml = <<<'EOF'
  92. --- %YAML:1.0
  93. foo
  94. ...
  95. EOF;
  96. $this->assertEquals('foo', $this->parser->parse($yaml));
  97. }
  98. public function getBlockChompingTests()
  99. {
  100. $tests = array();
  101. $yaml = <<<'EOF'
  102. foo: |-
  103. one
  104. two
  105. bar: |-
  106. one
  107. two
  108. EOF;
  109. $expected = array(
  110. 'foo' => "one\ntwo",
  111. 'bar' => "one\ntwo",
  112. );
  113. $tests['Literal block chomping strip with single trailing newline'] = array($expected, $yaml);
  114. $yaml = <<<'EOF'
  115. foo: |-
  116. one
  117. two
  118. bar: |-
  119. one
  120. two
  121. EOF;
  122. $expected = array(
  123. 'foo' => "one\ntwo",
  124. 'bar' => "one\ntwo",
  125. );
  126. $tests['Literal block chomping strip with multiple trailing newlines'] = array($expected, $yaml);
  127. $yaml = <<<'EOF'
  128. {}
  129. EOF;
  130. $expected = array();
  131. $tests['Literal block chomping strip with multiple trailing newlines after a 1-liner'] = array($expected, $yaml);
  132. $yaml = <<<'EOF'
  133. foo: |-
  134. one
  135. two
  136. bar: |-
  137. one
  138. two
  139. EOF;
  140. $expected = array(
  141. 'foo' => "one\ntwo",
  142. 'bar' => "one\ntwo",
  143. );
  144. $tests['Literal block chomping strip without trailing newline'] = array($expected, $yaml);
  145. $yaml = <<<'EOF'
  146. foo: |
  147. one
  148. two
  149. bar: |
  150. one
  151. two
  152. EOF;
  153. $expected = array(
  154. 'foo' => "one\ntwo\n",
  155. 'bar' => "one\ntwo\n",
  156. );
  157. $tests['Literal block chomping clip with single trailing newline'] = array($expected, $yaml);
  158. $yaml = <<<'EOF'
  159. foo: |
  160. one
  161. two
  162. bar: |
  163. one
  164. two
  165. EOF;
  166. $expected = array(
  167. 'foo' => "one\ntwo\n",
  168. 'bar' => "one\ntwo\n",
  169. );
  170. $tests['Literal block chomping clip with multiple trailing newlines'] = array($expected, $yaml);
  171. $yaml = <<<'EOF'
  172. foo:
  173. - bar: |
  174. one
  175. two
  176. EOF;
  177. $expected = array(
  178. 'foo' => array(
  179. array(
  180. 'bar' => "one\n\ntwo",
  181. ),
  182. ),
  183. );
  184. $tests['Literal block chomping clip with embedded blank line inside unindented collection'] = array($expected, $yaml);
  185. $yaml = <<<'EOF'
  186. foo: |
  187. one
  188. two
  189. bar: |
  190. one
  191. two
  192. EOF;
  193. $expected = array(
  194. 'foo' => "one\ntwo\n",
  195. 'bar' => "one\ntwo",
  196. );
  197. $tests['Literal block chomping clip without trailing newline'] = array($expected, $yaml);
  198. $yaml = <<<'EOF'
  199. foo: |+
  200. one
  201. two
  202. bar: |+
  203. one
  204. two
  205. EOF;
  206. $expected = array(
  207. 'foo' => "one\ntwo\n",
  208. 'bar' => "one\ntwo\n",
  209. );
  210. $tests['Literal block chomping keep with single trailing newline'] = array($expected, $yaml);
  211. $yaml = <<<'EOF'
  212. foo: |+
  213. one
  214. two
  215. bar: |+
  216. one
  217. two
  218. EOF;
  219. $expected = array(
  220. 'foo' => "one\ntwo\n\n",
  221. 'bar' => "one\ntwo\n\n",
  222. );
  223. $tests['Literal block chomping keep with multiple trailing newlines'] = array($expected, $yaml);
  224. $yaml = <<<'EOF'
  225. foo: |+
  226. one
  227. two
  228. bar: |+
  229. one
  230. two
  231. EOF;
  232. $expected = array(
  233. 'foo' => "one\ntwo\n",
  234. 'bar' => "one\ntwo",
  235. );
  236. $tests['Literal block chomping keep without trailing newline'] = array($expected, $yaml);
  237. $yaml = <<<'EOF'
  238. foo: >-
  239. one
  240. two
  241. bar: >-
  242. one
  243. two
  244. EOF;
  245. $expected = array(
  246. 'foo' => 'one two',
  247. 'bar' => 'one two',
  248. );
  249. $tests['Folded block chomping strip with single trailing newline'] = array($expected, $yaml);
  250. $yaml = <<<'EOF'
  251. foo: >-
  252. one
  253. two
  254. bar: >-
  255. one
  256. two
  257. EOF;
  258. $expected = array(
  259. 'foo' => 'one two',
  260. 'bar' => 'one two',
  261. );
  262. $tests['Folded block chomping strip with multiple trailing newlines'] = array($expected, $yaml);
  263. $yaml = <<<'EOF'
  264. foo: >-
  265. one
  266. two
  267. bar: >-
  268. one
  269. two
  270. EOF;
  271. $expected = array(
  272. 'foo' => 'one two',
  273. 'bar' => 'one two',
  274. );
  275. $tests['Folded block chomping strip without trailing newline'] = array($expected, $yaml);
  276. $yaml = <<<'EOF'
  277. foo: >
  278. one
  279. two
  280. bar: >
  281. one
  282. two
  283. EOF;
  284. $expected = array(
  285. 'foo' => "one two\n",
  286. 'bar' => "one two\n",
  287. );
  288. $tests['Folded block chomping clip with single trailing newline'] = array($expected, $yaml);
  289. $yaml = <<<'EOF'
  290. foo: >
  291. one
  292. two
  293. bar: >
  294. one
  295. two
  296. EOF;
  297. $expected = array(
  298. 'foo' => "one two\n",
  299. 'bar' => "one two\n",
  300. );
  301. $tests['Folded block chomping clip with multiple trailing newlines'] = array($expected, $yaml);
  302. $yaml = <<<'EOF'
  303. foo: >
  304. one
  305. two
  306. bar: >
  307. one
  308. two
  309. EOF;
  310. $expected = array(
  311. 'foo' => "one two\n",
  312. 'bar' => 'one two',
  313. );
  314. $tests['Folded block chomping clip without trailing newline'] = array($expected, $yaml);
  315. $yaml = <<<'EOF'
  316. foo: >+
  317. one
  318. two
  319. bar: >+
  320. one
  321. two
  322. EOF;
  323. $expected = array(
  324. 'foo' => "one two\n",
  325. 'bar' => "one two\n",
  326. );
  327. $tests['Folded block chomping keep with single trailing newline'] = array($expected, $yaml);
  328. $yaml = <<<'EOF'
  329. foo: >+
  330. one
  331. two
  332. bar: >+
  333. one
  334. two
  335. EOF;
  336. $expected = array(
  337. 'foo' => "one two\n\n",
  338. 'bar' => "one two\n\n",
  339. );
  340. $tests['Folded block chomping keep with multiple trailing newlines'] = array($expected, $yaml);
  341. $yaml = <<<'EOF'
  342. foo: >+
  343. one
  344. two
  345. bar: >+
  346. one
  347. two
  348. EOF;
  349. $expected = array(
  350. 'foo' => "one two\n",
  351. 'bar' => 'one two',
  352. );
  353. $tests['Folded block chomping keep without trailing newline'] = array($expected, $yaml);
  354. return $tests;
  355. }
  356. /**
  357. * @dataProvider getBlockChompingTests
  358. */
  359. public function testBlockChomping($expected, $yaml)
  360. {
  361. $this->assertSame($expected, $this->parser->parse($yaml));
  362. }
  363. /**
  364. * Regression test for issue #7989.
  365. *
  366. * @see https://github.com/symfony/symfony/issues/7989
  367. */
  368. public function testBlockLiteralWithLeadingNewlines()
  369. {
  370. $yaml = <<<'EOF'
  371. foo: |-
  372. bar
  373. EOF;
  374. $expected = array(
  375. 'foo' => "\n\nbar",
  376. );
  377. $this->assertSame($expected, $this->parser->parse($yaml));
  378. }
  379. public function testObjectSupportEnabled()
  380. {
  381. $input = <<<'EOF'
  382. foo: !php/object:O:30:"Symfony\Component\Yaml\Tests\B":1:{s:1:"b";s:3:"foo";}
  383. bar: 1
  384. EOF;
  385. $this->assertEquals(array('foo' => new B(), 'bar' => 1), $this->parser->parse($input, Yaml::PARSE_OBJECT), '->parse() is able to parse objects');
  386. }
  387. /**
  388. * @group legacy
  389. */
  390. public function testObjectSupportEnabledPassingTrue()
  391. {
  392. $input = <<<'EOF'
  393. foo: !php/object:O:30:"Symfony\Component\Yaml\Tests\B":1:{s:1:"b";s:3:"foo";}
  394. bar: 1
  395. EOF;
  396. $this->assertEquals(array('foo' => new B(), 'bar' => 1), $this->parser->parse($input, false, true), '->parse() is able to parse objects');
  397. }
  398. /**
  399. * @group legacy
  400. */
  401. public function testObjectSupportEnabledWithDeprecatedTag()
  402. {
  403. $input = <<<'EOF'
  404. foo: !!php/object:O:30:"Symfony\Component\Yaml\Tests\B":1:{s:1:"b";s:3:"foo";}
  405. bar: 1
  406. EOF;
  407. $this->assertEquals(array('foo' => new B(), 'bar' => 1), $this->parser->parse($input, Yaml::PARSE_OBJECT), '->parse() is able to parse objects');
  408. }
  409. /**
  410. * @dataProvider invalidDumpedObjectProvider
  411. */
  412. public function testObjectSupportDisabledButNoExceptions($input)
  413. {
  414. $this->assertEquals(array('foo' => null, 'bar' => 1), $this->parser->parse($input), '->parse() does not parse objects');
  415. }
  416. /**
  417. * @dataProvider getObjectForMapTests
  418. */
  419. public function testObjectForMap($yaml, $expected)
  420. {
  421. $this->assertEquals($expected, $this->parser->parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP));
  422. }
  423. /**
  424. * @group legacy
  425. * @dataProvider getObjectForMapTests
  426. */
  427. public function testObjectForMapEnabledWithMappingUsingBooleanToggles($yaml, $expected)
  428. {
  429. $this->assertEquals($expected, $this->parser->parse($yaml, false, false, true));
  430. }
  431. public function getObjectForMapTests()
  432. {
  433. $tests = array();
  434. $yaml = <<<'EOF'
  435. foo:
  436. fiz: [cat]
  437. EOF;
  438. $expected = new \stdClass();
  439. $expected->foo = new \stdClass();
  440. $expected->foo->fiz = array('cat');
  441. $tests['mapping'] = array($yaml, $expected);
  442. $yaml = '{ "foo": "bar", "fiz": "cat" }';
  443. $expected = new \stdClass();
  444. $expected->foo = 'bar';
  445. $expected->fiz = 'cat';
  446. $tests['inline-mapping'] = array($yaml, $expected);
  447. $yaml = "foo: bar\nbaz: foobar";
  448. $expected = new \stdClass();
  449. $expected->foo = 'bar';
  450. $expected->baz = 'foobar';
  451. $tests['object-for-map-is-applied-after-parsing'] = array($yaml, $expected);
  452. $yaml = <<<'EOT'
  453. array:
  454. - key: one
  455. - key: two
  456. EOT;
  457. $expected = new \stdClass();
  458. $expected->array = array();
  459. $expected->array[0] = new \stdClass();
  460. $expected->array[0]->key = 'one';
  461. $expected->array[1] = new \stdClass();
  462. $expected->array[1]->key = 'two';
  463. $tests['nest-map-and-sequence'] = array($yaml, $expected);
  464. $yaml = <<<'YAML'
  465. map:
  466. 1: one
  467. 2: two
  468. YAML;
  469. $expected = new \stdClass();
  470. $expected->map = new \stdClass();
  471. $expected->map->{1} = 'one';
  472. $expected->map->{2} = 'two';
  473. $tests['numeric-keys'] = array($yaml, $expected);
  474. $yaml = <<<'YAML'
  475. map:
  476. 0: one
  477. 1: two
  478. YAML;
  479. $expected = new \stdClass();
  480. $expected->map = new \stdClass();
  481. $expected->map->{0} = 'one';
  482. $expected->map->{1} = 'two';
  483. $tests['zero-indexed-numeric-keys'] = array($yaml, $expected);
  484. return $tests;
  485. }
  486. /**
  487. * @dataProvider invalidDumpedObjectProvider
  488. * @expectedException \Symfony\Component\Yaml\Exception\ParseException
  489. */
  490. public function testObjectsSupportDisabledWithExceptions($yaml)
  491. {
  492. $this->parser->parse($yaml, Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE);
  493. }
  494. /**
  495. * @group legacy
  496. * @dataProvider invalidDumpedObjectProvider
  497. * @expectedException \Symfony\Component\Yaml\Exception\ParseException
  498. */
  499. public function testObjectsSupportDisabledWithExceptionsUsingBooleanToggles($yaml)
  500. {
  501. $this->parser->parse($yaml, true);
  502. }
  503. public function invalidDumpedObjectProvider()
  504. {
  505. $yamlTag = <<<'EOF'
  506. foo: !!php/object:O:30:"Symfony\Tests\Component\Yaml\B":1:{s:1:"b";s:3:"foo";}
  507. bar: 1
  508. EOF;
  509. $localTag = <<<'EOF'
  510. foo: !php/object:O:30:"Symfony\Tests\Component\Yaml\B":1:{s:1:"b";s:3:"foo";}
  511. bar: 1
  512. EOF;
  513. return array(
  514. 'yaml-tag' => array($yamlTag),
  515. 'local-tag' => array($localTag),
  516. );
  517. }
  518. /**
  519. * @requires extension iconv
  520. */
  521. public function testNonUtf8Exception()
  522. {
  523. $yamls = array(
  524. iconv('UTF-8', 'ISO-8859-1', "foo: 'äöüß'"),
  525. iconv('UTF-8', 'ISO-8859-15', "euro: '€'"),
  526. iconv('UTF-8', 'CP1252', "cp1252: '©ÉÇáñ'"),
  527. );
  528. foreach ($yamls as $yaml) {
  529. try {
  530. $this->parser->parse($yaml);
  531. $this->fail('charsets other than UTF-8 are rejected.');
  532. } catch (\Exception $e) {
  533. $this->assertInstanceOf('Symfony\Component\Yaml\Exception\ParseException', $e, 'charsets other than UTF-8 are rejected.');
  534. }
  535. }
  536. }
  537. /**
  538. * @expectedException \Symfony\Component\Yaml\Exception\ParseException
  539. */
  540. public function testUnindentedCollectionException()
  541. {
  542. $yaml = <<<'EOF'
  543. collection:
  544. -item1
  545. -item2
  546. -item3
  547. EOF;
  548. $this->parser->parse($yaml);
  549. }
  550. /**
  551. * @expectedException \Symfony\Component\Yaml\Exception\ParseException
  552. */
  553. public function testShortcutKeyUnindentedCollectionException()
  554. {
  555. $yaml = <<<'EOF'
  556. collection:
  557. - key: foo
  558. foo: bar
  559. EOF;
  560. $this->parser->parse($yaml);
  561. }
  562. /**
  563. * @expectedException \Symfony\Component\Yaml\Exception\ParseException
  564. * @expectedExceptionMessageRegExp /^Multiple documents are not supported.+/
  565. */
  566. public function testMultipleDocumentsNotSupportedException()
  567. {
  568. Yaml::parse(<<<'EOL'
  569. # Ranking of 1998 home runs
  570. ---
  571. - Mark McGwire
  572. - Sammy Sosa
  573. - Ken Griffey
  574. # Team ranking
  575. ---
  576. - Chicago Cubs
  577. - St Louis Cardinals
  578. EOL
  579. );
  580. }
  581. /**
  582. * @expectedException \Symfony\Component\Yaml\Exception\ParseException
  583. */
  584. public function testSequenceInAMapping()
  585. {
  586. Yaml::parse(<<<'EOF'
  587. yaml:
  588. hash: me
  589. - array stuff
  590. EOF
  591. );
  592. }
  593. public function testSequenceInMappingStartedBySingleDashLine()
  594. {
  595. $yaml = <<<'EOT'
  596. a:
  597. -
  598. b:
  599. -
  600. bar: baz
  601. - foo
  602. d: e
  603. EOT;
  604. $expected = array(
  605. 'a' => array(
  606. array(
  607. 'b' => array(
  608. array(
  609. 'bar' => 'baz',
  610. ),
  611. ),
  612. ),
  613. 'foo',
  614. ),
  615. 'd' => 'e',
  616. );
  617. $this->assertSame($expected, $this->parser->parse($yaml));
  618. }
  619. public function testSequenceFollowedByCommentEmbeddedInMapping()
  620. {
  621. $yaml = <<<'EOT'
  622. a:
  623. b:
  624. - c
  625. # comment
  626. d: e
  627. EOT;
  628. $expected = array(
  629. 'a' => array(
  630. 'b' => array('c'),
  631. 'd' => 'e',
  632. ),
  633. );
  634. $this->assertSame($expected, $this->parser->parse($yaml));
  635. }
  636. /**
  637. * @expectedException \Symfony\Component\Yaml\Exception\ParseException
  638. */
  639. public function testMappingInASequence()
  640. {
  641. Yaml::parse(<<<'EOF'
  642. yaml:
  643. - array stuff
  644. hash: me
  645. EOF
  646. );
  647. }
  648. /**
  649. * @expectedException \Symfony\Component\Yaml\Exception\ParseException
  650. * @expectedExceptionMessage missing colon
  651. */
  652. public function testScalarInSequence()
  653. {
  654. Yaml::parse(<<<'EOF'
  655. foo:
  656. - bar
  657. "missing colon"
  658. foo: bar
  659. EOF
  660. );
  661. }
  662. /**
  663. * > It is an error for two equal keys to appear in the same mapping node.
  664. * > In such a case the YAML processor may continue, ignoring the second
  665. * > `key: value` pair and issuing an appropriate warning. This strategy
  666. * > preserves a consistent information model for one-pass and random access
  667. * > applications.
  668. *
  669. * @see http://yaml.org/spec/1.2/spec.html#id2759572
  670. * @see http://yaml.org/spec/1.1/#id932806
  671. * @group legacy
  672. */
  673. public function testMappingDuplicateKeyBlock()
  674. {
  675. $input = <<<'EOD'
  676. parent:
  677. child: first
  678. child: duplicate
  679. parent:
  680. child: duplicate
  681. child: duplicate
  682. EOD;
  683. $expected = array(
  684. 'parent' => array(
  685. 'child' => 'first',
  686. ),
  687. );
  688. $this->assertSame($expected, Yaml::parse($input));
  689. }
  690. /**
  691. * @group legacy
  692. */
  693. public function testMappingDuplicateKeyFlow()
  694. {
  695. $input = <<<'EOD'
  696. parent: { child: first, child: duplicate }
  697. parent: { child: duplicate, child: duplicate }
  698. EOD;
  699. $expected = array(
  700. 'parent' => array(
  701. 'child' => 'first',
  702. ),
  703. );
  704. $this->assertSame($expected, Yaml::parse($input));
  705. }
  706. /**
  707. * @group legacy
  708. * @dataProvider getParseExceptionOnDuplicateData
  709. * @expectedDeprecation Duplicate key "%s" detected on line %d whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated %s.
  710. * throws \Symfony\Component\Yaml\Exception\ParseException in 4.0
  711. */
  712. public function testParseExceptionOnDuplicate($input, $duplicateKey, $lineNumber)
  713. {
  714. Yaml::parse($input);
  715. }
  716. public function getParseExceptionOnDuplicateData()
  717. {
  718. $tests = array();
  719. $yaml = <<<EOD
  720. parent: { child: first, child: duplicate }
  721. EOD;
  722. $tests[] = array($yaml, 'child', 1);
  723. $yaml = <<<EOD
  724. parent:
  725. child: first,
  726. child: duplicate
  727. EOD;
  728. $tests[] = array($yaml, 'child', 3);
  729. $yaml = <<<EOD
  730. parent: { child: foo }
  731. parent: { child: bar }
  732. EOD;
  733. $tests[] = array($yaml, 'parent', 2);
  734. $yaml = <<<EOD
  735. parent: { child_mapping: { value: bar}, child_mapping: { value: bar} }
  736. EOD;
  737. $tests[] = array($yaml, 'child_mapping', 1);
  738. $yaml = <<<EOD
  739. parent:
  740. child_mapping:
  741. value: bar
  742. child_mapping:
  743. value: bar
  744. EOD;
  745. $tests[] = array($yaml, 'child_mapping', 4);
  746. $yaml = <<<EOD
  747. parent: { child_sequence: ['key1', 'key2', 'key3'], child_sequence: ['key1', 'key2', 'key3'] }
  748. EOD;
  749. $tests[] = array($yaml, 'child_sequence', 1);
  750. $yaml = <<<EOD
  751. parent:
  752. child_sequence:
  753. - key1
  754. - key2
  755. - key3
  756. child_sequence:
  757. - key1
  758. - key2
  759. - key3
  760. EOD;
  761. $tests[] = array($yaml, 'child_sequence', 6);
  762. return $tests;
  763. }
  764. public function testEmptyValue()
  765. {
  766. $input = <<<'EOF'
  767. hash:
  768. EOF;
  769. $this->assertEquals(array('hash' => null), Yaml::parse($input));
  770. }
  771. public function testCommentAtTheRootIndent()
  772. {
  773. $this->assertEquals(array(
  774. 'services' => array(
  775. 'app.foo_service' => array(
  776. 'class' => 'Foo',
  777. ),
  778. 'app/bar_service' => array(
  779. 'class' => 'Bar',
  780. ),
  781. ),
  782. ), Yaml::parse(<<<'EOF'
  783. # comment 1
  784. services:
  785. # comment 2
  786. # comment 3
  787. app.foo_service:
  788. class: Foo
  789. # comment 4
  790. # comment 5
  791. app/bar_service:
  792. class: Bar
  793. EOF
  794. ));
  795. }
  796. public function testStringBlockWithComments()
  797. {
  798. $this->assertEquals(array('content' => <<<'EOT'
  799. # comment 1
  800. header
  801. # comment 2
  802. <body>
  803. <h1>title</h1>
  804. </body>
  805. footer # comment3
  806. EOT
  807. ), Yaml::parse(<<<'EOF'
  808. content: |
  809. # comment 1
  810. header
  811. # comment 2
  812. <body>
  813. <h1>title</h1>
  814. </body>
  815. footer # comment3
  816. EOF
  817. ));
  818. }
  819. public function testFoldedStringBlockWithComments()
  820. {
  821. $this->assertEquals(array(array('content' => <<<'EOT'
  822. # comment 1
  823. header
  824. # comment 2
  825. <body>
  826. <h1>title</h1>
  827. </body>
  828. footer # comment3
  829. EOT
  830. )), Yaml::parse(<<<'EOF'
  831. -
  832. content: |
  833. # comment 1
  834. header
  835. # comment 2
  836. <body>
  837. <h1>title</h1>
  838. </body>
  839. footer # comment3
  840. EOF
  841. ));
  842. }
  843. public function testNestedFoldedStringBlockWithComments()
  844. {
  845. $this->assertEquals(array(array(
  846. 'title' => 'some title',
  847. 'content' => <<<'EOT'
  848. # comment 1
  849. header
  850. # comment 2
  851. <body>
  852. <h1>title</h1>
  853. </body>
  854. footer # comment3
  855. EOT
  856. )), Yaml::parse(<<<'EOF'
  857. -
  858. title: some title
  859. content: |
  860. # comment 1
  861. header
  862. # comment 2
  863. <body>
  864. <h1>title</h1>
  865. </body>
  866. footer # comment3
  867. EOF
  868. ));
  869. }
  870. public function testReferenceResolvingInInlineStrings()
  871. {
  872. $this->assertEquals(array(
  873. 'var' => 'var-value',
  874. 'scalar' => 'var-value',
  875. 'list' => array('var-value'),
  876. 'list_in_list' => array(array('var-value')),
  877. 'map_in_list' => array(array('key' => 'var-value')),
  878. 'embedded_mapping' => array(array('key' => 'var-value')),
  879. 'map' => array('key' => 'var-value'),
  880. 'list_in_map' => array('key' => array('var-value')),
  881. 'map_in_map' => array('foo' => array('bar' => 'var-value')),
  882. ), Yaml::parse(<<<'EOF'
  883. var: &var var-value
  884. scalar: *var
  885. list: [ *var ]
  886. list_in_list: [[ *var ]]
  887. map_in_list: [ { key: *var } ]
  888. embedded_mapping: [ key: *var ]
  889. map: { key: *var }
  890. list_in_map: { key: [*var] }
  891. map_in_map: { foo: { bar: *var } }
  892. EOF
  893. ));
  894. }
  895. public function testYamlDirective()
  896. {
  897. $yaml = <<<'EOF'
  898. %YAML 1.2
  899. ---
  900. foo: 1
  901. bar: 2
  902. EOF;
  903. $this->assertEquals(array('foo' => 1, 'bar' => 2), $this->parser->parse($yaml));
  904. }
  905. public function testFloatKeys()
  906. {
  907. $yaml = <<<'EOF'
  908. foo:
  909. 1.2: "bar"
  910. 1.3: "baz"
  911. EOF;
  912. $expected = array(
  913. 'foo' => array(
  914. '1.2' => 'bar',
  915. '1.3' => 'baz',
  916. ),
  917. );
  918. $this->assertEquals($expected, $this->parser->parse($yaml));
  919. }
  920. /**
  921. * @expectedException \Symfony\Component\Yaml\Exception\ParseException
  922. * @expectedExceptionMessage A colon cannot be used in an unquoted mapping value
  923. */
  924. public function testColonInMappingValueException()
  925. {
  926. $yaml = <<<'EOF'
  927. foo: bar: baz
  928. EOF;
  929. $this->parser->parse($yaml);
  930. }
  931. public function testColonInMappingValueExceptionNotTriggeredByColonInComment()
  932. {
  933. $yaml = <<<'EOT'
  934. foo:
  935. bar: foobar # Note: a comment after a colon
  936. EOT;
  937. $this->assertSame(array('foo' => array('bar' => 'foobar')), $this->parser->parse($yaml));
  938. }
  939. /**
  940. * @dataProvider getCommentLikeStringInScalarBlockData
  941. */
  942. public function testCommentLikeStringsAreNotStrippedInBlockScalars($yaml, $expectedParserResult)
  943. {
  944. $this->assertSame($expectedParserResult, $this->parser->parse($yaml));
  945. }
  946. public function getCommentLikeStringInScalarBlockData()
  947. {
  948. $tests = array();
  949. $yaml = <<<'EOT'
  950. pages:
  951. -
  952. title: some title
  953. content: |
  954. # comment 1
  955. header
  956. # comment 2
  957. <body>
  958. <h1>title</h1>
  959. </body>
  960. footer # comment3
  961. EOT;
  962. $expected = array(
  963. 'pages' => array(
  964. array(
  965. 'title' => 'some title',
  966. 'content' => <<<'EOT'
  967. # comment 1
  968. header
  969. # comment 2
  970. <body>
  971. <h1>title</h1>
  972. </body>
  973. footer # comment3
  974. EOT
  975. ,
  976. ),
  977. ),
  978. );
  979. $tests[] = array($yaml, $expected);
  980. $yaml = <<<'EOT'
  981. test: |
  982. foo
  983. # bar
  984. baz
  985. collection:
  986. - one: |
  987. foo
  988. # bar
  989. baz
  990. - two: |
  991. foo
  992. # bar
  993. baz
  994. EOT;
  995. $expected = array(
  996. 'test' => <<<'EOT'
  997. foo
  998. # bar
  999. baz
  1000. EOT
  1001. ,
  1002. 'collection' => array(
  1003. array(
  1004. 'one' => <<<'EOT'
  1005. foo
  1006. # bar
  1007. baz
  1008. EOT
  1009. ,
  1010. ),
  1011. array(
  1012. 'two' => <<<'EOT'
  1013. foo
  1014. # bar
  1015. baz
  1016. EOT
  1017. ,
  1018. ),
  1019. ),
  1020. );
  1021. $tests[] = array($yaml, $expected);
  1022. $yaml = <<<'EOT'
  1023. foo:
  1024. bar:
  1025. scalar-block: >
  1026. line1
  1027. line2>
  1028. baz:
  1029. # comment
  1030. foobar: ~
  1031. EOT;
  1032. $expected = array(
  1033. 'foo' => array(
  1034. 'bar' => array(
  1035. 'scalar-block' => "line1 line2>\n",
  1036. ),
  1037. 'baz' => array(
  1038. 'foobar' => null,
  1039. ),
  1040. ),
  1041. );
  1042. $tests[] = array($yaml, $expected);
  1043. $yaml = <<<'EOT'
  1044. a:
  1045. b: hello
  1046. # c: |
  1047. # first row
  1048. # second row
  1049. d: hello
  1050. EOT;
  1051. $expected = array(
  1052. 'a' => array(
  1053. 'b' => 'hello',
  1054. 'd' => 'hello',
  1055. ),
  1056. );
  1057. $tests[] = array($yaml, $expected);
  1058. return $tests;
  1059. }
  1060. public function testBlankLinesAreParsedAsNewLinesInFoldedBlocks()
  1061. {
  1062. $yaml = <<<'EOT'
  1063. test: >
  1064. <h2>A heading</h2>
  1065. <ul>
  1066. <li>a list</li>
  1067. <li>may be a good example</li>
  1068. </ul>
  1069. EOT;
  1070. $this->assertSame(
  1071. array(
  1072. 'test' => <<<'EOT'
  1073. <h2>A heading</h2>
  1074. <ul> <li>a list</li> <li>may be a good example</li> </ul>
  1075. EOT
  1076. ,
  1077. ),
  1078. $this->parser->parse($yaml)
  1079. );
  1080. }
  1081. public function testAdditionallyIndentedLinesAreParsedAsNewLinesInFoldedBlocks()
  1082. {
  1083. $yaml = <<<'EOT'
  1084. test: >
  1085. <h2>A heading</h2>
  1086. <ul>
  1087. <li>a list</li>
  1088. <li>may be a good example</li>
  1089. </ul>
  1090. EOT;
  1091. $this->assertSame(
  1092. array(
  1093. 'test' => <<<'EOT'
  1094. <h2>A heading</h2>
  1095. <ul>
  1096. <li>a list</li>
  1097. <li>may be a good example</li>
  1098. </ul>
  1099. EOT
  1100. ,
  1101. ),
  1102. $this->parser->parse($yaml)
  1103. );
  1104. }
  1105. /**
  1106. * @dataProvider getBinaryData
  1107. */
  1108. public function testParseBinaryData($data)
  1109. {
  1110. $this->assertSame(array('data' => 'Hello world'), $this->parser->parse($data));
  1111. }
  1112. public function getBinaryData()
  1113. {
  1114. return array(
  1115. 'enclosed with double quotes' => array('data: !!binary "SGVsbG8gd29ybGQ="'),
  1116. 'enclosed with single quotes' => array("data: !!binary 'SGVsbG8gd29ybGQ='"),
  1117. 'containing spaces' => array('data: !!binary "SGVs bG8gd 29ybGQ="'),
  1118. 'in block scalar' => array(
  1119. <<<'EOT'
  1120. data: !!binary |
  1121. SGVsbG8gd29ybGQ=
  1122. EOT
  1123. ),
  1124. 'containing spaces in block scalar' => array(
  1125. <<<'EOT'
  1126. data: !!binary |
  1127. SGVs bG8gd 29ybGQ=
  1128. EOT
  1129. ),
  1130. );
  1131. }
  1132. /**
  1133. * @dataProvider getInvalidBinaryData
  1134. */
  1135. public function testParseInvalidBinaryData($data, $expectedMessage)
  1136. {
  1137. $this->setExpectedExceptionRegExp('\Symfony\Component\Yaml\Exception\ParseException', $expectedMessage);
  1138. $this->parser->parse($data);
  1139. }
  1140. public function getInvalidBinaryData()
  1141. {
  1142. return array(
  1143. 'length not a multiple of four' => array('data: !!binary "SGVsbG8d29ybGQ="', '/The normalized base64 encoded data \(data without whitespace characters\) length must be a multiple of four \(\d+ bytes given\)/'),
  1144. 'invalid characters' => array('!!binary "SGVsbG8#d29ybGQ="', '/The base64 encoded data \(.*\) contains invalid characters/'),
  1145. 'too many equals characters' => array('data: !!binary "SGVsbG8gd29yb==="', '/The base64 encoded data \(.*\) contains invalid characters/'),
  1146. 'misplaced equals character' => array('data: !!binary "SGVsbG8gd29ybG=Q"', '/The base64 encoded data \(.*\) contains invalid characters/'),
  1147. 'length not a multiple of four in block scalar' => array(
  1148. <<<'EOT'
  1149. data: !!binary |
  1150. SGVsbG8d29ybGQ=
  1151. EOT
  1152. ,
  1153. '/The normalized base64 encoded data \(data without whitespace characters\) length must be a multiple of four \(\d+ bytes given\)/',
  1154. ),
  1155. 'invalid characters in block scalar' => array(
  1156. <<<'EOT'
  1157. data: !!binary |
  1158. SGVsbG8#d29ybGQ=
  1159. EOT
  1160. ,
  1161. '/The base64 encoded data \(.*\) contains invalid characters/',
  1162. ),
  1163. 'too many equals characters in block scalar' => array(
  1164. <<<'EOT'
  1165. data: !!binary |
  1166. SGVsbG8gd29yb===
  1167. EOT
  1168. ,
  1169. '/The base64 encoded data \(.*\) contains invalid characters/',
  1170. ),
  1171. 'misplaced equals character in block scalar' => array(
  1172. <<<'EOT'
  1173. data: !!binary |
  1174. SGVsbG8gd29ybG=Q
  1175. EOT
  1176. ,
  1177. '/The base64 encoded data \(.*\) contains invalid characters/',
  1178. ),
  1179. );
  1180. }
  1181. public function testParseDateAsMappingValue()
  1182. {
  1183. $yaml = <<<'EOT'
  1184. date: 2002-12-14
  1185. EOT;
  1186. $expectedDate = new \DateTime();
  1187. $expectedDate->setTimeZone(new \DateTimeZone('UTC'));
  1188. $expectedDate->setDate(2002, 12, 14);
  1189. $expectedDate->setTime(0, 0, 0);
  1190. $this->assertEquals(array('date' => $expectedDate), $this->parser->parse($yaml, Yaml::PARSE_DATETIME));
  1191. }
  1192. /**
  1193. * @param $lineNumber
  1194. * @param $yaml
  1195. * @dataProvider parserThrowsExceptionWithCorrectLineNumberProvider
  1196. */
  1197. public function testParserThrowsExceptionWithCorrectLineNumber($lineNumber, $yaml)
  1198. {
  1199. $this->setExpectedException(
  1200. '\Symfony\Component\Yaml\Exception\ParseException',
  1201. sprintf('Unexpected characters near "," at line %d (near "bar: "123",").', $lineNumber)
  1202. );
  1203. $this->parser->parse($yaml);
  1204. }
  1205. public function parserThrowsExceptionWithCorrectLineNumberProvider()
  1206. {
  1207. return array(
  1208. array(
  1209. 4,
  1210. <<<'YAML'
  1211. foo:
  1212. -
  1213. # bar
  1214. bar: "123",
  1215. YAML
  1216. ),
  1217. array(
  1218. 5,
  1219. <<<'YAML'
  1220. foo:
  1221. -
  1222. # bar
  1223. # bar
  1224. bar: "123",
  1225. YAML
  1226. ),
  1227. array(
  1228. 8,
  1229. <<<'YAML'
  1230. foo:
  1231. -
  1232. # foobar
  1233. baz: 123
  1234. bar:
  1235. -
  1236. # bar
  1237. bar: "123",
  1238. YAML
  1239. ),
  1240. array(
  1241. 10,
  1242. <<<'YAML'
  1243. foo:
  1244. -
  1245. # foobar
  1246. # foobar
  1247. baz: 123
  1248. bar:
  1249. -
  1250. # bar
  1251. # bar
  1252. bar: "123",
  1253. YAML
  1254. ),
  1255. );
  1256. }
  1257. public function testParseMultiLineQuotedString()
  1258. {
  1259. $yaml = <<<EOT
  1260. foo: "bar
  1261. baz
  1262. foobar
  1263. foo"
  1264. bar: baz
  1265. EOT;
  1266. $this->assertSame(array('foo' => 'bar baz foobar foo', 'bar' => 'baz'), $this->parser->parse($yaml));
  1267. }
  1268. public function testParseMultiLineUnquotedString()
  1269. {
  1270. $yaml = <<<EOT
  1271. foo: bar
  1272. baz
  1273. foobar
  1274. foo
  1275. bar: baz
  1276. EOT;
  1277. $this->assertSame(array('foo' => 'bar baz foobar foo', 'bar' => 'baz'), $this->parser->parse($yaml));
  1278. }
  1279. }
  1280. class B
  1281. {
  1282. public $b = 'foo';
  1283. }