Completed
Push — toc-changes ( e65cac...16e29a )
by Colin
02:29
created

insertBeforeFirstLinkedHeading()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 5.0729

Importance

Changes 0
Metric Value
dl 0
loc 11
c 0
b 0
f 0
ccs 6
cts 7
cp 0.8571
rs 9.6111
cc 5
nc 3
nop 2
crap 5.0729
1
<?php
2
3
/*
4
 * This file is part of the league/commonmark package.
5
 *
6
 * (c) Colin O'Dell <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace League\CommonMark\Extension\TableOfContents;
13
14
use League\CommonMark\Configuration\ConfigurationAwareInterface;
15
use League\CommonMark\Configuration\ConfigurationInterface;
16
use League\CommonMark\Event\DocumentParsedEvent;
17
use League\CommonMark\Exception\InvalidOptionException;
18
use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
19
use League\CommonMark\Extension\HeadingPermalink\HeadingPermalink;
20
use League\CommonMark\Extension\TableOfContents\Node\TableOfContents;
21
use League\CommonMark\Extension\TableOfContents\Node\TableOfContentsPlaceholder;
22
use League\CommonMark\Node\Block\Document;
23
24
final class TableOfContentsBuilder implements ConfigurationAwareInterface
25
{
26
    public const POSITION_TOP = 'top';
27
    public const POSITION_BEFORE_HEADINGS = 'before-headings';
28
    public const POSITION_PLACEHOLDER = 'placeholder';
29
30
    /** @var ConfigurationInterface */
31
    private $config;
32
33 36
    public function onDocumentParsed(DocumentParsedEvent $event): void
34
    {
35 36
        $document = $event->getDocument();
36
37 36
        $generator = new TableOfContentsGenerator(
38 36
            $this->config->get('table_of_contents/style', TableOfContentsGenerator::STYLE_BULLET),
39 36
            $this->config->get('table_of_contents/normalize', TableOfContentsGenerator::NORMALIZE_RELATIVE),
40 36
            (int) $this->config->get('table_of_contents/min_heading_level', 1),
41 36
            (int) $this->config->get('table_of_contents/max_heading_level', 6)
42
        );
43
44 36
        $toc = $generator->generate($document);
45 36
        if ($toc === null) {
46
            // No linkable headers exist, so no TOC could be generated
47 6
            return;
48
        }
49
50
        // Add custom CSS class(es), if defined
51 33
        $class = $this->config->get('table_of_contents/html_class', 'table-of-contents');
52 33
        if (!empty($class)) {
53 33
            $toc->data['attributes']['class'] = $class;
54
        }
55
56
        // Add the TOC to the Document
57 33
        $position = $this->config->get('table_of_contents/position', self::POSITION_TOP);
58 33
        if ($position === self::POSITION_TOP) {
59 27
            $document->prependChild($toc);
60 6
        } elseif ($position === self::POSITION_BEFORE_HEADINGS) {
61 3
            $this->insertBeforeFirstLinkedHeading($document, $toc);
62 3
        } elseif ($position === self::POSITION_PLACEHOLDER) {
63 3
            $this->replacePlaceholders($document, $toc);
64
        } else {
65
            throw new InvalidOptionException(\sprintf('Invalid config option "%s" for "table_of_contents/position"', $position));
66
        }
67 33
    }
68
69 3
    private function insertBeforeFirstLinkedHeading(Document $document, TableOfContents $toc): void
70
    {
71 3
        $walker = $document->walker();
72 3
        while ($event = $walker->next()) {
73 3
            if ($event->isEntering() && ($node = $event->getNode()) instanceof HeadingPermalink && ($parent = $node->parent()) instanceof Heading) {
74 3
                $parent->insertBefore($toc);
75
76 3
                return;
77
            }
78
        }
79
    }
80
81 3
    private function replacePlaceholders(Document $document, TableOfContents $toc): void
82
    {
83 3
        $walker = $document->walker();
84 3
        while ($event = $walker->next()) {
85
            // Add the block once we find a placeholder (and we're about to leave it)
86 3
            if (!$event->getNode() instanceof TableOfContentsPlaceholder) {
87 3
                continue;
88
            }
89
90 3
            if ($event->isEntering()) {
91 3
                continue;
92
            }
93
94 3
            $event->getNode()->replaceWith(clone $toc);
95
        }
96 3
    }
97
98 36
    public function setConfiguration(ConfigurationInterface $config): void
99
    {
100 36
        $this->config = $config;
101 36
    }
102
}
103