DocParser   B
last analyzed

Coupling/Cohesion

Components 1
Dependencies 14

Complexity

Total Complexity 38

Size/Duplication

Total Lines 227
Duplicated Lines 0 %

Test Coverage

Coverage 99.07%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 38
c 3
b 0
f 0
lcom 1
cbo 14
dl 0
loc 227
ccs 107
cts 108
cp 0.9907
rs 8.3999

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A getEnvironment() 0 4 1
A preProcessInput() 0 13 2
A parse() 0 20 3
B incorporateLine() 0 38 4
A processDocument() 0 6 2
A processInlines() 0 13 4
A resetContainer() 0 19 4
B parseBlocks() 0 17 8
A isLazyParagraphContinuation() 0 7 4
B setAndPropagateLastLineBlank() 0 18 5
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;
16
17
use League\CommonMark\Block\Element\AbstractBlock;
18
use League\CommonMark\Block\Element\Document;
19
use League\CommonMark\Block\Element\InlineContainerInterface;
20
use League\CommonMark\Block\Element\Paragraph;
21
use League\CommonMark\Node\NodeWalker;
22
23
class DocParser
24
{
25
    /**
26
     * @var Environment
27
     */
28
    protected $environment;
29
30
    /**
31
     * @var InlineParserEngine
32
     */
33
    private $inlineParserEngine;
34
35
    /**
36
     * @var int|float
37
     */
38
    private $maxNestingLevel;
39
40
    /**
41
     * @param Environment $environment
42
     */
43 1950
    public function __construct(Environment $environment)
44 2
    {
45 1950
        $this->environment = $environment;
46 1950
        $this->inlineParserEngine = new InlineParserEngine($environment);
47 1950
        $this->maxNestingLevel = $environment->getConfig('max_nesting_level', INF);
48 1950
    }
49
50
    /**
51
     * @return Environment
52
     */
53 1950
    public function getEnvironment()
54
    {
55 1950
        return $this->environment;
56
    }
57
58
    /**
59
     * @param string $input
60
     *
61
     * @return string[]
62
     */
63 1941
    private function preProcessInput($input)
64 1
    {
65 1941
        $lines = preg_split('/\r\n|\n|\r/', $input);
66
67
        // Remove any newline which appears at the very end of the string.
68
        // We've already split the document by newlines, so we can simply drop
69
        // any empty element which appears on the end.
70 1941
        if (end($lines) === '') {
71 1926
            array_pop($lines);
72 642
        }
73
74 1941
        return $lines;
75 1
    }
76
77
    /**
78
     * @param string $input
79
     *
80
     * @return Document
81
     */
82 1941
    public function parse($input)
83
    {
84 1941
        $context = new Context(new Document(), $this->getEnvironment());
85
86 1941
        $lines = $this->preProcessInput($input);
87 1941
        foreach ($lines as $line) {
88 1938
            $context->setNextLine($line);
89 1938
            $this->incorporateLine($context);
90 647
        }
91
92 1941
        while ($tip = $context->getTip()) {
93 1941
            $tip->finalize($context, count($lines));
94 647
        }
95
96 1941
        $this->processInlines($context, $context->getDocument()->walker());
97
98 1941
        $this->processDocument($context);
99
100 1941
        return $context->getDocument();
101
    }
102
103 1938
    private function incorporateLine(ContextInterface $context)
104
    {
105 1938
        $cursor = new Cursor($context->getLine());
106 1938
        $context->getBlockCloser()->resetTip();
107
108 1938
        $context->setBlocksParsed(false);
109
110 1938
        $this->resetContainer($context, $cursor);
111 1938
        $context->getBlockCloser()->setLastMatchedContainer($context->getContainer());
112
113 1938
        $this->parseBlocks($context, $cursor);
114
115
        // What remains at the offset is a text line.  Add the text to the appropriate container.
116
        // First check for a lazy paragraph continuation:
117 1938
        if ($this->isLazyParagraphContinuation($context, $cursor)) {
118
            // lazy paragraph continuation
119 33
            $context->getTip()->addLine($cursor->getRemainder());
120
121 33
            return;
122
        }
123
124
        // not a lazy continuation
125
        // finalize any blocks not matched
126 1938
        $context->getBlockCloser()->closeUnmatchedBlocks();
127
128
        // Determine whether the last line is blank, updating parents as needed
129 1938
        $this->setAndPropagateLastLineBlank($context, $cursor);
130
131
        // Handle any remaining cursor contents
132 1938
        if ($context->getContainer()->acceptsLines()) {
133 726
            $context->getContainer()->handleRemainingContents($context, $cursor);
134 1774
        } elseif (!$cursor->isBlank()) {
135
            // Create paragraph container for line
136 1614
            $context->addBlock(new Paragraph());
137 1614
            $cursor->advanceToNextNonSpaceOrTab();
138 1614
            $context->getTip()->addLine($cursor->getRemainder());
139 538
        }
140 1938
    }
141
142 1941
    private function processDocument(ContextInterface $context)
143
    {
144 1941
        foreach ($this->getEnvironment()->getDocumentProcessors() as $documentProcessor) {
145
            $documentProcessor->processDocument($context->getDocument());
146 647
        }
147 1941
    }
148
149 1941
    private function processInlines(ContextInterface $context, NodeWalker $walker)
150
    {
151 1941
        while ($event = $walker->next()) {
152 1941
            if (!$event->isEntering()) {
153 1941
                continue;
154
            }
155
156 1941
            $node = $event->getNode();
157 1941
            if ($node instanceof InlineContainerInterface) {
158 1662
                $this->inlineParserEngine->parse($node, $context->getDocument()->getReferenceMap());
159 554
            }
160 647
        }
161 1941
    }
162
163
    /**
164
     * Sets the container to the last open child (or its parent)
165
     *
166
     * @param ContextInterface $context
167
     * @param Cursor           $cursor
168
     */
169 1938
    private function resetContainer(ContextInterface $context, Cursor $cursor)
170
    {
171 1938
        $container = $context->getDocument();
172
173 1938
        while ($container->hasChildren()) {
174 1080
            $lastChild = $container->lastChild();
175 1080
            if (!$lastChild->isOpen()) {
176 450
                break;
177
            }
178
179 1071
            $container = $lastChild;
180 1071
            if (!$container->matchesNextLine($cursor)) {
181 693
                $container = $container->parent(); // back up to the last matching block
182 693
                break;
183
            }
184 250
        }
185
186 1938
        $context->setContainer($container);
187 1938
    }
188
189
    /**
190
     * Parse blocks
191
     *
192
     * @param ContextInterface $context
193
     * @param Cursor           $cursor
194
     */
195 1938
    private function parseBlocks(ContextInterface $context, Cursor $cursor)
196
    {
197 1938
        while (!$context->getContainer()->isCode() && !$context->getBlocksParsed()) {
198 1938
            $parsed = false;
199 1938
            foreach ($this->environment->getBlockParsers() as $parser) {
200 1938
                if ($parser->parse($context, $cursor)) {
201 804
                    $parsed = true;
202 1560
                    break;
203
                }
204 646
            }
205
206 1938
            if (!$parsed || $context->getContainer()->acceptsLines() || $context->getTip()->getDepth() >= $this->maxNestingLevel) {
207 1911
                $context->setBlocksParsed(true);
208 1911
                break;
209
            }
210 132
        }
211 1938
    }
212
213
    /**
214
     * @param ContextInterface $context
215
     * @param Cursor           $cursor
216
     *
217
     * @return bool
218
     */
219 1938
    private function isLazyParagraphContinuation(ContextInterface $context, Cursor $cursor)
220
    {
221 1938
        return !$context->getBlockCloser()->areAllClosed() &&
222 1938
            !$cursor->isBlank() &&
223 1938
            $context->getTip() instanceof Paragraph &&
224 1938
            count($context->getTip()->getStrings()) > 0;
225
    }
226
227
    /**
228
     * @param ContextInterface $context
229
     * @param Cursor           $cursor
230
     */
231 1938
    private function setAndPropagateLastLineBlank(ContextInterface $context, Cursor $cursor)
232
    {
233 1938
        $container = $context->getContainer();
234
235 1938
        if ($cursor->isBlank() && $lastChild = $container->lastChild()) {
236 462
            if ($lastChild instanceof AbstractBlock) {
237 462
                $lastChild->setLastLineBlank(true);
238 154
            }
239 154
        }
240
241 1938
        $lastLineBlank = $container->shouldLastLineBeBlank($cursor, $context->getLineNumber());
242
243
        // Propagate lastLineBlank up through parents:
244 1938
        while ($container) {
245 1938
            $container->setLastLineBlank($lastLineBlank);
246 1938
            $container = $container->parent();
247 646
        }
248 1938
    }
249
}
250