not really known
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1412 lines
54 KiB

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