Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/tutor-pro/vendor/erusev/parsedown/Parsedown.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
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 +