Skip to content

Commit da7fc36

Browse files
committed
[Yaml] properly count skipped comment lines
1 parent 326465d commit da7fc36

File tree

2 files changed

+66
-60
lines changed

2 files changed

+66
-60
lines changed

src/Symfony/Component/Yaml/Parser.php

+47-60
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,21 @@ class Parser
3030
private $currentLineNb = -1;
3131
private $currentLine = '';
3232
private $refs = array();
33+
private $skippedLineNumbers = array();
34+
private $locallySkippedLineNumbers = array();
3335

3436
/**
3537
* Constructor.
3638
*
3739
* @param int $offset The offset of YAML document (used for line numbers in error messages)
3840
* @param int|null $totalNumberOfLines The overall number of lines being parsed
41+
* @param int[] $skippedLineNumbers Number of comment lines that have been skipped by the parser
3942
*/
40-
public function __construct($offset = 0, $totalNumberOfLines = null)
43+
public function __construct($offset = 0, $totalNumberOfLines = null, array $skippedLineNumbers = array())
4144
{
4245
$this->offset = $offset;
4346
$this->totalNumberOfLines = $totalNumberOfLines;
47+
$this->skippedLineNumbers = $skippedLineNumbers;
4448
}
4549

4650
/**
@@ -101,25 +105,18 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
101105

102106
// array
103107
if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
104-
$c = $this->getRealCurrentLineNb() + 1;
105-
$parser = new self($c, $this->totalNumberOfLines);
106-
$parser->refs = &$this->refs;
107-
$data[] = $parser->parse($this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap);
108+
$data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap);
108109
} else {
109110
if (isset($values['leadspaces'])
110111
&& preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $values['value'], $matches)
111112
) {
112113
// this is a compact notation element, add to next block and parse
113-
$c = $this->getRealCurrentLineNb();
114-
$parser = new self($c, $this->totalNumberOfLines);
115-
$parser->refs = &$this->refs;
116-
117114
$block = $values['value'];
118115
if ($this->isNextLineIndented()) {
119116
$block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + strlen($values['leadspaces']) + 1);
120117
}
121118

122-
$data[] = $parser->parse($block, $exceptionOnInvalidType, $objectSupport, $objectForMap);
119+
$data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $exceptionOnInvalidType, $objectSupport, $objectForMap);
123120
} else {
124121
$data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap);
125122
}
@@ -175,10 +172,7 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
175172
} else {
176173
$value = $this->getNextEmbedBlock();
177174
}
178-
$c = $this->getRealCurrentLineNb() + 1;
179-
$parser = new self($c, $this->totalNumberOfLines);
180-
$parser->refs = &$this->refs;
181-
$parsed = $parser->parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
175+
$parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
182176

183177
if (!is_array($parsed)) {
184178
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
@@ -226,10 +220,7 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
226220
$data[$key] = null;
227221
}
228222
} else {
229-
$c = $this->getRealCurrentLineNb() + 1;
230-
$parser = new self($c, $this->totalNumberOfLines);
231-
$parser->refs = &$this->refs;
232-
$value = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap);
223+
$value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap);
233224
// Spec: Keys MUST be unique; first one wins.
234225
// But overwriting is allowed when a merge node is used in current block.
235226
if ($allowOverwrite || !isset($data[$key])) {
@@ -317,14 +308,42 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
317308
return empty($data) ? null : $data;
318309
}
319310

311+
private function parseBlock($offset, $yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap)
312+
{
313+
$skippedLineNumbers = $this->skippedLineNumbers;
314+
315+
foreach ($this->locallySkippedLineNumbers as $lineNumber) {
316+
if ($lineNumber < $offset) {
317+
continue;
318+
}
319+
320+
$skippedLineNumbers[] = $lineNumber;
321+
}
322+
323+
$parser = new self($offset, $this->totalNumberOfLines, $skippedLineNumbers);
324+
$parser->refs = &$this->refs;
325+
326+
return $parser->parse($yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap);
327+
}
328+
320329
/**
321330
* Returns the current line number (takes the offset into account).
322331
*
323332
* @return int The current line number
324333
*/
325334
private function getRealCurrentLineNb()
326335
{
327-
return $this->currentLineNb + $this->offset;
336+
$realCurrentLineNumber = $this->currentLineNb + $this->offset;
337+
338+
foreach ($this->skippedLineNumbers as $skippedLineNumber) {
339+
if ($skippedLineNumber > $realCurrentLineNumber) {
340+
break;
341+
}
342+
343+
++$realCurrentLineNumber;
344+
}
345+
346+
return $realCurrentLineNumber;
328347
}
329348

330349
/**
@@ -426,7 +445,15 @@ private function getNextEmbedBlock($indentation = null, $inSequence = false)
426445
}
427446

428447
// we ignore "comment" lines only when we are not inside a scalar block
429-
if (empty($blockScalarIndentations) && $this->isCurrentLineComment() && false === $this->checkIfPreviousNonCommentLineIsCollectionItem()) {
448+
if (empty($blockScalarIndentations) && $this->isCurrentLineComment()) {
449+
// remember ignored comment lines (they are used later in nested
450+
// parser calls to determine real line numbers)
451+
//
452+
// CAUTION: beware to not populate the global property here as it
453+
// will otherwise influence the getRealCurrentLineNb() call here
454+
// for consecutive comment lines and subsequent embedded blocks
455+
$this->locallySkippedLineNumbers[] = $this->getRealCurrentLineNb();
456+
430457
continue;
431458
}
432459

@@ -786,44 +813,4 @@ private function isBlockScalarHeader()
786813
{
787814
return (bool) preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $this->currentLine);
788815
}
789-
790-
/**
791-
* Returns true if the current line is a collection item.
792-
*
793-
* @return bool
794-
*/
795-
private function isCurrentLineCollectionItem()
796-
{
797-
$ltrimmedLine = ltrim($this->currentLine, ' ');
798-
799-
return '' !== $ltrimmedLine && '-' === $ltrimmedLine[0];
800-
}
801-
802-
/**
803-
* Tests whether the current comment line is in a collection.
804-
*
805-
* @return bool
806-
*/
807-
private function checkIfPreviousNonCommentLineIsCollectionItem()
808-
{
809-
$isCollectionItem = false;
810-
$moves = 0;
811-
while ($this->moveToPreviousLine()) {
812-
++$moves;
813-
// If previous line is a comment, move back again.
814-
if ($this->isCurrentLineComment()) {
815-
continue;
816-
}
817-
$isCollectionItem = $this->isCurrentLineCollectionItem();
818-
break;
819-
}
820-
821-
// Move parser back to previous line.
822-
while ($moves > 0) {
823-
$this->moveToNextLine();
824-
--$moves;
825-
}
826-
827-
return $isCollectionItem;
828-
}
829816
}

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

+19
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,25 @@ public function testSequenceInMappingStartedBySingleDashLine()
607607
$this->assertSame($expected, $this->parser->parse($yaml));
608608
}
609609

610+
public function testSequenceFollowedByCommentEmbeddedInMapping()
611+
{
612+
$yaml = <<<EOT
613+
a:
614+
b:
615+
- c
616+
# comment
617+
d: e
618+
EOT;
619+
$expected = array(
620+
'a' => array(
621+
'b' => array('c'),
622+
'd' => 'e',
623+
),
624+
);
625+
626+
$this->assertSame($expected, $this->parser->parse($yaml));
627+
}
628+
610629
/**
611630
* @expectedException \Symfony\Component\Yaml\Exception\ParseException
612631
*/

0 commit comments

Comments
 (0)