Passed
Push — 2.0 ( b7ed7b...934f6b )
by Colin
35:11 queued 31:18
created

TableOfContentsBuilder   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 77
Duplicated Lines 0 %

Test Coverage

Coverage 95%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 16
eloc 39
c 1
b 0
f 1
dl 0
loc 77
ccs 38
cts 40
cp 0.95
rs 10

4 Methods

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