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

BlockElementParser   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 231
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 120
dl 0
loc 231
rs 7.44
c 0
b 0
f 0
wmc 52

11 Methods

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

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 (\substr($this->trimmedLine, 0, 1) === '#') {
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
            $this->markup .= $this->codeStart($codeMatch);
109
110
            if (!$codeBlock) {
111
                $this->markup .= $this->escape(\substr($this->line, $this->indentLen));
112
            }
113
114
            $this->codeInternal($codeBlock);
115
116
            $this->pointer++;
117
118
            $this->markup .= '</code></pre>';
119
120
            return \true;
121
        }
122
    }
123
124
    private function codeInternal($codeBlock)
125
    {
126
        while (isset($this->lines[$this->pointer + 1])) {
127
            $this->line = $this->escape($this->lines[$this->pointer + 1]);
128
129
            if (($codeBlock && \substr(\ltrim($this->line), 0, 3) !== '```')
130
                || \strpos($this->line, $this->indentStr) === 0
131
            ) {
132
                $this->markup .= "\n"; // @todo: donot use \n for first line
133
                $this->markup .= $codeBlock ? $this->line : \substr($this->line, $this->indentLen);
134
135
                $this->pointer++;
136
137
                continue;
138
            }
139
140
            break;
141
        }
142
    }
143
144
    protected function rule()
145
    {
146
        if ($this->trimmedPrevLine === ''
147
            && \preg_match(static::RE_MD_RULE, $this->trimmedLine)
148
        ) {
149
            $this->markup .= "\n<hr />";
150
151
            return \true;
152
        }
153
    }
154
155
    protected function listt()
156
    {
157
        $isUl = \in_array(\substr($this->trimmedLine, 0, 2), ['- ', '* ', '+ ']);
158
159
        if ($isUl || \preg_match(static::RE_MD_OL, $this->trimmedLine)) {
160
            $wrapper = $isUl ? 'ul' : 'ol';
161
162
            if (!$this->inList) {
163
                $this->stackList[] = "</$wrapper>";
164
165
                $this->markup .= "\n<$wrapper>\n";
166
                $this->inList  = \true;
167
168
                $this->listLevel++;
169
            }
170
171
            $this->markup .= '<li>' . \ltrim($this->trimmedLine, '+-*0123456789. ');
172
173
            $this->listInternal();
174
175
            return \true;
176
        }
177
    }
178
179
    private function listInternal()
180
    {
181
        $isUl = \in_array(\substr($this->trimmedNextLine, 0, 2), ['- ', '* ', '+ ']);
182
183
        if ($isUl || \preg_match(static::RE_MD_OL, $this->trimmedNextLine)) {
184
            $wrapper = $isUl ? 'ul' : 'ol';
185
            if ($this->nextIndent > $this->indent) {
186
                $this->stackList[] = "</li>\n";
187
                $this->stackList[] = "</$wrapper>";
188
                $this->markup .= "\n<$wrapper>\n";
189
190
                $this->listLevel++;
191
            } else {
192
                $this->markup .= "</li>\n";
193
            }
194
195
            if ($this->nextIndent < $this->indent) {
196
                $shift = \intval(($this->indent - $this->nextIndent) / $this->indentLen);
197
198
                while ($shift--) {
199
                    $this->markup .= \array_pop($this->stackList);
200
201
                    if ($this->listLevel > 2) {
202
                        $this->markup .= \array_pop($this->stackList);
203
                    }
204
                }
205
            }
206
        } else {
207
            $this->markup .= "</li>\n";
208
        }
209
    }
210
211
    protected function table()
212
    {
213
        static $headerCount = 0;
214
215
        if (!$this->inTable) {
216
            $headerCount = \substr_count(\trim($this->trimmedLine, '|'), '|');
217
218
            return $this->tableInternal($headerCount);
219
        }
220
221
        $this->markup .= $this->tableRow($this->trimmedLine, $headerCount);
222
223
        if (empty($this->trimmedNextLine)
224
            || !\substr_count(\trim($this->trimmedNextLine, '|'), '|')
225
        ) {
226
            $headerCount        = 0;
227
            $this->inTable      = \false;
228
            $this->stackTable[] = "</tbody>\n</table>";
229
        }
230
231
        return \true;
232
    }
233
234
    private function tableInternal($headerCount)
235
    {
236
        $columnCount = \preg_match_all(static::RE_MD_TCOL, \trim($this->trimmedNextLine, '|'));
237
238
        if ($headerCount > 0 && $headerCount <= $columnCount) {
239
            $this->pointer++;
240
241
            $this->inTable = \true;
242
            $this->markup .= $this->tableStart($this->trimmedLine);
243
244
            return \true;
245
        }
246
    }
247
}
248