Fixed YAML Parser does not ignore duplicate keys, violating YAML spec.
The current [YAML 1.2] specification clearly states: > JSON's RFC4627 requires that mappings keys merely “SHOULD” be unique, while YAML insists they “MUST” be. The outdated [YAML 1.1] spec contained a crystal clear note on how the error of duplicate keys is to be handled by parsers, which is (sadly) no longer contained in the latest 1.2 spec (only leaving the requirement): > It is an error for two equal keys to appear in the same mapping node. In such a case the YAML processor may continue, ignoring the second `key: value` pair and issuing an appropriate warning. This strategy preserves a consistent information model for one-pass and random access applications. [YAML 1.2]: http://yaml.org/spec/1.2/spec.html#id2759572 [YAML 1.1]: http://yaml.org/spec/1.1/#id932806
This commit is contained in:
parent
c0187c1465
commit
951acca387
@ -350,19 +350,37 @@ class Inline
|
||||
switch ($mapping[$i]) {
|
||||
case '[':
|
||||
// nested sequence
|
||||
$output[$key] = self::parseSequence($mapping, $i);
|
||||
$value = self::parseSequence($mapping, $i);
|
||||
// Spec: Keys MUST be unique; first one wins.
|
||||
// Parser cannot abort this mapping earlier, since lines
|
||||
// are processed sequentially.
|
||||
if (!isset($output[$key])) {
|
||||
$output[$key] = $value;
|
||||
}
|
||||
$done = true;
|
||||
break;
|
||||
case '{':
|
||||
// nested mapping
|
||||
$output[$key] = self::parseMapping($mapping, $i);
|
||||
$value = self::parseMapping($mapping, $i);
|
||||
// Spec: Keys MUST be unique; first one wins.
|
||||
// Parser cannot abort this mapping earlier, since lines
|
||||
// are processed sequentially.
|
||||
if (!isset($output[$key])) {
|
||||
$output[$key] = $value;
|
||||
}
|
||||
$done = true;
|
||||
break;
|
||||
case ':':
|
||||
case ' ':
|
||||
break;
|
||||
default:
|
||||
$output[$key] = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i);
|
||||
$value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i);
|
||||
// Spec: Keys MUST be unique; first one wins.
|
||||
// Parser cannot abort this mapping earlier, since lines
|
||||
// are processed sequentially.
|
||||
if (!isset($output[$key])) {
|
||||
$output[$key] = $value;
|
||||
}
|
||||
$done = true;
|
||||
--$i;
|
||||
}
|
||||
|
@ -178,18 +178,35 @@ class Parser
|
||||
} elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
|
||||
// if next line is less indented or equal, then it means that the current value is null
|
||||
if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
|
||||
// Spec: Keys MUST be unique; first one wins.
|
||||
// Parser cannot abort this mapping earlier, since lines
|
||||
// are processed sequentially.
|
||||
if (!isset($data[$key])) {
|
||||
$data[$key] = null;
|
||||
}
|
||||
} else {
|
||||
$c = $this->getRealCurrentLineNb() + 1;
|
||||
$parser = new Parser($c);
|
||||
$parser->refs =& $this->refs;
|
||||
$data[$key] = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport);
|
||||
$value = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport);
|
||||
// Spec: Keys MUST be unique; first one wins.
|
||||
// Parser cannot abort this mapping earlier, since lines
|
||||
// are processed sequentially.
|
||||
if (!isset($data[$key])) {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($isInPlace) {
|
||||
$data = $this->refs[$isInPlace];
|
||||
} else {
|
||||
$data[$key] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport);
|
||||
$value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport);;
|
||||
// Spec: Keys MUST be unique; first one wins.
|
||||
// Parser cannot abort this mapping earlier, since lines
|
||||
// are processed sequentially.
|
||||
if (!isset($data[$key])) {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -508,6 +508,53 @@ EOF
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* > It is an error for two equal keys to appear in the same mapping node.
|
||||
* > In such a case the YAML processor may continue, ignoring the second
|
||||
* > `key: value` pair and issuing an appropriate warning. This strategy
|
||||
* > preserves a consistent information model for one-pass and random access
|
||||
* > applications.
|
||||
*
|
||||
* @see http://yaml.org/spec/1.2/spec.html#id2759572
|
||||
* @see http://yaml.org/spec/1.1/#id932806
|
||||
*
|
||||
* @covers \Symfony\Component\Yaml\Parser::parse
|
||||
*/
|
||||
public function testMappingDuplicateKeyBlock()
|
||||
{
|
||||
$input = <<<EOD
|
||||
parent:
|
||||
child: first
|
||||
child: duplicate
|
||||
parent:
|
||||
child: duplicate
|
||||
child: duplicate
|
||||
EOD;
|
||||
$expected = array(
|
||||
'parent' => array(
|
||||
'child' => 'first',
|
||||
),
|
||||
);
|
||||
$this->assertSame($expected, Yaml::parse($input));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Symfony\Component\Yaml\Inline::parseMapping
|
||||
*/
|
||||
public function testMappingDuplicateKeyFlow()
|
||||
{
|
||||
$input = <<<EOD
|
||||
parent: { child: first, child: duplicate }
|
||||
parent: { child: duplicate, child: duplicate }
|
||||
EOD;
|
||||
$expected = array(
|
||||
'parent' => array(
|
||||
'child' => 'first',
|
||||
),
|
||||
);
|
||||
$this->assertSame($expected, Yaml::parse($input));
|
||||
}
|
||||
|
||||
public function testEmptyValue()
|
||||
{
|
||||
$input = <<<EOF
|
||||
|
Reference in New Issue
Block a user