[ Index ]

PHP Cross Reference of phpBB-3.2.11-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  
  16  /**
  17   * Parser parses YAML strings to convert them to PHP arrays.
  18   *
  19   * @author Fabien Potencier <fabien@symfony.com>
  20   */
  21  class Parser
  22  {
  23      const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
  24      // BC - wrongly named
  25      const FOLDED_SCALAR_PATTERN = self::BLOCK_SCALAR_HEADER_PATTERN;
  26  
  27      private $offset = 0;
  28      private $totalNumberOfLines;
  29      private $lines = array();
  30      private $currentLineNb = -1;
  31      private $currentLine = '';
  32      private $refs = array();
  33      private $skippedLineNumbers = array();
  34      private $locallySkippedLineNumbers = array();
  35  
  36      /**
  37       * @param int      $offset             The offset of YAML document (used for line numbers in error messages)
  38       * @param int|null $totalNumberOfLines The overall number of lines being parsed
  39       * @param int[]    $skippedLineNumbers Number of comment lines that have been skipped by the parser
  40       */
  41      public function __construct($offset = 0, $totalNumberOfLines = null, array $skippedLineNumbers = array())
  42      {
  43          $this->offset = $offset;
  44          $this->totalNumberOfLines = $totalNumberOfLines;
  45          $this->skippedLineNumbers = $skippedLineNumbers;
  46      }
  47  
  48      /**
  49       * Parses a YAML string to a PHP value.
  50       *
  51       * @param string $value                  A YAML string
  52       * @param bool   $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
  53       * @param bool   $objectSupport          True if object support is enabled, false otherwise
  54       * @param bool   $objectForMap           True if maps should return a stdClass instead of array()
  55       *
  56       * @return mixed A PHP value
  57       *
  58       * @throws ParseException If the YAML is not valid
  59       */
  60      public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
  61      {
  62          if (false === preg_match('//u', $value)) {
  63              throw new ParseException('The YAML value does not appear to be valid UTF-8.');
  64          }
  65  
  66          $this->refs = array();
  67  
  68          $mbEncoding = null;
  69          $e = null;
  70          $data = null;
  71  
  72          if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
  73              $mbEncoding = mb_internal_encoding();
  74              mb_internal_encoding('UTF-8');
  75          }
  76  
  77          try {
  78              $data = $this->doParse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
  79          } catch (\Exception $e) {
  80          } catch (\Throwable $e) {
  81          }
  82  
  83          if (null !== $mbEncoding) {
  84              mb_internal_encoding($mbEncoding);
  85          }
  86  
  87          $this->lines = array();
  88          $this->currentLine = '';
  89          $this->refs = array();
  90          $this->skippedLineNumbers = array();
  91          $this->locallySkippedLineNumbers = array();
  92  
  93          if (null !== $e) {
  94              throw $e;
  95          }
  96  
  97          return $data;
  98      }
  99  
 100      private function doParse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
 101      {
 102          $this->currentLineNb = -1;
 103          $this->currentLine = '';
 104          $value = $this->cleanup($value);
 105          $this->lines = explode("\n", $value);
 106          $this->locallySkippedLineNumbers = array();
 107  
 108          if (null === $this->totalNumberOfLines) {
 109              $this->totalNumberOfLines = \count($this->lines);
 110          }
 111  
 112          $data = array();
 113          $context = null;
 114          $allowOverwrite = false;
 115  
 116          while ($this->moveToNextLine()) {
 117              if ($this->isCurrentLineEmpty()) {
 118                  continue;
 119              }
 120  
 121              // tab?
 122              if ("\t" === $this->currentLine[0]) {
 123                  throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
 124              }
 125  
 126              $isRef = $mergeNode = false;
 127              if (self::preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+))?$#u', rtrim($this->currentLine), $values)) {
 128                  if ($context && 'mapping' == $context) {
 129                      throw new ParseException('You cannot define a sequence item when in a mapping', $this->getRealCurrentLineNb() + 1, $this->currentLine);
 130                  }
 131                  $context = 'sequence';
 132  
 133                  if (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
 134                      $isRef = $matches['ref'];
 135                      $values['value'] = $matches['value'];
 136                  }
 137  
 138                  // array
 139                  if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
 140                      $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap);
 141                  } else {
 142                      if (isset($values['leadspaces'])
 143                          && self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+))?$#u', rtrim($values['value']), $matches)
 144                      ) {
 145                          // this is a compact notation element, add to next block and parse
 146                          $block = $values['value'];
 147                          if ($this->isNextLineIndented()) {
 148                              $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1);
 149                          }
 150  
 151                          $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $exceptionOnInvalidType, $objectSupport, $objectForMap);
 152                      } else {
 153                          $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context);
 154                      }
 155                  }
 156                  if ($isRef) {
 157                      $this->refs[$isRef] = end($data);
 158                  }
 159              } elseif (
 160                  self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
 161                  && (false === strpos($values['key'], ' #') || \in_array($values['key'][0], array('"', "'")))
 162              ) {
 163                  if ($context && 'sequence' == $context) {
 164                      throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine);
 165                  }
 166                  $context = 'mapping';
 167  
 168                  // force correct settings
 169                  Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
 170                  try {
 171                      $key = Inline::parseScalar($values['key']);
 172                  } catch (ParseException $e) {
 173                      $e->setParsedLine($this->getRealCurrentLineNb() + 1);
 174                      $e->setSnippet($this->currentLine);
 175  
 176                      throw $e;
 177                  }
 178  
 179                  // Convert float keys to strings, to avoid being converted to integers by PHP
 180                  if (\is_float($key)) {
 181                      $key = (string) $key;
 182                  }
 183  
 184                  if ('<<' === $key && (!isset($values['value']) || !self::preg_match('#^&(?P<ref>[^ ]+)#u', $values['value'], $refMatches))) {
 185                      $mergeNode = true;
 186                      $allowOverwrite = true;
 187                      if (isset($values['value']) && 0 === strpos($values['value'], '*')) {
 188                          $refName = substr($values['value'], 1);
 189                          if (!array_key_exists($refName, $this->refs)) {
 190                              throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine);
 191                          }
 192  
 193                          $refValue = $this->refs[$refName];
 194  
 195                          if (!\is_array($refValue)) {
 196                              throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
 197                          }
 198  
 199                          $data += $refValue; // array union
 200                      } else {
 201                          if (isset($values['value']) && '' !== $values['value']) {
 202                              $value = $values['value'];
 203                          } else {
 204                              $value = $this->getNextEmbedBlock();
 205                          }
 206                          $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
 207  
 208                          if (!\is_array($parsed)) {
 209                              throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
 210                          }
 211  
 212                          if (isset($parsed[0])) {
 213                              // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
 214                              // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
 215                              // in the sequence override keys specified in later mapping nodes.
 216                              foreach ($parsed as $parsedItem) {
 217                                  if (!\is_array($parsedItem)) {
 218                                      throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem);
 219                                  }
 220  
 221                                  $data += $parsedItem; // array union
 222                              }
 223                          } else {
 224                              // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
 225                              // current mapping, unless the key already exists in it.
 226                              $data += $parsed; // array union
 227                          }
 228                      }
 229                  } elseif ('<<' !== $key && isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
 230                      $isRef = $matches['ref'];
 231                      $values['value'] = $matches['value'];
 232                  }
 233  
 234                  if ($mergeNode) {
 235                      // Merge keys
 236                  } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#') || '<<' === $key) {
 237                      // hash
 238                      // if next line is less indented or equal, then it means that the current value is null
 239                      if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
 240                          // Spec: Keys MUST be unique; first one wins.
 241                          // But overwriting is allowed when a merge node is used in current block.
 242                          if ($allowOverwrite || !isset($data[$key])) {
 243                              $data[$key] = null;
 244                          }
 245                      } else {
 246                          $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap);
 247  
 248                          if ('<<' === $key) {
 249                              $this->refs[$refMatches['ref']] = $value;
 250                              $data += $value;
 251                          } elseif ($allowOverwrite || !isset($data[$key])) {
 252                              // Spec: Keys MUST be unique; first one wins.
 253                              // But overwriting is allowed when a merge node is used in current block.
 254                              $data[$key] = $value;
 255                          }
 256                      }
 257                  } else {
 258                      $value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context);
 259                      // Spec: Keys MUST be unique; first one wins.
 260                      // But overwriting is allowed when a merge node is used in current block.
 261                      if ($allowOverwrite || !isset($data[$key])) {
 262                          $data[$key] = $value;
 263                      }
 264                  }
 265                  if ($isRef) {
 266                      $this->refs[$isRef] = $data[$key];
 267                  }
 268              } else {
 269                  // multiple documents are not supported
 270                  if ('---' === $this->currentLine) {
 271                      throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine);
 272                  }
 273  
 274                  // 1-liner optionally followed by newline(s)
 275                  if (\is_string($value) && $this->lines[0] === trim($value)) {
 276                      try {
 277                          $value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
 278                      } catch (ParseException $e) {
 279                          $e->setParsedLine($this->getRealCurrentLineNb() + 1);
 280                          $e->setSnippet($this->currentLine);
 281  
 282                          throw $e;
 283                      }
 284  
 285                      return $value;
 286                  }
 287  
 288                  throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
 289              }
 290          }
 291  
 292          if ($objectForMap && !\is_object($data) && 'mapping' === $context) {
 293              $object = new \stdClass();
 294  
 295              foreach ($data as $key => $value) {
 296                  $object->$key = $value;
 297              }
 298  
 299              $data = $object;
 300          }
 301  
 302          return empty($data) ? null : $data;
 303      }
 304  
 305      private function parseBlock($offset, $yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap)
 306      {
 307          $skippedLineNumbers = $this->skippedLineNumbers;
 308  
 309          foreach ($this->locallySkippedLineNumbers as $lineNumber) {
 310              if ($lineNumber < $offset) {
 311                  continue;
 312              }
 313  
 314              $skippedLineNumbers[] = $lineNumber;
 315          }
 316  
 317          $parser = new self($offset, $this->totalNumberOfLines, $skippedLineNumbers);
 318          $parser->refs = &$this->refs;
 319  
 320          return $parser->doParse($yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap);
 321      }
 322  
 323      /**
 324       * Returns the current line number (takes the offset into account).
 325       *
 326       * @return int The current line number
 327       */
 328      private function getRealCurrentLineNb()
 329      {
 330          $realCurrentLineNumber = $this->currentLineNb + $this->offset;
 331  
 332          foreach ($this->skippedLineNumbers as $skippedLineNumber) {
 333              if ($skippedLineNumber > $realCurrentLineNumber) {
 334                  break;
 335              }
 336  
 337              ++$realCurrentLineNumber;
 338          }
 339  
 340          return $realCurrentLineNumber;
 341      }
 342  
 343      /**
 344       * Returns the current line indentation.
 345       *
 346       * @return int The current line indentation
 347       */
 348      private function getCurrentLineIndentation()
 349      {
 350          return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' '));
 351      }
 352  
 353      /**
 354       * Returns the next embed block of YAML.
 355       *
 356       * @param int  $indentation The indent level at which the block is to be read, or null for default
 357       * @param bool $inSequence  True if the enclosing data structure is a sequence
 358       *
 359       * @return string A YAML string
 360       *
 361       * @throws ParseException When indentation problem are detected
 362       */
 363      private function getNextEmbedBlock($indentation = null, $inSequence = false)
 364      {
 365          $oldLineIndentation = $this->getCurrentLineIndentation();
 366          $blockScalarIndentations = array();
 367  
 368          if ($this->isBlockScalarHeader()) {
 369              $blockScalarIndentations[] = $this->getCurrentLineIndentation();
 370          }
 371  
 372          if (!$this->moveToNextLine()) {
 373              return;
 374          }
 375  
 376          if (null === $indentation) {
 377              $newIndent = $this->getCurrentLineIndentation();
 378  
 379              $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem();
 380  
 381              if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
 382                  throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
 383              }
 384          } else {
 385              $newIndent = $indentation;
 386          }
 387  
 388          $data = array();
 389          if ($this->getCurrentLineIndentation() >= $newIndent) {
 390              $data[] = substr($this->currentLine, $newIndent);
 391          } else {
 392              $this->moveToPreviousLine();
 393  
 394              return;
 395          }
 396  
 397          if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) {
 398              // the previous line contained a dash but no item content, this line is a sequence item with the same indentation
 399              // and therefore no nested list or mapping
 400              $this->moveToPreviousLine();
 401  
 402              return;
 403          }
 404  
 405          $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
 406  
 407          if (empty($blockScalarIndentations) && $this->isBlockScalarHeader()) {
 408              $blockScalarIndentations[] = $this->getCurrentLineIndentation();
 409          }
 410  
 411          $previousLineIndentation = $this->getCurrentLineIndentation();
 412  
 413          while ($this->moveToNextLine()) {
 414              $indent = $this->getCurrentLineIndentation();
 415  
 416              // terminate all block scalars that are more indented than the current line
 417              if (!empty($blockScalarIndentations) && $indent < $previousLineIndentation && '' !== trim($this->currentLine)) {
 418                  foreach ($blockScalarIndentations as $key => $blockScalarIndentation) {
 419                      if ($blockScalarIndentation >= $this->getCurrentLineIndentation()) {
 420                          unset($blockScalarIndentations[$key]);
 421                      }
 422                  }
 423              }
 424  
 425              if (empty($blockScalarIndentations) && !$this->isCurrentLineComment() && $this->isBlockScalarHeader()) {
 426                  $blockScalarIndentations[] = $this->getCurrentLineIndentation();
 427              }
 428  
 429              $previousLineIndentation = $indent;
 430  
 431              if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) {
 432                  $this->moveToPreviousLine();
 433                  break;
 434              }
 435  
 436              if ($this->isCurrentLineBlank()) {
 437                  $data[] = substr($this->currentLine, $newIndent);
 438                  continue;
 439              }
 440  
 441              // we ignore "comment" lines only when we are not inside a scalar block
 442              if (empty($blockScalarIndentations) && $this->isCurrentLineComment()) {
 443                  // remember ignored comment lines (they are used later in nested
 444                  // parser calls to determine real line numbers)
 445                  //
 446                  // CAUTION: beware to not populate the global property here as it
 447                  // will otherwise influence the getRealCurrentLineNb() call here
 448                  // for consecutive comment lines and subsequent embedded blocks
 449                  $this->locallySkippedLineNumbers[] = $this->getRealCurrentLineNb();
 450  
 451                  continue;
 452              }
 453  
 454              if ($indent >= $newIndent) {
 455                  $data[] = substr($this->currentLine, $newIndent);
 456              } elseif (0 == $indent) {
 457                  $this->moveToPreviousLine();
 458  
 459                  break;
 460              } else {
 461                  throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
 462              }
 463          }
 464  
 465          return implode("\n", $data);
 466      }
 467  
 468      /**
 469       * Moves the parser to the next line.
 470       *
 471       * @return bool
 472       */
 473      private function moveToNextLine()
 474      {
 475          if ($this->currentLineNb >= \count($this->lines) - 1) {
 476              return false;
 477          }
 478  
 479          $this->currentLine = $this->lines[++$this->currentLineNb];
 480  
 481          return true;
 482      }
 483  
 484      /**
 485       * Moves the parser to the previous line.
 486       *
 487       * @return bool
 488       */
 489      private function moveToPreviousLine()
 490      {
 491          if ($this->currentLineNb < 1) {
 492              return false;
 493          }
 494  
 495          $this->currentLine = $this->lines[--$this->currentLineNb];
 496  
 497          return true;
 498      }
 499  
 500      /**
 501       * Parses a YAML value.
 502       *
 503       * @param string $value                  A YAML value
 504       * @param bool   $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise
 505       * @param bool   $objectSupport          True if object support is enabled, false otherwise
 506       * @param bool   $objectForMap           True if maps should return a stdClass instead of array()
 507       * @param string $context                The parser context (either sequence or mapping)
 508       *
 509       * @return mixed A PHP value
 510       *
 511       * @throws ParseException When reference does not exist
 512       */
 513      private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $context)
 514      {
 515          if (0 === strpos($value, '*')) {
 516              if (false !== $pos = strpos($value, '#')) {
 517                  $value = substr($value, 1, $pos - 2);
 518              } else {
 519                  $value = substr($value, 1);
 520              }
 521  
 522              if (!array_key_exists($value, $this->refs)) {
 523                  throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine);
 524              }
 525  
 526              return $this->refs[$value];
 527          }
 528  
 529          if (self::preg_match('/^'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
 530              $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
 531  
 532              return $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers));
 533          }
 534  
 535          try {
 536              $parsedValue = Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
 537  
 538              if ('mapping' === $context && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
 539                  @trigger_error(sprintf('Using a colon in the unquoted mapping value "%s" in line %d is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $value, $this->getRealCurrentLineNb() + 1), E_USER_DEPRECATED);
 540  
 541                  // to be thrown in 3.0
 542                  // throw new ParseException('A colon cannot be used in an unquoted mapping value.');
 543              }
 544  
 545              return $parsedValue;
 546          } catch (ParseException $e) {
 547              $e->setParsedLine($this->getRealCurrentLineNb() + 1);
 548              $e->setSnippet($this->currentLine);
 549  
 550              throw $e;
 551          }
 552      }
 553  
 554      /**
 555       * Parses a block scalar.
 556       *
 557       * @param string $style       The style indicator that was used to begin this block scalar (| or >)
 558       * @param string $chomping    The chomping indicator that was used to begin this block scalar (+ or -)
 559       * @param int    $indentation The indentation indicator that was used to begin this block scalar
 560       *
 561       * @return string The text value
 562       */
 563      private function parseBlockScalar($style, $chomping = '', $indentation = 0)
 564      {
 565          $notEOF = $this->moveToNextLine();
 566          if (!$notEOF) {
 567              return '';
 568          }
 569  
 570          $isCurrentLineBlank = $this->isCurrentLineBlank();
 571          $blockLines = array();
 572  
 573          // leading blank lines are consumed before determining indentation
 574          while ($notEOF && $isCurrentLineBlank) {
 575              // newline only if not EOF
 576              if ($notEOF = $this->moveToNextLine()) {
 577                  $blockLines[] = '';
 578                  $isCurrentLineBlank = $this->isCurrentLineBlank();
 579              }
 580          }
 581  
 582          // determine indentation if not specified
 583          if (0 === $indentation) {
 584              if (self::preg_match('/^ +/', $this->currentLine, $matches)) {
 585                  $indentation = \strlen($matches[0]);
 586              }
 587          }
 588  
 589          if ($indentation > 0) {
 590              $pattern = sprintf('/^ {%d}(.*)$/', $indentation);
 591  
 592              while (
 593                  $notEOF && (
 594                      $isCurrentLineBlank ||
 595                      self::preg_match($pattern, $this->currentLine, $matches)
 596                  )
 597              ) {
 598                  if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) {
 599                      $blockLines[] = substr($this->currentLine, $indentation);
 600                  } elseif ($isCurrentLineBlank) {
 601                      $blockLines[] = '';
 602                  } else {
 603                      $blockLines[] = $matches[1];
 604                  }
 605  
 606                  // newline only if not EOF
 607                  if ($notEOF = $this->moveToNextLine()) {
 608                      $isCurrentLineBlank = $this->isCurrentLineBlank();
 609                  }
 610              }
 611          } elseif ($notEOF) {
 612              $blockLines[] = '';
 613          }
 614  
 615          if ($notEOF) {
 616              $blockLines[] = '';
 617              $this->moveToPreviousLine();
 618          } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) {
 619              $blockLines[] = '';
 620          }
 621  
 622          // folded style
 623          if ('>' === $style) {
 624              $text = '';
 625              $previousLineIndented = false;
 626              $previousLineBlank = false;
 627  
 628              for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) {
 629                  if ('' === $blockLines[$i]) {
 630                      $text .= "\n";
 631                      $previousLineIndented = false;
 632                      $previousLineBlank = true;
 633                  } elseif (' ' === $blockLines[$i][0]) {
 634                      $text .= "\n".$blockLines[$i];
 635                      $previousLineIndented = true;
 636                      $previousLineBlank = false;
 637                  } elseif ($previousLineIndented) {
 638                      $text .= "\n".$blockLines[$i];
 639                      $previousLineIndented = false;
 640                      $previousLineBlank = false;
 641                  } elseif ($previousLineBlank || 0 === $i) {
 642                      $text .= $blockLines[$i];
 643                      $previousLineIndented = false;
 644                      $previousLineBlank = false;
 645                  } else {
 646                      $text .= ' '.$blockLines[$i];
 647                      $previousLineIndented = false;
 648                      $previousLineBlank = false;
 649                  }
 650              }
 651          } else {
 652              $text = implode("\n", $blockLines);
 653          }
 654  
 655          // deal with trailing newlines
 656          if ('' === $chomping) {
 657              $text = preg_replace('/\n+$/', "\n", $text);
 658          } elseif ('-' === $chomping) {
 659              $text = preg_replace('/\n+$/', '', $text);
 660          }
 661  
 662          return $text;
 663      }
 664  
 665      /**
 666       * Returns true if the next line is indented.
 667       *
 668       * @return bool Returns true if the next line is indented, false otherwise
 669       */
 670      private function isNextLineIndented()
 671      {
 672          $currentIndentation = $this->getCurrentLineIndentation();
 673          $EOF = !$this->moveToNextLine();
 674  
 675          while (!$EOF && $this->isCurrentLineEmpty()) {
 676              $EOF = !$this->moveToNextLine();
 677          }
 678  
 679          if ($EOF) {
 680              return false;
 681          }
 682  
 683          $ret = $this->getCurrentLineIndentation() > $currentIndentation;
 684  
 685          $this->moveToPreviousLine();
 686  
 687          return $ret;
 688      }
 689  
 690      /**
 691       * Returns true if the current line is blank or if it is a comment line.
 692       *
 693       * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise
 694       */
 695      private function isCurrentLineEmpty()
 696      {
 697          return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
 698      }
 699  
 700      /**
 701       * Returns true if the current line is blank.
 702       *
 703       * @return bool Returns true if the current line is blank, false otherwise
 704       */
 705      private function isCurrentLineBlank()
 706      {
 707          return '' == trim($this->currentLine, ' ');
 708      }
 709  
 710      /**
 711       * Returns true if the current line is a comment line.
 712       *
 713       * @return bool Returns true if the current line is a comment line, false otherwise
 714       */
 715      private function isCurrentLineComment()
 716      {
 717          //checking explicitly the first char of the trim is faster than loops or strpos
 718          $ltrimmedLine = ltrim($this->currentLine, ' ');
 719  
 720          return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0];
 721      }
 722  
 723      private function isCurrentLineLastLineInDocument()
 724      {
 725          return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
 726      }
 727  
 728      /**
 729       * Cleanups a YAML string to be parsed.
 730       *
 731       * @param string $value The input YAML string
 732       *
 733       * @return string A cleaned up YAML string
 734       */
 735      private function cleanup($value)
 736      {
 737          $value = str_replace(array("\r\n", "\r"), "\n", $value);
 738  
 739          // strip YAML header
 740          $count = 0;
 741          $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count);
 742          $this->offset += $count;
 743  
 744          // remove leading comments
 745          $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count);
 746          if (1 == $count) {
 747              // items have been removed, update the offset
 748              $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
 749              $value = $trimmedValue;
 750          }
 751  
 752          // remove start of the document marker (---)
 753          $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count);
 754          if (1 == $count) {
 755              // items have been removed, update the offset
 756              $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
 757              $value = $trimmedValue;
 758  
 759              // remove end of the document marker (...)
 760              $value = preg_replace('#\.\.\.\s*$#', '', $value);
 761          }
 762  
 763          return $value;
 764      }
 765  
 766      /**
 767       * Returns true if the next line starts unindented collection.
 768       *
 769       * @return bool Returns true if the next line starts unindented collection, false otherwise
 770       */
 771      private function isNextLineUnIndentedCollection()
 772      {
 773          $currentIndentation = $this->getCurrentLineIndentation();
 774          $notEOF = $this->moveToNextLine();
 775  
 776          while ($notEOF && $this->isCurrentLineEmpty()) {
 777              $notEOF = $this->moveToNextLine();
 778          }
 779  
 780          if (false === $notEOF) {
 781              return false;
 782          }
 783  
 784          $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem();
 785  
 786          $this->moveToPreviousLine();
 787  
 788          return $ret;
 789      }
 790  
 791      /**
 792       * Returns true if the string is un-indented collection item.
 793       *
 794       * @return bool Returns true if the string is un-indented collection item, false otherwise
 795       */
 796      private function isStringUnIndentedCollectionItem()
 797      {
 798          return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- ');
 799      }
 800  
 801      /**
 802       * Tests whether or not the current line is the header of a block scalar.
 803       *
 804       * @return bool
 805       */
 806      private function isBlockScalarHeader()
 807      {
 808          return (bool) self::preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $this->currentLine);
 809      }
 810  
 811      /**
 812       * A local wrapper for `preg_match` which will throw a ParseException if there
 813       * is an internal error in the PCRE engine.
 814       *
 815       * This avoids us needing to check for "false" every time PCRE is used
 816       * in the YAML engine
 817       *
 818       * @throws ParseException on a PCRE internal error
 819       *
 820       * @see preg_last_error()
 821       *
 822       * @internal
 823       */
 824      public static function preg_match($pattern, $subject, &$matches = null, $flags = 0, $offset = 0)
 825      {
 826          if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) {
 827              switch (preg_last_error()) {
 828                  case PREG_INTERNAL_ERROR:
 829                      $error = 'Internal PCRE error.';
 830                      break;
 831                  case PREG_BACKTRACK_LIMIT_ERROR:
 832                      $error = 'pcre.backtrack_limit reached.';
 833                      break;
 834                  case PREG_RECURSION_LIMIT_ERROR:
 835                      $error = 'pcre.recursion_limit reached.';
 836                      break;
 837                  case PREG_BAD_UTF8_ERROR:
 838                      $error = 'Malformed UTF-8 data.';
 839                      break;
 840                  case PREG_BAD_UTF8_OFFSET_ERROR:
 841                      $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
 842                      break;
 843                  default:
 844                      $error = 'Error.';
 845              }
 846  
 847              throw new ParseException($error);
 848          }
 849  
 850          return $ret;
 851      }
 852  }


Generated: Wed Nov 11 20:33:01 2020 Cross-referenced by PHPXref 0.7.1