Completed
Push — gcconnex ( 718fe4...369834 )
by Ilia
18:18
created

MarkdownExtra_Parser::_appendFootnotes_callback()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 41
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 27
nc 9
nop 1
dl 0
loc 41
rs 8.439
c 0
b 0
f 0
1
<?php
2
#
3
# Markdown Extra  -  A text-to-HTML conversion tool for web writers
4
#
5
# PHP Markdown & Extra  
6
# Copyright (c) 2004-2013 Michel Fortin  
7
# <http://michelf.ca/projects/php-markdown/>
8
#
9
# Original Markdown  
10
# Copyright (c) 2004-2006 John Gruber  
11
# <http://daringfireball.net/projects/markdown/>
12
#
13
14
15
define( 'MARKDOWN_VERSION',  "1.0.2" ); # 29 Nov 2013
16
define( 'MARKDOWNEXTRA_VERSION',  "1.2.8" ); # 29 Nov 2013
17
18
19
#
20
# Global default settings:
21
#
22
23
# Change to ">" for HTML output
24
@define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX',  " />");
25
26
# Define the width of a tab for code blocks.
27
@define( 'MARKDOWN_TAB_WIDTH',     4 );
28
29
# Optional title attribute for footnote links and backlinks.
30
@define( 'MARKDOWN_FN_LINK_TITLE',         "" );
31
@define( 'MARKDOWN_FN_BACKLINK_TITLE',     "" );
32
33
# Optional class attribute for footnote links and backlinks.
34
@define( 'MARKDOWN_FN_LINK_CLASS',         "" );
35
@define( 'MARKDOWN_FN_BACKLINK_CLASS',     "" );
36
37
# Optional class prefix for fenced code block.
38
@define( 'MARKDOWN_CODE_CLASS_PREFIX',     "" );
39
40
# Class attribute for code blocks goes on the `code` tag;
41
# setting this to true will put attributes on the `pre` tag instead.
42
@define( 'MARKDOWN_CODE_ATTR_ON_PRE',   false );
43
44
45
#
46
# WordPress settings:
47
#
48
49
# Change to false to remove Markdown from posts and/or comments.
50
@define( 'MARKDOWN_WP_POSTS',      true );
51
@define( 'MARKDOWN_WP_COMMENTS',   true );
52
53
54
55
### Standard Function Interface ###
56
57
@define( 'MARKDOWN_PARSER_CLASS',  'MarkdownExtra_Parser' );
58
59
function Markdown($text) {
60
#
61
# Initialize the parser and return the result of its transform method.
62
#
63
	# Setup static parser variable.
64
	static $parser;
65
	if (!isset($parser)) {
66
		$parser_class = MARKDOWN_PARSER_CLASS;
67
		$parser = new $parser_class;
68
	}
69
70
	# Transform text using parser.
71
	return $parser->transform($text);
72
}
73
74
75
### WordPress Plugin Interface ###
76
77
/*
78
Plugin Name: Markdown Extra
79
Plugin Name: Markdown
80
Plugin URI: http://michelf.ca/projects/php-markdown/
81
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.ca/projects/php-markdown/">More...</a>
82
Version: 1.2.8
83
Author: Michel Fortin
84
Author URI: http://michelf.ca/
85
*/
86
87
if (isset($wp_version)) {
88
	# More details about how it works here:
89
	# <http://michelf.ca/weblog/2005/wordpress-text-flow-vs-markdown/>
90
	
91
	# Post content and excerpts
92
	# - Remove WordPress paragraph generator.
93
	# - Run Markdown on excerpt, then remove all tags.
94
	# - Add paragraph tag around the excerpt, but remove it for the excerpt rss.
95
	if (MARKDOWN_WP_POSTS) {
96
		remove_filter('the_content',     'wpautop');
97
        remove_filter('the_content_rss', 'wpautop');
98
		remove_filter('the_excerpt',     'wpautop');
99
		add_filter('the_content',     'mdwp_MarkdownPost', 6);
100
        add_filter('the_content_rss', 'mdwp_MarkdownPost', 6);
101
		add_filter('get_the_excerpt', 'mdwp_MarkdownPost', 6);
102
		add_filter('get_the_excerpt', 'trim', 7);
103
		add_filter('the_excerpt',     'mdwp_add_p');
104
		add_filter('the_excerpt_rss', 'mdwp_strip_p');
105
		
106
		remove_filter('content_save_pre',  'balanceTags', 50);
107
		remove_filter('excerpt_save_pre',  'balanceTags', 50);
108
		add_filter('the_content',  	  'balanceTags', 50);
109
		add_filter('get_the_excerpt', 'balanceTags', 9);
110
	}
111
	
112
	# Add a footnote id prefix to posts when inside a loop.
113
	function mdwp_MarkdownPost($text) {
114
		static $parser;
115
		if (!$parser) {
116
			$parser_class = MARKDOWN_PARSER_CLASS;
117
			$parser = new $parser_class;
118
		}
119
		if (is_single() || is_page() || is_feed()) {
120
			$parser->fn_id_prefix = "";
121
		} else {
122
			$parser->fn_id_prefix = get_the_ID() . ".";
123
		}
124
		return $parser->transform($text);
125
	}
126
	
127
	# Comments
128
	# - Remove WordPress paragraph generator.
129
	# - Remove WordPress auto-link generator.
130
	# - Scramble important tags before passing them to the kses filter.
131
	# - Run Markdown on excerpt then remove paragraph tags.
132
	if (MARKDOWN_WP_COMMENTS) {
133
		remove_filter('comment_text', 'wpautop', 30);
134
		remove_filter('comment_text', 'make_clickable');
135
		add_filter('pre_comment_content', 'Markdown', 6);
136
		add_filter('pre_comment_content', 'mdwp_hide_tags', 8);
137
		add_filter('pre_comment_content', 'mdwp_show_tags', 12);
138
		add_filter('get_comment_text',    'Markdown', 6);
139
		add_filter('get_comment_excerpt', 'Markdown', 6);
140
		add_filter('get_comment_excerpt', 'mdwp_strip_p', 7);
141
	
142
		global $mdwp_hidden_tags, $mdwp_placeholders;
143
		$mdwp_hidden_tags = explode(' ',
144
			'<p> </p> <pre> </pre> <ol> </ol> <ul> </ul> <li> </li>');
145
		$mdwp_placeholders = explode(' ', str_rot13(
146
			'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR '.
147
			'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli'));
148
	}
149
	
150
	function mdwp_add_p($text) {
151
		if (!preg_match('{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text)) {
152
			$text = '<p>'.$text.'</p>';
153
			$text = preg_replace('{\n{2,}}', "</p>\n\n<p>", $text);
154
		}
155
		return $text;
156
	}
157
	
158
	function mdwp_strip_p($t) { return preg_replace('{</?p>}i', '', $t); }
159
160
	function mdwp_hide_tags($text) {
161
		global $mdwp_hidden_tags, $mdwp_placeholders;
162
		return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text);
163
	}
164
	function mdwp_show_tags($text) {
165
		global $mdwp_hidden_tags, $mdwp_placeholders;
166
		return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text);
167
	}
168
}
169
170
171
### bBlog Plugin Info ###
172
173
function identify_modifier_markdown() {
174
	return array(
175
		'name' => 'markdown',
176
		'type' => 'modifier',
177
		'nicename' => 'PHP Markdown Extra',
178
		'description' => 'A text-to-HTML conversion tool for web writers',
179
		'authors' => 'Michel Fortin and John Gruber',
180
		'licence' => 'GPL',
181
		'version' => MARKDOWNEXTRA_VERSION,
182
		'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.ca/projects/php-markdown/">More...</a>',
183
		);
184
}
185
186
187
### Smarty Modifier Interface ###
188
189
function smarty_modifier_markdown($text) {
190
	return Markdown($text);
191
}
192
193
194
### Textile Compatibility Mode ###
195
196
# Rename this file to "classTextile.php" and it can replace Textile everywhere.
197
198
if (strcasecmp(substr(__FILE__, -16), "classTextile.php") == 0) {
199
	# Try to include PHP SmartyPants. Should be in the same directory.
200
	@include_once 'smartypants.php';
201
	# Fake Textile class. It calls Markdown instead.
202
	class Textile {
203
		function TextileThis($text, $lite='', $encode='') {
204
			if ($lite == '' && $encode == '')    $text = Markdown($text);
205
			if (function_exists('SmartyPants'))  $text = SmartyPants($text);
206
			return $text;
207
		}
208
		# Fake restricted version: restrictions are not supported for now.
209
		function TextileRestricted($text, $lite='', $noimage='') {
210
			return $this->TextileThis($text, $lite);
211
		}
212
		# Workaround to ensure compatibility with TextPattern 4.0.3.
213
		function blockLite($text) { return $text; }
214
	}
215
}
216
217
218
219
#
220
# Markdown Parser Class
221
#
222
223
class Markdown_Parser {
224
225
	### Configuration Variables ###
226
227
	# Change to ">" for HTML output.
228
	var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
229
	var $tab_width = MARKDOWN_TAB_WIDTH;
230
	
231
	# Change to `true` to disallow markup or entities.
232
	var $no_markup = false;
233
	var $no_entities = false;
234
	
235
	# Predefined urls and titles for reference links and images.
236
	var $predef_urls = array();
237
	var $predef_titles = array();
238
239
240
	### Parser Implementation ###
241
242
	# Regex to match balanced [brackets].
243
	# Needed to insert a maximum bracked depth while converting to PHP.
244
	var $nested_brackets_depth = 6;
245
	var $nested_brackets_re;
246
	
247
	var $nested_url_parenthesis_depth = 4;
248
	var $nested_url_parenthesis_re;
249
250
	# Table of hash values for escaped characters:
251
	var $escape_chars = '\`*_{}[]()>#+-.!';
252
	var $escape_chars_re;
253
254
255
	function Markdown_Parser() {
256
	#
257
	# Constructor function. Initialize appropriate member variables.
258
	#
259
		$this->_initDetab();
260
		$this->prepareItalicsAndBold();
261
	
262
		$this->nested_brackets_re = 
263
			str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth).
264
			str_repeat('\])*', $this->nested_brackets_depth);
265
	
266
		$this->nested_url_parenthesis_re = 
267
			str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth).
268
			str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth);
269
		
270
		$this->escape_chars_re = '['.preg_quote($this->escape_chars).']';
271
		
272
		# Sort document, block, and span gamut in ascendent priority order.
273
		asort($this->document_gamut);
274
		asort($this->block_gamut);
275
		asort($this->span_gamut);
276
	}
277
278
279
	# Internal hashes used during transformation.
280
	var $urls = array();
281
	var $titles = array();
282
	var $html_hashes = array();
283
	
284
	# Status flag to avoid invalid nesting.
285
	var $in_anchor = false;
286
	
287
	
288
	function setup() {
289
	#
290
	# Called before the transformation process starts to setup parser 
291
	# states.
292
	#
293
		# Clear global hashes.
294
		$this->urls = $this->predef_urls;
295
		$this->titles = $this->predef_titles;
296
		$this->html_hashes = array();
297
		
298
		$this->in_anchor = false;
299
	}
300
	
301
	function teardown() {
302
	#
303
	# Called after the transformation process to clear any variable 
304
	# which may be taking up memory unnecessarly.
305
	#
306
		$this->urls = array();
307
		$this->titles = array();
308
		$this->html_hashes = array();
309
	}
310
311
312
	function transform($text) {
313
	#
314
	# Main function. Performs some preprocessing on the input text
315
	# and pass it through the document gamut.
316
	#
317
		$this->setup();
318
	
319
		# Remove UTF-8 BOM and marker character in input, if present.
320
		$text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text);
321
322
		# Standardize line endings:
323
		#   DOS to Unix and Mac to Unix
324
		$text = preg_replace('{\r\n?}', "\n", $text);
325
326
		# Make sure $text ends with a couple of newlines:
327
		$text .= "\n\n";
328
329
		# Convert all tabs to spaces.
330
		$text = $this->detab($text);
331
332
		# Turn block-level HTML blocks into hash entries
333
		$text = $this->hashHTMLBlocks($text);
334
335
		# Strip any lines consisting only of spaces and tabs.
336
		# This makes subsequent regexen easier to write, because we can
337
		# match consecutive blank lines with /\n+/ instead of something
338
		# contorted like /[ ]*\n+/ .
339
		$text = preg_replace('/^[ ]+$/m', '', $text);
340
341
		# Run document gamut methods.
342
		foreach ($this->document_gamut as $method => $priority) {
343
			$text = $this->$method($text);
344
		}
345
		
346
		$this->teardown();
347
348
		return $text . "\n";
349
	}
350
	
351
	var $document_gamut = array(
352
		# Strip link definitions, store in hashes.
353
		"stripLinkDefinitions" => 20,
354
		
355
		"runBasicBlockGamut"   => 30,
356
		);
357
358
359
	function stripLinkDefinitions($text) {
360
	#
361
	# Strips link definitions from text, stores the URLs and titles in
362
	# hash references.
363
	#
364
		$less_than_tab = $this->tab_width - 1;
365
366
		# Link defs are in the form: ^[id]: url "optional title"
367
		$text = preg_replace_callback('{
368
							^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?:	# id = $1
369
							  [ ]*
370
							  \n?				# maybe *one* newline
371
							  [ ]*
372
							(?:
373
							  <(.+?)>			# url = $2
374
							|
375
							  (\S+?)			# url = $3
376
							)
377
							  [ ]*
378
							  \n?				# maybe one newline
379
							  [ ]*
380
							(?:
381
								(?<=\s)			# lookbehind for whitespace
382
								["(]
383
								(.*?)			# title = $4
384
								[")]
385
								[ ]*
386
							)?	# title is optional
387
							(?:\n+|\Z)
388
			}xm',
389
			array(&$this, '_stripLinkDefinitions_callback'),
390
			$text);
391
		return $text;
392
	}
393
	function _stripLinkDefinitions_callback($matches) {
394
		$link_id = strtolower($matches[1]);
395
		$url = $matches[2] == '' ? $matches[3] : $matches[2];
396
		$this->urls[$link_id] = $url;
397
		$this->titles[$link_id] =& $matches[4];
398
		return ''; # String that will replace the block
399
	}
400
401
402
	function hashHTMLBlocks($text) {
403
		if ($this->no_markup)  return $text;
404
405
		$less_than_tab = $this->tab_width - 1;
406
407
		# Hashify HTML blocks:
408
		# We only want to do this for block-level HTML tags, such as headers,
409
		# lists, and tables. That's because we still want to wrap <p>s around
410
		# "paragraphs" that are wrapped in non-block-level tags, such as anchors,
411
		# phrase emphasis, and spans. The list of tags we're looking for is
412
		# hard-coded:
413
		#
414
		# *  List "a" is made of tags which can be both inline or block-level.
415
		#    These will be treated block-level when the start tag is alone on 
416
		#    its line, otherwise they're not matched here and will be taken as 
417
		#    inline later.
418
		# *  List "b" is made of tags which are always block-level;
419
		#
420
		$block_tags_a_re = 'ins|del';
421
		$block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'.
422
						   'script|noscript|form|fieldset|iframe|math|svg|'.
423
						   'article|section|nav|aside|hgroup|header|footer|'.
424
						   'figure';
425
426
		# Regular expression for the content of a block tag.
427
		$nested_tags_level = 4;
428
		$attr = '
429
			(?>				# optional tag attributes
430
			  \s			# starts with whitespace
431
			  (?>
432
				[^>"/]+		# text outside quotes
433
			  |
434
				/+(?!>)		# slash not followed by ">"
435
			  |
436
				"[^"]*"		# text inside double quotes (tolerate ">")
437
			  |
438
				\'[^\']*\'	# text inside single quotes (tolerate ">")
439
			  )*
440
			)?	
441
			';
442
		$content =
443
			str_repeat('
444
				(?>
445
				  [^<]+			# content without tag
446
				|
447
				  <\2			# nested opening tag
448
					'.$attr.'	# attributes
449
					(?>
450
					  />
451
					|
452
					  >', $nested_tags_level).	# end of opening tag
453
					  '.*?'.					# last level nested tag content
454
			str_repeat('
455
					  </\2\s*>	# closing nested tag
456
					)
457
				  |				
458
					<(?!/\2\s*>	# other tags with a different name
459
				  )
460
				)*',
461
				$nested_tags_level);
462
		$content2 = str_replace('\2', '\3', $content);
463
464
		# First, look for nested blocks, e.g.:
465
		# 	<div>
466
		# 		<div>
467
		# 		tags for inner block must be indented.
468
		# 		</div>
469
		# 	</div>
470
		#
471
		# The outermost tags must start at the left margin for this to match, and
472
		# the inner nested divs must be indented.
473
		# We need to do this before the next, more liberal match, because the next
474
		# match will start at the first `<div>` and stop at the first `</div>`.
475
		$text = preg_replace_callback('{(?>
476
			(?>
477
				(?<=\n\n)		# Starting after a blank line
478
				|				# or
479
				\A\n?			# the beginning of the doc
480
			)
481
			(						# save in $1
482
483
			  # Match from `\n<tag>` to `</tag>\n`, handling nested tags 
484
			  # in between.
485
					
486
						[ ]{0,'.$less_than_tab.'}
487
						<('.$block_tags_b_re.')# start tag = $2
488
						'.$attr.'>			# attributes followed by > and \n
489
						'.$content.'		# content, support nesting
490
						</\2>				# the matching end tag
491
						[ ]*				# trailing spaces/tabs
492
						(?=\n+|\Z)	# followed by a newline or end of document
493
494
			| # Special version for tags of group a.
495
496
						[ ]{0,'.$less_than_tab.'}
497
						<('.$block_tags_a_re.')# start tag = $3
498
						'.$attr.'>[ ]*\n	# attributes followed by >
499
						'.$content2.'		# content, support nesting
500
						</\3>				# the matching end tag
501
						[ ]*				# trailing spaces/tabs
502
						(?=\n+|\Z)	# followed by a newline or end of document
503
					
504
			| # Special case just for <hr />. It was easier to make a special 
505
			  # case than to make the other regex more complicated.
506
			
507
						[ ]{0,'.$less_than_tab.'}
508
						<(hr)				# start tag = $2
509
						'.$attr.'			# attributes
510
						/?>					# the matching end tag
511
						[ ]*
512
						(?=\n{2,}|\Z)		# followed by a blank line or end of document
513
			
514
			| # Special case for standalone HTML comments:
515
			
516
					[ ]{0,'.$less_than_tab.'}
517
					(?s:
518
						<!-- .*? -->
519
					)
520
					[ ]*
521
					(?=\n{2,}|\Z)		# followed by a blank line or end of document
522
			
523
			| # PHP and ASP-style processor instructions (<? and <%)
524
			
525
					[ ]{0,'.$less_than_tab.'}
526
					(?s:
527
						<([?%])			# $2
528
						.*?
529
						\2>
530
					)
531
					[ ]*
532
					(?=\n{2,}|\Z)		# followed by a blank line or end of document
533
					
534
			)
535
			)}Sxmi',
536
			array(&$this, '_hashHTMLBlocks_callback'),
537
			$text);
538
539
		return $text;
540
	}
541
	function _hashHTMLBlocks_callback($matches) {
542
		$text = $matches[1];
543
		$key  = $this->hashBlock($text);
544
		return "\n\n$key\n\n";
545
	}
546
	
547
	
548
	function hashPart($text, $boundary = 'X') {
549
	#
550
	# Called whenever a tag must be hashed when a function insert an atomic 
551
	# element in the text stream. Passing $text to through this function gives
552
	# a unique text-token which will be reverted back when calling unhash.
553
	#
554
	# The $boundary argument specify what character should be used to surround
555
	# the token. By convension, "B" is used for block elements that needs not
556
	# to be wrapped into paragraph tags at the end, ":" is used for elements
557
	# that are word separators and "X" is used in the general case.
558
	#
559
		# Swap back any tag hash found in $text so we do not have to `unhash`
560
		# multiple times at the end.
561
		$text = $this->unhash($text);
562
		
563
		# Then hash the block.
564
		static $i = 0;
565
		$key = "$boundary\x1A" . ++$i . $boundary;
566
		$this->html_hashes[$key] = $text;
567
		return $key; # String that will replace the tag.
568
	}
569
570
571
	function hashBlock($text) {
572
	#
573
	# Shortcut function for hashPart with block-level boundaries.
574
	#
575
		return $this->hashPart($text, 'B');
576
	}
577
578
579
	var $block_gamut = array(
580
	#
581
	# These are all the transformations that form block-level
582
	# tags like paragraphs, headers, and list items.
583
	#
584
		"doHeaders"         => 10,
585
		"doHorizontalRules" => 20,
586
		
587
		"doLists"           => 40,
588
		"doCodeBlocks"      => 50,
589
		"doBlockQuotes"     => 60,
590
		);
591
592
	function runBlockGamut($text) {
593
	#
594
	# Run block gamut tranformations.
595
	#
596
		# We need to escape raw HTML in Markdown source before doing anything 
597
		# else. This need to be done for each block, and not only at the 
598
		# begining in the Markdown function since hashed blocks can be part of
599
		# list items and could have been indented. Indented blocks would have 
600
		# been seen as a code block in a previous pass of hashHTMLBlocks.
601
		$text = $this->hashHTMLBlocks($text);
602
		
603
		return $this->runBasicBlockGamut($text);
604
	}
605
	
606
	function runBasicBlockGamut($text) {
607
	#
608
	# Run block gamut tranformations, without hashing HTML blocks. This is 
609
	# useful when HTML blocks are known to be already hashed, like in the first
610
	# whole-document pass.
611
	#
612
		foreach ($this->block_gamut as $method => $priority) {
613
			$text = $this->$method($text);
614
		}
615
		
616
		# Finally form paragraph and restore hashed blocks.
617
		$text = $this->formParagraphs($text);
618
619
		return $text;
620
	}
621
	
622
	
623
	function doHorizontalRules($text) {
624
		# Do Horizontal Rules:
625
		return preg_replace(
626
			'{
627
				^[ ]{0,3}	# Leading space
628
				([-*_])		# $1: First marker
629
				(?>			# Repeated marker group
630
					[ ]{0,2}	# Zero, one, or two spaces.
631
					\1			# Marker character
632
				){2,}		# Group repeated at least twice
633
				[ ]*		# Tailing spaces
634
				$			# End of line.
635
			}mx',
636
			"\n".$this->hashBlock("<hr$this->empty_element_suffix")."\n", 
637
			$text);
638
	}
639
640
641
	var $span_gamut = array(
642
	#
643
	# These are all the transformations that occur *within* block-level
644
	# tags like paragraphs, headers, and list items.
645
	#
646
		# Process character escapes, code spans, and inline HTML
647
		# in one shot.
648
		"parseSpan"           => -30,
649
650
		# Process anchor and image tags. Images must come first,
651
		# because ![foo][f] looks like an anchor.
652
		"doImages"            =>  10,
653
		"doAnchors"           =>  20,
654
		
655
		# Make links out of things like `<http://example.com/>`
656
		# Must come after doAnchors, because you can use < and >
657
		# delimiters in inline links like [this](<url>).
658
		"doAutoLinks"         =>  30,
659
		"encodeAmpsAndAngles" =>  40,
660
661
		"doItalicsAndBold"    =>  50,
662
		"doHardBreaks"        =>  60,
663
		);
664
665
	function runSpanGamut($text) {
666
	#
667
	# Run span gamut tranformations.
668
	#
669
		foreach ($this->span_gamut as $method => $priority) {
670
			$text = $this->$method($text);
671
		}
672
673
		return $text;
674
	}
675
	
676
	
677
	function doHardBreaks($text) {
678
		# Do hard breaks:
679
		return preg_replace_callback('/ {2,}\n/', 
680
			array(&$this, '_doHardBreaks_callback'), $text);
681
	}
682
	function _doHardBreaks_callback($matches) {
683
		return $this->hashPart("<br$this->empty_element_suffix\n");
684
	}
685
686
687
	function doAnchors($text) {
688
	#
689
	# Turn Markdown link shortcuts into XHTML <a> tags.
690
	#
691
		if ($this->in_anchor) return $text;
692
		$this->in_anchor = true;
693
		
694
		#
695
		# First, handle reference-style links: [link text] [id]
696
		#
697
		$text = preg_replace_callback('{
698
			(					# wrap whole match in $1
699
			  \[
700
				('.$this->nested_brackets_re.')	# link text = $2
701
			  \]
702
703
			  [ ]?				# one optional space
704
			  (?:\n[ ]*)?		# one optional newline followed by spaces
705
706
			  \[
707
				(.*?)		# id = $3
708
			  \]
709
			)
710
			}xs',
711
			array(&$this, '_doAnchors_reference_callback'), $text);
712
713
		#
714
		# Next, inline-style links: [link text](url "optional title")
715
		#
716
		$text = preg_replace_callback('{
717
			(				# wrap whole match in $1
718
			  \[
719
				('.$this->nested_brackets_re.')	# link text = $2
720
			  \]
721
			  \(			# literal paren
722
				[ \n]*
723
				(?:
724
					<(.+?)>	# href = $3
725
				|
726
					('.$this->nested_url_parenthesis_re.')	# href = $4
727
				)
728
				[ \n]*
729
				(			# $5
730
				  ([\'"])	# quote char = $6
731
				  (.*?)		# Title = $7
732
				  \6		# matching quote
733
				  [ \n]*	# ignore any spaces/tabs between closing quote and )
734
				)?			# title is optional
735
			  \)
736
			)
737
			}xs',
738
			array(&$this, '_doAnchors_inline_callback'), $text);
739
740
		#
741
		# Last, handle reference-style shortcuts: [link text]
742
		# These must come last in case you've also got [link text][1]
743
		# or [link text](/foo)
744
		#
745
		$text = preg_replace_callback('{
746
			(					# wrap whole match in $1
747
			  \[
748
				([^\[\]]+)		# link text = $2; can\'t contain [ or ]
749
			  \]
750
			)
751
			}xs',
752
			array(&$this, '_doAnchors_reference_callback'), $text);
753
754
		$this->in_anchor = false;
755
		return $text;
756
	}
757
	function _doAnchors_reference_callback($matches) {
758
		$whole_match =  $matches[1];
759
		$link_text   =  $matches[2];
760
		$link_id     =& $matches[3];
761
762
		if ($link_id == "") {
763
			# for shortcut links like [this][] or [this].
764
			$link_id = $link_text;
765
		}
766
		
767
		# lower-case and turn embedded newlines into spaces
768
		$link_id = strtolower($link_id);
769
		$link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
770
771
		if (isset($this->urls[$link_id])) {
772
			$url = $this->urls[$link_id];
773
			$url = $this->encodeAttribute($url);
774
			
775
			$result = "<a href=\"$url\"";
776
			if ( isset( $this->titles[$link_id] ) ) {
777
				$title = $this->titles[$link_id];
778
				$title = $this->encodeAttribute($title);
779
				$result .=  " title=\"$title\"";
780
			}
781
		
782
			$link_text = $this->runSpanGamut($link_text);
783
			$result .= ">$link_text</a>";
784
			$result = $this->hashPart($result);
785
		}
786
		else {
787
			$result = $whole_match;
788
		}
789
		return $result;
790
	}
791
	function _doAnchors_inline_callback($matches) {
792
		$whole_match	=  $matches[1];
793
		$link_text		=  $this->runSpanGamut($matches[2]);
794
		$url			=  $matches[3] == '' ? $matches[4] : $matches[3];
795
		$title			=& $matches[7];
796
797
		$url = $this->encodeAttribute($url);
798
799
		$result = "<a href=\"$url\"";
800
		if (isset($title)) {
801
			$title = $this->encodeAttribute($title);
802
			$result .=  " title=\"$title\"";
803
		}
804
		
805
		$link_text = $this->runSpanGamut($link_text);
806
		$result .= ">$link_text</a>";
807
808
		return $this->hashPart($result);
809
	}
810
811
812
	function doImages($text) {
813
	#
814
	# Turn Markdown image shortcuts into <img> tags.
815
	#
816
		#
817
		# First, handle reference-style labeled images: ![alt text][id]
818
		#
819
		$text = preg_replace_callback('{
820
			(				# wrap whole match in $1
821
			  !\[
822
				('.$this->nested_brackets_re.')		# alt text = $2
823
			  \]
824
825
			  [ ]?				# one optional space
826
			  (?:\n[ ]*)?		# one optional newline followed by spaces
827
828
			  \[
829
				(.*?)		# id = $3
830
			  \]
831
832
			)
833
			}xs', 
834
			array(&$this, '_doImages_reference_callback'), $text);
835
836
		#
837
		# Next, handle inline images:  ![alt text](url "optional title")
838
		# Don't forget: encode * and _
839
		#
840
		$text = preg_replace_callback('{
841
			(				# wrap whole match in $1
842
			  !\[
843
				('.$this->nested_brackets_re.')		# alt text = $2
844
			  \]
845
			  \s?			# One optional whitespace character
846
			  \(			# literal paren
847
				[ \n]*
848
				(?:
849
					<(\S*)>	# src url = $3
850
				|
851
					('.$this->nested_url_parenthesis_re.')	# src url = $4
852
				)
853
				[ \n]*
854
				(			# $5
855
				  ([\'"])	# quote char = $6
856
				  (.*?)		# title = $7
857
				  \6		# matching quote
858
				  [ \n]*
859
				)?			# title is optional
860
			  \)
861
			)
862
			}xs',
863
			array(&$this, '_doImages_inline_callback'), $text);
864
865
		return $text;
866
	}
867
	function _doImages_reference_callback($matches) {
868
		$whole_match = $matches[1];
869
		$alt_text    = $matches[2];
870
		$link_id     = strtolower($matches[3]);
871
872
		if ($link_id == "") {
873
			$link_id = strtolower($alt_text); # for shortcut links like ![this][].
874
		}
875
876
		$alt_text = $this->encodeAttribute($alt_text);
877
		if (isset($this->urls[$link_id])) {
878
			$url = $this->encodeAttribute($this->urls[$link_id]);
879
			$result = "<img src=\"$url\" alt=\"$alt_text\"";
880
			if (isset($this->titles[$link_id])) {
881
				$title = $this->titles[$link_id];
882
				$title = $this->encodeAttribute($title);
883
				$result .=  " title=\"$title\"";
884
			}
885
			$result .= $this->empty_element_suffix;
886
			$result = $this->hashPart($result);
887
		}
888
		else {
889
			# If there's no such link ID, leave intact:
890
			$result = $whole_match;
891
		}
892
893
		return $result;
894
	}
895
	function _doImages_inline_callback($matches) {
896
		$whole_match	= $matches[1];
897
		$alt_text		= $matches[2];
898
		$url			= $matches[3] == '' ? $matches[4] : $matches[3];
899
		$title			=& $matches[7];
900
901
		$alt_text = $this->encodeAttribute($alt_text);
902
		$url = $this->encodeAttribute($url);
903
		$result = "<img src=\"$url\" alt=\"$alt_text\"";
904
		if (isset($title)) {
905
			$title = $this->encodeAttribute($title);
906
			$result .=  " title=\"$title\""; # $title already quoted
907
		}
908
		$result .= $this->empty_element_suffix;
909
910
		return $this->hashPart($result);
911
	}
912
913
914
	function doHeaders($text) {
915
		# Setext-style headers:
916
		#	  Header 1
917
		#	  ========
918
		#  
919
		#	  Header 2
920
		#	  --------
921
		#
922
		$text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
923
			array(&$this, '_doHeaders_callback_setext'), $text);
924
925
		# atx-style headers:
926
		#	# Header 1
927
		#	## Header 2
928
		#	## Header 2 with closing hashes ##
929
		#	...
930
		#	###### Header 6
931
		#
932
		$text = preg_replace_callback('{
933
				^(\#{1,6})	# $1 = string of #\'s
934
				[ ]*
935
				(.+?)		# $2 = Header text
936
				[ ]*
937
				\#*			# optional closing #\'s (not counted)
938
				\n+
939
			}xm',
940
			array(&$this, '_doHeaders_callback_atx'), $text);
941
942
		return $text;
943
	}
944
	function _doHeaders_callback_setext($matches) {
945
		# Terrible hack to check we haven't found an empty list item.
946
		if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1]))
947
			return $matches[0];
948
		
949
		$level = $matches[2]{0} == '=' ? 1 : 2;
950
		$block = "<h$level>".$this->runSpanGamut($matches[1])."</h$level>";
951
		return "\n" . $this->hashBlock($block) . "\n\n";
952
	}
953
	function _doHeaders_callback_atx($matches) {
954
		$level = strlen($matches[1]);
955
		$block = "<h$level>".$this->runSpanGamut($matches[2])."</h$level>";
956
		return "\n" . $this->hashBlock($block) . "\n\n";
957
	}
958
959
960
	function doLists($text) {
961
	#
962
	# Form HTML ordered (numbered) and unordered (bulleted) lists.
963
	#
964
		$less_than_tab = $this->tab_width - 1;
965
966
		# Re-usable patterns to match list item bullets and number markers:
967
		$marker_ul_re  = '[*+-]';
968
		$marker_ol_re  = '\d+[\.]';
969
		$marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
970
971
		$markers_relist = array(
972
			$marker_ul_re => $marker_ol_re,
973
			$marker_ol_re => $marker_ul_re,
974
			);
975
976
		foreach ($markers_relist as $marker_re => $other_marker_re) {
977
			# Re-usable pattern to match any entirel ul or ol list:
978
			$whole_list_re = '
979
				(								# $1 = whole list
980
				  (								# $2
981
					([ ]{0,'.$less_than_tab.'})	# $3 = number of spaces
982
					('.$marker_re.')			# $4 = first list item marker
983
					[ ]+
984
				  )
985
				  (?s:.+?)
986
				  (								# $5
987
					  \z
988
					|
989
					  \n{2,}
990
					  (?=\S)
991
					  (?!						# Negative lookahead for another list item marker
992
						[ ]*
993
						'.$marker_re.'[ ]+
994
					  )
995
					|
996
					  (?=						# Lookahead for another kind of list
997
					    \n
998
						\3						# Must have the same indentation
999
						'.$other_marker_re.'[ ]+
1000
					  )
1001
				  )
1002
				)
1003
			'; // mx
1004
			
1005
			# We use a different prefix before nested lists than top-level lists.
1006
			# See extended comment in _ProcessListItems().
1007
		
1008
			if ($this->list_level) {
1009
				$text = preg_replace_callback('{
1010
						^
1011
						'.$whole_list_re.'
1012
					}mx',
1013
					array(&$this, '_doLists_callback'), $text);
1014
			}
1015
			else {
1016
				$text = preg_replace_callback('{
1017
						(?:(?<=\n)\n|\A\n?) # Must eat the newline
1018
						'.$whole_list_re.'
1019
					}mx',
1020
					array(&$this, '_doLists_callback'), $text);
1021
			}
1022
		}
1023
1024
		return $text;
1025
	}
1026
	function _doLists_callback($matches) {
1027
		# Re-usable patterns to match list item bullets and number markers:
1028
		$marker_ul_re  = '[*+-]';
1029
		$marker_ol_re  = '\d+[\.]';
1030
		$marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
1031
		
1032
		$list = $matches[1];
1033
		$list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol";
1034
		
1035
		$marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re );
1036
		
1037
		$list .= "\n";
1038
		$result = $this->processListItems($list, $marker_any_re);
1039
		
1040
		$result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>");
1041
		return "\n". $result ."\n\n";
1042
	}
1043
1044
	var $list_level = 0;
1045
1046
	function processListItems($list_str, $marker_any_re) {
1047
	#
1048
	#	Process the contents of a single ordered or unordered list, splitting it
1049
	#	into individual list items.
1050
	#
1051
		# The $this->list_level global keeps track of when we're inside a list.
1052
		# Each time we enter a list, we increment it; when we leave a list,
1053
		# we decrement. If it's zero, we're not in a list anymore.
1054
		#
1055
		# We do this because when we're not inside a list, we want to treat
1056
		# something like this:
1057
		#
1058
		#		I recommend upgrading to version
1059
		#		8. Oops, now this line is treated
1060
		#		as a sub-list.
1061
		#
1062
		# As a single paragraph, despite the fact that the second line starts
1063
		# with a digit-period-space sequence.
1064
		#
1065
		# Whereas when we're inside a list (or sub-list), that line will be
1066
		# treated as the start of a sub-list. What a kludge, huh? This is
1067
		# an aspect of Markdown's syntax that's hard to parse perfectly
1068
		# without resorting to mind-reading. Perhaps the solution is to
1069
		# change the syntax rules such that sub-lists must start with a
1070
		# starting cardinal number; e.g. "1." or "a.".
1071
		
1072
		$this->list_level++;
1073
1074
		# trim trailing blank lines:
1075
		$list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
1076
1077
		$list_str = preg_replace_callback('{
1078
			(\n)?							# leading line = $1
1079
			(^[ ]*)							# leading whitespace = $2
1080
			('.$marker_any_re.'				# list marker and space = $3
1081
				(?:[ ]+|(?=\n))	# space only required if item is not empty
1082
			)
1083
			((?s:.*?))						# list item text   = $4
1084
			(?:(\n+(?=\n))|\n)				# tailing blank line = $5
1085
			(?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n))))
1086
			}xm',
1087
			array(&$this, '_processListItems_callback'), $list_str);
1088
1089
		$this->list_level--;
1090
		return $list_str;
1091
	}
1092
	function _processListItems_callback($matches) {
1093
		$item = $matches[4];
1094
		$leading_line =& $matches[1];
1095
		$leading_space =& $matches[2];
1096
		$marker_space = $matches[3];
1097
		$tailing_blank_line =& $matches[5];
1098
1099
		if ($leading_line || $tailing_blank_line || 
1100
			preg_match('/\n{2,}/', $item))
1101
		{
1102
			# Replace marker with the appropriate whitespace indentation
1103
			$item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item;
1104
			$item = $this->runBlockGamut($this->outdent($item)."\n");
1105
		}
1106
		else {
1107
			# Recursion for sub-lists:
1108
			$item = $this->doLists($this->outdent($item));
1109
			$item = preg_replace('/\n+$/', '', $item);
1110
			$item = $this->runSpanGamut($item);
1111
		}
1112
1113
		return "<li>" . $item . "</li>\n";
1114
	}
1115
1116
1117
	function doCodeBlocks($text) {
1118
	#
1119
	#	Process Markdown `<pre><code>` blocks.
1120
	#
1121
		$text = preg_replace_callback('{
1122
				(?:\n\n|\A\n?)
1123
				(	            # $1 = the code block -- one or more lines, starting with a space/tab
1124
				  (?>
1125
					[ ]{'.$this->tab_width.'}  # Lines must start with a tab or a tab-width of spaces
1126
					.*\n+
1127
				  )+
1128
				)
1129
				((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
1130
			}xm',
1131
			array(&$this, '_doCodeBlocks_callback'), $text);
1132
1133
		return $text;
1134
	}
1135
	function _doCodeBlocks_callback($matches) {
1136
		$codeblock = $matches[1];
1137
1138
		$codeblock = $this->outdent($codeblock);
1139
		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
1140
1141
		# trim leading newlines and trailing newlines
1142
		$codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
1143
1144
		$codeblock = "<pre><code>$codeblock\n</code></pre>";
1145
		return "\n\n".$this->hashBlock($codeblock)."\n\n";
1146
	}
1147
1148
1149
	function makeCodeSpan($code) {
1150
	#
1151
	# Create a code span markup for $code. Called from handleSpanToken.
1152
	#
1153
		$code = htmlspecialchars(trim($code), ENT_NOQUOTES);
1154
		return $this->hashPart("<code>$code</code>");
1155
	}
1156
1157
1158
	var $em_relist = array(
1159
		''  => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S|$)(?![\.,:;]\s)',
1160
		'*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
1161
		'_' => '(?<=\S|^)(?<!_)_(?!_)',
1162
		);
1163
	var $strong_relist = array(
1164
		''   => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S|$)(?![\.,:;]\s)',
1165
		'**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
1166
		'__' => '(?<=\S|^)(?<!_)__(?!_)',
1167
		);
1168
	var $em_strong_relist = array(
1169
		''    => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S|$)(?![\.,:;]\s)',
1170
		'***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
1171
		'___' => '(?<=\S|^)(?<!_)___(?!_)',
1172
		);
1173
	var $em_strong_prepared_relist;
1174
	
1175
	function prepareItalicsAndBold() {
1176
	#
1177
	# Prepare regular expressions for searching emphasis tokens in any
1178
	# context.
1179
	#
1180
		foreach ($this->em_relist as $em => $em_re) {
1181
			foreach ($this->strong_relist as $strong => $strong_re) {
1182
				# Construct list of allowed token expressions.
1183
				$token_relist = array();
1184
				if (isset($this->em_strong_relist["$em$strong"])) {
1185
					$token_relist[] = $this->em_strong_relist["$em$strong"];
1186
				}
1187
				$token_relist[] = $em_re;
1188
				$token_relist[] = $strong_re;
1189
				
1190
				# Construct master expression from list.
1191
				$token_re = '{('. implode('|', $token_relist) .')}';
1192
				$this->em_strong_prepared_relist["$em$strong"] = $token_re;
1193
			}
1194
		}
1195
	}
1196
	
1197
	function doItalicsAndBold($text) {
1198
		$token_stack = array('');
1199
		$text_stack = array('');
1200
		$em = '';
1201
		$strong = '';
1202
		$tree_char_em = false;
1203
		
1204
		while (1) {
1205
			#
1206
			# Get prepared regular expression for seraching emphasis tokens
1207
			# in current context.
1208
			#
1209
			$token_re = $this->em_strong_prepared_relist["$em$strong"];
1210
			
1211
			#
1212
			# Each loop iteration search for the next emphasis token. 
1213
			# Each token is then passed to handleSpanToken.
1214
			#
1215
			$parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
1216
			$text_stack[0] .= $parts[0];
1217
			$token =& $parts[1];
1218
			$text =& $parts[2];
1219
			
1220
			if (empty($token)) {
1221
				# Reached end of text span: empty stack without emitting.
1222
				# any more emphasis.
1223
				while ($token_stack[0]) {
1224
					$text_stack[1] .= array_shift($token_stack);
1225
					$text_stack[0] .= array_shift($text_stack);
1226
				}
1227
				break;
1228
			}
1229
			
1230
			$token_len = strlen($token);
1231
			if ($tree_char_em) {
1232
				# Reached closing marker while inside a three-char emphasis.
1233
				if ($token_len == 3) {
1234
					# Three-char closing marker, close em and strong.
1235
					array_shift($token_stack);
1236
					$span = array_shift($text_stack);
1237
					$span = $this->runSpanGamut($span);
1238
					$span = "<strong><em>$span</em></strong>";
1239
					$text_stack[0] .= $this->hashPart($span);
1240
					$em = '';
1241
					$strong = '';
1242
				} else {
1243
					# Other closing marker: close one em or strong and
1244
					# change current token state to match the other
1245
					$token_stack[0] = str_repeat($token{0}, 3-$token_len);
1246
					$tag = $token_len == 2 ? "strong" : "em";
1247
					$span = $text_stack[0];
1248
					$span = $this->runSpanGamut($span);
1249
					$span = "<$tag>$span</$tag>";
1250
					$text_stack[0] = $this->hashPart($span);
1251
					$$tag = ''; # $$tag stands for $em or $strong
1252
				}
1253
				$tree_char_em = false;
1254
			} else if ($token_len == 3) {
1255
				if ($em) {
1256
					# Reached closing marker for both em and strong.
1257
					# Closing strong marker:
1258
					for ($i = 0; $i < 2; ++$i) {
1259
						$shifted_token = array_shift($token_stack);
1260
						$tag = strlen($shifted_token) == 2 ? "strong" : "em";
1261
						$span = array_shift($text_stack);
1262
						$span = $this->runSpanGamut($span);
1263
						$span = "<$tag>$span</$tag>";
1264
						$text_stack[0] .= $this->hashPart($span);
1265
						$$tag = ''; # $$tag stands for $em or $strong
1266
					}
1267
				} else {
1268
					# Reached opening three-char emphasis marker. Push on token 
1269
					# stack; will be handled by the special condition above.
1270
					$em = $token{0};
1271
					$strong = "$em$em";
1272
					array_unshift($token_stack, $token);
1273
					array_unshift($text_stack, '');
1274
					$tree_char_em = true;
1275
				}
1276
			} else if ($token_len == 2) {
1277
				if ($strong) {
1278
					# Unwind any dangling emphasis marker:
1279
					if (strlen($token_stack[0]) == 1) {
1280
						$text_stack[1] .= array_shift($token_stack);
1281
						$text_stack[0] .= array_shift($text_stack);
1282
					}
1283
					# Closing strong marker:
1284
					array_shift($token_stack);
1285
					$span = array_shift($text_stack);
1286
					$span = $this->runSpanGamut($span);
1287
					$span = "<strong>$span</strong>";
1288
					$text_stack[0] .= $this->hashPart($span);
1289
					$strong = '';
1290
				} else {
1291
					array_unshift($token_stack, $token);
1292
					array_unshift($text_stack, '');
1293
					$strong = $token;
1294
				}
1295
			} else {
1296
				# Here $token_len == 1
1297
				if ($em) {
1298
					if (strlen($token_stack[0]) == 1) {
1299
						# Closing emphasis marker:
1300
						array_shift($token_stack);
1301
						$span = array_shift($text_stack);
1302
						$span = $this->runSpanGamut($span);
1303
						$span = "<em>$span</em>";
1304
						$text_stack[0] .= $this->hashPart($span);
1305
						$em = '';
1306
					} else {
1307
						$text_stack[0] .= $token;
1308
					}
1309
				} else {
1310
					array_unshift($token_stack, $token);
1311
					array_unshift($text_stack, '');
1312
					$em = $token;
1313
				}
1314
			}
1315
		}
1316
		return $text_stack[0];
1317
	}
1318
1319
1320
	function doBlockQuotes($text) {
1321
		$text = preg_replace_callback('/
1322
			  (								# Wrap whole match in $1
1323
				(?>
1324
				  ^[ ]*>[ ]?			# ">" at the start of a line
1325
					.+\n					# rest of the first line
1326
				  (.+\n)*					# subsequent consecutive lines
1327
				  \n*						# blanks
1328
				)+
1329
			  )
1330
			/xm',
1331
			array(&$this, '_doBlockQuotes_callback'), $text);
1332
1333
		return $text;
1334
	}
1335
	function _doBlockQuotes_callback($matches) {
1336
		$bq = $matches[1];
1337
		# trim one level of quoting - trim whitespace-only lines
1338
		$bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
1339
		$bq = $this->runBlockGamut($bq);		# recurse
1340
1341
		$bq = preg_replace('/^/m', "  ", $bq);
1342
		# These leading spaces cause problem with <pre> content, 
1343
		# so we need to fix that:
1344
		$bq = preg_replace_callback('{(\s*<pre>.+?</pre>)}sx', 
1345
			array(&$this, '_doBlockQuotes_callback2'), $bq);
1346
1347
		return "\n". $this->hashBlock("<blockquote>\n$bq\n</blockquote>")."\n\n";
1348
	}
1349
	function _doBlockQuotes_callback2($matches) {
1350
		$pre = $matches[1];
1351
		$pre = preg_replace('/^  /m', '', $pre);
1352
		return $pre;
1353
	}
1354
1355
1356
	function formParagraphs($text) {
1357
	#
1358
	#	Params:
1359
	#		$text - string to process with html <p> tags
1360
	#
1361
		# Strip leading and trailing lines:
1362
		$text = preg_replace('/\A\n+|\n+\z/', '', $text);
1363
1364
		$grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
1365
1366
		#
1367
		# Wrap <p> tags and unhashify HTML blocks
1368
		#
1369
		foreach ($grafs as $key => $value) {
1370
			if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
1371
				# Is a paragraph.
1372
				$value = $this->runSpanGamut($value);
1373
				$value = preg_replace('/^([ ]*)/', "<p>", $value);
1374
				$value .= "</p>";
1375
				$grafs[$key] = $this->unhash($value);
1376
			}
1377
			else {
1378
				# Is a block.
1379
				# Modify elements of @grafs in-place...
1380
				$graf = $value;
1381
				$block = $this->html_hashes[$graf];
1382
				$graf = $block;
1383
//				if (preg_match('{
1384
//					\A
1385
//					(							# $1 = <div> tag
1386
//					  <div  \s+
1387
//					  [^>]*
1388
//					  \b
1389
//					  markdown\s*=\s*  ([\'"])	#	$2 = attr quote char
1390
//					  1
1391
//					  \2
1392
//					  [^>]*
1393
//					  >
1394
//					)
1395
//					(							# $3 = contents
1396
//					.*
1397
//					)
1398
//					(</div>)					# $4 = closing tag
1399
//					\z
1400
//					}xs', $block, $matches))
1401
//				{
1402
//					list(, $div_open, , $div_content, $div_close) = $matches;
1403
//
1404
//					# We can't call Markdown(), because that resets the hash;
1405
//					# that initialization code should be pulled into its own sub, though.
1406
//					$div_content = $this->hashHTMLBlocks($div_content);
1407
//					
1408
//					# Run document gamut methods on the content.
1409
//					foreach ($this->document_gamut as $method => $priority) {
1410
//						$div_content = $this->$method($div_content);
1411
//					}
1412
//
1413
//					$div_open = preg_replace(
1414
//						'{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
1415
//
1416
//					$graf = $div_open . "\n" . $div_content . "\n" . $div_close;
1417
//				}
1418
				$grafs[$key] = $graf;
1419
			}
1420
		}
1421
1422
		return implode("\n\n", $grafs);
1423
	}
1424
1425
1426
	function encodeAttribute($text) {
1427
	#
1428
	# Encode text for a double-quoted HTML attribute. This function
1429
	# is *not* suitable for attributes enclosed in single quotes.
1430
	#
1431
		$text = $this->encodeAmpsAndAngles($text);
1432
		$text = str_replace('"', '&quot;', $text);
1433
		return $text;
1434
	}
1435
	
1436
	
1437
	function encodeAmpsAndAngles($text) {
1438
	#
1439
	# Smart processing for ampersands and angle brackets that need to 
1440
	# be encoded. Valid character entities are left alone unless the
1441
	# no-entities mode is set.
1442
	#
1443
		if ($this->no_entities) {
1444
			$text = str_replace('&', '&amp;', $text);
1445
		} else {
1446
			# Ampersand-encoding based entirely on Nat Irons's Amputator
1447
			# MT plugin: <http://bumppo.net/projects/amputator/>
1448
			$text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', 
1449
								'&amp;', $text);;
1450
		}
1451
		# Encode remaining <'s
1452
		$text = str_replace('<', '&lt;', $text);
1453
1454
		return $text;
1455
	}
1456
1457
1458
	function doAutoLinks($text) {
1459
		$text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i', 
1460
			array(&$this, '_doAutoLinks_url_callback'), $text);
1461
1462
		# Email addresses: <[email protected]>
1463
		$text = preg_replace_callback('{
1464
			<
1465
			(?:mailto:)?
1466
			(
1467
				(?:
1468
					[-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+
1469
				|
1470
					".*?"
1471
				)
1472
				\@
1473
				(?:
1474
					[-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
1475
				|
1476
					\[[\d.a-fA-F:]+\]	# IPv4 & IPv6
1477
				)
1478
			)
1479
			>
1480
			}xi',
1481
			array(&$this, '_doAutoLinks_email_callback'), $text);
1482
		$text = preg_replace_callback('{<(tel:([^\'">\s]+))>}i',array(&$this, '_doAutoLinks_tel_callback'), $text);
1483
1484
		return $text;
1485
	}
1486
	function _doAutoLinks_tel_callback($matches) {
1487
		$url = $this->encodeAttribute($matches[1]);
1488
		$tel = $this->encodeAttribute($matches[2]);
1489
		$link = "<a href=\"$url\">$tel</a>";
1490
		return $this->hashPart($link);
1491
	}
1492
	function _doAutoLinks_url_callback($matches) {
1493
		$url = $this->encodeAttribute($matches[1]);
1494
		$link = "<a href=\"$url\">$url</a>";
1495
		return $this->hashPart($link);
1496
	}
1497
	function _doAutoLinks_email_callback($matches) {
1498
		$address = $matches[1];
1499
		$link = $this->encodeEmailAddress($address);
1500
		return $this->hashPart($link);
1501
	}
1502
1503
1504
	function encodeEmailAddress($addr) {
1505
	#
1506
	#	Input: an email address, e.g. "[email protected]"
1507
	#
1508
	#	Output: the email address as a mailto link, with each character
1509
	#		of the address encoded as either a decimal or hex entity, in
1510
	#		the hopes of foiling most address harvesting spam bots. E.g.:
1511
	#
1512
	#	  <p><a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x66;o&#111;
1513
	#        &#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;&#101;&#46;&#x63;&#111;
1514
	#        &#x6d;">&#x66;o&#111;&#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;
1515
	#        &#101;&#46;&#x63;&#111;&#x6d;</a></p>
1516
	#
1517
	#	Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
1518
	#   With some optimizations by Milian Wolff.
1519
	#
1520
		$addr = "mailto:" . $addr;
1521
		$chars = preg_split('/(?<!^)(?!$)/', $addr);
1522
		$seed = (int)abs(crc32($addr) / strlen($addr)); # Deterministic seed.
1523
		
1524
		foreach ($chars as $key => $char) {
1525
			$ord = ord($char);
1526
			# Ignore non-ascii chars.
1527
			if ($ord < 128) {
1528
				$r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
1529
				# roughly 10% raw, 45% hex, 45% dec
1530
				# '@' *must* be encoded. I insist.
1531
				if ($r > 90 && $char != '@') /* do nothing */;
1532
				else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';';
1533
				else              $chars[$key] = '&#'.$ord.';';
1534
			}
1535
		}
1536
		
1537
		$addr = implode('', $chars);
1538
		$text = implode('', array_slice($chars, 7)); # text without `mailto:`
1539
		$addr = "<a href=\"$addr\">$text</a>";
1540
1541
		return $addr;
1542
	}
1543
1544
1545
	function parseSpan($str) {
1546
	#
1547
	# Take the string $str and parse it into tokens, hashing embeded HTML,
1548
	# escaped characters and handling code spans.
1549
	#
1550
		$output = '';
1551
		
1552
		$span_re = '{
1553
				(
1554
					\\\\'.$this->escape_chars_re.'
1555
				|
1556
					(?<![`\\\\])
1557
					`+						# code span marker
1558
			'.( $this->no_markup ? '' : '
1559
				|
1560
					<!--    .*?     -->		# comment
1561
				|
1562
					<\?.*?\?> | <%.*?%>		# processing instruction
1563
				|
1564
					<[!$]?[-a-zA-Z0-9:_]+	# regular tags
1565
					(?>
1566
						\s
1567
						(?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
1568
					)?
1569
					>
1570
				|
1571
					<[-a-zA-Z0-9:_]+\s*/> # xml-style empty tag
1572
				|
1573
					</[-a-zA-Z0-9:_]+\s*> # closing tag
1574
			').'
1575
				)
1576
				}xs';
1577
1578
		while (1) {
1579
			#
1580
			# Each loop iteration seach for either the next tag, the next 
1581
			# openning code span marker, or the next escaped character. 
1582
			# Each token is then passed to handleSpanToken.
1583
			#
1584
			$parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
1585
			
1586
			# Create token from text preceding tag.
1587
			if ($parts[0] != "") {
1588
				$output .= $parts[0];
1589
			}
1590
			
1591
			# Check if we reach the end.
1592
			if (isset($parts[1])) {
1593
				$output .= $this->handleSpanToken($parts[1], $parts[2]);
1594
				$str = $parts[2];
1595
			}
1596
			else {
1597
				break;
1598
			}
1599
		}
1600
		
1601
		return $output;
1602
	}
1603
	
1604
	
1605
	function handleSpanToken($token, &$str) {
1606
	#
1607
	# Handle $token provided by parseSpan by determining its nature and 
1608
	# returning the corresponding value that should replace it.
1609
	#
1610
		switch ($token{0}) {
1611
			case "\\":
1612
				return $this->hashPart("&#". ord($token{1}). ";");
1613
			case "`":
1614
				# Search for end marker in remaining text.
1615
				if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm', 
1616
					$str, $matches))
1617
				{
1618
					$str = $matches[2];
1619
					$codespan = $this->makeCodeSpan($matches[1]);
1620
					return $this->hashPart($codespan);
1621
				}
1622
				return $token; // return as text since no ending marker found.
1623
			default:
1624
				return $this->hashPart($token);
1625
		}
1626
	}
1627
1628
1629
	function outdent($text) {
1630
	#
1631
	# Remove one level of line-leading tabs or spaces
1632
	#
1633
		return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text);
1634
	}
1635
1636
1637
	# String length function for detab. `_initDetab` will create a function to 
1638
	# hanlde UTF-8 if the default function does not exist.
1639
	var $utf8_strlen = 'mb_strlen';
1640
	
1641
	function detab($text) {
1642
	#
1643
	# Replace tabs with the appropriate amount of space.
1644
	#
1645
		# For each line we separate the line in blocks delemited by
1646
		# tab characters. Then we reconstruct every line by adding the 
1647
		# appropriate number of space between each blocks.
1648
		
1649
		$text = preg_replace_callback('/^.*\t.*$/m',
1650
			array(&$this, '_detab_callback'), $text);
1651
1652
		return $text;
1653
	}
1654
	function _detab_callback($matches) {
1655
		$line = $matches[0];
1656
		$strlen = $this->utf8_strlen; # strlen function for UTF-8.
1657
		
1658
		# Split in blocks.
1659
		$blocks = explode("\t", $line);
1660
		# Add each blocks to the line.
1661
		$line = $blocks[0];
1662
		unset($blocks[0]); # Do not add first block twice.
1663
		foreach ($blocks as $block) {
1664
			# Calculate amount of space, insert spaces, insert block.
1665
			$amount = $this->tab_width - 
1666
				$strlen($line, 'UTF-8') % $this->tab_width;
1667
			$line .= str_repeat(" ", $amount) . $block;
1668
		}
1669
		return $line;
1670
	}
1671
	function _initDetab() {
1672
	#
1673
	# Check for the availability of the function in the `utf8_strlen` property
1674
	# (initially `mb_strlen`). If the function is not available, create a 
1675
	# function that will loosely count the number of UTF-8 characters with a
1676
	# regular expression.
1677
	#
1678
		if (function_exists($this->utf8_strlen)) return;
1679
		$this->utf8_strlen = create_function('$text', 'return preg_match_all(
1680
			"/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/", 
1681
			$text, $m);');
1682
	}
1683
1684
1685
	function unhash($text) {
1686
	#
1687
	# Swap back in all the tags hashed by _HashHTMLBlocks.
1688
	#
1689
		return preg_replace_callback('/(.)\x1A[0-9]+\1/', 
1690
			array(&$this, '_unhash_callback'), $text);
1691
	}
1692
	function _unhash_callback($matches) {
1693
		return $this->html_hashes[$matches[0]];
1694
	}
1695
1696
}
1697
1698
1699
#
1700
# Markdown Extra Parser Class
1701
#
1702
1703
class MarkdownExtra_Parser extends Markdown_Parser {
1704
1705
	### Configuration Variables ###
1706
1707
	# Prefix for footnote ids.
1708
	var $fn_id_prefix = "";
1709
	
1710
	# Optional title attribute for footnote links and backlinks.
1711
	var $fn_link_title = MARKDOWN_FN_LINK_TITLE;
1712
	var $fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE;
1713
	
1714
	# Optional class attribute for footnote links and backlinks.
1715
	var $fn_link_class = MARKDOWN_FN_LINK_CLASS;
1716
	var $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS;
1717
1718
	# Optional class prefix for fenced code block.
1719
	var $code_class_prefix = MARKDOWN_CODE_CLASS_PREFIX;
1720
	# Class attribute for code blocks goes on the `code` tag;
1721
	# setting this to true will put attributes on the `pre` tag instead.
1722
	var $code_attr_on_pre = MARKDOWN_CODE_ATTR_ON_PRE;
1723
	
1724
	# Predefined abbreviations.
1725
	var $predef_abbr = array();
1726
1727
1728
	### Parser Implementation ###
1729
1730
	function MarkdownExtra_Parser() {
1731
	#
1732
	# Constructor function. Initialize the parser object.
1733
	#
1734
		# Add extra escapable characters before parent constructor 
1735
		# initialize the table.
1736
		$this->escape_chars .= ':|';
1737
		
1738
		# Insert extra document, block, and span transformations. 
1739
		# Parent constructor will do the sorting.
1740
		$this->document_gamut += array(
1741
			"doFencedCodeBlocks" => 5,
1742
			"stripFootnotes"     => 15,
1743
			"stripAbbreviations" => 25,
1744
			"appendFootnotes"    => 50,
1745
			);
1746
		$this->block_gamut += array(
1747
			"doFencedCodeBlocks" => 5,
1748
			"doTables"           => 15,
1749
			"doDefLists"         => 45,
1750
			);
1751
		$this->span_gamut += array(
1752
			"doFootnotes"        => 5,
1753
			"doAbbreviations"    => 70,
1754
			);
1755
		
1756
		parent::Markdown_Parser();
1757
	}
1758
	
1759
	
1760
	# Extra variables used during extra transformations.
1761
	var $footnotes = array();
1762
	var $footnotes_ordered = array();
1763
	var $footnotes_ref_count = array();
1764
	var $footnotes_numbers = array();
1765
	var $abbr_desciptions = array();
1766
	var $abbr_word_re = '';
1767
	
1768
	# Give the current footnote number.
1769
	var $footnote_counter = 1;
1770
	
1771
	
1772
	function setup() {
1773
	#
1774
	# Setting up Extra-specific variables.
1775
	#
1776
		parent::setup();
1777
		
1778
		$this->footnotes = array();
1779
		$this->footnotes_ordered = array();
1780
		$this->footnotes_ref_count = array();
1781
		$this->footnotes_numbers = array();
1782
		$this->abbr_desciptions = array();
1783
		$this->abbr_word_re = '';
1784
		$this->footnote_counter = 1;
1785
		
1786
		foreach ($this->predef_abbr as $abbr_word => $abbr_desc) {
1787
			if ($this->abbr_word_re)
1788
				$this->abbr_word_re .= '|';
1789
			$this->abbr_word_re .= preg_quote($abbr_word);
1790
			$this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
1791
		}
1792
	}
1793
	
1794
	function teardown() {
1795
	#
1796
	# Clearing Extra-specific variables.
1797
	#
1798
		$this->footnotes = array();
1799
		$this->footnotes_ordered = array();
1800
		$this->footnotes_ref_count = array();
1801
		$this->footnotes_numbers = array();
1802
		$this->abbr_desciptions = array();
1803
		$this->abbr_word_re = '';
1804
		
1805
		parent::teardown();
1806
	}
1807
	
1808
	
1809
	### Extra Attribute Parser ###
1810
1811
	# Expression to use to catch attributes (includes the braces)
1812
	var $id_class_attr_catch_re = '\{((?:[ ]*[#.][-_:a-zA-Z0-9]+){1,})[ ]*\}';
1813
	# Expression to use when parsing in a context when no capture is desired
1814
	var $id_class_attr_nocatch_re = '\{(?:[ ]*[#.][-_:a-zA-Z0-9]+){1,}[ ]*\}';
1815
1816
	function doExtraAttributes($tag_name, $attr) {
1817
	#
1818
	# Parse attributes caught by the $this->id_class_attr_catch_re expression
1819
	# and return the HTML-formatted list of attributes.
1820
	#
1821
	# Currently supported attributes are .class and #id.
1822
	#
1823
		if (empty($attr)) return "";
1824
		
1825
		# Split on components
1826
		preg_match_all('/[#.][-_:a-zA-Z0-9]+/', $attr, $matches);
1827
		$elements = $matches[0];
1828
1829
		# handle classes and ids (only first id taken into account)
1830
		$classes = array();
1831
		$id = false;
1832
		foreach ($elements as $element) {
1833
			if ($element{0} == '.') {
1834
				$classes[] = substr($element, 1);
1835
			} else if ($element{0} == '#') {
1836
				if ($id === false) $id = substr($element, 1);
1837
			}
1838
		}
1839
1840
		# compose attributes as string
1841
		$attr_str = "";
1842
		if (!empty($id)) {
1843
			$attr_str .= ' id="'.$id.'"';
1844
		}
1845
		if (!empty($classes)) {
1846
			$attr_str .= ' class="'.implode(" ", $classes).'"';
1847
		}
1848
		return $attr_str;
1849
	}
1850
1851
1852
	function stripLinkDefinitions($text) {
1853
	#
1854
	# Strips link definitions from text, stores the URLs and titles in
1855
	# hash references.
1856
	#
1857
		$less_than_tab = $this->tab_width - 1;
1858
1859
		# Link defs are in the form: ^[id]: url "optional title"
1860
		$text = preg_replace_callback('{
1861
							^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?:	# id = $1
1862
							  [ ]*
1863
							  \n?				# maybe *one* newline
1864
							  [ ]*
1865
							(?:
1866
							  <(.+?)>			# url = $2
1867
							|
1868
							  (\S+?)			# url = $3
1869
							)
1870
							  [ ]*
1871
							  \n?				# maybe one newline
1872
							  [ ]*
1873
							(?:
1874
								(?<=\s)			# lookbehind for whitespace
1875
								["(]
1876
								(.*?)			# title = $4
1877
								[")]
1878
								[ ]*
1879
							)?	# title is optional
1880
					(?:[ ]* '.$this->id_class_attr_catch_re.' )?  # $5 = extra id & class attr
1881
							(?:\n+|\Z)
1882
			}xm',
1883
			array(&$this, '_stripLinkDefinitions_callback'),
1884
			$text);
1885
		return $text;
1886
	}
1887
	function _stripLinkDefinitions_callback($matches) {
1888
		$link_id = strtolower($matches[1]);
1889
		$url = $matches[2] == '' ? $matches[3] : $matches[2];
1890
		$this->urls[$link_id] = $url;
1891
		$this->titles[$link_id] =& $matches[4];
1892
		$this->ref_attr[$link_id] = $this->doExtraAttributes("", $dummy =& $matches[5]);
1893
		return ''; # String that will replace the block
1894
	}
1895
1896
1897
	### HTML Block Parser ###
1898
	
1899
	# Tags that are always treated as block tags:
1900
	var $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend|article|section|nav|aside|hgroup|header|footer|figcaption';
1901
						   
1902
	# Tags treated as block tags only if the opening tag is alone on its line:
1903
	var $context_block_tags_re = 'script|noscript|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video';
1904
	
1905
	# Tags where markdown="1" default to span mode:
1906
	var $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address';
1907
	
1908
	# Tags which must not have their contents modified, no matter where 
1909
	# they appear:
1910
	var $clean_tags_re = 'script|math|svg';
1911
	
1912
	# Tags that do not need to be closed.
1913
	var $auto_close_tags_re = 'hr|img|param|source|track';
1914
	
1915
1916
	function hashHTMLBlocks($text) {
1917
	#
1918
	# Hashify HTML Blocks and "clean tags".
1919
	#
1920
	# We only want to do this for block-level HTML tags, such as headers,
1921
	# lists, and tables. That's because we still want to wrap <p>s around
1922
	# "paragraphs" that are wrapped in non-block-level tags, such as anchors,
1923
	# phrase emphasis, and spans. The list of tags we're looking for is
1924
	# hard-coded.
1925
	#
1926
	# This works by calling _HashHTMLBlocks_InMarkdown, which then calls
1927
	# _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1" 
1928
	# attribute is found within a tag, _HashHTMLBlocks_InHTML calls back
1929
	#  _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag.
1930
	# These two functions are calling each other. It's recursive!
1931
	#
1932
		if ($this->no_markup)  return $text;
1933
1934
		#
1935
		# Call the HTML-in-Markdown hasher.
1936
		#
1937
		list($text, ) = $this->_hashHTMLBlocks_inMarkdown($text);
1938
		
1939
		return $text;
1940
	}
1941
	function _hashHTMLBlocks_inMarkdown($text, $indent = 0, 
1942
										$enclosing_tag_re = '', $span = false)
1943
	{
1944
	#
1945
	# Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags.
1946
	#
1947
	# *   $indent is the number of space to be ignored when checking for code 
1948
	#     blocks. This is important because if we don't take the indent into 
1949
	#     account, something like this (which looks right) won't work as expected:
1950
	#
1951
	#     <div>
1952
	#         <div markdown="1">
1953
	#         Hello World.  <-- Is this a Markdown code block or text?
1954
	#         </div>  <-- Is this a Markdown code block or a real tag?
1955
	#     <div>
1956
	#
1957
	#     If you don't like this, just don't indent the tag on which
1958
	#     you apply the markdown="1" attribute.
1959
	#
1960
	# *   If $enclosing_tag_re is not empty, stops at the first unmatched closing 
1961
	#     tag with that name. Nested tags supported.
1962
	#
1963
	# *   If $span is true, text inside must treated as span. So any double 
1964
	#     newline will be replaced by a single newline so that it does not create 
1965
	#     paragraphs.
1966
	#
1967
	# Returns an array of that form: ( processed text , remaining text )
1968
	#
1969
		if ($text === '') return array('', '');
1970
1971
		# Regex to check for the presense of newlines around a block tag.
1972
		$newline_before_re = '/(?:^\n?|\n\n)*$/';
1973
		$newline_after_re = 
1974
			'{
1975
				^						# Start of text following the tag.
1976
				(?>[ ]*<!--.*?-->)?		# Optional comment.
1977
				[ ]*\n					# Must be followed by newline.
1978
			}xs';
1979
		
1980
		# Regex to match any tag.
1981
		$block_tag_re =
1982
			'{
1983
				(					# $2: Capture whole tag.
1984
					</?					# Any opening or closing tag.
1985
						(?>				# Tag name.
1986
							'.$this->block_tags_re.'			|
1987
							'.$this->context_block_tags_re.'	|
1988
							'.$this->clean_tags_re.'        	|
1989
							(?!\s)'.$enclosing_tag_re.'
1990
						)
1991
						(?:
1992
							(?=[\s"\'/a-zA-Z0-9])	# Allowed characters after tag name.
1993
							(?>
1994
								".*?"		|	# Double quotes (can contain `>`)
1995
								\'.*?\'   	|	# Single quotes (can contain `>`)
1996
								.+?				# Anything but quotes and `>`.
1997
							)*?
1998
						)?
1999
					>					# End of tag.
2000
				|
2001
					<!--    .*?     -->	# HTML Comment
2002
				|
2003
					<\?.*?\?> | <%.*?%>	# Processing instruction
2004
				|
2005
					<!\[CDATA\[.*?\]\]>	# CData Block
2006
				'. ( !$span ? ' # If not in span.
2007
				|
2008
					# Indented code block
2009
					(?: ^[ ]*\n | ^ | \n[ ]*\n )
2010
					[ ]{'.($indent+4).'}[^\n]* \n
2011
					(?>
2012
						(?: [ ]{'.($indent+4).'}[^\n]* | [ ]* ) \n
2013
					)*
2014
				|
2015
					# Fenced code block marker
2016
					(?<= ^ | \n )
2017
					[ ]{0,'.($indent+3).'}(?:~{3,}|`{3,})
2018
									[ ]*
2019
					(?:
2020
					\.?[-_:a-zA-Z0-9]+ # standalone class name
2021
					|
2022
						'.$this->id_class_attr_nocatch_re.' # extra attributes
2023
					)?
2024
					[ ]*
2025
					(?= \n )
2026
				' : '' ). ' # End (if not is span).
2027
				|
2028
					# Code span marker
2029
					# Note, this regex needs to go after backtick fenced
2030
					# code blocks but it should also be kept outside of the
2031
					# "if not in span" condition adding backticks to the parser
2032
					`+
2033
				)
2034
			}xs';
2035
2036
		
2037
		$depth = 0;		# Current depth inside the tag tree.
2038
		$parsed = "";	# Parsed text that will be returned.
2039
2040
		#
2041
		# Loop through every tag until we find the closing tag of the parent
2042
		# or loop until reaching the end of text if no parent tag specified.
2043
		#
2044
		do {
2045
			#
2046
			# Split the text using the first $tag_match pattern found.
2047
			# Text before  pattern will be first in the array, text after
2048
			# pattern will be at the end, and between will be any catches made 
2049
			# by the pattern.
2050
			#
2051
			$parts = preg_split($block_tag_re, $text, 2, 
2052
								PREG_SPLIT_DELIM_CAPTURE);
2053
			
2054
			# If in Markdown span mode, add a empty-string span-level hash 
2055
			# after each newline to prevent triggering any block element.
2056
			if ($span) {
2057
				$void = $this->hashPart("", ':');
2058
				$newline = "$void\n";
2059
				$parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void;
2060
			}
2061
			
2062
			$parsed .= $parts[0]; # Text before current tag.
2063
			
2064
			# If end of $text has been reached. Stop loop.
2065
			if (count($parts) < 3) {
2066
				$text = "";
2067
				break;
2068
			}
2069
			
2070
			$tag  = $parts[1]; # Tag to handle.
2071
			$text = $parts[2]; # Remaining text after current tag.
2072
			$tag_re = preg_quote($tag); # For use in a regular expression.
2073
			
2074
			#
2075
			# Check for: Fenced code block marker.
2076
			# Note: need to recheck the whole tag to disambiguate backtick
2077
			# fences from code spans
2078
			#
2079
			if (preg_match('{^\n?([ ]{0,'.($indent+3).'})(~{3,}|`{3,})[ ]*(?:\.?[-_:a-zA-Z0-9]+|'.$this->id_class_attr_nocatch_re.')?[ ]*\n?$}', $tag, $capture)) {
2080
				# Fenced code block marker: find matching end marker.
2081
				$fence_indent = strlen($capture[1]); # use captured indent in re
2082
				$fence_re = $capture[2]; # use captured fence in re
2083
				if (preg_match('{^(?>.*\n)*?[ ]{'.($fence_indent).'}'.$fence_re.'[ ]*(?:\n|$)}', $text,
2084
					$matches)) 
2085
				{
2086
					# End marker found: pass text unchanged until marker.
2087
					$parsed .= $tag . $matches[0];
2088
					$text = substr($text, strlen($matches[0]));
2089
				}
2090
				else {
2091
					# No end marker: just skip it.
2092
					$parsed .= $tag;
2093
				}
2094
			}
2095
			#
2096
			# Check for: Indented code block.
2097
			#
2098
			else if ($tag{0} == "\n" || $tag{0} == " ") {
2099
				# Indented code block: pass it unchanged, will be handled 
2100
				# later.
2101
				$parsed .= $tag;
2102
			}
2103
			#
2104
			# Check for: Code span marker
2105
			# Note: need to check this after backtick fenced code blocks
2106
			#
2107
			else if ($tag{0} == "`") {
2108
				# Find corresponding end marker.
2109
				$tag_re = preg_quote($tag);
2110
				if (preg_match('{^(?>.+?|\n(?!\n))*?(?<!`)'.$tag_re.'(?!`)}',
2111
					$text, $matches))
2112
				{
2113
					# End marker found: pass text unchanged until marker.
2114
					$parsed .= $tag . $matches[0];
2115
					$text = substr($text, strlen($matches[0]));
2116
				}
2117
				else {
2118
					# Unmatched marker: just skip it.
2119
					$parsed .= $tag;
2120
				}
2121
			}
2122
			#
2123
			# Check for: Opening Block level tag or
2124
			#            Opening Context Block tag (like ins and del) 
2125
			#               used as a block tag (tag is alone on it's line).
2126
			#
2127
			else if (preg_match('{^<(?:'.$this->block_tags_re.')\b}', $tag) ||
2128
				(	preg_match('{^<(?:'.$this->context_block_tags_re.')\b}', $tag) &&
2129
					preg_match($newline_before_re, $parsed) &&
2130
					preg_match($newline_after_re, $text)	)
2131
				)
2132
			{
2133
				# Need to parse tag and following text using the HTML parser.
2134
				list($block_text, $text) = 
2135
					$this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true);
2136
				
2137
				# Make sure it stays outside of any paragraph by adding newlines.
2138
				$parsed .= "\n\n$block_text\n\n";
2139
			}
2140
			#
2141
			# Check for: Clean tag (like script, math)
2142
			#            HTML Comments, processing instructions.
2143
			#
2144
			else if (preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) ||
2145
				$tag{1} == '!' || $tag{1} == '?')
2146
			{
2147
				# Need to parse tag and following text using the HTML parser.
2148
				# (don't check for markdown attribute)
2149
				list($block_text, $text) = 
2150
					$this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false);
2151
				
2152
				$parsed .= $block_text;
2153
			}
2154
			#
2155
			# Check for: Tag with same name as enclosing tag.
2156
			#
2157
			else if ($enclosing_tag_re !== '' &&
2158
				# Same name as enclosing tag.
2159
				preg_match('{^</?(?:'.$enclosing_tag_re.')\b}', $tag))
2160
			{
2161
				#
2162
				# Increase/decrease nested tag count.
2163
				#
2164
				if ($tag{1} == '/')						$depth--;
2165
				else if ($tag{strlen($tag)-2} != '/')	$depth++;
2166
2167
				if ($depth < 0) {
2168
					#
2169
					# Going out of parent element. Clean up and break so we
2170
					# return to the calling function.
2171
					#
2172
					$text = $tag . $text;
2173
					break;
2174
				}
2175
				
2176
				$parsed .= $tag;
2177
			}
2178
			else {
2179
				$parsed .= $tag;
2180
			}
2181
		} while ($depth >= 0);
2182
		
2183
		return array($parsed, $text);
2184
	}
2185
	function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) {
2186
	#
2187
	# Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags.
2188
	#
2189
	# *   Calls $hash_method to convert any blocks.
2190
	# *   Stops when the first opening tag closes.
2191
	# *   $md_attr indicate if the use of the `markdown="1"` attribute is allowed.
2192
	#     (it is not inside clean tags)
2193
	#
2194
	# Returns an array of that form: ( processed text , remaining text )
2195
	#
2196
		if ($text === '') return array('', '');
2197
		
2198
		# Regex to match `markdown` attribute inside of a tag.
2199
		$markdown_attr_re = '
2200
			{
2201
				\s*			# Eat whitespace before the `markdown` attribute
2202
				markdown
2203
				\s*=\s*
2204
				(?>
2205
					(["\'])		# $1: quote delimiter		
2206
					(.*?)		# $2: attribute value
2207
					\1			# matching delimiter	
2208
				|
2209
					([^\s>]*)	# $3: unquoted attribute value
2210
				)
2211
				()				# $4: make $3 always defined (avoid warnings)
2212
			}xs';
2213
		
2214
		# Regex to match any tag.
2215
		$tag_re = '{
2216
				(					# $2: Capture whole tag.
2217
					</?					# Any opening or closing tag.
2218
						[\w:$]+			# Tag name.
2219
						(?:
2220
							(?=[\s"\'/a-zA-Z0-9])	# Allowed characters after tag name.
2221
							(?>
2222
								".*?"		|	# Double quotes (can contain `>`)
2223
								\'.*?\'   	|	# Single quotes (can contain `>`)
2224
								.+?				# Anything but quotes and `>`.
2225
							)*?
2226
						)?
2227
					>					# End of tag.
2228
				|
2229
					<!--    .*?     -->	# HTML Comment
2230
				|
2231
					<\?.*?\?> | <%.*?%>	# Processing instruction
2232
				|
2233
					<!\[CDATA\[.*?\]\]>	# CData Block
2234
				)
2235
			}xs';
2236
		
2237
		$original_text = $text;		# Save original text in case of faliure.
2238
		
2239
		$depth		= 0;	# Current depth inside the tag tree.
2240
		$block_text	= "";	# Temporary text holder for current text.
2241
		$parsed		= "";	# Parsed text that will be returned.
2242
2243
		#
2244
		# Get the name of the starting tag.
2245
		# (This pattern makes $base_tag_name_re safe without quoting.)
2246
		#
2247
		if (preg_match('/^<([\w:$]*)\b/', $text, $matches))
2248
			$base_tag_name_re = $matches[1];
2249
2250
		#
2251
		# Loop through every tag until we find the corresponding closing tag.
2252
		#
2253
		do {
2254
			#
2255
			# Split the text using the first $tag_match pattern found.
2256
			# Text before  pattern will be first in the array, text after
2257
			# pattern will be at the end, and between will be any catches made 
2258
			# by the pattern.
2259
			#
2260
			$parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
2261
			
2262
			if (count($parts) < 3) {
2263
				#
2264
				# End of $text reached with unbalenced tag(s).
2265
				# In that case, we return original text unchanged and pass the
2266
				# first character as filtered to prevent an infinite loop in the 
2267
				# parent function.
2268
				#
2269
				return array($original_text{0}, substr($original_text, 1));
2270
			}
2271
			
2272
			$block_text .= $parts[0]; # Text before current tag.
2273
			$tag         = $parts[1]; # Tag to handle.
2274
			$text        = $parts[2]; # Remaining text after current tag.
2275
			
2276
			#
2277
			# Check for: Auto-close tag (like <hr/>)
2278
			#			 Comments and Processing Instructions.
2279
			#
2280
			if (preg_match('{^</?(?:'.$this->auto_close_tags_re.')\b}', $tag) ||
2281
				$tag{1} == '!' || $tag{1} == '?')
2282
			{
2283
				# Just add the tag to the block as if it was text.
2284
				$block_text .= $tag;
2285
			}
2286
			else {
2287
				#
2288
				# Increase/decrease nested tag count. Only do so if
2289
				# the tag's name match base tag's.
2290
				#
2291
				if (preg_match('{^</?'.$base_tag_name_re.'\b}', $tag)) {
2292
					if ($tag{1} == '/')						$depth--;
2293
					else if ($tag{strlen($tag)-2} != '/')	$depth++;
2294
				}
2295
				
2296
				#
2297
				# Check for `markdown="1"` attribute and handle it.
2298
				#
2299
				if ($md_attr && 
2300
					preg_match($markdown_attr_re, $tag, $attr_m) &&
2301
					preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3]))
2302
				{
2303
					# Remove `markdown` attribute from opening tag.
2304
					$tag = preg_replace($markdown_attr_re, '', $tag);
2305
					
2306
					# Check if text inside this tag must be parsed in span mode.
2307
					$this->mode = $attr_m[2] . $attr_m[3];
2308
					$span_mode = $this->mode == 'span' || $this->mode != 'block' &&
2309
						preg_match('{^<(?:'.$this->contain_span_tags_re.')\b}', $tag);
2310
					
2311
					# Calculate indent before tag.
2312
					if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) {
2313
						$strlen = $this->utf8_strlen;
2314
						$indent = $strlen($matches[1], 'UTF-8');
2315
					} else {
2316
						$indent = 0;
2317
					}
2318
					
2319
					# End preceding block with this tag.
2320
					$block_text .= $tag;
2321
					$parsed .= $this->$hash_method($block_text);
2322
					
2323
					# Get enclosing tag name for the ParseMarkdown function.
2324
					# (This pattern makes $tag_name_re safe without quoting.)
2325
					preg_match('/^<([\w:$]*)\b/', $tag, $matches);
2326
					$tag_name_re = $matches[1];
2327
					
2328
					# Parse the content using the HTML-in-Markdown parser.
2329
					list ($block_text, $text)
2330
						= $this->_hashHTMLBlocks_inMarkdown($text, $indent, 
2331
							$tag_name_re, $span_mode);
2332
					
2333
					# Outdent markdown text.
2334
					if ($indent > 0) {
2335
						$block_text = preg_replace("/^[ ]{1,$indent}/m", "", 
2336
													$block_text);
2337
					}
2338
					
2339
					# Append tag content to parsed text.
2340
					if (!$span_mode)	$parsed .= "\n\n$block_text\n\n";
2341
					else				$parsed .= "$block_text";
2342
					
2343
					# Start over with a new block.
2344
					$block_text = "";
2345
				}
2346
				else $block_text .= $tag;
2347
			}
2348
			
2349
		} while ($depth > 0);
2350
		
2351
		#
2352
		# Hash last block text that wasn't processed inside the loop.
2353
		#
2354
		$parsed .= $this->$hash_method($block_text);
2355
		
2356
		return array($parsed, $text);
2357
	}
2358
2359
2360
	function hashClean($text) {
2361
	#
2362
	# Called whenever a tag must be hashed when a function inserts a "clean" tag
2363
	# in $text, it passes through this function and is automaticaly escaped, 
2364
	# blocking invalid nested overlap.
2365
	#
2366
		return $this->hashPart($text, 'C');
2367
	}
2368
2369
2370
	function doAnchors($text) {
2371
	#
2372
	# Turn Markdown link shortcuts into XHTML <a> tags.
2373
	#
2374
		if ($this->in_anchor) return $text;
2375
		$this->in_anchor = true;
2376
		
2377
		#
2378
		# First, handle reference-style links: [link text] [id]
2379
		#
2380
		$text = preg_replace_callback('{
2381
			(					# wrap whole match in $1
2382
			  \[
2383
				('.$this->nested_brackets_re.')	# link text = $2
2384
			  \]
2385
2386
			  [ ]?				# one optional space
2387
			  (?:\n[ ]*)?		# one optional newline followed by spaces
2388
2389
			  \[
2390
				(.*?)		# id = $3
2391
			  \]
2392
			)
2393
			}xs',
2394
			array(&$this, '_doAnchors_reference_callback'), $text);
2395
2396
		#
2397
		# Next, inline-style links: [link text](url "optional title")
2398
		#
2399
		$text = preg_replace_callback('{
2400
			(				# wrap whole match in $1
2401
			  \[
2402
				('.$this->nested_brackets_re.')	# link text = $2
2403
			  \]
2404
			  \(			# literal paren
2405
				[ \n]*
2406
				(?:
2407
					<(.+?)>	# href = $3
2408
				|
2409
					('.$this->nested_url_parenthesis_re.')	# href = $4
2410
				)
2411
				[ \n]*
2412
				(			# $5
2413
				  ([\'"])	# quote char = $6
2414
				  (.*?)		# Title = $7
2415
				  \6		# matching quote
2416
				  [ \n]*	# ignore any spaces/tabs between closing quote and )
2417
				)?			# title is optional
2418
			  \)
2419
			  (?:[ ]? '.$this->id_class_attr_catch_re.' )?	 # $8 = id/class attributes
2420
			)
2421
			}xs',
2422
			array(&$this, '_doAnchors_inline_callback'), $text);
2423
2424
		#
2425
		# Last, handle reference-style shortcuts: [link text]
2426
		# These must come last in case you've also got [link text][1]
2427
		# or [link text](/foo)
2428
		#
2429
		$text = preg_replace_callback('{
2430
			(					# wrap whole match in $1
2431
			  \[
2432
				([^\[\]]+)		# link text = $2; can\'t contain [ or ]
2433
			  \]
2434
			)
2435
			}xs',
2436
			array(&$this, '_doAnchors_reference_callback'), $text);
2437
2438
		$this->in_anchor = false;
2439
		return $text;
2440
	}
2441
	function _doAnchors_reference_callback($matches) {
2442
		$whole_match =  $matches[1];
2443
		$link_text   =  $matches[2];
2444
		$link_id     =& $matches[3];
2445
2446
		if ($link_id == "") {
2447
			# for shortcut links like [this][] or [this].
2448
			$link_id = $link_text;
2449
		}
2450
		
2451
		# lower-case and turn embedded newlines into spaces
2452
		$link_id = strtolower($link_id);
2453
		$link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
2454
2455
		if (isset($this->urls[$link_id])) {
2456
			$url = $this->urls[$link_id];
2457
			$url = $this->encodeAttribute($url);
2458
			
2459
			$result = "<a href=\"$url\"";
2460
			if ( isset( $this->titles[$link_id] ) ) {
2461
				$title = $this->titles[$link_id];
2462
				$title = $this->encodeAttribute($title);
2463
				$result .=  " title=\"$title\"";
2464
			}
2465
			if (isset($this->ref_attr[$link_id]))
2466
				$result .= $this->ref_attr[$link_id];
2467
		
2468
			$link_text = $this->runSpanGamut($link_text);
2469
			$result .= ">$link_text</a>";
2470
			$result = $this->hashPart($result);
2471
		}
2472
		else {
2473
			$result = $whole_match;
2474
		}
2475
		return $result;
2476
	}
2477
	function _doAnchors_inline_callback($matches) {
2478
		$whole_match	=  $matches[1];
2479
		$link_text		=  $this->runSpanGamut($matches[2]);
2480
		$url			=  $matches[3] == '' ? $matches[4] : $matches[3];
2481
		$title			=& $matches[7];
2482
		$attr  = $this->doExtraAttributes("a", $dummy =& $matches[8]);
2483
2484
2485
		$url = $this->encodeAttribute($url);
2486
2487
		$result = "<a href=\"$url\"";
2488
		if (isset($title)) {
2489
			$title = $this->encodeAttribute($title);
2490
			$result .=  " title=\"$title\"";
2491
		}
2492
		$result .= $attr;
2493
		
2494
		$link_text = $this->runSpanGamut($link_text);
2495
		$result .= ">$link_text</a>";
2496
2497
		return $this->hashPart($result);
2498
	}
2499
2500
2501
	function doImages($text) {
2502
	#
2503
	# Turn Markdown image shortcuts into <img> tags.
2504
	#
2505
		#
2506
		# First, handle reference-style labeled images: ![alt text][id]
2507
		#
2508
		$text = preg_replace_callback('{
2509
			(				# wrap whole match in $1
2510
			  !\[
2511
				('.$this->nested_brackets_re.')		# alt text = $2
2512
			  \]
2513
2514
			  [ ]?				# one optional space
2515
			  (?:\n[ ]*)?		# one optional newline followed by spaces
2516
2517
			  \[
2518
				(.*?)		# id = $3
2519
			  \]
2520
2521
			)
2522
			}xs', 
2523
			array(&$this, '_doImages_reference_callback'), $text);
2524
2525
		#
2526
		# Next, handle inline images:  ![alt text](url "optional title")
2527
		# Don't forget: encode * and _
2528
		#
2529
		$text = preg_replace_callback('{
2530
			(				# wrap whole match in $1
2531
			  !\[
2532
				('.$this->nested_brackets_re.')		# alt text = $2
2533
			  \]
2534
			  \s?			# One optional whitespace character
2535
			  \(			# literal paren
2536
				[ \n]*
2537
				(?:
2538
					<(\S*)>	# src url = $3
2539
				|
2540
					('.$this->nested_url_parenthesis_re.')	# src url = $4
2541
				)
2542
				[ \n]*
2543
				(			# $5
2544
				  ([\'"])	# quote char = $6
2545
				  (.*?)		# title = $7
2546
				  \6		# matching quote
2547
				  [ \n]*
2548
				)?			# title is optional
2549
			  \)
2550
			  (?:[ ]? '.$this->id_class_attr_catch_re.' )?	 # $8 = id/class attributes
2551
			)
2552
			}xs',
2553
			array(&$this, '_doImages_inline_callback'), $text);
2554
2555
		return $text;
2556
	}
2557
	function _doImages_reference_callback($matches) {
2558
		$whole_match = $matches[1];
2559
		$alt_text    = $matches[2];
2560
		$link_id     = strtolower($matches[3]);
2561
2562
		if ($link_id == "") {
2563
			$link_id = strtolower($alt_text); # for shortcut links like ![this][].
2564
		}
2565
2566
		$alt_text = $this->encodeAttribute($alt_text);
2567
		if (isset($this->urls[$link_id])) {
2568
			$url = $this->encodeAttribute($this->urls[$link_id]);
2569
			$result = "<img src=\"$url\" alt=\"$alt_text\"";
2570
			if (isset($this->titles[$link_id])) {
2571
				$title = $this->titles[$link_id];
2572
				$title = $this->encodeAttribute($title);
2573
				$result .=  " title=\"$title\"";
2574
			}
2575
			if (isset($this->ref_attr[$link_id]))
2576
				$result .= $this->ref_attr[$link_id];
2577
			$result .= $this->empty_element_suffix;
2578
			$result = $this->hashPart($result);
2579
		}
2580
		else {
2581
			# If there's no such link ID, leave intact:
2582
			$result = $whole_match;
2583
		}
2584
2585
		return $result;
2586
	}
2587
	function _doImages_inline_callback($matches) {
2588
		$whole_match	= $matches[1];
2589
		$alt_text		= $matches[2];
2590
		$url			= $matches[3] == '' ? $matches[4] : $matches[3];
2591
		$title			=& $matches[7];
2592
		$attr  = $this->doExtraAttributes("img", $dummy =& $matches[8]);
2593
2594
		$alt_text = $this->encodeAttribute($alt_text);
2595
		$url = $this->encodeAttribute($url);
2596
		$result = "<img src=\"$url\" alt=\"$alt_text\"";
2597
		if (isset($title)) {
2598
			$title = $this->encodeAttribute($title);
2599
			$result .=  " title=\"$title\""; # $title already quoted
2600
		}
2601
		$result .= $attr;
2602
		$result .= $this->empty_element_suffix;
2603
2604
		return $this->hashPart($result);
2605
	}
2606
2607
2608
	function doHeaders($text) {
2609
	#
2610
	# Redefined to add id and class attribute support.
2611
	#
2612
		# Setext-style headers:
2613
		#	  Header 1  {#header1}
2614
		#	  ========
2615
		#  
2616
		#	  Header 2  {#header2 .class1 .class2}
2617
		#	  --------
2618
		#
2619
		$text = preg_replace_callback(
2620
			'{
2621
				(^.+?)								# $1: Header text
2622
				(?:[ ]+ '.$this->id_class_attr_catch_re.' )?	 # $3 = id/class attributes
2623
				[ ]*\n(=+|-+)[ ]*\n+				# $3: Header footer
2624
			}mx',
2625
			array(&$this, '_doHeaders_callback_setext'), $text);
2626
2627
		# atx-style headers:
2628
		#	# Header 1        {#header1}
2629
		#	## Header 2       {#header2}
2630
		#	## Header 2 with closing hashes ##  {#header3.class1.class2}
2631
		#	...
2632
		#	###### Header 6   {.class2}
2633
		#
2634
		$text = preg_replace_callback('{
2635
				^(\#{1,6})	# $1 = string of #\'s
2636
				[ ]*
2637
				(.+?)		# $2 = Header text
2638
				[ ]*
2639
				\#*			# optional closing #\'s (not counted)
2640
				(?:[ ]+ '.$this->id_class_attr_catch_re.' )?	 # $3 = id/class attributes
2641
				[ ]*
2642
				\n+
2643
			}xm',
2644
			array(&$this, '_doHeaders_callback_atx'), $text);
2645
2646
		return $text;
2647
	}
2648
	function _doHeaders_callback_setext($matches) {
2649
		if ($matches[3] == '-' && preg_match('{^- }', $matches[1]))
2650
			return $matches[0];
2651
		$level = $matches[3]{0} == '=' ? 1 : 2;
2652
		$attr  = $this->doExtraAttributes("h$level", $dummy =& $matches[2]);
2653
		$block = "<h$level$attr>".$this->runSpanGamut($matches[1])."</h$level>";
2654
		return "\n" . $this->hashBlock($block) . "\n\n";
2655
	}
2656
	function _doHeaders_callback_atx($matches) {
2657
		$level = strlen($matches[1]);
2658
		$attr  = $this->doExtraAttributes("h$level", $dummy =& $matches[3]);
2659
		$block = "<h$level$attr>".$this->runSpanGamut($matches[2])."</h$level>";
2660
		return "\n" . $this->hashBlock($block) . "\n\n";
2661
	}
2662
2663
2664
	function doTables($text) {
2665
	#
2666
	# Form HTML tables.
2667
	#
2668
		$less_than_tab = $this->tab_width - 1;
2669
		#
2670
		# Find tables with leading pipe.
2671
		#
2672
		#	| Header 1 | Header 2
2673
		#	| -------- | --------
2674
		#	| Cell 1   | Cell 2
2675
		#	| Cell 3   | Cell 4
2676
		#
2677
		$text = preg_replace_callback('
2678
			{
2679
				^							# Start of a line
2680
				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
2681
				[|]							# Optional leading pipe (present)
2682
				(.+) \n						# $1: Header row (at least one pipe)
2683
				
2684
				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
2685
				[|] ([ ]*[-:]+[-| :]*) \n	# $2: Header underline
2686
				
2687
				(							# $3: Cells
2688
					(?>
2689
						[ ]*				# Allowed whitespace.
2690
						[|] .* \n			# Row content.
2691
					)*
2692
				)
2693
				(?=\n|\Z)					# Stop at final double newline.
2694
			}xm',
2695
			array(&$this, '_doTable_leadingPipe_callback'), $text);
2696
		
2697
		#
2698
		# Find tables without leading pipe.
2699
		#
2700
		#	Header 1 | Header 2
2701
		#	-------- | --------
2702
		#	Cell 1   | Cell 2
2703
		#	Cell 3   | Cell 4
2704
		#
2705
		$text = preg_replace_callback('
2706
			{
2707
				^							# Start of a line
2708
				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
2709
				(\S.*[|].*) \n				# $1: Header row (at least one pipe)
2710
				
2711
				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
2712
				([-:]+[ ]*[|][-| :]*) \n	# $2: Header underline
2713
				
2714
				(							# $3: Cells
2715
					(?>
2716
						.* [|] .* \n		# Row content
2717
					)*
2718
				)
2719
				(?=\n|\Z)					# Stop at final double newline.
2720
			}xm',
2721
			array(&$this, '_DoTable_callback'), $text);
2722
2723
		return $text;
2724
	}
2725
	function _doTable_leadingPipe_callback($matches) {
2726
		$head		= $matches[1];
2727
		$underline	= $matches[2];
2728
		$content	= $matches[3];
2729
		
2730
		# Remove leading pipe for each row.
2731
		$content	= preg_replace('/^ *[|]/m', '', $content);
2732
		
2733
		return $this->_doTable_callback(array($matches[0], $head, $underline, $content));
2734
	}
2735
	function _doTable_callback($matches) {
2736
		$head		= $matches[1];
2737
		$underline	= $matches[2];
2738
		$content	= $matches[3];
2739
2740
		# Remove any tailing pipes for each line.
2741
		$head		= preg_replace('/[|] *$/m', '', $head);
2742
		$underline	= preg_replace('/[|] *$/m', '', $underline);
2743
		$content	= preg_replace('/[|] *$/m', '', $content);
2744
		
2745
		# Reading alignement from header underline.
2746
		$separators	= preg_split('/ *[|] */', $underline);
2747
		foreach ($separators as $n => $s) {
2748
			if (preg_match('/^ *-+: *$/', $s))		$attr[$n] = ' align="right"';
2749
			else if (preg_match('/^ *:-+: *$/', $s))$attr[$n] = ' align="center"';
2750
			else if (preg_match('/^ *:-+ *$/', $s))	$attr[$n] = ' align="left"';
2751
			else									$attr[$n] = '';
2752
		}
2753
		
2754
		# Parsing span elements, including code spans, character escapes, 
2755
		# and inline HTML tags, so that pipes inside those gets ignored.
2756
		$head		= $this->parseSpan($head);
2757
		$headers	= preg_split('/ *[|] */', $head);
2758
		$col_count	= count($headers);
2759
		$attr       = array_pad($attr, $col_count, '');
2760
		
2761
		# Write column headers.
2762
		$text = "<table>\n";
2763
		$text .= "<thead>\n";
2764
		$text .= "<tr>\n";
2765
		foreach ($headers as $n => $header)
2766
			$text .= "  <th$attr[$n]>".$this->runSpanGamut(trim($header))."</th>\n";
2767
		$text .= "</tr>\n";
2768
		$text .= "</thead>\n";
2769
		
2770
		# Split content by row.
2771
		$rows = explode("\n", trim($content, "\n"));
2772
		
2773
		$text .= "<tbody>\n";
2774
		foreach ($rows as $row) {
2775
			# Parsing span elements, including code spans, character escapes, 
2776
			# and inline HTML tags, so that pipes inside those gets ignored.
2777
			$row = $this->parseSpan($row);
2778
			
2779
			# Split row by cell.
2780
			$row_cells = preg_split('/ *[|] */', $row, $col_count);
2781
			$row_cells = array_pad($row_cells, $col_count, '');
2782
			
2783
			$text .= "<tr>\n";
2784
			foreach ($row_cells as $n => $cell)
2785
				$text .= "  <td$attr[$n]>".$this->runSpanGamut(trim($cell))."</td>\n";
2786
			$text .= "</tr>\n";
2787
		}
2788
		$text .= "</tbody>\n";
2789
		$text .= "</table>";
2790
		
2791
		return $this->hashBlock($text) . "\n";
2792
	}
2793
2794
	
2795
	function doDefLists($text) {
2796
	#
2797
	# Form HTML definition lists.
2798
	#
2799
		$less_than_tab = $this->tab_width - 1;
2800
2801
		# Re-usable pattern to match any entire dl list:
2802
		$whole_list_re = '(?>
2803
			(								# $1 = whole list
2804
			  (								# $2
2805
				[ ]{0,'.$less_than_tab.'}
2806
				((?>.*\S.*\n)+)				# $3 = defined term
2807
				\n?
2808
				[ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2809
			  )
2810
			  (?s:.+?)
2811
			  (								# $4
2812
				  \z
2813
				|
2814
				  \n{2,}
2815
				  (?=\S)
2816
				  (?!						# Negative lookahead for another term
2817
					[ ]{0,'.$less_than_tab.'}
2818
					(?: \S.*\n )+?			# defined term
2819
					\n?
2820
					[ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2821
				  )
2822
				  (?!						# Negative lookahead for another definition
2823
					[ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2824
				  )
2825
			  )
2826
			)
2827
		)'; // mx
2828
2829
		$text = preg_replace_callback('{
2830
				(?>\A\n?|(?<=\n\n))
2831
				'.$whole_list_re.'
2832
			}mx',
2833
			array(&$this, '_doDefLists_callback'), $text);
2834
2835
		return $text;
2836
	}
2837
	function _doDefLists_callback($matches) {
2838
		# Re-usable patterns to match list item bullets and number markers:
2839
		$list = $matches[1];
2840
		
2841
		# Turn double returns into triple returns, so that we can make a
2842
		# paragraph for the last item in a list, if necessary:
2843
		$result = trim($this->processDefListItems($list));
2844
		$result = "<dl>\n" . $result . "\n</dl>";
2845
		return $this->hashBlock($result) . "\n\n";
2846
	}
2847
2848
2849
	function processDefListItems($list_str) {
2850
	#
2851
	#	Process the contents of a single definition list, splitting it
2852
	#	into individual term and definition list items.
2853
	#
2854
		$less_than_tab = $this->tab_width - 1;
2855
		
2856
		# trim trailing blank lines:
2857
		$list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
2858
2859
		# Process definition terms.
2860
		$list_str = preg_replace_callback('{
2861
			(?>\A\n?|\n\n+)					# leading line
2862
			(								# definition terms = $1
2863
				[ ]{0,'.$less_than_tab.'}	# leading whitespace
2864
				(?!\:[ ]|[ ])				# negative lookahead for a definition
2865
											#   mark (colon) or more whitespace.
2866
				(?> \S.* \n)+?				# actual term (not whitespace).	
2867
			)			
2868
			(?=\n?[ ]{0,3}:[ ])				# lookahead for following line feed 
2869
											#   with a definition mark.
2870
			}xm',
2871
			array(&$this, '_processDefListItems_callback_dt'), $list_str);
2872
2873
		# Process actual definitions.
2874
		$list_str = preg_replace_callback('{
2875
			\n(\n+)?						# leading line = $1
2876
			(								# marker space = $2
2877
				[ ]{0,'.$less_than_tab.'}	# whitespace before colon
2878
				\:[ ]+						# definition mark (colon)
2879
			)
2880
			((?s:.+?))						# definition text = $3
2881
			(?= \n+ 						# stop at next definition mark,
2882
				(?:							# next term or end of text
2883
					[ ]{0,'.$less_than_tab.'} \:[ ]	|
2884
					<dt> | \z
2885
				)						
2886
			)					
2887
			}xm',
2888
			array(&$this, '_processDefListItems_callback_dd'), $list_str);
2889
2890
		return $list_str;
2891
	}
2892
	function _processDefListItems_callback_dt($matches) {
2893
		$terms = explode("\n", trim($matches[1]));
2894
		$text = '';
2895
		foreach ($terms as $term) {
2896
			$term = $this->runSpanGamut(trim($term));
2897
			$text .= "\n<dt>" . $term . "</dt>";
2898
		}
2899
		return $text . "\n";
2900
	}
2901
	function _processDefListItems_callback_dd($matches) {
2902
		$leading_line	= $matches[1];
2903
		$marker_space	= $matches[2];
2904
		$def			= $matches[3];
2905
2906
		if ($leading_line || preg_match('/\n{2,}/', $def)) {
2907
			# Replace marker with the appropriate whitespace indentation
2908
			$def = str_repeat(' ', strlen($marker_space)) . $def;
2909
			$def = $this->runBlockGamut($this->outdent($def . "\n\n"));
2910
			$def = "\n". $def ."\n";
2911
		}
2912
		else {
2913
			$def = rtrim($def);
2914
			$def = $this->runSpanGamut($this->outdent($def));
2915
		}
2916
2917
		return "\n<dd>" . $def . "</dd>\n";
2918
	}
2919
2920
2921
	function doFencedCodeBlocks($text) {
2922
	#
2923
	# Adding the fenced code block syntax to regular Markdown:
2924
	#
2925
	# ~~~
2926
	# Code block
2927
	# ~~~
2928
	#
2929
		$less_than_tab = $this->tab_width;
2930
		
2931
		$text = preg_replace_callback('{
2932
				(?:\n|\A)
2933
				# 1: Opening marker
2934
				(
2935
					(?:~{3,}|`{3,}) # 3 or more tildes/backticks.
2936
				)
2937
				[ ]*
2938
				(?:
2939
					\.?([-_:a-zA-Z0-9]+) # 2: standalone class name
2940
				|
2941
					'.$this->id_class_attr_catch_re.' # 3: Extra attributes
2942
				)?
2943
				[ ]* \n # Whitespace and newline following marker.
2944
				
2945
				# 4: Content
2946
				(
2947
					(?>
2948
						(?!\1 [ ]* \n)	# Not a closing marker.
2949
						.*\n+
2950
					)+
2951
				)
2952
				
2953
				# Closing marker.
2954
				\1 [ ]* (?= \n )
2955
			}xm',
2956
			array(&$this, '_doFencedCodeBlocks_callback'), $text);
2957
2958
		return $text;
2959
	}
2960
	function _doFencedCodeBlocks_callback($matches) {
2961
		$classname =& $matches[2];
2962
		$attrs     =& $matches[3];
2963
		$codeblock = $matches[4];
2964
		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
2965
		$codeblock = preg_replace_callback('/^\n+/',
2966
			array(&$this, '_doFencedCodeBlocks_newlines'), $codeblock);
2967
2968
		if ($classname != "") {
2969
			if ($classname{0} == '.')
2970
				$classname = substr($classname, 1);
2971
			$attr_str = ' class="'.$this->code_class_prefix.$classname.'"';
2972
		} else {
2973
			$attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs);
2974
		}
2975
		$pre_attr_str  = $this->code_attr_on_pre ? $attr_str : '';
2976
		$code_attr_str = $this->code_attr_on_pre ? '' : $attr_str;
2977
		$codeblock  = "<pre$pre_attr_str><code$code_attr_str>$codeblock</code></pre>";
2978
		
2979
		return "\n\n".$this->hashBlock($codeblock)."\n\n";
2980
	}
2981
	function _doFencedCodeBlocks_newlines($matches) {
2982
		return str_repeat("<br$this->empty_element_suffix", 
2983
			strlen($matches[0]));
2984
	}
2985
2986
2987
	#
2988
	# Redefining emphasis markers so that emphasis by underscore does not
2989
	# work in the middle of a word.
2990
	#
2991
	var $em_relist = array(
2992
		''  => '(?:(?<!\*)\*(?!\*)|(?<![a-zA-Z0-9_])_(?!_))(?=\S|$)(?![\.,:;]\s)',
2993
		'*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
2994
		'_' => '(?<=\S|^)(?<!_)_(?![a-zA-Z0-9_])',
2995
		);
2996
	var $strong_relist = array(
2997
		''   => '(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?=\S|$)(?![\.,:;]\s)',
2998
		'**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
2999
		'__' => '(?<=\S|^)(?<!_)__(?![a-zA-Z0-9_])',
3000
		);
3001
	var $em_strong_relist = array(
3002
		''    => '(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?=\S|$)(?![\.,:;]\s)',
3003
		'***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
3004
		'___' => '(?<=\S|^)(?<!_)___(?![a-zA-Z0-9_])',
3005
		);
3006
3007
3008
	function formParagraphs($text) {
3009
	#
3010
	#	Params:
3011
	#		$text - string to process with html <p> tags
3012
	#
3013
		# Strip leading and trailing lines:
3014
		$text = preg_replace('/\A\n+|\n+\z/', '', $text);
3015
		
3016
		$grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
3017
3018
		#
3019
		# Wrap <p> tags and unhashify HTML blocks
3020
		#
3021
		foreach ($grafs as $key => $value) {
3022
			$value = trim($this->runSpanGamut($value));
3023
			
3024
			# Check if this should be enclosed in a paragraph.
3025
			# Clean tag hashes & block tag hashes are left alone.
3026
			$is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value);
3027
			
3028
			if ($is_p) {
3029
				$value = "<p>$value</p>";
3030
			}
3031
			$grafs[$key] = $value;
3032
		}
3033
		
3034
		# Join grafs in one text, then unhash HTML tags. 
3035
		$text = implode("\n\n", $grafs);
3036
		
3037
		# Finish by removing any tag hashes still present in $text.
3038
		$text = $this->unhash($text);
3039
		
3040
		return $text;
3041
	}
3042
	
3043
	
3044
	### Footnotes
3045
	
3046
	function stripFootnotes($text) {
3047
	#
3048
	# Strips link definitions from text, stores the URLs and titles in
3049
	# hash references.
3050
	#
3051
		$less_than_tab = $this->tab_width - 1;
3052
3053
		# Link defs are in the form: [^id]: url "optional title"
3054
		$text = preg_replace_callback('{
3055
			^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?:	# note_id = $1
3056
			  [ ]*
3057
			  \n?					# maybe *one* newline
3058
			(						# text = $2 (no blank lines allowed)
3059
				(?:					
3060
					.+				# actual text
3061
				|
3062
					\n				# newlines but 
3063
					(?!\[\^.+?\]:\s)# negative lookahead for footnote marker.
3064
					(?!\n+[ ]{0,3}\S)# ensure line is not blank and followed 
3065
									# by non-indented content
3066
				)*
3067
			)		
3068
			}xm',
3069
			array(&$this, '_stripFootnotes_callback'),
3070
			$text);
3071
		return $text;
3072
	}
3073
	function _stripFootnotes_callback($matches) {
3074
		$note_id = $this->fn_id_prefix . $matches[1];
3075
		$this->footnotes[$note_id] = $this->outdent($matches[2]);
3076
		return ''; # String that will replace the block
3077
	}
3078
3079
3080
	function doFootnotes($text) {
3081
	#
3082
	# Replace footnote references in $text [^id] with a special text-token 
3083
	# which will be replaced by the actual footnote marker in appendFootnotes.
3084
	#
3085
		if (!$this->in_anchor) {
3086
			$text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text);
3087
		}
3088
		return $text;
3089
	}
3090
3091
	
3092
	function appendFootnotes($text) {
3093
	#
3094
	# Append footnote list to text.
3095
	#
3096
		$text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', 
3097
			array(&$this, '_appendFootnotes_callback'), $text);
3098
	
3099
		if (!empty($this->footnotes_ordered)) {
3100
			$text .= "\n\n";
3101
			$text .= "<div class=\"footnotes\">\n";
3102
			$text .= "<hr". $this->empty_element_suffix ."\n";
3103
			$text .= "<ol>\n\n";
3104
			
3105
			$attr = " rev=\"footnote\"";
3106
			if ($this->fn_backlink_class != "") {
3107
				$class = $this->fn_backlink_class;
3108
				$class = $this->encodeAttribute($class);
3109
				$attr .= " class=\"$class\"";
3110
			}
3111
			if ($this->fn_backlink_title != "") {
3112
				$title = $this->fn_backlink_title;
3113
				$title = $this->encodeAttribute($title);
3114
				$attr .= " title=\"$title\"";
3115
			}
3116
			$num = 0;
3117
			
3118
			while (!empty($this->footnotes_ordered)) {
3119
				$footnote = reset($this->footnotes_ordered);
3120
				$note_id = key($this->footnotes_ordered);
3121
				unset($this->footnotes_ordered[$note_id]);
3122
				$ref_count = $this->footnotes_ref_count[$note_id];
3123
				unset($this->footnotes_ref_count[$note_id]);
3124
				unset($this->footnotes[$note_id]);
3125
				
3126
				$footnote .= "\n"; # Need to append newline before parsing.
3127
				$footnote = $this->runBlockGamut("$footnote\n");				
3128
				$footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', 
3129
					array(&$this, '_appendFootnotes_callback'), $footnote);
3130
				
3131
				$attr = str_replace("%%", ++$num, $attr);
3132
				$note_id = $this->encodeAttribute($note_id);
3133
3134
				# Prepare backlink, multiple backlinks if multiple references
3135
				$backlink = "<a href=\"#fnref:$note_id\"$attr>&#8617;</a>";
3136
				for ($ref_num = 2; $ref_num <= $ref_count; ++$ref_num) {
3137
					$backlink .= " <a href=\"#fnref$ref_num:$note_id\"$attr>&#8617;</a>";
3138
				}
3139
				# Add backlink to last paragraph; create new paragraph if needed.
3140
				if (preg_match('{</p>$}', $footnote)) {
3141
					$footnote = substr($footnote, 0, -4) . "&#160;$backlink</p>";
3142
				} else {
3143
					$footnote .= "\n\n<p>$backlink</p>";
3144
				}
3145
				
3146
				$text .= "<li id=\"fn:$note_id\">\n";
3147
				$text .= $footnote . "\n";
3148
				$text .= "</li>\n\n";
3149
			}
3150
			
3151
			$text .= "</ol>\n";
3152
			$text .= "</div>";
3153
		}
3154
		return $text;
3155
	}
3156
	function _appendFootnotes_callback($matches) {
3157
		$node_id = $this->fn_id_prefix . $matches[1];
3158
		
3159
		# Create footnote marker only if it has a corresponding footnote *and*
3160
		# the footnote hasn't been used by another marker.
3161
		if (isset($this->footnotes[$node_id])) {
3162
			$num =& $this->footnotes_numbers[$node_id];
3163
			if (!isset($num)) {
3164
				# Transfer footnote content to the ordered list and give it its
3165
				# number
3166
				$this->footnotes_ordered[$node_id] = $this->footnotes[$node_id];
3167
				$this->footnotes_ref_count[$node_id] = 1;
3168
				$num = $this->footnote_counter++;
3169
				$ref_count_mark = '';
3170
			} else {
3171
				$ref_count_mark = $this->footnotes_ref_count[$node_id] += 1;
3172
			}
3173
			
3174
			$attr = " rel=\"footnote\"";
3175
			if ($this->fn_link_class != "") {
3176
				$class = $this->fn_link_class;
3177
				$class = $this->encodeAttribute($class);
3178
				$attr .= " class=\"$class\"";
3179
			}
3180
			if ($this->fn_link_title != "") {
3181
				$title = $this->fn_link_title;
3182
				$title = $this->encodeAttribute($title);
3183
				$attr .= " title=\"$title\"";
3184
			}
3185
			
3186
			$attr = str_replace("%%", $num, $attr);
3187
			$node_id = $this->encodeAttribute($node_id);
3188
			
3189
			return
3190
				"<sup id=\"fnref$ref_count_mark:$node_id\">".
3191
				"<a href=\"#fn:$node_id\"$attr>$num</a>".
3192
				"</sup>";
3193
		}
3194
		
3195
		return "[^".$matches[1]."]";
3196
	}
3197
		
3198
	
3199
	### Abbreviations ###
3200
	
3201
	function stripAbbreviations($text) {
3202
	#
3203
	# Strips abbreviations from text, stores titles in hash references.
3204
	#
3205
		$less_than_tab = $this->tab_width - 1;
3206
3207
		# Link defs are in the form: [id]*: url "optional title"
3208
		$text = preg_replace_callback('{
3209
			^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?:	# abbr_id = $1
3210
			(.*)					# text = $2 (no blank lines allowed)	
3211
			}xm',
3212
			array(&$this, '_stripAbbreviations_callback'),
3213
			$text);
3214
		return $text;
3215
	}
3216
	function _stripAbbreviations_callback($matches) {
3217
		$abbr_word = $matches[1];
3218
		$abbr_desc = $matches[2];
3219
		if ($this->abbr_word_re)
3220
			$this->abbr_word_re .= '|';
3221
		$this->abbr_word_re .= preg_quote($abbr_word);
3222
		$this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
3223
		return ''; # String that will replace the block
3224
	}
3225
	
3226
	
3227
	function doAbbreviations($text) {
3228
	#
3229
	# Find defined abbreviations in text and wrap them in <abbr> elements.
3230
	#
3231
		if ($this->abbr_word_re) {
3232
			// cannot use the /x modifier because abbr_word_re may 
3233
			// contain significant spaces:
3234
			$text = preg_replace_callback('{'.
3235
				'(?<![\w\x1A])'.
3236
				'(?:'.$this->abbr_word_re.')'.
3237
				'(?![\w\x1A])'.
3238
				'}', 
3239
				array(&$this, '_doAbbreviations_callback'), $text);
3240
		}
3241
		return $text;
3242
	}
3243
	function _doAbbreviations_callback($matches) {
3244
		$abbr = $matches[0];
3245
		if (isset($this->abbr_desciptions[$abbr])) {
3246
			$desc = $this->abbr_desciptions[$abbr];
3247
			if (empty($desc)) {
3248
				return $this->hashPart("<abbr>$abbr</abbr>");
3249
			} else {
3250
				$desc = $this->encodeAttribute($desc);
3251
				return $this->hashPart("<abbr title=\"$desc\">$abbr</abbr>");
3252
			}
3253
		} else {
3254
			return $matches[0];
3255
		}
3256
	}
3257
3258
}
3259
3260
3261
/*
3262
3263
PHP Markdown Extra
3264
==================
3265
3266
Description
3267
-----------
3268
3269
This is a PHP port of the original Markdown formatter written in Perl 
3270
by John Gruber. This special "Extra" version of PHP Markdown features 
3271
further enhancements to the syntax for making additional constructs 
3272
such as tables and definition list.
3273
3274
Markdown is a text-to-HTML filter; it translates an easy-to-read /
3275
easy-to-write structured text format into HTML. Markdown's text format
3276
is mostly similar to that of plain text email, and supports features such
3277
as headers, *emphasis*, code blocks, blockquotes, and links.
3278
3279
Markdown's syntax is designed not as a generic markup language, but
3280
specifically to serve as a front-end to (X)HTML. You can use span-level
3281
HTML tags anywhere in a Markdown document, and you can use block level
3282
HTML tags (like <div> and <table> as well).
3283
3284
For more information about Markdown's syntax, see:
3285
3286
<http://daringfireball.net/projects/markdown/>
3287
3288
3289
Bugs
3290
----
3291
3292
To file bug reports please send email to:
3293
3294
<[email protected]>
3295
3296
Please include with your report: (1) the example input; (2) the output you
3297
expected; (3) the output Markdown actually produced.
3298
3299
3300
Version History
3301
--------------- 
3302
3303
See the readme file for detailed release notes for this version.
3304
3305
3306
Copyright and License
3307
---------------------
3308
3309
PHP Markdown & Extra
3310
Copyright (c) 2004-2013 Michel Fortin
3311
<http://michelf.ca/>  
3312
All rights reserved.
3313
3314
Based on Markdown  
3315
Copyright (c) 2003-2006 John Gruber   
3316
<http://daringfireball.net/>   
3317
All rights reserved.
3318
3319
Redistribution and use in source and binary forms, with or without
3320
modification, are permitted provided that the following conditions are
3321
met:
3322
3323
*	Redistributions of source code must retain the above copyright notice,
3324
	this list of conditions and the following disclaimer.
3325
3326
*	Redistributions in binary form must reproduce the above copyright
3327
	notice, this list of conditions and the following disclaimer in the
3328
	documentation and/or other materials provided with the distribution.
3329
3330
*	Neither the name "Markdown" nor the names of its contributors may
3331
	be used to endorse or promote products derived from this software
3332
	without specific prior written permission.
3333
3334
This software is provided by the copyright holders and contributors "as
3335
is" and any express or implied warranties, including, but not limited
3336
to, the implied warranties of merchantability and fitness for a
3337
particular purpose are disclaimed. In no event shall the copyright owner
3338
or contributors be liable for any direct, indirect, incidental, special,
3339
exemplary, or consequential damages (including, but not limited to,
3340
procurement of substitute goods or services; loss of use, data, or
3341
profits; or business interruption) however caused and on any theory of
3342
liability, whether in contract, strict liability, or tort (including
3343
negligence or otherwise) arising in any way out of the use of this
3344
software, even if advised of the possibility of such damage.
3345
3346
*/
3347
?>
3348