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:
sun 2014-05-15 02:05:21 +02:00
parent c0187c1465
commit 951acca387
3 changed files with 88 additions and 6 deletions

View File

@ -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;
}

View File

@ -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()) {
$data[$key] = null;
// 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 {

View File

@ -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