Skip to content

Commit

Permalink
bug #11677 [YAML] resolve variables in inlined YAML (xabbuh)
Browse files Browse the repository at this point in the history
This PR was merged into the 2.3 branch.

Discussion
----------

[YAML] resolve variables in inlined YAML

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #11665
| License       | MIT
| Doc PR        |

#11569 does not resolve variables in inline YAML.

Commits
-------

45a5863 [YAML] resolve variables in inlined YAML
  • Loading branch information
fabpot committed Aug 27, 2014
2 parents 7510d06 + 45a5863 commit 8990ac6
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 24 deletions.
64 changes: 43 additions & 21 deletions src/Symfony/Component/Yaml/Inline.php
Expand Up @@ -32,12 +32,13 @@ class Inline
* @param string $value A YAML string
* @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
* @param bool $objectSupport true if object support is enabled, false otherwise
* @param array $references Mapping of variable names to values
*
* @return array A PHP array representing the YAML string
*
* @throws ParseException
*/
public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false)
public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $references = array())
{
self::$exceptionOnInvalidType = $exceptionOnInvalidType;
self::$objectSupport = $objectSupport;
Expand All @@ -56,15 +57,15 @@ public static function parse($value, $exceptionOnInvalidType = false, $objectSup
$i = 0;
switch ($value[0]) {
case '[':
$result = self::parseSequence($value, $i);
$result = self::parseSequence($value, $i, $references);
++$i;
break;
case '{':
$result = self::parseMapping($value, $i);
$result = self::parseMapping($value, $i, $references);
++$i;
break;
default:
$result = self::parseScalar($value, null, array('"', "'"), $i);
$result = self::parseScalar($value, null, array('"', "'"), $i, true, $references);
}

// some comments are allowed at the end
Expand Down Expand Up @@ -184,14 +185,15 @@ private static function dumpArray($value, $exceptionOnInvalidType, $objectSuppor
* @param scalar $scalar
* @param string $delimiters
* @param array $stringDelimiters
* @param int &$i
* @param bool $evaluate
* @param int &$i
* @param bool $evaluate
* @param array $references
*
* @return string A YAML string
*
* @throws ParseException When malformed inline YAML string is parsed
*/
public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true)
public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array())
{
if (in_array($scalar[$i], $stringDelimiters)) {
// quoted scalar
Expand Down Expand Up @@ -221,7 +223,7 @@ public static function parseScalar($scalar, $delimiters = null, $stringDelimiter
}

if ($evaluate) {
$output = self::evaluateScalar($output);
$output = self::evaluateScalar($output, $references);
}
}

Expand Down Expand Up @@ -262,13 +264,14 @@ private static function parseQuotedScalar($scalar, &$i)
* Parses a sequence to a YAML string.
*
* @param string $sequence
* @param int &$i
* @param int &$i
* @param array $references
*
* @return string A YAML string
*
* @throws ParseException When malformed inline YAML string is parsed
*/
private static function parseSequence($sequence, &$i = 0)
private static function parseSequence($sequence, &$i = 0, $references = array())
{
$output = array();
$len = strlen($sequence);
Expand All @@ -279,11 +282,11 @@ private static function parseSequence($sequence, &$i = 0)
switch ($sequence[$i]) {
case '[':
// nested sequence
$output[] = self::parseSequence($sequence, $i);
$output[] = self::parseSequence($sequence, $i, $references);
break;
case '{':
// nested mapping
$output[] = self::parseMapping($sequence, $i);
$output[] = self::parseMapping($sequence, $i, $references);
break;
case ']':
return $output;
Expand All @@ -292,12 +295,14 @@ private static function parseSequence($sequence, &$i = 0)
break;
default:
$isQuoted = in_array($sequence[$i], array('"', "'"));
$value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i);
$value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references);

if (!$isQuoted && false !== strpos($value, ': ')) {
// the value can be an array if a reference has been resolved to an array var
if (!is_array($value) && !$isQuoted && false !== strpos($value, ': ')) {
// embedded mapping?
try {
$value = self::parseMapping('{'.$value.'}');
$pos = 0;
$value = self::parseMapping('{'.$value.'}', $pos, $references);
} catch (\InvalidArgumentException $e) {
// no, it's not
}
Expand All @@ -318,13 +323,14 @@ private static function parseSequence($sequence, &$i = 0)
* Parses a mapping to a YAML string.
*
* @param string $mapping
* @param int &$i
* @param int &$i
* @param array $references
*
* @return string A YAML string
*
* @throws ParseException When malformed inline YAML string is parsed
*/
private static function parseMapping($mapping, &$i = 0)
private static function parseMapping($mapping, &$i = 0, $references = array())
{
$output = array();
$len = strlen($mapping);
Expand All @@ -350,19 +356,19 @@ private static function parseMapping($mapping, &$i = 0)
switch ($mapping[$i]) {
case '[':
// nested sequence
$output[$key] = self::parseSequence($mapping, $i);
$output[$key] = self::parseSequence($mapping, $i, $references);
$done = true;
break;
case '{':
// nested mapping
$output[$key] = self::parseMapping($mapping, $i);
$output[$key] = self::parseMapping($mapping, $i, $references);
$done = true;
break;
case ':':
case ' ':
break;
default:
$output[$key] = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i);
$output[$key] = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references);
$done = true;
--$i;
}
Expand All @@ -382,15 +388,31 @@ private static function parseMapping($mapping, &$i = 0)
* Evaluates scalars and replaces magic values.
*
* @param string $scalar
* @param array $references
*
* @return string A YAML string
*
* @throws ParseException when object parsing support was disabled and the parser detected a PHP object
*/
private static function evaluateScalar($scalar)
private static function evaluateScalar($scalar, $references = array())
{
$scalar = trim($scalar);
$scalarLower = strtolower($scalar);

if (0 === strpos($scalar, '*')) {
if (false !== $pos = strpos($scalar, '#')) {
$value = substr($scalar, 1, $pos - 2);
} else {
$value = substr($scalar, 1);
}

if (!array_key_exists($value, $references)) {
throw new ParseException(sprintf('Reference "%s" does not exist.', $value));
}

return $references[$value];
}

switch (true) {
case 'null' === $scalarLower:
case '' === $scalar:
Expand Down
6 changes: 3 additions & 3 deletions src/Symfony/Component/Yaml/Parser.php
Expand Up @@ -121,7 +121,7 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
$context = 'mapping';

// force correct settings
Inline::parse(null, $exceptionOnInvalidType, $objectSupport);
Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $this->refs);
try {
$key = Inline::parseScalar($values['key']);
} catch (ParseException $e) {
Expand Down Expand Up @@ -197,7 +197,7 @@ public function parse($value, $exceptionOnInvalidType = false, $objectSupport =
$lineCount = count($this->lines);
if (1 === $lineCount || (2 === $lineCount && empty($this->lines[1]))) {
try {
$value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport);
$value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $this->refs);
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
Expand Down Expand Up @@ -404,7 +404,7 @@ private function parseValue($value, $exceptionOnInvalidType, $objectSupport)
}

try {
return Inline::parse($value, $exceptionOnInvalidType, $objectSupport);
return Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $this->refs);
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
Expand Down
32 changes: 32 additions & 0 deletions src/Symfony/Component/Yaml/Tests/InlineTest.php
Expand Up @@ -115,6 +115,38 @@ public function testParseScalarWithCorrectlyQuotedStringShouldReturnString()
$this->assertSame($expect, Inline::parseScalar($value));
}

/**
* @dataProvider getDataForParseReferences
*/
public function testParseReferences($yaml, $expected)
{
$this->assertSame($expected, Inline::parse($yaml, false, false, array('var' => 'var-value')));
}

public function getDataForParseReferences()
{
return array(
'scalar' => array('*var', 'var-value'),
'list' => array('[ *var ]', array('var-value')),
'list-in-list' => array('[[ *var ]]', array(array('var-value'))),
'map-in-list' => array('[ { key: *var } ]', array(array('key' => 'var-value'))),
'embedded-mapping-in-list' => array('[ key: *var ]', array(array('key' => 'var-value'))),
'map' => array('{ key: *var }', array('key' => 'var-value')),
'list-in-map' => array('{ key: [*var] }', array('key' => array('var-value'))),
'map-in-map' => array('{ foo: { bar: *var } }', array('foo' => array('bar' => 'var-value'))),
);
}

public function testParseMapReferenceInSequence()
{
$foo = array(
'a' => 'Steve',
'b' => 'Clark',
'c' => 'Brian',
);
$this->assertSame(array($foo), Inline::parse('[*foo]', false, false, array('foo' => $foo)));
}

protected function getTestsForParse()
{
return array(
Expand Down
26 changes: 26 additions & 0 deletions src/Symfony/Component/Yaml/Tests/ParserTest.php
Expand Up @@ -602,6 +602,32 @@ public function testNestedFoldedStringBlockWithComments()
</body>
footer # comment3
EOF
));
}

public function testReferenceResolvingInInlineStrings()
{
$this->assertEquals(array(
'var' => 'var-value',
'scalar' => 'var-value',
'list' => array('var-value'),
'list_in_list' => array(array('var-value')),
'map_in_list' => array(array('key' => 'var-value')),
'embedded_mapping' => array(array('key' => 'var-value')),
'map' => array('key' => 'var-value'),
'list_in_map' => array('key' => array('var-value')),
'map_in_map' => array('foo' => array('bar' => 'var-value')),
), Yaml::parse(<<<EOF
var: &var var-value
scalar: *var
list: [ *var ]
list_in_list: [[ *var ]]
map_in_list: [ { key: *var } ]
embedded_mapping: [ key: *var ]
map: { key: *var }
list_in_map: { key: [*var] }
map_in_map: { foo: { bar: *var } }
EOF
));
}
Expand Down

0 comments on commit 8990ac6

Please sign in to comment.