Passed
Pull Request — master (#15)
by Jitendra
01:44
created

BlockElementParser   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 235
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 123
dl 0
loc 235
rs 6.4799
c 0
b 0
f 0
wmc 54

11 Methods

Rating   Name   Duplication   Size   Complexity  
A isBlock() 0 3 5
B parseBlockElements() 0 18 8
B listInternal() 0 29 8
A listt() 0 21 5
A codeInternal() 0 17 6
A table() 0 21 4
A atx() 0 9 3
B code() 0 23 7
A rule() 0 8 3
A setext() 0 8 2
A tableInternal() 0 11 3

How to fix   Complexity   

Complex Class

Complex classes like BlockElementParser 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.

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 BlockElementParser, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the HTMLUP package.
5
 *
6
 * (c) Jitendra Adhikari <[email protected]>
7
 *     <https://github.com/adhocore>
8
 *
9
 * Licensed under MIT license.
10
 */
11
12
namespace Ahc;
13
14
abstract class BlockElementParser
15
{
16
    use HtmlHelper;
17
18
    const RE_MD_QUOTE  = '~^\s*(>+)\s+~';
19
    const RE_RAW       = '/^<\/?\w.*?\/?>/';
20
    const RE_MD_SETEXT = '~^\s*(={3,}|-{3,})\s*$~';
21
    const RE_MD_CODE   = '/^```\s*([\w-]+)?/';
22
    const RE_MD_RULE   = '~^(_{3,}|\*{3,}|\-{3,})$~';
23
    const RE_MD_TCOL   = '~(\|\s*\:)?\s*\-{3,}\s*(\:\s*\|)?~';
24
    const RE_MD_OL     = '/^\d+\. /';
25
26
    protected $lines       = [];
27
    protected $stackList   = [];
28
    protected $stackBlock  = [];
29
    protected $stackTable  = [];
30
31
    protected $pointer     = -1;
32
    protected $listLevel   = 0;
33
    protected $quoteLevel  = 0;
34
    protected $indent      = 0;
35
    protected $nextIndent  = 0;
36
    protected $indentLen   = 4;
37
38
    protected $indentStr       = '    ';
39
    protected $line            = '';
40
    protected $trimmedLine     = '';
41
    protected $prevLine        = '';
42
    protected $trimmedPrevLine = '';
43
    protected $nextLine        = '';
44
    protected $trimmedNextLine = '';
45
    protected $markup          = '';
46
47
    protected $inList  = \false;
48
    protected $inQuote = \false;
49
    protected $inPara  = \false;
50
    protected $inHtml  = \false;
51
    protected $inTable = \false;
52
53
    protected function parseBlockElements()
54
    {
55
        while (isset($this->lines[++$this->pointer])) {
56
            $this->init();
0 ignored issues
show
Bug introduced by
The method init() does not exist on Ahc\BlockElementParser. Since it exists in all sub-types, consider adding an abstract or default implementation to Ahc\BlockElementParser. ( Ignorable by Annotation )

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

56
            $this->/** @scrutinizer ignore-call */ 
57
                   init();
Loading history...
57
58
            if ($this->flush() || $this->raw()) {
0 ignored issues
show
Bug introduced by
The method flush() does not exist on Ahc\BlockElementParser. Since it exists in all sub-types, consider adding an abstract or default implementation to Ahc\BlockElementParser. ( Ignorable by Annotation )

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

58
            if ($this->/** @scrutinizer ignore-call */ flush() || $this->raw()) {
Loading history...
Bug introduced by
The method raw() does not exist on Ahc\BlockElementParser. Since it exists in all sub-types, consider adding an abstract or default implementation to Ahc\BlockElementParser. ( Ignorable by Annotation )

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

58
            if ($this->flush() || $this->/** @scrutinizer ignore-call */ raw()) {
Loading history...
59
                continue;
60
            }
61
62
            $this->quote();
0 ignored issues
show
Bug introduced by
The method quote() does not exist on Ahc\BlockElementParser. Since it exists in all sub-types, consider adding an abstract or default implementation to Ahc\BlockElementParser. ( Ignorable by Annotation )

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

62
            $this->/** @scrutinizer ignore-call */ 
63
                   quote();
Loading history...
63
64
            if (($block = $this->isBlock()) || $this->inList) {
65
                $this->markup .= $block ? '' : $this->trimmedLine;
66
67
                continue;
68
            }
69
70
            $this->table() || $this->paragraph();
0 ignored issues
show
Bug introduced by
The method paragraph() does not exist on Ahc\BlockElementParser. Since it exists in all sub-types, consider adding an abstract or default implementation to Ahc\BlockElementParser. ( Ignorable by Annotation )

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

70
            $this->table() || $this->/** @scrutinizer ignore-call */ paragraph();
Loading history...
71
        }
72
    }
73
74
    protected function isBlock()
75
    {
76
        return $this->atx() || $this->setext() || $this->code() || $this->rule() || $this->listt();
77
    }
78
79
    protected function atx()
80
    {
81
        if (isset($this->trimmedLine[0]) && $this->trimmedLine[0] === '#') {
82
            $level = \strlen($this->trimmedLine) - \strlen(\ltrim($this->trimmedLine, '#'));
83
            $head  = $this->h($level, $this->trimmedLine);
84
85
            $this->markup .= $head;
86
87
            return (bool) $head;
88
        }
89
    }
90
91
    protected function setext()
92
    {
93
        if (\preg_match(static::RE_MD_SETEXT, $this->nextLine)) {
94
            $this->markup .= $this->h($this->nextLine, $this->trimmedLine);
95
96
            $this->pointer++;
97
98
            return \true;
99
        }
100
    }
101
102
    protected function code()
103
    {
104
        $isShifted = ($this->indent - $this->nextIndent) >= $this->indentLen;
105
        $codeBlock = \preg_match(static::RE_MD_CODE, $this->line, $codeMatch);
106
107
        if ($codeBlock || (!$this->inList && !$this->inQuote && $isShifted)) {
108
            $lang = isset($codeMatch[1])
109
                ? ' class="language-' . $codeMatch[1] . '"'
110
                : '';
111
112
            $this->markup .= "\n<pre><code{$lang}>";
113
114
            if (!$codeBlock) {
115
                $this->markup .= $this->escape(\substr($this->line, $this->indentLen));
116
            }
117
118
            $this->codeInternal($codeBlock);
119
120
            $this->pointer++;
121
122
            $this->markup .= '</code></pre>';
123
124
            return \true;
125
        }
126
    }
127
128
    private function codeInternal($codeBlock)
129
    {
130
        while (isset($this->lines[$this->pointer + 1])) {
131
            $this->line = $this->escape($this->lines[$this->pointer + 1]);
132
133
            if (($codeBlock && \substr(\ltrim($this->line), 0, 3) !== '```')
134
                || \strpos($this->line, $this->indentStr) === 0
135
            ) {
136
                $this->markup .= "\n"; // @todo: donot use \n for first line
137
                $this->markup .= $codeBlock ? $this->line : \substr($this->line, $this->indentLen);
138
139
                $this->pointer++;
140
141
                continue;
142
            }
143
144
            break;
145
        }
146
    }
147
148
    protected function rule()
149
    {
150
        if ($this->trimmedPrevLine === ''
151
            && \preg_match(static::RE_MD_RULE, $this->trimmedLine)
152
        ) {
153
            $this->markup .= "\n<hr />";
154
155
            return \true;
156
        }
157
    }
158
159
    protected function listt()
160
    {
161
        $isUl = \in_array(\substr($this->trimmedLine, 0, 2), ['- ', '* ', '+ ']);
162
163
        if ($isUl || \preg_match(static::RE_MD_OL, $this->trimmedLine)) {
164
            $wrapper = $isUl ? 'ul' : 'ol';
165
166
            if (!$this->inList) {
167
                $this->stackList[] = "</$wrapper>";
168
169
                $this->markup .= "\n<$wrapper>\n";
170
                $this->inList  = \true;
171
172
                $this->listLevel++;
173
            }
174
175
            $this->markup .= '<li>' . \ltrim($this->trimmedLine, '+-*0123456789. ');
176
177
            $this->listInternal();
178
179
            return \true;
180
        }
181
    }
182
183
    private function listInternal()
184
    {
185
        $isUl = \in_array(\substr($this->trimmedNextLine, 0, 2), ['- ', '* ', '+ ']);
186
187
        if ($isUl || \preg_match(static::RE_MD_OL, $this->trimmedNextLine)) {
188
            $wrapper = $isUl ? 'ul' : 'ol';
189
            if ($this->nextIndent > $this->indent) {
190
                $this->stackList[] = "</li>\n";
191
                $this->stackList[] = "</$wrapper>";
192
                $this->markup .= "\n<$wrapper>\n";
193
194
                $this->listLevel++;
195
            } else {
196
                $this->markup .= "</li>\n";
197
            }
198
199
            if ($this->nextIndent < $this->indent) {
200
                $shift = \intval(($this->indent - $this->nextIndent) / $this->indentLen);
201
202
                while ($shift--) {
203
                    $this->markup .= \array_pop($this->stackList);
204
205
                    if ($this->listLevel > 2) {
206
                        $this->markup .= \array_pop($this->stackList);
207
                    }
208
                }
209
            }
210
        } else {
211
            $this->markup .= "</li>\n";
212
        }
213
    }
214
215
    protected function table()
216
    {
217
        static $headerCount = 0;
218
219
        if (!$this->inTable) {
220
            $headerCount = \substr_count(\trim($this->trimmedLine, '|'), '|');
221
222
            return $this->tableInternal($headerCount);
223
        }
224
225
        $this->markup .= $this->tableRow($this->trimmedLine, $headerCount);
226
227
        if (empty($this->trimmedNextLine)
228
            || !\substr_count(\trim($this->trimmedNextLine, '|'), '|')
229
        ) {
230
            $headerCount        = 0;
231
            $this->inTable      = \false;
232
            $this->stackTable[] = "</tbody>\n</table>";
233
        }
234
235
        return \true;
236
    }
237
238
    private function tableInternal($headerCount)
239
    {
240
        $columnCount = \preg_match_all(static::RE_MD_TCOL, \trim($this->trimmedNextLine, '|'));
241
242
        if ($headerCount > 0 && $headerCount <= $columnCount) {
243
            $this->pointer++;
244
245
            $this->inTable = \true;
246
            $this->markup .= $this->tableStart($this->trimmedLine);
247
248
            return \true;
249
        }
250
    }
251
}
252