Skip to content

Commit 4cf7ec1

Browse files
committed
feature #33658 [Yaml] fix parsing inline YAML spanning multiple lines (xabbuh)
This PR was merged into the 4.4 branch. Discussion ---------- [Yaml] fix parsing inline YAML spanning multiple lines | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Fix #25239 #25379 #31333 | License | MIT | Doc PR | Commits ------- 85a5c31 fix parsing inline YAML spanning multiple lines
2 parents b180208 + 85a5c31 commit 4cf7ec1

File tree

5 files changed

+387
-6
lines changed

5 files changed

+387
-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 = [$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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy