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