bug #40923 [Yaml] expose references detected in inline notation structures (xabbuh)

This PR was merged into the 4.4 branch.

Discussion
----------

[Yaml] expose references detected in inline notation structures

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #40823
| License       | MIT
| Doc PR        |

Commits
-------

6d7d3fb520 expose references detected in inline notation structures
This commit is contained in:
Fabien Potencier 2021-04-29 14:36:56 +02:00
commit 061ac77773
4 changed files with 32 additions and 11 deletions

View File

@ -58,7 +58,7 @@ class Inline
*
* @throws ParseException
*/
public static function parse(string $value = null, int $flags = 0, array $references = [])
public static function parse(string $value = null, int $flags = 0, array &$references = [])
{
self::initialize($flags);
@ -267,7 +267,7 @@ class Inline
*
* @throws ParseException When malformed inline YAML string is parsed
*/
public static function parseScalar(string $scalar, int $flags = 0, array $delimiters = null, int &$i = 0, bool $evaluate = true, array $references = [])
public static function parseScalar(string $scalar, int $flags = 0, array $delimiters = null, int &$i = 0, bool $evaluate = true, array &$references = [])
{
if (\in_array($scalar[$i], ['"', "'"])) {
// quoted scalar
@ -343,7 +343,7 @@ class Inline
*
* @throws ParseException When malformed inline YAML string is parsed
*/
private static function parseSequence(string $sequence, int $flags, int &$i = 0, array $references = []): array
private static function parseSequence(string $sequence, int $flags, int &$i = 0, array &$references = []): array
{
$output = [];
$len = \strlen($sequence);
@ -385,6 +385,11 @@ class Inline
}
}
if (\is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) {
$references[$matches['ref']] = $matches['value'];
$value = $matches['value'];
}
--$i;
}
@ -407,7 +412,7 @@ class Inline
*
* @throws ParseException When malformed inline YAML string is parsed
*/
private static function parseMapping(string $mapping, int $flags, int &$i = 0, array $references = [])
private static function parseMapping(string $mapping, int $flags, int &$i = 0, array &$references = [])
{
$output = [];
$len = \strlen($mapping);
@ -433,14 +438,14 @@ class Inline
// key
$offsetBeforeKeyParsing = $i;
$isKeyQuoted = \in_array($mapping[$i], ['"', "'"], true);
$key = self::parseScalar($mapping, $flags, [':', ' '], $i, false, []);
$key = self::parseScalar($mapping, $flags, [':', ' '], $i, false);
if ($offsetBeforeKeyParsing === $i) {
throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping);
}
if ('!php/const' === $key) {
$key .= ' '.self::parseScalar($mapping, $flags, [':'], $i, false, []);
$key .= ' '.self::parseScalar($mapping, $flags, [':'], $i, false);
$key = self::evaluateScalar($key, $flags);
}
@ -522,6 +527,11 @@ class Inline
if ('<<' === $key) {
$output += $value;
} elseif ($allowOverwrite || !isset($output[$key])) {
if (\is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) {
$references[$matches['ref']] = $matches['value'];
$value = $matches['value'];
}
if (null !== $tag) {
$output[$key] = new TaggedValue($tag, $value);
} else {
@ -548,7 +558,7 @@ class Inline
*
* @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
*/
private static function evaluateScalar(string $scalar, int $flags, array $references = [])
private static function evaluateScalar(string $scalar, int $flags, array &$references = [])
{
$scalar = trim($scalar);
$scalarLower = strtolower($scalar);

View File

@ -25,6 +25,7 @@ class Parser
{
public const TAG_PATTERN = '(?P<tag>![\w!.\/:-]+)';
public const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
public const REFERENCE_PATTERN = '#^&(?P<ref>[^ ]++) *+(?P<value>.*)#u';
private $filename;
private $offset = 0;
@ -158,7 +159,7 @@ class Parser
}
$context = 'sequence';
if (isset($values['value']) && '&' === $values['value'][0] && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
if (isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) {
$isRef = $matches['ref'];
$this->refsBeingParsed[] = $isRef;
$values['value'] = $matches['value'];
@ -296,7 +297,7 @@ class Parser
$data += $parsed; // array union
}
}
} elseif ('<<' !== $key && isset($values['value']) && '&' === $values['value'][0] && self::preg_match('#^&(?P<ref>[^ ]++) *+(?P<value>.*)#u', $values['value'], $matches)) {
} elseif ('<<' !== $key && isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) {
$isRef = $matches['ref'];
$this->refsBeingParsed[] = $isRef;
$values['value'] = $matches['value'];

View File

@ -187,7 +187,8 @@ class InlineTest extends TestCase
*/
public function testParseReferences($yaml, $expected)
{
$this->assertSame($expected, Inline::parse($yaml, 0, ['var' => 'var-value']));
$references = ['var' => 'var-value'];
$this->assertSame($expected, Inline::parse($yaml, 0, $references));
}
public function getDataForParseReferences()
@ -211,7 +212,8 @@ class InlineTest extends TestCase
'b' => 'Clark',
'c' => 'Brian',
];
$this->assertSame([$foo], Inline::parse('[*foo]', 0, ['foo' => $foo]));
$references = ['foo' => $foo];
$this->assertSame([$foo], Inline::parse('[*foo]', 0, $references));
}
public function testParseUnquotedAsterisk()

View File

@ -1073,6 +1073,10 @@ EOF
'map' => ['key' => 'var-value'],
'list_in_map' => ['key' => ['var-value']],
'map_in_map' => ['foo' => ['bar' => 'var-value']],
'foo' => ['bar' => 'baz'],
'bar' => ['foo' => 'baz'],
'baz' => ['foo'],
'foobar' => ['foo'],
], Yaml::parse(<<<'EOF'
var: &var var-value
scalar: *var
@ -1083,6 +1087,10 @@ embedded_mapping: [ key: *var ]
map: { key: *var }
list_in_map: { key: [*var] }
map_in_map: { foo: { bar: *var } }
foo: { bar: &baz baz }
bar: { foo: *baz }
baz: [ &foo foo ]
foobar: [ *foo ]
EOF
));
}