Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. Parser.php

function Parser::doParse

1 call to Parser::doParse()
Parser::parse in vendor/symfony/yaml/Parser.php
Parses a YAML string to a PHP value.

File

vendor/symfony/yaml/Parser.php, line 102

Class

Parser
Parser parses YAML strings to convert them to PHP arrays.

Namespace

Symfony\Component\Yaml

Code

private function doParse(string $value, int $flags) : mixed {
    $this->currentLineNb = -1;
    $this->currentLine = '';
    $value = $this->cleanup($value);
    $this->lines = explode("\n", $value);
    $this->numberOfParsedLines = \count($this->lines);
    $this->locallySkippedLineNumbers = [];
    $this->totalNumberOfLines ??= $this->numberOfParsedLines;
    if (!$this->moveToNextLine()) {
        return null;
    }
    $data = [];
    $context = null;
    $allowOverwrite = false;
    while ($this->isCurrentLineEmpty()) {
        if (!$this->moveToNextLine()) {
            return null;
        }
    }
    // Resolves the tag and returns if end of the document
    if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, false)) && !$this->moveToNextLine()) {
        return new TaggedValue($tag, '');
    }
    do {
        if ($this->isCurrentLineEmpty()) {
            continue;
        }
        // tab?
        if ("\t" === $this->currentLine[0]) {
            throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
        }
        Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename);
        $isRef = $mergeNode = false;
        if ('-' === $this->currentLine[0] && self::preg_match('#^\\-((?P<leadspaces>\\s+)(?P<value>.+))?$#u', rtrim($this->currentLine), $values)) {
            if ($context && 'mapping' == $context) {
                throw new ParseException('You cannot define a sequence item when in a mapping.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
            }
            $context = 'sequence';
            if (isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) {
                $isRef = $matches['ref'];
                $this->refsBeingParsed[] = $isRef;
                $values['value'] = $matches['value'];
            }
            if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) {
                throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
            }
            // array
            if (isset($values['value']) && str_starts_with(ltrim($values['value'], ' '), '-')) {
                // Inline first child
                $currentLineNumber = $this->getRealCurrentLineNb();
                $sequenceIndentation = \strlen($values['leadspaces']) + 1;
                $sequenceYaml = substr($this->currentLine, $sequenceIndentation);
                $sequenceYaml .= "\n" . $this->getNextEmbedBlock($sequenceIndentation, true);
                $data[] = $this->parseBlock($currentLineNumber, rtrim($sequenceYaml), $flags);
            }
            elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || str_starts_with(ltrim($values['value'], ' '), '#')) {
                $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true) ?? '', $flags);
            }
            elseif (null !== ($subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags))) {
                $data[] = new TaggedValue($subTag, $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags));
            }
            else {
                if (isset($values['leadspaces']) && ('!' === $values['value'][0] || self::preg_match('#^(?P<key>' . Inline::REGEX_QUOTED_STRING . '|[^ \'"\\{\\[].*?) *\\:(\\s+(?P<value>.+?))?\\s*$#u', $this->trimTag($values['value']), $matches))) {
                    $block = $values['value'];
                    if ($this->isNextLineIndented() || isset($matches['value']) && '>-' === $matches['value']) {
                        $block .= "\n" . $this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1);
                    }
                    $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags);
                }
                else {
                    $data[] = $this->parseValue($values['value'], $flags, $context);
                }
            }
            if ($isRef) {
                $this->refs[$isRef] = end($data);
                array_pop($this->refsBeingParsed);
            }
        }
        elseif (self::preg_match('#^(?P<key>(?:![^\\s]++\\s++)?(?:' . Inline::REGEX_QUOTED_STRING . '|[^ \'"\\[\\{!].*?)) *\\:(( |\\t)++(?P<value>.+))?$#u', rtrim($this->currentLine), $values) && (!str_contains($values['key'], ' #') || \in_array($values['key'][0], [
            '"',
            "'",
        ]))) {
            if ($context && 'sequence' == $context) {
                throw new ParseException('You cannot define a mapping item when in a sequence.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
            }
            $context = 'mapping';
            try {
                $key = Inline::parseScalar($values['key']);
            } catch (ParseException $e) {
                $e->setParsedLine($this->getRealCurrentLineNb() + 1);
                $e->setSnippet($this->currentLine);
                throw $e;
            }
            if (!\is_string($key) && !\is_int($key)) {
                throw new ParseException((is_numeric($key) ? 'Numeric' : 'Non-string') . ' keys are not supported. Quote your evaluable mapping keys instead.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
            }
            // Convert float keys to strings, to avoid being converted to integers by PHP
            if (\is_float($key)) {
                $key = (string) $key;
            }
            if ('<<' === $key && (!isset($values['value']) || '&' !== $values['value'][0] || !self::preg_match('#^&(?P<ref>[^ ]+)#u', $values['value'], $refMatches))) {
                $mergeNode = true;
                $allowOverwrite = true;
                if (isset($values['value'][0]) && '*' === $values['value'][0]) {
                    $refName = substr(rtrim($values['value']), 1);
                    if (!\array_key_exists($refName, $this->refs)) {
                        if (false !== ($pos = array_search($refName, $this->refsBeingParsed, true))) {
                            throw new ParseException(\sprintf('Circular reference [%s] detected for reference "%s".', implode(', ', array_merge(\array_slice($this->refsBeingParsed, $pos), [
                                $refName,
                            ])), $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename);
                        }
                        throw new ParseException(\sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
                    }
                    $refValue = $this->refs[$refName];
                    if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) {
                        $refValue = (array) $refValue;
                    }
                    if (!\is_array($refValue)) {
                        throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
                    }
                    $data += $refValue;
                    // array union
                }
                else {
                    if (isset($values['value']) && '' !== $values['value']) {
                        $value = $values['value'];
                    }
                    else {
                        $value = $this->getNextEmbedBlock();
                    }
                    $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags);
                    if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) {
                        $parsed = (array) $parsed;
                    }
                    if (!\is_array($parsed)) {
                        throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
                    }
                    if (isset($parsed[0])) {
                        // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
                        // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
                        // in the sequence override keys specified in later mapping nodes.
                        foreach ($parsed as $parsedItem) {
                            if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) {
                                $parsedItem = (array) $parsedItem;
                            }
                            if (!\is_array($parsedItem)) {
                                throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename);
                            }
                            $data += $parsedItem;
                            // array union
                        }
                    }
                    else {
                        // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
                        // current mapping, unless the key already exists in it.
                        $data += $parsed;
                        // array union
                    }
                }
            }
            elseif ('<<' !== $key && isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) {
                $isRef = $matches['ref'];
                $this->refsBeingParsed[] = $isRef;
                $values['value'] = $matches['value'];
            }
            $subTag = null;
            if ($mergeNode) {
                // Merge keys
            }
            elseif (!isset($values['value']) || '' === $values['value'] || str_starts_with($values['value'], '#') || null !== ($subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) {
                // hash
                // if next line is less indented or equal, then it means that the current value is null
                if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
                    // Spec: Keys MUST be unique; first one wins.
                    // But overwriting is allowed when a merge node is used in current block.
                    if ($allowOverwrite || !isset($data[$key])) {
                        if (!$allowOverwrite && \array_key_exists($key, $data)) {
                            trigger_deprecation('symfony/yaml', '7.2', 'Duplicate key "%s" detected on line %d whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated and will throw a ParseException in 8.0.', $key, $this->getRealCurrentLineNb() + 1);
                        }
                        if (null !== $subTag) {
                            $data[$key] = new TaggedValue($subTag, '');
                        }
                        else {
                            $data[$key] = null;
                        }
                    }
                    else {
                        throw new ParseException(\sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine);
                    }
                }
                else {
                    // remember the parsed line number here in case we need it to provide some contexts in error messages below
                    $realCurrentLineNbKey = $this->getRealCurrentLineNb();
                    $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags);
                    if ('<<' === $key) {
                        $this->refs[$refMatches['ref']] = $value;
                        if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) {
                            $value = (array) $value;
                        }
                        $data += $value;
                    }
                    elseif ($allowOverwrite || !isset($data[$key])) {
                        if (!$allowOverwrite && \array_key_exists($key, $data)) {
                            trigger_deprecation('symfony/yaml', '7.2', 'Duplicate key "%s" detected on line %d whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated and will throw a ParseException in 8.0.', $key, $this->getRealCurrentLineNb() + 1);
                        }
                        // Spec: Keys MUST be unique; first one wins.
                        // But overwriting is allowed when a merge node is used in current block.
                        if (null !== $subTag) {
                            $data[$key] = new TaggedValue($subTag, $value);
                        }
                        else {
                            $data[$key] = $value;
                        }
                    }
                    else {
                        throw new ParseException(\sprintf('Duplicate key "%s" detected.', $key), $realCurrentLineNbKey + 1, $this->currentLine);
                    }
                }
            }
            else {
                $value = $this->parseValue(rtrim($values['value']), $flags, $context);
                // Spec: Keys MUST be unique; first one wins.
                // But overwriting is allowed when a merge node is used in current block.
                if ($allowOverwrite || !isset($data[$key])) {
                    if (!$allowOverwrite && \array_key_exists($key, $data)) {
                        trigger_deprecation('symfony/yaml', '7.2', 'Duplicate key "%s" detected on line %d whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated and will throw a ParseException in 8.0.', $key, $this->getRealCurrentLineNb() + 1);
                    }
                    $data[$key] = $value;
                }
                else {
                    throw new ParseException(\sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine);
                }
            }
            if ($isRef) {
                $this->refs[$isRef] = $data[$key];
                array_pop($this->refsBeingParsed);
            }
        }
        elseif ('"' === $this->currentLine[0] || "'" === $this->currentLine[0]) {
            if (null !== $context) {
                throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
            }
            try {
                return Inline::parse($this->lexInlineQuotedString(), $flags, $this->refs);
            } catch (ParseException $e) {
                $e->setParsedLine($this->getRealCurrentLineNb() + 1);
                $e->setSnippet($this->currentLine);
                throw $e;
            }
        }
        elseif ('{' === $this->currentLine[0]) {
            if (null !== $context) {
                throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
            }
            try {
                $parsedMapping = Inline::parse($this->lexInlineMapping(), $flags, $this->refs);
                while ($this->moveToNextLine()) {
                    if (!$this->isCurrentLineEmpty()) {
                        throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
                    }
                }
                return $parsedMapping;
            } catch (ParseException $e) {
                $e->setParsedLine($this->getRealCurrentLineNb() + 1);
                $e->setSnippet($this->currentLine);
                throw $e;
            }
        }
        elseif ('[' === $this->currentLine[0]) {
            if (null !== $context) {
                throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
            }
            try {
                $parsedSequence = Inline::parse($this->lexInlineSequence(), $flags, $this->refs);
                while ($this->moveToNextLine()) {
                    if (!$this->isCurrentLineEmpty()) {
                        throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
                    }
                }
                return $parsedSequence;
            } catch (ParseException $e) {
                $e->setParsedLine($this->getRealCurrentLineNb() + 1);
                $e->setSnippet($this->currentLine);
                throw $e;
            }
        }
        else {
            // multiple documents are not supported
            if ('---' === $this->currentLine) {
                throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
            }
            if (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1]) {
                throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
            }
            // 1-liner optionally followed by newline(s)
            if (\is_string($value) && $this->lines[0] === trim($value)) {
                try {
                    $value = Inline::parse($this->lines[0], $flags, $this->refs);
                } catch (ParseException $e) {
                    $e->setParsedLine($this->getRealCurrentLineNb() + 1);
                    $e->setSnippet($this->currentLine);
                    throw $e;
                }
                return $value;
            }
            // try to parse the value as a multi-line string as a last resort
            if (0 === $this->currentLineNb) {
                $previousLineWasNewline = false;
                $previousLineWasTerminatedWithBackslash = false;
                $value = '';
                foreach ($this->lines as $line) {
                    $trimmedLine = trim($line);
                    if ('#' === ($trimmedLine[0] ?? '')) {
                        continue;
                    }
                    // If the indentation is not consistent at offset 0, it is to be considered as a ParseError
                    if (0 === $this->offset && isset($line[0]) && ' ' === $line[0]) {
                        throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
                    }
                    if (str_contains($line, ': ')) {
                        throw new ParseException('Mapping values are not allowed in multi-line blocks.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
                    }
                    if ('' === $trimmedLine) {
                        $value .= "\n";
                    }
                    elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
                        $value .= ' ';
                    }
                    if ('' !== $trimmedLine && str_ends_with($line, '\\')) {
                        $value .= ltrim(substr($line, 0, -1));
                    }
                    elseif ('' !== $trimmedLine) {
                        $value .= $trimmedLine;
                    }
                    if ('' === $trimmedLine) {
                        $previousLineWasNewline = true;
                        $previousLineWasTerminatedWithBackslash = false;
                    }
                    elseif (str_ends_with($line, '\\')) {
                        $previousLineWasNewline = false;
                        $previousLineWasTerminatedWithBackslash = true;
                    }
                    else {
                        $previousLineWasNewline = false;
                        $previousLineWasTerminatedWithBackslash = false;
                    }
                }
                try {
                    return Inline::parse(trim($value));
                } catch (ParseException) {
                    // fall-through to the ParseException thrown below
                }
            }
            throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
        }
    } while ($this->moveToNextLine());
    if (null !== $tag) {
        $data = new TaggedValue($tag, $data);
    }
    if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && 'mapping' === $context && !\is_object($data)) {
        $object = new \stdClass();
        foreach ($data as $key => $value) {
            $object->{$key} = $value;
        }
        $data = $object;
    }
    return $data ?: null;
}

API Navigation

  • Drupal Core 11.1.x
  • Topics
  • Classes
  • Functions
  • Constants
  • Globals
  • Files
  • Namespaces
  • Deprecated
  • Services
RSS feed
Powered by Drupal