384978f34549164f97fc7ce2a79696a560446bff
[moodle.git] / lib / markdown.php
1 <?php
3 /**
4  *
5  * Markdown Extra  -  A text-to-HTML conversion tool for web writers
6  *
7  * PHP Markdown & Extra
8  * Copyright (c) 2004-2007 Michel Fortin
9  * <http://www.michelf.com/projects/php-markdown/>
10  *
11  * Original Markdown
12  * Copyright (c) 2004-2006 John Gruber
13  * <http://daringfireball.net/projects/markdown/>
14  *
15  * @package   moodlecore
16  * @copyright (c) 2004-2006 John Gruber  
17  */
19 /**  MARKDOWN_VERSION = 1.0.1j */
20 define( 'MARKDOWN_VERSION',  "1.0.1j" ); # Tue 4 Sep 2007
21 /**  MARKDOWNEXTRA_VERSION = 1.1.6 */
22 define( 'MARKDOWNEXTRA_VERSION',  "1.1.6" ); # Tue 4 Sep 2007
25 #
26 # Global default settings:
27 #
29 /** Change to ">" for HTML output */
30 @define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX',  " />");
32 /** Define the width of a tab for code blocks. */
33 @define( 'MARKDOWN_TAB_WIDTH',     4 );
35 /** Optional title attribute for footnote links and backlinks. */
36 @define( 'MARKDOWN_FN_LINK_TITLE',         "" );
37 @define( 'MARKDOWN_FN_BACKLINK_TITLE',     "" );
39 # Optional class attribute for footnote links and backlinks.
40 @define( 'MARKDOWN_FN_LINK_CLASS',         "" );
41 @define( 'MARKDOWN_FN_BACKLINK_CLASS',     "" );
44 #
45 # WordPress settings:
46 #
48 # Change to false to remove Markdown from posts and/or comments.
49 @define( 'MARKDOWN_WP_POSTS',      true );
50 @define( 'MARKDOWN_WP_COMMENTS',   true );
54 ### Standard Function Interface ###
56 @define( 'MARKDOWN_PARSER_CLASS',  'MarkdownExtra_Parser' );
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         }
69         # Transform text using parser.
70         return $parser->transform($text);
71 }
74 ### WordPress Plugin Interface ###
76 /*
77 Plugin Name: Markdown Extra
78 Plugin URI: http://www.michelf.com/projects/php-markdown/
79 Description: <a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://www.michelf.com/projects/php-markdown/">More...</a>
80 Version: 1.1.6
81 Author: Michel Fortin
82 Author URI: http://www.michelf.com/
83 */
85 if (isset($wp_version)) {
86         # More details about how it works here:
87         # <http://www.michelf.com/weblog/2005/wordpress-text-flow-vs-markdown/>
88         
89         # Post content and excerpts
90         # - Remove WordPress paragraph generator.
91         # - Run Markdown on excerpt, then remove all tags.
92         # - Add paragraph tag around the excerpt, but remove it for the excerpt rss.
93         if (MARKDOWN_WP_POSTS) {
94                 remove_filter('the_content',     'wpautop');
95         remove_filter('the_content_rss', 'wpautop');
96                 remove_filter('the_excerpt',     'wpautop');
97                 add_filter('the_content',     'Markdown', 6);
98         add_filter('the_content_rss', 'Markdown', 6);
99                 add_filter('get_the_excerpt', 'Markdown', 6);
100                 add_filter('get_the_excerpt', 'trim', 7);
101                 add_filter('the_excerpt',     'mdwp_add_p');
102                 add_filter('the_excerpt_rss', 'mdwp_strip_p');
103                 
104                 remove_filter('content_save_pre',  'balanceTags', 50);
105                 remove_filter('excerpt_save_pre',  'balanceTags', 50);
106                 add_filter('the_content',         'balanceTags', 50);
107                 add_filter('get_the_excerpt', 'balanceTags', 9);
108         }
109         
110         # Comments
111         # - Remove WordPress paragraph generator.
112         # - Remove WordPress auto-link generator.
113         # - Scramble important tags before passing them to the kses filter.
114         # - Run Markdown on excerpt then remove paragraph tags.
115         if (MARKDOWN_WP_COMMENTS) {
116                 remove_filter('comment_text', 'wpautop', 30);
117                 remove_filter('comment_text', 'make_clickable');
118                 add_filter('pre_comment_content', 'Markdown', 6);
119                 add_filter('pre_comment_content', 'mdwp_hide_tags', 8);
120                 add_filter('pre_comment_content', 'mdwp_show_tags', 12);
121                 add_filter('get_comment_text',    'Markdown', 6);
122                 add_filter('get_comment_excerpt', 'Markdown', 6);
123                 add_filter('get_comment_excerpt', 'mdwp_strip_p', 7);
124         
125                 global $mdwp_hidden_tags, $mdwp_placeholders;
126                 $mdwp_hidden_tags = explode(' ',
127                         '<p> </p> <pre> </pre> <ol> </ol> <ul> </ul> <li> </li>');
128                 $mdwp_placeholders = explode(' ', str_rot13(
129                         'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR '.
130                         'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli'));
131         }
132         
133         function mdwp_add_p($text) {
134                 if (!preg_match('{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text)) {
135                         $text = '<p>'.$text.'</p>';
136                         $text = preg_replace('{\n{2,}}', "</p>\n\n<p>", $text);
137                 }
138                 return $text;
139         }
140         
141         function mdwp_strip_p($t) { return preg_replace('{</?p>}i', '', $t); }
143         function mdwp_hide_tags($text) {
144                 global $mdwp_hidden_tags, $mdwp_placeholders;
145                 return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text);
146         }
147         function mdwp_show_tags($text) {
148                 global $mdwp_hidden_tags, $mdwp_placeholders;
149                 return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text);
150         }
154 ### bBlog Plugin Info ###
156 function identify_modifier_markdown() {
157         return array(
158                 'name' => 'markdown',
159                 'type' => 'modifier',
160                 'nicename' => 'PHP Markdown Extra',
161                 'description' => 'A text-to-HTML conversion tool for web writers',
162                 'authors' => 'Michel Fortin and John Gruber',
163                 'licence' => 'GPL',
164                 'version' => MARKDOWNEXTRA_VERSION,
165                 'help' => '<a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://www.michelf.com/projects/php-markdown/">More...</a>',
166                 );
170 ### Smarty Modifier Interface ###
172 function smarty_modifier_markdown($text) {
173         return Markdown($text);
177 ### Textile Compatibility Mode ###
179 # Rename this file to "classTextile.php" and it can replace Textile everywhere.
181 if (strcasecmp(substr(__FILE__, -16), "classTextile.php") == 0) {
182         # Try to include PHP SmartyPants. Should be in the same directory.
183         @include_once 'smartypants.php';
184         # Fake Textile class. It calls Markdown instead.
185     /**
186      * @package   moodlecore
187      * @copyright (c) 2004-2006 John Gruber
188      */
189         class Textile {
190                 function TextileThis($text, $lite='', $encode='') {
191                         if ($lite == '' && $encode == '')    $text = Markdown($text);
192                         if (function_exists('SmartyPants'))  $text = SmartyPants($text);
193                         return $text;
194                 }
195                 # Fake restricted version: restrictions are not supported for now.
196                 function TextileRestricted($text, $lite='', $noimage='') {
197                         return $this->TextileThis($text, $lite);
198                 }
199                 # Workaround to ensure compatibility with TextPattern 4.0.3.
200                 function blockLite($text) { return $text; }
201         }
206 /**
207  * Markdown Parser Class
208  *
209  * @package   moodlecore
210  * @copyright (c) 2004-2006 John Gruber
211  */
213 class Markdown_Parser {
215         # Regex to match balanced [brackets].
216         # Needed to insert a maximum bracked depth while converting to PHP.
217         var $nested_brackets_depth = 6;
218         var $nested_brackets;
219         
220         var $nested_url_parenthesis_depth = 4;
221         var $nested_url_parenthesis;
223         # Table of hash values for escaped characters:
224         var $escape_chars = '\`*_{}[]()>#+-.!';
226         # Change to ">" for HTML output.
227         var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
228         var $tab_width = MARKDOWN_TAB_WIDTH;
229         
230         # Change to `true` to disallow markup or entities.
231         var $no_markup = false;
232         var $no_entities = false;
235         function Markdown_Parser() {
236         #
237         # Constructor function. Initialize appropriate member variables.
238         #
239                 $this->_initDetab();
240         
241                 $this->nested_brackets = 
242                         str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth).
243                         str_repeat('\])*', $this->nested_brackets_depth);
244         
245                 $this->nested_url_parenthesis = 
246                         str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth).
247                         str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth);
248                 
249                 # Sort document, block, and span gamut in ascendent priority order.
250                 asort($this->document_gamut);
251                 asort($this->block_gamut);
252                 asort($this->span_gamut);
253         }
256         # Internal hashes used during transformation.
257         var $urls = array();
258         var $titles = array();
259         var $html_hashes = array();
260         
261         # Status flag to avoid invalid nesting.
262         var $in_anchor = false;
265         function transform($text) {
266         #
267         # Main function. The order in which other subs are called here is
268         # essential. Link and image substitutions need to happen before
269         # _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
270         # and <img> tags get encoded.
271         #
272                 # Clear the global hashes. If we don't clear these, you get conflicts
273                 # from other articles when generating a page which contains more than
274                 # one article (e.g. an index page that shows the N most recent
275                 # articles):
276                 $this->urls = array();
277                 $this->titles = array();
278                 $this->html_hashes = array();
280                 # Standardize line endings:
281                 #   DOS to Unix and Mac to Unix
282                 $text = preg_replace('{\r\n?}', "\n", $text);
284                 # Make sure $text ends with a couple of newlines:
285                 $text .= "\n\n";
287                 # Convert all tabs to spaces.
288                 $text = $this->detab($text);
290                 # Turn block-level HTML blocks into hash entries
291                 $text = $this->hashHTMLBlocks($text);
293                 # Strip any lines consisting only of spaces and tabs.
294                 # This makes subsequent regexen easier to write, because we can
295                 # match consecutive blank lines with /\n+/ instead of something
296                 # contorted like /[ ]*\n+/ .
297                 $text = preg_replace('/^[ ]+$/m', '', $text);
299                 # Run document gamut methods.
300                 foreach ($this->document_gamut as $method => $priority) {
301                         $text = $this->$method($text);
302                 }
304                 return $text . "\n";
305         }
306         
307         var $document_gamut = array(
308                 # Strip link definitions, store in hashes.
309                 "stripLinkDefinitions" => 20,
310                 
311                 "runBasicBlockGamut"   => 30,
312                 );
315         function stripLinkDefinitions($text) {
316         #
317         # Strips link definitions from text, stores the URLs and titles in
318         # hash references.
319         #
320                 $less_than_tab = $this->tab_width - 1;
322                 # Link defs are in the form: ^[id]: url "optional title"
323                 $text = preg_replace_callback('{
324                                                         ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1
325                                                           [ ]*
326                                                           \n?                           # maybe *one* newline
327                                                           [ ]*
328                                                         <?(\S+?)>?                      # url = $2
329                                                           [ ]*
330                                                           \n?                           # maybe one newline
331                                                           [ ]*
332                                                         (?:
333                                                                 (?<=\s)                 # lookbehind for whitespace
334                                                                 ["(]
335                                                                 (.*?)                   # title = $3
336                                                                 [")]
337                                                                 [ ]*
338                                                         )?      # title is optional
339                                                         (?:\n+|\Z)
340                         }xm',
341                         array(&$this, '_stripLinkDefinitions_callback'),
342                         $text);
343                 return $text;
344         }
345         function _stripLinkDefinitions_callback($matches) {
346                 $link_id = strtolower($matches[1]);
347                 $this->urls[$link_id] = $this->encodeAmpsAndAngles($matches[2]);
348                 if (isset($matches[3]))
349                         $this->titles[$link_id] = str_replace('"', '&quot;', $matches[3]);
350                 return ''; # String that will replace the block
351         }
354         function hashHTMLBlocks($text) {
355                 if ($this->no_markup)  return $text;
357                 $less_than_tab = $this->tab_width - 1;
359                 # Hashify HTML blocks:
360                 # We only want to do this for block-level HTML tags, such as headers,
361                 # lists, and tables. That's because we still want to wrap <p>s around
362                 # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
363                 # phrase emphasis, and spans. The list of tags we're looking for is
364                 # hard-coded:
365                 #
366                 # *  List "a" is made of tags which can be both inline or block-level.
367                 #    These will be treated block-level when the start tag is alone on 
368                 #    its line, otherwise they're not matched here and will be taken as 
369                 #    inline later.
370                 # *  List "b" is made of tags which are always block-level;
371                 #
372                 $block_tags_a = 'ins|del';
373                 $block_tags_b = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'.
374                                                 'script|noscript|form|fieldset|iframe|math';
376                 # Regular expression for the content of a block tag.
377                 $nested_tags_level = 4;
378                 $attr = '
379                         (?>                             # optional tag attributes
380                           \s                    # starts with whitespace
381                           (?>
382                                 [^>"/]+         # text outside quotes
383                           |
384                                 /+(?!>)         # slash not followed by ">"
385                           |
386                                 "[^"]*"         # text inside double quotes (tolerate ">")
387                           |
388                                 \'[^\']*\'      # text inside single quotes (tolerate ">")
389                           )*
390                         )?      
391                         ';
392                 $content =
393                         str_repeat('
394                                 (?>
395                                   [^<]+                 # content without tag
396                                 |
397                                   <\2                   # nested opening tag
398                                         '.$attr.'       # attributes
399                                         (?>
400                                           />
401                                         |
402                                           >', $nested_tags_level).      # end of opening tag
403                                           '.*?'.                                        # last level nested tag content
404                         str_repeat('
405                                           </\2\s*>      # closing nested tag
406                                         )
407                                   |                             
408                                         <(?!/\2\s*>     # other tags with a different name
409                                   )
410                                 )*',
411                                 $nested_tags_level);
412                 $content2 = str_replace('\2', '\3', $content);
414                 # First, look for nested blocks, e.g.:
415                 #       <div>
416                 #               <div>
417                 #               tags for inner block must be indented.
418                 #               </div>
419                 #       </div>
420                 #
421                 # The outermost tags must start at the left margin for this to match, and
422                 # the inner nested divs must be indented.
423                 # We need to do this before the next, more liberal match, because the next
424                 # match will start at the first `<div>` and stop at the first `</div>`.
425                 $text = preg_replace_callback('{(?>
426                         (?>
427                                 (?<=\n\n)               # Starting after a blank line
428                                 |                               # or
429                                 \A\n?                   # the beginning of the doc
430                         )
431                         (                                               # save in $1
433                           # Match from `\n<tag>` to `</tag>\n`, handling nested tags 
434                           # in between.
435                                         
436                                                 [ ]{0,'.$less_than_tab.'}
437                                                 <('.$block_tags_b.')# start tag = $2
438                                                 '.$attr.'>                      # attributes followed by > and \n
439                                                 '.$content.'            # content, support nesting
440                                                 </\2>                           # the matching end tag
441                                                 [ ]*                            # trailing spaces/tabs
442                                                 (?=\n+|\Z)      # followed by a newline or end of document
444                         | # Special version for tags of group a.
446                                                 [ ]{0,'.$less_than_tab.'}
447                                                 <('.$block_tags_a.')# start tag = $3
448                                                 '.$attr.'>[ ]*\n        # attributes followed by >
449                                                 '.$content2.'           # content, support nesting
450                                                 </\3>                           # the matching end tag
451                                                 [ ]*                            # trailing spaces/tabs
452                                                 (?=\n+|\Z)      # followed by a newline or end of document
453                                         
454                         | # Special case just for <hr />. It was easier to make a special 
455                           # case than to make the other regex more complicated.
456                         
457                                                 [ ]{0,'.$less_than_tab.'}
458                                                 <(hr)                           # start tag = $2
459                                                 \b                                      # word break
460                                                 ([^<>])*?                       # 
461                                                 /?>                                     # the matching end tag
462                                                 [ ]*
463                                                 (?=\n{2,}|\Z)           # followed by a blank line or end of document
464                         
465                         | # Special case for standalone HTML comments:
466                         
467                                         [ ]{0,'.$less_than_tab.'}
468                                         (?s:
469                                                 <!-- .*? -->
470                                         )
471                                         [ ]*
472                                         (?=\n{2,}|\Z)           # followed by a blank line or end of document
473                         
474                         | # PHP and ASP-style processor instructions (<? and <%)
475                         
476                                         [ ]{0,'.$less_than_tab.'}
477                                         (?s:
478                                                 <([?%])                 # $2
479                                                 .*?
480                                                 \2>
481                                         )
482                                         [ ]*
483                                         (?=\n{2,}|\Z)           # followed by a blank line or end of document
484                                         
485                         )
486                         )}Sxmi',
487                         array(&$this, '_hashHTMLBlocks_callback'),
488                         $text);
490                 return $text;
491         }
492         function _hashHTMLBlocks_callback($matches) {
493                 $text = $matches[1];
494                 $key  = $this->hashBlock($text);
495                 return "\n\n$key\n\n";
496         }
497         
498         
499         function hashPart($text, $boundary = 'X') {
500         #
501         # Called whenever a tag must be hashed when a function insert an atomic 
502         # element in the text stream. Passing $text to through this function gives
503         # a unique text-token which will be reverted back when calling unhash.
504         #
505         # The $boundary argument specify what character should be used to surround
506         # the token. By convension, "B" is used for block elements that needs not
507         # to be wrapped into paragraph tags at the end, ":" is used for elements
508         # that are word separators and "S" is used for general span-level elements.
509         #
510                 # Swap back any tag hash found in $text so we do not have to `unhash`
511                 # multiple times at the end.
512                 $text = $this->unhash($text);
513                 
514                 # Then hash the block.
515                 static $i = 0;
516                 $key = "$boundary\x1A" . ++$i . $boundary;
517                 $this->html_hashes[$key] = $text;
518                 return $key; # String that will replace the tag.
519         }
522         function hashBlock($text) {
523         #
524         # Shortcut function for hashPart with block-level boundaries.
525         #
526                 return $this->hashPart($text, 'B');
527         }
530         var $block_gamut = array(
531         #
532         # These are all the transformations that form block-level
533         # tags like paragraphs, headers, and list items.
534         #
535                 "doHeaders"         => 10,
536                 "doHorizontalRules" => 20,
537                 
538                 "doLists"           => 40,
539                 "doCodeBlocks"      => 50,
540                 "doBlockQuotes"     => 60,
541                 );
543         function runBlockGamut($text) {
544         #
545         # Run block gamut tranformations.
546         #
547                 # We need to escape raw HTML in Markdown source before doing anything 
548                 # else. This need to be done for each block, and not only at the 
549                 # begining in the Markdown function since hashed blocks can be part of
550                 # list items and could have been indented. Indented blocks would have 
551                 # been seen as a code block in a previous pass of hashHTMLBlocks.
552                 $text = $this->hashHTMLBlocks($text);
553                 
554                 return $this->runBasicBlockGamut($text);
555         }
556         
557         function runBasicBlockGamut($text) {
558         #
559         # Run block gamut tranformations, without hashing HTML blocks. This is 
560         # useful when HTML blocks are known to be already hashed, like in the first
561         # whole-document pass.
562         #
563                 foreach ($this->block_gamut as $method => $priority) {
564                         $text = $this->$method($text);
565                 }
566                 
567                 # Finally form paragraph and restore hashed blocks.
568                 $text = $this->formParagraphs($text);
570                 return $text;
571         }
572         
573         
574         function doHorizontalRules($text) {
575                 # Do Horizontal Rules:
576                 return preg_replace(
577                         '{
578                                 ^[ ]{0,3}       # Leading space
579                                 ([*-_])         # $1: First marker
580                                 (?>                     # Repeated marker group
581                                         [ ]{0,2}        # Zero, one, or two spaces.
582                                         \1                      # Marker character
583                                 ){2,}           # Group repeated at least twice
584                                 [ ]*            # Tailing spaces
585                                 $                       # End of line.
586                         }mx',
587                         "\n".$this->hashBlock("<hr$this->empty_element_suffix")."\n", 
588                         $text);
589         }
592         var $span_gamut = array(
593         #
594         # These are all the transformations that occur *within* block-level
595         # tags like paragraphs, headers, and list items.
596         #
597                 # Process character escapes, code spans, and inline HTML
598                 # in one shot.
599                 "parseSpan"           => -30,
601                 # Process anchor and image tags. Images must come first,
602                 # because ![foo][f] looks like an anchor.
603                 "doImages"            =>  10,
604                 "doAnchors"           =>  20,
605                 
606                 # Make links out of things like `<http://example.com/>`
607                 # Must come after doAnchors, because you can use < and >
608                 # delimiters in inline links like [this](<url>).
609                 "doAutoLinks"         =>  30,
610                 "encodeAmpsAndAngles" =>  40,
612                 "doItalicsAndBold"    =>  50,
613                 "doHardBreaks"        =>  60,
614                 );
616         function runSpanGamut($text) {
617         #
618         # Run span gamut tranformations.
619         #
620                 foreach ($this->span_gamut as $method => $priority) {
621                         $text = $this->$method($text);
622                 }
624                 return $text;
625         }
626         
627         
628         function doHardBreaks($text) {
629                 # Do hard breaks:
630                 return preg_replace_callback('/ {2,}\n/', 
631                         array(&$this, '_doHardBreaks_callback'), $text);
632         }
633         function _doHardBreaks_callback($matches) {
634                 return $this->hashPart("<br$this->empty_element_suffix\n");
635         }
638         function doAnchors($text) {
639         #
640         # Turn Markdown link shortcuts into XHTML <a> tags.
641         #
642                 if ($this->in_anchor) return $text;
643                 $this->in_anchor = true;
644                 
645                 #
646                 # First, handle reference-style links: [link text] [id]
647                 #
648                 $text = preg_replace_callback('{
649                         (                                       # wrap whole match in $1
650                           \[
651                                 ('.$this->nested_brackets.')    # link text = $2
652                           \]
654                           [ ]?                          # one optional space
655                           (?:\n[ ]*)?           # one optional newline followed by spaces
657                           \[
658                                 (.*?)           # id = $3
659                           \]
660                         )
661                         }xs',
662                         array(&$this, '_doAnchors_reference_callback'), $text);
664                 #
665                 # Next, inline-style links: [link text](url "optional title")
666                 #
667                 $text = preg_replace_callback('{
668                         (                               # wrap whole match in $1
669                           \[
670                                 ('.$this->nested_brackets.')    # link text = $2
671                           \]
672                           \(                    # literal paren
673                                 [ ]*
674                                 (?:
675                                         <(\S*)> # href = $3
676                                 |
677                                         ('.$this->nested_url_parenthesis.')     # href = $4
678                                 )
679                                 [ ]*
680                                 (                       # $5
681                                   ([\'"])       # quote char = $6
682                                   (.*?)         # Title = $7
683                                   \6            # matching quote
684                                   [ ]*  # ignore any spaces/tabs between closing quote and )
685                                 )?                      # title is optional
686                           \)
687                         )
688                         }xs',
689                         array(&$this, '_DoAnchors_inline_callback'), $text);
691                 #
692                 # Last, handle reference-style shortcuts: [link text]
693                 # These must come last in case you've also got [link test][1]
694                 # or [link test](/foo)
695                 #
696 //              $text = preg_replace_callback('{
697 //                      (                                       # wrap whole match in $1
698 //                        \[
699 //                              ([^\[\]]+)              # link text = $2; can\'t contain [ or ]
700 //                        \]
701 //                      )
702 //                      }xs',
703 //                      array(&$this, '_doAnchors_reference_callback'), $text);
705                 $this->in_anchor = false;
706                 return $text;
707         }
708         function _doAnchors_reference_callback($matches) {
709                 $whole_match =  $matches[1];
710                 $link_text   =  $matches[2];
711                 $link_id     =& $matches[3];
713                 if ($link_id == "") {
714                         # for shortcut links like [this][] or [this].
715                         $link_id = $link_text;
716                 }
717                 
718                 # lower-case and turn embedded newlines into spaces
719                 $link_id = strtolower($link_id);
720                 $link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
722                 if (isset($this->urls[$link_id])) {
723                         $url = $this->urls[$link_id];
724                         $url = $this->encodeAmpsAndAngles($url);
725                         
726                         $result = "<a href=\"$url\"";
727                         if ( isset( $this->titles[$link_id] ) ) {
728                                 $title = $this->titles[$link_id];
729                                 $title = $this->encodeAmpsAndAngles($title);
730                                 $result .=  " title=\"$title\"";
731                         }
732                 
733                         $link_text = $this->runSpanGamut($link_text);
734                         $result .= ">$link_text</a>";
735                         $result = $this->hashPart($result);
736                 }
737                 else {
738                         $result = $whole_match;
739                 }
740                 return $result;
741         }
742         function _doAnchors_inline_callback($matches) {
743                 $whole_match    =  $matches[1];
744                 $link_text              =  $this->runSpanGamut($matches[2]);
745                 $url                    =  $matches[3] == '' ? $matches[4] : $matches[3];
746                 $title                  =& $matches[7];
748                 $url = $this->encodeAmpsAndAngles($url);
750                 $result = "<a href=\"$url\"";
751                 if (isset($title)) {
752                         $title = str_replace('"', '&quot;', $title);
753                         $title = $this->encodeAmpsAndAngles($title);
754                         $result .=  " title=\"$title\"";
755                 }
756                 
757                 $link_text = $this->runSpanGamut($link_text);
758                 $result .= ">$link_text</a>";
760                 return $this->hashPart($result);
761         }
764         function doImages($text) {
765         #
766         # Turn Markdown image shortcuts into <img> tags.
767         #
768                 #
769                 # First, handle reference-style labeled images: ![alt text][id]
770                 #
771                 $text = preg_replace_callback('{
772                         (                               # wrap whole match in $1
773                           !\[
774                                 ('.$this->nested_brackets.')            # alt text = $2
775                           \]
777                           [ ]?                          # one optional space
778                           (?:\n[ ]*)?           # one optional newline followed by spaces
780                           \[
781                                 (.*?)           # id = $3
782                           \]
784                         )
785                         }xs', 
786                         array(&$this, '_doImages_reference_callback'), $text);
788                 #
789                 # Next, handle inline images:  ![alt text](url "optional title")
790                 # Don't forget: encode * and _
791                 #
792                 $text = preg_replace_callback('{
793                         (                               # wrap whole match in $1
794                           !\[
795                                 ('.$this->nested_brackets.')            # alt text = $2
796                           \]
797                           \s?                   # One optional whitespace character
798                           \(                    # literal paren
799                                 [ ]*
800                                 (?:
801                                         <(\S*)> # src url = $3
802                                 |
803                                         ('.$this->nested_url_parenthesis.')     # src url = $4
804                                 )
805                                 [ ]*
806                                 (                       # $5
807                                   ([\'"])       # quote char = $6
808                                   (.*?)         # title = $7
809                                   \6            # matching quote
810                                   [ ]*
811                                 )?                      # title is optional
812                           \)
813                         )
814                         }xs',
815                         array(&$this, '_doImages_inline_callback'), $text);
817                 return $text;
818         }
819         function _doImages_reference_callback($matches) {
820                 $whole_match = $matches[1];
821                 $alt_text    = $matches[2];
822                 $link_id     = strtolower($matches[3]);
824                 if ($link_id == "") {
825                         $link_id = strtolower($alt_text); # for shortcut links like ![this][].
826                 }
828                 $alt_text = str_replace('"', '&quot;', $alt_text);
829                 if (isset($this->urls[$link_id])) {
830                         $url = $this->urls[$link_id];
831                         $result = "<img src=\"$url\" alt=\"$alt_text\"";
832                         if (isset($this->titles[$link_id])) {
833                                 $title = $this->titles[$link_id];
834                                 $result .=  " title=\"$title\"";
835                         }
836                         $result .= $this->empty_element_suffix;
837                         $result = $this->hashPart($result);
838                 }
839                 else {
840                         # If there's no such link ID, leave intact:
841                         $result = $whole_match;
842                 }
844                 return $result;
845         }
846         function _doImages_inline_callback($matches) {
847                 $whole_match    = $matches[1];
848                 $alt_text               = $matches[2];
849                 $url                    = $matches[3] == '' ? $matches[4] : $matches[3];
850                 $title                  =& $matches[7];
852                 $alt_text = str_replace('"', '&quot;', $alt_text);
853                 $result = "<img src=\"$url\" alt=\"$alt_text\"";
854                 if (isset($title)) {
855                         $title = str_replace('"', '&quot;', $title);
856                         $result .=  " title=\"$title\""; # $title already quoted
857                 }
858                 $result .= $this->empty_element_suffix;
860                 return $this->hashPart($result);
861         }
864         function doHeaders($text) {
865                 # Setext-style headers:
866                 #         Header 1
867                 #         ========
868                 #  
869                 #         Header 2
870                 #         --------
871                 #
872                 $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
873                         array(&$this, '_doHeaders_callback_setext'), $text);
875                 # atx-style headers:
876                 #       # Header 1
877                 #       ## Header 2
878                 #       ## Header 2 with closing hashes ##
879                 #       ...
880                 #       ###### Header 6
881                 #
882                 $text = preg_replace_callback('{
883                                 ^(\#{1,6})      # $1 = string of #\'s
884                                 [ ]*
885                                 (.+?)           # $2 = Header text
886                                 [ ]*
887                                 \#*                     # optional closing #\'s (not counted)
888                                 \n+
889                         }xm',
890                         array(&$this, '_doHeaders_callback_atx'), $text);
892                 return $text;
893         }
894         function _doHeaders_callback_setext($matches) {
895                 $level = $matches[2]{0} == '=' ? 1 : 2;
896                 $block = "<h$level>".$this->runSpanGamut($matches[1])."</h$level>";
897                 return "\n" . $this->hashBlock($block) . "\n\n";
898         }
899         function _doHeaders_callback_atx($matches) {
900                 $level = strlen($matches[1]);
901                 $block = "<h$level>".$this->runSpanGamut($matches[2])."</h$level>";
902                 return "\n" . $this->hashBlock($block) . "\n\n";
903         }
906         function doLists($text) {
907         #
908         # Form HTML ordered (numbered) and unordered (bulleted) lists.
909         #
910                 $less_than_tab = $this->tab_width - 1;
912                 # Re-usable patterns to match list item bullets and number markers:
913                 $marker_ul  = '[*+-]';
914                 $marker_ol  = '\d+[.]';
915                 $marker_any = "(?:$marker_ul|$marker_ol)";
917                 $markers = array($marker_ul, $marker_ol);
919                 foreach ($markers as $marker) {
920                         # Re-usable pattern to match any entirel ul or ol list:
921                         $whole_list = '
922                                 (                                                               # $1 = whole list
923                                   (                                                             # $2
924                                         [ ]{0,'.$less_than_tab.'}
925                                         ('.$marker.')                           # $3 = first list item marker
926                                         [ ]+
927                                   )
928                                   (?s:.+?)
929                                   (                                                             # $4
930                                           \z
931                                         |
932                                           \n{2,}
933                                           (?=\S)
934                                           (?!                                           # Negative lookahead for another list item marker
935                                                 [ ]*
936                                                 '.$marker.'[ ]+
937                                           )
938                                   )
939                                 )
940                         '; // mx
941                         
942                         # We use a different prefix before nested lists than top-level lists.
943                         # See extended comment in _ProcessListItems().
944                 
945                         if ($this->list_level) {
946                                 $text = preg_replace_callback('{
947                                                 ^
948                                                 '.$whole_list.'
949                                         }mx',
950                                         array(&$this, '_doLists_callback'), $text);
951                         }
952                         else {
953                                 $text = preg_replace_callback('{
954                                                 (?:(?<=\n)\n|\A\n?) # Must eat the newline
955                                                 '.$whole_list.'
956                                         }mx',
957                                         array(&$this, '_doLists_callback'), $text);
958                         }
959                 }
961                 return $text;
962         }
963         function _doLists_callback($matches) {
964                 # Re-usable patterns to match list item bullets and number markers:
965                 $marker_ul  = '[*+-]';
966                 $marker_ol  = '\d+[.]';
967                 $marker_any = "(?:$marker_ul|$marker_ol)";
968                 
969                 $list = $matches[1];
970                 $list_type = preg_match("/$marker_ul/", $matches[3]) ? "ul" : "ol";
971                 
972                 $marker_any = ( $list_type == "ul" ? $marker_ul : $marker_ol );
973                 
974                 $list .= "\n";
975                 $result = $this->processListItems($list, $marker_any);
976                 
977                 $result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>");
978                 return "\n". $result ."\n\n";
979         }
981         var $list_level = 0;
983         function processListItems($list_str, $marker_any) {
984         #
985         #       Process the contents of a single ordered or unordered list, splitting it
986         #       into individual list items.
987         #
988                 # The $this->list_level global keeps track of when we're inside a list.
989                 # Each time we enter a list, we increment it; when we leave a list,
990                 # we decrement. If it's zero, we're not in a list anymore.
991                 #
992                 # We do this because when we're not inside a list, we want to treat
993                 # something like this:
994                 #
995                 #               I recommend upgrading to version
996                 #               8. Oops, now this line is treated
997                 #               as a sub-list.
998                 #
999                 # As a single paragraph, despite the fact that the second line starts
1000                 # with a digit-period-space sequence.
1001                 #
1002                 # Whereas when we're inside a list (or sub-list), that line will be
1003                 # treated as the start of a sub-list. What a kludge, huh? This is
1004                 # an aspect of Markdown's syntax that's hard to parse perfectly
1005                 # without resorting to mind-reading. Perhaps the solution is to
1006                 # change the syntax rules such that sub-lists must start with a
1007                 # starting cardinal number; e.g. "1." or "a.".
1008                 
1009                 $this->list_level++;
1011                 # trim trailing blank lines:
1012                 $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
1014                 $list_str = preg_replace_callback('{
1015                         (\n)?                                                   # leading line = $1
1016                         (^[ ]*)                                         # leading whitespace = $2
1017                         ('.$marker_any.') [ ]+          # list marker = $3
1018                         ((?s:.+?))                                              # list item text   = $4
1019                         (?:(\n+(?=\n))|\n)                              # tailing blank line = $5
1020                         (?= \n* (\z | \2 ('.$marker_any.') [ ]+))
1021                         }xm',
1022                         array(&$this, '_processListItems_callback'), $list_str);
1024                 $this->list_level--;
1025                 return $list_str;
1026         }
1027         function _processListItems_callback($matches) {
1028                 $item = $matches[4];
1029                 $leading_line =& $matches[1];
1030                 $leading_space =& $matches[2];
1031                 $tailing_blank_line =& $matches[5];
1033                 if ($leading_line || $tailing_blank_line || 
1034                         preg_match('/\n{2,}/', $item))
1035                 {
1036                         $item = $this->runBlockGamut($this->outdent($item)."\n");
1037                 }
1038                 else {
1039                         # Recursion for sub-lists:
1040                         $item = $this->doLists($this->outdent($item));
1041                         $item = preg_replace('/\n+$/', '', $item);
1042                         $item = $this->runSpanGamut($item);
1043                 }
1045                 return "<li>" . $item . "</li>\n";
1046         }
1049         function doCodeBlocks($text) {
1050         #
1051         #       Process Markdown `<pre><code>` blocks.
1052         #
1053                 $text = preg_replace_callback('{
1054                                 (?:\n\n|\A)
1055                                 (                   # $1 = the code block -- one or more lines, starting with a space/tab
1056                                   (?>
1057                                         [ ]{'.$this->tab_width.'}  # Lines must start with a tab or a tab-width of spaces
1058                                         .*\n+
1059                                   )+
1060                                 )
1061                                 ((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
1062                         }xm',
1063                         array(&$this, '_doCodeBlocks_callback'), $text);
1065                 return $text;
1066         }
1067         function _doCodeBlocks_callback($matches) {
1068                 $codeblock = $matches[1];
1070                 $codeblock = $this->outdent($codeblock);
1071                 $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
1073                 # trim leading newlines and trailing newlines
1074                 $codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
1076                 $codeblock = "<pre><code>$codeblock\n</code></pre>";
1077                 return "\n\n".$this->hashBlock($codeblock)."\n\n";
1078         }
1081         function makeCodeSpan($code) {
1082         #
1083         # Create a code span markup for $code. Called from handleSpanToken.
1084         #
1085                 $code = htmlspecialchars(trim($code), ENT_NOQUOTES);
1086                 return $this->hashPart("<code>$code</code>");
1087         }
1090         function doItalicsAndBold($text) {
1091                 # <strong> must go first:
1092                 $text = preg_replace_callback('{
1093                                 (                                               # $1: Marker
1094                                         (?<!\*\*) \* |          #     (not preceded by two chars of
1095                                         (?<!__)   _                     #      the same marker)
1096                                 )
1097                                 \1
1098                                 (?=\S)                                  # Not followed by whitespace 
1099                                 (?!\1\1)                                #   or two others marker chars.
1100                                 (                                               # $2: Content
1101                                         (?>
1102                                                 [^*_]+?                 # Anthing not em markers.
1103                                         |
1104                                                                                 # Balence any regular emphasis inside.
1105                                                 \1 (?=\S) .+? (?<=\S) \1
1106                                         |
1107                                                 .                               # Allow unbalenced * and _.
1108                                         )+?
1109                                 )
1110                                 (?<=\S) \1\1                    # End mark not preceded by whitespace.
1111                         }sx',
1112                         array(&$this, '_doItalicAndBold_strong_callback'), $text);
1113                 # Then <em>:
1114                 $text = preg_replace_callback(
1115                         '{ ( (?<!\*)\* | (?<!_)_ ) (?=\S) (?! \1) (.+?) (?<=\S)(?<!\s(?=\1).) \1 }sx',
1116                         array(&$this, '_doItalicAndBold_em_callback'), $text);
1118                 return $text;
1119         }
1120         function _doItalicAndBold_em_callback($matches) {
1121                 $text = $matches[2];
1122                 $text = $this->runSpanGamut($text);
1123                 return $this->hashPart("<em>$text</em>");
1124         }
1125         function _doItalicAndBold_strong_callback($matches) {
1126                 $text = $matches[2];
1127                 $text = $this->runSpanGamut($text);
1128                 return $this->hashPart("<strong>$text</strong>");
1129         }
1132         function doBlockQuotes($text) {
1133                 $text = preg_replace_callback('/
1134                           (                                                             # Wrap whole match in $1
1135                                 (?>
1136                                   ^[ ]*>[ ]?                    # ">" at the start of a line
1137                                         .+\n                                    # rest of the first line
1138                                   (.+\n)*                                       # subsequent consecutive lines
1139                                   \n*                                           # blanks
1140                                 )+
1141                           )
1142                         /xm',
1143                         array(&$this, '_doBlockQuotes_callback'), $text);
1145                 return $text;
1146         }
1147         function _doBlockQuotes_callback($matches) {
1148                 $bq = $matches[1];
1149                 # trim one level of quoting - trim whitespace-only lines
1150                 $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
1151                 $bq = $this->runBlockGamut($bq);                # recurse
1153                 $bq = preg_replace('/^/m', "  ", $bq);
1154                 # These leading spaces cause problem with <pre> content, 
1155                 # so we need to fix that:
1156                 $bq = preg_replace_callback('{(\s*<pre>.+?</pre>)}sx', 
1157                         array(&$this, '_DoBlockQuotes_callback2'), $bq);
1159                 return "\n". $this->hashBlock("<blockquote>\n$bq\n</blockquote>")."\n\n";
1160         }
1161         function _doBlockQuotes_callback2($matches) {
1162                 $pre = $matches[1];
1163                 $pre = preg_replace('/^  /m', '', $pre);
1164                 return $pre;
1165         }
1168         function formParagraphs($text) {
1169         #
1170         #       Params:
1171         #               $text - string to process with html <p> tags
1172         #
1173                 # Strip leading and trailing lines:
1174                 $text = preg_replace('/\A\n+|\n+\z/', '', $text);
1176                 $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
1178                 #
1179                 # Wrap <p> tags and unhashify HTML blocks
1180                 #
1181                 foreach ($grafs as $key => $value) {
1182                         if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
1183                                 # Is a paragraph.
1184                                 $value = $this->runSpanGamut($value);
1185                                 $value = preg_replace('/^([ ]*)/', "<p>", $value);
1186                                 $value .= "</p>";
1187                                 $grafs[$key] = $this->unhash($value);
1188                         }
1189                         else {
1190                                 # Is a block.
1191                                 # Modify elements of @grafs in-place...
1192                                 $graf = $value;
1193                                 $block = $this->html_hashes[$graf];
1194                                 $graf = $block;
1195 //                              if (preg_match('{
1196 //                                      \A
1197 //                                      (                                                       # $1 = <div> tag
1198 //                                        <div  \s+
1199 //                                        [^>]*
1200 //                                        \b
1201 //                                        markdown\s*=\s*  ([\'"])      #       $2 = attr quote char
1202 //                                        1
1203 //                                        \2
1204 //                                        [^>]*
1205 //                                        >
1206 //                                      )
1207 //                                      (                                                       # $3 = contents
1208 //                                      .*
1209 //                                      )
1210 //                                      (</div>)                                        # $4 = closing tag
1211 //                                      \z
1212 //                                      }xs', $block, $matches))
1213 //                              {
1214 //                                      list(, $div_open, , $div_content, $div_close) = $matches;
1215 //
1216 //                                      # We can't call Markdown(), because that resets the hash;
1217 //                                      # that initialization code should be pulled into its own sub, though.
1218 //                                      $div_content = $this->hashHTMLBlocks($div_content);
1219 //                                      
1220 //                                      # Run document gamut methods on the content.
1221 //                                      foreach ($this->document_gamut as $method => $priority) {
1222 //                                              $div_content = $this->$method($div_content);
1223 //                                      }
1224 //
1225 //                                      $div_open = preg_replace(
1226 //                                              '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
1227 //
1228 //                                      $graf = $div_open . "\n" . $div_content . "\n" . $div_close;
1229 //                              }
1230                                 $grafs[$key] = $graf;
1231                         }
1232                 }
1234                 return implode("\n\n", $grafs);
1235         }
1238         function encodeAmpsAndAngles($text) {
1239         # Smart processing for ampersands and angle brackets that need to be encoded.
1240                 if ($this->no_entities) {
1241                         $text = str_replace('&', '&amp;', $text);
1242                         $text = str_replace('<', '&lt;', $text);
1243                         return $text;
1244                 }
1246                 # Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
1247                 #   http://bumppo.net/projects/amputator/
1248                 $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', 
1249                                                          '&amp;', $text);;
1251                 # Encode naked <'s
1252                 $text = preg_replace('{<(?![a-z/?\$!%])}i', '&lt;', $text);
1254                 return $text;
1255         }
1258         function doAutoLinks($text) {
1259                 $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}', 
1260                         array(&$this, '_doAutoLinks_url_callback'), $text);
1262                 # Email addresses: <address@domain.foo>
1263                 $text = preg_replace_callback('{
1264                         <
1265                         (?:mailto:)?
1266                         (
1267                                 [-.\w\x80-\xFF]+
1268                                 \@
1269                                 [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
1270                         )
1271                         >
1272                         }xi',
1273                         array(&$this, '_doAutoLinks_email_callback'), $text);
1275                 return $text;
1276         }
1277         function _doAutoLinks_url_callback($matches) {
1278                 $url = $this->encodeAmpsAndAngles($matches[1]);
1279                 $link = "<a href=\"$url\">$url</a>";
1280                 return $this->hashPart($link);
1281         }
1282         function _doAutoLinks_email_callback($matches) {
1283                 $address = $matches[1];
1284                 $link = $this->encodeEmailAddress($address);
1285                 return $this->hashPart($link);
1286         }
1289         function encodeEmailAddress($addr) {
1290         #
1291         #       Input: an email address, e.g. "foo@example.com"
1292         #
1293         #       Output: the email address as a mailto link, with each character
1294         #               of the address encoded as either a decimal or hex entity, in
1295         #               the hopes of foiling most address harvesting spam bots. E.g.:
1296         #
1297         #         <p><a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x66;o&#111;
1298         #        &#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;&#101;&#46;&#x63;&#111;
1299         #        &#x6d;">&#x66;o&#111;&#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;
1300         #        &#101;&#46;&#x63;&#111;&#x6d;</a></p>
1301         #
1302         #       Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
1303         #   With some optimizations by Milian Wolff.
1304         #
1305                 $addr = "mailto:" . $addr;
1306                 $chars = preg_split('/(?<!^)(?!$)/', $addr);
1307                 $seed = (int)abs(crc32($addr) / strlen($addr)); # Deterministic seed.
1308                 
1309                 foreach ($chars as $key => $char) {
1310                         $ord = ord($char);
1311                         # Ignore non-ascii chars.
1312                         if ($ord < 128) {
1313                                 $r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
1314                                 # roughly 10% raw, 45% hex, 45% dec
1315                                 # '@' *must* be encoded. I insist.
1316                                 if ($r > 90 && $char != '@') /* do nothing */;
1317                                 else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';';
1318                                 else              $chars[$key] = '&#'.$ord.';';
1319                         }
1320                 }
1321                 
1322                 $addr = implode('', $chars);
1323                 $text = implode('', array_slice($chars, 7)); # text without `mailto:`
1324                 $addr = "<a href=\"$addr\">$text</a>";
1326                 return $addr;
1327         }
1330         function parseSpan($str) {
1331         #
1332         # Take the string $str and parse it into tokens, hashing embeded HTML,
1333         # escaped characters and handling code spans.
1334         #
1335                 $output = '';
1336                 
1337                 $regex = '{
1338                                 (
1339                                         \\\\['.preg_quote($this->escape_chars).']
1340                                 |
1341                                         (?<![`\\\\])
1342                                         `+                                              # code span marker
1343                         '.( $this->no_markup ? '' : '
1344                                 |
1345                                         <!--    .*?     -->             # comment
1346                                 |
1347                                         <\?.*?\?> | <%.*?%>             # processing instruction
1348                                 |
1349                                         <[/!$]?[-a-zA-Z0-9:]+   # regular tags
1350                                         (?>
1351                                                 \s
1352                                                 (?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
1353                                         )?
1354                                         >
1355                         ').'
1356                                 )
1357                                 }xs';
1359                 while (1) {
1360                         #
1361                         # Each loop iteration seach for either the next tag, the next 
1362                         # openning code span marker, or the next escaped character. 
1363                         # Each token is then passed to handleSpanToken.
1364                         #
1365                         $parts = preg_split($regex, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
1366                         
1367                         # Create token from text preceding tag.
1368                         if ($parts[0] != "") {
1369                                 $output .= $parts[0];
1370                         }
1371                         
1372                         # Check if we reach the end.
1373                         if (isset($parts[1])) {
1374                                 $output .= $this->handleSpanToken($parts[1], $parts[2]);
1375                                 $str = $parts[2];
1376                         }
1377                         else {
1378                                 break;
1379                         }
1380                 }
1381                 
1382                 return $output;
1383         }
1384         
1385         
1386         function handleSpanToken($token, &$str) {
1387         #
1388         # Handle $token provided by parseSpan by determining its nature and 
1389         # returning the corresponding value that should replace it.
1390         #
1391                 switch ($token{0}) {
1392                         case "\\":
1393                                 return $this->hashPart("&#". ord($token{1}). ";");
1394                         case "`":
1395                                 # Search for end marker in remaining text.
1396                                 if (preg_match('/^(.*?[^`])'.$token.'(?!`)(.*)$/sm', 
1397                                         $str, $matches))
1398                                 {
1399                                         $str = $matches[2];
1400                                         $codespan = $this->makeCodeSpan($matches[1]);
1401                                         return $this->hashPart($codespan);
1402                                 }
1403                                 return $token; // return as text since no ending marker found.
1404                         default:
1405                                 return $this->hashPart($token);
1406                 }
1407         }
1410         function outdent($text) {
1411         #
1412         # Remove one level of line-leading tabs or spaces
1413         #
1414                 return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text);
1415         }
1418         # String length function for detab. `_initDetab` will create a function to 
1419         # hanlde UTF-8 if the default function does not exist.
1420         var $utf8_strlen = 'mb_strlen';
1421         
1422         function detab($text) {
1423         #
1424         # Replace tabs with the appropriate amount of space.
1425         #
1426                 # For each line we separate the line in blocks delemited by
1427                 # tab characters. Then we reconstruct every line by adding the 
1428                 # appropriate number of space between each blocks.
1429                 
1430                 $text = preg_replace_callback('/^.*\t.*$/m',
1431                         array(&$this, '_detab_callback'), $text);
1433                 return $text;
1434         }
1435         function _detab_callback($matches) {
1436                 $line = $matches[0];
1437                 $strlen = $this->utf8_strlen; # strlen function for UTF-8.
1438                 
1439                 # Split in blocks.
1440                 $blocks = explode("\t", $line);
1441                 # Add each blocks to the line.
1442                 $line = $blocks[0];
1443                 unset($blocks[0]); # Do not add first block twice.
1444                 foreach ($blocks as $block) {
1445                         # Calculate amount of space, insert spaces, insert block.
1446                         $amount = $this->tab_width - 
1447                                 $strlen($line, 'UTF-8') % $this->tab_width;
1448                         $line .= str_repeat(" ", $amount) . $block;
1449                 }
1450                 return $line;
1451         }
1452         function _initDetab() {
1453         #
1454         # Check for the availability of the function in the `utf8_strlen` property
1455         # (initially `mb_strlen`). If the function is not available, create a 
1456         # function that will loosely count the number of UTF-8 characters with a
1457         # regular expression.
1458         #
1459                 if (function_exists($this->utf8_strlen)) return;
1460                 $this->utf8_strlen = create_function('$text', 'return preg_match_all(
1461                         "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/", 
1462                         $text, $m);');
1463         }
1466         function unhash($text) {
1467         #
1468         # Swap back in all the tags hashed by _HashHTMLBlocks.
1469         #
1470                 return preg_replace_callback('/(.)\x1A[0-9]+\1/', 
1471                         array(&$this, '_unhash_callback'), $text);
1472         }
1473         function _unhash_callback($matches) {
1474                 return $this->html_hashes[$matches[0]];
1475         }
1480 /**
1481  * Markdown Extra Parser Class
1482  *
1483  * @package   moodlecore
1484  * @copyright (c) 2004-2006 John Gruber
1485  */
1486 class MarkdownExtra_Parser extends Markdown_Parser {
1488         # Prefix for footnote ids.
1489         var $fn_id_prefix = "";
1490         
1491         # Optional title attribute for footnote links and backlinks.
1492         var $fn_link_title = MARKDOWN_FN_LINK_TITLE;
1493         var $fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE;
1494         
1495         # Optional class attribute for footnote links and backlinks.
1496         var $fn_link_class = MARKDOWN_FN_LINK_CLASS;
1497         var $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS;
1500         function MarkdownExtra_Parser() {
1501         #
1502         # Constructor function. Initialize the parser object.
1503         #
1504                 # Add extra escapable characters before parent constructor 
1505                 # initialize the table.
1506                 $this->escape_chars .= ':|';
1507                 
1508                 # Insert extra document, block, and span transformations. 
1509                 # Parent constructor will do the sorting.
1510                 $this->document_gamut += array(
1511                         "stripFootnotes"     => 15,
1512                         "stripAbbreviations" => 25,
1513                         "appendFootnotes"    => 50,
1514                         );
1515                 $this->block_gamut += array(
1516                         "doTables"           => 15,
1517                         "doDefLists"         => 45,
1518                         );
1519                 $this->span_gamut += array(
1520                         "doFootnotes"        => 5,
1521                         "doAbbreviations"    => 70,
1522                         );
1523                 
1524                 parent::Markdown_Parser();
1525         }
1526         
1527         
1528         # Extra hashes used during extra transformations.
1529         var $footnotes = array();
1530         var $footnotes_ordered = array();
1531         var $abbr_desciptions = array();
1532         var $abbr_matches = array();
1533         
1534         # Status flag to avoid invalid nesting.
1535         var $in_footnote = false;
1536         
1537         
1538         function transform($text) {
1539         #
1540         # Added clear to the new $html_hashes, reordered `hashHTMLBlocks` before 
1541         # blank line stripping and added extra parameter to `runBlockGamut`.
1542         #
1543                 # Clear the global hashes. If we don't clear these, you get conflicts
1544                 # from other articles when generating a page which contains more than
1545                 # one article (e.g. an index page that shows the N most recent
1546                 # articles):
1547                 $this->footnotes = array();
1548                 $this->footnotes_ordered = array();
1549                 $this->abbr_desciptions = array();
1550                 $this->abbr_matches = array();
1552                 return parent::transform($text);
1553         }
1554         
1555         
1556         ### HTML Block Parser ###
1557         
1558         # Tags that are always treated as block tags:
1559         var $block_tags = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend';
1560         
1561         # Tags treated as block tags only if the opening tag is alone on it's line:
1562         var $context_block_tags = 'script|noscript|math|ins|del';
1563         
1564         # Tags where markdown="1" default to span mode:
1565         var $contain_span_tags = 'p|h[1-6]|li|dd|dt|td|th|legend|address';
1566         
1567         # Tags which must not have their contents modified, no matter where 
1568         # they appear:
1569         var $clean_tags = 'script|math';
1570         
1571         # Tags that do not need to be closed.
1572         var $auto_close_tags = 'hr|img';
1573         
1575         function hashHTMLBlocks($text) {
1576         #
1577         # Hashify HTML Blocks and "clean tags".
1578         #
1579         # We only want to do this for block-level HTML tags, such as headers,
1580         # lists, and tables. That's because we still want to wrap <p>s around
1581         # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
1582         # phrase emphasis, and spans. The list of tags we're looking for is
1583         # hard-coded.
1584         #
1585         # This works by calling _HashHTMLBlocks_InMarkdown, which then calls
1586         # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1" 
1587         # attribute is found whitin a tag, _HashHTMLBlocks_InHTML calls back
1588         #  _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag.
1589         # These two functions are calling each other. It's recursive!
1590         #
1591                 #
1592                 # Call the HTML-in-Markdown hasher.
1593                 #
1594                 list($text, ) = $this->_hashHTMLBlocks_inMarkdown($text);
1595                 
1596                 return $text;
1597         }
1598         function _hashHTMLBlocks_inMarkdown($text, $indent = 0, 
1599                                                                                 $enclosing_tag = '', $span = false)
1600         {
1601         #
1602         # Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags.
1603         #
1604         # *   $indent is the number of space to be ignored when checking for code 
1605         #     blocks. This is important because if we don't take the indent into 
1606         #     account, something like this (which looks right) won't work as expected:
1607         #
1608         #     <div>
1609         #         <div markdown="1">
1610         #         Hello World.  <-- Is this a Markdown code block or text?
1611         #         </div>  <-- Is this a Markdown code block or a real tag?
1612         #     <div>
1613         #
1614         #     If you don't like this, just don't indent the tag on which
1615         #     you apply the markdown="1" attribute.
1616         #
1617         # *   If $enclosing_tag is not empty, stops at the first unmatched closing 
1618         #     tag with that name. Nested tags supported.
1619         #
1620         # *   If $span is true, text inside must treated as span. So any double 
1621         #     newline will be replaced by a single newline so that it does not create 
1622         #     paragraphs.
1623         #
1624         # Returns an array of that form: ( processed text , remaining text )
1625         #
1626                 if ($text === '') return array('', '');
1628                 # Regex to check for the presense of newlines around a block tag.
1629                 $newline_match_before = '/(?:^\n?|\n\n)*$/';
1630                 $newline_match_after = 
1631                         '{
1632                                 ^                                               # Start of text following the tag.
1633                                 (?:[ ]*<!--.*?-->)?             # Optional comment.
1634                                 [ ]*\n                                  # Must be followed by newline.
1635                         }xs';
1636                 
1637                 # Regex to match any tag.
1638                 $block_tag_match =
1639                         '{
1640                                 (                                       # $2: Capture hole tag.
1641                                         </?                                     # Any opening or closing tag.
1642                                                 (?:                             # Tag name.
1643                                                         '.$this->block_tags.'                   |
1644                                                         '.$this->context_block_tags.'   |
1645                                                         '.$this->clean_tags.'           |
1646                                                         (?!\s)'.$enclosing_tag.'
1647                                                 )
1648                                                 \s*                             # Whitespace.
1649                                                 (?>
1650                                                         ".*?"           |       # Double quotes (can contain `>`)
1651                                                         \'.*?\'         |       # Single quotes (can contain `>`)
1652                                                         .+?                             # Anything but quotes and `>`.
1653                                                 )*?
1654                                         >                                       # End of tag.
1655                                 |
1656                                         <!--    .*?     -->     # HTML Comment
1657                                 |
1658                                         <\?.*?\?> | <%.*?%>     # Processing instruction
1659                                 |
1660                                         <!\[CDATA\[.*?\]\]>     # CData Block
1661                                 )
1662                         }xs';
1664                 
1665                 $depth = 0;             # Current depth inside the tag tree.
1666                 $parsed = "";   # Parsed text that will be returned.
1668                 #
1669                 # Loop through every tag until we find the closing tag of the parent
1670                 # or loop until reaching the end of text if no parent tag specified.
1671                 #
1672                 do {
1673                         #
1674                         # Split the text using the first $tag_match pattern found.
1675                         # Text before  pattern will be first in the array, text after
1676                         # pattern will be at the end, and between will be any catches made 
1677                         # by the pattern.
1678                         #
1679                         $parts = preg_split($block_tag_match, $text, 2, 
1680                                                                 PREG_SPLIT_DELIM_CAPTURE);
1681                         
1682                         # If in Markdown span mode, add a empty-string span-level hash 
1683                         # after each newline to prevent triggering any block element.
1684                         if ($span) {
1685                                 $void = $this->hashPart("", ':');
1686                                 $newline = "$void\n";
1687                                 $parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void;
1688                         }
1689                         
1690                         $parsed .= $parts[0]; # Text before current tag.
1691                         
1692                         # If end of $text has been reached. Stop loop.
1693                         if (count($parts) < 3) {
1694                                 $text = "";
1695                                 break;
1696                         }
1697                         
1698                         $tag  = $parts[1]; # Tag to handle.
1699                         $text = $parts[2]; # Remaining text after current tag.
1700                         
1701                         #
1702                         # Check for: Tag inside code block or span
1703                         #
1704                         if (# Find current paragraph
1705                                 preg_match('/(?>^\n?|\n\n)((?>.+\n?)*?)$/', $parsed, $matches) &&
1706                                 (
1707                                 # Then match in it either a code block...
1708                                 preg_match('/^ {'.($indent+4).'}.*(?>\n {'.($indent+4).'}.*)*'.
1709                                                         '(?!\n)$/', $matches[1], $x) ||
1710                                 # ...or unbalenced code span markers. (the regex matches balenced)
1711                                 !preg_match('/^(?>[^`]+|(`+)(?>[^`]+|(?!\1[^`])`)*?\1(?!`))*$/s',
1712                                                          $matches[1])
1713                                 ))
1714                         {
1715                                 # Tag is in code block or span and may not be a tag at all. So we
1716                                 # simply skip the first char (should be a `<`).
1717                                 $parsed .= $tag{0};
1718                                 $text = substr($tag, 1) . $text; # Put back $tag minus first char.
1719                         }
1720                         #
1721                         # Check for: Opening Block level tag or
1722                         #            Opening Content Block tag (like ins and del) 
1723                         #               used as a block tag (tag is alone on it's line).
1724                         #
1725                         else if (preg_match("{^<(?:$this->block_tags)\b}", $tag) ||
1726                                 (       preg_match("{^<(?:$this->context_block_tags)\b}", $tag) &&
1727                                         preg_match($newline_match_before, $parsed) &&
1728                                         preg_match($newline_match_after, $text) )
1729                                 )
1730                         {
1731                                 # Need to parse tag and following text using the HTML parser.
1732                                 list($block_text, $text) = 
1733                                         $this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true);
1734                                 
1735                                 # Make sure it stays outside of any paragraph by adding newlines.
1736                                 $parsed .= "\n\n$block_text\n\n";
1737                         }
1738                         #
1739                         # Check for: Clean tag (like script, math)
1740                         #            HTML Comments, processing instructions.
1741                         #
1742                         else if (preg_match("{^<(?:$this->clean_tags)\b}", $tag) ||
1743                                 $tag{1} == '!' || $tag{1} == '?')
1744                         {
1745                                 # Need to parse tag and following text using the HTML parser.
1746                                 # (don't check for markdown attribute)
1747                                 list($block_text, $text) = 
1748                                         $this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false);
1749                                 
1750                                 $parsed .= $block_text;
1751                         }
1752                         #
1753                         # Check for: Tag with same name as enclosing tag.
1754                         #
1755                         else if ($enclosing_tag !== '' &&
1756                                 # Same name as enclosing tag.
1757                                 preg_match("{^</?(?:$enclosing_tag)\b}", $tag))
1758                         {
1759                                 #
1760                                 # Increase/decrease nested tag count.
1761                                 #
1762                                 if ($tag{1} == '/')                                             $depth--;
1763                                 else if ($tag{strlen($tag)-2} != '/')   $depth++;
1765                                 if ($depth < 0) {
1766                                         #
1767                                         # Going out of parent element. Clean up and break so we
1768                                         # return to the calling function.
1769                                         #
1770                                         $text = $tag . $text;
1771                                         break;
1772                                 }
1773                                 
1774                                 $parsed .= $tag;
1775                         }
1776                         else {
1777                                 $parsed .= $tag;
1778                         }
1779                 } while ($depth >= 0);
1780                 
1781                 return array($parsed, $text);
1782         }
1783         function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) {
1784         #
1785         # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags.
1786         #
1787         # *   Calls $hash_method to convert any blocks.
1788         # *   Stops when the first opening tag closes.
1789         # *   $md_attr indicate if the use of the `markdown="1"` attribute is allowed.
1790         #     (it is not inside clean tags)
1791         #
1792         # Returns an array of that form: ( processed text , remaining text )
1793         #
1794                 if ($text === '') return array('', '');
1795                 
1796                 # Regex to match `markdown` attribute inside of a tag.
1797                 $markdown_attr_match = '
1798                         {
1799                                 \s*                     # Eat whitespace before the `markdown` attribute
1800                                 markdown
1801                                 \s*=\s*
1802                                 (?:
1803                                         (["\'])         # $1: quote delimiter           
1804                                         (.*?)           # $2: attribute value
1805                                         \1                      # matching delimiter    
1806                                 |
1807                                         ([^\s>]*)       # $3: unquoted attribute value
1808                                 )
1809                                 ()                              # $4: make $3 always defined (avoid warnings)
1810                         }xs';
1811                 
1812                 # Regex to match any tag.
1813                 $tag_match = '{
1814                                 (                                       # $2: Capture hole tag.
1815                                         </?                                     # Any opening or closing tag.
1816                                                 [\w:$]+                 # Tag name.
1817                                                 \s*                             # Whitespace.
1818                                                 (?>
1819                                                         ".*?"           |       # Double quotes (can contain `>`)
1820                                                         \'.*?\'         |       # Single quotes (can contain `>`)
1821                                                         .+?                             # Anything but quotes and `>`.
1822                                                 )*?
1823                                         >                                       # End of tag.
1824                                 |
1825                                         <!--    .*?     -->     # HTML Comment
1826                                 |
1827                                         <\?.*?\?> | <%.*?%>     # Processing instruction
1828                                 |
1829                                         <!\[CDATA\[.*?\]\]>     # CData Block
1830                                 )
1831                         }xs';
1832                 
1833                 $original_text = $text;         # Save original text in case of faliure.
1834                 
1835                 $depth          = 0;    # Current depth inside the tag tree.
1836                 $block_text     = "";   # Temporary text holder for current text.
1837                 $parsed         = "";   # Parsed text that will be returned.
1839                 #
1840                 # Get the name of the starting tag.
1841                 #
1842                 if (preg_match("/^<([\w:$]*)\b/", $text, $matches))
1843                         $base_tag_name = $matches[1];
1845                 #
1846                 # Loop through every tag until we find the corresponding closing tag.
1847                 #
1848                 do {
1849                         #
1850                         # Split the text using the first $tag_match pattern found.
1851                         # Text before  pattern will be first in the array, text after
1852                         # pattern will be at the end, and between will be any catches made 
1853                         # by the pattern.
1854                         #
1855                         $parts = preg_split($tag_match, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
1856                         
1857                         if (count($parts) < 3) {
1858                                 #
1859                                 # End of $text reached with unbalenced tag(s).
1860                                 # In that case, we return original text unchanged and pass the
1861                                 # first character as filtered to prevent an infinite loop in the 
1862                                 # parent function.
1863                                 #
1864                                 return array($original_text{0}, substr($original_text, 1));
1865                         }
1866                         
1867                         $block_text .= $parts[0]; # Text before current tag.
1868                         $tag         = $parts[1]; # Tag to handle.
1869                         $text        = $parts[2]; # Remaining text after current tag.
1870                         
1871                         #
1872                         # Check for: Auto-close tag (like <hr/>)
1873                         #                        Comments and Processing Instructions.
1874                         #
1875                         if (preg_match("{^</?(?:$this->auto_close_tags)\b}", $tag) ||
1876                                 $tag{1} == '!' || $tag{1} == '?')
1877                         {
1878                                 # Just add the tag to the block as if it was text.
1879                                 $block_text .= $tag;
1880                         }
1881                         else {
1882                                 #
1883                                 # Increase/decrease nested tag count. Only do so if
1884                                 # the tag's name match base tag's.
1885                                 #
1886                                 if (preg_match("{^</?$base_tag_name\b}", $tag)) {
1887                                         if ($tag{1} == '/')                                             $depth--;
1888                                         else if ($tag{strlen($tag)-2} != '/')   $depth++;
1889                                 }
1890                                 
1891                                 #
1892                                 # Check for `markdown="1"` attribute and handle it.
1893                                 #
1894                                 if ($md_attr && 
1895                                         preg_match($markdown_attr_match, $tag, $attr_m) &&
1896                                         preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3]))
1897                                 {
1898                                         # Remove `markdown` attribute from opening tag.
1899                                         $tag = preg_replace($markdown_attr_match, '', $tag);
1900                                         
1901                                         # Check if text inside this tag must be parsed in span mode.
1902                                         $this->mode = $attr_m[2] . $attr_m[3];
1903                                         $span_mode = $this->mode == 'span' || $this->mode != 'block' &&
1904                                                 preg_match("{^<(?:$this->contain_span_tags)\b}", $tag);
1905                                         
1906                                         # Calculate indent before tag.
1907                                         preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches);
1908                                         $indent = strlen($matches[1]);
1909                                         
1910                                         # End preceding block with this tag.
1911                                         $block_text .= $tag;
1912                                         $parsed .= $this->$hash_method($block_text);
1913                                         
1914                                         # Get enclosing tag name for the ParseMarkdown function.
1915                                         preg_match('/^<([\w:$]*)\b/', $tag, $matches);
1916                                         $tag_name = $matches[1];
1917                                         
1918                                         # Parse the content using the HTML-in-Markdown parser.
1919                                         list ($block_text, $text)
1920                                                 = $this->_hashHTMLBlocks_inMarkdown($text, $indent, 
1921                                                                                                                 $tag_name, $span_mode);
1922                                         
1923                                         # Outdent markdown text.
1924                                         if ($indent > 0) {
1925                                                 $block_text = preg_replace("/^[ ]{1,$indent}/m", "", 
1926                                                                                                         $block_text);
1927                                         }
1928                                         
1929                                         # Append tag content to parsed text.
1930                                         if (!$span_mode)        $parsed .= "\n\n$block_text\n\n";
1931                                         else                            $parsed .= "$block_text";
1932                                         
1933                                         # Start over a new block.
1934                                         $block_text = "";
1935                                 }
1936                                 else $block_text .= $tag;
1937                         }
1938                         
1939                 } while ($depth > 0);
1940                 
1941                 #
1942                 # Hash last block text that wasn't processed inside the loop.
1943                 #
1944                 $parsed .= $this->$hash_method($block_text);
1945                 
1946                 return array($parsed, $text);
1947         }
1950         function hashClean($text) {
1951         #
1952         # Called whenever a tag must be hashed when a function insert a "clean" tag
1953         # in $text, it pass through this function and is automaticaly escaped, 
1954         # blocking invalid nested overlap.
1955         #
1956                 return $this->hashPart($text, 'C');
1957         }
1960         function doHeaders($text) {
1961         #
1962         # Redefined to add id attribute support.
1963         #
1964                 # Setext-style headers:
1965                 #         Header 1  {#header1}
1966                 #         ========
1967                 #  
1968                 #         Header 2  {#header2}
1969                 #         --------
1970                 #
1971                 $text = preg_replace_callback(
1972                         '{
1973                                 (^.+?)                                                          # $1: Header text
1974                                 (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})?        # $2: Id attribute
1975                                 [ ]*\n(=+|-+)[ ]*\n+                            # $3: Header footer
1976                         }mx',
1977                         array(&$this, '_doHeaders_callback_setext'), $text);
1979                 # atx-style headers:
1980                 #       # Header 1        {#header1}
1981                 #       ## Header 2       {#header2}
1982                 #       ## Header 2 with closing hashes ##  {#header3}
1983                 #       ...
1984                 #       ###### Header 6   {#header2}
1985                 #
1986                 $text = preg_replace_callback('{
1987                                 ^(\#{1,6})      # $1 = string of #\'s
1988                                 [ ]*
1989                                 (.+?)           # $2 = Header text
1990                                 [ ]*
1991                                 \#*                     # optional closing #\'s (not counted)
1992                                 (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # id attribute
1993                                 [ ]*
1994                                 \n+
1995                         }xm',
1996                         array(&$this, '_doHeaders_callback_atx'), $text);
1998                 return $text;
1999         }
2000         function _doHeaders_attr($attr) {
2001                 if (empty($attr))  return "";
2002                 return " id=\"$attr\"";
2003         }
2004         function _doHeaders_callback_setext($matches) {
2005                 $level = $matches[3]{0} == '=' ? 1 : 2;
2006                 $attr  = $this->_doHeaders_attr($id =& $matches[2]);
2007                 $block = "<h$level$attr>".$this->runSpanGamut($matches[1])."</h$level>";
2008                 return "\n" . $this->hashBlock($block) . "\n\n";
2009         }
2010         function _doHeaders_callback_atx($matches) {
2011                 $level = strlen($matches[1]);
2012                 $attr  = $this->_doHeaders_attr($id =& $matches[3]);
2013                 $block = "<h$level$attr>".$this->runSpanGamut($matches[2])."</h$level>";
2014                 return "\n" . $this->hashBlock($block) . "\n\n";
2015         }
2018         function doTables($text) {
2019         #
2020         # Form HTML tables.
2021         #
2022                 $less_than_tab = $this->tab_width - 1;
2023                 #
2024                 # Find tables with leading pipe.
2025                 #
2026                 #       | Header 1 | Header 2
2027                 #       | -------- | --------
2028                 #       | Cell 1   | Cell 2
2029                 #       | Cell 3   | Cell 4
2030                 #
2031                 $text = preg_replace_callback('
2032                         {
2033                                 ^                                                       # Start of a line
2034                                 [ ]{0,'.$less_than_tab.'}       # Allowed whitespace.
2035                                 [|]                                                     # Optional leading pipe (present)
2036                                 (.+) \n                                         # $1: Header row (at least one pipe)
2037                                 
2038                                 [ ]{0,'.$less_than_tab.'}       # Allowed whitespace.
2039                                 [|] ([ ]*[-:]+[-| :]*) \n       # $2: Header underline
2040                                 
2041                                 (                                                       # $3: Cells
2042                                         (?>
2043                                                 [ ]*                            # Allowed whitespace.
2044                                                 [|] .* \n                       # Row content.
2045                                         )*
2046                                 )
2047                                 (?=\n|\Z)                                       # Stop at final double newline.
2048                         }xm',
2049                         array(&$this, '_doTable_leadingPipe_callback'), $text);
2050                 
2051                 #
2052                 # Find tables without leading pipe.
2053                 #
2054                 #       Header 1 | Header 2
2055                 #       -------- | --------
2056                 #       Cell 1   | Cell 2
2057                 #       Cell 3   | Cell 4
2058                 #
2059                 $text = preg_replace_callback('
2060                         {
2061                                 ^                                                       # Start of a line
2062                                 [ ]{0,'.$less_than_tab.'}       # Allowed whitespace.
2063                                 (\S.*[|].*) \n                          # $1: Header row (at least one pipe)
2064                                 
2065                                 [ ]{0,'.$less_than_tab.'}       # Allowed whitespace.
2066                                 ([-:]+[ ]*[|][-| :]*) \n        # $2: Header underline
2067                                 
2068                                 (                                                       # $3: Cells
2069                                         (?>
2070                                                 .* [|] .* \n            # Row content
2071                                         )*
2072                                 )
2073                                 (?=\n|\Z)                                       # Stop at final double newline.
2074                         }xm',
2075                         array(&$this, '_DoTable_callback'), $text);
2077                 return $text;
2078         }
2079         function _doTable_leadingPipe_callback($matches) {
2080                 $head           = $matches[1];
2081                 $underline      = $matches[2];
2082                 $content        = $matches[3];
2083                 
2084                 # Remove leading pipe for each row.
2085                 $content        = preg_replace('/^ *[|]/m', '', $content);
2086                 
2087                 return $this->_doTable_callback(array($matches[0], $head, $underline, $content));
2088         }
2089         function _doTable_callback($matches) {
2090                 $head           = $matches[1];
2091                 $underline      = $matches[2];
2092                 $content        = $matches[3];
2094                 # Remove any tailing pipes for each line.
2095                 $head           = preg_replace('/[|] *$/m', '', $head);
2096                 $underline      = preg_replace('/[|] *$/m', '', $underline);
2097                 $content        = preg_replace('/[|] *$/m', '', $content);
2098                 
2099                 # Reading alignement from header underline.
2100                 $separators     = preg_split('/ *[|] */', $underline);
2101                 foreach ($separators as $n => $s) {
2102                         if (preg_match('/^ *-+: *$/', $s))              $attr[$n] = ' align="right"';
2103                         else if (preg_match('/^ *:-+: *$/', $s))$attr[$n] = ' align="center"';
2104                         else if (preg_match('/^ *:-+ *$/', $s)) $attr[$n] = ' align="left"';
2105                         else                                                                    $attr[$n] = '';
2106                 }
2107                 
2108                 # Parsing span elements, including code spans, character escapes, 
2109                 # and inline HTML tags, so that pipes inside those gets ignored.
2110                 $head           = $this->parseSpan($head);
2111                 $headers        = preg_split('/ *[|] */', $head);
2112                 $col_count      = count($headers);
2113                 
2114                 # Write column headers.
2115                 $text = "<table>\n";
2116                 $text .= "<thead>\n";
2117                 $text .= "<tr>\n";
2118                 foreach ($headers as $n => $header)
2119                         $text .= "  <th$attr[$n]>".$this->runSpanGamut(trim($header))."</th>\n";
2120                 $text .= "</tr>\n";
2121                 $text .= "</thead>\n";
2122                 
2123                 # Split content by row.
2124                 $rows = explode("\n", trim($content, "\n"));
2125                 
2126                 $text .= "<tbody>\n";
2127                 foreach ($rows as $row) {
2128                         # Parsing span elements, including code spans, character escapes, 
2129                         # and inline HTML tags, so that pipes inside those gets ignored.
2130                         $row = $this->parseSpan($row);
2131                         
2132                         # Split row by cell.
2133                         $row_cells = preg_split('/ *[|] */', $row, $col_count);
2134                         $row_cells = array_pad($row_cells, $col_count, '');
2135                         
2136                         $text .= "<tr>\n";
2137                         foreach ($row_cells as $n => $cell)
2138                                 $text .= "  <td$attr[$n]>".$this->runSpanGamut(trim($cell))."</td>\n";
2139                         $text .= "</tr>\n";
2140                 }
2141                 $text .= "</tbody>\n";
2142                 $text .= "</table>";
2143                 
2144                 return $this->hashBlock($text) . "\n";
2145         }
2147         
2148         function doDefLists($text) {
2149         #
2150         # Form HTML definition lists.
2151         #
2152                 $less_than_tab = $this->tab_width - 1;
2154                 # Re-usable pattern to match any entire dl list:
2155                 $whole_list = '(?>
2156                         (                                                               # $1 = whole list
2157                           (                                                             # $2
2158                                 [ ]{0,'.$less_than_tab.'}
2159                                 ((?>.*\S.*\n)+)                         # $3 = defined term
2160                                 \n?
2161                                 [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2162                           )
2163                           (?s:.+?)
2164                           (                                                             # $4
2165                                   \z
2166                                 |
2167                                   \n{2,}
2168                                   (?=\S)
2169                                   (?!                                           # Negative lookahead for another term
2170                                         [ ]{0,'.$less_than_tab.'}
2171                                         (?: \S.*\n )+?                  # defined term
2172                                         \n?
2173                                         [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2174                                   )
2175                                   (?!                                           # Negative lookahead for another definition
2176                                         [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2177                                   )
2178                           )
2179                         )
2180                 )'; // mx
2182                 $text = preg_replace_callback('{
2183                                 (?:(?<=\n\n)|\A\n?)
2184                                 '.$whole_list.'
2185                         }mx',
2186                         array(&$this, '_doDefLists_callback'), $text);
2188                 return $text;
2189         }
2190         function _doDefLists_callback($matches) {
2191                 # Re-usable patterns to match list item bullets and number markers:
2192                 $list = $matches[1];
2193                 
2194                 # Turn double returns into triple returns, so that we can make a
2195                 # paragraph for the last item in a list, if necessary:
2196                 $result = trim($this->processDefListItems($list));
2197                 $result = "<dl>\n" . $result . "\n</dl>";
2198                 return $this->hashBlock($result) . "\n\n";
2199         }
2202         function processDefListItems($list_str) {
2203         #
2204         #       Process the contents of a single definition list, splitting it
2205         #       into individual term and definition list items.
2206         #
2207                 $less_than_tab = $this->tab_width - 1;
2208                 
2209                 # trim trailing blank lines:
2210                 $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
2212                 # Process definition terms.
2213                 $list_str = preg_replace_callback('{
2214                         (?:\n\n+|\A\n?)                                 # leading line
2215                         (                                                               # definition terms = $1
2216                                 [ ]{0,'.$less_than_tab.'}       # leading whitespace
2217                                 (?![:][ ]|[ ])                          # negative lookahead for a definition 
2218                                                                                         #   mark (colon) or more whitespace.
2219                                 (?: \S.* \n)+?                          # actual term (not whitespace). 
2220                         )                       
2221                         (?=\n?[ ]{0,3}:[ ])                             # lookahead for following line feed 
2222                                                                                         #   with a definition mark.
2223                         }xm',
2224                         array(&$this, '_processDefListItems_callback_dt'), $list_str);
2226                 # Process actual definitions.
2227                 $list_str = preg_replace_callback('{
2228                         \n(\n+)?                                                # leading line = $1
2229                         [ ]{0,'.$less_than_tab.'}               # whitespace before colon
2230                         [:][ ]+                                                 # definition mark (colon)
2231                         ((?s:.+?))                                              # definition text = $2
2232                         (?= \n+                                                 # stop at next definition mark,
2233                                 (?:                                                     # next term or end of text
2234                                         [ ]{0,'.$less_than_tab.'} [:][ ]        |
2235                                         <dt> | \z
2236                                 )                                               
2237                         )                                       
2238                         }xm',
2239                         array(&$this, '_processDefListItems_callback_dd'), $list_str);
2241                 return $list_str;
2242         }
2243         function _processDefListItems_callback_dt($matches) {
2244                 $terms = explode("\n", trim($matches[1]));
2245                 $text = '';
2246                 foreach ($terms as $term) {
2247                         $term = $this->runSpanGamut(trim($term));
2248                         $text .= "\n<dt>" . $term . "</dt>";
2249                 }
2250                 return $text . "\n";
2251         }
2252         function _processDefListItems_callback_dd($matches) {
2253                 $leading_line   = $matches[1];
2254                 $def                    = $matches[2];
2256                 if ($leading_line || preg_match('/\n{2,}/', $def)) {
2257                         $def = $this->runBlockGamut($this->outdent($def . "\n\n"));
2258                         $def = "\n". $def ."\n";
2259                 }
2260                 else {
2261                         $def = rtrim($def);
2262                         $def = $this->runSpanGamut($this->outdent($def));
2263                 }
2265                 return "\n<dd>" . $def . "</dd>\n";
2266         }
2269         function doItalicsAndBold($text) {
2270         #
2271         # Redefined to change emphasis by underscore behaviour so that it does not 
2272         # work in the middle of a word.
2273         #
2274                 # <strong> must go first:
2275                 $text = preg_replace_callback(array(
2276                         '{
2277                                 (                                               # $1: Marker
2278                                         (?<![a-zA-Z0-9])        # Not preceded by alphanum
2279                                         (?<!__)                         #       or by two marker chars.
2280                                         __
2281                                 )
2282                                 (?=\S)                                  # Not followed by whitespace 
2283                                 (?!__)                                  #   or two others marker chars.
2284                                 (                                               # $2: Content
2285                                         (?>
2286                                                 [^_]+?                  # Anthing not em markers.
2287                                         |
2288                                                                                 # Balence any regular _ emphasis inside.
2289                                                 (?<![a-zA-Z0-9]) _ (?=\S) (.+?) 
2290                                                 (?<=\S) _ (?![a-zA-Z0-9])
2291                                         |
2292                                                 _+                              # Allow unbalenced as last resort.
2293                                         )+?
2294                                 )
2295                                 (?<=\S) __                              # End mark not preceded by whitespace.
2296                                 (?![a-zA-Z0-9])                 # Not followed by alphanum
2297                                 (?!__)                                  #   or two others marker chars.
2298                         }sx',
2299                         '{
2300                                 ( (?<!\*\*) \*\* )              # $1: Marker (not preceded by two *)
2301                                 (?=\S)                                  # Not followed by whitespace 
2302                                 (?!\1)                                  #   or two others marker chars.
2303                                 (                                               # $2: Content
2304                                         (?>
2305                                                 [^*]+?                  # Anthing not em markers.
2306                                         |
2307                                                                                 # Balence any regular * emphasis inside.
2308                                                 \* (?=\S) (.+?) (?<=\S) \*
2309                                         |
2310                                                 \*                              # Allow unbalenced as last resort.
2311                                         )+?
2312                                 )
2313                                 (?<=\S) \*\*                    # End mark not preceded by whitespace.
2314                         }sx',
2315                         ),
2316                         array(&$this, '_doItalicAndBold_strong_callback'), $text);
2317                 # Then <em>:
2318                 $text = preg_replace_callback(array(
2319                         '{ ( (?<![a-zA-Z0-9])(?<!_)_ ) (?=\S) (?! \1) (.+?) (?<=\S) \1(?![a-zA-Z0-9]) }sx',
2320                         '{ ( (?<!\*)\* ) (?=\S) (?! \1) (.+?) (?<=\S)(?<!\s\*) \1 }sx',
2321                         ),
2322                         array(&$this, '_doItalicAndBold_em_callback'), $text);
2324                 return $text;
2325         }
2328         function formParagraphs($text) {
2329         #
2330         #       Params:
2331         #               $text - string to process with html <p> tags
2332         #
2333                 # Strip leading and trailing lines:
2334                 $text = preg_replace('/\A\n+|\n+\z/', '', $text);
2335                 
2336                 $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
2338                 #
2339                 # Wrap <p> tags and unhashify HTML blocks
2340                 #
2341                 foreach ($grafs as $key => $value) {
2342                         $value = trim($this->runSpanGamut($value));
2343                         
2344                         # Check if this should be enclosed in a paragraph.
2345                         # Clean tag hashes & block tag hashes are left alone.
2346                         $is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value);
2347                         
2348                         if ($is_p) {
2349                                 $value = "<p>$value</p>";
2350                         }
2351                         $grafs[$key] = $value;
2352                 }
2353                 
2354                 # Join grafs in one text, then unhash HTML tags. 
2355                 $text = implode("\n\n", $grafs);
2356                 
2357                 # Finish by removing any tag hashes still present in $text.
2358                 $text = $this->unhash($text);
2359                 
2360                 return $text;
2361         }
2362         
2363         
2364         ### Footnotes
2365         
2366         function stripFootnotes($text) {
2367         #
2368         # Strips link definitions from text, stores the URLs and titles in
2369         # hash references.
2370         #
2371                 $less_than_tab = $this->tab_width - 1;
2373                 # Link defs are in the form: [^id]: url "optional title"
2374                 $text = preg_replace_callback('{
2375                         ^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?:      # note_id = $1
2376                           [ ]*
2377                           \n?                                   # maybe *one* newline
2378                         (                                               # text = $2 (no blank lines allowed)
2379                                 (?:                                     
2380                                         .+                              # actual text
2381                                 |
2382                                         \n                              # newlines but 
2383                                         (?!\[\^.+?\]:\s)# negative lookahead for footnote marker.
2384                                         (?!\n+[ ]{0,3}\S)# ensure line is not blank and followed 
2385                                                                         # by non-indented content
2386                                 )*
2387                         )               
2388                         }xm',
2389                         array(&$this, '_stripFootnotes_callback'),
2390                         $text);
2391                 return $text;
2392         }
2393         function _stripFootnotes_callback($matches) {
2394                 $note_id = $this->fn_id_prefix . $matches[1];
2395                 $this->footnotes[$note_id] = $this->outdent($matches[2]);
2396                 return ''; # String that will replace the block
2397         }
2400         function doFootnotes($text) {
2401         #
2402         # Replace footnote references in $text [^id] with a special text-token 
2403         # which will be can be
2404         #
2405                 if (!$this->in_footnote && !$this->in_anchor) {
2406                         $text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text);
2407                 }
2408                 return $text;
2409         }
2411         
2412         function appendFootnotes($text) {
2413         #
2414         # Append footnote list to text.
2415         #
2416         
2417                 $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', 
2418                         array(&$this, '_appendFootnotes_callback'), $text);
2419         
2420                 if (!empty($this->footnotes_ordered)) {
2421                         $text .= "\n\n";
2422                         $text .= "<div class=\"footnotes\">\n";
2423                         $text .= "<hr". MARKDOWN_EMPTY_ELEMENT_SUFFIX ."\n";
2424                         $text .= "<ol>\n\n";
2425                         
2426                         $attr = " rev=\"footnote\"";
2427                         if ($this->fn_backlink_class != "") {
2428                                 $class = $this->fn_backlink_class;
2429                                 $class = $this->encodeAmpsAndAngles($class);
2430                                 $class = str_replace('"', '&quot;', $class);
2431                                 $attr .= " class=\"$class\"";
2432                         }
2433                         if ($this->fn_backlink_title != "") {
2434                                 $title = $this->fn_backlink_title;
2435                                 $title = $this->encodeAmpsAndAngles($title);
2436                                 $title = str_replace('"', '&quot;', $title);
2437                                 $attr .= " title=\"$title\"";
2438                         }
2439                         $num = 0;
2440                         
2441                         $this->in_footnote = true;
2442                         
2443                         foreach ($this->footnotes_ordered as $note_id => $footnote) {
2444                                 $footnote .= "\n"; # Need to append newline before parsing.
2445                                 $footnote = $this->runBlockGamut("$footnote\n");
2446                                 
2447                                 $attr2 = str_replace("%%", ++$num, $attr);
2448                                 
2449                                 # Add backlink to last paragraph; create new paragraph if needed.
2450                                 $backlink = "<a href=\"#fnref:$note_id\"$attr2>&#8617;</a>";
2451                                 if (preg_match('{</p>$}', $footnote)) {
2452                                         $footnote = substr($footnote, 0, -4) . "&#160;$backlink</p>";
2453                                 } else {
2454                                         $footnote .= "\n\n<p>$backlink</p>";
2455                                 }
2456                                 
2457                                 $text .= "<li id=\"fn:$note_id\">\n";
2458                                 $text .= $footnote . "\n";
2459                                 $text .= "</li>\n\n";
2460                         }
2461                         
2462                         $this->in_footnote = false;
2463                         
2464                         $text .= "</ol>\n";
2465                         $text .= "</div>";
2466                 }
2467                 return $text;
2468         }
2469         function _appendFootnotes_callback($matches) {
2470                 $node_id = $this->fn_id_prefix . $matches[1];
2471                 
2472                 # Create footnote marker only if it has a corresponding footnote *and*
2473                 # the footnote hasn't been used by another marker.
2474                 if (isset($this->footnotes[$node_id])) {
2475                         # Transfert footnote content to the ordered list.
2476                         $this->footnotes_ordered[$node_id] = $this->footnotes[$node_id];
2477                         unset($this->footnotes[$node_id]);
2478                         
2479                         $num = count($this->footnotes_ordered);
2480                         $attr = " rel=\"footnote\"";
2481                         if ($this->fn_link_class != "") {
2482                                 $class = $this->fn_link_class;
2483                                 $class = $this->encodeAmpsAndAngles($class);
2484                                 $class = str_replace('"', '&quot;', $class);
2485                                 $attr .= " class=\"$class\"";
2486                         }
2487                         if ($this->fn_link_title != "") {
2488                                 $title = $this->fn_link_title;
2489                                 $title = $this->encodeAmpsAndAngles($title);
2490                                 $title = str_replace('"', '&quot;', $title);
2491                                 $attr .= " title=\"$title\"";
2492                         }
2493                         $attr = str_replace("%%", $num, $attr);
2494                         
2495                         return
2496                                 "<sup id=\"fnref:$node_id\">".
2497                                 "<a href=\"#fn:$node_id\"$attr>$num</a>".
2498                                 "</sup>";
2499                 }
2500                 
2501                 return "[^".$matches[1]."]";
2502         }
2503                 
2504         
2505         ### Abbreviations ###
2506         
2507         function stripAbbreviations($text) {
2508         #
2509         # Strips abbreviations from text, stores titles in hash references.
2510         #
2511                 $less_than_tab = $this->tab_width - 1;
2513                 # Link defs are in the form: [id]*: url "optional title"
2514                 $text = preg_replace_callback('{
2515                         ^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?:      # abbr_id = $1
2516                         (.*)                                    # text = $2 (no blank lines allowed)    
2517                         }xm',
2518                         array(&$this, '_stripAbbreviations_callback'),
2519                         $text);
2520                 return $text;
2521         }
2522         function _stripAbbreviations_callback($matches) {
2523                 $abbr_word = $matches[1];
2524                 $abbr_desc = $matches[2];
2525                 $this->abbr_matches[] = preg_quote($abbr_word);
2526                 $this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
2527                 return ''; # String that will replace the block
2528         }
2529         
2530         
2531         function doAbbreviations($text) {
2532         #
2533         # Find defined abbreviations in text and wrap them in <abbr> elements.
2534         #
2535                 if ($this->abbr_matches) {
2536                         // cannot use the /x modifier because abbr_matches may 
2537                         // contain spaces:
2538                         $text = preg_replace_callback('{'.
2539                                 '(?<![\w\x1A])'.
2540                                 '(?:'. implode('|', $this->abbr_matches) .')'.
2541                                 '(?![\w\x1A])'.
2542                                 '}', 
2543                                 array(&$this, '_doAbbreviations_callback'), $text);
2544                 }
2545                 return $text;
2546         }
2547         function _doAbbreviations_callback($matches) {
2548                 $abbr = $matches[0];
2549                 if (isset($this->abbr_desciptions[$abbr])) {
2550                         $desc = $this->abbr_desciptions[$abbr];
2551                         if (empty($desc)) {
2552                                 return $this->hashPart("<abbr>$abbr</abbr>");
2553                         } else {
2554                                 $desc = htmlspecialchars($desc, ENT_NOQUOTES);
2555                                 return $this->hashPart("<abbr title=\"$desc\">$abbr</abbr>");
2556                         }
2557                 } else {
2558                         return $matches[0];
2559                 }
2560         }
2565 /**
2566  * PHP Markdown Extra
2567  * ==================
2568  *
2569  * Description
2570  * -----------
2571  *
2572  * This is a PHP port of the original Markdown formatter written in Perl
2573  * by John Gruber. This special "Extra" version of PHP Markdown features
2574  * further enhancements to the syntax for making additional constructs
2575  * such as tables and definition list.
2576  *
2577  * Markdown is a text-to-HTML filter; it translates an easy-to-read /
2578  * easy-to-write structured text format into HTML. Markdown's text format
2579  * is most similar to that of plain text email, and supports features such
2580  * as headers, *emphasis*, code blocks, blockquotes, and links.
2581  *
2582  * Markdown's syntax is designed not as a generic markup language, but
2583  * specifically to serve as a front-end to (X)HTML. You can use span-level
2584  * HTML tags anywhere in a Markdown document, and you can use block level
2585  * HTML tags (like <div> and <table> as well).
2586  *
2587  * For more information about Markdown's syntax, see:
2588  *
2589  * <http://daringfireball.net/projects/markdown/>
2590  *
2591  *
2592  * Bugs
2593  * ----
2594  *
2595  * To file bug reports please send email to:
2596  *
2597  * <michel.fortin@michelf.com>
2598  *
2599  * Please include with your report: (1) the example input; (2) the output you
2600  * expected; (3) the output Markdown actually produced.
2601  *
2602  *
2603  * Version History
2604  * ---------------
2605  *
2606  * See the readme file for detailed release notes for this version.
2607  *
2608  *
2609  * Copyright and License
2610  * ---------------------
2611  *
2612  * PHP Markdown & Extra
2613  * Copyright (c) 2004-2007 Michel Fortin
2614  * <http://www.michelf.com/>
2615  * All rights reserved.
2616  *
2617  * Based on Markdown
2618  * Copyright (c) 2003-2006 John Gruber
2619  * <http://daringfireball.net/>
2620  * All rights reserved.
2621  *
2622  * Redistribution and use in source and binary forms, with or without
2623  * modification, are permitted provided that the following conditions are
2624  * met:
2625  *
2626  *      - Redistributions of source code must retain the above copyright notice,
2627  *      this list of conditions and the following disclaimer.
2628  *
2629  *  - Redistributions in binary form must reproduce the above copyright
2630  *      notice, this list of conditions and the following disclaimer in the
2631  *      documentation and/or other materials provided with the distribution.
2632  *
2633  *  - Neither the name "Markdown" nor the names of its contributors may
2634  *      be used to endorse or promote products derived from this software
2635  *      without specific prior written permission.
2636  *
2637  * This software is provided by the copyright holders and contributors "as
2638  * is" and any express or implied warranties, including, but not limited
2639  * to, the implied warranties of merchantability and fitness for a
2640  * particular purpose are disclaimed. In no event shall the copyright owner
2641  * or contributors be liable for any direct, indirect, incidental, special,
2642  * exemplary, or consequential damages (including, but not limited to,
2643  * procurement of substitute goods or services; loss of use, data, or
2644  * profits; or business interruption) however caused and on any theory of
2645  * liability, whether in contract, strict liability, or tort (including
2646  * negligence or otherwise) arising in any way out of the use of this
2647  * software, even if advised of the possibility of such damage.
2648  */
2649 ?>