vendor/symfony/yaml/Inline.php line 310

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