##// END OF EJS Templates
Added target="_blank" to hyperlinks in Markdown.Converter.js so that clicking on a link opens it in a new window. Fixes Issue #1701 and adds surrounding quotes.
Roy Hyunjin Han -
Show More
@@ -1,1318 +1,1319
1 1 var Markdown;
2 2
3 3 if (typeof exports === "object" && typeof require === "function") // we're in a CommonJS (e.g. Node.js) module
4 4 Markdown = exports;
5 5 else
6 6 Markdown = {};
7 7
8 8 // The following text is included for historical reasons, but should
9 9 // be taken with a pinch of salt; it's not all true anymore.
10 10
11 11 //
12 12 // Wherever possible, Showdown is a straight, line-by-line port
13 13 // of the Perl version of Markdown.
14 14 //
15 15 // This is not a normal parser design; it's basically just a
16 16 // series of string substitutions. It's hard to read and
17 17 // maintain this way, but keeping Showdown close to the original
18 18 // design makes it easier to port new features.
19 19 //
20 20 // More importantly, Showdown behaves like markdown.pl in most
21 21 // edge cases. So web applications can do client-side preview
22 22 // in Javascript, and then build identical HTML on the server.
23 23 //
24 24 // This port needs the new RegExp functionality of ECMA 262,
25 25 // 3rd Edition (i.e. Javascript 1.5). Most modern web browsers
26 26 // should do fine. Even with the new regular expression features,
27 27 // We do a lot of work to emulate Perl's regex functionality.
28 28 // The tricky changes in this file mostly have the "attacklab:"
29 29 // label. Major or self-explanatory changes don't.
30 30 //
31 31 // Smart diff tools like Araxis Merge will be able to match up
32 32 // this file with markdown.pl in a useful way. A little tweaking
33 33 // helps: in a copy of markdown.pl, replace "#" with "//" and
34 34 // replace "$text" with "text". Be sure to ignore whitespace
35 35 // and line endings.
36 36 //
37 37
38 38
39 39 //
40 40 // Usage:
41 41 //
42 42 // var text = "Markdown *rocks*.";
43 43 //
44 44 // var converter = new Markdown.Converter();
45 45 // var html = converter.makeHtml(text);
46 46 //
47 47 // alert(html);
48 48 //
49 49 // Note: move the sample code to the bottom of this
50 50 // file before uncommenting it.
51 51 //
52 52
53 53 (function () {
54 54
55 55 function identity(x) { return x; }
56 56 function returnFalse(x) { return false; }
57 57
58 58 function HookCollection() { }
59 59
60 60 HookCollection.prototype = {
61 61
62 62 chain: function (hookname, func) {
63 63 var original = this[hookname];
64 64 if (!original)
65 65 throw new Error("unknown hook " + hookname);
66 66
67 67 if (original === identity)
68 68 this[hookname] = func;
69 69 else
70 70 this[hookname] = function (x) { return func(original(x)); }
71 71 },
72 72 set: function (hookname, func) {
73 73 if (!this[hookname])
74 74 throw new Error("unknown hook " + hookname);
75 75 this[hookname] = func;
76 76 },
77 77 addNoop: function (hookname) {
78 78 this[hookname] = identity;
79 79 },
80 80 addFalse: function (hookname) {
81 81 this[hookname] = returnFalse;
82 82 }
83 83 };
84 84
85 85 Markdown.HookCollection = HookCollection;
86 86
87 87 // g_urls and g_titles allow arbitrary user-entered strings as keys. This
88 88 // caused an exception (and hence stopped the rendering) when the user entered
89 89 // e.g. [push] or [__proto__]. Adding a prefix to the actual key prevents this
90 90 // (since no builtin property starts with "s_"). See
91 91 // http://meta.stackoverflow.com/questions/64655/strange-wmd-bug
92 92 // (granted, switching from Array() to Object() alone would have left only __proto__
93 93 // to be a problem)
94 94 function SaveHash() { }
95 95 SaveHash.prototype = {
96 96 set: function (key, value) {
97 97 this["s_" + key] = value;
98 98 },
99 99 get: function (key) {
100 100 return this["s_" + key];
101 101 }
102 102 };
103 103
104 104 Markdown.Converter = function () {
105 105 var pluginHooks = this.hooks = new HookCollection();
106 106 pluginHooks.addNoop("plainLinkText"); // given a URL that was encountered by itself (without markup), should return the link text that's to be given to this link
107 107 pluginHooks.addNoop("preConversion"); // called with the orignal text as given to makeHtml. The result of this plugin hook is the actual markdown source that will be cooked
108 108 pluginHooks.addNoop("postConversion"); // called with the final cooked HTML code. The result of this plugin hook is the actual output of makeHtml
109 109
110 110 //
111 111 // Private state of the converter instance:
112 112 //
113 113
114 114 // Global hashes, used by various utility routines
115 115 var g_urls;
116 116 var g_titles;
117 117 var g_html_blocks;
118 118
119 119 // Used to track when we're inside an ordered or unordered list
120 120 // (see _ProcessListItems() for details):
121 121 var g_list_level;
122 122
123 123 this.makeHtml = function (text) {
124 124
125 125 //
126 126 // Main function. The order in which other subs are called here is
127 127 // essential. Link and image substitutions need to happen before
128 128 // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
129 129 // and <img> tags get encoded.
130 130 //
131 131
132 132 // This will only happen if makeHtml on the same converter instance is called from a plugin hook.
133 133 // Don't do that.
134 134 if (g_urls)
135 135 throw new Error("Recursive call to converter.makeHtml");
136 136
137 137 // Create the private state objects.
138 138 g_urls = new SaveHash();
139 139 g_titles = new SaveHash();
140 140 g_html_blocks = [];
141 141 g_list_level = 0;
142 142
143 143 text = pluginHooks.preConversion(text);
144 144
145 145 // attacklab: Replace ~ with ~T
146 146 // This lets us use tilde as an escape char to avoid md5 hashes
147 147 // The choice of character is arbitray; anything that isn't
148 148 // magic in Markdown will work.
149 149 text = text.replace(/~/g, "~T");
150 150
151 151 // attacklab: Replace $ with ~D
152 152 // RegExp interprets $ as a special character
153 153 // when it's in a replacement string
154 154 text = text.replace(/\$/g, "~D");
155 155
156 156 // Standardize line endings
157 157 text = text.replace(/\r\n/g, "\n"); // DOS to Unix
158 158 text = text.replace(/\r/g, "\n"); // Mac to Unix
159 159
160 160 // Make sure text begins and ends with a couple of newlines:
161 161 text = "\n\n" + text + "\n\n";
162 162
163 163 // Convert all tabs to spaces.
164 164 text = _Detab(text);
165 165
166 166 // Strip any lines consisting only of spaces and tabs.
167 167 // This makes subsequent regexen easier to write, because we can
168 168 // match consecutive blank lines with /\n+/ instead of something
169 169 // contorted like /[ \t]*\n+/ .
170 170 text = text.replace(/^[ \t]+$/mg, "");
171 171
172 172 // Turn block-level HTML blocks into hash entries
173 173 text = _HashHTMLBlocks(text);
174 174
175 175 // Strip link definitions, store in hashes.
176 176 text = _StripLinkDefinitions(text);
177 177
178 178 text = _RunBlockGamut(text);
179 179
180 180 text = _UnescapeSpecialChars(text);
181 181
182 182 // attacklab: Restore dollar signs
183 183 text = text.replace(/~D/g, "$$");
184 184
185 185 // attacklab: Restore tildes
186 186 text = text.replace(/~T/g, "~");
187 187
188 188 text = pluginHooks.postConversion(text);
189 189
190 190 g_html_blocks = g_titles = g_urls = null;
191 191
192 192 return text;
193 193 };
194 194
195 195 function _StripLinkDefinitions(text) {
196 196 //
197 197 // Strips link definitions from text, stores the URLs and titles in
198 198 // hash references.
199 199 //
200 200
201 201 // Link defs are in the form: ^[id]: url "optional title"
202 202
203 203 /*
204 204 text = text.replace(/
205 205 ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1
206 206 [ \t]*
207 207 \n? // maybe *one* newline
208 208 [ \t]*
209 209 <?(\S+?)>? // url = $2
210 210 (?=\s|$) // lookahead for whitespace instead of the lookbehind removed below
211 211 [ \t]*
212 212 \n? // maybe one newline
213 213 [ \t]*
214 214 ( // (potential) title = $3
215 215 (\n*) // any lines skipped = $4 attacklab: lookbehind removed
216 216 [ \t]+
217 217 ["(]
218 218 (.+?) // title = $5
219 219 [")]
220 220 [ \t]*
221 221 )? // title is optional
222 222 (?:\n+|$)
223 223 /gm, function(){...});
224 224 */
225 225
226 226 text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm,
227 227 function (wholeMatch, m1, m2, m3, m4, m5) {
228 228 m1 = m1.toLowerCase();
229 229 g_urls.set(m1, _EncodeAmpsAndAngles(m2)); // Link IDs are case-insensitive
230 230 if (m4) {
231 231 // Oops, found blank lines, so it's not a title.
232 232 // Put back the parenthetical statement we stole.
233 233 return m3;
234 234 } else if (m5) {
235 235 g_titles.set(m1, m5.replace(/"/g, "&quot;"));
236 236 }
237 237
238 238 // Completely remove the definition from the text
239 239 return "";
240 240 }
241 241 );
242 242
243 243 return text;
244 244 }
245 245
246 246 function _HashHTMLBlocks(text) {
247 247
248 248 // Hashify HTML blocks:
249 249 // We only want to do this for block-level HTML tags, such as headers,
250 250 // lists, and tables. That's because we still want to wrap <p>s around
251 251 // "paragraphs" that are wrapped in non-block-level tags, such as anchors,
252 252 // phrase emphasis, and spans. The list of tags we're looking for is
253 253 // hard-coded:
254 254 var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
255 255 var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"
256 256
257 257 // First, look for nested blocks, e.g.:
258 258 // <div>
259 259 // <div>
260 260 // tags for inner block must be indented.
261 261 // </div>
262 262 // </div>
263 263 //
264 264 // The outermost tags must start at the left margin for this to match, and
265 265 // the inner nested divs must be indented.
266 266 // We need to do this before the next, more liberal match, because the next
267 267 // match will start at the first `<div>` and stop at the first `</div>`.
268 268
269 269 // attacklab: This regex can be expensive when it fails.
270 270
271 271 /*
272 272 text = text.replace(/
273 273 ( // save in $1
274 274 ^ // start of line (with /m)
275 275 <($block_tags_a) // start tag = $2
276 276 \b // word break
277 277 // attacklab: hack around khtml/pcre bug...
278 278 [^\r]*?\n // any number of lines, minimally matching
279 279 </\2> // the matching end tag
280 280 [ \t]* // trailing spaces/tabs
281 281 (?=\n+) // followed by a newline
282 282 ) // attacklab: there are sentinel newlines at end of document
283 283 /gm,function(){...}};
284 284 */
285 285 text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm, hashElement);
286 286
287 287 //
288 288 // Now match more liberally, simply from `\n<tag>` to `</tag>\n`
289 289 //
290 290
291 291 /*
292 292 text = text.replace(/
293 293 ( // save in $1
294 294 ^ // start of line (with /m)
295 295 <($block_tags_b) // start tag = $2
296 296 \b // word break
297 297 // attacklab: hack around khtml/pcre bug...
298 298 [^\r]*? // any number of lines, minimally matching
299 299 .*</\2> // the matching end tag
300 300 [ \t]* // trailing spaces/tabs
301 301 (?=\n+) // followed by a newline
302 302 ) // attacklab: there are sentinel newlines at end of document
303 303 /gm,function(){...}};
304 304 */
305 305 text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm, hashElement);
306 306
307 307 // Special case just for <hr />. It was easier to make a special case than
308 308 // to make the other regex more complicated.
309 309
310 310 /*
311 311 text = text.replace(/
312 312 \n // Starting after a blank line
313 313 [ ]{0,3}
314 314 ( // save in $1
315 315 (<(hr) // start tag = $2
316 316 \b // word break
317 317 ([^<>])*?
318 318 \/?>) // the matching end tag
319 319 [ \t]*
320 320 (?=\n{2,}) // followed by a blank line
321 321 )
322 322 /g,hashElement);
323 323 */
324 324 text = text.replace(/\n[ ]{0,3}((<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, hashElement);
325 325
326 326 // Special case for standalone HTML comments:
327 327
328 328 /*
329 329 text = text.replace(/
330 330 \n\n // Starting after a blank line
331 331 [ ]{0,3} // attacklab: g_tab_width - 1
332 332 ( // save in $1
333 333 <!
334 334 (--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--) // see http://www.w3.org/TR/html-markup/syntax.html#comments and http://meta.stackoverflow.com/q/95256
335 335 >
336 336 [ \t]*
337 337 (?=\n{2,}) // followed by a blank line
338 338 )
339 339 /g,hashElement);
340 340 */
341 341 text = text.replace(/\n\n[ ]{0,3}(<!(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>[ \t]*(?=\n{2,}))/g, hashElement);
342 342
343 343 // PHP and ASP-style processor instructions (<?...?> and <%...%>)
344 344
345 345 /*
346 346 text = text.replace(/
347 347 (?:
348 348 \n\n // Starting after a blank line
349 349 )
350 350 ( // save in $1
351 351 [ ]{0,3} // attacklab: g_tab_width - 1
352 352 (?:
353 353 <([?%]) // $2
354 354 [^\r]*?
355 355 \2>
356 356 )
357 357 [ \t]*
358 358 (?=\n{2,}) // followed by a blank line
359 359 )
360 360 /g,hashElement);
361 361 */
362 362 text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, hashElement);
363 363
364 364 return text;
365 365 }
366 366
367 367 function hashElement(wholeMatch, m1) {
368 368 var blockText = m1;
369 369
370 370 // Undo double lines
371 371 blockText = blockText.replace(/^\n+/, "");
372 372
373 373 // strip trailing blank lines
374 374 blockText = blockText.replace(/\n+$/g, "");
375 375
376 376 // Replace the element text with a marker ("~KxK" where x is its key)
377 377 blockText = "\n\n~K" + (g_html_blocks.push(blockText) - 1) + "K\n\n";
378 378
379 379 return blockText;
380 380 }
381 381
382 382 function _RunBlockGamut(text, doNotUnhash) {
383 383 //
384 384 // These are all the transformations that form block-level
385 385 // tags like paragraphs, headers, and list items.
386 386 //
387 387 text = _DoHeaders(text);
388 388
389 389 // Do Horizontal Rules:
390 390 var replacement = "<hr />\n";
391 391 text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, replacement);
392 392 text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm, replacement);
393 393 text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, replacement);
394 394
395 395 text = _DoLists(text);
396 396 text = _DoCodeBlocks(text);
397 397 text = _DoBlockQuotes(text);
398 398
399 399 // We already ran _HashHTMLBlocks() before, in Markdown(), but that
400 400 // was to escape raw HTML in the original Markdown source. This time,
401 401 // we're escaping the markup we've just created, so that we don't wrap
402 402 // <p> tags around block-level tags.
403 403 text = _HashHTMLBlocks(text);
404 404 text = _FormParagraphs(text, doNotUnhash);
405 405
406 406 return text;
407 407 }
408 408
409 409 function _RunSpanGamut(text) {
410 410 //
411 411 // These are all the transformations that occur *within* block-level
412 412 // tags like paragraphs, headers, and list items.
413 413 //
414 414
415 415 text = _DoCodeSpans(text);
416 416 text = _EscapeSpecialCharsWithinTagAttributes(text);
417 417 text = _EncodeBackslashEscapes(text);
418 418
419 419 // Process anchor and image tags. Images must come first,
420 420 // because ![foo][f] looks like an anchor.
421 421 text = _DoImages(text);
422 422 text = _DoAnchors(text);
423 423
424 424 // Make links out of things like `<http://example.com/>`
425 425 // Must come after _DoAnchors(), because you can use < and >
426 426 // delimiters in inline links like [this](<url>).
427 427 text = _DoAutoLinks(text);
428 428 text = _EncodeAmpsAndAngles(text);
429 429 text = _DoItalicsAndBold(text);
430 430
431 431 // Do hard breaks:
432 432 text = text.replace(/ +\n/g, " <br>\n");
433 433
434 434 return text;
435 435 }
436 436
437 437 function _EscapeSpecialCharsWithinTagAttributes(text) {
438 438 //
439 439 // Within tags -- meaning between < and > -- encode [\ ` * _] so they
440 440 // don't conflict with their use in Markdown for code, italics and strong.
441 441 //
442 442
443 443 // Build a regex to find HTML tags and comments. See Friedl's
444 444 // "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
445 445
446 446 // SE: changed the comment part of the regex
447 447
448 448 var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>)/gi;
449 449
450 450 text = text.replace(regex, function (wholeMatch) {
451 451 var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, "$1`");
452 452 tag = escapeCharacters(tag, wholeMatch.charAt(1) == "!" ? "\\`*_/" : "\\`*_"); // also escape slashes in comments to prevent autolinking there -- http://meta.stackoverflow.com/questions/95987
453 453 return tag;
454 454 });
455 455
456 456 return text;
457 457 }
458 458
459 459 function _DoAnchors(text) {
460 460 //
461 461 // Turn Markdown link shortcuts into XHTML <a> tags.
462 462 //
463 463 //
464 464 // First, handle reference-style links: [link text] [id]
465 465 //
466 466
467 467 /*
468 468 text = text.replace(/
469 469 ( // wrap whole match in $1
470 470 \[
471 471 (
472 472 (?:
473 473 \[[^\]]*\] // allow brackets nested one level
474 474 |
475 475 [^\[] // or anything else
476 476 )*
477 477 )
478 478 \]
479 479
480 480 [ ]? // one optional space
481 481 (?:\n[ ]*)? // one optional newline followed by spaces
482 482
483 483 \[
484 484 (.*?) // id = $3
485 485 \]
486 486 )
487 487 ()()()() // pad remaining backreferences
488 488 /g, writeAnchorTag);
489 489 */
490 490 text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag);
491 491
492 492 //
493 493 // Next, inline-style links: [link text](url "optional title")
494 494 //
495 495
496 496 /*
497 497 text = text.replace(/
498 498 ( // wrap whole match in $1
499 499 \[
500 500 (
501 501 (?:
502 502 \[[^\]]*\] // allow brackets nested one level
503 503 |
504 504 [^\[\]] // or anything else
505 505 )*
506 506 )
507 507 \]
508 508 \( // literal paren
509 509 [ \t]*
510 510 () // no id, so leave $3 empty
511 511 <?( // href = $4
512 512 (?:
513 513 \([^)]*\) // allow one level of (correctly nested) parens (think MSDN)
514 514 |
515 515 [^()]
516 516 )*?
517 517 )>?
518 518 [ \t]*
519 519 ( // $5
520 520 (['"]) // quote char = $6
521 521 (.*?) // Title = $7
522 522 \6 // matching quote
523 523 [ \t]* // ignore any spaces/tabs between closing quote and )
524 524 )? // title is optional
525 525 \)
526 526 )
527 527 /g, writeAnchorTag);
528 528 */
529 529
530 530 text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?((?:\([^)]*\)|[^()])*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag);
531 531
532 532 //
533 533 // Last, handle reference-style shortcuts: [link text]
534 534 // These must come last in case you've also got [link test][1]
535 535 // or [link test](/foo)
536 536 //
537 537
538 538 /*
539 539 text = text.replace(/
540 540 ( // wrap whole match in $1
541 541 \[
542 542 ([^\[\]]+) // link text = $2; can't contain '[' or ']'
543 543 \]
544 544 )
545 545 ()()()()() // pad rest of backreferences
546 546 /g, writeAnchorTag);
547 547 */
548 548 text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
549 549
550 550 return text;
551 551 }
552 552
553 553 function writeAnchorTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
554 554 if (m7 == undefined) m7 = "";
555 555 var whole_match = m1;
556 556 var link_text = m2;
557 557 var link_id = m3.toLowerCase();
558 558 var url = m4;
559 559 var title = m7;
560 560
561 561 if (url == "") {
562 562 if (link_id == "") {
563 563 // lower-case and turn embedded newlines into spaces
564 564 link_id = link_text.toLowerCase().replace(/ ?\n/g, " ");
565 565 }
566 566 url = "#" + link_id;
567 567
568 568 if (g_urls.get(link_id) != undefined) {
569 569 url = g_urls.get(link_id);
570 570 if (g_titles.get(link_id) != undefined) {
571 571 title = g_titles.get(link_id);
572 572 }
573 573 }
574 574 else {
575 575 if (whole_match.search(/\(\s*\)$/m) > -1) {
576 576 // Special case for explicit empty url
577 577 url = "";
578 578 } else {
579 579 return whole_match;
580 580 }
581 581 }
582 582 }
583 583 url = encodeProblemUrlChars(url);
584 584 url = escapeCharacters(url, "*_");
585 585 var result = "<a href=\"" + url + "\"";
586 586
587 587 if (title != "") {
588 588 title = title.replace(/"/g, "&quot;");
589 589 title = escapeCharacters(title, "*_");
590 590 result += " title=\"" + title + "\"";
591 591 }
592 592
593 result += ">" + link_text + "</a>";
593 result += " target=\"_blank\">" + link_text + "</a>";
594 594
595 595 return result;
596 596 }
597 597
598 598 function _DoImages(text) {
599 599 //
600 600 // Turn Markdown image shortcuts into <img> tags.
601 601 //
602 602
603 603 //
604 604 // First, handle reference-style labeled images: ![alt text][id]
605 605 //
606 606
607 607 /*
608 608 text = text.replace(/
609 609 ( // wrap whole match in $1
610 610 !\[
611 611 (.*?) // alt text = $2
612 612 \]
613 613
614 614 [ ]? // one optional space
615 615 (?:\n[ ]*)? // one optional newline followed by spaces
616 616
617 617 \[
618 618 (.*?) // id = $3
619 619 \]
620 620 )
621 621 ()()()() // pad rest of backreferences
622 622 /g, writeImageTag);
623 623 */
624 624 text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag);
625 625
626 626 //
627 627 // Next, handle inline images: ![alt text](url "optional title")
628 628 // Don't forget: encode * and _
629 629
630 630 /*
631 631 text = text.replace(/
632 632 ( // wrap whole match in $1
633 633 !\[
634 634 (.*?) // alt text = $2
635 635 \]
636 636 \s? // One optional whitespace character
637 637 \( // literal paren
638 638 [ \t]*
639 639 () // no id, so leave $3 empty
640 640 <?(\S+?)>? // src url = $4
641 641 [ \t]*
642 642 ( // $5
643 643 (['"]) // quote char = $6
644 644 (.*?) // title = $7
645 645 \6 // matching quote
646 646 [ \t]*
647 647 )? // title is optional
648 648 \)
649 649 )
650 650 /g, writeImageTag);
651 651 */
652 652 text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag);
653 653
654 654 return text;
655 655 }
656 656
657 657 function writeImageTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
658 658 var whole_match = m1;
659 659 var alt_text = m2;
660 660 var link_id = m3.toLowerCase();
661 661 var url = m4;
662 662 var title = m7;
663 663
664 664 if (!title) title = "";
665 665
666 666 if (url == "") {
667 667 if (link_id == "") {
668 668 // lower-case and turn embedded newlines into spaces
669 669 link_id = alt_text.toLowerCase().replace(/ ?\n/g, " ");
670 670 }
671 671 url = "#" + link_id;
672 672
673 673 if (g_urls.get(link_id) != undefined) {
674 674 url = g_urls.get(link_id);
675 675 if (g_titles.get(link_id) != undefined) {
676 676 title = g_titles.get(link_id);
677 677 }
678 678 }
679 679 else {
680 680 return whole_match;
681 681 }
682 682 }
683 683
684 684 alt_text = alt_text.replace(/"/g, "&quot;");
685 685 url = escapeCharacters(url, "*_");
686 686 var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";
687 687
688 688 // attacklab: Markdown.pl adds empty title attributes to images.
689 689 // Replicate this bug.
690 690
691 691 //if (title != "") {
692 692 title = title.replace(/"/g, "&quot;");
693 693 title = escapeCharacters(title, "*_");
694 694 result += " title=\"" + title + "\"";
695 695 //}
696 696
697 697 result += " />";
698 698
699 699 return result;
700 700 }
701 701
702 702 function _DoHeaders(text) {
703 703
704 704 // Setext-style headers:
705 705 // Header 1
706 706 // ========
707 707 //
708 708 // Header 2
709 709 // --------
710 710 //
711 711 text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
712 712 function (wholeMatch, m1) { return "<h1>" + _RunSpanGamut(m1) + "</h1>\n\n"; }
713 713 );
714 714
715 715 text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
716 716 function (matchFound, m1) { return "<h2>" + _RunSpanGamut(m1) + "</h2>\n\n"; }
717 717 );
718 718
719 719 // atx-style headers:
720 720 // # Header 1
721 721 // ## Header 2
722 722 // ## Header 2 with closing hashes ##
723 723 // ...
724 724 // ###### Header 6
725 725 //
726 726
727 727 /*
728 728 text = text.replace(/
729 729 ^(\#{1,6}) // $1 = string of #'s
730 730 [ \t]*
731 731 (.+?) // $2 = Header text
732 732 [ \t]*
733 733 \#* // optional closing #'s (not counted)
734 734 \n+
735 735 /gm, function() {...});
736 736 */
737 737
738 738 text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
739 739 function (wholeMatch, m1, m2) {
740 740 var h_level = m1.length;
741 741 return "<h" + h_level + ">" + _RunSpanGamut(m2) + "</h" + h_level + ">\n\n";
742 742 }
743 743 );
744 744
745 745 return text;
746 746 }
747 747
748 748 function _DoLists(text) {
749 749 //
750 750 // Form HTML ordered (numbered) and unordered (bulleted) lists.
751 751 //
752 752
753 753 // attacklab: add sentinel to hack around khtml/safari bug:
754 754 // http://bugs.webkit.org/show_bug.cgi?id=11231
755 755 text += "~0";
756 756
757 757 // Re-usable pattern to match any entirel ul or ol list:
758 758
759 759 /*
760 760 var whole_list = /
761 761 ( // $1 = whole list
762 762 ( // $2
763 763 [ ]{0,3} // attacklab: g_tab_width - 1
764 764 ([*+-]|\d+[.]) // $3 = first list item marker
765 765 [ \t]+
766 766 )
767 767 [^\r]+?
768 768 ( // $4
769 769 ~0 // sentinel for workaround; should be $
770 770 |
771 771 \n{2,}
772 772 (?=\S)
773 773 (?! // Negative lookahead for another list item marker
774 774 [ \t]*
775 775 (?:[*+-]|\d+[.])[ \t]+
776 776 )
777 777 )
778 778 )
779 779 /g
780 780 */
781 781 var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
782 782
783 783 if (g_list_level) {
784 784 text = text.replace(whole_list, function (wholeMatch, m1, m2) {
785 785 var list = m1;
786 786 var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol";
787 787
788 788 var result = _ProcessListItems(list, list_type);
789 789
790 790 // Trim any trailing whitespace, to put the closing `</$list_type>`
791 791 // up on the preceding line, to get it past the current stupid
792 792 // HTML block parser. This is a hack to work around the terrible
793 793 // hack that is the HTML block parser.
794 794 result = result.replace(/\s+$/, "");
795 795 result = "<" + list_type + ">" + result + "</" + list_type + ">\n";
796 796 return result;
797 797 });
798 798 } else {
799 799 whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
800 800 text = text.replace(whole_list, function (wholeMatch, m1, m2, m3) {
801 801 var runup = m1;
802 802 var list = m2;
803 803
804 804 var list_type = (m3.search(/[*+-]/g) > -1) ? "ul" : "ol";
805 805 var result = _ProcessListItems(list, list_type);
806 806 result = runup + "<" + list_type + ">\n" + result + "</" + list_type + ">\n";
807 807 return result;
808 808 });
809 809 }
810 810
811 811 // attacklab: strip sentinel
812 812 text = text.replace(/~0/, "");
813 813
814 814 return text;
815 815 }
816 816
817 817 var _listItemMarkers = { ol: "\\d+[.]", ul: "[*+-]" };
818 818
819 819 function _ProcessListItems(list_str, list_type) {
820 820 //
821 821 // Process the contents of a single ordered or unordered list, splitting it
822 822 // into individual list items.
823 823 //
824 824 // list_type is either "ul" or "ol".
825 825
826 826 // The $g_list_level global keeps track of when we're inside a list.
827 827 // Each time we enter a list, we increment it; when we leave a list,
828 828 // we decrement. If it's zero, we're not in a list anymore.
829 829 //
830 830 // We do this because when we're not inside a list, we want to treat
831 831 // something like this:
832 832 //
833 833 // I recommend upgrading to version
834 834 // 8. Oops, now this line is treated
835 835 // as a sub-list.
836 836 //
837 837 // As a single paragraph, despite the fact that the second line starts
838 838 // with a digit-period-space sequence.
839 839 //
840 840 // Whereas when we're inside a list (or sub-list), that line will be
841 841 // treated as the start of a sub-list. What a kludge, huh? This is
842 842 // an aspect of Markdown's syntax that's hard to parse perfectly
843 843 // without resorting to mind-reading. Perhaps the solution is to
844 844 // change the syntax rules such that sub-lists must start with a
845 845 // starting cardinal number; e.g. "1." or "a.".
846 846
847 847 g_list_level++;
848 848
849 849 // trim trailing blank lines:
850 850 list_str = list_str.replace(/\n{2,}$/, "\n");
851 851
852 852 // attacklab: add sentinel to emulate \z
853 853 list_str += "~0";
854 854
855 855 // In the original attacklab showdown, list_type was not given to this function, and anything
856 856 // that matched /[*+-]|\d+[.]/ would just create the next <li>, causing this mismatch:
857 857 //
858 858 // Markdown rendered by WMD rendered by MarkdownSharp
859 859 // ------------------------------------------------------------------
860 860 // 1. first 1. first 1. first
861 861 // 2. second 2. second 2. second
862 862 // - third 3. third * third
863 863 //
864 864 // We changed this to behave identical to MarkdownSharp. This is the constructed RegEx,
865 865 // with {MARKER} being one of \d+[.] or [*+-], depending on list_type:
866 866
867 867 /*
868 868 list_str = list_str.replace(/
869 869 (^[ \t]*) // leading whitespace = $1
870 870 ({MARKER}) [ \t]+ // list marker = $2
871 871 ([^\r]+? // list item text = $3
872 872 (\n+)
873 873 )
874 874 (?=
875 875 (~0 | \2 ({MARKER}) [ \t]+)
876 876 )
877 877 /gm, function(){...});
878 878 */
879 879
880 880 var marker = _listItemMarkers[list_type];
881 881 var re = new RegExp("(^[ \\t]*)(" + marker + ")[ \\t]+([^\\r]+?(\\n+))(?=(~0|\\1(" + marker + ")[ \\t]+))", "gm");
882 882 var last_item_had_a_double_newline = false;
883 883 list_str = list_str.replace(re,
884 884 function (wholeMatch, m1, m2, m3) {
885 885 var item = m3;
886 886 var leading_space = m1;
887 887 var ends_with_double_newline = /\n\n$/.test(item);
888 888 var contains_double_newline = ends_with_double_newline || item.search(/\n{2,}/) > -1;
889 889
890 890 if (contains_double_newline || last_item_had_a_double_newline) {
891 891 item = _RunBlockGamut(_Outdent(item), /* doNotUnhash = */true);
892 892 }
893 893 else {
894 894 // Recursion for sub-lists:
895 895 item = _DoLists(_Outdent(item));
896 896 item = item.replace(/\n$/, ""); // chomp(item)
897 897 item = _RunSpanGamut(item);
898 898 }
899 899 last_item_had_a_double_newline = ends_with_double_newline;
900 900 return "<li>" + item + "</li>\n";
901 901 }
902 902 );
903 903
904 904 // attacklab: strip sentinel
905 905 list_str = list_str.replace(/~0/g, "");
906 906
907 907 g_list_level--;
908 908 return list_str;
909 909 }
910 910
911 911 function _DoCodeBlocks(text) {
912 912 //
913 913 // Process Markdown `<pre><code>` blocks.
914 914 //
915 915
916 916 /*
917 917 text = text.replace(/
918 918 (?:\n\n|^)
919 919 ( // $1 = the code block -- one or more lines, starting with a space/tab
920 920 (?:
921 921 (?:[ ]{4}|\t) // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
922 922 .*\n+
923 923 )+
924 924 )
925 925 (\n*[ ]{0,3}[^ \t\n]|(?=~0)) // attacklab: g_tab_width
926 926 /g ,function(){...});
927 927 */
928 928
929 929 // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
930 930 text += "~0";
931 931
932 932 text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
933 933 function (wholeMatch, m1, m2) {
934 934 var codeblock = m1;
935 935 var nextChar = m2;
936 936
937 937 codeblock = _EncodeCode(_Outdent(codeblock));
938 938 codeblock = _Detab(codeblock);
939 939 codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines
940 940 codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace
941 941
942 942 codeblock = "<pre><code>" + codeblock + "\n</code></pre>";
943 943
944 944 return "\n\n" + codeblock + "\n\n" + nextChar;
945 945 }
946 946 );
947 947
948 948 // attacklab: strip sentinel
949 949 text = text.replace(/~0/, "");
950 950
951 951 return text;
952 952 }
953 953
954 954 function hashBlock(text) {
955 955 text = text.replace(/(^\n+|\n+$)/g, "");
956 956 return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n";
957 957 }
958 958
959 959 function _DoCodeSpans(text) {
960 960 //
961 961 // * Backtick quotes are used for <code></code> spans.
962 962 //
963 963 // * You can use multiple backticks as the delimiters if you want to
964 964 // include literal backticks in the code span. So, this input:
965 965 //
966 966 // Just type ``foo `bar` baz`` at the prompt.
967 967 //
968 968 // Will translate to:
969 969 //
970 970 // <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
971 971 //
972 972 // There's no arbitrary limit to the number of backticks you
973 973 // can use as delimters. If you need three consecutive backticks
974 974 // in your code, use four for delimiters, etc.
975 975 //
976 976 // * You can use spaces to get literal backticks at the edges:
977 977 //
978 978 // ... type `` `bar` `` ...
979 979 //
980 980 // Turns to:
981 981 //
982 982 // ... type <code>`bar`</code> ...
983 983 //
984 984
985 985 /*
986 986 text = text.replace(/
987 987 (^|[^\\]) // Character before opening ` can't be a backslash
988 988 (`+) // $2 = Opening run of `
989 989 ( // $3 = The code block
990 990 [^\r]*?
991 991 [^`] // attacklab: work around lack of lookbehind
992 992 )
993 993 \2 // Matching closer
994 994 (?!`)
995 995 /gm, function(){...});
996 996 */
997 997
998 998 text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
999 999 function (wholeMatch, m1, m2, m3, m4) {
1000 1000 var c = m3;
1001 1001 c = c.replace(/^([ \t]*)/g, ""); // leading whitespace
1002 1002 c = c.replace(/[ \t]*$/g, ""); // trailing whitespace
1003 1003 c = _EncodeCode(c);
1004 1004 return m1 + "<code>" + c + "</code>";
1005 1005 }
1006 1006 );
1007 1007
1008 1008 return text;
1009 1009 }
1010 1010
1011 1011 function _EncodeCode(text) {
1012 1012 //
1013 1013 // Encode/escape certain characters inside Markdown code runs.
1014 1014 // The point is that in code, these characters are literals,
1015 1015 // and lose their special Markdown meanings.
1016 1016 //
1017 1017 // Encode all ampersands; HTML entities are not
1018 1018 // entities within a Markdown code span.
1019 1019 text = text.replace(/&/g, "&amp;");
1020 1020
1021 1021 // Do the angle bracket song and dance:
1022 1022 text = text.replace(/</g, "&lt;");
1023 1023 text = text.replace(/>/g, "&gt;");
1024 1024
1025 1025 // Now, escape characters that are magic in Markdown:
1026 1026 text = escapeCharacters(text, "\*_{}[]\\", false);
1027 1027
1028 1028 // jj the line above breaks this:
1029 1029 //---
1030 1030
1031 1031 //* Item
1032 1032
1033 1033 // 1. Subitem
1034 1034
1035 1035 // special char: *
1036 1036 //---
1037 1037
1038 1038 return text;
1039 1039 }
1040 1040
1041 1041 function _DoItalicsAndBold(text) {
1042 1042
1043 1043 // <strong> must go first:
1044 1044 text = text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g,
1045 1045 "$1<strong>$3</strong>$4");
1046 1046
1047 1047 text = text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g,
1048 1048 "$1<em>$3</em>$4");
1049 1049
1050 1050 return text;
1051 1051 }
1052 1052
1053 1053 function _DoBlockQuotes(text) {
1054 1054
1055 1055 /*
1056 1056 text = text.replace(/
1057 1057 ( // Wrap whole match in $1
1058 1058 (
1059 1059 ^[ \t]*>[ \t]? // '>' at the start of a line
1060 1060 .+\n // rest of the first line
1061 1061 (.+\n)* // subsequent consecutive lines
1062 1062 \n* // blanks
1063 1063 )+
1064 1064 )
1065 1065 /gm, function(){...});
1066 1066 */
1067 1067
1068 1068 text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
1069 1069 function (wholeMatch, m1) {
1070 1070 var bq = m1;
1071 1071
1072 1072 // attacklab: hack around Konqueror 3.5.4 bug:
1073 1073 // "----------bug".replace(/^-/g,"") == "bug"
1074 1074
1075 1075 bq = bq.replace(/^[ \t]*>[ \t]?/gm, "~0"); // trim one level of quoting
1076 1076
1077 1077 // attacklab: clean up hack
1078 1078 bq = bq.replace(/~0/g, "");
1079 1079
1080 1080 bq = bq.replace(/^[ \t]+$/gm, ""); // trim whitespace-only lines
1081 1081 bq = _RunBlockGamut(bq); // recurse
1082 1082
1083 1083 bq = bq.replace(/(^|\n)/g, "$1 ");
1084 1084 // These leading spaces screw with <pre> content, so we need to fix that:
1085 1085 bq = bq.replace(
1086 1086 /(\s*<pre>[^\r]+?<\/pre>)/gm,
1087 1087 function (wholeMatch, m1) {
1088 1088 var pre = m1;
1089 1089 // attacklab: hack around Konqueror 3.5.4 bug:
1090 1090 pre = pre.replace(/^ /mg, "~0");
1091 1091 pre = pre.replace(/~0/g, "");
1092 1092 return pre;
1093 1093 });
1094 1094
1095 1095 return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");
1096 1096 }
1097 1097 );
1098 1098 return text;
1099 1099 }
1100 1100
1101 1101 function _FormParagraphs(text, doNotUnhash) {
1102 1102 //
1103 1103 // Params:
1104 1104 // $text - string to process with html <p> tags
1105 1105 //
1106 1106
1107 1107 // Strip leading and trailing lines:
1108 1108 text = text.replace(/^\n+/g, "");
1109 1109 text = text.replace(/\n+$/g, "");
1110 1110
1111 1111 var grafs = text.split(/\n{2,}/g);
1112 1112 var grafsOut = [];
1113 1113
1114 1114 //
1115 1115 // Wrap <p> tags.
1116 1116 //
1117 1117 var end = grafs.length;
1118 1118 for (var i = 0; i < end; i++) {
1119 1119 var str = grafs[i];
1120 1120
1121 1121 // if this is an HTML marker, copy it
1122 1122 if (str.search(/~K(\d+)K/g) >= 0) {
1123 1123 grafsOut.push(str);
1124 1124 }
1125 1125 else if (str.search(/\S/) >= 0) {
1126 1126 str = _RunSpanGamut(str);
1127 1127 str = str.replace(/^([ \t]*)/g, "<p>");
1128 1128 str += "</p>"
1129 1129 grafsOut.push(str);
1130 1130 }
1131 1131
1132 1132 }
1133 1133 //
1134 1134 // Unhashify HTML blocks
1135 1135 //
1136 1136 if (!doNotUnhash) {
1137 1137 end = grafsOut.length;
1138 1138 for (var i = 0; i < end; i++) {
1139 1139 // if this is a marker for an html block...
1140 1140 while (grafsOut[i].search(/~K(\d+)K/) >= 0) {
1141 1141 var blockText = g_html_blocks[RegExp.$1];
1142 1142 blockText = blockText.replace(/\$/g, "$$$$"); // Escape any dollar signs
1143 1143 grafsOut[i] = grafsOut[i].replace(/~K\d+K/, blockText);
1144 1144 }
1145 1145 }
1146 1146 }
1147 1147 return grafsOut.join("\n\n");
1148 1148 }
1149 1149
1150 1150 function _EncodeAmpsAndAngles(text) {
1151 1151 // Smart processing for ampersands and angle brackets that need to be encoded.
1152 1152
1153 1153 // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
1154 1154 // http://bumppo.net/projects/amputator/
1155 1155 text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&amp;");
1156 1156
1157 1157 // Encode naked <'s
1158 1158 text = text.replace(/<(?![a-z\/?\$!])/gi, "&lt;");
1159 1159
1160 1160 return text;
1161 1161 }
1162 1162
1163 1163 function _EncodeBackslashEscapes(text) {
1164 1164 //
1165 1165 // Parameter: String.
1166 1166 // Returns: The string, with after processing the following backslash
1167 1167 // escape sequences.
1168 1168 //
1169 1169
1170 1170 // attacklab: The polite way to do this is with the new
1171 1171 // escapeCharacters() function:
1172 1172 //
1173 1173 // text = escapeCharacters(text,"\\",true);
1174 1174 // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
1175 1175 //
1176 1176 // ...but we're sidestepping its use of the (slow) RegExp constructor
1177 1177 // as an optimization for Firefox. This function gets called a LOT.
1178 1178
1179 1179 text = text.replace(/\\(\\)/g, escapeCharacters_callback);
1180 1180 text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback);
1181 1181 return text;
1182 1182 }
1183 1183
1184 1184 function _DoAutoLinks(text) {
1185 1185
1186 // note that at this point, all other URL in the text are already hyperlinked as <a href=""></a>
1186 // note that at this point, all other URL in the text are already
1187 // hyperlinked as <a href="" target="_blank"></a>
1187 1188 // *except* for the <http://www.foo.com> case
1188 1189
1189 1190 // automatically add < and > around unadorned raw hyperlinks
1190 1191 // must be preceded by space/BOF and followed by non-word/EOF character
1191 1192 text = text.replace(/(^|\s)(https?|ftp)(:\/\/[-A-Z0-9+&@#\/%?=~_|\[\]\(\)!:,\.;]*[-A-Z0-9+&@#\/%=~_|\[\]])($|\W)/gi, "$1<$2$3>$4");
1192 1193
1193 1194 // autolink anything like <http://example.com>
1194 1195
1195 var replacer = function (wholematch, m1) { return "<a href=\"" + m1 + "\">" + pluginHooks.plainLinkText(m1) + "</a>"; }
1196 var replacer = function (wholematch, m1) { return "<a href=\"" + m1 + "\" target=\"_blank\"">" + pluginHooks.plainLinkText(m1) + "</a>"; }
1196 1197 text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, replacer);
1197 1198
1198 1199 // Email addresses: <address@domain.foo>
1199 1200 /*
1200 1201 text = text.replace(/
1201 1202 <
1202 1203 (?:mailto:)?
1203 1204 (
1204 1205 [-.\w]+
1205 1206 \@
1206 1207 [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
1207 1208 )
1208 1209 >
1209 1210 /gi, _DoAutoLinks_callback());
1210 1211 */
1211 1212
1212 1213 /* disabling email autolinking, since we don't do that on the server, either
1213 1214 text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
1214 1215 function(wholeMatch,m1) {
1215 1216 return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );
1216 1217 }
1217 1218 );
1218 1219 */
1219 1220 return text;
1220 1221 }
1221 1222
1222 1223 function _UnescapeSpecialChars(text) {
1223 1224 //
1224 1225 // Swap back in all the special characters we've hidden.
1225 1226 //
1226 1227 text = text.replace(/~E(\d+)E/g,
1227 1228 function (wholeMatch, m1) {
1228 1229 var charCodeToReplace = parseInt(m1);
1229 1230 return String.fromCharCode(charCodeToReplace);
1230 1231 }
1231 1232 );
1232 1233 return text;
1233 1234 }
1234 1235
1235 1236 function _Outdent(text) {
1236 1237 //
1237 1238 // Remove one level of line-leading tabs or spaces
1238 1239 //
1239 1240
1240 1241 // attacklab: hack around Konqueror 3.5.4 bug:
1241 1242 // "----------bug".replace(/^-/g,"") == "bug"
1242 1243
1243 1244 text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width
1244 1245
1245 1246 // attacklab: clean up hack
1246 1247 text = text.replace(/~0/g, "")
1247 1248
1248 1249 return text;
1249 1250 }
1250 1251
1251 1252 function _Detab(text) {
1252 1253 if (!/\t/.test(text))
1253 1254 return text;
1254 1255
1255 1256 var spaces = [" ", " ", " ", " "],
1256 1257 skew = 0,
1257 1258 v;
1258 1259
1259 1260 return text.replace(/[\n\t]/g, function (match, offset) {
1260 1261 if (match === "\n") {
1261 1262 skew = offset + 1;
1262 1263 return match;
1263 1264 }
1264 1265 v = (offset - skew) % 4;
1265 1266 skew = offset + 1;
1266 1267 return spaces[v];
1267 1268 });
1268 1269 }
1269 1270
1270 1271 //
1271 1272 // attacklab: Utility functions
1272 1273 //
1273 1274
1274 1275 var _problemUrlChars = /(?:["'*()[\]:]|~D)/g;
1275 1276
1276 1277 // hex-encodes some unusual "problem" chars in URLs to avoid URL detection problems
1277 1278 function encodeProblemUrlChars(url) {
1278 1279 if (!url)
1279 1280 return "";
1280 1281
1281 1282 var len = url.length;
1282 1283
1283 1284 return url.replace(_problemUrlChars, function (match, offset) {
1284 1285 if (match == "~D") // escape for dollar
1285 1286 return "%24";
1286 1287 if (match == ":") {
1287 1288 if (offset == len - 1 || /[0-9\/]/.test(url.charAt(offset + 1)))
1288 1289 return ":"
1289 1290 }
1290 1291 return "%" + match.charCodeAt(0).toString(16);
1291 1292 });
1292 1293 }
1293 1294
1294 1295
1295 1296 function escapeCharacters(text, charsToEscape, afterBackslash) {
1296 1297 // First we have to escape the escape characters so that
1297 1298 // we can build a character class out of them
1298 1299 var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g, "\\$1") + "])";
1299 1300
1300 1301 if (afterBackslash) {
1301 1302 regexString = "\\\\" + regexString;
1302 1303 }
1303 1304
1304 1305 var regex = new RegExp(regexString, "g");
1305 1306 text = text.replace(regex, escapeCharacters_callback);
1306 1307
1307 1308 return text;
1308 1309 }
1309 1310
1310 1311
1311 1312 function escapeCharacters_callback(wholeMatch, m1) {
1312 1313 var charCodeToEscape = m1.charCodeAt(0);
1313 1314 return "~E" + charCodeToEscape + "E";
1314 1315 }
1315 1316
1316 1317 }; // end of the Markdown.Converter constructor
1317 1318
1318 1319 })();
General Comments 0
You need to be logged in to leave comments. Login now