Completed
Pull Request — master (#14)
by Jitendra
01:42
created

HtmlUp::tableInternal()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 3
nop 1
dl 0
loc 18
rs 9.9332
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
/**
15
 * HtmlUp - A **lightweight** && **fast** `markdown` to HTML Parser.
16
 *
17
 * Supports most of the markdown specs except deep nested elements.
18
 * Check readme.md for the details of its features && limitations.
19
 *
20
 * @author    adhocore | Jitendra Adhikari <[email protected]>
21
 * @copyright (c) 2014 Jitendra Adhikari
22
 */
23
class HtmlUp extends BlockElementParser
24
{
25
    /**
26
     * Constructor.
27
     *
28
     * @param string $markdown
29
     * @param int    $indentWidth
30
     */
31
    public function __construct($markdown = \null, $indentWidth = 4)
32
    {
33
        $this->scan($markdown, $indentWidth);
34
    }
35
36
    protected function scan($markdown, $indentWidth = 4)
37
    {
38
        if ('' === \trim($markdown)) {
39
            return;
40
        }
41
42
        $this->indentLen = $indentWidth == 2 ? 2 : 4;
43
        $this->indentStr = $indentWidth == 2 ? '  ' : '    ';
44
45
        // Normalize whitespaces
46
        $markdown = \str_replace("\t", $this->indentStr, $markdown);
47
        $markdown = \str_replace(["\r\n", "\r"], "\n", $markdown);
48
49
        $this->lines = \array_merge([''], \explode("\n", $markdown), ['']);
50
    }
51
52
    public function __toString()
53
    {
54
        return $this->parse();
55
    }
56
57
    /**
58
     * Parse markdown.
59
     *
60
     * @param string $markdown
61
     * @param int    $indentWidth
62
     *
63
     * @return string
64
     */
65
    public function parse($markdown = \null, $indentWidth = 4)
66
    {
67
        if (\null !== $markdown) {
68
            $this->reset(\true);
69
70
            $this->scan($markdown, $indentWidth);
71
        }
72
73
        if (empty($this->lines)) {
74
            return '';
75
        }
76
77
        $this->parseBlockElements();
78
79
        return (new SpanElementParser)->parse($this->markup);
80
    }
81
82
    protected function parseBlockElements()
83
    {
84
        while (isset($this->lines[++$this->pointer])) {
85
            $this->init();
86
87
            if ($this->flush() || $this->raw()) {
88
                continue;
89
            }
90
91
            $this->quote();
92
93
            if (($block = $this->isBlock()) || $this->inList) {
94
                $this->markup .= $block ? '' : $this->trimmedLine;
95
96
                continue;
97
            }
98
99
            $this->table() || $this->paragraph();
100
        }
101
    }
102
103
    protected function isBlock()
104
    {
105
        return $this->atx() || $this->setext() || $this->code() || $this->rule() || $this->listt();
106
    }
107
108
    protected function init()
109
    {
110
        list($this->prevLine, $this->trimmedPrevLine) = [$this->line, $this->trimmedLine];
111
112
        $this->line        = $this->lines[$this->pointer];
113
        $this->trimmedLine = \trim($this->line);
114
115
        $this->indent   = \strlen($this->line) - \strlen(\ltrim($this->line));
116
        $this->nextLine = isset($this->lines[$this->pointer + 1])
117
            ? $this->lines[$this->pointer + 1]
118
            : '';
119
        $this->trimmedNextLine = \trim($this->nextLine);
120
        $this->nextIndent      = \strlen($this->nextLine) - \strlen(\ltrim($this->nextLine));
121
    }
122
123
    protected function reset($all = \false)
124
    {
125
        $except = $all ? [] : \array_flip(['lines', 'pointer', 'markup', 'indentStr', 'indentLen']);
126
127
        // Reset all current values.
128
        foreach (\get_class_vars(__CLASS__) as $prop => $value) {
129
            isset($except[$prop]) || $this->{$prop} = $value;
130
        }
131
    }
132
133
    protected function flush()
134
    {
135
        if ('' !== $this->trimmedLine) {
136
            return \false;
137
        }
138
139
        while (!empty($this->stackList)) {
140
            $this->markup .= \array_pop($this->stackList);
141
        }
142
143
        while (!empty($this->stackBlock)) {
144
            $this->markup .= \array_pop($this->stackBlock);
145
        }
146
147
        while (!empty($this->stackTable)) {
148
            $this->markup .= \array_pop($this->stackTable);
149
        }
150
151
        $this->markup .= "\n";
152
153
        $this->reset(\false);
154
155
        return \true;
156
    }
157
158
    protected function raw()
159
    {
160
        if ($this->inHtml || \preg_match(static::RE_RAW, $this->trimmedLine)) {
161
            $this->markup .= "\n$this->line";
162
            if (!$this->inHtml && empty($this->lines[$this->pointer - 1])) {
163
                $this->inHtml = \true;
164
            }
165
166
            return \true;
167
        }
168
    }
169
170
    protected function quote()
171
    {
172
        if (\preg_match(static::RE_MD_QUOTE, $this->line, $quoteMatch)) {
173
            $this->line        = \substr($this->line, \strlen($quoteMatch[0]));
174
            $this->trimmedLine = \trim($this->line);
175
176
            if (!$this->inQuote || $this->quoteLevel < \strlen($quoteMatch[1])) {
177
                $this->markup .= "\n<blockquote>";
178
179
                $this->stackBlock[] = "\n</blockquote>";
180
181
                $this->quoteLevel++;
182
            }
183
184
            return $this->inQuote = \true;
185
        }
186
    }
187
188
    protected function atx()
189
    {
190
        if (isset($this->trimmedLine[0]) && $this->trimmedLine[0] === '#') {
191
            $level = \strlen($this->trimmedLine) - \strlen(\ltrim($this->trimmedLine, '#'));
192
193
            if ($level < 7) {
194
                $this->markup .= "\n<h{$level}>" . \ltrim(\ltrim($this->trimmedLine, '# ')) . "</h{$level}>";
195
196
                return \true;
197
            }
198
        }
199
    }
200
201
    protected function setext()
202
    {
203
        if (\preg_match(static::RE_MD_SETEXT, $this->nextLine)) {
204
            $level = \trim($this->nextLine, '- ') === '' ? 2 : 1;
205
206
            $this->markup .= "\n<h{$level}>{$this->trimmedLine}</h{$level}>";
207
208
            $this->pointer++;
209
210
            return \true;
211
        }
212
    }
213
214
    protected function code()
215
    {
216
        $isShifted = ($this->indent - $this->nextIndent) >= $this->indentLen;
217
        $codeBlock = \preg_match(static::RE_MD_CODE, $this->line, $codeMatch);
218
219
        if ($codeBlock || (!$this->inList && !$this->inQuote && $isShifted)) {
220
            $lang = isset($codeMatch[1])
221
                ? ' class="language-' . $codeMatch[1] . '"'
222
                : '';
223
224
            $this->markup .= "\n<pre><code{$lang}>";
225
226
            if (!$codeBlock) {
227
                $this->markup .= $this->escape(\substr($this->line, $this->indentLen));
228
            }
229
230
            $this->codeInternal($codeBlock);
231
232
            $this->pointer++;
233
234
            $this->markup .= '</code></pre>';
235
236
            return \true;
237
        }
238
    }
239
240
    protected function rule()
241
    {
242
        if ($this->trimmedPrevLine === ''
243
            && \preg_match(static::RE_MD_RULE, $this->trimmedLine)
244
        ) {
245
            $this->markup .= "\n<hr />";
246
247
            return \true;
248
        }
249
    }
250
251
    protected function listt()
252
    {
253
        $isUl = \in_array(\substr($this->trimmedLine, 0, 2), ['- ', '* ', '+ ']);
254
255
        if ($isUl || \preg_match(static::RE_MD_OL, $this->trimmedLine)) {
256
            $wrapper = $isUl ? 'ul' : 'ol';
257
258
            if (!$this->inList) {
259
                $this->stackList[] = "</$wrapper>";
260
                $this->markup .= "\n<$wrapper>\n";
261
                $this->inList      = \true;
262
263
                $this->listLevel++;
264
            }
265
266
            $this->markup .= '<li>' . \ltrim($this->trimmedLine, '+-*0123456789. ');
267
268
            $this->listInternal();
269
270
            return \true;
271
        }
272
    }
273
274
    protected function table()
275
    {
276
        static $headerCount = 0;
277
278
        if (!$this->inTable) {
279
            $headerCount = \substr_count(\trim($this->trimmedLine, '|'), '|');
280
281
            return $this->tableInternal($headerCount);
282
        }
283
284
        $this->markup .= "<tr>\n";
285
286
        foreach (\explode('|', \trim($this->trimmedLine, '|')) as $i => $col) {
287
            if ($i > $headerCount) {
288
                break;
289
            }
290
291
            $col           = \trim($col);
292
            $this->markup .= "<td>{$col}</td>\n";
293
        }
294
295
        $this->markup .= "</tr>\n";
296
297
        if (empty($this->trimmedNextLine)
298
            || !\substr_count(\trim($this->trimmedNextLine, '|'), '|')
299
        ) {
300
            $headerCount        = 0;
301
            $this->inTable      = \false;
302
            $this->stackTable[] = "</tbody>\n</table>";
303
        }
304
305
        return \true;
306
    }
307
308
    protected function paragraph()
309
    {
310
        $this->markup .= $this->inPara ? "\n<br />" : "\n<p>";
311
        $this->markup .= $this->trimmedLine;
312
313
        if (empty($this->trimmedNextLine)) {
314
            $this->markup .= '</p>';
315
            $this->inPara = \false;
316
        } else {
317
            $this->inPara = \true;
318
        }
319
    }
320
}
321