Verzeichnisstruktur phpBB-3.2.0


Veröffentlicht
06.01.2017

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: 09.10.2024, 12:54 - Dateigröße: 31.34 KiB


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