PluginProbe
QuadMenu – Mega Menu / 3.3.8
QuadMenu – Mega Menu v3.3.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 / redux / ReduxCore / inc / fields / raw / parsedown.php

parsedown.php in QuadMenu – Mega Menu 3.3.8, at redux/ReduxCore/inc/fields/raw/parsedown.php

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