+# Initialize the parser and return the result of its transform method.

+#

+# Setup static parser variable.

+static$parser;

+if (!isset($parser)) {

+$parser_class=MARKDOWN_PARSER_CLASS;

+$parser=new$parser_class;

+ }

+

+# Transform text using parser.

+return$parser->transform($text);

+}

+

+

+### WordPress Plugin Interface ###

+

+/*

+Plugin Name: Markdown

+Plugin URI: http://www.michelf.com/projects/php-markdown/

+Description: <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>

+# - 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_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');

+ add_filter('the_excerpt_rss', 'mdwp_strip_p');

+

+ remove_filter('content_save_pre', 'balanceTags', 50);

+ remove_filter('excerpt_save_pre', 'balanceTags', 50);

+ add_filter('the_content', 'balanceTags', 50);

+ add_filter('get_the_excerpt', 'balanceTags', 9);

+ }

+

+# Comments

+# - Remove WordPress paragraph generator.

+# - Remove WordPress auto-link generator.

+# - 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', 30);

+ remove_filter('comment_text', 'make_clickable');

+ add_filter('pre_comment_content', 'Markdown', 6);

+ add_filter('pre_comment_content', 'mdwp_hide_tags', 8);

+ add_filter('pre_comment_content', 'mdwp_show_tags', 12);

+ add_filter('get_comment_text', 'Markdown', 6);

+ add_filter('get_comment_excerpt', 'Markdown', 6);

+ add_filter('get_comment_excerpt', 'mdwp_strip_p', 7);

+

+global$mdwp_hidden_tags, $mdwp_placeholders;

+$mdwp_hidden_tags=explode('',

+'<p> </p> <pre> </pre> <ol> </ol> <ul> </ul> <li> </li>');

+$mdwp_placeholders=explode('', str_rot13(

+'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR '.

+'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli'));

+ }

+

+functionmdwp_add_p($text) {

+if (!preg_match('{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text)) {

+$text='<p>'.$text.'</p>';

+$text=preg_replace('{\n{2,}}', "</p>\n\n<p>", $text);

+ }

+return$text;

+ }

+

+functionmdwp_strip_p($t) { returnpreg_replace('{</?p>}i', '', $t); }

+

+functionmdwp_hide_tags($text) {

+global$mdwp_hidden_tags, $mdwp_placeholders;

+returnstr_replace($mdwp_hidden_tags, $mdwp_placeholders, $text);

+ }

+functionmdwp_show_tags($text) {

+global$mdwp_hidden_tags, $mdwp_placeholders;

+returnstr_replace($mdwp_placeholders, $mdwp_hidden_tags, $text);

+ }

+}

+

+

+### bBlog Plugin Info ###

+

+functionidentify_modifier_markdown() {

+returnarray(

+'name'=>'markdown',

+'type'=>'modifier',

+'nicename'=>'Markdown',

+'description'=>'A text-to-HTML conversion tool for web writers',

+'authors'=>'Michel Fortin and John Gruber',

+'licence'=>'BSD-like',

+'version'=>MARKDOWN_VERSION,

+'help'=>'<a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://www.michelf.com/projects/php-markdown/">More...</a>'

+ );

+}

+

+

+### Smarty Modifier Interface ###

+

+functionsmarty_modifier_markdown($text) {

+return Markdown($text);

+}

+

+

+### Textile Compatibility Mode ###

+

+# Rename this file to "classTextile.php" and it can replace Textile everywhere.

+

+if (strcasecmp(substr(__FILE__, -16), "classTextile.php") ==0) {

+# Try to include PHP SmartyPants. Should be in the same directory.

+@include_once'smartypants.php';

+# Fake Textile class. It calls Markdown instead.

+classTextile {

+functionTextileThis($text, $lite='', $encode='') {

+if ($lite==''&&$encode=='') $text= Markdown($text);

+if (function_exists('SmartyPants')) $text= SmartyPants($text);

+return$text;

+ }

+# Fake restricted version: restrictions are not supported for now.

+functionTextileRestricted($text, $lite='', $noimage='') {

+return$this->TextileThis($text, $lite);

+ }

+# Workaround to ensure compatibility with TextPattern 4.0.3.

+functionblockLite($text) { return$text; }

+ }

+}

+

+

+

+#

+# Markdown Parser Class

+#

+

+classMarkdown_Parser {

+

+# Regex to match balanced [brackets].

+# 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='\`*_{}[]()>#+-.!';

+

+# 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;

+

+

+functionMarkdown_Parser() {

+#

+# Constructor function. Initialize appropriate member variables.

+#

+$this->_initDetab();

+

+$this->nested_brackets=

+str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth).

+str_repeat('\])*', $this->nested_brackets_depth);

+

+$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);

+asort($this->block_gamut);

+asort($this->span_gamut);

+ }

+

+

+# Internal hashes used during transformation.

+var$urls=array();

+var$titles=array();

+var$html_hashes=array();

+

+# Status flag to avoid invalid nesting.

+var$in_anchor=false;

+

+

+functiontransform($text) {

+#

+# Main function. The order in which other subs are called here is

+# essential. Link and image substitutions need to happen before

+# _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>

+# and <img> tags get encoded.

+#

+# Clear the global hashes. If we don't clear these, you get conflicts

+# from other articles when generating a page which contains more than

+# one article (e.g. an index page that shows the N most recent

+# articles):

+$this->urls=array();

+$this->titles=array();

+$this->html_hashes=array();

+

+# Standardize line endings:

+# DOS to Unix and Mac to Unix

+$text=preg_replace('{\r\n?}', "\n", $text);

+

+# Make sure $text ends with a couple of newlines:

+$text.="\n\n";

+

+# Convert all tabs to spaces.

+$text=$this->detab($text);

+

+# Turn block-level HTML blocks into hash entries

+$text=$this->hashHTMLBlocks($text);

+

+# 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 /[ ]*\n+/ .

+$text=preg_replace('/^[ ]+$/m', '', $text);

+

+# Run document gamut methods.

+foreach ($this->document_gamutas$method=>$priority) {

+$text=$this->$method($text);

+ }

+

+return$text."\n";

+ }

+

+var$document_gamut=array(

+# Strip link definitions, store in hashes.

+"stripLinkDefinitions"=>20,

+

+"runBasicBlockGamut"=>30,

+ );

+

+

+functionstripLinkDefinitions($text) {

+#

+# Strips link definitions from text, stores the URLs and titles in

+# hash references.

+#

+$less_than_tab=$this->tab_width-1;

+

+# Link defs are in the form: ^[id]: url "optional title"

+$text=preg_replace_callback('{

+ ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1

+ [ ]*

+ \n? # maybe *one* newline

+ [ ]*

+ <?(\S+?)>? # url = $2

+ [ ]*

+ \n? # maybe one newline

+ [ ]*

+ (?:

+ (?<=\s) # lookbehind for whitespace

+ ["(]

+ (.*?) # title = $3

+ [")]

+ [ ]*

+ )? # title is optional

+ (?:\n+|\Z)

+ }xm',

+array(&$this, '_stripLinkDefinitions_callback'),

+$text);

+return$text;

+ }

+function_stripLinkDefinitions_callback($matches) {

+$link_id=strtolower($matches[1]);

+$this->urls[$link_id] =$this->encodeAmpsAndAngles($matches[2]);

+if (isset($matches[3]))

+$this->titles[$link_id] =str_replace('"', '&quot;', $matches[3]);

+return''; # String that will replace the block

+ }

+

+

+functionhashHTMLBlocks($text) {

+if ($this->no_markup) return$text;

+

+$less_than_tab=$this->tab_width-1;

+

+# Hashify HTML blocks:

+# We only want to do this for block-level HTML tags, such as headers,

+# lists, and tables. That's because we still want to wrap <p>s around

+# "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:

+#

+# * 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';

+

+# Regular expression for the content of a block tag.

+$nested_tags_level=4;

+$attr='

+ (?> # optional tag attributes

+ \s # starts with whitespace

+ (?>

+ [^>"/]+ # text outside quotes

+ |

+ /+(?!>) # slash not followed by ">"

+ |

+ "[^"]*" # text inside double quotes (tolerate ">")

+ |

+\'[^\']*\' # text inside single quotes (tolerate ">")

+ )*

+ )?

+';

+$content=

+str_repeat('

+ (?>

+ [^<]+ # content without tag

+ |

+ <\2 # nested opening tag

+'.$attr.' # attributes

+ (?>

+ />

+ |

+ >', $nested_tags_level).# end of opening tag

+'.*?'.# last level nested tag content

+str_repeat('

+ </\2\s*> # closing nested tag

+ )

+ |

+ <(?!/\2\s*> # other tags with a different name

+ )

+ )*',

+$nested_tags_level);

+$content2=str_replace('\2', '\3', $content);

+

+# First, look for nested blocks, e.g.:

+# <div>

+# <div>

+# tags for inner block must be indented.

+# </div>

+# </div>

+#

+# The outermost tags must start at the left margin for this to match, and

+# 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 `<div>` and stop at the first `</div>`.

+$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<tag>` to `</tag>\n`, handling nested tags

+ # in between.

+

+ [ ]{0,'.$less_than_tab.'}

+ <('.$block_tags_b.')# start tag = $2

+'.$attr.'> # attributes followed by > and \n

+'.$content.' # content, support nesting

+ </\2> # the matching end tag

+ [ ]* # trailing spaces/tabs

+ (?=\n+|\Z) # followed by a newline or end of document

+

+ | # 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

+ </\3> # the matching end tag

+ [ ]* # trailing spaces/tabs

+ (?=\n+|\Z) # followed by a newline or end of document

+

+ | # Special case just for <hr />. 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

+ [ ]*

+ (?=\n{2,}|\Z) # followed by a blank line or end of document

+

+ | # Special case for standalone HTML comments:

+

+ [ ]{0,'.$less_than_tab.'}

+ (?s:

+ <!-- .*? -->

+ )

+ [ ]*

+ (?=\n{2,}|\Z) # followed by a blank line or end of document

+

+ | # PHP and ASP-style processor instructions (<? and <%)

+

+ [ ]{0,'.$less_than_tab.'}

+ (?s:

+ <([?%]) # $2

+ .*?

+ \2>

+ )

+ [ ]*

+ (?=\n{2,}|\Z) # followed by a blank line or end of document

+

+ )

+ )}Sxmi',

+array(&$this, '_hashHTMLBlocks_callback'),

+$text);

+

+return$text;

+ }

+function_hashHTMLBlocks_callback($matches) {

+$text=$matches[1];

+$key=$this->hashBlock($text);

+return"\n\n$key\n\n";

+ }

+

+

+functionhashPart($text, $boundary='X') {

+#

+# 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.

+static$i=0;

+$key="$boundary\x1A".++$i.$boundary;

+$this->html_hashes[$key] =$text;

+return$key; # String that will replace the tag.

+ }

+

+

+functionhashBlock($text) {

+#

+# Shortcut function for hashPart with block-level boundaries.

+#

+return$this->hashPart($text, 'B');

+ }

+

+

+var$block_gamut=array(

+#

+# These are all the transformations that form block-level

+# tags like paragraphs, headers, and list items.

+#

+"doHeaders"=>10,

+"doHorizontalRules"=>20,

+

+"doLists"=>40,

+"doCodeBlocks"=>50,

+"doBlockQuotes"=>60,

+ );

+

+functionrunBlockGamut($text) {

+#

+# Run block gamut tranformations.

+#

+# We need to escape raw HTML in Markdown source before doing anything

+# else. This need to be done for each block, and not only at the

+# begining in the Markdown function since hashed blocks can be part of

+# list items and could have been indented. Indented blocks would have

+# been seen as a code block in a previous pass of hashHTMLBlocks.

+$text=$this->hashHTMLBlocks($text);

+

+return$this->runBasicBlockGamut($text);

+ }

+

+functionrunBasicBlockGamut($text) {

+#

+# Run block gamut tranformations, without hashing HTML blocks. This is

+# useful when HTML blocks are known to be already hashed, like in the first

+# whole-document pass.

+#

+foreach ($this->block_gamutas$method=>$priority) {

+$text=$this->$method($text);

+ }

+

+# Finally form paragraph and restore hashed blocks.

+$text=$this->formParagraphs($text);

+

+return$text;

+ }

+

+

+functiondoHorizontalRules($text) {

+# Do Horizontal Rules:

+returnpreg_replace(

+'{

+ ^[ ]{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("<hr$this->empty_element_suffix")."\n",

+$text);

+ }

+

+

+var$span_gamut=array(

+#

+# These are all the transformations that occur *within* block-level

+# tags like paragraphs, headers, and list items.

+#

+# 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.

+"doImages"=>10,

+"doAnchors"=>20,

+

+# Make links out of things like `<http://example.com/>`

+# Must come after doAnchors, because you can use < and >

+# delimiters in inline links like [this](<url>).

+"doAutoLinks"=>30,

+"encodeAmpsAndAngles"=>40,

+

+"doItalicsAndBold"=>50,

+"doHardBreaks"=>60,

+ );

+

+functionrunSpanGamut($text) {

+#

+# Run span gamut tranformations.

+#

+foreach ($this->span_gamutas$method=>$priority) {

+$text=$this->$method($text);

+ }

+

+return$text;

+ }

+

+

+functiondoHardBreaks($text) {

+# Do hard breaks:

+returnpreg_replace_callback('/ {2,}\n/',

+array(&$this, '_doHardBreaks_callback'), $text);

+ }

+function_doHardBreaks_callback($matches) {

+return$this->hashPart("<br$this->empty_element_suffix\n");

+ }

+

+

+functiondoAnchors($text) {

+#

+# Turn Markdown link shortcuts into XHTML <a> tags.

+#

+if ($this->in_anchor) return$text;

+$this->in_anchor=true;

+

+#

+# First, handle reference-style links: [link text] [id]

+#

+$text=preg_replace_callback('{

+ ( # wrap whole match in $1

+ \[

+ ('.$this->nested_brackets.') # link text = $2

+ \]

+

+ [ ]? # one optional space

+ (?:\n[ ]*)? # one optional newline followed by spaces

+

+ \[

+ (.*?) # id = $3

+ \]

+ )

+ }xs',

+array(&$this, '_doAnchors_reference_callback'), $text);

+

+#

+# Next, inline-style links: [link text](url "optional title")

+#

+$text=preg_replace_callback('{

+ ( # wrap whole match in $1

+ \[

+ ('.$this->nested_brackets.') # link text = $2

+ \]

+ \( # literal paren

+ [ ]*

+ (?:

+ <(\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

+ \)

+ )

+ }xs',

+array(&$this, '_DoAnchors_inline_callback'), $text);

+

+#

+# Last, handle reference-style shortcuts: [link text]

+# These must come last in case you've also got [link test][1]

+# or [link test](/foo)

+#

+// $text = preg_replace_callback('{

+// ( # wrap whole match in $1

+// \[

+// ([^\[\]]+) # link text = $2; can\'t contain [ or ]

+// \]

+// )

+// }xs',

+// array(&$this, '_doAnchors_reference_callback'), $text);

+

+$this->in_anchor=false;

+return$text;

+ }

+function_doAnchors_reference_callback($matches) {

+$whole_match=$matches[1];

+$link_text=$matches[2];

+$link_id=&$matches[3];

+

+if ($link_id=="") {

+# for shortcut links like [this][] or [this].

+$link_id=$link_text;

+ }

+

+# lower-case and turn embedded newlines into spaces

+$link_id=strtolower($link_id);

+$link_id=preg_replace('{[ ]?\n}', '', $link_id);

+

+if (isset($this->urls[$link_id])) {

+$url=$this->urls[$link_id];

+$url=$this->encodeAmpsAndAngles($url);

+

+$result="<a href=\"$url\"";

+if ( isset( $this->titles[$link_id] ) ) {

+$title=$this->titles[$link_id];

+$title=$this->encodeAmpsAndAngles($title);

+$result.=" title=\"$title\"";

+ }

+

+$link_text=$this->runSpanGamut($link_text);

+$result.=">$link_text</a>";

+$result=$this->hashPart($result);

+ }

+else {

+$result=$whole_match;

+ }

+return$result;

+ }

+function_doAnchors_inline_callback($matches) {

+$whole_match=$matches[1];

+$link_text=$this->runSpanGamut($matches[2]);

+$url=$matches[3] =='' ? $matches[4] : $matches[3];

+$title=&$matches[7];

+

+$url=$this->encodeAmpsAndAngles($url);

+

+$result="<a href=\"$url\"";

+if (isset($title)) {

+$title=str_replace('"', '&quot;', $title);

+$title=$this->encodeAmpsAndAngles($title);

+$result.=" title=\"$title\"";

+ }

+

+$link_text=$this->runSpanGamut($link_text);

+$result.=">$link_text</a>";

+

+return$this->hashPart($result);

+ }

+

+

+functiondoImages($text) {

+#

+# Turn Markdown image shortcuts into <img> tags.

+#

+#

+# First, handle reference-style labeled images: ![alt text][id]

+#

+$text=preg_replace_callback('{

+ ( # wrap whole match in $1

+ !\[

+ ('.$this->nested_brackets.') # alt text = $2

+ \]

+

+ [ ]? # one optional space

+ (?:\n[ ]*)? # one optional newline followed by spaces

+

+ \[

+ (.*?) # id = $3

+ \]

+

+ )

+ }xs',

+array(&$this, '_doImages_reference_callback'), $text);

+

+#

+# Next, handle inline images: ![alt text](url "optional title")

+# Don't forget: encode * and _

+#

+$text=preg_replace_callback('{

+ ( # wrap whole match in $1

+ !\[

+ ('.$this->nested_brackets.') # alt text = $2

+ \]

+ \s? # One optional whitespace character

+ \( # literal paren

+ [ ]*

+ (?:

+ <(\S*)> # src url = $3

+ |

+ ('.$this->nested_url_parenthesis.') # src url = $4

+ )

+ [ ]*

+ ( # $5

+ ([\'"]) # quote char = $6

+ (.*?) # title = $7

+ \6 # matching quote

+ [ ]*

+ )? # title is optional

+ \)

+ )

+ }xs',

+array(&$this, '_doImages_inline_callback'), $text);

+

+return$text;

+ }

+function_doImages_reference_callback($matches) {

+$whole_match=$matches[1];

+$alt_text=$matches[2];

+$link_id=strtolower($matches[3]);

+

+if ($link_id=="") {

+$link_id=strtolower($alt_text); # for shortcut links like ![this][].

+ }

+

+$alt_text=str_replace('"', '&quot;', $alt_text);

+if (isset($this->urls[$link_id])) {

+$url=$this->urls[$link_id];

+$result="<img src=\"$url\" alt=\"$alt_text\"";

+if (isset($this->titles[$link_id])) {

+$title=$this->titles[$link_id];

+$result.=" title=\"$title\"";

+ }

+$result.=$this->empty_element_suffix;

+$result=$this->hashPart($result);

+ }

+else {

+# If there's no such link ID, leave intact:

+$result=$whole_match;

+ }

+

+return$result;

+ }

+function_doImages_inline_callback($matches) {

+$whole_match=$matches[1];

+$alt_text=$matches[2];

+$url=$matches[3] =='' ? $matches[4] : $matches[3];

+$title=&$matches[7];

+

+$alt_text=str_replace('"', '&quot;', $alt_text);

+$result="<img src=\"$url\" alt=\"$alt_text\"";

+if (isset($title)) {

+$title=str_replace('"', '&quot;', $title);

+$result.=" title=\"$title\""; # $title already quoted

+ }

+$result.=$this->empty_element_suffix;

+

+return$this->hashPart($result);

+ }

+

+

+functiondoHeaders($text) {

+# Setext-style headers:

+# Header 1

+# ========

+#

+# Header 2

+# --------

+#

+$text=preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',

+array(&$this, '_doHeaders_callback_setext'), $text);

+

+# atx-style headers:

+# # Header 1

+# ## Header 2

+# ## Header 2 with closing hashes ##

+# ...

+# ###### Header 6

+#

+$text=preg_replace_callback('{

+ ^(\#{1,6}) # $1 = string of #\'s

+ [ ]*

+ (.+?) # $2 = Header text

+ [ ]*

+ \#* # optional closing #\'s (not counted)

+ \n+

+ }xm',

+array(&$this, '_doHeaders_callback_atx'), $text);

+

+return$text;

+ }

+function_doHeaders_callback_setext($matches) {

+$level=$matches[2]{0} =='=' ? 1 : 2;

+$block="<h$level>".$this->runSpanGamut($matches[1])."</h$level>";

+return"\n".$this->hashBlock($block) ."\n\n";

+ }

+function_doHeaders_callback_atx($matches) {

+$level=strlen($matches[1]);

+$block="<h$level>".$this->runSpanGamut($matches[2])."</h$level>";

+return"\n".$this->hashBlock($block) ."\n\n";

+ }

+

+

+functiondoLists($text) {

+#

+# Form HTML ordered (numbered) and unordered (bulleted) lists.

+#

+$less_than_tab=$this->tab_width-1;

+

+# Re-usable patterns to match list item bullets and number markers:

+$marker_ul='[*+-]';

+$marker_ol='\d+[.]';

+$marker_any="(?:$marker_ul|$marker_ol)";

+

+$markers=array($marker_ul, $marker_ol);

+

+foreach ($markersas$marker) {

+# Re-usable pattern to match any entirel ul or ol list:

+$whole_list='

+ ( # $1 = whole list

+ ( # $2

+ [ ]{0,'.$less_than_tab.'}

+ ('.$marker.') # $3 = first list item marker

+ [ ]+

+ )

+ (?s:.+?)

+ ( # $4

+ \z

+ |

+ \n{2,}

+ (?=\S)

+ (?! # Negative lookahead for another list item marker

+ [ ]*

+'.$marker.'[ ]+

+ )

+ )

+ )

+'; // mx

+

+# We use a different prefix before nested lists than top-level lists.

+# See extended comment in _ProcessListItems().

+

+if ($this->list_level) {

+$text=preg_replace_callback('{

+ ^

+'.$whole_list.'

+ }mx',

+array(&$this, '_doLists_callback'), $text);

+ }

+else {

+$text=preg_replace_callback('{

+ (?:(?<=\n)\n|\A\n?) # Must eat the newline

+'.$whole_list.'

+ }mx',

+array(&$this, '_doLists_callback'), $text);

+ }

+ }

+

+return$text;

+ }

+function_doLists_callback($matches) {

+# Re-usable patterns to match list item bullets and number markers:

+$marker_ul='[*+-]';

+$marker_ol='\d+[.]';

+$marker_any="(?:$marker_ul|$marker_ol)";

+

+$list=$matches[1];

+$list_type=preg_match("/$marker_ul/", $matches[3]) ? "ul" : "ol";

+

+$marker_any= ( $list_type=="ul" ? $marker_ul : $marker_ol );

+

+$list.="\n";

+$result=$this->processListItems($list, $marker_any);

+

+$result=$this->hashBlock("<$list_type>\n".$result."</$list_type>");

+return"\n".$result."\n\n";

+ }

+

+var$list_level=0;

+

+functionprocessListItems($list_str, $marker_any) {

+#

+# Process the contents of a single ordered or unordered list, splitting it

+# into individual list items.

+#

+# The $this->list_level global keeps track of when we're inside a list.

+# Each time we enter a list, we increment it; when we leave a list,

+# we decrement. If it's zero, we're not in a list anymore.

+#

+# We do this because when we're not inside a list, we want to treat

+# something like this:

+#

+# I recommend upgrading to version

+# 8. Oops, now this line is treated

+# as a sub-list.

+#

+# As a single paragraph, despite the fact that the second line starts

+# with a digit-period-space sequence.

+#

+# Whereas when we're inside a list (or sub-list), that line will be

+# treated as the start of a sub-list. What a kludge, huh? This is

+# an aspect of Markdown's syntax that's hard to parse perfectly

+# without resorting to mind-reading. Perhaps the solution is to

+# change the syntax rules such that sub-lists must start with a

+# starting cardinal number; e.g. "1." or "a.".

+

+$this->list_level++;

+

+# trim trailing blank lines:

+$list_str=preg_replace("/\n{2,}\\z/", "\n", $list_str);

+

+$list_str=preg_replace_callback('{

+ (\n)? # leading line = $1

+ (^[ ]*) # 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.') [ ]+))

+ }xm',

+array(&$this, '_processListItems_callback'), $list_str);

+

+$this->list_level--;

+return$list_str;

+ }

+function_processListItems_callback($matches) {

+$item=$matches[4];

+$leading_line=&$matches[1];

+$leading_space=&$matches[2];

+$tailing_blank_line=&$matches[5];

+

+if ($leading_line||$tailing_blank_line||

+preg_match('/\n{2,}/', $item))

+ {

+$item=$this->runBlockGamut($this->outdent($item)."\n");

+ }

+else {

+# Recursion for sub-lists:

+$item=$this->doLists($this->outdent($item));

+$item=preg_replace('/\n+$/', '', $item);

+$item=$this->runSpanGamut($item);

+ }

+

+return"<li>".$item."</li>\n";

+ }

+

+

+functiondoCodeBlocks($text) {

+#

+# Process Markdown `<pre><code>` blocks.

+#

+$text=preg_replace_callback('{

+ (?:\n\n|\A)

+ ( # $1 = the code block -- one or more lines, starting with a space/tab

+ (?>

+ [ ]{'.$this->tab_width.'} # Lines must start with a tab or a tab-width of spaces

+ .*\n+

+ )+

+ )

+ ((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc