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

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