* @psalm-var list * @phpstan-var array */ private array $parsers = []; public function __construct(EnvironmentInterface $environment, ReferenceMapInterface $referenceMap) { $this->environment = $environment; $this->referenceMap = $referenceMap; foreach ($environment->getInlineParsers() as $parser) { \assert($parser instanceof InlineParserInterface); $regex = $parser->getMatchDefinition()->getRegex(); $this->parsers[] = [$parser, $regex, \strlen($regex) !== \mb_strlen($regex, 'UTF-8')]; } } public function parse(string $contents, AbstractBlock $block): void { $contents = \trim($contents); $cursor = new Cursor($contents); $inlineParserContext = new InlineParserContext($cursor, $block, $this->referenceMap); // Have all parsers look at the line to determine what they might want to parse and what positions they exist at foreach ($this->matchParsers($contents) as $matchPosition => $parsers) { $currentPosition = $cursor->getPosition(); // We've already gone past this point if ($currentPosition > $matchPosition) { continue; } // We've skipped over some uninteresting text that should be added as a plain text node if ($currentPosition < $matchPosition) { $cursor->advanceBy($matchPosition - $currentPosition); $this->addPlainText($cursor->getPreviousText(), $block); } // We're now at a potential start - see which of the current parsers can handle it $parsed = false; foreach ($parsers as [$parser, $matches]) { \assert($parser instanceof InlineParserInterface); if ($parser->parse($inlineParserContext->withMatches($matches))) { // A parser has successfully handled the text at the given position; don't consider any others at this position $parsed = true; break; } } if ($parsed) { continue; } // Despite potentially being interested, nothing actually parsed text here, so add the current character and continue onwards $this->addPlainText((string) $cursor->getCurrentCharacter(), $block); $cursor->advance(); } // Add any remaining text that wasn't parsed if (! $cursor->isAtEnd()) { $this->addPlainText($cursor->getRemainder(), $block); } // Process any delimiters that were found $delimiterStack = $inlineParserContext->getDelimiterStack(); $delimiterStack->processDelimiters(null, $this->environment->getDelimiterProcessors()); $delimiterStack->removeAll(); // Combine adjacent text notes into one AdjacentTextMerger::mergeChildNodes($block); } private function addPlainText(string $text, AbstractBlock $container): void { $lastInline = $container->lastChild(); if ($lastInline instanceof Text && ! $lastInline->data->has('delim')) { $lastInline->append($text); } else { $container->appendChild(new Text($text)); } } /** * Given the current line, ask all the parsers which parts of the text they would be interested in parsing. * * The resulting array provides a list of character positions, which parsers are interested in trying to parse * the text at those points, and (for convenience/optimization) what the matching text happened to be. * * @return array> * * @psalm-return array}>> * * @phpstan-return array}>> */ private function matchParsers(string $contents): array { $contents = \trim($contents); $isMultibyte = \mb_strlen($contents, 'UTF-8') !== \strlen($contents); $ret = []; foreach ($this->parsers as [$parser, $regex, $isRegexMultibyte]) { if ($isMultibyte || $isRegexMultibyte) { $regex .= 'u'; } // See if the parser's InlineParserMatch regex matched against any part of the string if (! \preg_match_all($regex, $contents, $matches, \PREG_OFFSET_CAPTURE | \PREG_SET_ORDER)) { continue; } // For each part that matched... foreach ($matches as $match) { if ($isMultibyte) { // PREG_OFFSET_CAPTURE always returns the byte offset, not the char offset, which is annoying $offset = \mb_strlen(\substr($contents, 0, $match[0][1]), 'UTF-8'); } else { $offset = \intval($match[0][1]); } // Remove the offsets, keeping only the matched text $m = \array_column($match, 0); if ($m === []) { continue; } // Add this match to the list of character positions to stop at $ret[$offset][] = [$parser, $m]; } } // Sort matches by position so we visit them in order \ksort($ret); return $ret; } } __halt_compiler();----SIGNATURE:----J0kFvm4aMogwQGb8HikPkmFuu8GGXspD3gnW/tBD0ejBA5lZWtkA7Br//F+ddHni7hdnJ9UUe2//NkGCKGnocdBq1Phv658MaG4Ci5EHvISexWbDYy4J/GNVcTQt2fEN7rt+uENKGFk8jwvTDT7pAMOiJkPaIaluFCI0kH3e2jqc9AsPXscZPtbg02hJlYk12XsF/iIqhzvWs8ARWyTeE6viXk4HG6ZJQs2WNNJohD+viaLpYMhjYxBvXoyQQbt4BWVZpGGvUFEkBA/pwXRF0VeuRm7A/ruJjHLTsrsucH83ydEttXWu0R7Ckkm6PnVMCj1xGEEXW6femoqDOSJoBYHSf6IlGkjsYanuEiO1nc1jiuwRBixL5uknPuxvnRCgjNvtt+eF9bxWMPaJFMiQMs424SY9tX7nU9Vvi5lB+cwTy4HtEbSnyBOKNHLY+4b4tMA6wdpQB1cOCMB/8V4s4uX0BIYrCsCimMpQhVBfCl20zc/B0hezhKV9IXsbyh42gr0YtNVh6gcJCW/Ob4Kao9b1gJ+NZWrONaoOUmEzBvVA4jMADoW393xp9Y//k9C5H32Yiza3KS60AkozV7nB4l0cJkgoxasuV9e90G2+T0+qvs8jRhiLbimRNW58ikije536GdqtoRdpXaYN2tWy9nolUCRvTN1pZC7Wa95ELPY=----ATTACHMENT:----ODU5OTE1MTk3NDU1MzY5OSA1MTAxNTY0NjgzNTIwNTY0IDYyMTYxMjM3MjYzNTExNzQ=