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