Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

Parser.php

Zuletzt modifiziert: 02.04.2025, 15:02 - Dateigröße: 44.96 KiB


0001  <?php
0002   
0003  /*
0004   * This file is part of the Symfony package.
0005   *
0006   * (c) Fabien Potencier <fabien@symfony.com>
0007   *
0008   * For the full copyright and license information, please view the LICENSE
0009   * file that was distributed with this source code.
0010   */
0011   
0012  namespace Symfony\Component\Yaml;
0013   
0014  use Symfony\Component\Yaml\Exception\ParseException;
0015  use Symfony\Component\Yaml\Tag\TaggedValue;
0016   
0017  /**
0018   * Parser parses YAML strings to convert them to PHP arrays.
0019   *
0020   * @author Fabien Potencier <fabien@symfony.com>
0021   *
0022   * @final since version 3.4
0023   */
0024  class Parser
0025  {
0026      const TAG_PATTERN = '(?P<tag>![\w!.\/:-]+)';
0027      const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
0028   
0029      private $filename;
0030      private $offset = 0;
0031      private $totalNumberOfLines;
0032      private $lines = [];
0033      private $currentLineNb = -1;
0034      private $currentLine = '';
0035      private $refs = [];
0036      private $skippedLineNumbers = [];
0037      private $locallySkippedLineNumbers = [];
0038      private $refsBeingParsed = [];
0039   
0040      public function __construct()
0041      {
0042          if (\func_num_args() > 0) {
0043              @trigger_error(sprintf('The constructor arguments $offset, $totalNumberOfLines, $skippedLineNumbers of %s are deprecated and will be removed in 4.0', self::class), \E_USER_DEPRECATED);
0044   
0045              $this->offset = func_get_arg(0);
0046              if (\func_num_args() > 1) {
0047                  $this->totalNumberOfLines = func_get_arg(1);
0048              }
0049              if (\func_num_args() > 2) {
0050                  $this->skippedLineNumbers = func_get_arg(2);
0051              }
0052          }
0053      }
0054   
0055      /**
0056       * Parses a YAML file into a PHP value.
0057       *
0058       * @param string $filename The path to the YAML file to be parsed
0059       * @param int    $flags    A bit field of PARSE_* constants to customize the YAML parser behavior
0060       *
0061       * @return mixed The YAML converted to a PHP value
0062       *
0063       * @throws ParseException If the file could not be read or the YAML is not valid
0064       */
0065      public function parseFile($filename, $flags = 0)
0066      {
0067          if (!is_file($filename)) {
0068              throw new ParseException(sprintf('File "%s" does not exist.', $filename));
0069          }
0070   
0071          if (!is_readable($filename)) {
0072              throw new ParseException(sprintf('File "%s" cannot be read.', $filename));
0073          }
0074   
0075          $this->filename = $filename;
0076   
0077          try {
0078              return $this->parse(file_get_contents($filename), $flags);
0079          } finally {
0080              $this->filename = null;
0081          }
0082      }
0083   
0084      /**
0085       * Parses a YAML string to a PHP value.
0086       *
0087       * @param string $value A YAML string
0088       * @param int    $flags A bit field of PARSE_* constants to customize the YAML parser behavior
0089       *
0090       * @return mixed A PHP value
0091       *
0092       * @throws ParseException If the YAML is not valid
0093       */
0094      public function parse($value, $flags = 0)
0095      {
0096          if (\is_bool($flags)) {
0097              @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE flag instead.', \E_USER_DEPRECATED);
0098   
0099              if ($flags) {
0100                  $flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE;
0101              } else {
0102                  $flags = 0;
0103              }
0104          }
0105   
0106          if (\func_num_args() >= 3) {
0107              @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT flag instead.', \E_USER_DEPRECATED);
0108   
0109              if (func_get_arg(2)) {
0110                  $flags |= Yaml::PARSE_OBJECT;
0111              }
0112          }
0113   
0114          if (\func_num_args() >= 4) {
0115              @trigger_error('Passing a boolean flag to toggle object for map support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', \E_USER_DEPRECATED);
0116   
0117              if (func_get_arg(3)) {
0118                  $flags |= Yaml::PARSE_OBJECT_FOR_MAP;
0119              }
0120          }
0121   
0122          if (Yaml::PARSE_KEYS_AS_STRINGS & $flags) {
0123              @trigger_error('Using the Yaml::PARSE_KEYS_AS_STRINGS flag is deprecated since Symfony 3.4 as it will be removed in 4.0. Quote your keys when they are evaluable instead.', \E_USER_DEPRECATED);
0124          }
0125   
0126          if (false === preg_match('//u', $value)) {
0127              throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename);
0128          }
0129   
0130          $this->refs = [];
0131   
0132          $mbEncoding = null;
0133          $e = null;
0134          $data = null;
0135   
0136          if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
0137              $mbEncoding = mb_internal_encoding();
0138              mb_internal_encoding('UTF-8');
0139          }
0140   
0141          try {
0142              $data = $this->doParse($value, $flags);
0143          } catch (\Exception $e) {
0144          } catch (\Throwable $e) {
0145          }
0146   
0147          if (null !== $mbEncoding) {
0148              mb_internal_encoding($mbEncoding);
0149          }
0150   
0151          $this->lines = [];
0152          $this->currentLine = '';
0153          $this->refs = [];
0154          $this->skippedLineNumbers = [];
0155          $this->locallySkippedLineNumbers = [];
0156          $this->totalNumberOfLines = null;
0157   
0158          if (null !== $e) {
0159              throw $e;
0160          }
0161   
0162          return $data;
0163      }
0164   
0165      private function doParse($value, $flags)
0166      {
0167          $this->currentLineNb = -1;
0168          $this->currentLine = '';
0169          $value = $this->cleanup($value);
0170          $this->lines = explode("\n", $value);
0171          $this->locallySkippedLineNumbers = [];
0172   
0173          if (null === $this->totalNumberOfLines) {
0174              $this->totalNumberOfLines = \count($this->lines);
0175          }
0176   
0177          if (!$this->moveToNextLine()) {
0178              return null;
0179          }
0180   
0181          $data = [];
0182          $context = null;
0183          $allowOverwrite = false;
0184   
0185          while ($this->isCurrentLineEmpty()) {
0186              if (!$this->moveToNextLine()) {
0187                  return null;
0188              }
0189          }
0190   
0191          // Resolves the tag and returns if end of the document
0192          if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, false)) && !$this->moveToNextLine()) {
0193              return new TaggedValue($tag, '');
0194          }
0195   
0196          do {
0197              if ($this->isCurrentLineEmpty()) {
0198                  continue;
0199              }
0200   
0201              // tab?
0202              if ("\t" === $this->currentLine[0]) {
0203                  throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0204              }
0205   
0206              Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename);
0207   
0208              $isRef = $mergeNode = false;
0209              if (self::preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+))?$#u', rtrim($this->currentLine), $values)) {
0210                  if ($context && 'mapping' == $context) {
0211                      throw new ParseException('You cannot define a sequence item when in a mapping.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0212                  }
0213                  $context = 'sequence';
0214   
0215                  if (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
0216                      $isRef = $matches['ref'];
0217                      $this->refsBeingParsed[] = $isRef;
0218                      $values['value'] = $matches['value'];
0219                  }
0220   
0221                  if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) {
0222                      @trigger_error($this->getDeprecationMessage('Starting an unquoted string with a question mark followed by a space is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.'), \E_USER_DEPRECATED);
0223                  }
0224   
0225                  // array
0226                  if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
0227                      $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags);
0228                  } elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) {
0229                      $data[] = new TaggedValue(
0230                          $subTag,
0231                          $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags)
0232                      );
0233                  } else {
0234                      if (
0235                          isset($values['leadspaces'])
0236                          && (
0237                              '!' === $values['value'][0]
0238                              || self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->trimTag($values['value']), $matches)
0239                          )
0240                      ) {
0241                          // this is a compact notation element, add to next block and parse
0242                          $block = $values['value'];
0243                          if ($this->isNextLineIndented()) {
0244                              $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1);
0245                          }
0246   
0247                          $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags);
0248                      } else {
0249                          $data[] = $this->parseValue($values['value'], $flags, $context);
0250                      }
0251                  }
0252                  if ($isRef) {
0253                      $this->refs[$isRef] = end($data);
0254                      array_pop($this->refsBeingParsed);
0255                  }
0256              } elseif (
0257                  self::preg_match('#^(?P<key>(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(\s++(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
0258                  && (false === strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"]))
0259              ) {
0260                  if ($context && 'sequence' == $context) {
0261                      throw new ParseException('You cannot define a mapping item when in a sequence.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
0262                  }
0263                  $context = 'mapping';
0264   
0265                  try {
0266                      $i = 0;
0267                      $evaluateKey = !(Yaml::PARSE_KEYS_AS_STRINGS & $flags);
0268   
0269                      // constants in key will be evaluated anyway
0270                      if (isset($values['key'][0]) && '!' === $values['key'][0] && Yaml::PARSE_CONSTANT & $flags) {
0271                          $evaluateKey = true;
0272                      }
0273   
0274                      $key = Inline::parseScalar($values['key'], 0, null, $i, $evaluateKey);
0275                  } catch (ParseException $e) {
0276                      $e->setParsedLine($this->getRealCurrentLineNb() + 1);
0277                      $e->setSnippet($this->currentLine);
0278   
0279                      throw $e;
0280                  }
0281   
0282                  if (!\is_string($key) && !\is_int($key)) {
0283                      $keyType = is_numeric($key) ? 'numeric key' : 'non-string key';
0284                      @trigger_error($this->getDeprecationMessage(sprintf('Implicit casting of %s to string is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0. Quote your evaluable mapping keys instead.', $keyType)), \E_USER_DEPRECATED);
0285                  }
0286   
0287                  // Convert float keys to strings, to avoid being converted to integers by PHP
0288                  if (\is_float($key)) {
0289                      $key = (string) $key;
0290                  }
0291   
0292                  if ('<<' === $key && (!isset($values['value']) || !self::preg_match('#^&(?P<ref>[^ ]+)#u', $values['value'], $refMatches))) {
0293                      $mergeNode = true;
0294                      $allowOverwrite = true;
0295                      if (isset($values['value'][0]) && '*' === $values['value'][0]) {
0296                          $refName = substr(rtrim($values['value']), 1);
0297                          if (!\array_key_exists($refName, $this->refs)) {
0298                              if (false !== $pos = array_search($refName, $this->refsBeingParsed, true)) {
0299                                  throw new ParseException(sprintf('Circular reference [%s, %s] detected for reference "%s".', implode(', ', \array_slice($this->refsBeingParsed, $pos)), $refName, $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename);
0300                              }
0301   
0302                              throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0303                          }
0304   
0305                          $refValue = $this->refs[$refName];
0306   
0307                          if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) {
0308                              $refValue = (array) $refValue;
0309                          }
0310   
0311                          if (!\is_array($refValue)) {
0312                              throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0313                          }
0314   
0315                          $data += $refValue; // array union
0316                      } else {
0317                          if (isset($values['value']) && '' !== $values['value']) {
0318                              $value = $values['value'];
0319                          } else {
0320                              $value = $this->getNextEmbedBlock();
0321                          }
0322                          $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags);
0323   
0324                          if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) {
0325                              $parsed = (array) $parsed;
0326                          }
0327   
0328                          if (!\is_array($parsed)) {
0329                              throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0330                          }
0331   
0332                          if (isset($parsed[0])) {
0333                              // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
0334                              // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
0335                              // in the sequence override keys specified in later mapping nodes.
0336                              foreach ($parsed as $parsedItem) {
0337                                  if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) {
0338                                      $parsedItem = (array) $parsedItem;
0339                                  }
0340   
0341                                  if (!\is_array($parsedItem)) {
0342                                      throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename);
0343                                  }
0344   
0345                                  $data += $parsedItem; // array union
0346                              }
0347                          } else {
0348                              // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
0349                              // current mapping, unless the key already exists in it.
0350                              $data += $parsed; // array union
0351                          }
0352                      }
0353                  } elseif ('<<' !== $key && isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]++) *+(?P<value>.*)#u', $values['value'], $matches)) {
0354                      $isRef = $matches['ref'];
0355                      $this->refsBeingParsed[] = $isRef;
0356                      $values['value'] = $matches['value'];
0357                  }
0358   
0359                  $subTag = null;
0360                  if ($mergeNode) {
0361                      // Merge keys
0362                  } elseif (!isset($values['value']) || '' === $values['value'] || 0 === strpos($values['value'], '#') || (null !== $subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) {
0363                      // hash
0364                      // if next line is less indented or equal, then it means that the current value is null
0365                      if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
0366                          // Spec: Keys MUST be unique; first one wins.
0367                          // But overwriting is allowed when a merge node is used in current block.
0368                          if ($allowOverwrite || !isset($data[$key])) {
0369                              if (null !== $subTag) {
0370                                  $data[$key] = new TaggedValue($subTag, '');
0371                              } else {
0372                                  $data[$key] = null;
0373                              }
0374                          } else {
0375                              @trigger_error($this->getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), \E_USER_DEPRECATED);
0376                          }
0377                      } else {
0378                          $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags);
0379                          if ('<<' === $key) {
0380                              $this->refs[$refMatches['ref']] = $value;
0381   
0382                              if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) {
0383                                  $value = (array) $value;
0384                              }
0385   
0386                              $data += $value;
0387                          } elseif ($allowOverwrite || !isset($data[$key])) {
0388                              // Spec: Keys MUST be unique; first one wins.
0389                              // But overwriting is allowed when a merge node is used in current block.
0390                              if (null !== $subTag) {
0391                                  $data[$key] = new TaggedValue($subTag, $value);
0392                              } else {
0393                                  $data[$key] = $value;
0394                              }
0395                          } else {
0396                              @trigger_error($this->getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), \E_USER_DEPRECATED);
0397                          }
0398                      }
0399                  } else {
0400                      $value = $this->parseValue(rtrim($values['value']), $flags, $context);
0401                      // Spec: Keys MUST be unique; first one wins.
0402                      // But overwriting is allowed when a merge node is used in current block.
0403                      if ($allowOverwrite || !isset($data[$key])) {
0404                          $data[$key] = $value;
0405                      } else {
0406                          @trigger_error($this->getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), \E_USER_DEPRECATED);
0407                      }
0408                  }
0409                  if ($isRef) {
0410                      $this->refs[$isRef] = $data[$key];
0411                      array_pop($this->refsBeingParsed);
0412                  }
0413              } else {
0414                  // multiple documents are not supported
0415                  if ('---' === $this->currentLine) {
0416                      throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
0417                  }
0418   
0419                  if ($deprecatedUsage = (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1])) {
0420                      @trigger_error($this->getDeprecationMessage('Starting an unquoted string with a question mark followed by a space is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.'), \E_USER_DEPRECATED);
0421                  }
0422   
0423                  // 1-liner optionally followed by newline(s)
0424                  if (\is_string($value) && $this->lines[0] === trim($value)) {
0425                      try {
0426                          $value = Inline::parse($this->lines[0], $flags, $this->refs);
0427                      } catch (ParseException $e) {
0428                          $e->setParsedLine($this->getRealCurrentLineNb() + 1);
0429                          $e->setSnippet($this->currentLine);
0430   
0431                          throw $e;
0432                      }
0433   
0434                      return $value;
0435                  }
0436   
0437                  // try to parse the value as a multi-line string as a last resort
0438                  if (0 === $this->currentLineNb) {
0439                      $previousLineWasNewline = false;
0440                      $previousLineWasTerminatedWithBackslash = false;
0441                      $value = '';
0442   
0443                      foreach ($this->lines as $line) {
0444                          if ('' !== ltrim($line) && '#' === ltrim($line)[0]) {
0445                              continue;
0446                          }
0447                          // If the indentation is not consistent at offset 0, it is to be considered as a ParseError
0448                          if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) {
0449                              throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0450                          }
0451                          if ('' === trim($line)) {
0452                              $value .= "\n";
0453                          } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
0454                              $value .= ' ';
0455                          }
0456   
0457                          if ('' !== trim($line) && '\\' === substr($line, -1)) {
0458                              $value .= ltrim(substr($line, 0, -1));
0459                          } elseif ('' !== trim($line)) {
0460                              $value .= trim($line);
0461                          }
0462   
0463                          if ('' === trim($line)) {
0464                              $previousLineWasNewline = true;
0465                              $previousLineWasTerminatedWithBackslash = false;
0466                          } elseif ('\\' === substr($line, -1)) {
0467                              $previousLineWasNewline = false;
0468                              $previousLineWasTerminatedWithBackslash = true;
0469                          } else {
0470                              $previousLineWasNewline = false;
0471                              $previousLineWasTerminatedWithBackslash = false;
0472                          }
0473                      }
0474   
0475                      try {
0476                          return Inline::parse(trim($value));
0477                      } catch (ParseException $e) {
0478                          // fall-through to the ParseException thrown below
0479                      }
0480                  }
0481   
0482                  throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0483              }
0484          } while ($this->moveToNextLine());
0485   
0486          if (null !== $tag) {
0487              $data = new TaggedValue($tag, $data);
0488          }
0489   
0490          if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && !\is_object($data) && 'mapping' === $context) {
0491              $object = new \stdClass();
0492   
0493              foreach ($data as $key => $value) {
0494                  $object->$key = $value;
0495              }
0496   
0497              $data = $object;
0498          }
0499   
0500          return empty($data) ? null : $data;
0501      }
0502   
0503      private function parseBlock($offset, $yaml, $flags)
0504      {
0505          $skippedLineNumbers = $this->skippedLineNumbers;
0506   
0507          foreach ($this->locallySkippedLineNumbers as $lineNumber) {
0508              if ($lineNumber < $offset) {
0509                  continue;
0510              }
0511   
0512              $skippedLineNumbers[] = $lineNumber;
0513          }
0514   
0515          $parser = new self();
0516          $parser->offset = $offset;
0517          $parser->totalNumberOfLines = $this->totalNumberOfLines;
0518          $parser->skippedLineNumbers = $skippedLineNumbers;
0519          $parser->refs = &$this->refs;
0520          $parser->refsBeingParsed = $this->refsBeingParsed;
0521   
0522          return $parser->doParse($yaml, $flags);
0523      }
0524   
0525      /**
0526       * Returns the current line number (takes the offset into account).
0527       *
0528       * @internal
0529       *
0530       * @return int The current line number
0531       */
0532      public function getRealCurrentLineNb()
0533      {
0534          $realCurrentLineNumber = $this->currentLineNb + $this->offset;
0535   
0536          foreach ($this->skippedLineNumbers as $skippedLineNumber) {
0537              if ($skippedLineNumber > $realCurrentLineNumber) {
0538                  break;
0539              }
0540   
0541              ++$realCurrentLineNumber;
0542          }
0543   
0544          return $realCurrentLineNumber;
0545      }
0546   
0547      /**
0548       * Returns the current line indentation.
0549       *
0550       * @return int The current line indentation
0551       */
0552      private function getCurrentLineIndentation()
0553      {
0554          return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' '));
0555      }
0556   
0557      /**
0558       * Returns the next embed block of YAML.
0559       *
0560       * @param int  $indentation The indent level at which the block is to be read, or null for default
0561       * @param bool $inSequence  True if the enclosing data structure is a sequence
0562       *
0563       * @return string A YAML string
0564       *
0565       * @throws ParseException When indentation problem are detected
0566       */
0567      private function getNextEmbedBlock($indentation = null, $inSequence = false)
0568      {
0569          $oldLineIndentation = $this->getCurrentLineIndentation();
0570   
0571          if (!$this->moveToNextLine()) {
0572              return '';
0573          }
0574   
0575          if (null === $indentation) {
0576              $newIndent = null;
0577              $movements = 0;
0578   
0579              do {
0580                  $EOF = false;
0581   
0582                  // empty and comment-like lines do not influence the indentation depth
0583                  if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
0584                      $EOF = !$this->moveToNextLine();
0585   
0586                      if (!$EOF) {
0587                          ++$movements;
0588                      }
0589                  } else {
0590                      $newIndent = $this->getCurrentLineIndentation();
0591                  }
0592              } while (!$EOF && null === $newIndent);
0593   
0594              for ($i = 0; $i < $movements; ++$i) {
0595                  $this->moveToPreviousLine();
0596              }
0597   
0598              $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem();
0599   
0600              if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
0601                  throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0602              }
0603          } else {
0604              $newIndent = $indentation;
0605          }
0606   
0607          $data = [];
0608          if ($this->getCurrentLineIndentation() >= $newIndent) {
0609              $data[] = substr($this->currentLine, $newIndent);
0610          } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
0611              $data[] = $this->currentLine;
0612          } else {
0613              $this->moveToPreviousLine();
0614   
0615              return '';
0616          }
0617   
0618          if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) {
0619              // the previous line contained a dash but no item content, this line is a sequence item with the same indentation
0620              // and therefore no nested list or mapping
0621              $this->moveToPreviousLine();
0622   
0623              return '';
0624          }
0625   
0626          $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
0627          $isItComment = $this->isCurrentLineComment();
0628   
0629          while ($this->moveToNextLine()) {
0630              if ($isItComment && !$isItUnindentedCollection) {
0631                  $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
0632                  $isItComment = $this->isCurrentLineComment();
0633              }
0634   
0635              $indent = $this->getCurrentLineIndentation();
0636   
0637              if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) {
0638                  $this->moveToPreviousLine();
0639                  break;
0640              }
0641   
0642              if ($this->isCurrentLineBlank()) {
0643                  $data[] = substr($this->currentLine, $newIndent);
0644                  continue;
0645              }
0646   
0647              if ($indent >= $newIndent) {
0648                  $data[] = substr($this->currentLine, $newIndent);
0649              } elseif ($this->isCurrentLineComment()) {
0650                  $data[] = $this->currentLine;
0651              } elseif (0 == $indent) {
0652                  $this->moveToPreviousLine();
0653   
0654                  break;
0655              } else {
0656                  throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0657              }
0658          }
0659   
0660          return implode("\n", $data);
0661      }
0662   
0663      /**
0664       * Moves the parser to the next line.
0665       *
0666       * @return bool
0667       */
0668      private function moveToNextLine()
0669      {
0670          if ($this->currentLineNb >= \count($this->lines) - 1) {
0671              return false;
0672          }
0673   
0674          $this->currentLine = $this->lines[++$this->currentLineNb];
0675   
0676          return true;
0677      }
0678   
0679      /**
0680       * Moves the parser to the previous line.
0681       *
0682       * @return bool
0683       */
0684      private function moveToPreviousLine()
0685      {
0686          if ($this->currentLineNb < 1) {
0687              return false;
0688          }
0689   
0690          $this->currentLine = $this->lines[--$this->currentLineNb];
0691   
0692          return true;
0693      }
0694   
0695      /**
0696       * Parses a YAML value.
0697       *
0698       * @param string $value   A YAML value
0699       * @param int    $flags   A bit field of PARSE_* constants to customize the YAML parser behavior
0700       * @param string $context The parser context (either sequence or mapping)
0701       *
0702       * @return mixed A PHP value
0703       *
0704       * @throws ParseException When reference does not exist
0705       */
0706      private function parseValue($value, $flags, $context)
0707      {
0708          if (0 === strpos($value, '*')) {
0709              if (false !== $pos = strpos($value, '#')) {
0710                  $value = substr($value, 1, $pos - 2);
0711              } else {
0712                  $value = substr($value, 1);
0713              }
0714   
0715              if (!\array_key_exists($value, $this->refs)) {
0716                  if (false !== $pos = array_search($value, $this->refsBeingParsed, true)) {
0717                      throw new ParseException(sprintf('Circular reference [%s, %s] detected for reference "%s".', implode(', ', \array_slice($this->refsBeingParsed, $pos)), $value, $value), $this->currentLineNb + 1, $this->currentLine, $this->filename);
0718                  }
0719   
0720                  throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename);
0721              }
0722   
0723              return $this->refs[$value];
0724          }
0725   
0726          if (self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
0727              $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
0728   
0729              $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), abs((int) $modifiers));
0730   
0731              if ('' !== $matches['tag']) {
0732                  if ('!!binary' === $matches['tag']) {
0733                      return Inline::evaluateBinaryScalar($data);
0734                  } elseif ('tagged' === $matches['tag']) {
0735                      return new TaggedValue(substr($matches['tag'], 1), $data);
0736                  } elseif ('!' !== $matches['tag']) {
0737                      @trigger_error($this->getDeprecationMessage(sprintf('Using the custom tag "%s" for the value "%s" is deprecated since Symfony 3.3. It will be replaced by an instance of %s in 4.0.', $matches['tag'], $data, TaggedValue::class)), \E_USER_DEPRECATED);
0738                  }
0739              }
0740   
0741              return $data;
0742          }
0743   
0744          try {
0745              $quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null;
0746   
0747              // do not take following lines into account when the current line is a quoted single line value
0748              if (null !== $quotation && self::preg_match('/^'.$quotation.'.*'.$quotation.'(\s*#.*)?$/', $value)) {
0749                  return Inline::parse($value, $flags, $this->refs);
0750              }
0751   
0752              $lines = [];
0753   
0754              while ($this->moveToNextLine()) {
0755                  // unquoted strings end before the first unindented line
0756                  if (null === $quotation && 0 === $this->getCurrentLineIndentation()) {
0757                      $this->moveToPreviousLine();
0758   
0759                      break;
0760                  }
0761   
0762                  $lines[] = trim($this->currentLine);
0763   
0764                  // quoted string values end with a line that is terminated with the quotation character
0765                  $escapedLine = str_replace(['\\\\', '\\"'], '', $this->currentLine);
0766                  if ('' !== $escapedLine && substr($escapedLine, -1) === $quotation) {
0767                      break;
0768                  }
0769              }
0770   
0771              for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) {
0772                  if ('' === $lines[$i]) {
0773                      $value .= "\n";
0774                      $previousLineBlank = true;
0775                  } elseif ($previousLineBlank) {
0776                      $value .= $lines[$i];
0777                      $previousLineBlank = false;
0778                  } else {
0779                      $value .= ' '.$lines[$i];
0780                      $previousLineBlank = false;
0781                  }
0782              }
0783   
0784              Inline::$parsedLineNumber = $this->getRealCurrentLineNb();
0785   
0786              $parsedValue = Inline::parse($value, $flags, $this->refs);
0787   
0788              if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
0789                  throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename);
0790              }
0791   
0792              return $parsedValue;
0793          } catch (ParseException $e) {
0794              $e->setParsedLine($this->getRealCurrentLineNb() + 1);
0795              $e->setSnippet($this->currentLine);
0796   
0797              throw $e;
0798          }
0799      }
0800   
0801      /**
0802       * Parses a block scalar.
0803       *
0804       * @param string $style       The style indicator that was used to begin this block scalar (| or >)
0805       * @param string $chomping    The chomping indicator that was used to begin this block scalar (+ or -)
0806       * @param int    $indentation The indentation indicator that was used to begin this block scalar
0807       *
0808       * @return string The text value
0809       */
0810      private function parseBlockScalar($style, $chomping = '', $indentation = 0)
0811      {
0812          $notEOF = $this->moveToNextLine();
0813          if (!$notEOF) {
0814              return '';
0815          }
0816   
0817          $isCurrentLineBlank = $this->isCurrentLineBlank();
0818          $blockLines = [];
0819   
0820          // leading blank lines are consumed before determining indentation
0821          while ($notEOF && $isCurrentLineBlank) {
0822              // newline only if not EOF
0823              if ($notEOF = $this->moveToNextLine()) {
0824                  $blockLines[] = '';
0825                  $isCurrentLineBlank = $this->isCurrentLineBlank();
0826              }
0827          }
0828   
0829          // determine indentation if not specified
0830          if (0 === $indentation) {
0831              if (self::preg_match('/^ +/', $this->currentLine, $matches)) {
0832                  $indentation = \strlen($matches[0]);
0833              }
0834          }
0835   
0836          if ($indentation > 0) {
0837              $pattern = sprintf('/^ {%d}(.*)$/', $indentation);
0838   
0839              while (
0840                  $notEOF && (
0841                      $isCurrentLineBlank ||
0842                      self::preg_match($pattern, $this->currentLine, $matches)
0843                  )
0844              ) {
0845                  if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) {
0846                      $blockLines[] = substr($this->currentLine, $indentation);
0847                  } elseif ($isCurrentLineBlank) {
0848                      $blockLines[] = '';
0849                  } else {
0850                      $blockLines[] = $matches[1];
0851                  }
0852   
0853                  // newline only if not EOF
0854                  if ($notEOF = $this->moveToNextLine()) {
0855                      $isCurrentLineBlank = $this->isCurrentLineBlank();
0856                  }
0857              }
0858          } elseif ($notEOF) {
0859              $blockLines[] = '';
0860          }
0861   
0862          if ($notEOF) {
0863              $blockLines[] = '';
0864              $this->moveToPreviousLine();
0865          } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) {
0866              $blockLines[] = '';
0867          }
0868   
0869          // folded style
0870          if ('>' === $style) {
0871              $text = '';
0872              $previousLineIndented = false;
0873              $previousLineBlank = false;
0874   
0875              for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) {
0876                  if ('' === $blockLines[$i]) {
0877                      $text .= "\n";
0878                      $previousLineIndented = false;
0879                      $previousLineBlank = true;
0880                  } elseif (' ' === $blockLines[$i][0]) {
0881                      $text .= "\n".$blockLines[$i];
0882                      $previousLineIndented = true;
0883                      $previousLineBlank = false;
0884                  } elseif ($previousLineIndented) {
0885                      $text .= "\n".$blockLines[$i];
0886                      $previousLineIndented = false;
0887                      $previousLineBlank = false;
0888                  } elseif ($previousLineBlank || 0 === $i) {
0889                      $text .= $blockLines[$i];
0890                      $previousLineIndented = false;
0891                      $previousLineBlank = false;
0892                  } else {
0893                      $text .= ' '.$blockLines[$i];
0894                      $previousLineIndented = false;
0895                      $previousLineBlank = false;
0896                  }
0897              }
0898          } else {
0899              $text = implode("\n", $blockLines);
0900          }
0901   
0902          // deal with trailing newlines
0903          if ('' === $chomping) {
0904              $text = preg_replace('/\n+$/', "\n", $text);
0905          } elseif ('-' === $chomping) {
0906              $text = preg_replace('/\n+$/', '', $text);
0907          }
0908   
0909          return $text;
0910      }
0911   
0912      /**
0913       * Returns true if the next line is indented.
0914       *
0915       * @return bool Returns true if the next line is indented, false otherwise
0916       */
0917      private function isNextLineIndented()
0918      {
0919          $currentIndentation = $this->getCurrentLineIndentation();
0920          $movements = 0;
0921   
0922          do {
0923              $EOF = !$this->moveToNextLine();
0924   
0925              if (!$EOF) {
0926                  ++$movements;
0927              }
0928          } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()));
0929   
0930          if ($EOF) {
0931              return false;
0932          }
0933   
0934          $ret = $this->getCurrentLineIndentation() > $currentIndentation;
0935   
0936          for ($i = 0; $i < $movements; ++$i) {
0937              $this->moveToPreviousLine();
0938          }
0939   
0940          return $ret;
0941      }
0942   
0943      /**
0944       * Returns true if the current line is blank or if it is a comment line.
0945       *
0946       * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise
0947       */
0948      private function isCurrentLineEmpty()
0949      {
0950          return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
0951      }
0952   
0953      /**
0954       * Returns true if the current line is blank.
0955       *
0956       * @return bool Returns true if the current line is blank, false otherwise
0957       */
0958      private function isCurrentLineBlank()
0959      {
0960          return '' == trim($this->currentLine, ' ');
0961      }
0962   
0963      /**
0964       * Returns true if the current line is a comment line.
0965       *
0966       * @return bool Returns true if the current line is a comment line, false otherwise
0967       */
0968      private function isCurrentLineComment()
0969      {
0970          //checking explicitly the first char of the trim is faster than loops or strpos
0971          $ltrimmedLine = ltrim($this->currentLine, ' ');
0972   
0973          return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0];
0974      }
0975   
0976      private function isCurrentLineLastLineInDocument()
0977      {
0978          return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
0979      }
0980   
0981      /**
0982       * Cleanups a YAML string to be parsed.
0983       *
0984       * @param string $value The input YAML string
0985       *
0986       * @return string A cleaned up YAML string
0987       */
0988      private function cleanup($value)
0989      {
0990          $value = str_replace(["\r\n", "\r"], "\n", $value);
0991   
0992          // strip YAML header
0993          $count = 0;
0994          $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count);
0995          $this->offset += $count;
0996   
0997          // remove leading comments
0998          $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count);
0999          if (1 === $count) {
1000              // items have been removed, update the offset
1001              $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
1002              $value = $trimmedValue;
1003          }
1004   
1005          // remove start of the document marker (---)
1006          $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count);
1007          if (1 === $count) {
1008              // items have been removed, update the offset
1009              $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
1010              $value = $trimmedValue;
1011   
1012              // remove end of the document marker (...)
1013              $value = preg_replace('#\.\.\.\s*$#', '', $value);
1014          }
1015   
1016          return $value;
1017      }
1018   
1019      /**
1020       * Returns true if the next line starts unindented collection.
1021       *
1022       * @return bool Returns true if the next line starts unindented collection, false otherwise
1023       */
1024      private function isNextLineUnIndentedCollection()
1025      {
1026          $currentIndentation = $this->getCurrentLineIndentation();
1027          $movements = 0;
1028   
1029          do {
1030              $EOF = !$this->moveToNextLine();
1031   
1032              if (!$EOF) {
1033                  ++$movements;
1034              }
1035          } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()));
1036   
1037          if ($EOF) {
1038              return false;
1039          }
1040   
1041          $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem();
1042   
1043          for ($i = 0; $i < $movements; ++$i) {
1044              $this->moveToPreviousLine();
1045          }
1046   
1047          return $ret;
1048      }
1049   
1050      /**
1051       * Returns true if the string is un-indented collection item.
1052       *
1053       * @return bool Returns true if the string is un-indented collection item, false otherwise
1054       */
1055      private function isStringUnIndentedCollectionItem()
1056      {
1057          return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- ');
1058      }
1059   
1060      /**
1061       * A local wrapper for `preg_match` which will throw a ParseException if there
1062       * is an internal error in the PCRE engine.
1063       *
1064       * This avoids us needing to check for "false" every time PCRE is used
1065       * in the YAML engine
1066       *
1067       * @throws ParseException on a PCRE internal error
1068       *
1069       * @see preg_last_error()
1070       *
1071       * @internal
1072       */
1073      public static function preg_match($pattern, $subject, &$matches = null, $flags = 0, $offset = 0)
1074      {
1075          if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) {
1076              switch (preg_last_error()) {
1077                  case \PREG_INTERNAL_ERROR:
1078                      $error = 'Internal PCRE error.';
1079                      break;
1080                  case \PREG_BACKTRACK_LIMIT_ERROR:
1081                      $error = 'pcre.backtrack_limit reached.';
1082                      break;
1083                  case \PREG_RECURSION_LIMIT_ERROR:
1084                      $error = 'pcre.recursion_limit reached.';
1085                      break;
1086                  case \PREG_BAD_UTF8_ERROR:
1087                      $error = 'Malformed UTF-8 data.';
1088                      break;
1089                  case \PREG_BAD_UTF8_OFFSET_ERROR:
1090                      $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
1091                      break;
1092                  default:
1093                      $error = 'Error.';
1094              }
1095   
1096              throw new ParseException($error);
1097          }
1098   
1099          return $ret;
1100      }
1101   
1102      /**
1103       * Trim the tag on top of the value.
1104       *
1105       * Prevent values such as `!foo {quz: bar}` to be considered as
1106       * a mapping block.
1107       */
1108      private function trimTag($value)
1109      {
1110          if ('!' === $value[0]) {
1111              return ltrim(substr($value, 1, strcspn($value, " \r\n", 1)), ' ');
1112          }
1113   
1114          return $value;
1115      }
1116   
1117      /**
1118       * @return string|null
1119       */
1120      private function getLineTag($value, $flags, $nextLineCheck = true)
1121      {
1122          if ('' === $value || '!' !== $value[0] || 1 !== self::preg_match('/^'.self::TAG_PATTERN.' *( +#.*)?$/', $value, $matches)) {
1123              return null;
1124          }
1125   
1126          if ($nextLineCheck && !$this->isNextLineIndented()) {
1127              return null;
1128          }
1129   
1130          $tag = substr($matches['tag'], 1);
1131   
1132          // Built-in tags
1133          if ($tag && '!' === $tag[0]) {
1134              throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
1135          }
1136   
1137          if (Yaml::PARSE_CUSTOM_TAGS & $flags) {
1138              return $tag;
1139          }
1140   
1141          throw new ParseException(sprintf('Tags support is not enabled. You must use the flag `Yaml::PARSE_CUSTOM_TAGS` to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
1142      }
1143   
1144      private function getDeprecationMessage($message)
1145      {
1146          $message = rtrim($message, '.');
1147   
1148          if (null !== $this->filename) {
1149              $message .= ' in '.$this->filename;
1150          }
1151   
1152          $message .= ' on line '.($this->getRealCurrentLineNb() + 1);
1153   
1154          return $message.'.';
1155      }
1156  }
1157