Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
Parser.php
0001 <?php
0002
0003 /*
0004 * This file is part of the Symfony package.
0005 *
0006 * (c) Fabien Potencier <fabien@symfony.com>
0007 *
0008 * For the full copyright and license information, please view the LICENSE
0009 * file that was distributed with this source code.
0010 */
0011
0012 namespace Symfony\Component\Yaml;
0013
0014 use Symfony\Component\Yaml\Exception\ParseException;
0015 use Symfony\Component\Yaml\Tag\TaggedValue;
0016
0017 /**
0018 * Parser parses YAML strings to convert them to PHP arrays.
0019 *
0020 * @author Fabien Potencier <fabien@symfony.com>
0021 *
0022 * @final since version 3.4
0023 */
0024 class Parser
0025 {
0026 const TAG_PATTERN = '(?P<tag>![\w!.\/:-]+)';
0027 const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
0028
0029 private $filename;
0030 private $offset = 0;
0031 private $totalNumberOfLines;
0032 private $lines = [];
0033 private $currentLineNb = -1;
0034 private $currentLine = '';
0035 private $refs = [];
0036 private $skippedLineNumbers = [];
0037 private $locallySkippedLineNumbers = [];
0038 private $refsBeingParsed = [];
0039
0040 public function __construct()
0041 {
0042 if (\func_num_args() > 0) {
0043 @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);
0044
0045 $this->offset = func_get_arg(0);
0046 if (\func_num_args() > 1) {
0047 $this->totalNumberOfLines = func_get_arg(1);
0048 }
0049 if (\func_num_args() > 2) {
0050 $this->skippedLineNumbers = func_get_arg(2);
0051 }
0052 }
0053 }
0054
0055 /**
0056 * Parses a YAML file into a PHP value.
0057 *
0058 * @param string $filename The path to the YAML file to be parsed
0059 * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
0060 *
0061 * @return mixed The YAML converted to a PHP value
0062 *
0063 * @throws ParseException If the file could not be read or the YAML is not valid
0064 */
0065 public function parseFile($filename, $flags = 0)
0066 {
0067 if (!is_file($filename)) {
0068 throw new ParseException(sprintf('File "%s" does not exist.', $filename));
0069 }
0070
0071 if (!is_readable($filename)) {
0072 throw new ParseException(sprintf('File "%s" cannot be read.', $filename));
0073 }
0074
0075 $this->filename = $filename;
0076
0077 try {
0078 return $this->parse(file_get_contents($filename), $flags);
0079 } finally {
0080 $this->filename = null;
0081 }
0082 }
0083
0084 /**
0085 * Parses a YAML string to a PHP value.
0086 *
0087 * @param string $value A YAML string
0088 * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
0089 *
0090 * @return mixed A PHP value
0091 *
0092 * @throws ParseException If the YAML is not valid
0093 */
0094 public function parse($value, $flags = 0)
0095 {
0096 if (\is_bool($flags)) {
0097 @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);
0098
0099 if ($flags) {
0100 $flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE;
0101 } else {
0102 $flags = 0;
0103 }
0104 }
0105
0106 if (\func_num_args() >= 3) {
0107 @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);
0108
0109 if (func_get_arg(2)) {
0110 $flags |= Yaml::PARSE_OBJECT;
0111 }
0112 }
0113
0114 if (\func_num_args() >= 4) {
0115 @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);
0116
0117 if (func_get_arg(3)) {
0118 $flags |= Yaml::PARSE_OBJECT_FOR_MAP;
0119 }
0120 }
0121
0122 if (Yaml::PARSE_KEYS_AS_STRINGS & $flags) {
0123 @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);
0124 }
0125
0126 if (false === preg_match('//u', $value)) {
0127 throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename);
0128 }
0129
0130 $this->refs = [];
0131
0132 $mbEncoding = null;
0133 $e = null;
0134 $data = null;
0135
0136 if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
0137 $mbEncoding = mb_internal_encoding();
0138 mb_internal_encoding('UTF-8');
0139 }
0140
0141 try {
0142 $data = $this->doParse($value, $flags);
0143 } catch (\Exception $e) {
0144 } catch (\Throwable $e) {
0145 }
0146
0147 if (null !== $mbEncoding) {
0148 mb_internal_encoding($mbEncoding);
0149 }
0150
0151 $this->lines = [];
0152 $this->currentLine = '';
0153 $this->refs = [];
0154 $this->skippedLineNumbers = [];
0155 $this->locallySkippedLineNumbers = [];
0156 $this->totalNumberOfLines = null;
0157
0158 if (null !== $e) {
0159 throw $e;
0160 }
0161
0162 return $data;
0163 }
0164
0165 private function doParse($value, $flags)
0166 {
0167 $this->currentLineNb = -1;
0168 $this->currentLine = '';
0169 $value = $this->cleanup($value);
0170 $this->lines = explode("\n", $value);
0171 $this->locallySkippedLineNumbers = [];
0172
0173 if (null === $this->totalNumberOfLines) {
0174 $this->totalNumberOfLines = \count($this->lines);
0175 }
0176
0177 if (!$this->moveToNextLine()) {
0178 return null;
0179 }
0180
0181 $data = [];
0182 $context = null;
0183 $allowOverwrite = false;
0184
0185 while ($this->isCurrentLineEmpty()) {
0186 if (!$this->moveToNextLine()) {
0187 return null;
0188 }
0189 }
0190
0191 // Resolves the tag and returns if end of the document
0192 if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, false)) && !$this->moveToNextLine()) {
0193 return new TaggedValue($tag, '');
0194 }
0195
0196 do {
0197 if ($this->isCurrentLineEmpty()) {
0198 continue;
0199 }
0200
0201 // tab?
0202 if ("\t" === $this->currentLine[0]) {
0203 throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0204 }
0205
0206 Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename);
0207
0208 $isRef = $mergeNode = false;
0209 if (self::preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+))?$#u', rtrim($this->currentLine), $values)) {
0210 if ($context && 'mapping' == $context) {
0211 throw new ParseException('You cannot define a sequence item when in a mapping.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0212 }
0213 $context = 'sequence';
0214
0215 if (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
0216 $isRef = $matches['ref'];
0217 $this->refsBeingParsed[] = $isRef;
0218 $values['value'] = $matches['value'];
0219 }
0220
0221 if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) {
0222 @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);
0223 }
0224
0225 // array
0226 if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
0227 $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags);
0228 } elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) {
0229 $data[] = new TaggedValue(
0230 $subTag,
0231 $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags)
0232 );
0233 } else {
0234 if (
0235 isset($values['leadspaces'])
0236 && (
0237 '!' === $values['value'][0]
0238 || self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->trimTag($values['value']), $matches)
0239 )
0240 ) {
0241 // this is a compact notation element, add to next block and parse
0242 $block = $values['value'];
0243 if ($this->isNextLineIndented()) {
0244 $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1);
0245 }
0246
0247 $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags);
0248 } else {
0249 $data[] = $this->parseValue($values['value'], $flags, $context);
0250 }
0251 }
0252 if ($isRef) {
0253 $this->refs[$isRef] = end($data);
0254 array_pop($this->refsBeingParsed);
0255 }
0256 } elseif (
0257 self::preg_match('#^(?P<key>(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(\s++(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
0258 && (false === strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"]))
0259 ) {
0260 if ($context && 'sequence' == $context) {
0261 throw new ParseException('You cannot define a mapping item when in a sequence.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
0262 }
0263 $context = 'mapping';
0264
0265 try {
0266 $i = 0;
0267 $evaluateKey = !(Yaml::PARSE_KEYS_AS_STRINGS & $flags);
0268
0269 // constants in key will be evaluated anyway
0270 if (isset($values['key'][0]) && '!' === $values['key'][0] && Yaml::PARSE_CONSTANT & $flags) {
0271 $evaluateKey = true;
0272 }
0273
0274 $key = Inline::parseScalar($values['key'], 0, null, $i, $evaluateKey);
0275 } catch (ParseException $e) {
0276 $e->setParsedLine($this->getRealCurrentLineNb() + 1);
0277 $e->setSnippet($this->currentLine);
0278
0279 throw $e;
0280 }
0281
0282 if (!\is_string($key) && !\is_int($key)) {
0283 $keyType = is_numeric($key) ? 'numeric key' : 'non-string key';
0284 @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);
0285 }
0286
0287 // Convert float keys to strings, to avoid being converted to integers by PHP
0288 if (\is_float($key)) {
0289 $key = (string) $key;
0290 }
0291
0292 if ('<<' === $key && (!isset($values['value']) || !self::preg_match('#^&(?P<ref>[^ ]+)#u', $values['value'], $refMatches))) {
0293 $mergeNode = true;
0294 $allowOverwrite = true;
0295 if (isset($values['value'][0]) && '*' === $values['value'][0]) {
0296 $refName = substr(rtrim($values['value']), 1);
0297 if (!\array_key_exists($refName, $this->refs)) {
0298 if (false !== $pos = array_search($refName, $this->refsBeingParsed, true)) {
0299 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);
0300 }
0301
0302 throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0303 }
0304
0305 $refValue = $this->refs[$refName];
0306
0307 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) {
0308 $refValue = (array) $refValue;
0309 }
0310
0311 if (!\is_array($refValue)) {
0312 throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0313 }
0314
0315 $data += $refValue; // array union
0316 } else {
0317 if (isset($values['value']) && '' !== $values['value']) {
0318 $value = $values['value'];
0319 } else {
0320 $value = $this->getNextEmbedBlock();
0321 }
0322 $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags);
0323
0324 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) {
0325 $parsed = (array) $parsed;
0326 }
0327
0328 if (!\is_array($parsed)) {
0329 throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0330 }
0331
0332 if (isset($parsed[0])) {
0333 // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
0334 // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
0335 // in the sequence override keys specified in later mapping nodes.
0336 foreach ($parsed as $parsedItem) {
0337 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) {
0338 $parsedItem = (array) $parsedItem;
0339 }
0340
0341 if (!\is_array($parsedItem)) {
0342 throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename);
0343 }
0344
0345 $data += $parsedItem; // array union
0346 }
0347 } else {
0348 // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
0349 // current mapping, unless the key already exists in it.
0350 $data += $parsed; // array union
0351 }
0352 }
0353 } elseif ('<<' !== $key && isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]++) *+(?P<value>.*)#u', $values['value'], $matches)) {
0354 $isRef = $matches['ref'];
0355 $this->refsBeingParsed[] = $isRef;
0356 $values['value'] = $matches['value'];
0357 }
0358
0359 $subTag = null;
0360 if ($mergeNode) {
0361 // Merge keys
0362 } elseif (!isset($values['value']) || '' === $values['value'] || 0 === strpos($values['value'], '#') || (null !== $subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) {
0363 // hash
0364 // if next line is less indented or equal, then it means that the current value is null
0365 if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
0366 // Spec: Keys MUST be unique; first one wins.
0367 // But overwriting is allowed when a merge node is used in current block.
0368 if ($allowOverwrite || !isset($data[$key])) {
0369 if (null !== $subTag) {
0370 $data[$key] = new TaggedValue($subTag, '');
0371 } else {
0372 $data[$key] = null;
0373 }
0374 } else {
0375 @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);
0376 }
0377 } else {
0378 $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags);
0379 if ('<<' === $key) {
0380 $this->refs[$refMatches['ref']] = $value;
0381
0382 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) {
0383 $value = (array) $value;
0384 }
0385
0386 $data += $value;
0387 } elseif ($allowOverwrite || !isset($data[$key])) {
0388 // Spec: Keys MUST be unique; first one wins.
0389 // But overwriting is allowed when a merge node is used in current block.
0390 if (null !== $subTag) {
0391 $data[$key] = new TaggedValue($subTag, $value);
0392 } else {
0393 $data[$key] = $value;
0394 }
0395 } else {
0396 @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);
0397 }
0398 }
0399 } else {
0400 $value = $this->parseValue(rtrim($values['value']), $flags, $context);
0401 // Spec: Keys MUST be unique; first one wins.
0402 // But overwriting is allowed when a merge node is used in current block.
0403 if ($allowOverwrite || !isset($data[$key])) {
0404 $data[$key] = $value;
0405 } else {
0406 @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);
0407 }
0408 }
0409 if ($isRef) {
0410 $this->refs[$isRef] = $data[$key];
0411 array_pop($this->refsBeingParsed);
0412 }
0413 } else {
0414 // multiple documents are not supported
0415 if ('---' === $this->currentLine) {
0416 throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
0417 }
0418
0419 if ($deprecatedUsage = (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1])) {
0420 @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);
0421 }
0422
0423 // 1-liner optionally followed by newline(s)
0424 if (\is_string($value) && $this->lines[0] === trim($value)) {
0425 try {
0426 $value = Inline::parse($this->lines[0], $flags, $this->refs);
0427 } catch (ParseException $e) {
0428 $e->setParsedLine($this->getRealCurrentLineNb() + 1);
0429 $e->setSnippet($this->currentLine);
0430
0431 throw $e;
0432 }
0433
0434 return $value;
0435 }
0436
0437 // try to parse the value as a multi-line string as a last resort
0438 if (0 === $this->currentLineNb) {
0439 $previousLineWasNewline = false;
0440 $previousLineWasTerminatedWithBackslash = false;
0441 $value = '';
0442
0443 foreach ($this->lines as $line) {
0444 if ('' !== ltrim($line) && '#' === ltrim($line)[0]) {
0445 continue;
0446 }
0447 // If the indentation is not consistent at offset 0, it is to be considered as a ParseError
0448 if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) {
0449 throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0450 }
0451 if ('' === trim($line)) {
0452 $value .= "\n";
0453 } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
0454 $value .= ' ';
0455 }
0456
0457 if ('' !== trim($line) && '\\' === substr($line, -1)) {
0458 $value .= ltrim(substr($line, 0, -1));
0459 } elseif ('' !== trim($line)) {
0460 $value .= trim($line);
0461 }
0462
0463 if ('' === trim($line)) {
0464 $previousLineWasNewline = true;
0465 $previousLineWasTerminatedWithBackslash = false;
0466 } elseif ('\\' === substr($line, -1)) {
0467 $previousLineWasNewline = false;
0468 $previousLineWasTerminatedWithBackslash = true;
0469 } else {
0470 $previousLineWasNewline = false;
0471 $previousLineWasTerminatedWithBackslash = false;
0472 }
0473 }
0474
0475 try {
0476 return Inline::parse(trim($value));
0477 } catch (ParseException $e) {
0478 // fall-through to the ParseException thrown below
0479 }
0480 }
0481
0482 throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0483 }
0484 } while ($this->moveToNextLine());
0485
0486 if (null !== $tag) {
0487 $data = new TaggedValue($tag, $data);
0488 }
0489
0490 if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && !\is_object($data) && 'mapping' === $context) {
0491 $object = new \stdClass();
0492
0493 foreach ($data as $key => $value) {
0494 $object->$key = $value;
0495 }
0496
0497 $data = $object;
0498 }
0499
0500 return empty($data) ? null : $data;
0501 }
0502
0503 private function parseBlock($offset, $yaml, $flags)
0504 {
0505 $skippedLineNumbers = $this->skippedLineNumbers;
0506
0507 foreach ($this->locallySkippedLineNumbers as $lineNumber) {
0508 if ($lineNumber < $offset) {
0509 continue;
0510 }
0511
0512 $skippedLineNumbers[] = $lineNumber;
0513 }
0514
0515 $parser = new self();
0516 $parser->offset = $offset;
0517 $parser->totalNumberOfLines = $this->totalNumberOfLines;
0518 $parser->skippedLineNumbers = $skippedLineNumbers;
0519 $parser->refs = &$this->refs;
0520 $parser->refsBeingParsed = $this->refsBeingParsed;
0521
0522 return $parser->doParse($yaml, $flags);
0523 }
0524
0525 /**
0526 * Returns the current line number (takes the offset into account).
0527 *
0528 * @internal
0529 *
0530 * @return int The current line number
0531 */
0532 public function getRealCurrentLineNb()
0533 {
0534 $realCurrentLineNumber = $this->currentLineNb + $this->offset;
0535
0536 foreach ($this->skippedLineNumbers as $skippedLineNumber) {
0537 if ($skippedLineNumber > $realCurrentLineNumber) {
0538 break;
0539 }
0540
0541 ++$realCurrentLineNumber;
0542 }
0543
0544 return $realCurrentLineNumber;
0545 }
0546
0547 /**
0548 * Returns the current line indentation.
0549 *
0550 * @return int The current line indentation
0551 */
0552 private function getCurrentLineIndentation()
0553 {
0554 return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' '));
0555 }
0556
0557 /**
0558 * Returns the next embed block of YAML.
0559 *
0560 * @param int $indentation The indent level at which the block is to be read, or null for default
0561 * @param bool $inSequence True if the enclosing data structure is a sequence
0562 *
0563 * @return string A YAML string
0564 *
0565 * @throws ParseException When indentation problem are detected
0566 */
0567 private function getNextEmbedBlock($indentation = null, $inSequence = false)
0568 {
0569 $oldLineIndentation = $this->getCurrentLineIndentation();
0570
0571 if (!$this->moveToNextLine()) {
0572 return '';
0573 }
0574
0575 if (null === $indentation) {
0576 $newIndent = null;
0577 $movements = 0;
0578
0579 do {
0580 $EOF = false;
0581
0582 // empty and comment-like lines do not influence the indentation depth
0583 if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
0584 $EOF = !$this->moveToNextLine();
0585
0586 if (!$EOF) {
0587 ++$movements;
0588 }
0589 } else {
0590 $newIndent = $this->getCurrentLineIndentation();
0591 }
0592 } while (!$EOF && null === $newIndent);
0593
0594 for ($i = 0; $i < $movements; ++$i) {
0595 $this->moveToPreviousLine();
0596 }
0597
0598 $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem();
0599
0600 if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
0601 throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0602 }
0603 } else {
0604 $newIndent = $indentation;
0605 }
0606
0607 $data = [];
0608 if ($this->getCurrentLineIndentation() >= $newIndent) {
0609 $data[] = substr($this->currentLine, $newIndent);
0610 } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
0611 $data[] = $this->currentLine;
0612 } else {
0613 $this->moveToPreviousLine();
0614
0615 return '';
0616 }
0617
0618 if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) {
0619 // the previous line contained a dash but no item content, this line is a sequence item with the same indentation
0620 // and therefore no nested list or mapping
0621 $this->moveToPreviousLine();
0622
0623 return '';
0624 }
0625
0626 $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
0627 $isItComment = $this->isCurrentLineComment();
0628
0629 while ($this->moveToNextLine()) {
0630 if ($isItComment && !$isItUnindentedCollection) {
0631 $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
0632 $isItComment = $this->isCurrentLineComment();
0633 }
0634
0635 $indent = $this->getCurrentLineIndentation();
0636
0637 if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) {
0638 $this->moveToPreviousLine();
0639 break;
0640 }
0641
0642 if ($this->isCurrentLineBlank()) {
0643 $data[] = substr($this->currentLine, $newIndent);
0644 continue;
0645 }
0646
0647 if ($indent >= $newIndent) {
0648 $data[] = substr($this->currentLine, $newIndent);
0649 } elseif ($this->isCurrentLineComment()) {
0650 $data[] = $this->currentLine;
0651 } elseif (0 == $indent) {
0652 $this->moveToPreviousLine();
0653
0654 break;
0655 } else {
0656 throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
0657 }
0658 }
0659
0660 return implode("\n", $data);
0661 }
0662
0663 /**
0664 * Moves the parser to the next line.
0665 *
0666 * @return bool
0667 */
0668 private function moveToNextLine()
0669 {
0670 if ($this->currentLineNb >= \count($this->lines) - 1) {
0671 return false;
0672 }
0673
0674 $this->currentLine = $this->lines[++$this->currentLineNb];
0675
0676 return true;
0677 }
0678
0679 /**
0680 * Moves the parser to the previous line.
0681 *
0682 * @return bool
0683 */
0684 private function moveToPreviousLine()
0685 {
0686 if ($this->currentLineNb < 1) {
0687 return false;
0688 }
0689
0690 $this->currentLine = $this->lines[--$this->currentLineNb];
0691
0692 return true;
0693 }
0694
0695 /**
0696 * Parses a YAML value.
0697 *
0698 * @param string $value A YAML value
0699 * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
0700 * @param string $context The parser context (either sequence or mapping)
0701 *
0702 * @return mixed A PHP value
0703 *
0704 * @throws ParseException When reference does not exist
0705 */
0706 private function parseValue($value, $flags, $context)
0707 {
0708 if (0 === strpos($value, '*')) {
0709 if (false !== $pos = strpos($value, '#')) {
0710 $value = substr($value, 1, $pos - 2);
0711 } else {
0712 $value = substr($value, 1);
0713 }
0714
0715 if (!\array_key_exists($value, $this->refs)) {
0716 if (false !== $pos = array_search($value, $this->refsBeingParsed, true)) {
0717 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);
0718 }
0719
0720 throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename);
0721 }
0722
0723 return $this->refs[$value];
0724 }
0725
0726 if (self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
0727 $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
0728
0729 $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), abs((int) $modifiers));
0730
0731 if ('' !== $matches['tag']) {
0732 if ('!!binary' === $matches['tag']) {
0733 return Inline::evaluateBinaryScalar($data);
0734 } elseif ('tagged' === $matches['tag']) {
0735 return new TaggedValue(substr($matches['tag'], 1), $data);
0736 } elseif ('!' !== $matches['tag']) {
0737 @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);
0738 }
0739 }
0740
0741 return $data;
0742 }
0743
0744 try {
0745 $quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null;
0746
0747 // do not take following lines into account when the current line is a quoted single line value
0748 if (null !== $quotation && self::preg_match('/^'.$quotation.'.*'.$quotation.'(\s*#.*)?$/', $value)) {
0749 return Inline::parse($value, $flags, $this->refs);
0750 }
0751
0752 $lines = [];
0753
0754 while ($this->moveToNextLine()) {
0755 // unquoted strings end before the first unindented line
0756 if (null === $quotation && 0 === $this->getCurrentLineIndentation()) {
0757 $this->moveToPreviousLine();
0758
0759 break;
0760 }
0761
0762 $lines[] = trim($this->currentLine);
0763
0764 // quoted string values end with a line that is terminated with the quotation character
0765 $escapedLine = str_replace(['\\\\', '\\"'], '', $this->currentLine);
0766 if ('' !== $escapedLine && substr($escapedLine, -1) === $quotation) {
0767 break;
0768 }
0769 }
0770
0771 for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) {
0772 if ('' === $lines[$i]) {
0773 $value .= "\n";
0774 $previousLineBlank = true;
0775 } elseif ($previousLineBlank) {
0776 $value .= $lines[$i];
0777 $previousLineBlank = false;
0778 } else {
0779 $value .= ' '.$lines[$i];
0780 $previousLineBlank = false;
0781 }
0782 }
0783
0784 Inline::$parsedLineNumber = $this->getRealCurrentLineNb();
0785
0786 $parsedValue = Inline::parse($value, $flags, $this->refs);
0787
0788 if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
0789 throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename);
0790 }
0791
0792 return $parsedValue;
0793 } catch (ParseException $e) {
0794 $e->setParsedLine($this->getRealCurrentLineNb() + 1);
0795 $e->setSnippet($this->currentLine);
0796
0797 throw $e;
0798 }
0799 }
0800
0801 /**
0802 * Parses a block scalar.
0803 *
0804 * @param string $style The style indicator that was used to begin this block scalar (| or >)
0805 * @param string $chomping The chomping indicator that was used to begin this block scalar (+ or -)
0806 * @param int $indentation The indentation indicator that was used to begin this block scalar
0807 *
0808 * @return string The text value
0809 */
0810 private function parseBlockScalar($style, $chomping = '', $indentation = 0)
0811 {
0812 $notEOF = $this->moveToNextLine();
0813 if (!$notEOF) {
0814 return '';
0815 }
0816
0817 $isCurrentLineBlank = $this->isCurrentLineBlank();
0818 $blockLines = [];
0819
0820 // leading blank lines are consumed before determining indentation
0821 while ($notEOF && $isCurrentLineBlank) {
0822 // newline only if not EOF
0823 if ($notEOF = $this->moveToNextLine()) {
0824 $blockLines[] = '';
0825 $isCurrentLineBlank = $this->isCurrentLineBlank();
0826 }
0827 }
0828
0829 // determine indentation if not specified
0830 if (0 === $indentation) {
0831 if (self::preg_match('/^ +/', $this->currentLine, $matches)) {
0832 $indentation = \strlen($matches[0]);
0833 }
0834 }
0835
0836 if ($indentation > 0) {
0837 $pattern = sprintf('/^ {%d}(.*)$/', $indentation);
0838
0839 while (
0840 $notEOF && (
0841 $isCurrentLineBlank ||
0842 self::preg_match($pattern, $this->currentLine, $matches)
0843 )
0844 ) {
0845 if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) {
0846 $blockLines[] = substr($this->currentLine, $indentation);
0847 } elseif ($isCurrentLineBlank) {
0848 $blockLines[] = '';
0849 } else {
0850 $blockLines[] = $matches[1];
0851 }
0852
0853 // newline only if not EOF
0854 if ($notEOF = $this->moveToNextLine()) {
0855 $isCurrentLineBlank = $this->isCurrentLineBlank();
0856 }
0857 }
0858 } elseif ($notEOF) {
0859 $blockLines[] = '';
0860 }
0861
0862 if ($notEOF) {
0863 $blockLines[] = '';
0864 $this->moveToPreviousLine();
0865 } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) {
0866 $blockLines[] = '';
0867 }
0868
0869 // folded style
0870 if ('>' === $style) {
0871 $text = '';
0872 $previousLineIndented = false;
0873 $previousLineBlank = false;
0874
0875 for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) {
0876 if ('' === $blockLines[$i]) {
0877 $text .= "\n";
0878 $previousLineIndented = false;
0879 $previousLineBlank = true;
0880 } elseif (' ' === $blockLines[$i][0]) {
0881 $text .= "\n".$blockLines[$i];
0882 $previousLineIndented = true;
0883 $previousLineBlank = false;
0884 } elseif ($previousLineIndented) {
0885 $text .= "\n".$blockLines[$i];
0886 $previousLineIndented = false;
0887 $previousLineBlank = false;
0888 } elseif ($previousLineBlank || 0 === $i) {
0889 $text .= $blockLines[$i];
0890 $previousLineIndented = false;
0891 $previousLineBlank = false;
0892 } else {
0893 $text .= ' '.$blockLines[$i];
0894 $previousLineIndented = false;
0895 $previousLineBlank = false;
0896 }
0897 }
0898 } else {
0899 $text = implode("\n", $blockLines);
0900 }
0901
0902 // deal with trailing newlines
0903 if ('' === $chomping) {
0904 $text = preg_replace('/\n+$/', "\n", $text);
0905 } elseif ('-' === $chomping) {
0906 $text = preg_replace('/\n+$/', '', $text);
0907 }
0908
0909 return $text;
0910 }
0911
0912 /**
0913 * Returns true if the next line is indented.
0914 *
0915 * @return bool Returns true if the next line is indented, false otherwise
0916 */
0917 private function isNextLineIndented()
0918 {
0919 $currentIndentation = $this->getCurrentLineIndentation();
0920 $movements = 0;
0921
0922 do {
0923 $EOF = !$this->moveToNextLine();
0924
0925 if (!$EOF) {
0926 ++$movements;
0927 }
0928 } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()));
0929
0930 if ($EOF) {
0931 return false;
0932 }
0933
0934 $ret = $this->getCurrentLineIndentation() > $currentIndentation;
0935
0936 for ($i = 0; $i < $movements; ++$i) {
0937 $this->moveToPreviousLine();
0938 }
0939
0940 return $ret;
0941 }
0942
0943 /**
0944 * Returns true if the current line is blank or if it is a comment line.
0945 *
0946 * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise
0947 */
0948 private function isCurrentLineEmpty()
0949 {
0950 return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
0951 }
0952
0953 /**
0954 * Returns true if the current line is blank.
0955 *
0956 * @return bool Returns true if the current line is blank, false otherwise
0957 */
0958 private function isCurrentLineBlank()
0959 {
0960 return '' == trim($this->currentLine, ' ');
0961 }
0962
0963 /**
0964 * Returns true if the current line is a comment line.
0965 *
0966 * @return bool Returns true if the current line is a comment line, false otherwise
0967 */
0968 private function isCurrentLineComment()
0969 {
0970 //checking explicitly the first char of the trim is faster than loops or strpos
0971 $ltrimmedLine = ltrim($this->currentLine, ' ');
0972
0973 return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0];
0974 }
0975
0976 private function isCurrentLineLastLineInDocument()
0977 {
0978 return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
0979 }
0980
0981 /**
0982 * Cleanups a YAML string to be parsed.
0983 *
0984 * @param string $value The input YAML string
0985 *
0986 * @return string A cleaned up YAML string
0987 */
0988 private function cleanup($value)
0989 {
0990 $value = str_replace(["\r\n", "\r"], "\n", $value);
0991
0992 // strip YAML header
0993 $count = 0;
0994 $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count);
0995 $this->offset += $count;
0996
0997 // remove leading comments
0998 $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count);
0999 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