DocParser   B
last analyzed

Coupling/Cohesion

Components 1
Dependencies 14

Complexity

Total Complexity 37

Size/Duplication

Total Lines 217
Duplicated Lines 0 %

Test Coverage

Coverage 99.05%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 37
lcom 1
cbo 14
dl 0
loc 217
ccs 104
cts 105
cp 0.9905
rs 8.6
c 1
b 0
f 0

11 Methods

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