PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 6.2
Jetpack – WP Security, Backup, Speed, & Growth v6.2
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / _inc / lib / markdown / extra.php

extra.php in Jetpack – WP Security, Backup, Speed, & Growth 6.2, at _inc/lib/markdown/extra.php

3,208 lines 88.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 #
3 # Markdown Extra - A text-to-HTML conversion tool for web writers
4 #
5 # PHP Markdown & Extra
6 # Copyright (c) 2004-2013 Michel Fortin
7 # <http://michelf.ca/projects/php-markdown/>
8 #
9 # Original Markdown
10 # Copyright (c) 2004-2006 John Gruber
11 # <http://daringfireball.net/projects/markdown/>
12 #
13 # Tweaked to remove WordPress interface
14
15
16 define( 'MARKDOWN_VERSION', "1.0.2" ); # 29 Nov 2013
17 define( 'MARKDOWNEXTRA_VERSION', "1.2.8" ); # 29 Nov 2013
18
19
20 #
21 # Global default settings:
22 #
23
24 # Change to ">" for HTML output
25 @define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX', " />");
26
27 # Define the width of a tab for code blocks.
28 @define( 'MARKDOWN_TAB_WIDTH', 4 );
29
30 # Optional title attribute for footnote links and backlinks.
31 @define( 'MARKDOWN_FN_LINK_TITLE', "" );
32 @define( 'MARKDOWN_FN_BACKLINK_TITLE', "" );
33
34 # Optional class attribute for footnote links and backlinks.
35 @define( 'MARKDOWN_FN_LINK_CLASS', "jetpack-footnote" );
36 @define( 'MARKDOWN_FN_BACKLINK_CLASS', "" );
37
38 # Optional class prefix for fenced code block.
39 @define( 'MARKDOWN_CODE_CLASS_PREFIX', "" );
40
41 # Class attribute for code blocks goes on the `code` tag;
42 # setting this to true will put attributes on the `pre` tag instead.
43 @define( 'MARKDOWN_CODE_ATTR_ON_PRE', false );
44
45
46
47 ### Standard Function Interface ###
48
49 @define( 'MARKDOWN_PARSER_CLASS', 'MarkdownExtra_Parser' );
50
51 function Markdown($text) {
52 #
53 # Initialize the parser and return the result of its transform method.
54 #
55 # Setup static parser variable.
56 static $parser;
57 if (!isset($parser)) {
58 $parser_class = MARKDOWN_PARSER_CLASS;
59 $parser = new $parser_class;
60 }
61
62 # Transform text using parser.
63 return $parser->transform($text);
64 }
65
66 /**
67 * Returns the length of $text loosely counting the number of UTF-8 characters with regular expression.
68 * Used by the Markdown_Parser class when mb_strlen is not available.
69 *
70 * @since 5.9
71 *
72 * @return string Length of the multibyte string
73 *
74 */
75 function jetpack_utf8_strlen( $text ) {
76 return preg_match_all( "/[\\x00-\\xBF]|[\\xC0-\\xFF][\\x80-\\xBF]*/", $text, $m );
77 }
78
79 #
80 # Markdown Parser Class
81 #
82
83 class Markdown_Parser {
84
85 ### Configuration Variables ###
86
87 # Change to ">" for HTML output.
88 public $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
89 public $tab_width = MARKDOWN_TAB_WIDTH;
90
91 # Change to `true` to disallow markup or entities.
92 public $no_markup = false;
93 public $no_entities = false;
94
95 # Predefined urls and titles for reference links and images.
96 public $predef_urls = array();
97 public $predef_titles = array();
98
99
100 ### Parser Implementation ###
101
102 # Regex to match balanced [brackets].
103 # Needed to insert a maximum bracked depth while converting to PHP.
104 public $nested_brackets_depth = 6;
105 public $nested_brackets_re;
106
107 public $nested_url_parenthesis_depth = 4;
108 public $nested_url_parenthesis_re;
109
110 # Table of hash values for escaped characters:
111 public $escape_chars = '\`*_{}[]()>#+-.!';
112 public $escape_chars_re;
113
114
115 function __construct() {
116 #
117 # Constructor function. Initialize appropriate member variables.
118 #
119 $this->_initDetab();
120 $this->prepareItalicsAndBold();
121
122 $this->nested_brackets_re =
123 str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth).
124 str_repeat('\])*', $this->nested_brackets_depth);
125
126 $this->nested_url_parenthesis_re =
127 str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth).
128 str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth);
129
130 $this->escape_chars_re = '['.preg_quote($this->escape_chars).']';
131
132 # Sort document, block, and span gamut in ascendent priority order.
133 asort($this->document_gamut);
134 asort($this->block_gamut);
135 asort($this->span_gamut);
136 }
137
138
139 # Internal hashes used during transformation.
140 public $urls = array();
141 public $titles = array();
142 public $html_hashes = array();
143
144 # Status flag to avoid invalid nesting.
145 public $in_anchor = false;
146
147
148 function setup() {
149 #
150 # Called before the transformation process starts to setup parser
151 # states.
152 #
153 # Clear global hashes.
154 $this->urls = $this->predef_urls;
155 $this->titles = $this->predef_titles;
156 $this->html_hashes = array();
157
158 $this->in_anchor = false;
159 }
160
161 function teardown() {
162 #
163 # Called after the transformation process to clear any variable
164 # which may be taking up memory unnecessarly.
165 #
166 $this->urls = array();
167 $this->titles = array();
168 $this->html_hashes = array();
169 }
170
171
172 function transform($text) {
173 #
174 # Main function. Performs some preprocessing on the input text
175 # and pass it through the document gamut.
176 #
177 $this->setup();
178
179 # Remove UTF-8 BOM and marker character in input, if present.
180 $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text);
181
182 # Standardize line endings:
183 # DOS to Unix and Mac to Unix
184 $text = preg_replace('{\r\n?}', "\n", $text);
185
186 # Make sure $text ends with a couple of newlines:
187 $text .= "\n\n";
188
189 # Convert all tabs to spaces.
190 $text = $this->detab($text);
191
192 # Turn block-level HTML blocks into hash entries
193 $text = $this->hashHTMLBlocks($text);
194
195 # Strip any lines consisting only of spaces and tabs.
196 # This makes subsequent regexen easier to write, because we can
197 # match consecutive blank lines with /\n+/ instead of something
198 # contorted like /[ ]*\n+/ .
199 $text = preg_replace('/^[ ]+$/m', '', $text);
200
201 # Run document gamut methods.
202 foreach ($this->document_gamut as $method => $priority) {
203 $text = $this->$method($text);
204 }
205
206 $this->teardown();
207
208 return $text . "\n";
209 }
210
211 public $document_gamut = array(
212 # Strip link definitions, store in hashes.
213 "stripLinkDefinitions" => 20,
214
215 "runBasicBlockGamut" => 30,
216 );
217
218
219 function stripLinkDefinitions($text) {
220 #
221 # Strips link definitions from text, stores the URLs and titles in
222 # hash references.
223 #
224 $less_than_tab = $this->tab_width - 1;
225
226 # Link defs are in the form: ^[id]: url "optional title"
227 $text = preg_replace_callback('{
228 ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1
229 [ ]*
230 \n? # maybe *one* newline
231 [ ]*
232 (?:
233 <(.+?)> # url = $2
234 |
235 (\S+?) # url = $3
236 )
237 [ ]*
238 \n? # maybe one newline
239 [ ]*
240 (?:
241 (?<=\s) # lookbehind for whitespace
242 ["(]
243 (.*?) # title = $4
244 [")]
245 [ ]*
246 )? # title is optional
247 (?:\n+|\Z)
248 }xm',
249 array(&$this, '_stripLinkDefinitions_callback'),
250 $text);
251 return $text;
252 }
253 function _stripLinkDefinitions_callback($matches) {
254 $link_id = strtolower($matches[1]);
255 $url = $matches[2] == '' ? $matches[3] : $matches[2];
256 $this->urls[$link_id] = $url;
257 $this->titles[$link_id] =& $matches[4];
258 return ''; # String that will replace the block
259 }
260
261
262 function hashHTMLBlocks($text) {
263 if ($this->no_markup) return $text;
264
265 $less_than_tab = $this->tab_width - 1;
266
267 # Hashify HTML blocks:
268 # We only want to do this for block-level HTML tags, such as headers,
269 # lists, and tables. That's because we still want to wrap <p>s around
270 # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
271 # phrase emphasis, and spans. The list of tags we're looking for is
272 # hard-coded:
273 #
274 # * List "a" is made of tags which can be both inline or block-level.
275 # These will be treated block-level when the start tag is alone on
276 # its line, otherwise they're not matched here and will be taken as
277 # inline later.
278 # * List "b" is made of tags which are always block-level;
279 #
280 $block_tags_a_re = 'ins|del';
281 $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'.
282 'script|noscript|form|fieldset|iframe|math|svg|'.
283 'article|section|nav|aside|hgroup|header|footer|'.
284 'figure';
285
286 # Regular expression for the content of a block tag.
287 $nested_tags_level = 4;
288 $attr = '
289 (?> # optional tag attributes
290 \s # starts with whitespace
291 (?>
292 [^>"/]+ # text outside quotes
293 |
294 /+(?!>) # slash not followed by ">"
295 |
296 "[^"]*" # text inside double quotes (tolerate ">")
297 |
298 \'[^\']*\' # text inside single quotes (tolerate ">")
299 )*
300 )?
301 ';
302 $content =
303 str_repeat('
304 (?>
305 [^<]+ # content without tag
306 |
307 <\2 # nested opening tag
308 '.$attr.' # attributes
309 (?>
310 />
311 |
312 >', $nested_tags_level). # end of opening tag
313 '.*?'. # last level nested tag content
314 str_repeat('
315 </\2\s*> # closing nested tag
316 )
317 |
318 <(?!/\2\s*> # other tags with a different name
319 )
320 )*',
321 $nested_tags_level);
322 $content2 = str_replace('\2', '\3', $content);
323
324 # First, look for nested blocks, e.g.:
325 # <div>
326 # <div>
327 # tags for inner block must be indented.
328 # </div>
329 # </div>
330 #
331 # The outermost tags must start at the left margin for this to match, and
332 # the inner nested divs must be indented.
333 # We need to do this before the next, more liberal match, because the next
334 # match will start at the first `<div>` and stop at the first `</div>`.
335 $text = preg_replace_callback('{(?>
336 (?>
337 (?<=\n\n) # Starting after a blank line
338 | # or
339 \A\n? # the beginning of the doc
340 )
341 ( # save in $1
342
343 # Match from `\n<tag>` to `</tag>\n`, handling nested tags
344 # in between.
345
346 [ ]{0,'.$less_than_tab.'}
347 <('.$block_tags_b_re.')# start tag = $2
348 '.$attr.'> # attributes followed by > and \n
349 '.$content.' # content, support nesting
350 </\2> # the matching end tag
351 [ ]* # trailing spaces/tabs
352 (?=\n+|\Z) # followed by a newline or end of document
353
354 | # Special version for tags of group a.
355
356 [ ]{0,'.$less_than_tab.'}
357 <('.$block_tags_a_re.')# start tag = $3
358 '.$attr.'>[ ]*\n # attributes followed by >
359 '.$content2.' # content, support nesting
360 </\3> # the matching end tag
361 [ ]* # trailing spaces/tabs
362 (?=\n+|\Z) # followed by a newline or end of document
363
364 | # Special case just for <hr />. It was easier to make a special
365 # case than to make the other regex more complicated.
366
367 [ ]{0,'.$less_than_tab.'}
368 <(hr) # start tag = $2
369 '.$attr.' # attributes
370 /?> # the matching end tag
371 [ ]*
372 (?=\n{2,}|\Z) # followed by a blank line or end of document
373
374 | # Special case for standalone HTML comments:
375
376 [ ]{0,'.$less_than_tab.'}
377 (?s:
378 <!-- .*? -->
379 )
380 [ ]*
381 (?=\n{2,}|\Z) # followed by a blank line or end of document
382
383 | # PHP and ASP-style processor instructions (<? and <%)
384
385 [ ]{0,'.$less_than_tab.'}
386 (?s:
387 <([?%]) # $2
388 .*?
389 \2>
390 )
391 [ ]*
392 (?=\n{2,}|\Z) # followed by a blank line or end of document
393
394 )
395 )}Sxmi',
396 array(&$this, '_hashHTMLBlocks_callback'),
397 $text);
398
399 return $text;
400 }
401 function _hashHTMLBlocks_callback($matches) {
402 $text = $matches[1];
403 $key = $this->hashBlock($text);
404 return "\n\n$key\n\n";
405 }
406
407
408 function hashPart($text, $boundary = 'X') {
409 #
410 # Called whenever a tag must be hashed when a function insert an atomic
411 # element in the text stream. Passing $text to through this function gives
412 # a unique text-token which will be reverted back when calling unhash.
413 #
414 # The $boundary argument specify what character should be used to surround
415 # the token. By convension, "B" is used for block elements that needs not
416 # to be wrapped into paragraph tags at the end, ":" is used for elements
417 # that are word separators and "X" is used in the general case.
418 #
419 # Swap back any tag hash found in $text so we do not have to `unhash`
420 # multiple times at the end.
421 $text = $this->unhash($text);
422
423 # Then hash the block.
424 static $i = 0;
425 $key = "$boundary\x1A" . ++$i . $boundary;
426 $this->html_hashes[$key] = $text;
427 return $key; # String that will replace the tag.
428 }
429
430
431 function hashBlock($text) {
432 #
433 # Shortcut function for hashPart with block-level boundaries.
434 #
435 return $this->hashPart($text, 'B');
436 }
437
438
439 public $block_gamut = array(
440 #
441 # These are all the transformations that form block-level
442 # tags like paragraphs, headers, and list items.
443 #
444 "doHeaders" => 10,
445 "doHorizontalRules" => 20,
446
447 "doLists" => 40,
448 "doCodeBlocks" => 50,
449 "doBlockQuotes" => 60,
450 );
451
452 function runBlockGamut($text) {
453 #
454 # Run block gamut tranformations.
455 #
456 # We need to escape raw HTML in Markdown source before doing anything
457 # else. This need to be done for each block, and not only at the
458 # beginning in the Markdown function since hashed blocks can be part of
459 # list items and could have been indented. Indented blocks would have
460 # been seen as a code block in a previous pass of hashHTMLBlocks.
461 $text = $this->hashHTMLBlocks($text);
462
463 return $this->runBasicBlockGamut($text);
464 }
465
466 function runBasicBlockGamut($text) {
467 #
468 # Run block gamut tranformations, without hashing HTML blocks. This is
469 # useful when HTML blocks are known to be already hashed, like in the first
470 # whole-document pass.
471 #
472 foreach ($this->block_gamut as $method => $priority) {
473 $text = $this->$method($text);
474 }
475
476 # Finally form paragraph and restore hashed blocks.
477 $text = $this->formParagraphs($text);
478
479 return $text;
480 }
481
482
483 function doHorizontalRules($text) {
484 # Do Horizontal Rules:
485 return preg_replace(
486 '{
487 ^[ ]{0,3} # Leading space
488 ([-*_]) # $1: First marker
489 (?> # Repeated marker group
490 [ ]{0,2} # Zero, one, or two spaces.
491 \1 # Marker character
492 ){2,} # Group repeated at least twice
493 [ ]* # Tailing spaces
494 $ # End of line.
495 }mx',
496 "\n".$this->hashBlock("<hr$this->empty_element_suffix")."\n",
497 $text);
498 }
499
500
501 public $span_gamut = array(
502 #
503 # These are all the transformations that occur *within* block-level
504 # tags like paragraphs, headers, and list items.
505 #
506 # Process character escapes, code spans, and inline HTML
507 # in one shot.
508 "parseSpan" => -30,
509
510 # Process anchor and image tags. Images must come first,
511 # because ![foo][f] looks like an anchor.
512 "doImages" => 10,
513 "doAnchors" => 20,
514
515 # Make links out of things like `<http://example.com/>`
516 # Must come after doAnchors, because you can use < and >
517 # delimiters in inline links like [this](<url>).
518 "doAutoLinks" => 30,
519 "encodeAmpsAndAngles" => 40,
520
521 "doItalicsAndBold" => 50,
522 "doHardBreaks" => 60,
523 );
524
525 function runSpanGamut($text) {
526 #
527 # Run span gamut tranformations.
528 #
529 foreach ($this->span_gamut as $method => $priority) {
530 $text = $this->$method($text);
531 }
532
533 return $text;
534 }
535
536
537 function doHardBreaks($text) {
538 # Do hard breaks:
539 return preg_replace_callback('/ {2,}\n/',
540 array(&$this, '_doHardBreaks_callback'), $text);
541 }
542 function _doHardBreaks_callback($matches) {
543 return $this->hashPart("<br$this->empty_element_suffix\n");
544 }
545
546
547 function doAnchors($text) {
548 #
549 # Turn Markdown link shortcuts into XHTML <a> tags.
550 #
551 if ($this->in_anchor) return $text;
552 $this->in_anchor = true;
553
554 #
555 # First, handle reference-style links: [link text] [id]
556 #
557 $text = preg_replace_callback('{
558 ( # wrap whole match in $1
559 \[
560 ('.$this->nested_brackets_re.') # link text = $2
561 \]
562
563 [ ]? # one optional space
564 (?:\n[ ]*)? # one optional newline followed by spaces
565
566 \[
567 (.*?) # id = $3
568 \]
569 )
570 }xs',
571 array(&$this, '_doAnchors_reference_callback'), $text);
572
573 #
574 # Next, inline-style links: [link text](url "optional title")
575 #
576 $text = preg_replace_callback('{
577 ( # wrap whole match in $1
578 \[
579 ('.$this->nested_brackets_re.') # link text = $2
580 \]
581 \( # literal paren
582 [ \n]*
583 (?:
584 <(.+?)> # href = $3
585 |
586 ('.$this->nested_url_parenthesis_re.') # href = $4
587 )
588 [ \n]*
589 ( # $5
590 ([\'"]) # quote char = $6
591 (.*?) # Title = $7
592 \6 # matching quote
593 [ \n]* # ignore any spaces/tabs between closing quote and )
594 )? # title is optional
595 \)
596 )
597 }xs',
598 array(&$this, '_doAnchors_inline_callback'), $text);
599
600 #
601 # Last, handle reference-style shortcuts: [link text]
602 # These must come last in case you've also got [link text][1]
603 # or [link text](/foo)
604 #
605 $text = preg_replace_callback('{
606 ( # wrap whole match in $1
607 \[
608 ([^\[\]]+) # link text = $2; can\'t contain [ or ]
609 \]
610 )
611 }xs',
612 array(&$this, '_doAnchors_reference_callback'), $text);
613
614 $this->in_anchor = false;
615 return $text;
616 }
617 function _doAnchors_reference_callback($matches) {
618 $whole_match = $matches[1];
619 $link_text = $matches[2];
620 $link_id =& $matches[3];
621
622 if ($link_id == "") {
623 # for shortcut links like [this][] or [this].
624 $link_id = $link_text;
625 }
626
627 # lower-case and turn embedded newlines into spaces
628 $link_id = strtolower($link_id);
629 $link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
630
631 if (isset($this->urls[$link_id])) {
632 $url = $this->urls[$link_id];
633 $url = $this->encodeAttribute($url);
634
635 $result = "<a href=\"$url\"";
636 if ( isset( $this->titles[$link_id] ) ) {
637 $title = $this->titles[$link_id];
638 $title = $this->encodeAttribute($title);
639 $result .= " title=\"$title\"";
640 }
641
642 $link_text = $this->runSpanGamut($link_text);
643 $result .= ">$link_text</a>";
644 $result = $this->hashPart($result);
645 }
646 else {
647 $result = $whole_match;
648 }
649 return $result;
650 }
651 function _doAnchors_inline_callback($matches) {
652 $whole_match = $matches[1];
653 $link_text = $this->runSpanGamut($matches[2]);
654 $url = $matches[3] == '' ? $matches[4] : $matches[3];
655 $title =& $matches[7];
656
657 $url = $this->encodeAttribute($url);
658
659 $result = "<a href=\"$url\"";
660 if (isset($title)) {
661 $title = $this->encodeAttribute($title);
662 $result .= " title=\"$title\"";
663 }
664
665 $link_text = $this->runSpanGamut($link_text);
666 $result .= ">$link_text</a>";
667
668 return $this->hashPart($result);
669 }
670
671
672 function doImages($text) {
673 #
674 # Turn Markdown image shortcuts into <img> tags.
675 #
676 #
677 # First, handle reference-style labeled images: ![alt text][id]
678 #
679 $text = preg_replace_callback('{
680 ( # wrap whole match in $1
681 !\[
682 ('.$this->nested_brackets_re.') # alt text = $2
683 \]
684
685 [ ]? # one optional space
686 (?:\n[ ]*)? # one optional newline followed by spaces
687
688 \[
689 (.*?) # id = $3
690 \]
691
692 )
693 }xs',
694 array(&$this, '_doImages_reference_callback'), $text);
695
696 #
697 # Next, handle inline images: ![alt text](url "optional title")
698 # Don't forget: encode * and _
699 #
700 $text = preg_replace_callback('{
701 ( # wrap whole match in $1
702 !\[
703 ('.$this->nested_brackets_re.') # alt text = $2
704 \]
705 \s? # One optional whitespace character
706 \( # literal paren
707 [ \n]*
708 (?:
709 <(\S*)> # src url = $3
710 |
711 ('.$this->nested_url_parenthesis_re.') # src url = $4
712 )
713 [ \n]*
714 ( # $5
715 ([\'"]) # quote char = $6
716 (.*?) # title = $7
717 \6 # matching quote
718 [ \n]*
719 )? # title is optional
720 \)
721 )
722 }xs',
723 array(&$this, '_doImages_inline_callback'), $text);
724
725 return $text;
726 }
727 function _doImages_reference_callback($matches) {
728 $whole_match = $matches[1];
729 $alt_text = $matches[2];
730 $link_id = strtolower($matches[3]);
731
732 if ($link_id == "") {
733 $link_id = strtolower($alt_text); # for shortcut links like ![this][].
734 }
735
736 $alt_text = $this->encodeAttribute($alt_text);
737 if (isset($this->urls[$link_id])) {
738 $url = $this->encodeAttribute($this->urls[$link_id]);
739 $result = "<img src=\"$url\" alt=\"$alt_text\"";
740 if (isset($this->titles[$link_id])) {
741 $title = $this->titles[$link_id];
742 $title = $this->encodeAttribute($title);
743 $result .= " title=\"$title\"";
744 }
745 $result .= $this->empty_element_suffix;
746 $result = $this->hashPart($result);
747 }
748 else {
749 # If there's no such link ID, leave intact:
750 $result = $whole_match;
751 }
752
753 return $result;
754 }
755 function _doImages_inline_callback($matches) {
756 $whole_match = $matches[1];
757 $alt_text = $matches[2];
758 $url = $matches[3] == '' ? $matches[4] : $matches[3];
759 $title =& $matches[7];
760
761 $alt_text = $this->encodeAttribute($alt_text);
762 $url = $this->encodeAttribute($url);
763 $result = "<img src=\"$url\" alt=\"$alt_text\"";
764 if (isset($title)) {
765 $title = $this->encodeAttribute($title);
766 $result .= " title=\"$title\""; # $title already quoted
767 }
768 $result .= $this->empty_element_suffix;
769
770 return $this->hashPart($result);
771 }
772
773
774 function doHeaders($text) {
775 # Setext-style headers:
776 # Header 1
777 # ========
778 #
779 # Header 2
780 # --------
781 #
782 $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
783 array(&$this, '_doHeaders_callback_setext'), $text);
784
785 # atx-style headers:
786 # # Header 1
787 # ## Header 2
788 # ## Header 2 with closing hashes ##
789 # ...
790 # ###### Header 6
791 #
792 $text = preg_replace_callback('{
793 ^(\#{1,6}) # $1 = string of #\'s
794 [ ]*
795 (.+?) # $2 = Header text
796 [ ]*
797 \#* # optional closing #\'s (not counted)
798 \n+
799 }xm',
800 array(&$this, '_doHeaders_callback_atx'), $text);
801
802 return $text;
803 }
804 function _doHeaders_callback_setext($matches) {
805 # Terrible hack to check we haven't found an empty list item.
806 if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1]))
807 return $matches[0];
808
809 $level = $matches[2]{0} == '=' ? 1 : 2;
810 $block = "<h$level>".$this->runSpanGamut($matches[1])."</h$level>";
811 return "\n" . $this->hashBlock($block) . "\n\n";
812 }
813 function _doHeaders_callback_atx($matches) {
814 $level = strlen($matches[1]);
815 $block = "<h$level>".$this->runSpanGamut($matches[2])."</h$level>";
816 return "\n" . $this->hashBlock($block) . "\n\n";
817 }
818
819
820 function doLists($text) {
821 #
822 # Form HTML ordered (numbered) and unordered (bulleted) lists.
823 #
824 $less_than_tab = $this->tab_width - 1;
825
826 # Re-usable patterns to match list item bullets and number markers:
827 $marker_ul_re = '[*+-]';
828 $marker_ol_re = '\d+[\.]';
829 $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
830
831 $markers_relist = array(
832 $marker_ul_re => $marker_ol_re,
833 $marker_ol_re => $marker_ul_re,
834 );
835
836 foreach ($markers_relist as $marker_re => $other_marker_re) {
837 # Re-usable pattern to match any entirel ul or ol list:
838 $whole_list_re = '
839 ( # $1 = whole list
840 ( # $2
841 ([ ]{0,'.$less_than_tab.'}) # $3 = number of spaces
842 ('.$marker_re.') # $4 = first list item marker
843 [ ]+
844 )
845 (?s:.+?)
846 ( # $5
847 \z
848 |
849 \n{2,}
850 (?=\S)
851 (?! # Negative lookahead for another list item marker
852 [ ]*
853 '.$marker_re.'[ ]+
854 )
855 |
856 (?= # Lookahead for another kind of list
857 \n
858 \3 # Must have the same indentation
859 '.$other_marker_re.'[ ]+
860 )
861 )
862 )
863 '; // mx
864
865 # We use a different prefix before nested lists than top-level lists.
866 # See extended comment in _ProcessListItems().
867
868 if ($this->list_level) {
869 $text = preg_replace_callback('{
870 ^
871 '.$whole_list_re.'
872 }mx',
873 array(&$this, '_doLists_callback'), $text);
874 }
875 else {
876 $text = preg_replace_callback('{
877 (?:(?<=\n)\n|\A\n?) # Must eat the newline
878 '.$whole_list_re.'
879 }mx',
880 array(&$this, '_doLists_callback'), $text);
881 }
882 }
883
884 return $text;
885 }
886 function _doLists_callback($matches) {
887 # Re-usable patterns to match list item bullets and number markers:
888 $marker_ul_re = '[*+-]';
889 $marker_ol_re = '\d+[\.]';
890 $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
891
892 $list = $matches[1];
893 $list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol";
894
895 $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re );
896
897 $list .= "\n";
898 $result = $this->processListItems($list, $marker_any_re);
899
900 $result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>");
901 return "\n". $result ."\n\n";
902 }
903
904 public $list_level = 0;
905
906 function processListItems($list_str, $marker_any_re) {
907 #
908 # Process the contents of a single ordered or unordered list, splitting it
909 # into individual list items.
910 #
911 # The $this->list_level global keeps track of when we're inside a list.
912 # Each time we enter a list, we increment it; when we leave a list,
913 # we decrement. If it's zero, we're not in a list anymore.
914 #
915 # We do this because when we're not inside a list, we want to treat
916 # something like this:
917 #
918 # I recommend upgrading to version
919 # 8. Oops, now this line is treated
920 # as a sub-list.
921 #
922 # As a single paragraph, despite the fact that the second line starts
923 # with a digit-period-space sequence.
924 #
925 # Whereas when we're inside a list (or sub-list), that line will be
926 # treated as the start of a sub-list. What a kludge, huh? This is
927 # an aspect of Markdown's syntax that's hard to parse perfectly
928 # without resorting to mind-reading. Perhaps the solution is to
929 # change the syntax rules such that sub-lists must start with a
930 # starting cardinal number; e.g. "1." or "a.".
931
932 $this->list_level++;
933
934 # trim trailing blank lines:
935 $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
936
937 $list_str = preg_replace_callback('{
938 (\n)? # leading line = $1
939 (^[ ]*) # leading whitespace = $2
940 ('.$marker_any_re.' # list marker and space = $3
941 (?:[ ]+|(?=\n)) # space only required if item is not empty
942 )
943 ((?s:.*?)) # list item text = $4
944 (?:(\n+(?=\n))|\n) # tailing blank line = $5
945 (?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n))))
946 }xm',
947 array(&$this, '_processListItems_callback'), $list_str);
948
949 $this->list_level--;
950 return $list_str;
951 }
952 function _processListItems_callback($matches) {
953 $item = $matches[4];
954 $leading_line =& $matches[1];
955 $leading_space =& $matches[2];
956 $marker_space = $matches[3];
957 $tailing_blank_line =& $matches[5];
958
959 if ($leading_line || $tailing_blank_line ||
960 preg_match('/\n{2,}/', $item))
961 {
962 # Replace marker with the appropriate whitespace indentation
963 $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item;
964 $item = $this->runBlockGamut($this->outdent($item)."\n");
965 }
966 else {
967 # Recursion for sub-lists:
968 $item = $this->doLists($this->outdent($item));
969 $item = preg_replace('/\n+$/', '', $item);
970 $item = $this->runSpanGamut($item);
971 }
972
973 return "<li>" . $item . "</li>\n";
974 }
975
976
977 function doCodeBlocks($text) {
978 #
979 # Process Markdown `<pre><code>` blocks.
980 #
981 $text = preg_replace_callback('{
982 (?:\n\n|\A\n?)
983 ( # $1 = the code block -- one or more lines, starting with a space/tab
984 (?>
985 [ ]{'.$this->tab_width.'} # Lines must start with a tab or a tab-width of spaces
986 .*\n+
987 )+
988 )
989 ((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
990 }xm',
991 array(&$this, '_doCodeBlocks_callback'), $text);
992
993 return $text;
994 }
995 function _doCodeBlocks_callback($matches) {
996 $codeblock = $matches[1];
997
998 $codeblock = $this->outdent($codeblock);
999 $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
1000
1001 # trim leading newlines and trailing newlines
1002 $codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
1003
1004 $codeblock = "<pre><code>$codeblock\n</code></pre>";
1005 return "\n\n".$this->hashBlock($codeblock)."\n\n";
1006 }
1007
1008
1009 function makeCodeSpan($code) {
1010 #
1011 # Create a code span markup for $code. Called from handleSpanToken.
1012 #
1013 $code = htmlspecialchars(trim($code), ENT_NOQUOTES);
1014 return $this->hashPart("<code>$code</code>");
1015 }
1016
1017
1018 public $em_relist = array(
1019 '' => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S|$)(?![\.,:;]\s)',
1020 '*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
1021 '_' => '(?<=\S|^)(?<!_)_(?!_)',
1022 );
1023 public $strong_relist = array(
1024 '' => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S|$)(?![\.,:;]\s)',
1025 '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
1026 '__' => '(?<=\S|^)(?<!_)__(?!_)',
1027 );
1028 public $em_strong_relist = array(
1029 '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S|$)(?![\.,:;]\s)',
1030 '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
1031 '___' => '(?<=\S|^)(?<!_)___(?!_)',
1032 );
1033 public $em_strong_prepared_relist;
1034
1035 function prepareItalicsAndBold() {
1036 #
1037 # Prepare regular expressions for searching emphasis tokens in any
1038 # context.
1039 #
1040 foreach ($this->em_relist as $em => $em_re) {
1041 foreach ($this->strong_relist as $strong => $strong_re) {
1042 # Construct list of allowed token expressions.
1043 $token_relist = array();
1044 if (isset($this->em_strong_relist["$em$strong"])) {
1045 $token_relist[] = $this->em_strong_relist["$em$strong"];
1046 }
1047 $token_relist[] = $em_re;
1048 $token_relist[] = $strong_re;
1049
1050 # Construct master expression from list.
1051 $token_re = '{('. implode('|', $token_relist) .')}';
1052 $this->em_strong_prepared_relist["$em$strong"] = $token_re;
1053 }
1054 }
1055 }
1056
1057 function doItalicsAndBold($text) {
1058 $token_stack = array('');
1059 $text_stack = array('');
1060 $em = '';
1061 $strong = '';
1062 $tree_char_em = false;
1063
1064 while (1) {
1065 #
1066 # Get prepared regular expression for seraching emphasis tokens
1067 # in current context.
1068 #
1069 $token_re = $this->em_strong_prepared_relist["$em$strong"];
1070
1071 #
1072 # Each loop iteration search for the next emphasis token.
1073 # Each token is then passed to handleSpanToken.
1074 #
1075 $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
1076 $text_stack[0] .= $parts[0];
1077 $token =& $parts[1];
1078 $text =& $parts[2];
1079
1080 if (empty($token)) {
1081 # Reached end of text span: empty stack without emitting.
1082 # any more emphasis.
1083 while ($token_stack[0]) {
1084 $text_stack[1] .= array_shift($token_stack);
1085 $text_stack[0] .= array_shift($text_stack);
1086 }
1087 break;
1088 }
1089
1090 $token_len = strlen($token);
1091 if ($tree_char_em) {
1092 # Reached closing marker while inside a three-char emphasis.
1093 if ($token_len == 3) {
1094 # Three-char closing marker, close em and strong.
1095 array_shift($token_stack);
1096 $span = array_shift($text_stack);
1097 $span = $this->runSpanGamut($span);
1098 $span = "<strong><em>$span</em></strong>";
1099 $text_stack[0] .= $this->hashPart($span);
1100 $em = '';
1101 $strong = '';
1102 } else {
1103 # Other closing marker: close one em or strong and
1104 # change current token state to match the other
1105 $token_stack[0] = str_repeat($token{0}, 3-$token_len);
1106 $tag = $token_len == 2 ? "strong" : "em";
1107 $span = $text_stack[0];
1108 $span = $this->runSpanGamut($span);
1109 $span = "<$tag>$span</$tag>";
1110 $text_stack[0] = $this->hashPart($span);
1111 $$tag = ''; # $$tag stands for $em or $strong
1112 }
1113 $tree_char_em = false;
1114 } else if ($token_len == 3) {
1115 if ($em) {
1116 # Reached closing marker for both em and strong.
1117 # Closing strong marker:
1118 for ($i = 0; $i < 2; ++$i) {
1119 $shifted_token = array_shift($token_stack);
1120 $tag = strlen($shifted_token) == 2 ? "strong" : "em";
1121 $span = array_shift($text_stack);
1122 $span = $this->runSpanGamut($span);
1123 $span = "<$tag>$span</$tag>";
1124 $text_stack[0] .= $this->hashPart($span);
1125 $$tag = ''; # $$tag stands for $em or $strong
1126 }
1127 } else {
1128 # Reached opening three-char emphasis marker. Push on token
1129 # stack; will be handled by the special condition above.
1130 $em = $token{0};
1131 $strong = "$em$em";
1132 array_unshift($token_stack, $token);
1133 array_unshift($text_stack, '');
1134 $tree_char_em = true;
1135 }
1136 } else if ($token_len == 2) {
1137 if ($strong) {
1138 # Unwind any dangling emphasis marker:
1139 if (strlen($token_stack[0]) == 1) {
1140 $text_stack[1] .= array_shift($token_stack);
1141 $text_stack[0] .= array_shift($text_stack);
1142 }
1143 # Closing strong marker:
1144 array_shift($token_stack);
1145 $span = array_shift($text_stack);
1146 $span = $this->runSpanGamut($span);
1147 $span = "<strong>$span</strong>";
1148 $text_stack[0] .= $this->hashPart($span);
1149 $strong = '';
1150 } else {
1151 array_unshift($token_stack, $token);
1152 array_unshift($text_stack, '');
1153 $strong = $token;
1154 }
1155 } else {
1156 # Here $token_len == 1
1157 if ($em) {
1158 if (strlen($token_stack[0]) == 1) {
1159 # Closing emphasis marker:
1160 array_shift($token_stack);
1161 $span = array_shift($text_stack);
1162 $span = $this->runSpanGamut($span);
1163 $span = "<em>$span</em>";
1164 $text_stack[0] .= $this->hashPart($span);
1165 $em = '';
1166 } else {
1167 $text_stack[0] .= $token;
1168 }
1169 } else {
1170 array_unshift($token_stack, $token);
1171 array_unshift($text_stack, '');
1172 $em = $token;
1173 }
1174 }
1175 }
1176 return $text_stack[0];
1177 }
1178
1179
1180 function doBlockQuotes($text) {
1181 $text = preg_replace_callback('/
1182 ( # Wrap whole match in $1
1183 (?>
1184 ^[ ]*>[ ]? # ">" at the start of a line
1185 .+\n # rest of the first line
1186 (.+\n)* # subsequent consecutive lines
1187 \n* # blanks
1188 )+
1189 )
1190 /xm',
1191 array(&$this, '_doBlockQuotes_callback'), $text);
1192
1193 return $text;
1194 }
1195 function _doBlockQuotes_callback($matches) {
1196 $bq = $matches[1];
1197 # trim one level of quoting - trim whitespace-only lines
1198 $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
1199 $bq = $this->runBlockGamut($bq); # recurse
1200
1201 $bq = preg_replace('/^/m', " ", $bq);
1202 # These leading spaces cause problem with <pre> content,
1203 # so we need to fix that:
1204 $bq = preg_replace_callback('{(\s*<pre>.+?</pre>)}sx',
1205 array(&$this, '_doBlockQuotes_callback2'), $bq);
1206
1207 return "\n". $this->hashBlock("<blockquote>\n$bq\n</blockquote>")."\n\n";
1208 }
1209 function _doBlockQuotes_callback2($matches) {
1210 $pre = $matches[1];
1211 $pre = preg_replace('/^ /m', '', $pre);
1212 return $pre;
1213 }
1214
1215
1216 function formParagraphs($text) {
1217 #
1218 # Params:
1219 # $text - string to process with html <p> tags
1220 #
1221 # Strip leading and trailing lines:
1222 $text = preg_replace('/\A\n+|\n+\z/', '', $text);
1223
1224 $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
1225
1226 #
1227 # Wrap <p> tags and unhashify HTML blocks
1228 #
1229 foreach ($grafs as $key => $value) {
1230 if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
1231 # Is a paragraph.
1232 $value = $this->runSpanGamut($value);
1233 $value = preg_replace('/^([ ]*)/', "<p>", $value);
1234 $value .= "</p>";
1235 $grafs[$key] = $this->unhash($value);
1236 }
1237 else {
1238 # Is a block.
1239 # Modify elements of @grafs in-place...
1240 $graf = $value;
1241 $block = $this->html_hashes[$graf];
1242 $graf = $block;
1243 // if (preg_match('{
1244 // \A
1245 // ( # $1 = <div> tag
1246 // <div \s+
1247 // [^>]*
1248 // \b
1249 // markdown\s*=\s* ([\'"]) # $2 = attr quote char
1250 // 1
1251 // \2
1252 // [^>]*
1253 // >
1254 // )
1255 // ( # $3 = contents
1256 // .*
1257 // )
1258 // (</div>) # $4 = closing tag
1259 // \z
1260 // }xs', $block, $matches))
1261 // {
1262 // list(, $div_open, , $div_content, $div_close) = $matches;
1263 //
1264 // # We can't call Markdown(), because that resets the hash;
1265 // # that initialization code should be pulled into its own sub, though.
1266 // $div_content = $this->hashHTMLBlocks($div_content);
1267 //
1268 // # Run document gamut methods on the content.
1269 // foreach ($this->document_gamut as $method => $priority) {
1270 // $div_content = $this->$method($div_content);
1271 // }
1272 //
1273 // $div_open = preg_replace(
1274 // '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
1275 //
1276 // $graf = $div_open . "\n" . $div_content . "\n" . $div_close;
1277 // }
1278 $grafs[$key] = $graf;
1279 }
1280 }
1281
1282 return implode("\n\n", $grafs);
1283 }
1284
1285
1286 function encodeAttribute($text) {
1287 #
1288 # Encode text for a double-quoted HTML attribute. This function
1289 # is *not* suitable for attributes enclosed in single quotes.
1290 #
1291 $text = $this->encodeAmpsAndAngles($text);
1292 $text = str_replace('"', '&quot;', $text);
1293 return $text;
1294 }
1295
1296
1297 function encodeAmpsAndAngles($text) {
1298 #
1299 # Smart processing for ampersands and angle brackets that need to
1300 # be encoded. Valid character entities are left alone unless the
1301 # no-entities mode is set.
1302 #
1303 if ($this->no_entities) {
1304 $text = str_replace('&', '&amp;', $text);
1305 } else {
1306 # Ampersand-encoding based entirely on Nat Irons's Amputator
1307 # MT plugin: <http://bumppo.net/projects/amputator/>
1308 $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',
1309 '&amp;', $text);;
1310 }
1311 # Encode remaining <'s
1312 $text = str_replace('<', '&lt;', $text);
1313
1314 return $text;
1315 }
1316
1317
1318 function doAutoLinks($text) {
1319 $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i',
1320 array(&$this, '_doAutoLinks_url_callback'), $text);
1321
1322 # Email addresses: <address@domain.foo>
1323 $text = preg_replace_callback('{
1324 <
1325 (?:mailto:)?
1326 (
1327 (?:
1328 [-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+
1329 |
1330 ".*?"
1331 )
1332 \@
1333 (?:
1334 [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
1335 |
1336 \[[\d.a-fA-F:]+\] # IPv4 & IPv6
1337 )
1338 )
1339 >
1340 }xi',
1341 array(&$this, '_doAutoLinks_email_callback'), $text);
1342 $text = preg_replace_callback('{<(tel:([^\'">\s]+))>}i',array(&$this, '_doAutoLinks_tel_callback'), $text);
1343
1344 return $text;
1345 }
1346 function _doAutoLinks_tel_callback($matches) {
1347 $url = $this->encodeAttribute($matches[1]);
1348 $tel = $this->encodeAttribute($matches[2]);
1349 $link = "<a href=\"$url\">$tel</a>";
1350 return $this->hashPart($link);
1351 }
1352 function _doAutoLinks_url_callback($matches) {
1353 $url = $this->encodeAttribute($matches[1]);
1354 $link = "<a href=\"$url\">$url</a>";
1355 return $this->hashPart($link);
1356 }
1357 function _doAutoLinks_email_callback($matches) {
1358 $address = $matches[1];
1359 $link = $this->encodeEmailAddress($address);
1360 return $this->hashPart($link);
1361 }
1362
1363
1364 function encodeEmailAddress($addr) {
1365 #
1366 # Input: an email address, e.g. "foo@example.com"
1367 #
1368 # Output: the email address as a mailto link, with each character
1369 # of the address encoded as either a decimal or hex entity, in
1370 # the hopes of foiling most address harvesting spam bots. E.g.:
1371 #
1372 # <p><a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x66;o&#111;
1373 # &#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;&#101;&#46;&#x63;&#111;
1374 # &#x6d;">&#x66;o&#111;&#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;
1375 # &#101;&#46;&#x63;&#111;&#x6d;</a></p>
1376 #
1377 # Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
1378 # With some optimizations by Milian Wolff.
1379 #
1380 $addr = "mailto:" . $addr;
1381 $chars = preg_split('/(?<!^)(?!$)/', $addr);
1382 $seed = (int)abs(crc32($addr) / strlen($addr)); # Deterministic seed.
1383
1384 foreach ($chars as $key => $char) {
1385 $ord = ord($char);
1386 # Ignore non-ascii chars.
1387 if ($ord < 128) {
1388 $r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
1389 # roughly 10% raw, 45% hex, 45% dec
1390 # '@' *must* be encoded. I insist.
1391 if ($r > 90 && $char != '@') /* do nothing */;
1392 else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';';
1393 else $chars[$key] = '&#'.$ord.';';
1394 }
1395 }
1396
1397 $addr = implode('', $chars);
1398 $text = implode('', array_slice($chars, 7)); # text without `mailto:`
1399 $addr = "<a href=\"$addr\">$text</a>";
1400
1401 return $addr;
1402 }
1403
1404
1405 function parseSpan($str) {
1406 #
1407 # Take the string $str and parse it into tokens, hashing embedded HTML,
1408 # escaped characters and handling code spans.
1409 #
1410 $output = '';
1411
1412 $span_re = '{
1413 (
1414 \\\\'.$this->escape_chars_re.'
1415 |
1416 (?<![`\\\\])
1417 `+ # code span marker
1418 '.( $this->no_markup ? '' : '
1419 |
1420 <!-- .*? --> # comment
1421 |
1422 <\?.*?\?> | <%.*?%> # processing instruction
1423 |
1424 <[!$]?[-a-zA-Z0-9:_]+ # regular tags
1425 (?>
1426 \s
1427 (?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
1428 )?
1429 >
1430 |
1431 <[-a-zA-Z0-9:_]+\s*/> # xml-style empty tag
1432 |
1433 </[-a-zA-Z0-9:_]+\s*> # closing tag
1434 ').'
1435 )
1436 }xs';
1437
1438 while (1) {
1439 #
1440 # Each loop iteration search for either the next tag, the next
1441 # openning code span marker, or the next escaped character.
1442 # Each token is then passed to handleSpanToken.
1443 #
1444 $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
1445
1446 # Create token from text preceding tag.
1447 if ($parts[0] != "") {
1448 $output .= $parts[0];
1449 }
1450
1451 # Check if we reach the end.
1452 if (isset($parts[1])) {
1453 $output .= $this->handleSpanToken($parts[1], $parts[2]);
1454 $str = $parts[2];
1455 }
1456 else {
1457 break;
1458 }
1459 }
1460
1461 return $output;
1462 }
1463
1464
1465 function handleSpanToken($token, &$str) {
1466 #
1467 # Handle $token provided by parseSpan by determining its nature and
1468 # returning the corresponding value that should replace it.
1469 #
1470 switch ($token{0}) {
1471 case "\\":
1472 return $this->hashPart("&#". ord($token{1}). ";");
1473 case "`":
1474 # Search for end marker in remaining text.
1475 if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm',
1476 $str, $matches))
1477 {
1478 $str = $matches[2];
1479 $codespan = $this->makeCodeSpan($matches[1]);
1480 return $this->hashPart($codespan);
1481 }
1482 return $token; // return as text since no ending marker found.
1483 default:
1484 return $this->hashPart($token);
1485 }
1486 }
1487
1488
1489 function outdent($text) {
1490 #
1491 # Remove one level of line-leading tabs or spaces
1492 #
1493 return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text);
1494 }
1495
1496
1497 # String length function for detab. `_initDetab` will create a function to
1498 # hanlde UTF-8 if the default function does not exist.
1499 public $utf8_strlen = 'mb_strlen';
1500
1501 function detab($text) {
1502 #
1503 # Replace tabs with the appropriate amount of space.
1504 #
1505 # For each line we separate the line in blocks delemited by
1506 # tab characters. Then we reconstruct every line by adding the
1507 # appropriate number of space between each blocks.
1508
1509 $text = preg_replace_callback('/^.*\t.*$/m',
1510 array(&$this, '_detab_callback'), $text);
1511
1512 return $text;
1513 }
1514 function _detab_callback($matches) {
1515 $line = $matches[0];
1516 $strlen = $this->utf8_strlen; # strlen function for UTF-8.
1517
1518 # Split in blocks.
1519 $blocks = explode("\t", $line);
1520 # Add each blocks to the line.
1521 $line = $blocks[0];
1522 unset($blocks[0]); # Do not add first block twice.
1523 foreach ($blocks as $block) {
1524 # Calculate amount of space, insert spaces, insert block.
1525 $amount = $this->tab_width -
1526 $strlen($line, 'UTF-8') % $this->tab_width;
1527 $line .= str_repeat(" ", $amount) . $block;
1528 }
1529 return $line;
1530 }
1531 function _initDetab() {
1532 #
1533 # Check for the availability of the function in the `utf8_strlen` property
1534 # (initially `mb_strlen`). If the function is not available, use jetpack_utf8_strlen
1535 # that will loosely count the number of UTF-8 characters with a
1536 # regular expression.
1537 #
1538 if ( function_exists( $this->utf8_strlen ) ) {
1539 return;
1540 }
1541 $this->utf8_strlen = 'jetpack_utf8_strlen';
1542 }
1543
1544
1545 function unhash($text) {
1546 #
1547 # Swap back in all the tags hashed by _HashHTMLBlocks.
1548 #
1549 return preg_replace_callback('/(.)\x1A[0-9]+\1/',
1550 array(&$this, '_unhash_callback'), $text);
1551 }
1552 function _unhash_callback($matches) {
1553 return $this->html_hashes[$matches[0]];
1554 }
1555
1556 }
1557
1558
1559 #
1560 # Markdown Extra Parser Class
1561 #
1562
1563 class MarkdownExtra_Parser extends Markdown_Parser {
1564
1565 ### Configuration Variables ###
1566
1567 # Prefix for footnote ids.
1568 public $fn_id_prefix = "";
1569
1570 # Optional title attribute for footnote links and backlinks.
1571 public $fn_link_title = MARKDOWN_FN_LINK_TITLE;
1572 public $fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE;
1573
1574 # Optional class attribute for footnote links and backlinks.
1575 public $fn_link_class = MARKDOWN_FN_LINK_CLASS;
1576 public $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS;
1577
1578 # Optional class prefix for fenced code block.
1579 public $code_class_prefix = MARKDOWN_CODE_CLASS_PREFIX;
1580 # Class attribute for code blocks goes on the `code` tag;
1581 # setting this to true will put attributes on the `pre` tag instead.
1582 public $code_attr_on_pre = MARKDOWN_CODE_ATTR_ON_PRE;
1583
1584 # Predefined abbreviations.
1585 public $predef_abbr = array();
1586
1587
1588 ### Parser Implementation ###
1589
1590 function __construct() {
1591 #
1592 # Constructor function. Initialize the parser object.
1593 #
1594 # Add extra escapable characters before parent constructor
1595 # initialize the table.
1596 $this->escape_chars .= ':|';
1597
1598 # Insert extra document, block, and span transformations.
1599 # Parent constructor will do the sorting.
1600 $this->document_gamut += array(
1601 "doFencedCodeBlocks" => 5,
1602 "stripFootnotes" => 15,
1603 "stripAbbreviations" => 25,
1604 "appendFootnotes" => 50,
1605 );
1606 $this->block_gamut += array(
1607 "doFencedCodeBlocks" => 5,
1608 "doTables" => 15,
1609 "doDefLists" => 45,
1610 );
1611 $this->span_gamut += array(
1612 "doFootnotes" => 5,
1613 "doAbbreviations" => 70,
1614 );
1615
1616 parent::__construct();
1617 }
1618
1619
1620 # Extra variables used during extra transformations.
1621 public $footnotes = array();
1622 public $footnotes_ordered = array();
1623 public $footnotes_ref_count = array();
1624 public $footnotes_numbers = array();
1625 public $abbr_desciptions = array();
1626 public $abbr_word_re = '';
1627
1628 # Give the current footnote number.
1629 public $footnote_counter = 1;
1630
1631
1632 function setup() {
1633 #
1634 # Setting up Extra-specific variables.
1635 #
1636 parent::setup();
1637
1638 $this->footnotes = array();
1639 $this->footnotes_ordered = array();
1640 $this->footnotes_ref_count = array();
1641 $this->footnotes_numbers = array();
1642 $this->abbr_desciptions = array();
1643 $this->abbr_word_re = '';
1644 $this->footnote_counter = 1;
1645
1646 foreach ($this->predef_abbr as $abbr_word => $abbr_desc) {
1647 if ($this->abbr_word_re)
1648 $this->abbr_word_re .= '|';
1649 $this->abbr_word_re .= preg_quote($abbr_word);
1650 $this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
1651 }
1652 }
1653
1654 function teardown() {
1655 #
1656 # Clearing Extra-specific variables.
1657 #
1658 $this->footnotes = array();
1659 $this->footnotes_ordered = array();
1660 $this->footnotes_ref_count = array();
1661 $this->footnotes_numbers = array();
1662 $this->abbr_desciptions = array();
1663 $this->abbr_word_re = '';
1664
1665 parent::teardown();
1666 }
1667
1668
1669 ### Extra Attribute Parser ###
1670
1671 # Expression to use to catch attributes (includes the braces)
1672 public $id_class_attr_catch_re = '\{((?:[ ]*[#.][-_:a-zA-Z0-9]+){1,})[ ]*\}';
1673 # Expression to use when parsing in a context when no capture is desired
1674 public $id_class_attr_nocatch_re = '\{(?:[ ]*[#.][-_:a-zA-Z0-9]+){1,}[ ]*\}';
1675
1676 function doExtraAttributes($tag_name, $attr) {
1677 #
1678 # Parse attributes caught by the $this->id_class_attr_catch_re expression
1679 # and return the HTML-formatted list of attributes.
1680 #
1681 # Currently supported attributes are .class and #id.
1682 #
1683 if (empty($attr)) return "";
1684
1685 # Split on components
1686 preg_match_all('/[#.][-_:a-zA-Z0-9]+/', $attr, $matches);
1687 $elements = $matches[0];
1688
1689 # handle classes and ids (only first id taken into account)
1690 $classes = array();
1691 $id = false;
1692 foreach ($elements as $element) {
1693 if ($element{0} == '.') {
1694 $classes[] = substr($element, 1);
1695 } else if ($element{0} == '#') {
1696 if ($id === false) $id = substr($element, 1);
1697 }
1698 }
1699
1700 # compose attributes as string
1701 $attr_str = "";
1702 if (!empty($id)) {
1703 $attr_str .= ' id="'.$id.'"';
1704 }
1705 if (!empty($classes)) {
1706 $attr_str .= ' class="'.implode(" ", $classes).'"';
1707 }
1708 return $attr_str;
1709 }
1710
1711
1712 function stripLinkDefinitions($text) {
1713 #
1714 # Strips link definitions from text, stores the URLs and titles in
1715 # hash references.
1716 #
1717 $less_than_tab = $this->tab_width - 1;
1718
1719 # Link defs are in the form: ^[id]: url "optional title"
1720 $text = preg_replace_callback('{
1721 ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1
1722 [ ]*
1723 \n? # maybe *one* newline
1724 [ ]*
1725 (?:
1726 <(.+?)> # url = $2
1727 |
1728 (\S+?) # url = $3
1729 )
1730 [ ]*
1731 \n? # maybe one newline
1732 [ ]*
1733 (?:
1734 (?<=\s) # lookbehind for whitespace
1735 ["(]
1736 (.*?) # title = $4
1737 [")]
1738 [ ]*
1739 )? # title is optional
1740 (?:[ ]* '.$this->id_class_attr_catch_re.' )? # $5 = extra id & class attr
1741 (?:\n+|\Z)
1742 }xm',
1743 array(&$this, '_stripLinkDefinitions_callback'),
1744 $text);
1745 return $text;
1746 }
1747 function _stripLinkDefinitions_callback($matches) {
1748 $link_id = strtolower($matches[1]);
1749 $url = $matches[2] == '' ? $matches[3] : $matches[2];
1750 $this->urls[$link_id] = $url;
1751 $this->titles[$link_id] =& $matches[4];
1752 $this->ref_attr[$link_id] = $this->doExtraAttributes("", $dummy =& $matches[5]);
1753 return ''; # String that will replace the block
1754 }
1755
1756
1757 ### HTML Block Parser ###
1758
1759 # Tags that are always treated as block tags:
1760 public $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend|article|section|nav|aside|hgroup|header|footer|figcaption';
1761
1762 # Tags treated as block tags only if the opening tag is alone on its line:
1763 public $context_block_tags_re = 'script|noscript|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video';
1764
1765 # Tags where markdown="1" default to span mode:
1766 public $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address';
1767
1768 # Tags which must not have their contents modified, no matter where
1769 # they appear:
1770 public $clean_tags_re = 'script|math|svg';
1771
1772 # Tags that do not need to be closed.
1773 public $auto_close_tags_re = 'hr|img|param|source|track';
1774
1775
1776 function hashHTMLBlocks($text) {
1777 #
1778 # Hashify HTML Blocks and "clean tags".
1779 #
1780 # We only want to do this for block-level HTML tags, such as headers,
1781 # lists, and tables. That's because we still want to wrap <p>s around
1782 # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
1783 # phrase emphasis, and spans. The list of tags we're looking for is
1784 # hard-coded.
1785 #
1786 # This works by calling _HashHTMLBlocks_InMarkdown, which then calls
1787 # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1"
1788 # attribute is found within a tag, _HashHTMLBlocks_InHTML calls back
1789 # _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag.
1790 # These two functions are calling each other. It's recursive!
1791 #
1792 if ($this->no_markup) return $text;
1793
1794 #
1795 # Call the HTML-in-Markdown hasher.
1796 #
1797 list($text, ) = $this->_hashHTMLBlocks_inMarkdown($text);
1798
1799 return $text;
1800 }
1801 function _hashHTMLBlocks_inMarkdown($text, $indent = 0,
1802 $enclosing_tag_re = '', $span = false)
1803 {
1804 #
1805 # Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags.
1806 #
1807 # * $indent is the number of space to be ignored when checking for code
1808 # blocks. This is important because if we don't take the indent into
1809 # account, something like this (which looks right) won't work as expected:
1810 #
1811 # <div>
1812 # <div markdown="1">
1813 # Hello World. <-- Is this a Markdown code block or text?
1814 # </div> <-- Is this a Markdown code block or a real tag?
1815 # <div>
1816 #
1817 # If you don't like this, just don't indent the tag on which
1818 # you apply the markdown="1" attribute.
1819 #
1820 # * If $enclosing_tag_re is not empty, stops at the first unmatched closing
1821 # tag with that name. Nested tags supported.
1822 #
1823 # * If $span is true, text inside must treated as span. So any double
1824 # newline will be replaced by a single newline so that it does not create
1825 # paragraphs.
1826 #
1827 # Returns an array of that form: ( processed text , remaining text )
1828 #
1829 if ($text === '') return array('', '');
1830
1831 # Regex to check for the presence of newlines around a block tag.
1832 $newline_before_re = '/(?:^\n?|\n\n)*$/';
1833 $newline_after_re =
1834 '{
1835 ^ # Start of text following the tag.
1836 (?>[ ]*<!--.*?-->)? # Optional comment.
1837 [ ]*\n # Must be followed by newline.
1838 }xs';
1839
1840 # Regex to match any tag.
1841 $block_tag_re =
1842 '{
1843 ( # $2: Capture whole tag.
1844 </? # Any opening or closing tag.
1845 (?> # Tag name.
1846 '.$this->block_tags_re.' |
1847 '.$this->context_block_tags_re.' |
1848 '.$this->clean_tags_re.' |
1849 (?!\s)'.$enclosing_tag_re.'
1850 )
1851 (?:
1852 (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name.
1853 (?>
1854 ".*?" | # Double quotes (can contain `>`)
1855 \'.*?\' | # Single quotes (can contain `>`)
1856 .+? # Anything but quotes and `>`.
1857 )*?
1858 )?
1859 > # End of tag.
1860 |
1861 <!-- .*? --> # HTML Comment
1862 |
1863 <\?.*?\?> | <%.*?%> # Processing instruction
1864 |
1865 <!\[CDATA\[.*?\]\]> # CData Block
1866 '. ( !$span ? ' # If not in span.
1867 |
1868 # Indented code block
1869 (?: ^[ ]*\n | ^ | \n[ ]*\n )
1870 [ ]{'.($indent+4).'}[^\n]* \n
1871 (?>
1872 (?: [ ]{'.($indent+4).'}[^\n]* | [ ]* ) \n
1873 )*
1874 |
1875 # Fenced code block marker
1876 (?<= ^ | \n )
1877 [ ]{0,'.($indent+3).'}(?:~{3,}|`{3,})
1878 [ ]*
1879 (?:
1880 \.?[-_:a-zA-Z0-9]+ # standalone class name
1881 |
1882 '.$this->id_class_attr_nocatch_re.' # extra attributes
1883 )?
1884 [ ]*
1885 (?= \n )
1886 ' : '' ). ' # End (if not is span).
1887 |
1888 # Code span marker
1889 # Note, this regex needs to go after backtick fenced
1890 # code blocks but it should also be kept outside of the
1891 # "if not in span" condition adding backticks to the parser
1892 `+
1893 )
1894 }xs';
1895
1896
1897 $depth = 0; # Current depth inside the tag tree.
1898 $parsed = ""; # Parsed text that will be returned.
1899
1900 #
1901 # Loop through every tag until we find the closing tag of the parent
1902 # or loop until reaching the end of text if no parent tag specified.
1903 #
1904 do {
1905 #
1906 # Split the text using the first $tag_match pattern found.
1907 # Text before pattern will be first in the array, text after
1908 # pattern will be at the end, and between will be any catches made
1909 # by the pattern.
1910 #
1911 $parts = preg_split($block_tag_re, $text, 2,
1912 PREG_SPLIT_DELIM_CAPTURE);
1913
1914 # If in Markdown span mode, add a empty-string span-level hash
1915 # after each newline to prevent triggering any block element.
1916 if ($span) {
1917 $void = $this->hashPart("", ':');
1918 $newline = "$void\n";
1919 $parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void;
1920 }
1921
1922 $parsed .= $parts[0]; # Text before current tag.
1923
1924 # If end of $text has been reached. Stop loop.
1925 if (count($parts) < 3) {
1926 $text = "";
1927 break;
1928 }
1929
1930 $tag = $parts[1]; # Tag to handle.
1931 $text = $parts[2]; # Remaining text after current tag.
1932 $tag_re = preg_quote($tag); # For use in a regular expression.
1933
1934 #
1935 # Check for: Fenced code block marker.
1936 # Note: need to recheck the whole tag to disambiguate backtick
1937 # fences from code spans
1938 #
1939 if (preg_match('{^\n?([ ]{0,'.($indent+3).'})(~{3,}|`{3,})[ ]*(?:\.?[-_:a-zA-Z0-9]+|'.$this->id_class_attr_nocatch_re.')?[ ]*\n?$}', $tag, $capture)) {
1940 # Fenced code block marker: find matching end marker.
1941 $fence_indent = strlen($capture[1]); # use captured indent in re
1942 $fence_re = $capture[2]; # use captured fence in re
1943 if (preg_match('{^(?>.*\n)*?[ ]{'.($fence_indent).'}'.$fence_re.'[ ]*(?:\n|$)}', $text,
1944 $matches))
1945 {
1946 # End marker found: pass text unchanged until marker.
1947 $parsed .= $tag . $matches[0];
1948 $text = substr($text, strlen($matches[0]));
1949 }
1950 else {
1951 # No end marker: just skip it.
1952 $parsed .= $tag;
1953 }
1954 }
1955 #
1956 # Check for: Indented code block.
1957 #
1958 else if ($tag{0} == "\n" || $tag{0} == " ") {
1959 # Indented code block: pass it unchanged, will be handled
1960 # later.
1961 $parsed .= $tag;
1962 }
1963 #
1964 # Check for: Code span marker
1965 # Note: need to check this after backtick fenced code blocks
1966 #
1967 else if ($tag{0} == "`") {
1968 # Find corresponding end marker.
1969 $tag_re = preg_quote($tag);
1970 if (preg_match('{^(?>.+?|\n(?!\n))*?(?<!`)'.$tag_re.'(?!`)}',
1971 $text, $matches))
1972 {
1973 # End marker found: pass text unchanged until marker.
1974 $parsed .= $tag . $matches[0];
1975 $text = substr($text, strlen($matches[0]));
1976 }
1977 else {
1978 # Unmatched marker: just skip it.
1979 $parsed .= $tag;
1980 }
1981 }
1982 #
1983 # Check for: Opening Block level tag or
1984 # Opening Context Block tag (like ins and del)
1985 # used as a block tag (tag is alone on it's line).
1986 #
1987 else if (preg_match('{^<(?:'.$this->block_tags_re.')\b}', $tag) ||
1988 ( preg_match('{^<(?:'.$this->context_block_tags_re.')\b}', $tag) &&
1989 preg_match($newline_before_re, $parsed) &&
1990 preg_match($newline_after_re, $text) )
1991 )
1992 {
1993 # Need to parse tag and following text using the HTML parser.
1994 list($block_text, $text) =
1995 $this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true);
1996
1997 # Make sure it stays outside of any paragraph by adding newlines.
1998 $parsed .= "\n\n$block_text\n\n";
1999 }
2000 #
2001 # Check for: Clean tag (like script, math)
2002 # HTML Comments, processing instructions.
2003 #
2004 else if (preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) ||
2005 $tag{1} == '!' || $tag{1} == '?')
2006 {
2007 # Need to parse tag and following text using the HTML parser.
2008 # (don't check for markdown attribute)
2009 list($block_text, $text) =
2010 $this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false);
2011
2012 $parsed .= $block_text;
2013 }
2014 #
2015 # Check for: Tag with same name as enclosing tag.
2016 #
2017 else if ($enclosing_tag_re !== '' &&
2018 # Same name as enclosing tag.
2019 preg_match('{^</?(?:'.$enclosing_tag_re.')\b}', $tag))
2020 {
2021 #
2022 # Increase/decrease nested tag count.
2023 #
2024 if ($tag{1} == '/') $depth--;
2025 else if ($tag{strlen($tag)-2} != '/') $depth++;
2026
2027 if ($depth < 0) {
2028 #
2029 # Going out of parent element. Clean up and break so we
2030 # return to the calling function.
2031 #
2032 $text = $tag . $text;
2033 break;
2034 }
2035
2036 $parsed .= $tag;
2037 }
2038 else {
2039 $parsed .= $tag;
2040 }
2041 } while ($depth >= 0);
2042
2043 return array($parsed, $text);
2044 }
2045 function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) {
2046 #
2047 # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags.
2048 #
2049 # * Calls $hash_method to convert any blocks.
2050 # * Stops when the first opening tag closes.
2051 # * $md_attr indicate if the use of the `markdown="1"` attribute is allowed.
2052 # (it is not inside clean tags)
2053 #
2054 # Returns an array of that form: ( processed text , remaining text )
2055 #
2056 if ($text === '') return array('', '');
2057
2058 # Regex to match `markdown` attribute inside of a tag.
2059 $markdown_attr_re = '
2060 {
2061 \s* # Eat whitespace before the `markdown` attribute
2062 markdown
2063 \s*=\s*
2064 (?>
2065 (["\']) # $1: quote delimiter
2066 (.*?) # $2: attribute value
2067 \1 # matching delimiter
2068 |
2069 ([^\s>]*) # $3: unquoted attribute value
2070 )
2071 () # $4: make $3 always defined (avoid warnings)
2072 }xs';
2073
2074 # Regex to match any tag.
2075 $tag_re = '{
2076 ( # $2: Capture whole tag.
2077 </? # Any opening or closing tag.
2078 [\w:$]+ # Tag name.
2079 (?:
2080 (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name.
2081 (?>
2082 ".*?" | # Double quotes (can contain `>`)
2083 \'.*?\' | # Single quotes (can contain `>`)
2084 .+? # Anything but quotes and `>`.
2085 )*?
2086 )?
2087 > # End of tag.
2088 |
2089 <!-- .*? --> # HTML Comment
2090 |
2091 <\?.*?\?> | <%.*?%> # Processing instruction
2092 |
2093 <!\[CDATA\[.*?\]\]> # CData Block
2094 )
2095 }xs';
2096
2097 $original_text = $text; # Save original text in case of faliure.
2098
2099 $depth = 0; # Current depth inside the tag tree.
2100 $block_text = ""; # Temporary text holder for current text.
2101 $parsed = ""; # Parsed text that will be returned.
2102
2103 #
2104 # Get the name of the starting tag.
2105 # (This pattern makes $base_tag_name_re safe without quoting.)
2106 #
2107 if (preg_match('/^<([\w:$]*)\b/', $text, $matches))
2108 $base_tag_name_re = $matches[1];
2109
2110 #
2111 # Loop through every tag until we find the corresponding closing tag.
2112 #
2113 do {
2114 #
2115 # Split the text using the first $tag_match pattern found.
2116 # Text before pattern will be first in the array, text after
2117 # pattern will be at the end, and between will be any catches made
2118 # by the pattern.
2119 #
2120 $parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
2121
2122 if (count($parts) < 3) {
2123 #
2124 # End of $text reached with unbalenced tag(s).
2125 # In that case, we return original text unchanged and pass the
2126 # first character as filtered to prevent an infinite loop in the
2127 # parent function.
2128 #
2129 return array($original_text{0}, substr($original_text, 1));
2130 }
2131
2132 $block_text .= $parts[0]; # Text before current tag.
2133 $tag = $parts[1]; # Tag to handle.
2134 $text = $parts[2]; # Remaining text after current tag.
2135
2136 #
2137 # Check for: Auto-close tag (like <hr/>)
2138 # Comments and Processing Instructions.
2139 #
2140 if (preg_match('{^</?(?:'.$this->auto_close_tags_re.')\b}', $tag) ||
2141 $tag{1} == '!' || $tag{1} == '?')
2142 {
2143 # Just add the tag to the block as if it was text.
2144 $block_text .= $tag;
2145 }
2146 else {
2147 #
2148 # Increase/decrease nested tag count. Only do so if
2149 # the tag's name match base tag's.
2150 #
2151 if (preg_match('{^</?'.$base_tag_name_re.'\b}', $tag)) {
2152 if ($tag{1} == '/') $depth--;
2153 else if ($tag{strlen($tag)-2} != '/') $depth++;
2154 }
2155
2156 #
2157 # Check for `markdown="1"` attribute and handle it.
2158 #
2159 if ($md_attr &&
2160 preg_match($markdown_attr_re, $tag, $attr_m) &&
2161 preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3]))
2162 {
2163 # Remove `markdown` attribute from opening tag.
2164 $tag = preg_replace($markdown_attr_re, '', $tag);
2165
2166 # Check if text inside this tag must be parsed in span mode.
2167 $this->mode = $attr_m[2] . $attr_m[3];
2168 $span_mode = $this->mode == 'span' || $this->mode != 'block' &&
2169 preg_match('{^<(?:'.$this->contain_span_tags_re.')\b}', $tag);
2170
2171 # Calculate indent before tag.
2172 if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) {
2173 $strlen = $this->utf8_strlen;
2174 $indent = $strlen($matches[1], 'UTF-8');
2175 } else {
2176 $indent = 0;
2177 }
2178
2179 # End preceding block with this tag.
2180 $block_text .= $tag;
2181 $parsed .= $this->$hash_method($block_text);
2182
2183 # Get enclosing tag name for the ParseMarkdown function.
2184 # (This pattern makes $tag_name_re safe without quoting.)
2185 preg_match('/^<([\w:$]*)\b/', $tag, $matches);
2186 $tag_name_re = $matches[1];
2187
2188 # Parse the content using the HTML-in-Markdown parser.
2189 list ($block_text, $text)
2190 = $this->_hashHTMLBlocks_inMarkdown($text, $indent,
2191 $tag_name_re, $span_mode);
2192
2193 # Outdent markdown text.
2194 if ($indent > 0) {
2195 $block_text = preg_replace("/^[ ]{1,$indent}/m", "",
2196 $block_text);
2197 }
2198
2199 # Append tag content to parsed text.
2200 if (!$span_mode) $parsed .= "\n\n$block_text\n\n";
2201 else $parsed .= "$block_text";
2202
2203 # Start over with a new block.
2204 $block_text = "";
2205 }
2206 else $block_text .= $tag;
2207 }
2208
2209 } while ($depth > 0);
2210
2211 #
2212 # Hash last block text that wasn't processed inside the loop.
2213 #
2214 $parsed .= $this->$hash_method($block_text);
2215
2216 return array($parsed, $text);
2217 }
2218
2219
2220 function hashClean($text) {
2221 #
2222 # Called whenever a tag must be hashed when a function inserts a "clean" tag
2223 # in $text, it passes through this function and is automaticaly escaped,
2224 # blocking invalid nested overlap.
2225 #
2226 return $this->hashPart($text, 'C');
2227 }
2228
2229
2230 function doAnchors($text) {
2231 #
2232 # Turn Markdown link shortcuts into XHTML <a> tags.
2233 #
2234 if ($this->in_anchor) return $text;
2235 $this->in_anchor = true;
2236
2237 #
2238 # First, handle reference-style links: [link text] [id]
2239 #
2240 $text = preg_replace_callback('{
2241 ( # wrap whole match in $1
2242 \[
2243 ('.$this->nested_brackets_re.') # link text = $2
2244 \]
2245
2246 [ ]? # one optional space
2247 (?:\n[ ]*)? # one optional newline followed by spaces
2248
2249 \[
2250 (.*?) # id = $3
2251 \]
2252 )
2253 }xs',
2254 array(&$this, '_doAnchors_reference_callback'), $text);
2255
2256 #
2257 # Next, inline-style links: [link text](url "optional title")
2258 #
2259 $text = preg_replace_callback('{
2260 ( # wrap whole match in $1
2261 \[
2262 ('.$this->nested_brackets_re.') # link text = $2
2263 \]
2264 \( # literal paren
2265 [ \n]*
2266 (?:
2267 <(.+?)> # href = $3
2268 |
2269 ('.$this->nested_url_parenthesis_re.') # href = $4
2270 )
2271 [ \n]*
2272 ( # $5
2273 ([\'"]) # quote char = $6
2274 (.*?) # Title = $7
2275 \6 # matching quote
2276 [ \n]* # ignore any spaces/tabs between closing quote and )
2277 )? # title is optional
2278 \)
2279 (?:[ ]? '.$this->id_class_attr_catch_re.' )? # $8 = id/class attributes
2280 )
2281 }xs',
2282 array(&$this, '_doAnchors_inline_callback'), $text);
2283
2284 #
2285 # Last, handle reference-style shortcuts: [link text]
2286 # These must come last in case you've also got [link text][1]
2287 # or [link text](/foo)
2288 #
2289 $text = preg_replace_callback('{
2290 ( # wrap whole match in $1
2291 \[
2292 ([^\[\]]+) # link text = $2; can\'t contain [ or ]
2293 \]
2294 )
2295 }xs',
2296 array(&$this, '_doAnchors_reference_callback'), $text);
2297
2298 $this->in_anchor = false;
2299 return $text;
2300 }
2301 function _doAnchors_reference_callback($matches) {
2302 $whole_match = $matches[1];
2303 $link_text = $matches[2];
2304 $link_id =& $matches[3];
2305
2306 if ($link_id == "") {
2307 # for shortcut links like [this][] or [this].
2308 $link_id = $link_text;
2309 }
2310
2311 # lower-case and turn embedded newlines into spaces
2312 $link_id = strtolower($link_id);
2313 $link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
2314
2315 if (isset($this->urls[$link_id])) {
2316 $url = $this->urls[$link_id];
2317 $url = $this->encodeAttribute($url);
2318
2319 $result = "<a href=\"$url\"";
2320 if ( isset( $this->titles[$link_id] ) ) {
2321 $title = $this->titles[$link_id];
2322 $title = $this->encodeAttribute($title);
2323 $result .= " title=\"$title\"";
2324 }
2325 if (isset($this->ref_attr[$link_id]))
2326 $result .= $this->ref_attr[$link_id];
2327
2328 $link_text = $this->runSpanGamut($link_text);
2329 $result .= ">$link_text</a>";
2330 $result = $this->hashPart($result);
2331 }
2332 else {
2333 $result = $whole_match;
2334 }
2335 return $result;
2336 }
2337 function _doAnchors_inline_callback($matches) {
2338 $whole_match = $matches[1];
2339 $link_text = $this->runSpanGamut($matches[2]);
2340 $url = $matches[3] == '' ? $matches[4] : $matches[3];
2341 $title =& $matches[7];
2342 $attr = $this->doExtraAttributes("a", $dummy =& $matches[8]);
2343
2344
2345 $url = $this->encodeAttribute($url);
2346
2347 $result = "<a href=\"$url\"";
2348 if (isset($title)) {
2349 $title = $this->encodeAttribute($title);
2350 $result .= " title=\"$title\"";
2351 }
2352 $result .= $attr;
2353
2354 $link_text = $this->runSpanGamut($link_text);
2355 $result .= ">$link_text</a>";
2356
2357 return $this->hashPart($result);
2358 }
2359
2360
2361 function doImages($text) {
2362 #
2363 # Turn Markdown image shortcuts into <img> tags.
2364 #
2365 #
2366 # First, handle reference-style labeled images: ![alt text][id]
2367 #
2368 $text = preg_replace_callback('{
2369 ( # wrap whole match in $1
2370 !\[
2371 ('.$this->nested_brackets_re.') # alt text = $2
2372 \]
2373
2374 [ ]? # one optional space
2375 (?:\n[ ]*)? # one optional newline followed by spaces
2376
2377 \[
2378 (.*?) # id = $3
2379 \]
2380
2381 )
2382 }xs',
2383 array(&$this, '_doImages_reference_callback'), $text);
2384
2385 #
2386 # Next, handle inline images: ![alt text](url "optional title")
2387 # Don't forget: encode * and _
2388 #
2389 $text = preg_replace_callback('{
2390 ( # wrap whole match in $1
2391 !\[
2392 ('.$this->nested_brackets_re.') # alt text = $2
2393 \]
2394 \s? # One optional whitespace character
2395 \( # literal paren
2396 [ \n]*
2397 (?:
2398 <(\S*)> # src url = $3
2399 |
2400 ('.$this->nested_url_parenthesis_re.') # src url = $4
2401 )
2402 [ \n]*
2403 ( # $5
2404 ([\'"]) # quote char = $6
2405 (.*?) # title = $7
2406 \6 # matching quote
2407 [ \n]*
2408 )? # title is optional
2409 \)
2410 (?:[ ]? '.$this->id_class_attr_catch_re.' )? # $8 = id/class attributes
2411 )
2412 }xs',
2413 array(&$this, '_doImages_inline_callback'), $text);
2414
2415 return $text;
2416 }
2417 function _doImages_reference_callback($matches) {
2418 $whole_match = $matches[1];
2419 $alt_text = $matches[2];
2420 $link_id = strtolower($matches[3]);
2421
2422 if ($link_id == "") {
2423 $link_id = strtolower($alt_text); # for shortcut links like ![this][].
2424 }
2425
2426 $alt_text = $this->encodeAttribute($alt_text);
2427 if (isset($this->urls[$link_id])) {
2428 $url = $this->encodeAttribute($this->urls[$link_id]);
2429 $result = "<img src=\"$url\" alt=\"$alt_text\"";
2430 if (isset($this->titles[$link_id])) {
2431 $title = $this->titles[$link_id];
2432 $title = $this->encodeAttribute($title);
2433 $result .= " title=\"$title\"";
2434 }
2435 if (isset($this->ref_attr[$link_id]))
2436 $result .= $this->ref_attr[$link_id];
2437 $result .= $this->empty_element_suffix;
2438 $result = $this->hashPart($result);
2439 }
2440 else {
2441 # If there's no such link ID, leave intact:
2442 $result = $whole_match;
2443 }
2444
2445 return $result;
2446 }
2447 function _doImages_inline_callback($matches) {
2448 $whole_match = $matches[1];
2449 $alt_text = $matches[2];
2450 $url = $matches[3] == '' ? $matches[4] : $matches[3];
2451 $title =& $matches[7];
2452 $attr = $this->doExtraAttributes("img", $dummy =& $matches[8]);
2453
2454 $alt_text = $this->encodeAttribute($alt_text);
2455 $url = $this->encodeAttribute($url);
2456 $result = "<img src=\"$url\" alt=\"$alt_text\"";
2457 if (isset($title)) {
2458 $title = $this->encodeAttribute($title);
2459 $result .= " title=\"$title\""; # $title already quoted
2460 }
2461 $result .= $attr;
2462 $result .= $this->empty_element_suffix;
2463
2464 return $this->hashPart($result);
2465 }
2466
2467
2468 function doHeaders($text) {
2469 #
2470 # Redefined to add id and class attribute support.
2471 #
2472 # Setext-style headers:
2473 # Header 1 {#header1}
2474 # ========
2475 #
2476 # Header 2 {#header2 .class1 .class2}
2477 # --------
2478 #
2479 $text = preg_replace_callback(
2480 '{
2481 (^.+?) # $1: Header text
2482 (?:[ ]+ '.$this->id_class_attr_catch_re.' )? # $3 = id/class attributes
2483 [ ]*\n(=+|-+)[ ]*\n+ # $3: Header footer
2484 }mx',
2485 array(&$this, '_doHeaders_callback_setext'), $text);
2486
2487 # atx-style headers:
2488 # # Header 1 {#header1}
2489 # ## Header 2 {#header2}
2490 # ## Header 2 with closing hashes ## {#header3.class1.class2}
2491 # ...
2492 # ###### Header 6 {.class2}
2493 #
2494 $text = preg_replace_callback('{
2495 ^(\#{1,6}) # $1 = string of #\'s
2496 [ ]*
2497 (.+?) # $2 = Header text
2498 [ ]*
2499 \#* # optional closing #\'s (not counted)
2500 (?:[ ]+ '.$this->id_class_attr_catch_re.' )? # $3 = id/class attributes
2501 [ ]*
2502 \n+
2503 }xm',
2504 array(&$this, '_doHeaders_callback_atx'), $text);
2505
2506 return $text;
2507 }
2508 function _doHeaders_callback_setext($matches) {
2509 if ($matches[3] == '-' && preg_match('{^- }', $matches[1]))
2510 return $matches[0];
2511 $level = $matches[3]{0} == '=' ? 1 : 2;
2512 $attr = $this->doExtraAttributes("h$level", $dummy =& $matches[2]);
2513 $block = "<h$level$attr>".$this->runSpanGamut($matches[1])."</h$level>";
2514 return "\n" . $this->hashBlock($block) . "\n\n";
2515 }
2516 function _doHeaders_callback_atx($matches) {
2517 $level = strlen($matches[1]);
2518 $attr = $this->doExtraAttributes("h$level", $dummy =& $matches[3]);
2519 $block = "<h$level$attr>".$this->runSpanGamut($matches[2])."</h$level>";
2520 return "\n" . $this->hashBlock($block) . "\n\n";
2521 }
2522
2523
2524 function doTables($text) {
2525 #
2526 # Form HTML tables.
2527 #
2528 $less_than_tab = $this->tab_width - 1;
2529 #
2530 # Find tables with leading pipe.
2531 #
2532 # | Header 1 | Header 2
2533 # | -------- | --------
2534 # | Cell 1 | Cell 2
2535 # | Cell 3 | Cell 4
2536 #
2537 $text = preg_replace_callback('
2538 {
2539 ^ # Start of a line
2540 [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2541 [|] # Optional leading pipe (present)
2542 (.+) \n # $1: Header row (at least one pipe)
2543
2544 [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2545 [|] ([ ]*[-:]+[-| :]*) \n # $2: Header underline
2546
2547 ( # $3: Cells
2548 (?>
2549 [ ]* # Allowed whitespace.
2550 [|] .* \n # Row content.
2551 )*
2552 )
2553 (?=\n|\Z) # Stop at final double newline.
2554 }xm',
2555 array(&$this, '_doTable_leadingPipe_callback'), $text);
2556
2557 #
2558 # Find tables without leading pipe.
2559 #
2560 # Header 1 | Header 2
2561 # -------- | --------
2562 # Cell 1 | Cell 2
2563 # Cell 3 | Cell 4
2564 #
2565 $text = preg_replace_callback('
2566 {
2567 ^ # Start of a line
2568 [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2569 (\S.*[|].*) \n # $1: Header row (at least one pipe)
2570
2571 [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2572 ([-:]+[ ]*[|][-| :]*) \n # $2: Header underline
2573
2574 ( # $3: Cells
2575 (?>
2576 .* [|] .* \n # Row content
2577 )*
2578 )
2579 (?=\n|\Z) # Stop at final double newline.
2580 }xm',
2581 array(&$this, '_DoTable_callback'), $text);
2582
2583 return $text;
2584 }
2585 function _doTable_leadingPipe_callback($matches) {
2586 $head = $matches[1];
2587 $underline = $matches[2];
2588 $content = $matches[3];
2589
2590 # Remove leading pipe for each row.
2591 $content = preg_replace('/^ *[|]/m', '', $content);
2592
2593 return $this->_doTable_callback(array($matches[0], $head, $underline, $content));
2594 }
2595 function _doTable_callback($matches) {
2596 $head = $matches[1];
2597 $underline = $matches[2];
2598 $content = $matches[3];
2599
2600 # Remove any tailing pipes for each line.
2601 $head = preg_replace('/[|] *$/m', '', $head);
2602 $underline = preg_replace('/[|] *$/m', '', $underline);
2603 $content = preg_replace('/[|] *$/m', '', $content);
2604
2605 # Reading alignement from header underline.
2606 $separators = preg_split('/ *[|] */', $underline);
2607 foreach ($separators as $n => $s) {
2608 if (preg_match('/^ *-+: *$/', $s)) $attr[$n] = ' align="right"';
2609 else if (preg_match('/^ *:-+: *$/', $s))$attr[$n] = ' align="center"';
2610 else if (preg_match('/^ *:-+ *$/', $s)) $attr[$n] = ' align="left"';
2611 else $attr[$n] = '';
2612 }
2613
2614 # Parsing span elements, including code spans, character escapes,
2615 # and inline HTML tags, so that pipes inside those gets ignored.
2616 $head = $this->parseSpan($head);
2617 $headers = preg_split('/ *[|] */', $head);
2618 $col_count = count($headers);
2619 $attr = array_pad($attr, $col_count, '');
2620
2621 # Write column headers.
2622 $text = "<table>\n";
2623 $text .= "<thead>\n";
2624 $text .= "<tr>\n";
2625 foreach ($headers as $n => $header)
2626 $text .= " <th$attr[$n]>".$this->runSpanGamut(trim($header))."</th>\n";
2627 $text .= "</tr>\n";
2628 $text .= "</thead>\n";
2629
2630 # Split content by row.
2631 $rows = explode("\n", trim($content, "\n"));
2632
2633 $text .= "<tbody>\n";
2634 foreach ($rows as $row) {
2635 # Parsing span elements, including code spans, character escapes,
2636 # and inline HTML tags, so that pipes inside those gets ignored.
2637 $row = $this->parseSpan($row);
2638
2639 # Split row by cell.
2640 $row_cells = preg_split('/ *[|] */', $row, $col_count);
2641 $row_cells = array_pad($row_cells, $col_count, '');
2642
2643 $text .= "<tr>\n";
2644 foreach ($row_cells as $n => $cell)
2645 $text .= " <td$attr[$n]>".$this->runSpanGamut(trim($cell))."</td>\n";
2646 $text .= "</tr>\n";
2647 }
2648 $text .= "</tbody>\n";
2649 $text .= "</table>";
2650
2651 return $this->hashBlock($text) . "\n";
2652 }
2653
2654
2655 function doDefLists($text) {
2656 #
2657 # Form HTML definition lists.
2658 #
2659 $less_than_tab = $this->tab_width - 1;
2660
2661 # Re-usable pattern to match any entire dl list:
2662 $whole_list_re = '(?>
2663 ( # $1 = whole list
2664 ( # $2
2665 [ ]{0,'.$less_than_tab.'}
2666 ((?>.*\S.*\n)+) # $3 = defined term
2667 \n?
2668 [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2669 )
2670 (?s:.+?)
2671 ( # $4
2672 \z
2673 |
2674 \n{2,}
2675 (?=\S)
2676 (?! # Negative lookahead for another term
2677 [ ]{0,'.$less_than_tab.'}
2678 (?: \S.*\n )+? # defined term
2679 \n?
2680 [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2681 )
2682 (?! # Negative lookahead for another definition
2683 [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2684 )
2685 )
2686 )
2687 )'; // mx
2688
2689 $text = preg_replace_callback('{
2690 (?>\A\n?|(?<=\n\n))
2691 '.$whole_list_re.'
2692 }mx',
2693 array(&$this, '_doDefLists_callback'), $text);
2694
2695 return $text;
2696 }
2697 function _doDefLists_callback($matches) {
2698 # Re-usable patterns to match list item bullets and number markers:
2699 $list = $matches[1];
2700
2701 # Turn double returns into triple returns, so that we can make a
2702 # paragraph for the last item in a list, if necessary:
2703 $result = trim($this->processDefListItems($list));
2704 $result = "<dl>\n" . $result . "\n</dl>";
2705 return $this->hashBlock($result) . "\n\n";
2706 }
2707
2708
2709 function processDefListItems($list_str) {
2710 #
2711 # Process the contents of a single definition list, splitting it
2712 # into individual term and definition list items.
2713 #
2714 $less_than_tab = $this->tab_width - 1;
2715
2716 # trim trailing blank lines:
2717 $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
2718
2719 # Process definition terms.
2720 $list_str = preg_replace_callback('{
2721 (?>\A\n?|\n\n+) # leading line
2722 ( # definition terms = $1
2723 [ ]{0,'.$less_than_tab.'} # leading whitespace
2724 (?!\:[ ]|[ ]) # negative lookahead for a definition
2725 # mark (colon) or more whitespace.
2726 (?> \S.* \n)+? # actual term (not whitespace).
2727 )
2728 (?=\n?[ ]{0,3}:[ ]) # lookahead for following line feed
2729 # with a definition mark.
2730 }xm',
2731 array(&$this, '_processDefListItems_callback_dt'), $list_str);
2732
2733 # Process actual definitions.
2734 $list_str = preg_replace_callback('{
2735 \n(\n+)? # leading line = $1
2736 ( # marker space = $2
2737 [ ]{0,'.$less_than_tab.'} # whitespace before colon
2738 \:[ ]+ # definition mark (colon)
2739 )
2740 ((?s:.+?)) # definition text = $3
2741 (?= \n+ # stop at next definition mark,
2742 (?: # next term or end of text
2743 [ ]{0,'.$less_than_tab.'} \:[ ] |
2744 <dt> | \z
2745 )
2746 )
2747 }xm',
2748 array(&$this, '_processDefListItems_callback_dd'), $list_str);
2749
2750 return $list_str;
2751 }
2752 function _processDefListItems_callback_dt($matches) {
2753 $terms = explode("\n", trim($matches[1]));
2754 $text = '';
2755 foreach ($terms as $term) {
2756 $term = $this->runSpanGamut(trim($term));
2757 $text .= "\n<dt>" . $term . "</dt>";
2758 }
2759 return $text . "\n";
2760 }
2761 function _processDefListItems_callback_dd($matches) {
2762 $leading_line = $matches[1];
2763 $marker_space = $matches[2];
2764 $def = $matches[3];
2765
2766 if ($leading_line || preg_match('/\n{2,}/', $def)) {
2767 # Replace marker with the appropriate whitespace indentation
2768 $def = str_repeat(' ', strlen($marker_space)) . $def;
2769 $def = $this->runBlockGamut($this->outdent($def . "\n\n"));
2770 $def = "\n". $def ."\n";
2771 }
2772 else {
2773 $def = rtrim($def);
2774 $def = $this->runSpanGamut($this->outdent($def));
2775 }
2776
2777 return "\n<dd>" . $def . "</dd>\n";
2778 }
2779
2780
2781 function doFencedCodeBlocks($text) {
2782 #
2783 # Adding the fenced code block syntax to regular Markdown:
2784 #
2785 # ~~~
2786 # Code block
2787 # ~~~
2788 #
2789 $less_than_tab = $this->tab_width;
2790
2791 $text = preg_replace_callback('{
2792 (?:\n|\A)
2793 # 1: Opening marker
2794 (
2795 (?:~{3,}|`{3,}) # 3 or more tildes/backticks.
2796 )
2797 [ ]*
2798 (?:
2799 \.?([-_:a-zA-Z0-9]+) # 2: standalone class name
2800 |
2801 '.$this->id_class_attr_catch_re.' # 3: Extra attributes
2802 )?
2803 [ ]* \n # Whitespace and newline following marker.
2804
2805 # 4: Content
2806 (
2807 (?>
2808 (?!\1 [ ]* \n) # Not a closing marker.
2809 .*\n+
2810 )+
2811 )
2812
2813 # Closing marker.
2814 \1 [ ]* (?= \n )
2815 }xm',
2816 array(&$this, '_doFencedCodeBlocks_callback'), $text);
2817
2818 return $text;
2819 }
2820 function _doFencedCodeBlocks_callback($matches) {
2821 $classname =& $matches[2];
2822 $attrs =& $matches[3];
2823 $codeblock = $matches[4];
2824 $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
2825 $codeblock = preg_replace_callback('/^\n+/',
2826 array(&$this, '_doFencedCodeBlocks_newlines'), $codeblock);
2827
2828 if ($classname != "") {
2829 if ($classname{0} == '.')
2830 $classname = substr($classname, 1);
2831 $attr_str = ' class="'.$this->code_class_prefix.$classname.'"';
2832 } else {
2833 $attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs);
2834 }
2835 $pre_attr_str = $this->code_attr_on_pre ? $attr_str : '';
2836 $code_attr_str = $this->code_attr_on_pre ? '' : $attr_str;
2837 $codeblock = "<pre$pre_attr_str><code$code_attr_str>$codeblock</code></pre>";
2838
2839 return "\n\n".$this->hashBlock($codeblock)."\n\n";
2840 }
2841 function _doFencedCodeBlocks_newlines($matches) {
2842 return str_repeat("<br$this->empty_element_suffix",
2843 strlen($matches[0]));
2844 }
2845
2846
2847 #
2848 # Redefining emphasis markers so that emphasis by underscore does not
2849 # work in the middle of a word.
2850 #
2851 public $em_relist = array(
2852 '' => '(?:(?<!\*)\*(?!\*)|(?<![a-zA-Z0-9_])_(?!_))(?=\S|$)(?![\.,:;]\s)',
2853 '*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
2854 '_' => '(?<=\S|^)(?<!_)_(?![a-zA-Z0-9_])',
2855 );
2856 public $strong_relist = array(
2857 '' => '(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?=\S|$)(?![\.,:;]\s)',
2858 '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
2859 '__' => '(?<=\S|^)(?<!_)__(?![a-zA-Z0-9_])',
2860 );
2861 public $em_strong_relist = array(
2862 '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?=\S|$)(?![\.,:;]\s)',
2863 '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
2864 '___' => '(?<=\S|^)(?<!_)___(?![a-zA-Z0-9_])',
2865 );
2866
2867
2868 function formParagraphs($text) {
2869 #
2870 # Params:
2871 # $text - string to process with html <p> tags
2872 #
2873 # Strip leading and trailing lines:
2874 $text = preg_replace('/\A\n+|\n+\z/', '', $text);
2875
2876 $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
2877
2878 #
2879 # Wrap <p> tags and unhashify HTML blocks
2880 #
2881 foreach ($grafs as $key => $value) {
2882 $value = trim($this->runSpanGamut($value));
2883
2884 # Check if this should be enclosed in a paragraph.
2885 # Clean tag hashes & block tag hashes are left alone.
2886 $is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value);
2887
2888 if ($is_p) {
2889 $value = "<p>$value</p>";
2890 }
2891 $grafs[$key] = $value;
2892 }
2893
2894 # Join grafs in one text, then unhash HTML tags.
2895 $text = implode("\n\n", $grafs);
2896
2897 # Finish by removing any tag hashes still present in $text.
2898 $text = $this->unhash($text);
2899
2900 return $text;
2901 }
2902
2903
2904 ### Footnotes
2905
2906 function stripFootnotes($text) {
2907 #
2908 # Strips link definitions from text, stores the URLs and titles in
2909 # hash references.
2910 #
2911 $less_than_tab = $this->tab_width - 1;
2912
2913 # Link defs are in the form: [^id]: url "optional title"
2914 $text = preg_replace_callback('{
2915 ^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?: # note_id = $1
2916 [ ]*
2917 \n? # maybe *one* newline
2918 ( # text = $2 (no blank lines allowed)
2919 (?:
2920 .+ # actual text
2921 |
2922 \n # newlines but
2923 (?!\[\^.+?\]:\s)# negative lookahead for footnote marker.
2924 (?!\n+[ ]{0,3}\S)# ensure line is not blank and followed
2925 # by non-indented content
2926 )*
2927 )
2928 }xm',
2929 array(&$this, '_stripFootnotes_callback'),
2930 $text);
2931 return $text;
2932 }
2933 function _stripFootnotes_callback($matches) {
2934 $note_id = $this->fn_id_prefix . $matches[1];
2935 $this->footnotes[$note_id] = $this->outdent($matches[2]);
2936 return ''; # String that will replace the block
2937 }
2938
2939
2940 function doFootnotes($text) {
2941 #
2942 # Replace footnote references in $text [^id] with a special text-token
2943 # which will be replaced by the actual footnote marker in appendFootnotes.
2944 #
2945 if (!$this->in_anchor) {
2946 $text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text);
2947 }
2948 return $text;
2949 }
2950
2951
2952 function appendFootnotes($text) {
2953 #
2954 # Append footnote list to text.
2955 #
2956 $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}',
2957 array(&$this, '_appendFootnotes_callback'), $text);
2958
2959 if (!empty($this->footnotes_ordered)) {
2960 $text .= "\n\n";
2961 $text .= "<div class=\"footnotes\">\n";
2962 $text .= "<hr". $this->empty_element_suffix ."\n";
2963 $text .= "<ol>\n\n";
2964
2965 $attr = "";
2966 if ($this->fn_backlink_class != "") {
2967 $class = $this->fn_backlink_class;
2968 $class = $this->encodeAttribute($class);
2969 $attr .= " class=\"$class\"";
2970 }
2971 if ($this->fn_backlink_title != "") {
2972 $title = $this->fn_backlink_title;
2973 $title = $this->encodeAttribute($title);
2974 $attr .= " title=\"$title\"";
2975 }
2976 $num = 0;
2977
2978 while (!empty($this->footnotes_ordered)) {
2979 $footnote = reset($this->footnotes_ordered);
2980 $note_id = key($this->footnotes_ordered);
2981 unset($this->footnotes_ordered[$note_id]);
2982 $ref_count = $this->footnotes_ref_count[$note_id];
2983 unset($this->footnotes_ref_count[$note_id]);
2984 unset($this->footnotes[$note_id]);
2985
2986 $footnote .= "\n"; # Need to append newline before parsing.
2987 $footnote = $this->runBlockGamut("$footnote\n");
2988 $footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}',
2989 array(&$this, '_appendFootnotes_callback'), $footnote);
2990
2991 $attr = str_replace("%%", ++$num, $attr);
2992 $note_id = $this->encodeAttribute($note_id);
2993
2994 # Prepare backlink, multiple backlinks if multiple references
2995 $backlink = "<a href=\"#fnref:$note_id\"$attr>&#8617;</a>";
2996 for ($ref_num = 2; $ref_num <= $ref_count; ++$ref_num) {
2997 $backlink .= " <a href=\"#fnref$ref_num:$note_id\"$attr>&#8617;</a>";
2998 }
2999 # Add backlink to last paragraph; create new paragraph if needed.
3000 if (preg_match('{</p>$}', $footnote)) {
3001 $footnote = substr($footnote, 0, -4) . "&#160;$backlink</p>";
3002 } else {
3003 $footnote .= "\n\n<p>$backlink</p>";
3004 }
3005
3006 $text .= "<li id=\"fn:$note_id\">\n";
3007 $text .= $footnote . "\n";
3008 $text .= "</li>\n\n";
3009 }
3010
3011 $text .= "</ol>\n";
3012 $text .= "</div>";
3013 }
3014 return $text;
3015 }
3016 function _appendFootnotes_callback($matches) {
3017 $node_id = $this->fn_id_prefix . $matches[1];
3018
3019 # Create footnote marker only if it has a corresponding footnote *and*
3020 # the footnote hasn't been used by another marker.
3021 if (isset($this->footnotes[$node_id])) {
3022 $num =& $this->footnotes_numbers[$node_id];
3023 if (!isset($num)) {
3024 # Transfer footnote content to the ordered list and give it its
3025 # number
3026 $this->footnotes_ordered[$node_id] = $this->footnotes[$node_id];
3027 $this->footnotes_ref_count[$node_id] = 1;
3028 $num = $this->footnote_counter++;
3029 $ref_count_mark = '';
3030 } else {
3031 $ref_count_mark = $this->footnotes_ref_count[$node_id] += 1;
3032 }
3033
3034 $attr = "";
3035 if ($this->fn_link_class != "") {
3036 $class = $this->fn_link_class;
3037 $class = $this->encodeAttribute($class);
3038 $attr .= " class=\"$class\"";
3039 }
3040 if ($this->fn_link_title != "") {
3041 $title = $this->fn_link_title;
3042 $title = $this->encodeAttribute($title);
3043 $attr .= " title=\"$title\"";
3044 }
3045
3046 $attr = str_replace("%%", $num, $attr);
3047 $node_id = $this->encodeAttribute($node_id);
3048
3049 return
3050 "<sup id=\"fnref$ref_count_mark:$node_id\">".
3051 "<a href=\"#fn:$node_id\"$attr>$num</a>".
3052 "</sup>";
3053 }
3054
3055 return "[^".$matches[1]."]";
3056 }
3057
3058
3059 ### Abbreviations ###
3060
3061 function stripAbbreviations($text) {
3062 #
3063 # Strips abbreviations from text, stores titles in hash references.
3064 #
3065 $less_than_tab = $this->tab_width - 1;
3066
3067 # Link defs are in the form: [id]*: url "optional title"
3068 $text = preg_replace_callback('{
3069 ^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?: # abbr_id = $1
3070 (.*) # text = $2 (no blank lines allowed)
3071 }xm',
3072 array(&$this, '_stripAbbreviations_callback'),
3073 $text);
3074 return $text;
3075 }
3076 function _stripAbbreviations_callback($matches) {
3077 $abbr_word = $matches[1];
3078 $abbr_desc = $matches[2];
3079 if ($this->abbr_word_re)
3080 $this->abbr_word_re .= '|';
3081 $this->abbr_word_re .= preg_quote($abbr_word);
3082 $this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
3083 return ''; # String that will replace the block
3084 }
3085
3086
3087 function doAbbreviations($text) {
3088 #
3089 # Find defined abbreviations in text and wrap them in <abbr> elements.
3090 #
3091 if ($this->abbr_word_re) {
3092 // cannot use the /x modifier because abbr_word_re may
3093 // contain significant spaces:
3094 $text = preg_replace_callback('{'.
3095 '(?<![\w\x1A])'.
3096 '(?:'.$this->abbr_word_re.')'.
3097 '(?![\w\x1A])'.
3098 '}',
3099 array(&$this, '_doAbbreviations_callback'), $text);
3100 }
3101 return $text;
3102 }
3103 function _doAbbreviations_callback($matches) {
3104 $abbr = $matches[0];
3105 if (isset($this->abbr_desciptions[$abbr])) {
3106 $desc = $this->abbr_desciptions[$abbr];
3107 if (empty($desc)) {
3108 return $this->hashPart("<abbr>$abbr</abbr>");
3109 } else {
3110 $desc = $this->encodeAttribute($desc);
3111 return $this->hashPart("<abbr title=\"$desc\">$abbr</abbr>");
3112 }
3113 } else {
3114 return $matches[0];
3115 }
3116 }
3117
3118 }
3119
3120
3121 /*
3122
3123 PHP Markdown Extra
3124 ==================
3125
3126 Description
3127 -----------
3128
3129 This is a PHP port of the original Markdown formatter written in Perl
3130 by John Gruber. This special "Extra" version of PHP Markdown features
3131 further enhancements to the syntax for making additional constructs
3132 such as tables and definition list.
3133
3134 Markdown is a text-to-HTML filter; it translates an easy-to-read /
3135 easy-to-write structured text format into HTML. Markdown's text format
3136 is mostly similar to that of plain text email, and supports features such
3137 as headers, *emphasis*, code blocks, blockquotes, and links.
3138
3139 Markdown's syntax is designed not as a generic markup language, but
3140 specifically to serve as a front-end to (X)HTML. You can use span-level
3141 HTML tags anywhere in a Markdown document, and you can use block level
3142 HTML tags (like <div> and <table> as well).
3143
3144 For more information about Markdown's syntax, see:
3145
3146 <http://daringfireball.net/projects/markdown/>
3147
3148
3149 Bugs
3150 ----
3151
3152 To file bug reports please send email to:
3153
3154 <michel.fortin@michelf.ca>
3155
3156 Please include with your report: (1) the example input; (2) the output you
3157 expected; (3) the output Markdown actually produced.
3158
3159
3160 Version History
3161 ---------------
3162
3163 See the readme file for detailed release notes for this version.
3164
3165
3166 Copyright and License
3167 ---------------------
3168
3169 PHP Markdown & Extra
3170 Copyright (c) 2004-2013 Michel Fortin
3171 <http://michelf.ca/>
3172 All rights reserved.
3173
3174 Based on Markdown
3175 Copyright (c) 2003-2006 John Gruber
3176 <http://daringfireball.net/>
3177 All rights reserved.
3178
3179 Redistribution and use in source and binary forms, with or without
3180 modification, are permitted provided that the following conditions are
3181 met:
3182
3183 * Redistributions of source code must retain the above copyright notice,
3184 this list of conditions and the following disclaimer.
3185
3186 * Redistributions in binary form must reproduce the above copyright
3187 notice, this list of conditions and the following disclaimer in the
3188 documentation and/or other materials provided with the distribution.
3189
3190 * Neither the name "Markdown" nor the names of its contributors may
3191 be used to endorse or promote products derived from this software
3192 without specific prior written permission.
3193
3194 This software is provided by the copyright holders and contributors "as
3195 is" and any express or implied warranties, including, but not limited
3196 to, the implied warranties of merchantability and fitness for a
3197 particular purpose are disclaimed. In no event shall the copyright owner
3198 or contributors be liable for any direct, indirect, incidental, special,
3199 exemplary, or consequential damages (including, but not limited to,
3200 procurement of substitute goods or services; loss of use, data, or
3201 profits; or business interruption) however caused and on any theory of
3202 liability, whether in contract, strict liability, or tort (including
3203 negligence or otherwise) arising in any way out of the use of this
3204 software, even if advised of the possibility of such damage.
3205
3206 */
3207 ?>
3208