[ Index ]

PHP Cross Reference of phpBB-3.3.12-deutsch

title

Body

[close]

/vendor/symfony/yaml/ -> Parser.php (source)

   1  <?php
   2  
   3  /*
   4   * This file is part of the Symfony package.
   5   *
   6   * (c) Fabien Potencier <fabien@symfony.com>
   7   *
   8   * For the full copyright and license information, please view the LICENSE
   9   * file that was distributed with this source code.
  10   */
  11  
  12  namespace Symfony\Component\Yaml;
  13  
  14  use Symfony\Component\Yaml\Exception\ParseException;
  15  use Symfony\Component\Yaml\Tag\TaggedValue;
  16  
  17  /**
  18   * Parser parses YAML strings to convert them to PHP arrays.
  19   *
  20   * @author Fabien Potencier <fabien@symfony.com>
  21   *
  22   * @final since version 3.4
  23   */
  24  class Parser
  25  {
  26      const TAG_PATTERN = '(?P<tag>![\w!.\/:-]+)';
  27      const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
  28  
  29      private $filename;
  30      private $offset = 0;
  31      private $totalNumberOfLines;
  32      private $lines = [];
  33      private $currentLineNb = -1;
  34      private $currentLine = '';
  35      private $refs = [];
  36      private $skippedLineNumbers = [];
  37      private $locallySkippedLineNumbers = [];
  38      private $refsBeingParsed = [];
  39  
  40      public function __construct()
  41      {
  42          if (\func_num_args() > 0) {
  43              @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);
  44  
  45              $this->offset = func_get_arg(0);
  46              if (\func_num_args() > 1) {
  47                  $this->totalNumberOfLines = func_get_arg(1);
  48              }
  49              if (\func_num_args() > 2) {
  50                  $this->skippedLineNumbers = func_get_arg(2);
  51              }
  52          }
  53      }
  54  
  55      /**
  56       * Parses a YAML file into a PHP value.
  57       *
  58       * @param string $filename The path to the YAML file to be parsed
  59       * @param int    $flags    A bit field of PARSE_* constants to customize the YAML parser behavior
  60       *
  61       * @return mixed The YAML converted to a PHP value
  62       *
  63       * @throws ParseException If the file could not be read or the YAML is not valid
  64       */
  65      public function parseFile($filename, $flags = 0)
  66      {
  67          if (!is_file($filename)) {
  68              throw new ParseException(sprintf('File "%s" does not exist.', $filename));
  69          }
  70  
  71          if (!is_readable($filename)) {
  72              throw new ParseException(sprintf('File "%s" cannot be read.', $filename));
  73          }
  74  
  75          $this->filename = $filename;
  76  
  77          try {
  78              return $this->parse(file_get_contents($filename), $flags);
  79          } finally {
  80              $this->filename = null;
  81          }
  82      }
  83  
  84      /**
  85       * Parses a YAML string to a PHP value.
  86       *
  87       * @param string $value A YAML string
  88       * @param int    $flags A bit field of PARSE_* constants to customize the YAML parser behavior
  89       *
  90       * @return mixed A PHP value
  91       *
  92       * @throws ParseException If the YAML is not valid
  93       */
  94      public function parse($value, $flags = 0)
  95      {
  96          if (\is_bool($flags)) {
  97              @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);
  98  
  99              if ($flags) {
 100                  $flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE;
 101              } else {
 102                  $flags = 0;
 103              }
 104          }
 105  
 106          if (\func_num_args() >= 3) {
 107              @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);
 108  
 109              if (func_get_arg(2)) {
 110                  $flags |= Yaml::PARSE_OBJECT;
 111              }
 112          }
 113  
 114          if (\func_num_args() >= 4) {
 115              @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);
 116  
 117              if (func_get_arg(3)) {
 118                  $flags |= Yaml::PARSE_OBJECT_FOR_MAP;
 119              }
 120          }
 121  
 122          if (Yaml::PARSE_KEYS_AS_STRINGS & $flags) {
 123              @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);
 124          }
 125  
 126          if (false === preg_match('//u', $value)) {
 127              throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename);
 128          }
 129  
 130          $this->refs = [];
 131  
 132          $mbEncoding = null;
 133          $e = null;
 134          $data = null;
 135  
 136          if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
 137              $mbEncoding = mb_internal_encoding();
 138              mb_internal_encoding('UTF-8');
 139          }
 140  
 141          try {
 142              $data = $this->doParse($value, $flags);
 143          } catch (\Exception $e) {
 144          } catch (\Throwable $e) {
 145          }
 146  
 147          if (null !== $mbEncoding) {
 148              mb_internal_encoding($mbEncoding);
 149          }
 150  
 151          $this->lines = [];
 152          $this->currentLine = '';
 153          $this->refs = [];
 154          $this->skippedLineNumbers = [];
 155          $this->locallySkippedLineNumbers = [];
 156          $this->totalNumberOfLines = null;
 157  
 158          if (null !== $e) {
 159              throw $e;
 160          }
 161  
 162          return $data;
 163      }
 164  
 165      private function doParse($value, $flags)
 166      {
 167          $this->currentLineNb = -1;
 168          $this->currentLine = '';
 169          $value = $this->cleanup($value);
 170          $this->lines = explode("\n", $value);
 171          $this->locallySkippedLineNumbers = [];
 172  
 173          if (null === $this->totalNumberOfLines) {
 174              $this->totalNumberOfLines = \count($this->lines);
 175          }
 176  
 177          if (!$this->moveToNextLine()) {
 178              return null;
 179          }
 180  
 181          $data = [];
 182          $context = null;
 183          $allowOverwrite = false;
 184  
 185          while ($this->isCurrentLineEmpty()) {
 186              if (!$this->moveToNextLine()) {
 187                  return null;
 188              }
 189          }
 190  
 191          // Resolves the tag and returns if end of the document
 192          if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, false)) && !$this->moveToNextLine()) {
 193              return new TaggedValue($tag, '');
 194          }
 195  
 196          do {
 197              if ($this->isCurrentLineEmpty()) {
 198                  continue;
 199              }
 200  
 201              // tab?
 202              if ("\t" === $this->currentLine[0]) {
 203                  throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
 204              }
 205  
 206              Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename);
 207  
 208              $isRef = $mergeNode = false;
 209              if (self::preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+))?$#u', rtrim($this->currentLine), $values)) {
 210                  if ($context && 'mapping' == $context) {
 211                      throw new ParseException('You cannot define a sequence item when in a mapping.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
 212                  }
 213                  $context = 'sequence';
 214  
 215                  if (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
 216                      $isRef = $matches['ref'];
 217                      $this->refsBeingParsed[] = $isRef;
 218                      $values['value'] = $matches['value'];
 219                  }
 220  
 221                  if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) {
 222                      @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);
 223                  }
 224  
 225                  // array
 226                  if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
 227                      $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags);
 228                  } elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) {
 229                      $data[] = new TaggedValue(
 230                          $subTag,
 231                          $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags)
 232                      );
 233                  } else {
 234                      if (
 235                          isset($values['leadspaces'])
 236                          && (
 237                              '!' === $values['value'][0]
 238                              || self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->trimTag($values['value']), $matches)
 239                          )
 240                      ) {
 241                          // this is a compact notation element, add to next block and parse
 242                          $block = $values['value'];
 243                          if ($this->isNextLineIndented()) {
 244                              $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1);
 245                          }
 246  
 247                          $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags);
 248                      } else {
 249                          $data[] = $this->parseValue($values['value'], $flags, $context);
 250                      }
 251                  }
 252                  if ($isRef) {
 253                      $this->refs[$isRef] = end($data);
 254                      array_pop($this->refsBeingParsed);
 255                  }
 256              } elseif (
 257                  self::preg_match('#^(?P<key>(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(\s++(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
 258                  && (false === strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"]))
 259              ) {
 260                  if ($context && 'sequence' == $context) {
 261                      throw new ParseException('You cannot define a mapping item when in a sequence.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
 262                  }
 263                  $context = 'mapping';
 264  
 265                  try {
 266                      $i = 0;
 267                      $evaluateKey = !(Yaml::PARSE_KEYS_AS_STRINGS & $flags);
 268  
 269                      // constants in key will be evaluated anyway
 270                      if (isset($values['key'][0]) && '!' === $values['key'][0] && Yaml::PARSE_CONSTANT & $flags) {
 271                          $evaluateKey = true;
 272                      }
 273  
 274                      $key = Inline::parseScalar($values['key'], 0, null, $i, $evaluateKey);
 275                  } catch (ParseException $e) {
 276                      $e->setParsedLine($this->getRealCurrentLineNb() + 1);
 277                      $e->setSnippet($this->currentLine);
 278  
 279                      throw $e;
 280                  }
 281  
 282                  if (!\is_string($key) && !\is_int($key)) {
 283                      $keyType = is_numeric($key) ? 'numeric key' : 'non-string key';
 284                      @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);
 285                  }
 286  
 287                  // Convert float keys to strings, to avoid being converted to integers by PHP
 288                  if (\is_float($key)) {
 289                      $key = (string) $key;
 290                  }
 291  
 292                  if ('<<' === $key && (!isset($values['value']) || !self::preg_match('#^&(?P<ref>[^ ]+)#u', $values['value'], $refMatches))) {
 293                      $mergeNode = true;
 294                      $allowOverwrite = true;
 295                      if (isset($values['value'][0]) && '*' === $values['value'][0]) {
 296                          $refName = substr(rtrim($values['value']), 1);
 297                          if (!\array_key_exists($refName, $this->refs)) {
 298                              if (false !== $pos = array_search($refName, $this->refsBeingParsed, true)) {
 299                                  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);
 300                              }
 301  
 302                              throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
 303                          }
 304  
 305                          $refValue = $this->refs[$refName];
 306  
 307                          if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) {
 308                              $refValue = (array) $refValue;
 309                          }
 310  
 311                          if (!\is_array($refValue)) {
 312                              throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
 313                          }
 314  
 315                          $data += $refValue; // array union
 316                      } else {
 317                          if (isset($values['value']) && '' !== $values['value']) {
 318                              $value = $values['value'];
 319                          } else {
 320                              $value = $this->getNextEmbedBlock();
 321                          }
 322                          $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags);
 323  
 324                          if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) {
 325                              $parsed = (array) $parsed;
 326                          }
 327  
 328                          if (!\is_array($parsed)) {
 329                              throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
 330                          }
 331  
 332                          if (isset($parsed[0])) {
 333                              // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
 334                              // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
 335                              // in the sequence override keys specified in later mapping nodes.
 336                              foreach ($parsed as $parsedItem) {
 337                                  if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) {
 338                                      $parsedItem = (array) $parsedItem;
 339                                  }
 340  
 341                                  if (!\is_array($parsedItem)) {
 342                                      throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename);
 343                                  }
 344  
 345                                  $data += $parsedItem; // array union
 346                              }
 347                          } else {
 348                              // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
 349                              // current mapping, unless the key already exists in it.
 350                              $data += $parsed; // array union
 351                          }
 352                      }
 353                  } elseif ('<<' !== $key && isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]++) *+(?P<value>.*)#u', $values['value'], $matches)) {
 354                      $isRef = $matches['ref'];
 355                      $this->refsBeingParsed[] = $isRef;
 356                      $values['value'] = $matches['value'];
 357                  }
 358  
 359                  $subTag = null;
 360                  if ($mergeNode) {
 361                      // Merge keys
 362                  } elseif (!isset($values['value']) || '' === $values['value'] || 0 === strpos($values['value'], '#') || (null !== $subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) {
 363                      // hash
 364                      // if next line is less indented or equal, then it means that the current value is null
 365                      if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
 366                          // Spec: Keys MUST be unique; first one wins.
 367                          // But overwriting is allowed when a merge node is used in current block.
 368                          if ($allowOverwrite || !isset($data[$key])) {
 369                              if (null !== $subTag) {
 370                                  $data[$key] = new TaggedValue($subTag, '');
 371                              } else {
 372                                  $data[$key] = null;
 373                              }
 374                          } else {
 375                              @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);
 376                          }
 377                      } else {
 378                          $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags);
 379                          if ('<<' === $key) {
 380                              $this->refs[$refMatches['ref']] = $value;
 381  
 382                              if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) {
 383                                  $value = (array) $value;
 384                              }
 385  
 386                              $data += $value;
 387                          } elseif ($allowOverwrite || !isset($data[$key])) {
 388                              // Spec: Keys MUST be unique; first one wins.
 389                              // But overwriting is allowed when a merge node is used in current block.
 390                              if (null !== $subTag) {
 391                                  $data[$key] = new TaggedValue($subTag, $value);
 392                              } else {
 393                                  $data[$key] = $value;
 394                              }
 395                          } else {
 396                              @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);
 397                          }
 398                      }
 399                  } else {
 400                      $value = $this->parseValue(rtrim($values['value']), $flags, $context);
 401                      // Spec: Keys MUST be unique; first one wins.
 402                      // But overwriting is allowed when a merge node is used in current block.
 403                      if ($allowOverwrite || !isset($data[$key])) {
 404                          $data[$key] = $value;
 405                      } else {
 406                          @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);
 407                      }
 408                  }
 409                  if ($isRef) {
 410                      $this->refs[$isRef] = $data[$key];
 411                      array_pop($this->refsBeingParsed);
 412                  }
 413              } else {
 414                  // multiple documents are not supported
 415                  if ('---' === $this->currentLine) {
 416                      throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
 417                  }
 418  
 419                  if ($deprecatedUsage = (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1])) {
 420                      @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);
 421                  }
 422  
 423                  // 1-liner optionally followed by newline(s)
 424                  if (\is_string($value) && $this->lines[0] === trim($value)) {
 425                      try {
 426                          $value = Inline::parse($this->lines[0], $flags, $this->refs);
 427                      } catch (ParseException $e) {
 428                          $e->setParsedLine($this->getRealCurrentLineNb() + 1);
 429                          $e->setSnippet($this->currentLine);
 430  
 431                          throw $e;
 432                      }
 433  
 434                      return $value;
 435                  }
 436  
 437                  // try to parse the value as a multi-line string as a last resort
 438                  if (0 === $this->currentLineNb) {
 439                      $previousLineWasNewline = false;
 440                      $previousLineWasTerminatedWithBackslash = false;
 441                      $value = '';
 442  
 443                      foreach ($this->lines as $line) {
 444                          if ('' !== ltrim($line) && '#' === ltrim($line)[0]) {
 445                              continue;
 446                          }
 447                          // If the indentation is not consistent at offset 0, it is to be considered as a ParseError
 448                          if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) {
 449                              throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
 450                          }
 451                          if ('' === trim($line)) {
 452                              $value .= "\n";
 453                          } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
 454                              $value .= ' ';
 455                          }
 456  
 457                          if ('' !== trim($line) && '\\' === substr($line, -1)) {
 458                              $value .= ltrim(substr($line, 0, -1));
 459                          } elseif ('' !== trim($line)) {
 460                              $value .= trim($line);
 461                          }
 462  
 463                          if ('' === trim($line)) {
 464                              $previousLineWasNewline = true;
 465                              $previousLineWasTerminatedWithBackslash = false;
 466                          } elseif ('\\' === substr($line, -1)) {
 467                              $previousLineWasNewline = false;
 468                              $previousLineWasTerminatedWithBackslash = true;
 469                          } else {
 470                              $previousLineWasNewline = false;
 471                              $previousLineWasTerminatedWithBackslash = false;
 472                          }
 473                      }
 474  
 475                      try {
 476                          return Inline::parse(trim($value));
 477                      } catch (ParseException $e) {
 478                          // fall-through to the ParseException thrown below
 479                      }
 480                  }
 481  
 482                  throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
 483              }
 484          } while ($this->moveToNextLine());
 485  
 486          if (null !== $tag) {
 487              $data = new TaggedValue($tag, $data);
 488          }
 489  
 490          if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && !\is_object($data) && 'mapping' === $context) {
 491              $object = new \stdClass();
 492  
 493              foreach ($data as $key => $value) {
 494                  $object->$key = $value;
 495              }
 496  
 497              $data = $object;
 498          }
 499  
 500          return empty($data) ? null : $data;
 501      }
 502  
 503      private function parseBlock($offset, $yaml, $flags)
 504      {
 505          $skippedLineNumbers = $this->skippedLineNumbers;
 506  
 507          foreach ($this->locallySkippedLineNumbers as $lineNumber) {
 508              if ($lineNumber < $offset) {
 509                  continue;
 510              }
 511  
 512              $skippedLineNumbers[] = $lineNumber;
 513          }
 514  
 515          $parser = new self();
 516          $parser->offset = $offset;
 517          $parser->totalNumberOfLines = $this->totalNumberOfLines;
 518          $parser->skippedLineNumbers = $skippedLineNumbers;
 519          $parser->refs = &$this->refs;
 520          $parser->refsBeingParsed = $this->refsBeingParsed;
 521  
 522          return $parser->doParse($yaml, $flags);
 523      }
 524  
 525      /**
 526       * Returns the current line number (takes the offset into account).
 527       *
 528       * @internal
 529       *
 530       * @return int The current line number
 531       */
 532      public function getRealCurrentLineNb()
 533      {
 534          $realCurrentLineNumber = $this->currentLineNb + $this->offset;
 535  
 536          foreach ($this->skippedLineNumbers as $skippedLineNumber) {
 537              if ($skippedLineNumber > $realCurrentLineNumber) {
 538                  break;
 539              }
 540  
 541              ++$realCurrentLineNumber;
 542          }
 543  
 544          return $realCurrentLineNumber;
 545      }
 546  
 547      /**
 548       * Returns the current line indentation.
 549       *
 550       * @return int The current line indentation
 551       */
 552      private function getCurrentLineIndentation()
 553      {
 554          return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' '));
 555      }
 556  
 557      /**
 558       * Returns the next embed block of YAML.
 559       *
 560       * @param int  $indentation The indent level at which the block is to be read, or null for default
 561       * @param bool $inSequence  True if the enclosing data structure is a sequence
 562       *
 563       * @return string A YAML string
 564       *
 565       * @throws ParseException When indentation problem are detected
 566       */
 567      private function getNextEmbedBlock($indentation = null, $inSequence = false)
 568      {
 569          $oldLineIndentation = $this->getCurrentLineIndentation();
 570  
 571          if (!$this->moveToNextLine()) {
 572              return '';
 573          }
 574  
 575          if (null === $indentation) {
 576              $newIndent = null;
 577              $movements = 0;
 578  
 579              do {
 580                  $EOF = false;
 581  
 582                  // empty and comment-like lines do not influence the indentation depth
 583                  if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
 584                      $EOF = !$this->moveToNextLine();
 585  
 586                      if (!$EOF) {
 587                          ++$movements;
 588                      }
 589                  } else {
 590                      $newIndent = $this->getCurrentLineIndentation();
 591                  }
 592              } while (!$EOF && null === $newIndent);
 593  
 594              for ($i = 0; $i < $movements; ++$i) {
 595                  $this->moveToPreviousLine();
 596              }
 597  
 598              $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem();
 599  
 600              if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
 601                  throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
 602              }
 603          } else {
 604              $newIndent = $indentation;
 605          }
 606  
 607          $data = [];
 608          if ($this->getCurrentLineIndentation() >= $newIndent) {
 609              $data[] = substr($this->currentLine, $newIndent);
 610          } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
 611              $data[] = $this->currentLine;
 612          } else {
 613              $this->moveToPreviousLine();
 614  
 615              return '';
 616          }
 617  
 618          if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) {
 619              // the previous line contained a dash but no item content, this line is a sequence item with the same indentation
 620              // and therefore no nested list or mapping
 621              $this->moveToPreviousLine();
 622  
 623              return '';
 624          }
 625  
 626          $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
 627          $isItComment = $this->isCurrentLineComment();
 628  
 629          while ($this->moveToNextLine()) {
 630              if ($isItComment && !$isItUnindentedCollection) {
 631                  $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
 632                  $isItComment = $this->isCurrentLineComment();
 633              }
 634  
 635              $indent = $this->getCurrentLineIndentation();
 636  
 637              if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) {
 638                  $this->moveToPreviousLine();
 639                  break;
 640              }
 641  
 642              if ($this->isCurrentLineBlank()) {
 643                  $data[] = substr($this->currentLine, $newIndent);
 644                  continue;
 645              }
 646  
 647              if ($indent >= $newIndent) {
 648                  $data[] = substr($this->currentLine, $newIndent);
 649              } elseif ($this->isCurrentLineComment()) {
 650                  $data[] = $this->currentLine;
 651              } elseif (0 == $indent) {
 652                  $this->moveToPreviousLine();
 653  
 654                  break;
 655              } else {
 656                  throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
 657              }
 658          }
 659  
 660          return implode("\n", $data);
 661      }
 662  
 663      /**
 664       * Moves the parser to the next line.
 665       *
 666       * @return bool
 667       */
 668      private function moveToNextLine()
 669      {
 670          if ($this->currentLineNb >= \count($this->lines) - 1) {
 671              return false;
 672          }
 673  
 674          $this->currentLine = $this->lines[++$this->currentLineNb];
 675  
 676          return true;
 677      }
 678  
 679      /**
 680       * Moves the parser to the previous line.
 681       *
 682       * @return bool
 683       */
 684      private function moveToPreviousLine()
 685      {
 686          if ($this->currentLineNb < 1) {
 687              return false;
 688          }
 689  
 690          $this->currentLine = $this->lines[--$this->currentLineNb];
 691  
 692          return true;
 693      }
 694  
 695      /**
 696       * Parses a YAML value.
 697       *
 698       * @param string $value   A YAML value
 699       * @param int    $flags   A bit field of PARSE_* constants to customize the YAML parser behavior
 700       * @param string $context The parser context (either sequence or mapping)
 701       *
 702       * @return mixed A PHP value
 703       *
 704       * @throws ParseException When reference does not exist
 705       */
 706      private function parseValue($value, $flags, $context)
 707      {
 708          if (0 === strpos($value, '*')) {
 709              if (false !== $pos = strpos($value, '#')) {
 710                  $value = substr($value, 1, $pos - 2);
 711              } else {
 712                  $value = substr($value, 1);
 713              }
 714  
 715              if (!\array_key_exists($value, $this->refs)) {
 716                  if (false !== $pos = array_search($value, $this->refsBeingParsed, true)) {
 717                      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);
 718                  }
 719  
 720                  throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename);
 721              }
 722  
 723              return $this->refs[$value];
 724          }
 725  
 726          if (self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
 727              $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
 728  
 729              $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), abs((int) $modifiers));
 730  
 731              if ('' !== $matches['tag']) {
 732                  if ('!!binary' === $matches['tag']) {
 733                      return Inline::evaluateBinaryScalar($data);
 734                  } elseif ('tagged' === $matches['tag']) {
 735                      return new TaggedValue(substr($matches['tag'], 1), $data);
 736                  } elseif ('!' !== $matches['tag']) {
 737                      @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);
 738                  }
 739              }
 740  
 741              return $data;
 742          }
 743  
 744          try {
 745              $quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null;
 746  
 747              // do not take following lines into account when the current line is a quoted single line value
 748              if (null !== $quotation && self::preg_match('/^'.$quotation.'.*'.$quotation.'(\s*#.*)?$/', $value)) {
 749                  return Inline::parse($value, $flags, $this->refs);
 750              }
 751  
 752              $lines = [];
 753  
 754              while ($this->moveToNextLine()) {
 755                  // unquoted strings end before the first unindented line
 756                  if (null === $quotation && 0 === $this->getCurrentLineIndentation()) {
 757                      $this->moveToPreviousLine();
 758  
 759                      break;
 760                  }
 761  
 762                  $lines[] = trim($this->currentLine);
 763  
 764                  // quoted string values end with a line that is terminated with the quotation character
 765                  $escapedLine = str_replace(['\\\\', '\\"'], '', $this->currentLine);
 766                  if ('' !== $escapedLine && substr($escapedLine, -1) === $quotation) {
 767                      break;
 768                  }
 769              }
 770  
 771              for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) {
 772                  if ('' === $lines[$i]) {
 773                      $value .= "\n";
 774                      $previousLineBlank = true;
 775                  } elseif ($previousLineBlank) {
 776                      $value .= $lines[$i];
 777                      $previousLineBlank = false;
 778                  } else {
 779                      $value .= ' '.$lines[$i];
 780                      $previousLineBlank = false;
 781                  }
 782              }
 783  
 784              Inline::$parsedLineNumber = $this->getRealCurrentLineNb();
 785  
 786              $parsedValue = Inline::parse($value, $flags, $this->refs);
 787  
 788              if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
 789                  throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename);
 790              }
 791  
 792              return $parsedValue;
 793          } catch (ParseException $e) {
 794              $e->setParsedLine($this->getRealCurrentLineNb() + 1);
 795              $e->setSnippet($this->currentLine);
 796  
 797              throw $e;
 798          }
 799      }
 800  
 801      /**
 802       * Parses a block scalar.
 803       *
 804       * @param string $style       The style indicator that was used to begin this block scalar (| or >)
 805       * @param string $chomping    The chomping indicator that was used to begin this block scalar (+ or -)
 806       * @param int    $indentation The indentation indicator that was used to begin this block scalar
 807       *
 808       * @return string The text value
 809       */
 810      private function parseBlockScalar($style, $chomping = '', $indentation = 0)
 811      {
 812          $notEOF = $this->moveToNextLine();
 813          if (!$notEOF) {
 814              return '';
 815          }
 816  
 817          $isCurrentLineBlank = $this->isCurrentLineBlank();
 818          $blockLines = [];
 819  
 820          // leading blank lines are consumed before determining indentation
 821          while ($notEOF && $isCurrentLineBlank) {
 822              // newline only if not EOF
 823              if ($notEOF = $this->moveToNextLine()) {
 824                  $blockLines[] = '';
 825                  $isCurrentLineBlank = $this->isCurrentLineBlank();
 826              }
 827          }
 828  
 829          // determine indentation if not specified
 830          if (0 === $indentation) {
 831              if (self::preg_match('/^ +/', $this->currentLine, $matches)) {
 832                  $indentation = \strlen($matches[0]);
 833              }
 834          }
 835  
 836          if ($indentation > 0) {
 837              $pattern = sprintf('/^ {%d}(.*)$/', $indentation);
 838  
 839              while (
 840                  $notEOF && (
 841                      $isCurrentLineBlank ||
 842                      self::preg_match($pattern, $this->currentLine, $matches)
 843                  )
 844              ) {
 845                  if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) {
 846                      $blockLines[] = substr($this->currentLine, $indentation);
 847                  } elseif ($isCurrentLineBlank) {
 848                      $blockLines[] = '';
 849                  } else {
 850                      $blockLines[] = $matches[1];
 851                  }
 852  
 853                  // newline only if not EOF
 854                  if ($notEOF = $this->moveToNextLine()) {
 855                      $isCurrentLineBlank = $this->isCurrentLineBlank();
 856                  }
 857              }
 858          } elseif ($notEOF) {
 859              $blockLines[] = '';
 860          }
 861  
 862          if ($notEOF) {
 863              $blockLines[] = '';
 864              $this->moveToPreviousLine();
 865          } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) {
 866              $blockLines[] = '';
 867          }
 868  
 869          // folded style
 870          if ('>' === $style) {
 871              $text = '';
 872              $previousLineIndented = false;
 873              $previousLineBlank = false;
 874  
 875              for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) {
 876                  if ('' === $blockLines[$i]) {
 877                      $text .= "\n";
 878                      $previousLineIndented = false;
 879                      $previousLineBlank = true;
 880                  } elseif (' ' === $blockLines[$i][0]) {
 881                      $text .= "\n".$blockLines[$i];
 882                      $previousLineIndented = true;
 883                      $previousLineBlank = false;
 884                  } elseif ($previousLineIndented) {
 885                      $text .= "\n".$blockLines[$i];
 886                      $previousLineIndented = false;
 887                      $previousLineBlank = false;
 888                  } elseif ($previousLineBlank || 0 === $i) {
 889                      $text .= $blockLines[$i];
 890                      $previousLineIndented = false;
 891                      $previousLineBlank = false;
 892                  } else {
 893                      $text .= ' '.$blockLines[$i];
 894                      $previousLineIndented = false;
 895                      $previousLineBlank = false;
 896                  }
 897              }
 898          } else {
 899              $text = implode("\n", $blockLines);
 900          }
 901  
 902          // deal with trailing newlines
 903          if ('' === $chomping) {
 904              $text = preg_replace('/\n+$/', "\n", $text);
 905          } elseif ('-' === $chomping) {
 906              $text = preg_replace('/\n+$/', '', $text);
 907          }
 908  
 909          return $text;
 910      }
 911  
 912      /**
 913       * Returns true if the next line is indented.
 914       *
 915       * @return bool Returns true if the next line is indented, false otherwise
 916       */
 917      private function isNextLineIndented()
 918      {
 919          $currentIndentation = $this->getCurrentLineIndentation();
 920          $movements = 0;
 921  
 922          do {
 923              $EOF = !$this->moveToNextLine();
 924  
 925              if (!$EOF) {
 926                  ++$movements;
 927              }
 928          } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()));
 929  
 930          if ($EOF) {
 931              return false;
 932          }
 933  
 934          $ret = $this->getCurrentLineIndentation() > $currentIndentation;
 935  
 936          for ($i = 0; $i < $movements; ++$i) {
 937              $this->moveToPreviousLine();
 938          }
 939  
 940          return $ret;
 941      }
 942  
 943      /**
 944       * Returns true if the current line is blank or if it is a comment line.
 945       *
 946       * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise
 947       */
 948      private function isCurrentLineEmpty()
 949      {
 950          return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
 951      }
 952  
 953      /**
 954       * Returns true if the current line is blank.
 955       *
 956       * @return bool Returns true if the current line is blank, false otherwise
 957       */
 958      private function isCurrentLineBlank()
 959      {
 960          return '' == trim($this->currentLine, ' ');
 961      }
 962  
 963      /**
 964       * Returns true if the current line is a comment line.
 965       *
 966       * @return bool Returns true if the current line is a comment line, false otherwise
 967       */
 968      private function isCurrentLineComment()
 969      {
 970          //checking explicitly the first char of the trim is faster than loops or strpos
 971          $ltrimmedLine = ltrim($this->currentLine, ' ');
 972  
 973          return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0];
 974      }
 975  
 976      private function isCurrentLineLastLineInDocument()
 977      {
 978          return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
 979      }
 980  
 981      /**
 982       * Cleanups a YAML string to be parsed.
 983       *
 984       * @param string $value The input YAML string
 985       *
 986       * @return string A cleaned up YAML string
 987       */
 988      private function cleanup($value)
 989      {
 990          $value = str_replace(["\r\n", "\r"], "\n", $value);
 991  
 992          // strip YAML header
 993          $count = 0;
 994          $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count);
 995          $this->offset += $count;
 996  
 997          // remove leading comments
 998          $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count);
 999          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  }


Generated: Sun Jun 23 12:25:44 2024 Cross-referenced by PHPXref 0.7.1