vendor and env first commit
This commit is contained in:
+62
@@ -0,0 +1,62 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Vendored
+68
@@ -0,0 +1,68 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
<?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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
<?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 [];
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user