diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php index 0db774fcd148..1b34edebd28f 100644 --- a/src/Symfony/Component/Yaml/Inline.php +++ b/src/Symfony/Component/Yaml/Inline.php @@ -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; @@ -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 @@ -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 @@ -221,7 +223,7 @@ public static function parseScalar($scalar, $delimiters = null, $stringDelimiter } if ($evaluate) { - $output = self::evaluateScalar($output); + $output = self::evaluateScalar($output, $references); } } @@ -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); @@ -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; @@ -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 } @@ -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); @@ -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; } @@ -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: diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index 9bde67b57b54..d0aae87ab978 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -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) { @@ -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); @@ -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); diff --git a/src/Symfony/Component/Yaml/Tests/InlineTest.php b/src/Symfony/Component/Yaml/Tests/InlineTest.php index af5018f2026f..96a118d073ed 100644 --- a/src/Symfony/Component/Yaml/Tests/InlineTest.php +++ b/src/Symfony/Component/Yaml/Tests/InlineTest.php @@ -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( diff --git a/src/Symfony/Component/Yaml/Tests/ParserTest.php b/src/Symfony/Component/Yaml/Tests/ParserTest.php index 2335efc55990..01c7b5399511 100644 --- a/src/Symfony/Component/Yaml/Tests/ParserTest.php +++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php @@ -602,6 +602,32 @@ public function testNestedFoldedStringBlockWithComments() 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(<<