summaryrefslogtreecommitdiff
path: root/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsBuilder.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsBuilder.php')
-rw-r--r--vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsBuilder.php166
1 files changed, 166 insertions, 0 deletions
diff --git a/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsBuilder.php b/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsBuilder.php
new file mode 100644
index 0000000000..05d8be9d57
--- /dev/null
+++ b/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsBuilder.php
@@ -0,0 +1,166 @@
+<?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.
+ */
+
+namespace League\CommonMark\Extension\TableOfContents;
+
+use League\CommonMark\Block\Element\Document;
+use League\CommonMark\Block\Element\Heading;
+use League\CommonMark\Block\Element\ListBlock;
+use League\CommonMark\Block\Element\ListData;
+use League\CommonMark\Block\Element\ListItem;
+use League\CommonMark\Block\Element\Paragraph;
+use League\CommonMark\Event\DocumentParsedEvent;
+use League\CommonMark\Exception\InvalidOptionException;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalink;
+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\Inline\Element\Link;
+use League\CommonMark\Util\ConfigurationAwareInterface;
+use League\CommonMark\Util\ConfigurationInterface;
+
+final class TableOfContentsBuilder implements ConfigurationAwareInterface
+{
+ 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';
+
+ public const POSITION_TOP = 'top';
+ public const POSITION_BEFORE_HEADINGS = 'before-headings';
+
+ /** @var ConfigurationInterface */
+ private $config;
+
+ public function onDocumentParsed(DocumentParsedEvent $event): void
+ {
+ $document = $event->getDocument();
+ $toc = $this->createToc();
+
+ $normalizer = $this->getNormalizer($toc);
+ [$min, $max] = $this->getMinAndMaxHeadingLevels();
+
+ $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() < $min || $heading->getLevel() > $max) {
+ continue;
+ }
+
+ // Keep track of the first heading we see - we might need this later
+ $firstHeading = $firstHeading ?? $heading;
+
+ // Create the new link
+ $link = new Link('#' . $headingLink->getSlug(), $heading->getStringContent());
+ $paragraph = new Paragraph();
+ $paragraph->appendChild($link);
+
+ $listItem = new ListItem($toc->getListData());
+ $listItem->appendChild($paragraph);
+
+ // 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;
+ }
+
+ // Add the TOC to the Document
+ $position = $this->config->get('table_of_contents/position', self::POSITION_TOP);
+ if ($position === self::POSITION_TOP) {
+ $document->prependChild($toc);
+ } elseif ($position === self::POSITION_BEFORE_HEADINGS) {
+ $firstHeading->insertBefore($toc);
+ } else {
+ throw new InvalidOptionException(\sprintf('Invalid config option "%s" for "table_of_contents/position"', $position));
+ }
+ }
+
+ private function createToc(): TableOfContents
+ {
+ $listData = new ListData();
+
+ $style = $this->config->get('table_of_contents/style', self::STYLE_BULLET);
+ if ($style === self::STYLE_BULLET) {
+ $listData->type = ListBlock::TYPE_BULLET;
+ } elseif ($style === self::STYLE_ORDERED) {
+ $listData->type = ListBlock::TYPE_ORDERED;
+ } else {
+ throw new InvalidOptionException(\sprintf('Invalid config option "%s" for "table_of_contents/style"', $style));
+ }
+
+ $toc = new TableOfContents($listData);
+
+ $class = $this->config->get('table_of_contents/html_class', 'table-of-contents');
+ if (!empty($class)) {
+ $toc->data['attributes']['class'] = $class;
+ }
+
+ return $toc;
+ }
+
+ /**
+ * @return array<int>
+ */
+ private function getMinAndMaxHeadingLevels(): array
+ {
+ return [
+ (int) $this->config->get('table_of_contents/min_heading_level', 1),
+ (int) $this->config->get('table_of_contents/max_heading_level', 6),
+ ];
+ }
+
+ /**
+ * @param Document $document
+ *
+ * @return iterable<HeadingPermalink>
+ */
+ private function getHeadingLinks(Document $document)
+ {
+ $walker = $document->walker();
+ while ($event = $walker->next()) {
+ if ($event->isEntering() && ($node = $event->getNode()) instanceof HeadingPermalink) {
+ yield $node;
+ }
+ }
+ }
+
+ private function getNormalizer(TableOfContents $toc): NormalizerStrategyInterface
+ {
+ $strategy = $this->config->get('table_of_contents/normalize', self::NORMALIZE_RELATIVE);
+ if ($strategy === self::NORMALIZE_DISABLED) {
+ return new AsIsNormalizerStrategy($toc);
+ } elseif ($strategy === self::NORMALIZE_RELATIVE) {
+ return new RelativeNormalizerStrategy($toc);
+ } elseif ($strategy === self::NORMALIZE_FLAT) {
+ return new FlatNormalizerStrategy($toc);
+ }
+
+ throw new InvalidOptionException(\sprintf('Invalid config option "%s" for "table_of_contents/normalize"', $strategy));
+ }
+
+ public function setConfiguration(ConfigurationInterface $config)
+ {
+ $this->config = $config;
+ }
+}