vendor/symfony/yaml/Inline.php line 697

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