Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Markdown for Laravel

  • Loading branch information...
commit 10550e3d3e230fbb597ca591b21fb4960e0c8ea5 0 parents
Phill Sparks authored January 27, 2012
7  LICENSE
... ...
@@ -0,0 +1,7 @@
  1
+Copyright (c) 2012 Phill Sparks
  2
+
  3
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
  4
+
  5
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
  6
+
  7
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33  bundle.php
... ...
@@ -0,0 +1,33 @@
  1
+<?php
  2
+
  3
+/**
  4
+ * Markdown for Laravel
  5
+ * 
  6
+ * A simple bundle to provide Markdown functions.
  7
+ * 
  8
+ * <code>
  9
+ *     echo Markdown($text);
  10
+ * </code>
  11
+ * 
  12
+ * @package     Bundles
  13
+ * @subpackage  Markdown
  14
+ * @author      Phill Sparks <me@phills.me.uk>
  15
+ * 
  16
+ * @see  http://github.com/sparksp/laravel-markdown
  17
+ * @see  http://michelf.com/projects/php-markdown/
  18
+ */
  19
+
  20
+
  21
+// Config options for Markdown
  22
+define('MARKDOWN_EMPTY_ELEMENT_SUFFIX', '>');
  23
+define('MARKDOWN_PARSER_CLASS', 'Markdown\\MarkdownLaravel_Parser');
  24
+
  25
+// Map the Markdown classes
  26
+Autoloader::map(array(
  27
+	'Markdown\\View' => __DIR__.DIRECTORY_SEPARATOR.'view'.EXT,
  28
+	'Markdown_Page_Controller' => __DIR__.DIRECTORY_SEPARATOR.'controllers'.DIRECTORY_SEPARATOR.'page'.EXT,
  29
+));
  30
+
  31
+// It's safe to assume that if you've started the bundle you're
  32
+// going to want to use the parser.
  33
+require __DIR__.DIRECTORY_SEPARATOR."parser.php";
29  controllers/page.php
... ...
@@ -0,0 +1,29 @@
  1
+<?php
  2
+
  3
+/**
  4
+ * Markdown Page Controller
  5
+ * 
  6
+ * <code>
  7
+ *     Router::register('GET /(about)', 'markdown::page@show');
  8
+ * </code>
  9
+ * 
  10
+ * @package     Bundles
  11
+ * @subpackage  Markdown
  12
+ * @author      Phill Sparks <me@phills.me.uk>
  13
+ * 
  14
+ * @see  http://github.com/sparksp/laravel-markdown
  15
+ */
  16
+class Markdown_Page_Controller extends Controller {
  17
+
  18
+	/**
  19
+	 * A simple action to show the given slug.
  20
+	 * 
  21
+	 * @param  string  $name  The file name of the markdown view to show
  22
+	 * @return Markdown\View
  23
+	 */
  24
+	public function action_show($name)
  25
+	{
  26
+		return Markdown\View::make($name);
  27
+	}
  28
+
  29
+}
3,013  parser.php
... ...
@@ -0,0 +1,3013 @@
  1
+<?php namespace Markdown;
  2
+
  3
+#
  4
+# Markdown Extra  -  A text-to-HTML conversion tool for web writers
  5
+#
  6
+# PHP Markdown & Extra
  7
+# Copyright (c) 2004-2012 Michel Fortin  
  8
+# <http://michelf.com/projects/php-markdown/>
  9
+#
  10
+# Original Markdown
  11
+# Copyright (c) 2004-2006 John Gruber  
  12
+# <http://daringfireball.net/projects/markdown/>
  13
+#
  14
+
  15
+
  16
+define( 'MARKDOWN_VERSION',  "1.0.1o" ); # Sun 8 Jan 2012
  17
+define( 'MARKDOWNEXTRA_VERSION',  "1.2.5" ); # Sun 8 Jan 2012
  18
+
  19
+
  20
+#
  21
+# Global default settings:
  22
+#
  23
+
  24
+# Change to ">" for HTML output
  25
+@define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX',  " />");
  26
+
  27
+# Define the width of a tab for code blocks.
  28
+@define( 'MARKDOWN_TAB_WIDTH',     4 );
  29
+
  30
+# Optional title attribute for footnote links and backlinks.
  31
+@define( 'MARKDOWN_FN_LINK_TITLE',         "" );
  32
+@define( 'MARKDOWN_FN_BACKLINK_TITLE',     "" );
  33
+
  34
+# Optional class attribute for footnote links and backlinks.
  35
+@define( 'MARKDOWN_FN_LINK_CLASS',         "" );
  36
+@define( 'MARKDOWN_FN_BACKLINK_CLASS',     "" );
  37
+
  38
+
  39
+#
  40
+# WordPress settings:
  41
+#
  42
+
  43
+# Change to false to remove Markdown from posts and/or comments.
  44
+@define( 'MARKDOWN_WP_POSTS',      true );
  45
+@define( 'MARKDOWN_WP_COMMENTS',   true );
  46
+
  47
+
  48
+
  49
+### Standard Function Interface ###
  50
+
  51
+@define( 'MARKDOWN_PARSER_CLASS',  'MarkdownExtra_Parser' );
  52
+
  53
+function Markdown($text) {
  54
+#
  55
+# Initialize the parser and return the result of its transform method.
  56
+#
  57
+	# Setup static parser variable.
  58
+	static $parser;
  59
+	if (!isset($parser)) {
  60
+		$parser_class = MARKDOWN_PARSER_CLASS;
  61
+		$parser = new $parser_class;
  62
+	}
  63
+
  64
+	# Transform text using parser.
  65
+	return $parser->transform($text);
  66
+}
  67
+
  68
+
  69
+### WordPress Plugin Interface ###
  70
+
  71
+/*
  72
+Plugin Name: Markdown Extra
  73
+Plugin URI: http://michelf.com/projects/php-markdown/
  74
+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://michelf.com/projects/php-markdown/">More...</a>
  75
+Version: 1.2.5
  76
+Author: Michel Fortin
  77
+Author URI: http://michelf.com/
  78
+*/
  79
+
  80
+if (isset($wp_version)) {
  81
+	# More details about how it works here:
  82
+	# <http://michelf.com/weblog/2005/wordpress-text-flow-vs-markdown/>
  83
+	
  84
+	# Post content and excerpts
  85
+	# - Remove WordPress paragraph generator.
  86
+	# - Run Markdown on excerpt, then remove all tags.
  87
+	# - Add paragraph tag around the excerpt, but remove it for the excerpt rss.
  88
+	if (MARKDOWN_WP_POSTS) {
  89
+		remove_filter('the_content',     'wpautop');
  90
+        remove_filter('the_content_rss', 'wpautop');
  91
+		remove_filter('the_excerpt',     'wpautop');
  92
+		add_filter('the_content',     'mdwp_MarkdownPost', 6);
  93
+        add_filter('the_content_rss', 'mdwp_MarkdownPost', 6);
  94
+		add_filter('get_the_excerpt', 'mdwp_MarkdownPost', 6);
  95
+		add_filter('get_the_excerpt', 'trim', 7);
  96
+		add_filter('the_excerpt',     'mdwp_add_p');
  97
+		add_filter('the_excerpt_rss', 'mdwp_strip_p');
  98
+		
  99
+		remove_filter('content_save_pre',  'balanceTags', 50);
  100
+		remove_filter('excerpt_save_pre',  'balanceTags', 50);
  101
+		add_filter('the_content',  	  'balanceTags', 50);
  102
+		add_filter('get_the_excerpt', 'balanceTags', 9);
  103
+	}
  104
+	
  105
+	# Add a footnote id prefix to posts when inside a loop.
  106
+	function mdwp_MarkdownPost($text) {
  107
+		static $parser;
  108
+		if (!$parser) {
  109
+			$parser_class = MARKDOWN_PARSER_CLASS;
  110
+			$parser = new $parser_class;
  111
+		}
  112
+		if (is_single() || is_page() || is_feed()) {
  113
+			$parser->fn_id_prefix = "";
  114
+		} else {
  115
+			$parser->fn_id_prefix = get_the_ID() . ".";
  116
+		}
  117
+		return $parser->transform($text);
  118
+	}
  119
+	
  120
+	# Comments
  121
+	# - Remove WordPress paragraph generator.
  122
+	# - Remove WordPress auto-link generator.
  123
+	# - Scramble important tags before passing them to the kses filter.
  124
+	# - Run Markdown on excerpt then remove paragraph tags.
  125
+	if (MARKDOWN_WP_COMMENTS) {
  126
+		remove_filter('comment_text', 'wpautop', 30);
  127
+		remove_filter('comment_text', 'make_clickable');
  128
+		add_filter('pre_comment_content', 'Markdown', 6);
  129
+		add_filter('pre_comment_content', 'mdwp_hide_tags', 8);
  130
+		add_filter('pre_comment_content', 'mdwp_show_tags', 12);
  131
+		add_filter('get_comment_text',    'Markdown', 6);
  132
+		add_filter('get_comment_excerpt', 'Markdown', 6);
  133
+		add_filter('get_comment_excerpt', 'mdwp_strip_p', 7);
  134
+	
  135
+		global $mdwp_hidden_tags, $mdwp_placeholders;
  136
+		$mdwp_hidden_tags = explode(' ',
  137
+			'<p> </p> <pre> </pre> <ol> </ol> <ul> </ul> <li> </li>');
  138
+		$mdwp_placeholders = explode(' ', str_rot13(
  139
+			'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR '.
  140
+			'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli'));
  141
+	}
  142
+	
  143
+	function mdwp_add_p($text) {
  144
+		if (!preg_match('{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text)) {
  145
+			$text = '<p>'.$text.'</p>';
  146
+			$text = preg_replace('{\n{2,}}', "</p>\n\n<p>", $text);
  147
+		}
  148
+		return $text;
  149
+	}
  150
+	
  151
+	function mdwp_strip_p($t) { return preg_replace('{</?p>}i', '', $t); }
  152
+
  153
+	function mdwp_hide_tags($text) {
  154
+		global $mdwp_hidden_tags, $mdwp_placeholders;
  155
+		return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text);
  156
+	}
  157
+	function mdwp_show_tags($text) {
  158
+		global $mdwp_hidden_tags, $mdwp_placeholders;
  159
+		return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text);
  160
+	}
  161
+}
  162
+
  163
+
  164
+### bBlog Plugin Info ###
  165
+
  166
+function identify_modifier_markdown() {
  167
+	return array(
  168
+		'name' => 'markdown',
  169
+		'type' => 'modifier',
  170
+		'nicename' => 'PHP Markdown Extra',
  171
+		'description' => 'A text-to-HTML conversion tool for web writers',
  172
+		'authors' => 'Michel Fortin and John Gruber',
  173
+		'licence' => 'GPL',
  174
+		'version' => MARKDOWNEXTRA_VERSION,
  175
+		'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://michelf.com/projects/php-markdown/">More...</a>',
  176
+		);
  177
+}
  178
+
  179
+
  180
+### Smarty Modifier Interface ###
  181
+
  182
+function smarty_modifier_markdown($text) {
  183
+	return Markdown($text);
  184
+}
  185
+
  186
+
  187
+### Textile Compatibility Mode ###
  188
+
  189
+# Rename this file to "classTextile.php" and it can replace Textile everywhere.
  190
+
  191
+if (strcasecmp(substr(__FILE__, -16), "classTextile.php") == 0) {
  192
+	# Try to include PHP SmartyPants. Should be in the same directory.
  193
+	@include_once 'smartypants.php';
  194
+	# Fake Textile class. It calls Markdown instead.
  195
+	class Textile {
  196
+		function TextileThis($text, $lite='', $encode='') {
  197
+			if ($lite == '' && $encode == '')    $text = Markdown($text);
  198
+			if (function_exists('SmartyPants'))  $text = SmartyPants($text);
  199
+			return $text;
  200
+		}
  201
+		# Fake restricted version: restrictions are not supported for now.
  202
+		function TextileRestricted($text, $lite='', $noimage='') {
  203
+			return $this->TextileThis($text, $lite);
  204
+		}
  205
+		# Workaround to ensure compatibility with TextPattern 4.0.3.
  206
+		function blockLite($text) { return $text; }
  207
+	}
  208
+}
  209
+
  210
+
  211
+
  212
+#
  213
+# Markdown Parser Class
  214
+#
  215
+
  216
+class Markdown_Parser {
  217
+
  218
+	# Regex to match balanced [brackets].
  219
+	# Needed to insert a maximum bracked depth while converting to PHP.
  220
+	var $nested_brackets_depth = 6;
  221
+	var $nested_brackets_re;
  222
+	
  223
+	var $nested_url_parenthesis_depth = 4;
  224
+	var $nested_url_parenthesis_re;
  225
+
  226
+	# Table of hash values for escaped characters:
  227
+	var $escape_chars = '\`*_{}[]()>#+-.!';
  228
+	var $escape_chars_re;
  229
+
  230
+	# Change to ">" for HTML output.
  231
+	var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
  232
+	var $tab_width = MARKDOWN_TAB_WIDTH;
  233
+	
  234
+	# Change to `true` to disallow markup or entities.
  235
+	var $no_markup = false;
  236
+	var $no_entities = false;
  237
+	
  238
+	# Predefined urls and titles for reference links and images.
  239
+	var $predef_urls = array();
  240
+	var $predef_titles = array();
  241
+
  242
+
  243
+	function __construct() {
  244
+	#
  245
+	# Constructor function. Initialize appropriate member variables.
  246
+	#
  247
+		$this->_initDetab();
  248
+		$this->prepareItalicsAndBold();
  249
+	
  250
+		$this->nested_brackets_re = 
  251
+			str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth).
  252
+			str_repeat('\])*', $this->nested_brackets_depth);
  253
+	
  254
+		$this->nested_url_parenthesis_re = 
  255
+			str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth).
  256
+			str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth);
  257
+		
  258
+		$this->escape_chars_re = '['.preg_quote($this->escape_chars).']';
  259
+		
  260
+		# Sort document, block, and span gamut in ascendent priority order.
  261
+		asort($this->document_gamut);
  262
+		asort($this->block_gamut);
  263
+		asort($this->span_gamut);
  264
+	}
  265
+
  266
+
  267
+	# Internal hashes used during transformation.
  268
+	var $urls = array();
  269
+	var $titles = array();
  270
+	var $html_hashes = array();
  271
+	
  272
+	# Status flag to avoid invalid nesting.
  273
+	var $in_anchor = false;
  274
+	
  275
+	
  276
+	function setup() {
  277
+	#
  278
+	# Called before the transformation process starts to setup parser 
  279
+	# states.
  280
+	#
  281
+		# Clear global hashes.
  282
+		$this->urls = $this->predef_urls;
  283
+		$this->titles = $this->predef_titles;
  284
+		$this->html_hashes = array();
  285
+		
  286
+		$in_anchor = false;
  287
+	}
  288
+	
  289
+	function teardown() {
  290
+	#
  291
+	# Called after the transformation process to clear any variable 
  292
+	# which may be taking up memory unnecessarly.
  293
+	#
  294
+		$this->urls = array();
  295
+		$this->titles = array();
  296
+		$this->html_hashes = array();
  297
+	}
  298
+
  299
+
  300
+	function transform($text) {
  301
+	#
  302
+	# Main function. Performs some preprocessing on the input text
  303
+	# and pass it through the document gamut.
  304
+	#
  305
+		$this->setup();
  306
+	
  307
+		# Remove UTF-8 BOM and marker character in input, if present.
  308
+		$text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text);
  309
+
  310
+		# Standardize line endings:
  311
+		#   DOS to Unix and Mac to Unix
  312
+		$text = preg_replace('{\r\n?}', "\n", $text);
  313
+
  314
+		# Make sure $text ends with a couple of newlines:
  315
+		$text .= "\n\n";
  316
+
  317
+		# Convert all tabs to spaces.
  318
+		$text = $this->detab($text);
  319
+
  320
+		# Turn block-level HTML blocks into hash entries
  321
+		$text = $this->hashHTMLBlocks($text);
  322
+
  323
+		# Strip any lines consisting only of spaces and tabs.
  324
+		# This makes subsequent regexen easier to write, because we can
  325
+		# match consecutive blank lines with /\n+/ instead of something
  326
+		# contorted like /[ ]*\n+/ .
  327
+		$text = preg_replace('/^[ ]+$/m', '', $text);
  328
+
  329
+		# Run document gamut methods.
  330
+		foreach ($this->document_gamut as $method => $priority) {
  331
+			$text = $this->$method($text);
  332
+		}
  333
+		
  334
+		$this->teardown();
  335
+
  336
+		return $text . "\n";
  337
+	}
  338
+	
  339
+	var $document_gamut = array(
  340
+		# Strip link definitions, store in hashes.
  341
+		"stripLinkDefinitions" => 20,
  342
+		
  343
+		"runBasicBlockGamut"   => 30,
  344
+		);
  345
+
  346
+
  347
+	function stripLinkDefinitions($text) {
  348
+	#
  349
+	# Strips link definitions from text, stores the URLs and titles in
  350
+	# hash references.
  351
+	#
  352
+		$less_than_tab = $this->tab_width - 1;
  353
+
  354
+		# Link defs are in the form: ^[id]: url "optional title"
  355
+		$text = preg_replace_callback('{
  356
+							^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?:	# id = $1
  357
+							  [ ]*
  358
+							  \n?				# maybe *one* newline
  359
+							  [ ]*
  360
+							(?:
  361
+							  <(.+?)>			# url = $2
  362
+							|
  363
+							  (\S+?)			# url = $3
  364
+							)
  365
+							  [ ]*
  366
+							  \n?				# maybe one newline
  367
+							  [ ]*
  368
+							(?:
  369
+								(?<=\s)			# lookbehind for whitespace
  370
+								["(]
  371
+								(.*?)			# title = $4
  372
+								[")]
  373
+								[ ]*
  374
+							)?	# title is optional
  375
+							(?:\n+|\Z)
  376
+			}xm',
  377
+			array(&$this, '_stripLinkDefinitions_callback'),
  378
+			$text);
  379
+		return $text;
  380
+	}
  381
+	function _stripLinkDefinitions_callback($matches) {
  382
+		$link_id = strtolower($matches[1]);
  383
+		$url = $matches[2] == '' ? $matches[3] : $matches[2];
  384
+		$this->urls[$link_id] = $url;
  385
+		$this->titles[$link_id] =& $matches[4];
  386
+		return ''; # String that will replace the block
  387
+	}
  388
+
  389
+
  390
+	function hashHTMLBlocks($text) {
  391
+		if ($this->no_markup)  return $text;
  392
+
  393
+		$less_than_tab = $this->tab_width - 1;
  394
+
  395
+		# Hashify HTML blocks:
  396
+		# We only want to do this for block-level HTML tags, such as headers,
  397
+		# lists, and tables. That's because we still want to wrap <p>s around
  398
+		# "paragraphs" that are wrapped in non-block-level tags, such as anchors,
  399
+		# phrase emphasis, and spans. The list of tags we're looking for is
  400
+		# hard-coded:
  401
+		#
  402
+		# *  List "a" is made of tags which can be both inline or block-level.
  403
+		#    These will be treated block-level when the start tag is alone on 
  404
+		#    its line, otherwise they're not matched here and will be taken as 
  405
+		#    inline later.
  406
+		# *  List "b" is made of tags which are always block-level;
  407
+		#
  408
+		$block_tags_a_re = 'ins|del';
  409
+		$block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'.
  410
+						   'script|noscript|form|fieldset|iframe|math';
  411
+
  412
+		# Regular expression for the content of a block tag.
  413
+		$nested_tags_level = 4;
  414
+		$attr = '
  415
+			(?>				# optional tag attributes
  416
+			  \s			# starts with whitespace
  417
+			  (?>
  418
+				[^>"/]+		# text outside quotes
  419
+			  |
  420
+				/+(?!>)		# slash not followed by ">"
  421
+			  |
  422
+				"[^"]*"		# text inside double quotes (tolerate ">")
  423
+			  |
  424
+				\'[^\']*\'	# text inside single quotes (tolerate ">")
  425
+			  )*
  426
+			)?	
  427
+			';
  428
+		$content =
  429
+			str_repeat('
  430
+				(?>
  431
+				  [^<]+			# content without tag
  432
+				|
  433
+				  <\2			# nested opening tag
  434
+					'.$attr.'	# attributes
  435
+					(?>
  436
+					  />
  437
+					|
  438
+					  >', $nested_tags_level).	# end of opening tag
  439
+					  '.*?'.					# last level nested tag content
  440
+			str_repeat('
  441
+					  </\2\s*>	# closing nested tag
  442
+					)
  443
+				  |				
  444
+					<(?!/\2\s*>	# other tags with a different name
  445
+				  )
  446
+				)*',
  447
+				$nested_tags_level);
  448
+		$content2 = str_replace('\2', '\3', $content);
  449
+
  450
+		# First, look for nested blocks, e.g.:
  451
+		# 	<div>
  452
+		# 		<div>
  453
+		# 		tags for inner block must be indented.
  454
+		# 		</div>
  455
+		# 	</div>
  456
+		#
  457
+		# The outermost tags must start at the left margin for this to match, and
  458
+		# the inner nested divs must be indented.
  459
+		# We need to do this before the next, more liberal match, because the next
  460
+		# match will start at the first `<div>` and stop at the first `</div>`.
  461
+		$text = preg_replace_callback('{(?>
  462
+			(?>
  463
+				(?<=\n\n)		# Starting after a blank line
  464
+				|				# or
  465
+				\A\n?			# the beginning of the doc
  466
+			)
  467
+			(						# save in $1
  468
+
  469
+			  # Match from `\n<tag>` to `</tag>\n`, handling nested tags 
  470
+			  # in between.
  471
+					
  472
+						[ ]{0,'.$less_than_tab.'}
  473
+						<('.$block_tags_b_re.')# start tag = $2
  474
+						'.$attr.'>			# attributes followed by > and \n
  475
+						'.$content.'		# content, support nesting
  476
+						</\2>				# the matching end tag
  477
+						[ ]*				# trailing spaces/tabs
  478
+						(?=\n+|\Z)	# followed by a newline or end of document
  479
+
  480
+			| # Special version for tags of group a.
  481
+
  482
+						[ ]{0,'.$less_than_tab.'}
  483
+						<('.$block_tags_a_re.')# start tag = $3
  484
+						'.$attr.'>[ ]*\n	# attributes followed by >
  485
+						'.$content2.'		# content, support nesting
  486
+						</\3>				# the matching end tag
  487
+						[ ]*				# trailing spaces/tabs
  488
+						(?=\n+|\Z)	# followed by a newline or end of document
  489
+					
  490
+			| # Special case just for <hr />. It was easier to make a special 
  491
+			  # case than to make the other regex more complicated.
  492
+			
  493
+						[ ]{0,'.$less_than_tab.'}
  494
+						<(hr)				# start tag = $2
  495
+						'.$attr.'			# attributes
  496
+						/?>					# the matching end tag
  497
+						[ ]*
  498
+						(?=\n{2,}|\Z)		# followed by a blank line or end of document
  499
+			
  500
+			| # Special case for standalone HTML comments:
  501
+			
  502
+					[ ]{0,'.$less_than_tab.'}
  503
+					(?s:
  504
+						<!-- .*? -->
  505
+					)
  506
+					[ ]*
  507
+					(?=\n{2,}|\Z)		# followed by a blank line or end of document
  508
+			
  509
+			| # PHP and ASP-style processor instructions (<? and <%)
  510
+			
  511
+					[ ]{0,'.$less_than_tab.'}
  512
+					(?s:
  513
+						<([?%])			# $2
  514
+						.*?
  515
+						\2>
  516
+					)
  517
+					[ ]*
  518
+					(?=\n{2,}|\Z)		# followed by a blank line or end of document
  519
+					
  520
+			)
  521
+			)}Sxmi',
  522
+			array(&$this, '_hashHTMLBlocks_callback'),
  523
+			$text);
  524
+
  525
+		return $text;
  526
+	}
  527
+	function _hashHTMLBlocks_callback($matches) {
  528
+		$text = $matches[1];
  529
+		$key  = $this->hashBlock($text);
  530
+		return "\n\n$key\n\n";
  531
+	}
  532
+	
  533
+	
  534
+	function hashPart($text, $boundary = 'X') {
  535
+	#
  536
+	# Called whenever a tag must be hashed when a function insert an atomic 
  537
+	# element in the text stream. Passing $text to through this function gives
  538
+	# a unique text-token which will be reverted back when calling unhash.
  539
+	#
  540
+	# The $boundary argument specify what character should be used to surround
  541
+	# the token. By convension, "B" is used for block elements that needs not
  542
+	# to be wrapped into paragraph tags at the end, ":" is used for elements
  543
+	# that are word separators and "X" is used in the general case.
  544
+	#
  545
+		# Swap back any tag hash found in $text so we do not have to `unhash`
  546
+		# multiple times at the end.
  547
+		$text = $this->unhash($text);
  548
+		
  549
+		# Then hash the block.
  550
+		static $i = 0;
  551
+		$key = "$boundary\x1A" . ++$i . $boundary;
  552
+		$this->html_hashes[$key] = $text;
  553
+		return $key; # String that will replace the tag.
  554
+	}
  555
+
  556
+
  557
+	function hashBlock($text) {
  558
+	#
  559
+	# Shortcut function for hashPart with block-level boundaries.
  560
+	#
  561
+		return $this->hashPart($text, 'B');
  562
+	}
  563
+
  564
+
  565
+	var $block_gamut = array(
  566
+	#
  567
+	# These are all the transformations that form block-level
  568
+	# tags like paragraphs, headers, and list items.
  569
+	#
  570
+		"doHeaders"         => 10,
  571
+		"doHorizontalRules" => 20,
  572
+		
  573
+		"doLists"           => 40,
  574
+		"doCodeBlocks"      => 50,
  575
+		"doBlockQuotes"     => 60,
  576
+		);
  577
+
  578
+	function runBlockGamut($text) {
  579
+	#
  580
+	# Run block gamut tranformations.
  581
+	#
  582
+		# We need to escape raw HTML in Markdown source before doing anything 
  583
+		# else. This need to be done for each block, and not only at the 
  584
+		# begining in the Markdown function since hashed blocks can be part of
  585
+		# list items and could have been indented. Indented blocks would have 
  586
+		# been seen as a code block in a previous pass of hashHTMLBlocks.
  587
+		$text = $this->hashHTMLBlocks($text);
  588
+		
  589
+		return $this->runBasicBlockGamut($text);
  590
+	}
  591
+	
  592
+	function runBasicBlockGamut($text) {
  593
+	#
  594
+	# Run block gamut tranformations, without hashing HTML blocks. This is 
  595
+	# useful when HTML blocks are known to be already hashed, like in the first
  596
+	# whole-document pass.
  597
+	#
  598
+		foreach ($this->block_gamut as $method => $priority) {
  599
+			$text = $this->$method($text);
  600
+		}
  601
+		
  602
+		# Finally form paragraph and restore hashed blocks.
  603
+		$text = $this->formParagraphs($text);
  604
+
  605
+		return $text;
  606
+	}
  607
+	
  608
+	
  609
+	function doHorizontalRules($text) {
  610
+		# Do Horizontal Rules:
  611
+		return preg_replace(
  612
+			'{
  613
+				^[ ]{0,3}	# Leading space
  614
+				([-*_])		# $1: First marker
  615
+				(?>			# Repeated marker group
  616
+					[ ]{0,2}	# Zero, one, or two spaces.
  617
+					\1			# Marker character
  618
+				){2,}		# Group repeated at least twice
  619
+				[ ]*		# Tailing spaces
  620
+				$			# End of line.
  621
+			}mx',
  622
+			"\n".$this->hashBlock("<hr$this->empty_element_suffix")."\n", 
  623
+			$text);
  624
+	}
  625
+
  626
+
  627
+	var $span_gamut = array(
  628
+	#
  629
+	# These are all the transformations that occur *within* block-level
  630
+	# tags like paragraphs, headers, and list items.
  631
+	#
  632
+		# Process character escapes, code spans, and inline HTML
  633
+		# in one shot.
  634
+		"parseSpan"           => -30,
  635
+
  636
+		# Process anchor and image tags. Images must come first,
  637
+		# because ![foo][f] looks like an anchor.
  638
+		"doImages"            =>  10,
  639
+		"doAnchors"           =>  20,
  640
+		
  641
+		# Make links out of things like `<http://example.com/>`
  642
+		# Must come after doAnchors, because you can use < and >
  643
+		# delimiters in inline links like [this](<url>).
  644
+		"doAutoLinks"         =>  30,
  645
+		"encodeAmpsAndAngles" =>  40,
  646
+
  647
+		"doItalicsAndBold"    =>  50,
  648
+		"doHardBreaks"        =>  60,
  649
+		);
  650
+
  651
+	function runSpanGamut($text) {
  652
+	#
  653
+	# Run span gamut tranformations.
  654
+	#
  655
+		foreach ($this->span_gamut as $method => $priority) {
  656
+			$text = $this->$method($text);
  657
+		}
  658
+
  659
+		return $text;
  660
+	}
  661
+	
  662
+	
  663
+	function doHardBreaks($text) {
  664
+		# Do hard breaks:
  665
+		return preg_replace_callback('/ {2,}\n/', 
  666
+			array(&$this, '_doHardBreaks_callback'), $text);
  667
+	}
  668
+	function _doHardBreaks_callback($matches) {
  669
+		return $this->hashPart("<br$this->empty_element_suffix\n");
  670
+	}
  671
+
  672
+
  673
+	function doAnchors($text) {
  674
+	#
  675
+	# Turn Markdown link shortcuts into XHTML <a> tags.
  676
+	#
  677
+		if ($this->in_anchor) return $text;
  678
+		$this->in_anchor = true;
  679
+		
  680
+		#
  681
+		# First, handle reference-style links: [link text] [id]
  682
+		#
  683
+		$text = preg_replace_callback('{
  684
+			(					# wrap whole match in $1
  685
+			  \[
  686
+				('.$this->nested_brackets_re.')	# link text = $2
  687
+			  \]
  688
+
  689
+			  [ ]?				# one optional space
  690
+			  (?:\n[ ]*)?		# one optional newline followed by spaces
  691
+
  692
+			  \[
  693
+				(.*?)		# id = $3
  694
+			  \]
  695
+			)
  696
+			}xs',
  697
+			array(&$this, '_doAnchors_reference_callback'), $text);
  698
+
  699
+		#
  700
+		# Next, inline-style links: [link text](url "optional title")
  701
+		#
  702
+		$text = preg_replace_callback('{
  703
+			(				# wrap whole match in $1
  704
+			  \[
  705
+				('.$this->nested_brackets_re.')	# link text = $2
  706
+			  \]
  707
+			  \(			# literal paren
  708
+				[ \n]*
  709
+				(?:
  710
+					<(.+?)>	# href = $3
  711
+				|
  712
+					('.$this->nested_url_parenthesis_re.')	# href = $4
  713
+				)
  714
+				[ \n]*
  715
+				(			# $5
  716
+				  ([\'"])	# quote char = $6
  717
+				  (.*?)		# Title = $7
  718
+				  \6		# matching quote
  719
+				  [ \n]*	# ignore any spaces/tabs between closing quote and )
  720
+				)?			# title is optional
  721
+			  \)
  722
+			)
  723
+			}xs',
  724
+			array(&$this, '_doAnchors_inline_callback'), $text);
  725
+
  726
+		#
  727
+		# Last, handle reference-style shortcuts: [link text]
  728
+		# These must come last in case you've also got [link text][1]
  729
+		# or [link text](/foo)
  730
+		#
  731
+		$text = preg_replace_callback('{
  732
+			(					# wrap whole match in $1
  733
+			  \[
  734
+				([^\[\]]+)		# link text = $2; can\'t contain [ or ]
  735
+			  \]
  736
+			)
  737
+			}xs',
  738
+			array(&$this, '_doAnchors_reference_callback'), $text);
  739
+
  740
+		$this->in_anchor = false;
  741
+		return $text;
  742
+	}
  743
+	function _doAnchors_reference_callback($matches) {
  744
+		$whole_match =  $matches[1];
  745
+		$link_text   =  $matches[2];
  746
+		$link_id     =& $matches[3];
  747
+
  748
+		if ($link_id == "") {
  749
+			# for shortcut links like [this][] or [this].
  750
+			$link_id = $link_text;
  751
+		}
  752
+		
  753
+		# lower-case and turn embedded newlines into spaces
  754
+		$link_id = strtolower($link_id);
  755
+		$link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
  756
+
  757
+		if (isset($this->urls[$link_id])) {
  758
+			$url = $this->urls[$link_id];
  759
+			$url = $this->encodeAttribute($url);
  760
+			
  761
+			$result = "<a href=\"$url\"";
  762
+			if ( isset( $this->titles[$link_id] ) ) {
  763
+				$title = $this->titles[$link_id];
  764
+				$title = $this->encodeAttribute($title);
  765
+				$result .=  " title=\"$title\"";
  766
+			}
  767
+		
  768
+			$link_text = $this->runSpanGamut($link_text);
  769
+			$result .= ">$link_text</a>";
  770
+			$result = $this->hashPart($result);
  771
+		}
  772
+		else {
  773
+			$result = $whole_match;
  774
+		}
  775
+		return $result;
  776
+	}
  777
+	function _doAnchors_inline_callback($matches) {
  778
+		$whole_match	=  $matches[1];
  779
+		$link_text		=  $this->runSpanGamut($matches[2]);
  780
+		$url			=  $matches[3] == '' ? $matches[4] : $matches[3];
  781
+		$title			=& $matches[7];
  782
+
  783
+		$url = $this->encodeAttribute($url);
  784
+
  785
+		$result = "<a href=\"$url\"";
  786
+		if (isset($title)) {
  787
+			$title = $this->encodeAttribute($title);
  788
+			$result .=  " title=\"$title\"";
  789
+		}
  790
+		
  791
+		$link_text = $this->runSpanGamut($link_text);
  792
+		$result .= ">$link_text</a>";
  793
+
  794
+		return $this->hashPart($result);
  795
+	}
  796
+
  797
+
  798
+	function doImages($text) {
  799
+	#
  800
+	# Turn Markdown image shortcuts into <img> tags.
  801
+	#
  802
+		#
  803
+		# First, handle reference-style labeled images: ![alt text][id]
  804
+		#
  805
+		$text = preg_replace_callback('{
  806
+			(				# wrap whole match in $1
  807
+			  !\[
  808
+				('.$this->nested_brackets_re.')		# alt text = $2
  809
+			  \]
  810
+
  811
+			  [ ]?				# one optional space
  812
+			  (?:\n[ ]*)?		# one optional newline followed by spaces
  813
+
  814
+			  \[
  815
+				(.*?)		# id = $3
  816
+			  \]
  817
+
  818
+			)
  819
+			}xs', 
  820
+			array(&$this, '_doImages_reference_callback'), $text);
  821
+
  822
+		#
  823
+		# Next, handle inline images:  ![alt text](url "optional title")
  824
+		# Don't forget: encode * and _
  825
+		#
  826
+		$text = preg_replace_callback('{
  827
+			(				# wrap whole match in $1
  828
+			  !\[
  829
+				('.$this->nested_brackets_re.')		# alt text = $2
  830
+			  \]
  831
+			  \s?			# One optional whitespace character
  832
+			  \(			# literal paren
  833
+				[ \n]*
  834
+				(?:
  835
+					<(\S*)>	# src url = $3
  836
+				|
  837
+					('.$this->nested_url_parenthesis_re.')	# src url = $4
  838
+				)
  839
+				[ \n]*
  840
+				(			# $5
  841
+				  ([\'"])	# quote char = $6
  842
+				  (.*?)		# title = $7
  843
+				  \6		# matching quote
  844
+				  [ \n]*
  845
+				)?			# title is optional
  846
+			  \)
  847
+			)
  848
+			}xs',
  849
+			array(&$this, '_doImages_inline_callback'), $text);
  850
+
  851
+		return $text;
  852
+	}
  853
+	function _doImages_reference_callback($matches) {
  854
+		$whole_match = $matches[1];
  855
+		$alt_text    = $matches[2];
  856
+		$link_id     = strtolower($matches[3]);
  857
+
  858
+		if ($link_id == "") {
  859
+			$link_id = strtolower($alt_text); # for shortcut links like ![this][].
  860
+		}
  861
+
  862
+		$alt_text = $this->encodeAttribute($alt_text);
  863
+		if (isset($this->urls[$link_id])) {
  864
+			$url = $this->encodeAttribute($this->urls[$link_id]);
  865
+			$result = "<img src=\"$url\" alt=\"$alt_text\"";
  866
+			if (isset($this->titles[$link_id])) {
  867
+				$title = $this->titles[$link_id];
  868
+				$title = $this->encodeAttribute($title);
  869
+				$result .=  " title=\"$title\"";
  870
+			}
  871
+			$result .= $this->empty_element_suffix;
  872
+			$result = $this->hashPart($result);
  873
+		}
  874
+		else {
  875
+			# If there's no such link ID, leave intact:
  876
+			$result = $whole_match;
  877
+		}
  878
+
  879
+		return $result;
  880
+	}
  881
+	function _doImages_inline_callback($matches) {
  882
+		$whole_match	= $matches[1];
  883
+		$alt_text		= $matches[2];
  884
+		$url			= $matches[3] == '' ? $matches[4] : $matches[3];
  885
+		$title			=& $matches[7];
  886
+
  887
+		$alt_text = $this->encodeAttribute($alt_text);
  888
+		$url = $this->encodeAttribute($url);
  889
+		$result = "<img src=\"$url\" alt=\"$alt_text\"";
  890
+		if (isset($title)) {
  891
+			$title = $this->encodeAttribute($title);
  892
+			$result .=  " title=\"$title\""; # $title already quoted
  893
+		}
  894
+		$result .= $this->empty_element_suffix;
  895
+
  896
+		return $this->hashPart($result);
  897
+	}
  898
+
  899
+
  900
+	function doHeaders($text) {
  901
+		# Setext-style headers:
  902
+		#	  Header 1
  903
+		#	  ========
  904
+		#  
  905
+		#	  Header 2
  906
+		#	  --------
  907
+		#
  908
+		$text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
  909
+			array(&$this, '_doHeaders_callback_setext'), $text);
  910
+
  911
+		# atx-style headers:
  912
+		#	# Header 1
  913
+		#	## Header 2
  914
+		#	## Header 2 with closing hashes ##
  915
+		#	...
  916
+		#	###### Header 6
  917
+		#
  918
+		$text = preg_replace_callback('{
  919
+				^(\#{1,6})	# $1 = string of #\'s
  920
+				[ ]*
  921
+				(.+?)		# $2 = Header text
  922
+				[ ]*
  923
+				\#*			# optional closing #\'s (not counted)
  924
+				\n+
  925
+			}xm',
  926
+			array(&$this, '_doHeaders_callback_atx'), $text);
  927
+
  928
+		return $text;
  929
+	}
  930
+	function _doHeaders_callback_setext($matches) {
  931
+		# Terrible hack to check we haven't found an empty list item.
  932
+		if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1]))
  933
+			return $matches[0];
  934
+		
  935
+		$level = $matches[2]{0} == '=' ? 1 : 2;
  936
+		$block = "<h$level>".$this->runSpanGamut($matches[1])."</h$level>";
  937
+		return "\n" . $this->hashBlock($block) . "\n\n";
  938
+	}
  939
+	function _doHeaders_callback_atx($matches) {
  940
+		$level = strlen($matches[1]);
  941
+		$block = "<h$level>".$this->runSpanGamut($matches[2])."</h$level>";
  942
+		return "\n" . $this->hashBlock($block) . "\n\n";
  943
+	}
  944
+
  945
+
  946
+	function doLists($text) {
  947
+	#
  948
+	# Form HTML ordered (numbered) and unordered (bulleted) lists.
  949
+	#
  950
+		$less_than_tab = $this->tab_width - 1;
  951
+
  952
+		# Re-usable patterns to match list item bullets and number markers:
  953
+		$marker_ul_re  = '[*+-]';
  954
+		$marker_ol_re  = '\d+[\.]';
  955
+		$marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
  956
+
  957
+		$markers_relist = array(
  958
+			$marker_ul_re => $marker_ol_re,
  959
+			$marker_ol_re => $marker_ul_re,
  960
+			);
  961
+
  962
+		foreach ($markers_relist as $marker_re => $other_marker_re) {
  963
+			# Re-usable pattern to match any entirel ul or ol list:
  964
+			$whole_list_re = '
  965
+				(								# $1 = whole list
  966
+				  (								# $2
  967
+					([ ]{0,'.$less_than_tab.'})	# $3 = number of spaces
  968
+					('.$marker_re.')			# $4 = first list item marker
  969
+					[ ]+
  970
+				  )
  971
+				  (?s:.+?)
  972
+				  (								# $5
  973
+					  \z
  974
+					|
  975
+					  \n{2,}
  976
+					  (?=\S)
  977
+					  (?!						# Negative lookahead for another list item marker
  978
+						[ ]*
  979
+						'.$marker_re.'[ ]+
  980
+					  )
  981
+					|
  982
+					  (?=						# Lookahead for another kind of list
  983
+					    \n
  984
+						\3						# Must have the same indentation
  985
+						'.$other_marker_re.'[ ]+
  986
+					  )
  987
+				  )
  988
+				)
  989
+			'; // mx
  990
+			
  991
+			# We use a different prefix before nested lists than top-level lists.
  992
+			# See extended comment in _ProcessListItems().
  993
+		
  994
+			if ($this->list_level) {
  995
+				$text = preg_replace_callback('{
  996
+						^
  997
+						'.$whole_list_re.'
  998
+					}mx',
  999
+					array(&$this, '_doLists_callback'), $text);
  1000
+			}
  1001
+			else {
  1002
+				$text = preg_replace_callback('{
  1003
+						(?:(?<=\n)\n|\A\n?) # Must eat the newline
  1004
+						'.$whole_list_re.'
  1005
+					}mx',
  1006
+					array(&$this, '_doLists_callback'), $text);
  1007
+			}
  1008
+		}
  1009
+
  1010
+		return $text;
  1011
+	}
  1012
+	function _doLists_callback($matches) {
  1013
+		# Re-usable patterns to match list item bullets and number markers:
  1014
+		$marker_ul_re  = '[*+-]';
  1015
+		$marker_ol_re  = '\d+[\.]';
  1016
+		$marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
  1017
+		
  1018
+		$list = $matches[1];
  1019
+		$list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol";
  1020
+		
  1021
+		$marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re );
  1022
+		
  1023
+		$list .= "\n";
  1024
+		$result = $this->processListItems($list, $marker_any_re);
  1025
+		
  1026
+		$result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>");
  1027
+		return "\n". $result ."\n\n";
  1028
+	}
  1029
+
  1030
+	var $list_level = 0;
  1031
+
  1032
+	function processListItems($list_str, $marker_any_re) {
  1033
+	#
  1034
+	#	Process the contents of a single ordered or unordered list, splitting it
  1035
+	#	into individual list items.
  1036
+	#
  1037
+		# The $this->list_level global keeps track of when we're inside a list.
  1038
+		# Each time we enter a list, we increment it; when we leave a list,
  1039
+		# we decrement. If it's zero, we're not in a list anymore.
  1040
+		#
  1041
+		# We do this because when we're not inside a list, we want to treat
  1042
+		# something like this:
  1043
+		#
  1044
+		#		I recommend upgrading to version
  1045
+		#		8. Oops, now this line is treated
  1046
+		#		as a sub-list.
  1047
+		#
  1048
+		# As a single paragraph, despite the fact that the second line starts
  1049
+		# with a digit-period-space sequence.
  1050
+		#
  1051
+		# Whereas when we're inside a list (or sub-list), that line will be
  1052
+		# treated as the start of a sub-list. What a kludge, huh? This is
  1053
+		# an aspect of Markdown's syntax that's hard to parse perfectly
  1054
+		# without resorting to mind-reading. Perhaps the solution is to
  1055
+		# change the syntax rules such that sub-lists must start with a
  1056
+		# starting cardinal number; e.g. "1." or "a.".
  1057
+		
  1058
+		$this->list_level++;
  1059
+
  1060
+		# trim trailing blank lines:
  1061
+		$list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
  1062
+
  1063
+		$list_str = preg_replace_callback('{
  1064
+			(\n)?							# leading line = $1
  1065
+			(^[ ]*)							# leading whitespace = $2
  1066
+			('.$marker_any_re.'				# list marker and space = $3
  1067
+				(?:[ ]+|(?=\n))	# space only required if item is not empty
  1068
+			)
  1069
+			((?s:.*?))						# list item text   = $4
  1070
+			(?:(\n+(?=\n))|\n)				# tailing blank line = $5
  1071
+			(?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n))))
  1072
+			}xm',
  1073
+			array(&$this, '_processListItems_callback'), $list_str);
  1074
+
  1075
+		$this->list_level--;
  1076
+		return $list_str;
  1077
+	}
  1078
+	function _processListItems_callback($matches) {
  1079
+		$item = $matches[4];
  1080
+		$leading_line =& $matches[1];
  1081
+		$leading_space =& $matches[2];
  1082
+		$marker_space = $matches[3];
  1083
+		$tailing_blank_line =& $matches[5];
  1084
+
  1085
+		if ($leading_line || $tailing_blank_line || 
  1086
+			preg_match('/\n{2,}/', $item))
  1087
+		{
  1088
+			# Replace marker with the appropriate whitespace indentation
  1089
+			$item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item;
  1090
+			$item = $this->runBlockGamut($this->outdent($item)."\n");
  1091
+		}
  1092
+		else {
  1093
+			# Recursion for sub-lists:
  1094
+			$item = $this->doLists($this->outdent($item));
  1095
+			$item = preg_replace('/\n+$/', '', $item);
  1096
+			$item = $this->runSpanGamut($item);
  1097
+		}
  1098
+
  1099
+		return "<li>" . $item . "</li>\n";
  1100
+	}
  1101
+
  1102
+
  1103
+	function doCodeBlocks($text) {
  1104
+	#
  1105
+	#	Process Markdown `<pre><code>` blocks.
  1106
+	#
  1107
+		$text = preg_replace_callback('{
  1108
+				(?:\n\n|\A\n?)
  1109
+				(	            # $1 = the code block -- one or more lines, starting with a space/tab
  1110
+				  (?>
  1111
+					[ ]{'.$this->tab_width.'}  # Lines must start with a tab or a tab-width of spaces
  1112
+					.*\n+
  1113
+				  )+
  1114
+				)
  1115
+				((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
  1116
+			}xm',
  1117
+			array(&$this, '_doCodeBlocks_callback'), $text);
  1118
+
  1119
+		return $text;
  1120
+	}
  1121
+	function _doCodeBlocks_callback($matches) {
  1122
+		$codeblock = $matches[1];
  1123
+
  1124
+		$codeblock = $this->outdent($codeblock);
  1125
+		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
  1126
+
  1127
+		# trim leading newlines and trailing newlines
  1128
+		$codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
  1129
+
  1130
+		$codeblock = "<pre><code>$codeblock\n</code></pre>";
  1131
+		return "\n\n".$this->hashBlock($codeblock)."\n\n";
  1132
+	}
  1133
+
  1134
+
  1135
+	function makeCodeSpan($code) {
  1136
+	#
  1137
+	# Create a code span markup for $code. Called from handleSpanToken.
  1138
+	#
  1139
+		$code = htmlspecialchars(trim($code), ENT_NOQUOTES);
  1140
+		return $this->hashPart("<code>$code</code>");
  1141
+	}
  1142
+
  1143
+
  1144
+	var $em_relist = array(
  1145
+		''  => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S|$)(?![\.,:;]\s)',
  1146
+		'*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
  1147
+		'_' => '(?<=\S|^)(?<!_)_(?!_)',
  1148
+		);
  1149
+	var $strong_relist = array(
  1150
+		''   => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S|$)(?![\.,:;]\s)',
  1151
+		'**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
  1152
+		'__' => '(?<=\S|^)(?<!_)__(?!_)',