DocParser   A
last analyzed

Complexity

Total Complexity 40

Size/Duplication

Total Lines 232
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 99.07%

Importance

Changes 0
Metric Value
wmc 40
lcom 1
cbo 13
dl 0
loc 232
ccs 107
cts 108
cp 0.9907
rs 9.2
c 0
b 0
f 0

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 22 4
A incorporateLine() 0 38 4
A processDocument() 0 6 2
A processInlines() 0 13 4
A resetContainer() 0 22 5
B parseBlocks() 0 17 8
A isLazyParagraphContinuation() 0 7 4
A setAndPropagateLastLineBlank() 0 18 5

How to fix   Complexity   

Complex Class

Complex classes like DocParser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DocParser, and based on these observations, apply Extract Interface, too.

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
    }
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 1941
        $context->setEncoding(mb_detect_encoding($input, 'ASCII,UTF-8', true) ?: 'ISO-8859-1');
86
87 1941
        $lines = $this->preProcessInput($input);
88 1941
        foreach ($lines as $line) {
89 1938
            $context->setNextLine($line);
90 1938
            $this->incorporateLine($context);
91 647
        }
92
93 1941
        $lineCount = count($lines);
94 1941
        while ($tip = $context->getTip()) {
95 1941
            $tip->finalize($context, $lineCount);
96 647
        }
97
98 1941
        $this->processInlines($context, $context->getDocument()->walker());
99
100 1941
        $this->processDocument($context);
101
102 1941
        return $context->getDocument();
103
    }
104
105 1938
    private function incorporateLine(ContextInterface $context)
106
    {
107 1938
        $context->getBlockCloser()->resetTip();
108 1938
        $context->setBlocksParsed(false);
109
110 1938
        $cursor = new Cursor($context->getLine(), $context->getEncoding());
111
112 1938
        $this->resetContainer($context, $cursor);
113 1938
        $context->getBlockCloser()->setLastMatchedContainer($context->getContainer());
114
115 1938
        $this->parseBlocks($context, $cursor);
116
117
        // What remains at the offset is a text line.  Add the text to the appropriate container.
118
        // First check for a lazy paragraph continuation:
119 1938
        if ($this->isLazyParagraphContinuation($context, $cursor)) {
120
            // lazy paragraph continuation
121 33
            $context->getTip()->addLine($cursor->getRemainder());
122
123 33
            return;
124
        }
125
126
        // not a lazy continuation
127
        // finalize any blocks not matched
128 1938
        $context->getBlockCloser()->closeUnmatchedBlocks();
129
130
        // Determine whether the last line is blank, updating parents as needed
131 1938
        $this->setAndPropagateLastLineBlank($context, $cursor);
132
133
        // Handle any remaining cursor contents
134 1938
        if ($context->getContainer()->acceptsLines()) {
135 726
            $context->getContainer()->handleRemainingContents($context, $cursor);
136 1774
        } elseif (!$cursor->isBlank()) {
137
            // Create paragraph container for line
138 1614
            $context->addBlock(new Paragraph());
139 1614
            $cursor->advanceToNextNonSpaceOrTab();
140 1614
            $context->getTip()->addLine($cursor->getRemainder());
141 538
        }
142 1938
    }
143
144 1941
    private function processDocument(ContextInterface $context)
145
    {
146 1941
        foreach ($this->getEnvironment()->getDocumentProcessors() as $documentProcessor) {
147
            $documentProcessor->processDocument($context->getDocument());
148 647
        }
149 1941
    }
150
151 1941
    private function processInlines(ContextInterface $context, NodeWalker $walker)
152
    {
153 1941
        while ($event = $walker->next()) {
154 1941
            if (!$event->isEntering()) {
155 1941
                continue;
156
            }
157
158 1941
            $node = $event->getNode();
159 1941
            if ($node instanceof InlineContainerInterface) {
160 1662
                $this->inlineParserEngine->parse($node, $context->getDocument()->getReferenceMap());
161 554
            }
162 647
        }
163 1941
    }
164
165
    /**
166
     * Sets the container to the last open child (or its parent)
167
     *
168
     * @param ContextInterface $context
169
     * @param Cursor           $cursor
170
     */
171 1938
    private function resetContainer(ContextInterface $context, Cursor $cursor)
172
    {
173 1938
        $container = $context->getDocument();
174
175 1938
        while ($lastChild = $container->lastChild()) {
176 1080
            if (!($lastChild instanceof AbstractBlock)) {
177 450
                break;
178
            }
179
180 1071
            if (!$lastChild->isOpen()) {
181 1071
                break;
182 693
            }
183 693
184
            $container = $lastChild;
185 250
            if (!$container->matchesNextLine($cursor)) {
186
                $container = $container->parent(); // back up to the last matching block
187 1938
                break;
188 1938
            }
189
        }
190
191
        $context->setContainer($container);
192
    }
193
194
    /**
195
     * Parse blocks
196 1938
     *
197
     * @param ContextInterface $context
198 1938
     * @param Cursor           $cursor
199 1938
     */
200 1938
    private function parseBlocks(ContextInterface $context, Cursor $cursor)
201 1938
    {
202 804
        while (!$context->getContainer()->isCode() && !$context->getBlocksParsed()) {
203 1560
            $parsed = false;
204
            foreach ($this->environment->getBlockParsers() as $parser) {
205 646
                if ($parser->parse($context, $cursor)) {
206
                    $parsed = true;
207 1938
                    break;
208 1911
                }
209 1911
            }
210
211 132
            if (!$parsed || $context->getContainer()->acceptsLines() || $context->getTip()->getDepth() >= $this->maxNestingLevel) {
212 1938
                $context->setBlocksParsed(true);
213
                break;
214
            }
215
        }
216
    }
217
218
    /**
219
     * @param ContextInterface $context
220 1938
     * @param Cursor           $cursor
221
     *
222 1938
     * @return bool
223 1938
     */
224 1938
    private function isLazyParagraphContinuation(ContextInterface $context, Cursor $cursor)
225 1938
    {
226
        return $context->getTip() instanceof Paragraph &&
227
            !$context->getBlockCloser()->areAllClosed() &&
228
            !$cursor->isBlank() &&
229
            count($context->getTip()->getStrings()) > 0;
230
    }
231
232 1938
    /**
233
     * @param ContextInterface $context
234 1938
     * @param Cursor           $cursor
235
     */
236 1938
    private function setAndPropagateLastLineBlank(ContextInterface $context, Cursor $cursor)
237 462
    {
238 462
        $container = $context->getContainer();
239 154
240 154
        if ($cursor->isBlank() && $lastChild = $container->lastChild()) {
241
            if ($lastChild instanceof AbstractBlock) {
242 1938
                $lastChild->setLastLineBlank(true);
243
            }
244
        }
245 1938
246 1938
        $lastLineBlank = $container->shouldLastLineBeBlank($cursor, $context->getLineNumber());
247 1938
248 646
        // Propagate lastLineBlank up through parents:
249 1938
        while ($container instanceof AbstractBlock) {
250
            $container->setLastLineBlank($lastLineBlank);
251
            $container = $container->parent();
252
        }
253
    }
254
}
255