From df63d62031ee655edf24df49534ad381d7933c42 Mon Sep 17 00:00:00 2001 From: thepurpleblob Date: Mon, 17 Sep 2007 12:36:14 +0000 Subject: [PATCH] MDL-11104: Markdown updated to latest version. --- lib/markdown.php | 863 +++++++++++++++++++++-------------------------- 1 file changed, 379 insertions(+), 484 deletions(-) diff --git a/lib/markdown.php b/lib/markdown.php index 0c2cfed85e5..2ffa47205a0 100755 --- a/lib/markdown.php +++ b/lib/markdown.php @@ -12,8 +12,8 @@ # -define( 'MARKDOWN_VERSION', "1.0.1f" ); # Wed 7 Feb 2007 -define( 'MARKDOWNEXTRA_VERSION', "1.1.2" ); # Wed 7 Feb 2007 +define( 'MARKDOWN_VERSION', "1.0.1j" ); # Tue 4 Sep 2007 +define( 'MARKDOWNEXTRA_VERSION', "1.1.6" ); # Tue 4 Sep 2007 # @@ -21,18 +21,18 @@ define( 'MARKDOWNEXTRA_VERSION', "1.1.2" ); # Wed 7 Feb 2007 # # Change to ">" for HTML output -define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX', " />"); +@define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX', " />"); # Define the width of a tab for code blocks. -define( 'MARKDOWN_TAB_WIDTH', 4 ); +@define( 'MARKDOWN_TAB_WIDTH', 4 ); # Optional title attribute for footnote links and backlinks. -define( 'MARKDOWN_FN_LINK_TITLE', "" ); -define( 'MARKDOWN_FN_BACKLINK_TITLE', "" ); +@define( 'MARKDOWN_FN_LINK_TITLE', "" ); +@define( 'MARKDOWN_FN_BACKLINK_TITLE', "" ); # Optional class attribute for footnote links and backlinks. -define( 'MARKDOWN_FN_LINK_CLASS', "" ); -define( 'MARKDOWN_FN_BACKLINK_CLASS', "" ); +@define( 'MARKDOWN_FN_LINK_CLASS', "" ); +@define( 'MARKDOWN_FN_BACKLINK_CLASS', "" ); # @@ -40,14 +40,14 @@ define( 'MARKDOWN_FN_BACKLINK_CLASS', "" ); # # Change to false to remove Markdown from posts and/or comments. -define( 'MARKDOWN_WP_POSTS', true ); -define( 'MARKDOWN_WP_COMMENTS', true ); +@define( 'MARKDOWN_WP_POSTS', true ); +@define( 'MARKDOWN_WP_COMMENTS', true ); ### Standard Function Interface ### -define( 'MARKDOWN_PARSER_CLASS', 'MarkdownExtra_Parser' ); +@define( 'MARKDOWN_PARSER_CLASS', 'MarkdownExtra_Parser' ); function Markdown($text) { # @@ -71,7 +71,7 @@ function Markdown($text) { Plugin Name: Markdown Extra Plugin URI: http://www.michelf.com/projects/php-markdown/ Description: Markdown syntax allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by John Gruber. More... -Version: 1.1.2 +Version: 1.1.6 Author: Michel Fortin Author URI: http://www.michelf.com/ */ @@ -85,9 +85,11 @@ if (isset($wp_version)) { # - Run Markdown on excerpt, then remove all tags. # - Add paragraph tag around the excerpt, but remove it for the excerpt rss. if (MARKDOWN_WP_POSTS) { - remove_filter('the_content', 'wpautop'); - remove_filter('the_excerpt', 'wpautop'); + remove_filter('the_content', 'wpautop'); + remove_filter('the_content_rss', 'wpautop'); + remove_filter('the_excerpt', 'wpautop'); add_filter('the_content', 'Markdown', 6); + add_filter('the_content_rss', 'Markdown', 6); add_filter('get_the_excerpt', 'Markdown', 6); add_filter('get_the_excerpt', 'trim', 7); add_filter('the_excerpt', 'mdwp_add_p'); @@ -105,7 +107,7 @@ if (isset($wp_version)) { # - Scramble important tags before passing them to the kses filter. # - Run Markdown on excerpt then remove paragraph tags. if (MARKDOWN_WP_COMMENTS) { - remove_filter('comment_text', 'wpautop'); + remove_filter('comment_text', 'wpautop', 30); remove_filter('comment_text', 'make_clickable'); add_filter('pre_comment_content', 'Markdown', 6); add_filter('pre_comment_content', 'mdwp_hide_tags', 8); @@ -114,14 +116,12 @@ if (isset($wp_version)) { add_filter('get_comment_excerpt', 'Markdown', 6); add_filter('get_comment_excerpt', 'mdwp_strip_p', 7); - global $markdown_hidden_tags; - $markdown_hidden_tags = array( - '

' => md5('

'), '

' => md5('

'), - '
'	=> md5('
'),	'
'=> md5('
'), - '
    ' => md5('
      '), '
    ' => md5('
'), - ''), - '
  • ' => md5('
  • '), '
  • ' => md5(''), - ); + global $mdwp_hidden_tags, $mdwp_placeholders; + $mdwp_hidden_tags = explode(' ', + '

     
  • '); + $mdwp_placeholders = explode(' ', str_rot13( + 'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR '. + 'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli')); } function mdwp_add_p($text) { @@ -135,14 +135,12 @@ if (isset($wp_version)) { function mdwp_strip_p($t) { return preg_replace('{}i', '', $t); } function mdwp_hide_tags($text) { - global $markdown_hidden_tags; - return str_replace(array_keys($markdown_hidden_tags), - array_values($markdown_hidden_tags), $text); + global $mdwp_hidden_tags, $mdwp_placeholders; + return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text); } function mdwp_show_tags($text) { - global $markdown_hidden_tags; - return str_replace(array_values($markdown_hidden_tags), - array_keys($markdown_hidden_tags), $text); + global $mdwp_hidden_tags, $mdwp_placeholders; + return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text); } } @@ -205,15 +203,20 @@ class Markdown_Parser { # Needed to insert a maximum bracked depth while converting to PHP. var $nested_brackets_depth = 6; var $nested_brackets; + + var $nested_url_parenthesis_depth = 4; + var $nested_url_parenthesis; # Table of hash values for escaped characters: var $escape_chars = '\`*_{}[]()>#+-.!'; - var $escape_table = array(); - var $backslash_escape_table = array(); # Change to ">" for HTML output. var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX; var $tab_width = MARKDOWN_TAB_WIDTH; + + # Change to `true` to disallow markup or entities. + var $no_markup = false; + var $no_entities = false; function Markdown_Parser() { @@ -225,13 +228,10 @@ class Markdown_Parser { $this->nested_brackets = str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth). str_repeat('\])*', $this->nested_brackets_depth); - - # Create an identical table but for escaped characters. - foreach (preg_split('/(?!^|$)/', $this->escape_chars) as $char) { - $hash = md5($char); - $this->escape_table[$char] = $hash; - $this->backslash_escape_table["\\$char"] = $hash; - } + + $this->nested_url_parenthesis = + str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth). + str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth); # Sort document, block, and span gamut in ascendent priority order. asort($this->document_gamut); @@ -243,8 +243,10 @@ class Markdown_Parser { # Internal hashes used during transformation. var $urls = array(); var $titles = array(); - var $html_blocks = array(); - var $html_hashes = array(); # Contains both blocks and span hashes. + var $html_hashes = array(); + + # Status flag to avoid invalid nesting. + var $in_anchor = false; function transform($text) { @@ -260,12 +262,11 @@ class Markdown_Parser { # articles): $this->urls = array(); $this->titles = array(); - $this->html_blocks = array(); $this->html_hashes = array(); # Standardize line endings: # DOS to Unix and Mac to Unix - $text = str_replace(array("\r\n", "\r"), "\n", $text); + $text = preg_replace('{\r\n?}', "\n", $text); # Make sure $text ends with a couple of newlines: $text .= "\n\n"; @@ -279,8 +280,8 @@ class Markdown_Parser { # Strip any lines consisting only of spaces and tabs. # This makes subsequent regexen easier to write, because we can # match consecutive blank lines with /\n+/ instead of something - # contorted like /[ \t]*\n+/ . - $text = preg_replace('/^[ \t]+$/m', '', $text); + # contorted like /[ ]*\n+/ . + $text = preg_replace('/^[ ]+$/m', '', $text); # Run document gamut methods. foreach ($this->document_gamut as $method => $priority) { @@ -295,7 +296,6 @@ class Markdown_Parser { "stripLinkDefinitions" => 20, "runBasicBlockGamut" => 30, - "unescapeSpecialChars" => 90, ); @@ -309,19 +309,19 @@ class Markdown_Parser { # Link defs are in the form: ^[id]: url "optional title" $text = preg_replace_callback('{ ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1 - [ \t]* + [ ]* \n? # maybe *one* newline - [ \t]* + [ ]* ? # url = $2 - [ \t]* + [ ]* \n? # maybe one newline - [ \t]* + [ ]* (?: (?<=\s) # lookbehind for whitespace ["(] (.*?) # title = $3 [")] - [ \t]* + [ ]* )? # title is optional (?:\n+|\Z) }xm', @@ -339,6 +339,8 @@ class Markdown_Parser { function hashHTMLBlocks($text) { + if ($this->no_markup) return $text; + $less_than_tab = $this->tab_width - 1; # Hashify HTML blocks: @@ -347,8 +349,14 @@ class Markdown_Parser { # "paragraphs" that are wrapped in non-block-level tags, such as anchors, # phrase emphasis, and spans. The list of tags we're looking for is # hard-coded: - $block_tags_a = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'. - 'script|noscript|form|fieldset|iframe|math|ins|del'; + # + # * List "a" is made of tags which can be both inline or block-level. + # These will be treated block-level when the start tag is alone on + # its line, otherwise they're not matched here and will be taken as + # inline later. + # * List "b" is made of tags which are always block-level; + # + $block_tags_a = 'ins|del'; $block_tags_b = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'. 'script|noscript|form|fieldset|iframe|math'; @@ -375,7 +383,7 @@ class Markdown_Parser { | <\2 # nested opening tag '.$attr.' # attributes - (?: + (?> /> | >', $nested_tags_level). # end of opening tag @@ -388,6 +396,7 @@ class Markdown_Parser { ) )*', $nested_tags_level); + $content2 = str_replace('\2', '\3', $content); # First, look for nested blocks, e.g.: #
    @@ -400,95 +409,68 @@ class Markdown_Parser { # the inner nested divs must be indented. # We need to do this before the next, more liberal match, because the next # match will start at the first `
    ` and stop at the first `
    `. - $text = preg_replace_callback('{ - ( # save in $1 - ^ # start of line (with /m) - <('.$block_tags_a.')# start tag = $2 - '.$attr.'>\n # attributes followed by > and \n - '.$content.' # content, support nesting - # the matching end tag - [ \t]* # trailing spaces/tabs - (?=\n+|\Z) # followed by a newline or end of document - ) - }xm', - array(&$this, '_hashHTMLBlocks_callback'), - $text); + $text = preg_replace_callback('{(?> + (?> + (?<=\n\n) # Starting after a blank line + | # or + \A\n? # the beginning of the doc + ) + ( # save in $1 - # - # Match from `\n` to `\n`, handling nested tags in between. - # - $text = preg_replace_callback('{ - ( # save in $1 - ^ # start of line (with /m) + # Match from `\n` to `\n`, handling nested tags + # in between. + + [ ]{0,'.$less_than_tab.'} <('.$block_tags_b.')# start tag = $2 - '.$attr.'> # attributes followed by > + '.$attr.'> # attributes followed by > and \n '.$content.' # content, support nesting # the matching end tag - [ \t]* # trailing spaces/tabs + [ ]* # trailing spaces/tabs (?=\n+|\Z) # followed by a newline or end of document - ) - }xm', - array(&$this, '_hashHTMLBlocks_callback'), - $text); - # Special case just for
    . It was easier to make a special case than - # to make the other regex more complicated. - $text = preg_replace_callback('{ - (?: - (?<=\n\n) # Starting after a blank line - | # or - \A\n? # the beginning of the doc - ) - ( # save in $1 + | # Special version for tags of group a. + + [ ]{0,'.$less_than_tab.'} + <('.$block_tags_a.')# start tag = $3 + '.$attr.'>[ ]*\n # attributes followed by > + '.$content2.' # content, support nesting + # the matching end tag + [ ]* # trailing spaces/tabs + (?=\n+|\Z) # followed by a newline or end of document + + | # Special case just for
    . It was easier to make a special + # case than to make the other regex more complicated. + [ ]{0,'.$less_than_tab.'} <(hr) # start tag = $2 \b # word break ([^<>])*? # /?> # the matching end tag - [ \t]* + [ ]* (?=\n{2,}|\Z) # followed by a blank line or end of document - ) - }x', - array(&$this, '_hashHTMLBlocks_callback'), - $text); - - # Special case for standalone HTML comments: - $text = preg_replace_callback('{ - (?: - (?<=\n\n) # Starting after a blank line - | # or - \A\n? # the beginning of the doc - ) - ( # save in $1 + + | # Special case for standalone HTML comments: + [ ]{0,'.$less_than_tab.'} (?s: ) - [ \t]* + [ ]* (?=\n{2,}|\Z) # followed by a blank line or end of document - ) - }x', - array(&$this, '_hashHTMLBlocks_callback'), - $text); - - # PHP and ASP-style processor instructions ( ) - [ \t]* + [ ]* (?=\n{2,}|\Z) # followed by a blank line or end of document - ) - }x', + + ) + )}Sxmi', array(&$this, '_hashHTMLBlocks_callback'), $text); @@ -499,40 +481,36 @@ class Markdown_Parser { $key = $this->hashBlock($text); return "\n\n$key\n\n"; } - - - function hashBlock($text) { + + + function hashPart($text, $boundary = 'X') { # - # Called whenever a tag must be hashed when a function insert a block-level - # tag in $text, it pass through this function and is automaticaly escaped, - # which remove the need to call _HashHTMLBlocks at every step. + # Called whenever a tag must be hashed when a function insert an atomic + # element in the text stream. Passing $text to through this function gives + # a unique text-token which will be reverted back when calling unhash. + # + # The $boundary argument specify what character should be used to surround + # the token. By convension, "B" is used for block elements that needs not + # to be wrapped into paragraph tags at the end, ":" is used for elements + # that are word separators and "S" is used for general span-level elements. # # Swap back any tag hash found in $text so we do not have to `unhash` # multiple times at the end. $text = $this->unhash($text); # Then hash the block. - $key = md5($text); + static $i = 0; + $key = "$boundary\x1A" . ++$i . $boundary; $this->html_hashes[$key] = $text; - $this->html_blocks[$key] = $text; return $key; # String that will replace the tag. } - function hashSpan($text) { + function hashBlock($text) { # - # Called whenever a tag must be hashed when a function insert a span-level - # element in $text, it pass through this function and is automaticaly - # escaped, blocking invalid nested overlap. + # Shortcut function for hashPart with block-level boundaries. # - # Swap back any tag hash found in $text so we do not have to `unhash` - # multiple times at the end. - $text = $this->unhash($text); - - # Then hash the span. - $key = md5($text); - $this->html_hashes[$key] = $text; - return $key; # String that will replace the span tag. + return $this->hashPart($text, 'B'); } @@ -583,9 +561,16 @@ class Markdown_Parser { function doHorizontalRules($text) { # Do Horizontal Rules: return preg_replace( - array('{^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$}mx', - '{^[ ]{0,2}([ ]? -[ ]?){3,}[ \t]*$}mx', - '{^[ ]{0,2}([ ]? _[ ]?){3,}[ \t]*$}mx'), + '{ + ^[ ]{0,3} # Leading space + ([*-_]) # $1: First marker + (?> # Repeated marker group + [ ]{0,2} # Zero, one, or two spaces. + \1 # Marker character + ){2,} # Group repeated at least twice + [ ]* # Tailing spaces + $ # End of line. + }mx', "\n".$this->hashBlock("empty_element_suffix")."\n", $text); } @@ -596,9 +581,9 @@ class Markdown_Parser { # These are all the transformations that occur *within* block-level # tags like paragraphs, headers, and list items. # - "escapeSpecialCharsWithinTagAttributes" => -20, - "doCodeSpans" => -10, - "encodeBackslashEscapes" => -5, + # Process character escapes, code spans, and inline HTML + # in one shot. + "parseSpan" => -30, # Process anchor and image tags. Images must come first, # because ![foo][f] looks like an anchor. @@ -629,32 +614,11 @@ class Markdown_Parser { function doHardBreaks($text) { # Do hard breaks: - $br_tag = $this->hashSpan("empty_element_suffix\n"); - return preg_replace('/ {2,}\n/', $br_tag, $text); + return preg_replace_callback('/ {2,}\n/', + array(&$this, '_doHardBreaks_callback'), $text); } - - - function escapeSpecialCharsWithinTagAttributes($text) { - # - # Within tags -- meaning between < and > -- encode [\ ` * _] so they - # don't conflict with their use in Markdown for code, italics and strong. - # We're replacing each such character with its corresponding MD5 checksum - # value; this is likely overkill, but it should prevent us from colliding - # with the escape values by accident. - # - $tokens = $this->tokenizeHTML($text); - $text = ''; # rebuild $text from the tokens - - foreach ($tokens as $cur_token) { - if ($cur_token[0] == 'tag') { - $cur_token[1] = str_replace('\\', $this->escape_table['\\'], $cur_token[1]); - $cur_token[1] = str_replace(array('`'), $this->escape_table['`'], $cur_token[1]); - $cur_token[1] = str_replace('*', $this->escape_table['*'], $cur_token[1]); - $cur_token[1] = str_replace('_', $this->escape_table['_'], $cur_token[1]); - } - $text .= $cur_token[1]; - } - return $text; + function _doHardBreaks_callback($matches) { + return $this->hashPart("empty_element_suffix\n"); } @@ -662,6 +626,9 @@ class Markdown_Parser { # # Turn Markdown link shortcuts into XHTML tags. # + if ($this->in_anchor) return $text; + $this->in_anchor = true; + # # First, handle reference-style links: [link text] [id] # @@ -690,14 +657,18 @@ class Markdown_Parser { ('.$this->nested_brackets.') # link text = $2 \] \( # literal paren - [ \t]* - ? # href = $3 - [ \t]* - ( # $4 - ([\'"]) # quote char = $5 - (.*?) # Title = $6 - \5 # matching quote - [ \t]* # ignore any spaces/tabs between closing quote and ) + [ ]* + (?: + <(\S*)> # href = $3 + | + ('.$this->nested_url_parenthesis.') # href = $4 + ) + [ ]* + ( # $5 + ([\'"]) # quote char = $6 + (.*?) # Title = $7 + \6 # matching quote + [ ]* # ignore any spaces/tabs between closing quote and ) )? # title is optional \) ) @@ -718,6 +689,7 @@ class Markdown_Parser { // }xs', // array(&$this, '_doAnchors_reference_callback'), $text); + $this->in_anchor = false; return $text; } function _doAnchors_reference_callback($matches) { @@ -747,7 +719,7 @@ class Markdown_Parser { $link_text = $this->runSpanGamut($link_text); $result .= ">$link_text"; - $result = $this->hashSpan($result); + $result = $this->hashPart($result); } else { $result = $whole_match; @@ -757,9 +729,9 @@ class Markdown_Parser { function _doAnchors_inline_callback($matches) { $whole_match = $matches[1]; $link_text = $this->runSpanGamut($matches[2]); - $url = $matches[3]; - $title =& $matches[6]; - + $url = $matches[3] == '' ? $matches[4] : $matches[3]; + $title =& $matches[7]; + $url = $this->encodeAmpsAndAngles($url); $result = "runSpanGamut($link_text); $result .= ">$link_text"; - return $this->hashSpan($result); + return $this->hashPart($result); } @@ -811,14 +783,18 @@ class Markdown_Parser { \] \s? # One optional whitespace character \( # literal paren - [ \t]* - ? # src url = $3 - [ \t]* - ( # $4 - ([\'"]) # quote char = $5 - (.*?) # title = $6 - \5 # matching quote - [ \t]* + [ ]* + (?: + <(\S*)> # src url = $3 + | + ('.$this->nested_url_parenthesis.') # src url = $4 + ) + [ ]* + ( # $5 + ([\'"]) # quote char = $6 + (.*?) # title = $7 + \6 # matching quote + [ ]* )? # title is optional \) ) @@ -845,7 +821,7 @@ class Markdown_Parser { $result .= " title=\"$title\""; } $result .= $this->empty_element_suffix; - $result = $this->hashSpan($result); + $result = $this->hashPart($result); } else { # If there's no such link ID, leave intact: @@ -857,8 +833,8 @@ class Markdown_Parser { function _doImages_inline_callback($matches) { $whole_match = $matches[1]; $alt_text = $matches[2]; - $url = $matches[3]; - $title =& $matches[6]; + $url = $matches[3] == '' ? $matches[4] : $matches[3]; + $title =& $matches[7]; $alt_text = str_replace('"', '"', $alt_text); $result = "\"$alt_text\"";empty_element_suffix; - return $this->hashSpan($result); + return $this->hashPart($result); } @@ -880,10 +856,8 @@ class Markdown_Parser { # Header 2 # -------- # - $text = preg_replace_callback('{ ^(.+)[ \t]*\n=+[ \t]*\n+ }mx', - array(&$this, '_doHeaders_callback_setext_h1'), $text); - $text = preg_replace_callback('{ ^(.+)[ \t]*\n-+[ \t]*\n+ }mx', - array(&$this, '_doHeaders_callback_setext_h2'), $text); + $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx', + array(&$this, '_doHeaders_callback_setext'), $text); # atx-style headers: # # Header 1 @@ -894,9 +868,9 @@ class Markdown_Parser { # $text = preg_replace_callback('{ ^(\#{1,6}) # $1 = string of #\'s - [ \t]* + [ ]* (.+?) # $2 = Header text - [ \t]* + [ ]* \#* # optional closing #\'s (not counted) \n+ }xm', @@ -904,12 +878,9 @@ class Markdown_Parser { return $text; } - function _doHeaders_callback_setext_h1($matches) { - $block = "

    ".$this->runSpanGamut($matches[1])."

    "; - return "\n" . $this->hashBlock($block) . "\n\n"; - } - function _doHeaders_callback_setext_h2($matches) { - $block = "

    ".$this->runSpanGamut($matches[1])."

    "; + function _doHeaders_callback_setext($matches) { + $level = $matches[2]{0} == '=' ? 1 : 2; + $block = "".$this->runSpanGamut($matches[1]).""; return "\n" . $this->hashBlock($block) . "\n\n"; } function _doHeaders_callback_atx($matches) { @@ -939,7 +910,7 @@ class Markdown_Parser { ( # $2 [ ]{0,'.$less_than_tab.'} ('.$marker.') # $3 = first list item marker - [ \t]+ + [ ]+ ) (?s:.+?) ( # $4 @@ -948,8 +919,8 @@ class Markdown_Parser { \n{2,} (?=\S) (?! # Negative lookahead for another list item marker - [ \t]* - '.$marker.'[ \t]+ + [ ]* + '.$marker.'[ ]+ ) ) ) @@ -1029,11 +1000,11 @@ class Markdown_Parser { $list_str = preg_replace_callback('{ (\n)? # leading line = $1 - (^[ \t]*) # leading whitespace = $2 - ('.$marker_any.') [ \t]+ # list marker = $3 + (^[ ]*) # leading whitespace = $2 + ('.$marker_any.') [ ]+ # list marker = $3 ((?s:.+?)) # list item text = $4 (?:(\n+(?=\n))|\n) # tailing blank line = $5 - (?= \n* (\z | \2 ('.$marker_any.') [ \t]+)) + (?= \n* (\z | \2 ('.$marker_any.') [ ]+)) }xm', array(&$this, '_processListItems_callback'), $list_str); @@ -1069,8 +1040,8 @@ class Markdown_Parser { $text = preg_replace_callback('{ (?:\n\n|\A) ( # $1 = the code block -- one or more lines, starting with a space/tab - (?: - (?:[ ]{'.$this->tab_width.'} | \t) # Lines must start with a tab or a tab-width of spaces + (?> + [ ]{'.$this->tab_width.'} # Lines must start with a tab or a tab-width of spaces .*\n+ )+ ) @@ -1083,82 +1054,23 @@ class Markdown_Parser { function _doCodeBlocks_callback($matches) { $codeblock = $matches[1]; - $codeblock = $this->encodeCode($this->outdent($codeblock)); - // $codeblock = $this->detab($codeblock); - # trim leading newlines and trailing whitespace - $codeblock = preg_replace(array('/\A\n+/', '/\n+\z/'), '', $codeblock); + $codeblock = $this->outdent($codeblock); + $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES); - $result = "\n\n".$this->hashBlock("
    " . $codeblock . "\n
    ")."\n\n"; + # trim leading newlines and trailing newlines + $codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock); - return $result; + $codeblock = "
    $codeblock\n
    "; + return "\n\n".$this->hashBlock($codeblock)."\n\n"; } - function doCodeSpans($text) { + function makeCodeSpan($code) { # - # * Backtick quotes are used for spans. + # Create a code span markup for $code. Called from handleSpanToken. # - # * You can use multiple backticks as the delimiters if you want to - # include literal backticks in the code span. So, this input: - # - # Just type ``foo `bar` baz`` at the prompt. - # - # Will translate to: - # - #

    Just type foo `bar` baz at the prompt.

    - # - # There's no arbitrary limit to the number of backticks you - # can use as delimters. If you need three consecutive backticks - # in your code, use four for delimiters, etc. - # - # * You can use spaces to get literal backticks at the edges: - # - # ... type `` `bar` `` ... - # - # Turns to: - # - # ... type `bar` ... - # - $text = preg_replace_callback('@ - (?encodeCode($c); - return $this->hashSpan("$c"); - } - - - function encodeCode($_) { - # - # Encode/escape certain characters inside Markdown code runs. - # The point is that in code, these characters are literals, - # and lose their special Markdown meanings. - # - # Encode all ampersands; HTML entities are not - # entities within a Markdown code span. - $_ = str_replace('&', '&', $_); - - # Do the angle bracket song and dance: - $_ = str_replace(array('<', '>'), - array('<', '>'), $_); - - # Now, escape characters that are magic in Markdown: -// $_ = str_replace(array_keys($this->escape_table), -// array_values($this->escape_table), $_); - - return $_; + $code = htmlspecialchars(trim($code), ENT_NOQUOTES); + return $this->hashPart("$code"); } @@ -1173,13 +1085,13 @@ class Markdown_Parser { (?=\S) # Not followed by whitespace (?!\1\1) # or two others marker chars. ( # $2: Content - (?: + (?> [^*_]+? # Anthing not em markers. | # Balence any regular emphasis inside. \1 (?=\S) .+? (?<=\S) \1 | - (?! \1 ) . # Allow unbalenced * and _. + . # Allow unbalenced * and _. )+? ) (?<=\S) \1\1 # End mark not preceded by whitespace. @@ -1187,7 +1099,7 @@ class Markdown_Parser { array(&$this, '_doItalicAndBold_strong_callback'), $text); # Then : $text = preg_replace_callback( - '{ ( (?runSpanGamut($text); - return $this->hashSpan("$text"); + return $this->hashPart("$text"); } function _doItalicAndBold_strong_callback($matches) { $text = $matches[2]; $text = $this->runSpanGamut($text); - return $this->hashSpan("$text"); + return $this->hashPart("$text"); } function doBlockQuotes($text) { $text = preg_replace_callback('/ ( # Wrap whole match in $1 - ( - ^[ \t]*>[ \t]? # ">" at the start of a line + (?> + ^[ ]*>[ ]? # ">" at the start of a line .+\n # rest of the first line (.+\n)* # subsequent consecutive lines \n* # blanks @@ -1222,7 +1134,7 @@ class Markdown_Parser { function _doBlockQuotes_callback($matches) { $bq = $matches[1]; # trim one level of quoting - trim whitespace-only lines - $bq = preg_replace(array('/^[ \t]*>[ \t]?/m', '/^[ \t]+$/m'), '', $bq); + $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq); $bq = $this->runBlockGamut($bq); # recurse $bq = preg_replace('/^/m', " ", $bq); @@ -1246,29 +1158,26 @@ class Markdown_Parser { # $text - string to process with html

    tags # # Strip leading and trailing lines: - $text = preg_replace(array('/\A\n+/', '/\n+\z/'), '', $text); + $text = preg_replace('/\A\n+|\n+\z/', '', $text); $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); # - # Wrap

    tags. + # Wrap

    tags and unhashify HTML blocks # foreach ($grafs as $key => $value) { - if (!isset( $this->html_blocks[$value] )) { + if (!preg_match('/^B\x1A[0-9]+B$/', $value)) { + # Is a paragraph. $value = $this->runSpanGamut($value); - $value = preg_replace('/^([ \t]*)/', "

    ", $value); + $value = preg_replace('/^([ ]*)/', "

    ", $value); $value .= "

    "; $grafs[$key] = $this->unhash($value); } - } - - # - # Unhashify HTML blocks - # - foreach ($grafs as $key => $graf) { - # Modify elements of @grafs in-place... - if (isset($this->html_blocks[$graf])) { - $block = $this->html_blocks[$graf]; + else { + # Is a block. + # Modify elements of @grafs in-place... + $graf = $value; + $block = $this->html_hashes[$graf]; $graf = $block; // if (preg_match('{ // \A @@ -1315,6 +1224,11 @@ class Markdown_Parser { function encodeAmpsAndAngles($text) { # Smart processing for ampersands and angle brackets that need to be encoded. + if ($this->no_entities) { + $text = str_replace('&', '&', $text); + $text = str_replace('<', '<', $text); + return $text; + } # Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: # http://bumppo.net/projects/amputator/ @@ -1328,18 +1242,6 @@ class Markdown_Parser { } - function encodeBackslashEscapes($text) { - # - # Parameter: String. - # Returns: The string, with after processing the following backslash - # escape sequences. - # - # Must process escaped backslashes first. - return str_replace(array_keys($this->backslash_escape_table), - array_values($this->backslash_escape_table), $text); - } - - function doAutoLinks($text) { $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}', array(&$this, '_doAutoLinks_url_callback'), $text); @@ -1362,13 +1264,12 @@ class Markdown_Parser { function _doAutoLinks_url_callback($matches) { $url = $this->encodeAmpsAndAngles($matches[1]); $link = "$url"; - return $this->hashSpan($link); + return $this->hashPart($link); } function _doAutoLinks_email_callback($matches) { $address = $matches[1]; - $address = $this->unescapeSpecialChars($address); $link = $this->encodeEmailAddress($address); - return $this->hashSpan($link); + return $this->hashPart($link); } @@ -1413,82 +1314,83 @@ class Markdown_Parser { } - function unescapeSpecialChars($text) { + function parseSpan($str) { # - # Swap back in all the special characters we've hidden. + # Take the string $str and parse it into tokens, hashing embeded HTML, + # escaped characters and handling code spans. # - return str_replace(array_values($this->escape_table), - array_keys($this->escape_table), $text); - } - - - function tokenizeHTML($str) { - # - # Parameter: String containing HTML + Markdown markup. - # Returns: An array of the tokens comprising the input - # string. Each token is either a tag or a run of text - # between tags. Each element of the array is a - # two-element array; the first is either 'tag' or 'text'; - # the second is the actual value. - # Note: Markdown code spans are taken into account: no tag token is - # generated within a code span. - # - $tokens = array(); - - while ($str != "") { - # - # Each loop iteration seach for either the next tag or the next - # openning code span marker. If a code span marker is found, the - # code span is extracted in entierty and will result in an extra - # text token. - # - $parts = preg_split('{ + $output = ''; + + $regex = '{ ( + \\\\['.preg_quote($this->escape_chars).'] + | (?no_markup ? '' : ' | # comment | <\?.*?\?> | <%.*?%> # processing instruction | <[/!$]?[-a-zA-Z0-9:]+ # regular tags - (?: + (?> \s (?>[^"\'>]+|"[^"]*"|\'[^\']*\')* )? > + ').' ) - }xs', $str, 2, PREG_SPLIT_DELIM_CAPTURE); + }xs'; + + while (1) { + # + # Each loop iteration seach for either the next tag, the next + # openning code span marker, or the next escaped character. + # Each token is then passed to handleSpanToken. + # + $parts = preg_split($regex, $str, 2, PREG_SPLIT_DELIM_CAPTURE); # Create token from text preceding tag. if ($parts[0] != "") { - $tokens[] = array('text', $parts[0]); + $output .= $parts[0]; } # Check if we reach the end. - if (count($parts) < 3) { - break; + if (isset($parts[1])) { + $output .= $this->handleSpanToken($parts[1], $parts[2]); + $str = $parts[2]; } - - # Create token from tag or code span. - if ($parts[1]{0} == "`") { - $tokens[] = array('text', $parts[1]); - $str = $parts[2]; - - # Skip the whole code span, pass as text token. - if (preg_match('/^(.*(?hashPart("&#". ord($token{1}). ";"); + case "`": + # Search for end marker in remaining text. + if (preg_match('/^(.*?[^`])'.$token.'(?!`)(.*)$/sm', + $str, $matches)) + { + $str = $matches[2]; + $codespan = $this->makeCodeSpan($matches[1]); + return $this->hashPart($codespan); + } + return $token; // return as text since no ending marker found. + default: + return $this->hashPart($token); + } } @@ -1496,7 +1398,7 @@ class Markdown_Parser { # # Remove one level of line-leading tabs or spaces # - return preg_replace("/^(\\t|[ ]{1,$this->tab_width})/m", "", $text); + return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text); } @@ -1512,41 +1414,39 @@ class Markdown_Parser { # tab characters. Then we reconstruct every line by adding the # appropriate number of space between each blocks. - $strlen = $this->utf8_strlen; # best strlen function for UTF-8. - $lines = explode("\n", $text); - $text = ""; - - foreach ($lines as $line) { - # Split in blocks. - $blocks = explode("\t", $line); - # Add each blocks to the line. - $line = $blocks[0]; - unset($blocks[0]); # Do not add first block twice. - foreach ($blocks as $block) { - # Calculate amount of space, insert spaces, insert block. - $amount = $this->tab_width - - $strlen($line, 'UTF-8') % $this->tab_width; - $line .= str_repeat(" ", $amount) . $block; - } - $text .= "$line\n"; - } + $text = preg_replace_callback('/^.*\t.*$/m', + array(&$this, '_detab_callback'), $text); + return $text; } + function _detab_callback($matches) { + $line = $matches[0]; + $strlen = $this->utf8_strlen; # strlen function for UTF-8. + + # Split in blocks. + $blocks = explode("\t", $line); + # Add each blocks to the line. + $line = $blocks[0]; + unset($blocks[0]); # Do not add first block twice. + foreach ($blocks as $block) { + # Calculate amount of space, insert spaces, insert block. + $amount = $this->tab_width - + $strlen($line, 'UTF-8') % $this->tab_width; + $line .= str_repeat(" ", $amount) . $block; + } + return $line; + } function _initDetab() { # # Check for the availability of the function in the `utf8_strlen` property - # (probably `mb_strlen`). If the function is not available, create a + # (initially `mb_strlen`). If the function is not available, create a # function that will loosely count the number of UTF-8 characters with a # regular expression. # if (function_exists($this->utf8_strlen)) return; - $this->utf8_strlen = 'Markdown_UTF8_strlen'; - - if (function_exists($this->utf8_strlen)) return; - function Markdown_UTF8_strlen($text) { - return preg_match_all('/[\x00-\xBF]|[\xC0-\xFF][\x80-\xBF]*/', - $text, $m); - } + $this->utf8_strlen = create_function('$text', 'return preg_match_all( + "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/", + $text, $m);'); } @@ -1554,8 +1454,11 @@ class Markdown_Parser { # # Swap back in all the tags hashed by _HashHTMLBlocks. # - return str_replace(array_keys($this->html_hashes), - array_values($this->html_hashes), $text); + return preg_replace_callback('/(.)\x1A[0-9]+\1/', + array(&$this, '_unhash_callback'), $text); + } + function _unhash_callback($matches) { + return $this->html_hashes[$matches[0]]; } } @@ -1599,8 +1502,8 @@ class MarkdownExtra_Parser extends Markdown_Parser { "doDefLists" => 45, ); $this->span_gamut += array( - "doFootnotes" => 4, - "doAbbreviations" => 5, + "doFootnotes" => 5, + "doAbbreviations" => 70, ); parent::Markdown_Parser(); @@ -1612,7 +1515,9 @@ class MarkdownExtra_Parser extends Markdown_Parser { var $footnotes_ordered = array(); var $abbr_desciptions = array(); var $abbr_matches = array(); - var $html_cleans = array(); + + # Status flag to avoid invalid nesting. + var $in_footnote = false; function transform($text) { @@ -1628,7 +1533,6 @@ class MarkdownExtra_Parser extends Markdown_Parser { $this->footnotes_ordered = array(); $this->abbr_desciptions = array(); $this->abbr_matches = array(); - $this->html_cleans = array(); return parent::transform($text); } @@ -1727,7 +1631,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { (?!\s)'.$enclosing_tag.' ) \s* # Whitespace. - (?: + (?> ".*?" | # Double quotes (can contain `>`) \'.*?\' | # Single quotes (can contain `>`) .+? # Anything but quotes and `>`. @@ -1763,8 +1667,9 @@ class MarkdownExtra_Parser extends Markdown_Parser { # If in Markdown span mode, add a empty-string span-level hash # after each newline to prevent triggering any block element. if ($span) { - $newline = $this->hashSpan("") . "\n"; - $parts[0] = str_replace("\n", $newline, $parts[0]); + $void = $this->hashPart("", ':'); + $newline = "$void\n"; + $parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void; } $parsed .= $parts[0]; # Text before current tag. @@ -1782,7 +1687,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { # Check for: Tag inside code block or span # if (# Find current paragraph - preg_match('/(?>^\n?|\n\n)((?>.\n?)+?)$/', $parsed, $matches) && + preg_match('/(?>^\n?|\n\n)((?>.+\n?)*?)$/', $parsed, $matches) && ( # Then match in it either a code block... preg_match('/^ {'.($indent+4).'}.*(?>\n {'.($indent+4).'}.*)*'. @@ -1879,9 +1784,14 @@ class MarkdownExtra_Parser extends Markdown_Parser { \s* # Eat whitespace before the `markdown` attribute markdown \s*=\s* - (["\']) # $1: quote delimiter - (.*?) # $2: attribute value - \1 # matching delimiter + (?: + (["\']) # $1: quote delimiter + (.*?) # $2: attribute value + \1 # matching delimiter + | + ([^\s>]*) # $3: unquoted attribute value + ) + () # $4: make $3 always defined (avoid warnings) }xs'; # Regex to match any tag. @@ -1890,7 +1800,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { ".*?" | # Double quotes (can contain `>`) \'.*?\' | # Single quotes (can contain `>`) .+? # Anything but quotes and `>`. @@ -1967,14 +1877,14 @@ class MarkdownExtra_Parser extends Markdown_Parser { # Check for `markdown="1"` attribute and handle it. # if ($md_attr && - preg_match($markdown_attr_match, $tag, $attr_matches) && - preg_match('/^1|block|span$/', $attr_matches[2])) + preg_match($markdown_attr_match, $tag, $attr_m) && + preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3])) { # Remove `markdown` attribute from opening tag. $tag = preg_replace($markdown_attr_match, '', $tag); # Check if text inside this tag must be parsed in span mode. - $this->mode = $attr_matches[2]; + $this->mode = $attr_m[2] . $attr_m[3]; $span_mode = $this->mode == 'span' || $this->mode != 'block' && preg_match("{^<(?:$this->contain_span_tags)\b}", $tag); @@ -2028,15 +1938,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { # in $text, it pass through this function and is automaticaly escaped, # blocking invalid nested overlap. # - # Swap back any tag hash found in $text so we do not have to `unhash` - # multiple times at the end. - $text = $this->unhash($text); - - # Then hash the tag. - $key = md5($text); - $this->html_cleans[$key] = $text; - $this->html_hashes[$key] = $text; - return $key; # String that will replace the clean tag. + return $this->hashPart($text, 'C'); } @@ -2052,11 +1954,12 @@ class MarkdownExtra_Parser extends Markdown_Parser { # -------- # $text = preg_replace_callback( - '{ (^.+?) (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? [ \t]*\n=+[ \t]*\n+ }mx', - array(&$this, '_doHeaders_callback_setext_h1'), $text); - $text = preg_replace_callback( - '{ (^.+?) (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? [ \t]*\n-+[ \t]*\n+ }mx', - array(&$this, '_doHeaders_callback_setext_h2'), $text); + '{ + (^.+?) # $1: Header text + (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # $2: Id attribute + [ ]*\n(=+|-+)[ ]*\n+ # $3: Header footer + }mx', + array(&$this, '_doHeaders_callback_setext'), $text); # atx-style headers: # # Header 1 {#header1} @@ -2067,12 +1970,12 @@ class MarkdownExtra_Parser extends Markdown_Parser { # $text = preg_replace_callback('{ ^(\#{1,6}) # $1 = string of #\'s - [ \t]* + [ ]* (.+?) # $2 = Header text - [ \t]* + [ ]* \#* # optional closing #\'s (not counted) (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # id attribute - [ \t]* + [ ]* \n+ }xm', array(&$this, '_doHeaders_callback_atx'), $text); @@ -2083,14 +1986,10 @@ class MarkdownExtra_Parser extends Markdown_Parser { if (empty($attr)) return ""; return " id=\"$attr\""; } - function _doHeaders_callback_setext_h1($matches) { + function _doHeaders_callback_setext($matches) { + $level = $matches[3]{0} == '=' ? 1 : 2; $attr = $this->_doHeaders_attr($id =& $matches[2]); - $block = "".$this->runSpanGamut($matches[1]).""; - return "\n" . $this->hashBlock($block) . "\n\n"; - } - function _doHeaders_callback_setext_h2($matches) { - $attr = $this->_doHeaders_attr($id =& $matches[2]); - $block = "".$this->runSpanGamut($matches[1]).""; + $block = "".$this->runSpanGamut($matches[1]).""; return "\n" . $this->hashBlock($block) . "\n\n"; } function _doHeaders_callback_atx($matches) { @@ -2125,7 +2024,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { [|] ([ ]*[-:]+[-| :]*) \n # $2: Header underline ( # $3: Cells - (?: + (?> [ ]* # Allowed whitespace. [|] .* \n # Row content. )* @@ -2152,7 +2051,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { ([-:]+[ ]*[|][-| :]*) \n # $2: Header underline ( # $3: Cells - (?: + (?> .* [|] .* \n # Row content )* ) @@ -2191,9 +2090,9 @@ class MarkdownExtra_Parser extends Markdown_Parser { else $attr[$n] = ''; } - # Creating code spans before splitting the row is an easy way to - # handle a code span containg pipes. - $head = $this->doCodeSpans($head); + # Parsing span elements, including code spans, character escapes, + # and inline HTML tags, so that pipes inside those gets ignored. + $head = $this->parseSpan($head); $headers = preg_split('/ *[|] */', $head); $col_count = count($headers); @@ -2211,9 +2110,9 @@ class MarkdownExtra_Parser extends Markdown_Parser { $text .= "\n"; foreach ($rows as $row) { - # Creating code spans before splitting the row is an easy way to - # handle a code span containg pipes. - $row = $this->doCodeSpans($row); + # Parsing span elements, including code spans, character escapes, + # and inline HTML tags, so that pipes inside those gets ignored. + $row = $this->parseSpan($row); # Split row by cell. $row_cells = preg_split('/ *[|] */', $row, $col_count); @@ -2238,7 +2137,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { $less_than_tab = $this->tab_width - 1; # Re-usable pattern to match any entire dl list: - $whole_list = ' + $whole_list = '(?> ( # $1 = whole list ( # $2 [ ]{0,'.$less_than_tab.'} @@ -2263,7 +2162,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { ) ) ) - '; // mx + )'; // mx $text = preg_replace_callback('{ (?:(?<=\n\n)|\A\n?) @@ -2368,14 +2267,14 @@ class MarkdownExtra_Parser extends Markdown_Parser { (?=\S) # Not followed by whitespace (?!__) # or two others marker chars. ( # $2: Content - (?: + (?> [^_]+? # Anthing not em markers. | # Balence any regular _ emphasis inside. (? [^*]+? # Anthing not em markers. | # Balence any regular * emphasis inside. \* (?=\S) (.+?) (?<=\S) \* + | + \* # Allow unbalenced as last resort. )+? ) (?<=\S) \*\* # End mark not preceded by whitespace. @@ -2401,7 +2302,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { # Then : $text = preg_replace_callback(array( '{ ( (? tags # # Strip leading and trailing lines: - $text = preg_replace(array('/\A\n+/', '/\n+\z/'), '', $text); + $text = preg_replace('/\A\n+|\n+\z/', '', $text); $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); @@ -2427,11 +2328,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { # Check if this should be enclosed in a paragraph. # Clean tag hashes & block tag hashes are left alone. - $clean_key = $value; - $block_key = substr($value, 0, 32); - - $is_p = (!isset($this->html_blocks[$block_key]) && - !isset($this->html_cleans[$clean_key])); + $is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value); if ($is_p) { $value = "

    $value

    "; @@ -2461,7 +2358,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { # Link defs are in the form: [^id]: url "optional title" $text = preg_replace_callback('{ ^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?: # note_id = $1 - [ \t]* + [ ]* \n? # maybe *one* newline ( # text = $2 (no blank lines allowed) (?: @@ -2479,7 +2376,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { return $text; } function _stripFootnotes_callback($matches) { - $note_id = $matches[1]; + $note_id = $this->fn_id_prefix . $matches[1]; $this->footnotes[$note_id] = $this->outdent($matches[2]); return ''; # String that will replace the block } @@ -2490,7 +2387,9 @@ class MarkdownExtra_Parser extends Markdown_Parser { # Replace footnote references in $text [^id] with a special text-token # which will be can be # - $text = preg_replace('{\[\^(.+?)\]}', "a\0fn:\\1\0z", $text); + if (!$this->in_footnote && !$this->in_anchor) { + $text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text); + } return $text; } @@ -2499,7 +2398,8 @@ class MarkdownExtra_Parser extends Markdown_Parser { # # Append footnote list to text. # - $text = preg_replace_callback('{a\0fn:(.*?)\0z}', + + $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', array(&$this, '_appendFootnotes_callback'), $text); if (!empty($this->footnotes_ordered)) { @@ -2523,6 +2423,8 @@ class MarkdownExtra_Parser extends Markdown_Parser { } $num = 0; + $this->in_footnote = true; + foreach ($this->footnotes_ordered as $note_id => $footnote) { $footnote .= "\n"; # Need to append newline before parsing. $footnote = $this->runBlockGamut("$footnote\n"); @@ -2542,10 +2444,10 @@ class MarkdownExtra_Parser extends Markdown_Parser { $text .= "\n\n"; } + $this->in_footnote = false; + $text .= "\n"; $text .= "
    "; - - $text = preg_replace('{a\{fn:(.*?)\}z}', '[^\\1]', $text); } return $text; } @@ -2589,8 +2491,7 @@ class MarkdownExtra_Parser extends Markdown_Parser { function stripAbbreviations($text) { # - # Strips abbreviations from text, stores the URLs and titles in - # hash references. + # Strips abbreviations from text, stores titles in hash references. # $less_than_tab = $this->tab_width - 1; @@ -2614,12 +2515,16 @@ class MarkdownExtra_Parser extends Markdown_Parser { function doAbbreviations($text) { # - # Replace footnote references in $text [^id] with a link to the footnote. + # Find defined abbreviations in text and wrap them in elements. # if ($this->abbr_matches) { - $regex = '{(?abbr_matches) .')(?!\w)}'; - - $text = preg_replace_callback($regex, + // cannot use the /x modifier because abbr_matches may + // contain spaces: + $text = preg_replace_callback('{'. + '(?abbr_matches) .')'. + '(?![\w\x1A])'. + '}', array(&$this, '_doAbbreviations_callback'), $text); } return $text; @@ -2629,10 +2534,10 @@ class MarkdownExtra_Parser extends Markdown_Parser { if (isset($this->abbr_desciptions[$abbr])) { $desc = $this->abbr_desciptions[$abbr]; if (empty($desc)) { - return $this->hashSpan("$abbr"); + return $this->hashPart("$abbr"); } else { - $desc = $this->escapeSpecialCharsWithinTagAttributes($desc); - return $this->hashSpan("$abbr"); + $desc = htmlspecialchars($desc, ENT_NOQUOTES); + return $this->hashPart("$abbr"); } } else { return $matches[0]; @@ -2682,19 +2587,9 @@ expected; (3) the output Markdown actually produced. Version History ---------------- +--------------- -See Readme file for details. - -Extra 1.1.2 (7 Feb 2007) - -Extra 1.1.1 (28 Dec 2006) - -Extra 1.1 (1 Dec 2006) - -Extra 1.0.1 (9 Dec 2005) - -Extra 1.0 (5 Sep 2005) +See the readme file for detailed release notes for this version. Copyright and License