"Fossies" - the Fresh Open Source Software Archive

Member "grav/vendor/symfony/yaml/Inline.php" (1 Sep 2020, 30185 Bytes) of package /linux/www/grav-v1.6.27.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Inline.php" see the Fossies "Dox" file reference documentation.

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