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