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