ParserTest.php 34 KB

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