Skip to content

Commit

Permalink
[Yaml] fix priority of sequence merges according to spec
Browse files Browse the repository at this point in the history
fixes #11154
  • Loading branch information
Tobion committed Jun 19, 2014
1 parent 02614e0 commit 8c621ab
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 21 deletions.
60 changes: 40 additions & 20 deletions src/Symfony/Component/Yaml/Parser.php
Expand Up @@ -76,7 +76,7 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
}

$isRef = $isInPlace = $isProcessed = false;
$isRef = $mergeNode = false;
if (preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+?))?\s*$#u', $this->currentLine, $values)) {
if ($context && 'mapping' == $context) {
throw new ParseException('You cannot define a sequence item when in a mapping');
Expand Down Expand Up @@ -132,10 +132,23 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
}

if ('<<' === $key) {
$mergeNode = true;
if (isset($values['value']) && 0 === strpos($values['value'], '*')) {
$isInPlace = substr($values['value'], 1);
if (!array_key_exists($isInPlace, $this->refs)) {
throw new ParseException(sprintf('Reference "%s" does not exist.', $isInPlace), $this->getRealCurrentLineNb() + 1, $this->currentLine);
$refName = substr($values['value'], 1);
if (!array_key_exists($refName, $this->refs)) {
throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine);
}

$refValue = $this->refs[$refName];

if (!is_array($refValue)) {
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
}

foreach ($refValue as $key => $value) {
if (!isset($data[$key])) {
$data[$key] = $value;
}
}
} else {
if (isset($values['value']) && $values['value'] !== '') {
Expand All @@ -152,26 +165,37 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
}

$isProcessed = true;
if (isset($parsed[0])) {
// Numeric array, merge individual elements
// If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
// and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
// in the sequence override keys specified in later mapping nodes.
foreach ($parsed as $parsedItem) {
if (!is_array($parsedItem)) {
throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem);
}
$data = array_merge($data, $parsedItem);

foreach ($parsedItem as $key => $value) {
if (!isset($data[$key])) {
$data[$key] = $value;
}
}
}
} else {
// Associative array
$data = $parsed;
// If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
// current mapping, unless the key already exists in it.
foreach ($parsed as $key => $value) {
if (!isset($data[$key])) {
$data[$key] = $value;
}
}
}
}
} elseif (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
$isRef = $matches['ref'];
$values['value'] = $matches['value'];
}

if ($isProcessed) {
if ($mergeNode) {
// Merge keys
} elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
// hash
Expand All @@ -196,16 +220,12 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
}
}
} else {
if ($isInPlace) {
$data = $this->refs[$isInPlace];
} else {
$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;
}
$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 {
Expand Down
4 changes: 3 additions & 1 deletion src/Symfony/Component/Yaml/Tests/Fixtures/sfMergeKey.yml
Expand Up @@ -11,6 +11,8 @@ yaml: |
b: Clark
c: Brian
bar: &bar
a: before
d: other
<<: *foo
x: Oren
foo2: &foo2
Expand All @@ -24,4 +26,4 @@ yaml: |
head:
<<: [ *foo , *dong , *foo2 ]
php: |
array('foo' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian'), 'bar' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'x' => 'Oren'), 'foo2' => array('a' => 'Ballmer'), 'ding' => array('fi', 'fei', 'fo', 'fam'), 'check' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'fi', 'fei', 'fo', 'fam', 'isit' => 'tested'), 'head' => array('a' => 'Ballmer', 'b' => 'Clark', 'c' => 'Brian', 'fi', 'fei', 'fo', 'fam'))
array('foo' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian'), 'bar' => array('a' => 'before', 'd' => 'other', 'b' => 'Clark', 'c' => 'Brian', 'x' => 'Oren'), 'foo2' => array('a' => 'Ballmer'), 'ding' => array('fi', 'fei', 'fo', 'fam'), 'check' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'fi', 'fei', 'fo', 'fam', 'isit' => 'tested'), 'head' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'fi', 'fei', 'fo', 'fam'))

0 comments on commit 8c621ab

Please sign in to comment.