PluginProbe
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits / 3.1.0
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits v3.1.0
3.2.2 3.2.3 3.2.1 3.2.0 3.1.9 3.1.8 3.1.7 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.9 trunk 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.3 1.1.4 1.1.5 All 174 releases
master-addons / lib / markdown.php

markdown.php in Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits 3.1.0, at lib/markdown.php

1,731 lines 47.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
5 define( 'MARKDOWN_VERSION', "1.0.2" ); # 29 Nov 2013
6
7
8 #
9 # Global default settings:
10 #
11
12 # Change to ">" for HTML output
13 @define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX', " />");
14
15 # Define the width of a tab for code blocks.
16 @define( 'MARKDOWN_TAB_WIDTH', 4 );
17
18
19 #
20 # WordPress settings:
21 #
22
23 # Change to false to remove Markdown from posts and/or comments.
24 @define( 'MARKDOWN_WP_POSTS', true );
25 @define( 'MARKDOWN_WP_COMMENTS', true );
26
27
28
29 ### Standard Function Interface ###
30
31 @define( 'MARKDOWN_PARSER_CLASS', 'Markdown_Parser' );
32
33 function Markdown($text) {
34 #
35 # Initialize the parser and return the result of its transform method.
36 #
37 # Setup static parser variable.
38 static $parser;
39 if (!isset($parser)) {
40 $parser_class = MARKDOWN_PARSER_CLASS;
41 $parser = new $parser_class;
42 }
43
44 # Transform text using parser.
45 return $parser->transform($text);
46 }
47
48
49 if (isset($wp_version)) {
50 # More details about how it works here:
51 # <http://michelf.ca/weblog/2005/wordpress-text-flow-vs-markdown/>
52
53 # Post content and excerpts
54 # - Remove WordPress paragraph generator.
55 # - Run Markdown on excerpt, then remove all tags.
56 # - Add paragraph tag around the excerpt, but remove it for the excerpt rss.
57 if (MARKDOWN_WP_POSTS) {
58 remove_filter('the_content', 'wpautop');
59 remove_filter('the_content_rss', 'wpautop');
60 remove_filter('the_excerpt', 'wpautop');
61 add_filter('the_content', 'Markdown', 6);
62 add_filter('the_content_rss', 'Markdown', 6);
63 add_filter('get_the_excerpt', 'Markdown', 6);
64 add_filter('get_the_excerpt', 'trim', 7);
65 add_filter('the_excerpt', 'mdwp_add_p');
66 add_filter('the_excerpt_rss', 'mdwp_strip_p');
67
68 remove_filter('content_save_pre', 'balanceTags', 50);
69 remove_filter('excerpt_save_pre', 'balanceTags', 50);
70 add_filter('the_content', 'balanceTags', 50);
71 add_filter('get_the_excerpt', 'balanceTags', 9);
72 }
73
74 # Comments
75 # - Remove WordPress paragraph generator.
76 # - Remove WordPress auto-link generator.
77 # - Scramble important tags before passing them to the kses filter.
78 # - Run Markdown on excerpt then remove paragraph tags.
79 if (MARKDOWN_WP_COMMENTS) {
80 remove_filter('comment_text', 'wpautop', 30);
81 remove_filter('comment_text', 'make_clickable');
82 add_filter('pre_comment_content', 'Markdown', 6);
83 add_filter('pre_comment_content', 'mdwp_hide_tags', 8);
84 add_filter('pre_comment_content', 'mdwp_show_tags', 12);
85 add_filter('get_comment_text', 'Markdown', 6);
86 add_filter('get_comment_excerpt', 'Markdown', 6);
87 add_filter('get_comment_excerpt', 'mdwp_strip_p', 7);
88
89 global $mdwp_hidden_tags, $mdwp_placeholders;
90 $mdwp_hidden_tags = explode(' ',
91 '<p> </p> <pre> </pre> <ol> </ol> <ul> </ul> <li> </li>');
92 $mdwp_placeholders = explode(' ',
93 'cRw07MooOM H1xdtu4j4c cer2mzrA6X DGv31g9cer by0ZC1wmWE '.
94 'ZY5VwzoEby hyNAv1AfTL W7mEYWdChy yvN8pgy16G X9aubbHUyv');
95 }
96
97 function mdwp_add_p($text) {
98 if (!preg_match('{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text)) {
99 $text = '<p>'.$text.'</p>';
100 $text = preg_replace('{\n{2,}}', "</p>\n\n<p>", $text);
101 }
102 return $text;
103 }
104
105 function mdwp_strip_p($t) { return preg_replace('{</?p>}i', '', $t); }
106
107 function mdwp_hide_tags($text) {
108 global $mdwp_hidden_tags, $mdwp_placeholders;
109 return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text);
110 }
111 function mdwp_show_tags($text) {
112 global $mdwp_hidden_tags, $mdwp_placeholders;
113 return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text);
114 }
115 }
116
117
118 ### bBlog Plugin Info ###
119
120 function identify_modifier_markdown() {
121 return array(
122 'name' => 'markdown',
123 'type' => 'modifier',
124 'nicename' => 'Markdown',
125 'description' => 'A text-to-HTML conversion tool for web writers',
126 'authors' => 'Michel Fortin and John Gruber',
127 'licence' => 'BSD-like',
128 'version' => MARKDOWN_VERSION,
129 '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.ca/projects/php-markdown/">More...</a>'
130 );
131 }
132
133
134 ### Smarty Modifier Interface ###
135
136 function smarty_modifier_markdown($text) {
137 return Markdown($text);
138 }
139
140
141 ### Textile Compatibility Mode ###
142
143 # Rename this file to "classTextile.php" and it can replace Textile everywhere.
144
145 if (strcasecmp(substr(__FILE__, -16), "classTextile.php") == 0) {
146 # Try to include PHP SmartyPants. Should be in the same directory.
147 @include_once 'smartypants.php';
148 # Fake Textile class. It calls Markdown instead.
149 class Textile {
150 function TextileThis($text, $lite='', $encode='') {
151 if ($lite == '' && $encode == '') $text = Markdown($text);
152 if (function_exists('SmartyPants')) $text = SmartyPants($text);
153 return $text;
154 }
155 # Fake restricted version: restrictions are not supported for now.
156 function TextileRestricted($text, $lite='', $noimage='') {
157 return $this->TextileThis($text, $lite);
158 }
159 # Workaround to ensure compatibility with TextPattern 4.0.3.
160 function blockLite($text) { return $text; }
161 }
162 }
163
164
165
166 #
167 # Markdown Parser Class
168 #
169
170 class Markdown_Parser {
171
172 ### Configuration Variables ###
173
174 # Change to ">" for HTML output.
175 var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
176 var $tab_width = MARKDOWN_TAB_WIDTH;
177
178 # Change to `true` to disallow markup or entities.
179 var $no_markup = false;
180 var $no_entities = false;
181
182 # Predefined urls and titles for reference links and images.
183 var $predef_urls = array();
184 var $predef_titles = array();
185
186
187 ### Parser Implementation ###
188
189 # Regex to match balanced [brackets].
190 # Needed to insert a maximum bracked depth while converting to PHP.
191 var $nested_brackets_depth = 6;
192 var $nested_brackets_re;
193
194 var $nested_url_parenthesis_depth = 4;
195 var $nested_url_parenthesis_re;
196
197 # Table of hash values for escaped characters:
198 var $escape_chars = '\`*_{}[]()>#+-.!';
199 var $escape_chars_re;
200
201
202 function __construct() {
203 #
204 # Constructor function. Initialize appropriate member variables.
205 #
206 $this->_initDetab();
207 $this->prepareItalicsAndBold();
208
209 $this->nested_brackets_re =
210 str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth).
211 str_repeat('\])*', $this->nested_brackets_depth);
212
213 $this->nested_url_parenthesis_re =
214 str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth).
215 str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth);
216
217 $this->escape_chars_re = '['.preg_quote($this->escape_chars).']';
218
219 # Sort document, block, and span gamut in ascendent priority order.
220 asort($this->document_gamut);
221 asort($this->block_gamut);
222 asort($this->span_gamut);
223 }
224
225
226 # Internal hashes used during transformation.
227 var $urls = array();
228 var $titles = array();
229 var $html_hashes = array();
230
231 # Status flag to avoid invalid nesting.
232 var $in_anchor = false;
233
234
235 function setup() {
236 #
237 # Called before the transformation process starts to setup parser
238 # states.
239 #
240 # Clear global hashes.
241 $this->urls = $this->predef_urls;
242 $this->titles = $this->predef_titles;
243 $this->html_hashes = array();
244
245 $this->in_anchor = false;
246 }
247
248 function teardown() {
249 #
250 # Called after the transformation process to clear any variable
251 # which may be taking up memory unnecessarly.
252 #
253 $this->urls = array();
254 $this->titles = array();
255 $this->html_hashes = array();
256 }
257
258
259 function transform($text) {
260 #
261 # Main function. Performs some preprocessing on the input text
262 # and pass it through the document gamut.
263 #
264 $this->setup();
265
266 # Remove UTF-8 BOM and marker character in input, if present.
267 $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text);
268
269 # Standardize line endings:
270 # DOS to Unix and Mac to Unix
271 $text = preg_replace('{\r\n?}', "\n", $text);
272
273 # Make sure $text ends with a couple of newlines:
274 $text .= "\n\n";
275
276 # Convert all tabs to spaces.
277 $text = $this->detab($text);
278
279 # Turn block-level HTML blocks into hash entries
280 $text = $this->hashHTMLBlocks($text);
281
282 # Strip any lines consisting only of spaces and tabs.
283 # This makes subsequent regexen easier to write, because we can
284 # match consecutive blank lines with /\n+/ instead of something
285 # contorted like /[ ]*\n+/ .
286 $text = preg_replace('/^[ ]+$/m', '', $text);
287
288 # Run document gamut methods.
289 foreach ($this->document_gamut as $method => $priority) {
290 $text = $this->$method($text);
291 }
292
293 $this->teardown();
294
295 return $text . "\n";
296 }
297
298 var $document_gamut = array(
299 # Strip link definitions, store in hashes.
300 "stripLinkDefinitions" => 20,
301
302 "runBasicBlockGamut" => 30,
303 );
304
305
306 function stripLinkDefinitions($text) {
307 #
308 # Strips link definitions from text, stores the URLs and titles in
309 # hash references.
310 #
311 $less_than_tab = $this->tab_width - 1;
312
313 # Link defs are in the form: ^[id]: url "optional title"
314 $text = preg_replace_callback('{
315 ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1
316 [ ]*
317 \n? # maybe *one* newline
318 [ ]*
319 (?:
320 <(.+?)> # url = $2
321 |
322 (\S+?) # url = $3
323 )
324 [ ]*
325 \n? # maybe one newline
326 [ ]*
327 (?:
328 (?<=\s) # lookbehind for whitespace
329 ["(]
330 (.*?) # title = $4
331 [")]
332 [ ]*
333 )? # title is optional
334 (?:\n+|\Z)
335 }xm',
336 array(&$this, '_stripLinkDefinitions_callback'),
337 $text);
338 return $text;
339 }
340 function _stripLinkDefinitions_callback($matches) {
341 $link_id = strtolower($matches[1]);
342 $url = $matches[2] == '' ? $matches[3] : $matches[2];
343 $this->urls[$link_id] = $url;
344 $this->titles[$link_id] =& $matches[4];
345 return ''; # String that will replace the block
346 }
347
348
349 function hashHTMLBlocks($text) {
350 if ($this->no_markup) return $text;
351
352 $less_than_tab = $this->tab_width - 1;
353
354 # Hashify HTML blocks:
355 # We only want to do this for block-level HTML tags, such as headers,
356 # lists, and tables. That's because we still want to wrap <p>s around
357 # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
358 # phrase emphasis, and spans. The list of tags we're looking for is
359 # hard-coded:
360 #
361 # * List "a" is made of tags which can be both inline or block-level.
362 # These will be treated block-level when the start tag is alone on
363 # its line, otherwise they're not matched here and will be taken as
364 # inline later.
365 # * List "b" is made of tags which are always block-level;
366 #
367 $block_tags_a_re = 'ins|del';
368 $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'.
369 'script|noscript|form|fieldset|iframe|math|svg|'.
370 'article|section|nav|aside|hgroup|header|footer|'.
371 'figure';
372
373 # Regular expression for the content of a block tag.
374 $nested_tags_level = 4;
375 $attr = '
376 (?> # optional tag attributes
377 \s # starts with whitespace
378 (?>
379 [^>"/]+ # text outside quotes
380 |
381 /+(?!>) # slash not followed by ">"
382 |
383 "[^"]*" # text inside double quotes (tolerate ">")
384 |
385 \'[^\']*\' # text inside single quotes (tolerate ">")
386 )*
387 )?
388 ';
389 $content =
390 str_repeat('
391 (?>
392 [^<]+ # content without tag
393 |
394 <\2 # nested opening tag
395 '.$attr.' # attributes
396 (?>
397 />
398 |
399 >', $nested_tags_level). # end of opening tag
400 '.*?'. # last level nested tag content
401 str_repeat('
402 </\2\s*> # closing nested tag
403 )
404 |
405 <(?!/\2\s*> # other tags with a different name
406 )
407 )*',
408 $nested_tags_level);
409 $content2 = str_replace('\2', '\3', $content);
410
411 # First, look for nested blocks, e.g.:
412 # <div>
413 # <div>
414 # tags for inner block must be indented.
415 # </div>
416 # </div>
417 #
418 # The outermost tags must start at the left margin for this to match, and
419 # the inner nested divs must be indented.
420 # We need to do this before the next, more liberal match, because the next
421 # match will start at the first `<div>` and stop at the first `</div>`.
422 $text = preg_replace_callback('{(?>
423 (?>
424 (?<=\n\n) # Starting after a blank line
425 | # or
426 \A\n? # the beginning of the doc
427 )
428 ( # save in $1
429
430 # Match from `\n<tag>` to `</tag>\n`, handling nested tags
431 # in between.
432
433 [ ]{0,'.$less_than_tab.'}
434 <('.$block_tags_b_re.')# start tag = $2
435 '.$attr.'> # attributes followed by > and \n
436 '.$content.' # content, support nesting
437 </\2> # the matching end tag
438 [ ]* # trailing spaces/tabs
439 (?=\n+|\Z) # followed by a newline or end of document
440
441 | # Special version for tags of group a.
442
443 [ ]{0,'.$less_than_tab.'}
444 <('.$block_tags_a_re.')# start tag = $3
445 '.$attr.'>[ ]*\n # attributes followed by >
446 '.$content2.' # content, support nesting
447 </\3> # the matching end tag
448 [ ]* # trailing spaces/tabs
449 (?=\n+|\Z) # followed by a newline or end of document
450
451 | # Special case just for <hr />. It was easier to make a special
452 # case than to make the other regex more complicated.
453
454 [ ]{0,'.$less_than_tab.'}
455 <(hr) # start tag = $2
456 '.$attr.' # attributes
457 /?> # the matching end tag
458 [ ]*
459 (?=\n{2,}|\Z) # followed by a blank line or end of document
460
461 | # Special case for standalone HTML comments:
462
463 [ ]{0,'.$less_than_tab.'}
464 (?s:
465 <!-- .*? -->
466 )
467 [ ]*
468 (?=\n{2,}|\Z) # followed by a blank line or end of document
469
470 | # PHP and ASP-style processor instructions (<? and <%)
471
472 [ ]{0,'.$less_than_tab.'}
473 (?s:
474 <([?%]) # $2
475 .*?
476 \2>
477 )
478 [ ]*
479 (?=\n{2,}|\Z) # followed by a blank line or end of document
480
481 )
482 )}Sxmi',
483 array(&$this, '_hashHTMLBlocks_callback'),
484 $text);
485
486 return $text;
487 }
488 function _hashHTMLBlocks_callback($matches) {
489 $text = $matches[1];
490 $key = $this->hashBlock($text);
491 return "\n\n$key\n\n";
492 }
493
494
495 function hashPart($text, $boundary = 'X') {
496 #
497 # Called whenever a tag must be hashed when a function insert an atomic
498 # element in the text stream. Passing $text to through this function gives
499 # a unique text-token which will be reverted back when calling unhash.
500 #
501 # The $boundary argument specify what character should be used to surround
502 # the token. By convension, "B" is used for block elements that needs not
503 # to be wrapped into paragraph tags at the end, ":" is used for elements
504 # that are word separators and "X" is used in the general case.
505 #
506 # Swap back any tag hash found in $text so we do not have to `unhash`
507 # multiple times at the end.
508 $text = $this->unhash($text);
509
510 # Then hash the block.
511 static $i = 0;
512 $key = "$boundary\x1A" . ++$i . $boundary;
513 $this->html_hashes[$key] = $text;
514 return $key; # String that will replace the tag.
515 }
516
517
518 function hashBlock($text) {
519 #
520 # Shortcut function for hashPart with block-level boundaries.
521 #
522 return $this->hashPart($text, 'B');
523 }
524
525
526 var $block_gamut = array(
527 #
528 # These are all the transformations that form block-level
529 # tags like paragraphs, headers, and list items.
530 #
531 "doHeaders" => 10,
532 "doHorizontalRules" => 20,
533
534 "doLists" => 40,
535 "doCodeBlocks" => 50,
536 "doBlockQuotes" => 60,
537 );
538
539 function runBlockGamut($text) {
540 #
541 # Run block gamut tranformations.
542 #
543 # We need to escape raw HTML in Markdown source before doing anything
544 # else. This need to be done for each block, and not only at the
545 # begining in the Markdown function since hashed blocks can be part of
546 # list items and could have been indented. Indented blocks would have
547 # been seen as a code block in a previous pass of hashHTMLBlocks.
548 $text = $this->hashHTMLBlocks($text);
549
550 return $this->runBasicBlockGamut($text);
551 }
552
553 function runBasicBlockGamut($text) {
554 #
555 # Run block gamut tranformations, without hashing HTML blocks. This is
556 # useful when HTML blocks are known to be already hashed, like in the first
557 # whole-document pass.
558 #
559 foreach ($this->block_gamut as $method => $priority) {
560 $text = $this->$method($text);
561 }
562
563 # Finally form paragraph and restore hashed blocks.
564 $text = $this->formParagraphs($text);
565
566 return $text;
567 }
568
569
570 function doHorizontalRules($text) {
571 # Do Horizontal Rules:
572 return preg_replace(
573 '{
574 ^[ ]{0,3} # Leading space
575 ([-*_]) # $1: First marker
576 (?> # Repeated marker group
577 [ ]{0,2} # Zero, one, or two spaces.
578 \1 # Marker character
579 ){2,} # Group repeated at least twice
580 [ ]* # Tailing spaces
581 $ # End of line.
582 }mx',
583 "\n".$this->hashBlock("<hr$this->empty_element_suffix")."\n",
584 $text);
585 }
586
587
588 var $span_gamut = array(
589 #
590 # These are all the transformations that occur *within* block-level
591 # tags like paragraphs, headers, and list items.
592 #
593 # Process character escapes, code spans, and inline HTML
594 # in one shot.
595 "parseSpan" => -30,
596
597 # Process anchor and image tags. Images must come first,
598 # because ![foo][f] looks like an anchor.
599 "doImages" => 10,
600 "doAnchors" => 20,
601
602 # Make links out of things like `<http://example.com/>`
603 # Must come after doAnchors, because you can use < and >
604 # delimiters in inline links like [this](<url>).
605 "doAutoLinks" => 30,
606 "encodeAmpsAndAngles" => 40,
607
608 "doItalicsAndBold" => 50,
609 "doHardBreaks" => 60,
610 );
611
612 function runSpanGamut($text) {
613 #
614 # Run span gamut tranformations.
615 #
616 foreach ($this->span_gamut as $method => $priority) {
617 $text = $this->$method($text);
618 }
619
620 return $text;
621 }
622
623
624 function doHardBreaks($text) {
625 # Do hard breaks:
626 return preg_replace_callback('/ {2,}\n/',
627 array(&$this, '_doHardBreaks_callback'), $text);
628 }
629 function _doHardBreaks_callback($matches) {
630 return $this->hashPart("<br$this->empty_element_suffix\n");
631 }
632
633
634 function doAnchors($text) {
635 #
636 # Turn Markdown link shortcuts into XHTML <a> tags.
637 #
638 if ($this->in_anchor) return $text;
639 $this->in_anchor = true;
640
641 #
642 # First, handle reference-style links: [link text] [id]
643 #
644 $text = preg_replace_callback('{
645 ( # wrap whole match in $1
646 \[
647 ('.$this->nested_brackets_re.') # link text = $2
648 \]
649
650 [ ]? # one optional space
651 (?:\n[ ]*)? # one optional newline followed by spaces
652
653 \[
654 (.*?) # id = $3
655 \]
656 )
657 }xs',
658 array(&$this, '_doAnchors_reference_callback'), $text);
659
660 #
661 # Next, inline-style links: [link text](url "optional title")
662 #
663 $text = preg_replace_callback('{
664 ( # wrap whole match in $1
665 \[
666 ('.$this->nested_brackets_re.') # link text = $2
667 \]
668 \( # literal paren
669 [ \n]*
670 (?:
671 <(.+?)> # href = $3
672 |
673 ('.$this->nested_url_parenthesis_re.') # href = $4
674 )
675 [ \n]*
676 ( # $5
677 ([\'"]) # quote char = $6
678 (.*?) # Title = $7
679 \6 # matching quote
680 [ \n]* # ignore any spaces/tabs between closing quote and )
681 )? # title is optional
682 \)
683 )
684 }xs',
685 array(&$this, '_doAnchors_inline_callback'), $text);
686
687 #
688 # Last, handle reference-style shortcuts: [link text]
689 # These must come last in case you've also got [link text][1]
690 # or [link text](/foo)
691 #
692 $text = preg_replace_callback('{
693 ( # wrap whole match in $1
694 \[
695 ([^\[\]]+) # link text = $2; can\'t contain [ or ]
696 \]
697 )
698 }xs',
699 array(&$this, '_doAnchors_reference_callback'), $text);
700
701 $this->in_anchor = false;
702 return $text;
703 }
704 function _doAnchors_reference_callback($matches) {
705 $whole_match = $matches[1];
706 $link_text = $matches[2];
707 $link_id =& $matches[3];
708
709 if ($link_id == "") {
710 # for shortcut links like [this][] or [this].
711 $link_id = $link_text;
712 }
713
714 # lower-case and turn embedded newlines into spaces
715 $link_id = strtolower($link_id);
716 $link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
717
718 if (isset($this->urls[$link_id])) {
719 $url = $this->urls[$link_id];
720 $url = $this->encodeAttribute($url);
721
722 $result = "<a href=\"$url\"";
723 if ( isset( $this->titles[$link_id] ) ) {
724 $title = $this->titles[$link_id];
725 $title = $this->encodeAttribute($title);
726 $result .= " title=\"$title\"";
727 }
728
729 $link_text = $this->runSpanGamut($link_text);
730 $result .= ">$link_text</a>";
731 $result = $this->hashPart($result);
732 }
733 else {
734 $result = $whole_match;
735 }
736 return $result;
737 }
738 function _doAnchors_inline_callback($matches) {
739 $whole_match = $matches[1];
740 $link_text = $this->runSpanGamut($matches[2]);
741 $url = $matches[3] == '' ? $matches[4] : $matches[3];
742 $title =& $matches[7];
743
744 $url = $this->encodeAttribute($url);
745
746 $result = "<a href=\"$url\"";
747 if (isset($title)) {
748 $title = $this->encodeAttribute($title);
749 $result .= " title=\"$title\"";
750 }
751
752 $link_text = $this->runSpanGamut($link_text);
753 $result .= ">$link_text</a>";
754
755 return $this->hashPart($result);
756 }
757
758
759 function doImages($text) {
760 #
761 # Turn Markdown image shortcuts into <img> tags.
762 #
763 #
764 # First, handle reference-style labeled images: ![alt text][id]
765 #
766 $text = preg_replace_callback('{
767 ( # wrap whole match in $1
768 !\[
769 ('.$this->nested_brackets_re.') # alt text = $2
770 \]
771
772 [ ]? # one optional space
773 (?:\n[ ]*)? # one optional newline followed by spaces
774
775 \[
776 (.*?) # id = $3
777 \]
778
779 )
780 }xs',
781 array(&$this, '_doImages_reference_callback'), $text);
782
783 #
784 # Next, handle inline images: ![alt text](url "optional title")
785 # Don't forget: encode * and _
786 #
787 $text = preg_replace_callback('{
788 ( # wrap whole match in $1
789 !\[
790 ('.$this->nested_brackets_re.') # alt text = $2
791 \]
792 \s? # One optional whitespace character
793 \( # literal paren
794 [ \n]*
795 (?:
796 <(\S*)> # src url = $3
797 |
798 ('.$this->nested_url_parenthesis_re.') # src url = $4
799 )
800 [ \n]*
801 ( # $5
802 ([\'"]) # quote char = $6
803 (.*?) # title = $7
804 \6 # matching quote
805 [ \n]*
806 )? # title is optional
807 \)
808 )
809 }xs',
810 array(&$this, '_doImages_inline_callback'), $text);
811
812 return $text;
813 }
814 function _doImages_reference_callback($matches) {
815 $whole_match = $matches[1];
816 $alt_text = $matches[2];
817 $link_id = strtolower($matches[3]);
818
819 if ($link_id == "") {
820 $link_id = strtolower($alt_text); # for shortcut links like ![this][].
821 }
822
823 $alt_text = $this->encodeAttribute($alt_text);
824 if (isset($this->urls[$link_id])) {
825 $url = $this->encodeAttribute($this->urls[$link_id]);
826 $result = "<img src=\"$url\" alt=\"$alt_text\"";
827 if (isset($this->titles[$link_id])) {
828 $title = $this->titles[$link_id];
829 $title = $this->encodeAttribute($title);
830 $result .= " title=\"$title\"";
831 }
832 $result .= $this->empty_element_suffix;
833 $result = $this->hashPart($result);
834 }
835 else {
836 # If there's no such link ID, leave intact:
837 $result = $whole_match;
838 }
839
840 return $result;
841 }
842 function _doImages_inline_callback($matches) {
843 $whole_match = $matches[1];
844 $alt_text = $matches[2];
845 $url = $matches[3] == '' ? $matches[4] : $matches[3];
846 $title =& $matches[7];
847
848 $alt_text = $this->encodeAttribute($alt_text);
849 $url = $this->encodeAttribute($url);
850 $result = "<img src=\"$url\" alt=\"$alt_text\"";
851 if (isset($title)) {
852 $title = $this->encodeAttribute($title);
853 $result .= " title=\"$title\""; # $title already quoted
854 }
855 $result .= $this->empty_element_suffix;
856
857 return $this->hashPart($result);
858 }
859
860
861 function doHeaders($text) {
862 # Setext-style headers:
863 # Header 1
864 # ========
865 #
866 # Header 2
867 # --------
868 #
869 $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
870 array(&$this, '_doHeaders_callback_setext'), $text);
871
872 # atx-style headers:
873 # # Header 1
874 # ## Header 2
875 # ## Header 2 with closing hashes ##
876 # ...
877 # ###### Header 6
878 #
879 $text = preg_replace_callback('{
880 ^(\#{1,6}) # $1 = string of #\'s
881 [ ]*
882 (.+?) # $2 = Header text
883 [ ]*
884 \#* # optional closing #\'s (not counted)
885 \n+
886 }xm',
887 array(&$this, '_doHeaders_callback_atx'), $text);
888
889 return $text;
890 }
891 function _doHeaders_callback_setext($matches) {
892 # Terrible hack to check we haven't found an empty list item.
893 if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1]))
894 return $matches[0];
895
896 $level = $matches[2][0] == '=' ? 1 : 2;
897 $block = "<h$level>".$this->runSpanGamut($matches[1])."</h$level>";
898 return "\n" . $this->hashBlock($block) . "\n\n";
899 }
900 function _doHeaders_callback_atx($matches) {
901 $level = strlen($matches[1]);
902 $block = "<h$level>".$this->runSpanGamut($matches[2])."</h$level>";
903 return "\n" . $this->hashBlock($block) . "\n\n";
904 }
905
906
907 function doLists($text) {
908 #
909 # Form HTML ordered (numbered) and unordered (bulleted) lists.
910 #
911 $less_than_tab = $this->tab_width - 1;
912
913 # Re-usable patterns to match list item bullets and number markers:
914 $marker_ul_re = '[*+-]';
915 $marker_ol_re = '\d+[\.]';
916 $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
917
918 $markers_relist = array(
919 $marker_ul_re => $marker_ol_re,
920 $marker_ol_re => $marker_ul_re,
921 );
922
923 foreach ($markers_relist as $marker_re => $other_marker_re) {
924 # Re-usable pattern to match any entirel ul or ol list:
925 $whole_list_re = '
926 ( # $1 = whole list
927 ( # $2
928 ([ ]{0,'.$less_than_tab.'}) # $3 = number of spaces
929 ('.$marker_re.') # $4 = first list item marker
930 [ ]+
931 )
932 (?s:.+?)
933 ( # $5
934 \z
935 |
936 \n{2,}
937 (?=\S)
938 (?! # Negative lookahead for another list item marker
939 [ ]*
940 '.$marker_re.'[ ]+
941 )
942 |
943 (?= # Lookahead for another kind of list
944 \n
945 \3 # Must have the same indentation
946 '.$other_marker_re.'[ ]+
947 )
948 )
949 )
950 '; // mx
951
952 # We use a different prefix before nested lists than top-level lists.
953 # See extended comment in _ProcessListItems().
954
955 if ($this->list_level) {
956 $text = preg_replace_callback('{
957 ^
958 '.$whole_list_re.'
959 }mx',
960 array(&$this, '_doLists_callback'), $text);
961 }
962 else {
963 $text = preg_replace_callback('{
964 (?:(?<=\n)\n|\A\n?) # Must eat the newline
965 '.$whole_list_re.'
966 }mx',
967 array(&$this, '_doLists_callback'), $text);
968 }
969 }
970
971 return $text;
972 }
973 function _doLists_callback($matches) {
974 # Re-usable patterns to match list item bullets and number markers:
975 $marker_ul_re = '[*+-]';
976 $marker_ol_re = '\d+[\.]';
977 $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
978
979 $list = $matches[1];
980 $list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol";
981
982 $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re );
983
984 $list .= "\n";
985 $result = $this->processListItems($list, $marker_any_re);
986
987 $result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>");
988 return "\n". $result ."\n\n";
989 }
990
991 var $list_level = 0;
992
993 function processListItems($list_str, $marker_any_re) {
994 #
995 # Process the contents of a single ordered or unordered list, splitting it
996 # into individual list items.
997 #
998 # The $this->list_level global keeps track of when we're inside a list.
999 # Each time we enter a list, we increment it; when we leave a list,
1000 # we decrement. If it's zero, we're not in a list anymore.
1001 #
1002 # We do this because when we're not inside a list, we want to treat
1003 # something like this:
1004 #
1005 # I recommend upgrading to version
1006 # 8. Oops, now this line is treated
1007 # as a sub-list.
1008 #
1009 # As a single paragraph, despite the fact that the second line starts
1010 # with a digit-period-space sequence.
1011 #
1012 # Whereas when we're inside a list (or sub-list), that line will be
1013 # treated as the start of a sub-list. What a kludge, huh? This is
1014 # an aspect of Markdown's syntax that's hard to parse perfectly
1015 # without resorting to mind-reading. Perhaps the solution is to
1016 # change the syntax rules such that sub-lists must start with a
1017 # starting cardinal number; e.g. "1." or "a.".
1018
1019 $this->list_level++;
1020
1021 # trim trailing blank lines:
1022 $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
1023
1024 $list_str = preg_replace_callback('{
1025 (\n)? # leading line = $1
1026 (^[ ]*) # leading whitespace = $2
1027 ('.$marker_any_re.' # list marker and space = $3
1028 (?:[ ]+|(?=\n)) # space only required if item is not empty
1029 )
1030 ((?s:.*?)) # list item text = $4
1031 (?:(\n+(?=\n))|\n) # tailing blank line = $5
1032 (?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n))))
1033 }xm',
1034 array(&$this, '_processListItems_callback'), $list_str);
1035
1036 $this->list_level--;
1037 return $list_str;
1038 }
1039 function _processListItems_callback($matches) {
1040 $item = $matches[4];
1041 $leading_line =& $matches[1];
1042 $leading_space =& $matches[2];
1043 $marker_space = $matches[3];
1044 $tailing_blank_line =& $matches[5];
1045
1046 if ($leading_line || $tailing_blank_line ||
1047 preg_match('/\n{2,}/', $item))
1048 {
1049 # Replace marker with the appropriate whitespace indentation
1050 $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item;
1051 $item = $this->runBlockGamut($this->outdent($item)."\n");
1052 }
1053 else {
1054 # Recursion for sub-lists:
1055 $item = $this->doLists($this->outdent($item));
1056 $item = preg_replace('/\n+$/', '', $item);
1057 $item = $this->runSpanGamut($item);
1058 }
1059
1060 return "<li>" . $item . "</li>\n";
1061 }
1062
1063
1064 function doCodeBlocks($text) {
1065 #
1066 # Process Markdown `<pre><code>` blocks.
1067 #
1068 $text = preg_replace_callback('{
1069 (?:\n\n|\A\n?)
1070 ( # $1 = the code block -- one or more lines, starting with a space/tab
1071 (?>
1072 [ ]{'.$this->tab_width.'} # Lines must start with a tab or a tab-width of spaces
1073 .*\n+
1074 )+
1075 )
1076 ((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
1077 }xm',
1078 array(&$this, '_doCodeBlocks_callback'), $text);
1079
1080 return $text;
1081 }
1082 function _doCodeBlocks_callback($matches) {
1083 $codeblock = $matches[1];
1084
1085 $codeblock = $this->outdent($codeblock);
1086 $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
1087
1088 # trim leading newlines and trailing newlines
1089 $codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
1090
1091 $codeblock = "<pre><code>$codeblock\n</code></pre>";
1092 return "\n\n".$this->hashBlock($codeblock)."\n\n";
1093 }
1094
1095
1096 function makeCodeSpan($code) {
1097 #
1098 # Create a code span markup for $code. Called from handleSpanToken.
1099 #
1100 $code = htmlspecialchars(trim($code), ENT_NOQUOTES);
1101 return $this->hashPart("<code>$code</code>");
1102 }
1103
1104
1105 var $em_relist = array(
1106 '' => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S|$)(?![\.,:;]\s)',
1107 '*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
1108 '_' => '(?<=\S|^)(?<!_)_(?!_)',
1109 );
1110 var $strong_relist = array(
1111 '' => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S|$)(?![\.,:;]\s)',
1112 '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
1113 '__' => '(?<=\S|^)(?<!_)__(?!_)',
1114 );
1115 var $em_strong_relist = array(
1116 '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S|$)(?![\.,:;]\s)',
1117 '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
1118 '___' => '(?<=\S|^)(?<!_)___(?!_)',
1119 );
1120 var $em_strong_prepared_relist;
1121
1122 function prepareItalicsAndBold() {
1123 #
1124 # Prepare regular expressions for searching emphasis tokens in any
1125 # context.
1126 #
1127 foreach ($this->em_relist as $em => $em_re) {
1128 foreach ($this->strong_relist as $strong => $strong_re) {
1129 # Construct list of allowed token expressions.
1130 $token_relist = array();
1131 if (isset($this->em_strong_relist["$em$strong"])) {
1132 $token_relist[] = $this->em_strong_relist["$em$strong"];
1133 }
1134 $token_relist[] = $em_re;
1135 $token_relist[] = $strong_re;
1136
1137 # Construct master expression from list.
1138 $token_re = '{('. implode('|', $token_relist) .')}';
1139 $this->em_strong_prepared_relist["$em$strong"] = $token_re;
1140 }
1141 }
1142 }
1143
1144 function doItalicsAndBold($text) {
1145 $token_stack = array('');
1146 $text_stack = array('');
1147 $em = '';
1148 $strong = '';
1149 $tree_char_em = false;
1150
1151 while (1) {
1152 #
1153 # Get prepared regular expression for seraching emphasis tokens
1154 # in current context.
1155 #
1156 $token_re = $this->em_strong_prepared_relist["$em$strong"];
1157
1158 #
1159 # Each loop iteration search for the next emphasis token.
1160 # Each token is then passed to handleSpanToken.
1161 #
1162 $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
1163 $text_stack[0] .= $parts[0];
1164 $token =& $parts[1];
1165 $text =& $parts[2];
1166
1167 if (empty($token)) {
1168 # Reached end of text span: empty stack without emitting.
1169 # any more emphasis.
1170 while ($token_stack[0]) {
1171 $text_stack[1] .= array_shift($token_stack);
1172 $text_stack[0] .= array_shift($text_stack);
1173 }
1174 break;
1175 }
1176
1177 $token_len = strlen($token);
1178 if ($tree_char_em) {
1179 # Reached closing marker while inside a three-char emphasis.
1180 if ($token_len == 3) {
1181 # Three-char closing marker, close em and strong.
1182 array_shift($token_stack);
1183 $span = array_shift($text_stack);
1184 $span = $this->runSpanGamut($span);
1185 $span = "<strong><em>$span</em></strong>";
1186 $text_stack[0] .= $this->hashPart($span);
1187 $em = '';
1188 $strong = '';
1189 } else {
1190 # Other closing marker: close one em or strong and
1191 # change current token state to match the other
1192 $token_stack[0] = str_repeat($token[0], 3-$token_len);
1193 $tag = $token_len == 2 ? "strong" : "em";
1194 $span = $text_stack[0];
1195 $span = $this->runSpanGamut($span);
1196 $span = "<$tag>$span</$tag>";
1197 $text_stack[0] = $this->hashPart($span);
1198 $$tag = ''; # $$tag stands for $em or $strong
1199 }
1200 $tree_char_em = false;
1201 } else if ($token_len == 3) {
1202 if ($em) {
1203 # Reached closing marker for both em and strong.
1204 # Closing strong marker:
1205 for ($i = 0; $i < 2; ++$i) {
1206 $shifted_token = array_shift($token_stack);
1207 $tag = strlen($shifted_token) == 2 ? "strong" : "em";
1208 $span = array_shift($text_stack);
1209 $span = $this->runSpanGamut($span);
1210 $span = "<$tag>$span</$tag>";
1211 $text_stack[0] .= $this->hashPart($span);
1212 $$tag = ''; # $$tag stands for $em or $strong
1213 }
1214 } else {
1215 # Reached opening three-char emphasis marker. Push on token
1216 # stack; will be handled by the special condition above.
1217 $em = $token[0];
1218 $strong = "$em$em";
1219 array_unshift($token_stack, $token);
1220 array_unshift($text_stack, '');
1221 $tree_char_em = true;
1222 }
1223 } else if ($token_len == 2) {
1224 if ($strong) {
1225 # Unwind any dangling emphasis marker:
1226 if (strlen($token_stack[0]) == 1) {
1227 $text_stack[1] .= array_shift($token_stack);
1228 $text_stack[0] .= array_shift($text_stack);
1229 }
1230 # Closing strong marker:
1231 array_shift($token_stack);
1232 $span = array_shift($text_stack);
1233 $span = $this->runSpanGamut($span);
1234 $span = "<strong>$span</strong>";
1235 $text_stack[0] .= $this->hashPart($span);
1236 $strong = '';
1237 } else {
1238 array_unshift($token_stack, $token);
1239 array_unshift($text_stack, '');
1240 $strong = $token;
1241 }
1242 } else {
1243 # Here $token_len == 1
1244 if ($em) {
1245 if (strlen($token_stack[0]) == 1) {
1246 # Closing emphasis marker:
1247 array_shift($token_stack);
1248 $span = array_shift($text_stack);
1249 $span = $this->runSpanGamut($span);
1250 $span = "<em>$span</em>";
1251 $text_stack[0] .= $this->hashPart($span);
1252 $em = '';
1253 } else {
1254 $text_stack[0] .= $token;
1255 }
1256 } else {
1257 array_unshift($token_stack, $token);
1258 array_unshift($text_stack, '');
1259 $em = $token;
1260 }
1261 }
1262 }
1263 return $text_stack[0];
1264 }
1265
1266
1267 function doBlockQuotes($text) {
1268 $text = preg_replace_callback('/
1269 ( # Wrap whole match in $1
1270 (?>
1271 ^[ ]*>[ ]? # ">" at the start of a line
1272 .+\n # rest of the first line
1273 (.+\n)* # subsequent consecutive lines
1274 \n* # blanks
1275 )+
1276 )
1277 /xm',
1278 array(&$this, '_doBlockQuotes_callback'), $text);
1279
1280 return $text;
1281 }
1282 function _doBlockQuotes_callback($matches) {
1283 $bq = $matches[1];
1284 # trim one level of quoting - trim whitespace-only lines
1285 $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
1286 $bq = $this->runBlockGamut($bq); # recurse
1287
1288 $bq = preg_replace('/^/m', " ", $bq);
1289 # These leading spaces cause problem with <pre> content,
1290 # so we need to fix that:
1291 $bq = preg_replace_callback('{(\s*<pre>.+?</pre>)}sx',
1292 array(&$this, '_doBlockQuotes_callback2'), $bq);
1293
1294 return "\n". $this->hashBlock("<blockquote>\n$bq\n</blockquote>")."\n\n";
1295 }
1296 function _doBlockQuotes_callback2($matches) {
1297 $pre = $matches[1];
1298 $pre = preg_replace('/^ /m', '', $pre);
1299 return $pre;
1300 }
1301
1302
1303 function formParagraphs($text) {
1304 #
1305 # Params:
1306 # $text - string to process with html <p> tags
1307 #
1308 # Strip leading and trailing lines:
1309 $text = preg_replace('/\A\n+|\n+\z/', '', $text);
1310
1311 $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
1312
1313 #
1314 # Wrap <p> tags and unhashify HTML blocks
1315 #
1316 foreach ($grafs as $key => $value) {
1317 if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
1318 # Is a paragraph.
1319 $value = $this->runSpanGamut($value);
1320 $value = preg_replace('/^([ ]*)/', "<p>", $value);
1321 $value .= "</p>";
1322 $grafs[$key] = $this->unhash($value);
1323 }
1324 else {
1325 # Is a block.
1326 # Modify elements of @grafs in-place...
1327 $graf = $value;
1328 $block = $this->html_hashes[$graf];
1329 $graf = $block;
1330 // if (preg_match('{
1331 // \A
1332 // ( # $1 = <div> tag
1333 // <div \s+
1334 // [^>]*
1335 // \b
1336 // markdown\s*=\s* ([\'"]) # $2 = attr quote char
1337 // 1
1338 // \2
1339 // [^>]*
1340 // >
1341 // )
1342 // ( # $3 = contents
1343 // .*
1344 // )
1345 // (</div>) # $4 = closing tag
1346 // \z
1347 // }xs', $block, $matches))
1348 // {
1349 // list(, $div_open, , $div_content, $div_close) = $matches;
1350 //
1351 // # We can't call Markdown(), because that resets the hash;
1352 // # that initialization code should be pulled into its own sub, though.
1353 // $div_content = $this->hashHTMLBlocks($div_content);
1354 //
1355 // # Run document gamut methods on the content.
1356 // foreach ($this->document_gamut as $method => $priority) {
1357 // $div_content = $this->$method($div_content);
1358 // }
1359 //
1360 // $div_open = preg_replace(
1361 // '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
1362 //
1363 // $graf = $div_open . "\n" . $div_content . "\n" . $div_close;
1364 // }
1365 $grafs[$key] = $graf;
1366 }
1367 }
1368
1369 return implode("\n\n", $grafs);
1370 }
1371
1372
1373 function encodeAttribute($text) {
1374 #
1375 # Encode text for a double-quoted HTML attribute. This function
1376 # is *not* suitable for attributes enclosed in single quotes.
1377 #
1378 $text = $this->encodeAmpsAndAngles($text);
1379 $text = str_replace('"', '&quot;', $text);
1380 return $text;
1381 }
1382
1383
1384 function encodeAmpsAndAngles($text) {
1385 #
1386 # Smart processing for ampersands and angle brackets that need to
1387 # be encoded. Valid character entities are left alone unless the
1388 # no-entities mode is set.
1389 #
1390 if ($this->no_entities) {
1391 $text = str_replace('&', '&amp;', $text);
1392 } else {
1393 # Ampersand-encoding based entirely on Nat Irons's Amputator
1394 # MT plugin: <http://bumppo.net/projects/amputator/>
1395 $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',
1396 '&amp;', $text);;
1397 }
1398 # Encode remaining <'s
1399 $text = str_replace('<', '&lt;', $text);
1400
1401 return $text;
1402 }
1403
1404
1405 function doAutoLinks($text) {
1406 $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i',
1407 array(&$this, '_doAutoLinks_url_callback'), $text);
1408
1409 # Email addresses: <address@domain.foo>
1410 $text = preg_replace_callback('{
1411 <
1412 (?:mailto:)?
1413 (
1414 (?:
1415 [-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+
1416 |
1417 ".*?"
1418 )
1419 \@
1420 (?:
1421 [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
1422 |
1423 \[[\d.a-fA-F:]+\] # IPv4 & IPv6
1424 )
1425 )
1426 >
1427 }xi',
1428 array(&$this, '_doAutoLinks_email_callback'), $text);
1429 $text = preg_replace_callback('{<(tel:([^\'">\s]+))>}i',array(&$this, '_doAutoLinks_tel_callback'), $text);
1430
1431 return $text;
1432 }
1433 function _doAutoLinks_tel_callback($matches) {
1434 $url = $this->encodeAttribute($matches[1]);
1435 $tel = $this->encodeAttribute($matches[2]);
1436 $link = "<a href=\"$url\">$tel</a>";
1437 return $this->hashPart($link);
1438 }
1439 function _doAutoLinks_url_callback($matches) {
1440 $url = $this->encodeAttribute($matches[1]);
1441 $link = "<a href=\"$url\">$url</a>";
1442 return $this->hashPart($link);
1443 }
1444 function _doAutoLinks_email_callback($matches) {
1445 $address = $matches[1];
1446 $link = $this->encodeEmailAddress($address);
1447 return $this->hashPart($link);
1448 }
1449
1450
1451 function encodeEmailAddress($addr) {
1452 #
1453 # Input: an email address, e.g. "foo@example.com"
1454 #
1455 # Output: the email address as a mailto link, with each character
1456 # of the address encoded as either a decimal or hex entity, in
1457 # the hopes of foiling most address harvesting spam bots. E.g.:
1458 #
1459 # <p><a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x66;o&#111;
1460 # &#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;&#101;&#46;&#x63;&#111;
1461 # &#x6d;">&#x66;o&#111;&#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;
1462 # &#101;&#46;&#x63;&#111;&#x6d;</a></p>
1463 #
1464 # Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
1465 # With some optimizations by Milian Wolff.
1466 #
1467 $addr = "mailto:" . $addr;
1468 $chars = preg_split('/(?<!^)(?!$)/', $addr);
1469 $seed = (int)abs(crc32($addr) / strlen($addr)); # Deterministic seed.
1470
1471 foreach ($chars as $key => $char) {
1472 $ord = ord($char);
1473 # Ignore non-ascii chars.
1474 if ($ord < 128) {
1475 $r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
1476 # roughly 10% raw, 45% hex, 45% dec
1477 # '@' *must* be encoded. I insist.
1478 if ($r > 90 && $char != '@') /* do nothing */;
1479 else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';';
1480 else $chars[$key] = '&#'.$ord.';';
1481 }
1482 }
1483
1484 $addr = implode('', $chars);
1485 $text = implode('', array_slice($chars, 7)); # text without `mailto:`
1486 $addr = "<a href=\"$addr\">$text</a>";
1487
1488 return $addr;
1489 }
1490
1491
1492 function parseSpan($str) {
1493 #
1494 # Take the string $str and parse it into tokens, hashing embeded HTML,
1495 # escaped characters and handling code spans.
1496 #
1497 $output = '';
1498
1499 $span_re = '{
1500 (
1501 \\\\'.$this->escape_chars_re.'
1502 |
1503 (?<![`\\\\])
1504 `+ # code span marker
1505 '.( $this->no_markup ? '' : '
1506 |
1507 <!-- .*? --> # comment
1508 |
1509 <\?.*?\?> | <%.*?%> # processing instruction
1510 |
1511 <[!$]?[-a-zA-Z0-9:_]+ # regular tags
1512 (?>
1513 \s
1514 (?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
1515 )?
1516 >
1517 |
1518 <[-a-zA-Z0-9:_]+\s*/> # xml-style empty tag
1519 |
1520 </[-a-zA-Z0-9:_]+\s*> # closing tag
1521 ').'
1522 )
1523 }xs';
1524
1525 while (1) {
1526 #
1527 # Each loop iteration seach for either the next tag, the next
1528 # openning code span marker, or the next escaped character.
1529 # Each token is then passed to handleSpanToken.
1530 #
1531 $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
1532
1533 # Create token from text preceding tag.
1534 if ($parts[0] != "") {
1535 $output .= $parts[0];
1536 }
1537
1538 # Check if we reach the end.
1539 if (isset($parts[1])) {
1540 $output .= $this->handleSpanToken($parts[1], $parts[2]);
1541 $str = $parts[2];
1542 }
1543 else {
1544 break;
1545 }
1546 }
1547
1548 return $output;
1549 }
1550
1551
1552 function handleSpanToken($token, &$str) {
1553 #
1554 # Handle $token provided by parseSpan by determining its nature and
1555 # returning the corresponding value that should replace it.
1556 #
1557 switch ($token[0]) {
1558 case "\\":
1559 return $this->hashPart("&#". ord($token[1]). ";");
1560 case "`":
1561 # Search for end marker in remaining text.
1562 if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm',
1563 $str, $matches))
1564 {
1565 $str = $matches[2];
1566 $codespan = $this->makeCodeSpan($matches[1]);
1567 return $this->hashPart($codespan);
1568 }
1569 return $token; // return as text since no ending marker found.
1570 default:
1571 return $this->hashPart($token);
1572 }
1573 }
1574
1575
1576 function outdent($text) {
1577 #
1578 # Remove one level of line-leading tabs or spaces
1579 #
1580 return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text);
1581 }
1582
1583
1584 # String length function for detab. `_initDetab` will create a function to
1585 # hanlde UTF-8 if the default function does not exist.
1586 var $utf8_strlen = 'mb_strlen';
1587
1588 function detab($text) {
1589 #
1590 # Replace tabs with the appropriate amount of space.
1591 #
1592 # For each line we separate the line in blocks delemited by
1593 # tab characters. Then we reconstruct every line by adding the
1594 # appropriate number of space between each blocks.
1595
1596 $text = preg_replace_callback('/^.*\t.*$/m',
1597 array(&$this, '_detab_callback'), $text);
1598
1599 return $text;
1600 }
1601 function _detab_callback($matches) {
1602 $line = $matches[0];
1603 $strlen = $this->utf8_strlen; # strlen function for UTF-8.
1604
1605 # Split in blocks.
1606 $blocks = explode("\t", $line);
1607 # Add each blocks to the line.
1608 $line = $blocks[0];
1609 unset($blocks[0]); # Do not add first block twice.
1610 foreach ($blocks as $block) {
1611 # Calculate amount of space, insert spaces, insert block.
1612 $amount = $this->tab_width -
1613 $strlen($line, 'UTF-8') % $this->tab_width;
1614 $line .= str_repeat(" ", $amount) . $block;
1615 }
1616 return $line;
1617 }
1618 function _initDetab() {
1619 #
1620 # Check for the availability of the function in the `utf8_strlen` property
1621 # (initially `mb_strlen`). If the function is not available, create a
1622 # function that will loosely count the number of UTF-8 characters with a
1623 # regular expression.
1624 #
1625 if (is_callable($this->utf8_strlen)) return;
1626 $this->utf8_strlen = function($text) {
1627 return preg_match_all(
1628 "/[\\x00-\\xBF]|[\\xC0-\\xFF][\\x80-\\xBF]*/",
1629 $text, $m);
1630 };
1631 }
1632
1633
1634 function unhash($text) {
1635 #
1636 # Swap back in all the tags hashed by _HashHTMLBlocks.
1637 #
1638 return preg_replace_callback('/(.)\x1A[0-9]+\1/',
1639 array(&$this, '_unhash_callback'), $text);
1640 }
1641 function _unhash_callback($matches) {
1642 return $this->html_hashes[$matches[0]];
1643 }
1644
1645 }
1646
1647 /*
1648
1649 PHP Markdown
1650 ============
1651
1652 Description
1653 -----------
1654
1655 This is a PHP translation of the original Markdown formatter written in
1656 Perl by John Gruber.
1657
1658 Markdown is a text-to-HTML filter; it translates an easy-to-read /
1659 easy-to-write structured text format into HTML. Markdown's text format
1660 is mostly similar to that of plain text email, and supports features such
1661 as headers, *emphasis*, code blocks, blockquotes, and links.
1662
1663 Markdown's syntax is designed not as a generic markup language, but
1664 specifically to serve as a front-end to (X)HTML. You can use span-level
1665 HTML tags anywhere in a Markdown document, and you can use block level
1666 HTML tags (like <div> and <table> as well).
1667
1668 For more information about Markdown's syntax, see:
1669
1670 <http://daringfireball.net/projects/markdown/>
1671
1672
1673 Bugs
1674 ----
1675
1676 To file bug reports please send email to:
1677
1678 <michel.fortin@michelf.ca>
1679
1680 Please include with your report: (1) the example input; (2) the output you
1681 expected; (3) the output Markdown actually produced.
1682
1683
1684 Version History
1685 ---------------
1686
1687 See the readme file for detailed release notes for this version.
1688
1689
1690 Copyright and License
1691 ---------------------
1692
1693 PHP Markdown
1694 Copyright (c) 2004-2013 Michel Fortin
1695 <http://michelf.ca/>
1696 All rights reserved.
1697
1698 Based on Markdown
1699 Copyright (c) 2003-2006 John Gruber
1700 <http://daringfireball.net/>
1701 All rights reserved.
1702
1703 Redistribution and use in source and binary forms, with or without
1704 modification, are permitted provided that the following conditions are
1705 met:
1706
1707 * Redistributions of source code must retain the above copyright notice,
1708 this list of conditions and the following disclaimer.
1709
1710 * Redistributions in binary form must reproduce the above copyright
1711 notice, this list of conditions and the following disclaimer in the
1712 documentation and/or other materials provided with the distribution.
1713
1714 * Neither the name "Markdown" nor the names of its contributors may
1715 be used to endorse or promote products derived from this software
1716 without specific prior written permission.
1717
1718 This software is provided by the copyright holders and contributors "as
1719 is" and any express or implied warranties, including, but not limited
1720 to, the implied warranties of merchantability and fitness for a
1721 particular purpose are disclaimed. In no event shall the copyright owner
1722 or contributors be liable for any direct, indirect, incidental, special,
1723 exemplary, or consequential damages (including, but not limited to,
1724 procurement of substitute goods or services; loss of use, data, or
1725 profits; or business interruption) however caused and on any theory of
1726 liability, whether in contract, strict liability, or tort (including
1727 negligence or otherwise) arising in any way out of the use of this
1728 software, even if advised of the possibility of such damage.
1729
1730 */
1731