| 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: | |