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