Completed
Push — master ( 6589e9...f70bf0 )
by Jitendra
9s
created

BlockElementParser::rule()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 2
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
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
84
            if ($level < 7) {
85
                $this->markup .= "\n<h{$level}>" . \ltrim(\ltrim($this->trimmedLine, '# ')) . "</h{$level}>";
86
87
                return \true;
88
            }
89
        }
90
    }
91
92
    protected function setext()
93
    {
94
        if (\preg_match(static::RE_MD_SETEXT, $this->nextLine)) {
95
            $level = \trim($this->nextLine, '- ') === '' ? 2 : 1;
96
97
            $this->markup .= "\n<h{$level}>{$this->trimmedLine}</h{$level}>";
98
99
            $this->pointer++;
100
101
            return \true;
102
        }
103
    }
104
105
    protected function code()
106
    {
107
        $isShifted = ($this->indent - $this->nextIndent) >= $this->indentLen;
108
        $codeBlock = \preg_match(static::RE_MD_CODE, $this->line, $codeMatch);
109
110
        if ($codeBlock || (!$this->inList && !$this->inQuote && $isShifted)) {
111
            $lang = isset($codeMatch[1])
112
                ? ' class="language-' . $codeMatch[1] . '"'
113
                : '';
114
115
            $this->markup .= "\n<pre><code{$lang}>";
116
117
            if (!$codeBlock) {
118
                $this->markup .= $this->escape(\substr($this->line, $this->indentLen));
119
            }
120
121
            $this->codeInternal($codeBlock);
122
123
            $this->pointer++;
124
125
            $this->markup .= '</code></pre>';
126
127
            return \true;
128
        }
129
    }
130
131
    private function codeInternal($codeBlock)
132
    {
133
        while (isset($this->lines[$this->pointer + 1])) {
134
            $this->line = $this->escape($this->lines[$this->pointer + 1]);
135
136
            if (($codeBlock && \substr(\ltrim($this->line), 0, 3) !== '```')
137
                || \strpos($this->line, $this->indentStr) === 0
138
            ) {
139
                $this->markup .= "\n"; // @todo: donot use \n for first line
140
                $this->markup .= $codeBlock ? $this->line : \substr($this->line, $this->indentLen);
141
142
                $this->pointer++;
143
            } else {
144
                break;
145
            }
146
        }
147
    }
148
149
    protected function rule()
150
    {
151
        if ($this->trimmedPrevLine === ''
152
            && \preg_match(static::RE_MD_RULE, $this->trimmedLine)
153
        ) {
154
            $this->markup .= "\n<hr />";
155
156
            return \true;
157
        }
158
    }
159
160
    protected function listt()
161
    {
162
        $isUl = \in_array(\substr($this->trimmedLine, 0, 2), ['- ', '* ', '+ ']);
163
164
        if ($isUl || \preg_match(static::RE_MD_OL, $this->trimmedLine)) {
165
            $wrapper = $isUl ? 'ul' : 'ol';
166
167
            if (!$this->inList) {
168
                $this->stackList[] = "</$wrapper>";
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 .= "<tr>\n";
226
227
        foreach (\explode('|', \trim($this->trimmedLine, '|')) as $i => $col) {
228
            if ($i > $headerCount) {
229
                break;
230
            }
231
232
            $col           = \trim($col);
233
            $this->markup .= "<td>{$col}</td>\n";
234
        }
235
236
        $this->markup .= "</tr>\n";
237
238
        if (empty($this->trimmedNextLine)
239
            || !\substr_count(\trim($this->trimmedNextLine, '|'), '|')
240
        ) {
241
            $headerCount        = 0;
242
            $this->inTable      = \false;
243
            $this->stackTable[] = "</tbody>\n</table>";
244
        }
245
246
        return \true;
247
    }
248
249
    private function tableInternal($headerCount)
250
    {
251
        $columnCount = \preg_match_all(static::RE_MD_TCOL, \trim($this->trimmedNextLine, '|'));
252
253
        if ($headerCount > 0 && $headerCount <= $columnCount) {
254
            $this->pointer++;
255
256
            $this->inTable = \true;
257
            $this->markup .= "<table>\n<thead>\n<tr>\n";
258
            $this->trimmedLine = \trim($this->trimmedLine, '|');
259
260
            foreach (\explode('|', $this->trimmedLine) as $hdr) {
261
                $this->markup .= '<th>' . \trim($hdr) . "</th>\n";
262
            }
263
264
            $this->markup .= "</tr>\n</thead>\n<tbody>\n";
265
266
            return \true;
267
        }
268
    }
269
}
270