1: <?php
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15: define( 'MARKDOWN_VERSION', "1.0.1o" );
16: define( 'MARKDOWNEXTRA_VERSION', "1.2.5" );
17:
18:
19:
20:
21:
22:
23:
24: @define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX', " />");
25:
26:
27: @define( 'MARKDOWN_TAB_WIDTH', 4 );
28:
29:
30: @define( 'MARKDOWN_FN_LINK_TITLE', "" );
31: @define( 'MARKDOWN_FN_BACKLINK_TITLE', "" );
32:
33:
34: @define( 'MARKDOWN_FN_LINK_CLASS', "" );
35: @define( 'MARKDOWN_FN_BACKLINK_CLASS', "" );
36:
37:
38:
39:
40:
41:
42:
43: @define( 'MARKDOWN_WP_POSTS', true );
44: @define( 'MARKDOWN_WP_COMMENTS', true );
45:
46:
47:
48:
49:
50: @define( 'MARKDOWN_PARSER_CLASS', 'MarkdownExtra_Parser' );
51:
52: function Markdown($text) {
53:
54:
55:
56:
57: static $parser;
58: if (!isset($parser)) {
59: $parser_class = MARKDOWN_PARSER_CLASS;
60: $parser = new $parser_class;
61: }
62:
63:
64: return $parser->transform($text);
65: }
66:
67:
68:
69:
70: 71: 72: 73: 74: 75: 76: 77:
78:
79: if (isset($wp_version)) {
80:
81:
82:
83:
84:
85:
86:
87: if (MARKDOWN_WP_POSTS) {
88: remove_filter('the_content', 'wpautop');
89: remove_filter('the_content_rss', 'wpautop');
90: remove_filter('the_excerpt', 'wpautop');
91: add_filter('the_content', 'mdwp_MarkdownPost', 6);
92: add_filter('the_content_rss', 'mdwp_MarkdownPost', 6);
93: add_filter('get_the_excerpt', 'mdwp_MarkdownPost', 6);
94: add_filter('get_the_excerpt', 'trim', 7);
95: add_filter('the_excerpt', 'mdwp_add_p');
96: add_filter('the_excerpt_rss', 'mdwp_strip_p');
97:
98: remove_filter('content_save_pre', 'balanceTags', 50);
99: remove_filter('excerpt_save_pre', 'balanceTags', 50);
100: add_filter('the_content', 'balanceTags', 50);
101: add_filter('get_the_excerpt', 'balanceTags', 9);
102: }
103:
104:
105: function mdwp_MarkdownPost($text) {
106: static $parser;
107: if (!$parser) {
108: $parser_class = MARKDOWN_PARSER_CLASS;
109: $parser = new $parser_class;
110: }
111: if (is_single() || is_page() || is_feed()) {
112: $parser->fn_id_prefix = "";
113: } else {
114: $parser->fn_id_prefix = get_the_ID() . ".";
115: }
116: return $parser->transform($text);
117: }
118:
119:
120:
121:
122:
123:
124: if (MARKDOWN_WP_COMMENTS) {
125: remove_filter('comment_text', 'wpautop', 30);
126: remove_filter('comment_text', 'make_clickable');
127: add_filter('pre_comment_content', 'Markdown', 6);
128: add_filter('pre_comment_content', 'mdwp_hide_tags', 8);
129: add_filter('pre_comment_content', 'mdwp_show_tags', 12);
130: add_filter('get_comment_text', 'Markdown', 6);
131: add_filter('get_comment_excerpt', 'Markdown', 6);
132: add_filter('get_comment_excerpt', 'mdwp_strip_p', 7);
133:
134: global $mdwp_hidden_tags, $mdwp_placeholders;
135: $mdwp_hidden_tags = explode(' ',
136: '<p> </p> <pre> </pre> <ol> </ol> <ul> </ul> <li> </li>');
137: $mdwp_placeholders = explode(' ', str_rot13(
138: 'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR '.
139: 'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli'));
140: }
141:
142: function mdwp_add_p($text) {
143: if (!preg_match('{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text)) {
144: $text = '<p>'.$text.'</p>';
145: $text = preg_replace('{\n{2,}}', "</p>\n\n<p>", $text);
146: }
147: return $text;
148: }
149:
150: function mdwp_strip_p($t) { return preg_replace('{</?p>}i', '', $t); }
151:
152: function mdwp_hide_tags($text) {
153: global $mdwp_hidden_tags, $mdwp_placeholders;
154: return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text);
155: }
156: function mdwp_show_tags($text) {
157: global $mdwp_hidden_tags, $mdwp_placeholders;
158: return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text);
159: }
160: }
161:
162:
163:
164:
165: function identify_modifier_markdown() {
166: return array(
167: 'name' => 'markdown',
168: 'type' => 'modifier',
169: 'nicename' => 'PHP Markdown Extra',
170: 'description' => 'A text-to-HTML conversion tool for web writers',
171: 'authors' => 'Michel Fortin and John Gruber',
172: 'licence' => 'GPL',
173: 'version' => MARKDOWNEXTRA_VERSION,
174: 'help' => '<a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://michelf.com/projects/php-markdown/">More...</a>',
175: );
176: }
177:
178:
179:
180:
181: function smarty_modifier_markdown($text) {
182: return Markdown($text);
183: }
184:
185:
186:
187:
188:
189:
190: if (strcasecmp(substr(__FILE__, -16), "classTextile.php") == 0) {
191:
192: @include_once 'smartypants.php';
193:
194: class Textile {
195: function TextileThis($text, $lite='', $encode='') {
196: if ($lite == '' && $encode == '') $text = Markdown($text);
197: if (function_exists('SmartyPants')) $text = SmartyPants($text);
198: return $text;
199: }
200:
201: function TextileRestricted($text, $lite='', $noimage='') {
202: return $this->TextileThis($text, $lite);
203: }
204:
205: function blockLite($text) { return $text; }
206: }
207: }
208:
209:
210:
211:
212:
213:
214:
215: class Markdown_Parser {
216:
217:
218:
219: var $nested_brackets_depth = 6;
220: var $nested_brackets_re;
221:
222: var $nested_url_parenthesis_depth = 4;
223: var $nested_url_parenthesis_re;
224:
225:
226: var $escape_chars = '\`*_{}[]()>#+-.!';
227: var $escape_chars_re;
228:
229:
230: var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
231: var $tab_width = MARKDOWN_TAB_WIDTH;
232:
233:
234: var $no_markup = false;
235: var $no_entities = false;
236:
237:
238: var $predef_urls = array();
239: var $predef_titles = array();
240:
241:
242: function Markdown_Parser() {
243:
244:
245:
246: $this->_initDetab();
247: $this->prepareItalicsAndBold();
248:
249: $this->nested_brackets_re =
250: str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth).
251: str_repeat('\])*', $this->nested_brackets_depth);
252:
253: $this->nested_url_parenthesis_re =
254: str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth).
255: str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth);
256:
257: $this->escape_chars_re = '['.preg_quote($this->escape_chars).']';
258:
259:
260: asort($this->document_gamut);
261: asort($this->block_gamut);
262: asort($this->span_gamut);
263: }
264:
265:
266:
267: var $urls = array();
268: var $titles = array();
269: var $html_hashes = array();
270:
271:
272: var $in_anchor = false;
273:
274:
275: function setup() {
276:
277:
278:
279:
280:
281: $this->urls = $this->predef_urls;
282: $this->titles = $this->predef_titles;
283: $this->html_hashes = array();
284:
285: $in_anchor = false;
286: }
287:
288: function teardown() {
289:
290:
291:
292:
293: $this->urls = array();
294: $this->titles = array();
295: $this->html_hashes = array();
296: }
297:
298:
299: function transform($text) {
300:
301:
302:
303:
304: $this->setup();
305:
306:
307: $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text);
308:
309:
310:
311: $text = preg_replace('{\r\n?}', "\n", $text);
312:
313:
314: $text .= "\n\n";
315:
316:
317: $text = $this->detab($text);
318:
319:
320: $text = $this->hashHTMLBlocks($text);
321:
322:
323:
324:
325:
326: $text = preg_replace('/^[ ]+$/m', '', $text);
327:
328:
329: foreach ($this->document_gamut as $method => $priority) {
330: $text = $this->$method($text);
331: }
332:
333: $this->teardown();
334:
335: return $text . "\n";
336: }
337:
338: var $document_gamut = array(
339:
340: "stripLinkDefinitions" => 20,
341:
342: "runBasicBlockGamut" => 30,
343: );
344:
345:
346: function stripLinkDefinitions($text) {
347:
348:
349:
350:
351: $less_than_tab = $this->tab_width - 1;
352:
353:
354: $text = preg_replace_callback('{
355: ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1
356: [ ]*
357: \n? # maybe *one* newline
358: [ ]*
359: (?:
360: <(.+?)> # url = $2
361: |
362: (\S+?) # url = $3
363: )
364: [ ]*
365: \n? # maybe one newline
366: [ ]*
367: (?:
368: (?<=\s) # lookbehind for whitespace
369: ["(]
370: (.*?) # title = $4
371: [")]
372: [ ]*
373: )? # title is optional
374: (?:\n+|\Z)
375: }xm',
376: array(&$this, '_stripLinkDefinitions_callback'),
377: $text);
378: return $text;
379: }
380: function _stripLinkDefinitions_callback($matches) {
381: $link_id = strtolower($matches[1]);
382: $url = $matches[2] == '' ? $matches[3] : $matches[2];
383: $this->urls[$link_id] = $url;
384: $this->titles[$link_id] =& $matches[4];
385: return '';
386: }
387:
388:
389: function hashHTMLBlocks($text) {
390: if ($this->no_markup) return $text;
391:
392: $less_than_tab = $this->tab_width - 1;
393:
394:
395:
396:
397:
398:
399:
400:
401:
402:
403:
404:
405:
406:
407: $block_tags_a_re = 'ins|del';
408: $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'.
409: 'script|noscript|form|fieldset|iframe|math';
410:
411:
412: $nested_tags_level = 4;
413: $attr = '
414: (?> # optional tag attributes
415: \s # starts with whitespace
416: (?>
417: [^>"/]+ # text outside quotes
418: |
419: /+(?!>) # slash not followed by ">"
420: |
421: "[^"]*" # text inside double quotes (tolerate ">")
422: |
423: \'[^\']*\' # text inside single quotes (tolerate ">")
424: )*
425: )?
426: ';
427: $content =
428: str_repeat('
429: (?>
430: [^<]+ # content without tag
431: |
432: <\2 # nested opening tag
433: '.$attr.' # attributes
434: (?>
435: />
436: |
437: >', $nested_tags_level).
438: '.*?'.
439: str_repeat('
440: </\2\s*> # closing nested tag
441: )
442: |
443: <(?!/\2\s*> # other tags with a different name
444: )
445: )*',
446: $nested_tags_level);
447: $content2 = str_replace('\2', '\3', $content);
448:
449:
450:
451:
452:
453:
454:
455:
456:
457:
458:
459:
460: $text = preg_replace_callback('{(?>
461: (?>
462: (?<=\n\n) # Starting after a blank line
463: | # or
464: \A\n? # the beginning of the doc
465: )
466: ( # save in $1
467:
468: # Match from `\n<tag>` to `</tag>\n`, handling nested tags
469: # in between.
470:
471: [ ]{0,'.$less_than_tab.'}
472: <('.$block_tags_b_re.')# start tag = $2
473: '.$attr.'> # attributes followed by > and \n
474: '.$content.' # content, support nesting
475: </\2> # the matching end tag
476: [ ]* # trailing spaces/tabs
477: (?=\n+|\Z) # followed by a newline or end of document
478:
479: | # Special version for tags of group a.
480:
481: [ ]{0,'.$less_than_tab.'}
482: <('.$block_tags_a_re.')# start tag = $3
483: '.$attr.'>[ ]*\n # attributes followed by >
484: '.$content2.' # content, support nesting
485: </\3> # the matching end tag
486: [ ]* # trailing spaces/tabs
487: (?=\n+|\Z) # followed by a newline or end of document
488:
489: | # Special case just for <hr />. It was easier to make a special
490: # case than to make the other regex more complicated.
491:
492: [ ]{0,'.$less_than_tab.'}
493: <(hr) # start tag = $2
494: '.$attr.' # attributes
495: /?> # the matching end tag
496: [ ]*
497: (?=\n{2,}|\Z) # followed by a blank line or end of document
498:
499: | # Special case for standalone HTML comments:
500:
501: [ ]{0,'.$less_than_tab.'}
502: (?s:
503: <!-- .*? -->
504: )
505: [ ]*
506: (?=\n{2,}|\Z) # followed by a blank line or end of document
507:
508: | # PHP and ASP-style processor instructions (<? and <%)
509:
510: [ ]{0,'.$less_than_tab.'}
511: (?s:
512: <([?%]) # $2
513: .*?
514: \2>
515: )
516: [ ]*
517: (?=\n{2,}|\Z) # followed by a blank line or end of document
518:
519: )
520: )}Sxmi',
521: array(&$this, '_hashHTMLBlocks_callback'),
522: $text);
523:
524: return $text;
525: }
526: function _hashHTMLBlocks_callback($matches) {
527: $text = $matches[1];
528: $key = $this->hashBlock($text);
529: return "\n\n$key\n\n";
530: }
531:
532:
533: function hashPart($text, $boundary = 'X') {
534:
535:
536:
537:
538:
539:
540:
541:
542:
543:
544:
545:
546: $text = $this->unhash($text);
547:
548:
549: static $i = 0;
550: $key = "$boundary\x1A" . ++$i . $boundary;
551: $this->html_hashes[$key] = $text;
552: return $key;
553: }
554:
555:
556: function hashBlock($text) {
557:
558:
559:
560: return $this->hashPart($text, 'B');
561: }
562:
563:
564: var $block_gamut = array(
565:
566:
567:
568:
569: "doHeaders" => 10,
570: "doHorizontalRules" => 20,
571:
572: "doLists" => 40,
573: "doCodeBlocks" => 50,
574: "doBlockQuotes" => 60,
575: );
576:
577: function runBlockGamut($text) {
578:
579:
580:
581:
582:
583:
584:
585:
586: $text = $this->hashHTMLBlocks($text);
587:
588: return $this->runBasicBlockGamut($text);
589: }
590:
591: function runBasicBlockGamut($text) {
592:
593:
594:
595:
596:
597: foreach ($this->block_gamut as $method => $priority) {
598: $text = $this->$method($text);
599: }
600:
601:
602: $text = $this->formParagraphs($text);
603:
604: return $text;
605: }
606:
607:
608: function doHorizontalRules($text) {
609:
610: return preg_replace(
611: '{
612: ^[ ]{0,3} # Leading space
613: ([-*_]) # $1: First marker
614: (?> # Repeated marker group
615: [ ]{0,2} # Zero, one, or two spaces.
616: \1 # Marker character
617: ){2,} # Group repeated at least twice
618: [ ]* # Tailing spaces
619: $ # End of line.
620: }mx',
621: "\n".$this->hashBlock("<hr$this->empty_element_suffix")."\n",
622: $text);
623: }
624:
625:
626: var $span_gamut = array(
627:
628:
629:
630:
631:
632:
633: "parseSpan" => -30,
634:
635:
636:
637: "doImages" => 10,
638: "doAnchors" => 20,
639:
640:
641:
642:
643: "doAutoLinks" => 30,
644: "encodeAmpsAndAngles" => 40,
645:
646: "doItalicsAndBold" => 50,
647: "doHardBreaks" => 60,
648: );
649:
650: function runSpanGamut($text) {
651:
652:
653:
654: foreach ($this->span_gamut as $method => $priority) {
655: $text = $this->$method($text);
656: }
657:
658: return $text;
659: }
660:
661:
662: function doHardBreaks($text) {
663:
664: return preg_replace_callback('/ {2,}\n/',
665: array(&$this, '_doHardBreaks_callback'), $text);
666: }
667: function _doHardBreaks_callback($matches) {
668: return $this->hashPart("<br$this->empty_element_suffix\n");
669: }
670:
671:
672: function doAnchors($text) {
673:
674:
675:
676: if ($this->in_anchor) return $text;
677: $this->in_anchor = true;
678:
679:
680:
681:
682: $text = preg_replace_callback('{
683: ( # wrap whole match in $1
684: \[
685: ('.$this->nested_brackets_re.') # link text = $2
686: \]
687:
688: [ ]? # one optional space
689: (?:\n[ ]*)? # one optional newline followed by spaces
690:
691: \[
692: (.*?) # id = $3
693: \]
694: )
695: }xs',
696: array(&$this, '_doAnchors_reference_callback'), $text);
697:
698:
699:
700:
701: $text = preg_replace_callback('{
702: ( # wrap whole match in $1
703: \[
704: ('.$this->nested_brackets_re.') # link text = $2
705: \]
706: \( # literal paren
707: [ \n]*
708: (?:
709: <(.+?)> # href = $3
710: |
711: ('.$this->nested_url_parenthesis_re.') # href = $4
712: )
713: [ \n]*
714: ( # $5
715: ([\'"]) # quote char = $6
716: (.*?) # Title = $7
717: \6 # matching quote
718: [ \n]* # ignore any spaces/tabs between closing quote and )
719: )? # title is optional
720: \)
721: )
722: }xs',
723: array(&$this, '_doAnchors_inline_callback'), $text);
724:
725:
726:
727:
728:
729:
730: $text = preg_replace_callback('{
731: ( # wrap whole match in $1
732: \[
733: ([^\[\]]+) # link text = $2; can\'t contain [ or ]
734: \]
735: )
736: }xs',
737: array(&$this, '_doAnchors_reference_callback'), $text);
738:
739: $this->in_anchor = false;
740: return $text;
741: }
742: function _doAnchors_reference_callback($matches) {
743: $whole_match = $matches[1];
744: $link_text = $matches[2];
745: $link_id =& $matches[3];
746:
747: if ($link_id == "") {
748:
749: $link_id = $link_text;
750: }
751:
752:
753: $link_id = strtolower($link_id);
754: $link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
755:
756: if (isset($this->urls[$link_id])) {
757: $url = $this->urls[$link_id];
758: $url = URL::to($url);
759: $url = $this->encodeAttribute($url);
760:
761: $result = "<a href=\"$url\"";
762: if ( isset( $this->titles[$link_id] ) ) {
763: $title = $this->titles[$link_id];
764: $title = $this->encodeAttribute($title);
765: $result .= " title=\"$title\"";
766: }
767:
768: $link_text = $this->runSpanGamut($link_text);
769: $result .= ">$link_text</a>";
770: $result = $this->hashPart($result);
771: }
772: else {
773: $result = $whole_match;
774: }
775: return $result;
776: }
777: function _doAnchors_inline_callback($matches) {
778: $whole_match = $matches[1];
779: $link_text = $this->runSpanGamut($matches[2]);
780: $url = $matches[3] == '' ? $matches[4] : $matches[3];
781: $title =& $matches[7];
782:
783: $url = URL::to($url);
784: $url = $this->encodeAttribute($url);
785:
786: $result = "<a href=\"$url\"";
787: if (isset($title)) {
788: $title = $this->encodeAttribute($title);
789: $result .= " title=\"$title\"";
790: }
791:
792: $link_text = $this->runSpanGamut($link_text);
793: $result .= ">$link_text</a>";
794:
795: return $this->hashPart($result);
796: }
797:
798:
799: function doImages($text) {
800:
801:
802:
803:
804:
805:
806: $text = preg_replace_callback('{
807: ( # wrap whole match in $1
808: !\[
809: ('.$this->nested_brackets_re.') # alt text = $2
810: \]
811:
812: [ ]? # one optional space
813: (?:\n[ ]*)? # one optional newline followed by spaces
814:
815: \[
816: (.*?) # id = $3
817: \]
818:
819: )
820: }xs',
821: array(&$this, '_doImages_reference_callback'), $text);
822:
823:
824:
825:
826:
827: $text = preg_replace_callback('{
828: ( # wrap whole match in $1
829: !\[
830: ('.$this->nested_brackets_re.') # alt text = $2
831: \]
832: \s? # One optional whitespace character
833: \( # literal paren
834: [ \n]*
835: (?:
836: <(\S*)> # src url = $3
837: |
838: ('.$this->nested_url_parenthesis_re.') # src url = $4
839: )
840: [ \n]*
841: ( # $5
842: ([\'"]) # quote char = $6
843: (.*?) # title = $7
844: \6 # matching quote
845: [ \n]*
846: )? # title is optional
847: \)
848: )
849: }xs',
850: array(&$this, '_doImages_inline_callback'), $text);
851:
852: return $text;
853: }
854: function _doImages_reference_callback($matches) {
855: $whole_match = $matches[1];
856: $alt_text = $matches[2];
857: $link_id = strtolower($matches[3]);
858:
859: if ($link_id == "") {
860: $link_id = strtolower($alt_text);
861: }
862:
863: $alt_text = $this->encodeAttribute($alt_text);
864: if (isset($this->urls[$link_id])) {
865: $url = $this->encodeAttribute($this->urls[$link_id]);
866: $result = "<img src=\"$url\" alt=\"$alt_text\"";
867: if (isset($this->titles[$link_id])) {
868: $title = $this->titles[$link_id];
869: $title = $this->encodeAttribute($title);
870: $result .= " title=\"$title\"";
871: }
872: $result .= $this->empty_element_suffix;
873: $result = $this->hashPart($result);
874: }
875: else {
876:
877: $result = $whole_match;
878: }
879:
880: return $result;
881: }
882: function _doImages_inline_callback($matches) {
883: $whole_match = $matches[1];
884: $alt_text = $matches[2];
885: $url = $matches[3] == '' ? $matches[4] : $matches[3];
886: $title =& $matches[7];
887:
888: $alt_text = $this->encodeAttribute($alt_text);
889: $url = $this->encodeAttribute($url);
890: $result = "<img src=\"$url\" alt=\"$alt_text\"";
891: if (isset($title)) {
892: $title = $this->encodeAttribute($title);
893: $result .= " title=\"$title\"";
894: }
895: $result .= $this->empty_element_suffix;
896:
897: return $this->hashPart($result);
898: }
899:
900:
901: function ($text) {
902:
903:
904:
905:
906:
907:
908:
909: $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
910: array(&$this, '_doHeaders_callback_setext'), $text);
911:
912:
913:
914:
915:
916:
917:
918:
919: $text = preg_replace_callback('{
920: ^(\#{1,6}) # $1 = string of #\'s
921: [ ]*
922: (.+?) # $2 = Header text
923: [ ]*
924: \#* # optional closing #\'s (not counted)
925: \n+
926: }xm',
927: array(&$this, '_doHeaders_callback_atx'), $text);
928:
929: return $text;
930: }
931: function ($matches) {
932:
933: if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1]))
934: return $matches[0];
935:
936: $level = $matches[2]{0} == '=' ? 1 : 2;
937: $block = "<h$level>".$this->runSpanGamut($matches[1])."</h$level>";
938: return "\n" . $this->hashBlock($block) . "\n\n";
939: }
940: function ($matches) {
941: $level = strlen($matches[1]);
942: $block = "<h$level>".$this->runSpanGamut($matches[2])."</h$level>";
943: return "\n" . $this->hashBlock($block) . "\n\n";
944: }
945:
946:
947: function doLists($text) {
948:
949:
950:
951: $less_than_tab = $this->tab_width - 1;
952:
953:
954: $marker_ul_re = '[*+-]';
955: $marker_ol_re = '\d+[\.]';
956: $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
957:
958: $markers_relist = array(
959: $marker_ul_re => $marker_ol_re,
960: $marker_ol_re => $marker_ul_re,
961: );
962:
963: foreach ($markers_relist as $marker_re => $other_marker_re) {
964:
965: $whole_list_re = '
966: ( # $1 = whole list
967: ( # $2
968: ([ ]{0,'.$less_than_tab.'}) # $3 = number of spaces
969: ('.$marker_re.') # $4 = first list item marker
970: [ ]+
971: )
972: (?s:.+?)
973: ( # $5
974: \z
975: |
976: \n{2,}
977: (?=\S)
978: (?! # Negative lookahead for another list item marker
979: [ ]*
980: '.$marker_re.'[ ]+
981: )
982: |
983: (?= # Lookahead for another kind of list
984: \n
985: \3 # Must have the same indentation
986: '.$other_marker_re.'[ ]+
987: )
988: )
989: )
990: ';
991:
992:
993:
994:
995: if ($this->list_level) {
996: $text = preg_replace_callback('{
997: ^
998: '.$whole_list_re.'
999: }mx',
1000: array(&$this, '_doLists_callback'), $text);
1001: }
1002: else {
1003: $text = preg_replace_callback('{
1004: (?:(?<=\n)\n|\A\n?) # Must eat the newline
1005: '.$whole_list_re.'
1006: }mx',
1007: array(&$this, '_doLists_callback'), $text);
1008: }
1009: }
1010:
1011: return $text;
1012: }
1013: function _doLists_callback($matches) {
1014:
1015: $marker_ul_re = '[*+-]';
1016: $marker_ol_re = '\d+[\.]';
1017: $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
1018:
1019: $list = $matches[1];
1020: $list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol";
1021:
1022: $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re );
1023:
1024: $list .= "\n";
1025: $result = $this->processListItems($list, $marker_any_re);
1026:
1027: $result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>");
1028: return "\n". $result ."\n\n";
1029: }
1030:
1031: var $list_level = 0;
1032:
1033: function processListItems($list_str, $marker_any_re) {
1034:
1035:
1036:
1037:
1038:
1039:
1040:
1041:
1042:
1043:
1044:
1045:
1046:
1047:
1048:
1049:
1050:
1051:
1052:
1053:
1054:
1055:
1056:
1057:
1058:
1059: $this->list_level++;
1060:
1061:
1062: $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
1063:
1064: $list_str = preg_replace_callback('{
1065: (\n)? # leading line = $1
1066: (^[ ]*) # leading whitespace = $2
1067: ('.$marker_any_re.' # list marker and space = $3
1068: (?:[ ]+|(?=\n)) # space only required if item is not empty
1069: )
1070: ((?s:.*?)) # list item text = $4
1071: (?:(\n+(?=\n))|\n) # tailing blank line = $5
1072: (?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n))))
1073: }xm',
1074: array(&$this, '_processListItems_callback'), $list_str);
1075:
1076: $this->list_level--;
1077: return $list_str;
1078: }
1079: function _processListItems_callback($matches) {
1080: $item = $matches[4];
1081: $leading_line =& $matches[1];
1082: $leading_space =& $matches[2];
1083: $marker_space = $matches[3];
1084: $tailing_blank_line =& $matches[5];
1085:
1086: if ($leading_line || $tailing_blank_line ||
1087: preg_match('/\n{2,}/', $item))
1088: {
1089:
1090: $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item;
1091: $item = $this->runBlockGamut($this->outdent($item)."\n");
1092: }
1093: else {
1094:
1095: $item = $this->doLists($this->outdent($item));
1096: $item = preg_replace('/\n+$/', '', $item);
1097: $item = $this->runSpanGamut($item);
1098: }
1099:
1100: return "<li>" . $item . "</li>\n";
1101: }
1102:
1103:
1104: function doCodeBlocks($text) {
1105:
1106:
1107:
1108: $text = preg_replace_callback('{
1109: (?:\n\n|\A\n?)
1110: ( # $1 = the code block -- one or more lines, starting with a space/tab
1111: (?>
1112: [ ]{'.$this->tab_width.'} # Lines must start with a tab or a tab-width of spaces
1113: .*\n+
1114: )+
1115: )
1116: ((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
1117: }xm',
1118: array(&$this, '_doCodeBlocks_callback'), $text);
1119:
1120: return $text;
1121: }
1122: function _doCodeBlocks_callback($matches) {
1123: $codeblock = $matches[1];
1124:
1125: $codeblock = $this->outdent($codeblock);
1126: $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
1127:
1128:
1129: $codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
1130:
1131: $codeblock = "<pre class=\"prettyprint linenums\">$codeblock\n</pre>";
1132: return "\n\n".$this->hashBlock($codeblock)."\n\n";
1133: }
1134:
1135:
1136: function makeCodeSpan($code) {
1137:
1138:
1139:
1140: $code = htmlspecialchars(trim($code), ENT_NOQUOTES);
1141: return $this->hashPart("<code>$code</code>");
1142: }
1143:
1144:
1145: var $em_relist = array(
1146: '' => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S|$)(?![\.,:;]\s)',
1147: '*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
1148: '_' => '(?<=\S|^)(?<!_)_(?!_)',
1149: );
1150: var $strong_relist = array(
1151: '' => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S|$)(?![\.,:;]\s)',
1152: '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
1153: '__' => '(?<=\S|^)(?<!_)__(?!_)',
1154: );
1155: var $em_strong_relist = array(
1156: '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S|$)(?![\.,:;]\s)',
1157: '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
1158: '___' => '(?<=\S|^)(?<!_)___(?!_)',
1159: );
1160: var $em_strong_prepared_relist;
1161:
1162: function prepareItalicsAndBold() {
1163:
1164:
1165:
1166:
1167: foreach ($this->em_relist as $em => $em_re) {
1168: foreach ($this->strong_relist as $strong => $strong_re) {
1169:
1170: $token_relist = array();
1171: if (isset($this->em_strong_relist["$em$strong"])) {
1172: $token_relist[] = $this->em_strong_relist["$em$strong"];
1173: }
1174: $token_relist[] = $em_re;
1175: $token_relist[] = $strong_re;
1176:
1177:
1178: $token_re = '{('. implode('|', $token_relist) .')}';
1179: $this->em_strong_prepared_relist["$em$strong"] = $token_re;
1180: }
1181: }
1182: }
1183:
1184: function doItalicsAndBold($text) {
1185: $token_stack = array('');
1186: $text_stack = array('');
1187: $em = '';
1188: $strong = '';
1189: $tree_char_em = false;
1190:
1191: while (1) {
1192:
1193:
1194:
1195:
1196: $token_re = $this->em_strong_prepared_relist["$em$strong"];
1197:
1198:
1199:
1200:
1201:
1202: $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
1203: $text_stack[0] .= $parts[0];
1204: $token =& $parts[1];
1205: $text =& $parts[2];
1206:
1207: if (empty($token)) {
1208:
1209:
1210: while ($token_stack[0]) {
1211: $text_stack[1] .= array_shift($token_stack);
1212: $text_stack[0] .= array_shift($text_stack);
1213: }
1214: break;
1215: }
1216:
1217: $token_len = strlen($token);
1218: if ($tree_char_em) {
1219:
1220: if ($token_len == 3) {
1221:
1222: array_shift($token_stack);
1223: $span = array_shift($text_stack);
1224: $span = $this->runSpanGamut($span);
1225: $span = "<strong><em>$span</em></strong>";
1226: $text_stack[0] .= $this->hashPart($span);
1227: $em = '';
1228: $strong = '';
1229: } else {
1230:
1231:
1232: $token_stack[0] = str_repeat($token{0}, 3-$token_len);
1233: $tag = $token_len == 2 ? "strong" : "em";
1234: $span = $text_stack[0];
1235: $span = $this->runSpanGamut($span);
1236: $span = "<$tag>$span</$tag>";
1237: $text_stack[0] = $this->hashPart($span);
1238: $$tag = '';
1239: }
1240: $tree_char_em = false;
1241: } else if ($token_len == 3) {
1242: if ($em) {
1243:
1244:
1245: for ($i = 0; $i < 2; ++$i) {
1246: $shifted_token = array_shift($token_stack);
1247: $tag = strlen($shifted_token) == 2 ? "strong" : "em";
1248: $span = array_shift($text_stack);
1249: $span = $this->runSpanGamut($span);
1250: $span = "<$tag>$span</$tag>";
1251: $text_stack[0] .= $this->hashPart($span);
1252: $$tag = '';
1253: }
1254: } else {
1255:
1256:
1257: $em = $token{0};
1258: $strong = "$em$em";
1259: array_unshift($token_stack, $token);
1260: array_unshift($text_stack, '');
1261: $tree_char_em = true;
1262: }
1263: } else if ($token_len == 2) {
1264: if ($strong) {
1265:
1266: if (strlen($token_stack[0]) == 1) {
1267: $text_stack[1] .= array_shift($token_stack);
1268: $text_stack[0] .= array_shift($text_stack);
1269: }
1270:
1271: array_shift($token_stack);
1272: $span = array_shift($text_stack);
1273: $span = $this->runSpanGamut($span);
1274: $span = "<strong>$span</strong>";
1275: $text_stack[0] .= $this->hashPart($span);
1276: $strong = '';
1277: } else {
1278: array_unshift($token_stack, $token);
1279: array_unshift($text_stack, '');
1280: $strong = $token;
1281: }
1282: } else {
1283:
1284: if ($em) {
1285: if (strlen($token_stack[0]) == 1) {
1286:
1287: array_shift($token_stack);
1288: $span = array_shift($text_stack);
1289: $span = $this->runSpanGamut($span);
1290: $span = "<em>$span</em>";
1291: $text_stack[0] .= $this->hashPart($span);
1292: $em = '';
1293: } else {
1294: $text_stack[0] .= $token;
1295: }
1296: } else {
1297: array_unshift($token_stack, $token);
1298: array_unshift($text_stack, '');
1299: $em = $token;
1300: }
1301: }
1302: }
1303: return $text_stack[0];
1304: }
1305:
1306:
1307: function doBlockQuotes($text) {
1308: $text = preg_replace_callback('/
1309: ( # Wrap whole match in $1
1310: (?>
1311: ^[ ]*>[ ]? # ">" at the start of a line
1312: .+\n # rest of the first line
1313: (.+\n)* # subsequent consecutive lines
1314: \n* # blanks
1315: )+
1316: )
1317: /xm',
1318: array(&$this, '_doBlockQuotes_callback'), $text);
1319:
1320: return $text;
1321: }
1322: function _doBlockQuotes_callback($matches) {
1323: $bq = $matches[1];
1324:
1325: $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
1326: $bq = $this->runBlockGamut($bq);
1327:
1328: $bq = preg_replace('/^/m', " ", $bq);
1329:
1330:
1331: $bq = preg_replace_callback('{(\s*<pre>.+?</pre>)}sx',
1332: array(&$this, '_doBlockQuotes_callback2'), $bq);
1333:
1334: return "\n". $this->hashBlock("<blockquote>\n$bq\n</blockquote>")."\n\n";
1335: }
1336: function _doBlockQuotes_callback2($matches) {
1337: $pre = $matches[1];
1338: $pre = preg_replace('/^ /m', '', $pre);
1339: return $pre;
1340: }
1341:
1342:
1343: function formParagraphs($text) {
1344:
1345:
1346:
1347:
1348:
1349: $text = preg_replace('/\A\n+|\n+\z/', '', $text);
1350:
1351: $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
1352:
1353:
1354:
1355:
1356: foreach ($grafs as $key => $value) {
1357: if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
1358:
1359: $value = $this->runSpanGamut($value);
1360: $value = preg_replace('/^([ ]*)/', "<p>", $value);
1361: $value .= "</p>";
1362: $grafs[$key] = $this->unhash($value);
1363: }
1364: else {
1365:
1366:
1367: $graf = $value;
1368: $block = $this->html_hashes[$graf];
1369: $graf = $block;
1370:
1371:
1372:
1373:
1374:
1375:
1376:
1377:
1378:
1379:
1380:
1381:
1382:
1383:
1384:
1385:
1386:
1387:
1388:
1389:
1390:
1391:
1392:
1393:
1394:
1395:
1396:
1397:
1398:
1399:
1400:
1401:
1402:
1403:
1404:
1405: $grafs[$key] = $graf;
1406: }
1407: }
1408:
1409: return implode("\n\n", $grafs);
1410: }
1411:
1412:
1413: function encodeAttribute($text) {
1414:
1415:
1416:
1417:
1418: $text = $this->encodeAmpsAndAngles($text);
1419: $text = str_replace('"', '"', $text);
1420: return $text;
1421: }
1422:
1423:
1424: function encodeAmpsAndAngles($text) {
1425:
1426:
1427:
1428:
1429:
1430: if ($this->no_entities) {
1431: $text = str_replace('&', '&', $text);
1432: } else {
1433:
1434:
1435: $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',
1436: '&', $text);;
1437: }
1438:
1439: $text = str_replace('<', '<', $text);
1440:
1441: return $text;
1442: }
1443:
1444:
1445: function doAutoLinks($text) {
1446: $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i',
1447: array(&$this, '_doAutoLinks_url_callback'), $text);
1448:
1449:
1450: $text = preg_replace_callback('{
1451: <
1452: (?:mailto:)?
1453: (
1454: (?:
1455: [-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+
1456: |
1457: ".*?"
1458: )
1459: \@
1460: (?:
1461: [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
1462: |
1463: \[[\d.a-fA-F:]+\] # IPv4 & IPv6
1464: )
1465: )
1466: >
1467: }xi',
1468: array(&$this, '_doAutoLinks_email_callback'), $text);
1469:
1470: return $text;
1471: }
1472: function _doAutoLinks_url_callback($matches) {
1473: $url = $this->encodeAttribute($matches[1]);
1474: $link = "<a href=\"$url\">$url</a>";
1475: return $this->hashPart($link);
1476: }
1477: function _doAutoLinks_email_callback($matches) {
1478: $address = $matches[1];
1479: $link = $this->encodeEmailAddress($address);
1480: return $this->hashPart($link);
1481: }
1482:
1483:
1484: function encodeEmailAddress($addr) {
1485:
1486:
1487:
1488:
1489:
1490:
1491:
1492:
1493:
1494:
1495:
1496:
1497:
1498:
1499:
1500: $addr = "mailto:" . $addr;
1501: $chars = preg_split('/(?<!^)(?!$)/', $addr);
1502: $seed = (int)abs(crc32($addr) / strlen($addr));
1503:
1504: foreach ($chars as $key => $char) {
1505: $ord = ord($char);
1506:
1507: if ($ord < 128) {
1508: $r = ($seed * (1 + $key)) % 100;
1509:
1510:
1511: if ($r > 90 && $char != '@') ;
1512: else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';';
1513: else $chars[$key] = '&#'.$ord.';';
1514: }
1515: }
1516:
1517: $addr = implode('', $chars);
1518: $text = implode('', array_slice($chars, 7));
1519: $addr = "<a href=\"$addr\">$text</a>";
1520:
1521: return $addr;
1522: }
1523:
1524:
1525: function parseSpan($str) {
1526:
1527:
1528:
1529:
1530: $output = '';
1531:
1532: $span_re = '{
1533: (
1534: \\\\'.$this->escape_chars_re.'
1535: |
1536: (?<![`\\\\])
1537: `+ # code span marker
1538: '.( $this->no_markup ? '' : '
1539: |
1540: <!-- .*? --> # comment
1541: |
1542: <\?.*?\?> | <%.*?%> # processing instruction
1543: |
1544: <[/!$]?[-a-zA-Z0-9:_]+ # regular tags
1545: (?>
1546: \s
1547: (?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
1548: )?
1549: >
1550: ').'
1551: )
1552: }xs';
1553:
1554: while (1) {
1555:
1556:
1557:
1558:
1559:
1560: $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
1561:
1562:
1563: if ($parts[0] != "") {
1564: $output .= $parts[0];
1565: }
1566:
1567:
1568: if (isset($parts[1])) {
1569: $output .= $this->handleSpanToken($parts[1], $parts[2]);
1570: $str = $parts[2];
1571: }
1572: else {
1573: break;
1574: }
1575: }
1576:
1577: return $output;
1578: }
1579:
1580:
1581: function handleSpanToken($token, &$str) {
1582:
1583:
1584:
1585:
1586: switch ($token{0}) {
1587: case "\\":
1588: return $this->hashPart("&#". ord($token{1}). ";");
1589: case "`":
1590:
1591: if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm',
1592: $str, $matches))
1593: {
1594: $str = $matches[2];
1595: $codespan = $this->makeCodeSpan($matches[1]);
1596: return $this->hashPart($codespan);
1597: }
1598: return $token;
1599: default:
1600: return $this->hashPart($token);
1601: }
1602: }
1603:
1604:
1605: function outdent($text) {
1606:
1607:
1608:
1609: return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text);
1610: }
1611:
1612:
1613:
1614:
1615: var $utf8_strlen = 'mb_strlen';
1616:
1617: function detab($text) {
1618:
1619:
1620:
1621:
1622:
1623:
1624:
1625: $text = preg_replace_callback('/^.*\t.*$/m',
1626: array(&$this, '_detab_callback'), $text);
1627:
1628: return $text;
1629: }
1630: function _detab_callback($matches) {
1631: $line = $matches[0];
1632: $strlen = $this->utf8_strlen;
1633:
1634:
1635: $blocks = explode("\t", $line);
1636:
1637: $line = $blocks[0];
1638: unset($blocks[0]);
1639: foreach ($blocks as $block) {
1640:
1641: $amount = $this->tab_width -
1642: $strlen($line, 'UTF-8') % $this->tab_width;
1643: $line .= str_repeat(" ", $amount) . $block;
1644: }
1645: return $line;
1646: }
1647: function _initDetab() {
1648:
1649:
1650:
1651:
1652:
1653:
1654: if (function_exists($this->utf8_strlen)) return;
1655: $this->utf8_strlen = create_function('$text', 'return preg_match_all(
1656: "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/",
1657: $text, $m);');
1658: }
1659:
1660:
1661: function unhash($text) {
1662:
1663:
1664:
1665: return preg_replace_callback('/(.)\x1A[0-9]+\1/',
1666: array(&$this, '_unhash_callback'), $text);
1667: }
1668: function _unhash_callback($matches) {
1669: return $this->html_hashes[$matches[0]];
1670: }
1671:
1672: }
1673:
1674:
1675:
1676:
1677:
1678:
1679: class extends Markdown_Parser {
1680:
1681:
1682: var $fn_id_prefix = "";
1683:
1684:
1685: var $fn_link_title = MARKDOWN_FN_LINK_TITLE;
1686: var $fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE;
1687:
1688:
1689: var $fn_link_class = MARKDOWN_FN_LINK_CLASS;
1690: var $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS;
1691:
1692:
1693: var $predef_abbr = array();
1694:
1695:
1696: function () {
1697:
1698:
1699:
1700:
1701:
1702: $this->escape_chars .= ':|';
1703:
1704:
1705:
1706: $this->document_gamut += array(
1707: "doFencedCodeBlocks" => 5,
1708: "stripFootnotes" => 15,
1709: "stripAbbreviations" => 25,
1710: "appendFootnotes" => 50,
1711: );
1712: $this->block_gamut += array(
1713: "doFencedCodeBlocks" => 5,
1714: "doTables" => 15,
1715: "doDefLists" => 45,
1716: );
1717: $this->span_gamut += array(
1718: "doFootnotes" => 5,
1719: "doAbbreviations" => 70,
1720: );
1721:
1722: parent::Markdown_Parser();
1723: }
1724:
1725:
1726:
1727: var = array();
1728: var = array();
1729: var $abbr_desciptions = array();
1730: var $abbr_word_re = '';
1731:
1732:
1733: var = 1;
1734:
1735:
1736: function setup() {
1737:
1738:
1739:
1740: parent::setup();
1741:
1742: $this->footnotes = array();
1743: $this->footnotes_ordered = array();
1744: $this->abbr_desciptions = array();
1745: $this->abbr_word_re = '';
1746: $this->footnote_counter = 1;
1747:
1748: foreach ($this->predef_abbr as $abbr_word => $abbr_desc) {
1749: if ($this->abbr_word_re)
1750: $this->abbr_word_re .= '|';
1751: $this->abbr_word_re .= preg_quote($abbr_word);
1752: $this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
1753: }
1754: }
1755:
1756: function teardown() {
1757:
1758:
1759:
1760: $this->footnotes = array();
1761: $this->footnotes_ordered = array();
1762: $this->abbr_desciptions = array();
1763: $this->abbr_word_re = '';
1764:
1765: parent::teardown();
1766: }
1767:
1768:
1769:
1770:
1771:
1772: var $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend';
1773:
1774:
1775: var $context_block_tags_re = 'script|noscript|math|ins|del';
1776:
1777:
1778: var $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address';
1779:
1780:
1781:
1782: var $clean_tags_re = 'script|math';
1783:
1784:
1785: var $auto_close_tags_re = 'hr|img';
1786:
1787:
1788: function hashHTMLBlocks($text) {
1789:
1790:
1791:
1792:
1793:
1794:
1795:
1796:
1797:
1798:
1799:
1800:
1801:
1802:
1803:
1804:
1805:
1806:
1807: list($text, ) = $this->_hashHTMLBlocks_inMarkdown($text);
1808:
1809: return $text;
1810: }
1811: function _hashHTMLBlocks_inMarkdown($text, $indent = 0,
1812: $enclosing_tag_re = '', $span = false)
1813: {
1814:
1815:
1816:
1817:
1818:
1819:
1820:
1821:
1822:
1823:
1824:
1825:
1826:
1827:
1828:
1829:
1830:
1831:
1832:
1833:
1834:
1835:
1836:
1837:
1838:
1839: if ($text === '') return array('', '');
1840:
1841:
1842: $newline_before_re = '/(?:^\n?|\n\n)*$/';
1843: $newline_after_re =
1844: '{
1845: ^ # Start of text following the tag.
1846: (?>[ ]*<!--.*?-->)? # Optional comment.
1847: [ ]*\n # Must be followed by newline.
1848: }xs';
1849:
1850:
1851: $block_tag_re =
1852: '{
1853: ( # $2: Capture hole tag.
1854: </? # Any opening or closing tag.
1855: (?> # Tag name.
1856: '.$this->block_tags_re.' |
1857: '.$this->context_block_tags_re.' |
1858: '.$this->clean_tags_re.' |
1859: (?!\s)'.$enclosing_tag_re.'
1860: )
1861: (?:
1862: (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name.
1863: (?>
1864: ".*?" | # Double quotes (can contain `>`)
1865: \'.*?\' | # Single quotes (can contain `>`)
1866: .+? # Anything but quotes and `>`.
1867: )*?
1868: )?
1869: > # End of tag.
1870: |
1871: <!-- .*? --> # HTML Comment
1872: |
1873: <\?.*?\?> | <%.*?%> # Processing instruction
1874: |
1875: <!\[CDATA\[.*?\]\]> # CData Block
1876: |
1877: # Code span marker
1878: `+
1879: '. ( !$span ? ' # If not in span.
1880: |
1881: # Indented code block
1882: (?: ^[ ]*\n | ^ | \n[ ]*\n )
1883: [ ]{'.($indent+4).'}[^\n]* \n
1884: (?>
1885: (?: [ ]{'.($indent+4).'}[^\n]* | [ ]* ) \n
1886: )*
1887: |
1888: # Fenced code block marker
1889: (?> ^ | \n )
1890: [ ]{0,'.($indent).'}~~~+[ ]*\n
1891: ' : '' ). ' # End (if not is span).
1892: )
1893: }xs';
1894:
1895:
1896: $depth = 0;
1897: $parsed = "";
1898:
1899:
1900:
1901:
1902:
1903: do {
1904:
1905:
1906:
1907:
1908:
1909:
1910: $parts = preg_split($block_tag_re, $text, 2,
1911: PREG_SPLIT_DELIM_CAPTURE);
1912:
1913:
1914:
1915: if ($span) {
1916: $void = $this->hashPart("", ':');
1917: $newline = "$void\n";
1918: $parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void;
1919: }
1920:
1921: $parsed .= $parts[0];
1922:
1923:
1924: if (count($parts) < 3) {
1925: $text = "";
1926: break;
1927: }
1928:
1929: $tag = $parts[1];
1930: $text = $parts[2];
1931: $tag_re = preg_quote($tag);
1932:
1933:
1934:
1935:
1936: if ($tag{0} == "`") {
1937:
1938: $tag_re = preg_quote($tag);
1939: if (preg_match('{^(?>.+?|\n(?!\n))*?(?<!`)'.$tag_re.'(?!`)}',
1940: $text, $matches))
1941: {
1942:
1943: $parsed .= $tag . $matches[0];
1944: $text = substr($text, strlen($matches[0]));
1945: }
1946: else {
1947:
1948: $parsed .= $tag;
1949: }
1950: }
1951:
1952:
1953:
1954: else if (preg_match('{^\n?[ ]{0,'.($indent+3).'}~}', $tag)) {
1955:
1956: $tag_re = preg_quote(trim($tag));
1957: if (preg_match('{^(?>.*\n)+?[ ]{0,'.($indent).'}'.$tag_re.'[ ]*\n}', $text,
1958: $matches))
1959: {
1960:
1961: $parsed .= $tag . $matches[0];
1962: $text = substr($text, strlen($matches[0]));
1963: }
1964: else {
1965:
1966: $parsed .= $tag;
1967: }
1968: }
1969:
1970:
1971:
1972: else if ($tag{0} == "\n" || $tag{0} == " ") {
1973:
1974:
1975: $parsed .= $tag;
1976: }
1977:
1978:
1979:
1980:
1981:
1982: else if (preg_match('{^<(?:'.$this->block_tags_re.')\b}', $tag) ||
1983: ( preg_match('{^<(?:'.$this->context_block_tags_re.')\b}', $tag) &&
1984: preg_match($newline_before_re, $parsed) &&
1985: preg_match($newline_after_re, $text) )
1986: )
1987: {
1988:
1989: list($block_text, $text) =
1990: $this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true);
1991:
1992:
1993: $parsed .= "\n\n$block_text\n\n";
1994: }
1995:
1996:
1997:
1998:
1999: else if (preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) ||
2000: $tag{1} == '!' || $tag{1} == '?')
2001: {
2002:
2003:
2004: list($block_text, $text) =
2005: $this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false);
2006:
2007: $parsed .= $block_text;
2008: }
2009:
2010:
2011:
2012: else if ($enclosing_tag_re !== '' &&
2013:
2014: preg_match('{^</?(?:'.$enclosing_tag_re.')\b}', $tag))
2015: {
2016:
2017:
2018:
2019: if ($tag{1} == '/') $depth--;
2020: else if ($tag{strlen($tag)-2} != '/') $depth++;
2021:
2022: if ($depth < 0) {
2023:
2024:
2025:
2026:
2027: $text = $tag . $text;
2028: break;
2029: }
2030:
2031: $parsed .= $tag;
2032: }
2033: else {
2034: $parsed .= $tag;
2035: }
2036: } while ($depth >= 0);
2037:
2038: return array($parsed, $text);
2039: }
2040: function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) {
2041:
2042:
2043:
2044:
2045:
2046:
2047:
2048:
2049:
2050:
2051: if ($text === '') return array('', '');
2052:
2053:
2054: $markdown_attr_re = '
2055: {
2056: \s* # Eat whitespace before the `markdown` attribute
2057: markdown
2058: \s*=\s*
2059: (?>
2060: (["\']) # $1: quote delimiter
2061: (.*?) # $2: attribute value
2062: \1 # matching delimiter
2063: |
2064: ([^\s>]*) # $3: unquoted attribute value
2065: )
2066: () # $4: make $3 always defined (avoid warnings)
2067: }xs';
2068:
2069:
2070: $tag_re = '{
2071: ( # $2: Capture hole tag.
2072: </? # Any opening or closing tag.
2073: [\w:$]+ # Tag name.
2074: (?:
2075: (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name.
2076: (?>
2077: ".*?" | # Double quotes (can contain `>`)
2078: \'.*?\' | # Single quotes (can contain `>`)
2079: .+? # Anything but quotes and `>`.
2080: )*?
2081: )?
2082: > # End of tag.
2083: |
2084: <!-- .*? --> # HTML Comment
2085: |
2086: <\?.*?\?> | <%.*?%> # Processing instruction
2087: |
2088: <!\[CDATA\[.*?\]\]> # CData Block
2089: )
2090: }xs';
2091:
2092: $original_text = $text;
2093:
2094: $depth = 0;
2095: $block_text = "";
2096: $parsed = "";
2097:
2098:
2099:
2100:
2101:
2102: if (preg_match('/^<([\w:$]*)\b/', $text, $matches))
2103: $base_tag_name_re = $matches[1];
2104:
2105:
2106:
2107:
2108: do {
2109:
2110:
2111:
2112:
2113:
2114:
2115: $parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
2116:
2117: if (count($parts) < 3) {
2118:
2119:
2120:
2121:
2122:
2123:
2124: return array($original_text{0}, substr($original_text, 1));
2125: }
2126:
2127: $block_text .= $parts[0];
2128: $tag = $parts[1];
2129: $text = $parts[2];
2130:
2131:
2132:
2133:
2134:
2135: if (preg_match('{^</?(?:'.$this->auto_close_tags_re.')\b}', $tag) ||
2136: $tag{1} == '!' || $tag{1} == '?')
2137: {
2138:
2139: $block_text .= $tag;
2140: }
2141: else {
2142:
2143:
2144:
2145:
2146: if (preg_match('{^</?'.$base_tag_name_re.'\b}', $tag)) {
2147: if ($tag{1} == '/') $depth--;
2148: else if ($tag{strlen($tag)-2} != '/') $depth++;
2149: }
2150:
2151:
2152:
2153:
2154: if ($md_attr &&
2155: preg_match($markdown_attr_re, $tag, $attr_m) &&
2156: preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3]))
2157: {
2158:
2159: $tag = preg_replace($markdown_attr_re, '', $tag);
2160:
2161:
2162: $this->mode = $attr_m[2] . $attr_m[3];
2163: $span_mode = $this->mode == 'span' || $this->mode != 'block' &&
2164: preg_match('{^<(?:'.$this->contain_span_tags_re.')\b}', $tag);
2165:
2166:
2167: if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) {
2168: $strlen = $this->utf8_strlen;
2169: $indent = $strlen($matches[1], 'UTF-8');
2170: } else {
2171: $indent = 0;
2172: }
2173:
2174:
2175: $block_text .= $tag;
2176: $parsed .= $this->$hash_method($block_text);
2177:
2178:
2179:
2180: preg_match('/^<([\w:$]*)\b/', $tag, $matches);
2181: $tag_name_re = $matches[1];
2182:
2183:
2184: list ($block_text, $text)
2185: = $this->_hashHTMLBlocks_inMarkdown($text, $indent,
2186: $tag_name_re, $span_mode);
2187:
2188:
2189: if ($indent > 0) {
2190: $block_text = preg_replace("/^[ ]{1,$indent}/m", "",
2191: $block_text);
2192: }
2193:
2194:
2195: if (!$span_mode) $parsed .= "\n\n$block_text\n\n";
2196: else $parsed .= "$block_text";
2197:
2198:
2199: $block_text = "";
2200: }
2201: else $block_text .= $tag;
2202: }
2203:
2204: } while ($depth > 0);
2205:
2206:
2207:
2208:
2209: $parsed .= $this->$hash_method($block_text);
2210:
2211: return array($parsed, $text);
2212: }
2213:
2214:
2215: function hashClean($text) {
2216:
2217:
2218:
2219:
2220:
2221: return $this->hashPart($text, 'C');
2222: }
2223:
2224:
2225: function ($text) {
2226:
2227:
2228:
2229:
2230:
2231:
2232:
2233:
2234:
2235:
2236: $text = preg_replace_callback(
2237: '{
2238: (^.+?) # $1: Header text
2239: (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # $2: Id attribute
2240: [ ]*\n(=+|-+)[ ]*\n+ # $3: Header footer
2241: }mx',
2242: array(&$this, '_doHeaders_callback_setext'), $text);
2243:
2244:
2245:
2246:
2247:
2248:
2249:
2250:
2251: $text = preg_replace_callback('{
2252: ^(\#{1,6}) # $1 = string of #\'s
2253: [ ]*
2254: (.+?) # $2 = Header text
2255: [ ]*
2256: \#* # optional closing #\'s (not counted)
2257: (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # id attribute
2258: [ ]*
2259: \n+
2260: }xm',
2261: array(&$this, '_doHeaders_callback_atx'), $text);
2262:
2263: return $text;
2264: }
2265: function ($attr) {
2266: if (empty($attr)) return "";
2267: return " id=\"$attr\"";
2268: }
2269: function ($matches) {
2270: if ($matches[3] == '-' && preg_match('{^- }', $matches[1]))
2271: return $matches[0];
2272: $level = $matches[3]{0} == '=' ? 1 : 2;
2273: $attr = $this->_doHeaders_attr($id =& $matches[2]);
2274: $block = "<h$level$attr>".$this->runSpanGamut($matches[1])."</h$level>";
2275: return "\n" . $this->hashBlock($block) . "\n\n";
2276: }
2277: function ($matches) {
2278: $level = strlen($matches[1]);
2279: $attr = $this->_doHeaders_attr($id =& $matches[3]);
2280: $block = "<h$level$attr>".$this->runSpanGamut($matches[2])."</h$level>";
2281: return "\n" . $this->hashBlock($block) . "\n\n";
2282: }
2283:
2284:
2285: function doTables($text) {
2286:
2287:
2288:
2289: $less_than_tab = $this->tab_width - 1;
2290:
2291:
2292:
2293:
2294:
2295:
2296:
2297:
2298: $text = preg_replace_callback('
2299: {
2300: ^ # Start of a line
2301: [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2302: [|] # Optional leading pipe (present)
2303: (.+) \n # $1: Header row (at least one pipe)
2304:
2305: [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2306: [|] ([ ]*[-:]+[-| :]*) \n # $2: Header underline
2307:
2308: ( # $3: Cells
2309: (?>
2310: [ ]* # Allowed whitespace.
2311: [|] .* \n # Row content.
2312: )*
2313: )
2314: (?=\n|\Z) # Stop at final double newline.
2315: }xm',
2316: array(&$this, '_doTable_leadingPipe_callback'), $text);
2317:
2318:
2319:
2320:
2321:
2322:
2323:
2324:
2325:
2326: $text = preg_replace_callback('
2327: {
2328: ^ # Start of a line
2329: [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2330: (\S.*[|].*) \n # $1: Header row (at least one pipe)
2331:
2332: [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2333: ([-:]+[ ]*[|][-| :]*) \n # $2: Header underline
2334:
2335: ( # $3: Cells
2336: (?>
2337: .* [|] .* \n # Row content
2338: )*
2339: )
2340: (?=\n|\Z) # Stop at final double newline.
2341: }xm',
2342: array(&$this, '_DoTable_callback'), $text);
2343:
2344: return $text;
2345: }
2346: function _doTable_leadingPipe_callback($matches) {
2347: $head = $matches[1];
2348: $underline = $matches[2];
2349: $content = $matches[3];
2350:
2351:
2352: $content = preg_replace('/^ *[|]/m', '', $content);
2353:
2354: return $this->_doTable_callback(array($matches[0], $head, $underline, $content));
2355: }
2356: function _doTable_callback($matches) {
2357: $head = $matches[1];
2358: $underline = $matches[2];
2359: $content = $matches[3];
2360:
2361:
2362: $head = preg_replace('/[|] *$/m', '', $head);
2363: $underline = preg_replace('/[|] *$/m', '', $underline);
2364: $content = preg_replace('/[|] *$/m', '', $content);
2365:
2366:
2367: $separators = preg_split('/ *[|] */', $underline);
2368: foreach ($separators as $n => $s) {
2369: if (preg_match('/^ *-+: *$/', $s)) $attr[$n] = ' align="right"';
2370: else if (preg_match('/^ *:-+: *$/', $s))$attr[$n] = ' align="center"';
2371: else if (preg_match('/^ *:-+ *$/', $s)) $attr[$n] = ' align="left"';
2372: else $attr[$n] = '';
2373: }
2374:
2375:
2376:
2377: $head = $this->parseSpan($head);
2378: $headers = preg_split('/ *[|] */', $head);
2379: $col_count = count($headers);
2380:
2381:
2382: $text = "<table>\n";
2383: $text .= "<thead>\n";
2384: $text .= "<tr>\n";
2385: foreach ($headers as $n => $header)
2386: $text .= " <th$attr[$n]>".$this->runSpanGamut(trim($header))."</th>\n";
2387: $text .= "</tr>\n";
2388: $text .= "</thead>\n";
2389:
2390:
2391: $rows = explode("\n", trim($content, "\n"));
2392:
2393: $text .= "<tbody>\n";
2394: foreach ($rows as $row) {
2395:
2396:
2397: $row = $this->parseSpan($row);
2398:
2399:
2400: $row_cells = preg_split('/ *[|] */', $row, $col_count);
2401: $row_cells = array_pad($row_cells, $col_count, '');
2402:
2403: $text .= "<tr>\n";
2404: foreach ($row_cells as $n => $cell)
2405: $text .= " <td$attr[$n]>".$this->runSpanGamut(trim($cell))."</td>\n";
2406: $text .= "</tr>\n";
2407: }
2408: $text .= "</tbody>\n";
2409: $text .= "</table>";
2410:
2411: return $this->hashBlock($text) . "\n";
2412: }
2413:
2414:
2415: function doDefLists($text) {
2416:
2417:
2418:
2419: $less_than_tab = $this->tab_width - 1;
2420:
2421:
2422: $whole_list_re = '(?>
2423: ( # $1 = whole list
2424: ( # $2
2425: [ ]{0,'.$less_than_tab.'}
2426: ((?>.*\S.*\n)+) # $3 = defined term
2427: \n?
2428: [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2429: )
2430: (?s:.+?)
2431: ( # $4
2432: \z
2433: |
2434: \n{2,}
2435: (?=\S)
2436: (?! # Negative lookahead for another term
2437: [ ]{0,'.$less_than_tab.'}
2438: (?: \S.*\n )+? # defined term
2439: \n?
2440: [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2441: )
2442: (?! # Negative lookahead for another definition
2443: [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2444: )
2445: )
2446: )
2447: )';
2448:
2449: $text = preg_replace_callback('{
2450: (?>\A\n?|(?<=\n\n))
2451: '.$whole_list_re.'
2452: }mx',
2453: array(&$this, '_doDefLists_callback'), $text);
2454:
2455: return $text;
2456: }
2457: function _doDefLists_callback($matches) {
2458:
2459: $list = $matches[1];
2460:
2461:
2462:
2463: $result = trim($this->processDefListItems($list));
2464: $result = "<dl>\n" . $result . "\n</dl>";
2465: return $this->hashBlock($result) . "\n\n";
2466: }
2467:
2468:
2469: function processDefListItems($list_str) {
2470:
2471:
2472:
2473:
2474: $less_than_tab = $this->tab_width - 1;
2475:
2476:
2477: $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
2478:
2479:
2480: $list_str = preg_replace_callback('{
2481: (?>\A\n?|\n\n+) # leading line
2482: ( # definition terms = $1
2483: [ ]{0,'.$less_than_tab.'} # leading whitespace
2484: (?![:][ ]|[ ]) # negative lookahead for a definition
2485: # mark (colon) or more whitespace.
2486: (?> \S.* \n)+? # actual term (not whitespace).
2487: )
2488: (?=\n?[ ]{0,3}:[ ]) # lookahead for following line feed
2489: # with a definition mark.
2490: }xm',
2491: array(&$this, '_processDefListItems_callback_dt'), $list_str);
2492:
2493:
2494: $list_str = preg_replace_callback('{
2495: \n(\n+)? # leading line = $1
2496: ( # marker space = $2
2497: [ ]{0,'.$less_than_tab.'} # whitespace before colon
2498: [:][ ]+ # definition mark (colon)
2499: )
2500: ((?s:.+?)) # definition text = $3
2501: (?= \n+ # stop at next definition mark,
2502: (?: # next term or end of text
2503: [ ]{0,'.$less_than_tab.'} [:][ ] |
2504: <dt> | \z
2505: )
2506: )
2507: }xm',
2508: array(&$this, '_processDefListItems_callback_dd'), $list_str);
2509:
2510: return $list_str;
2511: }
2512: function _processDefListItems_callback_dt($matches) {
2513: $terms = explode("\n", trim($matches[1]));
2514: $text = '';
2515: foreach ($terms as $term) {
2516: $term = $this->runSpanGamut(trim($term));
2517: $text .= "\n<dt>" . $term . "</dt>";
2518: }
2519: return $text . "\n";
2520: }
2521: function _processDefListItems_callback_dd($matches) {
2522: $leading_line = $matches[1];
2523: $marker_space = $matches[2];
2524: $def = $matches[3];
2525:
2526: if ($leading_line || preg_match('/\n{2,}/', $def)) {
2527:
2528: $def = str_repeat(' ', strlen($marker_space)) . $def;
2529: $def = $this->runBlockGamut($this->outdent($def . "\n\n"));
2530: $def = "\n". $def ."\n";
2531: }
2532: else {
2533: $def = rtrim($def);
2534: $def = $this->runSpanGamut($this->outdent($def));
2535: }
2536:
2537: return "\n<dd>" . $def . "</dd>\n";
2538: }
2539:
2540:
2541: function doFencedCodeBlocks($text) {
2542:
2543:
2544:
2545:
2546:
2547:
2548:
2549: $less_than_tab = $this->tab_width;
2550:
2551: $text = preg_replace_callback('{
2552: (?:\n|\A)
2553: # 1: Opening marker
2554: (
2555: ~{3,} # Marker: three tilde or more.
2556: )
2557: [ ]* \n # Whitespace and newline following marker.
2558:
2559: # 2: Content
2560: (
2561: (?>
2562: (?!\1 [ ]* \n) # Not a closing marker.
2563: .*\n+
2564: )+
2565: )
2566:
2567: # Closing marker.
2568: \1 [ ]* \n
2569: }xm',
2570: array(&$this, '_doFencedCodeBlocks_callback'), $text);
2571:
2572: return $text;
2573: }
2574: function _doFencedCodeBlocks_callback($matches) {
2575: $codeblock = $matches[2];
2576: $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
2577: $codeblock = preg_replace_callback('/^\n+/',
2578: array(&$this, '_doFencedCodeBlocks_newlines'), $codeblock);
2579: $codeblock = "<pre><code>$codeblock</code></pre>";
2580: return "\n\n".$this->hashBlock($codeblock)."\n\n";
2581: }
2582: function _doFencedCodeBlocks_newlines($matches) {
2583: return str_repeat("<br$this->empty_element_suffix",
2584: strlen($matches[0]));
2585: }
2586:
2587:
2588:
2589:
2590:
2591:
2592: var $em_relist = array(
2593: '' => '(?:(?<!\*)\*(?!\*)|(?<![a-zA-Z0-9_])_(?!_))(?=\S|$)(?![\.,:;]\s)',
2594: '*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
2595: '_' => '(?<=\S|^)(?<!_)_(?![a-zA-Z0-9_])',
2596: );
2597: var $strong_relist = array(
2598: '' => '(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?=\S|$)(?![\.,:;]\s)',
2599: '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
2600: '__' => '(?<=\S|^)(?<!_)__(?![a-zA-Z0-9_])',
2601: );
2602: var $em_strong_relist = array(
2603: '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?=\S|$)(?![\.,:;]\s)',
2604: '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
2605: '___' => '(?<=\S|^)(?<!_)___(?![a-zA-Z0-9_])',
2606: );
2607:
2608:
2609: function formParagraphs($text) {
2610:
2611:
2612:
2613:
2614:
2615: $text = preg_replace('/\A\n+|\n+\z/', '', $text);
2616:
2617: $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
2618:
2619:
2620:
2621:
2622: foreach ($grafs as $key => $value) {
2623: $value = trim($this->runSpanGamut($value));
2624:
2625:
2626:
2627: $is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value);
2628:
2629: if ($is_p) {
2630: $value = "<p>$value</p>";
2631: }
2632: $grafs[$key] = $value;
2633: }
2634:
2635:
2636: $text = implode("\n\n", $grafs);
2637:
2638:
2639: $text = $this->unhash($text);
2640:
2641: return $text;
2642: }
2643:
2644:
2645:
2646:
2647: function ($text) {
2648:
2649:
2650:
2651:
2652: $less_than_tab = $this->tab_width - 1;
2653:
2654:
2655: $text = preg_replace_callback('{
2656: ^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?: # note_id = $1
2657: [ ]*
2658: \n? # maybe *one* newline
2659: ( # text = $2 (no blank lines allowed)
2660: (?:
2661: .+ # actual text
2662: |
2663: \n # newlines but
2664: (?!\[\^.+?\]:\s)# negative lookahead for footnote marker.
2665: (?!\n+[ ]{0,3}\S)# ensure line is not blank and followed
2666: # by non-indented content
2667: )*
2668: )
2669: }xm',
2670: array(&$this, '_stripFootnotes_callback'),
2671: $text);
2672: return $text;
2673: }
2674: function ($matches) {
2675: $note_id = $this->fn_id_prefix . $matches[1];
2676: $this->footnotes[$note_id] = $this->outdent($matches[2]);
2677: return '';
2678: }
2679:
2680:
2681: function ($text) {
2682:
2683:
2684:
2685:
2686: if (!$this->in_anchor) {
2687: $text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text);
2688: }
2689: return $text;
2690: }
2691:
2692:
2693: function ($text) {
2694:
2695:
2696:
2697: $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}',
2698: array(&$this, '_appendFootnotes_callback'), $text);
2699:
2700: if (!empty($this->footnotes_ordered)) {
2701: $text .= "\n\n";
2702: $text .= "<div class=\"footnotes\">\n";
2703: $text .= "<hr". $this->empty_element_suffix ."\n";
2704: $text .= "<ol>\n\n";
2705:
2706: $attr = " rev=\"footnote\"";
2707: if ($this->fn_backlink_class != "") {
2708: $class = $this->fn_backlink_class;
2709: $class = $this->encodeAttribute($class);
2710: $attr .= " class=\"$class\"";
2711: }
2712: if ($this->fn_backlink_title != "") {
2713: $title = $this->fn_backlink_title;
2714: $title = $this->encodeAttribute($title);
2715: $attr .= " title=\"$title\"";
2716: }
2717: $num = 0;
2718:
2719: while (!empty($this->footnotes_ordered)) {
2720: $footnote = reset($this->footnotes_ordered);
2721: $note_id = key($this->footnotes_ordered);
2722: unset($this->footnotes_ordered[$note_id]);
2723:
2724: $footnote .= "\n";
2725: $footnote = $this->runBlockGamut("$footnote\n");
2726: $footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}',
2727: array(&$this, '_appendFootnotes_callback'), $footnote);
2728:
2729: $attr = str_replace("%%", ++$num, $attr);
2730: $note_id = $this->encodeAttribute($note_id);
2731:
2732:
2733: $backlink = "<a href=\"#fnref:$note_id\"$attr>↩</a>";
2734: if (preg_match('{</p>$}', $footnote)) {
2735: $footnote = substr($footnote, 0, -4) . " $backlink</p>";
2736: } else {
2737: $footnote .= "\n\n<p>$backlink</p>";
2738: }
2739:
2740: $text .= "<li id=\"fn:$note_id\">\n";
2741: $text .= $footnote . "\n";
2742: $text .= "</li>\n\n";
2743: }
2744:
2745: $text .= "</ol>\n";
2746: $text .= "</div>";
2747: }
2748: return $text;
2749: }
2750: function ($matches) {
2751: $node_id = $this->fn_id_prefix . $matches[1];
2752:
2753:
2754:
2755: if (isset($this->footnotes[$node_id])) {
2756:
2757: $this->footnotes_ordered[$node_id] = $this->footnotes[$node_id];
2758: unset($this->footnotes[$node_id]);
2759:
2760: $num = $this->footnote_counter++;
2761: $attr = " rel=\"footnote\"";
2762: if ($this->fn_link_class != "") {
2763: $class = $this->fn_link_class;
2764: $class = $this->encodeAttribute($class);
2765: $attr .= " class=\"$class\"";
2766: }
2767: if ($this->fn_link_title != "") {
2768: $title = $this->fn_link_title;
2769: $title = $this->encodeAttribute($title);
2770: $attr .= " title=\"$title\"";
2771: }
2772:
2773: $attr = str_replace("%%", $num, $attr);
2774: $node_id = $this->encodeAttribute($node_id);
2775:
2776: return
2777: "<sup id=\"fnref:$node_id\">".
2778: "<a href=\"#fn:$node_id\"$attr>$num</a>".
2779: "</sup>";
2780: }
2781:
2782: return "[^".$matches[1]."]";
2783: }
2784:
2785:
2786:
2787:
2788: function stripAbbreviations($text) {
2789:
2790:
2791:
2792: $less_than_tab = $this->tab_width - 1;
2793:
2794:
2795: $text = preg_replace_callback('{
2796: ^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?: # abbr_id = $1
2797: (.*) # text = $2 (no blank lines allowed)
2798: }xm',
2799: array(&$this, '_stripAbbreviations_callback'),
2800: $text);
2801: return $text;
2802: }
2803: function _stripAbbreviations_callback($matches) {
2804: $abbr_word = $matches[1];
2805: $abbr_desc = $matches[2];
2806: if ($this->abbr_word_re)
2807: $this->abbr_word_re .= '|';
2808: $this->abbr_word_re .= preg_quote($abbr_word);
2809: $this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
2810: return '';
2811: }
2812:
2813:
2814: function doAbbreviations($text) {
2815:
2816:
2817:
2818: if ($this->abbr_word_re) {
2819:
2820:
2821: $text = preg_replace_callback('{'.
2822: '(?<![\w\x1A])'.
2823: '(?:'.$this->abbr_word_re.')'.
2824: '(?![\w\x1A])'.
2825: '}',
2826: array(&$this, '_doAbbreviations_callback'), $text);
2827: }
2828: return $text;
2829: }
2830: function _doAbbreviations_callback($matches) {
2831: $abbr = $matches[0];
2832: if (isset($this->abbr_desciptions[$abbr])) {
2833: $desc = $this->abbr_desciptions[$abbr];
2834: if (empty($desc)) {
2835: return $this->hashPart("<abbr>$abbr</abbr>");
2836: } else {
2837: $desc = $this->encodeAttribute($desc);
2838: return $this->hashPart("<abbr title=\"$desc\">$abbr</abbr>");
2839: }
2840: } else {
2841: return $matches[0];
2842: }
2843: }
2844:
2845: }
2846:
2847:
2848: 2849: 2850: 2851: 2852: 2853: 2854: 2855: 2856: 2857: 2858: 2859: 2860: 2861: 2862: 2863: 2864: 2865: 2866: 2867: 2868: 2869: 2870: 2871: 2872: 2873: 2874: 2875: 2876: 2877: 2878: 2879: 2880: 2881: 2882: 2883: 2884: 2885: 2886: 2887: 2888: 2889: 2890: 2891: 2892: 2893: 2894: 2895: 2896: 2897: 2898: 2899: 2900: 2901: 2902: 2903: 2904: 2905: 2906: 2907: 2908: 2909: 2910: 2911: 2912: 2913: 2914: 2915: 2916: 2917: 2918: 2919: 2920: 2921: 2922: 2923: 2924: 2925: 2926: 2927: 2928: 2929: 2930: 2931: 2932: 2933:
2934: ?>