PluginProbe
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits / 1.0.8
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits v1.0.8
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 1.0.8, at lib/markdown.php

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