comparison vendor/symfony/yaml/Parser.php @ 14:1fec387a4317

Update Drupal core to 8.5.2 via Composer
author Chris Cannam
date Mon, 23 Apr 2018 09:46:53 +0100
parents 4c8ae668cc8c
children 129ea1e6d783
comparison
equal deleted inserted replaced
13:5fb285c0d0e3 14:1fec387a4317
10 */ 10 */
11 11
12 namespace Symfony\Component\Yaml; 12 namespace Symfony\Component\Yaml;
13 13
14 use Symfony\Component\Yaml\Exception\ParseException; 14 use Symfony\Component\Yaml\Exception\ParseException;
15 use Symfony\Component\Yaml\Tag\TaggedValue;
15 16
16 /** 17 /**
17 * Parser parses YAML strings to convert them to PHP arrays. 18 * Parser parses YAML strings to convert them to PHP arrays.
18 * 19 *
19 * @author Fabien Potencier <fabien@symfony.com> 20 * @author Fabien Potencier <fabien@symfony.com>
21 *
22 * @final since version 3.4
20 */ 23 */
21 class Parser 24 class Parser
22 { 25 {
23 const TAG_PATTERN = '((?P<tag>![\w!.\/:-]+) +)?'; 26 const TAG_PATTERN = '(?P<tag>![\w!.\/:-]+)';
24 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> +#.*)?';
25 28
29 private $filename;
26 private $offset = 0; 30 private $offset = 0;
27 private $totalNumberOfLines; 31 private $totalNumberOfLines;
28 private $lines = array(); 32 private $lines = array();
29 private $currentLineNb = -1; 33 private $currentLineNb = -1;
30 private $currentLine = ''; 34 private $currentLine = '';
31 private $refs = array(); 35 private $refs = array();
32 private $skippedLineNumbers = array(); 36 private $skippedLineNumbers = array();
33 private $locallySkippedLineNumbers = array(); 37 private $locallySkippedLineNumbers = array();
34 38
35 /** 39 public function __construct()
36 * Constructor. 40 {
37 * 41 if (func_num_args() > 0) {
38 * @param int $offset The offset of YAML document (used for line numbers in error messages) 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);
39 * @param int|null $totalNumberOfLines The overall number of lines being parsed 43
40 * @param int[] $skippedLineNumbers Number of comment lines that have been skipped by the parser 44 $this->offset = func_get_arg(0);
41 */ 45 if (func_num_args() > 1) {
42 public function __construct($offset = 0, $totalNumberOfLines = null, array $skippedLineNumbers = array()) 46 $this->totalNumberOfLines = func_get_arg(1);
43 { 47 }
44 $this->offset = $offset; 48 if (func_num_args() > 2) {
45 $this->totalNumberOfLines = $totalNumberOfLines; 49 $this->skippedLineNumbers = func_get_arg(2);
46 $this->skippedLineNumbers = $skippedLineNumbers; 50 }
51 }
52 }
53
54 /**
55 * Parses a YAML file into a PHP value.
56 *
57 * @param string $filename The path to the YAML file to be parsed
58 * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
59 *
60 * @return mixed The YAML converted to a PHP value
61 *
62 * @throws ParseException If the file could not be read or the YAML is not valid
63 */
64 public function parseFile($filename, $flags = 0)
65 {
66 if (!is_file($filename)) {
67 throw new ParseException(sprintf('File "%s" does not exist.', $filename));
68 }
69
70 if (!is_readable($filename)) {
71 throw new ParseException(sprintf('File "%s" cannot be read.', $filename));
72 }
73
74 $this->filename = $filename;
75
76 try {
77 return $this->parse(file_get_contents($filename), $flags);
78 } finally {
79 $this->filename = null;
80 }
47 } 81 }
48 82
49 /** 83 /**
50 * Parses a YAML string to a PHP value. 84 * Parses a YAML string to a PHP value.
51 * 85 *
57 * @throws ParseException If the YAML is not valid 91 * @throws ParseException If the YAML is not valid
58 */ 92 */
59 public function parse($value, $flags = 0) 93 public function parse($value, $flags = 0)
60 { 94 {
61 if (is_bool($flags)) { 95 if (is_bool($flags)) {
62 @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE flag instead.', E_USER_DEPRECATED); 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);
63 97
64 if ($flags) { 98 if ($flags) {
65 $flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE; 99 $flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE;
66 } else { 100 } else {
67 $flags = 0; 101 $flags = 0;
68 } 102 }
69 } 103 }
70 104
71 if (func_num_args() >= 3) { 105 if (func_num_args() >= 3) {
72 @trigger_error('Passing a boolean flag to toggle object support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT flag instead.', E_USER_DEPRECATED); 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);
73 107
74 if (func_get_arg(2)) { 108 if (func_get_arg(2)) {
75 $flags |= Yaml::PARSE_OBJECT; 109 $flags |= Yaml::PARSE_OBJECT;
76 } 110 }
77 } 111 }
78 112
79 if (func_num_args() >= 4) { 113 if (func_num_args() >= 4) {
80 @trigger_error('Passing a boolean flag to toggle object for map support is deprecated since version 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', E_USER_DEPRECATED); 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);
81 115
82 if (func_get_arg(3)) { 116 if (func_get_arg(3)) {
83 $flags |= Yaml::PARSE_OBJECT_FOR_MAP; 117 $flags |= Yaml::PARSE_OBJECT_FOR_MAP;
84 } 118 }
85 } 119 }
86 120
121 if (Yaml::PARSE_KEYS_AS_STRINGS & $flags) {
122 @trigger_error('Using the Yaml::PARSE_KEYS_AS_STRINGS flag is deprecated since Symfony 3.4 as it will be removed in 4.0. Quote your keys when they are evaluable instead.', E_USER_DEPRECATED);
123 }
124
87 if (false === preg_match('//u', $value)) { 125 if (false === preg_match('//u', $value)) {
88 throw new ParseException('The YAML value does not appear to be valid UTF-8.'); 126 throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename);
89 } 127 }
90 128
91 $this->refs = array(); 129 $this->refs = array();
92 130
93 $mbEncoding = null; 131 $mbEncoding = null;
132 170
133 if (null === $this->totalNumberOfLines) { 171 if (null === $this->totalNumberOfLines) {
134 $this->totalNumberOfLines = count($this->lines); 172 $this->totalNumberOfLines = count($this->lines);
135 } 173 }
136 174
175 if (!$this->moveToNextLine()) {
176 return null;
177 }
178
137 $data = array(); 179 $data = array();
138 $context = null; 180 $context = null;
139 $allowOverwrite = false; 181 $allowOverwrite = false;
140 182
141 while ($this->moveToNextLine()) { 183 while ($this->isCurrentLineEmpty()) {
184 if (!$this->moveToNextLine()) {
185 return null;
186 }
187 }
188
189 // Resolves the tag and returns if end of the document
190 if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, false)) && !$this->moveToNextLine()) {
191 return new TaggedValue($tag, '');
192 }
193
194 do {
142 if ($this->isCurrentLineEmpty()) { 195 if ($this->isCurrentLineEmpty()) {
143 continue; 196 continue;
144 } 197 }
145 198
146 // tab? 199 // tab?
147 if ("\t" === $this->currentLine[0]) { 200 if ("\t" === $this->currentLine[0]) {
148 throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine); 201 throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
149 } 202 }
203
204 Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename);
150 205
151 $isRef = $mergeNode = false; 206 $isRef = $mergeNode = false;
152 if (self::preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+))?$#u', rtrim($this->currentLine), $values)) { 207 if (self::preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+))?$#u', rtrim($this->currentLine), $values)) {
153 if ($context && 'mapping' == $context) { 208 if ($context && 'mapping' == $context) {
154 throw new ParseException('You cannot define a sequence item when in a mapping', $this->getRealCurrentLineNb() + 1, $this->currentLine); 209 throw new ParseException('You cannot define a sequence item when in a mapping', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
155 } 210 }
156 $context = 'sequence'; 211 $context = 'sequence';
157 212
158 if (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) { 213 if (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
159 $isRef = $matches['ref']; 214 $isRef = $matches['ref'];
160 $values['value'] = $matches['value']; 215 $values['value'] = $matches['value'];
161 } 216 }
162 217
218 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);
220 }
221
163 // array 222 // array
164 if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { 223 if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
165 $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags); 224 $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags);
225 } elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) {
226 $data[] = new TaggedValue(
227 $subTag,
228 $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags)
229 );
166 } else { 230 } else {
167 if (isset($values['leadspaces']) 231 if (isset($values['leadspaces'])
168 && self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+))?$#u', rtrim($values['value']), $matches) 232 && self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->trimTag($values['value']), $matches)
169 ) { 233 ) {
170 // this is a compact notation element, add to next block and parse 234 // this is a compact notation element, add to next block and parse
171 $block = $values['value']; 235 $block = $values['value'];
172 if ($this->isNextLineIndented()) { 236 if ($this->isNextLineIndented()) {
173 $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + strlen($values['leadspaces']) + 1); 237 $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + strlen($values['leadspaces']) + 1);
180 } 244 }
181 if ($isRef) { 245 if ($isRef) {
182 $this->refs[$isRef] = end($data); 246 $this->refs[$isRef] = end($data);
183 } 247 }
184 } elseif ( 248 } elseif (
185 self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P<value>.+))?$#u', rtrim($this->currentLine), $values) 249 self::preg_match('#^(?P<key>(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(\s++(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
186 && (false === strpos($values['key'], ' #') || in_array($values['key'][0], array('"', "'"))) 250 && (false === strpos($values['key'], ' #') || in_array($values['key'][0], array('"', "'")))
187 ) { 251 ) {
188 if ($context && 'sequence' == $context) { 252 if ($context && 'sequence' == $context) {
189 throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine); 253 throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine, $this->filename);
190 } 254 }
191 $context = 'mapping'; 255 $context = 'mapping';
192 256
193 // force correct settings
194 Inline::parse(null, $flags, $this->refs);
195 try { 257 try {
196 Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); 258 $i = 0;
197 $key = Inline::parseScalar($values['key']); 259 $evaluateKey = !(Yaml::PARSE_KEYS_AS_STRINGS & $flags);
260
261 // constants in key will be evaluated anyway
262 if (isset($values['key'][0]) && '!' === $values['key'][0] && Yaml::PARSE_CONSTANT & $flags) {
263 $evaluateKey = true;
264 }
265
266 $key = Inline::parseScalar($values['key'], 0, null, $i, $evaluateKey);
198 } catch (ParseException $e) { 267 } catch (ParseException $e) {
199 $e->setParsedLine($this->getRealCurrentLineNb() + 1); 268 $e->setParsedLine($this->getRealCurrentLineNb() + 1);
200 $e->setSnippet($this->currentLine); 269 $e->setSnippet($this->currentLine);
201 270
202 throw $e; 271 throw $e;
203 } 272 }
204 273
274 if (!is_string($key) && !is_int($key)) {
275 $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);
277 }
278
205 // Convert float keys to strings, to avoid being converted to integers by PHP 279 // Convert float keys to strings, to avoid being converted to integers by PHP
206 if (is_float($key)) { 280 if (is_float($key)) {
207 $key = (string) $key; 281 $key = (string) $key;
208 } 282 }
209 283
210 if ('<<' === $key) { 284 if ('<<' === $key && (!isset($values['value']) || !self::preg_match('#^&(?P<ref>[^ ]+)#u', $values['value'], $refMatches))) {
211 $mergeNode = true; 285 $mergeNode = true;
212 $allowOverwrite = true; 286 $allowOverwrite = true;
213 if (isset($values['value']) && 0 === strpos($values['value'], '*')) { 287 if (isset($values['value'][0]) && '*' === $values['value'][0]) {
214 $refName = substr($values['value'], 1); 288 $refName = substr(rtrim($values['value']), 1);
215 if (!array_key_exists($refName, $this->refs)) { 289 if (!array_key_exists($refName, $this->refs)) {
216 throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine); 290 throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
217 } 291 }
218 292
219 $refValue = $this->refs[$refName]; 293 $refValue = $this->refs[$refName];
220 294
295 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) {
296 $refValue = (array) $refValue;
297 }
298
221 if (!is_array($refValue)) { 299 if (!is_array($refValue)) {
222 throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); 300 throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
223 } 301 }
224 302
225 $data += $refValue; // array union 303 $data += $refValue; // array union
226 } else { 304 } else {
227 if (isset($values['value']) && $values['value'] !== '') { 305 if (isset($values['value']) && '' !== $values['value']) {
228 $value = $values['value']; 306 $value = $values['value'];
229 } else { 307 } else {
230 $value = $this->getNextEmbedBlock(); 308 $value = $this->getNextEmbedBlock();
231 } 309 }
232 $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags); 310 $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags);
233 311
312 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) {
313 $parsed = (array) $parsed;
314 }
315
234 if (!is_array($parsed)) { 316 if (!is_array($parsed)) {
235 throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); 317 throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
236 } 318 }
237 319
238 if (isset($parsed[0])) { 320 if (isset($parsed[0])) {
239 // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes 321 // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
240 // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier 322 // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
241 // in the sequence override keys specified in later mapping nodes. 323 // in the sequence override keys specified in later mapping nodes.
242 foreach ($parsed as $parsedItem) { 324 foreach ($parsed as $parsedItem) {
325 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) {
326 $parsedItem = (array) $parsedItem;
327 }
328
243 if (!is_array($parsedItem)) { 329 if (!is_array($parsedItem)) {
244 throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem); 330 throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename);
245 } 331 }
246 332
247 $data += $parsedItem; // array union 333 $data += $parsedItem; // array union
248 } 334 }
249 } else { 335 } else {
250 // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the 336 // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
251 // current mapping, unless the key already exists in it. 337 // current mapping, unless the key already exists in it.
252 $data += $parsed; // array union 338 $data += $parsed; // array union
253 } 339 }
254 } 340 }
255 } elseif (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) { 341 } elseif ('<<' !== $key && isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]++) *+(?P<value>.*)#u', $values['value'], $matches)) {
256 $isRef = $matches['ref']; 342 $isRef = $matches['ref'];
257 $values['value'] = $matches['value']; 343 $values['value'] = $matches['value'];
258 } 344 }
259 345
346 $subTag = null;
260 if ($mergeNode) { 347 if ($mergeNode) {
261 // Merge keys 348 // Merge keys
262 } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { 349 } elseif (!isset($values['value']) || '' === $values['value'] || 0 === strpos($values['value'], '#') || (null !== $subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) {
263 // hash 350 // hash
264 // if next line is less indented or equal, then it means that the current value is null 351 // if next line is less indented or equal, then it means that the current value is null
265 if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { 352 if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
266 // Spec: Keys MUST be unique; first one wins. 353 // Spec: Keys MUST be unique; first one wins.
267 // But overwriting is allowed when a merge node is used in current block. 354 // But overwriting is allowed when a merge node is used in current block.
268 if ($allowOverwrite || !isset($data[$key])) { 355 if ($allowOverwrite || !isset($data[$key])) {
269 $data[$key] = null; 356 if (null !== $subTag) {
357 $data[$key] = new TaggedValue($subTag, '');
358 } else {
359 $data[$key] = null;
360 }
270 } else { 361 } else {
271 @trigger_error(sprintf('Duplicate key "%s" detected on line %d whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since version 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key, $this->getRealCurrentLineNb() + 1), E_USER_DEPRECATED); 362 @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);
272 } 363 }
273 } else { 364 } else {
274 // remember the parsed line number here in case we need it to provide some contexts in error messages below
275 $realCurrentLineNbKey = $this->getRealCurrentLineNb();
276 $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags); 365 $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags);
277 // Spec: Keys MUST be unique; first one wins. 366 if ('<<' === $key) {
278 // But overwriting is allowed when a merge node is used in current block. 367 $this->refs[$refMatches['ref']] = $value;
279 if ($allowOverwrite || !isset($data[$key])) { 368
280 $data[$key] = $value; 369 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) {
370 $value = (array) $value;
371 }
372
373 $data += $value;
374 } elseif ($allowOverwrite || !isset($data[$key])) {
375 // Spec: Keys MUST be unique; first one wins.
376 // But overwriting is allowed when a merge node is used in current block.
377 if (null !== $subTag) {
378 $data[$key] = new TaggedValue($subTag, $value);
379 } else {
380 $data[$key] = $value;
381 }
281 } else { 382 } else {
282 @trigger_error(sprintf('Duplicate key "%s" detected on line %d whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since version 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key, $realCurrentLineNbKey + 1), E_USER_DEPRECATED); 383 @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);
283 } 384 }
284 } 385 }
285 } else { 386 } else {
286 $value = $this->parseValue($values['value'], $flags, $context); 387 $value = $this->parseValue(rtrim($values['value']), $flags, $context);
287 // Spec: Keys MUST be unique; first one wins. 388 // Spec: Keys MUST be unique; first one wins.
288 // But overwriting is allowed when a merge node is used in current block. 389 // But overwriting is allowed when a merge node is used in current block.
289 if ($allowOverwrite || !isset($data[$key])) { 390 if ($allowOverwrite || !isset($data[$key])) {
290 $data[$key] = $value; 391 $data[$key] = $value;
291 } else { 392 } else {
292 @trigger_error(sprintf('Duplicate key "%s" detected on line %d whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since version 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key, $this->getRealCurrentLineNb() + 1), E_USER_DEPRECATED); 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);
293 } 394 }
294 } 395 }
295 if ($isRef) { 396 if ($isRef) {
296 $this->refs[$isRef] = $data[$key]; 397 $this->refs[$isRef] = $data[$key];
297 } 398 }
298 } else { 399 } else {
299 // multiple documents are not supported 400 // multiple documents are not supported
300 if ('---' === $this->currentLine) { 401 if ('---' === $this->currentLine) {
301 throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine); 402 throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
403 }
404
405 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);
302 } 407 }
303 408
304 // 1-liner optionally followed by newline(s) 409 // 1-liner optionally followed by newline(s)
305 if (is_string($value) && $this->lines[0] === trim($value)) { 410 if (is_string($value) && $this->lines[0] === trim($value)) {
306 try { 411 try {
307 Inline::$parsedLineNumber = $this->getRealCurrentLineNb();
308 $value = Inline::parse($this->lines[0], $flags, $this->refs); 412 $value = Inline::parse($this->lines[0], $flags, $this->refs);
309 } catch (ParseException $e) { 413 } catch (ParseException $e) {
310 $e->setParsedLine($this->getRealCurrentLineNb() + 1); 414 $e->setParsedLine($this->getRealCurrentLineNb() + 1);
311 $e->setSnippet($this->currentLine); 415 $e->setSnippet($this->currentLine);
312 416
314 } 418 }
315 419
316 return $value; 420 return $value;
317 } 421 }
318 422
319 throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine); 423 // try to parse the value as a multi-line string as a last resort
320 } 424 if (0 === $this->currentLineNb) {
425 $previousLineWasNewline = false;
426 $previousLineWasTerminatedWithBackslash = false;
427 $value = '';
428
429 foreach ($this->lines as $line) {
430 // If the indentation is not consistent at offset 0, it is to be considered as a ParseError
431 if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) {
432 throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
433 }
434 if ('' === trim($line)) {
435 $value .= "\n";
436 } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
437 $value .= ' ';
438 }
439
440 if ('' !== trim($line) && '\\' === substr($line, -1)) {
441 $value .= ltrim(substr($line, 0, -1));
442 } elseif ('' !== trim($line)) {
443 $value .= trim($line);
444 }
445
446 if ('' === trim($line)) {
447 $previousLineWasNewline = true;
448 $previousLineWasTerminatedWithBackslash = false;
449 } elseif ('\\' === substr($line, -1)) {
450 $previousLineWasNewline = false;
451 $previousLineWasTerminatedWithBackslash = true;
452 } else {
453 $previousLineWasNewline = false;
454 $previousLineWasTerminatedWithBackslash = false;
455 }
456 }
457
458 try {
459 return Inline::parse(trim($value));
460 } catch (ParseException $e) {
461 // fall-through to the ParseException thrown below
462 }
463 }
464
465 throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
466 }
467 } while ($this->moveToNextLine());
468
469 if (null !== $tag) {
470 $data = new TaggedValue($tag, $data);
321 } 471 }
322 472
323 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && !is_object($data) && 'mapping' === $context) { 473 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && !is_object($data) && 'mapping' === $context) {
324 $object = new \stdClass(); 474 $object = new \stdClass();
325 475
343 } 493 }
344 494
345 $skippedLineNumbers[] = $lineNumber; 495 $skippedLineNumbers[] = $lineNumber;
346 } 496 }
347 497
348 $parser = new self($offset, $this->totalNumberOfLines, $skippedLineNumbers); 498 $parser = new self();
499 $parser->offset = $offset;
500 $parser->totalNumberOfLines = $this->totalNumberOfLines;
501 $parser->skippedLineNumbers = $skippedLineNumbers;
349 $parser->refs = &$this->refs; 502 $parser->refs = &$this->refs;
350 503
351 return $parser->doParse($yaml, $flags); 504 return $parser->doParse($yaml, $flags);
352 } 505 }
353 506
354 /** 507 /**
355 * Returns the current line number (takes the offset into account). 508 * Returns the current line number (takes the offset into account).
356 * 509 *
510 * @internal
511 *
357 * @return int The current line number 512 * @return int The current line number
358 */ 513 */
359 private function getRealCurrentLineNb() 514 public function getRealCurrentLineNb()
360 { 515 {
361 $realCurrentLineNumber = $this->currentLineNb + $this->offset; 516 $realCurrentLineNumber = $this->currentLineNb + $this->offset;
362 517
363 foreach ($this->skippedLineNumbers as $skippedLineNumber) { 518 foreach ($this->skippedLineNumbers as $skippedLineNumber) {
364 if ($skippedLineNumber > $realCurrentLineNumber) { 519 if ($skippedLineNumber > $realCurrentLineNumber) {
395 { 550 {
396 $oldLineIndentation = $this->getCurrentLineIndentation(); 551 $oldLineIndentation = $this->getCurrentLineIndentation();
397 $blockScalarIndentations = array(); 552 $blockScalarIndentations = array();
398 553
399 if ($this->isBlockScalarHeader()) { 554 if ($this->isBlockScalarHeader()) {
400 $blockScalarIndentations[] = $this->getCurrentLineIndentation(); 555 $blockScalarIndentations[] = $oldLineIndentation;
401 } 556 }
402 557
403 if (!$this->moveToNextLine()) { 558 if (!$this->moveToNextLine()) {
404 return; 559 return;
405 } 560 }
406 561
407 if (null === $indentation) { 562 if (null === $indentation) {
408 $newIndent = $this->getCurrentLineIndentation(); 563 $newIndent = null;
564 $movements = 0;
565
566 do {
567 $EOF = false;
568
569 // empty and comment-like lines do not influence the indentation depth
570 if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
571 $EOF = !$this->moveToNextLine();
572
573 if (!$EOF) {
574 ++$movements;
575 }
576 } else {
577 $newIndent = $this->getCurrentLineIndentation();
578 }
579 } while (!$EOF && null === $newIndent);
580
581 for ($i = 0; $i < $movements; ++$i) {
582 $this->moveToPreviousLine();
583 }
409 584
410 $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem(); 585 $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem();
411 586
412 if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { 587 if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
413 throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); 588 throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
414 } 589 }
415 } else { 590 } else {
416 $newIndent = $indentation; 591 $newIndent = $indentation;
417 } 592 }
418 593
419 $data = array(); 594 $data = array();
420 if ($this->getCurrentLineIndentation() >= $newIndent) { 595 if ($this->getCurrentLineIndentation() >= $newIndent) {
421 $data[] = substr($this->currentLine, $newIndent); 596 $data[] = substr($this->currentLine, $newIndent);
597 } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
598 $data[] = $this->currentLine;
422 } else { 599 } else {
423 $this->moveToPreviousLine(); 600 $this->moveToPreviousLine();
424 601
425 return; 602 return;
426 } 603 }
443 620
444 while ($this->moveToNextLine()) { 621 while ($this->moveToNextLine()) {
445 $indent = $this->getCurrentLineIndentation(); 622 $indent = $this->getCurrentLineIndentation();
446 623
447 // terminate all block scalars that are more indented than the current line 624 // terminate all block scalars that are more indented than the current line
448 if (!empty($blockScalarIndentations) && $indent < $previousLineIndentation && trim($this->currentLine) !== '') { 625 if (!empty($blockScalarIndentations) && $indent < $previousLineIndentation && '' !== trim($this->currentLine)) {
449 foreach ($blockScalarIndentations as $key => $blockScalarIndentation) { 626 foreach ($blockScalarIndentations as $key => $blockScalarIndentation) {
450 if ($blockScalarIndentation >= $this->getCurrentLineIndentation()) { 627 if ($blockScalarIndentation >= $indent) {
451 unset($blockScalarIndentations[$key]); 628 unset($blockScalarIndentations[$key]);
452 } 629 }
453 } 630 }
454 } 631 }
455 632
456 if (empty($blockScalarIndentations) && !$this->isCurrentLineComment() && $this->isBlockScalarHeader()) { 633 if (empty($blockScalarIndentations) && !$this->isCurrentLineComment() && $this->isBlockScalarHeader()) {
457 $blockScalarIndentations[] = $this->getCurrentLineIndentation(); 634 $blockScalarIndentations[] = $indent;
458 } 635 }
459 636
460 $previousLineIndentation = $indent; 637 $previousLineIndentation = $indent;
461 638
462 if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { 639 if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) {
467 if ($this->isCurrentLineBlank()) { 644 if ($this->isCurrentLineBlank()) {
468 $data[] = substr($this->currentLine, $newIndent); 645 $data[] = substr($this->currentLine, $newIndent);
469 continue; 646 continue;
470 } 647 }
471 648
472 // we ignore "comment" lines only when we are not inside a scalar block
473 if (empty($blockScalarIndentations) && $this->isCurrentLineComment()) {
474 // remember ignored comment lines (they are used later in nested
475 // parser calls to determine real line numbers)
476 //
477 // CAUTION: beware to not populate the global property here as it
478 // will otherwise influence the getRealCurrentLineNb() call here
479 // for consecutive comment lines and subsequent embedded blocks
480 $this->locallySkippedLineNumbers[] = $this->getRealCurrentLineNb();
481
482 continue;
483 }
484
485 if ($indent >= $newIndent) { 649 if ($indent >= $newIndent) {
486 $data[] = substr($this->currentLine, $newIndent); 650 $data[] = substr($this->currentLine, $newIndent);
651 } elseif ($this->isCurrentLineComment()) {
652 $data[] = $this->currentLine;
487 } elseif (0 == $indent) { 653 } elseif (0 == $indent) {
488 $this->moveToPreviousLine(); 654 $this->moveToPreviousLine();
489 655
490 break; 656 break;
491 } else { 657 } else {
492 throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); 658 throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
493 } 659 }
494 } 660 }
495 661
496 return implode("\n", $data); 662 return implode("\n", $data);
497 } 663 }
547 } else { 713 } else {
548 $value = substr($value, 1); 714 $value = substr($value, 1);
549 } 715 }
550 716
551 if (!array_key_exists($value, $this->refs)) { 717 if (!array_key_exists($value, $this->refs)) {
552 throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine); 718 throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename);
553 } 719 }
554 720
555 return $this->refs[$value]; 721 return $this->refs[$value];
556 } 722 }
557 723
558 if (self::preg_match('/^'.self::TAG_PATTERN.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) { 724 if (self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
559 $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : ''; 725 $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
560 726
561 $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers)); 727 $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers));
562 728
563 if (isset($matches['tag']) && '!!binary' === $matches['tag']) { 729 if ('' !== $matches['tag']) {
564 return Inline::evaluateBinaryScalar($data); 730 if ('!!binary' === $matches['tag']) {
731 return Inline::evaluateBinaryScalar($data);
732 } elseif ('tagged' === $matches['tag']) {
733 return new TaggedValue(substr($matches['tag'], 1), $data);
734 } elseif ('!' !== $matches['tag']) {
735 @trigger_error($this->getDeprecationMessage(sprintf('Using the custom tag "%s" for the value "%s" is deprecated since Symfony 3.3. It will be replaced by an instance of %s in 4.0.', $matches['tag'], $data, TaggedValue::class)), E_USER_DEPRECATED);
736 }
565 } 737 }
566 738
567 return $data; 739 return $data;
568 } 740 }
569 741
570 try { 742 try {
571 $quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null; 743 $quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null;
572 744
573 // do not take following lines into account when the current line is a quoted single line value 745 // do not take following lines into account when the current line is a quoted single line value
574 if (null !== $quotation && preg_match('/^'.$quotation.'.*'.$quotation.'(\s*#.*)?$/', $value)) { 746 if (null !== $quotation && self::preg_match('/^'.$quotation.'.*'.$quotation.'(\s*#.*)?$/', $value)) {
575 return Inline::parse($value, $flags, $this->refs); 747 return Inline::parse($value, $flags, $this->refs);
576 } 748 }
749
750 $lines = array();
577 751
578 while ($this->moveToNextLine()) { 752 while ($this->moveToNextLine()) {
579 // unquoted strings end before the first unindented line 753 // unquoted strings end before the first unindented line
580 if (null === $quotation && $this->getCurrentLineIndentation() === 0) { 754 if (null === $quotation && 0 === $this->getCurrentLineIndentation()) {
581 $this->moveToPreviousLine(); 755 $this->moveToPreviousLine();
582 756
583 break; 757 break;
584 } 758 }
585 759
586 $value .= ' '.trim($this->currentLine); 760 $lines[] = trim($this->currentLine);
587 761
588 // quoted string values end with a line that is terminated with the quotation character 762 // quoted string values end with a line that is terminated with the quotation character
589 if ('' !== $this->currentLine && substr($this->currentLine, -1) === $quotation) { 763 if ('' !== $this->currentLine && substr($this->currentLine, -1) === $quotation) {
590 break; 764 break;
591 } 765 }
592 } 766 }
593 767
768 for ($i = 0, $linesCount = count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) {
769 if ('' === $lines[$i]) {
770 $value .= "\n";
771 $previousLineBlank = true;
772 } elseif ($previousLineBlank) {
773 $value .= $lines[$i];
774 $previousLineBlank = false;
775 } else {
776 $value .= ' '.$lines[$i];
777 $previousLineBlank = false;
778 }
779 }
780
594 Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); 781 Inline::$parsedLineNumber = $this->getRealCurrentLineNb();
782
595 $parsedValue = Inline::parse($value, $flags, $this->refs); 783 $parsedValue = Inline::parse($value, $flags, $this->refs);
596 784
597 if ('mapping' === $context && is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) { 785 if ('mapping' === $context && is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
598 throw new ParseException('A colon cannot be used in an unquoted mapping value.'); 786 throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename);
599 } 787 }
600 788
601 return $parsedValue; 789 return $parsedValue;
602 } catch (ParseException $e) { 790 } catch (ParseException $e) {
603 $e->setParsedLine($this->getRealCurrentLineNb() + 1); 791 $e->setParsedLine($this->getRealCurrentLineNb() + 1);
724 * @return bool Returns true if the next line is indented, false otherwise 912 * @return bool Returns true if the next line is indented, false otherwise
725 */ 913 */
726 private function isNextLineIndented() 914 private function isNextLineIndented()
727 { 915 {
728 $currentIndentation = $this->getCurrentLineIndentation(); 916 $currentIndentation = $this->getCurrentLineIndentation();
729 $EOF = !$this->moveToNextLine(); 917 $movements = 0;
730 918
731 while (!$EOF && $this->isCurrentLineEmpty()) { 919 do {
732 $EOF = !$this->moveToNextLine(); 920 $EOF = !$this->moveToNextLine();
733 } 921
922 if (!$EOF) {
923 ++$movements;
924 }
925 } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()));
734 926
735 if ($EOF) { 927 if ($EOF) {
736 return false; 928 return false;
737 } 929 }
738 930
739 $ret = $this->getCurrentLineIndentation() > $currentIndentation; 931 $ret = $this->getCurrentLineIndentation() > $currentIndentation;
740 932
741 $this->moveToPreviousLine(); 933 for ($i = 0; $i < $movements; ++$i) {
934 $this->moveToPreviousLine();
935 }
742 936
743 return $ret; 937 return $ret;
744 } 938 }
745 939
746 /** 940 /**
771 private function isCurrentLineComment() 965 private function isCurrentLineComment()
772 { 966 {
773 //checking explicitly the first char of the trim is faster than loops or strpos 967 //checking explicitly the first char of the trim is faster than loops or strpos
774 $ltrimmedLine = ltrim($this->currentLine, ' '); 968 $ltrimmedLine = ltrim($this->currentLine, ' ');
775 969
776 return '' !== $ltrimmedLine && $ltrimmedLine[0] === '#'; 970 return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0];
777 } 971 }
778 972
779 private function isCurrentLineLastLineInDocument() 973 private function isCurrentLineLastLineInDocument()
780 { 974 {
781 return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1); 975 return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
797 $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count); 991 $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count);
798 $this->offset += $count; 992 $this->offset += $count;
799 993
800 // remove leading comments 994 // remove leading comments
801 $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); 995 $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count);
802 if ($count == 1) { 996 if (1 === $count) {
803 // items have been removed, update the offset 997 // items have been removed, update the offset
804 $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); 998 $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
805 $value = $trimmedValue; 999 $value = $trimmedValue;
806 } 1000 }
807 1001
808 // remove start of the document marker (---) 1002 // remove start of the document marker (---)
809 $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count); 1003 $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count);
810 if ($count == 1) { 1004 if (1 === $count) {
811 // items have been removed, update the offset 1005 // items have been removed, update the offset
812 $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); 1006 $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
813 $value = $trimmedValue; 1007 $value = $trimmedValue;
814 1008
815 // remove end of the document marker (...) 1009 // remove end of the document marker (...)
825 * @return bool Returns true if the next line starts unindented collection, false otherwise 1019 * @return bool Returns true if the next line starts unindented collection, false otherwise
826 */ 1020 */
827 private function isNextLineUnIndentedCollection() 1021 private function isNextLineUnIndentedCollection()
828 { 1022 {
829 $currentIndentation = $this->getCurrentLineIndentation(); 1023 $currentIndentation = $this->getCurrentLineIndentation();
830 $notEOF = $this->moveToNextLine(); 1024 $movements = 0;
831 1025
832 while ($notEOF && $this->isCurrentLineEmpty()) { 1026 do {
833 $notEOF = $this->moveToNextLine(); 1027 $EOF = !$this->moveToNextLine();
834 } 1028
835 1029 if (!$EOF) {
836 if (false === $notEOF) { 1030 ++$movements;
1031 }
1032 } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()));
1033
1034 if ($EOF) {
837 return false; 1035 return false;
838 } 1036 }
839 1037
840 $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem(); 1038 $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem();
841 1039
842 $this->moveToPreviousLine(); 1040 for ($i = 0; $i < $movements; ++$i) {
1041 $this->moveToPreviousLine();
1042 }
843 1043
844 return $ret; 1044 return $ret;
845 } 1045 }
846 1046
847 /** 1047 /**
903 throw new ParseException($error); 1103 throw new ParseException($error);
904 } 1104 }
905 1105
906 return $ret; 1106 return $ret;
907 } 1107 }
1108
1109 /**
1110 * Trim the tag on top of the value.
1111 *
1112 * Prevent values such as `!foo {quz: bar}` to be considered as
1113 * a mapping block.
1114 */
1115 private function trimTag($value)
1116 {
1117 if ('!' === $value[0]) {
1118 return ltrim(substr($value, 1, strcspn($value, " \r\n", 1)), ' ');
1119 }
1120
1121 return $value;
1122 }
1123
1124 private function getLineTag($value, $flags, $nextLineCheck = true)
1125 {
1126 if ('' === $value || '!' !== $value[0] || 1 !== self::preg_match('/^'.self::TAG_PATTERN.' *( +#.*)?$/', $value, $matches)) {
1127 return;
1128 }
1129
1130 if ($nextLineCheck && !$this->isNextLineIndented()) {
1131 return;
1132 }
1133
1134 $tag = substr($matches['tag'], 1);
1135
1136 // Built-in tags
1137 if ($tag && '!' === $tag[0]) {
1138 throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
1139 }
1140
1141 if (Yaml::PARSE_CUSTOM_TAGS & $flags) {
1142 return $tag;
1143 }
1144
1145 throw new ParseException(sprintf('Tags support is not enabled. You must use the flag `Yaml::PARSE_CUSTOM_TAGS` to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
1146 }
1147
1148 private function getDeprecationMessage($message)
1149 {
1150 $message = rtrim($message, '.');
1151
1152 if (null !== $this->filename) {
1153 $message .= ' in '.$this->filename;
1154 }
1155
1156 $message .= ' on line '.($this->getRealCurrentLineNb() + 1);
1157
1158 return $message.'.';
1159 }
908 } 1160 }