InlineTest.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  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\Inline;
  14. use Symfony\Component\Yaml\Tag\TaggedValue;
  15. use Symfony\Component\Yaml\Yaml;
  16. class InlineTest extends TestCase
  17. {
  18. protected function setUp(): void
  19. {
  20. Inline::initialize(0, 0);
  21. }
  22. /**
  23. * @dataProvider getTestsForParse
  24. */
  25. public function testParse($yaml, $value, $flags = 0)
  26. {
  27. $this->assertSame($value, Inline::parse($yaml, $flags), sprintf('::parse() converts an inline YAML to a PHP structure (%s)', $yaml));
  28. }
  29. /**
  30. * @dataProvider getTestsForParseWithMapObjects
  31. */
  32. public function testParseWithMapObjects($yaml, $value, $flags = Yaml::PARSE_OBJECT_FOR_MAP)
  33. {
  34. $actual = Inline::parse($yaml, $flags);
  35. $this->assertSame(serialize($value), serialize($actual));
  36. }
  37. /**
  38. * @dataProvider getTestsForParsePhpConstants
  39. */
  40. public function testParsePhpConstants($yaml, $value)
  41. {
  42. $actual = Inline::parse($yaml, Yaml::PARSE_CONSTANT);
  43. $this->assertSame($value, $actual);
  44. }
  45. public function getTestsForParsePhpConstants()
  46. {
  47. return [
  48. ['!php/const Symfony\Component\Yaml\Yaml::PARSE_CONSTANT', Yaml::PARSE_CONSTANT],
  49. ['!php/const PHP_INT_MAX', PHP_INT_MAX],
  50. ['[!php/const PHP_INT_MAX]', [PHP_INT_MAX]],
  51. ['{ foo: !php/const PHP_INT_MAX }', ['foo' => PHP_INT_MAX]],
  52. ['!php/const NULL', null],
  53. ];
  54. }
  55. public function testParsePhpConstantThrowsExceptionWhenUndefined()
  56. {
  57. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  58. $this->expectExceptionMessage('The constant "WRONG_CONSTANT" is not defined');
  59. Inline::parse('!php/const WRONG_CONSTANT', Yaml::PARSE_CONSTANT);
  60. }
  61. public function testParsePhpConstantThrowsExceptionOnInvalidType()
  62. {
  63. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  64. $this->expectExceptionMessageRegExp('#The string "!php/const PHP_INT_MAX" could not be parsed as a constant.*#');
  65. Inline::parse('!php/const PHP_INT_MAX', Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE);
  66. }
  67. /**
  68. * @dataProvider getTestsForDump
  69. */
  70. public function testDump($yaml, $value, $parseFlags = 0)
  71. {
  72. $this->assertEquals($yaml, Inline::dump($value), sprintf('::dump() converts a PHP structure to an inline YAML (%s)', $yaml));
  73. $this->assertSame($value, Inline::parse(Inline::dump($value), $parseFlags), 'check consistency');
  74. }
  75. public function testDumpNumericValueWithLocale()
  76. {
  77. $locale = setlocale(LC_NUMERIC, 0);
  78. if (false === $locale) {
  79. $this->markTestSkipped('Your platform does not support locales.');
  80. }
  81. try {
  82. $requiredLocales = ['fr_FR.UTF-8', 'fr_FR.UTF8', 'fr_FR.utf-8', 'fr_FR.utf8', 'French_France.1252'];
  83. if (false === setlocale(LC_NUMERIC, $requiredLocales)) {
  84. $this->markTestSkipped('Could not set any of required locales: '.implode(', ', $requiredLocales));
  85. }
  86. $this->assertEquals('1.2', Inline::dump(1.2));
  87. $this->assertStringContainsStringIgnoringCase('fr', setlocale(LC_NUMERIC, 0));
  88. } finally {
  89. setlocale(LC_NUMERIC, $locale);
  90. }
  91. }
  92. public function testHashStringsResemblingExponentialNumericsShouldNotBeChangedToINF()
  93. {
  94. $value = '686e444';
  95. $this->assertSame($value, Inline::parse(Inline::dump($value)));
  96. }
  97. public function testParseScalarWithNonEscapedBlackslashShouldThrowException()
  98. {
  99. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  100. $this->expectExceptionMessage('Found unknown escape character "\V".');
  101. Inline::parse('"Foo\Var"');
  102. }
  103. public function testParseScalarWithNonEscapedBlackslashAtTheEndShouldThrowException()
  104. {
  105. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  106. Inline::parse('"Foo\\"');
  107. }
  108. public function testParseScalarWithIncorrectlyQuotedStringShouldThrowException()
  109. {
  110. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  111. $value = "'don't do somthin' like that'";
  112. Inline::parse($value);
  113. }
  114. public function testParseScalarWithIncorrectlyDoubleQuotedStringShouldThrowException()
  115. {
  116. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  117. $value = '"don"t do somthin" like that"';
  118. Inline::parse($value);
  119. }
  120. public function testParseInvalidMappingKeyShouldThrowException()
  121. {
  122. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  123. $value = '{ "foo " bar": "bar" }';
  124. Inline::parse($value);
  125. }
  126. public function testParseMappingKeyWithColonNotFollowedBySpace()
  127. {
  128. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  129. $this->expectExceptionMessage('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}")');
  130. Inline::parse('{foo:""}');
  131. }
  132. public function testParseInvalidMappingShouldThrowException()
  133. {
  134. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  135. Inline::parse('[foo] bar');
  136. }
  137. public function testParseInvalidSequenceShouldThrowException()
  138. {
  139. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  140. Inline::parse('{ foo: bar } bar');
  141. }
  142. public function testParseInvalidTaggedSequenceShouldThrowException()
  143. {
  144. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  145. Inline::parse('!foo { bar: baz } qux', Yaml::PARSE_CUSTOM_TAGS);
  146. }
  147. public function testParseScalarWithCorrectlyQuotedStringShouldReturnString()
  148. {
  149. $value = "'don''t do somthin'' like that'";
  150. $expect = "don't do somthin' like that";
  151. $this->assertSame($expect, Inline::parseScalar($value));
  152. }
  153. /**
  154. * @dataProvider getDataForParseReferences
  155. */
  156. public function testParseReferences($yaml, $expected)
  157. {
  158. $this->assertSame($expected, Inline::parse($yaml, 0, ['var' => 'var-value']));
  159. }
  160. public function getDataForParseReferences()
  161. {
  162. return [
  163. 'scalar' => ['*var', 'var-value'],
  164. 'list' => ['[ *var ]', ['var-value']],
  165. 'list-in-list' => ['[[ *var ]]', [['var-value']]],
  166. 'map-in-list' => ['[ { key: *var } ]', [['key' => 'var-value']]],
  167. 'embedded-mapping-in-list' => ['[ key: *var ]', [['key' => 'var-value']]],
  168. 'map' => ['{ key: *var }', ['key' => 'var-value']],
  169. 'list-in-map' => ['{ key: [*var] }', ['key' => ['var-value']]],
  170. 'map-in-map' => ['{ foo: { bar: *var } }', ['foo' => ['bar' => 'var-value']]],
  171. ];
  172. }
  173. public function testParseMapReferenceInSequence()
  174. {
  175. $foo = [
  176. 'a' => 'Steve',
  177. 'b' => 'Clark',
  178. 'c' => 'Brian',
  179. ];
  180. $this->assertSame([$foo], Inline::parse('[*foo]', 0, ['foo' => $foo]));
  181. }
  182. public function testParseUnquotedAsterisk()
  183. {
  184. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  185. $this->expectExceptionMessage('A reference must contain at least one character at line 1.');
  186. Inline::parse('{ foo: * }');
  187. }
  188. public function testParseUnquotedAsteriskFollowedByAComment()
  189. {
  190. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  191. $this->expectExceptionMessage('A reference must contain at least one character at line 1.');
  192. Inline::parse('{ foo: * #foo }');
  193. }
  194. /**
  195. * @dataProvider getReservedIndicators
  196. */
  197. public function testParseUnquotedScalarStartingWithReservedIndicator($indicator)
  198. {
  199. $this->expectException(ParseException::class);
  200. $this->expectExceptionMessage(sprintf('cannot start a plain scalar; you need to quote the scalar at line 1 (near "%sfoo").', $indicator));
  201. Inline::parse(sprintf('{ foo: %sfoo }', $indicator));
  202. }
  203. public function getReservedIndicators()
  204. {
  205. return [['@'], ['`']];
  206. }
  207. /**
  208. * @dataProvider getScalarIndicators
  209. */
  210. public function testParseUnquotedScalarStartingWithScalarIndicator($indicator)
  211. {
  212. $this->expectException(ParseException::class);
  213. $this->expectExceptionMessage(sprintf('cannot start a plain scalar; you need to quote the scalar at line 1 (near "%sfoo").', $indicator));
  214. Inline::parse(sprintf('{ foo: %sfoo }', $indicator));
  215. }
  216. public function getScalarIndicators()
  217. {
  218. return [['|'], ['>'], ['%']];
  219. }
  220. /**
  221. * @dataProvider getDataForIsHash
  222. */
  223. public function testIsHash($array, $expected)
  224. {
  225. $this->assertSame($expected, Inline::isHash($array));
  226. }
  227. public function getDataForIsHash()
  228. {
  229. return [
  230. [[], false],
  231. [[1, 2, 3], false],
  232. [[2 => 1, 1 => 2, 0 => 3], true],
  233. [['foo' => 1, 'bar' => 2], true],
  234. ];
  235. }
  236. public function getTestsForParse()
  237. {
  238. return [
  239. ['', ''],
  240. ['null', null],
  241. ['false', false],
  242. ['true', true],
  243. ['12', 12],
  244. ['-12', -12],
  245. ['1_2', 12],
  246. ['_12', '_12'],
  247. ['12_', 12],
  248. ['"quoted string"', 'quoted string'],
  249. ["'quoted string'", 'quoted string'],
  250. ['12.30e+02', 12.30e+02],
  251. ['123.45_67', 123.4567],
  252. ['0x4D2', 0x4D2],
  253. ['0x_4_D_2_', 0x4D2],
  254. ['02333', 02333],
  255. ['0_2_3_3_3', 02333],
  256. ['.Inf', -log(0)],
  257. ['-.Inf', log(0)],
  258. ["'686e444'", '686e444'],
  259. ['686e444', 646e444],
  260. ['123456789123456789123456789123456789', '123456789123456789123456789123456789'],
  261. ['"foo\r\nbar"', "foo\r\nbar"],
  262. ["'foo#bar'", 'foo#bar'],
  263. ["'foo # bar'", 'foo # bar'],
  264. ["'#cfcfcf'", '#cfcfcf'],
  265. ['::form_base.html.twig', '::form_base.html.twig'],
  266. // Pre-YAML-1.2 booleans
  267. ["'y'", 'y'],
  268. ["'n'", 'n'],
  269. ["'yes'", 'yes'],
  270. ["'no'", 'no'],
  271. ["'on'", 'on'],
  272. ["'off'", 'off'],
  273. ['2007-10-30', gmmktime(0, 0, 0, 10, 30, 2007)],
  274. ['2007-10-30T02:59:43Z', gmmktime(2, 59, 43, 10, 30, 2007)],
  275. ['2007-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 2007)],
  276. ['1960-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 1960)],
  277. ['1730-10-30T02:59:43Z', gmmktime(2, 59, 43, 10, 30, 1730)],
  278. ['"a \\"string\\" with \'quoted strings inside\'"', 'a "string" with \'quoted strings inside\''],
  279. ["'a \"string\" with ''quoted strings inside'''", 'a "string" with \'quoted strings inside\''],
  280. // sequences
  281. // urls are no key value mapping. see #3609. Valid yaml "key: value" mappings require a space after the colon
  282. ['[foo, http://urls.are/no/mappings, false, null, 12]', ['foo', 'http://urls.are/no/mappings', false, null, 12]],
  283. ['[ foo , bar , false , null , 12 ]', ['foo', 'bar', false, null, 12]],
  284. ['[\'foo,bar\', \'foo bar\']', ['foo,bar', 'foo bar']],
  285. // mappings
  286. ['{foo: bar,bar: foo,"false": false, "null": null,integer: 12}', ['foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12]],
  287. ['{ foo : bar, bar : foo, "false" : false, "null" : null, integer : 12 }', ['foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12]],
  288. ['{foo: \'bar\', bar: \'foo: bar\'}', ['foo' => 'bar', 'bar' => 'foo: bar']],
  289. ['{\'foo\': \'bar\', "bar": \'foo: bar\'}', ['foo' => 'bar', 'bar' => 'foo: bar']],
  290. ['{\'foo\'\'\': \'bar\', "bar\"": \'foo: bar\'}', ['foo\'' => 'bar', 'bar"' => 'foo: bar']],
  291. ['{\'foo: \': \'bar\', "bar: ": \'foo: bar\'}', ['foo: ' => 'bar', 'bar: ' => 'foo: bar']],
  292. ['{"foo:bar": "baz"}', ['foo:bar' => 'baz']],
  293. ['{"foo":"bar"}', ['foo' => 'bar']],
  294. // nested sequences and mappings
  295. ['[foo, [bar, foo]]', ['foo', ['bar', 'foo']]],
  296. ['[foo, {bar: foo}]', ['foo', ['bar' => 'foo']]],
  297. ['{ foo: {bar: foo} }', ['foo' => ['bar' => 'foo']]],
  298. ['{ foo: [bar, foo] }', ['foo' => ['bar', 'foo']]],
  299. ['{ foo:{bar: foo} }', ['foo' => ['bar' => 'foo']]],
  300. ['{ foo:[bar, foo] }', ['foo' => ['bar', 'foo']]],
  301. ['[ foo, [ bar, foo ] ]', ['foo', ['bar', 'foo']]],
  302. ['[{ foo: {bar: foo} }]', [['foo' => ['bar' => 'foo']]]],
  303. ['[foo, [bar, [foo, [bar, foo]], foo]]', ['foo', ['bar', ['foo', ['bar', 'foo']], 'foo']]],
  304. ['[foo, {bar: foo, foo: [foo, {bar: foo}]}, [foo, {bar: foo}]]', ['foo', ['bar' => 'foo', 'foo' => ['foo', ['bar' => 'foo']]], ['foo', ['bar' => 'foo']]]],
  305. ['[foo, bar: { foo: bar }]', ['foo', '1' => ['bar' => ['foo' => 'bar']]]],
  306. ['[foo, \'@foo.baz\', { \'%foo%\': \'foo is %foo%\', bar: \'%foo%\' }, true, \'@service_container\']', ['foo', '@foo.baz', ['%foo%' => 'foo is %foo%', 'bar' => '%foo%'], true, '@service_container']],
  307. ];
  308. }
  309. public function getTestsForParseWithMapObjects()
  310. {
  311. return [
  312. ['', ''],
  313. ['null', null],
  314. ['false', false],
  315. ['true', true],
  316. ['12', 12],
  317. ['-12', -12],
  318. ['"quoted string"', 'quoted string'],
  319. ["'quoted string'", 'quoted string'],
  320. ['12.30e+02', 12.30e+02],
  321. ['0x4D2', 0x4D2],
  322. ['02333', 02333],
  323. ['.Inf', -log(0)],
  324. ['-.Inf', log(0)],
  325. ["'686e444'", '686e444'],
  326. ['686e444', 646e444],
  327. ['123456789123456789123456789123456789', '123456789123456789123456789123456789'],
  328. ['"foo\r\nbar"', "foo\r\nbar"],
  329. ["'foo#bar'", 'foo#bar'],
  330. ["'foo # bar'", 'foo # bar'],
  331. ["'#cfcfcf'", '#cfcfcf'],
  332. ['::form_base.html.twig', '::form_base.html.twig'],
  333. ['2007-10-30', gmmktime(0, 0, 0, 10, 30, 2007)],
  334. ['2007-10-30T02:59:43Z', gmmktime(2, 59, 43, 10, 30, 2007)],
  335. ['2007-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 2007)],
  336. ['1960-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 1960)],
  337. ['1730-10-30T02:59:43Z', gmmktime(2, 59, 43, 10, 30, 1730)],
  338. ['"a \\"string\\" with \'quoted strings inside\'"', 'a "string" with \'quoted strings inside\''],
  339. ["'a \"string\" with ''quoted strings inside'''", 'a "string" with \'quoted strings inside\''],
  340. // sequences
  341. // urls are no key value mapping. see #3609. Valid yaml "key: value" mappings require a space after the colon
  342. ['[foo, http://urls.are/no/mappings, false, null, 12]', ['foo', 'http://urls.are/no/mappings', false, null, 12]],
  343. ['[ foo , bar , false , null , 12 ]', ['foo', 'bar', false, null, 12]],
  344. ['[\'foo,bar\', \'foo bar\']', ['foo,bar', 'foo bar']],
  345. // mappings
  346. ['{foo: bar,bar: foo,"false": false,"null": null,integer: 12}', (object) ['foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12], Yaml::PARSE_OBJECT_FOR_MAP],
  347. ['{ foo : bar, bar : foo, "false" : false, "null" : null, integer : 12 }', (object) ['foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12], Yaml::PARSE_OBJECT_FOR_MAP],
  348. ['{foo: \'bar\', bar: \'foo: bar\'}', (object) ['foo' => 'bar', 'bar' => 'foo: bar']],
  349. ['{\'foo\': \'bar\', "bar": \'foo: bar\'}', (object) ['foo' => 'bar', 'bar' => 'foo: bar']],
  350. ['{\'foo\'\'\': \'bar\', "bar\"": \'foo: bar\'}', (object) ['foo\'' => 'bar', 'bar"' => 'foo: bar']],
  351. ['{\'foo: \': \'bar\', "bar: ": \'foo: bar\'}', (object) ['foo: ' => 'bar', 'bar: ' => 'foo: bar']],
  352. ['{"foo:bar": "baz"}', (object) ['foo:bar' => 'baz']],
  353. ['{"foo":"bar"}', (object) ['foo' => 'bar']],
  354. // nested sequences and mappings
  355. ['[foo, [bar, foo]]', ['foo', ['bar', 'foo']]],
  356. ['[foo, {bar: foo}]', ['foo', (object) ['bar' => 'foo']]],
  357. ['{ foo: {bar: foo} }', (object) ['foo' => (object) ['bar' => 'foo']]],
  358. ['{ foo: [bar, foo] }', (object) ['foo' => ['bar', 'foo']]],
  359. ['[ foo, [ bar, foo ] ]', ['foo', ['bar', 'foo']]],
  360. ['[{ foo: {bar: foo} }]', [(object) ['foo' => (object) ['bar' => 'foo']]]],
  361. ['[foo, [bar, [foo, [bar, foo]], foo]]', ['foo', ['bar', ['foo', ['bar', 'foo']], 'foo']]],
  362. ['[foo, {bar: foo, foo: [foo, {bar: foo}]}, [foo, {bar: foo}]]', ['foo', (object) ['bar' => 'foo', 'foo' => ['foo', (object) ['bar' => 'foo']]], ['foo', (object) ['bar' => 'foo']]]],
  363. ['[foo, bar: { foo: bar }]', ['foo', '1' => (object) ['bar' => (object) ['foo' => 'bar']]]],
  364. ['[foo, \'@foo.baz\', { \'%foo%\': \'foo is %foo%\', bar: \'%foo%\' }, true, \'@service_container\']', ['foo', '@foo.baz', (object) ['%foo%' => 'foo is %foo%', 'bar' => '%foo%'], true, '@service_container']],
  365. ['{}', new \stdClass()],
  366. ['{ foo : bar, bar : {} }', (object) ['foo' => 'bar', 'bar' => new \stdClass()]],
  367. ['{ foo : [], bar : {} }', (object) ['foo' => [], 'bar' => new \stdClass()]],
  368. ['{foo: \'bar\', bar: {} }', (object) ['foo' => 'bar', 'bar' => new \stdClass()]],
  369. ['{\'foo\': \'bar\', "bar": {}}', (object) ['foo' => 'bar', 'bar' => new \stdClass()]],
  370. ['{\'foo\': \'bar\', "bar": \'{}\'}', (object) ['foo' => 'bar', 'bar' => '{}']],
  371. ['[foo, [{}, {}]]', ['foo', [new \stdClass(), new \stdClass()]]],
  372. ['[foo, [[], {}]]', ['foo', [[], new \stdClass()]]],
  373. ['[foo, [[{}, {}], {}]]', ['foo', [[new \stdClass(), new \stdClass()], new \stdClass()]]],
  374. ['[foo, {bar: {}}]', ['foo', '1' => (object) ['bar' => new \stdClass()]]],
  375. ];
  376. }
  377. public function getTestsForDump()
  378. {
  379. return [
  380. ['null', null],
  381. ['false', false],
  382. ['true', true],
  383. ['12', 12],
  384. ["'1_2'", '1_2'],
  385. ['_12', '_12'],
  386. ["'12_'", '12_'],
  387. ["'quoted string'", 'quoted string'],
  388. ['!!float 1230', 12.30e+02],
  389. ['1234', 0x4D2],
  390. ['1243', 02333],
  391. ["'0x_4_D_2_'", '0x_4_D_2_'],
  392. ["'0_2_3_3_3'", '0_2_3_3_3'],
  393. ['.Inf', -log(0)],
  394. ['-.Inf', log(0)],
  395. ["'686e444'", '686e444'],
  396. ['"foo\r\nbar"', "foo\r\nbar"],
  397. ["'foo#bar'", 'foo#bar'],
  398. ["'foo # bar'", 'foo # bar'],
  399. ["'#cfcfcf'", '#cfcfcf'],
  400. ["'a \"string\" with ''quoted strings inside'''", 'a "string" with \'quoted strings inside\''],
  401. ["'-dash'", '-dash'],
  402. ["'-'", '-'],
  403. // Pre-YAML-1.2 booleans
  404. ["'y'", 'y'],
  405. ["'n'", 'n'],
  406. ["'yes'", 'yes'],
  407. ["'no'", 'no'],
  408. ["'on'", 'on'],
  409. ["'off'", 'off'],
  410. // sequences
  411. ['[foo, bar, false, null, 12]', ['foo', 'bar', false, null, 12]],
  412. ['[\'foo,bar\', \'foo bar\']', ['foo,bar', 'foo bar']],
  413. // mappings
  414. ['{ foo: bar, bar: foo, \'false\': false, \'null\': null, integer: 12 }', ['foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12]],
  415. ['{ foo: bar, bar: \'foo: bar\' }', ['foo' => 'bar', 'bar' => 'foo: bar']],
  416. // nested sequences and mappings
  417. ['[foo, [bar, foo]]', ['foo', ['bar', 'foo']]],
  418. ['[foo, [bar, [foo, [bar, foo]], foo]]', ['foo', ['bar', ['foo', ['bar', 'foo']], 'foo']]],
  419. ['{ foo: { bar: foo } }', ['foo' => ['bar' => 'foo']]],
  420. ['[foo, { bar: foo }]', ['foo', ['bar' => 'foo']]],
  421. ['[foo, { bar: foo, foo: [foo, { bar: foo }] }, [foo, { bar: foo }]]', ['foo', ['bar' => 'foo', 'foo' => ['foo', ['bar' => 'foo']]], ['foo', ['bar' => 'foo']]]],
  422. ['[foo, \'@foo.baz\', { \'%foo%\': \'foo is %foo%\', bar: \'%foo%\' }, true, \'@service_container\']', ['foo', '@foo.baz', ['%foo%' => 'foo is %foo%', 'bar' => '%foo%'], true, '@service_container']],
  423. ['{ foo: { bar: { 1: 2, baz: 3 } } }', ['foo' => ['bar' => [1 => 2, 'baz' => 3]]]],
  424. ];
  425. }
  426. /**
  427. * @dataProvider getTimestampTests
  428. */
  429. public function testParseTimestampAsUnixTimestampByDefault($yaml, $year, $month, $day, $hour, $minute, $second)
  430. {
  431. $this->assertSame(gmmktime($hour, $minute, $second, $month, $day, $year), Inline::parse($yaml));
  432. }
  433. /**
  434. * @dataProvider getTimestampTests
  435. */
  436. public function testParseTimestampAsDateTimeObject($yaml, $year, $month, $day, $hour, $minute, $second, $timezone)
  437. {
  438. $expected = new \DateTime($yaml);
  439. $expected->setTimeZone(new \DateTimeZone('UTC'));
  440. $expected->setDate($year, $month, $day);
  441. $expected->setTime($hour, $minute, $second, 1000000 * ($second - (int) $second));
  442. $date = Inline::parse($yaml, Yaml::PARSE_DATETIME);
  443. $this->assertEquals($expected, $date);
  444. $this->assertSame($timezone, $date->format('O'));
  445. }
  446. public function getTimestampTests()
  447. {
  448. return [
  449. 'canonical' => ['2001-12-15T02:59:43.1Z', 2001, 12, 15, 2, 59, 43.1, '+0000'],
  450. 'ISO-8601' => ['2001-12-15t21:59:43.10-05:00', 2001, 12, 16, 2, 59, 43.1, '-0500'],
  451. 'spaced' => ['2001-12-15 21:59:43.10 -5', 2001, 12, 16, 2, 59, 43.1, '-0500'],
  452. 'date' => ['2001-12-15', 2001, 12, 15, 0, 0, 0, '+0000'],
  453. ];
  454. }
  455. /**
  456. * @dataProvider getTimestampTests
  457. */
  458. public function testParseNestedTimestampListAsDateTimeObject($yaml, $year, $month, $day, $hour, $minute, $second)
  459. {
  460. $expected = new \DateTime($yaml);
  461. $expected->setTimeZone(new \DateTimeZone('UTC'));
  462. $expected->setDate($year, $month, $day);
  463. $expected->setTime($hour, $minute, $second, 1000000 * ($second - (int) $second));
  464. $expectedNested = ['nested' => [$expected]];
  465. $yamlNested = "{nested: [$yaml]}";
  466. $this->assertEquals($expectedNested, Inline::parse($yamlNested, Yaml::PARSE_DATETIME));
  467. }
  468. /**
  469. * @dataProvider getDateTimeDumpTests
  470. */
  471. public function testDumpDateTime($dateTime, $expected)
  472. {
  473. $this->assertSame($expected, Inline::dump($dateTime));
  474. }
  475. public function getDateTimeDumpTests()
  476. {
  477. $tests = [];
  478. $dateTime = new \DateTime('2001-12-15 21:59:43', new \DateTimeZone('UTC'));
  479. $tests['date-time-utc'] = [$dateTime, '2001-12-15T21:59:43+00:00'];
  480. $dateTime = new \DateTimeImmutable('2001-07-15 21:59:43', new \DateTimeZone('Europe/Berlin'));
  481. $tests['immutable-date-time-europe-berlin'] = [$dateTime, '2001-07-15T21:59:43+02:00'];
  482. return $tests;
  483. }
  484. /**
  485. * @dataProvider getBinaryData
  486. */
  487. public function testParseBinaryData($data)
  488. {
  489. $this->assertSame('Hello world', Inline::parse($data));
  490. }
  491. public function getBinaryData()
  492. {
  493. return [
  494. 'enclosed with double quotes' => ['!!binary "SGVsbG8gd29ybGQ="'],
  495. 'enclosed with single quotes' => ["!!binary 'SGVsbG8gd29ybGQ='"],
  496. 'containing spaces' => ['!!binary "SGVs bG8gd 29ybGQ="'],
  497. ];
  498. }
  499. /**
  500. * @dataProvider getInvalidBinaryData
  501. */
  502. public function testParseInvalidBinaryData($data, $expectedMessage)
  503. {
  504. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  505. $this->expectExceptionMessageRegExp($expectedMessage);
  506. Inline::parse($data);
  507. }
  508. public function getInvalidBinaryData()
  509. {
  510. return [
  511. 'length not a multiple of four' => ['!!binary "SGVsbG8d29ybGQ="', '/The normalized base64 encoded data \(data without whitespace characters\) length must be a multiple of four \(\d+ bytes given\)/'],
  512. 'invalid characters' => ['!!binary "SGVsbG8#d29ybGQ="', '/The base64 encoded data \(.*\) contains invalid characters/'],
  513. 'too many equals characters' => ['!!binary "SGVsbG8gd29yb==="', '/The base64 encoded data \(.*\) contains invalid characters/'],
  514. 'misplaced equals character' => ['!!binary "SGVsbG8gd29ybG=Q"', '/The base64 encoded data \(.*\) contains invalid characters/'],
  515. ];
  516. }
  517. public function testNotSupportedMissingValue()
  518. {
  519. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  520. $this->expectExceptionMessage('Malformed inline YAML string: {this, is not, supported} at line 1.');
  521. Inline::parse('{this, is not, supported}');
  522. }
  523. public function testVeryLongQuotedStrings()
  524. {
  525. $longStringWithQuotes = str_repeat("x\r\n\\\"x\"x", 1000);
  526. $yamlString = Inline::dump(['longStringWithQuotes' => $longStringWithQuotes]);
  527. $arrayFromYaml = Inline::parse($yamlString);
  528. $this->assertEquals($longStringWithQuotes, $arrayFromYaml['longStringWithQuotes']);
  529. }
  530. public function testMappingKeysCannotBeOmitted()
  531. {
  532. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  533. $this->expectExceptionMessage('Missing mapping key');
  534. Inline::parse('{: foo}');
  535. }
  536. /**
  537. * @dataProvider getTestsForNullValues
  538. */
  539. public function testParseMissingMappingValueAsNull($yaml, $expected)
  540. {
  541. $this->assertSame($expected, Inline::parse($yaml));
  542. }
  543. public function getTestsForNullValues()
  544. {
  545. return [
  546. 'null before closing curly brace' => ['{foo:}', ['foo' => null]],
  547. 'null before comma' => ['{foo:, bar: baz}', ['foo' => null, 'bar' => 'baz']],
  548. ];
  549. }
  550. public function testTheEmptyStringIsAValidMappingKey()
  551. {
  552. $this->assertSame(['' => 'foo'], Inline::parse('{ "": foo }'));
  553. }
  554. /**
  555. * @dataProvider getNotPhpCompatibleMappingKeyData
  556. */
  557. public function testImplicitStringCastingOfMappingKeysIsDeprecated($yaml, $expected)
  558. {
  559. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  560. $this->expectExceptionMessage('Implicit casting of incompatible mapping keys to strings is not supported. Quote your evaluable mapping keys instead');
  561. $this->assertSame($expected, Inline::parse($yaml));
  562. }
  563. public function getNotPhpCompatibleMappingKeyData()
  564. {
  565. return [
  566. 'boolean-true' => ['{true: "foo"}', ['true' => 'foo']],
  567. 'boolean-false' => ['{false: "foo"}', ['false' => 'foo']],
  568. 'null' => ['{null: "foo"}', ['null' => 'foo']],
  569. 'float' => ['{0.25: "foo"}', ['0.25' => 'foo']],
  570. ];
  571. }
  572. public function testTagWithoutValueInSequence()
  573. {
  574. $value = Inline::parse('[!foo]', Yaml::PARSE_CUSTOM_TAGS);
  575. $this->assertInstanceOf(TaggedValue::class, $value[0]);
  576. $this->assertSame('foo', $value[0]->getTag());
  577. $this->assertSame('', $value[0]->getValue());
  578. }
  579. public function testTagWithEmptyValueInSequence()
  580. {
  581. $value = Inline::parse('[!foo ""]', Yaml::PARSE_CUSTOM_TAGS);
  582. $this->assertInstanceOf(TaggedValue::class, $value[0]);
  583. $this->assertSame('foo', $value[0]->getTag());
  584. $this->assertSame('', $value[0]->getValue());
  585. }
  586. public function testTagWithoutValueInMapping()
  587. {
  588. $value = Inline::parse('{foo: !bar}', Yaml::PARSE_CUSTOM_TAGS);
  589. $this->assertInstanceOf(TaggedValue::class, $value['foo']);
  590. $this->assertSame('bar', $value['foo']->getTag());
  591. $this->assertSame('', $value['foo']->getValue());
  592. }
  593. public function testTagWithEmptyValueInMapping()
  594. {
  595. $value = Inline::parse('{foo: !bar ""}', Yaml::PARSE_CUSTOM_TAGS);
  596. $this->assertInstanceOf(TaggedValue::class, $value['foo']);
  597. $this->assertSame('bar', $value['foo']->getTag());
  598. $this->assertSame('', $value['foo']->getValue());
  599. }
  600. public function testUnfinishedInlineMap()
  601. {
  602. $this->expectException('Symfony\Component\Yaml\Exception\ParseException');
  603. $this->expectExceptionMessage('Unexpected end of line, expected one of ",}" at line 1 (near "{abc: \'def\'").');
  604. Inline::parse("{abc: 'def'");
  605. }
  606. }