1<?php
2#
3# Markdown Extra  -  A text-to-HTML conversion tool for web writers
4#
5# PHP Markdown & Extra
6# Copyright (c) 2004-2008 Michel Fortin
7# <http://www.michelf.com/projects/php-markdown/>
8#
9# Original Markdown
10# Copyright (c) 2004-2006 John Gruber
11# <http://daringfireball.net/projects/markdown/>
12#
13
14
15define( 'MARKDOWN_VERSION',  "1.0.1m" ); # Sat 21 Jun 2008
16define( 'MARKDOWNEXTRA_VERSION',  "1.2.3" ); # Wed 31 Dec 2008
17
18
19#
20# Global default settings:
21#
22
23# Change to ">" for HTML output
24@define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX',  " />");
25
26# Define the width of a tab for code blocks.
27@define( 'MARKDOWN_TAB_WIDTH',     4 );
28
29# Optional title attribute for footnote links and backlinks.
30@define( 'MARKDOWN_FN_LINK_TITLE',         "" );
31@define( 'MARKDOWN_FN_BACKLINK_TITLE',     "" );
32
33# Optional class attribute for footnote links and backlinks.
34@define( 'MARKDOWN_FN_LINK_CLASS',         "" );
35@define( 'MARKDOWN_FN_BACKLINK_CLASS',     "" );
36
37
38#
39# WordPress settings:
40#
41
42# Change to false to remove Markdown from posts and/or comments.
43@define( 'MARKDOWN_WP_POSTS',      true );
44@define( 'MARKDOWN_WP_COMMENTS',   true );
45
46
47
48### Standard Function Interface ###
49
50@define( 'MARKDOWN_PARSER_CLASS',  'MarkdownExtra_Parser' );
51
52function Markdown($text) {
53#
54# Initialize the parser and return the result of its transform method.
55#
56    # Setup static parser variable.
57    static $parser;
58    if (!isset($parser)) {
59        $parser_class = MARKDOWN_PARSER_CLASS;
60        $parser = new $parser_class;
61    }
62
63    # Transform text using parser.
64    return $parser->transform($text);
65}
66
67
68### WordPress Plugin Interface ###
69
70/*
71Plugin Name: Markdown Extra
72Plugin URI: http://www.michelf.com/projects/php-markdown/
73Description: <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>
74Version: 1.2.2
75Author: Michel Fortin
76Author URI: http://www.michelf.com/
77*/
78
79if (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); }
151
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    }
160}
161
162
163### bBlog Plugin Info ###
164
165function 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        );
176}
177
178
179### Smarty Modifier Interface ###
180
181function smarty_modifier_markdown($text) {
182    return Markdown($text);
183}
184
185
186### Textile Compatibility Mode ###
187
188# Rename this file to "classTextile.php" and it can replace Textile everywhere.
189
190if (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    }
207}
208
209
210
211#
212# Markdown Parser Class
213#
214
215class Markdown_Parser {
216
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;
224
225    # Table of hash values for escaped characters:
226    var $escape_chars = '\`*_{}[]()>#+-.!';
227    var $escape_chars_re;
228
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 = false;
236
237    # Predefined urls and titles for reference links and images.
238    var $predef_urls = array();
239    var $predef_titles = array();
240
241
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    }
264
265
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    }
297
298
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);
308
309        # Standardize line endings:
310        #   DOS to Unix and Mac to Unix
311        $text = preg_replace('{\r\n?}', "\n", $text);
312
313        # Make sure $text ends with a couple of newlines:
314        $text .= "\n\n";
315
316        # Convert all tabs to spaces.
317        $text = $this->detab($text);
318
319        # Turn block-level HTML blocks into hash entries
320        $text = $this->hashHTMLBlocks($text);
321
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);
327
328        # Run document gamut methods.
329        foreach ($this->document_gamut as $method => $priority) {
330            $text = $this->$method($text);
331        }
332
333        $this->teardown();
334
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        );
344
345
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;
352
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    }
382
383
384    function hashHTMLBlocks($text) {
385        if ($this->no_markup)  return $text;
386
387        $less_than_tab = $this->tab_width - 1;
388
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';
405
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);
443
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
462
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
473
474            | # Special version for tags of group a.
475
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);
518
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    }
549
550
551    function hashBlock($text) {
552    #
553    # Shortcut function for hashPart with block-level boundaries.
554    #
555        return $this->hashPart($text, 'B');
556    }
557
558
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        );
571
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);
598
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    }
619
620
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,
629
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,
640
641        "doItalicsAndBold"    =>  50,
642        "doHardBreaks"        =>  60,
643        );
644
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        }
652
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    }
665
666
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              \]
682
683              [ ]?              # one optional space
684              (?:\n[ ]*)?       # one optional newline followed by spaces
685
686              \[
687                (.*?)       # id = $3
688              \]
689            )
690            }xs',
691            array(&$this, '_doAnchors_reference_callback'), $text);
692
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);
719
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);
733
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];
741
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);
750
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];
776
777        $url = $this->encodeAttribute($url);
778
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>";
787
788        return $this->hashPart($result);
789    }
790
791
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              \]
804
805              [ ]?              # one optional space
806              (?:\n[ ]*)?       # one optional newline followed by spaces
807
808              \[
809                (.*?)       # id = $3
810              \]
811
812            )
813            }xs',
814            array(&$this, '_doImages_reference_callback'), $text);
815
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);
844
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]);
851
852        if ($link_id == "") {
853            $link_id = strtolower($alt_text); # for shortcut links like ![this][].
854        }
855
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        }
872
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];
880
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;
889
890        return $this->hashPart($result);
891    }
892
893
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);
904
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);
921
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    }
938
939
940    function doLists($text) {
941    #
942    # Form HTML ordered (numbered) and unordered (bulleted) lists.
943    #
944        $less_than_tab = $this->tab_width - 1;
945
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)";
950
951        $markers_relist = array($marker_ul_re, $marker_ol_re);
952
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        }
994
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    }
1014
1015    var $list_level = 0;
1016
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++;
1044
1045        # trim trailing blank lines:
1046        $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
1047
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);
1059
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];
1069
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        }
1083
1084        return "<li>" . $item . "</li>\n";
1085    }
1086
1087
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);
1103
1104        return $text;
1105    }
1106    function _doCodeBlocks_callback($matches) {
1107        $codeblock = $matches[1];
1108
1109        $codeblock = $this->outdent($codeblock);
1110        $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
1111
1112        # trim leading newlines and trailing newlines
1113        $codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
1114
1115        $codeblock = "<pre><code>$codeblock\n</code></pre>";
1116        return "\n\n".$this->hashBlock($codeblock)."\n\n";
1117    }
1118
1119
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    }
1127
1128
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    }
1289
1290
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);
1303
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
1311
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);
1317
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    }
1325
1326
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);
1334
1335        $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
1336
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;
1374//
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//                  }
1383//
1384//                  $div_open = preg_replace(
1385//                      '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
1386//
1387//                  $graf = $div_open . "\n" . $div_content . "\n" . $div_close;
1388//              }
1389                $grafs[$key] = $graf;
1390            }
1391        }
1392
1393        return implode("\n\n", $grafs);
1394    }
1395
1396
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);
1424
1425        return $text;
1426    }
1427
1428
1429    function doAutoLinks($text) {
1430        $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i',
1431            array(&$this, '_doAutoLinks_url_callback'), $text);
1432
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);
1445
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    }
1458
1459
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>";
1496
1497        return $addr;
1498    }
1499
1500
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';
1529
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    }
1579
1580
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    }
1587
1588
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);
1603
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    }
1635
1636
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    }
1647
1648}
1649
1650
1651#
1652# Markdown Extra Parser Class
1653#
1654
1655class MarkdownExtra_Parser extends Markdown_Parser {
1656
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();
1670
1671
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
1763
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('', '');
1816
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';
1870
1871
1872        $depth = 0;     # Current depth inside the tag tree.
1873        $parsed = "";   # Parsed text that will be returned.
1874
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++;
1996
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.
2072
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];
2079
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    }
2188
2189
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    }
2198
2199
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);
2218
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);
2237
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    }
2258
2259
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);
2318
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];
2335
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    }
2388
2389
2390    function doDefLists($text) {
2391    #
2392    # Form HTML definition lists.
2393    #
2394        $less_than_tab = $this->tab_width - 1;
2395
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
2423
2424        $text = preg_replace_callback('{
2425                (?>\A\n?|(?<=\n\n))
2426                '.$whole_list_re.'
2427            }mx',
2428            array(&$this, '_doDefLists_callback'), $text);
2429
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    }
2442
2443
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);
2453
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);
2467
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);
2484
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];
2500
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        }
2511
2512        return "\n<dd>" . $def . "</dd>\n";
2513    }
2514
2515
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);
2546
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    }
2561
2562
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        );
2582
2583
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);
2593
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;
2628
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    }
2654
2655
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    }
2666
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;
2768
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    }
2819
2820}
2821
2822
2823/*
2824
2825PHP Markdown Extra
2826==================
2827
2828Description
2829-----------
2830
2831This is a PHP port of the original Markdown formatter written in Perl
2832by John Gruber. This special "Extra" version of PHP Markdown features
2833further enhancements to the syntax for making additional constructs
2834such as tables and definition list.
2835
2836Markdown is a text-to-HTML filter; it translates an easy-to-read /
2837easy-to-write structured text format into HTML. Markdown's text format
2838is most similar to that of plain text email, and supports features such
2839as headers, *emphasis*, code blocks, blockquotes, and links.
2840
2841Markdown's syntax is designed not as a generic markup language, but
2842specifically to serve as a front-end to (X)HTML. You can use span-level
2843HTML tags anywhere in a Markdown document, and you can use block level
2844HTML tags (like <div> and <table> as well).
2845
2846For more information about Markdown's syntax, see:
2847
2848<http://daringfireball.net/projects/markdown/>
2849
2850
2851Bugs
2852----
2853
2854To file bug reports please send email to:
2855
2856<michel.fortin@michelf.com>
2857
2858Please include with your report: (1) the example input; (2) the output you
2859expected; (3) the output Markdown actually produced.
2860
2861
2862Version History
2863---------------
2864
2865See the readme file for detailed release notes for this version.
2866
2867
2868Copyright and License
2869---------------------
2870
2871PHP Markdown & Extra
2872Copyright (c) 2004-2008 Michel Fortin
2873<http://www.michelf.com/>
2874All rights reserved.
2875
2876Based on Markdown
2877Copyright (c) 2003-2006 John Gruber
2878<http://daringfireball.net/>
2879All rights reserved.
2880
2881Redistribution and use in source and binary forms, with or without
2882modification, are permitted provided that the following conditions are
2883met:
2884
2885*   Redistributions of source code must retain the above copyright notice,
2886    this list of conditions and the following disclaimer.
2887
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.
2891
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.
2895
2896This software is provided by the copyright holders and contributors "as
2897is" and any express or implied warranties, including, but not limited
2898to, the implied warranties of merchantability and fitness for a
2899particular purpose are disclaimed. In no event shall the copyright owner
2900or contributors be liable for any direct, indirect, incidental, special,
2901exemplary, or consequential damages (including, but not limited to,
2902procurement of substitute goods or services; loss of use, data, or
2903profits; or business interruption) however caused and on any theory of
2904liability, whether in contract, strict liability, or tort (including
2905negligence or otherwise) arising in any way out of the use of this
2906software, even if advised of the possibility of such damage.
2907
2908*/
2909?>