PluginProbe
WP Ultimate Post Grid / 2.6.0
WP Ultimate Post Grid v2.6.0
4.1.1 trunk 1.5 1.6 1.7 1.7.1 1.7.2 1.8 1.9 2.0 2.1 2.2 2.3 2.4.0 2.5.0 2.6.0 2.7.0 2.8.0 2.8.2 3.0.0 3.3.0 3.4.0 3.5.0 3.6.0 3.7.0 All 32 releases
wp-ultimate-post-grid / vendor / vafpress / includes / markdown / parser.php

parser.php in WP Ultimate Post Grid 2.6.0, at vendor/vafpress/includes/markdown/parser.php

1,627 lines 45.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 #
3 # Markdown - A text-to-HTML conversion tool for web writers
4 #
5 # PHP Markdown
6 # Copyright (c) 2004-2012 Michel Fortin
7 # <http://michelf.com/projects/php-markdown/>
8 #
9 # Original Markdown
10 # Copyright (c) 2004-2006 John Gruber
11 # <http://daringfireball.net/projects/markdown/>
12 #
13
14
15 define( 'MARKDOWN_VERSION', "1.0.1o" ); # Sun 8 Jan 2012
16
17
18 #
19 # Global default settings:
20 #
21
22 # Change to ">" for HTML output
23 @define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX', " />");
24
25 # Define the width of a tab for code blocks.
26 @define( 'MARKDOWN_TAB_WIDTH', 4 );
27
28 ### Standard Function Interface ###
29
30 @define( 'MARKDOWN_PARSER_CLASS', 'Markdown_Parser' );
31
32
33 if( !function_exists('Markdown') )
34 {
35 function Markdown($text) {
36 #
37 # Initialize the parser and return the result of its transform method.
38 #
39 # Setup static parser variable.
40 static $parser;
41 if (!isset($parser)) {
42 $parser_class = MARKDOWN_PARSER_CLASS;
43 $parser = new $parser_class;
44 }
45
46 # Transform text using parser.
47 return $parser->transform($text);
48 }
49 }
50
51
52 ### bBlog Plugin Info ###
53 if( !function_exists('identify_modifier_markdown') )
54 {
55 function identify_modifier_markdown() {
56 return array(
57 'name' => 'markdown',
58 'type' => 'modifier',
59 'nicename' => 'Markdown',
60 'description' => 'A text-to-HTML conversion tool for web writers',
61 'authors' => 'Michel Fortin and John Gruber',
62 'licence' => 'BSD-like',
63 'version' => MARKDOWN_VERSION,
64 'help' => '<a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://michelf.com/projects/php-markdown/">More...</a>'
65 );
66 }
67 }
68
69
70 ### Smarty Modifier Interface ###
71
72 if( !function_exists('smarty_modifier_markdown') )
73 {
74 function smarty_modifier_markdown($text) {
75 return Markdown($text);
76 }
77 }
78
79 #
80 # Markdown Parser Class
81 #
82
83 if( !class_exists('Markdown_Parser') )
84 {
85 class Markdown_Parser
86 {
87
88 # Regex to match balanced [brackets].
89 # Needed to insert a maximum bracked depth while converting to PHP.
90 var $nested_brackets_depth = 6;
91 var $nested_brackets_re;
92
93 var $nested_url_parenthesis_depth = 4;
94 var $nested_url_parenthesis_re;
95
96 # Table of hash values for escaped characters:
97 var $escape_chars = '\`*_{}[]()>#+-.!';
98 var $escape_chars_re;
99
100 # Change to ">" for HTML output.
101 var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
102 var $tab_width = MARKDOWN_TAB_WIDTH;
103
104 # Change to `true` to disallow markup or entities.
105 var $no_markup = false;
106 var $no_entities = false;
107
108 # Predefined urls and titles for reference links and images.
109 var $predef_urls = array();
110 var $predef_titles = array();
111
112
113 function Markdown_Parser() {
114 #
115 # Constructor function. Initialize appropriate member variables.
116 #
117 $this->_initDetab();
118 $this->prepareItalicsAndBold();
119
120 $this->nested_brackets_re =
121 str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth).
122 str_repeat('\])*', $this->nested_brackets_depth);
123
124 $this->nested_url_parenthesis_re =
125 str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth).
126 str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth);
127
128 $this->escape_chars_re = '['.preg_quote($this->escape_chars).']';
129
130 # Sort document, block, and span gamut in ascendent priority order.
131 asort($this->document_gamut);
132 asort($this->block_gamut);
133 asort($this->span_gamut);
134 }
135
136
137 # Internal hashes used during transformation.
138 var $urls = array();
139 var $titles = array();
140 var $html_hashes = array();
141
142 # Status flag to avoid invalid nesting.
143 var $in_anchor = false;
144
145
146 function setup() {
147 #
148 # Called before the transformation process starts to setup parser
149 # states.
150 #
151 # Clear global hashes.
152 $this->urls = $this->predef_urls;
153 $this->titles = $this->predef_titles;
154 $this->html_hashes = array();
155
156 $in_anchor = false;
157 }
158
159 function teardown() {
160 #
161 # Called after the transformation process to clear any variable
162 # which may be taking up memory unnecessarly.
163 #
164 $this->urls = array();
165 $this->titles = array();
166 $this->html_hashes = array();
167 }
168
169
170 function transform($text) {
171 #
172 # Main function. Performs some preprocessing on the input text
173 # and pass it through the document gamut.
174 #
175 $this->setup();
176
177 # Remove UTF-8 BOM and marker character in input, if present.
178 $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text);
179
180 # Standardize line endings:
181 # DOS to Unix and Mac to Unix
182 $text = preg_replace('{\r\n?}', "\n", $text);
183
184 # Make sure $text ends with a couple of newlines:
185 $text .= "\n\n";
186
187 # Convert all tabs to spaces.
188 $text = $this->detab($text);
189
190 # Turn block-level HTML blocks into hash entries
191 $text = $this->hashHTMLBlocks($text);
192
193 # Strip any lines consisting only of spaces and tabs.
194 # This makes subsequent regexen easier to write, because we can
195 # match consecutive blank lines with /\n+/ instead of something
196 # contorted like /[ ]*\n+/ .
197 $text = preg_replace('/^[ ]+$/m', '', $text);
198
199 # Run document gamut methods.
200 foreach ($this->document_gamut as $method => $priority) {
201 $text = $this->$method($text);
202 }
203
204 $this->teardown();
205
206 return $text . "\n";
207 }
208
209 var $document_gamut = array(
210 # Strip link definitions, store in hashes.
211 "stripLinkDefinitions" => 20,
212
213 "runBasicBlockGamut" => 30,
214 );
215
216
217 function stripLinkDefinitions($text) {
218 #
219 # Strips link definitions from text, stores the URLs and titles in
220 # hash references.
221 #
222 $less_than_tab = $this->tab_width - 1;
223
224 # Link defs are in the form: ^[id]: url "optional title"
225 $text = preg_replace_callback('{
226 ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1
227 [ ]*
228 \n? # maybe *one* newline
229 [ ]*
230 (?:
231 <(.+?)> # url = $2
232 |
233 (\S+?) # url = $3
234 )
235 [ ]*
236 \n? # maybe one newline
237 [ ]*
238 (?:
239 (?<=\s) # lookbehind for whitespace
240 ["(]
241 (.*?) # title = $4
242 [")]
243 [ ]*
244 )? # title is optional
245 (?:\n+|\Z)
246 }xm',
247 array(&$this, '_stripLinkDefinitions_callback'),
248 $text);
249 return $text;
250 }
251 function _stripLinkDefinitions_callback($matches) {
252 $link_id = strtolower($matches[1]);
253 $url = $matches[2] == '' ? $matches[3] : $matches[2];
254 $this->urls[$link_id] = $url;
255 $this->titles[$link_id] =& $matches[4];
256 return ''; # String that will replace the block
257 }
258
259
260 function hashHTMLBlocks($text) {
261 if ($this->no_markup) return $text;
262
263 $less_than_tab = $this->tab_width - 1;
264
265 # Hashify HTML blocks:
266 # We only want to do this for block-level HTML tags, such as headers,
267 # lists, and tables. That's because we still want to wrap <p>s around
268 # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
269 # phrase emphasis, and spans. The list of tags we're looking for is
270 # hard-coded:
271 #
272 # * List "a" is made of tags which can be both inline or block-level.
273 # These will be treated block-level when the start tag is alone on
274 # its line, otherwise they're not matched here and will be taken as
275 # inline later.
276 # * List "b" is made of tags which are always block-level;
277 #
278 $block_tags_a_re = 'ins|del';
279 $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'.
280 'script|noscript|form|fieldset|iframe|math';
281
282 # Regular expression for the content of a block tag.
283 $nested_tags_level = 4;
284 $attr = '
285 (?> # optional tag attributes
286 \s # starts with whitespace
287 (?>
288 [^>"/]+ # text outside quotes
289 |
290 /+(?!>) # slash not followed by ">"
291 |
292 "[^"]*" # text inside double quotes (tolerate ">")
293 |
294 \'[^\']*\' # text inside single quotes (tolerate ">")
295 )*
296 )?
297 ';
298 $content =
299 str_repeat('
300 (?>
301 [^<]+ # content without tag
302 |
303 <\2 # nested opening tag
304 '.$attr.' # attributes
305 (?>
306 />
307 |
308 >', $nested_tags_level). # end of opening tag
309 '.*?'. # last level nested tag content
310 str_repeat('
311 </\2\s*> # closing nested tag
312 )
313 |
314 <(?!/\2\s*> # other tags with a different name
315 )
316 )*',
317 $nested_tags_level);
318 $content2 = str_replace('\2', '\3', $content);
319
320 # First, look for nested blocks, e.g.:
321 # <div>
322 # <div>
323 # tags for inner block must be indented.
324 # </div>
325 # </div>
326 #
327 # The outermost tags must start at the left margin for this to match, and
328 # the inner nested divs must be indented.
329 # We need to do this before the next, more liberal match, because the next
330 # match will start at the first `<div>` and stop at the first `</div>`.
331 $text = preg_replace_callback('{(?>
332 (?>
333 (?<=\n\n) # Starting after a blank line
334 | # or
335 \A\n? # the beginning of the doc
336 )
337 ( # save in $1
338
339 # Match from `\n<tag>` to `</tag>\n`, handling nested tags
340 # in between.
341
342 [ ]{0,'.$less_than_tab.'}
343 <('.$block_tags_b_re.')# start tag = $2
344 '.$attr.'> # attributes followed by > and \n
345 '.$content.' # content, support nesting
346 </\2> # the matching end tag
347 [ ]* # trailing spaces/tabs
348 (?=\n+|\Z) # followed by a newline or end of document
349
350 | # Special version for tags of group a.
351
352 [ ]{0,'.$less_than_tab.'}
353 <('.$block_tags_a_re.')# start tag = $3
354 '.$attr.'>[ ]*\n # attributes followed by >
355 '.$content2.' # content, support nesting
356 </\3> # the matching end tag
357 [ ]* # trailing spaces/tabs
358 (?=\n+|\Z) # followed by a newline or end of document
359
360 | # Special case just for <hr />. It was easier to make a special
361 # case than to make the other regex more complicated.
362
363 [ ]{0,'.$less_than_tab.'}
364 <(hr) # start tag = $2
365 '.$attr.' # attributes
366 /?> # the matching end tag
367 [ ]*
368 (?=\n{2,}|\Z) # followed by a blank line or end of document
369
370 | # Special case for standalone HTML comments:
371
372 [ ]{0,'.$less_than_tab.'}
373 (?s:
374 <!-- .*? -->
375 )
376 [ ]*
377 (?=\n{2,}|\Z) # followed by a blank line or end of document
378
379 | # PHP and ASP-style processor instructions (<?php and <%)
380
381 [ ]{0,'.$less_than_tab.'}
382 (?s:
383 <([?%]) # $2
384 .*?
385 \2>
386 )
387 [ ]*
388 (?=\n{2,}|\Z) # followed by a blank line or end of document
389
390 )
391 )}Sxmi',
392 array(&$this, '_hashHTMLBlocks_callback'),
393 $text);
394
395 return $text;
396 }
397 function _hashHTMLBlocks_callback($matches) {
398 $text = $matches[1];
399 $key = $this->hashBlock($text);
400 return "\n\n$key\n\n";
401 }
402
403
404 function hashPart($text, $boundary = 'X') {
405 #
406 # Called whenever a tag must be hashed when a function insert an atomic
407 # element in the text stream. Passing $text to through this function gives
408 # a unique text-token which will be reverted back when calling unhash.
409 #
410 # The $boundary argument specify what character should be used to surround
411 # the token. By convension, "B" is used for block elements that needs not
412 # to be wrapped into paragraph tags at the end, ":" is used for elements
413 # that are word separators and "X" is used in the general case.
414 #
415 # Swap back any tag hash found in $text so we do not have to `unhash`
416 # multiple times at the end.
417 $text = $this->unhash($text);
418
419 # Then hash the block.
420 static $i = 0;
421 $key = "$boundary\x1A" . ++$i . $boundary;
422 $this->html_hashes[$key] = $text;
423 return $key; # String that will replace the tag.
424 }
425
426
427 function hashBlock($text) {
428 #
429 # Shortcut function for hashPart with block-level boundaries.
430 #
431 return $this->hashPart($text, 'B');
432 }
433
434
435 var $block_gamut = array(
436 #
437 # These are all the transformations that form block-level
438 # tags like paragraphs, headers, and list items.
439 #
440 "doHeaders" => 10,
441 "doHorizontalRules" => 20,
442
443 "doLists" => 40,
444 "doCodeBlocks" => 50,
445 "doBlockQuotes" => 60,
446 );
447
448 function runBlockGamut($text) {
449 #
450 # Run block gamut tranformations.
451 #
452 # We need to escape raw HTML in Markdown source before doing anything
453 # else. This need to be done for each block, and not only at the
454 # begining in the Markdown function since hashed blocks can be part of
455 # list items and could have been indented. Indented blocks would have
456 # been seen as a code block in a previous pass of hashHTMLBlocks.
457 $text = $this->hashHTMLBlocks($text);
458
459 return $this->runBasicBlockGamut($text);
460 }
461
462 function runBasicBlockGamut($text) {
463 #
464 # Run block gamut tranformations, without hashing HTML blocks. This is
465 # useful when HTML blocks are known to be already hashed, like in the first
466 # whole-document pass.
467 #
468 foreach ($this->block_gamut as $method => $priority) {
469 $text = $this->$method($text);
470 }
471
472 # Finally form paragraph and restore hashed blocks.
473 $text = $this->formParagraphs($text);
474
475 return $text;
476 }
477
478
479 function doHorizontalRules($text) {
480 # Do Horizontal Rules:
481 return preg_replace(
482 '{
483 ^[ ]{0,3} # Leading space
484 ([-*_]) # $1: First marker
485 (?> # Repeated marker group
486 [ ]{0,2} # Zero, one, or two spaces.
487 \1 # Marker character
488 ){2,} # Group repeated at least twice
489 [ ]* # Tailing spaces
490 $ # End of line.
491 }mx',
492 "\n".$this->hashBlock("<hr$this->empty_element_suffix")."\n",
493 $text);
494 }
495
496
497 var $span_gamut = array(
498 #
499 # These are all the transformations that occur *within* block-level
500 # tags like paragraphs, headers, and list items.
501 #
502 # Process character escapes, code spans, and inline HTML
503 # in one shot.
504 "parseSpan" => -30,
505
506 # Process anchor and image tags. Images must come first,
507 # because ![foo][f] looks like an anchor.
508 "doImages" => 10,
509 "doAnchors" => 20,
510
511 # Make links out of things like `<http://example.com/>`
512 # Must come after doAnchors, because you can use < and >
513 # delimiters in inline links like [this](<url>).
514 "doAutoLinks" => 30,
515 "encodeAmpsAndAngles" => 40,
516
517 "doItalicsAndBold" => 50,
518 "doHardBreaks" => 60,
519 );
520
521 function runSpanGamut($text) {
522 #
523 # Run span gamut tranformations.
524 #
525 foreach ($this->span_gamut as $method => $priority) {
526 $text = $this->$method($text);
527 }
528
529 return $text;
530 }
531
532
533 function doHardBreaks($text) {
534 # Do hard breaks:
535 return preg_replace_callback('/ {2,}\n/',
536 array(&$this, '_doHardBreaks_callback'), $text);
537 }
538 function _doHardBreaks_callback($matches) {
539 return $this->hashPart("<br$this->empty_element_suffix\n");
540 }
541
542
543 function doAnchors($text) {
544 #
545 # Turn Markdown link shortcuts into XHTML <a> tags.
546 #
547 if ($this->in_anchor) return $text;
548 $this->in_anchor = true;
549
550 #
551 # First, handle reference-style links: [link text] [id]
552 #
553 $text = preg_replace_callback('{
554 ( # wrap whole match in $1
555 \[
556 ('.$this->nested_brackets_re.') # link text = $2
557 \]
558
559 [ ]? # one optional space
560 (?:\n[ ]*)? # one optional newline followed by spaces
561
562 \[
563 (.*?) # id = $3
564 \]
565 )
566 }xs',
567 array(&$this, '_doAnchors_reference_callback'), $text);
568
569 #
570 # Next, inline-style links: [link text](url "optional title")
571 #
572 $text = preg_replace_callback('{
573 ( # wrap whole match in $1
574 \[
575 ('.$this->nested_brackets_re.') # link text = $2
576 \]
577 \( # literal paren
578 [ \n]*
579 (?:
580 <(.+?)> # href = $3
581 |
582 ('.$this->nested_url_parenthesis_re.') # href = $4
583 )
584 [ \n]*
585 ( # $5
586 ([\'"]) # quote char = $6
587 (.*?) # Title = $7
588 \6 # matching quote
589 [ \n]* # ignore any spaces/tabs between closing quote and )
590 )? # title is optional
591 \)
592 )
593 }xs',
594 array(&$this, '_doAnchors_inline_callback'), $text);
595
596 #
597 # Last, handle reference-style shortcuts: [link text]
598 # These must come last in case you've also got [link text][1]
599 # or [link text](/foo)
600 #
601 $text = preg_replace_callback('{
602 ( # wrap whole match in $1
603 \[
604 ([^\[\]]+) # link text = $2; can\'t contain [ or ]
605 \]
606 )
607 }xs',
608 array(&$this, '_doAnchors_reference_callback'), $text);
609
610 $this->in_anchor = false;
611 return $text;
612 }
613 function _doAnchors_reference_callback($matches) {
614 $whole_match = $matches[1];
615 $link_text = $matches[2];
616 $link_id =& $matches[3];
617
618 if ($link_id == "") {
619 # for shortcut links like [this][] or [this].
620 $link_id = $link_text;
621 }
622
623 # lower-case and turn embedded newlines into spaces
624 $link_id = strtolower($link_id);
625 $link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
626
627 if (isset($this->urls[$link_id])) {
628 $url = $this->urls[$link_id];
629 $url = $this->encodeAttribute($url);
630
631 $result = "<a href=\"$url\"";
632 if ( isset( $this->titles[$link_id] ) ) {
633 $title = $this->titles[$link_id];
634 $title = $this->encodeAttribute($title);
635 $result .= " title=\"$title\"";
636 }
637
638 $link_text = $this->runSpanGamut($link_text);
639 $result .= ">$link_text</a>";
640 $result = $this->hashPart($result);
641 }
642 else {
643 $result = $whole_match;
644 }
645 return $result;
646 }
647 function _doAnchors_inline_callback($matches) {
648 $whole_match = $matches[1];
649 $link_text = $this->runSpanGamut($matches[2]);
650 $url = $matches[3] == '' ? $matches[4] : $matches[3];
651 $title =& $matches[7];
652
653 $url = $this->encodeAttribute($url);
654
655 $result = "<a href=\"$url\"";
656 if (isset($title)) {
657 $title = $this->encodeAttribute($title);
658 $result .= " title=\"$title\"";
659 }
660
661 $link_text = $this->runSpanGamut($link_text);
662 $result .= ">$link_text</a>";
663
664 return $this->hashPart($result);
665 }
666
667
668 function doImages($text) {
669 #
670 # Turn Markdown image shortcuts into <img> tags.
671 #
672 #
673 # First, handle reference-style labeled images: ![alt text][id]
674 #
675 $text = preg_replace_callback('{
676 ( # wrap whole match in $1
677 !\[
678 ('.$this->nested_brackets_re.') # alt text = $2
679 \]
680
681 [ ]? # one optional space
682 (?:\n[ ]*)? # one optional newline followed by spaces
683
684 \[
685 (.*?) # id = $3
686 \]
687
688 )
689 }xs',
690 array(&$this, '_doImages_reference_callback'), $text);
691
692 #
693 # Next, handle inline images: ![alt text](url "optional title")
694 # Don't forget: encode * and _
695 #
696 $text = preg_replace_callback('{
697 ( # wrap whole match in $1
698 !\[
699 ('.$this->nested_brackets_re.') # alt text = $2
700 \]
701 \s? # One optional whitespace character
702 \( # literal paren
703 [ \n]*
704 (?:
705 <(\S*)> # src url = $3
706 |
707 ('.$this->nested_url_parenthesis_re.') # src url = $4
708 )
709 [ \n]*
710 ( # $5
711 ([\'"]) # quote char = $6
712 (.*?) # title = $7
713 \6 # matching quote
714 [ \n]*
715 )? # title is optional
716 \)
717 )
718 }xs',
719 array(&$this, '_doImages_inline_callback'), $text);
720
721 return $text;
722 }
723 function _doImages_reference_callback($matches) {
724 $whole_match = $matches[1];
725 $alt_text = $matches[2];
726 $link_id = strtolower($matches[3]);
727
728 if ($link_id == "") {
729 $link_id = strtolower($alt_text); # for shortcut links like ![this][].
730 }
731
732 $alt_text = $this->encodeAttribute($alt_text);
733 if (isset($this->urls[$link_id])) {
734 $url = $this->encodeAttribute($this->urls[$link_id]);
735 $result = "<img src=\"$url\" alt=\"$alt_text\"";
736 if (isset($this->titles[$link_id])) {
737 $title = $this->titles[$link_id];
738 $title = $this->encodeAttribute($title);
739 $result .= " title=\"$title\"";
740 }
741 $result .= $this->empty_element_suffix;
742 $result = $this->hashPart($result);
743 }
744 else {
745 # If there's no such link ID, leave intact:
746 $result = $whole_match;
747 }
748
749 return $result;
750 }
751 function _doImages_inline_callback($matches) {
752 $whole_match = $matches[1];
753 $alt_text = $matches[2];
754 $url = $matches[3] == '' ? $matches[4] : $matches[3];
755 $title =& $matches[7];
756
757 $alt_text = $this->encodeAttribute($alt_text);
758 $url = $this->encodeAttribute($url);
759 $result = "<img src=\"$url\" alt=\"$alt_text\"";
760 if (isset($title)) {
761 $title = $this->encodeAttribute($title);
762 $result .= " title=\"$title\""; # $title already quoted
763 }
764 $result .= $this->empty_element_suffix;
765
766 return $this->hashPart($result);
767 }
768
769
770 function doHeaders($text) {
771 # Setext-style headers:
772 # Header 1
773 # ========
774 #
775 # Header 2
776 # --------
777 #
778 $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
779 array(&$this, '_doHeaders_callback_setext'), $text);
780
781 # atx-style headers:
782 # # Header 1
783 # ## Header 2
784 # ## Header 2 with closing hashes ##
785 # ...
786 # ###### Header 6
787 #
788 $text = preg_replace_callback('{
789 ^(\#{1,6}) # $1 = string of #\'s
790 [ ]*
791 (.+?) # $2 = Header text
792 [ ]*
793 \#* # optional closing #\'s (not counted)
794 \n+
795 }xm',
796 array(&$this, '_doHeaders_callback_atx'), $text);
797
798 return $text;
799 }
800 function _doHeaders_callback_setext($matches) {
801 # Terrible hack to check we haven't found an empty list item.
802 if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1]))
803 return $matches[0];
804
805 $level = $matches[2]{0} == '=' ? 1 : 2;
806 $block = "<h$level>".$this->runSpanGamut($matches[1])."</h$level>";
807 return "\n" . $this->hashBlock($block) . "\n\n";
808 }
809 function _doHeaders_callback_atx($matches) {
810 $level = strlen($matches[1]);
811 $block = "<h$level>".$this->runSpanGamut($matches[2])."</h$level>";
812 return "\n" . $this->hashBlock($block) . "\n\n";
813 }
814
815
816 function doLists($text) {
817 #
818 # Form HTML ordered (numbered) and unordered (bulleted) lists.
819 #
820 $less_than_tab = $this->tab_width - 1;
821
822 # Re-usable patterns to match list item bullets and number markers:
823 $marker_ul_re = '[*+-]';
824 $marker_ol_re = '\d+[\.]';
825 $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
826
827 $markers_relist = array(
828 $marker_ul_re => $marker_ol_re,
829 $marker_ol_re => $marker_ul_re,
830 );
831
832 foreach ($markers_relist as $marker_re => $other_marker_re) {
833 # Re-usable pattern to match any entirel ul or ol list:
834 $whole_list_re = '
835 ( # $1 = whole list
836 ( # $2
837 ([ ]{0,'.$less_than_tab.'}) # $3 = number of spaces
838 ('.$marker_re.') # $4 = first list item marker
839 [ ]+
840 )
841 (?s:.+?)
842 ( # $5
843 \z
844 |
845 \n{2,}
846 (?=\S)
847 (?! # Negative lookahead for another list item marker
848 [ ]*
849 '.$marker_re.'[ ]+
850 )
851 |
852 (?= # Lookahead for another kind of list
853 \n
854 \3 # Must have the same indentation
855 '.$other_marker_re.'[ ]+
856 )
857 )
858 )
859 '; // mx
860
861 # We use a different prefix before nested lists than top-level lists.
862 # See extended comment in _ProcessListItems().
863
864 if ($this->list_level) {
865 $text = preg_replace_callback('{
866 ^
867 '.$whole_list_re.'
868 }mx',
869 array(&$this, '_doLists_callback'), $text);
870 }
871 else {
872 $text = preg_replace_callback('{
873 (?:(?<=\n)\n|\A\n?) # Must eat the newline
874 '.$whole_list_re.'
875 }mx',
876 array(&$this, '_doLists_callback'), $text);
877 }
878 }
879
880 return $text;
881 }
882 function _doLists_callback($matches) {
883 # Re-usable patterns to match list item bullets and number markers:
884 $marker_ul_re = '[*+-]';
885 $marker_ol_re = '\d+[\.]';
886 $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
887
888 $list = $matches[1];
889 $list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol";
890
891 $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re );
892
893 $list .= "\n";
894 $result = $this->processListItems($list, $marker_any_re);
895
896 $result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>");
897 return "\n". $result ."\n\n";
898 }
899
900 var $list_level = 0;
901
902 function processListItems($list_str, $marker_any_re) {
903 #
904 # Process the contents of a single ordered or unordered list, splitting it
905 # into individual list items.
906 #
907 # The $this->list_level global keeps track of when we're inside a list.
908 # Each time we enter a list, we increment it; when we leave a list,
909 # we decrement. If it's zero, we're not in a list anymore.
910 #
911 # We do this because when we're not inside a list, we want to treat
912 # something like this:
913 #
914 # I recommend upgrading to version
915 # 8. Oops, now this line is treated
916 # as a sub-list.
917 #
918 # As a single paragraph, despite the fact that the second line starts
919 # with a digit-period-space sequence.
920 #
921 # Whereas when we're inside a list (or sub-list), that line will be
922 # treated as the start of a sub-list. What a kludge, huh? This is
923 # an aspect of Markdown's syntax that's hard to parse perfectly
924 # without resorting to mind-reading. Perhaps the solution is to
925 # change the syntax rules such that sub-lists must start with a
926 # starting cardinal number; e.g. "1." or "a.".
927
928 $this->list_level++;
929
930 # trim trailing blank lines:
931 $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
932
933 $list_str = preg_replace_callback('{
934 (\n)? # leading line = $1
935 (^[ ]*) # leading whitespace = $2
936 ('.$marker_any_re.' # list marker and space = $3
937 (?:[ ]+|(?=\n)) # space only required if item is not empty
938 )
939 ((?s:.*?)) # list item text = $4
940 (?:(\n+(?=\n))|\n) # tailing blank line = $5
941 (?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n))))
942 }xm',
943 array(&$this, '_processListItems_callback'), $list_str);
944
945 $this->list_level--;
946 return $list_str;
947 }
948 function _processListItems_callback($matches) {
949 $item = $matches[4];
950 $leading_line =& $matches[1];
951 $leading_space =& $matches[2];
952 $marker_space = $matches[3];
953 $tailing_blank_line =& $matches[5];
954
955 if ($leading_line || $tailing_blank_line ||
956 preg_match('/\n{2,}/', $item))
957 {
958 # Replace marker with the appropriate whitespace indentation
959 $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item;
960 $item = $this->runBlockGamut($this->outdent($item)."\n");
961 }
962 else {
963 # Recursion for sub-lists:
964 $item = $this->doLists($this->outdent($item));
965 $item = preg_replace('/\n+$/', '', $item);
966 $item = $this->runSpanGamut($item);
967 }
968
969 return "<li>" . $item . "</li>\n";
970 }
971
972
973 function doCodeBlocks($text) {
974 #
975 # Process Markdown `<pre><code>` blocks.
976 #
977 $text = preg_replace_callback('{
978 (?:\n\n|\A\n?)
979 ( # $1 = the code block -- one or more lines, starting with a space/tab
980 (?>
981 [ ]{'.$this->tab_width.'} # Lines must start with a tab or a tab-width of spaces
982 .*\n+
983 )+
984 )
985 ((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
986 }xm',
987 array(&$this, '_doCodeBlocks_callback'), $text);
988
989 return $text;
990 }
991 function _doCodeBlocks_callback($matches) {
992 $codeblock = $matches[1];
993
994 $codeblock = $this->outdent($codeblock);
995 $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
996
997 # trim leading newlines and trailing newlines
998 $codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
999
1000 $codeblock = "<pre><code>$codeblock\n</code></pre>";
1001 return "\n\n".$this->hashBlock($codeblock)."\n\n";
1002 }
1003
1004
1005 function makeCodeSpan($code) {
1006 #
1007 # Create a code span markup for $code. Called from handleSpanToken.
1008 #
1009 $code = htmlspecialchars(trim($code), ENT_NOQUOTES);
1010 return $this->hashPart("<code>$code</code>");
1011 }
1012
1013
1014 var $em_relist = array(
1015 '' => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S|$)(?![\.,:;]\s)',
1016 '*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
1017 '_' => '(?<=\S|^)(?<!_)_(?!_)',
1018 );
1019 var $strong_relist = array(
1020 '' => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S|$)(?![\.,:;]\s)',
1021 '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
1022 '__' => '(?<=\S|^)(?<!_)__(?!_)',
1023 );
1024 var $em_strong_relist = array(
1025 '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S|$)(?![\.,:;]\s)',
1026 '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
1027 '___' => '(?<=\S|^)(?<!_)___(?!_)',
1028 );
1029 var $em_strong_prepared_relist;
1030
1031 function prepareItalicsAndBold() {
1032 #
1033 # Prepare regular expressions for searching emphasis tokens in any
1034 # context.
1035 #
1036 foreach ($this->em_relist as $em => $em_re) {
1037 foreach ($this->strong_relist as $strong => $strong_re) {
1038 # Construct list of allowed token expressions.
1039 $token_relist = array();
1040 if (isset($this->em_strong_relist["$em$strong"])) {
1041 $token_relist[] = $this->em_strong_relist["$em$strong"];
1042 }
1043 $token_relist[] = $em_re;
1044 $token_relist[] = $strong_re;
1045
1046 # Construct master expression from list.
1047 $token_re = '{('. implode('|', $token_relist) .')}';
1048 $this->em_strong_prepared_relist["$em$strong"] = $token_re;
1049 }
1050 }
1051 }
1052
1053 function doItalicsAndBold($text) {
1054 $token_stack = array('');
1055 $text_stack = array('');
1056 $em = '';
1057 $strong = '';
1058 $tree_char_em = false;
1059
1060 while (1) {
1061 #
1062 # Get prepared regular expression for seraching emphasis tokens
1063 # in current context.
1064 #
1065 $token_re = $this->em_strong_prepared_relist["$em$strong"];
1066
1067 #
1068 # Each loop iteration search for the next emphasis token.
1069 # Each token is then passed to handleSpanToken.
1070 #
1071 $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
1072 $text_stack[0] .= $parts[0];
1073 $token =& $parts[1];
1074 $text =& $parts[2];
1075
1076 if (empty($token)) {
1077 # Reached end of text span: empty stack without emitting.
1078 # any more emphasis.
1079 while ($token_stack[0]) {
1080 $text_stack[1] .= array_shift($token_stack);
1081 $text_stack[0] .= array_shift($text_stack);
1082 }
1083 break;
1084 }
1085
1086 $token_len = strlen($token);
1087 if ($tree_char_em) {
1088 # Reached closing marker while inside a three-char emphasis.
1089 if ($token_len == 3) {
1090 # Three-char closing marker, close em and strong.
1091 array_shift($token_stack);
1092 $span = array_shift($text_stack);
1093 $span = $this->runSpanGamut($span);
1094 $span = "<strong><em>$span</em></strong>";
1095 $text_stack[0] .= $this->hashPart($span);
1096 $em = '';
1097 $strong = '';
1098 } else {
1099 # Other closing marker: close one em or strong and
1100 # change current token state to match the other
1101 $token_stack[0] = str_repeat($token{0}, 3-$token_len);
1102 $tag = $token_len == 2 ? "strong" : "em";
1103 $span = $text_stack[0];
1104 $span = $this->runSpanGamut($span);
1105 $span = "<$tag>$span</$tag>";
1106 $text_stack[0] = $this->hashPart($span);
1107 $$tag = ''; # $$tag stands for $em or $strong
1108 }
1109 $tree_char_em = false;
1110 } else if ($token_len == 3) {
1111 if ($em) {
1112 # Reached closing marker for both em and strong.
1113 # Closing strong marker:
1114 for ($i = 0; $i < 2; ++$i) {
1115 $shifted_token = array_shift($token_stack);
1116 $tag = strlen($shifted_token) == 2 ? "strong" : "em";
1117 $span = array_shift($text_stack);
1118 $span = $this->runSpanGamut($span);
1119 $span = "<$tag>$span</$tag>";
1120 $text_stack[0] .= $this->hashPart($span);
1121 $$tag = ''; # $$tag stands for $em or $strong
1122 }
1123 } else {
1124 # Reached opening three-char emphasis marker. Push on token
1125 # stack; will be handled by the special condition above.
1126 $em = $token{0};
1127 $strong = "$em$em";
1128 array_unshift($token_stack, $token);
1129 array_unshift($text_stack, '');
1130 $tree_char_em = true;
1131 }
1132 } else if ($token_len == 2) {
1133 if ($strong) {
1134 # Unwind any dangling emphasis marker:
1135 if (strlen($token_stack[0]) == 1) {
1136 $text_stack[1] .= array_shift($token_stack);
1137 $text_stack[0] .= array_shift($text_stack);
1138 }
1139 # Closing strong marker:
1140 array_shift($token_stack);
1141 $span = array_shift($text_stack);
1142 $span = $this->runSpanGamut($span);
1143 $span = "<strong>$span</strong>";
1144 $text_stack[0] .= $this->hashPart($span);
1145 $strong = '';
1146 } else {
1147 array_unshift($token_stack, $token);
1148 array_unshift($text_stack, '');
1149 $strong = $token;
1150 }
1151 } else {
1152 # Here $token_len == 1
1153 if ($em) {
1154 if (strlen($token_stack[0]) == 1) {
1155 # Closing emphasis marker:
1156 array_shift($token_stack);
1157 $span = array_shift($text_stack);
1158 $span = $this->runSpanGamut($span);
1159 $span = "<em>$span</em>";
1160 $text_stack[0] .= $this->hashPart($span);
1161 $em = '';
1162 } else {
1163 $text_stack[0] .= $token;
1164 }
1165 } else {
1166 array_unshift($token_stack, $token);
1167 array_unshift($text_stack, '');
1168 $em = $token;
1169 }
1170 }
1171 }
1172 return $text_stack[0];
1173 }
1174
1175
1176 function doBlockQuotes($text) {
1177 $text = preg_replace_callback('/
1178 ( # Wrap whole match in $1
1179 (?>
1180 ^[ ]*>[ ]? # ">" at the start of a line
1181 .+\n # rest of the first line
1182 (.+\n)* # subsequent consecutive lines
1183 \n* # blanks
1184 )+
1185 )
1186 /xm',
1187 array(&$this, '_doBlockQuotes_callback'), $text);
1188
1189 return $text;
1190 }
1191 function _doBlockQuotes_callback($matches) {
1192 $bq = $matches[1];
1193 # trim one level of quoting - trim whitespace-only lines
1194 $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
1195 $bq = $this->runBlockGamut($bq); # recurse
1196
1197 $bq = preg_replace('/^/m', " ", $bq);
1198 # These leading spaces cause problem with <pre> content,
1199 # so we need to fix that:
1200 $bq = preg_replace_callback('{(\s*<pre>.+?</pre>)}sx',
1201 array(&$this, '_doBlockQuotes_callback2'), $bq);
1202
1203 return "\n". $this->hashBlock("<blockquote>\n$bq\n</blockquote>")."\n\n";
1204 }
1205 function _doBlockQuotes_callback2($matches) {
1206 $pre = $matches[1];
1207 $pre = preg_replace('/^ /m', '', $pre);
1208 return $pre;
1209 }
1210
1211
1212 function formParagraphs($text) {
1213 #
1214 # Params:
1215 # $text - string to process with html <p> tags
1216 #
1217 # Strip leading and trailing lines:
1218 $text = preg_replace('/\A\n+|\n+\z/', '', $text);
1219
1220 $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
1221
1222 #
1223 # Wrap <p> tags and unhashify HTML blocks
1224 #
1225 foreach ($grafs as $key => $value) {
1226 if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
1227 # Is a paragraph.
1228 $value = $this->runSpanGamut($value);
1229 $value = preg_replace('/^([ ]*)/', "<p>", $value);
1230 $value .= "</p>";
1231 $grafs[$key] = $this->unhash($value);
1232 }
1233 else {
1234 # Is a block.
1235 # Modify elements of @grafs in-place...
1236 $graf = $value;
1237 $block = $this->html_hashes[$graf];
1238 $graf = $block;
1239 // if (preg_match('{
1240 // \A
1241 // ( # $1 = <div> tag
1242 // <div \s+
1243 // [^>]*
1244 // \b
1245 // markdown\s*=\s* ([\'"]) # $2 = attr quote char
1246 // 1
1247 // \2
1248 // [^>]*
1249 // >
1250 // )
1251 // ( # $3 = contents
1252 // .*
1253 // )
1254 // (</div>) # $4 = closing tag
1255 // \z
1256 // }xs', $block, $matches))
1257 // {
1258 // list(, $div_open, , $div_content, $div_close) = $matches;
1259 //
1260 // # We can't call Markdown(), because that resets the hash;
1261 // # that initialization code should be pulled into its own sub, though.
1262 // $div_content = $this->hashHTMLBlocks($div_content);
1263 //
1264 // # Run document gamut methods on the content.
1265 // foreach ($this->document_gamut as $method => $priority) {
1266 // $div_content = $this->$method($div_content);
1267 // }
1268 //
1269 // $div_open = preg_replace(
1270 // '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
1271 //
1272 // $graf = $div_open . "\n" . $div_content . "\n" . $div_close;
1273 // }
1274 $grafs[$key] = $graf;
1275 }
1276 }
1277
1278 return implode("\n\n", $grafs);
1279 }
1280
1281
1282 function encodeAttribute($text) {
1283 #
1284 # Encode text for a double-quoted HTML attribute. This function
1285 # is *not* suitable for attributes enclosed in single quotes.
1286 #
1287 $text = $this->encodeAmpsAndAngles($text);
1288 $text = str_replace('"', '&quot;', $text);
1289 return $text;
1290 }
1291
1292
1293 function encodeAmpsAndAngles($text) {
1294 #
1295 # Smart processing for ampersands and angle brackets that need to
1296 # be encoded. Valid character entities are left alone unless the
1297 # no-entities mode is set.
1298 #
1299 if ($this->no_entities) {
1300 $text = str_replace('&', '&amp;', $text);
1301 } else {
1302 # Ampersand-encoding based entirely on Nat Irons's Amputator
1303 # MT plugin: <http://bumppo.net/projects/amputator/>
1304 $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',
1305 '&amp;', $text);;
1306 }
1307 # Encode remaining <'s
1308 $text = str_replace('<', '&lt;', $text);
1309
1310 return $text;
1311 }
1312
1313
1314 function doAutoLinks($text) {
1315 $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i',
1316 array(&$this, '_doAutoLinks_url_callback'), $text);
1317
1318 # Email addresses: <address@domain.foo>
1319 $text = preg_replace_callback('{
1320 <
1321 (?:mailto:)?
1322 (
1323 (?:
1324 [-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+
1325 |
1326 ".*?"
1327 )
1328 \@
1329 (?:
1330 [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
1331 |
1332 \[[\d.a-fA-F:]+\] # IPv4 & IPv6
1333 )
1334 )
1335 >
1336 }xi',
1337 array(&$this, '_doAutoLinks_email_callback'), $text);
1338
1339 return $text;
1340 }
1341 function _doAutoLinks_url_callback($matches) {
1342 $url = $this->encodeAttribute($matches[1]);
1343 $link = "<a href=\"$url\">$url</a>";
1344 return $this->hashPart($link);
1345 }
1346 function _doAutoLinks_email_callback($matches) {
1347 $address = $matches[1];
1348 $link = $this->encodeEmailAddress($address);
1349 return $this->hashPart($link);
1350 }
1351
1352
1353 function encodeEmailAddress($addr) {
1354 #
1355 # Input: an email address, e.g. "foo@example.com"
1356 #
1357 # Output: the email address as a mailto link, with each character
1358 # of the address encoded as either a decimal or hex entity, in
1359 # the hopes of foiling most address harvesting spam bots. E.g.:
1360 #
1361 # <p><a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x66;o&#111;
1362 # &#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;&#101;&#46;&#x63;&#111;
1363 # &#x6d;">&#x66;o&#111;&#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;
1364 # &#101;&#46;&#x63;&#111;&#x6d;</a></p>
1365 #
1366 # Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
1367 # With some optimizations by Milian Wolff.
1368 #
1369 $addr = "mailto:" . $addr;
1370 $chars = preg_split('/(?<!^)(?!$)/', $addr);
1371 $seed = (int)abs(crc32($addr) / strlen($addr)); # Deterministic seed.
1372
1373 foreach ($chars as $key => $char) {
1374 $ord = ord($char);
1375 # Ignore non-ascii chars.
1376 if ($ord < 128) {
1377 $r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
1378 # roughly 10% raw, 45% hex, 45% dec
1379 # '@' *must* be encoded. I insist.
1380 if ($r > 90 && $char != '@') /* do nothing */;
1381 else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';';
1382 else $chars[$key] = '&#'.$ord.';';
1383 }
1384 }
1385
1386 $addr = implode('', $chars);
1387 $text = implode('', array_slice($chars, 7)); # text without `mailto:`
1388 $addr = "<a href=\"$addr\">$text</a>";
1389
1390 return $addr;
1391 }
1392
1393
1394 function parseSpan($str) {
1395 #
1396 # Take the string $str and parse it into tokens, hashing embeded HTML,
1397 # escaped characters and handling code spans.
1398 #
1399 $output = '';
1400
1401 $span_re = '{
1402 (
1403 \\\\'.$this->escape_chars_re.'
1404 |
1405 (?<![`\\\\])
1406 `+ # code span marker
1407 '.( $this->no_markup ? '' : '
1408 |
1409 <!-- .*? --> # comment
1410 |
1411 <\?.*?\?> | <%.*?%> # processing instruction
1412 |
1413 <[/!$]?[-a-zA-Z0-9:_]+ # regular tags
1414 (?>
1415 \s
1416 (?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
1417 )?
1418 >
1419 ').'
1420 )
1421 }xs';
1422
1423 while (1) {
1424 #
1425 # Each loop iteration seach for either the next tag, the next
1426 # openning code span marker, or the next escaped character.
1427 # Each token is then passed to handleSpanToken.
1428 #
1429 $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
1430
1431 # Create token from text preceding tag.
1432 if ($parts[0] != "") {
1433 $output .= $parts[0];
1434 }
1435
1436 # Check if we reach the end.
1437 if (isset($parts[1])) {
1438 $output .= $this->handleSpanToken($parts[1], $parts[2]);
1439 $str = $parts[2];
1440 }
1441 else {
1442 break;
1443 }
1444 }
1445
1446 return $output;
1447 }
1448
1449
1450 function handleSpanToken($token, &$str) {
1451 #
1452 # Handle $token provided by parseSpan by determining its nature and
1453 # returning the corresponding value that should replace it.
1454 #
1455 switch ($token{0}) {
1456 case "\\":
1457 return $this->hashPart("&#". ord($token{1}). ";");
1458 case "`":
1459 # Search for end marker in remaining text.
1460 if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm',
1461 $str, $matches))
1462 {
1463 $str = $matches[2];
1464 $codespan = $this->makeCodeSpan($matches[1]);
1465 return $this->hashPart($codespan);
1466 }
1467 return $token; // return as text since no ending marker found.
1468 default:
1469 return $this->hashPart($token);
1470 }
1471 }
1472
1473
1474 function outdent($text) {
1475 #
1476 # Remove one level of line-leading tabs or spaces
1477 #
1478 return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text);
1479 }
1480
1481
1482 # String length function for detab. `_initDetab` will create a function to
1483 # hanlde UTF-8 if the default function does not exist.
1484 var $utf8_strlen = 'mb_strlen';
1485
1486 function detab($text) {
1487 #
1488 # Replace tabs with the appropriate amount of space.
1489 #
1490 # For each line we separate the line in blocks delemited by
1491 # tab characters. Then we reconstruct every line by adding the
1492 # appropriate number of space between each blocks.
1493
1494 $text = preg_replace_callback('/^.*\t.*$/m',
1495 array(&$this, '_detab_callback'), $text);
1496
1497 return $text;
1498 }
1499 function _detab_callback($matches) {
1500 $line = $matches[0];
1501 $strlen = $this->utf8_strlen; # strlen function for UTF-8.
1502
1503 # Split in blocks.
1504 $blocks = explode("\t", $line);
1505 # Add each blocks to the line.
1506 $line = $blocks[0];
1507 unset($blocks[0]); # Do not add first block twice.
1508 foreach ($blocks as $block) {
1509 # Calculate amount of space, insert spaces, insert block.
1510 $amount = $this->tab_width -
1511 $strlen($line, 'UTF-8') % $this->tab_width;
1512 $line .= str_repeat(" ", $amount) . $block;
1513 }
1514 return $line;
1515 }
1516 function _initDetab() {
1517 #
1518 # Check for the availability of the function in the `utf8_strlen` property
1519 # (initially `mb_strlen`). If the function is not available, create a
1520 # function that will loosely count the number of UTF-8 characters with a
1521 # regular expression.
1522 #
1523 if (function_exists($this->utf8_strlen)) return;
1524 $this->utf8_strlen = create_function('$text', 'return preg_match_all(
1525 "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/",
1526 $text, $m);');
1527 }
1528
1529
1530 function unhash($text) {
1531 #
1532 # Swap back in all the tags hashed by _HashHTMLBlocks.
1533 #
1534 return preg_replace_callback('/(.)\x1A[0-9]+\1/',
1535 array(&$this, '_unhash_callback'), $text);
1536 }
1537 function _unhash_callback($matches) {
1538 return $this->html_hashes[$matches[0]];
1539 }
1540
1541 }
1542 }
1543
1544 /*
1545
1546 PHP Markdown
1547 ============
1548
1549 Description
1550 -----------
1551
1552 This is a PHP translation of the original Markdown formatter written in
1553 Perl by John Gruber.
1554
1555 Markdown is a text-to-HTML filter; it translates an easy-to-read /
1556 easy-to-write structured text format into HTML. Markdown's text format
1557 is most similar to that of plain text email, and supports features such
1558 as headers, *emphasis*, code blocks, blockquotes, and links.
1559
1560 Markdown's syntax is designed not as a generic markup language, but
1561 specifically to serve as a front-end to (X)HTML. You can use span-level
1562 HTML tags anywhere in a Markdown document, and you can use block level
1563 HTML tags (like <div> and <table> as well).
1564
1565 For more information about Markdown's syntax, see:
1566
1567 <http://daringfireball.net/projects/markdown/>
1568
1569
1570 Bugs
1571 ----
1572
1573 To file bug reports please send email to:
1574
1575 <michel.fortin@michelf.com>
1576
1577 Please include with your report: (1) the example input; (2) the output you
1578 expected; (3) the output Markdown actually produced.
1579
1580
1581 Version History
1582 ---------------
1583
1584 See the readme file for detailed release notes for this version.
1585
1586
1587 Copyright and License
1588 ---------------------
1589
1590 PHP Markdown
1591 Copyright (c) 2004-2009 Michel Fortin
1592 <http://michelf.com/>
1593 All rights reserved.
1594
1595 Based on Markdown
1596 Copyright (c) 2003-2006 John Gruber
1597 <http://daringfireball.net/>
1598 All rights reserved.
1599
1600 Redistribution and use in source and binary forms, with or without
1601 modification, are permitted provided that the following conditions are
1602 met:
1603
1604 * Redistributions of source code must retain the above copyright notice,
1605 this list of conditions and the following disclaimer.
1606
1607 * Redistributions in binary form must reproduce the above copyright
1608 notice, this list of conditions and the following disclaimer in the
1609 documentation and/or other materials provided with the distribution.
1610
1611 * Neither the name "Markdown" nor the names of its contributors may
1612 be used to endorse or promote products derived from this software
1613 without specific prior written permission.
1614
1615 This software is provided by the copyright holders and contributors "as
1616 is" and any express or implied warranties, including, but not limited
1617 to, the implied warranties of merchantability and fitness for a
1618 particular purpose are disclaimed. In no event shall the copyright owner
1619 or contributors be liable for any direct, indirect, incidental, special,
1620 exemplary, or consequential damages (including, but not limited to,
1621 procurement of substitute goods or services; loss of use, data, or
1622 profits; or business interruption) however caused and on any theory of
1623 liability, whether in contract, strict liability, or tort (including
1624 negligence or otherwise) arising in any way out of the use of this
1625 software, even if advised of the possibility of such damage.
1626
1627 */