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