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. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
Parser.php
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