Processor   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 224
Duplicated Lines 0 %

Test Coverage

Coverage 89.52%

Importance

Changes 4
Bugs 2 Features 0
Metric Value
eloc 99
dl 0
loc 224
ccs 94
cts 105
cp 0.8952
rs 9.44
c 4
b 2
f 0
wmc 37

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A cloneChildren() 0 18 2
B onDocumentParsed() 0 36 11
B ensureHeadingHasId() 0 31 7
A generate() 0 33 6
A getUniqueId() 0 21 4
A setNull() 0 5 1
A render() 0 33 5
1
<?php namespace Todaymade\Daux\Format\HTML\ContentTypes\Markdown\TOC;
2
3
use DeepCopy\DeepCopy;
4
use League\CommonMark\Block\Element\Document;
5
use League\CommonMark\Block\Element\Heading;
6
use League\CommonMark\Block\Element\ListBlock;
7
use League\CommonMark\Block\Element\ListData;
8
use League\CommonMark\Block\Element\ListItem;
9
use League\CommonMark\Block\Element\Paragraph;
10
use League\CommonMark\Event\DocumentParsedEvent;
11
use League\CommonMark\Inline\Element\Link;
12
use League\CommonMark\Inline\Element\Text;
13
use League\CommonMark\Node\Node;
14
use ReflectionMethod;
15
use Todaymade\Daux\Config;
16
use Todaymade\Daux\ContentTypes\Markdown\TableOfContents;
17
use Todaymade\Daux\DauxHelper;
18
19
class Processor
20
{
21
    /**
22
     * @var Config
23
     */
24
    protected $config;
25
26 5
    public function __construct(Config $config)
27
    {
28 5
        $this->config = $config;
29 5
    }
30
31
    public function onDocumentParsed(DocumentParsedEvent $event)
32
    {
33
        $document = $event->getDocument();
34
        /** @var TableOfContents[] $tocs */
35
        $tocs = [];
36 5
37
        $headings = [];
38 5
39
        $document->heading_ids = [];
0 ignored issues
show
Bug introduced by Stéphane Goetz
The property heading_ids does not seem to exist on League\CommonMark\Block\Element\Document.
Loading history...
40 5
        $walker = $document->walker();
41
        while ($event = $walker->next()) {
42 5
            $node = $event->getNode();
43
44 5
            if ($node instanceof TableOfContents && !$event->isEntering()) {
45 5
                $tocs[] = $node;
46 5
47 5
                continue;
48
            }
49 5
50 4
            if (!($node instanceof Heading) || !$event->isEntering()) {
51 4
                continue;
52
            }
53
54 5
            $this->ensureHeadingHasId($document, $node);
55 5
            $headings[] = new Entry($node);
56
        }
57
58 5
        if (count($headings) && (count($tocs) || $this->config->getHTML()->hasAutomaticTableOfContents())) {
59 5
            $generated = $this->generate($headings);
60
61
            if (count($tocs)) {
62 5
                foreach ($tocs as $toc) {
63 4
                    $toc->appendChild($this->render($generated->getChildren()));
64
                }
65 4
            } else {
66 4
                $document->prependChild($this->render($generated->getChildren()));
67 4
            }
68
        }
69
    }
70
71
    protected function getUniqueId(Document $document, $proposed)
72
    {
73 5
        if ($proposed == 'page_') {
74
            $proposed = 'page_section_' . (count($document->heading_ids) + 1);
0 ignored issues
show
Bug introduced by Stéphane Goetz
The property heading_ids does not seem to exist on League\CommonMark\Block\Element\Document.
Loading history...
75 5
        }
76 5
77
        // Quick path, it's a unique ID
78
        if (!in_array($proposed, $document->heading_ids)) {
79
            $document->heading_ids[] = $proposed;
80
81 5
            return $proposed;
82 5
        }
83 5
84
        $extension = 1; // Initialize the variable at one, so on the first iteration we have 2
85
        do {
86 1
            ++$extension;
87
        } while (in_array("$proposed-$extension", $document->heading_ids));
88 1
89 1
        $document->heading_ids[] = "$proposed-$extension";
90
91 1
        return "$proposed-$extension";
92
    }
93
94
    protected function ensureHeadingHasId(Document $document, Heading $node)
95
    {
96
        // If the node has an ID, no need to generate it, just check it's unique
97 5
        $attributes = $node->getData('attributes', []);
98
        if (array_key_exists('id', $attributes) && !empty($attributes['id'])) {
99
            $node->data['attributes']['id'] = $this->getUniqueId($document, $attributes['id']);
100 5
101 5
            return;
102
        }
103
104
        // Well, seems we have to generate an ID
105
        $walker = $node->walker();
106
        $inside = [];
107
        while ($event = $walker->next()) {
108 5
            $insideNode = $event->getNode();
109 5
110 5
            if ($insideNode instanceof Heading) {
111 5
                continue;
112
            }
113 5
114 5
            $inside[] = $insideNode;
115
        }
116
117 5
        $text = '';
118
        foreach ($inside as $other) {
119
            if ($other instanceof Text) {
120 5
                $text .= ' ' . $other->getContent();
121 5
            }
122 5
        }
123 5
124
        $node->data['attributes']['id'] = $this->getUniqueId($document, 'page_' . DauxHelper::slug($text));
125
    }
126
127 5
    /**
128 5
     * Make a tree of the list of headings.
129
     *
130
     * @param Entry[] $headings
131
     *
132
     * @return RootEntry
133
     */
134
    public function generate($headings)
135
    {
136 4
        /** @var Entry $previous */
137
        $root = $previous = new RootEntry();
138
        foreach ($headings as $heading) {
139 4
            if ($heading->getLevel() < $previous->getLevel()) {
140 4
                $parent = $previous;
141 4
                do {
142
                    $parent = $parent->getParent();
143
                } while ($heading->getLevel() <= $parent->getLevel() && $parent->getLevel() != 0);
144
145
                $parent->addChild($heading);
146
                $previous = $heading;
147
148
                continue;
149
            }
150
151
            if ($heading->getLevel() > $previous->getLevel()) {
152
                $previous->addChild($heading);
153 4
                $previous = $heading;
154 4
155 4
                continue;
156 4
            }
157
158
            //if ($heading->getLevel() == $previous->getLevel()) {
159
            $previous->getParent()->addChild($heading);
160 2
            $previous = $heading;
161 2
162 2
            continue;
163
            //}
164
        }
165
166 4
        return $root;
167
    }
168
169
    /**
170
     * @param Entry[] $entries
171
     *
172
     * @return ListBlock
173 4
     */
174
    protected function render(array $entries)
175 4
    {
176 4
        $data = new ListData();
177
        $data->type = ListBlock::TYPE_UNORDERED;
0 ignored issues
show
Deprecated Code introduced by Stéphane Goetz
The constant League\CommonMark\Block\...stBlock::TYPE_UNORDERED has been deprecated: This constant is deprecated in league/commonmark 1.4 and will be removed in 2.0; use TYPE_BULLET instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

177
        $data->type = /** @scrutinizer ignore-deprecated */ ListBlock::TYPE_UNORDERED;

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
178 4
179 4
        $list = new ListBlock($data);
180
        $list->data['attributes']['class'] = 'TableOfContents';
181 4
182 4
        foreach ($entries as $entry) {
183
            $item = new ListItem($data);
184 4
185
            $a = new Link('#' . $entry->getId());
186 4
187 4
            $content = $entry->getContent();
188 4
            if ($content != null) {
189 4
                foreach ($this->cloneChildren($content) as $node) {
190
                    $a->appendChild($node);
191
                }
192
            }
193 4
194 4
            $p = new Paragraph();
195
            $p->appendChild($a);
196 4
197
            $item->appendChild($p);
198 4
199
            if (!empty($entry->getChildren())) {
200
                $item->appendChild($this->render($entry->getChildren()));
201
            }
202 4
203
            $list->appendChild($item);
204
        }
205 4
206
        return $list;
207
    }
208
209
    /**
210
     * Set the specified property to null on the object.
211
     *
212
     * @param Heading $object The object to modify
213
     * @param string $property The property to nullify
214 4
     */
215
    protected function setNull(Heading $object, $property)
216 4
    {
217 4
        $prop = new \ReflectionProperty(get_class($object), $property);
218 4
        $prop->setAccessible(true);
219 4
        $prop->setValue($object, null);
220
    }
221
222
    /**
223
     * @return Node[]
224
     */
225 4
    protected function cloneChildren(Heading $node)
226
    {
227 4
        $firstClone = clone $node;
228
229
        // We have no choice but to hack into the
230
        // system to reset the parent, previous and next
231 4
        $this->setNull($firstClone, 'parent');
232 4
        $this->setNull($firstClone, 'previous');
233 4
        $this->setNull($firstClone, 'next');
234
235
        // Also, the child elements need to know the next parents
236 4
        foreach ($firstClone->children() as $subnode) {
237 4
            $method = new ReflectionMethod(get_class($subnode), 'setParent');
238 4
            $method->setAccessible(true);
239 4
            $method->invoke($subnode, $firstClone);
240
        }
241
242 4
        return (new DeepCopy())->copy($firstClone)->children();
243
    }
244
}
245