Markdown.Converter.js 52 KB

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