Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit aed0273

Browse files
committed
fix parsing inline YAML spanning multiple lines
1 parent 789448b commit aed0273

File tree

5 files changed

+388
-6
lines changed

5 files changed

+388
-6
lines changed

src/Symfony/Component/Yaml/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
4.4.0
55
-----
66

7+
* Added support for parsing the inline notation spanning multiple lines.
78
* Added support to dump `null` as `~` by using the `Yaml::DUMP_NULL_AS_TILDE` flag.
89
* deprecated accepting STDIN implicitly when using the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit.
910

src/Symfony/Component/Yaml/Inline.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ public static function parseScalar(string $scalar, int $flags = 0, array $delimi
274274
$output = self::parseQuotedScalar($scalar, $i);
275275

276276
if (null !== $delimiters) {
277-
$tmp = ltrim(substr($scalar, $i), ' ');
277+
$tmp = ltrim(substr($scalar, $i), " \n");
278278
if ('' === $tmp) {
279279
throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
280280
}
@@ -419,6 +419,7 @@ private static function parseMapping(string $mapping, int $flags, int &$i = 0, a
419419
switch ($mapping[$i]) {
420420
case ' ':
421421
case ',':
422+
case "\n":
422423
++$i;
423424
continue 2;
424425
case '}':
@@ -450,7 +451,7 @@ private static function parseMapping(string $mapping, int $flags, int &$i = 0, a
450451
}
451452
}
452453

453-
if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}'], true))) {
454+
if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}', "\n"], true))) {
454455
throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping);
455456
}
456457

@@ -459,7 +460,7 @@ private static function parseMapping(string $mapping, int $flags, int &$i = 0, a
459460
}
460461

461462
while ($i < $len) {
462-
if (':' === $mapping[$i] || ' ' === $mapping[$i]) {
463+
if (':' === $mapping[$i] || ' ' === $mapping[$i] || "\n" === $mapping[$i]) {
463464
++$i;
464465

465466
continue;
@@ -508,7 +509,7 @@ private static function parseMapping(string $mapping, int $flags, int &$i = 0, a
508509
}
509510
break;
510511
default:
511-
$value = self::parseScalar($mapping, $flags, [',', '}'], $i, null === $tag, $references);
512+
$value = self::parseScalar($mapping, $flags, [',', '}', "\n"], $i, null === $tag, $references);
512513
// Spec: Keys MUST be unique; first one wins.
513514
// Parser cannot abort this mapping earlier, since lines
514515
// are processed sequentially.

src/Symfony/Component/Yaml/Parser.php

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,61 @@ private function doParse(string $value, int $flags)
353353
$this->refs[$isRef] = $data[$key];
354354
array_pop($this->refsBeingParsed);
355355
}
356+
} elseif ('"' === $this->currentLine[0] || "'" === $this->currentLine[0]) {
357+
if (null !== $context) {
358+
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
359+
}
360+
361+
try {
362+
return Inline::parse($this->parseQuotedString($this->currentLine), $flags, $this->refs);
363+
} catch (ParseException $e) {
364+
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
365+
$e->setSnippet($this->currentLine);
366+
367+
throw $e;
368+
}
369+
} elseif ('{' === $this->currentLine[0]) {
370+
if (null !== $context) {
371+
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
372+
}
373+
374+
try {
375+
$parsedMapping = Inline::parse($this->lexInlineMapping($this->currentLine), $flags, $this->refs);
376+
377+
while ($this->moveToNextLine()) {
378+
if (!$this->isCurrentLineEmpty()) {
379+
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
380+
}
381+
}
382+
383+
return $parsedMapping;
384+
} catch (ParseException $e) {
385+
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
386+
$e->setSnippet($this->currentLine);
387+
388+
throw $e;
389+
}
390+
} elseif ('[' === $this->currentLine[0]) {
391+
if (null !== $context) {
392+
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
393+
}
394+
395+
try {
396+
$parsedSequence = Inline::parse($this->lexInlineSequence($this->currentLine), $flags, $this->refs);
397+
398+
while ($this->moveToNextLine()) {
399+
if (!$this->isCurrentLineEmpty()) {
400+
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
401+
}
402+
}
403+
404+
return $parsedSequence;
405+
} catch (ParseException $e) {
406+
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
407+
$e->setSnippet($this->currentLine);
408+
409+
throw $e;
410+
}
356411
} else {
357412
// multiple documents are not supported
358413
if ('---' === $this->currentLine) {
@@ -678,6 +733,12 @@ private function parseValue(string $value, int $flags, string $context)
678733
}
679734

680735
try {
736+
if ('' !== $value && '{' === $value[0]) {
737+
return Inline::parse($this->lexInlineMapping($value), $flags, $this->refs);
738+
} elseif ('' !== $value && '[' === $value[0]) {
739+
return Inline::parse($this->lexInlineSequence($value), $flags, $this->refs);
740+
}
741+
681742
$quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null;
682743

683744
// do not take following lines into account when the current line is a quoted single line value
@@ -1072,4 +1133,122 @@ private function getLineTag(string $value, int $flags, bool $nextLineCheck = tru
10721133

10731134
throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
10741135
}
1136+
1137+
private function parseQuotedString($yaml)
1138+
{
1139+
if ('' === $yaml || ('"' !== $yaml[0] && "'" !== $yaml[0])) {
1140+
throw new \InvalidArgumentException(sprintf('"%s" is not a quoted string.', $yaml));
1141+
}
1142+
1143+
$lines = [$yaml];
1144+
1145+
while ($this->moveToNextLine()) {
1146+
$lines[] = $this->currentLine;
1147+
1148+
if (!$this->isCurrentLineEmpty() && $yaml[0] === $this->currentLine[-1]) {
1149+
break;
1150+
}
1151+
}
1152+
1153+
$value = '';
1154+
1155+
for ($i = 0, $linesCount = \count($lines), $previousLineWasNewline = false, $previousLineWasTerminatedWithBackslash = false; $i < $linesCount; ++$i) {
1156+
if ('' === trim($lines[$i])) {
1157+
$value .= "\n";
1158+
} elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
1159+
$value .= ' ';
1160+
}
1161+
1162+
if ('' !== trim($lines[$i]) && '\\' === substr($lines[$i], -1)) {
1163+
$value .= ltrim(substr($lines[$i], 0, -1));
1164+
} elseif ('' !== trim($lines[$i])) {
1165+
$value .= trim($lines[$i]);
1166+
}
1167+
1168+
if ('' === trim($lines[$i])) {
1169+
$previousLineWasNewline = true;
1170+
$previousLineWasTerminatedWithBackslash = false;
1171+
} elseif ('\\' === substr($lines[$i], -1)) {
1172+
$previousLineWasNewline = false;
1173+
$previousLineWasTerminatedWithBackslash = true;
1174+
} else {
1175+
$previousLineWasNewline = false;
1176+
$previousLineWasTerminatedWithBackslash = false;
1177+
}
1178+
}
1179+
1180+
return $value;
1181+
1182+
for ($i = 1; isset($yaml[$i]) && $quotation !== $yaml[$i]; ++$i) {
1183+
}
1184+
1185+
// quoted single line string
1186+
if (isset($yaml[$i]) && $quotation === $yaml[$i]) {
1187+
return $yaml;
1188+
}
1189+
1190+
$lines = array($yaml);
1191+
1192+
while ($this->moveToNextLine()) {
1193+
for ($i = 1; isset($this->currentLine[$i]) && $quotation !== $this->currentLine[$i]; ++$i) {
1194+
}
1195+
1196+
$lines[] = trim($this->currentLine);
1197+
1198+
if (isset($this->currentLine[$i]) && $quotation === $this->currentLine[$i]) {
1199+
break;
1200+
}
1201+
}
1202+
}
1203+
1204+
private function lexInlineMapping(string $yaml): string
1205+
{
1206+
if ('' === $yaml || '{' !== $yaml[0]) {
1207+
throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml));
1208+
}
1209+
1210+
for ($i = 1; isset($yaml[$i]) && '}' !== $yaml[$i]; ++$i) {
1211+
}
1212+
1213+
if (isset($yaml[$i]) && '}' === $yaml[$i]) {
1214+
return $yaml;
1215+
}
1216+
1217+
$lines = [$yaml];
1218+
1219+
while ($this->moveToNextLine()) {
1220+
$lines[] = $this->currentLine;
1221+
}
1222+
1223+
return implode("\n", $lines);
1224+
}
1225+
1226+
private function lexInlineSequence($yaml)
1227+
{
1228+
if ('' === $yaml || '[' !== $yaml[0]) {
1229+
throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml));
1230+
}
1231+
1232+
for ($i = 1; isset($yaml[$i]) && ']' !== $yaml[$i]; ++$i) {
1233+
}
1234+
1235+
if (isset($yaml[$i]) && ']' === $yaml[$i]) {
1236+
return $yaml;
1237+
}
1238+
1239+
$value = $yaml;
1240+
1241+
while ($this->moveToNextLine()) {
1242+
for ($i = 1; isset($this->currentLine[$i]) && ']' !== $this->currentLine[$i]; ++$i) {
1243+
}
1244+
1245+
$value .= trim($this->currentLine);
1246+
1247+
if (isset($this->currentLine[$i]) && ']' === $this->currentLine[$i]) {
1248+
break;
1249+
}
1250+
}
1251+
1252+
return $value;
1253+
}
10751254
}

src/Symfony/Component/Yaml/Tests/InlineTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -711,7 +711,7 @@ public function testTagWithEmptyValueInMapping()
711711
public function testUnfinishedInlineMap()
712712
{
713713
$this->expectException('Symfony\Component\Yaml\Exception\ParseException');
714-
$this->expectExceptionMessage('Unexpected end of line, expected one of ",}" at line 1 (near "{abc: \'def\'").');
714+
$this->expectExceptionMessage("Unexpected end of line, expected one of \",}\n\" at line 1 (near \"{abc: 'def'\").");
715715
Inline::parse("{abc: 'def'");
716716
}
717717
}

0 commit comments

Comments
 (0)