PluginProbe
QuadMenu – Mega Menu / 1.8.8
QuadMenu – Mega Menu v1.8.8
3.3.8 3.3.7 3.3.6 trunk 1.8.4 1.8.5 1.8.7 1.8.8 1.8.9 1.9.0 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 All 88 releases
quadmenu / lib / ReduxCore / inc / fields / raw / parsedown.php

parsedown.php in QuadMenu – Mega Menu 1.8.8, at lib/ReduxCore/inc/fields/raw/parsedown.php

1,549 lines 37.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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.6.0';
21
22 # ~
23
24 function text($text)
25 {
26 # make sure no definitions are set
27 $this->DefinitionData = array();
28
29 # standardize line breaks
30 $text = str_replace(array("\r\n", "\r"), "\n", $text);
31
32 # remove surrounding line breaks
33 $text = trim($text, "\n");
34
35 # split text into lines
36 $lines = explode("\n", $text);
37
38 # iterate through lines to identify blocks
39 $markup = $this->lines($lines);
40
41 # trim line breaks
42 $markup = trim($markup, "\n");
43
44 return $markup;
45 }
46
47 #
48 # Setters
49 #
50
51 function setBreaksEnabled($breaksEnabled)
52 {
53 $this->breaksEnabled = $breaksEnabled;
54
55 return $this;
56 }
57
58 protected $breaksEnabled;
59
60 function setMarkupEscaped($markupEscaped)
61 {
62 $this->markupEscaped = $markupEscaped;
63
64 return $this;
65 }
66
67 protected $markupEscaped;
68
69 function setUrlsLinked($urlsLinked)
70 {
71 $this->urlsLinked = $urlsLinked;
72
73 return $this;
74 }
75
76 protected $urlsLinked = true;
77
78 #
79 # Lines
80 #
81
82 protected $BlockTypes = array(
83 '#' => array('Header'),
84 '*' => array('Rule', 'List'),
85 '+' => array('List'),
86 '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
87 '0' => array('List'),
88 '1' => array('List'),
89 '2' => array('List'),
90 '3' => array('List'),
91 '4' => array('List'),
92 '5' => array('List'),
93 '6' => array('List'),
94 '7' => array('List'),
95 '8' => array('List'),
96 '9' => array('List'),
97 ':' => array('Table'),
98 '<' => array('Comment', 'Markup'),
99 '=' => array('SetextHeader'),
100 '>' => array('Quote'),
101 '[' => array('Reference'),
102 '_' => array('Rule'),
103 '`' => array('FencedCode'),
104 '|' => array('Table'),
105 '~' => array('FencedCode'),
106 );
107
108 # ~
109
110 protected $unmarkedBlockTypes = array(
111 'Code',
112 );
113
114 #
115 # Blocks
116 #
117
118 protected function lines(array $lines)
119 {
120 $CurrentBlock = null;
121
122 foreach ($lines as $line)
123 {
124 if (chop($line) === '')
125 {
126 if (isset($CurrentBlock))
127 {
128 $CurrentBlock['interrupted'] = true;
129 }
130
131 continue;
132 }
133
134 if (strpos($line, "\t") !== false)
135 {
136 $parts = explode("\t", $line);
137
138 $line = $parts[0];
139
140 unset($parts[0]);
141
142 foreach ($parts as $part)
143 {
144 $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
145
146 $line .= str_repeat(' ', $shortage);
147 $line .= $part;
148 }
149 }
150
151 $indent = 0;
152
153 while (isset($line[$indent]) and $line[$indent] === ' ')
154 {
155 $indent ++;
156 }
157
158 $text = $indent > 0 ? substr($line, $indent) : $line;
159
160 # ~
161
162 $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
163
164 # ~
165
166 if (isset($CurrentBlock['continuable']))
167 {
168 $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
169
170 if (isset($Block))
171 {
172 $CurrentBlock = $Block;
173
174 continue;
175 }
176 else
177 {
178 if ($this->isBlockCompletable($CurrentBlock['type']))
179 {
180 $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
181 }
182 }
183 }
184
185 # ~
186
187 $marker = $text[0];
188
189 # ~
190
191 $blockTypes = $this->unmarkedBlockTypes;
192
193 if (isset($this->BlockTypes[$marker]))
194 {
195 foreach ($this->BlockTypes[$marker] as $blockType)
196 {
197 $blockTypes []= $blockType;
198 }
199 }
200
201 #
202 # ~
203
204 foreach ($blockTypes as $blockType)
205 {
206 $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
207
208 if (isset($Block))
209 {
210 $Block['type'] = $blockType;
211
212 if ( ! isset($Block['identified']))
213 {
214 $Blocks []= $CurrentBlock;
215
216 $Block['identified'] = true;
217 }
218
219 if ($this->isBlockContinuable($blockType))
220 {
221 $Block['continuable'] = true;
222 }
223
224 $CurrentBlock = $Block;
225
226 continue 2;
227 }
228 }
229
230 # ~
231
232 if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
233 {
234 $CurrentBlock['element']['text'] .= "\n".$text;
235 }
236 else
237 {
238 $Blocks []= $CurrentBlock;
239
240 $CurrentBlock = $this->paragraph($Line);
241
242 $CurrentBlock['identified'] = true;
243 }
244 }
245
246 # ~
247
248 if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
249 {
250 $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
251 }
252
253 # ~
254
255 $Blocks []= $CurrentBlock;
256
257 unset($Blocks[0]);
258
259 # ~
260
261 $markup = '';
262
263 foreach ($Blocks as $Block)
264 {
265 if (isset($Block['hidden']))
266 {
267 continue;
268 }
269
270 $markup .= "\n";
271 $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
272 }
273
274 $markup .= "\n";
275
276 # ~
277
278 return $markup;
279 }
280
281 protected function isBlockContinuable($Type)
282 {
283 return method_exists($this, 'block'.$Type.'Continue');
284 }
285
286 protected function isBlockCompletable($Type)
287 {
288 return method_exists($this, 'block'.$Type.'Complete');
289 }
290
291 #
292 # Code
293
294 protected function blockCode($Line, $Block = null)
295 {
296 if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
297 {
298 return;
299 }
300
301 if ($Line['indent'] >= 4)
302 {
303 $text = substr($Line['body'], 4);
304
305 $Block = array(
306 'element' => array(
307 'name' => 'pre',
308 'handler' => 'element',
309 'text' => array(
310 'name' => 'code',
311 'text' => $text,
312 ),
313 ),
314 );
315
316 return $Block;
317 }
318 }
319
320 protected function blockCodeContinue($Line, $Block)
321 {
322 if ($Line['indent'] >= 4)
323 {
324 if (isset($Block['interrupted']))
325 {
326 $Block['element']['text']['text'] .= "\n";
327
328 unset($Block['interrupted']);
329 }
330
331 $Block['element']['text']['text'] .= "\n";
332
333 $text = substr($Line['body'], 4);
334
335 $Block['element']['text']['text'] .= $text;
336
337 return $Block;
338 }
339 }
340
341 protected function blockCodeComplete($Block)
342 {
343 $text = $Block['element']['text']['text'];
344
345 $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
346
347 $Block['element']['text']['text'] = $text;
348
349 return $Block;
350 }
351
352 #
353 # Comment
354
355 protected function blockComment($Line)
356 {
357 if ($this->markupEscaped)
358 {
359 return;
360 }
361
362 if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
363 {
364 $Block = array(
365 'markup' => $Line['body'],
366 );
367
368 if (preg_match('/-->$/', $Line['text']))
369 {
370 $Block['closed'] = true;
371 }
372
373 return $Block;
374 }
375 }
376
377 protected function blockCommentContinue($Line, array $Block)
378 {
379 if (isset($Block['closed']))
380 {
381 return;
382 }
383
384 $Block['markup'] .= "\n" . $Line['body'];
385
386 if (preg_match('/-->$/', $Line['text']))
387 {
388 $Block['closed'] = true;
389 }
390
391 return $Block;
392 }
393
394 #
395 # Fenced Code
396
397 protected function blockFencedCode($Line)
398 {
399 if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
400 {
401 $Element = array(
402 'name' => 'code',
403 'text' => '',
404 );
405
406 if (isset($matches[1]))
407 {
408 $class = 'language-'.$matches[1];
409
410 $Element['attributes'] = array(
411 'class' => $class,
412 );
413 }
414
415 $Block = array(
416 'char' => $Line['text'][0],
417 'element' => array(
418 'name' => 'pre',
419 'handler' => 'element',
420 'text' => $Element,
421 ),
422 );
423
424 return $Block;
425 }
426 }
427
428 protected function blockFencedCodeContinue($Line, $Block)
429 {
430 if (isset($Block['complete']))
431 {
432 return;
433 }
434
435 if (isset($Block['interrupted']))
436 {
437 $Block['element']['text']['text'] .= "\n";
438
439 unset($Block['interrupted']);
440 }
441
442 if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
443 {
444 $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
445
446 $Block['complete'] = true;
447
448 return $Block;
449 }
450
451 $Block['element']['text']['text'] .= "\n".$Line['body'];
452
453 return $Block;
454 }
455
456 protected function blockFencedCodeComplete($Block)
457 {
458 $text = $Block['element']['text']['text'];
459
460 $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
461
462 $Block['element']['text']['text'] = $text;
463
464 return $Block;
465 }
466
467 #
468 # Header
469
470 protected function blockHeader($Line)
471 {
472 if (isset($Line['text'][1]))
473 {
474 $level = 1;
475
476 while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
477 {
478 $level ++;
479 }
480
481 if ($level > 6)
482 {
483 return;
484 }
485
486 $text = trim($Line['text'], '# ');
487
488 $Block = array(
489 'element' => array(
490 'name' => 'h' . min(6, $level),
491 'text' => $text,
492 'handler' => 'line',
493 ),
494 );
495
496 return $Block;
497 }
498 }
499
500 #
501 # List
502
503 protected function blockList($Line)
504 {
505 list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
506
507 if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
508 {
509 $Block = array(
510 'indent' => $Line['indent'],
511 'pattern' => $pattern,
512 'element' => array(
513 'name' => $name,
514 'handler' => 'elements',
515 ),
516 );
517
518 if($name === 'ol')
519 {
520 $listStart = stristr($matches[0], '.', true);
521
522 if($listStart !== '1')
523 {
524 $Block['element']['attributes'] = array('start' => $listStart);
525 }
526 }
527
528 $Block['li'] = array(
529 'name' => 'li',
530 'handler' => 'li',
531 'text' => array(
532 $matches[2],
533 ),
534 );
535
536 $Block['element']['text'] []= & $Block['li'];
537
538 return $Block;
539 }
540 }
541
542 protected function blockListContinue($Line, array $Block)
543 {
544 if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
545 {
546 if (isset($Block['interrupted']))
547 {
548 $Block['li']['text'] []= '';
549
550 unset($Block['interrupted']);
551 }
552
553 unset($Block['li']);
554
555 $text = isset($matches[1]) ? $matches[1] : '';
556
557 $Block['li'] = array(
558 'name' => 'li',
559 'handler' => 'li',
560 'text' => array(
561 $text,
562 ),
563 );
564
565 $Block['element']['text'] []= & $Block['li'];
566
567 return $Block;
568 }
569
570 if ($Line['text'][0] === '[' and $this->blockReference($Line))
571 {
572 return $Block;
573 }
574
575 if ( ! isset($Block['interrupted']))
576 {
577 $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
578
579 $Block['li']['text'] []= $text;
580
581 return $Block;
582 }
583
584 if ($Line['indent'] > 0)
585 {
586 $Block['li']['text'] []= '';
587
588 $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
589
590 $Block['li']['text'] []= $text;
591
592 unset($Block['interrupted']);
593
594 return $Block;
595 }
596 }
597
598 #
599 # Quote
600
601 protected function blockQuote($Line)
602 {
603 if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
604 {
605 $Block = array(
606 'element' => array(
607 'name' => 'blockquote',
608 'handler' => 'lines',
609 'text' => (array) $matches[1],
610 ),
611 );
612
613 return $Block;
614 }
615 }
616
617 protected function blockQuoteContinue($Line, array $Block)
618 {
619 if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
620 {
621 if (isset($Block['interrupted']))
622 {
623 $Block['element']['text'] []= '';
624
625 unset($Block['interrupted']);
626 }
627
628 $Block['element']['text'] []= $matches[1];
629
630 return $Block;
631 }
632
633 if ( ! isset($Block['interrupted']))
634 {
635 $Block['element']['text'] []= $Line['text'];
636
637 return $Block;
638 }
639 }
640
641 #
642 # Rule
643
644 protected function blockRule($Line)
645 {
646 if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
647 {
648 $Block = array(
649 'element' => array(
650 'name' => 'hr'
651 ),
652 );
653
654 return $Block;
655 }
656 }
657
658 #
659 # Setext
660
661 protected function blockSetextHeader($Line, array $Block = null)
662 {
663 if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
664 {
665 return;
666 }
667
668 if (chop($Line['text'], $Line['text'][0]) === '')
669 {
670 $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
671
672 return $Block;
673 }
674 }
675
676 #
677 # Markup
678
679 protected function blockMarkup($Line)
680 {
681 if ($this->markupEscaped)
682 {
683 return;
684 }
685
686 if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
687 {
688 $element = strtolower($matches[1]);
689
690 if (in_array($element, $this->textLevelElements))
691 {
692 return;
693 }
694
695 $Block = array(
696 'name' => $matches[1],
697 'depth' => 0,
698 'markup' => $Line['text'],
699 );
700
701 $length = strlen($matches[0]);
702
703 $remainder = substr($Line['text'], $length);
704
705 if (trim($remainder) === '')
706 {
707 if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
708 {
709 $Block['closed'] = true;
710
711 $Block['void'] = true;
712 }
713 }
714 else
715 {
716 if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
717 {
718 return;
719 }
720
721 if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
722 {
723 $Block['closed'] = true;
724 }
725 }
726
727 return $Block;
728 }
729 }
730
731 protected function blockMarkupContinue($Line, array $Block)
732 {
733 if (isset($Block['closed']))
734 {
735 return;
736 }
737
738 if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
739 {
740 $Block['depth'] ++;
741 }
742
743 if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
744 {
745 if ($Block['depth'] > 0)
746 {
747 $Block['depth'] --;
748 }
749 else
750 {
751 $Block['closed'] = true;
752 }
753 }
754
755 if (isset($Block['interrupted']))
756 {
757 $Block['markup'] .= "\n";
758
759 unset($Block['interrupted']);
760 }
761
762 $Block['markup'] .= "\n".$Line['body'];
763
764 return $Block;
765 }
766
767 #
768 # Reference
769
770 protected function blockReference($Line)
771 {
772 if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
773 {
774 $id = strtolower($matches[1]);
775
776 $Data = array(
777 'url' => $matches[2],
778 'title' => null,
779 );
780
781 if (isset($matches[3]))
782 {
783 $Data['title'] = $matches[3];
784 }
785
786 $this->DefinitionData['Reference'][$id] = $Data;
787
788 $Block = array(
789 'hidden' => true,
790 );
791
792 return $Block;
793 }
794 }
795
796 #
797 # Table
798
799 protected function blockTable($Line, array $Block = null)
800 {
801 if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
802 {
803 return;
804 }
805
806 if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
807 {
808 $alignments = array();
809
810 $divider = $Line['text'];
811
812 $divider = trim($divider);
813 $divider = trim($divider, '|');
814
815 $dividerCells = explode('|', $divider);
816
817 foreach ($dividerCells as $dividerCell)
818 {
819 $dividerCell = trim($dividerCell);
820
821 if ($dividerCell === '')
822 {
823 continue;
824 }
825
826 $alignment = null;
827
828 if ($dividerCell[0] === ':')
829 {
830 $alignment = 'left';
831 }
832
833 if (substr($dividerCell, - 1) === ':')
834 {
835 $alignment = $alignment === 'left' ? 'center' : 'right';
836 }
837
838 $alignments []= $alignment;
839 }
840
841 # ~
842
843 $HeaderElements = array();
844
845 $header = $Block['element']['text'];
846
847 $header = trim($header);
848 $header = trim($header, '|');
849
850 $headerCells = explode('|', $header);
851
852 foreach ($headerCells as $index => $headerCell)
853 {
854 $headerCell = trim($headerCell);
855
856 $HeaderElement = array(
857 'name' => 'th',
858 'text' => $headerCell,
859 'handler' => 'line',
860 );
861
862 if (isset($alignments[$index]))
863 {
864 $alignment = $alignments[$index];
865
866 $HeaderElement['attributes'] = array(
867 'style' => 'text-align: '.$alignment.';',
868 );
869 }
870
871 $HeaderElements []= $HeaderElement;
872 }
873
874 # ~
875
876 $Block = array(
877 'alignments' => $alignments,
878 'identified' => true,
879 'element' => array(
880 'name' => 'table',
881 'handler' => 'elements',
882 ),
883 );
884
885 $Block['element']['text'] []= array(
886 'name' => 'thead',
887 'handler' => 'elements',
888 );
889
890 $Block['element']['text'] []= array(
891 'name' => 'tbody',
892 'handler' => 'elements',
893 'text' => array(),
894 );
895
896 $Block['element']['text'][0]['text'] []= array(
897 'name' => 'tr',
898 'handler' => 'elements',
899 'text' => $HeaderElements,
900 );
901
902 return $Block;
903 }
904 }
905
906 protected function blockTableContinue($Line, array $Block)
907 {
908 if (isset($Block['interrupted']))
909 {
910 return;
911 }
912
913 if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
914 {
915 $Elements = array();
916
917 $row = $Line['text'];
918
919 $row = trim($row);
920 $row = trim($row, '|');
921
922 preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
923
924 foreach ($matches[0] as $index => $cell)
925 {
926 $cell = trim($cell);
927
928 $Element = array(
929 'name' => 'td',
930 'handler' => 'line',
931 'text' => $cell,
932 );
933
934 if (isset($Block['alignments'][$index]))
935 {
936 $Element['attributes'] = array(
937 'style' => 'text-align: '.$Block['alignments'][$index].';',
938 );
939 }
940
941 $Elements []= $Element;
942 }
943
944 $Element = array(
945 'name' => 'tr',
946 'handler' => 'elements',
947 'text' => $Elements,
948 );
949
950 $Block['element']['text'][1]['text'] []= $Element;
951
952 return $Block;
953 }
954 }
955
956 #
957 # ~
958 #
959
960 protected function paragraph($Line)
961 {
962 $Block = array(
963 'element' => array(
964 'name' => 'p',
965 'text' => $Line['text'],
966 'handler' => 'line',
967 ),
968 );
969
970 return $Block;
971 }
972
973 #
974 # Inline Elements
975 #
976
977 protected $InlineTypes = array(
978 '"' => array('SpecialCharacter'),
979 '!' => array('Image'),
980 '&' => array('SpecialCharacter'),
981 '*' => array('Emphasis'),
982 ':' => array('Url'),
983 '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
984 '>' => array('SpecialCharacter'),
985 '[' => array('Link'),
986 '_' => array('Emphasis'),
987 '`' => array('Code'),
988 '~' => array('Strikethrough'),
989 '\\' => array('EscapeSequence'),
990 );
991
992 # ~
993
994 protected $inlineMarkerList = '!"*_&[:<>`~\\';
995
996 #
997 # ~
998 #
999
1000 public function line($text)
1001 {
1002 $markup = '';
1003
1004 # $excerpt is based on the first occurrence of a marker
1005
1006 while ($excerpt = strpbrk($text, $this->inlineMarkerList))
1007 {
1008 $marker = $excerpt[0];
1009
1010 $markerPosition = strpos($text, $marker);
1011
1012 $Excerpt = array('text' => $excerpt, 'context' => $text);
1013
1014 foreach ($this->InlineTypes[$marker] as $inlineType)
1015 {
1016 $Inline = $this->{'inline'.$inlineType}($Excerpt);
1017
1018 if ( ! isset($Inline))
1019 {
1020 continue;
1021 }
1022
1023 # makes sure that the inline belongs to "our" marker
1024
1025 if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
1026 {
1027 continue;
1028 }
1029
1030 # sets a default inline position
1031
1032 if ( ! isset($Inline['position']))
1033 {
1034 $Inline['position'] = $markerPosition;
1035 }
1036
1037 # the text that comes before the inline
1038 $unmarkedText = substr($text, 0, $Inline['position']);
1039
1040 # compile the unmarked text
1041 $markup .= $this->unmarkedText($unmarkedText);
1042
1043 # compile the inline
1044 $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
1045
1046 # remove the examined text
1047 $text = substr($text, $Inline['position'] + $Inline['extent']);
1048
1049 continue 2;
1050 }
1051
1052 # the marker does not belong to an inline
1053
1054 $unmarkedText = substr($text, 0, $markerPosition + 1);
1055
1056 $markup .= $this->unmarkedText($unmarkedText);
1057
1058 $text = substr($text, $markerPosition + 1);
1059 }
1060
1061 $markup .= $this->unmarkedText($text);
1062
1063 return $markup;
1064 }
1065
1066 #
1067 # ~
1068 #
1069
1070 protected function inlineCode($Excerpt)
1071 {
1072 $marker = $Excerpt['text'][0];
1073
1074 if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
1075 {
1076 $text = $matches[2];
1077 $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
1078 $text = preg_replace("/[ ]*\n/", ' ', $text);
1079
1080 return array(
1081 'extent' => strlen($matches[0]),
1082 'element' => array(
1083 'name' => 'code',
1084 'text' => $text,
1085 ),
1086 );
1087 }
1088 }
1089
1090 protected function inlineEmailTag($Excerpt)
1091 {
1092 if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
1093 {
1094 $url = $matches[1];
1095
1096 if ( ! isset($matches[2]))
1097 {
1098 $url = 'mailto:' . $url;
1099 }
1100
1101 return array(
1102 'extent' => strlen($matches[0]),
1103 'element' => array(
1104 'name' => 'a',
1105 'text' => $matches[1],
1106 'attributes' => array(
1107 'href' => $url,
1108 ),
1109 ),
1110 );
1111 }
1112 }
1113
1114 protected function inlineEmphasis($Excerpt)
1115 {
1116 if ( ! isset($Excerpt['text'][1]))
1117 {
1118 return;
1119 }
1120
1121 $marker = $Excerpt['text'][0];
1122
1123 if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
1124 {
1125 $emphasis = 'strong';
1126 }
1127 elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
1128 {
1129 $emphasis = 'em';
1130 }
1131 else
1132 {
1133 return;
1134 }
1135
1136 return array(
1137 'extent' => strlen($matches[0]),
1138 'element' => array(
1139 'name' => $emphasis,
1140 'handler' => 'line',
1141 'text' => $matches[1],
1142 ),
1143 );
1144 }
1145
1146 protected function inlineEscapeSequence($Excerpt)
1147 {
1148 if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
1149 {
1150 return array(
1151 'markup' => $Excerpt['text'][1],
1152 'extent' => 2,
1153 );
1154 }
1155 }
1156
1157 protected function inlineImage($Excerpt)
1158 {
1159 if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
1160 {
1161 return;
1162 }
1163
1164 $Excerpt['text']= substr($Excerpt['text'], 1);
1165
1166 $Link = $this->inlineLink($Excerpt);
1167
1168 if ($Link === null)
1169 {
1170 return;
1171 }
1172
1173 $Inline = array(
1174 'extent' => $Link['extent'] + 1,
1175 'element' => array(
1176 'name' => 'img',
1177 'attributes' => array(
1178 'src' => $Link['element']['attributes']['href'],
1179 'alt' => $Link['element']['text'],
1180 ),
1181 ),
1182 );
1183
1184 $Inline['element']['attributes'] += $Link['element']['attributes'];
1185
1186 unset($Inline['element']['attributes']['href']);
1187
1188 return $Inline;
1189 }
1190
1191 protected function inlineLink($Excerpt)
1192 {
1193 $Element = array(
1194 'name' => 'a',
1195 'handler' => 'line',
1196 'text' => null,
1197 'attributes' => array(
1198 'href' => null,
1199 'title' => null,
1200 ),
1201 );
1202
1203 $extent = 0;
1204
1205 $remainder = $Excerpt['text'];
1206
1207 if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
1208 {
1209 $Element['text'] = $matches[1];
1210
1211 $extent += strlen($matches[0]);
1212
1213 $remainder = substr($remainder, $extent);
1214 }
1215 else
1216 {
1217 return;
1218 }
1219
1220 if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
1221 {
1222 $Element['attributes']['href'] = $matches[1];
1223
1224 if (isset($matches[2]))
1225 {
1226 $Element['attributes']['title'] = substr($matches[2], 1, - 1);
1227 }
1228
1229 $extent += strlen($matches[0]);
1230 }
1231 else
1232 {
1233 if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
1234 {
1235 $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
1236 $definition = strtolower($definition);
1237
1238 $extent += strlen($matches[0]);
1239 }
1240 else
1241 {
1242 $definition = strtolower($Element['text']);
1243 }
1244
1245 if ( ! isset($this->DefinitionData['Reference'][$definition]))
1246 {
1247 return;
1248 }
1249
1250 $Definition = $this->DefinitionData['Reference'][$definition];
1251
1252 $Element['attributes']['href'] = $Definition['url'];
1253 $Element['attributes']['title'] = $Definition['title'];
1254 }
1255
1256 $Element['attributes']['href'] = str_replace(array('&', '<'), array('&amp;', '&lt;'), $Element['attributes']['href']);
1257
1258 return array(
1259 'extent' => $extent,
1260 'element' => $Element,
1261 );
1262 }
1263
1264 protected function inlineMarkup($Excerpt)
1265 {
1266 if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false)
1267 {
1268 return;
1269 }
1270
1271 if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches))
1272 {
1273 return array(
1274 'markup' => $matches[0],
1275 'extent' => strlen($matches[0]),
1276 );
1277 }
1278
1279 if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
1280 {
1281 return array(
1282 'markup' => $matches[0],
1283 'extent' => strlen($matches[0]),
1284 );
1285 }
1286
1287 if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
1288 {
1289 return array(
1290 'markup' => $matches[0],
1291 'extent' => strlen($matches[0]),
1292 );
1293 }
1294 }
1295
1296 protected function inlineSpecialCharacter($Excerpt)
1297 {
1298 if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
1299 {
1300 return array(
1301 'markup' => '&amp;',
1302 'extent' => 1,
1303 );
1304 }
1305
1306 $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
1307
1308 if (isset($SpecialCharacter[$Excerpt['text'][0]]))
1309 {
1310 return array(
1311 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
1312 'extent' => 1,
1313 );
1314 }
1315 }
1316
1317 protected function inlineStrikethrough($Excerpt)
1318 {
1319 if ( ! isset($Excerpt['text'][1]))
1320 {
1321 return;
1322 }
1323
1324 if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
1325 {
1326 return array(
1327 'extent' => strlen($matches[0]),
1328 'element' => array(
1329 'name' => 'del',
1330 'text' => $matches[1],
1331 'handler' => 'line',
1332 ),
1333 );
1334 }
1335 }
1336
1337 protected function inlineUrl($Excerpt)
1338 {
1339 if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
1340 {
1341 return;
1342 }
1343
1344 if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
1345 {
1346 $Inline = array(
1347 'extent' => strlen($matches[0][0]),
1348 'position' => $matches[0][1],
1349 'element' => array(
1350 'name' => 'a',
1351 'text' => $matches[0][0],
1352 'attributes' => array(
1353 'href' => $matches[0][0],
1354 ),
1355 ),
1356 );
1357
1358 return $Inline;
1359 }
1360 }
1361
1362 protected function inlineUrlTag($Excerpt)
1363 {
1364 if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
1365 {
1366 $url = str_replace(array('&', '<'), array('&amp;', '&lt;'), $matches[1]);
1367
1368 return array(
1369 'extent' => strlen($matches[0]),
1370 'element' => array(
1371 'name' => 'a',
1372 'text' => $url,
1373 'attributes' => array(
1374 'href' => $url,
1375 ),
1376 ),
1377 );
1378 }
1379 }
1380
1381 # ~
1382
1383 protected function unmarkedText($text)
1384 {
1385 if ($this->breaksEnabled)
1386 {
1387 $text = preg_replace('/[ ]*\n/', "<br />\n", $text);
1388 }
1389 else
1390 {
1391 $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
1392 $text = str_replace(" \n", "\n", $text);
1393 }
1394
1395 return $text;
1396 }
1397
1398 #
1399 # Handlers
1400 #
1401
1402 protected function element(array $Element)
1403 {
1404 $markup = '<'.$Element['name'];
1405
1406 if (isset($Element['attributes']))
1407 {
1408 foreach ($Element['attributes'] as $name => $value)
1409 {
1410 if ($value === null)
1411 {
1412 continue;
1413 }
1414
1415 $markup .= ' '.$name.'="'.$value.'"';
1416 }
1417 }
1418
1419 if (isset($Element['text']))
1420 {
1421 $markup .= '>';
1422
1423 if (isset($Element['handler']))
1424 {
1425 $markup .= $this->{$Element['handler']}($Element['text']);
1426 }
1427 else
1428 {
1429 $markup .= $Element['text'];
1430 }
1431
1432 $markup .= '</'.$Element['name'].'>';
1433 }
1434 else
1435 {
1436 $markup .= ' />';
1437 }
1438
1439 return $markup;
1440 }
1441
1442 protected function elements(array $Elements)
1443 {
1444 $markup = '';
1445
1446 foreach ($Elements as $Element)
1447 {
1448 $markup .= "\n" . $this->element($Element);
1449 }
1450
1451 $markup .= "\n";
1452
1453 return $markup;
1454 }
1455
1456 # ~
1457
1458 protected function li($lines)
1459 {
1460 $markup = $this->lines($lines);
1461
1462 $trimmedMarkup = trim($markup);
1463
1464 if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>')
1465 {
1466 $markup = $trimmedMarkup;
1467 $markup = substr($markup, 3);
1468
1469 $position = strpos($markup, "</p>");
1470
1471 $markup = substr_replace($markup, '', $position, 4);
1472 }
1473
1474 return $markup;
1475 }
1476
1477 #
1478 # Deprecated Methods
1479 #
1480
1481 function parse($text)
1482 {
1483 $markup = $this->text($text);
1484
1485 return $markup;
1486 }
1487
1488 #
1489 # Static Methods
1490 #
1491
1492 static function instance($name = 'default')
1493 {
1494 if (isset(self::$instances[$name]))
1495 {
1496 return self::$instances[$name];
1497 }
1498
1499 $instance = new static();
1500
1501 self::$instances[$name] = $instance;
1502
1503 return $instance;
1504 }
1505
1506 private static $instances = array();
1507
1508 #
1509 # Fields
1510 #
1511
1512 protected $DefinitionData;
1513
1514 #
1515 # Read-Only
1516
1517 protected $specialCharacters = array(
1518 '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
1519 );
1520
1521 protected $StrongRegex = array(
1522 '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
1523 '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
1524 );
1525
1526 protected $EmRegex = array(
1527 '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
1528 '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
1529 );
1530
1531 protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
1532
1533 protected $voidElements = array(
1534 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
1535 );
1536
1537 protected $textLevelElements = array(
1538 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
1539 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
1540 'i', 'rp', 'del', 'code', 'strike', 'marquee',
1541 'q', 'rt', 'ins', 'font', 'strong',
1542 's', 'tt', 'kbd', 'mark',
1543 'u', 'xm', 'sub', 'nobr',
1544 'sup', 'ruby',
1545 'var', 'span',
1546 'wbr', 'time',
1547 );
1548 }
1549