comparison vendor/symfony/yaml/Parser.php @ 4:a9cd425dd02b

Update, including to Drupal core 8.6.10
author Chris Cannam
date Thu, 28 Feb 2019 13:11:55 +0000
parents c75dbcec494b
children 12f9dff5fda9
comparison
equal deleted inserted replaced
3:307d7a7fd348 4:a9cd425dd02b
27 const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?'; 27 const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
28 28
29 private $filename; 29 private $filename;
30 private $offset = 0; 30 private $offset = 0;
31 private $totalNumberOfLines; 31 private $totalNumberOfLines;
32 private $lines = array(); 32 private $lines = [];
33 private $currentLineNb = -1; 33 private $currentLineNb = -1;
34 private $currentLine = ''; 34 private $currentLine = '';
35 private $refs = array(); 35 private $refs = [];
36 private $skippedLineNumbers = array(); 36 private $skippedLineNumbers = [];
37 private $locallySkippedLineNumbers = array(); 37 private $locallySkippedLineNumbers = [];
38 private $refsBeingParsed = [];
38 39
39 public function __construct() 40 public function __construct()
40 { 41 {
41 if (func_num_args() > 0) { 42 if (\func_num_args() > 0) {
42 @trigger_error(sprintf('The constructor arguments $offset, $totalNumberOfLines, $skippedLineNumbers of %s are deprecated and will be removed in 4.0', self::class), E_USER_DEPRECATED); 43 @trigger_error(sprintf('The constructor arguments $offset, $totalNumberOfLines, $skippedLineNumbers of %s are deprecated and will be removed in 4.0', self::class), E_USER_DEPRECATED);
43 44
44 $this->offset = func_get_arg(0); 45 $this->offset = func_get_arg(0);
45 if (func_num_args() > 1) { 46 if (\func_num_args() > 1) {
46 $this->totalNumberOfLines = func_get_arg(1); 47 $this->totalNumberOfLines = func_get_arg(1);
47 } 48 }
48 if (func_num_args() > 2) { 49 if (\func_num_args() > 2) {
49 $this->skippedLineNumbers = func_get_arg(2); 50 $this->skippedLineNumbers = func_get_arg(2);
50 } 51 }
51 } 52 }
52 } 53 }
53 54
90 * 91 *
91 * @throws ParseException If the YAML is not valid 92 * @throws ParseException If the YAML is not valid
92 */ 93 */
93 public function parse($value, $flags = 0) 94 public function parse($value, $flags = 0)
94 { 95 {
95 if (is_bool($flags)) { 96 if (\is_bool($flags)) {
96 @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED); 97 @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED);
97 98
98 if ($flags) { 99 if ($flags) {
99 $flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE; 100 $flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE;
100 } else { 101 } else {
101 $flags = 0; 102 $flags = 0;
102 } 103 }
103 } 104 }
104 105
105 if (func_num_args() >= 3) { 106 if (\func_num_args() >= 3) {
106 @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT flag instead.', E_USER_DEPRECATED); 107 @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT flag instead.', E_USER_DEPRECATED);
107 108
108 if (func_get_arg(2)) { 109 if (func_get_arg(2)) {
109 $flags |= Yaml::PARSE_OBJECT; 110 $flags |= Yaml::PARSE_OBJECT;
110 } 111 }
111 } 112 }
112 113
113 if (func_num_args() >= 4) { 114 if (\func_num_args() >= 4) {
114 @trigger_error('Passing a boolean flag to toggle object for map support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', E_USER_DEPRECATED); 115 @trigger_error('Passing a boolean flag to toggle object for map support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', E_USER_DEPRECATED);
115 116
116 if (func_get_arg(3)) { 117 if (func_get_arg(3)) {
117 $flags |= Yaml::PARSE_OBJECT_FOR_MAP; 118 $flags |= Yaml::PARSE_OBJECT_FOR_MAP;
118 } 119 }
124 125
125 if (false === preg_match('//u', $value)) { 126 if (false === preg_match('//u', $value)) {
126 throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename); 127 throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename);
127 } 128 }
128 129
129 $this->refs = array(); 130 $this->refs = [];
130 131
131 $mbEncoding = null; 132 $mbEncoding = null;
132 $e = null; 133 $e = null;
133 $data = null; 134 $data = null;
134 135
145 146
146 if (null !== $mbEncoding) { 147 if (null !== $mbEncoding) {
147 mb_internal_encoding($mbEncoding); 148 mb_internal_encoding($mbEncoding);
148 } 149 }
149 150
150 $this->lines = array(); 151 $this->lines = [];
151 $this->currentLine = ''; 152 $this->currentLine = '';
152 $this->refs = array(); 153 $this->refs = [];
153 $this->skippedLineNumbers = array(); 154 $this->skippedLineNumbers = [];
154 $this->locallySkippedLineNumbers = array(); 155 $this->locallySkippedLineNumbers = [];
155 156
156 if (null !== $e) { 157 if (null !== $e) {
157 throw $e; 158 throw $e;
158 } 159 }
159 160
164 { 165 {
165 $this->currentLineNb = -1; 166 $this->currentLineNb = -1;
166 $this->currentLine = ''; 167 $this->currentLine = '';
167 $value = $this->cleanup($value); 168 $value = $this->cleanup($value);
168 $this->lines = explode("\n", $value); 169 $this->lines = explode("\n", $value);
169 $this->locallySkippedLineNumbers = array(); 170 $this->locallySkippedLineNumbers = [];
170 171
171 if (null === $this->totalNumberOfLines) { 172 if (null === $this->totalNumberOfLines) {
172 $this->totalNumberOfLines = count($this->lines); 173 $this->totalNumberOfLines = \count($this->lines);
173 } 174 }
174 175
175 if (!$this->moveToNextLine()) { 176 if (!$this->moveToNextLine()) {
176 return null; 177 return null;
177 } 178 }
178 179
179 $data = array(); 180 $data = [];
180 $context = null; 181 $context = null;
181 $allowOverwrite = false; 182 $allowOverwrite = false;
182 183
183 while ($this->isCurrentLineEmpty()) { 184 while ($this->isCurrentLineEmpty()) {
184 if (!$this->moveToNextLine()) { 185 if (!$this->moveToNextLine()) {
210 } 211 }
211 $context = 'sequence'; 212 $context = 'sequence';
212 213
213 if (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) { 214 if (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
214 $isRef = $matches['ref']; 215 $isRef = $matches['ref'];
216 $this->refsBeingParsed[] = $isRef;
215 $values['value'] = $matches['value']; 217 $values['value'] = $matches['value'];
216 } 218 }
217 219
218 if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) { 220 if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) {
219 @trigger_error($this->getDeprecationMessage('Starting an unquoted string with a question mark followed by a space is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.'), E_USER_DEPRECATED); 221 @trigger_error($this->getDeprecationMessage('Starting an unquoted string with a question mark followed by a space is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.'), E_USER_DEPRECATED);
232 && self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->trimTag($values['value']), $matches) 234 && self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->trimTag($values['value']), $matches)
233 ) { 235 ) {
234 // this is a compact notation element, add to next block and parse 236 // this is a compact notation element, add to next block and parse
235 $block = $values['value']; 237 $block = $values['value'];
236 if ($this->isNextLineIndented()) { 238 if ($this->isNextLineIndented()) {
237 $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + strlen($values['leadspaces']) + 1); 239 $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1);
238 } 240 }
239 241
240 $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags); 242 $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags);
241 } else { 243 } else {
242 $data[] = $this->parseValue($values['value'], $flags, $context); 244 $data[] = $this->parseValue($values['value'], $flags, $context);
243 } 245 }
244 } 246 }
245 if ($isRef) { 247 if ($isRef) {
246 $this->refs[$isRef] = end($data); 248 $this->refs[$isRef] = end($data);
249 array_pop($this->refsBeingParsed);
247 } 250 }
248 } elseif ( 251 } elseif (
249 self::preg_match('#^(?P<key>(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(\s++(?P<value>.+))?$#u', rtrim($this->currentLine), $values) 252 self::preg_match('#^(?P<key>(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(\s++(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
250 && (false === strpos($values['key'], ' #') || in_array($values['key'][0], array('"', "'"))) 253 && (false === strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"]))
251 ) { 254 ) {
252 if ($context && 'sequence' == $context) { 255 if ($context && 'sequence' == $context) {
253 throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine, $this->filename); 256 throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine, $this->filename);
254 } 257 }
255 $context = 'mapping'; 258 $context = 'mapping';
269 $e->setSnippet($this->currentLine); 272 $e->setSnippet($this->currentLine);
270 273
271 throw $e; 274 throw $e;
272 } 275 }
273 276
274 if (!is_string($key) && !is_int($key)) { 277 if (!\is_string($key) && !\is_int($key)) {
275 $keyType = is_numeric($key) ? 'numeric key' : 'non-string key'; 278 $keyType = is_numeric($key) ? 'numeric key' : 'non-string key';
276 @trigger_error($this->getDeprecationMessage(sprintf('Implicit casting of %s to string is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0. Quote your evaluable mapping keys instead.', $keyType)), E_USER_DEPRECATED); 279 @trigger_error($this->getDeprecationMessage(sprintf('Implicit casting of %s to string is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0. Quote your evaluable mapping keys instead.', $keyType)), E_USER_DEPRECATED);
277 } 280 }
278 281
279 // Convert float keys to strings, to avoid being converted to integers by PHP 282 // Convert float keys to strings, to avoid being converted to integers by PHP
280 if (is_float($key)) { 283 if (\is_float($key)) {
281 $key = (string) $key; 284 $key = (string) $key;
282 } 285 }
283 286
284 if ('<<' === $key && (!isset($values['value']) || !self::preg_match('#^&(?P<ref>[^ ]+)#u', $values['value'], $refMatches))) { 287 if ('<<' === $key && (!isset($values['value']) || !self::preg_match('#^&(?P<ref>[^ ]+)#u', $values['value'], $refMatches))) {
285 $mergeNode = true; 288 $mergeNode = true;
286 $allowOverwrite = true; 289 $allowOverwrite = true;
287 if (isset($values['value'][0]) && '*' === $values['value'][0]) { 290 if (isset($values['value'][0]) && '*' === $values['value'][0]) {
288 $refName = substr(rtrim($values['value']), 1); 291 $refName = substr(rtrim($values['value']), 1);
289 if (!array_key_exists($refName, $this->refs)) { 292 if (!array_key_exists($refName, $this->refs)) {
293 if (false !== $pos = array_search($refName, $this->refsBeingParsed, true)) {
294 throw new ParseException(sprintf('Circular reference [%s, %s] detected for reference "%s".', implode(', ', \array_slice($this->refsBeingParsed, $pos)), $refName, $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename);
295 }
296
290 throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); 297 throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
291 } 298 }
292 299
293 $refValue = $this->refs[$refName]; 300 $refValue = $this->refs[$refName];
294 301
295 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) { 302 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) {
296 $refValue = (array) $refValue; 303 $refValue = (array) $refValue;
297 } 304 }
298 305
299 if (!is_array($refValue)) { 306 if (!\is_array($refValue)) {
300 throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); 307 throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
301 } 308 }
302 309
303 $data += $refValue; // array union 310 $data += $refValue; // array union
304 } else { 311 } else {
311 318
312 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) { 319 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) {
313 $parsed = (array) $parsed; 320 $parsed = (array) $parsed;
314 } 321 }
315 322
316 if (!is_array($parsed)) { 323 if (!\is_array($parsed)) {
317 throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); 324 throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
318 } 325 }
319 326
320 if (isset($parsed[0])) { 327 if (isset($parsed[0])) {
321 // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes 328 // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
324 foreach ($parsed as $parsedItem) { 331 foreach ($parsed as $parsedItem) {
325 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) { 332 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) {
326 $parsedItem = (array) $parsedItem; 333 $parsedItem = (array) $parsedItem;
327 } 334 }
328 335
329 if (!is_array($parsedItem)) { 336 if (!\is_array($parsedItem)) {
330 throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename); 337 throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename);
331 } 338 }
332 339
333 $data += $parsedItem; // array union 340 $data += $parsedItem; // array union
334 } 341 }
338 $data += $parsed; // array union 345 $data += $parsed; // array union
339 } 346 }
340 } 347 }
341 } elseif ('<<' !== $key && isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]++) *+(?P<value>.*)#u', $values['value'], $matches)) { 348 } elseif ('<<' !== $key && isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]++) *+(?P<value>.*)#u', $values['value'], $matches)) {
342 $isRef = $matches['ref']; 349 $isRef = $matches['ref'];
350 $this->refsBeingParsed[] = $isRef;
343 $values['value'] = $matches['value']; 351 $values['value'] = $matches['value'];
344 } 352 }
345 353
346 $subTag = null; 354 $subTag = null;
347 if ($mergeNode) { 355 if ($mergeNode) {
393 @trigger_error($this->getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), E_USER_DEPRECATED); 401 @trigger_error($this->getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), E_USER_DEPRECATED);
394 } 402 }
395 } 403 }
396 if ($isRef) { 404 if ($isRef) {
397 $this->refs[$isRef] = $data[$key]; 405 $this->refs[$isRef] = $data[$key];
406 array_pop($this->refsBeingParsed);
398 } 407 }
399 } else { 408 } else {
400 // multiple documents are not supported 409 // multiple documents are not supported
401 if ('---' === $this->currentLine) { 410 if ('---' === $this->currentLine) {
402 throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename); 411 throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
405 if ($deprecatedUsage = (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1])) { 414 if ($deprecatedUsage = (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1])) {
406 @trigger_error($this->getDeprecationMessage('Starting an unquoted string with a question mark followed by a space is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.'), E_USER_DEPRECATED); 415 @trigger_error($this->getDeprecationMessage('Starting an unquoted string with a question mark followed by a space is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.'), E_USER_DEPRECATED);
407 } 416 }
408 417
409 // 1-liner optionally followed by newline(s) 418 // 1-liner optionally followed by newline(s)
410 if (is_string($value) && $this->lines[0] === trim($value)) { 419 if (\is_string($value) && $this->lines[0] === trim($value)) {
411 try { 420 try {
412 $value = Inline::parse($this->lines[0], $flags, $this->refs); 421 $value = Inline::parse($this->lines[0], $flags, $this->refs);
413 } catch (ParseException $e) { 422 } catch (ParseException $e) {
414 $e->setParsedLine($this->getRealCurrentLineNb() + 1); 423 $e->setParsedLine($this->getRealCurrentLineNb() + 1);
415 $e->setSnippet($this->currentLine); 424 $e->setSnippet($this->currentLine);
468 477
469 if (null !== $tag) { 478 if (null !== $tag) {
470 $data = new TaggedValue($tag, $data); 479 $data = new TaggedValue($tag, $data);
471 } 480 }
472 481
473 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && !is_object($data) && 'mapping' === $context) { 482 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && !\is_object($data) && 'mapping' === $context) {
474 $object = new \stdClass(); 483 $object = new \stdClass();
475 484
476 foreach ($data as $key => $value) { 485 foreach ($data as $key => $value) {
477 $object->$key = $value; 486 $object->$key = $value;
478 } 487 }
498 $parser = new self(); 507 $parser = new self();
499 $parser->offset = $offset; 508 $parser->offset = $offset;
500 $parser->totalNumberOfLines = $this->totalNumberOfLines; 509 $parser->totalNumberOfLines = $this->totalNumberOfLines;
501 $parser->skippedLineNumbers = $skippedLineNumbers; 510 $parser->skippedLineNumbers = $skippedLineNumbers;
502 $parser->refs = &$this->refs; 511 $parser->refs = &$this->refs;
512 $parser->refsBeingParsed = $this->refsBeingParsed;
503 513
504 return $parser->doParse($yaml, $flags); 514 return $parser->doParse($yaml, $flags);
505 } 515 }
506 516
507 /** 517 /**
531 * 541 *
532 * @return int The current line indentation 542 * @return int The current line indentation
533 */ 543 */
534 private function getCurrentLineIndentation() 544 private function getCurrentLineIndentation()
535 { 545 {
536 return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' ')); 546 return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' '));
537 } 547 }
538 548
539 /** 549 /**
540 * Returns the next embed block of YAML. 550 * Returns the next embed block of YAML.
541 * 551 *
547 * @throws ParseException When indentation problem are detected 557 * @throws ParseException When indentation problem are detected
548 */ 558 */
549 private function getNextEmbedBlock($indentation = null, $inSequence = false) 559 private function getNextEmbedBlock($indentation = null, $inSequence = false)
550 { 560 {
551 $oldLineIndentation = $this->getCurrentLineIndentation(); 561 $oldLineIndentation = $this->getCurrentLineIndentation();
552 $blockScalarIndentations = array();
553
554 if ($this->isBlockScalarHeader()) {
555 $blockScalarIndentations[] = $oldLineIndentation;
556 }
557 562
558 if (!$this->moveToNextLine()) { 563 if (!$this->moveToNextLine()) {
559 return; 564 return;
560 } 565 }
561 566
589 } 594 }
590 } else { 595 } else {
591 $newIndent = $indentation; 596 $newIndent = $indentation;
592 } 597 }
593 598
594 $data = array(); 599 $data = [];
595 if ($this->getCurrentLineIndentation() >= $newIndent) { 600 if ($this->getCurrentLineIndentation() >= $newIndent) {
596 $data[] = substr($this->currentLine, $newIndent); 601 $data[] = substr($this->currentLine, $newIndent);
597 } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { 602 } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
598 $data[] = $this->currentLine; 603 $data[] = $this->currentLine;
599 } else { 604 } else {
610 return; 615 return;
611 } 616 }
612 617
613 $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); 618 $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
614 619
615 if (empty($blockScalarIndentations) && $this->isBlockScalarHeader()) {
616 $blockScalarIndentations[] = $this->getCurrentLineIndentation();
617 }
618
619 $previousLineIndentation = $this->getCurrentLineIndentation();
620
621 while ($this->moveToNextLine()) { 620 while ($this->moveToNextLine()) {
622 $indent = $this->getCurrentLineIndentation(); 621 $indent = $this->getCurrentLineIndentation();
623
624 // terminate all block scalars that are more indented than the current line
625 if (!empty($blockScalarIndentations) && $indent < $previousLineIndentation && '' !== trim($this->currentLine)) {
626 foreach ($blockScalarIndentations as $key => $blockScalarIndentation) {
627 if ($blockScalarIndentation >= $indent) {
628 unset($blockScalarIndentations[$key]);
629 }
630 }
631 }
632
633 if (empty($blockScalarIndentations) && !$this->isCurrentLineComment() && $this->isBlockScalarHeader()) {
634 $blockScalarIndentations[] = $indent;
635 }
636
637 $previousLineIndentation = $indent;
638 622
639 if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { 623 if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) {
640 $this->moveToPreviousLine(); 624 $this->moveToPreviousLine();
641 break; 625 break;
642 } 626 }
667 * 651 *
668 * @return bool 652 * @return bool
669 */ 653 */
670 private function moveToNextLine() 654 private function moveToNextLine()
671 { 655 {
672 if ($this->currentLineNb >= count($this->lines) - 1) { 656 if ($this->currentLineNb >= \count($this->lines) - 1) {
673 return false; 657 return false;
674 } 658 }
675 659
676 $this->currentLine = $this->lines[++$this->currentLineNb]; 660 $this->currentLine = $this->lines[++$this->currentLineNb];
677 661
713 } else { 697 } else {
714 $value = substr($value, 1); 698 $value = substr($value, 1);
715 } 699 }
716 700
717 if (!array_key_exists($value, $this->refs)) { 701 if (!array_key_exists($value, $this->refs)) {
702 if (false !== $pos = array_search($value, $this->refsBeingParsed, true)) {
703 throw new ParseException(sprintf('Circular reference [%s, %s] detected for reference "%s".', implode(', ', \array_slice($this->refsBeingParsed, $pos)), $value, $value), $this->currentLineNb + 1, $this->currentLine, $this->filename);
704 }
705
718 throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); 706 throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename);
719 } 707 }
720 708
721 return $this->refs[$value]; 709 return $this->refs[$value];
722 } 710 }
745 // do not take following lines into account when the current line is a quoted single line value 733 // do not take following lines into account when the current line is a quoted single line value
746 if (null !== $quotation && self::preg_match('/^'.$quotation.'.*'.$quotation.'(\s*#.*)?$/', $value)) { 734 if (null !== $quotation && self::preg_match('/^'.$quotation.'.*'.$quotation.'(\s*#.*)?$/', $value)) {
747 return Inline::parse($value, $flags, $this->refs); 735 return Inline::parse($value, $flags, $this->refs);
748 } 736 }
749 737
750 $lines = array(); 738 $lines = [];
751 739
752 while ($this->moveToNextLine()) { 740 while ($this->moveToNextLine()) {
753 // unquoted strings end before the first unindented line 741 // unquoted strings end before the first unindented line
754 if (null === $quotation && 0 === $this->getCurrentLineIndentation()) { 742 if (null === $quotation && 0 === $this->getCurrentLineIndentation()) {
755 $this->moveToPreviousLine(); 743 $this->moveToPreviousLine();
763 if ('' !== $this->currentLine && substr($this->currentLine, -1) === $quotation) { 751 if ('' !== $this->currentLine && substr($this->currentLine, -1) === $quotation) {
764 break; 752 break;
765 } 753 }
766 } 754 }
767 755
768 for ($i = 0, $linesCount = count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) { 756 for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) {
769 if ('' === $lines[$i]) { 757 if ('' === $lines[$i]) {
770 $value .= "\n"; 758 $value .= "\n";
771 $previousLineBlank = true; 759 $previousLineBlank = true;
772 } elseif ($previousLineBlank) { 760 } elseif ($previousLineBlank) {
773 $value .= $lines[$i]; 761 $value .= $lines[$i];
780 768
781 Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); 769 Inline::$parsedLineNumber = $this->getRealCurrentLineNb();
782 770
783 $parsedValue = Inline::parse($value, $flags, $this->refs); 771 $parsedValue = Inline::parse($value, $flags, $this->refs);
784 772
785 if ('mapping' === $context && is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) { 773 if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
786 throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename); 774 throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename);
787 } 775 }
788 776
789 return $parsedValue; 777 return $parsedValue;
790 } catch (ParseException $e) { 778 } catch (ParseException $e) {
810 if (!$notEOF) { 798 if (!$notEOF) {
811 return ''; 799 return '';
812 } 800 }
813 801
814 $isCurrentLineBlank = $this->isCurrentLineBlank(); 802 $isCurrentLineBlank = $this->isCurrentLineBlank();
815 $blockLines = array(); 803 $blockLines = [];
816 804
817 // leading blank lines are consumed before determining indentation 805 // leading blank lines are consumed before determining indentation
818 while ($notEOF && $isCurrentLineBlank) { 806 while ($notEOF && $isCurrentLineBlank) {
819 // newline only if not EOF 807 // newline only if not EOF
820 if ($notEOF = $this->moveToNextLine()) { 808 if ($notEOF = $this->moveToNextLine()) {
824 } 812 }
825 813
826 // determine indentation if not specified 814 // determine indentation if not specified
827 if (0 === $indentation) { 815 if (0 === $indentation) {
828 if (self::preg_match('/^ +/', $this->currentLine, $matches)) { 816 if (self::preg_match('/^ +/', $this->currentLine, $matches)) {
829 $indentation = strlen($matches[0]); 817 $indentation = \strlen($matches[0]);
830 } 818 }
831 } 819 }
832 820
833 if ($indentation > 0) { 821 if ($indentation > 0) {
834 $pattern = sprintf('/^ {%d}(.*)$/', $indentation); 822 $pattern = sprintf('/^ {%d}(.*)$/', $indentation);
837 $notEOF && ( 825 $notEOF && (
838 $isCurrentLineBlank || 826 $isCurrentLineBlank ||
839 self::preg_match($pattern, $this->currentLine, $matches) 827 self::preg_match($pattern, $this->currentLine, $matches)
840 ) 828 )
841 ) { 829 ) {
842 if ($isCurrentLineBlank && strlen($this->currentLine) > $indentation) { 830 if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) {
843 $blockLines[] = substr($this->currentLine, $indentation); 831 $blockLines[] = substr($this->currentLine, $indentation);
844 } elseif ($isCurrentLineBlank) { 832 } elseif ($isCurrentLineBlank) {
845 $blockLines[] = ''; 833 $blockLines[] = '';
846 } else { 834 } else {
847 $blockLines[] = $matches[1]; 835 $blockLines[] = $matches[1];
867 if ('>' === $style) { 855 if ('>' === $style) {
868 $text = ''; 856 $text = '';
869 $previousLineIndented = false; 857 $previousLineIndented = false;
870 $previousLineBlank = false; 858 $previousLineBlank = false;
871 859
872 for ($i = 0, $blockLinesCount = count($blockLines); $i < $blockLinesCount; ++$i) { 860 for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) {
873 if ('' === $blockLines[$i]) { 861 if ('' === $blockLines[$i]) {
874 $text .= "\n"; 862 $text .= "\n";
875 $previousLineIndented = false; 863 $previousLineIndented = false;
876 $previousLineBlank = true; 864 $previousLineBlank = true;
877 } elseif (' ' === $blockLines[$i][0]) { 865 } elseif (' ' === $blockLines[$i][0]) {
982 * 970 *
983 * @return string A cleaned up YAML string 971 * @return string A cleaned up YAML string
984 */ 972 */
985 private function cleanup($value) 973 private function cleanup($value)
986 { 974 {
987 $value = str_replace(array("\r\n", "\r"), "\n", $value); 975 $value = str_replace(["\r\n", "\r"], "\n", $value);
988 976
989 // strip YAML header 977 // strip YAML header
990 $count = 0; 978 $count = 0;
991 $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count); 979 $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count);
992 $this->offset += $count; 980 $this->offset += $count;
1050 * @return bool Returns true if the string is un-indented collection item, false otherwise 1038 * @return bool Returns true if the string is un-indented collection item, false otherwise
1051 */ 1039 */
1052 private function isStringUnIndentedCollectionItem() 1040 private function isStringUnIndentedCollectionItem()
1053 { 1041 {
1054 return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- '); 1042 return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- ');
1055 }
1056
1057 /**
1058 * Tests whether or not the current line is the header of a block scalar.
1059 *
1060 * @return bool
1061 */
1062 private function isBlockScalarHeader()
1063 {
1064 return (bool) self::preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $this->currentLine);
1065 } 1043 }
1066 1044
1067 /** 1045 /**
1068 * A local wrapper for `preg_match` which will throw a ParseException if there 1046 * A local wrapper for `preg_match` which will throw a ParseException if there
1069 * is an internal error in the PCRE engine. 1047 * is an internal error in the PCRE engine.