Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 344
- Log:
Massive changeset which brings the old, ROOL customised Instiki
version up to date, but without any ROOL customisations in this
latest checked-in version (which is 0.19.1). This is deliberate,
so that it's easy to see the changes made for the ROOL version
in a subsequent changeset. The 'app/views/shared' directory is not
part of Instiki but is kept to maintain the change history with
updated ROOL customisations, some of which involve the same files
in that same directory.
- Author:
- rool
- Date:
- Sat Mar 19 19:52:13 +0000 2011
- Size:
- 35648 Bytes
- Properties:
- Property svn:executable is set to *
1 | #!/usr/bin/perl |
2 | |
3 | # |
4 | # Markdown -- A text-to-HTML conversion tool for web writers |
5 | # |
6 | # Copyright (c) 2004 John Gruber |
7 | # <http://daringfireball.net/projects/markdown/> |
8 | # |
9 | |
10 | |
11 | package Markdown; |
12 | require 5.006_000; |
13 | use strict; |
14 | use warnings; |
15 | |
16 | use Digest::MD5 qw(md5_hex); |
17 | use vars qw($VERSION); |
18 | $VERSION = '1.0.1'; |
19 | # Tue 14 Dec 2004 |
20 | |
21 | ## Disabled; causes problems under Perl 5.6.1: |
22 | # use utf8; |
23 | # binmode( STDOUT, ":utf8" ); # c.f.: http://acis.openlib.org/dev/perl-unicode-struggle.html |
24 | |
25 | |
26 | # |
27 | # Global default settings: |
28 | # |
29 | my $g_empty_element_suffix = " />"; # Change to ">" for HTML output |
30 | my $g_tab_width = 4; |
31 | |
32 | |
33 | # |
34 | # Globals: |
35 | # |
36 | |
37 | # Regex to match balanced [brackets]. See Friedl's |
38 | # "Mastering Regular Expressions", 2nd Ed., pp. 328-331. |
39 | my $g_nested_brackets; |
40 | $g_nested_brackets = qr{ |
41 | (?> # Atomic matching |
42 | [^\[\]]+ # Anything other than brackets |
43 | | |
44 | \[ |
45 | (??{ $g_nested_brackets }) # Recursive set of nested brackets |
46 | \] |
47 | )* |
48 | }x; |
49 | |
50 | |
51 | # Table of hash values for escaped characters: |
52 | my %g_escape_table; |
53 | foreach my $char (split //, '\\`*_{}[]()>#+-.!') { |
54 | $g_escape_table{$char} = md5_hex($char); |
55 | } |
56 | |
57 | |
58 | # Global hashes, used by various utility routines |
59 | my %g_urls; |
60 | my %g_titles; |
61 | my %g_html_blocks; |
62 | |
63 | # Used to track when we're inside an ordered or unordered list |
64 | # (see _ProcessListItems() for details): |
65 | my $g_list_level = 0; |
66 | |
67 | |
68 | #### Blosxom plug-in interface ########################################## |
69 | |
70 | # Set $g_blosxom_use_meta to 1 to use Blosxom's meta plug-in to determine |
71 | # which posts Markdown should process, using a "meta-markup: markdown" |
72 | # header. If it's set to 0 (the default), Markdown will process all |
73 | # entries. |
74 | my $g_blosxom_use_meta = 0; |
75 | |
76 | sub start { 1; } |
77 | sub story { |
78 | my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_; |
79 | |
80 | if ( (! $g_blosxom_use_meta) or |
81 | (defined($meta::markup) and ($meta::markup =~ /^\s*markdown\s*$/i)) |
82 | ){ |
83 | $$body_ref = Markdown($$body_ref); |
84 | } |
85 | 1; |
86 | } |
87 | |
88 | |
89 | #### Movable Type plug-in interface ##################################### |
90 | eval {require MT}; # Test to see if we're running in MT. |
91 | unless ($@) { |
92 | require MT; |
93 | import MT; |
94 | require MT::Template::Context; |
95 | import MT::Template::Context; |
96 | |
97 | eval {require MT::Plugin}; # Test to see if we're running >= MT 3.0. |
98 | unless ($@) { |
99 | require MT::Plugin; |
100 | import MT::Plugin; |
101 | my $plugin = new MT::Plugin({ |
102 | name => "Markdown", |
103 | description => "A plain-text-to-HTML formatting plugin. (Version: $VERSION)", |
104 | doc_link => 'http://daringfireball.net/projects/markdown/' |
105 | }); |
106 | MT->add_plugin( $plugin ); |
107 | } |
108 | |
109 | MT::Template::Context->add_container_tag(MarkdownOptions => sub { |
110 | my $ctx = shift; |
111 | my $args = shift; |
112 | my $builder = $ctx->stash('builder'); |
113 | my $tokens = $ctx->stash('tokens'); |
114 | |
115 | if (defined ($args->{'output'}) ) { |
116 | $ctx->stash('markdown_output', lc $args->{'output'}); |
117 | } |
118 | |
119 | defined (my $str = $builder->build($ctx, $tokens) ) |
120 | or return $ctx->error($builder->errstr); |
121 | $str; # return value |
122 | }); |
123 | |
124 | MT->add_text_filter('markdown' => { |
125 | label => 'Markdown', |
126 | docs => 'http://daringfireball.net/projects/markdown/', |
127 | on_format => sub { |
128 | my $text = shift; |
129 | my $ctx = shift; |
130 | my $raw = 0; |
131 | if (defined $ctx) { |
132 | my $output = $ctx->stash('markdown_output'); |
133 | if (defined $output && $output =~ m/^html/i) { |
134 | $g_empty_element_suffix = ">"; |
135 | $ctx->stash('markdown_output', ''); |
136 | } |
137 | elsif (defined $output && $output eq 'raw') { |
138 | $raw = 1; |
139 | $ctx->stash('markdown_output', ''); |
140 | } |
141 | else { |
142 | $raw = 0; |
143 | $g_empty_element_suffix = " />"; |
144 | } |
145 | } |
146 | $text = $raw ? $text : Markdown($text); |
147 | $text; |
148 | }, |
149 | }); |
150 | |
151 | # If SmartyPants is loaded, add a combo Markdown/SmartyPants text filter: |
152 | my $smartypants; |
153 | |
154 | { |
155 | no warnings "once"; |
156 | $smartypants = $MT::Template::Context::Global_filters{'smarty_pants'}; |
157 | } |
158 | $smartypants = 0; |
159 | |
160 | if ($smartypants) { |
161 | MT->add_text_filter('markdown_with_smartypants' => { |
162 | label => 'Markdown With SmartyPants', |
163 | docs => 'http://daringfireball.net/projects/markdown/', |
164 | on_format => sub { |
165 | my $text = shift; |
166 | my $ctx = shift; |
167 | if (defined $ctx) { |
168 | my $output = $ctx->stash('markdown_output'); |
169 | if (defined $output && $output eq 'html') { |
170 | $g_empty_element_suffix = ">"; |
171 | } |
172 | else { |
173 | $g_empty_element_suffix = " />"; |
174 | } |
175 | } |
176 | $text = Markdown($text); |
177 | $text = $smartypants->($text, '1'); |
178 | }, |
179 | }); |
180 | } |
181 | } |
182 | else { |
183 | #### BBEdit/command-line text filter interface ########################## |
184 | # Needs to be hidden from MT (and Blosxom when running in static mode). |
185 | |
186 | # We're only using $blosxom::version once; tell Perl not to warn us: |
187 | no warnings 'once'; |
188 | unless ( defined($blosxom::version) ) { |
189 | use warnings; |
190 | |
191 | #### Check for command-line switches: ################# |
192 | my %cli_opts; |
193 | use Getopt::Long; |
194 | Getopt::Long::Configure('pass_through'); |
195 | GetOptions(\%cli_opts, |
196 | 'version', |
197 | 'shortversion', |
198 | 'html4tags', |
199 | ); |
200 | if ($cli_opts{'version'}) { # Version info |
201 | print "\nThis is Markdown, version $VERSION.\n"; |
202 | print "Copyright 2004 John Gruber\n"; |
203 | print "http://daringfireball.net/projects/markdown/\n\n"; |
204 | exit 0; |
205 | } |
206 | if ($cli_opts{'shortversion'}) { # Just the version number string. |
207 | print $VERSION; |
208 | exit 0; |
209 | } |
210 | if ($cli_opts{'html4tags'}) { # Use HTML tag style instead of XHTML |
211 | $g_empty_element_suffix = ">"; |
212 | } |
213 | |
214 | |
215 | #### Process incoming text: ########################### |
216 | my $text; |
217 | { |
218 | local $/; # Slurp the whole file |
219 | $text = <>; |
220 | } |
221 | print Markdown($text); |
222 | } |
223 | } |
224 | |
225 | |
226 | |
227 | sub Markdown { |
228 | # |
229 | # Main function. The order in which other subs are called here is |
230 | # essential. Link and image substitutions need to happen before |
231 | # _EscapeSpecialChars(), so that any *'s or _'s in the <a> |
232 | # and <img> tags get encoded. |
233 | # |
234 | my $text = shift; |
235 | |
236 | # Clear the global hashes. If we don't clear these, you get conflicts |
237 | # from other articles when generating a page which contains more than |
238 | # one article (e.g. an index page that shows the N most recent |
239 | # articles): |
240 | %g_urls = (); |
241 | %g_titles = (); |
242 | %g_html_blocks = (); |
243 | |
244 | |
245 | # Standardize line endings: |
246 | $text =~ s{\r\n}{\n}g; # DOS to Unix |
247 | $text =~ s{\r}{\n}g; # Mac to Unix |
248 | |
249 | # Make sure $text ends with a couple of newlines: |
250 | $text .= "\n\n"; |
251 | |
252 | # Convert all tabs to spaces. |
253 | $text = _Detab($text); |
254 | |
255 | # Strip any lines consisting only of spaces and tabs. |
256 | # This makes subsequent regexen easier to write, because we can |
257 | # match consecutive blank lines with /\n+/ instead of something |
258 | # contorted like /[ \t]*\n+/ . |
259 | $text =~ s/^[ \t]+$//mg; |
260 | |
261 | # Turn block-level HTML blocks into hash entries |
262 | $text = _HashHTMLBlocks($text); |
263 | |
264 | # Strip link definitions, store in hashes. |
265 | $text = _StripLinkDefinitions($text); |
266 | |
267 | $text = _RunBlockGamut($text); |
268 | |
269 | $text = _UnescapeSpecialChars($text); |
270 | |
271 | return $text . "\n"; |
272 | } |
273 | |
274 | |
275 | sub _StripLinkDefinitions { |
276 | # |
277 | # Strips link definitions from text, stores the URLs and titles in |
278 | # hash references. |
279 | # |
280 | my $text = shift; |
281 | my $less_than_tab = $g_tab_width - 1; |
282 | |
283 | # Link defs are in the form: ^[id]: url "optional title" |
284 | while ($text =~ s{ |
285 | ^[ ]{0,$less_than_tab}\[(.+)\]: # id = $1 |
286 | [ \t]* |
287 | \n? # maybe *one* newline |
288 | [ \t]* |
289 | <?(\S+?)>? # url = $2 |
290 | [ \t]* |
291 | \n? # maybe one newline |
292 | [ \t]* |
293 | (?: |
294 | (?<=\s) # lookbehind for whitespace |
295 | ["(] |
296 | (.+?) # title = $3 |
297 | [")] |
298 | [ \t]* |
299 | )? # title is optional |
300 | (?:\n+|\Z) |
301 | } |
302 | {}mx) { |
303 | $g_urls{lc $1} = _EncodeAmpsAndAngles( $2 ); # Link IDs are case-insensitive |
304 | if ($3) { |
305 | $g_titles{lc $1} = $3; |
306 | $g_titles{lc $1} =~ s/"/"/g; |
307 | } |
308 | } |
309 | |
310 | return $text; |
311 | } |
312 | |
313 | |
314 | sub _HashHTMLBlocks { |
315 | my $text = shift; |
316 | my $less_than_tab = $g_tab_width - 1; |
317 | |
318 | # Hashify HTML blocks: |
319 | # We only want to do this for block-level HTML tags, such as headers, |
320 | # lists, and tables. That's because we still want to wrap <p>s around |
321 | # "paragraphs" that are wrapped in non-block-level tags, such as anchors, |
322 | # phrase emphasis, and spans. The list of tags we're looking for is |
323 | # hard-coded: |
324 | my $block_tags_a = qr/p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del/; |
325 | my $block_tags_b = qr/p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math/; |
326 | |
327 | # First, look for nested blocks, e.g.: |
328 | # <div> |
329 | # <div> |
330 | # tags for inner block must be indented. |
331 | # </div> |
332 | # </div> |
333 | # |
334 | # The outermost tags must start at the left margin for this to match, and |
335 | # the inner nested divs must be indented. |
336 | # We need to do this before the next, more liberal match, because the next |
337 | # match will start at the first `<div>` and stop at the first `</div>`. |
338 | $text =~ s{ |
339 | ( # save in $1 |
340 | ^ # start of line (with /m) |
341 | <($block_tags_a) # start tag = $2 |
342 | \b # word break |
343 | (.*\n)*? # any number of lines, minimally matching |
344 | </\2> # the matching end tag |
345 | [ \t]* # trailing spaces/tabs |
346 | (?=\n+|\Z) # followed by a newline or end of document |
347 | ) |
348 | }{ |
349 | my $key = md5_hex($1); |
350 | $g_html_blocks{$key} = $1; |
351 | "\n\n" . $key . "\n\n"; |
352 | }egmx; |
353 | |
354 | |
355 | # |
356 | # Now match more liberally, simply from `\n<tag>` to `</tag>\n` |
357 | # |
358 | $text =~ s{ |
359 | ( # save in $1 |
360 | ^ # start of line (with /m) |
361 | <($block_tags_b) # start tag = $2 |
362 | \b # word break |
363 | (.*\n)*? # any number of lines, minimally matching |
364 | .*</\2> # the matching end tag |
365 | [ \t]* # trailing spaces/tabs |
366 | (?=\n+|\Z) # followed by a newline or end of document |
367 | ) |
368 | }{ |
369 | my $key = md5_hex($1); |
370 | $g_html_blocks{$key} = $1; |
371 | "\n\n" . $key . "\n\n"; |
372 | }egmx; |
373 | # Special case just for <hr />. It was easier to make a special case than |
374 | # to make the other regex more complicated. |
375 | $text =~ s{ |
376 | (?: |
377 | (?<=\n\n) # Starting after a blank line |
378 | | # or |
379 | \A\n? # the beginning of the doc |
380 | ) |
381 | ( # save in $1 |
382 | [ ]{0,$less_than_tab} |
383 | <(hr) # start tag = $2 |
384 | \b # word break |
385 | ([^<>])*? # |
386 | /?> # the matching end tag |
387 | [ \t]* |
388 | (?=\n{2,}|\Z) # followed by a blank line or end of document |
389 | ) |
390 | }{ |
391 | my $key = md5_hex($1); |
392 | $g_html_blocks{$key} = $1; |
393 | "\n\n" . $key . "\n\n"; |
394 | }egx; |
395 | |
396 | # Special case for standalone HTML comments: |
397 | $text =~ s{ |
398 | (?: |
399 | (?<=\n\n) # Starting after a blank line |
400 | | # or |
401 | \A\n? # the beginning of the doc |
402 | ) |
403 | ( # save in $1 |
404 | [ ]{0,$less_than_tab} |
405 | (?s: |
406 | <! |
407 | (--.*?--\s*)+ |
408 | > |
409 | ) |
410 | [ \t]* |
411 | (?=\n{2,}|\Z) # followed by a blank line or end of document |
412 | ) |
413 | }{ |
414 | my $key = md5_hex($1); |
415 | $g_html_blocks{$key} = $1; |
416 | "\n\n" . $key . "\n\n"; |
417 | }egx; |
418 | |
419 | |
420 | return $text; |
421 | } |
422 | |
423 | |
424 | sub _RunBlockGamut { |
425 | # |
426 | # These are all the transformations that form block-level |
427 | # tags like paragraphs, headers, and list items. |
428 | # |
429 | my $text = shift; |
430 | |
431 | $text = _DoHeaders($text); |
432 | |
433 | # Do Horizontal Rules: |
434 | $text =~ s{^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx; |
435 | $text =~ s{^[ ]{0,2}([ ]? -[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx; |
436 | $text =~ s{^[ ]{0,2}([ ]? _[ ]?){3,}[ \t]*$}{\n<hr$g_empty_element_suffix\n}gmx; |
437 | |
438 | $text = _DoLists($text); |
439 | |
440 | $text = _DoCodeBlocks($text); |
441 | |
442 | $text = _DoBlockQuotes($text); |
443 | |
444 | # We already ran _HashHTMLBlocks() before, in Markdown(), but that |
445 | # was to escape raw HTML in the original Markdown source. This time, |
446 | # we're escaping the markup we've just created, so that we don't wrap |
447 | # <p> tags around block-level tags. |
448 | $text = _HashHTMLBlocks($text); |
449 | |
450 | $text = _FormParagraphs($text); |
451 | |
452 | return $text; |
453 | } |
454 | |
455 | |
456 | sub _RunSpanGamut { |
457 | # |
458 | # These are all the transformations that occur *within* block-level |
459 | # tags like paragraphs, headers, and list items. |
460 | # |
461 | my $text = shift; |
462 | |
463 | $text = _DoCodeSpans($text); |
464 | |
465 | $text = _EscapeSpecialChars($text); |
466 | |
467 | # Process anchor and image tags. Images must come first, |
468 | # because ![foo][f] looks like an anchor. |
469 | $text = _DoImages($text); |
470 | $text = _DoAnchors($text); |
471 | |
472 | # Make links out of things like `<http://example.com/>` |
473 | # Must come after _DoAnchors(), because you can use < and > |
474 | # delimiters in inline links like [this](<url>). |
475 | $text = _DoAutoLinks($text); |
476 | |
477 | $text = _EncodeAmpsAndAngles($text); |
478 | |
479 | $text = _DoItalicsAndBold($text); |
480 | |
481 | # Do hard breaks: |
482 | $text =~ s/ {2,}\n/ <br$g_empty_element_suffix\n/g; |
483 | |
484 | return $text; |
485 | } |
486 | |
487 | |
488 | sub _EscapeSpecialChars { |
489 | my $text = shift; |
490 | my $tokens ||= _TokenizeHTML($text); |
491 | |
492 | $text = ''; # rebuild $text from the tokens |
493 | # my $in_pre = 0; # Keep track of when we're inside <pre> or <code> tags. |
494 | # my $tags_to_skip = qr!<(/?)(?:pre|code|kbd|script|math)[\s>]!; |
495 | |
496 | foreach my $cur_token (@$tokens) { |
497 | if ($cur_token->[0] eq "tag") { |
498 | # Within tags, encode * and _ so they don't conflict |
499 | # with their use in Markdown for italics and strong. |
500 | # We're replacing each such character with its |
501 | # corresponding MD5 checksum value; this is likely |
502 | # overkill, but it should prevent us from colliding |
503 | # with the escape values by accident. |
504 | $cur_token->[1] =~ s! \* !$g_escape_table{'*'}!gx; |
505 | $cur_token->[1] =~ s! _ !$g_escape_table{'_'}!gx; |
506 | $text .= $cur_token->[1]; |
507 | } else { |
508 | my $t = $cur_token->[1]; |
509 | $t = _EncodeBackslashEscapes($t); |
510 | $text .= $t; |
511 | } |
512 | } |
513 | return $text; |
514 | } |
515 | |
516 | |
517 | sub _DoAnchors { |
518 | # |
519 | # Turn Markdown link shortcuts into XHTML <a> tags. |
520 | # |
521 | my $text = shift; |
522 | |
523 | # |
524 | # First, handle reference-style links: [link text] [id] |
525 | # |
526 | $text =~ s{ |
527 | ( # wrap whole match in $1 |
528 | \[ |
529 | ($g_nested_brackets) # link text = $2 |
530 | \] |
531 | |
532 | [ ]? # one optional space |
533 | (?:\n[ ]*)? # one optional newline followed by spaces |
534 | |
535 | \[ |
536 | (.*?) # id = $3 |
537 | \] |
538 | ) |
539 | }{ |
540 | my $result; |
541 | my $whole_match = $1; |
542 | my $link_text = $2; |
543 | my $link_id = lc $3; |
544 | |
545 | if ($link_id eq "") { |
546 | $link_id = lc $link_text; # for shortcut links like [this][]. |
547 | } |
548 | |
549 | if (defined $g_urls{$link_id}) { |
550 | my $url = $g_urls{$link_id}; |
551 | $url =~ s! \* !$g_escape_table{'*'}!gx; # We've got to encode these to avoid |
552 | $url =~ s! _ !$g_escape_table{'_'}!gx; # conflicting with italics/bold. |
553 | $result = "<a href=\"$url\""; |
554 | if ( defined $g_titles{$link_id} ) { |
555 | my $title = $g_titles{$link_id}; |
556 | $title =~ s! \* !$g_escape_table{'*'}!gx; |
557 | $title =~ s! _ !$g_escape_table{'_'}!gx; |
558 | $result .= " title=\"$title\""; |
559 | } |
560 | $result .= ">$link_text</a>"; |
561 | } |
562 | else { |
563 | $result = $whole_match; |
564 | } |
565 | $result; |
566 | }xsge; |
567 | |
568 | # |
569 | # Next, inline-style links: [link text](url "optional title") |
570 | # |
571 | $text =~ s{ |
572 | ( # wrap whole match in $1 |
573 | \[ |
574 | ($g_nested_brackets) # link text = $2 |
575 | \] |
576 | \( # literal paren |
577 | [ \t]* |
578 | <?(.*?)>? # href = $3 |
579 | [ \t]* |
580 | ( # $4 |
581 | (['"]) # quote char = $5 |
582 | (.*?) # Title = $6 |
583 | \5 # matching quote |
584 | )? # title is optional |
585 | \) |
586 | ) |
587 | }{ |
588 | my $result; |
589 | my $whole_match = $1; |
590 | my $link_text = $2; |
591 | my $url = $3; |
592 | my $title = $6; |
593 | |
594 | $url =~ s! \* !$g_escape_table{'*'}!gx; # We've got to encode these to avoid |
595 | $url =~ s! _ !$g_escape_table{'_'}!gx; # conflicting with italics/bold. |
596 | $result = "<a href=\"$url\""; |
597 | |
598 | if (defined $title) { |
599 | $title =~ s/"/"/g; |
600 | $title =~ s! \* !$g_escape_table{'*'}!gx; |
601 | $title =~ s! _ !$g_escape_table{'_'}!gx; |
602 | $result .= " title=\"$title\""; |
603 | } |
604 | |
605 | $result .= ">$link_text</a>"; |
606 | |
607 | $result; |
608 | }xsge; |
609 | |
610 | return $text; |
611 | } |
612 | |
613 | |
614 | sub _DoImages { |
615 | # |
616 | # Turn Markdown image shortcuts into <img> tags. |
617 | # |
618 | my $text = shift; |
619 | |
620 | # |
621 | # First, handle reference-style labeled images: ![alt text][id] |
622 | # |
623 | $text =~ s{ |
624 | ( # wrap whole match in $1 |
625 | !\[ |
626 | (.*?) # alt text = $2 |
627 | \] |
628 | |
629 | [ ]? # one optional space |
630 | (?:\n[ ]*)? # one optional newline followed by spaces |
631 | |
632 | \[ |
633 | (.*?) # id = $3 |
634 | \] |
635 | |
636 | ) |
637 | }{ |
638 | my $result; |
639 | my $whole_match = $1; |
640 | my $alt_text = $2; |
641 | my $link_id = lc $3; |
642 | |
643 | if ($link_id eq "") { |
644 | $link_id = lc $alt_text; # for shortcut links like ![this][]. |
645 | } |
646 | |
647 | $alt_text =~ s/"/"/g; |
648 | if (defined $g_urls{$link_id}) { |
649 | my $url = $g_urls{$link_id}; |
650 | $url =~ s! \* !$g_escape_table{'*'}!gx; # We've got to encode these to avoid |
651 | $url =~ s! _ !$g_escape_table{'_'}!gx; # conflicting with italics/bold. |
652 | $result = "<img src=\"$url\" alt=\"$alt_text\""; |
653 | if (defined $g_titles{$link_id}) { |
654 | my $title = $g_titles{$link_id}; |
655 | $title =~ s! \* !$g_escape_table{'*'}!gx; |
656 | $title =~ s! _ !$g_escape_table{'_'}!gx; |
657 | $result .= " title=\"$title\""; |
658 | } |
659 | $result .= $g_empty_element_suffix; |
660 | } |
661 | else { |
662 | # If there's no such link ID, leave intact: |
663 | $result = $whole_match; |
664 | } |
665 | |
666 | $result; |
667 | }xsge; |
668 | |
669 | # |
670 | # Next, handle inline images: ![alt text](url "optional title") |
671 | # Don't forget: encode * and _ |
672 | |
673 | $text =~ s{ |
674 | ( # wrap whole match in $1 |
675 | !\[ |
676 | (.*?) # alt text = $2 |
677 | \] |
678 | \( # literal paren |
679 | [ \t]* |
680 | <?(\S+?)>? # src url = $3 |
681 | [ \t]* |
682 | ( # $4 |
683 | (['"]) # quote char = $5 |
684 | (.*?) # title = $6 |
685 | \5 # matching quote |
686 | [ \t]* |
687 | )? # title is optional |
688 | \) |
689 | ) |
690 | }{ |
691 | my $result; |
692 | my $whole_match = $1; |
693 | my $alt_text = $2; |
694 | my $url = $3; |
695 | my $title = ''; |
696 | if (defined($6)) { |
697 | $title = $6; |
698 | } |
699 | |
700 | $alt_text =~ s/"/"/g; |
701 | $title =~ s/"/"/g; |
702 | $url =~ s! \* !$g_escape_table{'*'}!gx; # We've got to encode these to avoid |
703 | $url =~ s! _ !$g_escape_table{'_'}!gx; # conflicting with italics/bold. |
704 | $result = "<img src=\"$url\" alt=\"$alt_text\""; |
705 | if (defined $title) { |
706 | $title =~ s! \* !$g_escape_table{'*'}!gx; |
707 | $title =~ s! _ !$g_escape_table{'_'}!gx; |
708 | $result .= " title=\"$title\""; |
709 | } |
710 | $result .= $g_empty_element_suffix; |
711 | |
712 | $result; |
713 | }xsge; |
714 | |
715 | return $text; |
716 | } |
717 | |
718 | |
719 | sub _DoHeaders { |
720 | my $text = shift; |
721 | |
722 | # Setext-style headers: |
723 | # Header 1 |
724 | # ======== |
725 | # |
726 | # Header 2 |
727 | # -------- |
728 | # |
729 | $text =~ s{ ^(.+)[ \t]*\n=+[ \t]*\n+ }{ |
730 | "<h1>" . _RunSpanGamut($1) . "</h1>\n\n"; |
731 | }egmx; |
732 | |
733 | $text =~ s{ ^(.+)[ \t]*\n-+[ \t]*\n+ }{ |
734 | "<h2>" . _RunSpanGamut($1) . "</h2>\n\n"; |
735 | }egmx; |
736 | |
737 | |
738 | # atx-style headers: |
739 | # # Header 1 |
740 | # ## Header 2 |
741 | # ## Header 2 with closing hashes ## |
742 | # ... |
743 | # ###### Header 6 |
744 | # |
745 | $text =~ s{ |
746 | ^(\#{1,6}) # $1 = string of #'s |
747 | [ \t]* |
748 | (.+?) # $2 = Header text |
749 | [ \t]* |
750 | \#* # optional closing #'s (not counted) |
751 | \n+ |
752 | }{ |
753 | my $h_level = length($1); |
754 | "<h$h_level>" . _RunSpanGamut($2) . "</h$h_level>\n\n"; |
755 | }egmx; |
756 | |
757 | return $text; |
758 | } |
759 | |
760 | |
761 | sub _DoLists { |
762 | # |
763 | # Form HTML ordered (numbered) and unordered (bulleted) lists. |
764 | # |
765 | my $text = shift; |
766 | my $less_than_tab = $g_tab_width - 1; |
767 | |
768 | # Re-usable patterns to match list item bullets and number markers: |
769 | my $marker_ul = qr/[*+-]/; |
770 | my $marker_ol = qr/\d+[.]/; |
771 | my $marker_any = qr/(?:$marker_ul|$marker_ol)/; |
772 | |
773 | # Re-usable pattern to match any entirel ul or ol list: |
774 | my $whole_list = qr{ |
775 | ( # $1 = whole list |
776 | ( # $2 |
777 | [ ]{0,$less_than_tab} |
778 | (${marker_any}) # $3 = first list item marker |
779 | [ \t]+ |
780 | ) |
781 | (?s:.+?) |
782 | ( # $4 |
783 | \z |
784 | | |
785 | \n{2,} |
786 | (?=\S) |
787 | (?! # Negative lookahead for another list item marker |
788 | [ \t]* |
789 | ${marker_any}[ \t]+ |
790 | ) |
791 | ) |
792 | ) |
793 | }mx; |
794 | |
795 | # We use a different prefix before nested lists than top-level lists. |
796 | # See extended comment in _ProcessListItems(). |
797 | # |
798 | # Note: There's a bit of duplication here. My original implementation |
799 | # created a scalar regex pattern as the conditional result of the test on |
800 | # $g_list_level, and then only ran the $text =~ s{...}{...}egmx |
801 | # substitution once, using the scalar as the pattern. This worked, |
802 | # everywhere except when running under MT on my hosting account at Pair |
803 | # Networks. There, this caused all rebuilds to be killed by the reaper (or |
804 | # perhaps they crashed, but that seems incredibly unlikely given that the |
805 | # same script on the same server ran fine *except* under MT. I've spent |
806 | # more time trying to figure out why this is happening than I'd like to |
807 | # admit. My only guess, backed up by the fact that this workaround works, |
808 | # is that Perl optimizes the substition when it can figure out that the |
809 | # pattern will never change, and when this optimization isn't on, we run |
810 | # afoul of the reaper. Thus, the slightly redundant code to that uses two |
811 | # static s/// patterns rather than one conditional pattern. |
812 | |
813 | if ($g_list_level) { |
814 | $text =~ s{ |
815 | ^ |
816 | $whole_list |
817 | }{ |
818 | my $list = $1; |
819 | my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol"; |
820 | # Turn double returns into triple returns, so that we can make a |
821 | # paragraph for the last item in a list, if necessary: |
822 | $list =~ s/\n{2,}/\n\n\n/g; |
823 | my $result = _ProcessListItems($list, $marker_any); |
824 | $result = "<$list_type>\n" . $result . "</$list_type>\n"; |
825 | $result; |
826 | }egmx; |
827 | } |
828 | else { |
829 | $text =~ s{ |
830 | (?:(?<=\n\n)|\A\n?) |
831 | $whole_list |
832 | }{ |
833 | my $list = $1; |
834 | my $list_type = ($3 =~ m/$marker_ul/) ? "ul" : "ol"; |
835 | # Turn double returns into triple returns, so that we can make a |
836 | # paragraph for the last item in a list, if necessary: |
837 | $list =~ s/\n{2,}/\n\n\n/g; |
838 | my $result = _ProcessListItems($list, $marker_any); |
839 | $result = "<$list_type>\n" . $result . "</$list_type>\n"; |
840 | $result; |
841 | }egmx; |
842 | } |
843 | |
844 | |
845 | return $text; |
846 | } |
847 | |
848 | |
849 | sub _ProcessListItems { |
850 | # |
851 | # Process the contents of a single ordered or unordered list, splitting it |
852 | # into individual list items. |
853 | # |
854 | |
855 | my $list_str = shift; |
856 | my $marker_any = shift; |
857 | |
858 | |
859 | # The $g_list_level global keeps track of when we're inside a list. |
860 | # Each time we enter a list, we increment it; when we leave a list, |
861 | # we decrement. If it's zero, we're not in a list anymore. |
862 | # |
863 | # We do this because when we're not inside a list, we want to treat |
864 | # something like this: |
865 | # |
866 | # I recommend upgrading to version |
867 | # 8. Oops, now this line is treated |
868 | # as a sub-list. |
869 | # |
870 | # As a single paragraph, despite the fact that the second line starts |
871 | # with a digit-period-space sequence. |
872 | # |
873 | # Whereas when we're inside a list (or sub-list), that line will be |
874 | # treated as the start of a sub-list. What a kludge, huh? This is |
875 | # an aspect of Markdown's syntax that's hard to parse perfectly |
876 | # without resorting to mind-reading. Perhaps the solution is to |
877 | # change the syntax rules such that sub-lists must start with a |
878 | # starting cardinal number; e.g. "1." or "a.". |
879 | |
880 | $g_list_level++; |
881 | |
882 | # trim trailing blank lines: |
883 | $list_str =~ s/\n{2,}\z/\n/; |
884 | |
885 | |
886 | $list_str =~ s{ |
887 | (\n)? # leading line = $1 |
888 | (^[ \t]*) # leading whitespace = $2 |
889 | ($marker_any) [ \t]+ # list marker = $3 |
890 | ((?s:.+?) # list item text = $4 |
891 | (\n{1,2})) |
892 | (?= \n* (\z | \2 ($marker_any) [ \t]+)) |
893 | }{ |
894 | my $item = $4; |
895 | my $leading_line = $1; |
896 | my $leading_space = $2; |
897 | |
898 | if ($leading_line or ($item =~ m/\n{2,}/)) { |
899 | $item = _RunBlockGamut(_Outdent($item)); |
900 | } |
901 | else { |
902 | # Recursion for sub-lists: |
903 | $item = _DoLists(_Outdent($item)); |
904 | chomp $item; |
905 | $item = _RunSpanGamut($item); |
906 | } |
907 | |
908 | "<li>" . $item . "</li>\n"; |
909 | }egmx; |
910 | |
911 | $g_list_level--; |
912 | return $list_str; |
913 | } |
914 | |
915 | |
916 | |
917 | sub _DoCodeBlocks { |
918 | # |
919 | # Process Markdown `<pre><code>` blocks. |
920 | # |
921 | |
922 | my $text = shift; |
923 | |
924 | $text =~ s{ |
925 | (?:\n\n|\A) |
926 | ( # $1 = the code block -- one or more lines, starting with a space/tab |
927 | (?: |
928 | (?:[ ]{$g_tab_width} | \t) # Lines must start with a tab or a tab-width of spaces |
929 | .*\n+ |
930 | )+ |
931 | ) |
932 | ((?=^[ ]{0,$g_tab_width}\S)|\Z) # Lookahead for non-space at line-start, or end of doc |
933 | }{ |
934 | my $codeblock = $1; |
935 | my $result; # return value |
936 | |
937 | $codeblock = _EncodeCode(_Outdent($codeblock)); |
938 | $codeblock = _Detab($codeblock); |
939 | $codeblock =~ s/\A\n+//; # trim leading newlines |
940 | $codeblock =~ s/\s+\z//; # trim trailing whitespace |
941 | |
942 | $result = "\n\n<pre><code>" . $codeblock . "\n</code></pre>\n\n"; |
943 | |
944 | $result; |
945 | }egmx; |
946 | |
947 | return $text; |
948 | } |
949 | |
950 | |
951 | sub _DoCodeSpans { |
952 | # |
953 | # * Backtick quotes are used for <code></code> spans. |
954 | # |
955 | # * You can use multiple backticks as the delimiters if you want to |
956 | # include literal backticks in the code span. So, this input: |
957 | # |
958 | # Just type ``foo `bar` baz`` at the prompt. |
959 | # |
960 | # Will translate to: |
961 | # |
962 | # <p>Just type <code>foo `bar` baz</code> at the prompt.</p> |
963 | # |
964 | # There's no arbitrary limit to the number of backticks you |
965 | # can use as delimters. If you need three consecutive backticks |
966 | # in your code, use four for delimiters, etc. |
967 | # |
968 | # * You can use spaces to get literal backticks at the edges: |
969 | # |
970 | # ... type `` `bar` `` ... |
971 | # |
972 | # Turns to: |
973 | # |
974 | # ... type <code>`bar`</code> ... |
975 | # |
976 | |
977 | my $text = shift; |
978 | |
979 | $text =~ s@ |
980 | (`+) # $1 = Opening run of ` |
981 | (.+?) # $2 = The code block |
982 | (?<!`) |
983 | \1 # Matching closer |
984 | (?!`) |
985 | @ |
986 | my $c = "$2"; |
987 | $c =~ s/^[ \t]*//g; # leading whitespace |
988 | $c =~ s/[ \t]*$//g; # trailing whitespace |
989 | $c = _EncodeCode($c); |
990 | "<code>$c</code>"; |
991 | @egsx; |
992 | |
993 | return $text; |
994 | } |
995 | |
996 | |
997 | sub _EncodeCode { |
998 | # |
999 | # Encode/escape certain characters inside Markdown code runs. |
1000 | # The point is that in code, these characters are literals, |
1001 | # and lose their special Markdown meanings. |
1002 | # |
1003 | local $_ = shift; |
1004 | |
1005 | # Encode all ampersands; HTML entities are not |
1006 | # entities within a Markdown code span. |
1007 | s/&/&/g; |
1008 | |
1009 | # Encode $'s, but only if we're running under Blosxom. |
1010 | # (Blosxom interpolates Perl variables in article bodies.) |
1011 | { |
1012 | no warnings 'once'; |
1013 | if (defined($blosxom::version)) { |
1014 | s/\$/$/g; |
1015 | } |
1016 | } |
1017 | |
1018 | |
1019 | # Do the angle bracket song and dance: |
1020 | s! < !<!gx; |
1021 | s! > !>!gx; |
1022 | |
1023 | # Now, escape characters that are magic in Markdown: |
1024 | s! \* !$g_escape_table{'*'}!gx; |
1025 | s! _ !$g_escape_table{'_'}!gx; |
1026 | s! { !$g_escape_table{'{'}!gx; |
1027 | s! } !$g_escape_table{'}'}!gx; |
1028 | s! \[ !$g_escape_table{'['}!gx; |
1029 | s! \] !$g_escape_table{']'}!gx; |
1030 | s! \\ !$g_escape_table{'\\'}!gx; |
1031 | |
1032 | return $_; |
1033 | } |
1034 | |
1035 | |
1036 | sub _DoItalicsAndBold { |
1037 | my $text = shift; |
1038 | |
1039 | # <strong> must go first: |
1040 | $text =~ s{ (\*\*|__) (?=\S) (.+?[*_]*) (?<=\S) \1 } |
1041 | {<strong>$2</strong>}gsx; |
1042 | |
1043 | $text =~ s{ (\*|_) (?=\S) (.+?) (?<=\S) \1 } |
1044 | {<em>$2</em>}gsx; |
1045 | |
1046 | return $text; |
1047 | } |
1048 | |
1049 | |
1050 | sub _DoBlockQuotes { |
1051 | my $text = shift; |
1052 | |
1053 | $text =~ s{ |
1054 | ( # Wrap whole match in $1 |
1055 | ( |
1056 | ^[ \t]*>[ \t]? # '>' at the start of a line |
1057 | .+\n # rest of the first line |
1058 | (.+\n)* # subsequent consecutive lines |
1059 | \n* # blanks |
1060 | )+ |
1061 | ) |
1062 | }{ |
1063 | my $bq = $1; |
1064 | $bq =~ s/^[ \t]*>[ \t]?//gm; # trim one level of quoting |
1065 | $bq =~ s/^[ \t]+$//mg; # trim whitespace-only lines |
1066 | $bq = _RunBlockGamut($bq); # recurse |
1067 | |
1068 | $bq =~ s/^/ /g; |
1069 | # These leading spaces screw with <pre> content, so we need to fix that: |
1070 | $bq =~ s{ |
1071 | (\s*<pre>.+?</pre>) |
1072 | }{ |
1073 | my $pre = $1; |
1074 | $pre =~ s/^ //mg; |
1075 | $pre; |
1076 | }egsx; |
1077 | |
1078 | "<blockquote>\n$bq\n</blockquote>\n\n"; |
1079 | }egmx; |
1080 | |
1081 | |
1082 | return $text; |
1083 | } |
1084 | |
1085 | |
1086 | sub _FormParagraphs { |
1087 | # |
1088 | # Params: |
1089 | # $text - string to process with html <p> tags |
1090 | # |
1091 | my $text = shift; |
1092 | |
1093 | # Strip leading and trailing lines: |
1094 | $text =~ s/\A\n+//; |
1095 | $text =~ s/\n+\z//; |
1096 | |
1097 | my @grafs = split(/\n{2,}/, $text); |
1098 | |
1099 | # |
1100 | # Wrap <p> tags. |
1101 | # |
1102 | foreach (@grafs) { |
1103 | unless (defined( $g_html_blocks{$_} )) { |
1104 | $_ = _RunSpanGamut($_); |
1105 | s/^([ \t]*)/<p>/; |
1106 | $_ .= "</p>"; |
1107 | } |
1108 | } |
1109 | |
1110 | # |
1111 | # Unhashify HTML blocks |
1112 | # |
1113 | foreach (@grafs) { |
1114 | if (defined( $g_html_blocks{$_} )) { |
1115 | $_ = $g_html_blocks{$_}; |
1116 | } |
1117 | } |
1118 | |
1119 | return join "\n\n", @grafs; |
1120 | } |
1121 | |
1122 | |
1123 | sub _EncodeAmpsAndAngles { |
1124 | # Smart processing for ampersands and angle brackets that need to be encoded. |
1125 | |
1126 | my $text = shift; |
1127 | |
1128 | # Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: |
1129 | # http://bumppo.net/projects/amputator/ |
1130 | $text =~ s/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/&/g; |
1131 | |
1132 | # Encode naked <'s |
1133 | $text =~ s{<(?![a-z/?\$!])}{<}gi; |
1134 | |
1135 | return $text; |
1136 | } |
1137 | |
1138 | |
1139 | sub _EncodeBackslashEscapes { |
1140 | # |
1141 | # Parameter: String. |
1142 | # Returns: The string, with after processing the following backslash |
1143 | # escape sequences. |
1144 | # |
1145 | local $_ = shift; |
1146 | |
1147 | s! \\\\ !$g_escape_table{'\\'}!gx; # Must process escaped backslashes first. |
1148 | s! \\` !$g_escape_table{'`'}!gx; |
1149 | s! \\\* !$g_escape_table{'*'}!gx; |
1150 | s! \\_ !$g_escape_table{'_'}!gx; |
1151 | s! \\\{ !$g_escape_table{'{'}!gx; |
1152 | s! \\\} !$g_escape_table{'}'}!gx; |
1153 | s! \\\[ !$g_escape_table{'['}!gx; |
1154 | s! \\\] !$g_escape_table{']'}!gx; |
1155 | s! \\\( !$g_escape_table{'('}!gx; |
1156 | s! \\\) !$g_escape_table{')'}!gx; |
1157 | s! \\> !$g_escape_table{'>'}!gx; |
1158 | s! \\\# !$g_escape_table{'#'}!gx; |
1159 | s! \\\+ !$g_escape_table{'+'}!gx; |
1160 | s! \\\- !$g_escape_table{'-'}!gx; |
1161 | s! \\\. !$g_escape_table{'.'}!gx; |
1162 | s{ \\! }{$g_escape_table{'!'}}gx; |
1163 | |
1164 | return $_; |
1165 | } |
1166 | |
1167 | |
1168 | sub _DoAutoLinks { |
1169 | my $text = shift; |
1170 | |
1171 | $text =~ s{<((https?|ftp):[^'">\s]+)>}{<a href="$1">$1</a>}gi; |
1172 | |
1173 | # Email addresses: <address@domain.foo> |
1174 | $text =~ s{ |
1175 | < |
1176 | (?:mailto:)? |
1177 | ( |
1178 | [-.\w]+ |
1179 | \@ |
1180 | [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ |
1181 | ) |
1182 | > |
1183 | }{ |
1184 | _EncodeEmailAddress( _UnescapeSpecialChars($1) ); |
1185 | }egix; |
1186 | |
1187 | return $text; |
1188 | } |
1189 | |
1190 | |
1191 | sub _EncodeEmailAddress { |
1192 | # |
1193 | # Input: an email address, e.g. "foo@example.com" |
1194 | # |
1195 | # Output: the email address as a mailto link, with each character |
1196 | # of the address encoded as either a decimal or hex entity, in |
1197 | # the hopes of foiling most address harvesting spam bots. E.g.: |
1198 | # |
1199 | # <a href="mailto:foo@e |
1200 | # xample.com">foo |
1201 | # @example.com</a> |
1202 | # |
1203 | # Based on a filter by Matthew Wickline, posted to the BBEdit-Talk |
1204 | # mailing list: <http://tinyurl.com/yu7ue> |
1205 | # |
1206 | |
1207 | my $addr = shift; |
1208 | |
1209 | srand; |
1210 | my @encode = ( |
1211 | sub { '&#' . ord(shift) . ';' }, |
1212 | sub { '&#x' . sprintf( "%X", ord(shift) ) . ';' }, |
1213 | sub { shift }, |
1214 | ); |
1215 | |
1216 | $addr = "mailto:" . $addr; |
1217 | |
1218 | $addr =~ s{(.)}{ |
1219 | my $char = $1; |
1220 | if ( $char eq '@' ) { |
1221 | # this *must* be encoded. I insist. |
1222 | $char = $encode[int rand 1]->($char); |
1223 | } elsif ( $char ne ':' ) { |
1224 | # leave ':' alone (to spot mailto: later) |
1225 | my $r = rand; |
1226 | # roughly 10% raw, 45% hex, 45% dec |
1227 | $char = ( |
1228 | $r > .9 ? $encode[2]->($char) : |
1229 | $r < .45 ? $encode[1]->($char) : |
1230 | $encode[0]->($char) |
1231 | ); |
1232 | } |
1233 | $char; |
1234 | }gex; |
1235 | |
1236 | $addr = qq{<a href="$addr">$addr</a>}; |
1237 | $addr =~ s{">.+?:}{">}; # strip the mailto: from the visible part |
1238 | |
1239 | return $addr; |
1240 | } |
1241 | |
1242 | |
1243 | sub _UnescapeSpecialChars { |
1244 | # |
1245 | # Swap back in all the special characters we've hidden. |
1246 | # |
1247 | my $text = shift; |
1248 | |
1249 | while( my($char, $hash) = each(%g_escape_table) ) { |
1250 | $text =~ s/$hash/$char/g; |
1251 | } |
1252 | return $text; |
1253 | } |
1254 | |
1255 | |
1256 | sub _TokenizeHTML { |
1257 | # |
1258 | # Parameter: String containing HTML markup. |
1259 | # Returns: Reference to an array of the tokens comprising the input |
1260 | # string. Each token is either a tag (possibly with nested, |
1261 | # tags contained therein, such as <a href="<MTFoo>">, or a |
1262 | # run of text between tags. Each element of the array is a |
1263 | # two-element array; the first is either 'tag' or 'text'; |
1264 | # the second is the actual value. |
1265 | # |
1266 | # |
1267 | # Derived from the _tokenize() subroutine from Brad Choate's MTRegex plugin. |
1268 | # <http://www.bradchoate.com/past/mtregex.php> |
1269 | # |
1270 | |
1271 | my $str = shift; |
1272 | my $pos = 0; |
1273 | my $len = length $str; |
1274 | my @tokens; |
1275 | |
1276 | my $depth = 6; |
1277 | my $nested_tags = join('|', ('(?:<[a-z/!$](?:[^<>]') x $depth) . (')*>)' x $depth); |
1278 | my $match = qr/(?s: <! ( -- .*? -- \s* )+ > ) | # comment |
1279 | (?s: <\? .*? \?> ) | # processing instruction |
1280 | $nested_tags/ix; # nested tags |
1281 | |
1282 | while ($str =~ m/($match)/g) { |
1283 | my $whole_tag = $1; |
1284 | my $sec_start = pos $str; |
1285 | my $tag_start = $sec_start - length $whole_tag; |
1286 | if ($pos < $tag_start) { |
1287 | push @tokens, ['text', substr($str, $pos, $tag_start - $pos)]; |
1288 | } |
1289 | push @tokens, ['tag', $whole_tag]; |
1290 | $pos = pos $str; |
1291 | } |
1292 | push @tokens, ['text', substr($str, $pos, $len - $pos)] if $pos < $len; |
1293 | \@tokens; |
1294 | } |
1295 | |
1296 | |
1297 | sub _Outdent { |
1298 | # |
1299 | # Remove one level of line-leading tabs or spaces |
1300 | # |
1301 | my $text = shift; |
1302 | |
1303 | $text =~ s/^(\t|[ ]{1,$g_tab_width})//gm; |
1304 | return $text; |
1305 | } |
1306 | |
1307 | |
1308 | sub _Detab { |
1309 | # |
1310 | # Cribbed from a post by Bart Lateur: |
1311 | # <http://www.nntp.perl.org/group/perl.macperl.anyperl/154> |
1312 | # |
1313 | my $text = shift; |
1314 | |
1315 | $text =~ s{(.*?)\t}{$1.(' ' x ($g_tab_width - length($1) % $g_tab_width))}ge; |
1316 | return $text; |
1317 | } |
1318 | |
1319 | |
1320 | 1; |
1321 | |
1322 | __END__ |
1323 | |
1324 | |
1325 | =pod |
1326 | |
1327 | =head1 NAME |
1328 | |
1329 | B<Markdown> |
1330 | |
1331 | |
1332 | =head1 SYNOPSIS |
1333 | |
1334 | B<Markdown.pl> [ B<--html4tags> ] [ B<--version> ] [ B<-shortversion> ] |
1335 | [ I<file> ... ] |
1336 | |
1337 | |
1338 | =head1 DESCRIPTION |
1339 | |
1340 | Markdown is a text-to-HTML filter; it translates an easy-to-read / |
1341 | easy-to-write structured text format into HTML. Markdown's text format |
1342 | is most similar to that of plain text email, and supports features such |
1343 | as headers, *emphasis*, code blocks, blockquotes, and links. |
1344 | |
1345 | Markdown's syntax is designed not as a generic markup language, but |
1346 | specifically to serve as a front-end to (X)HTML. You can use span-level |
1347 | HTML tags anywhere in a Markdown document, and you can use block level |
1348 | HTML tags (like <div> and <table> as well). |
1349 | |
1350 | For more information about Markdown's syntax, see: |
1351 | |
1352 | http://daringfireball.net/projects/markdown/ |
1353 | |
1354 | |
1355 | =head1 OPTIONS |
1356 | |
1357 | Use "--" to end switch parsing. For example, to open a file named "-z", use: |
1358 | |
1359 | Markdown.pl -- -z |
1360 | |
1361 | =over 4 |
1362 | |
1363 | |
1364 | =item B<--html4tags> |
1365 | |
1366 | Use HTML 4 style for empty element tags, e.g.: |
1367 | |
1368 | <br> |
1369 | |
1370 | instead of Markdown's default XHTML style tags, e.g.: |
1371 | |
1372 | <br /> |
1373 | |
1374 | |
1375 | =item B<-v>, B<--version> |
1376 | |
1377 | Display Markdown's version number and copyright information. |
1378 | |
1379 | |
1380 | =item B<-s>, B<--shortversion> |
1381 | |
1382 | Display the short-form version number. |
1383 | |
1384 | |
1385 | =back |
1386 | |
1387 | |
1388 | |
1389 | =head1 BUGS |
1390 | |
1391 | To file bug reports or feature requests (other than topics listed in the |
1392 | Caveats section above) please send email to: |
1393 | |
1394 | support@daringfireball.net |
1395 | |
1396 | Please include with your report: (1) the example input; (2) the output |
1397 | you expected; (3) the output Markdown actually produced. |
1398 | |
1399 | |
1400 | =head1 VERSION HISTORY |
1401 | |
1402 | See the readme file for detailed release notes for this version. |
1403 | |
1404 | 1.0.1 - 14 Dec 2004 |
1405 | |
1406 | 1.0 - 28 Aug 2004 |
1407 | |
1408 | |
1409 | =head1 AUTHOR |
1410 | |
1411 | John Gruber |
1412 | http://daringfireball.net |
1413 | |
1414 | PHP port and other contributions by Michel Fortin |
1415 | http://michelf.com |
1416 | |
1417 | |
1418 | =head1 COPYRIGHT AND LICENSE |
1419 | |
1420 | Copyright (c) 2003-2004 John Gruber |
1421 | <http://daringfireball.net/> |
1422 | All rights reserved. |
1423 | |
1424 | Redistribution and use in source and binary forms, with or without |
1425 | modification, are permitted provided that the following conditions are |
1426 | met: |
1427 | |
1428 | * Redistributions of source code must retain the above copyright notice, |
1429 | this list of conditions and the following disclaimer. |
1430 | |
1431 | * Redistributions in binary form must reproduce the above copyright |
1432 | notice, this list of conditions and the following disclaimer in the |
1433 | documentation and/or other materials provided with the distribution. |
1434 | |
1435 | * Neither the name "Markdown" nor the names of its contributors may |
1436 | be used to endorse or promote products derived from this software |
1437 | without specific prior written permission. |
1438 | |
1439 | This software is provided by the copyright holders and contributors "as |
1440 | is" and any express or implied warranties, including, but not limited |
1441 | to, the implied warranties of merchantability and fitness for a |
1442 | particular purpose are disclaimed. In no event shall the copyright owner |
1443 | or contributors be liable for any direct, indirect, incidental, special, |
1444 | exemplary, or consequential damages (including, but not limited to, |
1445 | procurement of substitute goods or services; loss of use, data, or |
1446 | profits; or business interruption) however caused and on any theory of |
1447 | liability, whether in contract, strict liability, or tort (including |
1448 | negligence or otherwise) arising in any way out of the use of this |
1449 | software, even if advised of the possibility of such damage. |
1450 | |
1451 | =cut |