Parser::doParse()   F
last analyzed

Complexity

Conditions 113
Paths > 20000

Size

Total Lines 336
Code Lines 200

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 113
eloc 200
nc 103540
nop 2
dl 0
loc 336
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Symfony\Component\Yaml;
13
14
use Symfony\Component\Yaml\Exception\ParseException;
15
use Symfony\Component\Yaml\Tag\TaggedValue;
16
17
/**
18
 * Parser parses YAML strings to convert them to PHP arrays.
19
 *
20
 * @author Fabien Potencier <[email protected]>
21
 *
22
 * @final since version 3.4
23
 */
24
class Parser
25
{
26
    const TAG_PATTERN = '(?P<tag>![\w!.\/:-]+)';
27
    const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
28
29
    private $filename;
30
    private $offset = 0;
31
    private $totalNumberOfLines;
32
    private $lines = [];
33
    private $currentLineNb = -1;
34
    private $currentLine = '';
35
    private $refs = [];
36
    private $skippedLineNumbers = [];
37
    private $locallySkippedLineNumbers = [];
38
    private $refsBeingParsed = [];
39
40
    public function __construct()
41
    {
42
        if (\func_num_args() > 0) {
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);
44
45
            $this->offset = func_get_arg(0);
46
            if (\func_num_args() > 1) {
47
                $this->totalNumberOfLines = func_get_arg(1);
48
            }
49
            if (\func_num_args() > 2) {
50
                $this->skippedLineNumbers = func_get_arg(2);
51
            }
52
        }
53
    }
54
55
    /**
56
     * Parses a YAML file into a PHP value.
57
     *
58
     * @param string $filename The path to the YAML file to be parsed
59
     * @param int    $flags    A bit field of PARSE_* constants to customize the YAML parser behavior
60
     *
61
     * @return mixed The YAML converted to a PHP value
62
     *
63
     * @throws ParseException If the file could not be read or the YAML is not valid
64
     */
65
    public function parseFile($filename, $flags = 0)
66
    {
67
        if (!is_file($filename)) {
68
            throw new ParseException(sprintf('File "%s" does not exist.', $filename));
69
        }
70
71
        if (!is_readable($filename)) {
72
            throw new ParseException(sprintf('File "%s" cannot be read.', $filename));
73
        }
74
75
        $this->filename = $filename;
76
77
        try {
78
            return $this->parse(file_get_contents($filename), $flags);
79
        } finally {
80
            $this->filename = null;
81
        }
82
    }
83
84
    /**
85
     * Parses a YAML string to a PHP value.
86
     *
87
     * @param string $value A YAML string
88
     * @param int    $flags A bit field of PARSE_* constants to customize the YAML parser behavior
89
     *
90
     * @return mixed A PHP value
91
     *
92
     * @throws ParseException If the YAML is not valid
93
     */
94
    public function parse($value, $flags = 0)
95
    {
96
        if (\is_bool($flags)) {
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);
98
99
            if ($flags) {
100
                $flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE;
101
            } else {
102
                $flags = 0;
103
            }
104
        }
105
106
        if (\func_num_args() >= 3) {
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);
108
109
            if (func_get_arg(2)) {
110
                $flags |= Yaml::PARSE_OBJECT;
111
            }
112
        }
113
114
        if (\func_num_args() >= 4) {
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);
116
117
            if (func_get_arg(3)) {
118
                $flags |= Yaml::PARSE_OBJECT_FOR_MAP;
119
            }
120
        }
121
122
        if (Yaml::PARSE_KEYS_AS_STRINGS & $flags) {
0 ignored issues
show
Deprecated Code introduced by
The constant Symfony\Component\Yaml\Yaml::PARSE_KEYS_AS_STRINGS has been deprecated: since version 3.4, to be removed in 4.0. Quote your evaluable keys instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

122
        if (/** @scrutinizer ignore-deprecated */ Yaml::PARSE_KEYS_AS_STRINGS & $flags) {

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
123
            @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);
124
        }
125
126
        if (false === preg_match('//u', $value)) {
127
            throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename);
128
        }
129
130
        $this->refs = [];
131
132
        $mbEncoding = null;
133
        $e = null;
134
        $data = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $data is dead and can be removed.
Loading history...
135
136
        if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
137
            $mbEncoding = mb_internal_encoding();
138
            mb_internal_encoding('UTF-8');
139
        }
140
141
        try {
142
            $data = $this->doParse($value, $flags);
143
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
144
        } catch (\Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
145
        }
146
147
        if (null !== $mbEncoding) {
148
            mb_internal_encoding($mbEncoding);
0 ignored issues
show
Bug introduced by
It seems like $mbEncoding can also be of type true; however, parameter $encoding of mb_internal_encoding() does only seem to accept null|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

148
            mb_internal_encoding(/** @scrutinizer ignore-type */ $mbEncoding);
Loading history...
149
        }
150
151
        $this->lines = [];
152
        $this->currentLine = '';
153
        $this->refs = [];
154
        $this->skippedLineNumbers = [];
155
        $this->locallySkippedLineNumbers = [];
156
        $this->totalNumberOfLines = null;
157
158
        if (null !== $e) {
159
            throw $e;
160
        }
161
162
        return $data;
163
    }
164
165
    private function doParse($value, $flags)
166
    {
167
        $this->currentLineNb = -1;
168
        $this->currentLine = '';
169
        $value = $this->cleanup($value);
170
        $this->lines = explode("\n", $value);
171
        $this->locallySkippedLineNumbers = [];
172
173
        if (null === $this->totalNumberOfLines) {
174
            $this->totalNumberOfLines = \count($this->lines);
175
        }
176
177
        if (!$this->moveToNextLine()) {
178
            return null;
179
        }
180
181
        $data = [];
182
        $context = null;
183
        $allowOverwrite = false;
184
185
        while ($this->isCurrentLineEmpty()) {
186
            if (!$this->moveToNextLine()) {
187
                return null;
188
            }
189
        }
190
191
        // Resolves the tag and returns if end of the document
192
        if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, false)) && !$this->moveToNextLine()) {
193
            return new TaggedValue($tag, '');
194
        }
195
196
        do {
197
            if ($this->isCurrentLineEmpty()) {
198
                continue;
199
            }
200
201
            // tab?
202
            if ("\t" === $this->currentLine[0]) {
203
                throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
204
            }
205
206
            Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename);
207
208
            $isRef = $mergeNode = false;
209
            if (self::preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+))?$#u', rtrim($this->currentLine), $values)) {
210
                if ($context && 'mapping' == $context) {
211
                    throw new ParseException('You cannot define a sequence item when in a mapping.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
212
                }
213
                $context = 'sequence';
214
215
                if (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
216
                    $isRef = $matches['ref'];
217
                    $this->refsBeingParsed[] = $isRef;
218
                    $values['value'] = $matches['value'];
219
                }
220
221
                if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) {
222
                    @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);
223
                }
224
225
                // array
226
                if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
227
                    $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags);
228
                } elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) {
229
                    $data[] = new TaggedValue(
230
                        $subTag,
231
                        $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags)
232
                    );
233
                } else {
234
                    if (
235
                        isset($values['leadspaces'])
236
                        && (
237
                            '!' === $values['value'][0]
238
                            || self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->trimTag($values['value']), $matches)
239
                        )
240
                    ) {
241
                        // this is a compact notation element, add to next block and parse
242
                        $block = $values['value'];
243
                        if ($this->isNextLineIndented()) {
244
                            $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1);
245
                        }
246
247
                        $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags);
248
                    } else {
249
                        $data[] = $this->parseValue($values['value'], $flags, $context);
250
                    }
251
                }
252
                if ($isRef) {
253
                    $this->refs[$isRef] = end($data);
254
                    array_pop($this->refsBeingParsed);
255
                }
256
            } elseif (
257
                self::preg_match('#^(?P<key>(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(\s++(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
258
                && (false === strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"]))
259
            ) {
260
                if ($context && 'sequence' == $context) {
261
                    throw new ParseException('You cannot define a mapping item when in a sequence.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
262
                }
263
                $context = 'mapping';
264
265
                try {
266
                    $i = 0;
267
                    $evaluateKey = !(Yaml::PARSE_KEYS_AS_STRINGS & $flags);
0 ignored issues
show
Deprecated Code introduced by
The constant Symfony\Component\Yaml\Yaml::PARSE_KEYS_AS_STRINGS has been deprecated: since version 3.4, to be removed in 4.0. Quote your evaluable keys instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

267
                    $evaluateKey = !(/** @scrutinizer ignore-deprecated */ Yaml::PARSE_KEYS_AS_STRINGS & $flags);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
268
269
                    // constants in key will be evaluated anyway
270
                    if (isset($values['key'][0]) && '!' === $values['key'][0] && Yaml::PARSE_CONSTANT & $flags) {
271
                        $evaluateKey = true;
272
                    }
273
274
                    $key = Inline::parseScalar($values['key'], 0, null, $i, $evaluateKey);
275
                } catch (ParseException $e) {
276
                    $e->setParsedLine($this->getRealCurrentLineNb() + 1);
277
                    $e->setSnippet($this->currentLine);
278
279
                    throw $e;
280
                }
281
282
                if (!\is_string($key) && !\is_int($key)) {
283
                    $keyType = is_numeric($key) ? 'numeric key' : 'non-string key';
284
                    @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);
285
                }
286
287
                // Convert float keys to strings, to avoid being converted to integers by PHP
288
                if (\is_float($key)) {
289
                    $key = (string) $key;
290
                }
291
292
                if ('<<' === $key && (!isset($values['value']) || !self::preg_match('#^&(?P<ref>[^ ]+)#u', $values['value'], $refMatches))) {
293
                    $mergeNode = true;
294
                    $allowOverwrite = true;
295
                    if (isset($values['value'][0]) && '*' === $values['value'][0]) {
296
                        $refName = substr(rtrim($values['value']), 1);
297
                        if (!\array_key_exists($refName, $this->refs)) {
298
                            if (false !== $pos = array_search($refName, $this->refsBeingParsed, true)) {
299
                                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);
0 ignored issues
show
Bug introduced by
It seems like $pos can also be of type string; however, parameter $offset of array_slice() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

299
                                throw new ParseException(sprintf('Circular reference [%s, %s] detected for reference "%s".', implode(', ', \array_slice($this->refsBeingParsed, /** @scrutinizer ignore-type */ $pos)), $refName, $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename);
Loading history...
300
                            }
301
302
                            throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
303
                        }
304
305
                        $refValue = $this->refs[$refName];
306
307
                        if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) {
308
                            $refValue = (array) $refValue;
309
                        }
310
311
                        if (!\is_array($refValue)) {
312
                            throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
313
                        }
314
315
                        $data += $refValue; // array union
316
                    } else {
317
                        if (isset($values['value']) && '' !== $values['value']) {
318
                            $value = $values['value'];
319
                        } else {
320
                            $value = $this->getNextEmbedBlock();
321
                        }
322
                        $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags);
323
324
                        if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) {
325
                            $parsed = (array) $parsed;
326
                        }
327
328
                        if (!\is_array($parsed)) {
329
                            throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
330
                        }
331
332
                        if (isset($parsed[0])) {
333
                            // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
334
                            // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
335
                            // in the sequence override keys specified in later mapping nodes.
336
                            foreach ($parsed as $parsedItem) {
337
                                if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) {
338
                                    $parsedItem = (array) $parsedItem;
339
                                }
340
341
                                if (!\is_array($parsedItem)) {
342
                                    throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename);
343
                                }
344
345
                                $data += $parsedItem; // array union
346
                            }
347
                        } else {
348
                            // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
349
                            // current mapping, unless the key already exists in it.
350
                            $data += $parsed; // array union
351
                        }
352
                    }
353
                } elseif ('<<' !== $key && isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]++) *+(?P<value>.*)#u', $values['value'], $matches)) {
354
                    $isRef = $matches['ref'];
355
                    $this->refsBeingParsed[] = $isRef;
356
                    $values['value'] = $matches['value'];
357
                }
358
359
                $subTag = null;
360
                if ($mergeNode) {
361
                    // Merge keys
362
                } elseif (!isset($values['value']) || '' === $values['value'] || 0 === strpos($values['value'], '#') || (null !== $subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) {
363
                    // hash
364
                    // if next line is less indented or equal, then it means that the current value is null
365
                    if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
366
                        // Spec: Keys MUST be unique; first one wins.
367
                        // But overwriting is allowed when a merge node is used in current block.
368
                        if ($allowOverwrite || !isset($data[$key])) {
369
                            if (null !== $subTag) {
370
                                $data[$key] = new TaggedValue($subTag, '');
0 ignored issues
show
Bug introduced by
$subTag of type void is incompatible with the type string expected by parameter $tag of Symfony\Component\Yaml\T...gedValue::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

370
                                $data[$key] = new TaggedValue(/** @scrutinizer ignore-type */ $subTag, '');
Loading history...
371
                            } else {
372
                                $data[$key] = null;
373
                            }
374
                        } else {
375
                            @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);
376
                        }
377
                    } else {
378
                        $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags);
379
                        if ('<<' === $key) {
380
                            $this->refs[$refMatches['ref']] = $value;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $refMatches does not seem to be defined for all execution paths leading up to this point.
Loading history...
381
382
                            if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) {
383
                                $value = (array) $value;
384
                            }
385
386
                            $data += $value;
387
                        } elseif ($allowOverwrite || !isset($data[$key])) {
388
                            // Spec: Keys MUST be unique; first one wins.
389
                            // But overwriting is allowed when a merge node is used in current block.
390
                            if (null !== $subTag) {
391
                                $data[$key] = new TaggedValue($subTag, $value);
392
                            } else {
393
                                $data[$key] = $value;
394
                            }
395
                        } else {
396
                            @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);
397
                        }
398
                    }
399
                } else {
400
                    $value = $this->parseValue(rtrim($values['value']), $flags, $context);
401
                    // Spec: Keys MUST be unique; first one wins.
402
                    // But overwriting is allowed when a merge node is used in current block.
403
                    if ($allowOverwrite || !isset($data[$key])) {
404
                        $data[$key] = $value;
405
                    } else {
406
                        @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);
407
                    }
408
                }
409
                if ($isRef) {
410
                    $this->refs[$isRef] = $data[$key];
411
                    array_pop($this->refsBeingParsed);
412
                }
413
            } else {
414
                // multiple documents are not supported
415
                if ('---' === $this->currentLine) {
416
                    throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
417
                }
418
419
                if ($deprecatedUsage = (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1])) {
420
                    @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);
421
                }
422
423
                // 1-liner optionally followed by newline(s)
424
                if (\is_string($value) && $this->lines[0] === trim($value)) {
425
                    try {
426
                        $value = Inline::parse($this->lines[0], $flags, $this->refs);
427
                    } catch (ParseException $e) {
428
                        $e->setParsedLine($this->getRealCurrentLineNb() + 1);
429
                        $e->setSnippet($this->currentLine);
430
431
                        throw $e;
432
                    }
433
434
                    return $value;
435
                }
436
437
                // try to parse the value as a multi-line string as a last resort
438
                if (0 === $this->currentLineNb) {
439
                    $previousLineWasNewline = false;
440
                    $previousLineWasTerminatedWithBackslash = false;
441
                    $value = '';
442
443
                    foreach ($this->lines as $line) {
444
                        if ('' !== ltrim($line) && '#' === ltrim($line)[0]) {
445
                            continue;
446
                        }
447
                        // If the indentation is not consistent at offset 0, it is to be considered as a ParseError
448
                        if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) {
449
                            throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
450
                        }
451
                        if ('' === trim($line)) {
452
                            $value .= "\n";
453
                        } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
454
                            $value .= ' ';
455
                        }
456
457
                        if ('' !== trim($line) && '\\' === substr($line, -1)) {
458
                            $value .= ltrim(substr($line, 0, -1));
459
                        } elseif ('' !== trim($line)) {
460
                            $value .= trim($line);
461
                        }
462
463
                        if ('' === trim($line)) {
464
                            $previousLineWasNewline = true;
465
                            $previousLineWasTerminatedWithBackslash = false;
466
                        } elseif ('\\' === substr($line, -1)) {
467
                            $previousLineWasNewline = false;
468
                            $previousLineWasTerminatedWithBackslash = true;
469
                        } else {
470
                            $previousLineWasNewline = false;
471
                            $previousLineWasTerminatedWithBackslash = false;
472
                        }
473
                    }
474
475
                    try {
476
                        return Inline::parse(trim($value));
477
                    } catch (ParseException $e) {
478
                        // fall-through to the ParseException thrown below
479
                    }
480
                }
481
482
                throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
483
            }
484
        } while ($this->moveToNextLine());
485
486
        if (null !== $tag) {
487
            $data = new TaggedValue($tag, $data);
488
        }
489
490
        if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && !\is_object($data) && 'mapping' === $context) {
491
            $object = new \stdClass();
492
493
            foreach ($data as $key => $value) {
0 ignored issues
show
introduced by
$value is overwriting one of the parameters of this function.
Loading history...
494
                $object->$key = $value;
495
            }
496
497
            $data = $object;
498
        }
499
500
        return empty($data) ? null : $data;
501
    }
502
503
    private function parseBlock($offset, $yaml, $flags)
504
    {
505
        $skippedLineNumbers = $this->skippedLineNumbers;
506
507
        foreach ($this->locallySkippedLineNumbers as $lineNumber) {
508
            if ($lineNumber < $offset) {
509
                continue;
510
            }
511
512
            $skippedLineNumbers[] = $lineNumber;
513
        }
514
515
        $parser = new self();
516
        $parser->offset = $offset;
517
        $parser->totalNumberOfLines = $this->totalNumberOfLines;
518
        $parser->skippedLineNumbers = $skippedLineNumbers;
519
        $parser->refs = &$this->refs;
520
        $parser->refsBeingParsed = $this->refsBeingParsed;
521
522
        return $parser->doParse($yaml, $flags);
523
    }
524
525
    /**
526
     * Returns the current line number (takes the offset into account).
527
     *
528
     * @internal
529
     *
530
     * @return int The current line number
531
     */
532
    public function getRealCurrentLineNb()
533
    {
534
        $realCurrentLineNumber = $this->currentLineNb + $this->offset;
535
536
        foreach ($this->skippedLineNumbers as $skippedLineNumber) {
537
            if ($skippedLineNumber > $realCurrentLineNumber) {
538
                break;
539
            }
540
541
            ++$realCurrentLineNumber;
542
        }
543
544
        return $realCurrentLineNumber;
545
    }
546
547
    /**
548
     * Returns the current line indentation.
549
     *
550
     * @return int The current line indentation
551
     */
552
    private function getCurrentLineIndentation()
553
    {
554
        return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' '));
555
    }
556
557
    /**
558
     * Returns the next embed block of YAML.
559
     *
560
     * @param int  $indentation The indent level at which the block is to be read, or null for default
561
     * @param bool $inSequence  True if the enclosing data structure is a sequence
562
     *
563
     * @return string A YAML string
564
     *
565
     * @throws ParseException When indentation problem are detected
566
     */
567
    private function getNextEmbedBlock($indentation = null, $inSequence = false)
568
    {
569
        $oldLineIndentation = $this->getCurrentLineIndentation();
570
571
        if (!$this->moveToNextLine()) {
572
            return '';
573
        }
574
575
        if (null === $indentation) {
576
            $newIndent = null;
577
            $movements = 0;
578
579
            do {
580
                $EOF = false;
581
582
                // empty and comment-like lines do not influence the indentation depth
583
                if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
584
                    $EOF = !$this->moveToNextLine();
585
586
                    if (!$EOF) {
587
                        ++$movements;
588
                    }
589
                } else {
590
                    $newIndent = $this->getCurrentLineIndentation();
591
                }
592
            } while (!$EOF && null === $newIndent);
593
594
            for ($i = 0; $i < $movements; ++$i) {
595
                $this->moveToPreviousLine();
596
            }
597
598
            $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem();
599
600
            if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
601
                throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
602
            }
603
        } else {
604
            $newIndent = $indentation;
605
        }
606
607
        $data = [];
608
        if ($this->getCurrentLineIndentation() >= $newIndent) {
609
            $data[] = substr($this->currentLine, $newIndent);
0 ignored issues
show
Bug introduced by
It seems like $newIndent can also be of type null; however, parameter $offset of substr() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

609
            $data[] = substr($this->currentLine, /** @scrutinizer ignore-type */ $newIndent);
Loading history...
610
        } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
611
            $data[] = $this->currentLine;
612
        } else {
613
            $this->moveToPreviousLine();
614
615
            return '';
616
        }
617
618
        if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) {
619
            // the previous line contained a dash but no item content, this line is a sequence item with the same indentation
620
            // and therefore no nested list or mapping
621
            $this->moveToPreviousLine();
622
623
            return '';
624
        }
625
626
        $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
627
        $isItComment = $this->isCurrentLineComment();
628
629
        while ($this->moveToNextLine()) {
630
            if ($isItComment && !$isItUnindentedCollection) {
631
                $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
632
                $isItComment = $this->isCurrentLineComment();
633
            }
634
635
            $indent = $this->getCurrentLineIndentation();
636
637
            if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) {
638
                $this->moveToPreviousLine();
639
                break;
640
            }
641
642
            if ($this->isCurrentLineBlank()) {
643
                $data[] = substr($this->currentLine, $newIndent);
644
                continue;
645
            }
646
647
            if ($indent >= $newIndent) {
648
                $data[] = substr($this->currentLine, $newIndent);
649
            } elseif ($this->isCurrentLineComment()) {
650
                $data[] = $this->currentLine;
651
            } elseif (0 == $indent) {
652
                $this->moveToPreviousLine();
653
654
                break;
655
            } else {
656
                throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
657
            }
658
        }
659
660
        return implode("\n", $data);
661
    }
662
663
    /**
664
     * Moves the parser to the next line.
665
     *
666
     * @return bool
667
     */
668
    private function moveToNextLine()
669
    {
670
        if ($this->currentLineNb >= \count($this->lines) - 1) {
671
            return false;
672
        }
673
674
        $this->currentLine = $this->lines[++$this->currentLineNb];
675
676
        return true;
677
    }
678
679
    /**
680
     * Moves the parser to the previous line.
681
     *
682
     * @return bool
683
     */
684
    private function moveToPreviousLine()
685
    {
686
        if ($this->currentLineNb < 1) {
687
            return false;
688
        }
689
690
        $this->currentLine = $this->lines[--$this->currentLineNb];
691
692
        return true;
693
    }
694
695
    /**
696
     * Parses a YAML value.
697
     *
698
     * @param string $value   A YAML value
699
     * @param int    $flags   A bit field of PARSE_* constants to customize the YAML parser behavior
700
     * @param string $context The parser context (either sequence or mapping)
701
     *
702
     * @return mixed A PHP value
703
     *
704
     * @throws ParseException When reference does not exist
705
     */
706
    private function parseValue($value, $flags, $context)
707
    {
708
        if (0 === strpos($value, '*')) {
709
            if (false !== $pos = strpos($value, '#')) {
710
                $value = substr($value, 1, $pos - 2);
711
            } else {
712
                $value = substr($value, 1);
713
            }
714
715
            if (!\array_key_exists($value, $this->refs)) {
716
                if (false !== $pos = array_search($value, $this->refsBeingParsed, true)) {
717
                    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);
0 ignored issues
show
Bug introduced by
It seems like $pos can also be of type string; however, parameter $offset of array_slice() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

717
                    throw new ParseException(sprintf('Circular reference [%s, %s] detected for reference "%s".', implode(', ', \array_slice($this->refsBeingParsed, /** @scrutinizer ignore-type */ $pos)), $value, $value), $this->currentLineNb + 1, $this->currentLine, $this->filename);
Loading history...
718
                }
719
720
                throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename);
721
            }
722
723
            return $this->refs[$value];
724
        }
725
726
        if (self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
727
            $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
728
729
            $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), abs((int) $modifiers));
0 ignored issues
show
Bug introduced by
It seems like abs((int)$modifiers) can also be of type double; however, parameter $indentation of Symfony\Component\Yaml\Parser::parseBlockScalar() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

729
            $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), /** @scrutinizer ignore-type */ abs((int) $modifiers));
Loading history...
730
731
            if ('' !== $matches['tag']) {
732
                if ('!!binary' === $matches['tag']) {
733
                    return Inline::evaluateBinaryScalar($data);
734
                } elseif ('tagged' === $matches['tag']) {
735
                    return new TaggedValue(substr($matches['tag'], 1), $data);
736
                } elseif ('!' !== $matches['tag']) {
737
                    @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);
738
                }
739
            }
740
741
            return $data;
742
        }
743
744
        try {
745
            $quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null;
746
747
            // do not take following lines into account when the current line is a quoted single line value
748
            if (null !== $quotation && self::preg_match('/^'.$quotation.'.*'.$quotation.'(\s*#.*)?$/', $value)) {
749
                return Inline::parse($value, $flags, $this->refs);
750
            }
751
752
            $lines = [];
753
754
            while ($this->moveToNextLine()) {
755
                // unquoted strings end before the first unindented line
756
                if (null === $quotation && 0 === $this->getCurrentLineIndentation()) {
757
                    $this->moveToPreviousLine();
758
759
                    break;
760
                }
761
762
                $lines[] = trim($this->currentLine);
763
764
                // quoted string values end with a line that is terminated with the quotation character
765
                $escapedLine = str_replace(['\\\\', '\\"'], '', $this->currentLine);
766
                if ('' !== $escapedLine && substr($escapedLine, -1) === $quotation) {
767
                    break;
768
                }
769
            }
770
771
            for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) {
772
                if ('' === $lines[$i]) {
773
                    $value .= "\n";
774
                    $previousLineBlank = true;
775
                } elseif ($previousLineBlank) {
776
                    $value .= $lines[$i];
777
                    $previousLineBlank = false;
778
                } else {
779
                    $value .= ' '.$lines[$i];
780
                    $previousLineBlank = false;
781
                }
782
            }
783
784
            Inline::$parsedLineNumber = $this->getRealCurrentLineNb();
785
786
            $parsedValue = Inline::parse($value, $flags, $this->refs);
787
788
            if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
789
                throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename);
790
            }
791
792
            return $parsedValue;
793
        } catch (ParseException $e) {
794
            $e->setParsedLine($this->getRealCurrentLineNb() + 1);
795
            $e->setSnippet($this->currentLine);
796
797
            throw $e;
798
        }
799
    }
800
801
    /**
802
     * Parses a block scalar.
803
     *
804
     * @param string $style       The style indicator that was used to begin this block scalar (| or >)
805
     * @param string $chomping    The chomping indicator that was used to begin this block scalar (+ or -)
806
     * @param int    $indentation The indentation indicator that was used to begin this block scalar
807
     *
808
     * @return string The text value
809
     */
810
    private function parseBlockScalar($style, $chomping = '', $indentation = 0)
811
    {
812
        $notEOF = $this->moveToNextLine();
813
        if (!$notEOF) {
814
            return '';
815
        }
816
817
        $isCurrentLineBlank = $this->isCurrentLineBlank();
818
        $blockLines = [];
819
820
        // leading blank lines are consumed before determining indentation
821
        while ($notEOF && $isCurrentLineBlank) {
822
            // newline only if not EOF
823
            if ($notEOF = $this->moveToNextLine()) {
824
                $blockLines[] = '';
825
                $isCurrentLineBlank = $this->isCurrentLineBlank();
826
            }
827
        }
828
829
        // determine indentation if not specified
830
        if (0 === $indentation) {
831
            if (self::preg_match('/^ +/', $this->currentLine, $matches)) {
832
                $indentation = \strlen($matches[0]);
833
            }
834
        }
835
836
        if ($indentation > 0) {
837
            $pattern = sprintf('/^ {%d}(.*)$/', $indentation);
838
839
            while (
840
                $notEOF && (
841
                    $isCurrentLineBlank ||
842
                    self::preg_match($pattern, $this->currentLine, $matches)
843
                )
844
            ) {
845
                if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) {
846
                    $blockLines[] = substr($this->currentLine, $indentation);
847
                } elseif ($isCurrentLineBlank) {
848
                    $blockLines[] = '';
849
                } else {
850
                    $blockLines[] = $matches[1];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $matches does not seem to be defined for all execution paths leading up to this point.
Loading history...
851
                }
852
853
                // newline only if not EOF
854
                if ($notEOF = $this->moveToNextLine()) {
855
                    $isCurrentLineBlank = $this->isCurrentLineBlank();
856
                }
857
            }
858
        } elseif ($notEOF) {
859
            $blockLines[] = '';
860
        }
861
862
        if ($notEOF) {
863
            $blockLines[] = '';
864
            $this->moveToPreviousLine();
865
        } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) {
866
            $blockLines[] = '';
867
        }
868
869
        // folded style
870
        if ('>' === $style) {
871
            $text = '';
872
            $previousLineIndented = false;
873
            $previousLineBlank = false;
874
875
            for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) {
876
                if ('' === $blockLines[$i]) {
877
                    $text .= "\n";
878
                    $previousLineIndented = false;
879
                    $previousLineBlank = true;
880
                } elseif (' ' === $blockLines[$i][0]) {
881
                    $text .= "\n".$blockLines[$i];
882
                    $previousLineIndented = true;
883
                    $previousLineBlank = false;
884
                } elseif ($previousLineIndented) {
885
                    $text .= "\n".$blockLines[$i];
886
                    $previousLineIndented = false;
887
                    $previousLineBlank = false;
888
                } elseif ($previousLineBlank || 0 === $i) {
889
                    $text .= $blockLines[$i];
890
                    $previousLineIndented = false;
891
                    $previousLineBlank = false;
892
                } else {
893
                    $text .= ' '.$blockLines[$i];
894
                    $previousLineIndented = false;
895
                    $previousLineBlank = false;
896
                }
897
            }
898
        } else {
899
            $text = implode("\n", $blockLines);
900
        }
901
902
        // deal with trailing newlines
903
        if ('' === $chomping) {
904
            $text = preg_replace('/\n+$/', "\n", $text);
905
        } elseif ('-' === $chomping) {
906
            $text = preg_replace('/\n+$/', '', $text);
907
        }
908
909
        return $text;
910
    }
911
912
    /**
913
     * Returns true if the next line is indented.
914
     *
915
     * @return bool Returns true if the next line is indented, false otherwise
916
     */
917
    private function isNextLineIndented()
918
    {
919
        $currentIndentation = $this->getCurrentLineIndentation();
920
        $movements = 0;
921
922
        do {
923
            $EOF = !$this->moveToNextLine();
924
925
            if (!$EOF) {
926
                ++$movements;
927
            }
928
        } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()));
929
930
        if ($EOF) {
931
            return false;
932
        }
933
934
        $ret = $this->getCurrentLineIndentation() > $currentIndentation;
935
936
        for ($i = 0; $i < $movements; ++$i) {
937
            $this->moveToPreviousLine();
938
        }
939
940
        return $ret;
941
    }
942
943
    /**
944
     * Returns true if the current line is blank or if it is a comment line.
945
     *
946
     * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise
947
     */
948
    private function isCurrentLineEmpty()
949
    {
950
        return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
951
    }
952
953
    /**
954
     * Returns true if the current line is blank.
955
     *
956
     * @return bool Returns true if the current line is blank, false otherwise
957
     */
958
    private function isCurrentLineBlank()
959
    {
960
        return '' == trim($this->currentLine, ' ');
961
    }
962
963
    /**
964
     * Returns true if the current line is a comment line.
965
     *
966
     * @return bool Returns true if the current line is a comment line, false otherwise
967
     */
968
    private function isCurrentLineComment()
969
    {
970
        //checking explicitly the first char of the trim is faster than loops or strpos
971
        $ltrimmedLine = ltrim($this->currentLine, ' ');
972
973
        return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0];
974
    }
975
976
    private function isCurrentLineLastLineInDocument()
977
    {
978
        return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
979
    }
980
981
    /**
982
     * Cleanups a YAML string to be parsed.
983
     *
984
     * @param string $value The input YAML string
985
     *
986
     * @return string A cleaned up YAML string
987
     */
988
    private function cleanup($value)
989
    {
990
        $value = str_replace(["\r\n", "\r"], "\n", $value);
991
992
        // strip YAML header
993
        $count = 0;
994
        $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count);
995
        $this->offset += $count;
996
997
        // remove leading comments
998
        $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count);
999
        if (1 === $count) {
1000
            // items have been removed, update the offset
1001
            $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
1002
            $value = $trimmedValue;
1003
        }
1004
1005
        // remove start of the document marker (---)
1006
        $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count);
1007
        if (1 === $count) {
1008
            // items have been removed, update the offset
1009
            $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
1010
            $value = $trimmedValue;
1011
1012
            // remove end of the document marker (...)
1013
            $value = preg_replace('#\.\.\.\s*$#', '', $value);
1014
        }
1015
1016
        return $value;
1017
    }
1018
1019
    /**
1020
     * Returns true if the next line starts unindented collection.
1021
     *
1022
     * @return bool Returns true if the next line starts unindented collection, false otherwise
1023
     */
1024
    private function isNextLineUnIndentedCollection()
1025
    {
1026
        $currentIndentation = $this->getCurrentLineIndentation();
1027
        $movements = 0;
1028
1029
        do {
1030
            $EOF = !$this->moveToNextLine();
1031
1032
            if (!$EOF) {
1033
                ++$movements;
1034
            }
1035
        } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()));
1036
1037
        if ($EOF) {
1038
            return false;
1039
        }
1040
1041
        $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem();
1042
1043
        for ($i = 0; $i < $movements; ++$i) {
1044
            $this->moveToPreviousLine();
1045
        }
1046
1047
        return $ret;
1048
    }
1049
1050
    /**
1051
     * Returns true if the string is un-indented collection item.
1052
     *
1053
     * @return bool Returns true if the string is un-indented collection item, false otherwise
1054
     */
1055
    private function isStringUnIndentedCollectionItem()
1056
    {
1057
        return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- ');
1058
    }
1059
1060
    /**
1061
     * A local wrapper for `preg_match` which will throw a ParseException if there
1062
     * is an internal error in the PCRE engine.
1063
     *
1064
     * This avoids us needing to check for "false" every time PCRE is used
1065
     * in the YAML engine
1066
     *
1067
     * @throws ParseException on a PCRE internal error
1068
     *
1069
     * @see preg_last_error()
1070
     *
1071
     * @internal
1072
     */
1073
    public static function preg_match($pattern, $subject, &$matches = null, $flags = 0, $offset = 0)
1074
    {
1075
        if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) {
1076
            switch (preg_last_error()) {
1077
                case \PREG_INTERNAL_ERROR:
1078
                    $error = 'Internal PCRE error.';
1079
                    break;
1080
                case \PREG_BACKTRACK_LIMIT_ERROR:
1081
                    $error = 'pcre.backtrack_limit reached.';
1082
                    break;
1083
                case \PREG_RECURSION_LIMIT_ERROR:
1084
                    $error = 'pcre.recursion_limit reached.';
1085
                    break;
1086
                case \PREG_BAD_UTF8_ERROR:
1087
                    $error = 'Malformed UTF-8 data.';
1088
                    break;
1089
                case \PREG_BAD_UTF8_OFFSET_ERROR:
1090
                    $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
1091
                    break;
1092
                default:
1093
                    $error = 'Error.';
1094
            }
1095
1096
            throw new ParseException($error);
1097
        }
1098
1099
        return $ret;
1100
    }
1101
1102
    /**
1103
     * Trim the tag on top of the value.
1104
     *
1105
     * Prevent values such as `!foo {quz: bar}` to be considered as
1106
     * a mapping block.
1107
     */
1108
    private function trimTag($value)
1109
    {
1110
        if ('!' === $value[0]) {
1111
            return ltrim(substr($value, 1, strcspn($value, " \r\n", 1)), ' ');
1112
        }
1113
1114
        return $value;
1115
    }
1116
1117
    /**
1118
     * @return string|null
1119
     */
1120
    private function getLineTag($value, $flags, $nextLineCheck = true)
1121
    {
1122
        if ('' === $value || '!' !== $value[0] || 1 !== self::preg_match('/^'.self::TAG_PATTERN.' *( +#.*)?$/', $value, $matches)) {
1123
            return null;
1124
        }
1125
1126
        if ($nextLineCheck && !$this->isNextLineIndented()) {
1127
            return null;
1128
        }
1129
1130
        $tag = substr($matches['tag'], 1);
1131
1132
        // Built-in tags
1133
        if ($tag && '!' === $tag[0]) {
1134
            throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
1135
        }
1136
1137
        if (Yaml::PARSE_CUSTOM_TAGS & $flags) {
1138
            return $tag;
1139
        }
1140
1141
        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);
1142
    }
1143
1144
    private function getDeprecationMessage($message)
1145
    {
1146
        $message = rtrim($message, '.');
1147
1148
        if (null !== $this->filename) {
1149
            $message .= ' in '.$this->filename;
1150
        }
1151
1152
        $message .= ' on line '.($this->getRealCurrentLineNb() + 1);
1153
1154
        return $message.'.';
1155
    }
1156
}
1157