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