GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 092f5d...2848dd )
by やかみ
03:55
created

MarkdownExtra::appendFootnotes()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 64
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
dl 0
loc 64
rs 7.2058
c 0
b 0
f 0
eloc 44
nc 5
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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