xref: /template/strap/vendor/symfony/yaml/Inline.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1*04fd306cSNickeau<?php
2*04fd306cSNickeau
3*04fd306cSNickeau/*
4*04fd306cSNickeau * This file is part of the Symfony package.
5*04fd306cSNickeau *
6*04fd306cSNickeau * (c) Fabien Potencier <fabien@symfony.com>
7*04fd306cSNickeau *
8*04fd306cSNickeau * For the full copyright and license information, please view the LICENSE
9*04fd306cSNickeau * file that was distributed with this source code.
10*04fd306cSNickeau */
11*04fd306cSNickeau
12*04fd306cSNickeaunamespace Symfony\Component\Yaml;
13*04fd306cSNickeau
14*04fd306cSNickeauuse Symfony\Component\Yaml\Exception\DumpException;
15*04fd306cSNickeauuse Symfony\Component\Yaml\Exception\ParseException;
16*04fd306cSNickeauuse Symfony\Component\Yaml\Tag\TaggedValue;
17*04fd306cSNickeau
18*04fd306cSNickeau/**
19*04fd306cSNickeau * Inline implements a YAML parser/dumper for the YAML inline syntax.
20*04fd306cSNickeau *
21*04fd306cSNickeau * @author Fabien Potencier <fabien@symfony.com>
22*04fd306cSNickeau *
23*04fd306cSNickeau * @internal
24*04fd306cSNickeau */
25*04fd306cSNickeauclass Inline
26*04fd306cSNickeau{
27*04fd306cSNickeau    public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';
28*04fd306cSNickeau
29*04fd306cSNickeau    public static $parsedLineNumber = -1;
30*04fd306cSNickeau    public static $parsedFilename;
31*04fd306cSNickeau
32*04fd306cSNickeau    private static $exceptionOnInvalidType = false;
33*04fd306cSNickeau    private static $objectSupport = false;
34*04fd306cSNickeau    private static $objectForMap = false;
35*04fd306cSNickeau    private static $constantSupport = false;
36*04fd306cSNickeau
37*04fd306cSNickeau    public static function initialize(int $flags, int $parsedLineNumber = null, string $parsedFilename = null)
38*04fd306cSNickeau    {
39*04fd306cSNickeau        self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags);
40*04fd306cSNickeau        self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags);
41*04fd306cSNickeau        self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags);
42*04fd306cSNickeau        self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags);
43*04fd306cSNickeau        self::$parsedFilename = $parsedFilename;
44*04fd306cSNickeau
45*04fd306cSNickeau        if (null !== $parsedLineNumber) {
46*04fd306cSNickeau            self::$parsedLineNumber = $parsedLineNumber;
47*04fd306cSNickeau        }
48*04fd306cSNickeau    }
49*04fd306cSNickeau
50*04fd306cSNickeau    /**
51*04fd306cSNickeau     * Converts a YAML string to a PHP value.
52*04fd306cSNickeau     *
53*04fd306cSNickeau     * @param string|null $value      A YAML string
54*04fd306cSNickeau     * @param int         $flags      A bit field of Yaml::PARSE_* constants to customize the YAML parser behavior
55*04fd306cSNickeau     * @param array       $references Mapping of variable names to values
56*04fd306cSNickeau     *
57*04fd306cSNickeau     * @return mixed
58*04fd306cSNickeau     *
59*04fd306cSNickeau     * @throws ParseException
60*04fd306cSNickeau     */
61*04fd306cSNickeau    public static function parse(string $value = null, int $flags = 0, array &$references = [])
62*04fd306cSNickeau    {
63*04fd306cSNickeau        self::initialize($flags);
64*04fd306cSNickeau
65*04fd306cSNickeau        $value = trim($value);
66*04fd306cSNickeau
67*04fd306cSNickeau        if ('' === $value) {
68*04fd306cSNickeau            return '';
69*04fd306cSNickeau        }
70*04fd306cSNickeau
71*04fd306cSNickeau        if (2 /* MB_OVERLOAD_STRING */ & (int) \ini_get('mbstring.func_overload')) {
72*04fd306cSNickeau            $mbEncoding = mb_internal_encoding();
73*04fd306cSNickeau            mb_internal_encoding('ASCII');
74*04fd306cSNickeau        }
75*04fd306cSNickeau
76*04fd306cSNickeau        try {
77*04fd306cSNickeau            $i = 0;
78*04fd306cSNickeau            $tag = self::parseTag($value, $i, $flags);
79*04fd306cSNickeau            switch ($value[$i]) {
80*04fd306cSNickeau                case '[':
81*04fd306cSNickeau                    $result = self::parseSequence($value, $flags, $i, $references);
82*04fd306cSNickeau                    ++$i;
83*04fd306cSNickeau                    break;
84*04fd306cSNickeau                case '{':
85*04fd306cSNickeau                    $result = self::parseMapping($value, $flags, $i, $references);
86*04fd306cSNickeau                    ++$i;
87*04fd306cSNickeau                    break;
88*04fd306cSNickeau                default:
89*04fd306cSNickeau                    $result = self::parseScalar($value, $flags, null, $i, true, $references);
90*04fd306cSNickeau            }
91*04fd306cSNickeau
92*04fd306cSNickeau            // some comments are allowed at the end
93*04fd306cSNickeau            if (preg_replace('/\s*#.*$/A', '', substr($value, $i))) {
94*04fd306cSNickeau                throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
95*04fd306cSNickeau            }
96*04fd306cSNickeau
97*04fd306cSNickeau            if (null !== $tag && '' !== $tag) {
98*04fd306cSNickeau                return new TaggedValue($tag, $result);
99*04fd306cSNickeau            }
100*04fd306cSNickeau
101*04fd306cSNickeau            return $result;
102*04fd306cSNickeau        } finally {
103*04fd306cSNickeau            if (isset($mbEncoding)) {
104*04fd306cSNickeau                mb_internal_encoding($mbEncoding);
105*04fd306cSNickeau            }
106*04fd306cSNickeau        }
107*04fd306cSNickeau    }
108*04fd306cSNickeau
109*04fd306cSNickeau    /**
110*04fd306cSNickeau     * Dumps a given PHP variable to a YAML string.
111*04fd306cSNickeau     *
112*04fd306cSNickeau     * @param mixed $value The PHP variable to convert
113*04fd306cSNickeau     * @param int   $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
114*04fd306cSNickeau     *
115*04fd306cSNickeau     * @throws DumpException When trying to dump PHP resource
116*04fd306cSNickeau     */
117*04fd306cSNickeau    public static function dump($value, int $flags = 0): string
118*04fd306cSNickeau    {
119*04fd306cSNickeau        switch (true) {
120*04fd306cSNickeau            case \is_resource($value):
121*04fd306cSNickeau                if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) {
122*04fd306cSNickeau                    throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value)));
123*04fd306cSNickeau                }
124*04fd306cSNickeau
125*04fd306cSNickeau                return self::dumpNull($flags);
126*04fd306cSNickeau            case $value instanceof \DateTimeInterface:
127*04fd306cSNickeau                return $value->format('c');
128*04fd306cSNickeau            case $value instanceof \UnitEnum:
129*04fd306cSNickeau                return sprintf('!php/const %s::%s', \get_class($value), $value->name);
130*04fd306cSNickeau            case \is_object($value):
131*04fd306cSNickeau                if ($value instanceof TaggedValue) {
132*04fd306cSNickeau                    return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags);
133*04fd306cSNickeau                }
134*04fd306cSNickeau
135*04fd306cSNickeau                if (Yaml::DUMP_OBJECT & $flags) {
136*04fd306cSNickeau                    return '!php/object '.self::dump(serialize($value));
137*04fd306cSNickeau                }
138*04fd306cSNickeau
139*04fd306cSNickeau                if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) {
140*04fd306cSNickeau                    $output = [];
141*04fd306cSNickeau
142*04fd306cSNickeau                    foreach ($value as $key => $val) {
143*04fd306cSNickeau                        $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags));
144*04fd306cSNickeau                    }
145*04fd306cSNickeau
146*04fd306cSNickeau                    return sprintf('{ %s }', implode(', ', $output));
147*04fd306cSNickeau                }
148*04fd306cSNickeau
149*04fd306cSNickeau                if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) {
150*04fd306cSNickeau                    throw new DumpException('Object support when dumping a YAML file has been disabled.');
151*04fd306cSNickeau                }
152*04fd306cSNickeau
153*04fd306cSNickeau                return self::dumpNull($flags);
154*04fd306cSNickeau            case \is_array($value):
155*04fd306cSNickeau                return self::dumpArray($value, $flags);
156*04fd306cSNickeau            case null === $value:
157*04fd306cSNickeau                return self::dumpNull($flags);
158*04fd306cSNickeau            case true === $value:
159*04fd306cSNickeau                return 'true';
160*04fd306cSNickeau            case false === $value:
161*04fd306cSNickeau                return 'false';
162*04fd306cSNickeau            case \is_int($value):
163*04fd306cSNickeau                return $value;
164*04fd306cSNickeau            case is_numeric($value) && false === strpbrk($value, "\f\n\r\t\v"):
165*04fd306cSNickeau                $locale = setlocale(\LC_NUMERIC, 0);
166*04fd306cSNickeau                if (false !== $locale) {
167*04fd306cSNickeau                    setlocale(\LC_NUMERIC, 'C');
168*04fd306cSNickeau                }
169*04fd306cSNickeau                if (\is_float($value)) {
170*04fd306cSNickeau                    $repr = (string) $value;
171*04fd306cSNickeau                    if (is_infinite($value)) {
172*04fd306cSNickeau                        $repr = str_ireplace('INF', '.Inf', $repr);
173*04fd306cSNickeau                    } elseif (floor($value) == $value && $repr == $value) {
174*04fd306cSNickeau                        // Preserve float data type since storing a whole number will result in integer value.
175*04fd306cSNickeau                        if (false === strpos($repr, 'E')) {
176*04fd306cSNickeau                            $repr = $repr.'.0';
177*04fd306cSNickeau                        }
178*04fd306cSNickeau                    }
179*04fd306cSNickeau                } else {
180*04fd306cSNickeau                    $repr = \is_string($value) ? "'$value'" : (string) $value;
181*04fd306cSNickeau                }
182*04fd306cSNickeau                if (false !== $locale) {
183*04fd306cSNickeau                    setlocale(\LC_NUMERIC, $locale);
184*04fd306cSNickeau                }
185*04fd306cSNickeau
186*04fd306cSNickeau                return $repr;
187*04fd306cSNickeau            case '' == $value:
188*04fd306cSNickeau                return "''";
189*04fd306cSNickeau            case self::isBinaryString($value):
190*04fd306cSNickeau                return '!!binary '.base64_encode($value);
191*04fd306cSNickeau            case Escaper::requiresDoubleQuoting($value):
192*04fd306cSNickeau                return Escaper::escapeWithDoubleQuotes($value);
193*04fd306cSNickeau            case Escaper::requiresSingleQuoting($value):
194*04fd306cSNickeau            case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value):
195*04fd306cSNickeau            case Parser::preg_match(self::getHexRegex(), $value):
196*04fd306cSNickeau            case Parser::preg_match(self::getTimestampRegex(), $value):
197*04fd306cSNickeau                return Escaper::escapeWithSingleQuotes($value);
198*04fd306cSNickeau            default:
199*04fd306cSNickeau                return $value;
200*04fd306cSNickeau        }
201*04fd306cSNickeau    }
202*04fd306cSNickeau
203*04fd306cSNickeau    /**
204*04fd306cSNickeau     * Check if given array is hash or just normal indexed array.
205*04fd306cSNickeau     *
206*04fd306cSNickeau     * @param array|\ArrayObject|\stdClass $value The PHP array or array-like object to check
207*04fd306cSNickeau     */
208*04fd306cSNickeau    public static function isHash($value): bool
209*04fd306cSNickeau    {
210*04fd306cSNickeau        if ($value instanceof \stdClass || $value instanceof \ArrayObject) {
211*04fd306cSNickeau            return true;
212*04fd306cSNickeau        }
213*04fd306cSNickeau
214*04fd306cSNickeau        $expectedKey = 0;
215*04fd306cSNickeau
216*04fd306cSNickeau        foreach ($value as $key => $val) {
217*04fd306cSNickeau            if ($key !== $expectedKey++) {
218*04fd306cSNickeau                return true;
219*04fd306cSNickeau            }
220*04fd306cSNickeau        }
221*04fd306cSNickeau
222*04fd306cSNickeau        return false;
223*04fd306cSNickeau    }
224*04fd306cSNickeau
225*04fd306cSNickeau    /**
226*04fd306cSNickeau     * Dumps a PHP array to a YAML string.
227*04fd306cSNickeau     *
228*04fd306cSNickeau     * @param array $value The PHP array to dump
229*04fd306cSNickeau     * @param int   $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
230*04fd306cSNickeau     */
231*04fd306cSNickeau    private static function dumpArray(array $value, int $flags): string
232*04fd306cSNickeau    {
233*04fd306cSNickeau        // array
234*04fd306cSNickeau        if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags) && !self::isHash($value)) {
235*04fd306cSNickeau            $output = [];
236*04fd306cSNickeau            foreach ($value as $val) {
237*04fd306cSNickeau                $output[] = self::dump($val, $flags);
238*04fd306cSNickeau            }
239*04fd306cSNickeau
240*04fd306cSNickeau            return sprintf('[%s]', implode(', ', $output));
241*04fd306cSNickeau        }
242*04fd306cSNickeau
243*04fd306cSNickeau        // hash
244*04fd306cSNickeau        $output = [];
245*04fd306cSNickeau        foreach ($value as $key => $val) {
246*04fd306cSNickeau            $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags));
247*04fd306cSNickeau        }
248*04fd306cSNickeau
249*04fd306cSNickeau        return sprintf('{ %s }', implode(', ', $output));
250*04fd306cSNickeau    }
251*04fd306cSNickeau
252*04fd306cSNickeau    private static function dumpNull(int $flags): string
253*04fd306cSNickeau    {
254*04fd306cSNickeau        if (Yaml::DUMP_NULL_AS_TILDE & $flags) {
255*04fd306cSNickeau            return '~';
256*04fd306cSNickeau        }
257*04fd306cSNickeau
258*04fd306cSNickeau        return 'null';
259*04fd306cSNickeau    }
260*04fd306cSNickeau
261*04fd306cSNickeau    /**
262*04fd306cSNickeau     * Parses a YAML scalar.
263*04fd306cSNickeau     *
264*04fd306cSNickeau     * @return mixed
265*04fd306cSNickeau     *
266*04fd306cSNickeau     * @throws ParseException When malformed inline YAML string is parsed
267*04fd306cSNickeau     */
268*04fd306cSNickeau    public static function parseScalar(string $scalar, int $flags = 0, array $delimiters = null, int &$i = 0, bool $evaluate = true, array &$references = [], bool &$isQuoted = null)
269*04fd306cSNickeau    {
270*04fd306cSNickeau        if (\in_array($scalar[$i], ['"', "'"], true)) {
271*04fd306cSNickeau            // quoted scalar
272*04fd306cSNickeau            $isQuoted = true;
273*04fd306cSNickeau            $output = self::parseQuotedScalar($scalar, $i);
274*04fd306cSNickeau
275*04fd306cSNickeau            if (null !== $delimiters) {
276*04fd306cSNickeau                $tmp = ltrim(substr($scalar, $i), " \n");
277*04fd306cSNickeau                if ('' === $tmp) {
278*04fd306cSNickeau                    throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
279*04fd306cSNickeau                }
280*04fd306cSNickeau                if (!\in_array($tmp[0], $delimiters)) {
281*04fd306cSNickeau                    throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
282*04fd306cSNickeau                }
283*04fd306cSNickeau            }
284*04fd306cSNickeau        } else {
285*04fd306cSNickeau            // "normal" string
286*04fd306cSNickeau            $isQuoted = false;
287*04fd306cSNickeau
288*04fd306cSNickeau            if (!$delimiters) {
289*04fd306cSNickeau                $output = substr($scalar, $i);
290*04fd306cSNickeau                $i += \strlen($output);
291*04fd306cSNickeau
292*04fd306cSNickeau                // remove comments
293*04fd306cSNickeau                if (Parser::preg_match('/[ \t]+#/', $output, $match, \PREG_OFFSET_CAPTURE)) {
294*04fd306cSNickeau                    $output = substr($output, 0, $match[0][1]);
295*04fd306cSNickeau                }
296*04fd306cSNickeau            } elseif (Parser::preg_match('/^(.*?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
297*04fd306cSNickeau                $output = $match[1];
298*04fd306cSNickeau                $i += \strlen($output);
299*04fd306cSNickeau                $output = trim($output);
300*04fd306cSNickeau            } else {
301*04fd306cSNickeau                throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $scalar), self::$parsedLineNumber + 1, null, self::$parsedFilename);
302*04fd306cSNickeau            }
303*04fd306cSNickeau
304*04fd306cSNickeau            // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >)
305*04fd306cSNickeau            if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0] || '%' === $output[0])) {
306*04fd306cSNickeau                throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]), self::$parsedLineNumber + 1, $output, self::$parsedFilename);
307*04fd306cSNickeau            }
308*04fd306cSNickeau
309*04fd306cSNickeau            if ($evaluate) {
310*04fd306cSNickeau                $output = self::evaluateScalar($output, $flags, $references, $isQuoted);
311*04fd306cSNickeau            }
312*04fd306cSNickeau        }
313*04fd306cSNickeau
314*04fd306cSNickeau        return $output;
315*04fd306cSNickeau    }
316*04fd306cSNickeau
317*04fd306cSNickeau    /**
318*04fd306cSNickeau     * Parses a YAML quoted scalar.
319*04fd306cSNickeau     *
320*04fd306cSNickeau     * @throws ParseException When malformed inline YAML string is parsed
321*04fd306cSNickeau     */
322*04fd306cSNickeau    private static function parseQuotedScalar(string $scalar, int &$i = 0): string
323*04fd306cSNickeau    {
324*04fd306cSNickeau        if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
325*04fd306cSNickeau            throw new ParseException(sprintf('Malformed inline YAML string: "%s".', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
326*04fd306cSNickeau        }
327*04fd306cSNickeau
328*04fd306cSNickeau        $output = substr($match[0], 1, -1);
329*04fd306cSNickeau
330*04fd306cSNickeau        $unescaper = new Unescaper();
331*04fd306cSNickeau        if ('"' == $scalar[$i]) {
332*04fd306cSNickeau            $output = $unescaper->unescapeDoubleQuotedString($output);
333*04fd306cSNickeau        } else {
334*04fd306cSNickeau            $output = $unescaper->unescapeSingleQuotedString($output);
335*04fd306cSNickeau        }
336*04fd306cSNickeau
337*04fd306cSNickeau        $i += \strlen($match[0]);
338*04fd306cSNickeau
339*04fd306cSNickeau        return $output;
340*04fd306cSNickeau    }
341*04fd306cSNickeau
342*04fd306cSNickeau    /**
343*04fd306cSNickeau     * Parses a YAML sequence.
344*04fd306cSNickeau     *
345*04fd306cSNickeau     * @throws ParseException When malformed inline YAML string is parsed
346*04fd306cSNickeau     */
347*04fd306cSNickeau    private static function parseSequence(string $sequence, int $flags, int &$i = 0, array &$references = []): array
348*04fd306cSNickeau    {
349*04fd306cSNickeau        $output = [];
350*04fd306cSNickeau        $len = \strlen($sequence);
351*04fd306cSNickeau        ++$i;
352*04fd306cSNickeau
353*04fd306cSNickeau        // [foo, bar, ...]
354*04fd306cSNickeau        while ($i < $len) {
355*04fd306cSNickeau            if (']' === $sequence[$i]) {
356*04fd306cSNickeau                return $output;
357*04fd306cSNickeau            }
358*04fd306cSNickeau            if (',' === $sequence[$i] || ' ' === $sequence[$i]) {
359*04fd306cSNickeau                ++$i;
360*04fd306cSNickeau
361*04fd306cSNickeau                continue;
362*04fd306cSNickeau            }
363*04fd306cSNickeau
364*04fd306cSNickeau            $tag = self::parseTag($sequence, $i, $flags);
365*04fd306cSNickeau            switch ($sequence[$i]) {
366*04fd306cSNickeau                case '[':
367*04fd306cSNickeau                    // nested sequence
368*04fd306cSNickeau                    $value = self::parseSequence($sequence, $flags, $i, $references);
369*04fd306cSNickeau                    break;
370*04fd306cSNickeau                case '{':
371*04fd306cSNickeau                    // nested mapping
372*04fd306cSNickeau                    $value = self::parseMapping($sequence, $flags, $i, $references);
373*04fd306cSNickeau                    break;
374*04fd306cSNickeau                default:
375*04fd306cSNickeau                    $value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references, $isQuoted);
376*04fd306cSNickeau
377*04fd306cSNickeau                    // the value can be an array if a reference has been resolved to an array var
378*04fd306cSNickeau                    if (\is_string($value) && !$isQuoted && false !== strpos($value, ': ')) {
379*04fd306cSNickeau                        // embedded mapping?
380*04fd306cSNickeau                        try {
381*04fd306cSNickeau                            $pos = 0;
382*04fd306cSNickeau                            $value = self::parseMapping('{'.$value.'}', $flags, $pos, $references);
383*04fd306cSNickeau                        } catch (\InvalidArgumentException $e) {
384*04fd306cSNickeau                            // no, it's not
385*04fd306cSNickeau                        }
386*04fd306cSNickeau                    }
387*04fd306cSNickeau
388*04fd306cSNickeau                    if (!$isQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) {
389*04fd306cSNickeau                        $references[$matches['ref']] = $matches['value'];
390*04fd306cSNickeau                        $value = $matches['value'];
391*04fd306cSNickeau                    }
392*04fd306cSNickeau
393*04fd306cSNickeau                    --$i;
394*04fd306cSNickeau            }
395*04fd306cSNickeau
396*04fd306cSNickeau            if (null !== $tag && '' !== $tag) {
397*04fd306cSNickeau                $value = new TaggedValue($tag, $value);
398*04fd306cSNickeau            }
399*04fd306cSNickeau
400*04fd306cSNickeau            $output[] = $value;
401*04fd306cSNickeau
402*04fd306cSNickeau            ++$i;
403*04fd306cSNickeau        }
404*04fd306cSNickeau
405*04fd306cSNickeau        throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $sequence), self::$parsedLineNumber + 1, null, self::$parsedFilename);
406*04fd306cSNickeau    }
407*04fd306cSNickeau
408*04fd306cSNickeau    /**
409*04fd306cSNickeau     * Parses a YAML mapping.
410*04fd306cSNickeau     *
411*04fd306cSNickeau     * @return array|\stdClass
412*04fd306cSNickeau     *
413*04fd306cSNickeau     * @throws ParseException When malformed inline YAML string is parsed
414*04fd306cSNickeau     */
415*04fd306cSNickeau    private static function parseMapping(string $mapping, int $flags, int &$i = 0, array &$references = [])
416*04fd306cSNickeau    {
417*04fd306cSNickeau        $output = [];
418*04fd306cSNickeau        $len = \strlen($mapping);
419*04fd306cSNickeau        ++$i;
420*04fd306cSNickeau        $allowOverwrite = false;
421*04fd306cSNickeau
422*04fd306cSNickeau        // {foo: bar, bar:foo, ...}
423*04fd306cSNickeau        while ($i < $len) {
424*04fd306cSNickeau            switch ($mapping[$i]) {
425*04fd306cSNickeau                case ' ':
426*04fd306cSNickeau                case ',':
427*04fd306cSNickeau                case "\n":
428*04fd306cSNickeau                    ++$i;
429*04fd306cSNickeau                    continue 2;
430*04fd306cSNickeau                case '}':
431*04fd306cSNickeau                    if (self::$objectForMap) {
432*04fd306cSNickeau                        return (object) $output;
433*04fd306cSNickeau                    }
434*04fd306cSNickeau
435*04fd306cSNickeau                    return $output;
436*04fd306cSNickeau            }
437*04fd306cSNickeau
438*04fd306cSNickeau            // key
439*04fd306cSNickeau            $offsetBeforeKeyParsing = $i;
440*04fd306cSNickeau            $isKeyQuoted = \in_array($mapping[$i], ['"', "'"], true);
441*04fd306cSNickeau            $key = self::parseScalar($mapping, $flags, [':', ' '], $i, false);
442*04fd306cSNickeau
443*04fd306cSNickeau            if ($offsetBeforeKeyParsing === $i) {
444*04fd306cSNickeau                throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping);
445*04fd306cSNickeau            }
446*04fd306cSNickeau
447*04fd306cSNickeau            if ('!php/const' === $key) {
448*04fd306cSNickeau                $key .= ' '.self::parseScalar($mapping, $flags, [':'], $i, false);
449*04fd306cSNickeau                $key = self::evaluateScalar($key, $flags);
450*04fd306cSNickeau            }
451*04fd306cSNickeau
452*04fd306cSNickeau            if (false === $i = strpos($mapping, ':', $i)) {
453*04fd306cSNickeau                break;
454*04fd306cSNickeau            }
455*04fd306cSNickeau
456*04fd306cSNickeau            if (!$isKeyQuoted) {
457*04fd306cSNickeau                $evaluatedKey = self::evaluateScalar($key, $flags, $references);
458*04fd306cSNickeau
459*04fd306cSNickeau                if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) {
460*04fd306cSNickeau                    throw new ParseException('Implicit casting of incompatible mapping keys to strings is not supported. Quote your evaluable mapping keys instead.', self::$parsedLineNumber + 1, $mapping);
461*04fd306cSNickeau                }
462*04fd306cSNickeau            }
463*04fd306cSNickeau
464*04fd306cSNickeau            if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}', "\n"], true))) {
465*04fd306cSNickeau                throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping);
466*04fd306cSNickeau            }
467*04fd306cSNickeau
468*04fd306cSNickeau            if ('<<' === $key) {
469*04fd306cSNickeau                $allowOverwrite = true;
470*04fd306cSNickeau            }
471*04fd306cSNickeau
472*04fd306cSNickeau            while ($i < $len) {
473*04fd306cSNickeau                if (':' === $mapping[$i] || ' ' === $mapping[$i] || "\n" === $mapping[$i]) {
474*04fd306cSNickeau                    ++$i;
475*04fd306cSNickeau
476*04fd306cSNickeau                    continue;
477*04fd306cSNickeau                }
478*04fd306cSNickeau
479*04fd306cSNickeau                $tag = self::parseTag($mapping, $i, $flags);
480*04fd306cSNickeau                switch ($mapping[$i]) {
481*04fd306cSNickeau                    case '[':
482*04fd306cSNickeau                        // nested sequence
483*04fd306cSNickeau                        $value = self::parseSequence($mapping, $flags, $i, $references);
484*04fd306cSNickeau                        // Spec: Keys MUST be unique; first one wins.
485*04fd306cSNickeau                        // Parser cannot abort this mapping earlier, since lines
486*04fd306cSNickeau                        // are processed sequentially.
487*04fd306cSNickeau                        // But overwriting is allowed when a merge node is used in current block.
488*04fd306cSNickeau                        if ('<<' === $key) {
489*04fd306cSNickeau                            foreach ($value as $parsedValue) {
490*04fd306cSNickeau                                $output += $parsedValue;
491*04fd306cSNickeau                            }
492*04fd306cSNickeau                        } elseif ($allowOverwrite || !isset($output[$key])) {
493*04fd306cSNickeau                            if (null !== $tag) {
494*04fd306cSNickeau                                $output[$key] = new TaggedValue($tag, $value);
495*04fd306cSNickeau                            } else {
496*04fd306cSNickeau                                $output[$key] = $value;
497*04fd306cSNickeau                            }
498*04fd306cSNickeau                        } elseif (isset($output[$key])) {
499*04fd306cSNickeau                            throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping);
500*04fd306cSNickeau                        }
501*04fd306cSNickeau                        break;
502*04fd306cSNickeau                    case '{':
503*04fd306cSNickeau                        // nested mapping
504*04fd306cSNickeau                        $value = self::parseMapping($mapping, $flags, $i, $references);
505*04fd306cSNickeau                        // Spec: Keys MUST be unique; first one wins.
506*04fd306cSNickeau                        // Parser cannot abort this mapping earlier, since lines
507*04fd306cSNickeau                        // are processed sequentially.
508*04fd306cSNickeau                        // But overwriting is allowed when a merge node is used in current block.
509*04fd306cSNickeau                        if ('<<' === $key) {
510*04fd306cSNickeau                            $output += $value;
511*04fd306cSNickeau                        } elseif ($allowOverwrite || !isset($output[$key])) {
512*04fd306cSNickeau                            if (null !== $tag) {
513*04fd306cSNickeau                                $output[$key] = new TaggedValue($tag, $value);
514*04fd306cSNickeau                            } else {
515*04fd306cSNickeau                                $output[$key] = $value;
516*04fd306cSNickeau                            }
517*04fd306cSNickeau                        } elseif (isset($output[$key])) {
518*04fd306cSNickeau                            throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping);
519*04fd306cSNickeau                        }
520*04fd306cSNickeau                        break;
521*04fd306cSNickeau                    default:
522*04fd306cSNickeau                        $value = self::parseScalar($mapping, $flags, [',', '}', "\n"], $i, null === $tag, $references, $isValueQuoted);
523*04fd306cSNickeau                        // Spec: Keys MUST be unique; first one wins.
524*04fd306cSNickeau                        // Parser cannot abort this mapping earlier, since lines
525*04fd306cSNickeau                        // are processed sequentially.
526*04fd306cSNickeau                        // But overwriting is allowed when a merge node is used in current block.
527*04fd306cSNickeau                        if ('<<' === $key) {
528*04fd306cSNickeau                            $output += $value;
529*04fd306cSNickeau                        } elseif ($allowOverwrite || !isset($output[$key])) {
530*04fd306cSNickeau                            if (!$isValueQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) {
531*04fd306cSNickeau                                $references[$matches['ref']] = $matches['value'];
532*04fd306cSNickeau                                $value = $matches['value'];
533*04fd306cSNickeau                            }
534*04fd306cSNickeau
535*04fd306cSNickeau                            if (null !== $tag) {
536*04fd306cSNickeau                                $output[$key] = new TaggedValue($tag, $value);
537*04fd306cSNickeau                            } else {
538*04fd306cSNickeau                                $output[$key] = $value;
539*04fd306cSNickeau                            }
540*04fd306cSNickeau                        } elseif (isset($output[$key])) {
541*04fd306cSNickeau                            throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping);
542*04fd306cSNickeau                        }
543*04fd306cSNickeau                        --$i;
544*04fd306cSNickeau                }
545*04fd306cSNickeau                ++$i;
546*04fd306cSNickeau
547*04fd306cSNickeau                continue 2;
548*04fd306cSNickeau            }
549*04fd306cSNickeau        }
550*04fd306cSNickeau
551*04fd306cSNickeau        throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $mapping), self::$parsedLineNumber + 1, null, self::$parsedFilename);
552*04fd306cSNickeau    }
553*04fd306cSNickeau
554*04fd306cSNickeau    /**
555*04fd306cSNickeau     * Evaluates scalars and replaces magic values.
556*04fd306cSNickeau     *
557*04fd306cSNickeau     * @return mixed
558*04fd306cSNickeau     *
559*04fd306cSNickeau     * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
560*04fd306cSNickeau     */
561*04fd306cSNickeau    private static function evaluateScalar(string $scalar, int $flags, array &$references = [], bool &$isQuotedString = null)
562*04fd306cSNickeau    {
563*04fd306cSNickeau        $isQuotedString = false;
564*04fd306cSNickeau        $scalar = trim($scalar);
565*04fd306cSNickeau
566*04fd306cSNickeau        if (0 === strpos($scalar, '*')) {
567*04fd306cSNickeau            if (false !== $pos = strpos($scalar, '#')) {
568*04fd306cSNickeau                $value = substr($scalar, 1, $pos - 2);
569*04fd306cSNickeau            } else {
570*04fd306cSNickeau                $value = substr($scalar, 1);
571*04fd306cSNickeau            }
572*04fd306cSNickeau
573*04fd306cSNickeau            // an unquoted *
574*04fd306cSNickeau            if (false === $value || '' === $value) {
575*04fd306cSNickeau                throw new ParseException('A reference must contain at least one character.', self::$parsedLineNumber + 1, $value, self::$parsedFilename);
576*04fd306cSNickeau            }
577*04fd306cSNickeau
578*04fd306cSNickeau            if (!\array_key_exists($value, $references)) {
579*04fd306cSNickeau                throw new ParseException(sprintf('Reference "%s" does not exist.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
580*04fd306cSNickeau            }
581*04fd306cSNickeau
582*04fd306cSNickeau            return $references[$value];
583*04fd306cSNickeau        }
584*04fd306cSNickeau
585*04fd306cSNickeau        $scalarLower = strtolower($scalar);
586*04fd306cSNickeau
587*04fd306cSNickeau        switch (true) {
588*04fd306cSNickeau            case 'null' === $scalarLower:
589*04fd306cSNickeau            case '' === $scalar:
590*04fd306cSNickeau            case '~' === $scalar:
591*04fd306cSNickeau                return null;
592*04fd306cSNickeau            case 'true' === $scalarLower:
593*04fd306cSNickeau                return true;
594*04fd306cSNickeau            case 'false' === $scalarLower:
595*04fd306cSNickeau                return false;
596*04fd306cSNickeau            case '!' === $scalar[0]:
597*04fd306cSNickeau                switch (true) {
598*04fd306cSNickeau                    case 0 === strpos($scalar, '!!str '):
599*04fd306cSNickeau                        $s = (string) substr($scalar, 6);
600*04fd306cSNickeau
601*04fd306cSNickeau                        if (\in_array($s[0] ?? '', ['"', "'"], true)) {
602*04fd306cSNickeau                            $isQuotedString = true;
603*04fd306cSNickeau                            $s = self::parseQuotedScalar($s);
604*04fd306cSNickeau                        }
605*04fd306cSNickeau
606*04fd306cSNickeau                        return $s;
607*04fd306cSNickeau                    case 0 === strpos($scalar, '! '):
608*04fd306cSNickeau                        return substr($scalar, 2);
609*04fd306cSNickeau                    case 0 === strpos($scalar, '!php/object'):
610*04fd306cSNickeau                        if (self::$objectSupport) {
611*04fd306cSNickeau                            if (!isset($scalar[12])) {
612*04fd306cSNickeau                                trigger_deprecation('symfony/yaml', '5.1', 'Using the !php/object tag without a value is deprecated.');
613*04fd306cSNickeau
614*04fd306cSNickeau                                return false;
615*04fd306cSNickeau                            }
616*04fd306cSNickeau
617*04fd306cSNickeau                            return unserialize(self::parseScalar(substr($scalar, 12)));
618*04fd306cSNickeau                        }
619*04fd306cSNickeau
620*04fd306cSNickeau                        if (self::$exceptionOnInvalidType) {
621*04fd306cSNickeau                            throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
622*04fd306cSNickeau                        }
623*04fd306cSNickeau
624*04fd306cSNickeau                        return null;
625*04fd306cSNickeau                    case 0 === strpos($scalar, '!php/const'):
626*04fd306cSNickeau                        if (self::$constantSupport) {
627*04fd306cSNickeau                            if (!isset($scalar[11])) {
628*04fd306cSNickeau                                trigger_deprecation('symfony/yaml', '5.1', 'Using the !php/const tag without a value is deprecated.');
629*04fd306cSNickeau
630*04fd306cSNickeau                                return '';
631*04fd306cSNickeau                            }
632*04fd306cSNickeau
633*04fd306cSNickeau                            $i = 0;
634*04fd306cSNickeau                            if (\defined($const = self::parseScalar(substr($scalar, 11), 0, null, $i, false))) {
635*04fd306cSNickeau                                return \constant($const);
636*04fd306cSNickeau                            }
637*04fd306cSNickeau
638*04fd306cSNickeau                            throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
639*04fd306cSNickeau                        }
640*04fd306cSNickeau                        if (self::$exceptionOnInvalidType) {
641*04fd306cSNickeau                            throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
642*04fd306cSNickeau                        }
643*04fd306cSNickeau
644*04fd306cSNickeau                        return null;
645*04fd306cSNickeau                    case 0 === strpos($scalar, '!!float '):
646*04fd306cSNickeau                        return (float) substr($scalar, 8);
647*04fd306cSNickeau                    case 0 === strpos($scalar, '!!binary '):
648*04fd306cSNickeau                        return self::evaluateBinaryScalar(substr($scalar, 9));
649*04fd306cSNickeau                }
650*04fd306cSNickeau
651*04fd306cSNickeau                throw new ParseException(sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.', $scalar), self::$parsedLineNumber, $scalar, self::$parsedFilename);
652*04fd306cSNickeau            case preg_match('/^(?:\+|-)?0o(?P<value>[0-7_]++)$/', $scalar, $matches):
653*04fd306cSNickeau                $value = str_replace('_', '', $matches['value']);
654*04fd306cSNickeau
655*04fd306cSNickeau                if ('-' === $scalar[0]) {
656*04fd306cSNickeau                    return -octdec($value);
657*04fd306cSNickeau                }
658*04fd306cSNickeau
659*04fd306cSNickeau                return octdec($value);
660*04fd306cSNickeau            case \in_array($scalar[0], ['+', '-', '.'], true) || is_numeric($scalar[0]):
661*04fd306cSNickeau                if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) {
662*04fd306cSNickeau                    $scalar = str_replace('_', '', $scalar);
663*04fd306cSNickeau                }
664*04fd306cSNickeau
665*04fd306cSNickeau                switch (true) {
666*04fd306cSNickeau                    case ctype_digit($scalar):
667*04fd306cSNickeau                        if (preg_match('/^0[0-7]+$/', $scalar)) {
668*04fd306cSNickeau                            trigger_deprecation('symfony/yaml', '5.1', 'Support for parsing numbers prefixed with 0 as octal numbers. They will be parsed as strings as of 6.0. Use "%s" to represent the octal number.', '0o'.substr($scalar, 1));
669*04fd306cSNickeau
670*04fd306cSNickeau                            return octdec($scalar);
671*04fd306cSNickeau                        }
672*04fd306cSNickeau
673*04fd306cSNickeau                        $cast = (int) $scalar;
674*04fd306cSNickeau
675*04fd306cSNickeau                        return ($scalar === (string) $cast) ? $cast : $scalar;
676*04fd306cSNickeau                    case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
677*04fd306cSNickeau                        if (preg_match('/^-0[0-7]+$/', $scalar)) {
678*04fd306cSNickeau                            trigger_deprecation('symfony/yaml', '5.1', 'Support for parsing numbers prefixed with 0 as octal numbers. They will be parsed as strings as of 6.0. Use "%s" to represent the octal number.', '-0o'.substr($scalar, 2));
679*04fd306cSNickeau
680*04fd306cSNickeau                            return -octdec(substr($scalar, 1));
681*04fd306cSNickeau                        }
682*04fd306cSNickeau
683*04fd306cSNickeau                        $cast = (int) $scalar;
684*04fd306cSNickeau
685*04fd306cSNickeau                        return ($scalar === (string) $cast) ? $cast : $scalar;
686*04fd306cSNickeau                    case is_numeric($scalar):
687*04fd306cSNickeau                    case Parser::preg_match(self::getHexRegex(), $scalar):
688*04fd306cSNickeau                        $scalar = str_replace('_', '', $scalar);
689*04fd306cSNickeau
690*04fd306cSNickeau                        return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar;
691*04fd306cSNickeau                    case '.inf' === $scalarLower:
692*04fd306cSNickeau                    case '.nan' === $scalarLower:
693*04fd306cSNickeau                        return -log(0);
694*04fd306cSNickeau                    case '-.inf' === $scalarLower:
695*04fd306cSNickeau                        return log(0);
696*04fd306cSNickeau                    case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar):
697*04fd306cSNickeau                        return (float) str_replace('_', '', $scalar);
698*04fd306cSNickeau                    case Parser::preg_match(self::getTimestampRegex(), $scalar):
699*04fd306cSNickeau                        // When no timezone is provided in the parsed date, YAML spec says we must assume UTC.
700*04fd306cSNickeau                        $time = new \DateTime($scalar, new \DateTimeZone('UTC'));
701*04fd306cSNickeau
702*04fd306cSNickeau                        if (Yaml::PARSE_DATETIME & $flags) {
703*04fd306cSNickeau                            return $time;
704*04fd306cSNickeau                        }
705*04fd306cSNickeau
706*04fd306cSNickeau                        try {
707*04fd306cSNickeau                            if (false !== $scalar = $time->getTimestamp()) {
708*04fd306cSNickeau                                return $scalar;
709*04fd306cSNickeau                            }
710*04fd306cSNickeau                        } catch (\ValueError $e) {
711*04fd306cSNickeau                            // no-op
712*04fd306cSNickeau                        }
713*04fd306cSNickeau
714*04fd306cSNickeau                        return $time->format('U');
715*04fd306cSNickeau                }
716*04fd306cSNickeau        }
717*04fd306cSNickeau
718*04fd306cSNickeau        return (string) $scalar;
719*04fd306cSNickeau    }
720*04fd306cSNickeau
721*04fd306cSNickeau    private static function parseTag(string $value, int &$i, int $flags): ?string
722*04fd306cSNickeau    {
723*04fd306cSNickeau        if ('!' !== $value[$i]) {
724*04fd306cSNickeau            return null;
725*04fd306cSNickeau        }
726*04fd306cSNickeau
727*04fd306cSNickeau        $tagLength = strcspn($value, " \t\n[]{},", $i + 1);
728*04fd306cSNickeau        $tag = substr($value, $i + 1, $tagLength);
729*04fd306cSNickeau
730*04fd306cSNickeau        $nextOffset = $i + $tagLength + 1;
731*04fd306cSNickeau        $nextOffset += strspn($value, ' ', $nextOffset);
732*04fd306cSNickeau
733*04fd306cSNickeau        if ('' === $tag && (!isset($value[$nextOffset]) || \in_array($value[$nextOffset], [']', '}', ','], true))) {
734*04fd306cSNickeau            throw new ParseException('Using the unquoted scalar value "!" is not supported. You must quote it.', self::$parsedLineNumber + 1, $value, self::$parsedFilename);
735*04fd306cSNickeau        }
736*04fd306cSNickeau
737*04fd306cSNickeau        // Is followed by a scalar and is a built-in tag
738*04fd306cSNickeau        if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || 'str' === $tag || 'php/const' === $tag || 'php/object' === $tag)) {
739*04fd306cSNickeau            // Manage in {@link self::evaluateScalar()}
740*04fd306cSNickeau            return null;
741*04fd306cSNickeau        }
742*04fd306cSNickeau
743*04fd306cSNickeau        $i = $nextOffset;
744*04fd306cSNickeau
745*04fd306cSNickeau        // Built-in tags
746*04fd306cSNickeau        if ('' !== $tag && '!' === $tag[0]) {
747*04fd306cSNickeau            throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
748*04fd306cSNickeau        }
749*04fd306cSNickeau
750*04fd306cSNickeau        if ('' !== $tag && !isset($value[$i])) {
751*04fd306cSNickeau            throw new ParseException(sprintf('Missing value for tag "%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
752*04fd306cSNickeau        }
753*04fd306cSNickeau
754*04fd306cSNickeau        if ('' === $tag || Yaml::PARSE_CUSTOM_TAGS & $flags) {
755*04fd306cSNickeau            return $tag;
756*04fd306cSNickeau        }
757*04fd306cSNickeau
758*04fd306cSNickeau        throw new ParseException(sprintf('Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
759*04fd306cSNickeau    }
760*04fd306cSNickeau
761*04fd306cSNickeau    public static function evaluateBinaryScalar(string $scalar): string
762*04fd306cSNickeau    {
763*04fd306cSNickeau        $parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar));
764*04fd306cSNickeau
765*04fd306cSNickeau        if (0 !== (\strlen($parsedBinaryData) % 4)) {
766*04fd306cSNickeau            throw new ParseException(sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', \strlen($parsedBinaryData)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
767*04fd306cSNickeau        }
768*04fd306cSNickeau
769*04fd306cSNickeau        if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) {
770*04fd306cSNickeau            throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
771*04fd306cSNickeau        }
772*04fd306cSNickeau
773*04fd306cSNickeau        return base64_decode($parsedBinaryData, true);
774*04fd306cSNickeau    }
775*04fd306cSNickeau
776*04fd306cSNickeau    private static function isBinaryString(string $value): bool
777*04fd306cSNickeau    {
778*04fd306cSNickeau        return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value);
779*04fd306cSNickeau    }
780*04fd306cSNickeau
781*04fd306cSNickeau    /**
782*04fd306cSNickeau     * Gets a regex that matches a YAML date.
783*04fd306cSNickeau     *
784*04fd306cSNickeau     * @see http://www.yaml.org/spec/1.2/spec.html#id2761573
785*04fd306cSNickeau     */
786*04fd306cSNickeau    private static function getTimestampRegex(): string
787*04fd306cSNickeau    {
788*04fd306cSNickeau        return <<<EOF
789*04fd306cSNickeau        ~^
790*04fd306cSNickeau        (?P<year>[0-9][0-9][0-9][0-9])
791*04fd306cSNickeau        -(?P<month>[0-9][0-9]?)
792*04fd306cSNickeau        -(?P<day>[0-9][0-9]?)
793*04fd306cSNickeau        (?:(?:[Tt]|[ \t]+)
794*04fd306cSNickeau        (?P<hour>[0-9][0-9]?)
795*04fd306cSNickeau        :(?P<minute>[0-9][0-9])
796*04fd306cSNickeau        :(?P<second>[0-9][0-9])
797*04fd306cSNickeau        (?:\.(?P<fraction>[0-9]*))?
798*04fd306cSNickeau        (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
799*04fd306cSNickeau        (?::(?P<tz_minute>[0-9][0-9]))?))?)?
800*04fd306cSNickeau        $~x
801*04fd306cSNickeauEOF;
802*04fd306cSNickeau    }
803*04fd306cSNickeau
804*04fd306cSNickeau    /**
805*04fd306cSNickeau     * Gets a regex that matches a YAML number in hexadecimal notation.
806*04fd306cSNickeau     */
807*04fd306cSNickeau    private static function getHexRegex(): string
808*04fd306cSNickeau    {
809*04fd306cSNickeau        return '~^0x[0-9a-f_]++$~i';
810*04fd306cSNickeau    }
811*04fd306cSNickeau}
812