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

Skip to content

Commit 40a2cfb

Browse files
committed
feature #48022 [Yaml] Fix Yaml Parser with quote end in a new line (maxbeckers)
This PR was submitted for the 5.4 branch but it was merged into the 7.1 branch instead. Discussion ---------- [Yaml] Fix Yaml Parser with quote end in a new line | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | Fix #33082 | License | MIT | Doc PR | N/A This is a fix for issue #33082. The bug described in the ticket breaks on a ending quote in a new line: ``` foo: bar: 'baz ' baz: 'Lorem' ``` Before the fix: `Symfony\Component\Yaml\Exception\ParseException: Malformed inline YAML string: 'baz at line 4.` There was already a PR #33119, which was closed because of problems. Commits ------- 21cec3f [Yaml] Fix Yaml Parser with quote end in a new line
2 parents d2d36b5 + 21cec3f commit 40a2cfb

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

src/Symfony/Component/Yaml/Parser.php

+57
Original file line numberDiff line numberDiff line change
@@ -597,8 +597,12 @@ private function getNextEmbedBlock(?int $indentation = null, bool $inSequence =
597597
}
598598

599599
$data = [];
600+
$isInMultiLineQuote = false;
600601

601602
if ($this->getCurrentLineIndentation() >= $newIndent) {
603+
if ($this->isCurrentLineMultiLineQuoteStart()) {
604+
$isInMultiLineQuote = true;
605+
}
602606
$data[] = substr($this->currentLine, $newIndent ?? 0);
603607
} elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
604608
$data[] = $this->currentLine;
@@ -635,6 +639,16 @@ private function getNextEmbedBlock(?int $indentation = null, bool $inSequence =
635639
if ($this->isCurrentLineBlank()) {
636640
$data[] = substr($this->currentLine, $newIndent);
637641
continue;
642+
} elseif (!$isInMultiLineQuote && $this->isCurrentLineMultiLineQuoteStart()) {
643+
$isInMultiLineQuote = true;
644+
$data[] = substr($this->currentLine, $newIndent);
645+
continue;
646+
} elseif ($isInMultiLineQuote) {
647+
$data[] = $this->currentLine;
648+
if ("'" === (rtrim($this->currentLine)[-1] ?? '')) {
649+
$isInMultiLineQuote = false;
650+
}
651+
continue;
638652
}
639653

640654
if ($indent >= $newIndent) {
@@ -965,6 +979,49 @@ private function isCurrentLineLastLineInDocument(): bool
965979
return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
966980
}
967981

982+
/**
983+
* Returns true if the current line is the beginning of a multiline quoted block.
984+
*/
985+
private function isCurrentLineMultiLineQuoteStart(): bool
986+
{
987+
$trimmedLine = trim($this->currentLine);
988+
$trimmedLineLength = \strlen($trimmedLine);
989+
$quoteCount = 0;
990+
$value = '';
991+
// check if the key is quoted
992+
for ($i = 0; $i < $trimmedLineLength; ++$i) {
993+
$char = $trimmedLine[$i];
994+
if ("'" === $char) {
995+
++$quoteCount;
996+
} elseif (':' === $char && 0 === $quoteCount % 2 && ($i === $trimmedLineLength - 1 || ' ' === $trimmedLine[$i + 1])) {
997+
// key and value are separated by the first colon after the (quoted) key followed by a space or linebreak
998+
$value = trim(substr($trimmedLine, ++$i), ' ');
999+
break;
1000+
}
1001+
}
1002+
1003+
if (0 !== strpos($value, "'")) {
1004+
return false;
1005+
}
1006+
1007+
$lineEndQuoteCount = 0;
1008+
for ($i = \strlen($value) - 1; $i > 0; --$i) {
1009+
$char = $value[$i];
1010+
if ("'" === $char) {
1011+
++$lineEndQuoteCount;
1012+
} else {
1013+
break;
1014+
}
1015+
}
1016+
1017+
return 0 === $lineEndQuoteCount % 2;
1018+
}
1019+
1020+
/**
1021+
* Cleanups a YAML string to be parsed.
1022+
*
1023+
* @param string $value The input YAML string
1024+
*/
9681025
private function cleanup(string $value): string
9691026
{
9701027
$value = str_replace(["\r\n", "\r"], "\n", $value);

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

+51
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Yaml\Tests;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Yaml\Exception\ParseException;
1516
use Symfony\Component\Yaml\Yaml;
1617

1718
class YamlTest extends TestCase
@@ -24,6 +25,56 @@ public function testParseAndDump()
2425
$this->assertEquals($data, $parsed);
2526
}
2627

28+
public function testParseWithMultilineQuotes()
29+
{
30+
$yaml = <<<YAML
31+
foo:
32+
bar: 'baz
33+
biz
34+
35+
'
36+
baz: 'Lorem
37+
38+
ipsum'
39+
error: Une erreur s'est produite.
40+
trialMode: 'période d''essai'
41+
double_line: 'Les utilisateurs sélectionnés
42+
n''ont pas d''email.
43+
44+
'
45+
a: 'b''
46+
c'
47+
empty: ''
48+
foo:bar: 'foobar'
49+
YAML;
50+
51+
$this->assertSame(['foo' => [
52+
'bar' => "baz biz\n",
53+
'baz' => "Lorem\nipsum",
54+
'error' => "Une erreur s'est produite.",
55+
'trialMode' => "période d'essai",
56+
'double_line' => "Les utilisateurs sélectionnés n'ont pas d'email.\n",
57+
'a' => "b' c",
58+
'empty' => '',
59+
'foo:bar' => 'foobar',
60+
]], Yaml::parse($yaml));
61+
}
62+
63+
public function testParseWithMultilineQuotesExpectException()
64+
{
65+
$yaml = <<<YAML
66+
foo:
67+
bar: 'baz
68+
69+
'
70+
'
71+
YAML;
72+
73+
$this->expectException(ParseException::class);
74+
$this->expectExceptionMessage('Unable to parse at line 5 (near "\'").');
75+
Yaml::parse($yaml);
76+
}
77+
2778
public function testZeroIndentationThrowsException()
2879
{
2980
$this->expectException(\InvalidArgumentException::class);

0 commit comments

Comments
 (0)