Completed
Push — master ( 0b0b24...54ff37 )
by Colin
02:37
created

DocParser::resetContainer()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5.0113

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 12
cts 13
cp 0.9231
rs 9.2568
c 0
b 0
f 0
cc 5
nc 5
nop 2
crap 5.0113
1
<?php
2
3
/*
4
 * This file is part of the league/commonmark package.
5
 *
6
 * (c) Colin O'Dell <[email protected]>
7
 *
8
 * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
9
 *  - (c) John MacFarlane
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace League\CommonMark\Parser;
16
17
use League\CommonMark\Environment\EnvironmentInterface;
18
use League\CommonMark\Event\DocumentParsedEvent;
19
use League\CommonMark\Event\DocumentPreParsedEvent;
20
use League\CommonMark\Input\MarkdownInput;
21
use League\CommonMark\Node\Block\AbstractBlock;
22
use League\CommonMark\Node\Block\AbstractStringContainerBlock;
23
use League\CommonMark\Node\Block\Document;
24
use League\CommonMark\Node\Block\Paragraph;
25
use League\CommonMark\Node\Block\StringContainerInterface;
26
27
final class DocParser implements DocParserInterface
28
{
29
    /**
30
     * @var EnvironmentInterface
31
     */
32
    private $environment;
33
34
    /**
35
     * @var InlineParserEngine
36
     */
37
    private $inlineParserEngine;
38
39
    /**
40
     * @var int|float
41
     */
42
    private $maxNestingLevel;
43
44
    /**
45
     * @param EnvironmentInterface $environment
46
     */
47 2505
    public function __construct(EnvironmentInterface $environment)
48
    {
49 2505
        $this->environment = $environment;
50 2505
        $this->inlineParserEngine = new InlineParserEngine($environment);
51 2505
        $this->maxNestingLevel = $environment->getConfig('max_nesting_level', \INF);
52 2505
    }
53
54
    /**
55
     * @param string $input
56
     *
57
     * @throws \RuntimeException
58
     *
59
     * @return Document
60
     */
61 2496
    public function parse(string $input): Document
62
    {
63 2496
        $document = new Document();
64
65 2496
        $preParsedEvent = new DocumentPreParsedEvent($document, new MarkdownInput($input));
66 2490
        $this->environment->dispatch($preParsedEvent);
67 2490
        $markdown = $preParsedEvent->getMarkdown();
68
69 2490
        $context = new Context($document, $this->environment);
70
71 2490
        foreach ($markdown->getLines() as $line) {
72 2490
            $context->setNextLine($line);
73 2490
            $this->incorporateLine($context);
74
        }
75
76 2490
        $lineCount = $markdown->getLineCount();
77 2490
        while ($tip = $context->getTip()) {
78 2490
            $tip->finalize($context, $lineCount);
79
        }
80
81 2490
        $this->processInlines($context);
82
83 2490
        $this->environment->dispatch(new DocumentParsedEvent($document));
84
85 2487
        return $document;
86
    }
87
88 2490
    private function incorporateLine(ContextInterface $context): void
89
    {
90 2490
        $context->getBlockCloser()->resetTip();
91 2490
        $context->setBlocksParsed(false);
92
93 2490
        $cursor = new Cursor($context->getLine());
94
95 2490
        $this->resetContainer($context, $cursor);
96 2490
        $context->getBlockCloser()->setLastMatchedContainer($context->getContainer());
97
98 2490
        $this->parseBlocks($context, $cursor);
99
100
        // What remains at the offset is a text line.  Add the text to the appropriate container.
101
        // First check for a lazy paragraph continuation:
102 2490
        if ($this->handleLazyParagraphContinuation($context, $cursor)) {
103 36
            return;
104
        }
105
106
        // not a lazy continuation
107
        // finalize any blocks not matched
108 2490
        $context->getBlockCloser()->closeUnmatchedBlocks();
109
110
        // Determine whether the last line is blank, updating parents as needed
111 2490
        $this->setAndPropagateLastLineBlank($context, $cursor);
112
113
        // Handle any remaining cursor contents
114 2490
        if ($context->getContainer() instanceof StringContainerInterface) {
115 918
            $context->getContainer()->handleRemainingContents($context, $cursor);
116 2214
        } elseif (!$cursor->isBlank()) {
117
            // Create paragraph container for line
118 2130
            $p = new Paragraph();
119 2130
            $context->addBlock($p);
120 2130
            $cursor->advanceToNextNonSpaceOrTab();
121 2130
            $p->addLine($cursor->getRemainder());
122
        }
123 2490
    }
124
125 2490
    private function processInlines(ContextInterface $context): void
126
    {
127 2490
        $walker = $context->getDocument()->walker();
128
129 2490
        while ($event = $walker->next()) {
130 2490
            if (!$event->isEntering()) {
131 2490
                continue;
132
            }
133
134 2490
            $node = $event->getNode();
135 2490
            if ($node instanceof AbstractStringContainerBlock) {
136 2448
                $this->inlineParserEngine->parse($node, $context->getDocument()->getReferenceMap());
137
            }
138
        }
139 2490
    }
140
141
    /**
142
     * Sets the container to the last open child (or its parent)
143
     *
144
     * @param ContextInterface $context
145
     * @param Cursor           $cursor
146
     */
147 2490
    private function resetContainer(ContextInterface $context, Cursor $cursor): void
148
    {
149 2490
        $container = $context->getDocument();
150
151 2490
        while ($lastChild = $container->lastChild()) {
152 1299
            if (!($lastChild instanceof AbstractBlock)) {
153
                break;
154
            }
155
156 1299
            if (!$lastChild->isOpen()) {
157 537
                break;
158
            }
159
160 1290
            $container = $lastChild;
161 1290
            if (!$container->matchesNextLine($cursor)) {
162 852
                $container = $container->parent(); // back up to the last matching block
163 852
                break;
164
            }
165
        }
166
167 2490
        $context->setContainer($container);
168 2490
    }
169
170
    /**
171
     * Parse blocks
172
     *
173
     * @param ContextInterface $context
174
     * @param Cursor           $cursor
175
     */
176 2490
    private function parseBlocks(ContextInterface $context, Cursor $cursor): void
177
    {
178 2490
        while (!$context->getContainer()->isCode() && !$context->getBlocksParsed()) {
179 2490
            $parsed = false;
180 2490
            foreach ($this->environment->getBlockParsers() as $parser) {
181 2490
                if ($parser->parse($context, $cursor)) {
182 972
                    $parsed = true;
183 972
                    break;
184
                }
185
            }
186
187 2490
            if (!$parsed || $context->getContainer() instanceof StringContainerInterface || (($tip = $context->getTip()) && $tip->getDepth() >= $this->maxNestingLevel)) {
188 2463
                $context->setBlocksParsed(true);
189 2463
                break;
190
            }
191
        }
192 2490
    }
193
194 2490
    private function handleLazyParagraphContinuation(ContextInterface $context, Cursor $cursor): bool
195
    {
196 2490
        $tip = $context->getTip();
197
198 2490
        if ($tip instanceof Paragraph &&
199 2490
            !$context->getBlockCloser()->areAllClosed() &&
200 2490
            !$cursor->isBlank() &&
201 2490
            \count($tip->getStrings()) > 0) {
202
203
            // lazy paragraph continuation
204 36
            $tip->addLine($cursor->getRemainder());
205
206 36
            return true;
207
        }
208
209 2490
        return false;
210
    }
211
212 2490
    private function setAndPropagateLastLineBlank(ContextInterface $context, Cursor $cursor): void
213
    {
214 2490
        $container = $context->getContainer();
215
216 2490
        if ($cursor->isBlank() && $lastChild = $container->lastChild()) {
217 603
            if ($lastChild instanceof AbstractBlock) {
218 603
                $lastChild->setLastLineBlank(true);
219
            }
220
        }
221
222 2490
        $lastLineBlank = $container->shouldLastLineBeBlank($cursor, $context->getLineNumber());
223
224
        // Propagate lastLineBlank up through parents:
225 2490
        while ($container instanceof AbstractBlock && $container->endsWithBlankLine() !== $lastLineBlank) {
226 753
            $container->setLastLineBlank($lastLineBlank);
227 753
            $container = $container->parent();
228
        }
229 2490
    }
230
}
231