This commit is contained in:
Paolo A
2024-08-13 13:44:16 +00:00
parent 1bbb23088d
commit e796d76612
4001 changed files with 30101 additions and 40075 deletions

View File

@@ -14,48 +14,49 @@ declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Event;
use League\CommonMark\Block\Element\Paragraph;
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\Inline\Element\Text;
use League\CommonMark\Node\Block\Paragraph;
use League\CommonMark\Node\Inline\Text;
use League\CommonMark\Reference\Reference;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class AnonymousFootnotesListener implements ConfigurationAwareInterface
{
/** @var ConfigurationInterface */
private $config;
private ConfigurationInterface $config;
public function onDocumentParsed(DocumentParsedEvent $event): void
{
$document = $event->getDocument();
$walker = $document->walker();
while ($event = $walker->next()) {
$node = $event->getNode();
if ($node instanceof FootnoteRef && $event->isEntering() && null !== $text = $node->getContent()) {
// Anonymous footnote needs to create a footnote from its content
$existingReference = $node->getReference();
$reference = new Reference(
$existingReference->getLabel(),
'#' . $this->config->get('footnote/ref_id_prefix', 'fnref:') . $existingReference->getLabel(),
$existingReference->getTitle()
);
$footnote = new Footnote($reference);
$footnote->addBackref(new FootnoteBackref($reference));
$paragraph = new Paragraph();
$paragraph->appendChild(new Text($text));
$footnote->appendChild($paragraph);
$document->appendChild($footnote);
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 $config): void
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $config;
$this->config = $configuration;
}
}

View File

@@ -14,61 +14,43 @@ declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Event;
use League\CommonMark\Block\Element\Document;
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\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class GatherFootnotesListener implements ConfigurationAwareInterface
{
/** @var ConfigurationInterface */
private $config;
private ConfigurationInterface $config;
public function onDocumentParsed(DocumentParsedEvent $event): void
{
$document = $event->getDocument();
$walker = $document->walker();
$document = $event->getDocument();
$footnotes = [];
while ($event = $walker->next()) {
if (!$event->isEntering()) {
continue;
}
$node = $event->getNode();
if (!$node instanceof Footnote) {
foreach ($document->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) {
if (! $node instanceof Footnote) {
continue;
}
// Look for existing reference with footnote label
$ref = $document->getReferenceMap()->getReference($node->getReference()->getLabel());
$ref = $document->getReferenceMap()->get($node->getReference()->getLabel());
if ($ref !== null) {
// Use numeric title to get footnotes order
$footnotes[\intval($ref->getTitle())] = $node;
$footnotes[(int) $ref->getTitle()] = $node;
} else {
// Footnote call is missing, append footnote at the end
$footnotes[INF] = $node;
$footnotes[\PHP_INT_MAX] = $node;
}
/*
* Look for all footnote refs pointing to this footnote
* and create each footnote backrefs.
*/
$backrefs = $document->getData(
'#' . $this->config->get('footnote/footnote_id_prefix', 'fn:') . $node->getReference()->getDestination(),
[]
);
/** @var Reference $backref */
foreach ($backrefs as $backref) {
$node->addBackref(new FootnoteBackref(new Reference(
$backref->getLabel(),
'#' . $this->config->get('footnote/ref_id_prefix', 'fnref:') . $backref->getLabel(),
$backref->getTitle()
)));
$key = '#' . $this->config->get('footnote/footnote_id_prefix') . $node->getReference()->getDestination();
if ($document->data->has($key)) {
$this->createBackrefs($node, $document->data->get($key));
}
}
@@ -93,8 +75,32 @@ final class GatherFootnotesListener implements ConfigurationAwareInterface
return $footnoteContainer;
}
public function setConfiguration(ConfigurationInterface $config): void
/**
* 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
{
$this->config = $config;
// 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;
}
}

View File

@@ -22,26 +22,19 @@ final class NumberFootnotesListener
{
public function onDocumentParsed(DocumentParsedEvent $event): void
{
$document = $event->getDocument();
$walker = $document->walker();
$nextCounter = 1;
$usedLabels = [];
$document = $event->getDocument();
$nextCounter = 1;
$usedLabels = [];
$usedCounters = [];
while ($event = $walker->next()) {
if (!$event->isEntering()) {
foreach ($document->iterator() as $node) {
if (! $node instanceof FootnoteRef) {
continue;
}
$node = $event->getNode();
if (!$node instanceof FootnoteRef) {
continue;
}
$existingReference = $node->getReference();
$label = $existingReference->getLabel();
$counter = $nextCounter;
$existingReference = $node->getReference();
$label = $existingReference->getLabel();
$counter = $nextCounter;
$canIncrementCounter = true;
if (\array_key_exists($label, $usedLabels)) {
@@ -49,8 +42,8 @@ final class NumberFootnotesListener
* Reference is used again, we need to point
* to the same footnote. But with a different ID
*/
$counter = $usedCounters[$label];
$label = $label . '__' . ++$usedLabels[$label];
$counter = $usedCounters[$label];
$label .= '__' . ++$usedLabels[$label];
$canIncrementCounter = false;
}
@@ -63,19 +56,15 @@ final class NumberFootnotesListener
// Override reference with numeric link
$node->setReference($newReference);
$document->getReferenceMap()->addReference($newReference);
$document->getReferenceMap()->add($newReference);
/*
* Store created references in document for
* creating FootnoteBackrefs
*/
if (false === $document->getData($existingReference->getDestination(), false)) {
$document->data[$existingReference->getDestination()] = [];
}
$document->data->append($existingReference->getDestination(), $newReference);
$document->data[$existingReference->getDestination()][] = $newReference;
$usedLabels[$label] = 1;
$usedLabels[$label] = 1;
$usedCounters[$label] = $nextCounter;
if ($canIncrementCounter) {

View File

@@ -14,10 +14,11 @@ declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote;
use League\CommonMark\ConfigurableEnvironmentInterface;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\ExtensionInterface;
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;
@@ -25,29 +26,45 @@ 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\FootnoteParser;
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 ExtensionInterface
final class FootnoteExtension implements ConfigurableExtensionInterface
{
public function register(ConfigurableEnvironmentInterface $environment)
public function configureSchema(ConfigurationBuilderInterface $builder): void
{
$environment->addBlockParser(new FootnoteParser(), 51);
$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->addBlockRenderer(FootnoteContainer::class, new FootnoteContainerRenderer());
$environment->addBlockRenderer(Footnote::class, new FootnoteRenderer());
$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->addInlineRenderer(FootnoteRef::class, new FootnoteRefRenderer());
$environment->addInlineRenderer(FootnoteBackref::class, new FootnoteBackrefRenderer());
$environment->addEventListener(DocumentParsedEvent::class, [new AnonymousFootnotesListener(), 'onDocumentParsed']);
$environment->addEventListener(DocumentParsedEvent::class, [new NumberFootnotesListener(), 'onDocumentParsed']);
$environment->addEventListener(DocumentParsedEvent::class, [new GatherFootnotesListener(), 'onDocumentParsed']);
$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);
}
}

View File

@@ -14,62 +14,24 @@ declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Node;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Cursor;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Reference\ReferenceInterface;
use League\CommonMark\Reference\ReferenceableInterface;
/**
* @method children() AbstractBlock[]
*/
final class Footnote extends AbstractBlock
final class Footnote extends AbstractBlock implements ReferenceableInterface
{
/**
* @var FootnoteBackref[]
*/
private $backrefs = [];
/**
* @var ReferenceInterface
*/
private $reference;
/** @psalm-readonly */
private ReferenceInterface $reference;
public function __construct(ReferenceInterface $reference)
{
parent::__construct();
$this->reference = $reference;
}
public function canContain(AbstractBlock $block): bool
{
return true;
}
public function isCode(): bool
{
return false;
}
public function matchesNextLine(Cursor $cursor): bool
{
return false;
}
public function getReference(): ReferenceInterface
{
return $this->reference;
}
public function addBackref(FootnoteBackref $backref): self
{
$this->backrefs[] = $backref;
return $this;
}
/**
* @return FootnoteBackref[]
*/
public function getBackrefs(): array
{
return $this->backrefs;
}
}

View File

@@ -14,19 +14,22 @@ declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Node;
use League\CommonMark\Inline\Element\AbstractInline;
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
final class FootnoteBackref extends AbstractInline implements ReferenceableInterface
{
/** @var ReferenceInterface */
private $reference;
/** @psalm-readonly */
private ReferenceInterface $reference;
public function __construct(ReferenceInterface $reference)
{
parent::__construct();
$this->reference = $reference;
}

View File

@@ -14,26 +14,8 @@ declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Node;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Cursor;
use League\CommonMark\Node\Block\AbstractBlock;
/**
* @method children() AbstractBlock[]
*/
final class FootnoteContainer extends AbstractBlock
{
public function canContain(AbstractBlock $block): bool
{
return $block instanceof Footnote;
}
public function isCode(): bool
{
return false;
}
public function matchesNextLine(Cursor $cursor): bool
{
return false;
}
}

View File

@@ -14,27 +14,30 @@ declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Node;
use League\CommonMark\Inline\Element\AbstractInline;
use League\CommonMark\Node\Inline\AbstractInline;
use League\CommonMark\Reference\ReferenceInterface;
use League\CommonMark\Reference\ReferenceableInterface;
final class FootnoteRef extends AbstractInline
final class FootnoteRef extends AbstractInline implements ReferenceableInterface
{
/** @var ReferenceInterface */
private $reference;
private ReferenceInterface $reference;
/** @var string|null */
private $content;
/** @psalm-readonly */
private ?string $content = null;
/**
* @param ReferenceInterface $reference
* @param string|null $content
* @param array<mixed> $data
* @param array<mixed> $data
*/
public function __construct(ReferenceInterface $reference, ?string $content = null, array $data = [])
{
parent::__construct();
$this->reference = $reference;
$this->content = $content;
$this->data = $data;
$this->content = $content;
if (\count($data) > 0) {
$this->data->import($data);
}
}
public function getReference(): ReferenceInterface
@@ -42,11 +45,9 @@ final class FootnoteRef extends AbstractInline
return $this->reference;
}
public function setReference(ReferenceInterface $reference): FootnoteRef
public function setReference(ReferenceInterface $reference): void
{
$this->reference = $reference;
return $this;
}
public function getContent(): ?string

View File

@@ -14,72 +14,53 @@ 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\Inline\Parser\InlineParserInterface;
use League\CommonMark\InlineParserContext;
use League\CommonMark\Normalizer\SlugNormalizer;
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\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
use League\Config\ConfigurationInterface;
final class AnonymousFootnoteRefParser implements InlineParserInterface, ConfigurationAwareInterface
final class AnonymousFootnoteRefParser implements InlineParserInterface, EnvironmentAwareInterface
{
/** @var ConfigurationInterface */
private $config;
private ConfigurationInterface $config;
/** @var TextNormalizerInterface */
private $slugNormalizer;
/** @psalm-readonly-allow-private-mutation */
private TextNormalizerInterface $slugNormalizer;
public function __construct()
public function getMatchDefinition(): InlineParserMatch
{
$this->slugNormalizer = new SlugNormalizer();
}
public function getCharacters(): array
{
return ['^'];
return InlineParserMatch::regex('\^\[([^\]]+)\]');
}
public function parse(InlineParserContext $inlineContext): bool
{
$container = $inlineContext->getContainer();
$cursor = $inlineContext->getCursor();
$nextChar = $cursor->peek();
if ($nextChar !== '[') {
return false;
}
$state = $cursor->saveState();
$inlineContext->getCursor()->advanceBy($inlineContext->getFullMatchLength());
$m = $cursor->match('/\^\[[^\n^\]]+\]/');
if ($m !== null) {
if (\preg_match('#\^\[([^\]]+)\]#', $m, $matches) > 0) {
$reference = $this->createReference($matches[1]);
$container->appendChild(new FootnoteRef($reference, $matches[1]));
[$label] = $inlineContext->getSubMatches();
$reference = $this->createReference($label);
$inlineContext->getContainer()->appendChild(new FootnoteRef($reference, $label));
return true;
}
}
$cursor->restoreState($state);
return false;
return true;
}
private function createReference(string $label): Reference
{
$refLabel = $this->slugNormalizer->normalize($label);
$refLabel = \mb_substr($refLabel, 0, 20);
$refLabel = $this->slugNormalizer->normalize($label, ['length' => 20]);
return new Reference(
$refLabel,
'#' . $this->config->get('footnote/footnote_id_prefix', 'fn:') . $refLabel,
'#' . $this->config->get('footnote/footnote_id_prefix') . $refLabel,
$label
);
}
public function setConfiguration(ConfigurationInterface $config): void
public function setEnvironment(EnvironmentInterface $environment): void
{
$this->config = $config;
$this->config = $environment->getConfiguration();
$this->slugNormalizer = $environment->getSlugNormalizer();
}
}

View File

@@ -14,50 +14,55 @@ declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Parser;
use League\CommonMark\Block\Parser\BlockParserInterface;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
use League\CommonMark\Extension\Footnote\Node\Footnote;
use League\CommonMark\Reference\Reference;
use League\CommonMark\Util\RegexHelper;
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 implements BlockParserInterface
final class FootnoteParser extends AbstractBlockContinueParser
{
public function parse(ContextInterface $context, Cursor $cursor): bool
/** @psalm-readonly */
private Footnote $block;
/** @psalm-readonly-allow-private-mutation */
private ?int $indentation = null;
public function __construct(ReferenceInterface $reference)
{
if ($cursor->isIndented()) {
return false;
}
$match = RegexHelper::matchFirst(
'/^\[\^([^\n^\]]+)\]\:\s/',
$cursor->getLine(),
$cursor->getNextNonSpacePosition()
);
if (!$match) {
return false;
}
$cursor->advanceToNextNonSpaceOrTab();
$cursor->advanceBy(\strlen($match[0]));
$str = $cursor->getRemainder();
\preg_replace('/^\[\^([^\n^\]]+)\]\:\s/', '', $str);
if (\preg_match('/^\[\^([^\n^\]]+)\]\:\s/', $match[0], $matches) > 0) {
$context->addBlock($this->createFootnote($matches[1]));
$context->setBlocksParsed(true);
return true;
}
return false;
$this->block = new Footnote($reference);
}
private function createFootnote(string $label): Footnote
public function getBlock(): Footnote
{
return new Footnote(
new Reference($label, $label, $label)
);
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;
}
}

View File

@@ -15,58 +15,43 @@ declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Parser;
use League\CommonMark\Extension\Footnote\Node\FootnoteRef;
use League\CommonMark\Inline\Parser\InlineParserInterface;
use League\CommonMark\InlineParserContext;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
use League\CommonMark\Reference\Reference;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class FootnoteRefParser implements InlineParserInterface, ConfigurationAwareInterface
{
/** @var ConfigurationInterface */
private $config;
private ConfigurationInterface $config;
public function getCharacters(): array
public function getMatchDefinition(): InlineParserMatch
{
return ['['];
return InlineParserMatch::regex('\[\^([^\s\]]+)\]');
}
public function parse(InlineParserContext $inlineContext): bool
{
$container = $inlineContext->getContainer();
$cursor = $inlineContext->getCursor();
$nextChar = $cursor->peek();
if ($nextChar !== '^') {
return false;
}
$inlineContext->getCursor()->advanceBy($inlineContext->getFullMatchLength());
$state = $cursor->saveState();
[$label] = $inlineContext->getSubMatches();
$inlineContext->getContainer()->appendChild(new FootnoteRef($this->createReference($label)));
$m = $cursor->match('#\[\^([^\]]+)\]#');
if ($m !== null) {
if (\preg_match('#\[\^([^\]]+)\]#', $m, $matches) > 0) {
$container->appendChild(new FootnoteRef($this->createReference($matches[1])));
return true;
}
}
$cursor->restoreState($state);
return false;
return true;
}
private function createReference(string $label): Reference
{
return new Reference(
$label,
'#' . $this->config->get('footnote/footnote_id_prefix', 'fn:') . $label,
'#' . $this->config->get('footnote/footnote_id_prefix') . $label,
$label
);
}
public function setConfiguration(ConfigurationInterface $config): void
public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $config;
$this->config = $configuration;
}
}

View File

@@ -14,36 +14,68 @@ declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Renderer;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\Extension\Footnote\Node\FootnoteBackref;
use League\CommonMark\HtmlElement;
use League\CommonMark\Inline\Element\AbstractInline;
use League\CommonMark\Inline\Renderer\InlineRendererInterface;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
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 InlineRendererInterface, ConfigurationAwareInterface
final class FootnoteBackrefRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface
{
/** @var ConfigurationInterface */
private $config;
public const DEFAULT_SYMBOL = '↩';
public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer)
private ConfigurationInterface $config;
/**
* @param FootnoteBackref $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): string
{
if (!($inline instanceof FootnoteBackref)) {
throw new \InvalidArgumentException('Incompatible inline type: ' . \get_class($inline));
}
FootnoteBackref::assertInstanceOf($node);
$attrs = $inline->getData('attributes', []);
$attrs['class'] = $attrs['class'] ?? $this->config->get('footnote/backref_class', 'footnote-backref');
$attrs['rev'] = 'footnote';
$attrs['href'] = \mb_strtolower($inline->getReference()->getDestination());
$attrs['role'] = 'doc-backlink';
$attrs = $node->data->getData('attributes');
return '&nbsp;' . new HtmlElement('a', $attrs, '&#8617;', true);
$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 '&nbsp;' . new HtmlElement('a', $attrs->export(), \htmlspecialchars($symbol), true);
}
public function setConfiguration(ConfigurationInterface $configuration)
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(),
];
}
}

View File

@@ -14,39 +14,58 @@ declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Renderer;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Renderer\BlockRendererInterface;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\Extension\Footnote\Node\FootnoteContainer;
use League\CommonMark\HtmlElement;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
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 BlockRendererInterface, ConfigurationAwareInterface
final class FootnoteContainerRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface
{
/** @var ConfigurationInterface */
private $config;
private ConfigurationInterface $config;
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
/**
* @param FootnoteContainer $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
if (!($block instanceof FootnoteContainer)) {
throw new \InvalidArgumentException('Incompatible block type: ' . \get_class($block));
}
FootnoteContainer::assertInstanceOf($node);
$attrs = $block->getData('attributes', []);
$attrs['class'] = $attrs['class'] ?? $this->config->get('footnote/container_class', 'footnotes');
$attrs['role'] = 'doc-endnotes';
$attrs = $node->data->getData('attributes');
$contents = new HtmlElement('ol', [], $htmlRenderer->renderBlocks($block->children()));
if ($this->config->get('footnote/container_add_hr', true)) {
$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, $contents);
return new HtmlElement('div', $attrs->export(), $contents);
}
public function setConfiguration(ConfigurationInterface $configuration)
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 [];
}
}

View File

@@ -14,49 +14,74 @@ declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Renderer;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\Extension\Footnote\Node\FootnoteRef;
use League\CommonMark\HtmlElement;
use League\CommonMark\Inline\Element\AbstractInline;
use League\CommonMark\Inline\Renderer\InlineRendererInterface;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
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 InlineRendererInterface, ConfigurationAwareInterface
final class FootnoteRefRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface
{
/** @var ConfigurationInterface */
private $config;
private ConfigurationInterface $config;
public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer)
/**
* @param FootnoteRef $node
*
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
if (!($inline instanceof FootnoteRef)) {
throw new \InvalidArgumentException('Incompatible inline type: ' . \get_class($inline));
}
FootnoteRef::assertInstanceOf($node);
$attrs = $inline->getData('attributes', []);
$class = $attrs['class'] ?? $this->config->get('footnote/ref_class', 'footnote-ref');
$idPrefix = $this->config->get('footnote/ref_id_prefix', 'fnref:');
$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($inline->getReference()->getLabel()),
'id' => $idPrefix . \mb_strtolower($node->getReference()->getLabel(), 'UTF-8'),
],
new HTMLElement(
new HtmlElement(
'a',
[
'class' => $class,
'href' => \mb_strtolower($inline->getReference()->getDestination()),
'role' => 'doc-noteref',
],
$inline->getReference()->getTitle()
$attrs->export(),
$node->getReference()->getTitle()
),
true
);
}
public function setConfiguration(ConfigurationInterface $configuration)
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(),
];
}
}

View File

@@ -14,51 +14,67 @@ declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Renderer;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Renderer\BlockRendererInterface;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\Extension\Footnote\Node\Footnote;
use League\CommonMark\HtmlElement;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
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 BlockRendererInterface, ConfigurationAwareInterface
final class FootnoteRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface
{
/** @var ConfigurationInterface */
private $config;
private ConfigurationInterface $config;
/**
* @param Footnote $block
* @param ElementRendererInterface $htmlRenderer
* @param bool $inTightList
* @param Footnote $node
*
* @return HtmlElement
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable
{
if (!($block instanceof Footnote)) {
throw new \InvalidArgumentException('Incompatible block type: ' . \get_class($block));
}
Footnote::assertInstanceOf($node);
$attrs = $block->getData('attributes', []);
$attrs['class'] = $attrs['class'] ?? $this->config->get('footnote/footnote_class', 'footnote');
$attrs['id'] = $this->config->get('footnote/footnote_id_prefix', 'fn:') . \mb_strtolower($block->getReference()->getLabel());
$attrs['role'] = 'doc-endnote';
$attrs = $node->data->getData('attributes');
foreach ($block->getBackrefs() as $backref) {
$block->lastChild()->appendChild($backref);
}
$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,
$htmlRenderer->renderBlocks($block->children()),
$attrs->export(),
$childRenderer->renderNodes($node->children()),
true
);
}
public function setConfiguration(ConfigurationInterface $configuration)
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(),
];
}
}