gluon-web-remote

Web remote Administration for big size of gluon routers
git clone git://archive.git.mtrnord.blog/MTRNord/gluon-web-remote.git
Log | Files | Refs | README

Parser.php (27088B)


      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 FOLDED_SCALAR_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
     24 
     25     private $offset = 0;
     26     private $lines = array();
     27     private $currentLineNb = -1;
     28     private $currentLine = '';
     29     private $refs = array();
     30 
     31     /**
     32      * Constructor.
     33      *
     34      * @param int $offset The offset of YAML document (used for line numbers in error messages)
     35      */
     36     public function __construct($offset = 0)
     37     {
     38         $this->offset = $offset;
     39     }
     40 
     41     /**
     42      * Parses a YAML string to a PHP value.
     43      *
     44      * @param string $value                  A YAML string
     45      * @param bool   $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
     46      * @param bool   $objectSupport          true if object support is enabled, false otherwise
     47      * @param bool   $objectForMap           true if maps should return a stdClass instead of array()
     48      *
     49      * @return mixed A PHP value
     50      *
     51      * @throws ParseException If the YAML is not valid
     52      */
     53     public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
     54     {
     55         if (!preg_match('//u', $value)) {
     56             throw new ParseException('The YAML value does not appear to be valid UTF-8.');
     57         }
     58         $this->currentLineNb = -1;
     59         $this->currentLine = '';
     60         $value = $this->cleanup($value);
     61         $this->lines = explode("\n", $value);
     62 
     63         if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
     64             $mbEncoding = mb_internal_encoding();
     65             mb_internal_encoding('UTF-8');
     66         }
     67 
     68         $data = array();
     69         $context = null;
     70         $allowOverwrite = false;
     71         while ($this->moveToNextLine()) {
     72             if ($this->isCurrentLineEmpty()) {
     73                 continue;
     74             }
     75 
     76             // tab?
     77             if ("\t" === $this->currentLine[0]) {
     78                 throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
     79             }
     80 
     81             $isRef = $mergeNode = false;
     82             if (preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+?))?\s*$#u', $this->currentLine, $values)) {
     83                 if ($context && 'mapping' == $context) {
     84                     throw new ParseException('You cannot define a sequence item when in a mapping');
     85                 }
     86                 $context = 'sequence';
     87 
     88                 if (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
     89                     $isRef = $matches['ref'];
     90                     $values['value'] = $matches['value'];
     91                 }
     92 
     93                 // array
     94                 if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
     95                     $c = $this->getRealCurrentLineNb() + 1;
     96                     $parser = new self($c);
     97                     $parser->refs = &$this->refs;
     98                     $data[] = $parser->parse($this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap);
     99                 } else {
    100                     if (isset($values['leadspaces'])
    101                         && preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $values['value'], $matches)
    102                     ) {
    103                         // this is a compact notation element, add to next block and parse
    104                         $c = $this->getRealCurrentLineNb();
    105                         $parser = new self($c);
    106                         $parser->refs = &$this->refs;
    107 
    108                         $block = $values['value'];
    109                         if ($this->isNextLineIndented()) {
    110                             $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + strlen($values['leadspaces']) + 1);
    111                         }
    112 
    113                         $data[] = $parser->parse($block, $exceptionOnInvalidType, $objectSupport, $objectForMap);
    114                     } else {
    115                         $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap);
    116                     }
    117                 }
    118                 if ($isRef) {
    119                     $this->refs[$isRef] = end($data);
    120                 }
    121             } 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('"', "'")))) {
    122                 if ($context && 'sequence' == $context) {
    123                     throw new ParseException('You cannot define a mapping item when in a sequence');
    124                 }
    125                 $context = 'mapping';
    126 
    127                 // force correct settings
    128                 Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
    129                 try {
    130                     $key = Inline::parseScalar($values['key']);
    131                 } catch (ParseException $e) {
    132                     $e->setParsedLine($this->getRealCurrentLineNb() + 1);
    133                     $e->setSnippet($this->currentLine);
    134 
    135                     throw $e;
    136                 }
    137 
    138                 // Convert float keys to strings, to avoid being converted to integers by PHP
    139                 if (is_float($key)) {
    140                     $key = (string) $key;
    141                 }
    142 
    143                 if ('<<' === $key) {
    144                     $mergeNode = true;
    145                     $allowOverwrite = true;
    146                     if (isset($values['value']) && 0 === strpos($values['value'], '*')) {
    147                         $refName = substr($values['value'], 1);
    148                         if (!array_key_exists($refName, $this->refs)) {
    149                             throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine);
    150                         }
    151 
    152                         $refValue = $this->refs[$refName];
    153 
    154                         if (!is_array($refValue)) {
    155                             throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
    156                         }
    157 
    158                         foreach ($refValue as $key => $value) {
    159                             if (!isset($data[$key])) {
    160                                 $data[$key] = $value;
    161                             }
    162                         }
    163                     } else {
    164                         if (isset($values['value']) && $values['value'] !== '') {
    165                             $value = $values['value'];
    166                         } else {
    167                             $value = $this->getNextEmbedBlock();
    168                         }
    169                         $c = $this->getRealCurrentLineNb() + 1;
    170                         $parser = new self($c);
    171                         $parser->refs = &$this->refs;
    172                         $parsed = $parser->parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
    173 
    174                         if (!is_array($parsed)) {
    175                             throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
    176                         }
    177 
    178                         if (isset($parsed[0])) {
    179                             // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
    180                             // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
    181                             // in the sequence override keys specified in later mapping nodes.
    182                             foreach ($parsed as $parsedItem) {
    183                                 if (!is_array($parsedItem)) {
    184                                     throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem);
    185                                 }
    186 
    187                                 foreach ($parsedItem as $key => $value) {
    188                                     if (!isset($data[$key])) {
    189                                         $data[$key] = $value;
    190                                     }
    191                                 }
    192                             }
    193                         } else {
    194                             // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
    195                             // current mapping, unless the key already exists in it.
    196                             foreach ($parsed as $key => $value) {
    197                                 if (!isset($data[$key])) {
    198                                     $data[$key] = $value;
    199                                 }
    200                             }
    201                         }
    202                     }
    203                 } elseif (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
    204                     $isRef = $matches['ref'];
    205                     $values['value'] = $matches['value'];
    206                 }
    207 
    208                 if ($mergeNode) {
    209                     // Merge keys
    210                 } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
    211                     // hash
    212                     // if next line is less indented or equal, then it means that the current value is null
    213                     if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
    214                         // Spec: Keys MUST be unique; first one wins.
    215                         // But overwriting is allowed when a merge node is used in current block.
    216                         if ($allowOverwrite || !isset($data[$key])) {
    217                             $data[$key] = null;
    218                         }
    219                     } else {
    220                         $c = $this->getRealCurrentLineNb() + 1;
    221                         $parser = new self($c);
    222                         $parser->refs = &$this->refs;
    223                         $value = $parser->parse($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);
    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.');
    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 (is_array($value)) {
    259                         $first = reset($value);
    260                         if (is_string($first) && 0 === strpos($first, '*')) {
    261                             $data = array();
    262                             foreach ($value as $alias) {
    263                                 $data[] = $this->refs[substr($alias, 1)];
    264                             }
    265                             $value = $data;
    266                         }
    267                     }
    268 
    269                     if (isset($mbEncoding)) {
    270                         mb_internal_encoding($mbEncoding);
    271                     }
    272 
    273                     return $value;
    274                 }
    275 
    276                 switch (preg_last_error()) {
    277                     case PREG_INTERNAL_ERROR:
    278                         $error = 'Internal PCRE error.';
    279                         break;
    280                     case PREG_BACKTRACK_LIMIT_ERROR:
    281                         $error = 'pcre.backtrack_limit reached.';
    282                         break;
    283                     case PREG_RECURSION_LIMIT_ERROR:
    284                         $error = 'pcre.recursion_limit reached.';
    285                         break;
    286                     case PREG_BAD_UTF8_ERROR:
    287                         $error = 'Malformed UTF-8 data.';
    288                         break;
    289                     case PREG_BAD_UTF8_OFFSET_ERROR:
    290                         $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
    291                         break;
    292                     default:
    293                         $error = 'Unable to parse.';
    294                 }
    295 
    296                 throw new ParseException($error, $this->getRealCurrentLineNb() + 1, $this->currentLine);
    297             }
    298         }
    299 
    300         if (isset($mbEncoding)) {
    301             mb_internal_encoding($mbEncoding);
    302         }
    303 
    304         return empty($data) ? null : $data;
    305     }
    306 
    307     /**
    308      * Returns the current line number (takes the offset into account).
    309      *
    310      * @return int The current line number
    311      */
    312     private function getRealCurrentLineNb()
    313     {
    314         return $this->currentLineNb + $this->offset;
    315     }
    316 
    317     /**
    318      * Returns the current line indentation.
    319      *
    320      * @return int The current line indentation
    321      */
    322     private function getCurrentLineIndentation()
    323     {
    324         return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' '));
    325     }
    326 
    327     /**
    328      * Returns the next embed block of YAML.
    329      *
    330      * @param int  $indentation The indent level at which the block is to be read, or null for default
    331      * @param bool $inSequence  True if the enclosing data structure is a sequence
    332      *
    333      * @return string A YAML string
    334      *
    335      * @throws ParseException When indentation problem are detected
    336      */
    337     private function getNextEmbedBlock($indentation = null, $inSequence = false)
    338     {
    339         $oldLineIndentation = $this->getCurrentLineIndentation();
    340 
    341         if (!$this->moveToNextLine()) {
    342             return;
    343         }
    344 
    345         if (null === $indentation) {
    346             $newIndent = $this->getCurrentLineIndentation();
    347 
    348             $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem($this->currentLine);
    349 
    350             if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
    351                 throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
    352             }
    353         } else {
    354             $newIndent = $indentation;
    355         }
    356 
    357         $data = array();
    358         if ($this->getCurrentLineIndentation() >= $newIndent) {
    359             $data[] = substr($this->currentLine, $newIndent);
    360         } else {
    361             $this->moveToPreviousLine();
    362 
    363             return;
    364         }
    365 
    366         if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) {
    367             // the previous line contained a dash but no item content, this line is a sequence item with the same indentation
    368             // and therefore no nested list or mapping
    369             $this->moveToPreviousLine();
    370 
    371             return;
    372         }
    373 
    374         $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem($this->currentLine);
    375 
    376         // Comments must not be removed inside a string block (ie. after a line ending with "|")
    377         $removeCommentsPattern = '~'.self::FOLDED_SCALAR_PATTERN.'$~';
    378         $removeComments = !preg_match($removeCommentsPattern, $this->currentLine);
    379 
    380         while ($this->moveToNextLine()) {
    381             $indent = $this->getCurrentLineIndentation();
    382 
    383             if ($indent === $newIndent) {
    384                 $removeComments = !preg_match($removeCommentsPattern, $this->currentLine);
    385             }
    386 
    387             if ($isItUnindentedCollection && !$this->isStringUnIndentedCollectionItem($this->currentLine) && $newIndent === $indent) {
    388                 $this->moveToPreviousLine();
    389                 break;
    390             }
    391 
    392             if ($this->isCurrentLineBlank()) {
    393                 $data[] = substr($this->currentLine, $newIndent);
    394                 continue;
    395             }
    396 
    397             if ($removeComments && $this->isCurrentLineComment()) {
    398                 continue;
    399             }
    400 
    401             if ($indent >= $newIndent) {
    402                 $data[] = substr($this->currentLine, $newIndent);
    403             } elseif (0 == $indent) {
    404                 $this->moveToPreviousLine();
    405 
    406                 break;
    407             } else {
    408                 throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
    409             }
    410         }
    411 
    412         return implode("\n", $data);
    413     }
    414 
    415     /**
    416      * Moves the parser to the next line.
    417      *
    418      * @return bool
    419      */
    420     private function moveToNextLine()
    421     {
    422         if ($this->currentLineNb >= count($this->lines) - 1) {
    423             return false;
    424         }
    425 
    426         $this->currentLine = $this->lines[++$this->currentLineNb];
    427 
    428         return true;
    429     }
    430 
    431     /**
    432      * Moves the parser to the previous line.
    433      */
    434     private function moveToPreviousLine()
    435     {
    436         $this->currentLine = $this->lines[--$this->currentLineNb];
    437     }
    438 
    439     /**
    440      * Parses a YAML value.
    441      *
    442      * @param string $value                  A YAML value
    443      * @param bool   $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise
    444      * @param bool   $objectSupport          True if object support is enabled, false otherwise
    445      * @param bool   $objectForMap           true if maps should return a stdClass instead of array()
    446      *
    447      * @return mixed A PHP value
    448      *
    449      * @throws ParseException When reference does not exist
    450      */
    451     private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $objectForMap)
    452     {
    453         if (0 === strpos($value, '*')) {
    454             if (false !== $pos = strpos($value, '#')) {
    455                 $value = substr($value, 1, $pos - 2);
    456             } else {
    457                 $value = substr($value, 1);
    458             }
    459 
    460             if (!array_key_exists($value, $this->refs)) {
    461                 throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLine);
    462             }
    463 
    464             return $this->refs[$value];
    465         }
    466 
    467         if (preg_match('/^'.self::FOLDED_SCALAR_PATTERN.'$/', $value, $matches)) {
    468             $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
    469 
    470             return $this->parseFoldedScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers));
    471         }
    472 
    473         try {
    474             return Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
    475         } catch (ParseException $e) {
    476             $e->setParsedLine($this->getRealCurrentLineNb() + 1);
    477             $e->setSnippet($this->currentLine);
    478 
    479             throw $e;
    480         }
    481     }
    482 
    483     /**
    484      * Parses a folded scalar.
    485      *
    486      * @param string $separator   The separator that was used to begin this folded scalar (| or >)
    487      * @param string $indicator   The indicator that was used to begin this folded scalar (+ or -)
    488      * @param int    $indentation The indentation that was used to begin this folded scalar
    489      *
    490      * @return string The text value
    491      */
    492     private function parseFoldedScalar($separator, $indicator = '', $indentation = 0)
    493     {
    494         $notEOF = $this->moveToNextLine();
    495         if (!$notEOF) {
    496             return '';
    497         }
    498 
    499         $isCurrentLineBlank = $this->isCurrentLineBlank();
    500         $text = '';
    501 
    502         // leading blank lines are consumed before determining indentation
    503         while ($notEOF && $isCurrentLineBlank) {
    504             // newline only if not EOF
    505             if ($notEOF = $this->moveToNextLine()) {
    506                 $text .= "\n";
    507                 $isCurrentLineBlank = $this->isCurrentLineBlank();
    508             }
    509         }
    510 
    511         // determine indentation if not specified
    512         if (0 === $indentation) {
    513             if (preg_match('/^ +/', $this->currentLine, $matches)) {
    514                 $indentation = strlen($matches[0]);
    515             }
    516         }
    517 
    518         if ($indentation > 0) {
    519             $pattern = sprintf('/^ {%d}(.*)$/', $indentation);
    520 
    521             while (
    522                 $notEOF && (
    523                     $isCurrentLineBlank ||
    524                     preg_match($pattern, $this->currentLine, $matches)
    525                 )
    526             ) {
    527                 if ($isCurrentLineBlank) {
    528                     $text .= substr($this->currentLine, $indentation);
    529                 } else {
    530                     $text .= $matches[1];
    531                 }
    532 
    533                 // newline only if not EOF
    534                 if ($notEOF = $this->moveToNextLine()) {
    535                     $text .= "\n";
    536                     $isCurrentLineBlank = $this->isCurrentLineBlank();
    537                 }
    538             }
    539         } elseif ($notEOF) {
    540             $text .= "\n";
    541         }
    542 
    543         if ($notEOF) {
    544             $this->moveToPreviousLine();
    545         }
    546 
    547         // replace all non-trailing single newlines with spaces in folded blocks
    548         if ('>' === $separator) {
    549             preg_match('/(\n*)$/', $text, $matches);
    550             $text = preg_replace('/(?<!\n)\n(?!\n)/', ' ', rtrim($text, "\n"));
    551             $text .= $matches[1];
    552         }
    553 
    554         // deal with trailing newlines as indicated
    555         if ('' === $indicator) {
    556             $text = preg_replace('/\n+$/', "\n", $text);
    557         } elseif ('-' === $indicator) {
    558             $text = preg_replace('/\n+$/', '', $text);
    559         }
    560 
    561         return $text;
    562     }
    563 
    564     /**
    565      * Returns true if the next line is indented.
    566      *
    567      * @return bool Returns true if the next line is indented, false otherwise
    568      */
    569     private function isNextLineIndented()
    570     {
    571         $currentIndentation = $this->getCurrentLineIndentation();
    572         $EOF = !$this->moveToNextLine();
    573 
    574         while (!$EOF && $this->isCurrentLineEmpty()) {
    575             $EOF = !$this->moveToNextLine();
    576         }
    577 
    578         if ($EOF) {
    579             return false;
    580         }
    581 
    582         $ret = false;
    583         if ($this->getCurrentLineIndentation() > $currentIndentation) {
    584             $ret = true;
    585         }
    586 
    587         $this->moveToPreviousLine();
    588 
    589         return $ret;
    590     }
    591 
    592     /**
    593      * Returns true if the current line is blank or if it is a comment line.
    594      *
    595      * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise
    596      */
    597     private function isCurrentLineEmpty()
    598     {
    599         return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
    600     }
    601 
    602     /**
    603      * Returns true if the current line is blank.
    604      *
    605      * @return bool Returns true if the current line is blank, false otherwise
    606      */
    607     private function isCurrentLineBlank()
    608     {
    609         return '' == trim($this->currentLine, ' ');
    610     }
    611 
    612     /**
    613      * Returns true if the current line is a comment line.
    614      *
    615      * @return bool Returns true if the current line is a comment line, false otherwise
    616      */
    617     private function isCurrentLineComment()
    618     {
    619         //checking explicitly the first char of the trim is faster than loops or strpos
    620         $ltrimmedLine = ltrim($this->currentLine, ' ');
    621 
    622         return $ltrimmedLine[0] === '#';
    623     }
    624 
    625     /**
    626      * Cleanups a YAML string to be parsed.
    627      *
    628      * @param string $value The input YAML string
    629      *
    630      * @return string A cleaned up YAML string
    631      */
    632     private function cleanup($value)
    633     {
    634         $value = str_replace(array("\r\n", "\r"), "\n", $value);
    635 
    636         // strip YAML header
    637         $count = 0;
    638         $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count);
    639         $this->offset += $count;
    640 
    641         // remove leading comments
    642         $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count);
    643         if ($count == 1) {
    644             // items have been removed, update the offset
    645             $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
    646             $value = $trimmedValue;
    647         }
    648 
    649         // remove start of the document marker (---)
    650         $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count);
    651         if ($count == 1) {
    652             // items have been removed, update the offset
    653             $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
    654             $value = $trimmedValue;
    655 
    656             // remove end of the document marker (...)
    657             $value = preg_replace('#\.\.\.\s*$#', '', $value);
    658         }
    659 
    660         return $value;
    661     }
    662 
    663     /**
    664      * Returns true if the next line starts unindented collection.
    665      *
    666      * @return bool Returns true if the next line starts unindented collection, false otherwise
    667      */
    668     private function isNextLineUnIndentedCollection()
    669     {
    670         $currentIndentation = $this->getCurrentLineIndentation();
    671         $notEOF = $this->moveToNextLine();
    672 
    673         while ($notEOF && $this->isCurrentLineEmpty()) {
    674             $notEOF = $this->moveToNextLine();
    675         }
    676 
    677         if (false === $notEOF) {
    678             return false;
    679         }
    680 
    681         $ret = false;
    682         if (
    683             $this->getCurrentLineIndentation() == $currentIndentation
    684             &&
    685             $this->isStringUnIndentedCollectionItem($this->currentLine)
    686         ) {
    687             $ret = true;
    688         }
    689 
    690         $this->moveToPreviousLine();
    691 
    692         return $ret;
    693     }
    694 
    695     /**
    696      * Returns true if the string is un-indented collection item.
    697      *
    698      * @return bool Returns true if the string is un-indented collection item, false otherwise
    699      */
    700     private function isStringUnIndentedCollectionItem()
    701     {
    702         return (0 === strpos($this->currentLine, '- '));
    703     }
    704 }