PKnTZX ArrayCollection.phpnuW+A * * 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 * @phpstan-implements \ArrayAccess */ final class ArrayCollection implements \IteratorAggregate, \Countable, \ArrayAccess { /** * @var array * @phpstan-var array */ private array $elements; /** * Constructor * * @param array $elements * * @phpstan-param array $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 * * @phpstan-return \ArrayIterator */ #[\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 * * @phpstan-return array */ public function slice(int $offset, ?int $length = null): array { return \array_slice($this->elements, $offset, $length, true); } /** * @return array * * @phpstan-return array */ public function toArray(): array { return $this->elements; } } PKnTZ((RegexHelper.phpnuW+A * * 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|search|section|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 = ']*>'; public const PARTIAL_CDATA = ''; 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 = '/^[!"#$%&\'()*+,\-.\\/:;<=>?@\\[\\]\\\\^_`{|}~\p{P}\p{S}]/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; } public static function isWhitespace(string $character): bool { /** @psalm-suppress InvalidLiteralArgument */ return $character !== '' && \strpos(" \t\n\x0b\x0c\x0d", $character) !== false; } /** * @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 '/>/'; 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; } } PKnTZ)QiiPrioritizedList.phpnuW+A * * 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 */ final class PrioritizedList implements \IteratorAggregate { /** * @var array> * @phpstan-var array> */ private array $list = []; /** * @var \Traversable|null * @phpstan-var \Traversable|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 * * @phpstan-return \Traversable */ #[\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; } } PKnTZ w?Xml.phpnuW+A * * 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(['&', '<', '>', '"'], ['&', '<', '>', '"'], $string); } } PKnTZBR(; ; UrlEncoder.phpnuW+A * * 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; } } PKnTZMyHtml5EntityDecoder.phpnuW+A * * 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)); } } PKnTZy0@,,HtmlElement.phpnuW+A * * 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 */ private array $attributes = []; /** @var \Stringable|\Stringable[]|string */ private $contents; /** @psalm-readonly */ private bool $selfClosing; /** * @param string $tagName Name of the HTML tag * @param array $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 * * @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() . 'tagName . '>'; } elseif ($this->selfClosing && $this->tagName === 'input') { $result .= '>'; } elseif ($this->selfClosing) { $result .= ' />'; } else { $result .= '>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; } } PKnTZؼHtmlFilter.phpnuW+A * * 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)); } } } PKnTZ⦇zSpecReader.phpnuW+A * * 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 */ 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('/.*$/', '', $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) { \assert(isset($match[1], $match[2], $match[3])); 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 * * @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); } } PKnTZ'NNLinkParserHelper.phpnuW+A * * 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 ($cursor->getCurrentCharacter() === '<') { return self::parseDestinationBraces($cursor); } $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 { $remainder = $cursor->getRemainder(); $openParens = 0; $len = \strlen($remainder); for ($i = 0; $i < $len; $i++) { $c = $remainder[$i]; if ($c === '\\' && $i + 1 < $len && RegexHelper::isEscapable($remainder[$i + 1])) { $i++; } elseif ($c === '(') { $openParens++; // Limit to 32 nested parens for pathological cases if ($openParens > 32) { return null; } } elseif ($c === ')') { if ($openParens < 1) { break; } $openParens--; } elseif (\ord($c) <= 32 && RegexHelper::isWhitespace($c)) { break; } } if ($openParens !== 0) { return null; } if ($i === 0 && (! isset($c) || $c !== ')')) { return null; } $destination = \substr($remainder, 0, $i); $cursor->advanceBy(\mb_strlen($destination, 'UTF-8')); return $destination; } /** @var \WeakReference|null */ private static ?\WeakReference $lastCursor = null; private static bool $lastCursorLacksClosingBrace = false; private static function parseDestinationBraces(Cursor $cursor): ?string { // Optimization: If we've previously parsed this cursor and returned `null`, we know // that no closing brace exists, so we can skip the regex entirely. This helps avoid // certain pathological cases where the regex engine can take a very long time to // determine that no match exists. if (self::$lastCursor !== null && self::$lastCursor->get() === $cursor) { if (self::$lastCursorLacksClosingBrace) { return null; } } else { self::$lastCursor = \WeakReference::create($cursor); } if ($res = $cursor->match(RegexHelper::REGEX_LINK_DESTINATION_BRACES)) { self::$lastCursorLacksClosingBrace = false; // Chop off surrounding <..>: return UrlEncoder::unescapeAndEncode( RegexHelper::unescape(\substr($res, 1, -1)) ); } self::$lastCursorLacksClosingBrace = true; return null; } } PKjZW'  Regex.phpnuW+A */ 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 */ 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 */ 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 */ 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 */ private static function pregAndWrap(callable $operation, string $subject) { $result = $operation($subject); if (\preg_last_error() !== \PREG_NO_ERROR) { /** @var \GrahamCampbell\ResultType\Result */ return Error::create(\preg_last_error_msg()); } /** @var \GrahamCampbell\ResultType\Result */ return Success::create($result); } } PKjZژ Str.phpnuW+A */ public static function utf8(string $input, ?string $encoding = null) { if ($encoding !== null && !\in_array($encoding, \mb_list_encodings(), true)) { /** @var \GrahamCampbell\ResultType\Result */ 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 */ return Success::create($converted); } /** * Search for a given substring of the input. * * @param string $haystack * @param string $needle * * @return \PhpOption\Option */ public static function pos(string $haystack, string $needle) { /** @var \PhpOption\Option */ 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'); } } PKrnZFqqJson.phpnuW+AhasConstant($member)) { return ReflectionClassConstant::create($value, $member); } elseif ($filter & self::METHOD && $class->hasMethod($member)) { return $class->getMethod($member); } elseif ($filter & self::PROPERTY && $class->hasProperty($member)) { return $class->getProperty($member); } elseif ($filter & self::STATIC_PROPERTY && $class->hasProperty($member) && $class->getProperty($member)->isStatic()) { return $class->getProperty($member); } else { throw new RuntimeException(\sprintf('Unknown member %s on class %s', $member, \is_object($value) ? \get_class($value) : $value)); } } /** * Get a ReflectionClass (or ReflectionObject, or ReflectionNamespace) if possible. * * @throws \InvalidArgumentException if $value is not a namespace or class name or instance * * @param mixed $value * * @return \ReflectionClass|ReflectionNamespace */ private static function getClass($value) { if (\is_object($value)) { return new \ReflectionObject($value); } if (!\is_string($value)) { throw new \InvalidArgumentException('Mirror expects an object or class'); } if (\class_exists($value) || \interface_exists($value) || \trait_exists($value)) { return new \ReflectionClass($value); } $namespace = \preg_replace('/(^\\\\|\\\\$)/', '', $value); if (self::namespaceExists($namespace)) { return new ReflectionNamespace($namespace); } throw new \InvalidArgumentException('Unknown namespace, class or function: '.$value); } /** * Check declared namespaces for a given namespace. */ private static function namespaceExists(string $value): bool { return \in_array(\strtolower($value), self::getDeclaredNamespaces()); } /** * Get an array of all currently declared namespaces. * * Note that this relies on at least one function, class, interface, trait * or constant to have been declared in that namespace. */ private static function getDeclaredNamespaces(): array { $functions = \get_defined_functions(); $allNames = \array_merge( $functions['internal'], $functions['user'], \get_declared_classes(), \get_declared_interfaces(), \get_declared_traits(), \array_keys(\get_defined_constants()) ); $namespaces = []; foreach ($allNames as $name) { $chunks = \explode('\\', \strtolower($name)); // the last one is the function or class or whatever... \array_pop($chunks); while (!empty($chunks)) { $namespaces[\implode('\\', $chunks)] = true; \array_pop($chunks); } } $namespaceNames = \array_keys($namespaces); \sort($namespaceNames); return $namespaceNames; } } PKrnZ֙.c Docblock.phpnuW+A * @author Justin Hileman */ class Docblock { /** * Tags in the docblock that have a whitespace-delimited number of parameters * (such as `@param type var desc` and `@return type desc`) and the names of * those parameters. * * @var array */ public static $vectors = [ 'throws' => ['type', 'desc'], 'param' => ['type', 'var', 'desc'], 'return' => ['type', 'desc'], ]; protected $reflector; /** * The description of the symbol. * * @var string */ public $desc; /** * The tags defined in the docblock. * * The array has keys which are the tag names (excluding the @) and values * that are arrays, each of which is an entry for the tag. * * In the case where the tag name is defined in {@see DocBlock::$vectors} the * value within the tag-value array is an array in itself with keys as * described by {@see DocBlock::$vectors}. * * @var array */ public $tags; /** * The entire DocBlock comment that was parsed. * * @var string */ public $comment; /** * Docblock constructor. * * @param \Reflector $reflector */ public function __construct(\Reflector $reflector) { $this->reflector = $reflector; if ($reflector instanceof \ReflectionClass || $reflector instanceof \ReflectionClassConstant || $reflector instanceof \ReflectionFunctionAbstract || $reflector instanceof \ReflectionProperty) { $this->setComment($reflector->getDocComment()); } } /** * Set and parse the docblock comment. * * @param string $comment The docblock */ protected function setComment(string $comment) { $this->desc = ''; $this->tags = []; $this->comment = $comment; $this->parseComment($comment); } /** * Find the length of the docblock prefix. * * @param array $lines * * @return int Prefix length */ protected static function prefixLength(array $lines): int { // find only lines with interesting things $lines = \array_filter($lines, function ($line) { return \substr($line, \strspn($line, "* \t\n\r\0\x0B")); }); // if we sort the lines, we only have to compare two items \sort($lines); $first = \reset($lines); $last = \end($lines); // Special case for single-line comments if (\count($lines) === 1) { return \strspn($first, "* \t\n\r\0\x0B"); } // find the longest common substring $count = \min(\strlen($first), \strlen($last)); for ($i = 0; $i < $count; $i++) { if ($first[$i] !== $last[$i]) { return $i; } } return $count; } /** * Parse the comment into the component parts and set the state of the object. * * @param string $comment The docblock */ protected function parseComment(string $comment) { // Strip the opening and closing tags of the docblock $comment = \substr($comment, 3, -2); // Split into arrays of lines $comment = \array_filter(\preg_split('/\r?\n\r?/', $comment)); // Trim asterisks and whitespace from the beginning and whitespace from the end of lines $prefixLength = self::prefixLength($comment); $comment = \array_map(function ($line) use ($prefixLength) { return \rtrim(\substr($line, $prefixLength)); }, $comment); // Group the lines together by @tags $blocks = []; $b = -1; foreach ($comment as $line) { if (self::isTagged($line)) { $b++; $blocks[] = []; } elseif ($b === -1) { $b = 0; $blocks[] = []; } $blocks[$b][] = $line; } // Parse the blocks foreach ($blocks as $block => $body) { $body = \trim(\implode("\n", $body)); if ($block === 0 && !self::isTagged($body)) { // This is the description block $this->desc = $body; } else { // This block is tagged $tag = \substr(self::strTag($body), 1); $body = \ltrim(\substr($body, \strlen($tag) + 2)); if (isset(self::$vectors[$tag])) { // The tagged block is a vector $count = \count(self::$vectors[$tag]); if ($body) { $parts = \preg_split('/\s+/', $body, $count); } else { $parts = []; } // Default the trailing values $parts = \array_pad($parts, $count, null); // Store as a mapped array $this->tags[$tag][] = \array_combine(self::$vectors[$tag], $parts); } else { // The tagged block is only text $this->tags[$tag][] = $body; } } } } /** * Whether or not a docblock contains a given @tag. * * @param string $tag The name of the @tag to check for */ public function hasTag(string $tag): bool { return \is_array($this->tags) && \array_key_exists($tag, $this->tags); } /** * The value of a tag. * * @param string $tag * * @return array|null */ public function tag(string $tag) { // TODO: Add proper null-type return values once the lowest PHP version supported is 7.1 return $this->hasTag($tag) ? $this->tags[$tag] : null; } /** * Whether or not a string begins with a @tag. * * @param string $str */ public static function isTagged(string $str): bool { return isset($str[1]) && $str[0] === '@' && !\preg_match('/[^A-Za-z]/', $str[1]); } /** * The tag at the beginning of a string. * * @param string $str * * @return string|null */ public static function strTag(string $str) { if (\preg_match('/^@[a-z0-9_]+/', $str, $matches)) { return $matches[0]; } } } PKZZHtmlDumperOutput.phpnuW+A */ namespace Whoops\Util; /** * Used as output callable for Symfony\Component\VarDumper\Dumper\HtmlDumper::dump() * * @see TemplateHelper::dump() */ class HtmlDumperOutput { private $output; public function __invoke($line, $depth) { // A negative depth means "end of dump" if ($depth >= 0) { // Adds a two spaces indentation to the line $this->output .= str_repeat(' ', $depth) . $line . "\n"; } } public function getOutput() { return $this->output; } public function clear() { $this->output = null; } } PKZ %%TemplateHelper.phpnuW+A */ namespace Whoops\Util; use Symfony\Component\VarDumper\Caster\Caster; use Symfony\Component\VarDumper\Cloner\AbstractCloner; use Symfony\Component\VarDumper\Cloner\VarCloner; use Symfony\Component\VarDumper\Dumper\HtmlDumper; use Whoops\Exception\Frame; /** * Exposes useful tools for working with/in templates */ class TemplateHelper { /** * An array of variables to be passed to all templates * @var array */ private $variables = []; /** * @var HtmlDumper */ private $htmlDumper; /** * @var HtmlDumperOutput */ private $htmlDumperOutput; /** * @var AbstractCloner */ private $cloner; /** * @var string */ private $applicationRootPath; public function __construct() { // root path for ordinary composer projects $this->applicationRootPath = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))); } /** * Escapes a string for output in an HTML document * * @param string $raw * @return string */ public function escape($raw) { $flags = ENT_QUOTES; // HHVM has all constants defined, but only ENT_IGNORE // works at the moment if (defined("ENT_SUBSTITUTE") && !defined("HHVM_VERSION")) { $flags |= ENT_SUBSTITUTE; } else { // This is for 5.3. // The documentation warns of a potential security issue, // but it seems it does not apply in our case, because // we do not blacklist anything anywhere. $flags |= ENT_IGNORE; } $raw = str_replace(chr(9), ' ', $raw); return htmlspecialchars($raw, $flags, "UTF-8"); } /** * Escapes a string for output in an HTML document, but preserves * URIs within it, and converts them to clickable anchor elements. * * @param string $raw * @return string */ public function escapeButPreserveUris($raw) { $escaped = $this->escape($raw); return preg_replace( "@([A-z]+?://([-\w\.]+[-\w])+(:\d+)?(/([\w/_\.#-]*(\?\S+)?[^\.\s])?)?)@", "$1", $escaped ); } /** * Makes sure that the given string breaks on the delimiter. * * @param string $delimiter * @param string $s * @return string */ public function breakOnDelimiter($delimiter, $s) { $parts = explode($delimiter, $s); foreach ($parts as &$part) { $part = '' . $part . ''; } return implode($delimiter, $parts); } /** * Replace the part of the path that all files have in common. * * @param string $path * @return string */ public function shorten($path) { if ($this->applicationRootPath != "/") { $path = str_replace($this->applicationRootPath, '…', $path); } return $path; } private function getDumper() { if (!$this->htmlDumper && class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) { $this->htmlDumperOutput = new HtmlDumperOutput(); // re-use the same var-dumper instance, so it won't re-render the global styles/scripts on each dump. $this->htmlDumper = new HtmlDumper($this->htmlDumperOutput); $styles = [ 'default' => 'color:#FFFFFF; line-height:normal; font:12px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace !important; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: normal', 'num' => 'color:#BCD42A', 'const' => 'color: #4bb1b1;', 'str' => 'color:#BCD42A', 'note' => 'color:#ef7c61', 'ref' => 'color:#A0A0A0', 'public' => 'color:#FFFFFF', 'protected' => 'color:#FFFFFF', 'private' => 'color:#FFFFFF', 'meta' => 'color:#FFFFFF', 'key' => 'color:#BCD42A', 'index' => 'color:#ef7c61', ]; $this->htmlDumper->setStyles($styles); } return $this->htmlDumper; } /** * Format the given value into a human readable string. * * @param mixed $value * @return string */ public function dump($value) { $dumper = $this->getDumper(); if ($dumper) { // re-use the same DumpOutput instance, so it won't re-render the global styles/scripts on each dump. // exclude verbose information (e.g. exception stack traces) if (class_exists('Symfony\Component\VarDumper\Caster\Caster')) { $cloneVar = $this->getCloner()->cloneVar($value, Caster::EXCLUDE_VERBOSE); // Symfony VarDumper 2.6 Caster class dont exist. } else { $cloneVar = $this->getCloner()->cloneVar($value); } $dumper->dump( $cloneVar, $this->htmlDumperOutput ); $output = $this->htmlDumperOutput->getOutput(); $this->htmlDumperOutput->clear(); return $output; } return htmlspecialchars(print_r($value, true)); } /** * Format the args of the given Frame as a human readable html string * * @param Frame $frame * @return string the rendered html */ public function dumpArgs(Frame $frame) { // we support frame args only when the optional dumper is available if (!$this->getDumper()) { return ''; } $html = ''; $numFrames = count($frame->getArgs()); if ($numFrames > 0) { $html = '
    '; foreach ($frame->getArgs() as $j => $frameArg) { $html .= '
  1. '. $this->dump($frameArg) .'
  2. '; } $html .= '
'; } return $html; } /** * Convert a string to a slug version of itself * * @param string $original * @return string */ public function slug($original) { $slug = str_replace(" ", "-", $original); $slug = preg_replace('/[^\w\d\-\_]/i', '', $slug); return strtolower($slug); } /** * Given a template path, render it within its own scope. This * method also accepts an array of additional variables to be * passed to the template. * * @param string $template */ public function render($template, ?array $additionalVariables = null) { $variables = $this->getVariables(); // Pass the helper to the template: $variables["tpl"] = $this; if ($additionalVariables !== null) { $variables = array_replace($variables, $additionalVariables); } call_user_func(function () { extract(func_get_arg(1)); require func_get_arg(0); }, $template, $variables); } /** * Sets the variables to be passed to all templates rendered * by this template helper. */ public function setVariables(array $variables) { $this->variables = $variables; } /** * Sets a single template variable, by its name: * * @param string $variableName * @param mixed $variableValue */ public function setVariable($variableName, $variableValue) { $this->variables[$variableName] = $variableValue; } /** * Gets a single template variable, by its name, or * $defaultValue if the variable does not exist * * @param string $variableName * @param mixed $defaultValue * @return mixed */ public function getVariable($variableName, $defaultValue = null) { return isset($this->variables[$variableName]) ? $this->variables[$variableName] : $defaultValue; } /** * Unsets a single template variable, by its name * * @param string $variableName */ public function delVariable($variableName) { unset($this->variables[$variableName]); } /** * Returns all variables for this helper * * @return array */ public function getVariables() { return $this->variables; } /** * Set the cloner used for dumping variables. * * @param AbstractCloner $cloner */ public function setCloner($cloner) { $this->cloner = $cloner; } /** * Get the cloner used for dumping variables. * * @return AbstractCloner */ public function getCloner() { if (!$this->cloner) { $this->cloner = new VarCloner(); } return $this->cloner; } /** * Set the application root path. * * @param string $applicationRootPath */ public function setApplicationRootPath($applicationRootPath) { $this->applicationRootPath = $applicationRootPath; } /** * Return the application root path. * * @return string */ public function getApplicationRootPath() { return $this->applicationRootPath; } } PKZMisc.phpnuW+A */ namespace Whoops\Util; class Misc { /** * Can we at this point in time send HTTP headers? * * Currently this checks if we are even serving an HTTP request, * as opposed to running from a command line. * * If we are serving an HTTP request, we check if it's not too late. * * @return bool */ public static function canSendHeaders() { return isset($_SERVER["REQUEST_URI"]) && !headers_sent(); } public static function isAjaxRequest() { return ( !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'); } /** * Check, if possible, that this execution was triggered by a command line. * @return bool */ public static function isCommandLine() { return PHP_SAPI == 'cli'; } /** * Translate ErrorException code into the represented constant. * * @param int $error_code * @return string */ public static function translateErrorCode($error_code) { $constants = get_defined_constants(true); if (array_key_exists('Core', $constants)) { foreach ($constants['Core'] as $constant => $value) { if (substr($constant, 0, 2) == 'E_' && $value == $error_code) { return $constant; } } } return "E_UNKNOWN"; } /** * Determine if an error level is fatal (halts execution) * * @param int $level * @return bool */ public static function isLevelFatal($level) { $errors = E_ERROR; $errors |= E_PARSE; $errors |= E_CORE_ERROR; $errors |= E_CORE_WARNING; $errors |= E_COMPILE_ERROR; $errors |= E_COMPILE_WARNING; return ($level & $errors) > 0; } } PKZJ SystemFacade.phpnuW+A */ namespace Whoops\Util; class SystemFacade { /** * Turns on output buffering. * * @return bool */ public function startOutputBuffering() { return ob_start(); } /** * @param callable $handler * @param int $types * * @return callable|null */ public function setErrorHandler(callable $handler, $types = 'use-php-defaults') { // Since PHP 5.4 the constant E_ALL contains all errors (even E_STRICT) if ($types === 'use-php-defaults') { $types = E_ALL; } return set_error_handler($handler, $types); } /** * @param callable $handler * * @return callable|null */ public function setExceptionHandler(callable $handler) { return set_exception_handler($handler); } /** * @return void */ public function restoreExceptionHandler() { restore_exception_handler(); } /** * @return void */ public function restoreErrorHandler() { restore_error_handler(); } /** * @param callable $function * * @return void */ public function registerShutdownFunction(callable $function) { register_shutdown_function($function); } /** * @return string|false */ public function cleanOutputBuffer() { return ob_get_clean(); } /** * @return int */ public function getOutputBufferLevel() { return ob_get_level(); } /** * @return bool */ public function endOutputBuffering() { return ob_end_clean(); } /** * @return void */ public function flushOutputBuffer() { flush(); } /** * @return int */ public function getErrorReportingLevel() { return error_reporting(); } /** * @return array|null */ public function getLastError() { return error_get_last(); } /** * @param int $httpCode * * @return int */ public function setHttpResponseCode($httpCode) { if (!headers_sent()) { // Ensure that no 'location' header is present as otherwise this // will override the HTTP code being set here, and mask the // expected error page. header_remove('location'); } return http_response_code($httpCode); } /** * @param int $exitStatus */ public function stopExecution($exitStatus) { exit($exitStatus); } } PKEZ AttributesHelper.phpnuW+A * (c) 2015 Martin Hasoň * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Attributes\Util; use League\CommonMark\Node\Node; use League\CommonMark\Parser\Cursor; use League\CommonMark\Util\RegexHelper; /** * @internal */ final class AttributesHelper { private const SINGLE_ATTRIBUTE = '\s*([.]-?[_a-z][^\s.}]*|[#][^\s}]+|' . RegexHelper::PARTIAL_ATTRIBUTENAME . RegexHelper::PARTIAL_ATTRIBUTEVALUESPEC . ')\s*'; private const ATTRIBUTE_LIST = '/^{:?(' . self::SINGLE_ATTRIBUTE . ')+}/i'; /** * @return array */ public static function parseAttributes(Cursor $cursor): array { $state = $cursor->saveState(); $cursor->advanceToNextNonSpaceOrNewline(); // Quick check to see if we might have attributes if ($cursor->getCharacter() !== '{') { $cursor->restoreState($state); return []; } // Attempt to match the entire attribute list expression // While this is less performant than checking for '{' now and '}' later, it simplifies // matching individual attributes since they won't need to look ahead for the closing '}' // while dealing with the fact that attributes can technically contain curly braces. // So we'll just match the start and end braces up front. $attributeExpression = $cursor->match(self::ATTRIBUTE_LIST); if ($attributeExpression === null) { $cursor->restoreState($state); return []; } // Trim the leading '{' or '{:' and the trailing '}' $attributeExpression = \ltrim(\substr($attributeExpression, 1, -1), ':'); $attributeCursor = new Cursor($attributeExpression); /** @var array $attributes */ $attributes = []; while ($attribute = \trim((string) $attributeCursor->match('/^' . self::SINGLE_ATTRIBUTE . '/i'))) { if ($attribute[0] === '#') { $attributes['id'] = \substr($attribute, 1); continue; } if ($attribute[0] === '.') { $attributes['class'][] = \substr($attribute, 1); continue; } /** @psalm-suppress PossiblyUndefinedArrayOffset */ [$name, $value] = \explode('=', $attribute, 2); if ($value === 'true') { $attributes[$name] = true; continue; } $first = $value[0]; $last = \substr($value, -1); if (($first === '"' && $last === '"') || ($first === "'" && $last === "'") && \strlen($value) > 1) { $value = \substr($value, 1, -1); } if (\strtolower(\trim($name)) === 'class') { foreach (\array_filter(\explode(' ', \trim($value))) as $class) { $attributes['class'][] = $class; } } else { $attributes[\trim($name)] = \trim($value); } } if (isset($attributes['class'])) { $attributes['class'] = \implode(' ', (array) $attributes['class']); } return $attributes; } /** * @param Node|array $attributes1 * @param Node|array $attributes2 * * @return array */ public static function mergeAttributes($attributes1, $attributes2): array { $attributes = []; foreach ([$attributes1, $attributes2] as $arg) { if ($arg instanceof Node) { $arg = $arg->data->get('attributes'); } /** @var array $arg */ $arg = (array) $arg; if (isset($arg['class'])) { if (\is_string($arg['class'])) { $arg['class'] = \array_filter(\explode(' ', \trim($arg['class']))); } foreach ($arg['class'] as $class) { $attributes['class'][] = $class; } unset($arg['class']); } $attributes = \array_merge($attributes, $arg); } if (isset($attributes['class'])) { $attributes['class'] = \implode(' ', $attributes['class']); } return $attributes; } } PKZZ;;XliffUtils.phpnuW+A * * 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 */ 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(''); $tmpfile = tempnam(sys_get_temp_dir(), 'symfony'); register_shutdown_function(static function () use ($tmpfile) { @unlink($tmpfile); }); $schema = ' '; file_put_contents($tmpfile, ' '); } 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; } } PKZU^  ArrayConverter.phpnuW+A * * 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 */ 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, explode('.', $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); } } } } PKnTZX ArrayCollection.phpnuW+APKnTZ(( RegexHelper.phpnuW+APKnTZ)Qii6PrioritizedList.phpnuW+APKnTZ w?a=Xml.phpnuW+APKnTZBR(; ; z@UrlEncoder.phpnuW+APKnTZMyJHtml5EntityDecoder.phpnuW+APKnTZy0@,,PRHtmlElement.phpnuW+APKnTZؼbHtmlFilter.phpnuW+APKnTZ⦇zhSpecReader.phpnuW+APKnTZ'NNqLinkParserHelper.phpnuW+APKjZW'  BRegex.phpnuW+APKjZژ |Str.phpnuW+APKrnZFqqJson.phpnuW+APKrnZ_'I ;Mirror.phpnuW+APKrnZ֙.c 5Docblock.phpnuW+APKZZHtmlDumperOutput.phpnuW+APKZ %%TemplateHelper.phpnuW+APKZ_Misc.phpnuW+APKZJ ZSystemFacade.phpnuW+APKEZ )AttributesHelper.phpnuW+APKZZ;;`XliffUtils.phpnuW+APKZU^  3ArrayConverter.phpnuW+APK*?