Make WordPress Core

Ticket #45: 0000045-markdown.phps

File 0000045-markdown.phps, 32.0 KB (added by rakaur, 20 years ago)
Line 
1<?php
2
3#
4# Markdown  -  A text-to-HTML conversion tool for web writers
5#
6# Copyright (c) 2004 John Gruber 
7# <http://daringfireball.net/projects/markdown/>
8#
9# Copyright (c) 2004 Michel Fortin - Translation to PHP 
10# <http://www.michelf.com/>
11#
12
13
14
15global  $MarkdownPHPVersion, $MarkdownSyntaxVersion,
16                $md_empty_element_suffix, $md_tab_width,
17                $md_nested_brackets_depth, $md_nested_brackets,
18                $md_escape_table, $md_backslash_escape_table;
19
20
21$MarkdownPHPVersion    = '1.0b7'; # Sun 6 Jun 2004
22$MarkdownSyntaxVersion = '1.0b7'; # Fri 29 Apr 2004
23
24
25#
26# Global default settings:
27#
28$md_empty_element_suffix = " />";     # Change to ">" for HTML output
29$md_tab_width = 4;
30
31
32# -- WordPress plugin interface -----------------------------------------------
33/*
34Plugin Name: Markdown
35Plugin URI: http://www.michelf.com/projects/php-markdown/
36Description: <a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. This plugin <strong>enables Markdown for your posts and comments</strong>. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. Thanks to <a href="http://photomatt.net/">Matt</a> for making the first Markdown WP plugin. If you use this plugin you should disable Textile 1 and 2 because they do not play well with Markdown.
37Version: 1.0b7
38Author: Michel Fortin
39Author URI: http://www.michelf.com/
40*/
41if (isset($wp_version)) {
42        # Remove default WordPress autop filter
43    remove_filter('the_content', 'wpautop');
44    remove_filter('the_excerpt', 'wpautop');
45    // remove_filter('comment_text', 'wpautop', 30); // 30 because of a bug in 1.2
46        # Add Markdown filter with priority 6 (same as Texturize).
47    add_filter('the_content', 'Markdown', 6);
48    add_filter('the_excerpt', 'Markdown', 6);
49    // add_filter('comment_text', 'Markdown', 30); // 30 because of the same bug
50}
51
52# -- Textile Compatibility Mode -----------------------------------------------
53# Rename this file to "classTextile.php" and it can replace Textile anywhere.
54if (strcasecmp(substr(__FILE__, -16), "classTextile.php") == 0) {
55    # Try to include PHP SmartyPants. Should be in the same directory.
56    @include_once 'smartypants.php';
57    # Fake Textile class. It calls Markdown instead.
58    class Textile {
59        function TextileThis($text, $lite='', $encode='', $noimage='', $strict='') {
60            if ($lite == '' && $encode == '')   $text = Markdown($text);
61            if (function_exists('SmartyPants')) $text = SmartyPants($text);
62            return $text;
63        }
64    }
65}
66
67
68#
69# Globals:
70#
71
72# Regex to match balanced [brackets].
73# Needed to insert a maximum bracked depth while converting to PHP.
74$md_nested_brackets_depth = 6;
75$md_nested_brackets =
76        str_repeat('(?>[^\[\]]+|\[', $md_nested_brackets_depth).
77        str_repeat('\])*', $md_nested_brackets_depth);
78
79# Table of hash values for escaped characters:
80$md_escape_table = array(
81        "\\" => md5("\\"),
82        "`" => md5("`"),
83        "*" => md5("*"),
84        "_" => md5("_"),
85        "{" => md5("{"),
86        "}" => md5("}"),
87        "[" => md5("["),
88        "]" => md5("]"),
89        "(" => md5("("),
90        ")" => md5(")"),
91        "#" => md5("#"),
92        "." => md5("."),
93        "!" => md5("!")
94);
95# Create an identical table but for escaped characters.
96$md_backslash_escape_table;
97foreach ($md_escape_table as $key => $char)
98        $md_backslash_escape_table["\\$key"] = $char;
99
100
101function Markdown($text) {
102#
103# Main function. The order in which other subs are called here is
104# essential. Link and image substitutions need to happen before
105# _EscapeSpecialChars(), so that any *'s or _'s in the <a>
106# and <img> tags get encoded.
107#
108        # Clear the global hashes. If we don't clear these, you get conflicts
109        # from other articles when generating a page which contains more than
110        # one article (e.g. an index page that shows the N most recent
111        # articles):
112        global $md_urls, $md_titles, $md_html_blocks;
113        $md_urls = array();
114        $md_titles = array();
115        $md_html_blocks = array();
116
117        # Standardize line endings:
118        #   DOS to Unix and Mac to Unix
119        $text = str_replace(array("\r\n", "\r"), "\n", $text);
120
121        # Make sure $text ends with a couple of newlines:
122        $text .= "\n\n";
123       
124        # Convert all tabs to spaces.
125        $text = _Detab($text);
126
127        # Strip any lines consisting only of spaces and tabs.
128        # This makes subsequent regexen easier to write, because we can
129        # match consecutive blank lines with /\n+/ instead of something
130        # contorted like /[ \t]*\n+/ .
131        $text = preg_replace('/^[ \t]+$/m', '', $text);
132
133        # Turn block-level HTML blocks into hash entries
134        $text = _HashHTMLBlocks($text);
135
136        # Strip link definitions, store in hashes.
137        $text = _StripLinkDefinitions($text);
138
139        # _EscapeSpecialChars() must be called very early, to get
140        # backslash escapes processed.
141        $text = _EscapeSpecialChars($text);
142
143        $text = _RunBlockGamut($text);
144
145        $text = _UnescapeSpecialChars($text);
146
147        return $text . "\n";
148}
149
150
151function _StripLinkDefinitions($text) {
152#
153# Strips link definitions from text, stores the URLs and titles in
154# hash references.
155#
156        # Link defs are in the form: ^[id]: url "optional title"
157        $text = preg_replace_callback('{
158                                                ^[ \t]*\[(.+)\]:        # id = $1
159                                                  [ \t]*
160                                                  \n?                           # maybe *one* newline
161                                                  [ \t]*
162                                                <?(\S+?)>?                      # url = $2
163                                                  [ \t]*
164                                                  \n?                           # maybe one newline
165                                                  [ \t]*
166                                                (?:
167                                                        # Todo: Titles are delimited by "quotes" or (parens).
168                                                        ["(]
169                                                        (.+?)                   # title = $3
170                                                        [")]
171                                                        [ \t]*
172                                                )?      # title is optional
173                                                (?:\n+|\Z)
174                }xm',
175                '_StripLinkDefinitions_callback',
176                $text);
177        return $text;
178}
179function _StripLinkDefinitions_callback($matches) {
180        global $md_urls, $md_titles;
181        $link_id = strtolower($matches[1]);
182        $md_urls[$link_id] = _EncodeAmpsAndAngles($matches[2]);
183        if (isset($matches[3]))
184                $md_titles[$link_id] = htmlentities($matches[3]);
185        return ''; # String that will replace the block
186}
187
188
189function _HashHTMLBlocks($text) {
190        # Hashify HTML blocks:
191        # We only want to do this for block-level HTML tags, such as headers,
192        # lists, and tables. That's because we still want to wrap <p>s around
193        # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
194        # phrase emphasis, and spans. The list of tags we're looking for is
195        # hard-coded:
196        $block_tags_a = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|ins|del';
197        $block_tags_b = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script';
198       
199        # First, look for nested blocks, e.g.:
200        #       <div>
201        #               <div>
202        #               tags for inner block must be indented.
203        #               </div>
204        #       </div>
205        #
206        # The outermost tags must start at the left margin for this to match, and
207        # the inner nested divs must be indented.
208        # We need to do this before the next, more liberal match, because the next
209        # match will start at the first `<div>` and stop at the first `</div>`.
210        $text = preg_replace_callback("{
211                                (                                               # save in $1
212                                        ^                                       # start of line  (with /m)
213                                        <($block_tags_a)        # start tag = $2
214                                        \\b                                     # word break
215                                        (.*\\n)*?                       # any number of lines, minimally matching
216                                        </\\2>                          # the matching end tag
217                                        [ \\t]*                         # trailing spaces/tabs
218                                        (?=\\n+|\\Z)    # followed by a newline or end of document
219                                )
220                }xm",
221                '_HashHTMLBlocks_callback',
222                $text);
223
224        #
225        # Now match more liberally, simply from `\n<tag>` to `</tag>\n`
226        #
227        $text = preg_replace_callback("{
228                                (                                               # save in $1
229                                        ^                                       # start of line  (with /m)
230                                        <($block_tags_b)        # start tag = $2
231                                        \\b                                     # word break
232                                        (.*\\n)*?                       # any number of lines, minimally matching
233                                        .*</\\2>                                # the matching end tag
234                                        [ \\t]*                         # trailing spaces/tabs
235                                        (?=\\n+|\\Z)    # followed by a newline or end of document
236                                )
237                }xm",
238                '_HashHTMLBlocks_callback',
239                $text);
240       
241        # Special case just for <hr />. It was easier to make a special case than
242        # to make the other regex more complicated.     
243        $text = preg_replace_callback('{
244                                (?:
245                                        (?<=\n\n)               # Starting after a blank line
246                                        |                               # or
247                                        \A\n?                   # the beginning of the doc
248                                )
249                                (                                               # save in $1
250                                        [ \t]*
251                                        <(hr)                           # start tag = $2
252                                        \b                                      # word break
253                                        ([^<>])*?                       #
254                                        /?>                                     # the matching end tag
255                                        (?=\n{2,}|\Z)           # followed by a blank line or end of document
256                                )
257                }x',
258                '_HashHTMLBlocks_callback',
259                $text);
260
261        return $text;
262}
263function _HashHTMLBlocks_callback($matches) {
264        global $md_html_blocks;
265        $text = $matches[1];
266        $key = md5($text);
267        $md_html_blocks[$key] = $text;
268        return "\n\n$key\n\n"; # String that will replace the block
269}
270
271
272function _RunBlockGamut($text) {
273#
274# These are all the transformations that form block-level
275# tags like paragraphs, headers, and list items.
276#
277        global $md_empty_element_suffix;
278       
279        $text = _DoHeaders($text);
280
281        # Do Horizontal Rules:
282        $text = preg_replace(
283                array('/^( ?\* ?){3,}$/m',
284                          '/^( ?- ?){3,}$/m',
285                          '/^( ?_ ?){3,}$/m'),
286                "\n<hr$md_empty_element_suffix\n",
287                $text);
288
289        $text = _DoLists($text);
290
291        $text = _DoCodeBlocks($text);
292
293        $text = _DoBlockQuotes($text);
294
295        # Make links out of things like `<http://example.com/>`
296        $text = _DoAutoLinks($text);
297
298        # We already ran _HashHTMLBlocks() before, in Markdown(), but that
299        # was to escape raw HTML in the original Markdown source. This time,
300        # we're escaping the markup we've just created, so that we don't wrap
301        # <p> tags around block-level tags.
302        $text = _HashHTMLBlocks($text);
303
304        $text = _FormParagraphs($text);
305
306        return $text;
307}
308
309
310function _RunSpanGamut($text) {
311#
312# These are all the transformations that occur *within* block-level
313# tags like paragraphs, headers, and list items.
314#
315        global $md_empty_element_suffix;
316        $text = _DoCodeSpans($text);
317
318        # Fix unencoded ampersands and <'s:
319        $text = _EncodeAmpsAndAngles($text);
320
321        # Process anchor and image tags. Images must come first,
322        # because ![foo][f] looks like an anchor.
323        $text = _DoImages($text);
324        $text = _DoAnchors($text);
325
326
327        $text = _DoItalicsAndBold($text);
328       
329        # Do hard breaks:
330        $text = preg_replace('/ {2,}\n/', "<br$md_empty_element_suffix\n", $text);
331
332        return $text;
333}
334
335
336function _EscapeSpecialChars($text) {
337        global $md_escape_table;
338        $tokens = _TokenizeHTML($text);
339
340        $text = '';   # rebuild $text from the tokens
341        $in_pre = 0;  # Keep track of when we're inside <pre> or <code> tags.
342        $tags_to_skip = "!<(/?)(?:pre|code|kbd|script|math)[\s>]!";
343
344        foreach ($tokens as $cur_token) {
345                if ($cur_token[0] == 'tag') {
346                        # Within tags, encode * and _ so they don't conflict
347                        # with their use in Markdown for italics and strong.
348                        # We're replacing each such character with its
349                        # corresponding MD5 checksum value; this is likely
350                        # overkill, but it should prevent us from colliding
351                        # with the escape values by accident.
352                        $cur_token[1] = str_replace(array('*', '_'),
353                                array($md_escape_table['*'], $md_escape_table['_']),
354                                $cur_token[1]);
355                        $text .= $cur_token[1];
356                } else {
357                        $t = $cur_token[1];
358                        if (! $in_pre) {
359                                $t = _EncodeBackslashEscapes($t);
360                                # $t =~ s{([a-z])/([a-z])}{$1&thinsp;/&thinsp;$2}ig;
361                        }
362                        $text .= $t;
363                }
364        }
365        return $text;
366}
367
368
369function _DoAnchors($text) {
370#
371# Turn Markdown link shortcuts into XHTML <a> tags.
372#
373        global $md_nested_brackets;
374        #
375        # First, handle reference-style links: [link text] [id]
376        #
377        $text = preg_replace_callback("{
378                (                                       # wrap whole match in $1
379                  \\[
380                    ($md_nested_brackets)       # link text = $2
381                  \\]
382
383                  [ ]?                          # one optional space
384                  (?:\\n[ ]*)?          # one optional newline followed by spaces
385
386                  \\[
387                    (.*?)               # id = $3
388                  \\]
389                )
390                }xs",
391                '_DoAnchors_reference_callback', $text);
392               
393        #
394        # Next, inline-style links: [link text](url "optional title")
395        #
396        $text = preg_replace_callback("{
397                (                               # wrap whole match in $1
398                  \\[
399                        ($md_nested_brackets)   # link text = $2
400                  \\]
401                  \\(                   # literal paren
402                        [ \\t]*
403                        <?(.+?)>?       # href = $3
404                        [ \\t]*
405                        (                       # title = $4
406                          (['\"])       # quote char = $5
407                          .*?
408                          \\5           # matching quote
409                        )?                      # title is optional
410                  \\)
411                )
412                }xs",
413                '_DoAnchors_inline_callback', $text);
414       
415        return $text;
416}
417function _DoAnchors_reference_callback($matches) {
418        global $md_urls, $md_titles, $md_escape_table;
419        $whole_match = $matches[1];
420        $link_text   = $matches[2];
421        $link_id     = strtolower($matches[3]);
422
423        if ($link_id == "") {
424                $link_id = strtolower($link_text); # for shortcut links like [this][].
425        }
426
427        if (isset($md_urls[$link_id])) {
428                $url = $md_urls[$link_id];
429                # We've got to encode these to avoid conflicting with italics/bold.
430                $url = str_replace(array('*', '_'),
431                                                   array($md_escape_table['*'], $md_escape_table['_']),
432                                                   $url);
433                $result = "<a href=\"$url\"";
434                if ( isset( $md_titles[$link_id] ) ) {
435                        $title = $md_titles[$link_id];
436                        $title = str_replace(array('*',     '_'),
437                                                                 array($md_escape_table['*'],
438                                                                           $md_escape_table['_']), $title);
439                        $result .=  " title=\"$title\"";
440                }
441                $result .= ">$link_text</a>";
442        }
443        else {
444                $result = $whole_match;
445        }
446        return $result;
447}
448function _DoAnchors_inline_callback($matches) {
449        global $md_escape_table;
450        $whole_match = $matches[1];
451        $link_text   = $matches[2];
452        $url                    = $matches[3];
453        $title          = $matches[4];
454
455        # We've got to encode these to avoid conflicting with italics/bold.
456        $url = str_replace(array('*', '_'),
457                                           array($md_escape_table['*'], $md_escape_table['_']),
458                                           $url);
459        $result = "<a href=\"$url\"";
460        if ($title) {
461                $title = str_replace(array('*', '_'),
462                                                     array($md_escape_table['*'], $md_escape_table['_']),
463                                                     $title);
464                $result .=  " title=$title";
465        }
466        $result .= ">$link_text</a>";
467
468        return $result;
469}
470
471
472function _DoImages($text) {
473#
474# Turn Markdown image shortcuts into <img> tags.
475#
476        #
477        # First, handle reference-style labeled images: ![alt text][id]
478        #
479        $text = preg_replace_callback('{
480                (                               # wrap whole match in $1
481                  !\[
482                    (.*?)               # alt text = $2
483                  \]
484
485                  [ ]?                          # one optional space
486                  (?:\n[ ]*)?           # one optional newline followed by spaces
487
488                  \[
489                    (.*?)               # id = $3
490                  \]
491
492                )
493                }xs',
494                '_DoImages_reference_callback', $text);
495
496        #
497        # Next, handle inline images:  ![alt text](url "optional title")
498        # Don't forget: encode * and _
499
500        $text = preg_replace_callback("{
501                (                               # wrap whole match in $1
502                  !\\[
503                        (.*?)           # alt text = $2
504                  \\]
505                  \\(                   # literal paren
506                        [ \\t]*
507                        <?(\S+?)>?      # src url = $3
508                        [ \\t]*
509                        (                       # title = $4
510                          (['\"])       # quote char = $5
511                          .*?
512                          \\5           # matching quote
513                          [ \\t]*
514                        )?                      # title is optional
515                  \\)
516                )
517                }xs",
518                '_DoImages_inline_callback', $text);
519
520        return $text;
521}
522function _DoImages_reference_callback($matches) {
523        global $md_urls, $md_titles, $md_empty_element_suffix, $md_escape_table;
524        $whole_match = $matches[1];
525        $alt_text    = $matches[2];
526        $link_id     = strtolower($matches[3]);
527
528        if ($link_id == "") {
529                $link_id = strtolower($alt_text); # for shortcut links like ![this][].
530        }
531       
532        if (isset($md_urls[$link_id])) {
533                $url = $md_urls[$link_id];
534                # We've got to encode these to avoid conflicting with italics/bold.
535                $url = str_replace(array('*', '_'),
536                                                   array($md_escape_table['*'], $md_escape_table['_']),
537                                                   $url);
538                $result = "<img src=\"$url\" alt=\"$alt_text\"";
539                if (isset($md_titles[$link_id])) {
540                        $title = $md_titles[$link_id];
541                        $title = str_replace(array('*', '_'),
542                                                                 array($md_escape_table['*'],
543                                                                           $md_escape_table['_']), $title);
544                        $result .=  " title=\"$title\"";
545                }
546                $result .= $md_empty_element_suffix;
547        }
548        else {
549                # If there's no such link ID, leave intact:
550                $result = $whole_match;
551        }
552
553        return $result;
554}
555function _DoImages_inline_callback($matches) {
556        global $md_empty_element_suffix, $md_escape_table;
557        $whole_match = $matches[1];
558        $alt_text    = $matches[2];
559        $url                    = $matches[3];
560        $title          = $matches[4];
561
562        # We've got to encode these to avoid conflicting with italics/bold.
563        $url = str_replace(array('*', '_'),
564                                           array($md_escape_table['*'], $md_escape_table['_']),
565                                           $url);
566        $result = "<img src=\"$url\" alt=\"$alt_text\"";
567        if (isset($title)) {
568                $title = str_replace(array('*', '_'),
569                                                         array($md_escape_table['*'], $md_escape_table['_']),
570                                                         $title);
571                $result .=  " title=$title"; # $title already quoted
572        }
573        $result .= $md_empty_element_suffix;
574
575        return $result;
576}
577
578
579function _DoHeaders($text) {
580        # Setext-style headers:
581        #         Header 1
582        #         ========
583        # 
584        #         Header 2
585        #         --------
586        #
587        $text = preg_replace(
588                array("/(.+)[ \t]*\n=+[ \t]*\n+/e",
589                          "/(.+)[ \t]*\n-+[ \t]*\n+/e"),
590                array("'<h1>'._RunSpanGamut(_UnslashQuotes('\\1')).'</h1>\n\n'",
591                          "'<h2>'._RunSpanGamut(_UnslashQuotes('\\1')).'</h2>\n\n'"),
592                $text);
593
594        # atx-style headers:
595        #       # Header 1
596        #       ## Header 2
597        #       ## Header 2 with closing hashes ##
598        #       ...
599        #       ###### Header 6
600        #
601        $text = preg_replace("{
602                        ^(\\#{1,6})     # $1 = string of #'s
603                        [ \\t]*
604                        (.+?)           # $2 = Header text
605                        [ \\t]*
606                        \\#*                    # optional closing #'s (not counted)
607                        \\n+
608                }xme",
609                "'<h'.strlen('\\1').'>'._RunSpanGamut(_UnslashQuotes('\\2')).'</h'.strlen('\\1').'>\n\n'",
610                $text);
611
612        return $text;
613}
614
615
616function _DoLists($text) {
617#
618# Form HTML ordered (numbered) and unordered (bulleted) lists.
619#
620        global $md_tab_width;
621        $less_than_tab = $md_tab_width - 1;
622
623        $text = preg_replace_callback("{
624                        (
625                          (
626                            ^[ ]{0,$less_than_tab}
627                            (\\*|\\d+[.])
628                            [ \\t]+
629                          )
630                          (?s:.+?)
631                          (
632                              \\z
633                            |
634                                  \\n{2,}
635                                  (?=\\S)
636                                  (?![ \\t]* (\\*|\\d+[.]) [ \\t]+)
637                          )
638                        )
639                }xm",
640                '_DoLists_callback', $text);
641
642        return $text;
643}
644function _DoLists_callback($matches) {
645        $list_type = ($matches[3] == "*") ? "ul" : "ol";
646        $list = $matches[1];
647        # Turn double returns into triple returns, so that we can make a
648        # paragraph for the last item in a list, if necessary:
649        $list = preg_replace("/\n{2,}/", "\n\n\n", $list);
650        $result = _ProcessListItems($list);
651        $result = "<$list_type>\n" . $result . "</$list_type>\n";
652        return $result;
653}
654
655
656function _ProcessListItems($list_str) {
657        # trim trailing blank lines:
658        $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
659
660        $list_str = preg_replace_callback('{
661                (\n)?                                                   # leading line = $1
662                (^[ \t]*)                                               # leading whitespace = $2
663                (\*|\d+[.]) [ \t]+                              # list marker = $3
664                ((?s:.+?)                                               # list item text   = $4
665                (\n{1,2}))
666                (?= \n* (\z | \2 (\*|\d+[.]) [ \t]+))
667                }xm',
668                '_ProcessListItems_callback', $list_str);
669
670        return $list_str;
671}
672function _ProcessListItems_callback($matches) {
673        $item = $matches[4];
674        $leading_line = $matches[1];
675        $leading_space = $matches[2];
676
677        if ($leading_line || preg_match('/\n{2,}/', $item)) {
678                $item = _RunBlockGamut(_Outdent($item));
679                #$item =~ s/\n+/\n/g;
680        }
681        else {
682                # Recursion for sub-lists:
683                $item = _DoLists(_Outdent($item));
684                $item = rtrim($item, "\n");
685                $item = _RunSpanGamut($item);
686        }
687
688        return "<li>" . $item . "</li>\n";
689}
690
691
692function _DoCodeBlocks($text) {
693#
694#       Process Markdown `<pre><code>` blocks.
695#       
696        global $md_tab_width;
697        $text = preg_replace_callback("{
698                        (.?)                    # $1 = preceding character
699                        (:)                             # $2 = colon delimiter
700                        (\\n+)                  # $3 = newlines after colon
701                        (                   # $4 = the code block -- one or more lines, starting with a space/tab
702                          (?:
703                            (?:[ ]\{$md_tab_width} | \\t)  # Lines must start with a tab or a tab-width of spaces
704                            .*\\n+
705                          )+
706                        )
707                        ((?=^[ ]{0,$md_tab_width}\\S)|\\Z)      # Lookahead for non-space at line-start, or end of doc
708                }xm",
709                '_DoCodeBlocks_callback', $text);
710
711        return $text;
712}
713function _DoCodeBlocks_callback($matches) {
714        $prevchar  = $matches[1];
715        $newlines  = $matches[2];
716        $codeblock = $matches[4];
717       
718        #
719        # Check the preceding character before the ":". If it's not
720        # whitespace, then the ":" remains; if it is whitespace,
721        # the ":" disappears completely, along with the space character.
722        #
723        $prefix = "";
724        if (!(preg_match('/\s/', $prevchar) || ($prevchar == ""))) {
725                        $prefix = "$prevchar:";
726        }
727        $codeblock = _EncodeCode(_Outdent($codeblock));
728        $codeblock = _Detab($codeblock);
729        # trim leading newlines and trailing whitespace
730        $codeblock = preg_replace(array('/\A\n+/', '/\s+\z/'), '', $codeblock);
731       
732        $result = $prefix . "\n\n<pre><code>" . $codeblock . "\n</code></pre>\n\n";
733
734        return $result;
735}
736
737
738function _DoCodeSpans($text) {
739#
740#       *       Backtick quotes are used for <code></code> spans.
741#
742#       *       You can use multiple backticks as the delimiters if you want to
743#               include literal backticks in the code span. So, this input:
744#     
745#         Just type ``foo `bar` baz`` at the prompt.
746#     
747#       Will translate to:
748#     
749#         <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
750#     
751#               There's no arbitrary limit to the number of backticks you
752#               can use as delimters. If you need three consecutive backticks
753#               in your code, use four for delimiters, etc.
754#
755#       *       You can use spaces to get literal backticks at the edges:
756#     
757#         ... type `` `bar` `` ...
758#     
759#       Turns to:
760#     
761#         ... type <code>`bar`</code> ...
762#
763        $text = preg_replace_callback("@
764                        (`+)            # Opening run of `
765                        (.+?)           # the code block
766                        (?<!`)
767                        \\1
768                        (?!`)
769                @xs",
770                '_DoCodeSpans_callback', $text);
771
772        return $text;
773}
774function _DoCodeSpans_callback($matches) {
775        $c = $matches[2];
776        $c = preg_replace('/^[ \t]*/', '', $c); # leading whitespace
777        $c = preg_replace('/[ \t]*$/', '', $c); # trailing whitespace
778        $c = _EncodeCode($c);
779        return "<code>$c</code>";
780}
781
782
783function _EncodeCode($_) {
784#
785# Encode/escape certain characters inside Markdown code runs.
786# The point is that in code, these characters are literals,
787# and lose their special Markdown meanings.
788#
789        global $md_escape_table;
790
791        # Encode all ampersands; HTML entities are not
792        # entities within a Markdown code span.
793        $_ = str_replace('&', '&amp;', $_);
794
795        # Do the angle bracket song and dance:
796        $_ = str_replace(array('<',    '>'),
797                                         array('&lt;', '&gt;'), $_);
798
799        # Now, escape characters that are magic in Markdown:
800        $_ = str_replace(array_keys($md_escape_table),
801                                         array_values($md_escape_table), $_);
802
803        return $_;
804}
805
806
807function _DoItalicsAndBold($text) {
808        # <strong> must go first:
809        $text = preg_replace('{ (\*\*|__) (?=\S) (.+?) (?<=\S) \1 }sx',
810                '<strong>\2</strong>', $text);
811        # Then <em>:
812        $text = preg_replace('{ (\*|_) (?=\S) (.+?) (?<=\S) \1 }sx',
813                '<em>\2</em>', $text);
814
815        return $text;
816}
817
818
819function _DoBlockQuotes($text) {
820        $text = preg_replace_callback('/
821                  (                                                             # Wrap whole match in $1
822                        (
823                          ^[ \t]*>[ \t]?                        # ">" at the start of a line
824                                .+\n                                    # rest of the first line
825                          (.+\n)*                                       # subsequent consecutive lines
826                          \n*                                           # blanks
827                        )+
828                  )
829                /xm',
830                '_DoBlockQuotes_callback', $text);
831
832        return $text;
833}
834function _DoBlockQuotes_callback($matches) {
835        $bq = $matches[1];
836        # trim one level of quoting - trim whitespace-only lines
837        $bq = preg_replace(array('/^[ \t]*>[ \t]?/m', '/^[ \t]+$/m'), '', $bq);
838        $bq = _RunBlockGamut($bq);              # recurse
839        $bq = preg_replace('/^/m', "\t", $bq);
840       
841        return "<blockquote>\n$bq\n</blockquote>\n\n";
842}
843
844
845function _FormParagraphs($text) {
846#
847#       Params:
848#               $text - string to process with html <p> tags
849#
850        global $md_html_blocks;
851
852        # Strip leading and trailing lines:
853        $text = preg_replace(array('/\A\n+/', '/\n+\z/'), '', $text);
854       
855        $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
856        $count = count($grafs);
857
858        #
859        # Wrap <p> tags.
860        #
861        foreach ($grafs as $key => $value) {
862                if (!isset( $md_html_blocks[$value] )) {
863                        $value = _RunSpanGamut($value);
864                        $value = preg_replace('/^([ \t]*)/', '<p>', $value);
865                        $value .= "</p>";
866                        $grafs[$key] = $value;
867                }
868        }
869
870        #
871        # Unhashify HTML blocks
872        #
873        foreach ($grafs as $key => $value) {
874                if (isset( $md_html_blocks[$value] )) {
875                        $grafs[$key] = $md_html_blocks[$value];
876                }
877        }
878
879        return implode("\n\n", $grafs);
880}
881
882
883function _EncodeAmpsAndAngles($text) {
884# Smart processing for ampersands and angle brackets that need to be encoded.
885
886        # Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
887        #   http://bumppo.net/projects/amputator/
888        $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',
889                                                 '&amp;', $text);;
890
891        # Encode naked <'s
892        $text = preg_replace('{<(?![a-z/?\$!])}i', '&lt;', $text);
893
894        return $text;
895}
896
897
898function _EncodeBackslashEscapes($text) {
899#
900#   Parameter:  String.
901#   Returns:    The string, with after processing the following backslash
902#               escape sequences.
903#
904        global $md_escape_table, $md_backslash_escape_table;
905        # Must process escaped backslashes first.
906        return str_replace(array_keys($md_backslash_escape_table),
907                                           array_values($md_backslash_escape_table), $text);
908}
909
910
911function _DoAutoLinks($text) {
912        $text = preg_replace("!<((https?|ftp):[^'\">\\s]+)>!",
913                                                 '<a href="\1">\1</a>', $text);
914       
915        # Email addresses: <address@domain.foo>
916        $text = preg_replace('{
917                <
918                (
919                        [-.\w]+
920                        \@
921                        [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
922                )
923                >
924                }exi',
925                "_EncodeEmailAddress(_UnescapeSpecialChars(_UnslashQuotes('\\1')))",
926                $text);
927       
928        return $text;
929}
930
931
932function _EncodeEmailAddress($addr) {
933#
934#       Input: an email address, e.g. "foo@example.com"
935#
936#       Output: the email address as a mailto link, with each character
937#               of the address encoded as either a decimal or hex entity, in
938#               the hopes of foiling most address harvesting spam bots. E.g.:
939#
940#         <a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
941#       x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
942#       &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>
943#
944#       Based by a filter by Matthew Wickline, posted to the BBEdit-Talk
945#       mailing list: <http://tinyurl.com/yu7ue>
946#
947        $addr = "mailto:" . $addr;
948        $length = strlen($addr);
949
950        # leave ':' alone (to spot mailto: later)
951        $addr = preg_replace_callback('/([^\:])/',
952                                                                  '_EncodeEmailAddress_callback', $addr);
953
954        $addr = "<a href=\"$addr\">$addr</a>";
955        # strip the mailto: from the visible part
956        $addr = preg_replace('/">.+?:/', '">', $addr);
957
958        return $addr;
959}
960function _EncodeEmailAddress_callback($matches) {
961        $char = $matches[1];
962        $r = rand(0, 100);
963        # roughly 10% raw, 45% hex, 45% dec
964        # '@' *must* be encoded. I insist.
965        if ($r > 90 && $char != '@') return $char;
966        if ($r < 45) return '&#x'.dechex(ord($char)).';';
967        return '&#'.ord($char).';';
968}
969
970
971function _UnescapeSpecialChars($text) {
972#
973# Swap back in all the special characters we've hidden.
974#
975        global $md_escape_table;
976        return str_replace(array_values($md_escape_table),
977                                           array_keys($md_escape_table), $text);
978}
979
980
981# Tokenize_HTML is shared between PHP Markdown and PHP SmartyPants.
982# We only define it if it is not already defined.
983if (!function_exists('_TokenizeHTML')) {
984        function _TokenizeHTML($str) {
985        #
986        #   Parameter:  String containing HTML markup.
987        #   Returns:    An array of the tokens comprising the input
988        #               string. Each token is either a tag (possibly with nested,
989        #               tags contained therein, such as <a href="<MTFoo>">, or a
990        #               run of text between tags. Each element of the array is a
991        #               two-element array; the first is either 'tag' or 'text';
992        #               the second is the actual value.
993        #
994        #
995        #   Regular expression derived from the _tokenize() subroutine in
996    #   Brad Choate's MTRegex plugin.
997    #   <http://www.bradchoate.com/past/mtregex.php>
998        #
999                $index = 0;
1000                $tokens = array();
1001
1002                $depth = 6;
1003                $nested_tags = str_repeat('(?:<[a-z\/!$](?:[^<>]|',$depth)
1004                                           .str_repeat(')*>)', $depth);
1005                $match = "(?s:<!(?:--.*?--\s*)+>)|".  # comment
1006                                 "(?s:<\?.*?\?>)|".         # processing instruction
1007                                 "$nested_tags";            # nested tags
1008                 
1009        $parts = preg_split("/($match)/", $str, -1, PREG_SPLIT_DELIM_CAPTURE);
1010       
1011        foreach ($parts as $part) {
1012            if (++$index % 2 && $part != '')
1013                array_push($tokens, array('text', $part));
1014            else
1015                array_push($tokens, array('tag', $part));
1016        }
1017       
1018                return $tokens;
1019        }
1020}
1021
1022function _Outdent($text) {
1023#
1024# Remove one level of line-leading tabs or spaces
1025#
1026        global $md_tab_width;
1027        return preg_replace("/^(\\t|[ ]{1,$md_tab_width})/m", "", $text);
1028}
1029
1030
1031function _Detab($text) {
1032#
1033# Inspired from a post by Bart Lateur:
1034# <http://www.nntp.perl.org/group/perl.macperl.anyperl/154>
1035#
1036        global $md_tab_width;
1037        $text = preg_replace(
1038                "/(.*?)\t/e",
1039                "'\\1'.str_repeat(' ', $md_tab_width - strlen('\\1') % $md_tab_width)",
1040                $text);
1041        return $text;
1042}
1043
1044
1045function _UnslashQuotes($text) {
1046#
1047#   This function is useful to remove automaticaly slashed double quotes
1048#   when using preg_replace and evaluating an expression.
1049#   Parameter:  String.
1050#   Returns:    The string with any slash-double-quote (\") sequence replaced
1051#               by a single double quote.
1052#
1053        return str_replace('\"', '"', $text);
1054}
1055
1056
1057/*
1058
1059PHP Markdown
1060============
1061
1062Description
1063-----------
1064
1065This is a PHP translation of the original Markdown parser written in
1066Perl by John Gruber.
1067
1068Markdown is a text-to-HTML filter; it translates an easy-to-read /
1069easy-to-write structured text format into HTML. Markdown's text format
1070is most similar to that of plain text email, and supports features such
1071as headers, *emphasis*, code blocks, blockquotes, and links.
1072
1073Markdown's syntax is designed not as a generic markup language, but
1074specifically to serve as a front-end to (X)HTML. You can use span-level
1075HTML tags anywhere in a Markdown document, and you can use block level
1076HTML tags (like <div> and <table> as well).
1077
1078For more information about Markdown's syntax, see:
1079
1080<http://daringfireball.net/projects/markdown/>
1081
1082
1083Bugs
1084----
1085
1086To file bug reports please send email to:
1087
1088<michel.fortin@michelf.com>
1089
1090Please include with your report: (1) the example input; (2) the output you
1091 expected; (3) the output Markdown actually produced.
1092
1093
1094Version history
1095---------------
1096
10971.0b7: Sat 12 Jun 2004
1098
1099*   Added 'math' to $tags_to_skip pattern, for MathML users.
1100
1101*   Tweaked regex for identifying HTML entities in
1102    _EncodeAmpsAndAngles(), so as to allow for the very long entity
1103    names used by MathML. (Thanks to Jacques Distler for the patch.)
1104
1105*   Changed the internals of _TokenizeHTML to lower the PHP version
1106    requirement to PHP 4.0.5.
1107
1108
11091.0b6: Sun 6 Jun 2004
1110
1111*   Added a WordPress plugin interface. This means that you can
1112        directly put the `markdown.php` file into the `wp-content/plugins`
1113        directory and then activate it from the administrative interface.
1114   
1115*   Added a Textile compatibility interface. Rename this file to
1116    `classTextile.php` and it can replace Textile anywhere.
1117
1118*   The title attribute of reference-style links were ignored.
1119        This is now fixed.
1120
1121*   Changed internal variables names so that they begin with `md_`
1122    instead of `g_`. This should reduce the risk of name collision with
1123    other programs.
1124
1125
11261.0b5: Sun 2 May 2004
1127       
1128*       Workaround for supporting <ins> and <del> as block-level tags.
1129        This only works if the start and end tags are on lines by
1130        themselves.
1131
1132*       Three or more underscores can now be used for horizontal rules.
1133
1134*       Lines containing only whitespace are trimmed from blockquotes.
1135
1136*       You can now optionally wrap URLs with angle brackets -- like so:
1137        `<http://example.com>` -- in link definitions and inline links and
1138        images.
1139
1140*       `_` and `*` characters in links and images are no longer escaped
1141        as HTML entities. Instead, we use the ridiculous but effective MD5
1142        hashing trick that's used to hide these characters elsewhere. The
1143        end result is that the HTML output uses the literal `*` and `_`
1144        characters, rather than the ugly entities.
1145
1146*       Passing an empty string to the Markdown function no longer creates
1147        an empty paragraph.
1148       
1149*       Added a global declaration at the beginning of the file. This
1150        means you can now `include 'markdown.php'` from inside a function.
1151
1152
11531.0b4.1: Sun 4 Apr 2004
1154
1155*       Fixed a bug where image tags did not close.
1156
1157*       Fixed a bug where brakets `[]` inside a link caused the link to be
1158        ignored. PHP Markdown support only 6 (!) level of brakets inside a link
1159        (while John's original version of Markdown in Perl support much more).
1160
1161
11621.0b4: Sat 27 Mar 2004
1163       
1164*       First release of PHP Markdown, based on the 1.0b4 release.
1165
1166
1167Author & contributors
1168---------------------
1169
1170Original version by John Gruber 
1171<http://daringfireball.net/>
1172
1173PHP translation by Michel Fortin 
1174<http://www.michelf.com/>
1175
1176First WordPress plugin interface written by Matt Mullenweg
1177(<http://photomatt.net/>)
1178
1179
1180Copyright and license
1181---------------------
1182
1183Copyright (c) 2003-2004 John Gruber 
1184<http://daringfireball.net/> 
1185All rights reserved.
1186
1187Copyright (c) 2004 Michel Fortin 
1188<http://www.michelf.com/>
1189
1190Markdown is free software; you can redistribute it and/or modify it
1191under the terms of the GNU General Public License as published by the
1192Free Software Foundation; either version 2 of the License, or (at your
1193option) any later version.
1194
1195Markdown is distributed in the hope that it will be useful, but WITHOUT
1196ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1197FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
1198for more details.
1199
1200*/
1201?>