Passed
Pull Request — main (#1074)
by
unknown
02:24
created

TableOfContentsBuilder::onDocumentParsed()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 35
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 6.0026

Importance

Changes 0
Metric Value
cc 6
eloc 23
nc 9
nop 1
dl 0
loc 35
ccs 23
cts 24
cp 0.9583
crap 6.0026
rs 8.9297
c 0
b 0
f 0
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\CommonMark\Node\NodeIterator;
23
use League\Config\ConfigurationAwareInterface;
24
use League\Config\ConfigurationInterface;
25
use League\Config\Exception\InvalidConfigurationException;
26
27
final class TableOfContentsBuilder implements ConfigurationAwareInterface
28
{
29
    public const POSITION_TOP             = 'top';
30
    public const POSITION_BEFORE_HEADINGS = 'before-headings';
31
    public const POSITION_PLACEHOLDER     = 'placeholder';
32
33
    /** @psalm-readonly-allow-private-mutation */
34
    private ConfigurationInterface $config;
35
36 70
    public function onDocumentParsed(DocumentParsedEvent $event): void
37
    {
38 70
        $document = $event->getDocument();
39
40 70
        $generator = new TableOfContentsGenerator(
41 70
            (string) $this->config->get('table_of_contents/style'),
42 70
            (string) $this->config->get('table_of_contents/normalize'),
43 70
            (int) $this->config->get('table_of_contents/min_heading_level'),
44 70
            (int) $this->config->get('table_of_contents/max_heading_level'),
45 70
            (string) $this->config->get('heading_permalink/fragment_prefix'),
46 70
            (string) $this->config->get('table_of_contents/label'),
47 70
        );
48
49 70
        $toc = $generator->generate($document);
50 70
        if ($toc === null) {
51
            // No linkable headers exist, so no TOC could be generated
52 8
            return;
53
        }
54
55
        // Add custom CSS class(es), if defined
56 62
        $class = $this->config->get('table_of_contents/html_class');
57 62
        if ($class !== null) {
58 62
            $toc->data->append('attributes/class', $class);
59
        }
60
61
        // Add the TOC to the Document
62 62
        $position = $this->config->get('table_of_contents/position');
63 62
        if ($position === self::POSITION_TOP) {
64 50
            $document->prependChild($toc);
65 12
        } elseif ($position === self::POSITION_BEFORE_HEADINGS) {
66 4
            $this->insertBeforeFirstLinkedHeading($document, $toc);
67 8
        } elseif ($position === self::POSITION_PLACEHOLDER) {
68 8
            $this->replacePlaceholders($document, $toc);
69
        } else {
70
            throw InvalidConfigurationException::forConfigOption('table_of_contents/position', $position);
71
        }
72
    }
73
74 4
    private function insertBeforeFirstLinkedHeading(Document $document, TableOfContents $toc): void
75
    {
76 4
        foreach ($document->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) {
77 4
            if (! $node instanceof Heading) {
78 4
                continue;
79
            }
80
81 4
            foreach ($node->children() as $child) {
82 4
                if ($child instanceof HeadingPermalink) {
83 4
                    $node->insertBefore($toc);
84
85 4
                    return;
86
                }
87
            }
88
        }
89
    }
90
91 8
    private function replacePlaceholders(Document $document, TableOfContents $toc): void
92
    {
93 8
        foreach ($document->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) {
94
            // Add the block once we find a placeholder
95 8
            if (! $node instanceof TableOfContentsPlaceholder) {
96 8
                continue;
97
            }
98
99 4
            $node->replaceWith(clone $toc);
100
        }
101
    }
102
103 70
    public function setConfiguration(ConfigurationInterface $configuration): void
104
    {
105 70
        $this->config = $configuration;
106
    }
107
}
108