JOOMLA中国
  • Joomla中国首页
  • 社区
  • 教程
  • 应用市场
  • B计划
Joomla! Framework TM
  • Namespace
  • Class
  • Tree
  • Deprecated

Namespaces

  • Composer
    • Autoload
  • Joomla
    • Application
      • Cli
        • Output
          • Processor
      • Web
    • Data
    • DI
      • Exception
    • Event
    • Filter
    • Input
    • Ldap
    • Registry
      • Format
    • Session
      • Storage
    • String
    • Uri
    • Utilities
  • None
  • PasswordCompat
    • binary
  • PHP
  • Psr
    • Log
  • Symfony
    • Component
      • Yaml
        • Exception
    • Polyfill
      • Util

Classes

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