PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-a.5
Jetpack – WP Security, Backup, Speed, & Growth v16.1-a.5
16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / _inc / lib / markdown / extra.php
jetpack / _inc / lib / markdown Last commit date
README.md 8 years ago extra.php 8 months ago gfm.php 8 months ago
extra.php
3230 lines
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: <address@domain.foo>
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. "foo@example.com"
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 <michel.fortin@michelf.ca>
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 ?>
3230