Browse Source

Parsedown 使用 composer 安装的

huwhois 1 year ago
parent
commit
fff580aacc

+ 47 - 0
application/common.php

@ -0,0 +1,47 @@
1
<?php
2
/**
3
 * 下划线转驼峰
4
 * 思路:
5
 * step1.原字符串转小写,原字符串中的分隔符用空格替换,在字符串开头加上分隔符
6
 * step2.将字符串中每个单词的首字母转换为大写,再去空格,去字符串首部附加的分隔符.
7
 */
8
function camelize($uncamelized_words, $separator='_')
9
{
10
    $uncamelized_words = $separator. str_replace($separator, " ", strtolower($uncamelized_words));
11
    return ltrim(str_replace(" ", "", ucwords($uncamelized_words)), $separator );
12
}
13
14
/**
15
 * 驼峰命名转下划线命名
16
 * 思路:
17
 * 小写和大写紧挨一起的地方,加上分隔符,然后全部转小写
18
 */
19
function uncamelize($camelCaps, $separator='_')
20
{
21
    return strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . $separator . "$2", $camelCaps));
22
}
23
24
/**
25
 * 防sql注入字符串转义
26
 * @param $content 要转义内容
27
 * @return array|string
28
 */
29
function escapeString($content)
30
{
31
    $pattern = "/(select[\s])|(insert[\s])|(update[\s])|(delete[\s])|(from[\s])|(where[\s])|(drop[\s])/i";
32
    if (is_array($content)) {
33
        foreach ($content as $key => $value) {
34
            $content[$key] = addslashes(trim($value));
35
            if (preg_match($pattern, $content[$key])) {
36
                echo $content[$key];
37
                $content[$key] = '';
38
            }
39
        }
40
    } else {
41
        $content = addslashes(trim($content));
42
        if (preg_match($pattern, $content)) {
43
            $content = '';
44
        }
45
    }
46
    return $content;
47
}

+ 225 - 0
application/model/MySqlite.php

@ -0,0 +1,225 @@
1
<?php
2
namespace app;
3
4
class MySqlite
5
{
6
    private $mydb;
7
    protected $tablename;
8
9
    public function __construct()
10
    {
11
        $mydb = new \SQLite3(DB . DS . 'mysqlitedb.db');
12
        if (!$mydb) {
13
            throw new \Exception("$mydb->lastErrorMsg()", 1);
14
        } else {
15
            // echo "Opened database successfully\n";
16
            $this->mydb = $mydb;
17
        }
18
    }
19
20
    /**
21
     * 执行sql
22
     * @param string $sql
23
     * @return mixd $res
24
     */
25
    public function exec($sql)
26
    {
27
        @$res = $this->mydb->exec($sql);
28
        return $res;
29
    }
30
31
    public function query($sql)
32
    {
33
        $result = $this->mydb->query($sql);
34
        return $result;
35
    }
36
    
37
    public function lastInsertRowID ()
38
    {
39
        $result = $this->mydb->lastInsertRowID();
40
        return $result;
41
    }
42
43
    public function lastErrorMsg()
44
    {
45
        return $this->mydb->lastErrorMsg();
46
    }
47
48
    /**
49
     * 查询数组列表
50
     */
51
    public function select($sql)
52
    {
53
        $result = $this->mydb->query($sql);
54
        $data = array();
55
        // var_dump($result);exit;
56
        while ($arr = $result->fetchArray(SQLITE3_ASSOC)) {
57
            $data[] = $arr;
58
        }
59
        return $data;
60
    }
61
62
    /**
63
     * 查询一条
64
     */
65
    public function getOneById($id, $tablename='')
66
    {
67
        $tablename = $tablename ? $tablename : $this->tablename;
68
        $sql = "SELECT * FROM `$tablename` WHERE `id`=$id;";
69
        $result = $this->mydb->query($sql);
70
        // var_dump($result);
71
        $data = $result->fetchArray(SQLITE3_ASSOC);
72
        // var_dump($data);
73
        return $data;
74
    }
75
76
    public function getOne($sql = "")
77
    {
78
        $result = $this->mydb->query($sql);
79
        // var_dump($result);
80
        $data = $result->fetchArray(SQLITE3_ASSOC);
81
        // var_dump($data);
82
        return $data;
83
    }
84
85
    /**
86
     * 单列合计
87
     */
88
    public function sumColumn($column, $tablename)
89
    {
90
        $tablename = $tablename ? $tablename : $this->tablename;
91
        $sql = "SELECT sum(`$column`) as sumData FROM `$tablename`;";
92
        $result = $this->mydb->query($sql);
93
        // $data = $result->fetchArray();
94
        // var_dump($data['sumData']);exit;
95
        if ($data = $result->fetchArray(SQLITE3_ASSOC)) {
96
            return $data['sumData'];
97
        }
98
        return 0;
99
    }
100
101
    /**
102
     * 列表结果集
103
     */
104
    public function dataList($where='', $order= '', $desc = false, $limit = 0)
105
    {
106
        if ($order) {
107
            $where .= " order by $order";
108
            if ($desc) {
109
                $where .= " desc";
110
            } else {
111
                $where .= " asc";
112
            }
113
        }
114
115
        if ($limit) {
116
            $where .= " limit " . $limit;
117
        }
118
119
        $sql = "select * from $this->tablename $where;";
120
        
121
        return $this->select($sql);
122
    }
123
124
    /**
125
     * 分页结果
126
     */
127
    public function pageList($where, $page = 1, $limit = 10)
128
    {
129
        $res = $this->query("select count(*) as total from $this->tablename $where;"); 
130
        $data = $res->fetchArray(SQLITE3_ASSOC);
131
        
132
        $offset = ($page - 1) * $limit;
133
        $sql = "select * from $this->tablename $where limit $offset, $limit;";
134
        $list = $this->select($sql);
135
        $data['list'] = $list;
136
        $data['page'] = $page;
137
        $data['limit'] = $limit;
138
        
139
        return $data;
140
    }
141
    
142
143
    //public function list($where='')
144
    //{
145
    //    $sql = "select * from $this->tablename $where;";
146
    //    return $this->select($sql);
147
    //}
148
149
    public function listByName($name='', $order = '', $desc = false)
150
    {
151
        $where = "";
152
        if ($name) {
153
            $where = " where name like '%$name%'";
154
        }
155
156
        if ($order) {
157
            $where .= " order by $order";
158
            if ($desc) {
159
                $where .= " desc";
160
            } else {
161
                $where .= " asc";
162
            }
163
        }
164
        $sql = "select * from $this->tablename $where;";
165
        $res = $this->select($sql);
166
167
        return $res;
168
    }
169
170
    /**
171
     * save
172
     */
173
    public function save($data)
174
    {
175
        $columns = "";
176
        $values = "";
177
        foreach ($data as $key => $value) {
178
            $columns .=  "`" . $key . "`,";
179
            $values .=  "'" . $value . "',";
180
        }
181
        $columns = rtrim($columns, ',');
182
        $values = rtrim($values, ',');
183
        $sql = "INSERT INTO `$this->tablename`(" . $columns . ") VALUES(". $values . ");";
184
        return $this->exec($sql);
185
    }
186
    
187
    /**
188
     * updateById
189
     */
190
    public function updateById($data)
191
    {
192
        $id = $data['id'];
193
        unset($data['id']);
194
        $columns = "";
195
        foreach ($data as $key => $value) {
196
            $columns .= "`" . $key . "`='" . $value ."',";
197
        }
198
        $columns = rtrim($columns, ',');
199
        $sql = "UPDATE `$this->tablename` SET $columns WHERE `id`=$id";
200
        return $this->exec($sql);
201
    }
202
203
    /**
204
     * deleteByIds
205
     */
206
    public function deleteById($id)
207
    {
208
        $sql = "DELETE FROM `$this->tablename` WHERE `id` IN(";
209
        if (is_array($id)) {
210
            for ($i=0; $i < count($id); $i++) { 
211
                $sql .= $id[$i] . ',';
212
            }
213
            $sql = rtrim($sql, ',');
214
            $sql .= ");";
215
        } else {
216
            $sql = "DELETE FROM `$this->tablename` WHERE `id`=$id;";
217
        }
218
        return $this->exec($sql);
219
    }
220
221
    public function __destruct()
222
    {
223
        $this->mydb->close();
224
    }
225
}

+ 0 - 20
application/parsedown/LICENSE.txt

@ -1,20 +0,0 @@
1
The MIT License (MIT)
2
3
Copyright (c) 2013-2018 Emanuil Rusev, erusev.com
4
5
Permission is hereby granted, free of charge, to any person obtaining a copy of
6
this software and associated documentation files (the "Software"), to deal in
7
the Software without restriction, including without limitation the rights to
8
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
the Software, and to permit persons to whom the Software is furnished to do so,
10
subject to the following conditions:
11
12
The above copyright notice and this permission notice shall be included in all
13
copies or substantial portions of the Software.
14
15
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 0 - 1994
application/parsedown/Parsedown.php

@ -1,1994 +0,0 @@
1
<?php
2
3
#
4
#
5
# Parsedown
6
# http://parsedown.org
7
#
8
# (c) Emanuil Rusev
9
# http://erusev.com
10
#
11
# For the full license information, view the LICENSE file that was distributed
12
# with this source code.
13
#
14
#
15
16
class Parsedown
17
{
18
    # ~
19
20
    const version = '1.8.0-beta-7';
21
22
    # ~
23
24
    function text($text)
25
    {
26
        $Elements = $this->textElements($text);
27
28
        # convert to markup
29
        $markup = $this->elements($Elements);
30
31
        # trim line breaks
32
        $markup = trim($markup, "\n");
33
34
        return $markup;
35
    }
36
37
    protected function textElements($text)
38
    {
39
        # make sure no definitions are set
40
        $this->DefinitionData = array();
41
42
        # standardize line breaks
43
        $text = str_replace(array("\r\n", "\r"), "\n", $text);
44
45
        # remove surrounding line breaks
46
        $text = trim($text, "\n");
47
48
        # split text into lines
49
        $lines = explode("\n", $text);
50
51
        # iterate through lines to identify blocks
52
        return $this->linesElements($lines);
53
    }
54
55
    #
56
    # Setters
57
    #
58
59
    function setBreaksEnabled($breaksEnabled)
60
    {
61
        $this->breaksEnabled = $breaksEnabled;
62
63
        return $this;
64
    }
65
66
    protected $breaksEnabled;
67
68
    function setMarkupEscaped($markupEscaped)
69
    {
70
        $this->markupEscaped = $markupEscaped;
71
72
        return $this;
73
    }
74
75
    protected $markupEscaped;
76
77
    function setUrlsLinked($urlsLinked)
78
    {
79
        $this->urlsLinked = $urlsLinked;
80
81
        return $this;
82
    }
83
84
    protected $urlsLinked = true;
85
86
    function setSafeMode($safeMode)
87
    {
88
        $this->safeMode = (bool) $safeMode;
89
90
        return $this;
91
    }
92
93
    protected $safeMode;
94
95
    function setStrictMode($strictMode)
96
    {
97
        $this->strictMode = (bool) $strictMode;
98
99
        return $this;
100
    }
101
102
    protected $strictMode;
103
104
    protected $safeLinksWhitelist = array(
105
        'http://',
106
        'https://',
107
        'ftp://',
108
        'ftps://',
109
        'mailto:',
110
        'tel:',
111
        'data:image/png;base64,',
112
        'data:image/gif;base64,',
113
        'data:image/jpeg;base64,',
114
        'irc:',
115
        'ircs:',
116
        'git:',
117
        'ssh:',
118
        'news:',
119
        'steam:',
120
    );
121
122
    #
123
    # Lines
124
    #
125
126
    protected $BlockTypes = array(
127
        '#' => array('Header'),
128
        '*' => array('Rule', 'List'),
129
        '+' => array('List'),
130
        '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
131
        '0' => array('List'),
132
        '1' => array('List'),
133
        '2' => array('List'),
134
        '3' => array('List'),
135
        '4' => array('List'),
136
        '5' => array('List'),
137
        '6' => array('List'),
138
        '7' => array('List'),
139
        '8' => array('List'),
140
        '9' => array('List'),
141
        ':' => array('Table'),
142
        '<' => array('Comment', 'Markup'),
143
        '=' => array('SetextHeader'),
144
        '>' => array('Quote'),
145
        '[' => array('Reference'),
146
        '_' => array('Rule'),
147
        '`' => array('FencedCode'),
148
        '|' => array('Table'),
149
        '~' => array('FencedCode'),
150
    );
151
152
    # ~
153
154
    protected $unmarkedBlockTypes = array(
155
        'Code',
156
    );
157
158
    #
159
    # Blocks
160
    #
161
162
    protected function lines(array $lines)
163
    {
164
        return $this->elements($this->linesElements($lines));
165
    }
166
167
    protected function linesElements(array $lines)
168
    {
169
        $Elements = array();
170
        $CurrentBlock = null;
171
172
        foreach ($lines as $line)
173
        {
174
            if (chop($line) === '')
175
            {
176
                if (isset($CurrentBlock))
177
                {
178
                    $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted'])
179
                        ? $CurrentBlock['interrupted'] + 1 : 1
180
                    );
181
                }
182
183
                continue;
184
            }
185
186
            while (($beforeTab = strstr($line, "\t", true)) !== false)
187
            {
188
                $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4;
189
190
                $line = $beforeTab
191
                    . str_repeat(' ', $shortage)
192
                    . substr($line, strlen($beforeTab) + 1)
193
                ;
194
            }
195
196
            $indent = strspn($line, ' ');
197
198
            $text = $indent > 0 ? substr($line, $indent) : $line;
199
200
            # ~
201
202
            $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
203
204
            # ~
205
206
            if (isset($CurrentBlock['continuable']))
207
            {
208
                $methodName = 'block' . $CurrentBlock['type'] . 'Continue';
209
                $Block = $this->$methodName($Line, $CurrentBlock);
210
211
                if (isset($Block))
212
                {
213
                    $CurrentBlock = $Block;
214
215
                    continue;
216
                }
217
                else
218
                {
219
                    if ($this->isBlockCompletable($CurrentBlock['type']))
220
                    {
221
                        $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
222
                        $CurrentBlock = $this->$methodName($CurrentBlock);
223
                    }
224
                }
225
            }
226
227
            # ~
228
229
            $marker = $text[0];
230
231
            # ~
232
233
            $blockTypes = $this->unmarkedBlockTypes;
234
235
            if (isset($this->BlockTypes[$marker]))
236
            {
237
                foreach ($this->BlockTypes[$marker] as $blockType)
238
                {
239
                    $blockTypes []= $blockType;
240
                }
241
            }
242
243
            #
244
            # ~
245
246
            foreach ($blockTypes as $blockType)
247
            {
248
                $Block = $this->{"block$blockType"}($Line, $CurrentBlock);
249
250
                if (isset($Block))
251
                {
252
                    $Block['type'] = $blockType;
253
254
                    if ( ! isset($Block['identified']))
255
                    {
256
                        if (isset($CurrentBlock))
257
                        {
258
                            $Elements[] = $this->extractElement($CurrentBlock);
259
                        }
260
261
                        $Block['identified'] = true;
262
                    }
263
264
                    if ($this->isBlockContinuable($blockType))
265
                    {
266
                        $Block['continuable'] = true;
267
                    }
268
269
                    $CurrentBlock = $Block;
270
271
                    continue 2;
272
                }
273
            }
274
275
            # ~
276
277
            if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph')
278
            {
279
                $Block = $this->paragraphContinue($Line, $CurrentBlock);
280
            }
281
282
            if (isset($Block))
283
            {
284
                $CurrentBlock = $Block;
285
            }
286
            else
287
            {
288
                if (isset($CurrentBlock))
289
                {
290
                    $Elements[] = $this->extractElement($CurrentBlock);
291
                }
292
293
                $CurrentBlock = $this->paragraph($Line);
294
295
                $CurrentBlock['identified'] = true;
296
            }
297
        }
298
299
        # ~
300
301
        if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
302
        {
303
            $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
304
            $CurrentBlock = $this->$methodName($CurrentBlock);
305
        }
306
307
        # ~
308
309
        if (isset($CurrentBlock))
310
        {
311
            $Elements[] = $this->extractElement($CurrentBlock);
312
        }
313
314
        # ~
315
316
        return $Elements;
317
    }
318
319
    protected function extractElement(array $Component)
320
    {
321
        if ( ! isset($Component['element']))
322
        {
323
            if (isset($Component['markup']))
324
            {
325
                $Component['element'] = array('rawHtml' => $Component['markup']);
326
            }
327
            elseif (isset($Component['hidden']))
328
            {
329
                $Component['element'] = array();
330
            }
331
        }
332
333
        return $Component['element'];
334
    }
335
336
    protected function isBlockContinuable($Type)
337
    {
338
        return method_exists($this, 'block' . $Type . 'Continue');
339
    }
340
341
    protected function isBlockCompletable($Type)
342
    {
343
        return method_exists($this, 'block' . $Type . 'Complete');
344
    }
345
346
    #
347
    # Code
348
349
    protected function blockCode($Line, $Block = null)
350
    {
351
        if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted']))
352
        {
353
            return;
354
        }
355
356
        if ($Line['indent'] >= 4)
357
        {
358
            $text = substr($Line['body'], 4);
359
360
            $Block = array(
361
                'element' => array(
362
                    'name' => 'pre',
363
                    'element' => array(
364
                        'name' => 'code',
365
                        'text' => $text,
366
                    ),
367
                ),
368
            );
369
370
            return $Block;
371
        }
372
    }
373
374
    protected function blockCodeContinue($Line, $Block)
375
    {
376
        if ($Line['indent'] >= 4)
377
        {
378
            if (isset($Block['interrupted']))
379
            {
380
                $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
381
382
                unset($Block['interrupted']);
383
            }
384
385
            $Block['element']['element']['text'] .= "\n";
386
387
            $text = substr($Line['body'], 4);
388
389
            $Block['element']['element']['text'] .= $text;
390
391
            return $Block;
392
        }
393
    }
394
395
    protected function blockCodeComplete($Block)
396
    {
397
        return $Block;
398
    }
399
400
    #
401
    # Comment
402
403
    protected function blockComment($Line)
404
    {
405
        if ($this->markupEscaped or $this->safeMode)
406
        {
407
            return;
408
        }
409
410
        if (strpos($Line['text'], '<!--') === 0)
411
        {
412
            $Block = array(
413
                'element' => array(
414
                    'rawHtml' => $Line['body'],
415
                    'autobreak' => true,
416
                ),
417
            );
418
419
            if (strpos($Line['text'], '-->') !== false)
420
            {
421
                $Block['closed'] = true;
422
            }
423
424
            return $Block;
425
        }
426
    }
427
428
    protected function blockCommentContinue($Line, array $Block)
429
    {
430
        if (isset($Block['closed']))
431
        {
432
            return;
433
        }
434
435
        $Block['element']['rawHtml'] .= "\n" . $Line['body'];
436
437
        if (strpos($Line['text'], '-->') !== false)
438
        {
439
            $Block['closed'] = true;
440
        }
441
442
        return $Block;
443
    }
444
445
    #
446
    # Fenced Code
447
448
    protected function blockFencedCode($Line)
449
    {
450
        $marker = $Line['text'][0];
451
452
        $openerLength = strspn($Line['text'], $marker);
453
454
        if ($openerLength < 3)
455
        {
456
            return;
457
        }
458
459
        $infostring = trim(substr($Line['text'], $openerLength), "\t ");
460
461
        if (strpos($infostring, '`') !== false)
462
        {
463
            return;
464
        }
465
466
        $Element = array(
467
            'name' => 'code',
468
            'text' => '',
469
        );
470
471
        if ($infostring !== '')
472
        {
473
            /**
474
             * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
475
             * Every HTML element may have a class attribute specified.
476
             * The attribute, if specified, must have a value that is a set
477
             * of space-separated tokens representing the various classes
478
             * that the element belongs to.
479
             * [...]
480
             * The space characters, for the purposes of this specification,
481
             * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
482
             * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
483
             * U+000D CARRIAGE RETURN (CR).
484
             */
485
            $language = substr($infostring, 0, strcspn($infostring, " \t\n\f\r"));
486
487
            $Element['attributes'] = array('class' => "language-$language");
488
        }
489
490
        $Block = array(
491
            'char' => $marker,
492
            'openerLength' => $openerLength,
493
            'element' => array(
494
                'name' => 'pre',
495
                'element' => $Element,
496
            ),
497
        );
498
499
        return $Block;
500
    }
501
502
    protected function blockFencedCodeContinue($Line, $Block)
503
    {
504
        if (isset($Block['complete']))
505
        {
506
            return;
507
        }
508
509
        if (isset($Block['interrupted']))
510
        {
511
            $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
512
513
            unset($Block['interrupted']);
514
        }
515
516
        if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength']
517
            and chop(substr($Line['text'], $len), ' ') === ''
518
        ) {
519
            $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1);
520
521
            $Block['complete'] = true;
522
523
            return $Block;
524
        }
525
526
        $Block['element']['element']['text'] .= "\n" . $Line['body'];
527
528
        return $Block;
529
    }
530
531
    protected function blockFencedCodeComplete($Block)
532
    {
533
        return $Block;
534
    }
535
536
    #
537
    # Header
538
539
    protected function blockHeader($Line)
540
    {
541
        $level = strspn($Line['text'], '#');
542
543
        if ($level > 6)
544
        {
545
            return;
546
        }
547
548
        $text = trim($Line['text'], '#');
549
550
        if ($this->strictMode and isset($text[0]) and $text[0] !== ' ')
551
        {
552
            return;
553
        }
554
555
        $text = trim($text, ' ');
556
557
        $Block = array(
558
            'element' => array(
559
                'name' => 'h' . $level,
560
                'handler' => array(
561
                    'function' => 'lineElements',
562
                    'argument' => $text,
563
                    'destination' => 'elements',
564
                )
565
            ),
566
        );
567
568
        return $Block;
569
    }
570
571
    #
572
    # List
573
574
    protected function blockList($Line, array $CurrentBlock = null)
575
    {
576
        list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]');
577
578
        if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches))
579
        {
580
            $contentIndent = strlen($matches[2]);
581
582
            if ($contentIndent >= 5)
583
            {
584
                $contentIndent -= 1;
585
                $matches[1] = substr($matches[1], 0, -$contentIndent);
586
                $matches[3] = str_repeat(' ', $contentIndent) . $matches[3];
587
            }
588
            elseif ($contentIndent === 0)
589
            {
590
                $matches[1] .= ' ';
591
            }
592
593
            $markerWithoutWhitespace = strstr($matches[1], ' ', true);
594
595
            $Block = array(
596
                'indent' => $Line['indent'],
597
                'pattern' => $pattern,
598
                'data' => array(
599
                    'type' => $name,
600
                    'marker' => $matches[1],
601
                    'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)),
602
                ),
603
                'element' => array(
604
                    'name' => $name,
605
                    'elements' => array(),
606
                ),
607
            );
608
            $Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/');
609
610
            if ($name === 'ol')
611
            {
612
                $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0';
613
614
                if ($listStart !== '1')
615
                {
616
                    if (
617
                        isset($CurrentBlock)
618
                        and $CurrentBlock['type'] === 'Paragraph'
619
                        and ! isset($CurrentBlock['interrupted'])
620
                    ) {
621
                        return;
622
                    }
623
624
                    $Block['element']['attributes'] = array('start' => $listStart);
625
                }
626
            }
627
628
            $Block['li'] = array(
629
                'name' => 'li',
630
                'handler' => array(
631
                    'function' => 'li',
632
                    'argument' => !empty($matches[3]) ? array($matches[3]) : array(),
633
                    'destination' => 'elements'
634
                )
635
            );
636
637
            $Block['element']['elements'] []= & $Block['li'];
638
639
            return $Block;
640
        }
641
    }
642
643
    protected function blockListContinue($Line, array $Block)
644
    {
645
        if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument']))
646
        {
647
            return null;
648
        }
649
650
        $requiredIndent = ($Block['indent'] + strlen($Block['data']['marker']));
651
652
        if ($Line['indent'] < $requiredIndent
653
            and (
654
                (
655
                    $Block['data']['type'] === 'ol'
656
                    and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
657
                ) or (
658
                    $Block['data']['type'] === 'ul'
659
                    and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
660
                )
661
            )
662
        ) {
663
            if (isset($Block['interrupted']))
664
            {
665
                $Block['li']['handler']['argument'] []= '';
666
667
                $Block['loose'] = true;
668
669
                unset($Block['interrupted']);
670
            }
671
672
            unset($Block['li']);
673
674
            $text = isset($matches[1]) ? $matches[1] : '';
675
676
            $Block['indent'] = $Line['indent'];
677
678
            $Block['li'] = array(
679
                'name' => 'li',
680
                'handler' => array(
681
                    'function' => 'li',
682
                    'argument' => array($text),
683
                    'destination' => 'elements'
684
                )
685
            );
686
687
            $Block['element']['elements'] []= & $Block['li'];
688
689
            return $Block;
690
        }
691
        elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line))
692
        {
693
            return null;
694
        }
695
696
        if ($Line['text'][0] === '[' and $this->blockReference($Line))
697
        {
698
            return $Block;
699
        }
700
701
        if ($Line['indent'] >= $requiredIndent)
702
        {
703
            if (isset($Block['interrupted']))
704
            {
705
                $Block['li']['handler']['argument'] []= '';
706
707
                $Block['loose'] = true;
708
709
                unset($Block['interrupted']);
710
            }
711
712
            $text = substr($Line['body'], $requiredIndent);
713
714
            $Block['li']['handler']['argument'] []= $text;
715
716
            return $Block;
717
        }
718
719
        if ( ! isset($Block['interrupted']))
720
        {
721
            $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']);
722
723
            $Block['li']['handler']['argument'] []= $text;
724
725
            return $Block;
726
        }
727
    }
728
729
    protected function blockListComplete(array $Block)
730
    {
731
        if (isset($Block['loose']))
732
        {
733
            foreach ($Block['element']['elements'] as &$li)
734
            {
735
                if (end($li['handler']['argument']) !== '')
736
                {
737
                    $li['handler']['argument'] []= '';
738
                }
739
            }
740
        }
741
742
        return $Block;
743
    }
744
745
    #
746
    # Quote
747
748
    protected function blockQuote($Line)
749
    {
750
        if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
751
        {
752
            $Block = array(
753
                'element' => array(
754
                    'name' => 'blockquote',
755
                    'handler' => array(
756
                        'function' => 'linesElements',
757
                        'argument' => (array) $matches[1],
758
                        'destination' => 'elements',
759
                    )
760
                ),
761
            );
762
763
            return $Block;
764
        }
765
    }
766
767
    protected function blockQuoteContinue($Line, array $Block)
768
    {
769
        if (isset($Block['interrupted']))
770
        {
771
            return;
772
        }
773
774
        if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
775
        {
776
            $Block['element']['handler']['argument'] []= $matches[1];
777
778
            return $Block;
779
        }
780
781
        if ( ! isset($Block['interrupted']))
782
        {
783
            $Block['element']['handler']['argument'] []= $Line['text'];
784
785
            return $Block;
786
        }
787
    }
788
789
    #
790
    # Rule
791
792
    protected function blockRule($Line)
793
    {
794
        $marker = $Line['text'][0];
795
796
        if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '')
797
        {
798
            $Block = array(
799
                'element' => array(
800
                    'name' => 'hr',
801
                ),
802
            );
803
804
            return $Block;
805
        }
806
    }
807
808
    #
809
    # Setext
810
811
    protected function blockSetextHeader($Line, array $Block = null)
812
    {
813
        if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
814
        {
815
            return;
816
        }
817
818
        if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '')
819
        {
820
            $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
821
822
            return $Block;
823
        }
824
    }
825
826
    #
827
    # Markup
828
829
    protected function blockMarkup($Line)
830
    {
831
        if ($this->markupEscaped or $this->safeMode)
832
        {
833
            return;
834
        }
835
836
        if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches))
837
        {
838
            $element = strtolower($matches[1]);
839
840
            if (in_array($element, $this->textLevelElements))
841
            {
842
                return;
843
            }
844
845
            $Block = array(
846
                'name' => $matches[1],
847
                'element' => array(
848
                    'rawHtml' => $Line['text'],
849
                    'autobreak' => true,
850
                ),
851
            );
852
853
            return $Block;
854
        }
855
    }
856
857
    protected function blockMarkupContinue($Line, array $Block)
858
    {
859
        if (isset($Block['closed']) or isset($Block['interrupted']))
860
        {
861
            return;
862
        }
863
864
        $Block['element']['rawHtml'] .= "\n" . $Line['body'];
865
866
        return $Block;
867
    }
868
869
    #
870
    # Reference
871
872
    protected function blockReference($Line)
873
    {
874
        if (strpos($Line['text'], ']') !== false
875
            and preg_match('/^\[(.+?)\]:[ ]*+<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches)
876
        ) {
877
            $id = strtolower($matches[1]);
878
879
            $Data = array(
880
                'url' => $matches[2],
881
                'title' => isset($matches[3]) ? $matches[3] : null,
882
            );
883
884
            $this->DefinitionData['Reference'][$id] = $Data;
885
886
            $Block = array(
887
                'element' => array(),
888
            );
889
890
            return $Block;
891
        }
892
    }
893
894
    #
895
    # Table
896
897
    protected function blockTable($Line, array $Block = null)
898
    {
899
        if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
900
        {
901
            return;
902
        }
903
904
        if (
905
            strpos($Block['element']['handler']['argument'], '|') === false
906
            and strpos($Line['text'], '|') === false
907
            and strpos($Line['text'], ':') === false
908
            or strpos($Block['element']['handler']['argument'], "\n") !== false
909
        ) {
910
            return;
911
        }
912
913
        if (chop($Line['text'], ' -:|') !== '')
914
        {
915
            return;
916
        }
917
918
        $alignments = array();
919
920
        $divider = $Line['text'];
921
922
        $divider = trim($divider);
923
        $divider = trim($divider, '|');
924
925
        $dividerCells = explode('|', $divider);
926
927
        foreach ($dividerCells as $dividerCell)
928
        {
929
            $dividerCell = trim($dividerCell);
930
931
            if ($dividerCell === '')
932
            {
933
                return;
934
            }
935
936
            $alignment = null;
937
938
            if ($dividerCell[0] === ':')
939
            {
940
                $alignment = 'left';
941
            }
942
943
            if (substr($dividerCell, - 1) === ':')
944
            {
945
                $alignment = $alignment === 'left' ? 'center' : 'right';
946
            }
947
948
            $alignments []= $alignment;
949
        }
950
951
        # ~
952
953
        $HeaderElements = array();
954
955
        $header = $Block['element']['handler']['argument'];
956
957
        $header = trim($header);
958
        $header = trim($header, '|');
959
960
        $headerCells = explode('|', $header);
961
962
        if (count($headerCells) !== count($alignments))
963
        {
964
            return;
965
        }
966
967
        foreach ($headerCells as $index => $headerCell)
968
        {
969
            $headerCell = trim($headerCell);
970
971
            $HeaderElement = array(
972
                'name' => 'th',
973
                'handler' => array(
974
                    'function' => 'lineElements',
975
                    'argument' => $headerCell,
976
                    'destination' => 'elements',
977
                )
978
            );
979
980
            if (isset($alignments[$index]))
981
            {
982
                $alignment = $alignments[$index];
983
984
                $HeaderElement['attributes'] = array(
985
                    'style' => "text-align: $alignment;",
986
                );
987
            }
988
989
            $HeaderElements []= $HeaderElement;
990
        }
991
992
        # ~
993
994
        $Block = array(
995
            'alignments' => $alignments,
996
            'identified' => true,
997
            'element' => array(
998
                'name' => 'table',
999
                'elements' => array(),
1000
            ),
1001
        );
1002
1003
        $Block['element']['elements'] []= array(
1004
            'name' => 'thead',
1005
        );
1006
1007
        $Block['element']['elements'] []= array(
1008
            'name' => 'tbody',
1009
            'elements' => array(),
1010
        );
1011
1012
        $Block['element']['elements'][0]['elements'] []= array(
1013
            'name' => 'tr',
1014
            'elements' => $HeaderElements,
1015
        );
1016
1017
        return $Block;
1018
    }
1019
1020
    protected function blockTableContinue($Line, array $Block)
1021
    {
1022
        if (isset($Block['interrupted']))
1023
        {
1024
            return;
1025
        }
1026
1027
        if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|'))
1028
        {
1029
            $Elements = array();
1030
1031
            $row = $Line['text'];
1032
1033
            $row = trim($row);
1034
            $row = trim($row, '|');
1035
1036
            preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches);
1037
1038
            $cells = array_slice($matches[0], 0, count($Block['alignments']));
1039
1040
            foreach ($cells as $index => $cell)
1041
            {
1042
                $cell = trim($cell);
1043
1044
                $Element = array(
1045
                    'name' => 'td',
1046
                    'handler' => array(
1047
                        'function' => 'lineElements',
1048
                        'argument' => $cell,
1049
                        'destination' => 'elements',
1050
                    )
1051
                );
1052
1053
                if (isset($Block['alignments'][$index]))
1054
                {
1055
                    $Element['attributes'] = array(
1056
                        'style' => 'text-align: ' . $Block['alignments'][$index] . ';',
1057
                    );
1058
                }
1059
1060
                $Elements []= $Element;
1061
            }
1062
1063
            $Element = array(
1064
                'name' => 'tr',
1065
                'elements' => $Elements,
1066
            );
1067
1068
            $Block['element']['elements'][1]['elements'] []= $Element;
1069
1070
            return $Block;
1071
        }
1072
    }
1073
1074
    #
1075
    # ~
1076
    #
1077
1078
    protected function paragraph($Line)
1079
    {
1080
        return array(
1081
            'type' => 'Paragraph',
1082
            'element' => array(
1083
                'name' => 'p',
1084
                'handler' => array(
1085
                    'function' => 'lineElements',
1086
                    'argument' => $Line['text'],
1087
                    'destination' => 'elements',
1088
                ),
1089
            ),
1090
        );
1091
    }
1092
1093
    protected function paragraphContinue($Line, array $Block)
1094
    {
1095
        if (isset($Block['interrupted']))
1096
        {
1097
            return;
1098
        }
1099
1100
        $Block['element']['handler']['argument'] .= "\n".$Line['text'];
1101
1102
        return $Block;
1103
    }
1104
1105
    #
1106
    # Inline Elements
1107
    #
1108
1109
    protected $InlineTypes = array(
1110
        '!' => array('Image'),
1111
        '&' => array('SpecialCharacter'),
1112
        '*' => array('Emphasis'),
1113
        ':' => array('Url'),
1114
        '<' => array('UrlTag', 'EmailTag', 'Markup'),
1115
        '[' => array('Link'),
1116
        '_' => array('Emphasis'),
1117
        '`' => array('Code'),
1118
        '~' => array('Strikethrough'),
1119
        '\\' => array('EscapeSequence'),
1120
    );
1121
1122
    # ~
1123
1124
    protected $inlineMarkerList = '!*_&[:<`~\\';
1125
1126
    #
1127
    # ~
1128
    #
1129
1130
    public function line($text, $nonNestables = array())
1131
    {
1132
        return $this->elements($this->lineElements($text, $nonNestables));
1133
    }
1134
1135
    protected function lineElements($text, $nonNestables = array())
1136
    {
1137
        # standardize line breaks
1138
        $text = str_replace(array("\r\n", "\r"), "\n", $text);
1139
1140
        $Elements = array();
1141
1142
        $nonNestables = (empty($nonNestables)
1143
            ? array()
1144
            : array_combine($nonNestables, $nonNestables)
1145
        );
1146
1147
        # $excerpt is based on the first occurrence of a marker
1148
1149
        while ($excerpt = strpbrk($text, $this->inlineMarkerList))
1150
        {
1151
            $marker = $excerpt[0];
1152
1153
            $markerPosition = strlen($text) - strlen($excerpt);
1154
1155
            $Excerpt = array('text' => $excerpt, 'context' => $text);
1156
1157
            foreach ($this->InlineTypes[$marker] as $inlineType)
1158
            {
1159
                # check to see if the current inline type is nestable in the current context
1160
1161
                if (isset($nonNestables[$inlineType]))
1162
                {
1163
                    continue;
1164
                }
1165
1166
                $Inline = $this->{"inline$inlineType"}($Excerpt);
1167
1168
                if ( ! isset($Inline))
1169
                {
1170
                    continue;
1171
                }
1172
1173
                # makes sure that the inline belongs to "our" marker
1174
1175
                if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
1176
                {
1177
                    continue;
1178
                }
1179
1180
                # sets a default inline position
1181
1182
                if ( ! isset($Inline['position']))
1183
                {
1184
                    $Inline['position'] = $markerPosition;
1185
                }
1186
1187
                # cause the new element to 'inherit' our non nestables
1188
1189
1190
                $Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables'])
1191
                    ? array_merge($Inline['element']['nonNestables'], $nonNestables)
1192
                    : $nonNestables
1193
                ;
1194
1195
                # the text that comes before the inline
1196
                $unmarkedText = substr($text, 0, $Inline['position']);
1197
1198
                # compile the unmarked text
1199
                $InlineText = $this->inlineText($unmarkedText);
1200
                $Elements[] = $InlineText['element'];
1201
1202
                # compile the inline
1203
                $Elements[] = $this->extractElement($Inline);
1204
1205
                # remove the examined text
1206
                $text = substr($text, $Inline['position'] + $Inline['extent']);
1207
1208
                continue 2;
1209
            }
1210
1211
            # the marker does not belong to an inline
1212
1213
            $unmarkedText = substr($text, 0, $markerPosition + 1);
1214
1215
            $InlineText = $this->inlineText($unmarkedText);
1216
            $Elements[] = $InlineText['element'];
1217
1218
            $text = substr($text, $markerPosition + 1);
1219
        }
1220
1221
        $InlineText = $this->inlineText($text);
1222
        $Elements[] = $InlineText['element'];
1223
1224
        foreach ($Elements as &$Element)
1225
        {
1226
            if ( ! isset($Element['autobreak']))
1227
            {
1228
                $Element['autobreak'] = false;
1229
            }
1230
        }
1231
1232
        return $Elements;
1233
    }
1234
1235
    #
1236
    # ~
1237
    #
1238
1239
    protected function inlineText($text)
1240
    {
1241
        $Inline = array(
1242
            'extent' => strlen($text),
1243
            'element' => array(),
1244
        );
1245
1246
        $Inline['element']['elements'] = self::pregReplaceElements(
1247
            $this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/',
1248
            array(
1249
                array('name' => 'br'),
1250
                array('text' => "\n"),
1251
            ),
1252
            $text
1253
        );
1254
1255
        return $Inline;
1256
    }
1257
1258
    protected function inlineCode($Excerpt)
1259
    {
1260
        $marker = $Excerpt['text'][0];
1261
1262
        if (preg_match('/^(['.$marker.']++)[ ]*+(.+?)[ ]*+(?<!['.$marker.'])\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
1263
        {
1264
            $text = $matches[2];
1265
            $text = preg_replace('/[ ]*+\n/', ' ', $text);
1266
1267
            return array(
1268
                'extent' => strlen($matches[0]),
1269
                'element' => array(
1270
                    'name' => 'code',
1271
                    'text' => $text,
1272
                ),
1273
            );
1274
        }
1275
    }
1276
1277
    protected function inlineEmailTag($Excerpt)
1278
    {
1279
        $hostnameLabel = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?';
1280
1281
        $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@'
1282
            . $hostnameLabel . '(?:\.' . $hostnameLabel . ')*';
1283
1284
        if (strpos($Excerpt['text'], '>') !== false
1285
            and preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches)
1286
        ){
1287
            $url = $matches[1];
1288
1289
            if ( ! isset($matches[2]))
1290
            {
1291
                $url = "mailto:$url";
1292
            }
1293
1294
            return array(
1295
                'extent' => strlen($matches[0]),
1296
                'element' => array(
1297
                    'name' => 'a',
1298
                    'text' => $matches[1],
1299
                    'attributes' => array(
1300
                        'href' => $url,
1301
                    ),
1302
                ),
1303
            );
1304
        }
1305
    }
1306
1307
    protected function inlineEmphasis($Excerpt)
1308
    {
1309
        if ( ! isset($Excerpt['text'][1]))
1310
        {
1311
            return;
1312
        }
1313
1314
        $marker = $Excerpt['text'][0];
1315
1316
        if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
1317
        {
1318
            $emphasis = 'strong';
1319
        }
1320
        elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
1321
        {
1322
            $emphasis = 'em';
1323
        }
1324
        else
1325
        {
1326
            return;
1327
        }
1328
1329
        return array(
1330
            'extent' => strlen($matches[0]),
1331
            'element' => array(
1332
                'name' => $emphasis,
1333
                'handler' => array(
1334
                    'function' => 'lineElements',
1335
                    'argument' => $matches[1],
1336
                    'destination' => 'elements',
1337
                )
1338
            ),
1339
        );
1340
    }
1341
1342
    protected function inlineEscapeSequence($Excerpt)
1343
    {
1344
        if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
1345
        {
1346
            return array(
1347
                'element' => array('rawHtml' => $Excerpt['text'][1]),
1348
                'extent' => 2,
1349
            );
1350
        }
1351
    }
1352
1353
    protected function inlineImage($Excerpt)
1354
    {
1355
        if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
1356
        {
1357
            return;
1358
        }
1359
1360
        $Excerpt['text']= substr($Excerpt['text'], 1);
1361
1362
        $Link = $this->inlineLink($Excerpt);
1363
1364
        if ($Link === null)
1365
        {
1366
            return;
1367
        }
1368
1369
        $Inline = array(
1370
            'extent' => $Link['extent'] + 1,
1371
            'element' => array(
1372
                'name' => 'img',
1373
                'attributes' => array(
1374
                    'src' => $Link['element']['attributes']['href'],
1375
                    'alt' => $Link['element']['handler']['argument'],
1376
                ),
1377
                'autobreak' => true,
1378
            ),
1379
        );
1380
1381
        $Inline['element']['attributes'] += $Link['element']['attributes'];
1382
1383
        unset($Inline['element']['attributes']['href']);
1384
1385
        return $Inline;
1386
    }
1387
1388
    protected function inlineLink($Excerpt)
1389
    {
1390
        $Element = array(
1391
            'name' => 'a',
1392
            'handler' => array(
1393
                'function' => 'lineElements',
1394
                'argument' => null,
1395
                'destination' => 'elements',
1396
            ),
1397
            'nonNestables' => array('Url', 'Link'),
1398
            'attributes' => array(
1399
                'href' => null,
1400
                'title' => null,
1401
            ),
1402
        );
1403
1404
        $extent = 0;
1405
1406
        $remainder = $Excerpt['text'];
1407
1408
        if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
1409
        {
1410
            $Element['handler']['argument'] = $matches[1];
1411
1412
            $extent += strlen($matches[0]);
1413
1414
            $remainder = substr($remainder, $extent);
1415
        }
1416
        else
1417
        {
1418
            return;
1419
        }
1420
1421
        if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches))
1422
        {
1423
            $Element['attributes']['href'] = $matches[1];
1424
1425
            if (isset($matches[2]))
1426
            {
1427
                $Element['attributes']['title'] = substr($matches[2], 1, - 1);
1428
            }
1429
1430
            $extent += strlen($matches[0]);
1431
        }
1432
        else
1433
        {
1434
            if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
1435
            {
1436
                $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument'];
1437
                $definition = strtolower($definition);
1438
1439
                $extent += strlen($matches[0]);
1440
            }
1441
            else
1442
            {
1443
                $definition = strtolower($Element['handler']['argument']);
1444
            }
1445
1446
            if ( ! isset($this->DefinitionData['Reference'][$definition]))
1447
            {
1448
                return;
1449
            }
1450
1451
            $Definition = $this->DefinitionData['Reference'][$definition];
1452
1453
            $Element['attributes']['href'] = $Definition['url'];
1454
            $Element['attributes']['title'] = $Definition['title'];
1455
        }
1456
1457
        return array(
1458
            'extent' => $extent,
1459
            'element' => $Element,
1460
        );
1461
    }
1462
1463
    protected function inlineMarkup($Excerpt)
1464
    {
1465
        if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
1466
        {
1467
            return;
1468
        }
1469
1470
        if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches))
1471
        {
1472
            return array(
1473
                'element' => array('rawHtml' => $matches[0]),
1474
                'extent' => strlen($matches[0]),
1475
            );
1476
        }
1477
1478
        if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?+[^-])*-->/s', $Excerpt['text'], $matches))
1479
        {
1480
            return array(
1481
                'element' => array('rawHtml' => $matches[0]),
1482
                'extent' => strlen($matches[0]),
1483
            );
1484
        }
1485
1486
        if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches))
1487
        {
1488
            return array(
1489
                'element' => array('rawHtml' => $matches[0]),
1490
                'extent' => strlen($matches[0]),
1491
            );
1492
        }
1493
    }
1494
1495
    protected function inlineSpecialCharacter($Excerpt)
1496
    {
1497
        if (substr($Excerpt['text'], 1, 1) !== ' ' and strpos($Excerpt['text'], ';') !== false
1498
            and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches)
1499
        ) {
1500
            return array(
1501
                'element' => array('rawHtml' => '&' . $matches[1] . ';'),
1502
                'extent' => strlen($matches[0]),
1503
            );
1504
        }
1505
1506
        return;
1507
    }
1508
1509
    protected function inlineStrikethrough($Excerpt)
1510
    {
1511
        if ( ! isset($Excerpt['text'][1]))
1512
        {
1513
            return;
1514
        }
1515
1516
        if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
1517
        {
1518
            return array(
1519
                'extent' => strlen($matches[0]),
1520
                'element' => array(
1521
                    'name' => 'del',
1522
                    'handler' => array(
1523
                        'function' => 'lineElements',
1524
                        'argument' => $matches[1],
1525
                        'destination' => 'elements',
1526
                    )
1527
                ),
1528
            );
1529
        }
1530
    }
1531
1532
    protected function inlineUrl($Excerpt)
1533
    {
1534
        if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
1535
        {
1536
            return;
1537
        }
1538
1539
        if (strpos($Excerpt['context'], 'http') !== false
1540
            and preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)
1541
        ) {
1542
            $url = $matches[0][0];
1543
1544
            $Inline = array(
1545
                'extent' => strlen($matches[0][0]),
1546
                'position' => $matches[0][1],
1547
                'element' => array(
1548
                    'name' => 'a',
1549
                    'text' => $url,
1550
                    'attributes' => array(
1551
                        'href' => $url,
1552
                    ),
1553
                ),
1554
            );
1555
1556
            return $Inline;
1557
        }
1558
    }
1559
1560
    protected function inlineUrlTag($Excerpt)
1561
    {
1562
        if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches))
1563
        {
1564
            $url = $matches[1];
1565
1566
            return array(
1567
                'extent' => strlen($matches[0]),
1568
                'element' => array(
1569
                    'name' => 'a',
1570
                    'text' => $url,
1571
                    'attributes' => array(
1572
                        'href' => $url,
1573
                    ),
1574
                ),
1575
            );
1576
        }
1577
    }
1578
1579
    # ~
1580
1581
    protected function unmarkedText($text)
1582
    {
1583
        $Inline = $this->inlineText($text);
1584
        return $this->element($Inline['element']);
1585
    }
1586
1587
    #
1588
    # Handlers
1589
    #
1590
1591
    protected function handle(array $Element)
1592
    {
1593
        if (isset($Element['handler']))
1594
        {
1595
            if (!isset($Element['nonNestables']))
1596
            {
1597
                $Element['nonNestables'] = array();
1598
            }
1599
1600
            if (is_string($Element['handler']))
1601
            {
1602
                $function = $Element['handler'];
1603
                $argument = $Element['text'];
1604
                unset($Element['text']);
1605
                $destination = 'rawHtml';
1606
            }
1607
            else
1608
            {
1609
                $function = $Element['handler']['function'];
1610
                $argument = $Element['handler']['argument'];
1611
                $destination = $Element['handler']['destination'];
1612
            }
1613
1614
            $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']);
1615
1616
            if ($destination === 'handler')
1617
            {
1618
                $Element = $this->handle($Element);
1619
            }
1620
1621
            unset($Element['handler']);
1622
        }
1623
1624
        return $Element;
1625
    }
1626
1627
    protected function handleElementRecursive(array $Element)
1628
    {
1629
        return $this->elementApplyRecursive(array($this, 'handle'), $Element);
1630
    }
1631
1632
    protected function handleElementsRecursive(array $Elements)
1633
    {
1634
        return $this->elementsApplyRecursive(array($this, 'handle'), $Elements);
1635
    }
1636
1637
    protected function elementApplyRecursive($closure, array $Element)
1638
    {
1639
        $Element = call_user_func($closure, $Element);
1640
1641
        if (isset($Element['elements']))
1642
        {
1643
            $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']);
1644
        }
1645
        elseif (isset($Element['element']))
1646
        {
1647
            $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']);
1648
        }
1649
1650
        return $Element;
1651
    }
1652
1653
    protected function elementApplyRecursiveDepthFirst($closure, array $Element)
1654
    {
1655
        if (isset($Element['elements']))
1656
        {
1657
            $Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']);
1658
        }
1659
        elseif (isset($Element['element']))
1660
        {
1661
            $Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']);
1662
        }
1663
1664
        $Element = call_user_func($closure, $Element);
1665
1666
        return $Element;
1667
    }
1668
1669
    protected function elementsApplyRecursive($closure, array $Elements)
1670
    {
1671
        foreach ($Elements as &$Element)
1672
        {
1673
            $Element = $this->elementApplyRecursive($closure, $Element);
1674
        }
1675
1676
        return $Elements;
1677
    }
1678
1679
    protected function elementsApplyRecursiveDepthFirst($closure, array $Elements)
1680
    {
1681
        foreach ($Elements as &$Element)
1682
        {
1683
            $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element);
1684
        }
1685
1686
        return $Elements;
1687
    }
1688
1689
    protected function element(array $Element)
1690
    {
1691
        if ($this->safeMode)
1692
        {
1693
            $Element = $this->sanitiseElement($Element);
1694
        }
1695
1696
        # identity map if element has no handler
1697
        $Element = $this->handle($Element);
1698
1699
        $hasName = isset($Element['name']);
1700
1701
        $markup = '';
1702
1703
        if ($hasName)
1704
        {
1705
            $markup .= '<' . $Element['name'];
1706
1707
            if (isset($Element['attributes']))
1708
            {
1709
                foreach ($Element['attributes'] as $name => $value)
1710
                {
1711
                    if ($value === null)
1712
                    {
1713
                        continue;
1714
                    }
1715
1716
                    $markup .= " $name=\"".self::escape($value).'"';
1717
                }
1718
            }
1719
        }
1720
1721
        $permitRawHtml = false;
1722
1723
        if (isset($Element['text']))
1724
        {
1725
            $text = $Element['text'];
1726
        }
1727
        // very strongly consider an alternative if you're writing an
1728
        // extension
1729
        elseif (isset($Element['rawHtml']))
1730
        {
1731
            $text = $Element['rawHtml'];
1732
1733
            $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode'];
1734
            $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;
1735
        }
1736
1737
        $hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']);
1738
1739
        if ($hasContent)
1740
        {
1741
            $markup .= $hasName ? '>' : '';
1742
1743
            if (isset($Element['elements']))
1744
            {
1745
                $markup .= $this->elements($Element['elements']);
1746
            }
1747
            elseif (isset($Element['element']))
1748
            {
1749
                $markup .= $this->element($Element['element']);
1750
            }
1751
            else
1752
            {
1753
                if (!$permitRawHtml)
1754
                {
1755
                    $markup .= self::escape($text, true);
1756
                }
1757
                else
1758
                {
1759
                    $markup .= $text;
1760
                }
1761
            }
1762
1763
            $markup .= $hasName ? '</' . $Element['name'] . '>' : '';
1764
        }
1765
        elseif ($hasName)
1766
        {
1767
            $markup .= ' />';
1768
        }
1769
1770
        return $markup;
1771
    }
1772
1773
    protected function elements(array $Elements)
1774
    {
1775
        $markup = '';
1776
1777
        $autoBreak = true;
1778
1779
        foreach ($Elements as $Element)
1780
        {
1781
            if (empty($Element))
1782
            {
1783
                continue;
1784
            }
1785
1786
            $autoBreakNext = (isset($Element['autobreak'])
1787
                ? $Element['autobreak'] : isset($Element['name'])
1788
            );
1789
            // (autobreak === false) covers both sides of an element
1790
            $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext;
1791
1792
            $markup .= ($autoBreak ? "\n" : '') . $this->element($Element);
1793
            $autoBreak = $autoBreakNext;
1794
        }
1795
1796
        $markup .= $autoBreak ? "\n" : '';
1797
1798
        return $markup;
1799
    }
1800
1801
    # ~
1802
1803
    protected function li($lines)
1804
    {
1805
        $Elements = $this->linesElements($lines);
1806
1807
        if ( ! in_array('', $lines)
1808
            and isset($Elements[0]) and isset($Elements[0]['name'])
1809
            and $Elements[0]['name'] === 'p'
1810
        ) {
1811
            unset($Elements[0]['name']);
1812
        }
1813
1814
        return $Elements;
1815
    }
1816
1817
    #
1818
    # AST Convenience
1819
    #
1820
1821
    /**
1822
     * Replace occurrences $regexp with $Elements in $text. Return an array of
1823
     * elements representing the replacement.
1824
     */
1825
    protected static function pregReplaceElements($regexp, $Elements, $text)
1826
    {
1827
        $newElements = array();
1828
1829
        while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE))
1830
        {
1831
            $offset = $matches[0][1];
1832
            $before = substr($text, 0, $offset);
1833
            $after = substr($text, $offset + strlen($matches[0][0]));
1834
1835
            $newElements[] = array('text' => $before);
1836
1837
            foreach ($Elements as $Element)
1838
            {
1839
                $newElements[] = $Element;
1840
            }
1841
1842
            $text = $after;
1843
        }
1844
1845
        $newElements[] = array('text' => $text);
1846
1847
        return $newElements;
1848
    }
1849
1850
    #
1851
    # Deprecated Methods
1852
    #
1853
1854
    function parse($text)
1855
    {
1856
        $markup = $this->text($text);
1857
1858
        return $markup;
1859
    }
1860
1861
    protected function sanitiseElement(array $Element)
1862
    {
1863
        static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
1864
        static $safeUrlNameToAtt  = array(
1865
            'a'   => 'href',
1866
            'img' => 'src',
1867
        );
1868
1869
        if ( ! isset($Element['name']))
1870
        {
1871
            unset($Element['attributes']);
1872
            return $Element;
1873
        }
1874
1875
        if (isset($safeUrlNameToAtt[$Element['name']]))
1876
        {
1877
            $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
1878
        }
1879
1880
        if ( ! empty($Element['attributes']))
1881
        {
1882
            foreach ($Element['attributes'] as $att => $val)
1883
            {
1884
                # filter out badly parsed attribute
1885
                if ( ! preg_match($goodAttribute, $att))
1886
                {
1887
                    unset($Element['attributes'][$att]);
1888
                }
1889
                # dump onevent attribute
1890
                elseif (self::striAtStart($att, 'on'))
1891
                {
1892
                    unset($Element['attributes'][$att]);
1893
                }
1894
            }
1895
        }
1896
1897
        return $Element;
1898
    }
1899
1900
    protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
1901
    {
1902
        foreach ($this->safeLinksWhitelist as $scheme)
1903
        {
1904
            if (self::striAtStart($Element['attributes'][$attribute], $scheme))
1905
            {
1906
                return $Element;
1907
            }
1908
        }
1909
1910
        $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
1911
1912
        return $Element;
1913
    }
1914
1915
    #
1916
    # Static Methods
1917
    #
1918
1919
    protected static function escape($text, $allowQuotes = false)
1920
    {
1921
        return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
1922
    }
1923
1924
    protected static function striAtStart($string, $needle)
1925
    {
1926
        $len = strlen($needle);
1927
1928
        if ($len > strlen($string))
1929
        {
1930
            return false;
1931
        }
1932
        else
1933
        {
1934
            return strtolower(substr($string, 0, $len)) === strtolower($needle);
1935
        }
1936
    }
1937
1938
    static function instance($name = 'default')
1939
    {
1940
        if (isset(self::$instances[$name]))
1941
        {
1942
            return self::$instances[$name];
1943
        }
1944
1945
        $instance = new static();
1946
1947
        self::$instances[$name] = $instance;
1948
1949
        return $instance;
1950
    }
1951
1952
    private static $instances = array();
1953
1954
    #
1955
    # Fields
1956
    #
1957
1958
    protected $DefinitionData;
1959
1960
    #
1961
    # Read-Only
1962
1963
    protected $specialCharacters = array(
1964
        '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~'
1965
    );
1966
1967
    protected $StrongRegex = array(
1968
        '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s',
1969
        '_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us',
1970
    );
1971
1972
    protected $EmRegex = array(
1973
        '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
1974
        '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
1975
    );
1976
1977
    protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+';
1978
1979
    protected $voidElements = array(
1980
        'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
1981
    );
1982
1983
    protected $textLevelElements = array(
1984
        'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
1985
        'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
1986
        'i', 'rp', 'del', 'code',          'strike', 'marquee',
1987
        'q', 'rt', 'ins', 'font',          'strong',
1988
        's', 'tt', 'kbd', 'mark',
1989
        'u', 'xm', 'sub', 'nobr',
1990
                   'sup', 'ruby',
1991
                   'var', 'span',
1992
                   'wbr', 'time',
1993
    );
1994
}

+ 0 - 157
application/parsedown/README.md

@ -1,157 +0,0 @@
1
<!-- ![Parsedown](https://i.imgur.com/yE8afYV.png) -->
2
3
<p align="center"><img alt="Parsedown" src="https://i.imgur.com/fKVY6Kz.png" width="240" /></p>
4
5
<h1>Parsedown</h1>
6
7
[![Build Status](https://travis-ci.org/erusev/parsedown.svg)](https://travis-ci.org/erusev/parsedown)
8
[![Total Downloads](https://poser.pugx.org/erusev/parsedown/d/total.svg)](https://packagist.org/packages/erusev/parsedown)
9
[![Version](https://poser.pugx.org/erusev/parsedown/v/stable.svg)](https://packagist.org/packages/erusev/parsedown)
10
[![License](https://poser.pugx.org/erusev/parsedown/license.svg)](https://packagist.org/packages/erusev/parsedown)
11
12
Better Markdown Parser in PHP - <a href="http://parsedown.org/demo">Demo</a>.
13
14
## Features
15
16
* One File
17
* No Dependencies
18
* [Super Fast](http://parsedown.org/speed)
19
* Extensible
20
* [GitHub flavored](https://github.github.com/gfm)
21
* [Tested](http://parsedown.org/tests/) in 5.3 to 7.3
22
* [Markdown Extra extension](https://github.com/erusev/parsedown-extra)
23
24
## Installation
25
26
Install the [composer package]:
27
28
    composer require erusev/parsedown
29
30
Or download the [latest release] and include `Parsedown.php`
31
32
[composer package]: https://packagist.org/packages/erusev/parsedown "The Parsedown package on packagist.org"
33
[latest release]: https://github.com/erusev/parsedown/releases/latest "The latest release of Parsedown"
34
35
## Example
36
37
```php
38
$Parsedown = new Parsedown();
39
40
echo $Parsedown->text('Hello _Parsedown_!'); # prints: <p>Hello <em>Parsedown</em>!</p>
41
```
42
43
You can also parse inline markdown only:
44
45
```php
46
echo $Parsedown->line('Hello _Parsedown_!'); # prints: Hello <em>Parsedown</em>!
47
```
48
49
More examples in [the wiki](https://github.com/erusev/parsedown/wiki/) and in [this video tutorial](http://youtu.be/wYZBY8DEikI).
50
51
## Security
52
53
Parsedown is capable of escaping user-input within the HTML that it generates. Additionally Parsedown will apply sanitisation to additional scripting vectors (such as scripting link destinations) that are introduced by the markdown syntax itself.
54
55
To tell Parsedown that it is processing untrusted user-input, use the following:
56
57
```php
58
$Parsedown->setSafeMode(true);
59
```
60
61
If instead, you wish to allow HTML within untrusted user-input, but still want output to be free from XSS it is recommended that you make use of a HTML sanitiser that allows HTML tags to be whitelisted, like [HTML Purifier](http://htmlpurifier.org/).
62
63
In both cases you should strongly consider employing defence-in-depth measures, like [deploying a Content-Security-Policy](https://scotthelme.co.uk/content-security-policy-an-introduction/) (a browser security feature) so that your page is likely to be safe even if an attacker finds a vulnerability in one of the first lines of defence above.
64
65
#### Security of Parsedown Extensions
66
67
Safe mode does not necessarily yield safe results when using extensions to Parsedown. Extensions should be evaluated on their own to determine their specific safety against XSS.
68
69
## Escaping HTML
70
71
> **WARNING:** This method isn't safe from XSS!
72
73
If you wish to escape HTML **in trusted input**, you can use the following:
74
75
```php
76
$Parsedown->setMarkupEscaped(true);
77
```
78
79
Beware that this still allows users to insert unsafe scripting vectors, such as links like `[xss](javascript:alert%281%29)`.
80
81
## Questions
82
83
**How does Parsedown work?**
84
85
It tries to read Markdown like a human. First, it looks at the lines. It’s interested in how the lines start. This helps it recognise blocks. It knows, for example, that if a line starts with a `-` then perhaps it belongs to a list. Once it recognises the blocks, it continues to the content. As it reads, it watches out for special characters. This helps it recognise inline elements (or inlines).
86
87
We call this approach "line based". We believe that Parsedown is the first Markdown parser to use it. Since the release of Parsedown, other developers have used the same approach to develop other Markdown parsers in PHP and in other languages.
88
89
**Is it compliant with CommonMark?**
90
91
It passes most of the CommonMark tests. Most of the tests that don't pass deal with cases that are quite uncommon. Still, as CommonMark matures, compliance should improve.
92
93
**Who uses it?**
94
95
[Laravel Framework](https://laravel.com/), [Bolt CMS](http://bolt.cm/), [Grav CMS](http://getgrav.org/), [Herbie CMS](http://www.getherbie.org/), [Kirby CMS](http://getkirby.com/), [October CMS](http://octobercms.com/), [Pico CMS](http://picocms.org), [Statamic CMS](http://www.statamic.com/), [phpDocumentor](http://www.phpdoc.org/), [RaspberryPi.org](http://www.raspberrypi.org/), [Symfony Demo](https://github.com/symfony/demo) and [more](https://packagist.org/packages/erusev/parsedown/dependents).
96
97
**How can I help?**
98
99
Use it, star it, share it and if you feel generous, [donate](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=528P3NZQMP8N2).
100
101
**What else should I know?**
102
103
I also make [Nota](https://nota.md/) — a writing app designed for Markdown files :)
104
105
106
## Parsedown Extension
107
108
A markdown extension for [parsedown](https://github.com/erusev/parsedown) , supports `[TOC]` syntax to generate table of contents.
109
110
## Installation
111
112
Include both `Parsedown.php` and `ParsedownExtension.php` or install [the composer package](https://packagist.org/packages/mrgeneral/parsedown-extension).
113
114
## Example
115
116
``` php
117
        $text = <<<EOL
118
[TOC]
119
# This is head 1
120
## This is head 1-1
121
### This is head 1-3
122
# Sample text of head 2
123
## Sample text of head 2-1
124
EOL;
125
126
        $content = ParsedownExtension::instance()
127
            ->setTocEnabled(true)
128
            ->text($text);
129
```
130
131
```html
132
<ul>
133
	<li><a href="#This+is+head+1">This is head 1</a>
134
	<ul>
135
		<li><a href="#This+is+head+1-1">This is head 1-1</a>
136
		<ul>
137
			<li><a href="#This+is+head+1-3">This is head 1-3</a></li>
138
		</ul>
139
		</li>
140
	</ul>
141
	</li>
142
	<li><a href="#This+is+head+2">This is head 2</a>
143
	<ul>
144
		<li><a href="#This+is+head+2-1">This is head 2-1</a></li>
145
	</ul>
146
	</li>
147
</ul>
148
<h1 id="This+is+head+1">This is head 1</h1>
149
<h2 id="This+is+head+1-1">This is head 1-1</h2>
150
<h3 id="This+is+head+1-3">This is head 1-3</h3>
151
<h1 id="This+is+head+2">This is head 2</h1>
152
<h2 id="This+is+head+2-1">This is head 2-1</h2>
153
```
154
155
## Reporting issues
156
157
 You can [create an issue](https://github.com/mrgeneralgoo/parsedown-extension/issues/new)

+ 83 - 0
application/utils/ParsedownExtension.php

@ -0,0 +1,83 @@
1
<?php
2
3
declare(strict_types=1);
4
5
namespace app\utils;
6
7
class ParsedownExtension extends \Parsedown
8
{
9
    protected $isTocEnabled = false;
10
11
    protected $rawTocList = [];
12
13
    protected $findTocSyntaxRule = '#^<p> *\[TOC\]\s*</p>$#m';
14
15
    public function setTocEnabled($isTocEnable)
16
    {
17
        $this->isTocEnabled = $isTocEnable;
18
19
        return $this;
20
    }
21
22
    public function setTocSyntaxRule($findTocSyntaxRule)
23
    {
24
        $this->findTocSyntaxRule = $findTocSyntaxRule;
25
26
        return $this;
27
    }
28
29
    public function text($text)
30
    {
31
        $content = parent::text($text);
32
        
33
        if (!$this->isTocEnabled || empty($this->rawTocList) || !preg_match($this->findTocSyntaxRule, $content)) {
34
            return ["toc"=>"","content"=>$content];
35
        }
36
37
        $content = preg_replace($this->findTocSyntaxRule, "", $content);
38
        return ["toc"=>$this->buildToc(), "content"=>$content];
39
    }
40
41
    protected function buildToc()
42
    {
43
        $tocMarkdownContent = '';
44
        $topHeadLevel       = min(array_column($this->rawTocList, 'level'));
45
46
        foreach ($this->rawTocList as $id => $tocItem) {
47
            $tocMarkdownContent .= sprintf('%s- [%s](#%s)' . PHP_EOL, str_repeat('  ', $tocItem['level'] - $topHeadLevel), $this->line($tocItem['text']), $tocItem['id']);
48
        }
49
50
        return parent::text($tocMarkdownContent);
51
    }
52
53
    protected function blockHeader($line)
54
    {
55
        $block = parent::blockHeader($line);
56
        $text  = $block['element']['handler']['argument'];
57
        $no = 0;
58
        foreach ($this->rawTocList as $key => $value) {
59
            if ($text == $value['text']) {
60
                $no = $value['no'] + 1;
61
            }
62
        }
63
        
64
        $id = urlencode($this->line($text));
65
        
66
        if ($no != 0) {
67
            $id .= '-' . $no;
68
        }
69
        
70
        $block['element']['attributes'] = [
71
            'id' => $id,
72
        ];
73
74
        $this->rawTocList[] = [
75
            'id' => $id,
76
            'text'  => $text,
77
            'level' => str_replace('h', '', $block['element']['name']),
78
            'no'=> $no,
79
        ];
80
81
        return $block;
82
    }
83
}