Show
Ignore:
Timestamp:
01/27/06 11:05:57 (3 years ago)
Author:
fabien
Message:

upgrade to 0.6 (step 3)

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/lib/markdown.php

    r23 r71  
    22 
    33# 
    4 # PHP Markdown Extra  -  A text-to-HTML conversion tool for web writers 
    5 
    6 # Copyright (c) 2004-2005 Michel Fortin   
    7 # <http://www.michelf.com/projects/php-markdown/> 
    8 
    9 # Based on Markdown   
     4# Markdown  -  A text-to-HTML conversion tool for web writers 
     5
    106# Copyright (c) 2004-2005 John Gruber   
    117# <http://daringfireball.net/projects/markdown/> 
     8# 
     9# Copyright (c) 2004-2005 Michel Fortin - PHP Port   
     10# <http://www.michelf.com/projects/php-markdown/> 
    1211# 
    1312 
     
    1918        $md_list_level; 
    2019 
    21 $MarkdownPHPVersion    = 'Extra 1.0'; # Mon 5 Sep 2005 
     20$MarkdownPHPVersion    = '1.0.1c'; # Fri 9 Dec 2005 
    2221$MarkdownSyntaxVersion = '1.0.1';  # Sun 12 Dec 2004 
    2322 
     
    3837# -- WordPress Plugin Interface ----------------------------------------------- 
    3938/* 
    40 Plugin Name: PHP Markdown Extra 
     39Plugin Name: Markdown 
    4140Plugin URI: http://www.michelf.com/projects/php-markdown/ 
    4241Description: <a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://www.michelf.com/projects/php-markdown/">More...</a> 
    43 Version: Extra 1.0 
     42Version: 1.0.1c 
    4443Author: Michel Fortin 
    4544Author URI: http://www.michelf.com/ 
     
    112111        'name'          => 'markdown', 
    113112        'type'          => 'modifier', 
    114         'nicename'      => 'PHP Markdown Extra', 
     113        'nicename'      => 'Markdown', 
    115114        'description'   => 'A text-to-HTML conversion tool for web writers', 
    116115        'authors'       => 'Michel Fortin and John Gruber', 
     
    171170    "-" => md5("-"), 
    172171    "." => md5("."), 
    173     "!" => md5("!"), 
    174     ":" => md5(":"), 
    175     "|" => md5("|"), 
     172    "!" => md5("!") 
    176173); 
    177174# Create an identical table but for escaped characters. 
     
    181178 
    182179 
    183  
    184180function Markdown($text) { 
    185181# 
     
    193189    # one article (e.g. an index page that shows the N most recent 
    194190    # articles): 
    195     global $md_urls, $md_titles, $md_html_blocks, $md_html_hashes
     191    global $md_urls, $md_titles, $md_html_blocks
    196192    $md_urls = array(); 
    197193    $md_titles = array(); 
    198194    $md_html_blocks = array(); 
    199     $md_html_hashes = array(); 
    200195 
    201196    # Standardize line endings: 
     
    208203    # Convert all tabs to spaces. 
    209204    $text = _Detab($text); 
    210  
    211     # Turn block-level HTML blocks into hash entries 
    212     $text = _HashHTMLBlocks($text); 
    213205 
    214206    # Strip any lines consisting only of spaces and tabs. 
     
    218210    $text = preg_replace('/^[ \t]+$/m', '', $text); 
    219211 
     212    # Turn block-level HTML blocks into hash entries 
     213    $text = _HashHTMLBlocks($text); 
     214 
    220215    # Strip link definitions, store in hashes. 
    221216    $text = _StripLinkDefinitions($text); 
    222217 
    223     $text = _RunBlockGamut($text, FALSE); 
     218    $text = _RunBlockGamut($text); 
    224219 
    225220    $text = _UnescapeSpecialChars($text); 
     
    271266 
    272267function _HashHTMLBlocks($text) { 
    273 
    274 # Hashify HTML Blocks and "clean tags". 
    275 
    276 # We only want to do this for block-level HTML tags, such as headers, 
    277 # lists, and tables. That's because we still want to wrap <p>s around 
    278 # "paragraphs" that are wrapped in non-block-level tags, such as anchors, 
    279 # phrase emphasis, and spans. The list of tags we're looking for is 
    280 # hard-coded. 
    281 
    282 # This works by calling _HashHTMLBlocks_InMarkdown, which then calls 
    283 # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1"  
    284 # attribute is found whitin a tag, _HashHTMLBlocks_InHTML calls back 
    285 #  _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag. 
    286 # These two functions are calling each other. It's recursive! 
    287 #  
    288     global  $block_tags, $context_block_tags, $contain_span_tags,  
    289             $clean_tags, $auto_close_tags; 
    290      
    291     # Tags that are always treated as block tags: 
    292     $block_tags = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|'. 
    293                     'form|fieldset|iframe|hr|legend'; 
    294      
    295     # Tags treated as block tags only if the opening tag is alone on it's line: 
    296     $context_block_tags = 'script|noscript|math|ins|del'; 
    297      
    298     # Tags where markdown="1" default to span mode: 
    299     $contain_span_tags = 'p|h[1-6]|li|dd|dt|td|th|legend'; 
    300      
    301     # Tags which must not have their contents modified, no matter where  
    302     # they appear: 
    303     $clean_tags = 'script|math'; 
    304      
    305     # Tags that do not need to be closed. 
    306     $auto_close_tags = 'hr|img'; 
    307      
    308     # Regex to match any tag. 
    309     global $tag_match; 
    310     $tag_match = 
    311         '{ 
    312             (                   # $2: Capture hole tag. 
    313                 </?                 # Any opening or closing tag. 
    314                     [\w:$]+         # Tag name. 
    315                     \s*             # Whitespace. 
    316                     (?: 
    317                         ".*?"       |   # Double quotes (can contain `>`) 
    318                         \'.*?\'     |   # Single quotes (can contain `>`) 
    319                         .+?             # Anything but quotes and `>`. 
    320                     )*? 
    321                 >                   # End of tag. 
    322             | 
    323                 <!--    .*?     --> # HTML Comment 
    324             | 
    325                 <\?     .*?     \?> # Processing instruction 
    326             | 
    327                 <!\[CDATA\[.*?\]\]> # CData Block 
    328             ) 
    329         }xs'; 
    330      
    331     # 
    332     # Call the HTML-in-Markdown hasher. 
    333     # 
    334     list($text, ) = _HashHTMLBlocks_InMarkdown($text); 
    335      
    336     return $text; 
    337 
    338 function _HashHTMLBlocks_InMarkdown($text, $indent = 0,  
    339                                     $enclosing_tag = '', $md_span = false) 
    340 
    341 
    342 # Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags. 
    343 
    344 # *   $indent is the number of space to be ignored when checking for code  
    345 #     blocks. This is important because if we don't take the indent into  
    346 #     account, something like this (which looks right) won't work as expected: 
    347 
    348 #     <div> 
    349 #         <div markdown="1"> 
    350 #         Hello World.  <-- Is this a Markdown code block or text? 
    351 #         </div>  <-- Is this a Markdown code block or a real tag? 
    352 #     <div> 
    353 
    354 #     If you don't like this, just don't indent the tag on which 
    355 #     you apply the markdown="1" attribute. 
    356 
    357 # *   If $enclosing_tag is not empty, stops at the first unmatched closing  
    358 #     tag with that name. Nested tags supported. 
    359 
    360 # *   If $md_span is true, text inside must treated as span. So any double  
    361 #     newline will be replaced by a single newline so that it does not create  
    362 #     paragraphs. 
    363 
    364 # Returns an array of that form: ( processed text , remaining text ) 
    365 
    366     global  $block_tags, $context_block_tags, $clean_tags, $auto_close_tags, 
    367             $tag_match; 
    368      
    369     if ($text === '') return array('', ''); 
    370  
    371     # Regex to check for the presense of newlines around a block tag. 
    372     $newline_match_before = "/(?:^\n?|\n\n) *$/"; 
    373     $newline_match_after =  
    374         '{ 
    375             ^                       # Start of text following the tag. 
    376             (?:[ ]*<!--.*?-->)?     # Optional comment. 
    377             [ ]*\n                  # Must be followed by newline. 
    378         }xs'; 
    379      
    380     # Regex to match any tag. 
    381     $block_tag_match = 
    382         '{ 
    383             (                   # $2: Capture hole tag. 
    384                 </?                 # Any opening or closing tag. 
    385                     (?:             # Tag name. 
    386                         '.$block_tags.'         | 
    387                         '.$context_block_tags.' | 
    388                         '.$clean_tags.'         | 
    389                         (?!\s)'.$enclosing_tag.' 
     268    global $md_tab_width; 
     269    $less_than_tab = $md_tab_width - 1; 
     270 
     271    # Hashify HTML blocks: 
     272    # We only want to do this for block-level HTML tags, such as headers, 
     273    # lists, and tables. That's because we still want to wrap <p>s around 
     274    # "paragraphs" that are wrapped in non-block-level tags, such as anchors, 
     275    # phrase emphasis, and spans. The list of tags we're looking for is 
     276    # hard-coded: 
     277    $block_tags_a = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|'. 
     278                    'script|noscript|form|fieldset|iframe|math|ins|del'; 
     279    $block_tags_b = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|'. 
     280                    'script|noscript|form|fieldset|iframe|math'; 
     281 
     282    # First, look for nested blocks, e.g.: 
     283    #   <div> 
     284    #       <div> 
     285    #       tags for inner block must be indented. 
     286    #       </div> 
     287    #   </div> 
     288    # 
     289    # The outermost tags must start at the left margin for this to match, and 
     290    # the inner nested divs must be indented. 
     291    # We need to do this before the next, more liberal match, because the next 
     292    # match will start at the first `<div>` and stop at the first `</div>`. 
     293    $text = preg_replace_callback("{ 
     294                (                       # save in $1 
     295                    ^                   # start of line  (with /m) 
     296                    <($block_tags_a)    # start tag = $2 
     297                    \\b                 # word break 
     298                    (.*\\n)*?           # any number of lines, minimally matching 
     299                    </\\2>              # the matching end tag 
     300                    [ \\t]*             # trailing spaces/tabs 
     301                    (?=\\n+|\\Z)    # followed by a newline or end of document 
     302                ) 
     303        }xm", 
     304        '_HashHTMLBlocks_callback', 
     305        $text); 
     306 
     307    # 
     308    # Now match more liberally, simply from `\n<tag>` to `</tag>\n` 
     309    # 
     310    $text = preg_replace_callback("{ 
     311                (                       # save in $1 
     312                    ^                   # start of line  (with /m) 
     313                    <($block_tags_b)    # start tag = $2 
     314                    \\b                 # word break 
     315                    (.*\\n)*?           # any number of lines, minimally matching 
     316                    .*</\\2>                # the matching end tag 
     317                    [ \\t]*             # trailing spaces/tabs 
     318                    (?=\\n+|\\Z)    # followed by a newline or end of document 
     319                ) 
     320        }xm", 
     321        '_HashHTMLBlocks_callback', 
     322        $text); 
     323 
     324    # Special case just for <hr />. It was easier to make a special case than 
     325    # to make the other regex more complicated. 
     326    $text = preg_replace_callback('{ 
     327                (?: 
     328                    (?<=\n\n)       # Starting after a blank line 
     329                    |               # or 
     330                    \A\n?           # the beginning of the doc 
     331                ) 
     332                (                       # save in $1 
     333                    [ ]{0,'.$less_than_tab.'} 
     334                    <(hr)               # start tag = $2 
     335                    \b                  # word break 
     336                    ([^<>])*?           #  
     337                    /?>                 # the matching end tag 
     338                    [ \t]* 
     339                    (?=\n{2,}|\Z)       # followed by a blank line or end of document 
     340                ) 
     341        }x', 
     342        '_HashHTMLBlocks_callback', 
     343        $text); 
     344 
     345    # Special case for standalone HTML comments: 
     346    $text = preg_replace_callback('{ 
     347                (?: 
     348                    (?<=\n\n)       # Starting after a blank line 
     349                    |               # or 
     350                    \A\n?           # the beginning of the doc 
     351                ) 
     352                (                       # save in $1 
     353                    [ ]{0,'.$less_than_tab.'} 
     354                    (?s: 
     355                        <! 
     356                        (--.*?--\s*)+ 
     357                        > 
    390358                    ) 
    391                     \s*             # Whitespace. 
    392                     (?: 
    393                         ".*?"       |   # Double quotes (can contain `>`) 
    394                         \'.*?\'     |   # Single quotes (can contain `>`) 
    395                         .+?             # Anything but quotes and `>`. 
    396                     )*? 
    397                 >                   # End of tag. 
    398             | 
    399                 <!--    .*?     --> # HTML Comment 
    400             | 
    401                 <\?     .*?     \?> # Processing instruction 
    402             | 
    403                 <!\[CDATA\[.*?\]\]> # CData Block 
    404             ) 
    405         }xs'; 
    406  
    407      
    408     $depth = 0;     # Current depth inside the tag tree. 
    409     $parsed = "";   # Parsed text that will be returned. 
    410  
    411     # 
    412     # Loop through every tag until we find the closing tag of the parent 
    413     # or loop until reaching the end of text if no parent tag specified. 
    414     # 
    415     do { 
    416         # 
    417         # Split the text using the first $tag_match pattern found. 
    418         # Text before  pattern will be first in the array, text after 
    419         # pattern will be at the end, and between will be any catches made  
    420         # by the pattern. 
    421         # 
    422         $parts = preg_split($block_tag_match, $text, 2,  
    423                             PREG_SPLIT_DELIM_CAPTURE); 
    424          
    425         # If in Markdown span mode, replace any multiple newlines that would  
    426         # trigger a new paragraph. 
    427         if ($md_span) { 
    428             $parts[0] = preg_replace('/\n\n/', "\n", $parts[0]); 
    429         } 
    430          
    431         $parsed .= $parts[0]; # Text before current tag. 
    432          
    433         # If end of $text has been reached. Stop loop. 
    434         if (count($parts) < 3) { 
    435             $text = ""; 
    436             break; 
    437         } 
    438          
    439         $tag  = $parts[1]; # Tag to handle. 
    440         $text = $parts[2]; # Remaining text after current tag. 
    441          
    442         # 
    443         # Check for: Tag inside code block or span 
    444         # 
    445         if (# Find current paragraph 
    446             preg_match('/(?>^\n?|\n\n)((?>.\n?)+?)$/', $parsed, $matches) && 
    447             ( 
    448             # Then match in it either a code block... 
    449             preg_match('/^ {'.($indent+4).'}.*(?>\n {'.($indent+4).'}.*)*'. 
    450                         '(?!\n)$/', $matches[1], $x) || 
    451             # ...or unbalenced code span markers. (the regex matches balenced) 
    452             !preg_match('/^(?>[^`]+|(`+)(?>[^`]+|(?!\1[^`])`)*?\1(?!`))*$/s', 
    453                          $matches[1]) 
    454             )) 
    455         { 
    456             # Tag is in code block or span and may not be a tag at all. So we 
    457             # simply skip the first char (should be a `<`). 
    458             $parsed .= $tag{0}; 
    459             $text = substr($tag, 1) . $text; # Put back $tag minus first char. 
    460         } 
    461         # 
    462         # Check for: Opening Block level tag or 
    463         #            Opening Content Block tag (like ins and del)  
    464         #               used as a block tag (tag is alone on it's line). 
    465         # 
    466         else if (preg_match("{^<(?:$block_tags)\b}", $tag) || 
    467             (   preg_match("{^<(?:$context_block_tags)\b}", $tag) && 
    468                 preg_match($newline_match_before, $parsed) && 
    469                 preg_match($newline_match_after, $text) ) 
    470             ) 
    471         { 
    472             # Need to parse tag and following text using the HTML parser. 
    473             list($block_text, $text) =  
    474                 _HashHTMLBlocks_InHTML($tag . $text, 
    475                                     "_HashHTMLBlocks_HashBlock", TRUE); 
    476              
    477             # Make sure it stays outside of any paragraph by adding newlines. 
    478             $parsed .= "\n\n$block_text\n\n"; 
    479         } 
    480         # 
    481         # Check for: Clean tag (like script, math) 
    482         #            HTML Comments, processing instructions. 
    483         # 
    484         else if (preg_match("{^<(?:$clean_tags)\b}", $tag) || 
    485             $tag{1} == '!' || $tag{1} == '?') 
    486         { 
    487             # Need to parse tag and following text using the HTML parser. 
    488             # (don't check for markdown attribute) 
    489             list($block_text, $text) =  
    490                 _HashHTMLBlocks_InHTML($tag . $text,  
    491                                     "_HashHTMLBlocks_HashClean", FALSE); 
    492              
    493             $parsed .= $block_text; 
    494         } 
    495         # 
    496         # Check for: Tag with same name as enclosing tag. 
    497         # 
    498         else if ($enclosing_tag !== '' && 
    499             # Same name as enclosing tag. 
    500             preg_match("{^</?(?:$enclosing_tag)\b}", $tag)) 
    501         { 
    502             # 
    503             # Increase/decrease nested tag count. 
    504             # 
    505             if ($tag{1} == '/')                     $depth--; 
    506             else if ($tag{strlen($tag)-2} != '/')   $depth++; 
    507  
    508             if ($depth < 0) { 
    509                 # 
    510                 # Going out of parent element. Clean up and break so we 
    511                 # return to the calling function. 
    512                 # 
    513                 $text = $tag . $text; 
    514                 break; 
    515             } 
    516              
    517             $parsed .= $tag; 
    518         } 
    519         else { 
    520             $parsed .= $tag; 
    521         } 
    522     } while ($depth >= 0); 
    523      
    524     return array($parsed, $text); 
    525 
    526 function _HashHTMLBlocks_InHTML($text, $hash_function, $md_attr) { 
    527 
    528 # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags. 
    529 
    530 # *   Calls $hash_function to convert any blocks. 
    531 # *   Stops when the first opening tag closes. 
    532 # *   $md_attr indicate if the use of the `markdown="1"` attribute is allowed. 
    533 #     (it is not inside clean tags) 
    534 
    535 # Returns an array of that form: ( processed text , remaining text ) 
    536 
    537     global $auto_close_tags, $contain_span_tags, $tag_match; 
    538      
    539     if ($text === '') return array('', ''); 
    540      
    541     # Regex to match `markdown` attribute inside of a tag. 
    542     $markdown_attr_match = ' 
    543         { 
    544             \s*         # Eat whitespace before the `markdown` attribute 
    545             markdown 
    546             \s*=\s* 
    547             (["\'])     # $1: quote delimiter        
    548             (.*?)       # $2: attribute value 
    549             \1          # matching delimiter     
    550         }xs'; 
    551      
    552     $original_text = $text;     # Save original text in case of faliure. 
    553      
    554     $depth      = 0;    # Current depth inside the tag tree. 
    555     $block_text = "";   # Temporary text holder for current text. 
    556     $parsed     = "";   # Parsed text that will be returned. 
    557  
    558     # 
    559     # Get the name of the starting tag. 
    560     # 
    561     if (preg_match("/^<([\w:$]*)\b/", $text, $matches)) 
    562         $base_tag_name = $matches[1]; 
    563  
    564     # 
    565     # Loop through every tag until we find the corresponding closing tag. 
    566     # 
    567     do { 
    568         # 
    569         # Split the text using the first $tag_match pattern found. 
    570         # Text before  pattern will be first in the array, text after 
    571         # pattern will be at the end, and between will be any catches made  
    572         # by the pattern. 
    573         # 
    574         $parts = preg_split($tag_match, $text, 2, PREG_SPLIT_DELIM_CAPTURE); 
    575          
    576         if (count($parts) < 3) { 
    577             # 
    578             # End of $text reached with unbalenced tag(s). 
    579             # In that case, we return original text unchanged and pass the 
    580             # first character as filtered to prevent an infinite loop in the  
    581             # parent function. 
    582             # 
    583             return array($original_text{0}, substr($original_text, 1)); 
    584         } 
    585          
    586         $block_text .= $parts[0]; # Text before current tag. 
    587         $tag         = $parts[1]; # Tag to handle. 
    588         $text        = $parts[2]; # Remaining text after current tag. 
    589          
    590         # 
    591         # Check for: Auto-close tag (like <hr/>) 
    592         #            Comments and Processing Instructions. 
    593         # 
    594         if (preg_match("{^</?(?:$auto_close_tags)\b}", $tag) || 
    595             $tag{1} == '!' || $tag{1} == '?') 
    596         { 
    597             # Just add the tag to the block as if it was text. 
    598             $block_text .= $tag; 
    599         } 
    600         else { 
    601             # 
    602             # Increase/decrease nested tag count. Only do so if 
    603             # the tag's name match base tag's. 
    604             # 
    605             if (preg_match("{^</?$base_tag_name\b}", $tag)) { 
    606                 if ($tag{1} == '/')                     $depth--; 
    607                 else if ($tag{strlen($tag)-2} != '/')   $depth++; 
    608             } 
    609              
    610             # 
    611             # Check for `markdown="1"` attribute and handle it. 
    612             # 
    613             if ($md_attr &&  
    614                 preg_match($markdown_attr_match, $tag, $attr_matches) && 
    615                 preg_match('/^(?:1|block|span)$/', $attr_matches[2])) 
    616             { 
    617                 # Remove `markdown` attribute from opening tag. 
    618                 $tag = preg_replace($markdown_attr_match, '', $tag); 
    619                  
    620                 # Check if text inside this tag must be parsed in span mode. 
    621                 $md_mode = $attr_matches[2]; 
    622                 $span_mode = $md_mode == 'span' || $md_mode != 'block' && 
    623                             preg_match("{^<(?:$contain_span_tags)\b}", $tag); 
    624                  
    625                 # Calculate indent before tag. 
    626                 preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches); 
    627                 $indent = strlen($matches[1]); 
    628                  
    629                 # End preceding block with this tag. 
    630                 $block_text .= $tag; 
    631                 $parsed .= $hash_function($block_text, $span_mode); 
    632                  
    633                 # Get enclosing tag name for the ParseMarkdown function. 
    634                 preg_match('/^<([\w:$]*)\b/', $tag, $matches); 
    635                 $tag_name = $matches[1]; 
    636                  
    637                 # Parse the content using the HTML-in-Markdown parser. 
    638                 list ($block_text, $text) 
    639                     = _HashHTMLBlocks_InMarkdown($text, $indent,  
    640                                                     $tag_name, $span_mode); 
    641                  
    642                 # Outdent markdown text. 
    643                 if ($indent > 0) { 
    644                     $block_text = preg_replace("/^[ ]{1,$indent}/m", "",  
    645                                                 $block_text); 
    646                 } 
    647                  
    648                 # Append tag content to parsed text. 
    649                 if (!$span_mode)    $parsed .= "\n\n$block_text\n\n"; 
    650                 else                $parsed .= "$block_text"; 
    651                  
    652                 # Start over a new block. 
    653                 $block_text = ""; 
    654             } 
    655             else $block_text .= $tag; 
    656         } 
    657          
    658     } while ($depth > 0); 
    659      
    660     # 
    661     # Hash last block text that wasn't processed inside the loop. 
    662     # 
    663     $parsed .= $hash_function($block_text); 
    664      
    665     return array($parsed, $text); 
    666 
    667 function _HashHTMLBlocks_HashBlock($text) { 
    668     global $md_html_hashes, $md_html_blocks; 
     359                    [ \t]* 
     360                    (?=\n{2,}|\Z)       # followed by a blank line or end of document 
     361                ) 
     362            }x', 
     363            '_HashHTMLBlocks_callback', 
     364            $text); 
     365 
     366    return $text; 
     367
     368function _HashHTMLBlocks_callback($matches) { 
     369    global $md_html_blocks; 
     370    $text = $matches[1]; 
    669371    $key = md5($text); 
    670     $md_html_hashes[$key] = $text; 
    671372    $md_html_blocks[$key] = $text; 
    672     return $key; # String that will replace the tag. 
    673 
    674 function _HashHTMLBlocks_HashClean($text) { 
    675     global $md_html_hashes; 
    676     $key = md5($text); 
    677     $md_html_hashes[$key] = $text; 
    678     return $key; # String that will replace the clean tag. 
    679 
    680  
    681  
    682 function _HashBlock($text) { 
    683 
    684 # Called whenever a tag must be hashed. When a function insert a block-level  
    685 # tag in $text, it pass through this function and is automaticaly escaped,  
    686 # which remove the need to call _HashHTMLBlocks at every step. 
    687 
    688     # Swap back any tag hash found in $text so we do not have to _UnhashTags 
    689     # multiple times at the end. Must do this because of  
    690     $text = _UnhashTags($text); 
    691      
    692     # Then hash the block as normal. 
    693     return _HashHTMLBlocks_HashBlock($text); 
    694 
    695  
    696  
    697 function _RunBlockGamut($text, $hash_html_blocks = TRUE) { 
     373    return "\n\n$key\n\n"; # String that will replace the block 
     374
     375 
     376 
     377function _RunBlockGamut($text) { 
    698378# 
    699379# These are all the transformations that form block-level 
    700380# tags like paragraphs, headers, and list items. 
    701381# 
    702     if ($hash_html_blocks) { 
    703         # We need to escape raw HTML in Markdown source before doing anything  
    704         # else. This need to be done for each block, and not only at the  
    705         # begining in the Markdown function since hashed blocks can be part of 
    706         # a list item and could have been indented. Indented blocks would have  
    707         # been seen as a code block in previous pass of _HashHTMLBlocks. 
    708         $text = _HashHTMLBlocks($text); 
    709     } 
     382    global $md_empty_element_suffix; 
    710383 
    711384    $text = _DoHeaders($text); 
    712     $text = _DoTables($text); 
    713385 
    714386    # Do Horizontal Rules: 
    715     global $md_empty_element_suffix; 
    716387    $text = preg_replace( 
    717         array('{^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$}emx', 
    718               '{^[ ]{0,2}([ ]? -[ ]?){3,}[ \t]*$}emx', 
    719               '{^[ ]{0,2}([ ]? _[ ]?){3,}[ \t]*$}emx'), 
    720         "_HashBlock('\n<hr$md_empty_element_suffix\n')",  
     388        array('{^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$}mx', 
     389              '{^[ ]{0,2}([ ]? -[ ]?){3,}[ \t]*$}mx', 
     390              '{^[ ]{0,2}([ ]? _[ ]?){3,}[ \t]*$}mx'), 
     391        "\n<hr$md_empty_element_suffix\n",  
    721392        $text); 
    722393 
    723394    $text = _DoLists($text); 
    724     $text = _DoDefLists($text); 
    725395    $text = _DoCodeBlocks($text); 
    726396    $text = _DoBlockQuotes($text); 
     397 
     398    # We already ran _HashHTMLBlocks() before, in Markdown(), but that 
     399    # was to escape raw HTML in the original Markdown source. This time, 
     400    # we're escaping the markup we've just created, so that we don't wrap 
     401    # <p> tags around block-level tags. 
     402    $text = _HashHTMLBlocks($text); 
    727403    $text = _FormParagraphs($text); 
    728404 
     
    1020696    # 
    1021697    $text = preg_replace( 
    1022         array('{ (^.+?) (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? [ \t]*\n=+[ \t]*\n+ }emx', 
    1023               '{ (^.+?) (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? [ \t]*\n-+[ \t]*\n+ }emx'), 
    1024         array("_HashBlock('<h1'. ('\\2'? ' id=\"'._UnslashQuotes('\\2').'\"':''). 
    1025                 '>'._RunSpanGamut(_UnslashQuotes('\\1')).'</h1>' 
    1026               ) . '\n\n'", 
    1027               "_HashBlock('<h2'. ('\\2'? ' id=\"'._UnslashQuotes('\\2').'\"':''). 
    1028                 '>'._RunSpanGamut(_UnslashQuotes('\\1')).'</h2>' 
    1029               ) . '\n\n'"), 
     698        array('{ ^(.+)[ \t]*\n=+[ \t]*\n+ }emx', 
     699              '{ ^(.+)[ \t]*\n-+[ \t]*\n+ }emx'), 
     700        array("'<h1>'._RunSpanGamut(_UnslashQuotes('\\1')).'</h1>\n\n'", 
     701              "'<h2>'._RunSpanGamut(_UnslashQuotes('\\1')).'</h2>\n\n'"), 
    1030702        $text); 
    1031703 
     
    1037709    #   ###### Header 6 
    1038710    # 
    1039     $text = preg_replace('
    1040             ^(\#{1,6}) # $1 = string of #\'s 
    1041             [ \t]* 
     711    $text = preg_replace("
     712            ^(\\#{1,6})    # $1 = string of #'s 
     713            [ \\t]* 
    1042714            (.+?)       # $2 = Header text 
    1043             [ \t]* 
    1044             \#*         # optional closing #\'s (not counted) 
    1045             (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\}[ ]*)? # id attribute 
    1046             \n+ 
    1047         }xme', 
    1048         "_HashBlock( 
    1049             '<h'.strlen('\\1'). ('\\3'? ' id=\"'._UnslashQuotes('\\3').'\"':'').'>'. 
    1050             _RunSpanGamut(_UnslashQuotes('\\2')). 
    1051             '</h'.strlen('\\1').'>' 
    1052         ) . '\n\n'", 
     715            [ \\t]* 
     716            \\#*            # optional closing #'s (not counted) 
     717            \\n+ 
     718        }xme", 
     719        "'<h'.strlen('\\1').'>'._RunSpanGamut(_UnslashQuotes('\\2')).'</h'.strlen('\\1').'>\n\n'", 
    1053720        $text); 
    1054721 
    1055722    return $text; 
    1056 } 
    1057  
    1058  
    1059 function _DoTables($text) { 
    1060 # 
    1061 # Form HTML tables. 
    1062 # 
    1063     global $md_tab_width; 
    1064     $less_than_tab = $md_tab_width - 1; 
    1065     # 
    1066     # Find tables with leading pipe. 
    1067     # 
    1068     #   | Header 1 | Header 2 
    1069     #   | -------- | -------- 
    1070     #   | Cell 1   | Cell 2 
    1071     #   | Cell 3   | Cell 4 
    1072     # 
    1073     $text = preg_replace_callback(' 
    1074         { 
    1075             ^                           # Start of a line 
    1076             [ ]{0,'.$less_than_tab.'}   # Allowed whitespace. 
    1077             [|]                         # Optional leading pipe (present) 
    1078             (.+) \n                     # $1: Header row (at least one pipe) 
    1079              
    1080             [ ]{0,'.$less_than_tab.'}   # Allowed whitespace. 
    1081             [|] ([ ]*[-:]+[-| :]*) \n   # $2: Header underline 
    1082              
    1083             (                           # $3: Cells 
    1084                 (?: 
    1085                     [ ]*                # Allowed whitespace. 
    1086                     [|] .* \n           # Row content. 
    1087                 )* 
    1088             ) 
    1089             (?=\n|\Z)                   # Stop at final double newline. 
    1090         }xm', 
    1091         '_DoTable_LeadingPipe_callback', $text); 
    1092      
    1093     # 
    1094     # Find tables without leading pipe. 
    1095     # 
    1096     #   Header 1 | Header 2 
    1097     #   -------- | -------- 
    1098     #   Cell 1   | Cell 2 
    1099     #   Cell 3   | Cell 4 
    1100     # 
    1101     $text = preg_replace_callback(' 
    1102         { 
    1103             ^                           # Start of a line 
    1104             [ ]{0,'.$less_than_tab.'}   # Allowed whitespace. 
    1105             (\S.*[|].*) \n              # $1: Header row (at least one pipe) 
    1106              
    1107             [ ]{0,'.$less_than_tab.'}   # Allowed whitespace. 
    1108             ([-:]+[ ]*[|][-| :]*) \n    # $2: Header underline 
    1109              
    1110             (                           # $3: Cells 
    1111                 (?: 
    1112                     .* [|] .* \n        # Row content 
    1113                 )* 
    1114             ) 
    1115             (?=\n|\Z)                   # Stop at final double newline. 
    1116         }xm', 
    1117         '_DoTable_callback', $text); 
    1118  
    1119     return $text; 
    1120 } 
    1121 function _DoTable_LeadingPipe_callback($matches) { 
    1122     $head       = $matches[1]; 
    1123     $underline  = $matches[2]; 
    1124     $content    = $matches[3]; 
    1125      
    1126     # Remove leading pipe for each row. 
    1127     $content    = preg_replace('/^ *[|]/m', '', $content); 
    1128      
    1129     return _DoTable_callback(array($matches[0], $head, $underline, $content)); 
    1130 } 
    1131 function _DoTable_callback($matches) { 
    1132     $head       = $matches[1]; 
    1133     $underline  = $matches[2]; 
    1134     $content    = $matches[3]; 
    1135  
    1136     # Remove any tailing pipes for each line. 
    1137     $head       = preg_replace('/[|] *$/m', '', $head); 
    1138     $underline  = preg_replace('/[|] *$/m', '', $underline); 
    1139     $content    = preg_replace('/[|] *$/m', '', $content); 
    1140      
    1141     # Reading alignement from header underline. 
    1142     $separators = preg_split('/ *[|] */', $underline); 
    1143     foreach ($separators as $n => $s) { 
    1144         if (preg_match('/^ *-+: *$/', $s))      $attr[$n] = ' align="right"'; 
    1145         else if (preg_match('/^ *:-+: *$/', $s))$attr[$n] = ' align="center"'; 
    1146         else if (preg_match('/^ *:-+ *$/', $s)) $attr[$n] = ' align="left"'; 
    1147         else                                    $attr[$n] = ''; 
    1148     } 
    1149      
    1150     # Creating code spans before splitting the row is an easy way to  
    1151     # handle a code span containg pipes. 
    1152     $head   = _DoCodeSpans($head); 
    1153     $headers    = preg_split('/ *[|] */', $head); 
    1154     $col_count  = count($headers); 
    1155      
    1156     # Write column headers. 
    1157     $text = "<table>\n"; 
    1158     $text .= "<thead>\n"; 
    1159     $text .= "<tr>\n"; 
    1160     foreach ($headers as $n => $header) 
    1161         $text .= "  <th$attr[$n]>"._RunSpanGamut(trim($header))."</th>\n"; 
    1162     $text .= "</tr>\n"; 
    1163     $text .= "</thead>\n"; 
    1164      
    1165     # Split content by row. 
    1166     $rows = explode("\n", trim($content, "\n")); 
    1167      
    1168     $text .= "<tbody>\n"; 
    1169     foreach ($rows as $row) { 
    1170         # Creating code spans before splitting the row is an easy way to  
    1171         # handle a code span containg pipes. 
    1172         $row = _DoCodeSpans($row); 
    1173          
    1174         # Split row by cell. 
    1175         $row_cells = preg_split('/ *[|] */', $row, $col_count); 
    1176         $row_cells = array_pad($row_cells, $col_count, ''); 
    1177          
    1178         $text .= "<tr>\n"; 
    1179         foreach ($row_cells as $n => $cell) 
    1180             $text .= "  <td$attr[$n]>"._RunSpanGamut(trim($cell))."</td>\n"; 
    1181         $text .= "</tr>\n"; 
    1182     } 
    1183     $text .= "</tbody>\n"; 
    1184     $text .= "</table>"; 
    1185      
    1186     return _HashBlock($text) . "\n"; 
    1187723} 
    1188724 
     
    1233769                    '.$whole_list.' 
    1234770                }mx', 
    1235                 '_DoLists_callback', $text); 
     771                '_DoLists_callback_top', $text); 
    1236772        } 
    1237773        else { 
     
    1240776                    '.$whole_list.' 
    1241777                }mx', 
    1242                 '_DoLists_callback', $text); 
     778                '_DoLists_callback_nested', $text); 
    1243779        } 
    1244780    } 
     
    1246782    return $text; 
    1247783} 
    1248 function _DoLists_callback($matches) { 
     784function _DoLists_callback_top($matches) { 
    1249785    # Re-usable patterns to match list item bullets and number markers: 
    1250786    $marker_ul  = '[*+-]'; 
     
    1261797    $list = preg_replace("/\n{2,}/", "\n\n\n", $list); 
    1262798    $result = _ProcessListItems($list, $marker_any); 
    1263     $result = "<$list_type>\n" . $result . "</$list_type>"; 
    1264     return "\n" . _HashBlock($result) . "\n\n"; 
     799     
     800    # Trim any trailing whitespace, to put the closing `</$list_type>` 
     801    # up on the preceding line, to get it past the current stupid 
     802    # HTML block parser. This is a hack to work around the terrible 
     803    # hack that is the HTML block parser. 
     804    $result = rtrim($result); 
     805    $result = "<$list_type>" . $result . "</$list_type>\n"; 
     806    return $result; 
     807
     808function _DoLists_callback_nested($matches) { 
     809    # Re-usable patterns to match list item bullets and number markers: 
     810    $marker_ul  = '[*+-]'; 
     811    $marker_ol  = '\d+[.]'; 
     812    $marker_any = "(?:$marker_ul|$marker_ol)"; 
     813     
     814    $list = $matches[1]; 
     815    $list_type = preg_match("/$marker_ul/", $matches[3]) ? "ul" : "ol"; 
     816     
     817    $marker_any = ( $list_type == "ul" ? $marker_ul : $marker_ol ); 
     818     
     819    # Turn double returns into triple returns, so that we can make a 
     820    # paragraph for the last item in a list, if necessary: 
     821    $list = preg_replace("/\n{2,}/", "\n\n\n", $list); 
     822    $result = _ProcessListItems($list, $marker_any); 
     823    $result = "<$list_type>\n" . $result . "</$list_type>\n"; 
     824    return $result; 
    1265825} 
    1266826 
     
    1331891 
    1332892 
    1333 function _DoDefLists($text) { 
    1334 # 
    1335 # Form HTML definition lists. 
     893function _DoCodeBlocks($text) { 
     894# 
     895#  Process Markdown `<pre><code>` blocks. 
    1336896# 
    1337897    global $md_tab_width; 
    1338     $less_than_tab = $md_tab_width - 1; 
    1339  
    1340     # Re-usable patterns to match list item bullets and number markers: 
    1341  
    1342     # Re-usable pattern to match any entire dl list: 
    1343     $whole_list = ' 
    1344         (                               # $1 = whole list 
    1345           (                             # $2 
    1346             [ ]{0,'.$less_than_tab.'} 
    1347             ((?>.*\S.*\n)+)             # $3 = defined term 
    1348             \n? 
    1349             [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition 
    1350           ) 
    1351           (?s:.+?) 
    1352           (                             # $4 
    1353               \z 
    1354             | 
    1355               \n{2,} 
    1356               (?=\S) 
    1357               (?!                       # Negative lookahead for another term 
    1358                 [ ]{0,'.$less_than_tab.'} 
    1359                 (?: \S.*\n )+?          # defined term 
    1360                 \n? 
    1361                 [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition 
    1362               ) 
    1363               (?!                       # Negative lookahead for another definition 
    1364                 [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition 
    1365               ) 
    1366           ) 
    1367         ) 
    1368     '; // mx 
    1369  
    1370898    $text = preg_replace_callback('{ 
    1371             (?:(?<=\n\n)|\A\n?) 
    1372             '.$whole_list.' 
    1373         }mx', 
    1374         '_DoDefLists_callback', $text); 
    1375  
    1376     return $text; 
    1377 
    1378 function _DoDefLists_callback($matches) { 
    1379     # Re-usable patterns to match list item bullets and number markers: 
    1380     $list = $matches[1]; 
    1381      
    1382     # Turn double returns into triple returns, so that we can make a 
    1383     # paragraph for the last item in a list, if necessary: 
    1384     $result = trim(_ProcessDefListItems($list)); 
    1385     $result = "<dl>\n" . $result . "\n</dl>"; 
    1386     return _HashBlock($result) . "\n\n"; 
    1387 
    1388  
    1389  
    1390 function _ProcessDefListItems($list_str) { 
    1391 
    1392 #   Process the contents of a single ordered or unordered list, splitting it 
    1393 #   into individual list items. 
    1394 
    1395     global $md_tab_width; 
    1396     $less_than_tab = $md_tab_width - 1; 
    1397      
    1398     # trim trailing blank lines: 
    1399     $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); 
    1400  
    1401     # Process definition terms. 
    1402     $list_str = preg_replace_callback('{ 
    1403         (?:\n\n+|\A\n?)                 # leading line 
    1404         (                               # definition terms = $1 
    1405             [ ]{0,'.$less_than_tab.'}   # leading whitespace 
    1406             (?![:][ ]|[ ])              # negative lookahead for a definition  
    1407