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