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