One Hat Cyber Team
Your IP:
216.73.216.30
Server IP:
198.54.114.155
Server:
Linux server71.web-hosting.com 4.18.0-513.18.1.lve.el8.x86_64 #1 SMP Thu Feb 22 12:55:50 UTC 2024 x86_64
Server Software:
LiteSpeed
PHP Version:
5.6.40
Create File
|
Create Folder
Execute
Dir :
~
/
proc
/
self
/
cwd
/
View File Name :
Parser.tar
InputSource.php 0000644 00000001244 15107511434 0007537 0 ustar 00 <?php /* * This file is part of jwt-auth. * * (c) Sean Tymon <tymon148@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Parser; use Illuminate\Http\Request; use Tymon\JWTAuth\Contracts\Http\Parser as ParserContract; class InputSource implements ParserContract { use KeyTrait; /** * Try to parse the token from the request input source. * * @param \Illuminate\Http\Request $request * @return null|string */ public function parse(Request $request) { return $request->input($this->key); } } AuthHeaders.php 0000644 00000004111 15107511435 0007451 0 ustar 00 <?php /* * This file is part of jwt-auth. * * (c) Sean Tymon <tymon148@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Parser; use Illuminate\Http\Request; use Tymon\JWTAuth\Contracts\Http\Parser as ParserContract; class AuthHeaders implements ParserContract { /** * The header name. * * @var string */ protected $header = 'authorization'; /** * The header prefix. * * @var string */ protected $prefix = 'bearer'; /** * Attempt to parse the token from some other possible headers. * * @param \Illuminate\Http\Request $request * @return null|string */ protected function fromAltHeaders(Request $request) { return $request->server->get('HTTP_AUTHORIZATION') ?: $request->server->get('REDIRECT_HTTP_AUTHORIZATION'); } /** * Try to parse the token from the request header. * * @param \Illuminate\Http\Request $request * @return null|string */ public function parse(Request $request) { $header = $request->headers->get($this->header) ?: $this->fromAltHeaders($request); if ($header !== null) { $position = strripos($header, $this->prefix); if ($position !== false) { $header = substr($header, $position + strlen($this->prefix)); return trim( strpos($header, ',') !== false ? strstr($header, ',', true) : $header ); } } return null; } /** * Set the header name. * * @param string $headerName * @return $this */ public function setHeaderName($headerName) { $this->header = $headerName; return $this; } /** * Set the header prefix. * * @param string $headerPrefix * @return $this */ public function setHeaderPrefix($headerPrefix) { $this->prefix = $headerPrefix; return $this; } } Cookies.php 0000644 00000002037 15107511435 0006655 0 ustar 00 <?php /* * This file is part of jwt-auth. * * (c) Sean Tymon <tymon148@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Parser; use Illuminate\Http\Request; use Illuminate\Support\Facades\Crypt; use Tymon\JWTAuth\Contracts\Http\Parser as ParserContract; class Cookies implements ParserContract { use KeyTrait; /** * Decrypt or not the cookie while parsing. * * @var bool */ private $decrypt; public function __construct($decrypt = true) { $this->decrypt = $decrypt; } /** * Try to parse the token from the request cookies. * * @param \Illuminate\Http\Request $request * @return null|string */ public function parse(Request $request) { if ($this->decrypt && $request->hasCookie($this->key)) { return Crypt::decrypt($request->cookie($this->key)); } return $request->cookie($this->key); } } KeyTrait.php 0000644 00000001260 15107511435 0007012 0 ustar 00 <?php /* * This file is part of jwt-auth. * * (c) Sean Tymon <tymon148@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Parser; trait KeyTrait { /** * The key. * * @var string */ protected $key = 'token'; /** * Set the key. * * @param string $key * @return $this */ public function setKey($key) { $this->key = $key; return $this; } /** * Get the key. * * @return string */ public function getKey() { return $this->key; } } RouteParams.php 0000644 00000001667 15107511435 0007533 0 ustar 00 <?php /* * This file is part of jwt-auth. * * (c) Sean Tymon <tymon148@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Parser; use Illuminate\Http\Request; use Tymon\JWTAuth\Contracts\Http\Parser as ParserContract; class RouteParams implements ParserContract { use KeyTrait; /** * Try to get the token from the route parameters. * * @param \Illuminate\Http\Request $request * @return null|string */ public function parse(Request $request) { $route = $request->route(); // Route may not be an instance of Illuminate\Routing\Route // (it's an array in Lumen <5.2) or not exist at all // (if the request was never dispatched) if (is_callable([$route, 'parameter'])) { return $route->parameter($this->key); } } } Parser.php 0000644 00000027767 15107511435 0006536 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser; use Symfony\Component\CssSelector\Exception\SyntaxErrorException; use Symfony\Component\CssSelector\Node; use Symfony\Component\CssSelector\Parser\Tokenizer\Tokenizer; /** * CSS selector parser. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/scrapy/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class Parser implements ParserInterface { private Tokenizer $tokenizer; public function __construct(Tokenizer $tokenizer = null) { $this->tokenizer = $tokenizer ?? new Tokenizer(); } public function parse(string $source): array { $reader = new Reader($source); $stream = $this->tokenizer->tokenize($reader); return $this->parseSelectorList($stream); } /** * Parses the arguments for ":nth-child()" and friends. * * @param Token[] $tokens * * @throws SyntaxErrorException */ public static function parseSeries(array $tokens): array { foreach ($tokens as $token) { if ($token->isString()) { throw SyntaxErrorException::stringAsFunctionArgument(); } } $joined = trim(implode('', array_map(fn (Token $token) => $token->getValue(), $tokens))); $int = function ($string) { if (!is_numeric($string)) { throw SyntaxErrorException::stringAsFunctionArgument(); } return (int) $string; }; switch (true) { case 'odd' === $joined: return [2, 1]; case 'even' === $joined: return [2, 0]; case 'n' === $joined: return [1, 0]; case !str_contains($joined, 'n'): return [0, $int($joined)]; } $split = explode('n', $joined); $first = $split[0] ?? null; return [ $first ? ('-' === $first || '+' === $first ? $int($first.'1') : $int($first)) : 1, isset($split[1]) && $split[1] ? $int($split[1]) : 0, ]; } private function parseSelectorList(TokenStream $stream): array { $stream->skipWhitespace(); $selectors = []; while (true) { $selectors[] = $this->parserSelectorNode($stream); if ($stream->getPeek()->isDelimiter([','])) { $stream->getNext(); $stream->skipWhitespace(); } else { break; } } return $selectors; } private function parserSelectorNode(TokenStream $stream): Node\SelectorNode { [$result, $pseudoElement] = $this->parseSimpleSelector($stream); while (true) { $stream->skipWhitespace(); $peek = $stream->getPeek(); if ($peek->isFileEnd() || $peek->isDelimiter([','])) { break; } if (null !== $pseudoElement) { throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector'); } if ($peek->isDelimiter(['+', '>', '~'])) { $combinator = $stream->getNext()->getValue(); $stream->skipWhitespace(); } else { $combinator = ' '; } [$nextSelector, $pseudoElement] = $this->parseSimpleSelector($stream); $result = new Node\CombinedSelectorNode($result, $combinator, $nextSelector); } return new Node\SelectorNode($result, $pseudoElement); } /** * Parses next simple node (hash, class, pseudo, negation). * * @throws SyntaxErrorException */ private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = false): array { $stream->skipWhitespace(); $selectorStart = \count($stream->getUsed()); $result = $this->parseElementNode($stream); $pseudoElement = null; while (true) { $peek = $stream->getPeek(); if ($peek->isWhitespace() || $peek->isFileEnd() || $peek->isDelimiter([',', '+', '>', '~']) || ($insideNegation && $peek->isDelimiter([')'])) ) { break; } if (null !== $pseudoElement) { throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector'); } if ($peek->isHash()) { $result = new Node\HashNode($result, $stream->getNext()->getValue()); } elseif ($peek->isDelimiter(['.'])) { $stream->getNext(); $result = new Node\ClassNode($result, $stream->getNextIdentifier()); } elseif ($peek->isDelimiter(['['])) { $stream->getNext(); $result = $this->parseAttributeNode($result, $stream); } elseif ($peek->isDelimiter([':'])) { $stream->getNext(); if ($stream->getPeek()->isDelimiter([':'])) { $stream->getNext(); $pseudoElement = $stream->getNextIdentifier(); continue; } $identifier = $stream->getNextIdentifier(); if (\in_array(strtolower($identifier), ['first-line', 'first-letter', 'before', 'after'])) { // Special case: CSS 2.1 pseudo-elements can have a single ':'. // Any new pseudo-element must have two. $pseudoElement = $identifier; continue; } if (!$stream->getPeek()->isDelimiter(['('])) { $result = new Node\PseudoNode($result, $identifier); if ('Pseudo[Element[*]:scope]' === $result->__toString()) { $used = \count($stream->getUsed()); if (!(2 === $used || 3 === $used && $stream->getUsed()[0]->isWhiteSpace() || $used >= 3 && $stream->getUsed()[$used - 3]->isDelimiter([',']) || $used >= 4 && $stream->getUsed()[$used - 3]->isWhiteSpace() && $stream->getUsed()[$used - 4]->isDelimiter([',']) )) { throw SyntaxErrorException::notAtTheStartOfASelector('scope'); } } continue; } $stream->getNext(); $stream->skipWhitespace(); if ('not' === strtolower($identifier)) { if ($insideNegation) { throw SyntaxErrorException::nestedNot(); } [$argument, $argumentPseudoElement] = $this->parseSimpleSelector($stream, true); $next = $stream->getNext(); if (null !== $argumentPseudoElement) { throw SyntaxErrorException::pseudoElementFound($argumentPseudoElement, 'inside ::not()'); } if (!$next->isDelimiter([')'])) { throw SyntaxErrorException::unexpectedToken('")"', $next); } $result = new Node\NegationNode($result, $argument); } else { $arguments = []; $next = null; while (true) { $stream->skipWhitespace(); $next = $stream->getNext(); if ($next->isIdentifier() || $next->isString() || $next->isNumber() || $next->isDelimiter(['+', '-']) ) { $arguments[] = $next; } elseif ($next->isDelimiter([')'])) { break; } else { throw SyntaxErrorException::unexpectedToken('an argument', $next); } } if (!$arguments) { throw SyntaxErrorException::unexpectedToken('at least one argument', $next); } $result = new Node\FunctionNode($result, $identifier, $arguments); } } else { throw SyntaxErrorException::unexpectedToken('selector', $peek); } } if (\count($stream->getUsed()) === $selectorStart) { throw SyntaxErrorException::unexpectedToken('selector', $stream->getPeek()); } return [$result, $pseudoElement]; } private function parseElementNode(TokenStream $stream): Node\ElementNode { $peek = $stream->getPeek(); if ($peek->isIdentifier() || $peek->isDelimiter(['*'])) { if ($peek->isIdentifier()) { $namespace = $stream->getNext()->getValue(); } else { $stream->getNext(); $namespace = null; } if ($stream->getPeek()->isDelimiter(['|'])) { $stream->getNext(); $element = $stream->getNextIdentifierOrStar(); } else { $element = $namespace; $namespace = null; } } else { $element = $namespace = null; } return new Node\ElementNode($namespace, $element); } private function parseAttributeNode(Node\NodeInterface $selector, TokenStream $stream): Node\AttributeNode { $stream->skipWhitespace(); $attribute = $stream->getNextIdentifierOrStar(); if (null === $attribute && !$stream->getPeek()->isDelimiter(['|'])) { throw SyntaxErrorException::unexpectedToken('"|"', $stream->getPeek()); } if ($stream->getPeek()->isDelimiter(['|'])) { $stream->getNext(); if ($stream->getPeek()->isDelimiter(['='])) { $namespace = null; $stream->getNext(); $operator = '|='; } else { $namespace = $attribute; $attribute = $stream->getNextIdentifier(); $operator = null; } } else { $namespace = $operator = null; } if (null === $operator) { $stream->skipWhitespace(); $next = $stream->getNext(); if ($next->isDelimiter([']'])) { return new Node\AttributeNode($selector, $namespace, $attribute, 'exists', null); } elseif ($next->isDelimiter(['='])) { $operator = '='; } elseif ($next->isDelimiter(['^', '$', '*', '~', '|', '!']) && $stream->getPeek()->isDelimiter(['=']) ) { $operator = $next->getValue().'='; $stream->getNext(); } else { throw SyntaxErrorException::unexpectedToken('operator', $next); } } $stream->skipWhitespace(); $value = $stream->getNext(); if ($value->isNumber()) { // if the value is a number, it's casted into a string $value = new Token(Token::TYPE_STRING, (string) $value->getValue(), $value->getPosition()); } if (!($value->isIdentifier() || $value->isString())) { throw SyntaxErrorException::unexpectedToken('string or identifier', $value); } $stream->skipWhitespace(); $next = $stream->getNext(); if (!$next->isDelimiter([']'])) { throw SyntaxErrorException::unexpectedToken('"]"', $next); } return new Node\AttributeNode($selector, $namespace, $attribute, $operator, $value->getValue()); } } LumenRouteParams.php 0000644 00000001564 15107511435 0010530 0 ustar 00 <?php /* * This file is part of jwt-auth. * * (c) Sean Tymon <tymon148@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Parser; use Illuminate\Http\Request; use Illuminate\Support\Arr; class LumenRouteParams extends RouteParams { /** * Try to get the token from the route parameters. * * @param \Illuminate\Http\Request $request * @return null|string */ public function parse(Request $request) { // WARNING: Only use this parser if you know what you're doing! // It will only work with poorly-specified aspects of certain Lumen releases. // Route is the expected kind of array, and has a parameter with the key we want. return Arr::get($request->route(), '2.'.$this->key); } } QueryString.php 0000644 00000001244 15107511435 0007554 0 ustar 00 <?php /* * This file is part of jwt-auth. * * (c) Sean Tymon <tymon148@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Parser; use Illuminate\Http\Request; use Tymon\JWTAuth\Contracts\Http\Parser as ParserContract; class QueryString implements ParserContract { use KeyTrait; /** * Try to parse the token from the request query string. * * @param \Illuminate\Http\Request $request * @return null|string */ public function parse(Request $request) { return $request->query($this->key); } } MarkdownParserInterface.php 0000644 00000001043 15107554763 0012050 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Exception\CommonMarkException; use League\CommonMark\Node\Block\Document; interface MarkdownParserInterface { /** * @throws CommonMarkException */ public function parse(string $input): Document; } MarkdownParserStateInterface.php 0000644 00000002064 15107554763 0013055 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Parser\Block\BlockContinueParserInterface; interface MarkdownParserStateInterface { /** * Returns the deepest open block parser */ public function getActiveBlockParser(): BlockContinueParserInterface; /** * Open block parser that was last matched during the continue phase. This is different from the currently active * block parser, as an unmatched block is only closed when a new block is started. */ public function getLastMatchedBlockParser(): BlockContinueParserInterface; /** * Returns the current content of the paragraph if the matched block is a paragraph. The content can be multiple * lines separated by newlines. */ public function getParagraphContent(): ?string; } InlineParserEngineInterface.php 0000644 00000001203 15107554763 0012630 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Node\Block\AbstractBlock; /** * Parser for inline content (text, links, emphasized text, etc). */ interface InlineParserEngineInterface { /** * Parse the given contents as inlines and insert them into the given block */ public function parse(string $contents, AbstractBlock $block): void; } InlineParserContext.php 0000644 00000005466 15107554763 0011245 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Delimiter\DelimiterStack; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Reference\ReferenceMapInterface; final class InlineParserContext { /** @psalm-readonly */ private AbstractBlock $container; /** @psalm-readonly */ private ReferenceMapInterface $referenceMap; /** @psalm-readonly */ private Cursor $cursor; /** @psalm-readonly */ private DelimiterStack $delimiterStack; /** * @var string[] * @psalm-var non-empty-array<string> * * @psalm-readonly-allow-private-mutation */ private array $matches; public function __construct(Cursor $contents, AbstractBlock $container, ReferenceMapInterface $referenceMap) { $this->referenceMap = $referenceMap; $this->container = $container; $this->cursor = $contents; $this->delimiterStack = new DelimiterStack(); } public function getContainer(): AbstractBlock { return $this->container; } public function getReferenceMap(): ReferenceMapInterface { return $this->referenceMap; } public function getCursor(): Cursor { return $this->cursor; } public function getDelimiterStack(): DelimiterStack { return $this->delimiterStack; } /** * @return string The full text that matched the InlineParserMatch definition */ public function getFullMatch(): string { return $this->matches[0]; } /** * @return int The length of the full match (in characters, not bytes) */ public function getFullMatchLength(): int { return \mb_strlen($this->matches[0], 'UTF-8'); } /** * @return string[] Similar to preg_match(), index 0 will contain the full match, and any other array elements will be captured sub-matches * * @psalm-return non-empty-array<string> */ public function getMatches(): array { return $this->matches; } /** * @return string[] */ public function getSubMatches(): array { return \array_slice($this->matches, 1); } /** * @param string[] $matches * * @psalm-param non-empty-array<string> $matches */ public function withMatches(array $matches): InlineParserContext { $ctx = clone $this; $ctx->matches = $matches; return $ctx; } } MarkdownParser.php 0000644 00000027575 15107554763 0010251 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * Additional code based on commonmark-java (https://github.com/commonmark/commonmark-java) * - (c) Atlassian Pty Ltd * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Environment\EnvironmentInterface; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Event\DocumentPreParsedEvent; use League\CommonMark\Exception\CommonMarkException; use League\CommonMark\Input\MarkdownInput; use League\CommonMark\Node\Block\Document; use League\CommonMark\Node\Block\Paragraph; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Block\BlockContinueParserWithInlinesInterface; use League\CommonMark\Parser\Block\BlockStart; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Block\DocumentBlockParser; use League\CommonMark\Parser\Block\ParagraphParser; use League\CommonMark\Reference\ReferenceInterface; use League\CommonMark\Reference\ReferenceMap; final class MarkdownParser implements MarkdownParserInterface { /** @psalm-readonly */ private EnvironmentInterface $environment; /** @psalm-readonly-allow-private-mutation */ private int $maxNestingLevel; /** @psalm-readonly-allow-private-mutation */ private ReferenceMap $referenceMap; /** @psalm-readonly-allow-private-mutation */ private int $lineNumber = 0; /** @psalm-readonly-allow-private-mutation */ private Cursor $cursor; /** * @var array<int, BlockContinueParserInterface> * * @psalm-readonly-allow-private-mutation */ private array $activeBlockParsers = []; /** * @var array<int, BlockContinueParserWithInlinesInterface> * * @psalm-readonly-allow-private-mutation */ private array $closedBlockParsers = []; public function __construct(EnvironmentInterface $environment) { $this->environment = $environment; } private function initialize(): void { $this->referenceMap = new ReferenceMap(); $this->lineNumber = 0; $this->activeBlockParsers = []; $this->closedBlockParsers = []; $this->maxNestingLevel = $this->environment->getConfiguration()->get('max_nesting_level'); } /** * @throws CommonMarkException */ public function parse(string $input): Document { $this->initialize(); $documentParser = new DocumentBlockParser($this->referenceMap); $this->activateBlockParser($documentParser); $preParsedEvent = new DocumentPreParsedEvent($documentParser->getBlock(), new MarkdownInput($input)); $this->environment->dispatch($preParsedEvent); $markdownInput = $preParsedEvent->getMarkdown(); foreach ($markdownInput->getLines() as $lineNumber => $line) { $this->lineNumber = $lineNumber; $this->parseLine($line); } // finalizeAndProcess $this->closeBlockParsers(\count($this->activeBlockParsers), $this->lineNumber); $this->processInlines(); $this->environment->dispatch(new DocumentParsedEvent($documentParser->getBlock())); return $documentParser->getBlock(); } /** * Analyze a line of text and update the document appropriately. We parse markdown text by calling this on each * line of input, then finalizing the document. */ private function parseLine(string $line): void { $this->cursor = new Cursor($line); $matches = $this->parseBlockContinuation(); if ($matches === null) { return; } $unmatchedBlocks = \count($this->activeBlockParsers) - $matches; $blockParser = $this->activeBlockParsers[$matches - 1]; $startedNewBlock = false; // Unless last matched container is a code block, try new container starts, // adding children to the last matched container: $tryBlockStarts = $blockParser->getBlock() instanceof Paragraph || $blockParser->isContainer(); while ($tryBlockStarts) { // this is a little performance optimization if ($this->cursor->isBlank()) { $this->cursor->advanceToEnd(); break; } if ($blockParser->getBlock()->getDepth() >= $this->maxNestingLevel) { break; } $blockStart = $this->findBlockStart($blockParser); if ($blockStart === null || $blockStart->isAborting()) { $this->cursor->advanceToNextNonSpaceOrTab(); break; } if (($state = $blockStart->getCursorState()) !== null) { $this->cursor->restoreState($state); } $startedNewBlock = true; // We're starting a new block. If we have any previous blocks that need to be closed, we need to do it now. if ($unmatchedBlocks > 0) { $this->closeBlockParsers($unmatchedBlocks, $this->lineNumber - 1); $unmatchedBlocks = 0; } if ($blockStart->isReplaceActiveBlockParser()) { $this->prepareActiveBlockParserForReplacement(); } foreach ($blockStart->getBlockParsers() as $newBlockParser) { $blockParser = $this->addChild($newBlockParser); $tryBlockStarts = $newBlockParser->isContainer(); } } // What remains at the offset is a text line. Add the text to the appropriate block. // First check for a lazy paragraph continuation: if (! $startedNewBlock && ! $this->cursor->isBlank() && $this->getActiveBlockParser()->canHaveLazyContinuationLines()) { $this->getActiveBlockParser()->addLine($this->cursor->getRemainder()); } else { // finalize any blocks not matched if ($unmatchedBlocks > 0) { $this->closeBlockParsers($unmatchedBlocks, $this->lineNumber); } if (! $blockParser->isContainer()) { $this->getActiveBlockParser()->addLine($this->cursor->getRemainder()); } elseif (! $this->cursor->isBlank()) { $this->addChild(new ParagraphParser()); $this->getActiveBlockParser()->addLine($this->cursor->getRemainder()); } } } private function parseBlockContinuation(): ?int { // For each containing block, try to parse the associated line start. // The document will always match, so we can skip the first block parser and start at 1 matches $matches = 1; for ($i = 1; $i < \count($this->activeBlockParsers); $i++) { $blockParser = $this->activeBlockParsers[$i]; $blockContinue = $blockParser->tryContinue(clone $this->cursor, $this->getActiveBlockParser()); if ($blockContinue === null) { break; } if ($blockContinue->isFinalize()) { $this->closeBlockParsers(\count($this->activeBlockParsers) - $i, $this->lineNumber); return null; } if (($state = $blockContinue->getCursorState()) !== null) { $this->cursor->restoreState($state); } $matches++; } return $matches; } private function findBlockStart(BlockContinueParserInterface $lastMatchedBlockParser): ?BlockStart { $matchedBlockParser = new MarkdownParserState($this->getActiveBlockParser(), $lastMatchedBlockParser); foreach ($this->environment->getBlockStartParsers() as $blockStartParser) { \assert($blockStartParser instanceof BlockStartParserInterface); if (($result = $blockStartParser->tryStart(clone $this->cursor, $matchedBlockParser)) !== null) { return $result; } } return null; } private function closeBlockParsers(int $count, int $endLineNumber): void { for ($i = 0; $i < $count; $i++) { $blockParser = $this->deactivateBlockParser(); $this->finalize($blockParser, $endLineNumber); // phpcs:disable SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed if ($blockParser instanceof BlockContinueParserWithInlinesInterface) { // Remember for inline parsing $this->closedBlockParsers[] = $blockParser; } } } /** * Finalize a block. Close it and do any necessary postprocessing, e.g. creating string_content from strings, * setting the 'tight' or 'loose' status of a list, and parsing the beginnings of paragraphs for reference * definitions. */ private function finalize(BlockContinueParserInterface $blockParser, int $endLineNumber): void { if ($blockParser instanceof ParagraphParser) { $this->updateReferenceMap($blockParser->getReferences()); } $blockParser->getBlock()->setEndLine($endLineNumber); $blockParser->closeBlock(); } /** * Walk through a block & children recursively, parsing string content into inline content where appropriate. */ private function processInlines(): void { $p = new InlineParserEngine($this->environment, $this->referenceMap); foreach ($this->closedBlockParsers as $blockParser) { $blockParser->parseInlines($p); } } /** * Add block of type tag as a child of the tip. If the tip can't accept children, close and finalize it and try * its parent, and so on til we find a block that can accept children. */ private function addChild(BlockContinueParserInterface $blockParser): BlockContinueParserInterface { $blockParser->getBlock()->setStartLine($this->lineNumber); while (! $this->getActiveBlockParser()->canContain($blockParser->getBlock())) { $this->closeBlockParsers(1, $this->lineNumber - 1); } $this->getActiveBlockParser()->getBlock()->appendChild($blockParser->getBlock()); $this->activateBlockParser($blockParser); return $blockParser; } private function activateBlockParser(BlockContinueParserInterface $blockParser): void { $this->activeBlockParsers[] = $blockParser; } /** * @throws ParserLogicException */ private function deactivateBlockParser(): BlockContinueParserInterface { $popped = \array_pop($this->activeBlockParsers); if ($popped === null) { throw new ParserLogicException('The last block parser should not be deactivated'); } return $popped; } private function prepareActiveBlockParserForReplacement(): void { // Note that we don't want to parse inlines or finalize this block, as it's getting replaced. $old = $this->deactivateBlockParser(); if ($old instanceof ParagraphParser) { $this->updateReferenceMap($old->getReferences()); } $old->getBlock()->detach(); } /** * @param ReferenceInterface[] $references */ private function updateReferenceMap(iterable $references): void { foreach ($references as $reference) { if (! $this->referenceMap->contains($reference->getLabel())) { $this->referenceMap->add($reference); } } } /** * @throws ParserLogicException */ public function getActiveBlockParser(): BlockContinueParserInterface { $active = \end($this->activeBlockParsers); if ($active === false) { throw new ParserLogicException('No active block parsers are available'); } return $active; } } CursorState.php 0000644 00000002150 15107554763 0007546 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; /** * Encapsulates the current state of a cursor in case you need to rollback later. * * WARNING: Do not attempt to use this class for ANYTHING except for * type hinting and passing this object back into restoreState(). * The constructor, methods, and inner contents may change in any * future release without warning! * * @internal * * @psalm-immutable */ final class CursorState { /** * @var array<int, mixed> * * @psalm-readonly */ private array $state; /** * @internal * * @param array<int, mixed> $state */ public function __construct(array $state) { $this->state = $state; } /** * @internal * * @return array<int, mixed> */ public function toArray(): array { return $this->state; } } Inline/InlineParserInterface.php 0000644 00000001026 15107554763 0012723 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Inline; use League\CommonMark\Parser\InlineParserContext; interface InlineParserInterface { public function getMatchDefinition(): InlineParserMatch; public function parse(InlineParserContext $inlineContext): bool; } Inline/InlineParserMatch.php 0000644 00000004272 15107554763 0012065 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Inline; use League\CommonMark\Exception\InvalidArgumentException; final class InlineParserMatch { private string $regex; private bool $caseSensitive; private function __construct(string $regex, bool $caseSensitive = false) { $this->regex = $regex; $this->caseSensitive = $caseSensitive; } public function caseSensitive(): self { $this->caseSensitive = true; return $this; } /** * @internal * * @psalm-return non-empty-string */ public function getRegex(): string { return '/' . $this->regex . '/' . ($this->caseSensitive ? '' : 'i'); } /** * Match the given string (case-insensitive) */ public static function string(string $str): self { return new self(\preg_quote($str, '/')); } /** * Match any of the given strings (case-insensitive) */ public static function oneOf(string ...$str): self { return new self(\implode('|', \array_map(static fn (string $str): string => \preg_quote($str, '/'), $str))); } /** * Match a partial regular expression without starting/ending delimiters, anchors, or flags */ public static function regex(string $regex): self { return new self($regex); } public static function join(self ...$definitions): self { $regex = ''; $caseSensitive = null; foreach ($definitions as $definition) { $regex .= '(' . $definition->regex . ')'; if ($caseSensitive === null) { $caseSensitive = $definition->caseSensitive; } elseif ($caseSensitive !== $definition->caseSensitive) { throw new InvalidArgumentException('Case-sensitive and case-insensitive definitions cannot be combined'); } } return new self($regex, $caseSensitive ?? false); } } Inline/NewlineParser.php 0000644 00000003054 15107554763 0011270 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Inline; use League\CommonMark\Node\Inline\Newline; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Parser\InlineParserContext; final class NewlineParser implements InlineParserInterface { public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::regex('\\n'); } public function parse(InlineParserContext $inlineContext): bool { $inlineContext->getCursor()->advanceBy(1); // Check previous inline for trailing spaces $spaces = 0; $lastInline = $inlineContext->getContainer()->lastChild(); if ($lastInline instanceof Text) { $trimmed = \rtrim($lastInline->getLiteral(), ' '); $spaces = \strlen($lastInline->getLiteral()) - \strlen($trimmed); if ($spaces) { $lastInline->setLiteral($trimmed); } } if ($spaces >= 2) { $inlineContext->getContainer()->appendChild(new Newline(Newline::HARDBREAK)); } else { $inlineContext->getContainer()->appendChild(new Newline(Newline::SOFTBREAK)); } return true; } } Block/AbstractBlockContinueParser.php 0000644 00000001663 15107554763 0013732 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Node\Block\AbstractBlock; /** * Base class for a block parser * * Slightly more convenient to extend from vs. implementing the interface */ abstract class AbstractBlockContinueParser implements BlockContinueParserInterface { public function isContainer(): bool { return false; } public function canHaveLazyContinuationLines(): bool { return false; } public function canContain(AbstractBlock $childBlock): bool { return false; } public function addLine(string $line): void { } public function closeBlock(): void { } } Block/BlockContinueParserWithInlinesInterface.php 0000644 00000001151 15107554763 0016235 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Parser\InlineParserEngineInterface; interface BlockContinueParserWithInlinesInterface extends BlockContinueParserInterface { /** * Parse any inlines inside of the current block */ public function parseInlines(InlineParserEngineInterface $inlineParser): void; } Block/BlockContinueParserInterface.php 0000644 00000003500 15107554763 0014057 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Parser\Cursor; /** * Interface for a block continuation parser * * A block continue parser can only handle a single block instance. The current block being parsed is stored within this parser and * can be returned once parsing has completed. If you need to parse multiple block continuations, instantiate a new parser for each one. */ interface BlockContinueParserInterface { /** * Return the current block being parsed by this parser */ public function getBlock(): AbstractBlock; /** * Return whether we are parsing a container block */ public function isContainer(): bool; /** * Return whether we are interested in possibly lazily parsing any subsequent lines */ public function canHaveLazyContinuationLines(): bool; /** * Determine whether the current block being parsed can contain the given child block */ public function canContain(AbstractBlock $childBlock): bool; /** * Attempt to parse the given line */ public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue; /** * Add the given line of text to the current block */ public function addLine(string $line): void; /** * Close and finalize the current block */ public function closeBlock(): void; } Block/SkipLinesStartingWithLettersParser.php 0000644 00000003216 15107554763 0015317 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\MarkdownParserStateInterface; use League\CommonMark\Util\RegexHelper; /** * @internal * * This "parser" is actually a performance optimization. * * Most lines in a typical Markdown document probably won't match a block start. This is especially true for lines starting * with letters - nothing in the core CommonMark spec or our supported extensions will match those lines as blocks. Therefore, * if we can identify those lines and skip block start parsing, we can optimize performance by ~10%. * * Previously this optimization was hard-coded in the MarkdownParser but did not allow users to override this behavior. * By implementing this optimization as a block parser instead, users wanting custom blocks starting with letters * can instead register their block parser with a higher priority to ensure their parser is always called first. */ final class SkipLinesStartingWithLettersParser implements BlockStartParserInterface { public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart { if (! $cursor->isIndented() && RegexHelper::isLetter($cursor->getNextNonSpaceCharacter())) { $cursor->advanceToNextNonSpaceOrTab(); return BlockStart::abort(); } return BlockStart::none(); } } Block/BlockStartParserInterface.php 0000644 00000002061 15107554763 0013371 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\MarkdownParserStateInterface; /** * Interface for a block parser which identifies block starts. */ interface BlockStartParserInterface { /** * Check whether we should handle the block at the current position * * @param Cursor $cursor A cloned copy of the cursor at the current parsing location * @param MarkdownParserStateInterface $parserState Additional information about the state of the Markdown parser * * @return BlockStart|null The BlockStart that has been identified, or null if the block doesn't match here */ public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart; } Block/BlockStart.php 0000644 00000005241 15107554763 0010376 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\CursorState; /** * Result object for starting parsing of a block; see static methods for constructors */ final class BlockStart { /** * @var BlockContinueParserInterface[] * * @psalm-readonly */ private array $blockParsers; /** @psalm-readonly-allow-private-mutation */ private ?CursorState $cursorState = null; /** @psalm-readonly-allow-private-mutation */ private bool $replaceActiveBlockParser = false; private bool $isAborting = false; private function __construct(BlockContinueParserInterface ...$blockParsers) { $this->blockParsers = $blockParsers; } /** * @return BlockContinueParserInterface[] */ public function getBlockParsers(): iterable { return $this->blockParsers; } public function getCursorState(): ?CursorState { return $this->cursorState; } public function isReplaceActiveBlockParser(): bool { return $this->replaceActiveBlockParser; } /** * @internal */ public function isAborting(): bool { return $this->isAborting; } /** * Signal that we want to parse at the given cursor position * * @return $this */ public function at(Cursor $cursor): self { $this->cursorState = $cursor->saveState(); return $this; } /** * Signal that we want to replace the active block parser with this one * * @return $this */ public function replaceActiveBlockParser(): self { $this->replaceActiveBlockParser = true; return $this; } /** * Signal that we cannot parse whatever is here * * @return null */ public static function none(): ?self { return null; } /** * Signal that we'd like to register the given parser(s) so they can parse the current block */ public static function of(BlockContinueParserInterface ...$blockParsers): self { return new self(...$blockParsers); } /** * Signal that the block parsing process should be aborted (no other block starts should be checked) * * @internal */ public static function abort(): self { $ret = new self(); $ret->isAborting = true; return $ret; } } Block/DocumentBlockParser.php 0000644 00000002410 15107554763 0012227 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Node\Block\Document; use League\CommonMark\Parser\Cursor; use League\CommonMark\Reference\ReferenceMapInterface; /** * Parser implementation which ensures everything is added to the root-level Document */ final class DocumentBlockParser extends AbstractBlockContinueParser { /** @psalm-readonly */ private Document $document; public function __construct(ReferenceMapInterface $referenceMap) { $this->document = new Document($referenceMap); } public function getBlock(): Document { return $this->document; } public function isContainer(): bool { return true; } public function canContain(AbstractBlock $childBlock): bool { return true; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { return BlockContinue::at($cursor); } } Block/ParagraphParser.php 0000644 00000004273 15107554763 0011414 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Node\Block\Paragraph; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\InlineParserEngineInterface; use League\CommonMark\Reference\ReferenceInterface; use League\CommonMark\Reference\ReferenceParser; final class ParagraphParser extends AbstractBlockContinueParser implements BlockContinueParserWithInlinesInterface { /** @psalm-readonly */ private Paragraph $block; /** @psalm-readonly */ private ReferenceParser $referenceParser; public function __construct() { $this->block = new Paragraph(); $this->referenceParser = new ReferenceParser(); } public function canHaveLazyContinuationLines(): bool { return true; } public function getBlock(): Paragraph { return $this->block; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { if ($cursor->isBlank()) { return BlockContinue::none(); } return BlockContinue::at($cursor); } public function addLine(string $line): void { $this->referenceParser->parse($line); } public function closeBlock(): void { if ($this->referenceParser->hasReferences() && $this->referenceParser->getParagraphContent() === '') { $this->block->detach(); } } public function parseInlines(InlineParserEngineInterface $inlineParser): void { $content = $this->getContentString(); if ($content !== '') { $inlineParser->parse($content, $this->block); } } public function getContentString(): string { return $this->referenceParser->getParagraphContent(); } /** * @return ReferenceInterface[] */ public function getReferences(): iterable { return $this->referenceParser->getReferences(); } } Block/BlockContinue.php 0000644 00000003037 15107554763 0011066 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\CursorState; /** * Result object for continuing parsing of a block; see static methods for constructors. * * @psalm-immutable */ final class BlockContinue { /** @psalm-readonly */ private ?CursorState $cursorState = null; /** @psalm-readonly */ private bool $finalize; private function __construct(?CursorState $cursorState = null, bool $finalize = false) { $this->cursorState = $cursorState; $this->finalize = $finalize; } public function getCursorState(): ?CursorState { return $this->cursorState; } public function isFinalize(): bool { return $this->finalize; } /** * Signal that we cannot continue here * * @return null */ public static function none(): ?self { return null; } /** * Signal that we're continuing at the given position */ public static function at(Cursor $cursor): self { return new self($cursor->saveState(), false); } /** * Signal that we want to finalize and close the block */ public static function finished(): self { return new self(null, true); } } MarkdownParserState.php 0000644 00000003140 15107554763 0011230 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Block\ParagraphParser; /** * @internal You should rely on the interface instead */ final class MarkdownParserState implements MarkdownParserStateInterface { /** @psalm-readonly */ private BlockContinueParserInterface $activeBlockParser; /** @psalm-readonly */ private BlockContinueParserInterface $lastMatchedBlockParser; public function __construct(BlockContinueParserInterface $activeBlockParser, BlockContinueParserInterface $lastMatchedBlockParser) { $this->activeBlockParser = $activeBlockParser; $this->lastMatchedBlockParser = $lastMatchedBlockParser; } public function getActiveBlockParser(): BlockContinueParserInterface { return $this->activeBlockParser; } public function getLastMatchedBlockParser(): BlockContinueParserInterface { return $this->lastMatchedBlockParser; } public function getParagraphContent(): ?string { if (! $this->lastMatchedBlockParser instanceof ParagraphParser) { return null; } $paragraphParser = $this->lastMatchedBlockParser; $content = $paragraphParser->getContentString(); return $content === '' ? null : $content; } } Cursor.php 0000644 00000033673 15107554763 0006563 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Exception\UnexpectedEncodingException; class Cursor { public const INDENT_LEVEL = 4; /** @psalm-readonly */ private string $line; /** @psalm-readonly */ private int $length; /** * @var int * * It's possible for this to be 1 char past the end, meaning we've parsed all chars and have * reached the end. In this state, any character-returning method MUST return null. */ private int $currentPosition = 0; private int $column = 0; private int $indent = 0; private int $previousPosition = 0; private ?int $nextNonSpaceCache = null; private bool $partiallyConsumedTab = false; /** * @var int|false * * @psalm-readonly */ private $lastTabPosition; /** @psalm-readonly */ private bool $isMultibyte; /** @var array<int, string> */ private array $charCache = []; /** * @param string $line The line being parsed (ASCII or UTF-8) */ public function __construct(string $line) { if (! \mb_check_encoding($line, 'UTF-8')) { throw new UnexpectedEncodingException('Unexpected encoding - UTF-8 or ASCII was expected'); } $this->line = $line; $this->length = \mb_strlen($line, 'UTF-8') ?: 0; $this->isMultibyte = $this->length !== \strlen($line); $this->lastTabPosition = $this->isMultibyte ? \mb_strrpos($line, "\t", 0, 'UTF-8') : \strrpos($line, "\t"); } /** * Returns the position of the next character which is not a space (or tab) */ public function getNextNonSpacePosition(): int { if ($this->nextNonSpaceCache !== null) { return $this->nextNonSpaceCache; } if ($this->currentPosition >= $this->length) { return $this->length; } $cols = $this->column; for ($i = $this->currentPosition; $i < $this->length; $i++) { // This if-else was copied out of getCharacter() for performance reasons if ($this->isMultibyte) { $c = $this->charCache[$i] ??= \mb_substr($this->line, $i, 1, 'UTF-8'); } else { $c = $this->line[$i]; } if ($c === ' ') { $cols++; } elseif ($c === "\t") { $cols += 4 - ($cols % 4); } else { break; } } $this->indent = $cols - $this->column; return $this->nextNonSpaceCache = $i; } /** * Returns the next character which isn't a space (or tab) */ public function getNextNonSpaceCharacter(): ?string { $index = $this->getNextNonSpacePosition(); if ($index >= $this->length) { return null; } if ($this->isMultibyte) { return $this->charCache[$index] ??= \mb_substr($this->line, $index, 1, 'UTF-8'); } return $this->line[$index]; } /** * Calculates the current indent (number of spaces after current position) */ public function getIndent(): int { if ($this->nextNonSpaceCache === null) { $this->getNextNonSpacePosition(); } return $this->indent; } /** * Whether the cursor is indented to INDENT_LEVEL */ public function isIndented(): bool { if ($this->nextNonSpaceCache === null) { $this->getNextNonSpacePosition(); } return $this->indent >= self::INDENT_LEVEL; } public function getCharacter(?int $index = null): ?string { if ($index === null) { $index = $this->currentPosition; } // Index out-of-bounds, or we're at the end if ($index < 0 || $index >= $this->length) { return null; } if ($this->isMultibyte) { return $this->charCache[$index] ??= \mb_substr($this->line, $index, 1, 'UTF-8'); } return $this->line[$index]; } /** * Slightly-optimized version of getCurrent(null) */ public function getCurrentCharacter(): ?string { if ($this->currentPosition >= $this->length) { return null; } if ($this->isMultibyte) { return $this->charCache[$this->currentPosition] ??= \mb_substr($this->line, $this->currentPosition, 1, 'UTF-8'); } return $this->line[$this->currentPosition]; } /** * Returns the next character (or null, if none) without advancing forwards */ public function peek(int $offset = 1): ?string { return $this->getCharacter($this->currentPosition + $offset); } /** * Whether the remainder is blank */ public function isBlank(): bool { return $this->nextNonSpaceCache === $this->length || $this->getNextNonSpacePosition() === $this->length; } /** * Move the cursor forwards */ public function advance(): void { $this->advanceBy(1); } /** * Move the cursor forwards * * @param int $characters Number of characters to advance by * @param bool $advanceByColumns Whether to advance by columns instead of spaces */ public function advanceBy(int $characters, bool $advanceByColumns = false): void { $this->previousPosition = $this->currentPosition; $this->nextNonSpaceCache = null; if ($this->currentPosition >= $this->length || $characters === 0) { return; } // Optimization to avoid tab handling logic if we have no tabs if ($this->lastTabPosition === false || $this->currentPosition > $this->lastTabPosition) { $length = \min($characters, $this->length - $this->currentPosition); $this->partiallyConsumedTab = false; $this->currentPosition += $length; $this->column += $length; return; } $nextFewChars = $this->isMultibyte ? \mb_substr($this->line, $this->currentPosition, $characters, 'UTF-8') : \substr($this->line, $this->currentPosition, $characters); if ($characters === 1) { $asArray = [$nextFewChars]; } elseif ($this->isMultibyte) { /** @var string[] $asArray */ $asArray = \mb_str_split($nextFewChars, 1, 'UTF-8'); } else { $asArray = \str_split($nextFewChars); } foreach ($asArray as $c) { if ($c === "\t") { $charsToTab = 4 - ($this->column % 4); if ($advanceByColumns) { $this->partiallyConsumedTab = $charsToTab > $characters; $charsToAdvance = $charsToTab > $characters ? $characters : $charsToTab; $this->column += $charsToAdvance; $this->currentPosition += $this->partiallyConsumedTab ? 0 : 1; $characters -= $charsToAdvance; } else { $this->partiallyConsumedTab = false; $this->column += $charsToTab; $this->currentPosition++; $characters--; } } else { $this->partiallyConsumedTab = false; $this->currentPosition++; $this->column++; $characters--; } if ($characters <= 0) { break; } } } /** * Advances the cursor by a single space or tab, if present */ public function advanceBySpaceOrTab(): bool { $character = $this->getCurrentCharacter(); if ($character === ' ' || $character === "\t") { $this->advanceBy(1, true); return true; } return false; } /** * Parse zero or more space/tab characters * * @return int Number of positions moved */ public function advanceToNextNonSpaceOrTab(): int { $newPosition = $this->nextNonSpaceCache ?? $this->getNextNonSpacePosition(); if ($newPosition === $this->currentPosition) { return 0; } $this->advanceBy($newPosition - $this->currentPosition); $this->partiallyConsumedTab = false; // We've just advanced to where that non-space is, // so any subsequent calls to find the next one will // always return the current position. $this->nextNonSpaceCache = $this->currentPosition; $this->indent = 0; return $this->currentPosition - $this->previousPosition; } /** * Parse zero or more space characters, including at most one newline. * * Tab characters are not parsed with this function. * * @return int Number of positions moved */ public function advanceToNextNonSpaceOrNewline(): int { $remainder = $this->getRemainder(); // Optimization: Avoid the regex if we know there are no spaces or newlines if ($remainder === '' || ($remainder[0] !== ' ' && $remainder[0] !== "\n")) { $this->previousPosition = $this->currentPosition; return 0; } $matches = []; \preg_match('/^ *(?:\n *)?/', $remainder, $matches, \PREG_OFFSET_CAPTURE); // [0][0] contains the matched text // [0][1] contains the index of that match $increment = $matches[0][1] + \strlen($matches[0][0]); $this->advanceBy($increment); return $this->currentPosition - $this->previousPosition; } /** * Move the position to the very end of the line * * @return int The number of characters moved */ public function advanceToEnd(): int { $this->previousPosition = $this->currentPosition; $this->nextNonSpaceCache = null; $this->currentPosition = $this->length; return $this->currentPosition - $this->previousPosition; } public function getRemainder(): string { if ($this->currentPosition >= $this->length) { return ''; } $prefix = ''; $position = $this->currentPosition; if ($this->partiallyConsumedTab) { $position++; $charsToTab = 4 - ($this->column % 4); $prefix = \str_repeat(' ', $charsToTab); } $subString = $this->isMultibyte ? \mb_substr($this->line, $position, null, 'UTF-8') : \substr($this->line, $position); return $prefix . $subString; } public function getLine(): string { return $this->line; } public function isAtEnd(): bool { return $this->currentPosition >= $this->length; } /** * Try to match a regular expression * * Returns the matching text and advances to the end of that match * * @psalm-param non-empty-string $regex */ public function match(string $regex): ?string { $subject = $this->getRemainder(); if (! \preg_match($regex, $subject, $matches, \PREG_OFFSET_CAPTURE)) { return null; } // $matches[0][0] contains the matched text // $matches[0][1] contains the index of that match if ($this->isMultibyte) { // PREG_OFFSET_CAPTURE always returns the byte offset, not the char offset, which is annoying $offset = \mb_strlen(\substr($subject, 0, $matches[0][1]), 'UTF-8'); $matchLength = \mb_strlen($matches[0][0], 'UTF-8'); } else { $offset = $matches[0][1]; $matchLength = \strlen($matches[0][0]); } // [0][0] contains the matched text // [0][1] contains the index of that match $this->advanceBy($offset + $matchLength); return $matches[0][0]; } /** * Encapsulates the current state of this cursor in case you need to rollback later. * * WARNING: Do not parse or use the return value for ANYTHING except for * passing it back into restoreState(), as the number of values and their * contents may change in any future release without warning. */ public function saveState(): CursorState { return new CursorState([ $this->currentPosition, $this->previousPosition, $this->nextNonSpaceCache, $this->indent, $this->column, $this->partiallyConsumedTab, ]); } /** * Restore the cursor to a previous state. * * Pass in the value previously obtained by calling saveState(). */ public function restoreState(CursorState $state): void { [ $this->currentPosition, $this->previousPosition, $this->nextNonSpaceCache, $this->indent, $this->column, $this->partiallyConsumedTab, ] = $state->toArray(); } public function getPosition(): int { return $this->currentPosition; } public function getPreviousText(): string { if ($this->isMultibyte) { return \mb_substr($this->line, $this->previousPosition, $this->currentPosition - $this->previousPosition, 'UTF-8'); } return \substr($this->line, $this->previousPosition, $this->currentPosition - $this->previousPosition); } public function getSubstring(int $start, ?int $length = null): string { if ($this->isMultibyte) { return \mb_substr($this->line, $start, $length, 'UTF-8'); } if ($length !== null) { return \substr($this->line, $start, $length); } return \substr($this->line, $start); } public function getColumn(): int { return $this->column; } } ParserLogicException.php 0000644 00000000701 15107554763 0011361 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Exception\CommonMarkException; class ParserLogicException extends \LogicException implements CommonMarkException { } InlineParserEngine.php 0000644 00000015005 15107554763 0011014 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Environment\EnvironmentInterface; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Node\Inline\AdjacentTextMerger; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Reference\ReferenceMapInterface; /** * @internal */ final class InlineParserEngine implements InlineParserEngineInterface { /** @psalm-readonly */ private EnvironmentInterface $environment; /** @psalm-readonly */ private ReferenceMapInterface $referenceMap; /** * @var array<int, InlineParserInterface|string|bool> * @psalm-var list<array{0: InlineParserInterface, 1: non-empty-string, 2: bool}> * @phpstan-var array<int, array{0: InlineParserInterface, 1: non-empty-string, 2: bool}> */ 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<array<int, InlineParserInterface|string>> * * @psalm-return array<int, list<array{0: InlineParserInterface, 1: non-empty-array<string>}>> * * @phpstan-return array<int, array<int, array{0: InlineParserInterface, 1: non-empty-array<string>}>> */ private function matchParsers(string $contents): array { $contents = \trim($contents); $isMultibyte = ! \mb_check_encoding($contents, 'ASCII'); $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; } } DecimalMoneyParser.php 0000644 00000004716 15107576314 0011021 0 ustar 00 <?php declare(strict_types=1); namespace Money\Parser; use Money\Currencies; use Money\Currency; use Money\Exception\ParserException; use Money\Money; use Money\MoneyParser; use Money\Number; use function ltrim; use function preg_match; use function sprintf; use function str_pad; use function strlen; use function substr; use function trim; /** * Parses a decimal string into a Money object. */ final class DecimalMoneyParser implements MoneyParser { public const DECIMAL_PATTERN = '/^(?P<sign>-)?(?P<digits>\d+)?\.?(?P<fraction>\d+)?$/'; private Currencies $currencies; public function __construct(Currencies $currencies) { $this->currencies = $currencies; } public function parse(string $money, Currency|null $fallbackCurrency = null): Money { if ($fallbackCurrency === null) { throw new ParserException('DecimalMoneyParser cannot parse currency symbols. Use fallbackCurrency argument'); } $decimal = trim($money); if ($decimal === '') { return new Money(0, $fallbackCurrency); } if (! preg_match(self::DECIMAL_PATTERN, $decimal, $matches) || ! isset($matches['digits'])) { throw new ParserException(sprintf('Cannot parse "%s" to Money.', $decimal)); } $negative = isset($matches['sign']) && $matches['sign'] === '-'; $decimal = $matches['digits']; if ($negative) { $decimal = '-' . $decimal; } $subunit = $this->currencies->subunitFor($fallbackCurrency); if (isset($matches['fraction'])) { $fractionDigits = strlen($matches['fraction']); $decimal .= $matches['fraction']; $decimal = Number::roundMoneyValue($decimal, $subunit, $fractionDigits); if ($fractionDigits > $subunit) { $decimal = substr($decimal, 0, $subunit - $fractionDigits); } elseif ($fractionDigits < $subunit) { $decimal .= str_pad('', $subunit - $fractionDigits, '0'); } } else { $decimal .= str_pad('', $subunit, '0'); } if ($negative) { $decimal = '-' . ltrim(substr($decimal, 1), '0'); } else { $decimal = ltrim($decimal, '0'); } if ($decimal === '' || $decimal === '-') { $decimal = '0'; } /** @psalm-var numeric-string $decimal */ return new Money($decimal, $fallbackCurrency); } } AggregateMoneyParser.php 0000644 00000001760 15107576314 0011345 0 ustar 00 <?php declare(strict_types=1); namespace Money\Parser; use Money\Currency; use Money\Exception; use Money\Money; use Money\MoneyParser; use function sprintf; /** * Parses a string into a Money object using other parsers. */ final class AggregateMoneyParser implements MoneyParser { /** * @var MoneyParser[] * @psalm-var non-empty-array<MoneyParser> */ private array $parsers; /** * @param MoneyParser[] $parsers * @psalm-param non-empty-array<MoneyParser> $parsers */ public function __construct(array $parsers) { $this->parsers = $parsers; } public function parse(string $money, Currency|null $fallbackCurrency = null): Money { foreach ($this->parsers as $parser) { try { return $parser->parse($money, $fallbackCurrency); } catch (Exception\ParserException $e) { } } throw new Exception\ParserException(sprintf('Unable to parse %s', $money)); } } IntlLocalizedDecimalParser.php 0000644 00000004555 15107576314 0012470 0 ustar 00 <?php declare(strict_types=1); namespace Money\Parser; use Money\Currencies; use Money\Currency; use Money\Exception\ParserException; use Money\Money; use Money\MoneyParser; use Money\Number; use NumberFormatter; use function ltrim; use function str_pad; use function str_replace; use function strlen; use function strpos; use function substr; /** * Parses a string into a Money object using intl extension. */ final class IntlLocalizedDecimalParser implements MoneyParser { private NumberFormatter $formatter; private Currencies $currencies; public function __construct(NumberFormatter $formatter, Currencies $currencies) { $this->formatter = $formatter; $this->currencies = $currencies; } public function parse(string $money, Currency|null $fallbackCurrency = null): Money { if ($fallbackCurrency === null) { throw new ParserException('IntlLocalizedDecimalParser cannot parse currency symbols. Use forceCurrency argument'); } $decimal = $this->formatter->parse($money); if ($decimal === false) { throw new ParserException('Cannot parse ' . $money . ' to Money. ' . $this->formatter->getErrorMessage()); } $decimal = (string) $decimal; $subunit = $this->currencies->subunitFor($fallbackCurrency); $decimalPosition = strpos($decimal, '.'); if ($decimalPosition !== false) { $decimalLength = strlen($decimal); $fractionDigits = $decimalLength - $decimalPosition - 1; $decimal = str_replace('.', '', $decimal); $decimal = Number::roundMoneyValue($decimal, $subunit, $fractionDigits); if ($fractionDigits > $subunit) { $decimal = substr($decimal, 0, $decimalPosition + $subunit); } elseif ($fractionDigits < $subunit) { $decimal .= str_pad('', $subunit - $fractionDigits, '0'); } } else { $decimal .= str_pad('', $subunit, '0'); } if ($decimal[0] === '-') { $decimal = '-' . ltrim(substr($decimal, 1), '0'); } else { $decimal = ltrim($decimal, '0'); } if ($decimal === '') { $decimal = '0'; } /** @psalm-var numeric-string $decimal */ return new Money($decimal, $fallbackCurrency); } } IntlMoneyParser.php 0000644 00000004744 15107576314 0010372 0 ustar 00 <?php declare(strict_types=1); namespace Money\Parser; use Money\Currencies; use Money\Currency; use Money\Exception\ParserException; use Money\Money; use Money\MoneyParser; use Money\Number; use NumberFormatter; use function assert; use function ltrim; use function str_pad; use function str_replace; use function strlen; use function strpos; use function substr; /** * Parses a string into a Money object using intl extension. */ final class IntlMoneyParser implements MoneyParser { private NumberFormatter $formatter; private Currencies $currencies; public function __construct(NumberFormatter $formatter, Currencies $currencies) { $this->formatter = $formatter; $this->currencies = $currencies; } public function parse(string $money, Currency|null $fallbackCurrency = null): Money { $currency = ''; // phpcs:ignore /** @var string|float|bool|null $decimal */ $decimal = $this->formatter->parseCurrency($money, $currency); if ($decimal === false || $decimal === null) { throw new ParserException('Cannot parse ' . $money . ' to Money. ' . $this->formatter->getErrorMessage()); } if ($fallbackCurrency === null) { assert($currency !== ''); $fallbackCurrency = new Currency($currency); } $decimal = (string) $decimal; $subunit = $this->currencies->subunitFor($fallbackCurrency); $decimalPosition = strpos($decimal, '.'); if ($decimalPosition !== false) { $decimalLength = strlen($decimal); $fractionDigits = $decimalLength - $decimalPosition - 1; $decimal = str_replace('.', '', $decimal); $decimal = Number::roundMoneyValue($decimal, $subunit, $fractionDigits); if ($fractionDigits > $subunit) { $decimal = substr($decimal, 0, $decimalPosition + $subunit); } elseif ($fractionDigits < $subunit) { $decimal .= str_pad('', $subunit - $fractionDigits, '0'); } } else { $decimal .= str_pad('', $subunit, '0'); } if ($decimal[0] === '-') { $decimal = '-' . ltrim(substr($decimal, 1), '0'); } else { $decimal = ltrim($decimal, '0'); } if ($decimal === '') { $decimal = '0'; } /** @psalm-var numeric-string $decimal */ return new Money($decimal, $fallbackCurrency); } } BitcoinMoneyParser.php 0000644 00000003453 15107576314 0011047 0 ustar 00 <?php declare(strict_types=1); namespace Money\Parser; use Money\Currencies\BitcoinCurrencies; use Money\Currency; use Money\Exception\ParserException; use Money\Money; use Money\MoneyParser; use function ltrim; use function rtrim; use function str_pad; use function str_replace; use function strlen; use function strpos; use function substr; /** * Parses Bitcoin currency to Money. */ final class BitcoinMoneyParser implements MoneyParser { private int $fractionDigits; public function __construct(int $fractionDigits) { $this->fractionDigits = $fractionDigits; } public function parse(string $money, Currency|null $fallbackCurrency = null): Money { if (strpos($money, BitcoinCurrencies::SYMBOL) === false) { throw new ParserException('Value cannot be parsed as Bitcoin'); } $currency = $fallbackCurrency ?? new Currency(BitcoinCurrencies::CODE); $decimal = str_replace(BitcoinCurrencies::SYMBOL, '', $money); $decimalSeparator = strpos($decimal, '.'); if ($decimalSeparator !== false) { $decimal = rtrim($decimal, '0'); $lengthDecimal = strlen($decimal); $decimal = str_replace('.', '', $decimal); $decimal .= str_pad('', ($lengthDecimal - $decimalSeparator - $this->fractionDigits - 1) * -1, '0'); } else { $decimal .= str_pad('', $this->fractionDigits, '0'); } if (substr($decimal, 0, 1) === '-') { $decimal = '-' . ltrim(substr($decimal, 1), '0'); } else { $decimal = ltrim($decimal, '0'); } if ($decimal === '') { $decimal = '0'; } /** @psalm-var numeric-string $decimal */ return new Money($decimal, $currency); } } ParserInterface.php 0000644 00000001451 15111200554 0010325 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser; use Symfony\Component\CssSelector\Node\SelectorNode; /** * CSS selector parser interface. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ interface ParserInterface { /** * Parses given selector source into an array of tokens. * * @return SelectorNode[] */ public function parse(string $source): array; } EntryParser.php 0000644 00000030240 15111200554 0007524 0 ustar 00 <?php declare(strict_types=1); namespace Dotenv\Parser; use Dotenv\Util\Regex; use Dotenv\Util\Str; use GrahamCampbell\ResultType\Error; use GrahamCampbell\ResultType\Result; use GrahamCampbell\ResultType\Success; final class EntryParser { private const INITIAL_STATE = 0; private const UNQUOTED_STATE = 1; private const SINGLE_QUOTED_STATE = 2; private const DOUBLE_QUOTED_STATE = 3; private const ESCAPE_SEQUENCE_STATE = 4; private const WHITESPACE_STATE = 5; private const COMMENT_STATE = 6; private const REJECT_STATES = [self::SINGLE_QUOTED_STATE, self::DOUBLE_QUOTED_STATE, self::ESCAPE_SEQUENCE_STATE]; /** * This class is a singleton. * * @codeCoverageIgnore * * @return void */ private function __construct() { // } /** * Parse a raw entry into a proper entry. * * That is, turn a raw environment variable entry into a name and possibly * a value. We wrap the answer in a result type. * * @param string $entry * * @return \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Entry,string> */ public static function parse(string $entry) { return self::splitStringIntoParts($entry)->flatMap(static function (array $parts) { [$name, $value] = $parts; return self::parseName($name)->flatMap(static function (string $name) use ($value) { /** @var Result<Value|null,string> */ $parsedValue = $value === null ? Success::create(null) : self::parseValue($value); return $parsedValue->map(static function (?Value $value) use ($name) { return new Entry($name, $value); }); }); }); } /** * Split the compound string into parts. * * @param string $line * * @return \GrahamCampbell\ResultType\Result<array{string,string|null},string> */ private static function splitStringIntoParts(string $line) { /** @var array{string,string|null} */ $result = Str::pos($line, '=')->map(static function () use ($line) { return \array_map('trim', \explode('=', $line, 2)); })->getOrElse([$line, null]); if ($result[0] === '') { /** @var \GrahamCampbell\ResultType\Result<array{string,string|null},string> */ return Error::create(self::getErrorMessage('an unexpected equals', $line)); } /** @var \GrahamCampbell\ResultType\Result<array{string,string|null},string> */ return Success::create($result); } /** * Parse the given variable name. * * That is, strip the optional quotes and leading "export" from the * variable name. We wrap the answer in a result type. * * @param string $name * * @return \GrahamCampbell\ResultType\Result<string,string> */ private static function parseName(string $name) { if (Str::len($name) > 8 && Str::substr($name, 0, 6) === 'export' && \ctype_space(Str::substr($name, 6, 1))) { $name = \ltrim(Str::substr($name, 6)); } if (self::isQuotedName($name)) { $name = Str::substr($name, 1, -1); } if (!self::isValidName($name)) { /** @var \GrahamCampbell\ResultType\Result<string,string> */ return Error::create(self::getErrorMessage('an invalid name', $name)); } /** @var \GrahamCampbell\ResultType\Result<string,string> */ return Success::create($name); } /** * Is the given variable name quoted? * * @param string $name * * @return bool */ private static function isQuotedName(string $name) { if (Str::len($name) < 3) { return false; } $first = Str::substr($name, 0, 1); $last = Str::substr($name, -1, 1); return ($first === '"' && $last === '"') || ($first === '\'' && $last === '\''); } /** * Is the given variable name valid? * * @param string $name * * @return bool */ private static function isValidName(string $name) { return Regex::matches('~(*UTF8)\A[\p{Ll}\p{Lu}\p{M}\p{N}_.]+\z~', $name)->success()->getOrElse(false); } /** * Parse the given variable value. * * This has the effect of stripping quotes and comments, dealing with * special characters, and locating nested variables, but not resolving * them. Formally, we run a finite state automaton with an output tape: a * transducer. We wrap the answer in a result type. * * @param string $value * * @return \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value,string> */ private static function parseValue(string $value) { if (\trim($value) === '') { /** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value,string> */ return Success::create(Value::blank()); } return \array_reduce(\iterator_to_array(Lexer::lex($value)), static function (Result $data, string $token) { return $data->flatMap(static function (array $data) use ($token) { return self::processToken($data[1], $token)->map(static function (array $val) use ($data) { return [$data[0]->append($val[0], $val[1]), $val[2]]; }); }); }, Success::create([Value::blank(), self::INITIAL_STATE]))->flatMap(static function (array $result) { /** @psalm-suppress DocblockTypeContradiction */ if (in_array($result[1], self::REJECT_STATES, true)) { /** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value,string> */ return Error::create('a missing closing quote'); } /** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value,string> */ return Success::create($result[0]); })->mapError(static function (string $err) use ($value) { return self::getErrorMessage($err, $value); }); } /** * Process the given token. * * @param int $state * @param string $token * * @return \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ private static function processToken(int $state, string $token) { switch ($state) { case self::INITIAL_STATE: if ($token === '\'') { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create(['', false, self::SINGLE_QUOTED_STATE]); } elseif ($token === '"') { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create(['', false, self::DOUBLE_QUOTED_STATE]); } elseif ($token === '#') { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create(['', false, self::COMMENT_STATE]); } elseif ($token === '$') { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create([$token, true, self::UNQUOTED_STATE]); } else { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create([$token, false, self::UNQUOTED_STATE]); } case self::UNQUOTED_STATE: if ($token === '#') { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create(['', false, self::COMMENT_STATE]); } elseif (\ctype_space($token)) { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create(['', false, self::WHITESPACE_STATE]); } elseif ($token === '$') { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create([$token, true, self::UNQUOTED_STATE]); } else { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create([$token, false, self::UNQUOTED_STATE]); } case self::SINGLE_QUOTED_STATE: if ($token === '\'') { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create(['', false, self::WHITESPACE_STATE]); } else { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create([$token, false, self::SINGLE_QUOTED_STATE]); } case self::DOUBLE_QUOTED_STATE: if ($token === '"') { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create(['', false, self::WHITESPACE_STATE]); } elseif ($token === '\\') { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create(['', false, self::ESCAPE_SEQUENCE_STATE]); } elseif ($token === '$') { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create([$token, true, self::DOUBLE_QUOTED_STATE]); } else { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create([$token, false, self::DOUBLE_QUOTED_STATE]); } case self::ESCAPE_SEQUENCE_STATE: if ($token === '"' || $token === '\\') { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create([$token, false, self::DOUBLE_QUOTED_STATE]); } elseif ($token === '$') { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create([$token, false, self::DOUBLE_QUOTED_STATE]); } else { $first = Str::substr($token, 0, 1); if (\in_array($first, ['f', 'n', 'r', 't', 'v'], true)) { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create([\stripcslashes('\\'.$first).Str::substr($token, 1), false, self::DOUBLE_QUOTED_STATE]); } else { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Error::create('an unexpected escape sequence'); } } case self::WHITESPACE_STATE: if ($token === '#') { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create(['', false, self::COMMENT_STATE]); } elseif (!\ctype_space($token)) { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Error::create('unexpected whitespace'); } else { /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create(['', false, self::WHITESPACE_STATE]); } case self::COMMENT_STATE: /** @var \GrahamCampbell\ResultType\Result<array{string,bool,int},string> */ return Success::create(['', false, self::COMMENT_STATE]); default: throw new \Error('Parser entered invalid state.'); } } /** * Generate a friendly error message. * * @param string $cause * @param string $subject * * @return string */ private static function getErrorMessage(string $cause, string $subject) { return \sprintf( 'Encountered %s at [%s].', $cause, \strtok($subject, "\n") ); } } Value.php 0000644 00000003064 15111200554 0006326 0 ustar 00 <?php declare(strict_types=1); namespace Dotenv\Parser; use Dotenv\Util\Str; final class Value { /** * The string representation of the parsed value. * * @var string */ private $chars; /** * The locations of the variables in the value. * * @var int[] */ private $vars; /** * Internal constructor for a value. * * @param string $chars * @param int[] $vars * * @return void */ private function __construct(string $chars, array $vars) { $this->chars = $chars; $this->vars = $vars; } /** * Create an empty value instance. * * @return \Dotenv\Parser\Value */ public static function blank() { return new self('', []); } /** * Create a new value instance, appending the characters. * * @param string $chars * @param bool $var * * @return \Dotenv\Parser\Value */ public function append(string $chars, bool $var) { return new self( $this->chars.$chars, $var ? \array_merge($this->vars, [Str::len($this->chars)]) : $this->vars ); } /** * Get the string representation of the parsed value. * * @return string */ public function getChars() { return $this->chars; } /** * Get the locations of the variables in the value. * * @return int[] */ public function getVars() { $vars = $this->vars; \rsort($vars); return $vars; } } Lines.php 0000644 00000006113 15111200554 0006322 0 ustar 00 <?php declare(strict_types=1); namespace Dotenv\Parser; use Dotenv\Util\Regex; use Dotenv\Util\Str; final class Lines { /** * This class is a singleton. * * @codeCoverageIgnore * * @return void */ private function __construct() { // } /** * Process the array of lines of environment variables. * * This will produce an array of raw entries, one per variable. * * @param string[] $lines * * @return string[] */ public static function process(array $lines) { $output = []; $multiline = false; $multilineBuffer = []; foreach ($lines as $line) { [$multiline, $line, $multilineBuffer] = self::multilineProcess($multiline, $line, $multilineBuffer); if (!$multiline && !self::isCommentOrWhitespace($line)) { $output[] = $line; } } return $output; } /** * Used to make all multiline variable process. * * @param bool $multiline * @param string $line * @param string[] $buffer * * @return array{bool,string,string[]} */ private static function multilineProcess(bool $multiline, string $line, array $buffer) { $startsOnCurrentLine = $multiline ? false : self::looksLikeMultilineStart($line); // check if $line can be multiline variable if ($startsOnCurrentLine) { $multiline = true; } if ($multiline) { \array_push($buffer, $line); if (self::looksLikeMultilineStop($line, $startsOnCurrentLine)) { $multiline = false; $line = \implode("\n", $buffer); $buffer = []; } } return [$multiline, $line, $buffer]; } /** * Determine if the given line can be the start of a multiline variable. * * @param string $line * * @return bool */ private static function looksLikeMultilineStart(string $line) { return Str::pos($line, '="')->map(static function () use ($line) { return self::looksLikeMultilineStop($line, true) === false; })->getOrElse(false); } /** * Determine if the given line can be the start of a multiline variable. * * @param string $line * @param bool $started * * @return bool */ private static function looksLikeMultilineStop(string $line, bool $started) { if ($line === '"') { return true; } return Regex::occurrences('/(?=([^\\\\]"))/', \str_replace('\\\\', '', $line))->map(static function (int $count) use ($started) { return $started ? $count > 1 : $count >= 1; })->success()->getOrElse(false); } /** * Determine if the line in the file is a comment or whitespace. * * @param string $line * * @return bool */ private static function isCommentOrWhitespace(string $line) { $line = \trim($line); return $line === '' || (isset($line[0]) && $line[0] === '#'); } } Entry.php 0000644 00000001766 15111200554 0006362 0 ustar 00 <?php declare(strict_types=1); namespace Dotenv\Parser; use PhpOption\Option; final class Entry { /** * The entry name. * * @var string */ private $name; /** * The entry value. * * @var \Dotenv\Parser\Value|null */ private $value; /** * Create a new entry instance. * * @param string $name * @param \Dotenv\Parser\Value|null $value * * @return void */ public function __construct(string $name, Value $value = null) { $this->name = $name; $this->value = $value; } /** * Get the entry name. * * @return string */ public function getName() { return $this->name; } /** * Get the entry value. * * @return \PhpOption\Option<\Dotenv\Parser\Value> */ public function getValue() { /** @var \PhpOption\Option<\Dotenv\Parser\Value> */ return Option::fromValue($this->value); } } Lexer.php 0000644 00000002370 15111200554 0006330 0 ustar 00 <?php declare(strict_types=1); namespace Dotenv\Parser; final class Lexer { /** * The regex for each type of token. */ private const PATTERNS = [ '[\r\n]{1,1000}', '[^\S\r\n]{1,1000}', '\\\\', '\'', '"', '\\#', '\\$', '([^(\s\\\\\'"\\#\\$)]|\\(|\\)){1,1000}', ]; /** * This class is a singleton. * * @codeCoverageIgnore * * @return void */ private function __construct() { // } /** * Convert content into a token stream. * * Multibyte string processing is not needed here, and nether is error * handling, for performance reasons. * * @param string $content * * @return \Generator<string> */ public static function lex(string $content) { static $regex; if ($regex === null) { $regex = '(('.\implode(')|(', self::PATTERNS).'))A'; } $offset = 0; while (isset($content[$offset])) { if (!\preg_match($regex, $content, $matches, 0, $offset)) { throw new \Error(\sprintf('Lexer encountered unexpected character [%s].', $content[$offset])); } $offset += \strlen($matches[0]); yield $matches[0]; } } } Tokenizer/Tokenizer.php 0000644 00000003770 15111433420 0011202 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser\Tokenizer; use Symfony\Component\CssSelector\Parser\Handler; use Symfony\Component\CssSelector\Parser\Reader; use Symfony\Component\CssSelector\Parser\Token; use Symfony\Component\CssSelector\Parser\TokenStream; /** * CSS selector tokenizer. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class Tokenizer { /** * @var Handler\HandlerInterface[] */ private array $handlers; public function __construct() { $patterns = new TokenizerPatterns(); $escaping = new TokenizerEscaping($patterns); $this->handlers = [ new Handler\WhitespaceHandler(), new Handler\IdentifierHandler($patterns, $escaping), new Handler\HashHandler($patterns, $escaping), new Handler\StringHandler($patterns, $escaping), new Handler\NumberHandler($patterns), new Handler\CommentHandler(), ]; } /** * Tokenize selector source code. */ public function tokenize(Reader $reader): TokenStream { $stream = new TokenStream(); while (!$reader->isEOF()) { foreach ($this->handlers as $handler) { if ($handler->handle($reader, $stream)) { continue 2; } } $stream->push(new Token(Token::TYPE_DELIMITER, $reader->getSubstring(1), $reader->getPosition())); $reader->moveForward(1); } return $stream ->push(new Token(Token::TYPE_FILE_END, null, $reader->getPosition())) ->freeze(); } } Tokenizer/TokenizerEscaping.php 0000644 00000003402 15111433420 0012644 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser\Tokenizer; /** * CSS selector tokenizer escaping applier. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class TokenizerEscaping { private TokenizerPatterns $patterns; public function __construct(TokenizerPatterns $patterns) { $this->patterns = $patterns; } public function escapeUnicode(string $value): string { $value = $this->replaceUnicodeSequences($value); return preg_replace($this->patterns->getSimpleEscapePattern(), '$1', $value); } public function escapeUnicodeAndNewLine(string $value): string { $value = preg_replace($this->patterns->getNewLineEscapePattern(), '', $value); return $this->escapeUnicode($value); } private function replaceUnicodeSequences(string $value): string { return preg_replace_callback($this->patterns->getUnicodeEscapePattern(), function ($match) { $c = hexdec($match[1]); if (0x80 > $c %= 0x200000) { return \chr($c); } if (0x800 > $c) { return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F); } if (0x10000 > $c) { return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); } return ''; }, $value); } } Tokenizer/TokenizerPatterns.php 0000644 00000005456 15111433420 0012726 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser\Tokenizer; /** * CSS selector tokenizer patterns builder. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class TokenizerPatterns { private string $unicodeEscapePattern; private string $simpleEscapePattern; private string $newLineEscapePattern; private string $escapePattern; private string $stringEscapePattern; private string $nonAsciiPattern; private string $nmCharPattern; private string $nmStartPattern; private string $identifierPattern; private string $hashPattern; private string $numberPattern; private string $quotedStringPattern; public function __construct() { $this->unicodeEscapePattern = '\\\\([0-9a-f]{1,6})(?:\r\n|[ \n\r\t\f])?'; $this->simpleEscapePattern = '\\\\(.)'; $this->newLineEscapePattern = '\\\\(?:\n|\r\n|\r|\f)'; $this->escapePattern = $this->unicodeEscapePattern.'|\\\\[^\n\r\f0-9a-f]'; $this->stringEscapePattern = $this->newLineEscapePattern.'|'.$this->escapePattern; $this->nonAsciiPattern = '[^\x00-\x7F]'; $this->nmCharPattern = '[_a-z0-9-]|'.$this->escapePattern.'|'.$this->nonAsciiPattern; $this->nmStartPattern = '[_a-z]|'.$this->escapePattern.'|'.$this->nonAsciiPattern; $this->identifierPattern = '-?(?:'.$this->nmStartPattern.')(?:'.$this->nmCharPattern.')*'; $this->hashPattern = '#((?:'.$this->nmCharPattern.')+)'; $this->numberPattern = '[+-]?(?:[0-9]*\.[0-9]+|[0-9]+)'; $this->quotedStringPattern = '([^\n\r\f\\\\%s]|'.$this->stringEscapePattern.')*'; } public function getNewLineEscapePattern(): string { return '~'.$this->newLineEscapePattern.'~'; } public function getSimpleEscapePattern(): string { return '~'.$this->simpleEscapePattern.'~'; } public function getUnicodeEscapePattern(): string { return '~'.$this->unicodeEscapePattern.'~i'; } public function getIdentifierPattern(): string { return '~^'.$this->identifierPattern.'~i'; } public function getHashPattern(): string { return '~^'.$this->hashPattern.'~i'; } public function getNumberPattern(): string { return '~^'.$this->numberPattern.'~'; } public function getQuotedStringPattern(string $quote): string { return '~^'.sprintf($this->quotedStringPattern, $quote).'~i'; } } Shortcut/EmptyStringParser.php 0000644 00000002251 15111433420 0012524 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser\Shortcut; use Symfony\Component\CssSelector\Node\ElementNode; use Symfony\Component\CssSelector\Node\SelectorNode; use Symfony\Component\CssSelector\Parser\ParserInterface; /** * CSS selector class parser shortcut. * * This shortcut ensure compatibility with previous version. * - The parser fails to parse an empty string. * - In the previous version, an empty string matches each tags. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class EmptyStringParser implements ParserInterface { public function parse(string $source): array { // Matches an empty string if ('' == $source) { return [new SelectorNode(new ElementNode(null, '*'))]; } return []; } } Shortcut/ClassParser.php 0000644 00000003027 15111433420 0011306 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser\Shortcut; use Symfony\Component\CssSelector\Node\ClassNode; use Symfony\Component\CssSelector\Node\ElementNode; use Symfony\Component\CssSelector\Node\SelectorNode; use Symfony\Component\CssSelector\Parser\ParserInterface; /** * CSS selector class parser shortcut. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class ClassParser implements ParserInterface { public function parse(string $source): array { // Matches an optional namespace, optional element, and required class // $source = 'test|input.ab6bd_field'; // $matches = array (size=4) // 0 => string 'test|input.ab6bd_field' (length=22) // 1 => string 'test' (length=4) // 2 => string 'input' (length=5) // 3 => string 'ab6bd_field' (length=11) if (preg_match('/^(?:([a-z]++)\|)?+([\w-]++|\*)?+\.([\w-]++)$/i', trim($source), $matches)) { return [ new SelectorNode(new ClassNode(new ElementNode($matches[1] ?: null, $matches[2] ?: null), $matches[3])), ]; } return []; } } Shortcut/error_log 0000644 00000002766 15111433420 0010301 0 ustar 00 [25-Nov-2025 19:57:45 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Component\CssSelector\Parser\ParserInterface" not found in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php:32 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php on line 32 [25-Nov-2025 20:01:14 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Component\CssSelector\Parser\ParserInterface" not found in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Shortcut/ClassParser.php:29 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Shortcut/ClassParser.php on line 29 [25-Nov-2025 20:23:30 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Component\CssSelector\Parser\ParserInterface" not found in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Shortcut/HashParser.php:29 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Shortcut/HashParser.php on line 29 [25-Nov-2025 22:03:41 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Component\CssSelector\Parser\ParserInterface" not found in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Shortcut/ElementParser.php:28 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Shortcut/ElementParser.php on line 28 Shortcut/HashParser.php 0000644 00000003017 15111433420 0011123 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser\Shortcut; use Symfony\Component\CssSelector\Node\ElementNode; use Symfony\Component\CssSelector\Node\HashNode; use Symfony\Component\CssSelector\Node\SelectorNode; use Symfony\Component\CssSelector\Parser\ParserInterface; /** * CSS selector hash parser shortcut. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class HashParser implements ParserInterface { public function parse(string $source): array { // Matches an optional namespace, optional element, and required id // $source = 'test|input#ab6bd_field'; // $matches = array (size=4) // 0 => string 'test|input#ab6bd_field' (length=22) // 1 => string 'test' (length=4) // 2 => string 'input' (length=5) // 3 => string 'ab6bd_field' (length=11) if (preg_match('/^(?:([a-z]++)\|)?+([\w-]++|\*)?+#([\w-]++)$/i', trim($source), $matches)) { return [ new SelectorNode(new HashNode(new ElementNode($matches[1] ?: null, $matches[2] ?: null), $matches[3])), ]; } return []; } } Shortcut/ElementParser.php 0000644 00000002507 15111433420 0011634 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser\Shortcut; use Symfony\Component\CssSelector\Node\ElementNode; use Symfony\Component\CssSelector\Node\SelectorNode; use Symfony\Component\CssSelector\Parser\ParserInterface; /** * CSS selector element parser shortcut. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class ElementParser implements ParserInterface { public function parse(string $source): array { // Matches an optional namespace, required element or `*` // $source = 'testns|testel'; // $matches = array (size=3) // 0 => string 'testns|testel' (length=13) // 1 => string 'testns' (length=6) // 2 => string 'testel' (length=6) if (preg_match('/^(?:([a-z]++)\|)?([\w-]++|\*)$/i', trim($source), $matches)) { return [new SelectorNode(new ElementNode($matches[1] ?: null, $matches[2]))]; } return []; } } TokenStream.php 0000644 00000006364 15111433420 0007514 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser; use Symfony\Component\CssSelector\Exception\InternalErrorException; use Symfony\Component\CssSelector\Exception\SyntaxErrorException; /** * CSS selector token stream. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class TokenStream { /** * @var Token[] */ private array $tokens = []; /** * @var Token[] */ private array $used = []; private int $cursor = 0; private ?Token $peeked; private bool $peeking = false; /** * Pushes a token. * * @return $this */ public function push(Token $token): static { $this->tokens[] = $token; return $this; } /** * Freezes stream. * * @return $this */ public function freeze(): static { return $this; } /** * Returns next token. * * @throws InternalErrorException If there is no more token */ public function getNext(): Token { if ($this->peeking) { $this->peeking = false; $this->used[] = $this->peeked; return $this->peeked; } if (!isset($this->tokens[$this->cursor])) { throw new InternalErrorException('Unexpected token stream end.'); } return $this->tokens[$this->cursor++]; } /** * Returns peeked token. */ public function getPeek(): Token { if (!$this->peeking) { $this->peeked = $this->getNext(); $this->peeking = true; } return $this->peeked; } /** * Returns used tokens. * * @return Token[] */ public function getUsed(): array { return $this->used; } /** * Returns next identifier token. * * @throws SyntaxErrorException If next token is not an identifier */ public function getNextIdentifier(): string { $next = $this->getNext(); if (!$next->isIdentifier()) { throw SyntaxErrorException::unexpectedToken('identifier', $next); } return $next->getValue(); } /** * Returns next identifier or null if star delimiter token is found. * * @throws SyntaxErrorException If next token is not an identifier or a star delimiter */ public function getNextIdentifierOrStar(): ?string { $next = $this->getNext(); if ($next->isIdentifier()) { return $next->getValue(); } if ($next->isDelimiter(['*'])) { return null; } throw SyntaxErrorException::unexpectedToken('identifier or "*"', $next); } /** * Skips next whitespace if any. */ public function skipWhitespace(): void { $peek = $this->getPeek(); if ($peek->isWhitespace()) { $this->getNext(); } } } error_log 0000644 00000000536 15111433420 0006457 0 ustar 00 [25-Nov-2025 03:03:27 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Component\CssSelector\Parser\ParserInterface" not found in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Parser.php:28 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Parser.php on line 28 Handler/HandlerInterface.php 0000644 00000001410 15111433420 0012016 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser\Handler; use Symfony\Component\CssSelector\Parser\Reader; use Symfony\Component\CssSelector\Parser\TokenStream; /** * CSS selector handler interface. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ interface HandlerInterface { public function handle(Reader $reader, TokenStream $stream): bool; } Handler/StringHandler.php 0000644 00000004606 15111433420 0011376 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser\Handler; use Symfony\Component\CssSelector\Exception\InternalErrorException; use Symfony\Component\CssSelector\Exception\SyntaxErrorException; use Symfony\Component\CssSelector\Parser\Reader; use Symfony\Component\CssSelector\Parser\Token; use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; use Symfony\Component\CssSelector\Parser\TokenStream; /** * CSS selector comment handler. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class StringHandler implements HandlerInterface { private TokenizerPatterns $patterns; private TokenizerEscaping $escaping; public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) { $this->patterns = $patterns; $this->escaping = $escaping; } public function handle(Reader $reader, TokenStream $stream): bool { $quote = $reader->getSubstring(1); if (!\in_array($quote, ["'", '"'])) { return false; } $reader->moveForward(1); $match = $reader->findPattern($this->patterns->getQuotedStringPattern($quote)); if (!$match) { throw new InternalErrorException(sprintf('Should have found at least an empty match at %d.', $reader->getPosition())); } // check unclosed strings if (\strlen($match[0]) === $reader->getRemainingLength()) { throw SyntaxErrorException::unclosedString($reader->getPosition() - 1); } // check quotes pairs validity if ($quote !== $reader->getSubstring(1, \strlen($match[0]))) { throw SyntaxErrorException::unclosedString($reader->getPosition() - 1); } $string = $this->escaping->escapeUnicodeAndNewLine($match[0]); $stream->push(new Token(Token::TYPE_STRING, $string, $reader->getPosition())); $reader->moveForward(\strlen($match[0]) + 1); return true; } } Handler/NumberHandler.php 0000644 00000002537 15111433420 0011361 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser\Handler; use Symfony\Component\CssSelector\Parser\Reader; use Symfony\Component\CssSelector\Parser\Token; use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; use Symfony\Component\CssSelector\Parser\TokenStream; /** * CSS selector comment handler. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class NumberHandler implements HandlerInterface { private TokenizerPatterns $patterns; public function __construct(TokenizerPatterns $patterns) { $this->patterns = $patterns; } public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern($this->patterns->getNumberPattern()); if (!$match) { return false; } $stream->push(new Token(Token::TYPE_NUMBER, $match[0], $reader->getPosition())); $reader->moveForward(\strlen($match[0])); return true; } } Handler/HashHandler.php 0000644 00000003103 15111433420 0011002 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser\Handler; use Symfony\Component\CssSelector\Parser\Reader; use Symfony\Component\CssSelector\Parser\Token; use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; use Symfony\Component\CssSelector\Parser\TokenStream; /** * CSS selector comment handler. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class HashHandler implements HandlerInterface { private TokenizerPatterns $patterns; private TokenizerEscaping $escaping; public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) { $this->patterns = $patterns; $this->escaping = $escaping; } public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern($this->patterns->getHashPattern()); if (!$match) { return false; } $value = $this->escaping->escapeUnicode($match[1]); $stream->push(new Token(Token::TYPE_HASH, $value, $reader->getPosition())); $reader->moveForward(\strlen($match[0])); return true; } } Handler/CommentHandler.php 0000644 00000002115 15111433420 0011523 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser\Handler; use Symfony\Component\CssSelector\Parser\Reader; use Symfony\Component\CssSelector\Parser\TokenStream; /** * CSS selector comment handler. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class CommentHandler implements HandlerInterface { public function handle(Reader $reader, TokenStream $stream): bool { if ('/*' !== $reader->getSubstring(2)) { return false; } $offset = $reader->getOffset('*/'); if (false === $offset) { $reader->moveToEnd(); } else { $reader->moveForward($offset + 2); } return true; } } Handler/IdentifierHandler.php 0000644 00000003125 15111433420 0012205 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser\Handler; use Symfony\Component\CssSelector\Parser\Reader; use Symfony\Component\CssSelector\Parser\Token; use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; use Symfony\Component\CssSelector\Parser\TokenStream; /** * CSS selector comment handler. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class IdentifierHandler implements HandlerInterface { private TokenizerPatterns $patterns; private TokenizerEscaping $escaping; public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) { $this->patterns = $patterns; $this->escaping = $escaping; } public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern($this->patterns->getIdentifierPattern()); if (!$match) { return false; } $value = $this->escaping->escapeUnicode($match[0]); $stream->push(new Token(Token::TYPE_IDENTIFIER, $value, $reader->getPosition())); $reader->moveForward(\strlen($match[0])); return true; } } Handler/error_log 0000644 00000004454 15111433420 0010037 0 ustar 00 [25-Nov-2025 18:04:03 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Component\CssSelector\Parser\Handler\HandlerInterface" not found in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Handler/WhitespaceHandler.php:28 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Handler/WhitespaceHandler.php on line 28 [25-Nov-2025 19:52:37 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Component\CssSelector\Parser\Handler\HandlerInterface" not found in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Handler/CommentHandler.php:27 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Handler/CommentHandler.php on line 27 [25-Nov-2025 20:00:38 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Component\CssSelector\Parser\Handler\HandlerInterface" not found in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Handler/IdentifierHandler.php:30 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Handler/IdentifierHandler.php on line 30 [25-Nov-2025 20:03:51 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Component\CssSelector\Parser\Handler\HandlerInterface" not found in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Handler/NumberHandler.php:29 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Handler/NumberHandler.php on line 29 [25-Nov-2025 21:36:41 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Component\CssSelector\Parser\Handler\HandlerInterface" not found in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Handler/HashHandler.php:30 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Handler/HashHandler.php on line 30 [25-Nov-2025 22:04:03 UTC] PHP Fatal error: Uncaught Error: Interface "Symfony\Component\CssSelector\Parser\Handler\HandlerInterface" not found in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Handler/StringHandler.php:32 Stack trace: #0 {main} thrown in /home/fluxyjvi/public_html/project/vendor/symfony/css-selector/Parser/Handler/StringHandler.php on line 32 Handler/WhitespaceHandler.php 0000644 00000002202 15111433420 0012212 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser\Handler; use Symfony\Component\CssSelector\Parser\Reader; use Symfony\Component\CssSelector\Parser\Token; use Symfony\Component\CssSelector\Parser\TokenStream; /** * CSS selector whitespace handler. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class WhitespaceHandler implements HandlerInterface { public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern('~^[ \t\r\n\f]+~'); if (false === $match) { return false; } $stream->push(new Token(Token::TYPE_WHITESPACE, $match[0], $reader->getPosition())); $reader->moveForward(\strlen($match[0])); return true; } } Token.php 0000644 00000004733 15111433420 0006336 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser; /** * CSS selector token. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class Token { public const TYPE_FILE_END = 'eof'; public const TYPE_DELIMITER = 'delimiter'; public const TYPE_WHITESPACE = 'whitespace'; public const TYPE_IDENTIFIER = 'identifier'; public const TYPE_HASH = 'hash'; public const TYPE_NUMBER = 'number'; public const TYPE_STRING = 'string'; private ?string $type; private ?string $value; private ?int $position; public function __construct(?string $type, ?string $value, ?int $position) { $this->type = $type; $this->value = $value; $this->position = $position; } public function getType(): ?int { return $this->type; } public function getValue(): ?string { return $this->value; } public function getPosition(): ?int { return $this->position; } public function isFileEnd(): bool { return self::TYPE_FILE_END === $this->type; } public function isDelimiter(array $values = []): bool { if (self::TYPE_DELIMITER !== $this->type) { return false; } if (!$values) { return true; } return \in_array($this->value, $values); } public function isWhitespace(): bool { return self::TYPE_WHITESPACE === $this->type; } public function isIdentifier(): bool { return self::TYPE_IDENTIFIER === $this->type; } public function isHash(): bool { return self::TYPE_HASH === $this->type; } public function isNumber(): bool { return self::TYPE_NUMBER === $this->type; } public function isString(): bool { return self::TYPE_STRING === $this->type; } public function __toString(): string { if ($this->value) { return sprintf('<%s "%s" at %s>', $this->type, $this->value, $this->position); } return sprintf('<%s at %s>', $this->type, $this->position); } } Reader.php 0000644 00000003612 15111433420 0006453 0 ustar 00 <?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\CssSelector\Parser; /** * CSS selector reader. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class Reader { private string $source; private int $length; private int $position = 0; public function __construct(string $source) { $this->source = $source; $this->length = \strlen($source); } public function isEOF(): bool { return $this->position >= $this->length; } public function getPosition(): int { return $this->position; } public function getRemainingLength(): int { return $this->length - $this->position; } public function getSubstring(int $length, int $offset = 0): string { return substr($this->source, $this->position + $offset, $length); } /** * @return int|false */ public function getOffset(string $string): int|bool { $position = strpos($this->source, $string, $this->position); return false === $position ? false : $position - $this->position; } public function findPattern(string $pattern): array|false { $source = substr($this->source, $this->position); if (preg_match($pattern, $source, $matches)) { return $matches; } return false; } public function moveForward(int $length): void { $this->position += $length; } public function moveToEnd(): void { $this->position = $this->length; } }