/home/mip/public_html/img/credit/datatables/Extension.tar
ConfigurableExtensionInterface.php 0000644 00000001005 15152100360 0013360 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension;
use League\Config\ConfigurationBuilderInterface;
interface ConfigurableExtensionInterface extends ExtensionInterface
{
public function configureSchema(ConfigurationBuilderInterface $builder): void;
}
Mention/Mention.php 0000644 00000004002 15152100360 0010264 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Mention;
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
use League\CommonMark\Node\Inline\Text;
class Mention extends Link
{
private string $name;
private string $prefix;
private string $identifier;
public function __construct(string $name, string $prefix, string $identifier, ?string $label = null)
{
$this->name = $name;
$this->prefix = $prefix;
$this->identifier = $identifier;
parent::__construct('', $label ?? \sprintf('%s%s', $prefix, $identifier));
}
public function getLabel(): ?string
{
if (($labelNode = $this->findLabelNode()) === null) {
return null;
}
return $labelNode->getLiteral();
}
public function getIdentifier(): string
{
return $this->identifier;
}
public function getName(): ?string
{
return $this->name;
}
public function getPrefix(): string
{
return $this->prefix;
}
public function hasUrl(): bool
{
return $this->url !== '';
}
/**
* @return $this
*/
public function setLabel(string $label): self
{
if (($labelNode = $this->findLabelNode()) === null) {
$labelNode = new Text();
$this->prependChild($labelNode);
}
$labelNode->setLiteral($label);
return $this;
}
private function findLabelNode(): ?Text
{
foreach ($this->children() as $child) {
if ($child instanceof Text) {
return $child;
}
}
return null;
}
}
Mention/MentionExtension.php 0000644 00000005236 15152100360 0012173 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Mention;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Extension\ConfigurableExtensionInterface;
use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
use League\Config\ConfigurationBuilderInterface;
use League\Config\Exception\InvalidConfigurationException;
use Nette\Schema\Expect;
final class MentionExtension implements ConfigurableExtensionInterface
{
public function configureSchema(ConfigurationBuilderInterface $builder): void
{
$isAValidPartialRegex = static function (string $regex): bool {
$regex = '/' . $regex . '/i';
return @\preg_match($regex, '') !== false;
};
$builder->addSchema('mentions', Expect::arrayOf(
Expect::structure([
'prefix' => Expect::string()->required(),
'pattern' => Expect::string()->assert($isAValidPartialRegex, 'Pattern must not include starting/ending delimiters (like "/")')->required(),
'generator' => Expect::anyOf(
Expect::type(MentionGeneratorInterface::class),
Expect::string(),
Expect::type('callable')
)->required(),
])
));
}
public function register(EnvironmentBuilderInterface $environment): void
{
$mentions = $environment->getConfiguration()->get('mentions');
foreach ($mentions as $name => $mention) {
if ($mention['generator'] instanceof MentionGeneratorInterface) {
$environment->addInlineParser(new MentionParser($name, $mention['prefix'], $mention['pattern'], $mention['generator']));
} elseif (\is_string($mention['generator'])) {
$environment->addInlineParser(MentionParser::createWithStringTemplate($name, $mention['prefix'], $mention['pattern'], $mention['generator']));
} elseif (\is_callable($mention['generator'])) {
$environment->addInlineParser(MentionParser::createWithCallback($name, $mention['prefix'], $mention['pattern'], $mention['generator']));
} else {
throw new InvalidConfigurationException(\sprintf('The "generator" provided for the "%s" MentionParser configuration must be a string template, callable, or an object that implements %s.', $name, MentionGeneratorInterface::class));
}
}
}
}
Mention/MentionParser.php 0000644 00000005606 15152100360 0011454 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Mention;
use League\CommonMark\Extension\Mention\Generator\CallbackGenerator;
use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
use League\CommonMark\Extension\Mention\Generator\StringTemplateLinkGenerator;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
final class MentionParser implements InlineParserInterface
{
/** @psalm-readonly */
private string $name;
/** @psalm-readonly */
private string $prefix;
/** @psalm-readonly */
private string $identifierPattern;
/** @psalm-readonly */
private MentionGeneratorInterface $mentionGenerator;
public function __construct(string $name, string $prefix, string $identifierPattern, MentionGeneratorInterface $mentionGenerator)
{
$this->name = $name;
$this->prefix = $prefix;
$this->identifierPattern = $identifierPattern;
$this->mentionGenerator = $mentionGenerator;
}
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::join(
InlineParserMatch::string($this->prefix),
InlineParserMatch::regex($this->identifierPattern)
);
}
public function parse(InlineParserContext $inlineContext): bool
{
$cursor = $inlineContext->getCursor();
// The prefix must not have any other characters immediately prior
$previousChar = $cursor->peek(-1);
if ($previousChar !== null && \preg_match('/\w/', $previousChar)) {
// peek() doesn't modify the cursor, so no need to restore state first
return false;
}
[$prefix, $identifier] = $inlineContext->getSubMatches();
$mention = $this->mentionGenerator->generateMention(new Mention($this->name, $prefix, $identifier));
if ($mention === null) {
return false;
}
$cursor->advanceBy($inlineContext->getFullMatchLength());
$inlineContext->getContainer()->appendChild($mention);
return true;
}
public static function createWithStringTemplate(string $name, string $prefix, string $mentionRegex, string $urlTemplate): MentionParser
{
return new self($name, $prefix, $mentionRegex, new StringTemplateLinkGenerator($urlTemplate));
}
public static function createWithCallback(string $name, string $prefix, string $mentionRegex, callable $callback): MentionParser
{
return new self($name, $prefix, $mentionRegex, new CallbackGenerator($callback));
}
}
Mention/Generator/MentionGeneratorInterface.php 0000644 00000001036 15152100360 0015706 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Mention\Generator;
use League\CommonMark\Extension\Mention\Mention;
use League\CommonMark\Node\Inline\AbstractInline;
interface MentionGeneratorInterface
{
public function generateMention(Mention $mention): ?AbstractInline;
}
Mention/Generator/StringTemplateLinkGenerator.php 0000644 00000001516 15152100360 0016237 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Mention\Generator;
use League\CommonMark\Extension\Mention\Mention;
use League\CommonMark\Node\Inline\AbstractInline;
final class StringTemplateLinkGenerator implements MentionGeneratorInterface
{
private string $urlTemplate;
public function __construct(string $urlTemplate)
{
$this->urlTemplate = $urlTemplate;
}
public function generateMention(Mention $mention): ?AbstractInline
{
$mention->setUrl(\sprintf($this->urlTemplate, $mention->getIdentifier()));
return $mention;
}
}
Mention/Generator/CallbackGenerator.php 0000644 00000003107 15152100360 0014151 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Mention\Generator;
use League\CommonMark\Exception\LogicException;
use League\CommonMark\Extension\Mention\Mention;
use League\CommonMark\Node\Inline\AbstractInline;
final class CallbackGenerator implements MentionGeneratorInterface
{
/**
* A callback function which sets the URL on the passed mention and returns the mention, return a new AbstractInline based object or null if the mention is not a match
*
* @var callable(Mention): ?AbstractInline
*/
private $callback;
public function __construct(callable $callback)
{
$this->callback = $callback;
}
/**
* @throws LogicException
*/
public function generateMention(Mention $mention): ?AbstractInline
{
$result = \call_user_func($this->callback, $mention);
if ($result === null) {
return null;
}
if ($result instanceof AbstractInline && ! ($result instanceof Mention)) {
return $result;
}
if ($result instanceof Mention && $result->hasUrl()) {
return $mention;
}
throw new LogicException('CallbackGenerator callable must set the URL on the passed mention and return the mention, return a new AbstractInline based object or null if the mention is not a match');
}
}
HeadingPermalink/HeadingPermalinkRenderer.php 0000644 00000005741 15152100360 0015350 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\HeadingPermalink;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
/**
* Renders the HeadingPermalink elements
*/
final class HeadingPermalinkRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface
{
public const DEFAULT_SYMBOL = 'ΒΆ';
/** @psalm-readonly-allow-private-mutation */
private ConfigurationInterface $config;
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}
/**
* @param HeadingPermalink $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
HeadingPermalink::assertInstanceOf($node);
$slug = $node->getSlug();
$fragmentPrefix = (string) $this->config->get('heading_permalink/fragment_prefix');
if ($fragmentPrefix !== '') {
$fragmentPrefix .= '-';
}
$attrs = $node->data->getData('attributes');
$appendId = ! $this->config->get('heading_permalink/apply_id_to_heading');
if ($appendId) {
$idPrefix = (string) $this->config->get('heading_permalink/id_prefix');
if ($idPrefix !== '') {
$idPrefix .= '-';
}
$attrs->set('id', $idPrefix . $slug);
}
$attrs->set('href', '#' . $fragmentPrefix . $slug);
$attrs->append('class', $this->config->get('heading_permalink/html_class'));
$hidden = $this->config->get('heading_permalink/aria_hidden');
if ($hidden) {
$attrs->set('aria-hidden', 'true');
}
$attrs->set('title', $this->config->get('heading_permalink/title'));
$symbol = $this->config->get('heading_permalink/symbol');
\assert(\is_string($symbol));
return new HtmlElement('a', $attrs->export(), \htmlspecialchars($symbol), false);
}
public function getXmlTagName(Node $node): string
{
return 'heading_permalink';
}
/**
* @param HeadingPermalink $node
*
* @return array<string, scalar>
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function getXmlAttributes(Node $node): array
{
HeadingPermalink::assertInstanceOf($node);
return [
'slug' => $node->getSlug(),
];
}
}
HeadingPermalink/HeadingPermalinkProcessor.php 0000644 00000007353 15152100360 0015562 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\HeadingPermalink;
use League\CommonMark\Environment\EnvironmentAwareInterface;
use League\CommonMark\Environment\EnvironmentInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
use League\CommonMark\Node\NodeIterator;
use League\CommonMark\Node\RawMarkupContainerInterface;
use League\CommonMark\Node\StringContainerHelper;
use League\CommonMark\Normalizer\TextNormalizerInterface;
use League\Config\ConfigurationInterface;
use League\Config\Exception\InvalidConfigurationException;
/**
* Searches the Document for Heading elements and adds HeadingPermalinks to each one
*/
final class HeadingPermalinkProcessor implements EnvironmentAwareInterface
{
public const INSERT_BEFORE = 'before';
public const INSERT_AFTER = 'after';
public const INSERT_NONE = 'none';
/** @psalm-readonly-allow-private-mutation */
private TextNormalizerInterface $slugNormalizer;
/** @psalm-readonly-allow-private-mutation */
private ConfigurationInterface $config;
public function setEnvironment(EnvironmentInterface $environment): void
{
$this->config = $environment->getConfiguration();
$this->slugNormalizer = $environment->getSlugNormalizer();
}
public function __invoke(DocumentParsedEvent $e): void
{
$min = (int) $this->config->get('heading_permalink/min_heading_level');
$max = (int) $this->config->get('heading_permalink/max_heading_level');
$applyToHeading = (bool) $this->config->get('heading_permalink/apply_id_to_heading');
$idPrefix = (string) $this->config->get('heading_permalink/id_prefix');
$slugLength = (int) $this->config->get('slug_normalizer/max_length');
$headingClass = (string) $this->config->get('heading_permalink/heading_class');
if ($idPrefix !== '') {
$idPrefix .= '-';
}
foreach ($e->getDocument()->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) {
if ($node instanceof Heading && $node->getLevel() >= $min && $node->getLevel() <= $max) {
$this->addHeadingLink($node, $slugLength, $idPrefix, $applyToHeading, $headingClass);
}
}
}
private function addHeadingLink(Heading $heading, int $slugLength, string $idPrefix, bool $applyToHeading, string $headingClass): void
{
$text = StringContainerHelper::getChildText($heading, [RawMarkupContainerInterface::class]);
$slug = $this->slugNormalizer->normalize($text, [
'node' => $heading,
'length' => $slugLength,
]);
if ($applyToHeading) {
$heading->data->set('attributes/id', $idPrefix . $slug);
}
if ($headingClass !== '') {
$heading->data->append('attributes/class', $headingClass);
}
$headingLinkAnchor = new HeadingPermalink($slug);
switch ($this->config->get('heading_permalink/insert')) {
case self::INSERT_BEFORE:
$heading->prependChild($headingLinkAnchor);
return;
case self::INSERT_AFTER:
$heading->appendChild($headingLinkAnchor);
return;
case self::INSERT_NONE:
return;
default:
throw new InvalidConfigurationException("Invalid configuration value for heading_permalink/insert; expected 'before', 'after', or 'none'");
}
}
}
HeadingPermalink/HeadingPermalink.php 0000644 00000001346 15152100360 0013656 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\HeadingPermalink;
use League\CommonMark\Node\Inline\AbstractInline;
/**
* Represents an anchor link within a heading
*/
final class HeadingPermalink extends AbstractInline
{
/** @psalm-readonly */
private string $slug;
public function __construct(string $slug)
{
parent::__construct();
$this->slug = $slug;
}
public function getSlug(): string
{
return $this->slug;
}
}
HeadingPermalink/HeadingPermalinkExtension.php 0000644 00000004123 15152100360 0015547 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\HeadingPermalink;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\ConfigurableExtensionInterface;
use League\Config\ConfigurationBuilderInterface;
use Nette\Schema\Expect;
/**
* Extension which automatically anchor links to heading elements
*/
final class HeadingPermalinkExtension implements ConfigurableExtensionInterface
{
public function configureSchema(ConfigurationBuilderInterface $builder): void
{
$builder->addSchema('heading_permalink', Expect::structure([
'min_heading_level' => Expect::int()->min(1)->max(6)->default(1),
'max_heading_level' => Expect::int()->min(1)->max(6)->default(6),
'insert' => Expect::anyOf(HeadingPermalinkProcessor::INSERT_BEFORE, HeadingPermalinkProcessor::INSERT_AFTER, HeadingPermalinkProcessor::INSERT_NONE)->default(HeadingPermalinkProcessor::INSERT_BEFORE),
'id_prefix' => Expect::string()->default('content'),
'apply_id_to_heading' => Expect::bool()->default(false),
'heading_class' => Expect::string()->default(''),
'fragment_prefix' => Expect::string()->default('content'),
'html_class' => Expect::string()->default('heading-permalink'),
'title' => Expect::string()->default('Permalink'),
'symbol' => Expect::string()->default(HeadingPermalinkRenderer::DEFAULT_SYMBOL),
'aria_hidden' => Expect::bool()->default(true),
]));
}
public function register(EnvironmentBuilderInterface $environment): void
{
$environment->addEventListener(DocumentParsedEvent::class, new HeadingPermalinkProcessor(), -100);
$environment->addRenderer(HeadingPermalink::class, new HeadingPermalinkRenderer());
}
}
GithubFlavoredMarkdownExtension.php 0000644 00000002235 15152100360 0013555 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Extension\Autolink\AutolinkExtension;
use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
use League\CommonMark\Extension\Table\TableExtension;
use League\CommonMark\Extension\TaskList\TaskListExtension;
final class GithubFlavoredMarkdownExtension implements ExtensionInterface
{
public function register(EnvironmentBuilderInterface $environment): void
{
$environment->addExtension(new AutolinkExtension());
$environment->addExtension(new DisallowedRawHtmlExtension());
$environment->addExtension(new StrikethroughExtension());
$environment->addExtension(new TableExtension());
$environment->addExtension(new TaskListExtension());
}
}
TableOfContents/TableOfContentsBuilder.php 0000644 00000007364 15152100360 0014653 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\TableOfContents;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
use League\CommonMark\Extension\HeadingPermalink\HeadingPermalink;
use League\CommonMark\Extension\TableOfContents\Node\TableOfContents;
use League\CommonMark\Extension\TableOfContents\Node\TableOfContentsPlaceholder;
use League\CommonMark\Node\Block\Document;
use League\CommonMark\Node\NodeIterator;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
use League\Config\Exception\InvalidConfigurationException;
final class TableOfContentsBuilder implements ConfigurationAwareInterface
{
public const POSITION_TOP = 'top';
public const POSITION_BEFORE_HEADINGS = 'before-headings';
public const POSITION_PLACEHOLDER = 'placeholder';
/** @psalm-readonly-allow-private-mutation */
private ConfigurationInterface $config;
public function onDocumentParsed(DocumentParsedEvent $event): void
{
$document = $event->getDocument();
$generator = new TableOfContentsGenerator(
(string) $this->config->get('table_of_contents/style'),
(string) $this->config->get('table_of_contents/normalize'),
(int) $this->config->get('table_of_contents/min_heading_level'),
(int) $this->config->get('table_of_contents/max_heading_level'),
(string) $this->config->get('heading_permalink/fragment_prefix'),
);
$toc = $generator->generate($document);
if ($toc === null) {
// No linkable headers exist, so no TOC could be generated
return;
}
// Add custom CSS class(es), if defined
$class = $this->config->get('table_of_contents/html_class');
if ($class !== null) {
$toc->data->append('attributes/class', $class);
}
// Add the TOC to the Document
$position = $this->config->get('table_of_contents/position');
if ($position === self::POSITION_TOP) {
$document->prependChild($toc);
} elseif ($position === self::POSITION_BEFORE_HEADINGS) {
$this->insertBeforeFirstLinkedHeading($document, $toc);
} elseif ($position === self::POSITION_PLACEHOLDER) {
$this->replacePlaceholders($document, $toc);
} else {
throw InvalidConfigurationException::forConfigOption('table_of_contents/position', $position);
}
}
private function insertBeforeFirstLinkedHeading(Document $document, TableOfContents $toc): void
{
foreach ($document->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) {
if (! $node instanceof Heading) {
continue;
}
foreach ($node->children() as $child) {
if ($child instanceof HeadingPermalink) {
$node->insertBefore($toc);
return;
}
}
}
}
private function replacePlaceholders(Document $document, TableOfContents $toc): void
{
foreach ($document->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) {
// Add the block once we find a placeholder
if (! $node instanceof TableOfContentsPlaceholder) {
continue;
}
$node->replaceWith(clone $toc);
}
}
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}
}
TableOfContents/Node/TableOfContents.php 0000644 00000000701 15152100360 0014215 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\TableOfContents\Node;
use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock;
final class TableOfContents extends ListBlock
{
}
TableOfContents/Node/TableOfContentsPlaceholder.php 0000644 00000000677 15152100360 0016374 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\TableOfContents\Node;
use League\CommonMark\Node\Block\AbstractBlock;
final class TableOfContentsPlaceholder extends AbstractBlock
{
}
TableOfContents/TableOfContentsGenerator.php 0000644 00000013670 15152100360 0015210 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\TableOfContents;
use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock;
use League\CommonMark\Extension\CommonMark\Node\Block\ListData;
use League\CommonMark\Extension\CommonMark\Node\Block\ListItem;
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
use League\CommonMark\Extension\HeadingPermalink\HeadingPermalink;
use League\CommonMark\Extension\TableOfContents\Node\TableOfContents;
use League\CommonMark\Extension\TableOfContents\Normalizer\AsIsNormalizerStrategy;
use League\CommonMark\Extension\TableOfContents\Normalizer\FlatNormalizerStrategy;
use League\CommonMark\Extension\TableOfContents\Normalizer\NormalizerStrategyInterface;
use League\CommonMark\Extension\TableOfContents\Normalizer\RelativeNormalizerStrategy;
use League\CommonMark\Node\Block\Document;
use League\CommonMark\Node\NodeIterator;
use League\CommonMark\Node\RawMarkupContainerInterface;
use League\CommonMark\Node\StringContainerHelper;
use League\Config\Exception\InvalidConfigurationException;
final class TableOfContentsGenerator implements TableOfContentsGeneratorInterface
{
public const STYLE_BULLET = ListBlock::TYPE_BULLET;
public const STYLE_ORDERED = ListBlock::TYPE_ORDERED;
public const NORMALIZE_DISABLED = 'as-is';
public const NORMALIZE_RELATIVE = 'relative';
public const NORMALIZE_FLAT = 'flat';
/** @psalm-readonly */
private string $style;
/** @psalm-readonly */
private string $normalizationStrategy;
/** @psalm-readonly */
private int $minHeadingLevel;
/** @psalm-readonly */
private int $maxHeadingLevel;
/** @psalm-readonly */
private string $fragmentPrefix;
public function __construct(string $style, string $normalizationStrategy, int $minHeadingLevel, int $maxHeadingLevel, string $fragmentPrefix)
{
$this->style = $style;
$this->normalizationStrategy = $normalizationStrategy;
$this->minHeadingLevel = $minHeadingLevel;
$this->maxHeadingLevel = $maxHeadingLevel;
$this->fragmentPrefix = $fragmentPrefix;
if ($fragmentPrefix !== '') {
$this->fragmentPrefix .= '-';
}
}
public function generate(Document $document): ?TableOfContents
{
$toc = $this->createToc($document);
$normalizer = $this->getNormalizer($toc);
$firstHeading = null;
foreach ($this->getHeadingLinks($document) as $headingLink) {
$heading = $headingLink->parent();
// Make sure this is actually tied to a heading
if (! $heading instanceof Heading) {
continue;
}
// Skip any headings outside the configured min/max levels
if ($heading->getLevel() < $this->minHeadingLevel || $heading->getLevel() > $this->maxHeadingLevel) {
continue;
}
// Keep track of the first heading we see - we might need this later
$firstHeading ??= $heading;
// Keep track of the start and end lines
$toc->setStartLine($firstHeading->getStartLine());
$toc->setEndLine($heading->getEndLine());
// Create the new link
$link = new Link('#' . $this->fragmentPrefix . $headingLink->getSlug(), StringContainerHelper::getChildText($heading, [RawMarkupContainerInterface::class]));
$listItem = new ListItem($toc->getListData());
$listItem->setStartLine($heading->getStartLine());
$listItem->setEndLine($heading->getEndLine());
$listItem->appendChild($link);
// Add it to the correct place
$normalizer->addItem($heading->getLevel(), $listItem);
}
// Don't add the TOC if no headings were present
if (! $toc->hasChildren() || $firstHeading === null) {
return null;
}
return $toc;
}
private function createToc(Document $document): TableOfContents
{
$listData = new ListData();
if ($this->style === self::STYLE_BULLET) {
$listData->type = ListBlock::TYPE_BULLET;
} elseif ($this->style === self::STYLE_ORDERED) {
$listData->type = ListBlock::TYPE_ORDERED;
} else {
throw new InvalidConfigurationException(\sprintf('Invalid table of contents list style: "%s"', $this->style));
}
$toc = new TableOfContents($listData);
$toc->setStartLine($document->getStartLine());
$toc->setEndLine($document->getEndLine());
return $toc;
}
/**
* @return iterable<HeadingPermalink>
*/
private function getHeadingLinks(Document $document): iterable
{
foreach ($document->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) {
if (! $node instanceof Heading) {
continue;
}
foreach ($node->children() as $child) {
if ($child instanceof HeadingPermalink) {
yield $child;
}
}
}
}
private function getNormalizer(TableOfContents $toc): NormalizerStrategyInterface
{
switch ($this->normalizationStrategy) {
case self::NORMALIZE_DISABLED:
return new AsIsNormalizerStrategy($toc);
case self::NORMALIZE_RELATIVE:
return new RelativeNormalizerStrategy($toc);
case self::NORMALIZE_FLAT:
return new FlatNormalizerStrategy($toc);
default:
throw new InvalidConfigurationException(\sprintf('Invalid table of contents normalization strategy: "%s"', $this->normalizationStrategy));
}
}
}
TableOfContents/TableOfContentsGeneratorInterface.php 0000644 00000001056 15152100360 0017024 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\TableOfContents;
use League\CommonMark\Extension\TableOfContents\Node\TableOfContents;
use League\CommonMark\Node\Block\Document;
interface TableOfContentsGeneratorInterface
{
public function generate(Document $document): ?TableOfContents;
}
TableOfContents/TableOfContentsRenderer.php 0000644 00000002763 15152100360 0015031 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\TableOfContents;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class TableOfContentsRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/** @var NodeRendererInterface&XmlNodeRendererInterface */
private $innerRenderer;
/**
* @psalm-param NodeRendererInterface&XmlNodeRendererInterface $innerRenderer
*
* @phpstan-param NodeRendererInterface&XmlNodeRendererInterface $innerRenderer
*/
public function __construct(NodeRendererInterface $innerRenderer)
{
$this->innerRenderer = $innerRenderer;
}
/**
* {@inheritDoc}
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer)
{
return $this->innerRenderer->render($node, $childRenderer);
}
public function getXmlTagName(Node $node): string
{
return 'table_of_contents';
}
/**
* @return array<string, scalar>
*/
public function getXmlAttributes(Node $node): array
{
return $this->innerRenderer->getXmlAttributes($node);
}
}
TableOfContents/TableOfContentsPlaceholderRenderer.php 0000644 00000002020 15152100360 0017156 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\TableOfContents;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class TableOfContentsPlaceholderRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
public function render(Node $node, ChildNodeRendererInterface $childRenderer): string
{
return '<!-- table of contents -->';
}
public function getXmlTagName(Node $node): string
{
return 'table_of_contents_placeholder';
}
/**
* @return array<string, scalar>
*/
public function getXmlAttributes(Node $node): array
{
return [];
}
}
TableOfContents/TableOfContentsPlaceholderParser.php 0000644 00000004747 15152100360 0016666 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\TableOfContents;
use League\CommonMark\Extension\TableOfContents\Node\TableOfContentsPlaceholder;
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
use League\CommonMark\Parser\Block\BlockContinue;
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
use League\CommonMark\Parser\Block\BlockStart;
use League\CommonMark\Parser\Block\BlockStartParserInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\MarkdownParserStateInterface;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class TableOfContentsPlaceholderParser extends AbstractBlockContinueParser
{
/** @psalm-readonly */
private TableOfContentsPlaceholder $block;
public function __construct()
{
$this->block = new TableOfContentsPlaceholder();
}
public function getBlock(): TableOfContentsPlaceholder
{
return $this->block;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
return BlockContinue::none();
}
public static function blockStartParser(): BlockStartParserInterface
{
return new class () implements BlockStartParserInterface, ConfigurationAwareInterface {
/** @psalm-readonly-allow-private-mutation */
private ConfigurationInterface $config;
public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
{
$placeholder = $this->config->get('table_of_contents/placeholder');
if ($placeholder === null) {
return BlockStart::none();
}
// The placeholder must be the only thing on the line
if ($cursor->match('/^' . \preg_quote($placeholder, '/') . '$/') === null) {
return BlockStart::none();
}
return BlockStart::of(new TableOfContentsPlaceholderParser())->at($cursor);
}
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}
};
}
}
TableOfContents/Normalizer/RelativeNormalizerStrategy.php 0000644 00000004254 15152100360 0017770 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\TableOfContents\Normalizer;
use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock;
use League\CommonMark\Extension\CommonMark\Node\Block\ListItem;
use League\CommonMark\Extension\TableOfContents\Node\TableOfContents;
final class RelativeNormalizerStrategy implements NormalizerStrategyInterface
{
/** @psalm-readonly */
private TableOfContents $toc;
/**
* @var array<int, ListItem>
*
* @psalm-readonly-allow-private-mutation
*/
private array $listItemStack = [];
public function __construct(TableOfContents $toc)
{
$this->toc = $toc;
}
public function addItem(int $level, ListItem $listItemToAdd): void
{
$previousLevel = \array_key_last($this->listItemStack);
// Pop the stack if we're too deep
while ($previousLevel !== null && $level < $previousLevel) {
\array_pop($this->listItemStack);
$previousLevel = \array_key_last($this->listItemStack);
}
$lastListItem = \end($this->listItemStack);
// Need to go one level deeper? Add that level
if ($lastListItem !== false && $level > $previousLevel) {
$targetListBlock = new ListBlock($lastListItem->getListData());
$targetListBlock->setStartLine($listItemToAdd->getStartLine());
$targetListBlock->setEndLine($listItemToAdd->getEndLine());
$lastListItem->appendChild($targetListBlock);
// Otherwise we're at the right level
// If there's no stack we're adding this item directly to the TOC element
} elseif ($lastListItem === false) {
$targetListBlock = $this->toc;
// Otherwise add it to the last list item
} else {
$targetListBlock = $lastListItem->parent();
}
$targetListBlock->appendChild($listItemToAdd);
$this->listItemStack[$level] = $listItemToAdd;
}
}
TableOfContents/Normalizer/NormalizerStrategyInterface.php 0000644 00000001006 15152100360 0020105 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\TableOfContents\Normalizer;
use League\CommonMark\Extension\CommonMark\Node\Block\ListItem;
interface NormalizerStrategyInterface
{
public function addItem(int $level, ListItem $listItemToAdd): void;
}
TableOfContents/Normalizer/AsIsNormalizerStrategy.php 0000644 00000004545 15152100360 0017057 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\TableOfContents\Normalizer;
use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock;
use League\CommonMark\Extension\CommonMark\Node\Block\ListItem;
use League\CommonMark\Extension\TableOfContents\Node\TableOfContents;
final class AsIsNormalizerStrategy implements NormalizerStrategyInterface
{
/** @psalm-readonly-allow-private-mutation */
private ListBlock $parentListBlock;
/** @psalm-readonly-allow-private-mutation */
private int $parentLevel = 1;
/** @psalm-readonly-allow-private-mutation */
private ?ListItem $lastListItem = null;
public function __construct(TableOfContents $toc)
{
$this->parentListBlock = $toc;
}
public function addItem(int $level, ListItem $listItemToAdd): void
{
while ($level > $this->parentLevel) {
// Descend downwards, creating new ListBlocks if needed, until we reach the correct depth
if ($this->lastListItem === null) {
$this->lastListItem = new ListItem($this->parentListBlock->getListData());
$this->parentListBlock->appendChild($this->lastListItem);
}
$newListBlock = new ListBlock($this->parentListBlock->getListData());
$newListBlock->setStartLine($listItemToAdd->getStartLine());
$newListBlock->setEndLine($listItemToAdd->getEndLine());
$this->lastListItem->appendChild($newListBlock);
$this->parentListBlock = $newListBlock;
$this->lastListItem = null;
$this->parentLevel++;
}
while ($level < $this->parentLevel) {
// Search upwards for the previous parent list block
$search = $this->parentListBlock;
while ($search = $search->parent()) {
if ($search instanceof ListBlock) {
$this->parentListBlock = $search;
break;
}
}
$this->parentLevel--;
}
$this->parentListBlock->appendChild($listItemToAdd);
$this->lastListItem = $listItemToAdd;
}
}
TableOfContents/Normalizer/FlatNormalizerStrategy.php 0000644 00000001510 15152100360 0017073 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\TableOfContents\Normalizer;
use League\CommonMark\Extension\CommonMark\Node\Block\ListItem;
use League\CommonMark\Extension\TableOfContents\Node\TableOfContents;
final class FlatNormalizerStrategy implements NormalizerStrategyInterface
{
/** @psalm-readonly */
private TableOfContents $toc;
public function __construct(TableOfContents $toc)
{
$this->toc = $toc;
}
public function addItem(int $level, ListItem $listItemToAdd): void
{
$this->toc->appendChild($listItemToAdd);
}
}
TableOfContents/TableOfContentsExtension.php 0000644 00000005572 15152100360 0015240 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\TableOfContents;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock;
use League\CommonMark\Extension\CommonMark\Renderer\Block\ListBlockRenderer;
use League\CommonMark\Extension\ConfigurableExtensionInterface;
use League\CommonMark\Extension\TableOfContents\Node\TableOfContents;
use League\CommonMark\Extension\TableOfContents\Node\TableOfContentsPlaceholder;
use League\Config\ConfigurationBuilderInterface;
use Nette\Schema\Expect;
final class TableOfContentsExtension implements ConfigurableExtensionInterface
{
public function configureSchema(ConfigurationBuilderInterface $builder): void
{
$builder->addSchema('table_of_contents', Expect::structure([
'position' => Expect::anyOf(TableOfContentsBuilder::POSITION_BEFORE_HEADINGS, TableOfContentsBuilder::POSITION_PLACEHOLDER, TableOfContentsBuilder::POSITION_TOP)->default(TableOfContentsBuilder::POSITION_TOP),
'style' => Expect::anyOf(ListBlock::TYPE_BULLET, ListBlock::TYPE_ORDERED)->default(ListBlock::TYPE_BULLET),
'normalize' => Expect::anyOf(TableOfContentsGenerator::NORMALIZE_RELATIVE, TableOfContentsGenerator::NORMALIZE_FLAT, TableOfContentsGenerator::NORMALIZE_DISABLED)->default(TableOfContentsGenerator::NORMALIZE_RELATIVE),
'min_heading_level' => Expect::int()->min(1)->max(6)->default(1),
'max_heading_level' => Expect::int()->min(1)->max(6)->default(6),
'html_class' => Expect::string()->default('table-of-contents'),
'placeholder' => Expect::anyOf(Expect::string(), Expect::null())->default(null),
]));
}
public function register(EnvironmentBuilderInterface $environment): void
{
$environment->addRenderer(TableOfContents::class, new TableOfContentsRenderer(new ListBlockRenderer()));
$environment->addEventListener(DocumentParsedEvent::class, [new TableOfContentsBuilder(), 'onDocumentParsed'], -150);
// phpcs:ignore SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed
if ($environment->getConfiguration()->get('table_of_contents/position') === TableOfContentsBuilder::POSITION_PLACEHOLDER) {
$environment->addBlockStartParser(TableOfContentsPlaceholderParser::blockStartParser(), 200);
// If a placeholder cannot be replaced with a TOC element this renderer will ensure the parser won't error out
$environment->addRenderer(TableOfContentsPlaceholder::class, new TableOfContentsPlaceholderRenderer());
}
}
}
InlinesOnly/InlinesOnlyExtension.php 0000644 00000007203 15152100360 0013653 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\InlinesOnly;
use League\CommonMark as Core;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Extension\CommonMark;
use League\CommonMark\Extension\CommonMark\Delimiter\Processor\EmphasisDelimiterProcessor;
use League\CommonMark\Extension\ConfigurableExtensionInterface;
use League\Config\ConfigurationBuilderInterface;
use Nette\Schema\Expect;
final class InlinesOnlyExtension implements ConfigurableExtensionInterface
{
public function configureSchema(ConfigurationBuilderInterface $builder): void
{
$builder->addSchema('commonmark', Expect::structure([
'use_asterisk' => Expect::bool(true),
'use_underscore' => Expect::bool(true),
'enable_strong' => Expect::bool(true),
'enable_em' => Expect::bool(true),
]));
}
// phpcs:disable Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma,Squiz.WhiteSpace.SemicolonSpacing.Incorrect
public function register(EnvironmentBuilderInterface $environment): void
{
$childRenderer = new ChildRenderer();
$environment
->addInlineParser(new Core\Parser\Inline\NewlineParser(), 200)
->addInlineParser(new CommonMark\Parser\Inline\BacktickParser(), 150)
->addInlineParser(new CommonMark\Parser\Inline\EscapableParser(), 80)
->addInlineParser(new CommonMark\Parser\Inline\EntityParser(), 70)
->addInlineParser(new CommonMark\Parser\Inline\AutolinkParser(), 50)
->addInlineParser(new CommonMark\Parser\Inline\HtmlInlineParser(), 40)
->addInlineParser(new CommonMark\Parser\Inline\CloseBracketParser(), 30)
->addInlineParser(new CommonMark\Parser\Inline\OpenBracketParser(), 20)
->addInlineParser(new CommonMark\Parser\Inline\BangParser(), 10)
->addRenderer(Core\Node\Block\Document::class, $childRenderer, 0)
->addRenderer(Core\Node\Block\Paragraph::class, $childRenderer, 0)
->addRenderer(CommonMark\Node\Inline\Code::class, new CommonMark\Renderer\Inline\CodeRenderer(), 0)
->addRenderer(CommonMark\Node\Inline\Emphasis::class, new CommonMark\Renderer\Inline\EmphasisRenderer(), 0)
->addRenderer(CommonMark\Node\Inline\HtmlInline::class, new CommonMark\Renderer\Inline\HtmlInlineRenderer(), 0)
->addRenderer(CommonMark\Node\Inline\Image::class, new CommonMark\Renderer\Inline\ImageRenderer(), 0)
->addRenderer(CommonMark\Node\Inline\Link::class, new CommonMark\Renderer\Inline\LinkRenderer(), 0)
->addRenderer(Core\Node\Inline\Newline::class, new Core\Renderer\Inline\NewlineRenderer(), 0)
->addRenderer(CommonMark\Node\Inline\Strong::class, new CommonMark\Renderer\Inline\StrongRenderer(), 0)
->addRenderer(Core\Node\Inline\Text::class, new Core\Renderer\Inline\TextRenderer(), 0)
;
if ($environment->getConfiguration()->get('commonmark/use_asterisk')) {
$environment->addDelimiterProcessor(new EmphasisDelimiterProcessor('*'));
}
if ($environment->getConfiguration()->get('commonmark/use_underscore')) {
$environment->addDelimiterProcessor(new EmphasisDelimiterProcessor('_'));
}
}
}
InlinesOnly/ChildRenderer.php 0000644 00000001665 15152100360 0012233 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\InlinesOnly;
use League\CommonMark\Node\Block\Document;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
/**
* Simply renders child elements as-is, adding newlines as needed.
*/
final class ChildRenderer implements NodeRendererInterface
{
public function render(Node $node, ChildNodeRendererInterface $childRenderer): string
{
$out = $childRenderer->renderNodes($node->children());
if (! $node instanceof Document) {
$out .= $childRenderer->getBlockSeparator();
}
return $out;
}
}
FrontMatter/Input/MarkdownInputWithFrontMatter.php 0000644 00000002242 15152100360 0016436 0 ustar 00 <?php
/*
* 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.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\FrontMatter\Input;
use League\CommonMark\Extension\FrontMatter\FrontMatterProviderInterface;
use League\CommonMark\Input\MarkdownInput;
final class MarkdownInputWithFrontMatter extends MarkdownInput implements FrontMatterProviderInterface
{
/** @var mixed|null */
private $frontMatter;
/**
* @param string $content Markdown content without the raw front matter
* @param int $lineOffset Line offset (based on number of front matter lines removed)
* @param mixed|null $frontMatter Parsed front matter
*/
public function __construct(string $content, int $lineOffset = 0, $frontMatter = null)
{
parent::__construct($content, $lineOffset);
$this->frontMatter = $frontMatter;
}
/**
* {@inheritDoc}
*/
public function getFrontMatter()
{
return $this->frontMatter;
}
}
FrontMatter/Listener/FrontMatterPostRenderListener.php 0000644 00000001747 15152100360 0017272 0 ustar 00 <?php
/*
* 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.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\FrontMatter\Listener;
use League\CommonMark\Event\DocumentRenderedEvent;
use League\CommonMark\Extension\FrontMatter\Output\RenderedContentWithFrontMatter;
final class FrontMatterPostRenderListener
{
public function __invoke(DocumentRenderedEvent $event): void
{
if ($event->getOutput()->getDocument()->data->get('front_matter', null) === null) {
return;
}
$frontMatter = $event->getOutput()->getDocument()->data->get('front_matter');
$event->replaceOutput(new RenderedContentWithFrontMatter(
$event->getOutput()->getDocument(),
$event->getOutput()->getContent(),
$frontMatter
));
}
}
FrontMatter/Listener/FrontMatterPreParser.php 0000644 00000001713 15152100360 0015373 0 ustar 00 <?php
/*
* 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.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\FrontMatter\Listener;
use League\CommonMark\Event\DocumentPreParsedEvent;
use League\CommonMark\Extension\FrontMatter\FrontMatterParserInterface;
final class FrontMatterPreParser
{
private FrontMatterParserInterface $parser;
public function __construct(FrontMatterParserInterface $parser)
{
$this->parser = $parser;
}
public function __invoke(DocumentPreParsedEvent $event): void
{
$content = $event->getMarkdown()->getContent();
$parsed = $this->parser->parse($content);
$event->getDocument()->data->set('front_matter', $parsed->getFrontMatter());
$event->replaceMarkdown($parsed);
}
}
FrontMatter/FrontMatterProviderInterface.php 0000644 00000000677 15152100360 0015326 0 ustar 00 <?php
/*
* 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.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\FrontMatter;
interface FrontMatterProviderInterface
{
/**
* @return mixed|null
*/
public function getFrontMatter();
}
FrontMatter/Output/RenderedContentWithFrontMatter.php 0000644 00000002320 15152100360 0017115 0 ustar 00 <?php
/*
* 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.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\FrontMatter\Output;
use League\CommonMark\Extension\FrontMatter\FrontMatterProviderInterface;
use League\CommonMark\Node\Block\Document;
use League\CommonMark\Output\RenderedContent;
/**
* @psalm-immutable
*/
final class RenderedContentWithFrontMatter extends RenderedContent implements FrontMatterProviderInterface
{
/**
* @var mixed
*
* @psalm-readonly
*/
private $frontMatter;
/**
* @param Document $document The parsed Document object
* @param string $content The final HTML
* @param mixed|null $frontMatter Any parsed front matter
*/
public function __construct(Document $document, string $content, $frontMatter)
{
parent::__construct($document, $content);
$this->frontMatter = $frontMatter;
}
/**
* {@inheritDoc}
*/
public function getFrontMatter()
{
return $this->frontMatter;
}
}
FrontMatter/FrontMatterExtension.php 0000644 00000003372 15152100360 0013662 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\FrontMatter;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Event\DocumentPreParsedEvent;
use League\CommonMark\Event\DocumentRenderedEvent;
use League\CommonMark\Extension\ExtensionInterface;
use League\CommonMark\Extension\FrontMatter\Data\FrontMatterDataParserInterface;
use League\CommonMark\Extension\FrontMatter\Data\LibYamlFrontMatterParser;
use League\CommonMark\Extension\FrontMatter\Data\SymfonyYamlFrontMatterParser;
use League\CommonMark\Extension\FrontMatter\Listener\FrontMatterPostRenderListener;
use League\CommonMark\Extension\FrontMatter\Listener\FrontMatterPreParser;
final class FrontMatterExtension implements ExtensionInterface
{
/** @psalm-readonly */
private FrontMatterParserInterface $frontMatterParser;
public function __construct(?FrontMatterDataParserInterface $dataParser = null)
{
$this->frontMatterParser = new FrontMatterParser($dataParser ?? LibYamlFrontMatterParser::capable() ?? new SymfonyYamlFrontMatterParser());
}
public function getFrontMatterParser(): FrontMatterParserInterface
{
return $this->frontMatterParser;
}
public function register(EnvironmentBuilderInterface $environment): void
{
$environment->addEventListener(DocumentPreParsedEvent::class, new FrontMatterPreParser($this->frontMatterParser));
$environment->addEventListener(DocumentRenderedEvent::class, new FrontMatterPostRenderListener(), -500);
}
}
FrontMatter/FrontMatterParserInterface.php 0000644 00000001020 15152100360 0014747 0 ustar 00 <?php
/*
* 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.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\FrontMatter;
use League\CommonMark\Extension\FrontMatter\Input\MarkdownInputWithFrontMatter;
interface FrontMatterParserInterface
{
public function parse(string $markdownContent): MarkdownInputWithFrontMatter;
}
FrontMatter/Exception/InvalidFrontMatterException.php 0000644 00000001220 15152100360 0017077 0 ustar 00 <?php
/*
* 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.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\FrontMatter\Exception;
use League\CommonMark\Exception\CommonMarkException;
class InvalidFrontMatterException extends \RuntimeException implements CommonMarkException
{
public static function wrap(\Throwable $t): self
{
return new InvalidFrontMatterException('Failed to parse front matter: ' . $t->getMessage(), 0, $t);
}
}
FrontMatter/FrontMatterParser.php 0000644 00000004447 15152100360 0013146 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\FrontMatter;
use League\CommonMark\Extension\FrontMatter\Data\FrontMatterDataParserInterface;
use League\CommonMark\Extension\FrontMatter\Exception\InvalidFrontMatterException;
use League\CommonMark\Extension\FrontMatter\Input\MarkdownInputWithFrontMatter;
use League\CommonMark\Parser\Cursor;
final class FrontMatterParser implements FrontMatterParserInterface
{
/** @psalm-readonly */
private FrontMatterDataParserInterface $frontMatterParser;
private const REGEX_FRONT_MATTER = '/^---\\R.*?\\R---\\R/s';
public function __construct(FrontMatterDataParserInterface $frontMatterParser)
{
$this->frontMatterParser = $frontMatterParser;
}
/**
* @throws InvalidFrontMatterException if the front matter cannot be parsed
*/
public function parse(string $markdownContent): MarkdownInputWithFrontMatter
{
$cursor = new Cursor($markdownContent);
// Locate the front matter
$frontMatter = $cursor->match(self::REGEX_FRONT_MATTER);
if ($frontMatter === null) {
return new MarkdownInputWithFrontMatter($markdownContent);
}
// Trim the last line (ending ---s and newline)
$frontMatter = \preg_replace('/---\R$/', '', $frontMatter);
if ($frontMatter === null) {
return new MarkdownInputWithFrontMatter($markdownContent);
}
// Parse the resulting YAML data
$data = $this->frontMatterParser->parse($frontMatter);
// Advance through any remaining newlines which separated the front matter from the Markdown text
$trailingNewlines = $cursor->match('/^\R+/');
// Calculate how many lines the Markdown is offset from the front matter by counting the number of newlines
// Don't forget to add 1 because we stripped one out when trimming the trailing delims
$lineOffset = \preg_match_all('/\R/', $frontMatter . $trailingNewlines) + 1;
return new MarkdownInputWithFrontMatter($cursor->getRemainder(), $lineOffset, $data);
}
}
FrontMatter/Data/LibYamlFrontMatterParser.php 0000644 00000002276 15152100360 0015267 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\FrontMatter\Data;
use League\CommonMark\Exception\MissingDependencyException;
use League\CommonMark\Extension\FrontMatter\Exception\InvalidFrontMatterException;
final class LibYamlFrontMatterParser implements FrontMatterDataParserInterface
{
public static function capable(): ?LibYamlFrontMatterParser
{
if (! \extension_loaded('yaml')) {
return null;
}
return new LibYamlFrontMatterParser();
}
/**
* {@inheritDoc}
*/
public function parse(string $frontMatter)
{
if (! \extension_loaded('yaml')) {
throw new MissingDependencyException('Failed to parse yaml: "ext-yaml" extension is missing');
}
$result = @\yaml_parse($frontMatter);
if ($result === false) {
throw new InvalidFrontMatterException('Failed to parse front matter');
}
return $result;
}
}
FrontMatter/Data/SymfonyYamlFrontMatterParser.php 0000644 00000002141 15152100360 0016214 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\FrontMatter\Data;
use League\CommonMark\Exception\MissingDependencyException;
use League\CommonMark\Extension\FrontMatter\Exception\InvalidFrontMatterException;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;
final class SymfonyYamlFrontMatterParser implements FrontMatterDataParserInterface
{
/**
* {@inheritDoc}
*/
public function parse(string $frontMatter)
{
if (! \class_exists(Yaml::class)) {
throw new MissingDependencyException('Failed to parse yaml: "symfony/yaml" library is missing');
}
try {
/** @psalm-suppress ReservedWord */
return Yaml::parse($frontMatter);
} catch (ParseException $ex) {
throw InvalidFrontMatterException::wrap($ex);
}
}
}
FrontMatter/Data/FrontMatterDataParserInterface.php 0000644 00000001261 15152100360 0016421 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\FrontMatter\Data;
use League\CommonMark\Extension\FrontMatter\Exception\InvalidFrontMatterException;
interface FrontMatterDataParserInterface
{
/**
* @return mixed|null The parsed data (which may be null, if the input represents a null value)
*
* @throws InvalidFrontMatterException if parsing fails
*/
public function parse(string $frontMatter);
}
Table/TableRowRenderer.php 0000644 00000002670 15152100360 0011510 0 ustar 00 <?php
declare(strict_types=1);
/*
* This is part of the league/commonmark package.
*
* (c) Martin HasoΕ <martin.hason@gmail.com>
* (c) Webuni s.r.o. <info@webuni.cz>
* (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\Extension\Table;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class TableRowRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param TableRow $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
TableRow::assertInstanceOf($node);
$attrs = $node->data->get('attributes');
$separator = $childRenderer->getInnerSeparator();
return new HtmlElement('tr', $attrs, $separator . $childRenderer->renderNodes($node->children()) . $separator);
}
public function getXmlTagName(Node $node): string
{
return 'table_row';
}
/**
* {@inheritDoc}
*/
public function getXmlAttributes(Node $node): array
{
return [];
}
}
Table/TableParser.php 0000644 00000013470 15152100360 0010506 0 ustar 00 <?php
declare(strict_types=1);
/*
* This is part of the league/commonmark package.
*
* (c) Martin HasoΕ <martin.hason@gmail.com>
* (c) Webuni s.r.o. <info@webuni.cz>
* (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\Extension\Table;
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
use League\CommonMark\Parser\Block\BlockContinue;
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
use League\CommonMark\Parser\Block\BlockContinueParserWithInlinesInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\InlineParserEngineInterface;
use League\CommonMark\Util\ArrayCollection;
final class TableParser extends AbstractBlockContinueParser implements BlockContinueParserWithInlinesInterface
{
/** @psalm-readonly */
private Table $block;
/**
* @var ArrayCollection<string>
*
* @psalm-readonly-allow-private-mutation
*/
private ArrayCollection $bodyLines;
/**
* @var array<int, string|null>
* @psalm-var array<int, TableCell::ALIGN_*|null>
* @phpstan-var array<int, TableCell::ALIGN_*|null>
*
* @psalm-readonly
*/
private array $columns;
/**
* @var array<int, string>
*
* @psalm-readonly-allow-private-mutation
*/
private array $headerCells;
/** @psalm-readonly-allow-private-mutation */
private bool $nextIsSeparatorLine = true;
/**
* @param array<int, string|null> $columns
* @param array<int, string> $headerCells
*
* @psalm-param array<int, TableCell::ALIGN_*|null> $columns
*
* @phpstan-param array<int, TableCell::ALIGN_*|null> $columns
*/
public function __construct(array $columns, array $headerCells)
{
$this->block = new Table();
$this->bodyLines = new ArrayCollection();
$this->columns = $columns;
$this->headerCells = $headerCells;
}
public function canHaveLazyContinuationLines(): bool
{
return true;
}
public function getBlock(): Table
{
return $this->block;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
if (\strpos($cursor->getLine(), '|') === false) {
return BlockContinue::none();
}
return BlockContinue::at($cursor);
}
public function addLine(string $line): void
{
if ($this->nextIsSeparatorLine) {
$this->nextIsSeparatorLine = false;
} else {
$this->bodyLines[] = $line;
}
}
public function parseInlines(InlineParserEngineInterface $inlineParser): void
{
$headerColumns = \count($this->headerCells);
$head = new TableSection(TableSection::TYPE_HEAD);
$this->block->appendChild($head);
$headerRow = new TableRow();
$head->appendChild($headerRow);
for ($i = 0; $i < $headerColumns; $i++) {
$cell = $this->headerCells[$i];
$tableCell = $this->parseCell($cell, $i, $inlineParser);
$tableCell->setType(TableCell::TYPE_HEADER);
$headerRow->appendChild($tableCell);
}
$body = null;
foreach ($this->bodyLines as $rowLine) {
$cells = self::split($rowLine);
$row = new TableRow();
// Body can not have more columns than head
for ($i = 0; $i < $headerColumns; $i++) {
$cell = $cells[$i] ?? '';
$tableCell = $this->parseCell($cell, $i, $inlineParser);
$row->appendChild($tableCell);
}
if ($body === null) {
// It's valid to have a table without body. In that case, don't add an empty TableBody node.
$body = new TableSection();
$this->block->appendChild($body);
}
$body->appendChild($row);
}
}
private function parseCell(string $cell, int $column, InlineParserEngineInterface $inlineParser): TableCell
{
$tableCell = new TableCell();
if ($column < \count($this->columns)) {
$tableCell->setAlign($this->columns[$column]);
}
$inlineParser->parse(\trim($cell), $tableCell);
return $tableCell;
}
/**
* @internal
*
* @return array<int, string>
*/
public static function split(string $line): array
{
$cursor = new Cursor(\trim($line));
if ($cursor->getCurrentCharacter() === '|') {
$cursor->advanceBy(1);
}
$cells = [];
$sb = '';
while (! $cursor->isAtEnd()) {
switch ($c = $cursor->getCurrentCharacter()) {
case '\\':
if ($cursor->peek() === '|') {
// Pipe is special for table parsing. An escaped pipe doesn't result in a new cell, but is
// passed down to inline parsing as an unescaped pipe. Note that that applies even for the `\|`
// in an input like `\\|` - in other words, table parsing doesn't support escaping backslashes.
$sb .= '|';
$cursor->advanceBy(1);
} else {
// Preserve backslash before other characters or at end of line.
$sb .= '\\';
}
break;
case '|':
$cells[] = $sb;
$sb = '';
break;
default:
$sb .= $c;
}
$cursor->advanceBy(1);
}
if ($sb !== '') {
$cells[] = $sb;
}
return $cells;
}
}
Table/Table.php 0000644 00000000752 15152100360 0007330 0 ustar 00 <?php
declare(strict_types=1);
/*
* This is part of the league/commonmark package.
*
* (c) Martin HasoΕ <martin.hason@gmail.com>
* (c) Webuni s.r.o. <info@webuni.cz>
* (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\Extension\Table;
use League\CommonMark\Node\Block\AbstractBlock;
final class Table extends AbstractBlock
{
}
Table/TableRow.php 0000644 00000000755 15152100360 0010023 0 ustar 00 <?php
declare(strict_types=1);
/*
* This is part of the league/commonmark package.
*
* (c) Martin HasoΕ <martin.hason@gmail.com>
* (c) Webuni s.r.o. <info@webuni.cz>
* (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\Extension\Table;
use League\CommonMark\Node\Block\AbstractBlock;
final class TableRow extends AbstractBlock
{
}
Table/TableExtension.php 0000644 00000004664 15152100360 0011233 0 ustar 00 <?php
declare(strict_types=1);
/*
* This is part of the league/commonmark package.
*
* (c) Martin HasoΕ <martin.hason@gmail.com>
* (c) Webuni s.r.o. <info@webuni.cz>
* (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\Extension\Table;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Extension\ConfigurableExtensionInterface;
use League\CommonMark\Renderer\HtmlDecorator;
use League\Config\ConfigurationBuilderInterface;
use Nette\Schema\Expect;
final class TableExtension implements ConfigurableExtensionInterface
{
public function configureSchema(ConfigurationBuilderInterface $builder): void
{
$attributeArraySchema = Expect::arrayOf(
Expect::type('string|string[]|bool'), // attribute value(s)
'string' // attribute name
)->mergeDefaults(false);
$builder->addSchema('table', Expect::structure([
'wrap' => Expect::structure([
'enabled' => Expect::bool()->default(false),
'tag' => Expect::string()->default('div'),
'attributes' => Expect::arrayOf(Expect::string()),
]),
'alignment_attributes' => Expect::structure([
'left' => (clone $attributeArraySchema)->default(['align' => 'left']),
'center' => (clone $attributeArraySchema)->default(['align' => 'center']),
'right' => (clone $attributeArraySchema)->default(['align' => 'right']),
]),
]));
}
public function register(EnvironmentBuilderInterface $environment): void
{
$tableRenderer = new TableRenderer();
if ($environment->getConfiguration()->get('table/wrap/enabled')) {
$tableRenderer = new HtmlDecorator($tableRenderer, $environment->getConfiguration()->get('table/wrap/tag'), $environment->getConfiguration()->get('table/wrap/attributes'));
}
$environment
->addBlockStartParser(new TableStartParser())
->addRenderer(Table::class, $tableRenderer)
->addRenderer(TableSection::class, new TableSectionRenderer())
->addRenderer(TableRow::class, new TableRowRenderer())
->addRenderer(TableCell::class, new TableCellRenderer($environment->getConfiguration()->get('table/alignment_attributes')));
}
}
Table/TableCell.php 0000644 00000004243 15152100360 0010127 0 ustar 00 <?php
declare(strict_types=1);
/*
* This is part of the league/commonmark package.
*
* (c) Martin HasoΕ <martin.hason@gmail.com>
* (c) Webuni s.r.o. <info@webuni.cz>
* (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\Extension\Table;
use League\CommonMark\Node\Block\AbstractBlock;
final class TableCell extends AbstractBlock
{
public const TYPE_HEADER = 'header';
public const TYPE_DATA = 'data';
public const ALIGN_LEFT = 'left';
public const ALIGN_RIGHT = 'right';
public const ALIGN_CENTER = 'center';
/**
* @psalm-var self::TYPE_*
* @phpstan-var self::TYPE_*
*
* @psalm-readonly-allow-private-mutation
*/
private string $type = self::TYPE_DATA;
/**
* @psalm-var self::ALIGN_*|null
* @phpstan-var self::ALIGN_*|null
*
* @psalm-readonly-allow-private-mutation
*/
private ?string $align = null;
/**
* @psalm-param self::TYPE_* $type
* @psalm-param self::ALIGN_*|null $align
*
* @phpstan-param self::TYPE_* $type
* @phpstan-param self::ALIGN_*|null $align
*/
public function __construct(string $type = self::TYPE_DATA, ?string $align = null)
{
parent::__construct();
$this->type = $type;
$this->align = $align;
}
/**
* @psalm-return self::TYPE_*
*
* @phpstan-return self::TYPE_*
*/
public function getType(): string
{
return $this->type;
}
/**
* @psalm-param self::TYPE_* $type
*
* @phpstan-param self::TYPE_* $type
*/
public function setType(string $type): void
{
$this->type = $type;
}
/**
* @psalm-return self::ALIGN_*|null
*
* @phpstan-return self::ALIGN_*|null
*/
public function getAlign(): ?string
{
return $this->align;
}
/**
* @psalm-param self::ALIGN_*|null $align
*
* @phpstan-param self::ALIGN_*|null $align
*/
public function setAlign(?string $align): void
{
$this->align = $align;
}
}
Table/TableRenderer.php 0000644 00000002725 15152100360 0011021 0 ustar 00 <?php
declare(strict_types=1);
/*
* This is part of the league/commonmark package.
*
* (c) Martin HasoΕ <martin.hason@gmail.com>
* (c) Webuni s.r.o. <info@webuni.cz>
* (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\Extension\Table;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class TableRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param Table $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
Table::assertInstanceOf($node);
$attrs = $node->data->get('attributes');
$separator = $childRenderer->getInnerSeparator();
$children = $childRenderer->renderNodes($node->children());
return new HtmlElement('table', $attrs, $separator . \trim($children) . $separator);
}
public function getXmlTagName(Node $node): string
{
return 'table';
}
/**
* {@inheritDoc}
*/
public function getXmlAttributes(Node $node): array
{
return [];
}
}
Table/TableStartParser.php 0000644 00000010725 15152100360 0011524 0 ustar 00 <?php
declare(strict_types=1);
/*
* This is part of the league/commonmark package.
*
* (c) Martin HasoΕ <martin.hason@gmail.com>
* (c) Webuni s.r.o. <info@webuni.cz>
* (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\Extension\Table;
use League\CommonMark\Parser\Block\BlockStart;
use League\CommonMark\Parser\Block\BlockStartParserInterface;
use League\CommonMark\Parser\Block\ParagraphParser;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\MarkdownParserStateInterface;
final class TableStartParser implements BlockStartParserInterface
{
public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
{
$paragraph = $parserState->getParagraphContent();
if ($paragraph === null || \strpos($paragraph, '|') === false) {
return BlockStart::none();
}
$columns = self::parseSeparator($cursor);
if (\count($columns) === 0) {
return BlockStart::none();
}
$lines = \explode("\n", $paragraph);
$lastLine = \array_pop($lines);
$headerCells = TableParser::split($lastLine);
if (\count($headerCells) > \count($columns)) {
return BlockStart::none();
}
$cursor->advanceToEnd();
$parsers = [];
if (\count($lines) > 0) {
$p = new ParagraphParser();
$p->addLine(\implode("\n", $lines));
$parsers[] = $p;
}
$parsers[] = new TableParser($columns, $headerCells);
return BlockStart::of(...$parsers)
->at($cursor)
->replaceActiveBlockParser();
}
/**
* @return array<int, string|null>
*
* @psalm-return array<int, TableCell::ALIGN_*|null>
*
* @phpstan-return array<int, TableCell::ALIGN_*|null>
*/
private static function parseSeparator(Cursor $cursor): array
{
$columns = [];
$pipes = 0;
$valid = false;
while (! $cursor->isAtEnd()) {
switch ($c = $cursor->getCurrentCharacter()) {
case '|':
$cursor->advanceBy(1);
$pipes++;
if ($pipes > 1) {
// More than one adjacent pipe not allowed
return [];
}
// Need at least one pipe, even for a one-column table
$valid = true;
break;
case '-':
case ':':
if ($pipes === 0 && \count($columns) > 0) {
// Need a pipe after the first column (first column doesn't need to start with one)
return [];
}
$left = false;
$right = false;
if ($c === ':') {
$left = true;
$cursor->advanceBy(1);
}
if ($cursor->match('/^-+/') === null) {
// Need at least one dash
return [];
}
if ($cursor->getCurrentCharacter() === ':') {
$right = true;
$cursor->advanceBy(1);
}
$columns[] = self::getAlignment($left, $right);
// Next, need another pipe
$pipes = 0;
break;
case ' ':
case "\t":
// White space is allowed between pipes and columns
$cursor->advanceToNextNonSpaceOrTab();
break;
default:
// Any other character is invalid
return [];
}
}
if (! $valid) {
return [];
}
return $columns;
}
/**
* @psalm-return TableCell::ALIGN_*|null
*
* @phpstan-return TableCell::ALIGN_*|null
*
* @psalm-pure
*/
private static function getAlignment(bool $left, bool $right): ?string
{
if ($left && $right) {
return TableCell::ALIGN_CENTER;
}
if ($left) {
return TableCell::ALIGN_LEFT;
}
if ($right) {
return TableCell::ALIGN_RIGHT;
}
return null;
}
}
Table/TableCellRenderer.php 0000644 00000005071 15152100360 0011616 0 ustar 00 <?php
declare(strict_types=1);
/*
* This is part of the league/commonmark package.
*
* (c) Martin HasoΕ <martin.hason@gmail.com>
* (c) Webuni s.r.o. <info@webuni.cz>
* (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\Extension\Table;
use League\CommonMark\Extension\Attributes\Util\AttributesHelper;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class TableCellRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
private const DEFAULT_ATTRIBUTES = [
TableCell::ALIGN_LEFT => ['align' => 'left'],
TableCell::ALIGN_CENTER => ['align' => 'center'],
TableCell::ALIGN_RIGHT => ['align' => 'right'],
];
/** @var array<TableCell::ALIGN_*, array<string, string|string[]|bool>> */
private array $alignmentAttributes;
/**
* @param array<TableCell::ALIGN_*, array<string, string|string[]|bool>> $alignmentAttributes
*/
public function __construct(array $alignmentAttributes = self::DEFAULT_ATTRIBUTES)
{
$this->alignmentAttributes = $alignmentAttributes;
}
/**
* @param TableCell $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
TableCell::assertInstanceOf($node);
$attrs = $node->data->get('attributes');
if (($alignment = $node->getAlign()) !== null) {
$attrs = AttributesHelper::mergeAttributes($attrs, $this->alignmentAttributes[$alignment]);
}
$tag = $node->getType() === TableCell::TYPE_HEADER ? 'th' : 'td';
return new HtmlElement($tag, $attrs, $childRenderer->renderNodes($node->children()));
}
public function getXmlTagName(Node $node): string
{
return 'table_cell';
}
/**
* @param TableCell $node
*
* @return array<string, scalar>
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function getXmlAttributes(Node $node): array
{
TableCell::assertInstanceOf($node);
$ret = ['type' => $node->getType()];
if (($align = $node->getAlign()) !== null) {
$ret['align'] = $align;
}
return $ret;
}
}
Table/TableSectionRenderer.php 0000644 00000003455 15152100360 0012347 0 ustar 00 <?php
declare(strict_types=1);
/*
* This is part of the league/commonmark package.
*
* (c) Martin HasoΕ <martin.hason@gmail.com>
* (c) Webuni s.r.o. <info@webuni.cz>
* (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\Extension\Table;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class TableSectionRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param TableSection $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer)
{
TableSection::assertInstanceOf($node);
if (! $node->hasChildren()) {
return '';
}
$attrs = $node->data->get('attributes');
$separator = $childRenderer->getInnerSeparator();
$tag = $node->getType() === TableSection::TYPE_HEAD ? 'thead' : 'tbody';
return new HtmlElement($tag, $attrs, $separator . $childRenderer->renderNodes($node->children()) . $separator);
}
public function getXmlTagName(Node $node): string
{
return 'table_section';
}
/**
* @param TableSection $node
*
* @return array<string, scalar>
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function getXmlAttributes(Node $node): array
{
TableSection::assertInstanceOf($node);
return [
'type' => $node->getType(),
];
}
}
Table/TableSection.php 0000644 00000002441 15152100360 0010652 0 ustar 00 <?php
declare(strict_types=1);
/*
* This is part of the league/commonmark package.
*
* (c) Martin HasoΕ <martin.hason@gmail.com>
* (c) Webuni s.r.o. <info@webuni.cz>
* (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\Extension\Table;
use League\CommonMark\Node\Block\AbstractBlock;
final class TableSection extends AbstractBlock
{
public const TYPE_HEAD = 'head';
public const TYPE_BODY = 'body';
/**
* @psalm-var self::TYPE_*
* @phpstan-var self::TYPE_*
*
* @psalm-readonly
*/
private string $type;
/**
* @psalm-param self::TYPE_* $type
*
* @phpstan-param self::TYPE_* $type
*/
public function __construct(string $type = self::TYPE_BODY)
{
parent::__construct();
$this->type = $type;
}
/**
* @psalm-return self::TYPE_*
*
* @phpstan-return self::TYPE_*
*/
public function getType(): string
{
return $this->type;
}
public function isHead(): bool
{
return $this->type === self::TYPE_HEAD;
}
public function isBody(): bool
{
return $this->type === self::TYPE_BODY;
}
}
CommonMark/Node/Inline/Link.php 0000644 00000002124 15152100360 0012310 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Node\Inline;
use League\CommonMark\Node\Inline\Text;
class Link extends AbstractWebResource
{
protected ?string $title = null;
public function __construct(string $url, ?string $label = null, ?string $title = null)
{
parent::__construct($url);
if ($label !== null && $label !== '') {
$this->appendChild(new Text($label));
}
$this->title = $title;
}
public function getTitle(): ?string
{
if ($this->title === '') {
return null;
}
return $this->title;
}
public function setTitle(?string $title): void
{
$this->title = $title;
}
}
CommonMark/Node/Inline/Image.php 0000644 00000002125 15152100360 0012436 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Node\Inline;
use League\CommonMark\Node\Inline\Text;
class Image extends AbstractWebResource
{
protected ?string $title = null;
public function __construct(string $url, ?string $label = null, ?string $title = null)
{
parent::__construct($url);
if ($label !== null && $label !== '') {
$this->appendChild(new Text($label));
}
$this->title = $title;
}
public function getTitle(): ?string
{
if ($this->title === '') {
return null;
}
return $this->title;
}
public function setTitle(?string $title): void
{
$this->title = $title;
}
}
CommonMark/Node/Inline/Emphasis.php 0000644 00000001756 15152100360 0013176 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Node\Inline;
use League\CommonMark\Node\Inline\AbstractInline;
use League\CommonMark\Node\Inline\DelimitedInterface;
final class Emphasis extends AbstractInline implements DelimitedInterface
{
private string $delimiter;
public function __construct(string $delimiter = '_')
{
parent::__construct();
$this->delimiter = $delimiter;
}
public function getOpeningDelimiter(): string
{
return $this->delimiter;
}
public function getClosingDelimiter(): string
{
return $this->delimiter;
}
}
CommonMark/Node/Inline/Code.php 0000644 00000001066 15152100360 0012271 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Node\Inline;
use League\CommonMark\Node\Inline\AbstractStringContainer;
class Code extends AbstractStringContainer
{
}
CommonMark/Node/Inline/HtmlInline.php 0000644 00000001241 15152100360 0013455 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Node\Inline;
use League\CommonMark\Node\Inline\AbstractStringContainer;
use League\CommonMark\Node\RawMarkupContainerInterface;
final class HtmlInline extends AbstractStringContainer implements RawMarkupContainerInterface
{
}
CommonMark/Node/Inline/AbstractWebResource.php 0000644 00000001557 15152100360 0015335 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Node\Inline;
use League\CommonMark\Node\Inline\AbstractInline;
abstract class AbstractWebResource extends AbstractInline
{
protected string $url;
public function __construct(string $url)
{
parent::__construct();
$this->url = $url;
}
public function getUrl(): string
{
return $this->url;
}
public function setUrl(string $url): void
{
$this->url = $url;
}
}
CommonMark/Node/Inline/Strong.php 0000644 00000001755 15152100360 0012700 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Node\Inline;
use League\CommonMark\Node\Inline\AbstractInline;
use League\CommonMark\Node\Inline\DelimitedInterface;
final class Strong extends AbstractInline implements DelimitedInterface
{
private string $delimiter;
public function __construct(string $delimiter = '**')
{
parent::__construct();
$this->delimiter = $delimiter;
}
public function getOpeningDelimiter(): string
{
return $this->delimiter;
}
public function getClosingDelimiter(): string
{
return $this->delimiter;
}
}
CommonMark/Node/Block/ListItem.php 0000644 00000001500 15152100360 0012756 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Node\Block;
use League\CommonMark\Node\Block\AbstractBlock;
class ListItem extends AbstractBlock
{
/** @psalm-readonly */
protected ListData $listData;
public function __construct(ListData $listData)
{
parent::__construct();
$this->listData = $listData;
}
public function getListData(): ListData
{
return $this->listData;
}
}
CommonMark/Node/Block/BlockQuote.php 0000644 00000000652 15152100360 0013303 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Node\Block;
use League\CommonMark\Node\Block\AbstractBlock;
class BlockQuote extends AbstractBlock
{
}
CommonMark/Node/Block/ListBlock.php 0000644 00000002416 15152100360 0013121 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Node\Block;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Node\Block\TightBlockInterface;
class ListBlock extends AbstractBlock implements TightBlockInterface
{
public const TYPE_BULLET = 'bullet';
public const TYPE_ORDERED = 'ordered';
public const DELIM_PERIOD = 'period';
public const DELIM_PAREN = 'paren';
protected bool $tight = false;
/** @psalm-readonly */
protected ListData $listData;
public function __construct(ListData $listData)
{
parent::__construct();
$this->listData = $listData;
}
public function getListData(): ListData
{
return $this->listData;
}
public function isTight(): bool
{
return $this->tight;
}
public function setTight(bool $tight): void
{
$this->tight = $tight;
}
}
CommonMark/Node/Block/Heading.php 0000644 00000001542 15152100360 0012571 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Node\Block;
use League\CommonMark\Node\Block\AbstractBlock;
final class Heading extends AbstractBlock
{
private int $level;
public function __construct(int $level)
{
parent::__construct();
$this->level = $level;
}
public function getLevel(): int
{
return $this->level;
}
public function setLevel(int $level): void
{
$this->level = $level;
}
}
CommonMark/Node/Block/FencedCode.php 0000644 00000003770 15152100360 0013216 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Node\Block;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Node\StringContainerInterface;
final class FencedCode extends AbstractBlock implements StringContainerInterface
{
private ?string $info = null;
private string $literal = '';
private int $length;
private string $char;
private int $offset;
public function __construct(int $length, string $char, int $offset)
{
parent::__construct();
$this->length = $length;
$this->char = $char;
$this->offset = $offset;
}
public function getInfo(): ?string
{
return $this->info;
}
/**
* @return string[]
*/
public function getInfoWords(): array
{
return \preg_split('/\s+/', $this->info ?? '') ?: [];
}
public function setInfo(string $info): void
{
$this->info = $info;
}
public function getLiteral(): string
{
return $this->literal;
}
public function setLiteral(string $literal): void
{
$this->literal = $literal;
}
public function getChar(): string
{
return $this->char;
}
public function setChar(string $char): void
{
$this->char = $char;
}
public function getLength(): int
{
return $this->length;
}
public function setLength(int $length): void
{
$this->length = $length;
}
public function getOffset(): int
{
return $this->offset;
}
public function setOffset(int $offset): void
{
$this->offset = $offset;
}
}
CommonMark/Node/Block/ListData.php 0000644 00000002070 15152100360 0012734 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Node\Block;
class ListData
{
public ?int $start = null;
public int $padding = 0;
/**
* @psalm-var ListBlock::TYPE_*
* @phpstan-var ListBlock::TYPE_*
*/
public string $type;
/**
* @psalm-var ListBlock::DELIM_*|null
* @phpstan-var ListBlock::DELIM_*|null
*/
public ?string $delimiter = null;
public ?string $bulletChar = null;
public int $markerOffset;
public function equals(ListData $data): bool
{
return $this->type === $data->type &&
$this->delimiter === $data->delimiter &&
$this->bulletChar === $data->bulletChar;
}
}
CommonMark/Node/Block/ThematicBreak.php 0000644 00000000655 15152100360 0013741 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Node\Block;
use League\CommonMark\Node\Block\AbstractBlock;
class ThematicBreak extends AbstractBlock
{
}
CommonMark/Node/Block/IndentedCode.php 0000644 00000001350 15152100360 0013554 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Node\Block;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Node\StringContainerInterface;
final class IndentedCode extends AbstractBlock implements StringContainerInterface
{
private string $literal = '';
public function getLiteral(): string
{
return $this->literal;
}
public function setLiteral(string $literal): void
{
$this->literal = $literal;
}
}
CommonMark/Node/Block/HtmlBlock.php 0000644 00000003447 15152100360 0013117 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Node\Block;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Node\RawMarkupContainerInterface;
final class HtmlBlock extends AbstractBlock implements RawMarkupContainerInterface
{
// Any changes to these constants should be reflected in .phpstorm.meta.php
public const TYPE_1_CODE_CONTAINER = 1;
public const TYPE_2_COMMENT = 2;
public const TYPE_3 = 3;
public const TYPE_4 = 4;
public const TYPE_5_CDATA = 5;
public const TYPE_6_BLOCK_ELEMENT = 6;
public const TYPE_7_MISC_ELEMENT = 7;
/**
* @psalm-var self::TYPE_* $type
* @phpstan-var self::TYPE_* $type
*/
private int $type;
private string $literal = '';
/**
* @psalm-param self::TYPE_* $type
*
* @phpstan-param self::TYPE_* $type
*/
public function __construct(int $type)
{
parent::__construct();
$this->type = $type;
}
/**
* @psalm-return self::TYPE_*
*
* @phpstan-return self::TYPE_*
*/
public function getType(): int
{
return $this->type;
}
/**
* @psalm-param self::TYPE_* $type
*
* @phpstan-param self::TYPE_* $type
*/
public function setType(int $type): void
{
$this->type = $type;
}
public function getLiteral(): string
{
return $this->literal;
}
public function setLiteral(string $literal): void
{
$this->literal = $literal;
}
}
CommonMark/CommonMarkCoreExtension.php 0000644 00000012125 15152100360 0014063 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Extension\CommonMark\Delimiter\Processor\EmphasisDelimiterProcessor;
use League\CommonMark\Extension\ConfigurableExtensionInterface;
use League\CommonMark\Node as CoreNode;
use League\CommonMark\Parser as CoreParser;
use League\CommonMark\Renderer as CoreRenderer;
use League\Config\ConfigurationBuilderInterface;
use Nette\Schema\Expect;
final class CommonMarkCoreExtension implements ConfigurableExtensionInterface
{
public function configureSchema(ConfigurationBuilderInterface $builder): void
{
$builder->addSchema('commonmark', Expect::structure([
'use_asterisk' => Expect::bool(true),
'use_underscore' => Expect::bool(true),
'enable_strong' => Expect::bool(true),
'enable_em' => Expect::bool(true),
'unordered_list_markers' => Expect::listOf('string')->min(1)->default(['*', '+', '-'])->mergeDefaults(false),
]));
}
// phpcs:disable Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma,Squiz.WhiteSpace.SemicolonSpacing.Incorrect
public function register(EnvironmentBuilderInterface $environment): void
{
$environment
->addBlockStartParser(new Parser\Block\BlockQuoteStartParser(), 70)
->addBlockStartParser(new Parser\Block\HeadingStartParser(), 60)
->addBlockStartParser(new Parser\Block\FencedCodeStartParser(), 50)
->addBlockStartParser(new Parser\Block\HtmlBlockStartParser(), 40)
->addBlockStartParser(new Parser\Block\ThematicBreakStartParser(), 20)
->addBlockStartParser(new Parser\Block\ListBlockStartParser(), 10)
->addBlockStartParser(new Parser\Block\IndentedCodeStartParser(), -100)
->addInlineParser(new CoreParser\Inline\NewlineParser(), 200)
->addInlineParser(new Parser\Inline\BacktickParser(), 150)
->addInlineParser(new Parser\Inline\EscapableParser(), 80)
->addInlineParser(new Parser\Inline\EntityParser(), 70)
->addInlineParser(new Parser\Inline\AutolinkParser(), 50)
->addInlineParser(new Parser\Inline\HtmlInlineParser(), 40)
->addInlineParser(new Parser\Inline\CloseBracketParser(), 30)
->addInlineParser(new Parser\Inline\OpenBracketParser(), 20)
->addInlineParser(new Parser\Inline\BangParser(), 10)
->addRenderer(Node\Block\BlockQuote::class, new Renderer\Block\BlockQuoteRenderer(), 0)
->addRenderer(CoreNode\Block\Document::class, new CoreRenderer\Block\DocumentRenderer(), 0)
->addRenderer(Node\Block\FencedCode::class, new Renderer\Block\FencedCodeRenderer(), 0)
->addRenderer(Node\Block\Heading::class, new Renderer\Block\HeadingRenderer(), 0)
->addRenderer(Node\Block\HtmlBlock::class, new Renderer\Block\HtmlBlockRenderer(), 0)
->addRenderer(Node\Block\IndentedCode::class, new Renderer\Block\IndentedCodeRenderer(), 0)
->addRenderer(Node\Block\ListBlock::class, new Renderer\Block\ListBlockRenderer(), 0)
->addRenderer(Node\Block\ListItem::class, new Renderer\Block\ListItemRenderer(), 0)
->addRenderer(CoreNode\Block\Paragraph::class, new CoreRenderer\Block\ParagraphRenderer(), 0)
->addRenderer(Node\Block\ThematicBreak::class, new Renderer\Block\ThematicBreakRenderer(), 0)
->addRenderer(Node\Inline\Code::class, new Renderer\Inline\CodeRenderer(), 0)
->addRenderer(Node\Inline\Emphasis::class, new Renderer\Inline\EmphasisRenderer(), 0)
->addRenderer(Node\Inline\HtmlInline::class, new Renderer\Inline\HtmlInlineRenderer(), 0)
->addRenderer(Node\Inline\Image::class, new Renderer\Inline\ImageRenderer(), 0)
->addRenderer(Node\Inline\Link::class, new Renderer\Inline\LinkRenderer(), 0)
->addRenderer(CoreNode\Inline\Newline::class, new CoreRenderer\Inline\NewlineRenderer(), 0)
->addRenderer(Node\Inline\Strong::class, new Renderer\Inline\StrongRenderer(), 0)
->addRenderer(CoreNode\Inline\Text::class, new CoreRenderer\Inline\TextRenderer(), 0)
;
if ($environment->getConfiguration()->get('commonmark/use_asterisk')) {
$environment->addDelimiterProcessor(new EmphasisDelimiterProcessor('*'));
}
if ($environment->getConfiguration()->get('commonmark/use_underscore')) {
$environment->addDelimiterProcessor(new EmphasisDelimiterProcessor('_'));
}
}
}
CommonMark/Renderer/Inline/ImageRenderer.php 0000644 00000005735 15152100360 0015020 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Renderer\Inline;
use League\CommonMark\Extension\CommonMark\Node\Inline\Image;
use League\CommonMark\Node\Inline\Newline;
use League\CommonMark\Node\Node;
use League\CommonMark\Node\NodeIterator;
use League\CommonMark\Node\StringContainerInterface;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Util\RegexHelper;
use League\CommonMark\Xml\XmlNodeRendererInterface;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class ImageRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface
{
/** @psalm-readonly-allow-private-mutation */
private ConfigurationInterface $config;
/**
* @param Image $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
Image::assertInstanceOf($node);
$attrs = $node->data->get('attributes');
$forbidUnsafeLinks = ! $this->config->get('allow_unsafe_links');
if ($forbidUnsafeLinks && RegexHelper::isLinkPotentiallyUnsafe($node->getUrl())) {
$attrs['src'] = '';
} else {
$attrs['src'] = $node->getUrl();
}
$attrs['alt'] = $this->getAltText($node);
if (($title = $node->getTitle()) !== null) {
$attrs['title'] = $title;
}
return new HtmlElement('img', $attrs, '', true);
}
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}
public function getXmlTagName(Node $node): string
{
return 'image';
}
/**
* @param Image $node
*
* @return array<string, scalar>
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function getXmlAttributes(Node $node): array
{
Image::assertInstanceOf($node);
return [
'destination' => $node->getUrl(),
'title' => $node->getTitle() ?? '',
];
}
private function getAltText(Image $node): string
{
$altText = '';
foreach ((new NodeIterator($node)) as $n) {
if ($n instanceof StringContainerInterface) {
$altText .= $n->getLiteral();
} elseif ($n instanceof Newline) {
$altText .= "\n";
}
}
return $altText;
}
}
CommonMark/Renderer/Inline/LinkRenderer.php 0000644 00000005071 15152100360 0014664 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Renderer\Inline;
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Util\RegexHelper;
use League\CommonMark\Xml\XmlNodeRendererInterface;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class LinkRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface
{
/** @psalm-readonly-allow-private-mutation */
private ConfigurationInterface $config;
/**
* @param Link $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
Link::assertInstanceOf($node);
$attrs = $node->data->get('attributes');
$forbidUnsafeLinks = ! $this->config->get('allow_unsafe_links');
if (! ($forbidUnsafeLinks && RegexHelper::isLinkPotentiallyUnsafe($node->getUrl()))) {
$attrs['href'] = $node->getUrl();
}
if (($title = $node->getTitle()) !== null) {
$attrs['title'] = $title;
}
if (isset($attrs['target']) && $attrs['target'] === '_blank' && ! isset($attrs['rel'])) {
$attrs['rel'] = 'noopener noreferrer';
}
return new HtmlElement('a', $attrs, $childRenderer->renderNodes($node->children()));
}
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}
public function getXmlTagName(Node $node): string
{
return 'link';
}
/**
* @param Link $node
*
* @return array<string, scalar>
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function getXmlAttributes(Node $node): array
{
Link::assertInstanceOf($node);
return [
'destination' => $node->getUrl(),
'title' => $node->getTitle() ?? '',
];
}
}
CommonMark/Renderer/Inline/EmphasisRenderer.php 0000644 00000002741 15152100360 0015541 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Renderer\Inline;
use League\CommonMark\Extension\CommonMark\Node\Inline\Emphasis;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class EmphasisRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param Emphasis $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
Emphasis::assertInstanceOf($node);
$attrs = $node->data->get('attributes');
return new HtmlElement('em', $attrs, $childRenderer->renderNodes($node->children()));
}
public function getXmlTagName(Node $node): string
{
return 'emph';
}
/**
* {@inheritDoc}
*/
public function getXmlAttributes(Node $node): array
{
return [];
}
}
CommonMark/Renderer/Inline/CodeRenderer.php 0000644 00000002745 15152100360 0014646 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Renderer\Inline;
use League\CommonMark\Extension\CommonMark\Node\Inline\Code;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Util\Xml;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class CodeRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param Code $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
Code::assertInstanceOf($node);
$attrs = $node->data->get('attributes');
return new HtmlElement('code', $attrs, Xml::escape($node->getLiteral()));
}
public function getXmlTagName(Node $node): string
{
return 'code';
}
/**
* {@inheritDoc}
*/
public function getXmlAttributes(Node $node): array
{
return [];
}
}
CommonMark/Renderer/Inline/HtmlInlineRenderer.php 0000644 00000003462 15152100360 0016034 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Renderer\Inline;
use League\CommonMark\Extension\CommonMark\Node\Inline\HtmlInline;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlFilter;
use League\CommonMark\Xml\XmlNodeRendererInterface;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class HtmlInlineRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface
{
/** @psalm-readonly-allow-private-mutation */
private ConfigurationInterface $config;
/**
* @param HtmlInline $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): string
{
HtmlInline::assertInstanceOf($node);
$htmlInput = $this->config->get('html_input');
return HtmlFilter::filter($node->getLiteral(), $htmlInput);
}
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}
public function getXmlTagName(Node $node): string
{
return 'html_inline';
}
/**
* {@inheritDoc}
*/
public function getXmlAttributes(Node $node): array
{
return [];
}
}
CommonMark/Renderer/Inline/StrongRenderer.php 0000644 00000002737 15152100360 0015251 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Renderer\Inline;
use League\CommonMark\Extension\CommonMark\Node\Inline\Strong;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class StrongRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param Strong $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
Strong::assertInstanceOf($node);
$attrs = $node->data->get('attributes');
return new HtmlElement('strong', $attrs, $childRenderer->renderNodes($node->children()));
}
public function getXmlTagName(Node $node): string
{
return 'strong';
}
/**
* {@inheritDoc}
*/
public function getXmlAttributes(Node $node): array
{
return [];
}
}
CommonMark/Renderer/Block/ThematicBreakRenderer.php 0000644 00000002727 15152100360 0016313 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Renderer\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\ThematicBreak;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class ThematicBreakRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param ThematicBreak $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
ThematicBreak::assertInstanceOf($node);
$attrs = $node->data->get('attributes');
return new HtmlElement('hr', $attrs, '', true);
}
public function getXmlTagName(Node $node): string
{
return 'thematic_break';
}
/**
* {@inheritDoc}
*/
public function getXmlAttributes(Node $node): array
{
return [];
}
}
CommonMark/Renderer/Block/IndentedCodeRenderer.php 0000644 00000003143 15152100360 0016126 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Renderer\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\IndentedCode;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Util\Xml;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class IndentedCodeRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param IndentedCode $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
IndentedCode::assertInstanceOf($node);
$attrs = $node->data->get('attributes');
return new HtmlElement(
'pre',
[],
new HtmlElement('code', $attrs, Xml::escape($node->getLiteral()))
);
}
public function getXmlTagName(Node $node): string
{
return 'code_block';
}
/**
* @return array<string, scalar>
*/
public function getXmlAttributes(Node $node): array
{
return [];
}
}
CommonMark/Renderer/Block/HeadingRenderer.php 0000644 00000003300 15152100360 0015133 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Renderer\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class HeadingRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param Heading $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
Heading::assertInstanceOf($node);
$tag = 'h' . $node->getLevel();
$attrs = $node->data->get('attributes');
return new HtmlElement($tag, $attrs, $childRenderer->renderNodes($node->children()));
}
public function getXmlTagName(Node $node): string
{
return 'heading';
}
/**
* @param Heading $node
*
* @return array<string, scalar>
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function getXmlAttributes(Node $node): array
{
Heading::assertInstanceOf($node);
return ['level' => $node->getLevel()];
}
}
CommonMark/Renderer/Block/ListBlockRenderer.php 0000644 00000004636 15152100360 0015477 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Renderer\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class ListBlockRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param ListBlock $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
ListBlock::assertInstanceOf($node);
$listData = $node->getListData();
$tag = $listData->type === ListBlock::TYPE_BULLET ? 'ul' : 'ol';
$attrs = $node->data->get('attributes');
if ($listData->start !== null && $listData->start !== 1) {
$attrs['start'] = (string) $listData->start;
}
$innerSeparator = $childRenderer->getInnerSeparator();
return new HtmlElement($tag, $attrs, $innerSeparator . $childRenderer->renderNodes($node->children()) . $innerSeparator);
}
public function getXmlTagName(Node $node): string
{
return 'list';
}
/**
* @param ListBlock $node
*
* @return array<string, scalar>
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function getXmlAttributes(Node $node): array
{
ListBlock::assertInstanceOf($node);
$data = $node->getListData();
if ($data->type === ListBlock::TYPE_BULLET) {
return [
'type' => $data->type,
'tight' => $node->isTight() ? 'true' : 'false',
];
}
return [
'type' => $data->type,
'start' => $data->start ?? 1,
'tight' => $node->isTight(),
'delimiter' => $data->delimiter ?? ListBlock::DELIM_PERIOD,
];
}
}
CommonMark/Renderer/Block/HtmlBlockRenderer.php 0000644 00000003453 15152100360 0015464 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Renderer\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlFilter;
use League\CommonMark\Xml\XmlNodeRendererInterface;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class HtmlBlockRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface
{
/** @psalm-readonly-allow-private-mutation */
private ConfigurationInterface $config;
/**
* @param HtmlBlock $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): string
{
HtmlBlock::assertInstanceOf($node);
$htmlInput = $this->config->get('html_input');
return HtmlFilter::filter($node->getLiteral(), $htmlInput);
}
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}
public function getXmlTagName(Node $node): string
{
return 'html_block';
}
/**
* {@inheritDoc}
*/
public function getXmlAttributes(Node $node): array
{
return [];
}
}
CommonMark/Renderer/Block/BlockQuoteRenderer.php 0000644 00000003625 15152100360 0015656 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Renderer\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\BlockQuote;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class BlockQuoteRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param BlockQuote $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
BlockQuote::assertInstanceOf($node);
$attrs = $node->data->get('attributes');
$filling = $childRenderer->renderNodes($node->children());
$innerSeparator = $childRenderer->getInnerSeparator();
if ($filling === '') {
return new HtmlElement('blockquote', $attrs, $innerSeparator);
}
return new HtmlElement(
'blockquote',
$attrs,
$innerSeparator . $filling . $innerSeparator
);
}
public function getXmlTagName(Node $node): string
{
return 'block_quote';
}
/**
* @param BlockQuote $node
*
* @return array<string, scalar>
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function getXmlAttributes(Node $node): array
{
return [];
}
}
CommonMark/Renderer/Block/FencedCodeRenderer.php 0000644 00000004261 15152100360 0015562 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Renderer\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Util\Xml;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class FencedCodeRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param FencedCode $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
FencedCode::assertInstanceOf($node);
$attrs = $node->data->getData('attributes');
$infoWords = $node->getInfoWords();
if (\count($infoWords) !== 0 && $infoWords[0] !== '') {
$class = $infoWords[0];
if (! \str_starts_with($class, 'language-')) {
$class = 'language-' . $class;
}
$attrs->append('class', $class);
}
return new HtmlElement(
'pre',
[],
new HtmlElement('code', $attrs->export(), Xml::escape($node->getLiteral()))
);
}
public function getXmlTagName(Node $node): string
{
return 'code_block';
}
/**
* @param FencedCode $node
*
* @return array<string, scalar>
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function getXmlAttributes(Node $node): array
{
FencedCode::assertInstanceOf($node);
if (($info = $node->getInfo()) === null || $info === '') {
return [];
}
return ['info' => $info];
}
}
CommonMark/Renderer/Block/ListItemRenderer.php 0000644 00000004062 15152100360 0015334 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Renderer\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\ListItem;
use League\CommonMark\Extension\TaskList\TaskListItemMarker;
use League\CommonMark\Node\Block\Paragraph;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class ListItemRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param ListItem $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
ListItem::assertInstanceOf($node);
$contents = $childRenderer->renderNodes($node->children());
if (\substr($contents, 0, 1) === '<' && ! $this->startsTaskListItem($node)) {
$contents = "\n" . $contents;
}
if (\substr($contents, -1, 1) === '>') {
$contents .= "\n";
}
$attrs = $node->data->get('attributes');
return new HtmlElement('li', $attrs, $contents);
}
public function getXmlTagName(Node $node): string
{
return 'item';
}
/**
* {@inheritDoc}
*/
public function getXmlAttributes(Node $node): array
{
return [];
}
private function startsTaskListItem(ListItem $block): bool
{
$firstChild = $block->firstChild();
return $firstChild instanceof Paragraph && $firstChild->firstChild() instanceof TaskListItemMarker;
}
}
CommonMark/Parser/Inline/HtmlInlineParser.php 0000644 00000002375 15152100360 0015212 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Inline;
use League\CommonMark\Extension\CommonMark\Node\Inline\HtmlInline;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
use League\CommonMark\Util\RegexHelper;
final class HtmlInlineParser implements InlineParserInterface
{
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::regex(RegexHelper::PARTIAL_HTMLTAG)->caseSensitive();
}
public function parse(InlineParserContext $inlineContext): bool
{
$inline = $inlineContext->getFullMatch();
$inlineContext->getCursor()->advanceBy($inlineContext->getFullMatchLength());
$inlineContext->getContainer()->appendChild(new HtmlInline($inline));
return true;
}
}
CommonMark/Parser/Inline/EntityParser.php 0000644 00000002421 15152100360 0014413 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Inline;
use League\CommonMark\Node\Inline\Text;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
use League\CommonMark\Util\Html5EntityDecoder;
use League\CommonMark\Util\RegexHelper;
final class EntityParser implements InlineParserInterface
{
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::regex(RegexHelper::PARTIAL_ENTITY);
}
public function parse(InlineParserContext $inlineContext): bool
{
$entity = $inlineContext->getFullMatch();
$inlineContext->getCursor()->advanceBy($inlineContext->getFullMatchLength());
$inlineContext->getContainer()->appendChild(new Text(Html5EntityDecoder::decode($entity)));
return true;
}
}
CommonMark/Parser/Inline/AutolinkParser.php 0000644 00000003542 15152100360 0014732 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Inline;
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
use League\CommonMark\Util\UrlEncoder;
final class AutolinkParser implements InlineParserInterface
{
private const EMAIL_REGEX = '<([a-zA-Z0-9.!#$%&\'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>';
private const OTHER_LINK_REGEX = '<([A-Za-z][A-Za-z0-9.+-]{1,31}:[^<>\x00-\x20]*)>';
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::regex(self::EMAIL_REGEX . '|' . self::OTHER_LINK_REGEX);
}
public function parse(InlineParserContext $inlineContext): bool
{
$inlineContext->getCursor()->advanceBy($inlineContext->getFullMatchLength());
$matches = $inlineContext->getMatches();
if ($matches[1] !== '') {
$inlineContext->getContainer()->appendChild(new Link('mailto:' . UrlEncoder::unescapeAndEncode($matches[1]), $matches[1]));
return true;
}
if ($matches[2] !== '') {
$inlineContext->getContainer()->appendChild(new Link(UrlEncoder::unescapeAndEncode($matches[2]), $matches[2]));
return true;
}
return false; // This should never happen
}
}
CommonMark/Parser/Inline/EscapableParser.php 0000644 00000003153 15152100360 0015021 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Inline;
use League\CommonMark\Node\Inline\Newline;
use League\CommonMark\Node\Inline\Text;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
use League\CommonMark\Util\RegexHelper;
final class EscapableParser implements InlineParserInterface
{
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::string('\\');
}
public function parse(InlineParserContext $inlineContext): bool
{
$cursor = $inlineContext->getCursor();
$nextChar = $cursor->peek();
if ($nextChar === "\n") {
$cursor->advanceBy(2);
$inlineContext->getContainer()->appendChild(new Newline(Newline::HARDBREAK));
return true;
}
if ($nextChar !== null && RegexHelper::isEscapable($nextChar)) {
$cursor->advanceBy(2);
$inlineContext->getContainer()->appendChild(new Text($nextChar));
return true;
}
$cursor->advanceBy(1);
$inlineContext->getContainer()->appendChild(new Text('\\'));
return true;
}
}
CommonMark/Parser/Inline/OpenBracketParser.php 0000644 00000002541 15152100360 0015337 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Inline;
use League\CommonMark\Delimiter\Delimiter;
use League\CommonMark\Node\Inline\Text;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
final class OpenBracketParser implements InlineParserInterface
{
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::string('[');
}
public function parse(InlineParserContext $inlineContext): bool
{
$inlineContext->getCursor()->advanceBy(1);
$node = new Text('[', ['delim' => true]);
$inlineContext->getContainer()->appendChild($node);
// Add entry to stack for this opener
$delimiter = new Delimiter('[', 1, $node, true, false, $inlineContext->getCursor()->getPosition());
$inlineContext->getDelimiterStack()->push($delimiter);
return true;
}
}
CommonMark/Parser/Inline/BacktickParser.php 0000644 00000004136 15152100360 0014657 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Inline;
use League\CommonMark\Extension\CommonMark\Node\Inline\Code;
use League\CommonMark\Node\Inline\Text;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
final class BacktickParser implements InlineParserInterface
{
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::regex('`+');
}
public function parse(InlineParserContext $inlineContext): bool
{
$ticks = $inlineContext->getFullMatch();
$cursor = $inlineContext->getCursor();
$cursor->advanceBy($inlineContext->getFullMatchLength());
$currentPosition = $cursor->getPosition();
$previousState = $cursor->saveState();
while ($matchingTicks = $cursor->match('/`+/m')) {
if ($matchingTicks !== $ticks) {
continue;
}
$code = $cursor->getSubstring($currentPosition, $cursor->getPosition() - $currentPosition - \strlen($ticks));
$c = \preg_replace('/\n/m', ' ', $code) ?? '';
if (
$c !== '' &&
$c[0] === ' ' &&
\substr($c, -1, 1) === ' ' &&
\preg_match('/[^ ]/', $c)
) {
$c = \substr($c, 1, -1);
}
$inlineContext->getContainer()->appendChild(new Code($c));
return true;
}
// If we got here, we didn't match a closing backtick sequence
$cursor->restoreState($previousState);
$inlineContext->getContainer()->appendChild(new Text($ticks));
return true;
}
}
CommonMark/Parser/Inline/BangParser.php 0000644 00000002544 15152100360 0014014 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Inline;
use League\CommonMark\Delimiter\Delimiter;
use League\CommonMark\Node\Inline\Text;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
final class BangParser implements InlineParserInterface
{
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::string('![');
}
public function parse(InlineParserContext $inlineContext): bool
{
$cursor = $inlineContext->getCursor();
$cursor->advanceBy(2);
$node = new Text('![', ['delim' => true]);
$inlineContext->getContainer()->appendChild($node);
// Add entry to stack for this opener
$delimiter = new Delimiter('!', 1, $node, true, false, $cursor->getPosition());
$inlineContext->getDelimiterStack()->push($delimiter);
return true;
}
}
CommonMark/Parser/Inline/CloseBracketParser.php 0000644 00000016323 15152100360 0015506 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Inline;
use League\CommonMark\Environment\EnvironmentAwareInterface;
use League\CommonMark\Environment\EnvironmentInterface;
use League\CommonMark\Extension\CommonMark\Node\Inline\AbstractWebResource;
use League\CommonMark\Extension\CommonMark\Node\Inline\Image;
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
use League\CommonMark\Extension\Mention\Mention;
use League\CommonMark\Node\Inline\AdjacentTextMerger;
use League\CommonMark\Node\Inline\Text;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
use League\CommonMark\Reference\ReferenceInterface;
use League\CommonMark\Reference\ReferenceMapInterface;
use League\CommonMark\Util\LinkParserHelper;
use League\CommonMark\Util\RegexHelper;
final class CloseBracketParser implements InlineParserInterface, EnvironmentAwareInterface
{
/** @psalm-readonly-allow-private-mutation */
private EnvironmentInterface $environment;
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::string(']');
}
public function parse(InlineParserContext $inlineContext): bool
{
// Look through stack of delimiters for a [ or !
$opener = $inlineContext->getDelimiterStack()->searchByCharacter(['[', '!']);
if ($opener === null) {
return false;
}
if (! $opener->isActive()) {
// no matched opener; remove from emphasis stack
$inlineContext->getDelimiterStack()->removeDelimiter($opener);
return false;
}
$cursor = $inlineContext->getCursor();
$startPos = $cursor->getPosition();
$previousState = $cursor->saveState();
$cursor->advanceBy(1);
// Check to see if we have a link/image
// Inline link?
if ($result = $this->tryParseInlineLinkAndTitle($cursor)) {
$link = $result;
} elseif ($link = $this->tryParseReference($cursor, $inlineContext->getReferenceMap(), $opener->getIndex(), $startPos)) {
$reference = $link;
$link = ['url' => $link->getDestination(), 'title' => $link->getTitle()];
} else {
// No match
$inlineContext->getDelimiterStack()->removeDelimiter($opener); // Remove this opener from stack
$cursor->restoreState($previousState);
return false;
}
$isImage = $opener->getChar() === '!';
$inline = $this->createInline($link['url'], $link['title'], $isImage, $reference ?? null);
$opener->getInlineNode()->replaceWith($inline);
while (($label = $inline->next()) !== null) {
// Is there a Mention or Link contained within this link?
// CommonMark does not allow nested links, so we'll restore the original text.
if ($label instanceof Mention) {
$label->replaceWith($replacement = new Text($label->getPrefix() . $label->getIdentifier()));
$inline->appendChild($replacement);
} elseif ($label instanceof Link) {
foreach ($label->children() as $child) {
$label->insertBefore($child);
}
$label->detach();
} else {
$inline->appendChild($label);
}
}
// Process delimiters such as emphasis inside link/image
$delimiterStack = $inlineContext->getDelimiterStack();
$stackBottom = $opener->getPrevious();
$delimiterStack->processDelimiters($stackBottom, $this->environment->getDelimiterProcessors());
$delimiterStack->removeAll($stackBottom);
// Merge any adjacent Text nodes together
AdjacentTextMerger::mergeChildNodes($inline);
// processEmphasis will remove this and later delimiters.
// Now, for a link, we also remove earlier link openers (no links in links)
if (! $isImage) {
$inlineContext->getDelimiterStack()->removeEarlierMatches('[');
}
return true;
}
public function setEnvironment(EnvironmentInterface $environment): void
{
$this->environment = $environment;
}
/**
* @return array<string, string>|null
*/
private function tryParseInlineLinkAndTitle(Cursor $cursor): ?array
{
if ($cursor->getCurrentCharacter() !== '(') {
return null;
}
$previousState = $cursor->saveState();
$cursor->advanceBy(1);
$cursor->advanceToNextNonSpaceOrNewline();
if (($dest = LinkParserHelper::parseLinkDestination($cursor)) === null) {
$cursor->restoreState($previousState);
return null;
}
$cursor->advanceToNextNonSpaceOrNewline();
$previousCharacter = $cursor->peek(-1);
// We know from previous lines that we've advanced at least one space so far, so this next call should never be null
\assert(\is_string($previousCharacter));
$title = '';
// make sure there's a space before the title:
if (\preg_match(RegexHelper::REGEX_WHITESPACE_CHAR, $previousCharacter)) {
$title = LinkParserHelper::parseLinkTitle($cursor) ?? '';
}
$cursor->advanceToNextNonSpaceOrNewline();
if ($cursor->getCurrentCharacter() !== ')') {
$cursor->restoreState($previousState);
return null;
}
$cursor->advanceBy(1);
return ['url' => $dest, 'title' => $title];
}
private function tryParseReference(Cursor $cursor, ReferenceMapInterface $referenceMap, ?int $openerIndex, int $startPos): ?ReferenceInterface
{
if ($openerIndex === null) {
return null;
}
$savePos = $cursor->saveState();
$beforeLabel = $cursor->getPosition();
$n = LinkParserHelper::parseLinkLabel($cursor);
if ($n === 0 || $n === 2) {
$start = $openerIndex;
$length = $startPos - $openerIndex;
} else {
$start = $beforeLabel + 1;
$length = $n - 2;
}
$referenceLabel = $cursor->getSubstring($start, $length);
if ($n === 0) {
// If shortcut reference link, rewind before spaces we skipped
$cursor->restoreState($savePos);
}
return $referenceMap->get($referenceLabel);
}
private function createInline(string $url, string $title, bool $isImage, ?ReferenceInterface $reference = null): AbstractWebResource
{
if ($isImage) {
$inline = new Image($url, null, $title);
} else {
$inline = new Link($url, null, $title);
}
if ($reference) {
$inline->data->set('reference', $reference);
}
return $inline;
}
}
CommonMark/Parser/Block/IndentedCodeParser.php 0000644 00000004242 15152100360 0015303 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\IndentedCode;
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
use League\CommonMark\Parser\Block\BlockContinue;
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Util\ArrayCollection;
final class IndentedCodeParser extends AbstractBlockContinueParser
{
/** @psalm-readonly */
private IndentedCode $block;
/** @var ArrayCollection<string> */
private ArrayCollection $strings;
public function __construct()
{
$this->block = new IndentedCode();
$this->strings = new ArrayCollection();
}
public function getBlock(): IndentedCode
{
return $this->block;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
if ($cursor->isIndented()) {
$cursor->advanceBy(Cursor::INDENT_LEVEL, true);
return BlockContinue::at($cursor);
}
if ($cursor->isBlank()) {
$cursor->advanceToNextNonSpaceOrTab();
return BlockContinue::at($cursor);
}
return BlockContinue::none();
}
public function addLine(string $line): void
{
$this->strings[] = $line;
}
public function closeBlock(): void
{
$reversed = \array_reverse($this->strings->toArray(), true);
foreach ($reversed as $index => $line) {
if ($line !== '' && $line !== "\n" && ! \preg_match('/^(\n *)$/', $line)) {
break;
}
unset($reversed[$index]);
}
$fixed = \array_reverse($reversed);
$tmp = \implode("\n", $fixed);
if (\substr($tmp, -1) !== "\n") {
$tmp .= "\n";
}
$this->block->setLiteral($tmp);
}
}
CommonMark/Parser/Block/HeadingStartParser.php 0000644 00000004735 15152100360 0015342 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Block;
use League\CommonMark\Parser\Block\BlockStart;
use League\CommonMark\Parser\Block\BlockStartParserInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\MarkdownParserStateInterface;
use League\CommonMark\Util\RegexHelper;
class HeadingStartParser implements BlockStartParserInterface
{
public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
{
if ($cursor->isIndented() || ! \in_array($cursor->getNextNonSpaceCharacter(), ['#', '-', '='], true)) {
return BlockStart::none();
}
$cursor->advanceToNextNonSpaceOrTab();
if ($atxHeading = self::getAtxHeader($cursor)) {
return BlockStart::of($atxHeading)->at($cursor);
}
$setextHeadingLevel = self::getSetextHeadingLevel($cursor);
if ($setextHeadingLevel > 0) {
$content = $parserState->getParagraphContent();
if ($content !== null) {
$cursor->advanceToEnd();
return BlockStart::of(new HeadingParser($setextHeadingLevel, $content))
->at($cursor)
->replaceActiveBlockParser();
}
}
return BlockStart::none();
}
private static function getAtxHeader(Cursor $cursor): ?HeadingParser
{
$match = RegexHelper::matchFirst('/^#{1,6}(?:[ \t]+|$)/', $cursor->getRemainder());
if (! $match) {
return null;
}
$cursor->advanceToNextNonSpaceOrTab();
$cursor->advanceBy(\strlen($match[0]));
$level = \strlen(\trim($match[0]));
$str = $cursor->getRemainder();
$str = \preg_replace('/^[ \t]*#+[ \t]*$/', '', $str);
\assert(\is_string($str));
$str = \preg_replace('/[ \t]+#+[ \t]*$/', '', $str);
\assert(\is_string($str));
return new HeadingParser($level, $str);
}
private static function getSetextHeadingLevel(Cursor $cursor): int
{
$match = RegexHelper::matchFirst('/^(?:=+|-+)[ \t]*$/', $cursor->getRemainder());
if ($match === null) {
return 0;
}
return $match[0][0] === '=' ? 1 : 2;
}
}
CommonMark/Parser/Block/HtmlBlockParser.php 0000644 00000004372 15152100360 0014641 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock;
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
use League\CommonMark\Parser\Block\BlockContinue;
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Util\RegexHelper;
final class HtmlBlockParser extends AbstractBlockContinueParser
{
/** @psalm-readonly */
private HtmlBlock $block;
private string $content = '';
private bool $finished = false;
/**
* @psalm-param HtmlBlock::TYPE_* $blockType
*
* @phpstan-param HtmlBlock::TYPE_* $blockType
*/
public function __construct(int $blockType)
{
$this->block = new HtmlBlock($blockType);
}
public function getBlock(): HtmlBlock
{
return $this->block;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
if ($this->finished) {
return BlockContinue::none();
}
if ($cursor->isBlank() && \in_array($this->block->getType(), [HtmlBlock::TYPE_6_BLOCK_ELEMENT, HtmlBlock::TYPE_7_MISC_ELEMENT], true)) {
return BlockContinue::none();
}
return BlockContinue::at($cursor);
}
public function addLine(string $line): void
{
if ($this->content !== '') {
$this->content .= "\n";
}
$this->content .= $line;
// Check for end condition
// phpcs:disable SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed
if ($this->block->getType() <= HtmlBlock::TYPE_5_CDATA) {
if (\preg_match(RegexHelper::getHtmlBlockCloseRegex($this->block->getType()), $line) === 1) {
$this->finished = true;
}
}
}
public function closeBlock(): void
{
$this->block->setLiteral($this->content);
$this->content = '';
}
}
CommonMark/Parser/Block/IndentedCodeStartParser.php 0000644 00000002273 15152100360 0016323 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Block;
use League\CommonMark\Node\Block\Paragraph;
use League\CommonMark\Parser\Block\BlockStart;
use League\CommonMark\Parser\Block\BlockStartParserInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\MarkdownParserStateInterface;
final class IndentedCodeStartParser implements BlockStartParserInterface
{
public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
{
if (! $cursor->isIndented()) {
return BlockStart::none();
}
if ($parserState->getActiveBlockParser()->getBlock() instanceof Paragraph) {
return BlockStart::none();
}
if ($cursor->isBlank()) {
return BlockStart::none();
}
$cursor->advanceBy(Cursor::INDENT_LEVEL, true);
return BlockStart::of(new IndentedCodeParser())->at($cursor);
}
}
CommonMark/Parser/Block/BlockQuoteParser.php 0000644 00000003052 15152100360 0015024 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\BlockQuote;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
use League\CommonMark\Parser\Block\BlockContinue;
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
use League\CommonMark\Parser\Cursor;
final class BlockQuoteParser extends AbstractBlockContinueParser
{
/** @psalm-readonly */
private BlockQuote $block;
public function __construct()
{
$this->block = new BlockQuote();
}
public function getBlock(): BlockQuote
{
return $this->block;
}
public function isContainer(): bool
{
return true;
}
public function canContain(AbstractBlock $childBlock): bool
{
return true;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
if (! $cursor->isIndented() && $cursor->getNextNonSpaceCharacter() === '>') {
$cursor->advanceToNextNonSpaceOrTab();
$cursor->advanceBy(1);
$cursor->advanceBySpaceOrTab();
return BlockContinue::at($cursor);
}
return BlockContinue::none();
}
}
CommonMark/Parser/Block/ListItemParser.php 0000644 00000005573 15152100360 0014520 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock;
use League\CommonMark\Extension\CommonMark\Node\Block\ListData;
use League\CommonMark\Extension\CommonMark\Node\Block\ListItem;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Node\Block\Paragraph;
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
use League\CommonMark\Parser\Block\BlockContinue;
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
use League\CommonMark\Parser\Cursor;
final class ListItemParser extends AbstractBlockContinueParser
{
/** @psalm-readonly */
private ListItem $block;
private bool $hadBlankLine = false;
public function __construct(ListData $listData)
{
$this->block = new ListItem($listData);
}
public function getBlock(): ListItem
{
return $this->block;
}
public function isContainer(): bool
{
return true;
}
public function canContain(AbstractBlock $childBlock): bool
{
if ($this->hadBlankLine) {
// We saw a blank line in this list item, that means the list block is loose.
//
// spec: if any of its constituent list items directly contain two block-level elements with a blank line
// between them
$parent = $this->block->parent();
if ($parent instanceof ListBlock) {
$parent->setTight(false);
}
}
return true;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
if ($cursor->isBlank()) {
if ($this->block->firstChild() === null) {
// Blank line after empty list item
return BlockContinue::none();
}
$activeBlock = $activeBlockParser->getBlock();
// If the active block is a code block, blank lines in it should not affect if the list is tight.
$this->hadBlankLine = $activeBlock instanceof Paragraph || $activeBlock instanceof ListItem;
$cursor->advanceToNextNonSpaceOrTab();
return BlockContinue::at($cursor);
}
$contentIndent = $this->block->getListData()->markerOffset + $this->getBlock()->getListData()->padding;
if ($cursor->getIndent() >= $contentIndent) {
$cursor->advanceBy($contentIndent, true);
return BlockContinue::at($cursor);
}
// Note: We'll hit this case for lazy continuation lines, they will get added later.
return BlockContinue::none();
}
}
CommonMark/Parser/Block/ThematicBreakParser.php 0000644 00000002226 15152100360 0015461 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\ThematicBreak;
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
use League\CommonMark\Parser\Block\BlockContinue;
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
use League\CommonMark\Parser\Cursor;
final class ThematicBreakParser extends AbstractBlockContinueParser
{
/** @psalm-readonly */
private ThematicBreak $block;
public function __construct()
{
$this->block = new ThematicBreak();
}
public function getBlock(): ThematicBreak
{
return $this->block;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
// a horizontal rule can never container > 1 line, so fail to match
return BlockContinue::none();
}
}
CommonMark/Parser/Block/FencedCodeParser.php 0000644 00000005340 15152100360 0014735 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
use League\CommonMark\Parser\Block\BlockContinue;
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Util\ArrayCollection;
use League\CommonMark\Util\RegexHelper;
final class FencedCodeParser extends AbstractBlockContinueParser
{
/** @psalm-readonly */
private FencedCode $block;
/** @var ArrayCollection<string> */
private ArrayCollection $strings;
public function __construct(int $fenceLength, string $fenceChar, int $fenceOffset)
{
$this->block = new FencedCode($fenceLength, $fenceChar, $fenceOffset);
$this->strings = new ArrayCollection();
}
public function getBlock(): FencedCode
{
return $this->block;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
// Check for closing code fence
if (! $cursor->isIndented() && $cursor->getNextNonSpaceCharacter() === $this->block->getChar()) {
$match = RegexHelper::matchFirst('/^(?:`{3,}|~{3,})(?= *$)/', $cursor->getLine(), $cursor->getNextNonSpacePosition());
if ($match !== null && \strlen($match[0]) >= $this->block->getLength()) {
// closing fence - we're at end of line, so we can finalize now
return BlockContinue::finished();
}
}
// Skip optional spaces of fence offset
// Optimization: don't attempt to match if we're at a non-space position
if ($cursor->getNextNonSpacePosition() > $cursor->getPosition()) {
$cursor->match('/^ {0,' . $this->block->getOffset() . '}/');
}
return BlockContinue::at($cursor);
}
public function addLine(string $line): void
{
$this->strings[] = $line;
}
public function closeBlock(): void
{
// first line becomes info string
$firstLine = $this->strings->first();
if ($firstLine === false) {
$firstLine = '';
}
$this->block->setInfo(RegexHelper::unescape(\trim($firstLine)));
if ($this->strings->count() === 1) {
$this->block->setLiteral('');
} else {
$this->block->setLiteral(\implode("\n", $this->strings->slice(1)) . "\n");
}
}
}
CommonMark/Parser/Block/ListBlockStartParser.php 0000644 00000012553 15152100360 0015666 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock;
use League\CommonMark\Extension\CommonMark\Node\Block\ListData;
use League\CommonMark\Parser\Block\BlockStart;
use League\CommonMark\Parser\Block\BlockStartParserInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\MarkdownParserStateInterface;
use League\CommonMark\Util\RegexHelper;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class ListBlockStartParser implements BlockStartParserInterface, ConfigurationAwareInterface
{
/** @psalm-readonly-allow-private-mutation */
private ?ConfigurationInterface $config = null;
/**
* @psalm-var non-empty-string|null
*
* @psalm-readonly-allow-private-mutation
*/
private ?string $listMarkerRegex = null;
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}
public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
{
if ($cursor->isIndented()) {
return BlockStart::none();
}
$listData = $this->parseList($cursor, $parserState->getParagraphContent() !== null);
if ($listData === null) {
return BlockStart::none();
}
$listItemParser = new ListItemParser($listData);
// prepend the list block if needed
$matched = $parserState->getLastMatchedBlockParser();
if (! ($matched instanceof ListBlockParser) || ! $listData->equals($matched->getBlock()->getListData())) {
$listBlockParser = new ListBlockParser($listData);
// We start out with assuming a list is tight. If we find a blank line, we set it to loose later.
$listBlockParser->getBlock()->setTight(true);
return BlockStart::of($listBlockParser, $listItemParser)->at($cursor);
}
return BlockStart::of($listItemParser)->at($cursor);
}
private function parseList(Cursor $cursor, bool $inParagraph): ?ListData
{
$indent = $cursor->getIndent();
$tmpCursor = clone $cursor;
$tmpCursor->advanceToNextNonSpaceOrTab();
$rest = $tmpCursor->getRemainder();
if (\preg_match($this->listMarkerRegex ?? $this->generateListMarkerRegex(), $rest) === 1) {
$data = new ListData();
$data->markerOffset = $indent;
$data->type = ListBlock::TYPE_BULLET;
$data->delimiter = null;
$data->bulletChar = $rest[0];
$markerLength = 1;
} elseif (($matches = RegexHelper::matchFirst('/^(\d{1,9})([.)])/', $rest)) && (! $inParagraph || $matches[1] === '1')) {
$data = new ListData();
$data->markerOffset = $indent;
$data->type = ListBlock::TYPE_ORDERED;
$data->start = (int) $matches[1];
$data->delimiter = $matches[2] === '.' ? ListBlock::DELIM_PERIOD : ListBlock::DELIM_PAREN;
$data->bulletChar = null;
$markerLength = \strlen($matches[0]);
} else {
return null;
}
// Make sure we have spaces after
$nextChar = $tmpCursor->peek($markerLength);
if (! ($nextChar === null || $nextChar === "\t" || $nextChar === ' ')) {
return null;
}
// If it interrupts paragraph, make sure first line isn't blank
if ($inParagraph && ! RegexHelper::matchAt(RegexHelper::REGEX_NON_SPACE, $rest, $markerLength)) {
return null;
}
$cursor->advanceToNextNonSpaceOrTab(); // to start of marker
$cursor->advanceBy($markerLength, true); // to end of marker
$data->padding = self::calculateListMarkerPadding($cursor, $markerLength);
return $data;
}
private static function calculateListMarkerPadding(Cursor $cursor, int $markerLength): int
{
$start = $cursor->saveState();
$spacesStartCol = $cursor->getColumn();
while ($cursor->getColumn() - $spacesStartCol < 5) {
if (! $cursor->advanceBySpaceOrTab()) {
break;
}
}
$blankItem = $cursor->peek() === null;
$spacesAfterMarker = $cursor->getColumn() - $spacesStartCol;
if ($spacesAfterMarker >= 5 || $spacesAfterMarker < 1 || $blankItem) {
$cursor->restoreState($start);
$cursor->advanceBySpaceOrTab();
return $markerLength + 1;
}
return $markerLength + $spacesAfterMarker;
}
/**
* @psalm-return non-empty-string
*/
private function generateListMarkerRegex(): string
{
// No configuration given - use the defaults
if ($this->config === null) {
return $this->listMarkerRegex = '/^[*+-]/';
}
$markers = $this->config->get('commonmark/unordered_list_markers');
\assert(\is_array($markers));
return $this->listMarkerRegex = '/^[' . \preg_quote(\implode('', $markers), '/') . ']/';
}
}
CommonMark/Parser/Block/FencedCodeStartParser.php 0000644 00000002342 15152100360 0015752 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Block;
use League\CommonMark\Parser\Block\BlockStart;
use League\CommonMark\Parser\Block\BlockStartParserInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\MarkdownParserStateInterface;
final class FencedCodeStartParser implements BlockStartParserInterface
{
public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
{
if ($cursor->isIndented() || ! \in_array($cursor->getNextNonSpaceCharacter(), ['`', '~'], true)) {
return BlockStart::none();
}
$indent = $cursor->getIndent();
$fence = $cursor->match('/^[ \t]*(?:`{3,}(?!.*`)|~{3,})/');
if ($fence === null) {
return BlockStart::none();
}
// fenced code block
$fence = \ltrim($fence, " \t");
return BlockStart::of(new FencedCodeParser(\strlen($fence), $fence[0], $indent))->at($cursor);
}
}
CommonMark/Parser/Block/BlockQuoteStartParser.php 0000644 00000002131 15152100360 0016037 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Block;
use League\CommonMark\Parser\Block\BlockStart;
use League\CommonMark\Parser\Block\BlockStartParserInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\MarkdownParserStateInterface;
final class BlockQuoteStartParser implements BlockStartParserInterface
{
public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
{
if ($cursor->isIndented()) {
return BlockStart::none();
}
if ($cursor->getNextNonSpaceCharacter() !== '>') {
return BlockStart::none();
}
$cursor->advanceToNextNonSpaceOrTab();
$cursor->advanceBy(1);
$cursor->advanceBySpaceOrTab();
return BlockStart::of(new BlockQuoteParser())->at($cursor);
}
}
CommonMark/Parser/Block/HtmlBlockStartParser.php 0000644 00000004126 15152100360 0015654 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock;
use League\CommonMark\Node\Block\Paragraph;
use League\CommonMark\Parser\Block\BlockStart;
use League\CommonMark\Parser\Block\BlockStartParserInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\MarkdownParserStateInterface;
use League\CommonMark\Util\RegexHelper;
final class HtmlBlockStartParser implements BlockStartParserInterface
{
public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
{
if ($cursor->isIndented() || $cursor->getNextNonSpaceCharacter() !== '<') {
return BlockStart::none();
}
$tmpCursor = clone $cursor;
$tmpCursor->advanceToNextNonSpaceOrTab();
$line = $tmpCursor->getRemainder();
for ($blockType = 1; $blockType <= 7; $blockType++) {
/** @psalm-var HtmlBlock::TYPE_* $blockType */
/** @phpstan-var HtmlBlock::TYPE_* $blockType */
$match = RegexHelper::matchAt(
RegexHelper::getHtmlBlockOpenRegex($blockType),
$line
);
if ($match !== null && ($blockType < 7 || $this->isType7BlockAllowed($cursor, $parserState))) {
return BlockStart::of(new HtmlBlockParser($blockType))->at($cursor);
}
}
return BlockStart::none();
}
private function isType7BlockAllowed(Cursor $cursor, MarkdownParserStateInterface $parserState): bool
{
// Type 7 blocks can't interrupt paragraphs
if ($parserState->getLastMatchedBlockParser()->getBlock() instanceof Paragraph) {
return false;
}
// Even lazy ones
return ! $parserState->getActiveBlockParser()->canHaveLazyContinuationLines();
}
}
CommonMark/Parser/Block/ThematicBreakStartParser.php 0000644 00000002367 15152100360 0016505 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Block;
use League\CommonMark\Parser\Block\BlockStart;
use League\CommonMark\Parser\Block\BlockStartParserInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\MarkdownParserStateInterface;
use League\CommonMark\Util\RegexHelper;
final class ThematicBreakStartParser implements BlockStartParserInterface
{
public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
{
if ($cursor->isIndented()) {
return BlockStart::none();
}
$match = RegexHelper::matchAt(RegexHelper::REGEX_THEMATIC_BREAK, $cursor->getLine(), $cursor->getNextNonSpacePosition());
if ($match === null) {
return BlockStart::none();
}
// Advance to the end of the string, consuming the entire line (of the thematic break)
$cursor->advanceToEnd();
return BlockStart::of(new ThematicBreakParser())->at($cursor);
}
}
CommonMark/Parser/Block/HeadingParser.php 0000644 00000002744 15152100360 0014322 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
use League\CommonMark\Parser\Block\BlockContinue;
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
use League\CommonMark\Parser\Block\BlockContinueParserWithInlinesInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\InlineParserEngineInterface;
final class HeadingParser extends AbstractBlockContinueParser implements BlockContinueParserWithInlinesInterface
{
/** @psalm-readonly */
private Heading $block;
private string $content;
public function __construct(int $level, string $content)
{
$this->block = new Heading($level);
$this->content = $content;
}
public function getBlock(): Heading
{
return $this->block;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
return BlockContinue::none();
}
public function parseInlines(InlineParserEngineInterface $inlineParser): void
{
$inlineParser->parse($this->content, $this->block);
}
}
CommonMark/Parser/Block/ListBlockParser.php 0000644 00000004504 15152100360 0014645 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Parser\Block;
use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock;
use League\CommonMark\Extension\CommonMark\Node\Block\ListData;
use League\CommonMark\Extension\CommonMark\Node\Block\ListItem;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
use League\CommonMark\Parser\Block\BlockContinue;
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
use League\CommonMark\Parser\Cursor;
final class ListBlockParser extends AbstractBlockContinueParser
{
/** @psalm-readonly */
private ListBlock $block;
private bool $hadBlankLine = false;
private int $linesAfterBlank = 0;
public function __construct(ListData $listData)
{
$this->block = new ListBlock($listData);
}
public function getBlock(): ListBlock
{
return $this->block;
}
public function isContainer(): bool
{
return true;
}
public function canContain(AbstractBlock $childBlock): bool
{
if (! $childBlock instanceof ListItem) {
return false;
}
// Another list item is being added to this list block.
// If the previous line was blank, that means this list
// block is "loose" (not tight).
if ($this->hadBlankLine && $this->linesAfterBlank === 1) {
$this->block->setTight(false);
$this->hadBlankLine = false;
}
return true;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
if ($cursor->isBlank()) {
$this->hadBlankLine = true;
$this->linesAfterBlank = 0;
} elseif ($this->hadBlankLine) {
$this->linesAfterBlank++;
}
// List blocks themselves don't have any markers, only list items. So try to stay in the list.
// If there is a block start other than list item, canContain makes sure that this list is closed.
return BlockContinue::at($cursor);
}
}
CommonMark/Delimiter/Processor/EmphasisDelimiterProcessor.php 0000644 00000006225 15152100360 0020523 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* Additional emphasis processing code based on commonmark-java (https://github.com/atlassian/commonmark-java)
* - (c) Atlassian Pty Ltd
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\CommonMark\Delimiter\Processor;
use League\CommonMark\Delimiter\DelimiterInterface;
use League\CommonMark\Delimiter\Processor\DelimiterProcessorInterface;
use League\CommonMark\Extension\CommonMark\Node\Inline\Emphasis;
use League\CommonMark\Extension\CommonMark\Node\Inline\Strong;
use League\CommonMark\Node\Inline\AbstractStringContainer;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class EmphasisDelimiterProcessor implements DelimiterProcessorInterface, ConfigurationAwareInterface
{
/** @psalm-readonly */
private string $char;
/** @psalm-readonly-allow-private-mutation */
private ConfigurationInterface $config;
/**
* @param string $char The emphasis character to use (typically '*' or '_')
*/
public function __construct(string $char)
{
$this->char = $char;
}
public function getOpeningCharacter(): string
{
return $this->char;
}
public function getClosingCharacter(): string
{
return $this->char;
}
public function getMinLength(): int
{
return 1;
}
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int
{
// "Multiple of 3" rule for internal delimiter runs
if (($opener->canClose() || $closer->canOpen()) && $closer->getOriginalLength() % 3 !== 0 && ($opener->getOriginalLength() + $closer->getOriginalLength()) % 3 === 0) {
return 0;
}
// Calculate actual number of delimiters used from this closer
if ($opener->getLength() >= 2 && $closer->getLength() >= 2) {
if ($this->config->get('commonmark/enable_strong')) {
return 2;
}
return 0;
}
if ($this->config->get('commonmark/enable_em')) {
return 1;
}
return 0;
}
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void
{
if ($delimiterUse === 1) {
$emphasis = new Emphasis($this->char);
} elseif ($delimiterUse === 2) {
$emphasis = new Strong($this->char . $this->char);
} else {
return;
}
$next = $opener->next();
while ($next !== null && $next !== $closer) {
$tmp = $next->next();
$emphasis->appendChild($next);
$next = $tmp;
}
$opener->insertAfter($emphasis);
}
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}
}
Strikethrough/StrikethroughDelimiterProcessor.php 0000644 00000003241 15152100360 0016511 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com> and uAfrica.com (http://uafrica.com)
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Strikethrough;
use League\CommonMark\Delimiter\DelimiterInterface;
use League\CommonMark\Delimiter\Processor\DelimiterProcessorInterface;
use League\CommonMark\Node\Inline\AbstractStringContainer;
final class StrikethroughDelimiterProcessor implements DelimiterProcessorInterface
{
public function getOpeningCharacter(): string
{
return '~';
}
public function getClosingCharacter(): string
{
return '~';
}
public function getMinLength(): int
{
return 1;
}
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int
{
if ($opener->getLength() > 2 && $closer->getLength() > 2) {
return 0;
}
if ($opener->getLength() !== $closer->getLength()) {
return 0;
}
return \min($opener->getLength(), $closer->getLength());
}
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void
{
$strikethrough = new Strikethrough(\str_repeat('~', $delimiterUse));
$tmp = $opener->next();
while ($tmp !== null && $tmp !== $closer) {
$next = $tmp->next();
$strikethrough->appendChild($tmp);
$tmp = $next;
}
$opener->insertAfter($strikethrough);
}
}
Strikethrough/StrikethroughExtension.php 0000644 00000001447 15152100360 0014655 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com> and uAfrica.com (http://uafrica.com)
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Strikethrough;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Extension\ExtensionInterface;
final class StrikethroughExtension implements ExtensionInterface
{
public function register(EnvironmentBuilderInterface $environment): void
{
$environment->addDelimiterProcessor(new StrikethroughDelimiterProcessor());
$environment->addRenderer(Strikethrough::class, new StrikethroughRenderer());
}
}
Strikethrough/Strikethrough.php 0000644 00000001624 15152100360 0012755 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com> and uAfrica.com (http://uafrica.com)
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Strikethrough;
use League\CommonMark\Node\Inline\AbstractInline;
use League\CommonMark\Node\Inline\DelimitedInterface;
final class Strikethrough extends AbstractInline implements DelimitedInterface
{
private string $delimiter;
public function __construct(string $delimiter = '~~')
{
parent::__construct();
$this->delimiter = $delimiter;
}
public function getOpeningDelimiter(): string
{
return $this->delimiter;
}
public function getClosingDelimiter(): string
{
return $this->delimiter;
}
}
Strikethrough/StrikethroughRenderer.php 0000644 00000002473 15152100360 0014447 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com> and uAfrica.com (http://uafrica.com)
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Strikethrough;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class StrikethroughRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param Strikethrough $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
Strikethrough::assertInstanceOf($node);
return new HtmlElement('del', $node->data->get('attributes'), $childRenderer->renderNodes($node->children()));
}
public function getXmlTagName(Node $node): string
{
return 'strikethrough';
}
/**
* {@inheritDoc}
*/
public function getXmlAttributes(Node $node): array
{
return [];
}
}
Attributes/Node/Attributes.php 0000644 00000002567 15152100360 0012421 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) 2015 Martin HasoΕ <martin.hason@gmail.com>
*
* 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\Node;
use League\CommonMark\Node\Block\AbstractBlock;
final class Attributes extends AbstractBlock
{
public const TARGET_PARENT = 0;
public const TARGET_PREVIOUS = 1;
public const TARGET_NEXT = 2;
/** @var array<string, mixed> */
private array $attributes;
private int $target = self::TARGET_NEXT;
/**
* @param array<string, mixed> $attributes
*/
public function __construct(array $attributes)
{
parent::__construct();
$this->attributes = $attributes;
}
/**
* @return array<string, mixed>
*/
public function getAttributes(): array
{
return $this->attributes;
}
/**
* @param array<string, mixed> $attributes
*/
public function setAttributes(array $attributes): void
{
$this->attributes = $attributes;
}
public function getTarget(): int
{
return $this->target;
}
public function setTarget(int $target): void
{
$this->target = $target;
}
}
Attributes/Node/AttributesInline.php 0000644 00000002311 15152100360 0013543 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) 2015 Martin HasoΕ <martin.hason@gmail.com>
*
* 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\Node;
use League\CommonMark\Node\Inline\AbstractInline;
final class AttributesInline extends AbstractInline
{
/** @var array<string, mixed> */
private array $attributes;
private bool $block;
/**
* @param array<string, mixed> $attributes
*/
public function __construct(array $attributes, bool $block)
{
parent::__construct();
$this->attributes = $attributes;
$this->block = $block;
}
/**
* @return array<string, mixed>
*/
public function getAttributes(): array
{
return $this->attributes;
}
/**
* @param array<string, mixed> $attributes
*/
public function setAttributes(array $attributes): void
{
$this->attributes = $attributes;
}
public function isBlock(): bool
{
return $this->block;
}
}
Attributes/Util/AttributesHelper.php 0000644 00000010553 15152100360 0013603 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) 2015 Martin HasoΕ <martin.hason@gmail.com>
*
* 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-z0-9-]+|' . RegexHelper::PARTIAL_ATTRIBUTENAME . RegexHelper::PARTIAL_ATTRIBUTEVALUESPEC . ')\s*';
private const ATTRIBUTE_LIST = '/^{:?(' . self::SINGLE_ATTRIBUTE . ')+}/i';
/**
* @return array<string, mixed>
*/
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<string, mixed> $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);
$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<string, mixed> $attributes1
* @param Node|array<string, mixed> $attributes2
*
* @return array<string, mixed>
*/
public static function mergeAttributes($attributes1, $attributes2): array
{
$attributes = [];
foreach ([$attributes1, $attributes2] as $arg) {
if ($arg instanceof Node) {
$arg = $arg->data->get('attributes');
}
/** @var array<string, mixed> $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;
}
}
Attributes/Event/AttributesListener.php 0000644 00000010503 15152100360 0014310 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) 2015 Martin HasoΕ <martin.hason@gmail.com>
*
* 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\Event;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\Attributes\Node\Attributes;
use League\CommonMark\Extension\Attributes\Node\AttributesInline;
use League\CommonMark\Extension\Attributes\Util\AttributesHelper;
use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock;
use League\CommonMark\Extension\CommonMark\Node\Block\ListItem;
use League\CommonMark\Node\Inline\AbstractInline;
use League\CommonMark\Node\Node;
final class AttributesListener
{
private const DIRECTION_PREFIX = 'prefix';
private const DIRECTION_SUFFIX = 'suffix';
public function processDocument(DocumentParsedEvent $event): void
{
foreach ($event->getDocument()->iterator() as $node) {
if (! ($node instanceof Attributes || $node instanceof AttributesInline)) {
continue;
}
[$target, $direction] = self::findTargetAndDirection($node);
if ($target instanceof Node) {
$parent = $target->parent();
if ($parent instanceof ListItem && $parent->parent() instanceof ListBlock && $parent->parent()->isTight()) {
$target = $parent;
}
if ($direction === self::DIRECTION_SUFFIX) {
$attributes = AttributesHelper::mergeAttributes($target, $node->getAttributes());
} else {
$attributes = AttributesHelper::mergeAttributes($node->getAttributes(), $target);
}
$target->data->set('attributes', $attributes);
}
$node->detach();
}
}
/**
* @param Attributes|AttributesInline $node
*
* @return array<Node|string|null>
*/
private static function findTargetAndDirection($node): array
{
$target = null;
$direction = null;
$previous = $next = $node;
while (true) {
$previous = self::getPrevious($previous);
$next = self::getNext($next);
if ($previous === null && $next === null) {
if (! $node->parent() instanceof FencedCode) {
$target = $node->parent();
$direction = self::DIRECTION_SUFFIX;
}
break;
}
if ($node instanceof AttributesInline && ($previous === null || ($previous instanceof AbstractInline && $node->isBlock()))) {
continue;
}
if ($previous !== null && ! self::isAttributesNode($previous)) {
$target = $previous;
$direction = self::DIRECTION_SUFFIX;
break;
}
if ($next !== null && ! self::isAttributesNode($next)) {
$target = $next;
$direction = self::DIRECTION_PREFIX;
break;
}
}
return [$target, $direction];
}
/**
* Get any previous block (sibling or parent) this might apply to
*/
private static function getPrevious(?Node $node = null): ?Node
{
if ($node instanceof Attributes) {
if ($node->getTarget() === Attributes::TARGET_NEXT) {
return null;
}
if ($node->getTarget() === Attributes::TARGET_PARENT) {
return $node->parent();
}
}
return $node instanceof Node ? $node->previous() : null;
}
/**
* Get any previous block (sibling or parent) this might apply to
*/
private static function getNext(?Node $node = null): ?Node
{
if ($node instanceof Attributes && $node->getTarget() !== Attributes::TARGET_NEXT) {
return null;
}
return $node instanceof Node ? $node->next() : null;
}
private static function isAttributesNode(Node $node): bool
{
return $node instanceof Attributes || $node instanceof AttributesInline;
}
}
Attributes/AttributesExtension.php 0000644 00000002230 15152100360 0013414 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) 2015 Martin HasoΕ <martin.hason@gmail.com>
*
* 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;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\Attributes\Event\AttributesListener;
use League\CommonMark\Extension\Attributes\Parser\AttributesBlockStartParser;
use League\CommonMark\Extension\Attributes\Parser\AttributesInlineParser;
use League\CommonMark\Extension\ExtensionInterface;
final class AttributesExtension implements ExtensionInterface
{
public function register(EnvironmentBuilderInterface $environment): void
{
$environment->addBlockStartParser(new AttributesBlockStartParser());
$environment->addInlineParser(new AttributesInlineParser());
$environment->addEventListener(DocumentParsedEvent::class, [new AttributesListener(), 'processDocument']);
}
}
Attributes/Parser/AttributesInlineParser.php 0000644 00000003232 15152100360 0015272 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) 2015 Martin HasoΕ <martin.hason@gmail.com>
*
* 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\Parser;
use League\CommonMark\Extension\Attributes\Node\AttributesInline;
use League\CommonMark\Extension\Attributes\Util\AttributesHelper;
use League\CommonMark\Node\StringContainerInterface;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
final class AttributesInlineParser implements InlineParserInterface
{
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::string('{');
}
public function parse(InlineParserContext $inlineContext): bool
{
$cursor = $inlineContext->getCursor();
$char = (string) $cursor->peek(-1);
$attributes = AttributesHelper::parseAttributes($cursor);
if ($attributes === []) {
return false;
}
if ($char === ' ' && ($prev = $inlineContext->getContainer()->lastChild()) instanceof StringContainerInterface) {
$prev->setLiteral(\rtrim($prev->getLiteral(), ' '));
}
if ($char === '') {
$cursor->advanceToNextNonSpaceOrNewline();
}
$node = new AttributesInline($attributes, $char === ' ' || $char === '');
$inlineContext->getContainer()->appendChild($node);
return true;
}
}
Attributes/Parser/AttributesBlockContinueParser.php 0000644 00000006271 15152100360 0016621 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) 2015 Martin HasoΕ <martin.hason@gmail.com>
*
* 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\Parser;
use League\CommonMark\Extension\Attributes\Node\Attributes;
use League\CommonMark\Extension\Attributes\Util\AttributesHelper;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
use League\CommonMark\Parser\Block\BlockContinue;
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
use League\CommonMark\Parser\Cursor;
final class AttributesBlockContinueParser extends AbstractBlockContinueParser
{
private Attributes $block;
private AbstractBlock $container;
private bool $hasSubsequentLine = false;
/**
* @param array<string, mixed> $attributes The attributes identified by the block start parser
* @param AbstractBlock $container The node we were in when these attributes were discovered
*/
public function __construct(array $attributes, AbstractBlock $container)
{
$this->block = new Attributes($attributes);
$this->container = $container;
}
public function getBlock(): AbstractBlock
{
return $this->block;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
$this->hasSubsequentLine = true;
$cursor->advanceToNextNonSpaceOrTab();
// Does this next line also have attributes?
$attributes = AttributesHelper::parseAttributes($cursor);
$cursor->advanceToNextNonSpaceOrTab();
if ($cursor->isAtEnd() && $attributes !== []) {
// It does! Merge them into what we parsed previously
$this->block->setAttributes(AttributesHelper::mergeAttributes(
$this->block->getAttributes(),
$attributes
));
// Tell the core parser we've consumed everything
return BlockContinue::at($cursor);
}
// Okay, so there are no attributes on the next line
// If this next line is blank we know we can't target the next node, it must be a previous one
if ($cursor->isBlank()) {
$this->block->setTarget(Attributes::TARGET_PREVIOUS);
}
return BlockContinue::none();
}
public function closeBlock(): void
{
// Attributes appearing at the very end of the document won't have any last lines to check
// so we can make that determination here
if (! $this->hasSubsequentLine) {
$this->block->setTarget(Attributes::TARGET_PREVIOUS);
}
// We know this block must apply to the "previous" block, but that could be a sibling or parent,
// so we check the containing block to see which one it might be.
if ($this->block->getTarget() === Attributes::TARGET_PREVIOUS && $this->block->parent() === $this->container) {
$this->block->setTarget(Attributes::TARGET_PARENT);
}
}
}
Attributes/Parser/AttributesBlockStartParser.php 0000644 00000002513 15152100360 0016125 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) 2015 Martin HasoΕ <martin.hason@gmail.com>
*
* 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\Parser;
use League\CommonMark\Extension\Attributes\Util\AttributesHelper;
use League\CommonMark\Parser\Block\BlockStart;
use League\CommonMark\Parser\Block\BlockStartParserInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\MarkdownParserStateInterface;
final class AttributesBlockStartParser implements BlockStartParserInterface
{
public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
{
$originalPosition = $cursor->getPosition();
$attributes = AttributesHelper::parseAttributes($cursor);
if ($attributes === [] && $originalPosition === $cursor->getPosition()) {
return BlockStart::none();
}
if ($cursor->getNextNonSpaceCharacter() !== null) {
return BlockStart::none();
}
return BlockStart::of(new AttributesBlockContinueParser($attributes, $parserState->getActiveBlockParser()->getBlock()))->at($cursor);
}
}
TaskList/TaskListItemMarker.php 0000644 00000001470 15152100360 0012525 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\TaskList;
use League\CommonMark\Node\Inline\AbstractInline;
final class TaskListItemMarker extends AbstractInline
{
/** @psalm-readonly-allow-private-mutation */
private bool $checked;
public function __construct(bool $isCompleted)
{
parent::__construct();
$this->checked = $isCompleted;
}
public function isChecked(): bool
{
return $this->checked;
}
public function setChecked(bool $checked): void
{
$this->checked = $checked;
}
}
TaskList/TaskListItemMarkerParser.php 0000644 00000003131 15152100360 0013676 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\TaskList;
use League\CommonMark\Extension\CommonMark\Node\Block\ListItem;
use League\CommonMark\Node\Block\Paragraph;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
final class TaskListItemMarkerParser implements InlineParserInterface
{
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::oneOf('[ ]', '[x]');
}
public function parse(InlineParserContext $inlineContext): bool
{
$container = $inlineContext->getContainer();
// Checkbox must come at the beginning of the first paragraph of the list item
if ($container->hasChildren() || ! ($container instanceof Paragraph && $container->parent() && $container->parent() instanceof ListItem)) {
return false;
}
$cursor = $inlineContext->getCursor();
$oldState = $cursor->saveState();
$cursor->advanceBy(3);
if ($cursor->getNextNonSpaceCharacter() === null) {
$cursor->restoreState($oldState);
return false;
}
$isChecked = $inlineContext->getFullMatch() !== '[ ]';
$container->appendChild(new TaskListItemMarker($isChecked));
return true;
}
}
TaskList/TaskListExtension.php 0000644 00000001371 15152100360 0012441 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\TaskList;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Extension\ExtensionInterface;
final class TaskListExtension implements ExtensionInterface
{
public function register(EnvironmentBuilderInterface $environment): void
{
$environment->addInlineParser(new TaskListItemMarkerParser(), 35);
$environment->addRenderer(TaskListItemMarker::class, new TaskListItemMarkerRenderer());
}
}
TaskList/TaskListItemMarkerRenderer.php 0000644 00000003425 15152100360 0014216 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\TaskList;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
final class TaskListItemMarkerRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param TaskListItemMarker $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
TaskListItemMarker::assertInstanceOf($node);
$attrs = $node->data->get('attributes');
$checkbox = new HtmlElement('input', $attrs, '', true);
if ($node->isChecked()) {
$checkbox->setAttribute('checked', '');
}
$checkbox->setAttribute('disabled', '');
$checkbox->setAttribute('type', 'checkbox');
return $checkbox;
}
public function getXmlTagName(Node $node): string
{
return 'task_list_item_marker';
}
/**
* @param TaskListItemMarker $node
*
* @return array<string, scalar>
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function getXmlAttributes(Node $node): array
{
TaskListItemMarker::assertInstanceOf($node);
if ($node->isChecked()) {
return ['checked' => 'checked'];
}
return [];
}
}
SmartPunct/EllipsesParser.php 0000644 00000002117 15152100360 0012304 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (http://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\Extension\SmartPunct;
use League\CommonMark\Node\Inline\Text;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
final class EllipsesParser implements InlineParserInterface
{
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::oneOf('...', '. . .');
}
public function parse(InlineParserContext $inlineContext): bool
{
$inlineContext->getCursor()->advanceBy($inlineContext->getFullMatchLength());
$inlineContext->getContainer()->appendChild(new Text('β¦'));
return true;
}
}
SmartPunct/Quote.php 0000644 00000001501 15152100360 0010440 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (http://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\Extension\SmartPunct;
use League\CommonMark\Node\Inline\AbstractStringContainer;
final class Quote extends AbstractStringContainer
{
public const DOUBLE_QUOTE = '"';
public const DOUBLE_QUOTE_OPENER = 'β';
public const DOUBLE_QUOTE_CLOSER = 'β';
public const SINGLE_QUOTE = "'";
public const SINGLE_QUOTE_OPENER = 'β';
public const SINGLE_QUOTE_CLOSER = 'β';
}
SmartPunct/SmartPunctExtension.php 0000644 00000005200 15152100360 0013340 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (http://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\Extension\SmartPunct;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\ConfigurableExtensionInterface;
use League\CommonMark\Node\Block\Document;
use League\CommonMark\Node\Block\Paragraph;
use League\CommonMark\Node\Inline\Text;
use League\CommonMark\Renderer\Block as CoreBlockRenderer;
use League\CommonMark\Renderer\Inline as CoreInlineRenderer;
use League\Config\ConfigurationBuilderInterface;
use Nette\Schema\Expect;
final class SmartPunctExtension implements ConfigurableExtensionInterface
{
public function configureSchema(ConfigurationBuilderInterface $builder): void
{
$builder->addSchema('smartpunct', Expect::structure([
'double_quote_opener' => Expect::string(Quote::DOUBLE_QUOTE_OPENER),
'double_quote_closer' => Expect::string(Quote::DOUBLE_QUOTE_CLOSER),
'single_quote_opener' => Expect::string(Quote::SINGLE_QUOTE_OPENER),
'single_quote_closer' => Expect::string(Quote::SINGLE_QUOTE_CLOSER),
]));
}
public function register(EnvironmentBuilderInterface $environment): void
{
$environment
->addInlineParser(new QuoteParser(), 10)
->addInlineParser(new DashParser(), 0)
->addInlineParser(new EllipsesParser(), 0)
->addDelimiterProcessor(QuoteProcessor::createDoubleQuoteProcessor(
$environment->getConfiguration()->get('smartpunct/double_quote_opener'),
$environment->getConfiguration()->get('smartpunct/double_quote_closer')
))
->addDelimiterProcessor(QuoteProcessor::createSingleQuoteProcessor(
$environment->getConfiguration()->get('smartpunct/single_quote_opener'),
$environment->getConfiguration()->get('smartpunct/single_quote_closer')
))
->addEventListener(DocumentParsedEvent::class, new ReplaceUnpairedQuotesListener())
->addRenderer(Document::class, new CoreBlockRenderer\DocumentRenderer(), 0)
->addRenderer(Paragraph::class, new CoreBlockRenderer\ParagraphRenderer(), 0)
->addRenderer(Text::class, new CoreInlineRenderer\TextRenderer(), 0);
}
}
SmartPunct/QuoteParser.php 0000644 00000006674 15152100360 0011635 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (http://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\Extension\SmartPunct;
use League\CommonMark\Delimiter\Delimiter;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
use League\CommonMark\Util\RegexHelper;
final class QuoteParser implements InlineParserInterface
{
public const DOUBLE_QUOTES = [Quote::DOUBLE_QUOTE, Quote::DOUBLE_QUOTE_OPENER, Quote::DOUBLE_QUOTE_CLOSER];
public const SINGLE_QUOTES = [Quote::SINGLE_QUOTE, Quote::SINGLE_QUOTE_OPENER, Quote::SINGLE_QUOTE_CLOSER];
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::oneOf(...[...self::DOUBLE_QUOTES, ...self::SINGLE_QUOTES]);
}
/**
* Normalizes any quote characters found and manually adds them to the delimiter stack
*/
public function parse(InlineParserContext $inlineContext): bool
{
$char = $inlineContext->getFullMatch();
$cursor = $inlineContext->getCursor();
$normalizedCharacter = $this->getNormalizedQuoteCharacter($char);
$charBefore = $cursor->peek(-1);
if ($charBefore === null) {
$charBefore = "\n";
}
$cursor->advance();
$charAfter = $cursor->getCurrentCharacter();
if ($charAfter === null) {
$charAfter = "\n";
}
[$leftFlanking, $rightFlanking] = $this->determineFlanking($charBefore, $charAfter);
$canOpen = $leftFlanking && ! $rightFlanking;
$canClose = $rightFlanking;
$node = new Quote($normalizedCharacter, ['delim' => true]);
$inlineContext->getContainer()->appendChild($node);
// Add entry to stack to this opener
$inlineContext->getDelimiterStack()->push(new Delimiter($normalizedCharacter, 1, $node, $canOpen, $canClose));
return true;
}
private function getNormalizedQuoteCharacter(string $character): string
{
if (\in_array($character, self::DOUBLE_QUOTES, true)) {
return Quote::DOUBLE_QUOTE;
}
if (\in_array($character, self::SINGLE_QUOTES, true)) {
return Quote::SINGLE_QUOTE;
}
return $character;
}
/**
* @return bool[]
*/
private function determineFlanking(string $charBefore, string $charAfter): array
{
$afterIsWhitespace = \preg_match('/\pZ|\s/u', $charAfter);
$afterIsPunctuation = \preg_match(RegexHelper::REGEX_PUNCTUATION, $charAfter);
$beforeIsWhitespace = \preg_match('/\pZ|\s/u', $charBefore);
$beforeIsPunctuation = \preg_match(RegexHelper::REGEX_PUNCTUATION, $charBefore);
$leftFlanking = ! $afterIsWhitespace &&
! ($afterIsPunctuation &&
! $beforeIsWhitespace &&
! $beforeIsPunctuation);
$rightFlanking = ! $beforeIsWhitespace &&
! ($beforeIsPunctuation &&
! $afterIsWhitespace &&
! $afterIsPunctuation);
return [$leftFlanking, $rightFlanking];
}
}
SmartPunct/DashParser.php 0000644 00000003562 15152100360 0011410 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (http://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\Extension\SmartPunct;
use League\CommonMark\Node\Inline\Text;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
final class DashParser implements InlineParserInterface
{
private const EN_DASH = 'β';
private const EM_DASH = 'β';
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::regex('(?<!-)(-{2,})');
}
public function parse(InlineParserContext $inlineContext): bool
{
$count = $inlineContext->getFullMatchLength();
$inlineContext->getCursor()->advanceBy($count);
$enCount = 0;
$emCount = 0;
if ($count % 3 === 0) { // If divisible by 3, use all em dashes
$emCount = (int) ($count / 3);
} elseif ($count % 2 === 0) { // If divisible by 2, use all en dashes
$enCount = (int) ($count / 2);
} elseif ($count % 3 === 2) { // If 2 extra dashes, use en dash for last 2; em dashes for rest
$emCount = (int) (($count - 2) / 3);
$enCount = 1;
} else { // Use en dashes for last 4 hyphens; em dashes for rest
$emCount = (int) (($count - 4) / 3);
$enCount = 2;
}
$inlineContext->getContainer()->appendChild(new Text(
\str_repeat(self::EM_DASH, $emCount) . \str_repeat(self::EN_DASH, $enCount)
));
return true;
}
}
SmartPunct/ReplaceUnpairedQuotesListener.php 0000644 00000002462 15152100360 0015324 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\SmartPunct;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Node\Inline\AdjacentTextMerger;
use League\CommonMark\Node\Inline\Text;
use League\CommonMark\Node\Query;
/**
* Identifies any lingering Quote nodes that were missing pairs and converts them into Text nodes
*/
final class ReplaceUnpairedQuotesListener
{
public function __invoke(DocumentParsedEvent $event): void
{
$query = (new Query())->where(Query::type(Quote::class));
foreach ($query->findAll($event->getDocument()) as $quote) {
\assert($quote instanceof Quote);
$literal = $quote->getLiteral();
if ($literal === Quote::SINGLE_QUOTE) {
$literal = Quote::SINGLE_QUOTE_CLOSER;
} elseif ($literal === Quote::DOUBLE_QUOTE) {
$literal = Quote::DOUBLE_QUOTE_OPENER;
}
$quote->replaceWith($new = new Text($literal));
AdjacentTextMerger::mergeWithDirectlyAdjacentNodes($new);
}
}
}
SmartPunct/QuoteProcessor.php 0000644 00000004473 15152100360 0012353 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (http://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\Extension\SmartPunct;
use League\CommonMark\Delimiter\DelimiterInterface;
use League\CommonMark\Delimiter\Processor\DelimiterProcessorInterface;
use League\CommonMark\Node\Inline\AbstractStringContainer;
final class QuoteProcessor implements DelimiterProcessorInterface
{
/** @psalm-readonly */
private string $normalizedCharacter;
/** @psalm-readonly */
private string $openerCharacter;
/** @psalm-readonly */
private string $closerCharacter;
private function __construct(string $char, string $opener, string $closer)
{
$this->normalizedCharacter = $char;
$this->openerCharacter = $opener;
$this->closerCharacter = $closer;
}
public function getOpeningCharacter(): string
{
return $this->normalizedCharacter;
}
public function getClosingCharacter(): string
{
return $this->normalizedCharacter;
}
public function getMinLength(): int
{
return 1;
}
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int
{
return 1;
}
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void
{
$opener->insertAfter(new Quote($this->openerCharacter));
$closer->insertBefore(new Quote($this->closerCharacter));
}
/**
* Create a double-quote processor
*/
public static function createDoubleQuoteProcessor(string $opener = Quote::DOUBLE_QUOTE_OPENER, string $closer = Quote::DOUBLE_QUOTE_CLOSER): self
{
return new self(Quote::DOUBLE_QUOTE, $opener, $closer);
}
/**
* Create a single-quote processor
*/
public static function createSingleQuoteProcessor(string $opener = Quote::SINGLE_QUOTE_OPENER, string $closer = Quote::SINGLE_QUOTE_CLOSER): self
{
return new self(Quote::SINGLE_QUOTE, $opener, $closer);
}
}
Autolink/AutolinkExtension.php 0000644 00000001320 15152100360 0012513 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Autolink;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Extension\ExtensionInterface;
final class AutolinkExtension implements ExtensionInterface
{
public function register(EnvironmentBuilderInterface $environment): void
{
$environment->addInlineParser(new EmailAutolinkParser());
$environment->addInlineParser(new UrlAutolinkParser());
}
}
Autolink/UrlAutolinkParser.php 0000644 00000015752 15152100360 0012474 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Autolink;
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
final class UrlAutolinkParser implements InlineParserInterface
{
private const ALLOWED_AFTER = [null, ' ', "\t", "\n", "\x0b", "\x0c", "\x0d", '*', '_', '~', '('];
// RegEx adapted from https://github.com/symfony/symfony/blob/6.3/src/Symfony/Component/Validator/Constraints/UrlValidator.php
private const REGEX = '~
(
# Must start with a supported scheme + auth, or "www"
(?:
(?:%s):// # protocol
(?:(?:(?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+:)?((?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+)@)? # basic auth
|www\.)
(?:
(?:
(?:xn--[a-z0-9-]++\.)*+xn--[a-z0-9-]++ # a domain name using punycode
|
(?:[\pL\pN\pS\pM\-\_]++\.)+[\pL\pN\pM]++ # a multi-level domain name
|
[a-z0-9\-\_]++ # a single-level domain name
)\.?
| # or
\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address
| # or
\[
(?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::))))
\] # an IPv6 address
)
(?::[0-9]+)? # a port (optional)
(?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%%[0-9A-Fa-f]{2})* )* # a path
(?:\? (?:[\pL\pN\-._\~!$&\'\[\]()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a query (optional)
(?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a fragment (optional)
)~ixu';
/**
* @var string[]
*
* @psalm-readonly
*/
private array $prefixes = ['www'];
/**
* @psalm-var non-empty-string
*
* @psalm-readonly
*/
private string $finalRegex;
/**
* @param array<int, string> $allowedProtocols
*/
public function __construct(array $allowedProtocols = ['http', 'https', 'ftp'])
{
/**
* @psalm-suppress PropertyTypeCoercion
*/
$this->finalRegex = \sprintf(self::REGEX, \implode('|', $allowedProtocols));
foreach ($allowedProtocols as $protocol) {
$this->prefixes[] = $protocol . '://';
}
}
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::oneOf(...$this->prefixes);
}
public function parse(InlineParserContext $inlineContext): bool
{
$cursor = $inlineContext->getCursor();
// Autolinks can only come at the beginning of a line, after whitespace, or certain delimiting characters
$previousChar = $cursor->peek(-1);
if (! \in_array($previousChar, self::ALLOWED_AFTER, true)) {
return false;
}
// Check if we have a valid URL
if (! \preg_match($this->finalRegex, $cursor->getRemainder(), $matches)) {
return false;
}
$url = $matches[0];
// Does the URL end with punctuation that should be stripped?
if (\preg_match('/(.+?)([?!.,:*_~]+)$/', $url, $matches)) {
// Add the punctuation later
$url = $matches[1];
}
// Does the URL end with something that looks like an entity reference?
if (\preg_match('/(.+)(&[A-Za-z0-9]+;)$/', $url, $matches)) {
$url = $matches[1];
}
// Does the URL need unmatched parens chopped off?
if (\substr($url, -1) === ')' && ($diff = self::diffParens($url)) > 0) {
$url = \substr($url, 0, -$diff);
}
$cursor->advanceBy(\mb_strlen($url, 'UTF-8'));
// Auto-prefix 'http://' onto 'www' URLs
if (\substr($url, 0, 4) === 'www.') {
$inlineContext->getContainer()->appendChild(new Link('http://' . $url, $url));
return true;
}
$inlineContext->getContainer()->appendChild(new Link($url, $url));
return true;
}
/**
* @psalm-pure
*/
private static function diffParens(string $content): int
{
// Scan the entire autolink for the total number of parentheses.
// If there is a greater number of closing parentheses than opening ones,
// we donβt consider ANY of the last characters as part of the autolink,
// in order to facilitate including an autolink inside a parenthesis.
\preg_match_all('/[()]/', $content, $matches);
$charCount = ['(' => 0, ')' => 0];
foreach ($matches[0] as $char) {
$charCount[$char]++;
}
return $charCount[')'] - $charCount['('];
}
}
Autolink/EmailAutolinkParser.php 0000644 00000002651 15152100360 0012753 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Autolink;
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
final class EmailAutolinkParser implements InlineParserInterface
{
private const REGEX = '[A-Za-z0-9.\-_+]+@[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_.]+';
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::regex(self::REGEX);
}
public function parse(InlineParserContext $inlineContext): bool
{
$email = $inlineContext->getFullMatch();
// The last character cannot be - or _
if (\in_array(\substr($email, -1), ['-', '_'], true)) {
return false;
}
// Does the URL end with punctuation that should be stripped?
if (\substr($email, -1) === '.') {
$email = \substr($email, 0, -1);
}
$inlineContext->getCursor()->advanceBy(\strlen($email));
$inlineContext->getContainer()->appendChild(new Link('mailto:' . $email, $email));
return true;
}
}
DefaultAttributes/DefaultAttributesExtension.php 0000644 00000002375 15152100360 0016240 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\DefaultAttributes;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\ConfigurableExtensionInterface;
use League\Config\ConfigurationBuilderInterface;
use Nette\Schema\Expect;
final class DefaultAttributesExtension implements ConfigurableExtensionInterface
{
public function configureSchema(ConfigurationBuilderInterface $builder): void
{
$builder->addSchema('default_attributes', Expect::arrayOf(
Expect::arrayOf(
Expect::type('string|string[]|bool|callable'), // attribute value(s)
'string' // attribute name
),
'string' // node FQCN
)->default([]));
}
public function register(EnvironmentBuilderInterface $environment): void
{
$environment->addEventListener(DocumentParsedEvent::class, [new ApplyDefaultAttributesProcessor(), 'onDocumentParsed']);
}
}
DefaultAttributes/ApplyDefaultAttributesProcessor.php 0000644 00000004117 15152100360 0017245 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\DefaultAttributes;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\Attributes\Util\AttributesHelper;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class ApplyDefaultAttributesProcessor implements ConfigurationAwareInterface
{
private ConfigurationInterface $config;
public function onDocumentParsed(DocumentParsedEvent $event): void
{
/** @var array<string, array<string, mixed>> $map */
$map = $this->config->get('default_attributes');
// Don't bother iterating if no default attributes are configured
if (! $map) {
return;
}
foreach ($event->getDocument()->iterator() as $node) {
// Check to see if any default attributes were defined
if (($attributesToApply = $map[\get_class($node)] ?? []) === []) {
continue;
}
$newAttributes = [];
foreach ($attributesToApply as $name => $value) {
if (\is_callable($value)) {
$value = $value($node);
// Callables are allowed to return `null` indicating that no changes should be made
if ($value !== null) {
$newAttributes[$name] = $value;
}
} else {
$newAttributes[$name] = $value;
}
}
// Merge these attributes into the node
if (\count($newAttributes) > 0) {
$node->data->set('attributes', AttributesHelper::mergeAttributes($node, $newAttributes));
}
}
}
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}
}
Embed/EmbedProcessor.php 0000644 00000004025 15152100360 0011177 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Embed;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
use League\CommonMark\Node\Block\Paragraph;
use League\CommonMark\Node\Inline\Text;
use League\CommonMark\Node\NodeIterator;
final class EmbedProcessor
{
public const FALLBACK_REMOVE = 'remove';
public const FALLBACK_LINK = 'link';
private EmbedAdapterInterface $adapter;
private string $fallback;
public function __construct(EmbedAdapterInterface $adapter, string $fallback = self::FALLBACK_REMOVE)
{
$this->adapter = $adapter;
$this->fallback = $fallback;
}
public function __invoke(DocumentParsedEvent $event): void
{
$document = $event->getDocument();
$embeds = [];
foreach (new NodeIterator($document) as $node) {
if (! ($node instanceof Embed)) {
continue;
}
if ($node->parent() !== $document) {
$replacement = new Paragraph();
$replacement->appendChild(new Text($node->getUrl()));
$node->replaceWith($replacement);
} else {
$embeds[] = $node;
}
}
$this->adapter->updateEmbeds($embeds);
foreach ($embeds as $embed) {
if ($embed->getEmbedCode() !== null) {
continue;
}
if ($this->fallback === self::FALLBACK_REMOVE) {
$embed->detach();
} elseif ($this->fallback === self::FALLBACK_LINK) {
$paragraph = new Paragraph();
$paragraph->appendChild(new Link($embed->getUrl(), $embed->getUrl()));
$embed->replaceWith($paragraph);
}
}
}
}
Embed/DomainFilteringAdapter.php 0000644 00000002564 15152100360 0012645 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Embed;
class DomainFilteringAdapter implements EmbedAdapterInterface
{
private EmbedAdapterInterface $decorated;
/** @psalm-var non-empty-string */
private string $regex;
/**
* @param string[] $allowedDomains
*/
public function __construct(EmbedAdapterInterface $decorated, array $allowedDomains)
{
$this->decorated = $decorated;
$this->regex = self::createRegex($allowedDomains);
}
/**
* {@inheritDoc}
*/
public function updateEmbeds(array $embeds): void
{
$this->decorated->updateEmbeds(\array_values(\array_filter($embeds, function (Embed $embed): bool {
return \preg_match($this->regex, $embed->getUrl()) === 1;
})));
}
/**
* @param string[] $allowedDomains
*
* @psalm-return non-empty-string
*/
private static function createRegex(array $allowedDomains): string
{
$allowedDomains = \array_map('preg_quote', $allowedDomains);
return '/^(?:https?:\/\/)?(?:[^.]+\.)*(' . \implode('|', $allowedDomains) . ')/';
}
}
Embed/EmbedStartParser.php 0000644 00000003240 15152100360 0011470 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Embed;
use League\CommonMark\Parser\Block\BlockStart;
use League\CommonMark\Parser\Block\BlockStartParserInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\MarkdownParserStateInterface;
use League\CommonMark\Util\LinkParserHelper;
class EmbedStartParser implements BlockStartParserInterface
{
public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
{
if ($cursor->isIndented() || $parserState->getParagraphContent() !== null || ! ($parserState->getActiveBlockParser()->isContainer())) {
return BlockStart::none();
}
// 0-3 leading spaces are okay
$cursor->advanceToNextNonSpaceOrTab();
// The line must begin with "https://"
if (! str_starts_with($cursor->getRemainder(), 'https://')) {
return BlockStart::none();
}
// A valid link must be found next
if (($dest = LinkParserHelper::parseLinkDestination($cursor)) === null) {
return BlockStart::none();
}
// Skip any trailing whitespace
$cursor->advanceToNextNonSpaceOrTab();
// We must be at the end of the line; otherwise, this link was not by itself
if (! $cursor->isAtEnd()) {
return BlockStart::none();
}
return BlockStart::of(new EmbedParser($dest))->at($cursor);
}
}
Embed/EmbedAdapterInterface.php 0000644 00000001050 15152100360 0012414 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Embed;
/**
* Interface for a service which updates the embed code(s) for the given array of embeds
*/
interface EmbedAdapterInterface
{
/**
* @param Embed[] $embeds
*/
public function updateEmbeds(array $embeds): void;
}
Embed/EmbedExtension.php 0000644 00000003345 15152100360 0011200 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Embed;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\ConfigurableExtensionInterface;
use League\Config\ConfigurationBuilderInterface;
use Nette\Schema\Expect;
final class EmbedExtension implements ConfigurableExtensionInterface
{
public function configureSchema(ConfigurationBuilderInterface $builder): void
{
$builder->addSchema('embed', Expect::structure([
'adapter' => Expect::type(EmbedAdapterInterface::class),
'allowed_domains' => Expect::arrayOf('string')->default([]),
'fallback' => Expect::anyOf('link', 'remove')->default('link'),
]));
}
public function register(EnvironmentBuilderInterface $environment): void
{
$adapter = $environment->getConfiguration()->get('embed.adapter');
\assert($adapter instanceof EmbedAdapterInterface);
$allowedDomains = $environment->getConfiguration()->get('embed.allowed_domains');
if ($allowedDomains !== []) {
$adapter = new DomainFilteringAdapter($adapter, $allowedDomains);
}
$environment
->addBlockStartParser(new EmbedStartParser(), 300)
->addEventListener(DocumentParsedEvent::class, new EmbedProcessor($adapter, $environment->getConfiguration()->get('embed.fallback')), 1010)
->addRenderer(Embed::class, new EmbedRenderer());
}
}
Embed/Bridge/OscaroteroEmbedAdapter.php 0000644 00000002636 15152100360 0014043 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Embed\Bridge;
use Embed\Embed as EmbedLib;
use League\CommonMark\Exception\MissingDependencyException;
use League\CommonMark\Extension\Embed\Embed;
use League\CommonMark\Extension\Embed\EmbedAdapterInterface;
final class OscaroteroEmbedAdapter implements EmbedAdapterInterface
{
private EmbedLib $embedLib;
public function __construct(?EmbedLib $embed = null)
{
if ($embed === null) {
if (! \class_exists(EmbedLib::class)) {
throw new MissingDependencyException('The embed/embed package is not installed. Please install it with Composer to use this adapter.');
}
$embed = new EmbedLib();
}
$this->embedLib = $embed;
}
/**
* {@inheritDoc}
*/
public function updateEmbeds(array $embeds): void
{
$extractors = $this->embedLib->getMulti(...\array_map(static fn (Embed $embed) => $embed->getUrl(), $embeds));
foreach ($extractors as $i => $extractor) {
if ($extractor->code !== null) {
$embeds[$i]->setEmbedCode($extractor->code->html);
}
}
}
}
Embed/EmbedParser.php 0000644 00000002470 15152100360 0010456 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Embed;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Parser\Block\BlockContinue;
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
use League\CommonMark\Parser\Cursor;
class EmbedParser implements BlockContinueParserInterface
{
private Embed $embed;
public function __construct(string $url)
{
$this->embed = new Embed($url);
}
public function getBlock(): AbstractBlock
{
return $this->embed;
}
public function isContainer(): bool
{
return false;
}
public function canHaveLazyContinuationLines(): bool
{
return false;
}
public function canContain(AbstractBlock $childBlock): bool
{
return false;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
return BlockContinue::none();
}
public function addLine(string $line): void
{
}
public function closeBlock(): void
{
}
}
Embed/EmbedRenderer.php 0000644 00000001502 15152100360 0010763 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Embed;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
class EmbedRenderer implements NodeRendererInterface
{
/**
* @param Embed $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer)
{
Embed::assertInstanceOf($node);
return $node->getEmbedCode() ?? '';
}
}
Embed/Embed.php 0000644 00000001775 15152100360 0007310 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Embed;
use League\CommonMark\Node\Block\AbstractBlock;
final class Embed extends AbstractBlock
{
private string $url;
private ?string $embedCode;
public function __construct(string $url, ?string $embedCode = null)
{
parent::__construct();
$this->url = $url;
$this->embedCode = $embedCode;
}
public function getUrl(): string
{
return $this->url;
}
public function setUrl(string $url): void
{
$this->url = $url;
}
public function getEmbedCode(): ?string
{
return $this->embedCode;
}
public function setEmbedCode(?string $embedCode): void
{
$this->embedCode = $embedCode;
}
}
ExternalLink/ExternalLinkExtension.php 0000644 00000003371 15152100360 0014147 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\ExternalLink;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\ConfigurableExtensionInterface;
use League\Config\ConfigurationBuilderInterface;
use Nette\Schema\Expect;
final class ExternalLinkExtension implements ConfigurableExtensionInterface
{
public function configureSchema(ConfigurationBuilderInterface $builder): void
{
$applyOptions = [
ExternalLinkProcessor::APPLY_NONE,
ExternalLinkProcessor::APPLY_ALL,
ExternalLinkProcessor::APPLY_INTERNAL,
ExternalLinkProcessor::APPLY_EXTERNAL,
];
$builder->addSchema('external_link', Expect::structure([
'internal_hosts' => Expect::type('string|string[]'),
'open_in_new_window' => Expect::bool(false),
'html_class' => Expect::string()->default(''),
'nofollow' => Expect::anyOf(...$applyOptions)->default(ExternalLinkProcessor::APPLY_NONE),
'noopener' => Expect::anyOf(...$applyOptions)->default(ExternalLinkProcessor::APPLY_EXTERNAL),
'noreferrer' => Expect::anyOf(...$applyOptions)->default(ExternalLinkProcessor::APPLY_EXTERNAL),
]));
}
public function register(EnvironmentBuilderInterface $environment): void
{
$environment->addEventListener(DocumentParsedEvent::class, new ExternalLinkProcessor($environment->getConfiguration()), -50);
}
}
ExternalLink/ExternalLinkProcessor.php 0000644 00000007303 15152100360 0014151 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\ExternalLink;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
use League\Config\ConfigurationInterface;
final class ExternalLinkProcessor
{
public const APPLY_NONE = '';
public const APPLY_ALL = 'all';
public const APPLY_EXTERNAL = 'external';
public const APPLY_INTERNAL = 'internal';
/** @psalm-readonly */
private ConfigurationInterface $config;
public function __construct(ConfigurationInterface $config)
{
$this->config = $config;
}
public function __invoke(DocumentParsedEvent $e): void
{
$internalHosts = $this->config->get('external_link/internal_hosts');
$openInNewWindow = $this->config->get('external_link/open_in_new_window');
$classes = $this->config->get('external_link/html_class');
foreach ($e->getDocument()->iterator() as $link) {
if (! ($link instanceof Link)) {
continue;
}
$host = \parse_url($link->getUrl(), PHP_URL_HOST);
if (! \is_string($host)) {
// Something is terribly wrong with this URL
continue;
}
if (self::hostMatches($host, $internalHosts)) {
$link->data->set('external', false);
$this->applyRelAttribute($link, false);
continue;
}
// Host does not match our list
$this->markLinkAsExternal($link, $openInNewWindow, $classes);
}
}
private function markLinkAsExternal(Link $link, bool $openInNewWindow, string $classes): void
{
$link->data->set('external', true);
$this->applyRelAttribute($link, true);
if ($openInNewWindow) {
$link->data->set('attributes/target', '_blank');
}
if ($classes !== '') {
$link->data->append('attributes/class', $classes);
}
}
private function applyRelAttribute(Link $link, bool $isExternal): void
{
$options = [
'nofollow' => $this->config->get('external_link/nofollow'),
'noopener' => $this->config->get('external_link/noopener'),
'noreferrer' => $this->config->get('external_link/noreferrer'),
];
foreach ($options as $type => $option) {
switch (true) {
case $option === self::APPLY_ALL:
case $isExternal && $option === self::APPLY_EXTERNAL:
case ! $isExternal && $option === self::APPLY_INTERNAL:
$link->data->append('attributes/rel', $type);
}
}
// No rel attributes? Mark the attribute as 'false' so LinkRenderer doesn't add defaults
if (! $link->data->has('attributes/rel')) {
$link->data->set('attributes/rel', false);
}
}
/**
* @internal This method is only public so we can easily test it. DO NOT USE THIS OUTSIDE OF THIS EXTENSION!
*
* @param non-empty-string|list<non-empty-string> $compareTo
*/
public static function hostMatches(string $host, $compareTo): bool
{
foreach ((array) $compareTo as $c) {
if (\strpos($c, '/') === 0) {
if (\preg_match($c, $host)) {
return true;
}
} elseif ($c === $host) {
return true;
}
}
return false;
}
}
DisallowedRawHtml/DisallowedRawHtmlExtension.php 0000644 00000003315 15152100360 0016121 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\DisallowedRawHtml;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock;
use League\CommonMark\Extension\CommonMark\Node\Inline\HtmlInline;
use League\CommonMark\Extension\CommonMark\Renderer\Block\HtmlBlockRenderer;
use League\CommonMark\Extension\CommonMark\Renderer\Inline\HtmlInlineRenderer;
use League\CommonMark\Extension\ConfigurableExtensionInterface;
use League\Config\ConfigurationBuilderInterface;
use Nette\Schema\Expect;
final class DisallowedRawHtmlExtension implements ConfigurableExtensionInterface
{
private const DEFAULT_DISALLOWED_TAGS = [
'title',
'textarea',
'style',
'xmp',
'iframe',
'noembed',
'noframes',
'script',
'plaintext',
];
public function configureSchema(ConfigurationBuilderInterface $builder): void
{
$builder->addSchema('disallowed_raw_html', Expect::structure([
'disallowed_tags' => Expect::listOf('string')->default(self::DEFAULT_DISALLOWED_TAGS)->mergeDefaults(false),
]));
}
public function register(EnvironmentBuilderInterface $environment): void
{
$environment->addRenderer(HtmlBlock::class, new DisallowedRawHtmlRenderer(new HtmlBlockRenderer()), 50);
$environment->addRenderer(HtmlInline::class, new DisallowedRawHtmlRenderer(new HtmlInlineRenderer()), 50);
}
}
DisallowedRawHtml/DisallowedRawHtmlRenderer.php 0000644 00000003603 15152100360 0015713 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\DisallowedRawHtml;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class DisallowedRawHtmlRenderer implements NodeRendererInterface, ConfigurationAwareInterface
{
/** @psalm-readonly */
private NodeRendererInterface $innerRenderer;
/** @psalm-readonly-allow-private-mutation */
private ConfigurationInterface $config;
public function __construct(NodeRendererInterface $innerRenderer)
{
$this->innerRenderer = $innerRenderer;
}
public function render(Node $node, ChildNodeRendererInterface $childRenderer): ?string
{
$rendered = (string) $this->innerRenderer->render($node, $childRenderer);
if ($rendered === '') {
return '';
}
$tags = (array) $this->config->get('disallowed_raw_html/disallowed_tags');
if (\count($tags) === 0) {
return $rendered;
}
$regex = \sprintf('/<(\/?(?:%s)[ \/>])/i', \implode('|', \array_map('preg_quote', $tags)));
// Match these types of tags: <title> </title> <title x="sdf"> <title/> <title />
return \preg_replace($regex, '<$1', $rendered);
}
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
if ($this->innerRenderer instanceof ConfigurationAwareInterface) {
$this->innerRenderer->setConfiguration($configuration);
}
}
}
Footnote/Node/FootnoteBackref.php 0000644 00000001766 15152100360 0013015 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* 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\Footnote\Node;
use League\CommonMark\Node\Inline\AbstractInline;
use League\CommonMark\Reference\ReferenceInterface;
use League\CommonMark\Reference\ReferenceableInterface;
/**
* Link from the footnote on the bottom of the document back to the reference
*/
final class FootnoteBackref extends AbstractInline implements ReferenceableInterface
{
/** @psalm-readonly */
private ReferenceInterface $reference;
public function __construct(ReferenceInterface $reference)
{
parent::__construct();
$this->reference = $reference;
}
public function getReference(): ReferenceInterface
{
return $this->reference;
}
}
Footnote/Node/FootnoteContainer.php 0000644 00000000723 15152100360 0013372 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* 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\Footnote\Node;
use League\CommonMark\Node\Block\AbstractBlock;
final class FootnoteContainer extends AbstractBlock
{
}
Footnote/Node/FootnoteRef.php 0000644 00000002547 15152100360 0012172 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* 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\Footnote\Node;
use League\CommonMark\Node\Inline\AbstractInline;
use League\CommonMark\Reference\ReferenceInterface;
use League\CommonMark\Reference\ReferenceableInterface;
final class FootnoteRef extends AbstractInline implements ReferenceableInterface
{
private ReferenceInterface $reference;
/** @psalm-readonly */
private ?string $content = null;
/**
* @param array<mixed> $data
*/
public function __construct(ReferenceInterface $reference, ?string $content = null, array $data = [])
{
parent::__construct();
$this->reference = $reference;
$this->content = $content;
if (\count($data) > 0) {
$this->data->import($data);
}
}
public function getReference(): ReferenceInterface
{
return $this->reference;
}
public function setReference(ReferenceInterface $reference): void
{
$this->reference = $reference;
}
public function getContent(): ?string
{
return $this->content;
}
}
Footnote/Node/Footnote.php 0000644 00000001626 15152100360 0011532 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* 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\Footnote\Node;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Reference\ReferenceInterface;
use League\CommonMark\Reference\ReferenceableInterface;
final class Footnote extends AbstractBlock implements ReferenceableInterface
{
/** @psalm-readonly */
private ReferenceInterface $reference;
public function __construct(ReferenceInterface $reference)
{
parent::__construct();
$this->reference = $reference;
}
public function getReference(): ReferenceInterface
{
return $this->reference;
}
}
Footnote/Event/AnonymousFootnotesListener.php 0000644 00000004066 15152100360 0015531 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* 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\Footnote\Event;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\Footnote\Node\Footnote;
use League\CommonMark\Extension\Footnote\Node\FootnoteBackref;
use League\CommonMark\Extension\Footnote\Node\FootnoteRef;
use League\CommonMark\Node\Block\Paragraph;
use League\CommonMark\Node\Inline\Text;
use League\CommonMark\Reference\Reference;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class AnonymousFootnotesListener implements ConfigurationAwareInterface
{
private ConfigurationInterface $config;
public function onDocumentParsed(DocumentParsedEvent $event): void
{
$document = $event->getDocument();
foreach ($document->iterator() as $node) {
if (! $node instanceof FootnoteRef || ($text = $node->getContent()) === null) {
continue;
}
// Anonymous footnote needs to create a footnote from its content
$existingReference = $node->getReference();
$newReference = new Reference(
$existingReference->getLabel(),
'#' . $this->config->get('footnote/ref_id_prefix') . $existingReference->getLabel(),
$existingReference->getTitle()
);
$paragraph = new Paragraph();
$paragraph->appendChild(new Text($text));
$paragraph->appendChild(new FootnoteBackref($newReference));
$footnote = new Footnote($newReference);
$footnote->appendChild($paragraph);
$document->appendChild($footnote);
}
}
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}
}
Footnote/Event/GatherFootnotesListener.php 0000644 00000006727 15152100360 0014761 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* 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\Footnote\Event;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\Footnote\Node\Footnote;
use League\CommonMark\Extension\Footnote\Node\FootnoteBackref;
use League\CommonMark\Extension\Footnote\Node\FootnoteContainer;
use League\CommonMark\Node\Block\Document;
use League\CommonMark\Node\NodeIterator;
use League\CommonMark\Reference\Reference;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class GatherFootnotesListener implements ConfigurationAwareInterface
{
private ConfigurationInterface $config;
public function onDocumentParsed(DocumentParsedEvent $event): void
{
$document = $event->getDocument();
$footnotes = [];
foreach ($document->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) {
if (! $node instanceof Footnote) {
continue;
}
// Look for existing reference with footnote label
$ref = $document->getReferenceMap()->get($node->getReference()->getLabel());
if ($ref !== null) {
// Use numeric title to get footnotes order
$footnotes[(int) $ref->getTitle()] = $node;
} else {
// Footnote call is missing, append footnote at the end
$footnotes[\PHP_INT_MAX] = $node;
}
$key = '#' . $this->config->get('footnote/footnote_id_prefix') . $node->getReference()->getDestination();
if ($document->data->has($key)) {
$this->createBackrefs($node, $document->data->get($key));
}
}
// Only add a footnote container if there are any
if (\count($footnotes) === 0) {
return;
}
$container = $this->getFootnotesContainer($document);
\ksort($footnotes);
foreach ($footnotes as $footnote) {
$container->appendChild($footnote);
}
}
private function getFootnotesContainer(Document $document): FootnoteContainer
{
$footnoteContainer = new FootnoteContainer();
$document->appendChild($footnoteContainer);
return $footnoteContainer;
}
/**
* Look for all footnote refs pointing to this footnote and create each footnote backrefs.
*
* @param Footnote $node The target footnote
* @param Reference[] $backrefs References to create backrefs for
*/
private function createBackrefs(Footnote $node, array $backrefs): void
{
// Backrefs should be added to the child paragraph
$target = $node->lastChild();
if ($target === null) {
// This should never happen, but you never know
$target = $node;
}
foreach ($backrefs as $backref) {
$target->appendChild(new FootnoteBackref(new Reference(
$backref->getLabel(),
'#' . $this->config->get('footnote/ref_id_prefix') . $backref->getLabel(),
$backref->getTitle()
)));
}
}
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}
}
Footnote/Event/FixOrphanedFootnotesAndRefsListener.php 0000644 00000004461 15152100360 0017212 0 ustar 00 <?php
/*
* 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.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Event;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\Footnote\Node\Footnote;
use League\CommonMark\Extension\Footnote\Node\FootnoteRef;
use League\CommonMark\Node\Block\Document;
use League\CommonMark\Node\Inline\Text;
final class FixOrphanedFootnotesAndRefsListener
{
public function onDocumentParsed(DocumentParsedEvent $event): void
{
$document = $event->getDocument();
$map = $this->buildMapOfKnownFootnotesAndRefs($document);
foreach ($map['_flat'] as $node) {
if ($node instanceof FootnoteRef && ! isset($map[Footnote::class][$node->getReference()->getLabel()])) {
// Found an orphaned FootnoteRef without a corresponding Footnote
// Restore the original footnote ref text
$node->replaceWith(new Text(\sprintf('[^%s]', $node->getReference()->getLabel())));
}
// phpcs:disable SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed
if ($node instanceof Footnote && ! isset($map[FootnoteRef::class][$node->getReference()->getLabel()])) {
// Found an orphaned Footnote without a corresponding FootnoteRef
// Remove the footnote
$node->detach();
}
}
}
/** @phpstan-ignore-next-line */
private function buildMapOfKnownFootnotesAndRefs(Document $document): array // @phpcs:ignore
{
$map = [
Footnote::class => [],
FootnoteRef::class => [],
'_flat' => [],
];
foreach ($document->iterator() as $node) {
if ($node instanceof Footnote) {
$map[Footnote::class][$node->getReference()->getLabel()] = true;
$map['_flat'][] = $node;
} elseif ($node instanceof FootnoteRef) {
$map[FootnoteRef::class][$node->getReference()->getLabel()] = true;
$map['_flat'][] = $node;
}
}
return $map;
}
}
Footnote/Event/NumberFootnotesListener.php 0000644 00000004446 15152100360 0014773 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* 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\Footnote\Event;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\Footnote\Node\FootnoteRef;
use League\CommonMark\Reference\Reference;
final class NumberFootnotesListener
{
public function onDocumentParsed(DocumentParsedEvent $event): void
{
$document = $event->getDocument();
$nextCounter = 1;
$usedLabels = [];
$usedCounters = [];
foreach ($document->iterator() as $node) {
if (! $node instanceof FootnoteRef) {
continue;
}
$existingReference = $node->getReference();
$label = $existingReference->getLabel();
$counter = $nextCounter;
$canIncrementCounter = true;
if (\array_key_exists($label, $usedLabels)) {
/*
* Reference is used again, we need to point
* to the same footnote. But with a different ID
*/
$counter = $usedCounters[$label];
$label .= '__' . ++$usedLabels[$label];
$canIncrementCounter = false;
}
// rewrite reference title to use a numeric link
$newReference = new Reference(
$label,
$existingReference->getDestination(),
(string) $counter
);
// Override reference with numeric link
$node->setReference($newReference);
$document->getReferenceMap()->add($newReference);
/*
* Store created references in document for
* creating FootnoteBackrefs
*/
$document->data->append($existingReference->getDestination(), $newReference);
$usedLabels[$label] = 1;
$usedCounters[$label] = $nextCounter;
if ($canIncrementCounter) {
$nextCounter++;
}
}
}
}
Footnote/FootnoteExtension.php 0000644 00000006725 15152100360 0012547 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* 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\Footnote;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\ConfigurableExtensionInterface;
use League\CommonMark\Extension\Footnote\Event\AnonymousFootnotesListener;
use League\CommonMark\Extension\Footnote\Event\FixOrphanedFootnotesAndRefsListener;
use League\CommonMark\Extension\Footnote\Event\GatherFootnotesListener;
use League\CommonMark\Extension\Footnote\Event\NumberFootnotesListener;
use League\CommonMark\Extension\Footnote\Node\Footnote;
use League\CommonMark\Extension\Footnote\Node\FootnoteBackref;
use League\CommonMark\Extension\Footnote\Node\FootnoteContainer;
use League\CommonMark\Extension\Footnote\Node\FootnoteRef;
use League\CommonMark\Extension\Footnote\Parser\AnonymousFootnoteRefParser;
use League\CommonMark\Extension\Footnote\Parser\FootnoteRefParser;
use League\CommonMark\Extension\Footnote\Parser\FootnoteStartParser;
use League\CommonMark\Extension\Footnote\Renderer\FootnoteBackrefRenderer;
use League\CommonMark\Extension\Footnote\Renderer\FootnoteContainerRenderer;
use League\CommonMark\Extension\Footnote\Renderer\FootnoteRefRenderer;
use League\CommonMark\Extension\Footnote\Renderer\FootnoteRenderer;
use League\Config\ConfigurationBuilderInterface;
use Nette\Schema\Expect;
final class FootnoteExtension implements ConfigurableExtensionInterface
{
public function configureSchema(ConfigurationBuilderInterface $builder): void
{
$builder->addSchema('footnote', Expect::structure([
'backref_class' => Expect::string('footnote-backref'),
'backref_symbol' => Expect::string('β©'),
'container_add_hr' => Expect::bool(true),
'container_class' => Expect::string('footnotes'),
'ref_class' => Expect::string('footnote-ref'),
'ref_id_prefix' => Expect::string('fnref:'),
'footnote_class' => Expect::string('footnote'),
'footnote_id_prefix' => Expect::string('fn:'),
]));
}
public function register(EnvironmentBuilderInterface $environment): void
{
$environment->addBlockStartParser(new FootnoteStartParser(), 51);
$environment->addInlineParser(new AnonymousFootnoteRefParser(), 35);
$environment->addInlineParser(new FootnoteRefParser(), 51);
$environment->addRenderer(FootnoteContainer::class, new FootnoteContainerRenderer());
$environment->addRenderer(Footnote::class, new FootnoteRenderer());
$environment->addRenderer(FootnoteRef::class, new FootnoteRefRenderer());
$environment->addRenderer(FootnoteBackref::class, new FootnoteBackrefRenderer());
$environment->addEventListener(DocumentParsedEvent::class, [new AnonymousFootnotesListener(), 'onDocumentParsed'], 40);
$environment->addEventListener(DocumentParsedEvent::class, [new FixOrphanedFootnotesAndRefsListener(), 'onDocumentParsed'], 30);
$environment->addEventListener(DocumentParsedEvent::class, [new NumberFootnotesListener(), 'onDocumentParsed'], 20);
$environment->addEventListener(DocumentParsedEvent::class, [new GatherFootnotesListener(), 'onDocumentParsed'], 10);
}
}
Footnote/Renderer/FootnoteRefRenderer.php 0000644 00000004701 15152100360 0014534 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* 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\Footnote\Renderer;
use League\CommonMark\Extension\Footnote\Node\FootnoteRef;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class FootnoteRefRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface
{
private ConfigurationInterface $config;
/**
* @param FootnoteRef $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
FootnoteRef::assertInstanceOf($node);
$attrs = $node->data->getData('attributes');
$attrs->append('class', $this->config->get('footnote/ref_class'));
$attrs->set('href', \mb_strtolower($node->getReference()->getDestination(), 'UTF-8'));
$attrs->set('role', 'doc-noteref');
$idPrefix = $this->config->get('footnote/ref_id_prefix');
return new HtmlElement(
'sup',
[
'id' => $idPrefix . \mb_strtolower($node->getReference()->getLabel(), 'UTF-8'),
],
new HtmlElement(
'a',
$attrs->export(),
$node->getReference()->getTitle()
),
true
);
}
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}
public function getXmlTagName(Node $node): string
{
return 'footnote_ref';
}
/**
* @param FootnoteRef $node
*
* @return array<string, scalar>
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function getXmlAttributes(Node $node): array
{
FootnoteRef::assertInstanceOf($node);
return [
'reference' => $node->getReference()->getLabel(),
];
}
}
Footnote/Renderer/FootnoteContainerRenderer.php 0000644 00000004101 15152100360 0015734 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* 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\Footnote\Renderer;
use League\CommonMark\Extension\Footnote\Node\FootnoteContainer;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class FootnoteContainerRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface
{
private ConfigurationInterface $config;
/**
* @param FootnoteContainer $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
FootnoteContainer::assertInstanceOf($node);
$attrs = $node->data->getData('attributes');
$attrs->append('class', $this->config->get('footnote/container_class'));
$attrs->set('role', 'doc-endnotes');
$contents = new HtmlElement('ol', [], $childRenderer->renderNodes($node->children()));
if ($this->config->get('footnote/container_add_hr')) {
$contents = [new HtmlElement('hr', [], null, true), $contents];
}
return new HtmlElement('div', $attrs->export(), $contents);
}
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}
public function getXmlTagName(Node $node): string
{
return 'footnote_container';
}
/**
* @return array<string, scalar>
*/
public function getXmlAttributes(Node $node): array
{
return [];
}
}
Footnote/Renderer/FootnoteBackrefRenderer.php 0000644 00000004530 15152100360 0015355 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* 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\Footnote\Renderer;
use League\CommonMark\Extension\Footnote\Node\FootnoteBackref;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class FootnoteBackrefRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface
{
public const DEFAULT_SYMBOL = 'β©';
private ConfigurationInterface $config;
/**
* @param FootnoteBackref $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): string
{
FootnoteBackref::assertInstanceOf($node);
$attrs = $node->data->getData('attributes');
$attrs->append('class', $this->config->get('footnote/backref_class'));
$attrs->set('rev', 'footnote');
$attrs->set('href', \mb_strtolower($node->getReference()->getDestination(), 'UTF-8'));
$attrs->set('role', 'doc-backlink');
$symbol = $this->config->get('footnote/backref_symbol');
\assert(\is_string($symbol));
return ' ' . new HtmlElement('a', $attrs->export(), \htmlspecialchars($symbol), true);
}
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}
public function getXmlTagName(Node $node): string
{
return 'footnote_backref';
}
/**
* @param FootnoteBackref $node
*
* @return array<string, scalar>
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function getXmlAttributes(Node $node): array
{
FootnoteBackref::assertInstanceOf($node);
return [
'reference' => $node->getReference()->getLabel(),
];
}
}
Footnote/Renderer/FootnoteRenderer.php 0000644 00000004341 15152100360 0014077 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* 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\Footnote\Renderer;
use League\CommonMark\Extension\Footnote\Node\Footnote;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class FootnoteRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface
{
private ConfigurationInterface $config;
/**
* @param Footnote $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
Footnote::assertInstanceOf($node);
$attrs = $node->data->getData('attributes');
$attrs->append('class', $this->config->get('footnote/footnote_class'));
$attrs->set('id', $this->config->get('footnote/footnote_id_prefix') . \mb_strtolower($node->getReference()->getLabel(), 'UTF-8'));
$attrs->set('role', 'doc-endnote');
return new HtmlElement(
'li',
$attrs->export(),
$childRenderer->renderNodes($node->children()),
true
);
}
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}
public function getXmlTagName(Node $node): string
{
return 'footnote';
}
/**
* @param Footnote $node
*
* @return array<string, scalar>
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function getXmlAttributes(Node $node): array
{
Footnote::assertInstanceOf($node);
return [
'reference' => $node->getReference()->getLabel(),
];
}
}
Footnote/Parser/AnonymousFootnoteRefParser.php 0000644 00000004161 15152100360 0015621 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* 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\Footnote\Parser;
use League\CommonMark\Environment\EnvironmentAwareInterface;
use League\CommonMark\Environment\EnvironmentInterface;
use League\CommonMark\Extension\Footnote\Node\FootnoteRef;
use League\CommonMark\Normalizer\TextNormalizerInterface;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
use League\CommonMark\Reference\Reference;
use League\Config\ConfigurationInterface;
final class AnonymousFootnoteRefParser implements InlineParserInterface, EnvironmentAwareInterface
{
private ConfigurationInterface $config;
/** @psalm-readonly-allow-private-mutation */
private TextNormalizerInterface $slugNormalizer;
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::regex('\^\[([^\]]+)\]');
}
public function parse(InlineParserContext $inlineContext): bool
{
$inlineContext->getCursor()->advanceBy($inlineContext->getFullMatchLength());
[$label] = $inlineContext->getSubMatches();
$reference = $this->createReference($label);
$inlineContext->getContainer()->appendChild(new FootnoteRef($reference, $label));
return true;
}
private function createReference(string $label): Reference
{
$refLabel = $this->slugNormalizer->normalize($label, ['length' => 20]);
return new Reference(
$refLabel,
'#' . $this->config->get('footnote/footnote_id_prefix') . $refLabel,
$label
);
}
public function setEnvironment(EnvironmentInterface $environment): void
{
$this->config = $environment->getConfiguration();
$this->slugNormalizer = $environment->getSlugNormalizer();
}
}
Footnote/Parser/FootnoteStartParser.php 0000644 00000003405 15152100360 0014271 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* 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\Footnote\Parser;
use League\CommonMark\Parser\Block\BlockStart;
use League\CommonMark\Parser\Block\BlockStartParserInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\MarkdownParserStateInterface;
use League\CommonMark\Reference\Reference;
use League\CommonMark\Util\RegexHelper;
final class FootnoteStartParser implements BlockStartParserInterface
{
public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
{
if ($cursor->isIndented() || $parserState->getLastMatchedBlockParser()->canHaveLazyContinuationLines()) {
return BlockStart::none();
}
$match = RegexHelper::matchFirst(
'/^\[\^([^\s^\]]+)\]\:(?:\s|$)/',
$cursor->getLine(),
$cursor->getNextNonSpacePosition()
);
if (! $match) {
return BlockStart::none();
}
$cursor->advanceToNextNonSpaceOrTab();
$cursor->advanceBy(\strlen($match[0]));
$str = $cursor->getRemainder();
\preg_replace('/^\[\^([^\s^\]]+)\]\:(?:\s|$)/', '', $str);
if (\preg_match('/^\[\^([^\s^\]]+)\]\:(?:\s|$)/', $match[0], $matches) !== 1) {
return BlockStart::none();
}
$reference = new Reference($matches[1], $matches[1], $matches[1]);
$footnoteParser = new FootnoteParser($reference);
return BlockStart::of($footnoteParser)->at($cursor);
}
}
Footnote/Parser/FootnoteParser.php 0000644 00000003404 15152100360 0013252 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* 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\Footnote\Parser;
use League\CommonMark\Extension\Footnote\Node\Footnote;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
use League\CommonMark\Parser\Block\BlockContinue;
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Reference\ReferenceInterface;
final class FootnoteParser extends AbstractBlockContinueParser
{
/** @psalm-readonly */
private Footnote $block;
/** @psalm-readonly-allow-private-mutation */
private ?int $indentation = null;
public function __construct(ReferenceInterface $reference)
{
$this->block = new Footnote($reference);
}
public function getBlock(): Footnote
{
return $this->block;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
if ($cursor->isBlank()) {
return BlockContinue::at($cursor);
}
if ($cursor->isIndented()) {
$this->indentation ??= $cursor->getIndent();
$cursor->advanceBy($this->indentation, true);
return BlockContinue::at($cursor);
}
return BlockContinue::none();
}
public function isContainer(): bool
{
return true;
}
public function canContain(AbstractBlock $childBlock): bool
{
return true;
}
}
Footnote/Parser/FootnoteRefParser.php 0000644 00000003253 15152100360 0013711 0 ustar 00 <?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* 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\Footnote\Parser;
use League\CommonMark\Extension\Footnote\Node\FootnoteRef;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
use League\CommonMark\Reference\Reference;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class FootnoteRefParser implements InlineParserInterface, ConfigurationAwareInterface
{
private ConfigurationInterface $config;
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::regex('\[\^([^\s\]]+)\]');
}
public function parse(InlineParserContext $inlineContext): bool
{
$inlineContext->getCursor()->advanceBy($inlineContext->getFullMatchLength());
[$label] = $inlineContext->getSubMatches();
$inlineContext->getContainer()->appendChild(new FootnoteRef($this->createReference($label)));
return true;
}
private function createReference(string $label): Reference
{
return new Reference(
$label,
'#' . $this->config->get('footnote/footnote_id_prefix') . $label,
$label
);
}
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}
}
ExtensionInterface.php 0000644 00000001143 15152100360 0011042 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
interface ExtensionInterface
{
public function register(EnvironmentBuilderInterface $environment): void;
}
DescriptionList/Node/DescriptionTerm.php 0000644 00000000656 15152100360 0014374 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\DescriptionList\Node;
use League\CommonMark\Node\Block\AbstractBlock;
class DescriptionTerm extends AbstractBlock
{
}
DescriptionList/Node/DescriptionList.php 0000644 00000000656 15152100360 0014400 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\DescriptionList\Node;
use League\CommonMark\Node\Block\AbstractBlock;
class DescriptionList extends AbstractBlock
{
}
DescriptionList/Node/Description.php 0000644 00000001503 15152100360 0013534 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\DescriptionList\Node;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Node\Block\TightBlockInterface;
class Description extends AbstractBlock implements TightBlockInterface
{
private bool $tight;
public function __construct(bool $tight = false)
{
parent::__construct();
$this->tight = $tight;
}
public function isTight(): bool
{
return $this->tight;
}
public function setTight(bool $tight): void
{
$this->tight = $tight;
}
}
DescriptionList/Event/LooseDescriptionHandler.php 0000644 00000004336 15152100360 0016237 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\DescriptionList\Event;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\DescriptionList\Node\Description;
use League\CommonMark\Extension\DescriptionList\Node\DescriptionList;
use League\CommonMark\Extension\DescriptionList\Node\DescriptionTerm;
use League\CommonMark\Node\Block\Paragraph;
use League\CommonMark\Node\Inline\Newline;
use League\CommonMark\Node\NodeIterator;
final class LooseDescriptionHandler
{
public function __invoke(DocumentParsedEvent $event): void
{
foreach ($event->getDocument()->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $description) {
if (! $description instanceof Description) {
continue;
}
// Does this description need to be added to a list?
if (! $description->parent() instanceof DescriptionList) {
$list = new DescriptionList();
// Taking any preceding paragraphs with it
if (($paragraph = $description->previous()) instanceof Paragraph) {
$list->appendChild($paragraph);
}
$description->replaceWith($list);
$list->appendChild($description);
}
// Is this description preceded by a paragraph that should really be a term?
if (! (($paragraph = $description->previous()) instanceof Paragraph)) {
continue;
}
// Convert the paragraph into one or more terms
$term = new DescriptionTerm();
$paragraph->replaceWith($term);
foreach ($paragraph->children() as $child) {
if ($child instanceof Newline) {
$newTerm = new DescriptionTerm();
$term->insertAfter($newTerm);
$term = $newTerm;
continue;
}
$term->appendChild($child);
}
}
}
}
DescriptionList/Event/ConsecutiveDescriptionListMerger.php 0000644 00000002223 15152100360 0020136 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\DescriptionList\Event;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\DescriptionList\Node\DescriptionList;
use League\CommonMark\Node\NodeIterator;
final class ConsecutiveDescriptionListMerger
{
public function __invoke(DocumentParsedEvent $event): void
{
foreach ($event->getDocument()->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) {
if (! $node instanceof DescriptionList) {
continue;
}
if (! ($prev = $node->previous()) instanceof DescriptionList) {
continue;
}
// There's another description list behind this one; merge the current one into that
foreach ($node->children() as $child) {
$prev->appendChild($child);
}
$node->detach();
}
}
}
DescriptionList/DescriptionListExtension.php 0000644 00000003557 15152100360 0015413 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\DescriptionList;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\DescriptionList\Event\ConsecutiveDescriptionListMerger;
use League\CommonMark\Extension\DescriptionList\Event\LooseDescriptionHandler;
use League\CommonMark\Extension\DescriptionList\Node\Description;
use League\CommonMark\Extension\DescriptionList\Node\DescriptionList;
use League\CommonMark\Extension\DescriptionList\Node\DescriptionTerm;
use League\CommonMark\Extension\DescriptionList\Parser\DescriptionStartParser;
use League\CommonMark\Extension\DescriptionList\Renderer\DescriptionListRenderer;
use League\CommonMark\Extension\DescriptionList\Renderer\DescriptionRenderer;
use League\CommonMark\Extension\DescriptionList\Renderer\DescriptionTermRenderer;
use League\CommonMark\Extension\ExtensionInterface;
final class DescriptionListExtension implements ExtensionInterface
{
public function register(EnvironmentBuilderInterface $environment): void
{
$environment->addBlockStartParser(new DescriptionStartParser());
$environment->addEventListener(DocumentParsedEvent::class, new LooseDescriptionHandler(), 1001);
$environment->addEventListener(DocumentParsedEvent::class, new ConsecutiveDescriptionListMerger(), 1000);
$environment->addRenderer(DescriptionList::class, new DescriptionListRenderer());
$environment->addRenderer(DescriptionTerm::class, new DescriptionTermRenderer());
$environment->addRenderer(Description::class, new DescriptionRenderer());
}
}
DescriptionList/Renderer/DescriptionListRenderer.php 0000644 00000002167 15152100360 0016747 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\DescriptionList\Renderer;
use League\CommonMark\Extension\DescriptionList\Node\DescriptionList;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
final class DescriptionListRenderer implements NodeRendererInterface
{
/**
* @param DescriptionList $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): HtmlElement
{
DescriptionList::assertInstanceOf($node);
$separator = $childRenderer->getBlockSeparator();
return new HtmlElement('dl', [], $separator . $childRenderer->renderNodes($node->children()) . $separator);
}
}
DescriptionList/Renderer/DescriptionTermRenderer.php 0000644 00000002042 15152100360 0016733 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\DescriptionList\Renderer;
use League\CommonMark\Extension\DescriptionList\Node\DescriptionTerm;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
final class DescriptionTermRenderer implements NodeRendererInterface
{
/**
* @param DescriptionTerm $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
DescriptionTerm::assertInstanceOf($node);
return new HtmlElement('dt', [], $childRenderer->renderNodes($node->children()));
}
}
DescriptionList/Renderer/DescriptionRenderer.php 0000644 00000002022 15152100360 0016101 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\DescriptionList\Renderer;
use League\CommonMark\Extension\DescriptionList\Node\Description;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
final class DescriptionRenderer implements NodeRendererInterface
{
/**
* @param Description $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
Description::assertInstanceOf($node);
return new HtmlElement('dd', [], $childRenderer->renderNodes($node->children()));
}
}
DescriptionList/Parser/DescriptionListContinueParser.php 0000644 00000002760 15152100360 0017627 0 ustar 00 <?php
/*
* 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.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\DescriptionList\Parser;
use League\CommonMark\Extension\DescriptionList\Node\Description;
use League\CommonMark\Extension\DescriptionList\Node\DescriptionList;
use League\CommonMark\Extension\DescriptionList\Node\DescriptionTerm;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
use League\CommonMark\Parser\Block\BlockContinue;
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
use League\CommonMark\Parser\Cursor;
final class DescriptionListContinueParser extends AbstractBlockContinueParser
{
private DescriptionList $block;
public function __construct()
{
$this->block = new DescriptionList();
}
public function getBlock(): DescriptionList
{
return $this->block;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
return BlockContinue::at($cursor);
}
public function isContainer(): bool
{
return true;
}
public function canContain(AbstractBlock $childBlock): bool
{
return $childBlock instanceof DescriptionTerm || $childBlock instanceof Description;
}
}
DescriptionList/Parser/DescriptionTermContinueParser.php 0000644 00000003011 15152100360 0017611 0 ustar 00 <?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\DescriptionList\Parser;
use League\CommonMark\Extension\DescriptionList\Node\DescriptionTerm;
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
use League\CommonMark\Parser\Block\BlockContinue;
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
use League\CommonMark\Parser\Block\BlockContinueParserWithInlinesInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\InlineParserEngineInterface;
final class DescriptionTermContinueParser extends AbstractBlockContinueParser implements BlockContinueParserWithInlinesInterface
{
private DescriptionTerm $block;
private string $term;
public function __construct(string $term)
{
$this->block = new DescriptionTerm();
$this->term = $term;
}
public function getBlock(): DescriptionTerm
{
return $this->block;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
return BlockContinue::finished();
}
public function parseInlines(InlineParserEngineInterface $inlineParser): void
{
if ($this->term !== '') {
$inlineParser->parse($this->term, $this->block);
}
}
}
DescriptionList/Parser/DescriptionStartParser.php 0000644 00000004773 15152100360 0016312 0 ustar 00 <?php
/*
* 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.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\DescriptionList\Parser;
use League\CommonMark\Extension\DescriptionList\Node\Description;
use League\CommonMark\Node\Block\Paragraph;
use League\CommonMark\Parser\Block\BlockStart;
use League\CommonMark\Parser\Block\BlockStartParserInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\MarkdownParserStateInterface;
final class DescriptionStartParser implements BlockStartParserInterface
{
public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
{
if ($cursor->isIndented()) {
return BlockStart::none();
}
$cursor->advanceToNextNonSpaceOrTab();
if ($cursor->match('/^:[ \t]+/') === null) {
return BlockStart::none();
}
$terms = $parserState->getParagraphContent();
$activeBlock = $parserState->getActiveBlockParser()->getBlock();
if ($terms !== null && $terms !== '') {
// New description; tight; term(s) sitting in pending block that we will replace
return BlockStart::of(...[new DescriptionListContinueParser()], ...self::splitTerms($terms), ...[new DescriptionContinueParser(true, $cursor->getPosition())])
->at($cursor)
->replaceActiveBlockParser();
}
if ($activeBlock instanceof Paragraph && $activeBlock->parent() instanceof Description) {
// Additional description in the same list as the parent description
return BlockStart::of(new DescriptionContinueParser(true, $cursor->getPosition()))->at($cursor);
}
if ($activeBlock->lastChild() instanceof Paragraph) {
// New description; loose; term(s) sitting in previous closed paragraph block
return BlockStart::of(new DescriptionContinueParser(false, $cursor->getPosition()))->at($cursor);
}
// No preceding terms
return BlockStart::none();
}
/**
* @return array<int, DescriptionTermContinueParser>
*/
private static function splitTerms(string $terms): array
{
$ret = [];
foreach (\explode("\n", $terms) as $term) {
$ret[] = new DescriptionTermContinueParser($term);
}
return $ret;
}
}
DescriptionList/Parser/DescriptionContinueParser.php 0000644 00000003534 15152100360 0016773 0 ustar 00 <?php
/*
* 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.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\DescriptionList\Parser;
use League\CommonMark\Extension\DescriptionList\Node\Description;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
use League\CommonMark\Parser\Block\BlockContinue;
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
use League\CommonMark\Parser\Cursor;
final class DescriptionContinueParser extends AbstractBlockContinueParser
{
private Description $block;
private int $indentation;
public function __construct(bool $tight, int $indentation)
{
$this->block = new Description($tight);
$this->indentation = $indentation;
}
public function getBlock(): Description
{
return $this->block;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
if ($cursor->isBlank()) {
if ($this->block->firstChild() === null) {
// Blank line after empty item
return BlockContinue::none();
}
$cursor->advanceToNextNonSpaceOrTab();
return BlockContinue::at($cursor);
}
if ($cursor->getIndent() >= $this->indentation) {
$cursor->advanceBy($this->indentation, true);
return BlockContinue::at($cursor);
}
return BlockContinue::none();
}
public function isContainer(): bool
{
return true;
}
public function canContain(AbstractBlock $childBlock): bool
{
return true;
}
}