/home/mip/www/img/credit/datatables/Util.tar
ArrayConverter.php000064400000007271151520544220010232 0ustar00<?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\Translation\Util;

/**
 * ArrayConverter generates tree like structure from a message catalogue.
 * e.g. this
 *   'foo.bar1' => 'test1',
 *   'foo.bar2' => 'test2'
 * converts to follows:
 *   foo:
 *     bar1: test1
 *     bar2: test2.
 *
 * @author Gennady Telegin <gtelegin@gmail.com>
 */
class ArrayConverter
{
    /**
     * Converts linear messages array to tree-like array.
     * For example this array('foo.bar' => 'value') will be converted to ['foo' => ['bar' => 'value']].
     *
     * @param array $messages Linear messages array
     */
    public static function expandToTree(array $messages): array
    {
        $tree = [];

        foreach ($messages as $id => $value) {
            $referenceToElement = &self::getElementByPath($tree, self::getKeyParts($id));

            $referenceToElement = $value;

            unset($referenceToElement);
        }

        return $tree;
    }

    private static function &getElementByPath(array &$tree, array $parts): mixed
    {
        $elem = &$tree;
        $parentOfElem = null;

        foreach ($parts as $i => $part) {
            if (isset($elem[$part]) && \is_string($elem[$part])) {
                /* Process next case:
                 *    'foo': 'test1',
                 *    'foo.bar': 'test2'
                 *
                 * $tree['foo'] was string before we found array {bar: test2}.
                 *  Treat new element as string too, e.g. add $tree['foo.bar'] = 'test2';
                 */
                $elem = &$elem[implode('.', \array_slice($parts, $i))];
                break;
            }

            $parentOfElem = &$elem;
            $elem = &$elem[$part];
        }

        if ($elem && \is_array($elem) && $parentOfElem) {
            /* Process next case:
             *    'foo.bar': 'test1'
             *    'foo': 'test2'
             *
             * $tree['foo'] was array = {bar: 'test1'} before we found string constant `foo`.
             * Cancel treating $tree['foo'] as array and cancel back it expansion,
             *  e.g. make it $tree['foo.bar'] = 'test1' again.
             */
            self::cancelExpand($parentOfElem, $part, $elem);
        }

        return $elem;
    }

    private static function cancelExpand(array &$tree, string $prefix, array $node): void
    {
        $prefix .= '.';

        foreach ($node as $id => $value) {
            if (\is_string($value)) {
                $tree[$prefix.$id] = $value;
            } else {
                self::cancelExpand($tree, $prefix.$id, $value);
            }
        }
    }

    /**
     * @return string[]
     */
    private static function getKeyParts(string $key): array
    {
        $parts = explode('.', $key);
        $partsCount = \count($parts);

        $result = [];
        $buffer = '';

        foreach ($parts as $index => $part) {
            if (0 === $index && '' === $part) {
                $buffer = '.';

                continue;
            }

            if ($index === $partsCount - 1 && '' === $part) {
                $buffer .= '.';
                $result[] = $buffer;

                continue;
            }

            if (isset($parts[$index + 1]) && '' === $parts[$index + 1]) {
                $buffer .= $part;

                continue;
            }

            if ($buffer) {
                $result[] = $buffer.$part;
                $buffer = '';

                continue;
            }

            $result[] = $part;
        }

        return $result;
    }
}
XliffUtils.php000064400000014473151520544220007357 0ustar00<?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\Translation\Util;

use Symfony\Component\Translation\Exception\InvalidArgumentException;
use Symfony\Component\Translation\Exception\InvalidResourceException;

/**
 * Provides some utility methods for XLIFF translation files, such as validating
 * their contents according to the XSD schema.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class XliffUtils
{
    /**
     * Gets xliff file version based on the root "version" attribute.
     *
     * Defaults to 1.2 for backwards compatibility.
     *
     * @throws InvalidArgumentException
     */
    public static function getVersionNumber(\DOMDocument $dom): string
    {
        /** @var \DOMNode $xliff */
        foreach ($dom->getElementsByTagName('xliff') as $xliff) {
            $version = $xliff->attributes->getNamedItem('version');
            if ($version) {
                return $version->nodeValue;
            }

            $namespace = $xliff->attributes->getNamedItem('xmlns');
            if ($namespace) {
                if (0 !== substr_compare('urn:oasis:names:tc:xliff:document:', $namespace->nodeValue, 0, 34)) {
                    throw new InvalidArgumentException(sprintf('Not a valid XLIFF namespace "%s".', $namespace));
                }

                return substr($namespace, 34);
            }
        }

        // Falls back to v1.2
        return '1.2';
    }

    /**
     * Validates and parses the given file into a DOMDocument.
     *
     * @throws InvalidResourceException
     */
    public static function validateSchema(\DOMDocument $dom): array
    {
        $xliffVersion = static::getVersionNumber($dom);
        $internalErrors = libxml_use_internal_errors(true);
        if ($shouldEnable = self::shouldEnableEntityLoader()) {
            $disableEntities = libxml_disable_entity_loader(false);
        }
        try {
            $isValid = @$dom->schemaValidateSource(self::getSchema($xliffVersion));
            if (!$isValid) {
                return self::getXmlErrors($internalErrors);
            }
        } finally {
            if ($shouldEnable) {
                libxml_disable_entity_loader($disableEntities);
            }
        }

        $dom->normalizeDocument();

        libxml_clear_errors();
        libxml_use_internal_errors($internalErrors);

        return [];
    }

    private static function shouldEnableEntityLoader(): bool
    {
        static $dom, $schema;
        if (null === $dom) {
            $dom = new \DOMDocument();
            $dom->loadXML('<?xml version="1.0"?><test/>');

            $tmpfile = tempnam(sys_get_temp_dir(), 'symfony');
            register_shutdown_function(static function () use ($tmpfile) {
                @unlink($tmpfile);
            });
            $schema = '<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <xsd:include schemaLocation="file:///'.str_replace('\\', '/', $tmpfile).'" />
</xsd:schema>';
            file_put_contents($tmpfile, '<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <xsd:element name="test" type="testType" />
  <xsd:complexType name="testType"/>
</xsd:schema>');
        }

        return !@$dom->schemaValidateSource($schema);
    }

    public static function getErrorsAsString(array $xmlErrors): string
    {
        $errorsAsString = '';

        foreach ($xmlErrors as $error) {
            $errorsAsString .= sprintf("[%s %s] %s (in %s - line %d, column %d)\n",
                \LIBXML_ERR_WARNING === $error['level'] ? 'WARNING' : 'ERROR',
                $error['code'],
                $error['message'],
                $error['file'],
                $error['line'],
                $error['column']
            );
        }

        return $errorsAsString;
    }

    private static function getSchema(string $xliffVersion): string
    {
        if ('1.2' === $xliffVersion) {
            $schemaSource = file_get_contents(__DIR__.'/../Resources/schemas/xliff-core-1.2-transitional.xsd');
            $xmlUri = 'http://www.w3.org/2001/xml.xsd';
        } elseif ('2.0' === $xliffVersion) {
            $schemaSource = file_get_contents(__DIR__.'/../Resources/schemas/xliff-core-2.0.xsd');
            $xmlUri = 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd';
        } else {
            throw new InvalidArgumentException(sprintf('No support implemented for loading XLIFF version "%s".', $xliffVersion));
        }

        return self::fixXmlLocation($schemaSource, $xmlUri);
    }

    /**
     * Internally changes the URI of a dependent xsd to be loaded locally.
     */
    private static function fixXmlLocation(string $schemaSource, string $xmlUri): string
    {
        $newPath = str_replace('\\', '/', __DIR__).'/../Resources/schemas/xml.xsd';
        $parts = explode('/', $newPath);
        $locationstart = 'file:///';
        if (0 === stripos($newPath, 'phar://')) {
            $tmpfile = tempnam(sys_get_temp_dir(), 'symfony');
            if ($tmpfile) {
                copy($newPath, $tmpfile);
                $parts = explode('/', str_replace('\\', '/', $tmpfile));
            } else {
                array_shift($parts);
                $locationstart = 'phar:///';
            }
        }

        $drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : '';
        $newPath = $locationstart.$drive.implode('/', array_map('rawurlencode', $parts));

        return str_replace($xmlUri, $newPath, $schemaSource);
    }

    /**
     * Returns the XML errors of the internal XML parser.
     */
    private static function getXmlErrors(bool $internalErrors): array
    {
        $errors = [];
        foreach (libxml_get_errors() as $error) {
            $errors[] = [
                'level' => \LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
                'code' => $error->code,
                'message' => trim($error->message),
                'file' => $error->file ?: 'n/a',
                'line' => $error->line,
                'column' => $error->column,
            ];
        }

        libxml_clear_errors();
        libxml_use_internal_errors($internalErrors);

        return $errors;
    }
}
Regex.php000064400000006000151520660020006320 0ustar00<?php

declare(strict_types=1);

namespace Dotenv\Util;

use GrahamCampbell\ResultType\Error;
use GrahamCampbell\ResultType\Success;

/**
 * @internal
 */
final class Regex
{
    /**
     * This class is a singleton.
     *
     * @codeCoverageIgnore
     *
     * @return void
     */
    private function __construct()
    {
        //
    }

    /**
     * Perform a preg match, wrapping up the result.
     *
     * @param string $pattern
     * @param string $subject
     *
     * @return \GrahamCampbell\ResultType\Result<bool,string>
     */
    public static function matches(string $pattern, string $subject)
    {
        return self::pregAndWrap(static function (string $subject) use ($pattern) {
            return @\preg_match($pattern, $subject) === 1;
        }, $subject);
    }

    /**
     * Perform a preg match all, wrapping up the result.
     *
     * @param string $pattern
     * @param string $subject
     *
     * @return \GrahamCampbell\ResultType\Result<int,string>
     */
    public static function occurrences(string $pattern, string $subject)
    {
        return self::pregAndWrap(static function (string $subject) use ($pattern) {
            return (int) @\preg_match_all($pattern, $subject);
        }, $subject);
    }

    /**
     * Perform a preg replace callback, wrapping up the result.
     *
     * @param string   $pattern
     * @param callable $callback
     * @param string   $subject
     * @param int|null $limit
     *
     * @return \GrahamCampbell\ResultType\Result<string,string>
     */
    public static function replaceCallback(string $pattern, callable $callback, string $subject, int $limit = null)
    {
        return self::pregAndWrap(static function (string $subject) use ($pattern, $callback, $limit) {
            return (string) @\preg_replace_callback($pattern, $callback, $subject, $limit ?? -1);
        }, $subject);
    }

    /**
     * Perform a preg split, wrapping up the result.
     *
     * @param string $pattern
     * @param string $subject
     *
     * @return \GrahamCampbell\ResultType\Result<string[],string>
     */
    public static function split(string $pattern, string $subject)
    {
        return self::pregAndWrap(static function (string $subject) use ($pattern) {
            /** @var string[] */
            return (array) @\preg_split($pattern, $subject);
        }, $subject);
    }

    /**
     * Perform a preg operation, wrapping up the result.
     *
     * @template V
     *
     * @param callable(string):V $operation
     * @param string             $subject
     *
     * @return \GrahamCampbell\ResultType\Result<V,string>
     */
    private static function pregAndWrap(callable $operation, string $subject)
    {
        $result = $operation($subject);

        if (\preg_last_error() !== \PREG_NO_ERROR) {
            /** @var \GrahamCampbell\ResultType\Result<V,string> */
            return Error::create(\preg_last_error_msg());
        }

        /** @var \GrahamCampbell\ResultType\Result<V,string> */
        return Success::create($result);
    }
}
Str.php000064400000004735151520660020006033 0ustar00<?php

declare(strict_types=1);

namespace Dotenv\Util;

use GrahamCampbell\ResultType\Error;
use GrahamCampbell\ResultType\Success;
use PhpOption\Option;

/**
 * @internal
 */
final class Str
{
    /**
     * This class is a singleton.
     *
     * @codeCoverageIgnore
     *
     * @return void
     */
    private function __construct()
    {
        //
    }

    /**
     * Convert a string to UTF-8 from the given encoding.
     *
     * @param string      $input
     * @param string|null $encoding
     *
     * @return \GrahamCampbell\ResultType\Result<string,string>
     */
    public static function utf8(string $input, string $encoding = null)
    {
        if ($encoding !== null && !\in_array($encoding, \mb_list_encodings(), true)) {
            /** @var \GrahamCampbell\ResultType\Result<string,string> */
            return Error::create(
                \sprintf('Illegal character encoding [%s] specified.', $encoding)
            );
        }
        $converted = $encoding === null ?
            @\mb_convert_encoding($input, 'UTF-8') :
            @\mb_convert_encoding($input, 'UTF-8', $encoding);
        /**
         * this is for support UTF-8 with BOM encoding
         * @see https://en.wikipedia.org/wiki/Byte_order_mark
         * @see https://github.com/vlucas/phpdotenv/issues/500
         */
        if (\substr($converted, 0, 3) == "\xEF\xBB\xBF") {
            $converted = \substr($converted, 3);
        }
        /** @var \GrahamCampbell\ResultType\Result<string,string> */
        return Success::create($converted);
    }

    /**
     * Search for a given substring of the input.
     *
     * @param string $haystack
     * @param string $needle
     *
     * @return \PhpOption\Option<int>
     */
    public static function pos(string $haystack, string $needle)
    {
        /** @var \PhpOption\Option<int> */
        return Option::fromValue(\mb_strpos($haystack, $needle, 0, 'UTF-8'), false);
    }

    /**
     * Grab the specified substring of the input.
     *
     * @param string   $input
     * @param int      $start
     * @param int|null $length
     *
     * @return string
     */
    public static function substr(string $input, int $start, int $length = null)
    {
        return \mb_substr($input, $start, $length, 'UTF-8');
    }

    /**
     * Compute the length of the given string.
     *
     * @param string $input
     *
     * @return int
     */
    public static function len(string $input)
    {
        return \mb_strlen($input, 'UTF-8');
    }
}
SpecReader.php000064400000004200151521003530007261 0ustar00<?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\Util;

use League\CommonMark\Exception\IOException;

/**
 * Reads in a CommonMark spec document and extracts the input/output examples for testing against them
 */
final class SpecReader
{
    private function __construct()
    {
    }

    /**
     * @return iterable<string, array{input: string, output: string, type: string, section: string, number: int}>
     */
    public static function read(string $data): iterable
    {
        // Normalize newlines for platform independence
        $data = \preg_replace('/\r\n?/', "\n", $data);
        \assert($data !== null);
        $data = \preg_replace('/<!-- END TESTS -->.*$/', '', $data);
        \assert($data !== null);
        \preg_match_all('/^`{32} (example ?\w*)\n([\s\S]*?)^\.\n([\s\S]*?)^`{32}$|^#{1,6} *(.*)$/m', $data, $matches, PREG_SET_ORDER);

        $currentSection = 'Example';
        $exampleNumber  = 0;

        foreach ($matches as $match) {
            if (isset($match[4])) {
                $currentSection = $match[4];
                continue;
            }

            yield \trim($currentSection . ' #' . $exampleNumber) => [
                'input'   => \str_replace('→', "\t", $match[2]),
                'output'  => \str_replace('→', "\t", $match[3]),
                'type'    => $match[1],
                'section' => $currentSection,
                'number'  => $exampleNumber++,
            ];
        }
    }

    /**
     * @return iterable<string, array{input: string, output: string, type: string, section: string, number: int}>
     *
     * @throws IOException if the file cannot be loaded
     */
    public static function readFile(string $filename): iterable
    {
        if (($data = \file_get_contents($filename)) === false) {
            throw new IOException(\sprintf('Failed to load spec from %s', $filename));
        }

        return self::read($data);
    }
}
UrlEncoder.php000064400000005073151521003530007317 0ustar00<?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\Util;

use League\CommonMark\Exception\UnexpectedEncodingException;

/**
 * @psalm-immutable
 */
final class UrlEncoder
{
    private const ENCODE_CACHE = ['%00', '%01', '%02', '%03', '%04', '%05', '%06', '%07', '%08', '%09', '%0A', '%0B', '%0C', '%0D', '%0E', '%0F', '%10', '%11', '%12', '%13', '%14', '%15', '%16', '%17', '%18', '%19', '%1A', '%1B', '%1C', '%1D', '%1E', '%1F', '%20', '!', '%22', '#', '$', '%25', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '%3C', '=', '%3E', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '%5B', '%5C', '%5D', '%5E', '_', '%60', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '%7B', '%7C', '%7D', '~', '%7F'];

    /**
     * @throws UnexpectedEncodingException if a non-UTF-8-compatible encoding is used
     *
     * @psalm-pure
     */
    public static function unescapeAndEncode(string $uri): string
    {
        // Optimization: if the URL only includes characters we know will be kept as-is, then just return the URL as-is.
        if (\preg_match('/^[A-Za-z0-9~!@#$&*()\-_=+;:,.\/?]+$/', $uri)) {
            return $uri;
        }

        if (! \mb_check_encoding($uri, 'UTF-8')) {
            throw new UnexpectedEncodingException('Unexpected encoding - UTF-8 or ASCII was expected');
        }

        $result = '';

        $chars = \mb_str_split($uri, 1, 'UTF-8');

        $l = \count($chars);
        for ($i = 0; $i < $l; $i++) {
            $code = $chars[$i];
            if ($code === '%' && $i + 2 < $l) {
                if (\preg_match('/^[0-9a-f]{2}$/i', $chars[$i + 1] . $chars[$i + 2]) === 1) {
                    $result .= '%' . $chars[$i + 1] . $chars[$i + 2];
                    $i      += 2;
                    continue;
                }
            }

            if (\ord($code) < 128) {
                $result .= self::ENCODE_CACHE[\ord($code)];
                continue;
            }

            $result .= \rawurlencode($code);
        }

        return $result;
    }
}
PrioritizedList.php000064400000003151151521003530010410 0ustar00<?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\Util;

/**
 * @internal
 *
 * @phpstan-template T
 * @phpstan-implements \IteratorAggregate<T>
 */
final class PrioritizedList implements \IteratorAggregate
{
    /**
     * @var array<int, array<mixed>>
     * @phpstan-var array<int, array<T>>
     */
    private array $list = [];

    /**
     * @var \Traversable<mixed>|null
     * @phpstan-var \Traversable<T>|null
     */
    private ?\Traversable $optimized = null;

    /**
     * @param mixed $item
     *
     * @phpstan-param T $item
     */
    public function add($item, int $priority): void
    {
        $this->list[$priority][] = $item;
        $this->optimized         = null;
    }

    /**
     * @return \Traversable<int, mixed>
     *
     * @phpstan-return \Traversable<int, T>
     */
    #[\ReturnTypeWillChange]
    public function getIterator(): \Traversable
    {
        if ($this->optimized === null) {
            \krsort($this->list);

            $sorted = [];
            foreach ($this->list as $group) {
                foreach ($group as $item) {
                    $sorted[] = $item;
                }
            }

            $this->optimized = new \ArrayIterator($sorted);
        }

        return $this->optimized;
    }
}
LinkParserHelper.php000064400000007652151521003530010474 0ustar00<?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\Util;

use League\CommonMark\Parser\Cursor;

/**
 * @psalm-immutable
 */
final class LinkParserHelper
{
    /**
     * Attempt to parse link destination
     *
     * @return string|null The string, or null if no match
     */
    public static function parseLinkDestination(Cursor $cursor): ?string
    {
        if ($res = $cursor->match(RegexHelper::REGEX_LINK_DESTINATION_BRACES)) {
            // Chop off surrounding <..>:
            return UrlEncoder::unescapeAndEncode(
                RegexHelper::unescape(\substr($res, 1, -1))
            );
        }

        if ($cursor->getCurrentCharacter() === '<') {
            return null;
        }

        $destination = self::manuallyParseLinkDestination($cursor);
        if ($destination === null) {
            return null;
        }

        return UrlEncoder::unescapeAndEncode(
            RegexHelper::unescape($destination)
        );
    }

    public static function parseLinkLabel(Cursor $cursor): int
    {
        $match = $cursor->match('/^\[(?:[^\\\\\[\]]|\\\\.){0,1000}\]/');
        if ($match === null) {
            return 0;
        }

        $length = \mb_strlen($match, 'UTF-8');

        if ($length > 1001) {
            return 0;
        }

        return $length;
    }

    public static function parsePartialLinkLabel(Cursor $cursor): ?string
    {
        return $cursor->match('/^(?:[^\\\\\[\]]+|\\\\.?)*/');
    }

    /**
     * Attempt to parse link title (sans quotes)
     *
     * @return string|null The string, or null if no match
     */
    public static function parseLinkTitle(Cursor $cursor): ?string
    {
        if ($title = $cursor->match('/' . RegexHelper::PARTIAL_LINK_TITLE . '/')) {
            // Chop off quotes from title and unescape
            return RegexHelper::unescape(\substr($title, 1, -1));
        }

        return null;
    }

    public static function parsePartialLinkTitle(Cursor $cursor, string $endDelimiter): ?string
    {
        $endDelimiter = \preg_quote($endDelimiter, '/');
        $regex        = \sprintf('/(%s|[^%s\x00])*(?:%s)?/', RegexHelper::PARTIAL_ESCAPED_CHAR, $endDelimiter, $endDelimiter);
        if (($partialTitle = $cursor->match($regex)) === null) {
            return null;
        }

        return RegexHelper::unescape($partialTitle);
    }

    private static function manuallyParseLinkDestination(Cursor $cursor): ?string
    {
        $oldPosition = $cursor->getPosition();
        $oldState    = $cursor->saveState();

        $openParens = 0;
        while (($c = $cursor->getCurrentCharacter()) !== null) {
            if ($c === '\\' && ($peek = $cursor->peek()) !== null && RegexHelper::isEscapable($peek)) {
                $cursor->advanceBy(2);
            } elseif ($c === '(') {
                $cursor->advanceBy(1);
                $openParens++;
            } elseif ($c === ')') {
                if ($openParens < 1) {
                    break;
                }

                $cursor->advanceBy(1);
                $openParens--;
            } elseif (\preg_match(RegexHelper::REGEX_WHITESPACE_CHAR, $c)) {
                break;
            } else {
                $cursor->advanceBy(1);
            }
        }

        if ($openParens !== 0) {
            return null;
        }

        if ($cursor->getPosition() === $oldPosition && (! isset($c) || $c !== ')')) {
            return null;
        }

        $newPos = $cursor->getPosition();
        $cursor->restoreState($oldState);

        $cursor->advanceBy($newPos - $cursor->getPosition());

        return $cursor->getPreviousText();
    }
}
RegexHelper.php000064400000024021151521003530007461 0ustar00<?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\Util;

use League\CommonMark\Exception\InvalidArgumentException;
use League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock;

/**
 * Provides regular expressions and utilities for parsing Markdown
 *
 * All of the PARTIAL_ regex constants assume that they'll be used in case-insensitive searches
 * All other complete regexes provided by this class (either via constants or methods) will have case-insensitivity enabled.
 *
 * @phpcs:disable Generic.Strings.UnnecessaryStringConcat.Found
 *
 * @psalm-immutable
 */
final class RegexHelper
{
    // Partial regular expressions (wrap with `/` on each side and add the case-insensitive `i` flag before use)
    public const PARTIAL_ENTITY                = '&(?:#x[a-f0-9]{1,6}|#[0-9]{1,7}|[a-z][a-z0-9]{1,31});';
    public const PARTIAL_ESCAPABLE             = '[!"#$%&\'()*+,.\/:;<=>?@[\\\\\]^_`{|}~-]';
    public const PARTIAL_ESCAPED_CHAR          = '\\\\' . self::PARTIAL_ESCAPABLE;
    public const PARTIAL_IN_DOUBLE_QUOTES      = '"(' . self::PARTIAL_ESCAPED_CHAR . '|[^"\x00])*"';
    public const PARTIAL_IN_SINGLE_QUOTES      = '\'(' . self::PARTIAL_ESCAPED_CHAR . '|[^\'\x00])*\'';
    public const PARTIAL_IN_PARENS             = '\\((' . self::PARTIAL_ESCAPED_CHAR . '|[^)\x00])*\\)';
    public const PARTIAL_REG_CHAR              = '[^\\\\()\x00-\x20]';
    public const PARTIAL_IN_PARENS_NOSP        = '\((' . self::PARTIAL_REG_CHAR . '|' . self::PARTIAL_ESCAPED_CHAR . '|\\\\)*\)';
    public const PARTIAL_TAGNAME               = '[a-z][a-z0-9-]*';
    public const PARTIAL_BLOCKTAGNAME          = '(?:address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h1|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)';
    public const PARTIAL_ATTRIBUTENAME         = '[a-z_:][a-z0-9:._-]*';
    public const PARTIAL_UNQUOTEDVALUE         = '[^"\'=<>`\x00-\x20]+';
    public const PARTIAL_SINGLEQUOTEDVALUE     = '\'[^\']*\'';
    public const PARTIAL_DOUBLEQUOTEDVALUE     = '"[^"]*"';
    public const PARTIAL_ATTRIBUTEVALUE        = '(?:' . self::PARTIAL_UNQUOTEDVALUE . '|' . self::PARTIAL_SINGLEQUOTEDVALUE . '|' . self::PARTIAL_DOUBLEQUOTEDVALUE . ')';
    public const PARTIAL_ATTRIBUTEVALUESPEC    = '(?:' . '\s*=' . '\s*' . self::PARTIAL_ATTRIBUTEVALUE . ')';
    public const PARTIAL_ATTRIBUTE             = '(?:' . '\s+' . self::PARTIAL_ATTRIBUTENAME . self::PARTIAL_ATTRIBUTEVALUESPEC . '?)';
    public const PARTIAL_OPENTAG               = '<' . self::PARTIAL_TAGNAME . self::PARTIAL_ATTRIBUTE . '*' . '\s*\/?>';
    public const PARTIAL_CLOSETAG              = '<\/' . self::PARTIAL_TAGNAME . '\s*[>]';
    public const PARTIAL_OPENBLOCKTAG          = '<' . self::PARTIAL_BLOCKTAGNAME . self::PARTIAL_ATTRIBUTE . '*' . '\s*\/?>';
    public const PARTIAL_CLOSEBLOCKTAG         = '<\/' . self::PARTIAL_BLOCKTAGNAME . '\s*[>]';
    public const PARTIAL_HTMLCOMMENT           = '<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->';
    public const PARTIAL_PROCESSINGINSTRUCTION = '[<][?][\s\S]*?[?][>]';
    public const PARTIAL_DECLARATION           = '<![A-Z]+' . '[^>]*>';
    public const PARTIAL_CDATA                 = '<!\[CDATA\[[\s\S]*?]\]>';
    public const PARTIAL_HTMLTAG               = '(?:' . self::PARTIAL_OPENTAG . '|' . self::PARTIAL_CLOSETAG . '|' . self::PARTIAL_HTMLCOMMENT . '|' .
        self::PARTIAL_PROCESSINGINSTRUCTION . '|' . self::PARTIAL_DECLARATION . '|' . self::PARTIAL_CDATA . ')';
    public const PARTIAL_HTMLBLOCKOPEN         = '<(?:' . self::PARTIAL_BLOCKTAGNAME . '(?:[\s\/>]|$)' . '|' .
        '\/' . self::PARTIAL_BLOCKTAGNAME . '(?:[\s>]|$)' . '|' . '[?!])';
    public const PARTIAL_LINK_TITLE            = '^(?:"(' . self::PARTIAL_ESCAPED_CHAR . '|[^"\x00])*"' .
        '|' . '\'(' . self::PARTIAL_ESCAPED_CHAR . '|[^\'\x00])*\'' .
        '|' . '\((' . self::PARTIAL_ESCAPED_CHAR . '|[^()\x00])*\))';

    public const REGEX_PUNCTUATION        = '/^[\x{2000}-\x{206F}\x{2E00}-\x{2E7F}\p{Pc}\p{Pd}\p{Pe}\p{Pf}\p{Pi}\p{Po}\p{Ps}\\\\\'!"#\$%&\(\)\*\+,\-\.\\/:;<=>\?@\[\]\^_`\{\|\}~]/u';
    public const REGEX_UNSAFE_PROTOCOL    = '/^javascript:|vbscript:|file:|data:/i';
    public const REGEX_SAFE_DATA_PROTOCOL = '/^data:image\/(?:png|gif|jpeg|webp)/i';
    public const REGEX_NON_SPACE          = '/[^ \t\f\v\r\n]/';

    public const REGEX_WHITESPACE_CHAR         = '/^[ \t\n\x0b\x0c\x0d]/';
    public const REGEX_UNICODE_WHITESPACE_CHAR = '/^\pZ|\s/u';
    public const REGEX_THEMATIC_BREAK          = '/^(?:\*[ \t]*){3,}$|^(?:_[ \t]*){3,}$|^(?:-[ \t]*){3,}$/';
    public const REGEX_LINK_DESTINATION_BRACES = '/^(?:<(?:[^<>\\n\\\\\\x00]|\\\\.)*>)/';

    /**
     * @psalm-pure
     */
    public static function isEscapable(string $character): bool
    {
        return \preg_match('/' . self::PARTIAL_ESCAPABLE . '/', $character) === 1;
    }

    /**
     * @psalm-pure
     */
    public static function isLetter(?string $character): bool
    {
        if ($character === null) {
            return false;
        }

        return \preg_match('/[\pL]/u', $character) === 1;
    }

    /**
     * Attempt to match a regex in string s at offset offset
     *
     * @psalm-param non-empty-string $regex
     *
     * @return int|null Index of match, or null
     *
     * @psalm-pure
     */
    public static function matchAt(string $regex, string $string, int $offset = 0): ?int
    {
        $matches = [];
        $string  = \mb_substr($string, $offset, null, 'UTF-8');
        if (! \preg_match($regex, $string, $matches, \PREG_OFFSET_CAPTURE)) {
            return null;
        }

        // PREG_OFFSET_CAPTURE always returns the byte offset, not the char offset, which is annoying
        $charPos = \mb_strlen(\mb_strcut($string, 0, $matches[0][1], 'UTF-8'), 'UTF-8');

        return $offset + $charPos;
    }

    /**
     * Functional wrapper around preg_match_all which only returns the first set of matches
     *
     * @psalm-param non-empty-string $pattern
     *
     * @return string[]|null
     *
     * @psalm-pure
     */
    public static function matchFirst(string $pattern, string $subject, int $offset = 0): ?array
    {
        if ($offset !== 0) {
            $subject = \substr($subject, $offset);
        }

        \preg_match_all($pattern, $subject, $matches, \PREG_SET_ORDER);

        if ($matches === []) {
            return null;
        }

        return $matches[0] ?: null;
    }

    /**
     * Replace backslash escapes with literal characters
     *
     * @psalm-pure
     */
    public static function unescape(string $string): string
    {
        $allEscapedChar = '/\\\\(' . self::PARTIAL_ESCAPABLE . ')/';

        $escaped = \preg_replace($allEscapedChar, '$1', $string);
        \assert(\is_string($escaped));

        return \preg_replace_callback('/' . self::PARTIAL_ENTITY . '/i', static fn ($e) => Html5EntityDecoder::decode($e[0]), $escaped);
    }

    /**
     * @internal
     *
     * @param int $type HTML block type
     *
     * @psalm-param HtmlBlock::TYPE_* $type
     *
     * @phpstan-param HtmlBlock::TYPE_* $type
     *
     * @psalm-return non-empty-string
     *
     * @throws InvalidArgumentException if an invalid type is given
     *
     * @psalm-pure
     */
    public static function getHtmlBlockOpenRegex(int $type): string
    {
        switch ($type) {
            case HtmlBlock::TYPE_1_CODE_CONTAINER:
                return '/^<(?:script|pre|textarea|style)(?:\s|>|$)/i';
            case HtmlBlock::TYPE_2_COMMENT:
                return '/^<!--/';
            case HtmlBlock::TYPE_3:
                return '/^<[?]/';
            case HtmlBlock::TYPE_4:
                return '/^<![A-Z]/i';
            case HtmlBlock::TYPE_5_CDATA:
                return '/^<!\[CDATA\[/i';
            case HtmlBlock::TYPE_6_BLOCK_ELEMENT:
                return '%^<[/]?(?:address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[123456]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?:\s|[/]?[>]|$)%i';
            case HtmlBlock::TYPE_7_MISC_ELEMENT:
                return '/^(?:' . self::PARTIAL_OPENTAG . '|' . self::PARTIAL_CLOSETAG . ')\\s*$/i';
            default:
                throw new InvalidArgumentException('Invalid HTML block type');
        }
    }

    /**
     * @internal
     *
     * @param int $type HTML block type
     *
     * @psalm-param HtmlBlock::TYPE_* $type
     *
     * @phpstan-param HtmlBlock::TYPE_* $type
     *
     * @psalm-return non-empty-string
     *
     * @throws InvalidArgumentException if an invalid type is given
     *
     * @psalm-pure
     */
    public static function getHtmlBlockCloseRegex(int $type): string
    {
        switch ($type) {
            case HtmlBlock::TYPE_1_CODE_CONTAINER:
                return '%<\/(?:script|pre|textarea|style)>%i';
            case HtmlBlock::TYPE_2_COMMENT:
                return '/-->/';
            case HtmlBlock::TYPE_3:
                return '/\?>/';
            case HtmlBlock::TYPE_4:
                return '/>/';
            case HtmlBlock::TYPE_5_CDATA:
                return '/\]\]>/';
            default:
                throw new InvalidArgumentException('Invalid HTML block type');
        }
    }

    /**
     * @psalm-pure
     */
    public static function isLinkPotentiallyUnsafe(string $url): bool
    {
        return \preg_match(self::REGEX_UNSAFE_PROTOCOL, $url) !== 0 && \preg_match(self::REGEX_SAFE_DATA_PROTOCOL, $url) === 0;
    }
}
Html5EntityDecoder.php000064400000003427151521003530010732 0ustar00<?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\Util;

/**
 * @psalm-immutable
 */
final class Html5EntityDecoder
{
    /**
     * @psalm-pure
     */
    public static function decode(string $entity): string
    {
        if (\substr($entity, -1) !== ';') {
            return $entity;
        }

        if (\substr($entity, 0, 2) === '&#') {
            if (\strtolower(\substr($entity, 2, 1)) === 'x') {
                return self::fromHex(\substr($entity, 3, -1));
            }

            return self::fromDecimal(\substr($entity, 2, -1));
        }

        return \html_entity_decode($entity, \ENT_QUOTES | \ENT_HTML5, 'UTF-8');
    }

    /**
     * @param mixed $number
     *
     * @psalm-pure
     */
    private static function fromDecimal($number): string
    {
        // Only convert code points within planes 0-2, excluding NULL
        // phpcs:ignore Generic.PHP.ForbiddenFunctions.Found
        if (empty($number) || $number > 0x2FFFF) {
            return self::fromHex('fffd');
        }

        $entity = '&#' . $number . ';';

        $converted = \mb_decode_numericentity($entity, [0x0, 0x2FFFF, 0, 0xFFFF], 'UTF-8');

        if ($converted === $entity) {
            return self::fromHex('fffd');
        }

        return $converted;
    }

    /**
     * @psalm-pure
     */
    private static function fromHex(string $hexChars): string
    {
        return self::fromDecimal(\hexdec($hexChars));
    }
}
HtmlFilter.php000064400000002674151521003530007333 0ustar00<?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\Util;

use League\CommonMark\Exception\InvalidArgumentException;

/**
 * @psalm-immutable
 */
final class HtmlFilter
{
    // Return the entire string as-is
    public const ALLOW = 'allow';
    // Escape the entire string so any HTML/JS won't be interpreted as such
    public const ESCAPE = 'escape';
    // Return an empty string
    public const STRIP = 'strip';

    /**
     * Runs the given HTML through the given filter
     *
     * @param string $html   HTML input to be filtered
     * @param string $filter One of the HtmlFilter constants
     *
     * @return string Filtered HTML
     *
     * @throws InvalidArgumentException when an invalid $filter is given
     *
     * @psalm-pure
     */
    public static function filter(string $html, string $filter): string
    {
        switch ($filter) {
            case self::STRIP:
                return '';
            case self::ESCAPE:
                return \htmlspecialchars($html, \ENT_NOQUOTES);
            case self::ALLOW:
                return $html;
            default:
                throw new InvalidArgumentException(\sprintf('Invalid filter provided: "%s"', $filter));
        }
    }
}
ArrayCollection.php000064400000006624151521003530010352 0ustar00<?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\Util;

/**
 * Array collection
 *
 * Provides a wrapper around a standard PHP array.
 *
 * @internal
 *
 * @phpstan-template T
 * @phpstan-implements \IteratorAggregate<int, T>
 * @phpstan-implements \ArrayAccess<int, T>
 */
final class ArrayCollection implements \IteratorAggregate, \Countable, \ArrayAccess
{
    /**
     * @var array<int, mixed>
     * @phpstan-var array<int, T>
     */
    private array $elements;

    /**
     * Constructor
     *
     * @param array<int|string, mixed> $elements
     *
     * @phpstan-param array<int, T> $elements
     */
    public function __construct(array $elements = [])
    {
        $this->elements = $elements;
    }

    /**
     * @return mixed|false
     *
     * @phpstan-return T|false
     */
    public function first()
    {
        return \reset($this->elements);
    }

    /**
     * @return mixed|false
     *
     * @phpstan-return T|false
     */
    public function last()
    {
        return \end($this->elements);
    }

    /**
     * Retrieve an external iterator
     *
     * @return \ArrayIterator<int, mixed>
     *
     * @phpstan-return \ArrayIterator<int, T>
     */
    #[\ReturnTypeWillChange]
    public function getIterator(): \ArrayIterator
    {
        return new \ArrayIterator($this->elements);
    }

    /**
     * Count elements of an object
     *
     * @return int The count as an integer.
     */
    public function count(): int
    {
        return \count($this->elements);
    }

    /**
     * Whether an offset exists
     *
     * {@inheritDoc}
     *
     * @phpstan-param int $offset
     */
    public function offsetExists($offset): bool
    {
        return \array_key_exists($offset, $this->elements);
    }

    /**
     * Offset to retrieve
     *
     * {@inheritDoc}
     *
     * @phpstan-param int $offset
     *
     * @phpstan-return T|null
     */
    #[\ReturnTypeWillChange]
    public function offsetGet($offset)
    {
        return $this->elements[$offset] ?? null;
    }

    /**
     * Offset to set
     *
     * {@inheritDoc}
     *
     * @phpstan-param int|null $offset
     * @phpstan-param T        $value
     */
    #[\ReturnTypeWillChange]
    public function offsetSet($offset, $value): void
    {
        if ($offset === null) {
            $this->elements[] = $value;
        } else {
            $this->elements[$offset] = $value;
        }
    }

    /**
     * Offset to unset
     *
     * {@inheritDoc}
     *
     * @phpstan-param int $offset
     */
    #[\ReturnTypeWillChange]
    public function offsetUnset($offset): void
    {
        if (! \array_key_exists($offset, $this->elements)) {
            return;
        }

        unset($this->elements[$offset]);
    }

    /**
     * Returns a subset of the array
     *
     * @return array<int, mixed>
     *
     * @phpstan-return array<int, T>
     */
    public function slice(int $offset, ?int $length = null): array
    {
        return \array_slice($this->elements, $offset, $length, true);
    }

    /**
     * @return array<int, mixed>
     *
     * @phpstan-return array<int, T>
     */
    public function toArray(): array
    {
        return $this->elements;
    }
}
Xml.php000064400000001342151521003530006010 0ustar00<?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\Util;

/**
 * Utility class for handling/generating XML and HTML
 *
 * @psalm-immutable
 */
final class Xml
{
    /**
     * @psalm-pure
     */
    public static function escape(string $string): string
    {
        return \str_replace(['&', '<', '>', '"'], ['&amp;', '&lt;', '&gt;', '&quot;'], $string);
    }
}
HtmlElement.php000064400000010054151521003530007466 0ustar00<?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\Util;

final class HtmlElement implements \Stringable
{
    /** @psalm-readonly */
    private string $tagName;

    /** @var array<string, string|bool> */
    private array $attributes = [];

    /** @var \Stringable|\Stringable[]|string */
    private $contents;

    /** @psalm-readonly */
    private bool $selfClosing;

    /**
     * @param string                                $tagName     Name of the HTML tag
     * @param array<string, string|string[]|bool>   $attributes  Array of attributes (values should be unescaped)
     * @param \Stringable|\Stringable[]|string|null $contents    Inner contents, pre-escaped if needed
     * @param bool                                  $selfClosing Whether the tag is self-closing
     */
    public function __construct(string $tagName, array $attributes = [], $contents = '', bool $selfClosing = false)
    {
        $this->tagName     = $tagName;
        $this->selfClosing = $selfClosing;

        foreach ($attributes as $name => $value) {
            $this->setAttribute($name, $value);
        }

        $this->setContents($contents ?? '');
    }

    /** @psalm-immutable */
    public function getTagName(): string
    {
        return $this->tagName;
    }

    /**
     * @return array<string, string|bool>
     *
     * @psalm-immutable
     */
    public function getAllAttributes(): array
    {
        return $this->attributes;
    }

    /**
     * @return string|bool|null
     *
     * @psalm-immutable
     */
    public function getAttribute(string $key)
    {
        return $this->attributes[$key] ?? null;
    }

    /**
     * @param string|string[]|bool $value
     */
    public function setAttribute(string $key, $value = true): self
    {
        if (\is_array($value)) {
            $this->attributes[$key] = \implode(' ', \array_unique($value));
        } else {
            $this->attributes[$key] = $value;
        }

        return $this;
    }

    /**
     * @return \Stringable|\Stringable[]|string
     *
     * @psalm-immutable
     */
    public function getContents(bool $asString = true)
    {
        if (! $asString) {
            return $this->contents;
        }

        return $this->getContentsAsString();
    }

    /**
     * Sets the inner contents of the tag (must be pre-escaped if needed)
     *
     * @param \Stringable|\Stringable[]|string $contents
     *
     * @return $this
     */
    public function setContents($contents): self
    {
        $this->contents = $contents ?? ''; // @phpstan-ignore-line

        return $this;
    }

    /** @psalm-immutable */
    public function __toString(): string
    {
        $result = '<' . $this->tagName;

        foreach ($this->attributes as $key => $value) {
            if ($value === true) {
                $result .= ' ' . $key;
            } elseif ($value === false) {
                continue;
            } else {
                $result .= ' ' . $key . '="' . Xml::escape($value) . '"';
            }
        }

        if ($this->contents !== '') {
            $result .= '>' . $this->getContentsAsString() . '</' . $this->tagName . '>';
        } elseif ($this->selfClosing && $this->tagName === 'input') {
            $result .= '>';
        } elseif ($this->selfClosing) {
            $result .= ' />';
        } else {
            $result .= '></' . $this->tagName . '>';
        }

        return $result;
    }

    /** @psalm-immutable */
    private function getContentsAsString(): string
    {
        if (\is_string($this->contents)) {
            return $this->contents;
        }

        if (\is_array($this->contents)) {
            return \implode('', $this->contents);
        }

        return (string) $this->contents;
    }
}