PageRenderTime 76ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/libs/snuownd.js

https://github.com/creesch/reddit-moderator-toolbox
JavaScript | 3835 lines | 3128 code | 204 blank | 503 comment | 416 complexity | 8f54cc06d352d2d5ec12191843ae3072 MD5 | raw file
Possible License(s): Apache-2.0
  1. /**
  2. @preserve snuownd.js - javascript port of reddit's "snudown" markdown parser
  3. https://github.com/gamefreak/snuownd
  4. */
  5. /**
  6. * @license Copyright (c) 2009, Natacha Porté
  7. * Copyright (c) 2011, Vicent Marti
  8. * Copyright (c) 2012, Scott McClaugherty
  9. *
  10. * Permission to use, copy, modify, and distribute this software for any
  11. * purpose with or without fee is hereby granted, provided that the above
  12. * copyright notice and this permission notice appear in all copies.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  15. * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  16. * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  17. * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  18. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  19. * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  20. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  21. */
  22. // up to date with commit b6baacb79996cec794a20d3abcae51adec5cc3cd
  23. /**
  24. @module SnuOwnd
  25. */
  26. (function(exports){
  27. function _isspace(c) {return c == ' ' || c == '\n';}
  28. function isspace(c) {return /[\x09-\x0d ]/.test(c);}
  29. function isalnum(c) { return /[A-Za-z0-9]/.test(c); }
  30. function isalpha(c) { return /[A-Za-z]/.test(c); }
  31. function ispunct(c) {return /[\x20-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]/.test(c); }
  32. function urlHexCode(number) {
  33. var hex_str = '0123456789ABCDEF';
  34. return '%'+hex_str[(number&0xf0)>>4]+hex_str[(number&0x0f)>>0];
  35. }
  36. function escapeUTF8Char(char) {
  37. var code = char.charCodeAt(0);
  38. if (code < 0x80) {
  39. return urlHexCode(code);
  40. } else if((code > 0x7f) && (code < 0x0800)) {
  41. var seq = urlHexCode(code >> 6 & 0xff | 0xc0);
  42. seq += urlHexCode(code >> 0 & 0x3f | 0x80);
  43. return seq;
  44. } else {
  45. var seq = urlHexCode(code >> 12 & 0xff | 0xe0);
  46. seq += urlHexCode(code >> 6 & 0x3f | 0x80);
  47. seq += urlHexCode(code >> 0 & 0x3f | 0x80);
  48. return seq;
  49. }
  50. }
  51. function find_block_tag (str) {
  52. var wordList = [
  53. 'p', 'dl', 'div', 'math',
  54. 'table', 'ul', 'del', 'form',
  55. 'blockquote', 'figure', 'ol', 'fieldset',
  56. 'h1', 'h6', 'pre', 'script',
  57. 'h5', 'noscript', 'style', 'iframe',
  58. 'h4', 'ins', 'h3', 'h2'
  59. ];
  60. if (wordList.indexOf(str.toLowerCase()) != -1) {
  61. return str.toLowerCase();
  62. }
  63. return '';
  64. }
  65. function sdhtml_is_tag(tag_data, tagname) {
  66. var i;
  67. var closed = 0;
  68. var tag_size = tag_data.length;
  69. if (tag_size < 3 || tag_data[0] != '<') return HTML_TAG_NONE;
  70. i = 1;
  71. if (tag_data[i] == '/') {
  72. closed = 1;
  73. i++;
  74. }
  75. var tagname_c = 0;
  76. for (; i < tag_size; ++i, ++tagname_c) {
  77. if (tagname_c >= tagname.length) break;
  78. if (tag_data[i] != tagname[tagname_c]) return HTML_TAG_NONE;
  79. }
  80. if (i == tag_size) return HTML_TAG_NONE;
  81. if (isspace(tag_data[i]) || tag_data[i] == '>')
  82. return closed ? HTML_TAG_CLOSE : HTML_TAG_OPEN;
  83. return HTML_TAG_NONE;
  84. }
  85. function unscape_text(out, src) {
  86. var i = 0, org;
  87. while (i < src.s.length) {
  88. org = i;
  89. while (i < src.s.length && src.s[i] != '\\') i++;
  90. if (i > org) out.s += src.s.slice(org, i);
  91. if (i + 1 >= src.s.length) break;
  92. out.s += src.s[i + 1];
  93. i += 2;
  94. }
  95. }
  96. /**
  97. * According to the OWASP rules:
  98. *
  99. * & --> &amp;
  100. * < --> &lt;
  101. * > --> &gt;
  102. * " --> &quot;
  103. * ' --> &#x27; &apos; is not recommended
  104. * / --> &#x2F; forward slash is included as it helps end an HTML entity
  105. *
  106. */
  107. var HTML_ESCAPE_TABLE = [
  108. 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0, 7, 7, 0, 7, 7,
  109. 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
  110. 0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 4,
  111. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 0,
  112. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  113. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  114. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  115. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  116. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  117. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  118. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  119. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  120. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  121. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  122. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  123. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  124. ];
  125. var HTML_ESCAPES = ["", "&quot;", "&amp;", "&#39;", "&#47;", "&lt;", "&gt;", "" /* throw out control characters */ ];
  126. function escape_html(out, src, secure) {
  127. var i = 0, org, esc = 0;
  128. while (i < src.length) {
  129. org = i;
  130. while (i < src.length && !(esc = HTML_ESCAPE_TABLE[src.charCodeAt(i)]))
  131. i++;
  132. if (i > org) out.s += src.slice(org, i);
  133. /* escaping */
  134. if (i >= src.length) break;
  135. /* The forward slash is only escaped in secure mode */
  136. if (src[i] == '/' && !secure) {
  137. out.s += '/';
  138. } else if (HTML_ESCAPE_TABLE[src.charCodeAt(i)] == 7) {
  139. /* skip control characters */
  140. } else {
  141. out.s += HTML_ESCAPES[esc];
  142. }
  143. i++;
  144. }
  145. }
  146. /*
  147. * The following characters will not be escaped:
  148. *
  149. * -_.+!*'(),%#@?=;:/,+&$ alphanum
  150. *
  151. * Note that this character set is the addition of:
  152. *
  153. * - The characters which are safe to be in an URL
  154. * - The characters which are *not* safe to be in
  155. * an URL because they are RESERVED characters.
  156. *
  157. * We asume (lazily) that any RESERVED char that
  158. * appears inside an URL is actually meant to
  159. * have its native function (i.e. as an URL
  160. * component/separator) and hence needs no escaping.
  161. *
  162. * There are two exceptions: the chacters & (amp)
  163. * and ' (single quote) do not appear in the table.
  164. * They are meant to appear in the URL as components,
  165. * yet they require special HTML-entity escaping
  166. * to generate valid HTML markup.
  167. *
  168. * All other characters will be escaped to %XX.
  169. *
  170. */
  171. var HREF_SAFE = [
  172. 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 2, 2,
  173. 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  174. 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
  175. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
  176. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  177. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
  178. 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  179. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
  180. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  181. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  182. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  183. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  184. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  185. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  186. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  187. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  188. ];
  189. function escape_href(out, src) {
  190. var i = 0, org;
  191. while (i < src.length) {
  192. org = i;
  193. while (i < src.length && HREF_SAFE[src.charCodeAt(i)] != 0) i++;
  194. if (i > org) out.s += src.slice(org, i);
  195. /* escaping */
  196. if (i >= src.length) break;
  197. /* throw out control characters */
  198. if (HREF_SAFE[src.charCodeAt(i)] == 2) {
  199. i++;
  200. continue;
  201. }
  202. switch (src[i]) {
  203. /* amp appears all the time in URLs, but needs
  204. * HTML-entity escaping to be inside an href */
  205. case '&':
  206. out.s += '&amp;';
  207. break;
  208. /* the single quote is a valid URL character
  209. * according to the standard; it needs HTML
  210. * entity escaping too */
  211. case '\'':
  212. out.s += '&#x27;';
  213. break;
  214. /* the space can be escaped to %20 or a plus
  215. * sign. we're going with the generic escape
  216. * for now. the plus thing is more commonly seen
  217. * when building GET strings */
  218. /*
  219. //This was disabled
  220. case ' ':
  221. out.s += '+'
  222. break;
  223. //*/
  224. /* every other character goes with a %XX escaping */
  225. default:
  226. out.s += escapeUTF8Char(src[i]);
  227. /*
  228. var cc = src.charCodeAt(i);
  229. hex_str[1] = hex_chars[(cc >> 4) & 0xF];
  230. hex_str[2] = hex_chars[cc & 0xF];
  231. out.s += hex_str.join('');
  232. */
  233. }
  234. i++;
  235. }
  236. }
  237. // function autolink_delim(uint8_t *data, size_t link_end, size_t offset, size_t size)
  238. function autolink_delim(data, link_end) {
  239. var cclose, copen = 0;
  240. var i;
  241. for (i = 0; i < link_end; ++i)
  242. if (data[i] == '<') {
  243. link_end = i;
  244. break;
  245. }
  246. while (link_end > 0) {
  247. if ('?!.,'.indexOf(data[link_end - 1]) != -1) link_end--;
  248. else if (data[link_end - 1] == ';') {
  249. var new_end = link_end - 2;
  250. while (new_end > 0 && isalpha(data[new_end])) new_end--;
  251. if (new_end < link_end - 2 && data[new_end] == '&')
  252. link_end = new_end;
  253. else link_end--;
  254. }
  255. else break;
  256. }
  257. if (link_end == 0) return 0;
  258. cclose = data[link_end - 1];
  259. switch (cclose) {
  260. case '"': copen = '"'; break;
  261. case '\'': copen = '\''; break;
  262. case ')': copen = '('; break;
  263. case ']': copen = '['; break;
  264. case '}': copen = '{'; break;
  265. }
  266. if (copen != 0) {
  267. var closing = 0;
  268. var opening = 0;
  269. var j = 0;
  270. /* Try to close the final punctuation sign in this same line;
  271. * if we managed to close it outside of the URL, that means that it's
  272. * not part of the URL. If it closes inside the URL, that means it
  273. * is part of the URL.
  274. *
  275. * Examples:
  276. *
  277. * foo http://www.pokemon.com/Pikachu_(Electric) bar
  278. * => http://www.pokemon.com/Pikachu_(Electric)
  279. *
  280. * foo (http://www.pokemon.com/Pikachu_(Electric)) bar
  281. * => http://www.pokemon.com/Pikachu_(Electric)
  282. *
  283. * foo http://www.pokemon.com/Pikachu_(Electric)) bar
  284. * => http://www.pokemon.com/Pikachu_(Electric))
  285. *
  286. * (foo http://www.pokemon.com/Pikachu_(Electric)) bar
  287. * => foo http://www.pokemon.com/Pikachu_(Electric)
  288. */
  289. while (j < link_end) {
  290. if (data[j] == copen) opening++;
  291. else if (data[j] == cclose) closing++;
  292. j++;
  293. }
  294. if (closing != opening) link_end--;
  295. }
  296. return link_end;
  297. }
  298. function check_domain(data, allow_short) {
  299. var i, np = 0;
  300. if (!isalnum(data[0])) return 0;
  301. for (i = 1; i < data.length - 1; ++i) {
  302. if (data[i] == '.') np++;
  303. else if (!isalnum(data[i]) && data[i] != '-') break;
  304. }
  305. /* a valid domain needs to have at least a dot.
  306. * that's as far as we get */
  307. if (allow_short) {
  308. /* We don't need a valid domain in the strict sence (with
  309. * at least one dot; so just make sure it's composed of valid
  310. * domain characters and return the length of the valid
  311. * sequence. */
  312. return i;
  313. } else {
  314. return np ? i : 0;
  315. }
  316. }
  317. function sd_autolink_issafe(link) {
  318. var valid_uris = [
  319. "http://", "https://", "ftp://", "mailto://",
  320. "/", "git://", "steam://", "irc://", "news://", "mumble://",
  321. "ssh://", "ircs://", "#"];
  322. var i;
  323. for (i = 0; i < valid_uris.length; ++i) {
  324. var len = valid_uris[i].length;
  325. if (link.length > len &&
  326. link.toLowerCase().indexOf(valid_uris[i]) == 0 &&
  327. /[A-Za-z0-9#\/?]/.test(link[len]))
  328. return 1;
  329. }
  330. return 0;
  331. }
  332. function sd_autolink__url(rewind_p, link, data_, offset, size, flags) {
  333. var data = data_.slice(offset);
  334. var link_end, rewind = 0, domain_len;
  335. if (size < 4 || data_[offset+1] != '/' || data_[offset+2] != '/') return 0;
  336. while (rewind < offset && isalpha(data_[offset-rewind - 1])) rewind++;
  337. if (!sd_autolink_issafe(data_.substr(offset-rewind, size+rewind))) return 0;
  338. link_end = "://".length;
  339. domain_len = check_domain(data.slice(link_end), flags & SD_AUTOLINK_SHORT_DOMAINS);
  340. if (domain_len == 0) return 0;
  341. link_end += domain_len;
  342. while (link_end < size && !isspace(data_[offset+link_end])) link_end++;
  343. link_end = autolink_delim(data, link_end);
  344. if (link_end == 0) return 0;
  345. //TODO
  346. link.s += data_.substr(offset-rewind, link_end+rewind);
  347. rewind_p.p = rewind;
  348. return link_end;
  349. }
  350. function sd_autolink__subreddit(rewind_p, link, data_, offset, size) {
  351. var data = data_.slice(offset);
  352. var link_end;
  353. var allMinus = false;
  354. if (size < 3) return 0;
  355. /* make sure this / is part of /r/ */
  356. if (data.indexOf('/r/') != 0) return 0;
  357. link_end = "/r/".length;
  358. if (data.substr(link_end-1, 4).toLowerCase() == "all-") {
  359. allMinus = true;
  360. }
  361. do {
  362. var start = link_end;
  363. var max_length = 24;
  364. /* special case: /r/reddit.com (the only subreddit with a '.') */
  365. if ( size >= link_end+10 && data.substr(link_end, 10).toLowerCase() == 'reddit.com') {
  366. link_end += 10;
  367. max_length = 10;
  368. } else {
  369. /* If not the special case make sure it starts with (t:)?[A-Za-z0-9] */
  370. /* support autolinking to timereddits, /r/t:when (1 April 2012) */
  371. if ( size > link_end+2 && data.substr(link_end, 2) == 't:')
  372. link_end += 2; /* Jump over the 't:' */
  373. /* the first character of a subreddit name must be a letter or digit */
  374. if (!isalnum(data[link_end]))
  375. return 0;
  376. link_end += 1;
  377. }
  378. /* consume valid characters ([A-Za-z0-9_]) until we run out */
  379. while (link_end < size && (isalnum(data[link_end]) ||
  380. data[link_end] == '_'))
  381. link_end++;
  382. /* valid subreddit names are between 3 and 21 characters, with
  383. * some subreddits having 2-character names. Don't bother with
  384. * autolinking for anything outside this length range.
  385. * (chksrname function in reddit/.../validator.py) */
  386. if ( link_end-start < 2 || link_end-start > max_length )
  387. return 0;
  388. /* If we are linking to a multireddit, continue */
  389. } while ( link_end < size && (data[link_end] == '+' || (allMinus && data[link_end] == '-')) && link_end++ );
  390. if (link_end < size && data[link_end] == '/') {
  391. while (link_end < size && (isalnum(data[link_end]) ||
  392. data[link_end] == '_' ||
  393. data[link_end] == '/' ||
  394. data[link_end] == '-')) {
  395. link_end++;
  396. }
  397. }
  398. /* make the link */
  399. link.s += data.slice(0, link_end);
  400. rewind_p.p = 0;
  401. return link_end;
  402. }
  403. function sd_autolink__username(rewind_p, link, data_, offset, size) {
  404. var data = data_.slice(offset);
  405. var link_end;
  406. if (size < 6) return 0;
  407. /* make sure this / is part of /u/ */
  408. if (data.indexOf('/u/') != 0) return 0;
  409. /* the first letter of a username must... well, be valid, we don't care otherwise */
  410. link_end = "/u/".length;
  411. if (!isalnum(data[link_end]) && data[link_end] != '_' && data[link_end] != '-')
  412. return 0;
  413. link_end += 1;
  414. /* consume valid characters ([A-Za-z0-9_-/]) until we run out */
  415. while (link_end < size && (isalnum(data[link_end]) ||
  416. data[link_end] == '_' ||
  417. data[link_end] == '/' ||
  418. data[link_end] == '-'))
  419. link_end++;
  420. /* make the link */
  421. link.s += data.slice(0, link_end);
  422. rewind_p.p = 0;
  423. return link_end;
  424. }
  425. function sd_autolink__email(rewind_p, link, data_, offset, size, flags) {
  426. var data = data_.slice(offset);
  427. var link_end, rewind;
  428. var nb = 0, np = 0;
  429. for (rewind = 0; rewind < offset; ++rewind) {
  430. var c = data_[offset-rewind - 1];
  431. if (isalnum(c)) continue;
  432. if (".+-_".indexOf(c) != -1) continue;
  433. break;
  434. }
  435. if (rewind == 0) return 0;
  436. for (link_end = 0; link_end < size; ++link_end) {
  437. var c = data_[offset+link_end];
  438. if (isalnum(c)) continue;
  439. if (c == '@') nb++;
  440. else if (c == '.' && link_end < size - 1) np++;
  441. else if (c != '-' && c != '_') break;
  442. }
  443. if (link_end < 2 || nb != 1 || np == 0) return 0;
  444. //TODO
  445. link_end = autolink_delim(data, link_end);
  446. if (link_end == 0) return 0;
  447. // link.s += data_.slice(offset - rewind, link_end + rewind
  448. link.s += data_.substr(offset - rewind, link_end + rewind);
  449. rewind_p.p = rewind;
  450. return link_end;
  451. }
  452. function sd_autolink__www(rewind_p, link, data_, offset, size, flags) {
  453. var data = data_.slice(offset);
  454. var link_end;
  455. if (offset > 0 && !ispunct(data_[offset-1]) && !isspace(data_[offset-1]))
  456. return 0;
  457. // if (size < 4 || memcmp(data, "www.", strlen("www.")) != 0)
  458. if (size < 4 || (data.slice(0,4) != 'www.')) return 0;
  459. link_end = check_domain(data, 0);
  460. if (link_end == 0)
  461. return 0;
  462. while (link_end < size && !isspace(data[link_end])) link_end++;
  463. link_end = autolink_delim(data, link_end);
  464. if (link_end == 0) return 0;
  465. link.s += data.slice(0, link_end);
  466. rewind_p.p = 0;
  467. return link_end;
  468. }
  469. /**
  470. Initialize a Callbacks object.
  471. @constructor
  472. @param {Object.<string, ?function>} callbacks A set of callbacks to use as the methods on this object.
  473. */
  474. function Callbacks(callbacks) {
  475. if (callbacks) {
  476. for (var name in callbacks) {
  477. if (name in this) this[name] = callbacks[name];
  478. }
  479. }
  480. }
  481. Callbacks.prototype = {
  482. /**
  483. Renders a code block.
  484. Syntax highlighting specific to lanugage may be performed here.
  485. @method
  486. @param {Buffer} out The output string buffer to append to.
  487. @param {Buffer} text The input text.
  488. @param {Buffer} language The name of the code langage.
  489. @param {?Object} context A renderer specific context object.
  490. */
  491. blockcode: null,
  492. /**
  493. Renders a blockquote.
  494. @method
  495. @param {Buffer} out The output string buffer to append to.
  496. @param {Buffer} text The input text.
  497. @param {?Object} context A renderer specific context object.
  498. */
  499. blockquote: null,
  500. /**
  501. Renders a block of HTML code.
  502. @method
  503. @param {Buffer} out The output string buffer to append to.
  504. @param {Buffer} text The input text.
  505. @param {?Object} context A renderer specific context object.
  506. */
  507. blockhtml: null,
  508. /**
  509. Renders a header.
  510. @method
  511. @param {Buffer} out The output string buffer to append to.
  512. @param {Buffer} text The input text.
  513. @param {Number} level The header level.
  514. @param {?Object} context A renderer specific context object.
  515. */
  516. header: null,
  517. /**
  518. Renders a horizontal rule.
  519. @method
  520. @param {Buffer} out The output string buffer to append to.
  521. @param {?Object} context A renderer specific context object.
  522. */
  523. hrule: null,
  524. /**
  525. Renders a list.
  526. <p>
  527. This method handles the list wrapper, which in terms of HTML would be &lt;ol&gt; or &lt;ul&gt;.
  528. This method is not responsible for handling list elements, all such processing should
  529. already have occured on text pased to the method . All that it is intended
  530. to do is to wrap the text parameter in anything needed.
  531. </p>
  532. @example
  533. out.s += "&lt;ul&gt;" + text.s + "&lt;/ul&gt;"
  534. @method
  535. @param {Buffer} out The output string buffer to append to.
  536. @param {Buffer} text The input that goes inside the list.
  537. @param {Number} flags A bitfield holding a portion of the render state. The only bit that this should be concerned with is MKD_LIST_ORDERED
  538. @param {?Object} context A renderer specific context object.
  539. */
  540. list: null,
  541. /**
  542. Renders a list.
  543. <p>
  544. Wraps the text in a list element.
  545. </p>
  546. @example
  547. out.s += "&lt;li&gt;" + text.s + "&lt;/li&gt;"
  548. @method
  549. @param {Buffer} out The output string buffer to append to.
  550. @param {Buffer} text The contents of the list element.
  551. @param {Number} flags A bitfield holding a portion of the render state. The only bit that this should be concerned with is MKD_LI_BLOCK.
  552. @param {?Object} context A renderer specific context object.
  553. */
  554. listitem: null,
  555. /**
  556. Renders a paragraph.
  557. @example
  558. out.s += "&lt;p&gt;" + text.s + "&lt;/p&gt;";
  559. @method
  560. @param {Buffer} out The output string buffer to append to.
  561. @param {Buffer} text The input text.
  562. @param {?Object} context A renderer specific context object.
  563. */
  564. paragraph: null,
  565. /**
  566. Renders a table.
  567. @example
  568. out.s += "<table><thead>";
  569. out.s += header.s;
  570. out.s += "</thead><tbody>";
  571. out.s += body.s;
  572. out.s += "</tbody></table>";
  573. @method
  574. @param {Buffer} out The output string buffer to append to.
  575. @param {Buffer} head The table header.
  576. @param {Buffer} body The table body.
  577. @param {?Object} context A renderer specific context object.
  578. */
  579. table: null,
  580. /**
  581. Renders a table row.
  582. @example
  583. out.s += "&lt;tr&gt;" + text.s + "&lt;/tr&gt;";
  584. @method
  585. @param {Buffer} out The output string buffer to append to.
  586. @param {Buffer} text The input text.
  587. @param {?Object} context A renderer specific context object.
  588. */
  589. table_row: null,
  590. /**
  591. Renders a table cell.
  592. @example
  593. out.s += "&lt;td&gt;" + text.s + "&lt;/td&gt;";
  594. @method
  595. @param {Buffer} out The output string buffer to append to.
  596. @param {Buffer} text The input text.
  597. @param {Number} flags A bit filed indicating a portion of the output state. Relevant bits are: MKD_TABLE_HEADER, MKD_TABLE_ALIGN_CENTER. MKD_TABLE_ALIGN_L, and MKD_TABLE_ALIGN_R.
  598. @param {?Object} context A renderer specific context object.
  599. */
  600. table_cell: null,
  601. /**
  602. Renders a link that was autodetected.
  603. @example
  604. out.s += "&lt;a href=\""+ text.s + "\"&gt;" + text.s + "&lt;/a&gt;";
  605. @method
  606. @param {Buffer} out The output string buffer to append to.
  607. @param {Buffer} text The address being linked to.
  608. @param {Number} type Equal to MKDA_NORMAL or MKDA_EMAIL
  609. @param {?Object} context A renderer specific context object.
  610. @returns {Boolean} Whether or not the tag was rendered.
  611. */
  612. autolink: null,
  613. /**
  614. Renders inline code.
  615. @method
  616. @param {Buffer} out The output string buffer to append to.
  617. @param {Buffer} text The text being wrapped.
  618. @param {?Object} context A renderer specific context object.
  619. @returns {Boolean} Whether or not the tag was rendered.
  620. */
  621. codespan: null,
  622. /**
  623. Renders text with double emphasis. Default is equivalent to the HTML &lt;strong&gt; tag.
  624. @method
  625. @param {Buffer} out The output string buffer to append to.
  626. @param {Buffer} text The text being wrapped.
  627. @param {?Object} context A renderer specific context object.
  628. @returns {Boolean} Whether or not the tag was rendered.
  629. */
  630. double_emphasis: null,
  631. /**
  632. Renders text with single emphasis. Default is equivalent to the HTML &lt;em&gt; tag.
  633. @method
  634. @param {Buffer} out The output string buffer to append to.
  635. @param {Buffer} text The text being wrapped.
  636. @param {?Object} context A renderer specific context object.
  637. @returns {Boolean} Whether or not the tag was rendered.
  638. */
  639. emphasis: null,
  640. /**
  641. Renders an image.
  642. @example
  643. out.s = "&lt;img src=\"" + link.s + "\" title=\"" + title.s + "\" alt=\"" + alt.s + "\"/&gt;";"
  644. @method
  645. @param {Buffer} out The output string buffer to append to.
  646. @param {Buffer} link The address of the image.
  647. @param {Buffer} title Title text for the image
  648. @param {Buffer} alt Alt text for the image
  649. @param {?Object} context A renderer specific context object.
  650. @returns {Boolean} Whether or not the tag was rendered.
  651. */
  652. image: null,
  653. /**
  654. Renders line break.
  655. @example
  656. out.s += "&lt;br/&gt;";
  657. @method
  658. @param {Buffer} out The output string buffer to append to.
  659. @param {?Object} context A renderer specific context object.
  660. @returns {Boolean} Whether or not the tag was rendered.
  661. */
  662. linebreak: null,
  663. /**
  664. Renders a link.
  665. @example
  666. out.s = "&lt;a href=\"" + link.s + "\" title=\"" + title.s + "\"&gt;" + content.s + "&lt;/a&gt;";
  667. @method
  668. @param {Buffer} out The output string buffer to append to.
  669. @param {Buffer} link The link address.
  670. @param {Buffer} title Title text for the link.
  671. @param {Buffer} content Link text.
  672. @param {?Object} context A renderer specific context object.
  673. @returns {Boolean} Whether or not the tag was rendered.
  674. */
  675. link: null,
  676. /**
  677. Copies and potentially escapes some HTML.
  678. @method
  679. @param {Buffer} out The output string buffer to append to.
  680. @param {Buffer} text The input text.
  681. @param {?Object} context A renderer specific context object.
  682. @returns {Boolean} Whether or not the tag was rendered.
  683. */
  684. raw_html_tag: null,
  685. /**
  686. Renders text with triple emphasis. Default is equivalent to both the &lt;em&gt; and &lt;strong&gt; HTML tags.
  687. @method
  688. @param {Buffer} out The output string buffer to append to.
  689. @param {Buffer} text The text being wrapped.
  690. @param {?Object} context A renderer specific context object.
  691. @returns {Boolean} Whether or not the tag was rendered.
  692. */
  693. triple_emphasis: null,
  694. /**
  695. Renders text crossd out.
  696. @method
  697. @param {Buffer} out The output string buffer to append to.
  698. @param {Buffer} text The text being wrapped.
  699. @param {?Object} context A renderer specific context object.
  700. @returns {Boolean} Whether or not the tag was rendered.
  701. */
  702. strikethrough: null,
  703. /**
  704. Renders text as superscript.
  705. @method
  706. @param {Buffer} out The output string buffer to append to.
  707. @param {Buffer} text The text being wrapped.
  708. @param {?Object} context A renderer specific context object.
  709. @returns {Boolean} Whether or not the tag was rendered.
  710. */
  711. superscript: null,
  712. /**
  713. Escapes an HTML entity.
  714. @method
  715. @param {Buffer} out The output string buffer to append to.
  716. @param {Buffer} text The text being wrapped.
  717. @param {?Object} context A renderer specific context object.
  718. */
  719. entity: null,
  720. /**
  721. Renders plain text.
  722. @method
  723. @param {Buffer} out The output string buffer to append to.
  724. @param {Buffer} text The text being rendered.
  725. @param {?Object} context A renderer specific context object.
  726. */
  727. normal_text: null,
  728. /**
  729. Creates opening boilerplate for a table of contents.
  730. @method
  731. @param {Buffer} out The output string buffer to append to.
  732. @param {?Object} context A renderer specific context object.
  733. */
  734. doc_header: null,
  735. /**
  736. Creates closing boilerplate for a table of contents.
  737. @method
  738. @param {Buffer} out The output string buffer to append to.
  739. @param {?Object} context A renderer specific context object.
  740. */
  741. doc_footer: null
  742. };
  743. /**
  744. A renderer object
  745. @constructor
  746. @param {Callbacks} callbacks The callbacks object to use for the renderer.
  747. @param {?Callbacks} context Renderer specific context information.
  748. */
  749. function Renderer(callbacks, context) {
  750. this.callbacks = callbacks;
  751. this.context = context;
  752. }
  753. /**
  754. Instantiates a custom Renderer object.
  755. @param {Callbacks} callbacks The callbacks object to use for the renderer.
  756. @param {?Callbacks} context Renderer specific context information.
  757. @returns {Renderer}
  758. */
  759. function createCustomRenderer(callbacks, context) {
  760. return new Renderer(callbacks, context)
  761. }
  762. exports.createCustomRenderer = createCustomRenderer;
  763. function defaultRenderState() {
  764. return {
  765. nofollow: 0,
  766. target: null,
  767. tocData: {
  768. headerCount: 0,
  769. currentLevel: 0,
  770. levelOffset: 0
  771. },
  772. toc_id_prefix: null,
  773. html_element_whitelist: DEFAULT_HTML_ELEMENT_WHITELIST,
  774. html_attr_whitelist: DEFAULT_HTML_ATTR_WHITELIST,
  775. flags: 0,
  776. //(flags != undefined?flags:HTML_SKIP_HTML | HTML_SKIP_IMAGES | HTML_SAFELINK | HTML_ESCAPE | HTML_USE_XHTML),
  777. /* extra callbacks */
  778. // void (*link_attributes)(struct buf *ob, const struct buf *url, void *self);
  779. link_attributes: function link_attributes(out, url, options) {
  780. if (options.nofollow) out.s += ' rel="nofollow"';
  781. if (options.target != null) {
  782. out.s += ' target="' + options.target + '"';
  783. }
  784. }
  785. };
  786. }
  787. exports.defaultRenderState = defaultRenderState;
  788. /**
  789. Produces a renderer object that will match Reddit's output.
  790. @param {?Number=} flags A bitfield containing flags specific to the reddit HTML renderer. Passing undefined, null, or null value will produce reddit exact output.
  791. @returns {Renderer} A renderer object that will match Reddit's output.
  792. */
  793. function getRedditRenderer(flags) {
  794. var state =defaultRenderState();
  795. if (flags == null) {
  796. state.flags = DEFAULT_BODY_FLAGS;
  797. } else {
  798. state.flags = flags;
  799. }
  800. var renderer = new Renderer(getRedditCallbacks() , state);
  801. if (renderer.context.flags & HTML_SKIP_IMAGES)
  802. renderer.callbacks.image = null;
  803. if (renderer.context.flags & HTML_SKIP_LINKS) {
  804. renderer.callbacks.link = null;
  805. renderer.callbacks.autolink = null;
  806. }
  807. if (renderer.context.flags & HTML_SKIP_HTML || renderer.context.flags & HTML_ESCAPE)
  808. renderer.callbacks.blockhtml = null;
  809. return renderer;
  810. }
  811. exports.getRedditRenderer = getRedditRenderer;
  812. /**
  813. Produces a renderer object that will match Reddit's for a table of contents.
  814. @returns {Renderer} A renderer object that will match Reddit's output.
  815. */
  816. function getTocRenderer() {
  817. var state = defaultRenderState();
  818. state.flags = HTML_TOC | HTML_SKIP_HTML;
  819. var renderer = new Renderer(getTocCallbacks(), state);
  820. return renderer;
  821. }
  822. exports.getTocRenderer = getTocRenderer;
  823. /**
  824. Create a Callbacks object with the given callback table.
  825. @param {Object.<string, function>} callbacks A table of callbacks to place int a callbacks object.
  826. @returns {Callbacks} A callbacks object holding the provided callbacks.
  827. */
  828. function createCustomCallbacks(callbacks) {
  829. return new Callbacks(callbacks);
  830. }
  831. exports.createCustomCallbacks = createCustomCallbacks;
  832. /**
  833. Produce a callbacks object that matches Reddit's output.
  834. @returns {Callbacks} A callbacks object that matches Reddit's output.
  835. */
  836. function getRedditCallbacks(){
  837. return new Callbacks({
  838. blockcode: cb_blockcode,
  839. blockquote: cb_blockquote,
  840. blockhtml: cb_blockhtml,
  841. header: cb_header,
  842. hrule: cb_hrule,
  843. list: cb_list,
  844. listitem: cb_listitem,
  845. paragraph: cb_paragraph,
  846. table: cb_table,
  847. table_row: cb_table_row,
  848. table_cell: cb_table_cell,
  849. autolink: cb_autolink,
  850. codespan: cb_codespan,
  851. double_emphasis: cb_double_emphasis,
  852. emphasis: cb_emphasis,
  853. image: cb_image,
  854. linebreak: cb_linebreak,
  855. link: cb_link,
  856. raw_html_tag: cb_raw_html_tag,
  857. triple_emphasis: cb_triple_emphasis,
  858. strikethrough: cb_strikethrough,
  859. superscript: cb_superscript,
  860. entity: null,
  861. normal_text: cb_normal_text,
  862. doc_header: null,
  863. doc_footer: cb_reset_toc
  864. });
  865. }
  866. exports.getRedditCallbacks = getRedditCallbacks;
  867. /**
  868. Produce a callbacks object for rendering a table of contents.
  869. @returns {Callbacks} A callbacks object for rendering a table of contents.
  870. */
  871. function getTocCallbacks() {
  872. return new Callbacks({
  873. blockcode: null,
  874. blockquote: null,
  875. blockhtml: null,
  876. header: cb_toc_header,
  877. hrule: null,
  878. list: null,
  879. listitem: null,
  880. paragraph: null,
  881. table: null,
  882. table_row: null,
  883. table_cell: null,
  884. autolink: null,
  885. codespan: cb_codespan,
  886. double_emphasis: cb_double_emphasis,
  887. emphasis: cb_emphasis,
  888. image: null,
  889. linebreak: null,
  890. link: cb_toc_link,
  891. raw_html_tag: null,
  892. triple_emphasis: cb_triple_emphasis,
  893. strikethrough: cb_strikethrough,
  894. superscript: cb_superscript,
  895. entity: null,
  896. normal_text: null,
  897. doc_header: null,
  898. doc_footer: cb_toc_finalize
  899. });
  900. }
  901. exports.getTocCallbacks = getTocCallbacks;
  902. /* block level callbacks - NULL skips the block */
  903. // void (*blockcode)(struct buf *ob, const struct buf *text, const struct buf *lang, void *opaque);
  904. function cb_blockcode(out, text, lang, options) {
  905. if (out.s.length) out.s += '\n';
  906. if (lang && lang.s.length) {
  907. var i, cls;
  908. out.s += '<pre><code class="';
  909. for (i = 0, cls = 0; i < lang.s.length; ++i, ++cls) {
  910. while (i < lang.s.length && isspace(lang.s[i]))
  911. i++;
  912. if (i < lang.s.length) {
  913. var org = i;
  914. while (i < lang.s.length && !isspace(lang.s[i])) i++;
  915. if (lang.s[org] == '.') org++;
  916. if (cls) out.s += ' ';
  917. escape_html(out, lang.s.slice(org, i), false);
  918. }
  919. }
  920. out.s += '">';
  921. } else
  922. out.s += '<pre><code>';
  923. if (text) escape_html(out, text.s, false);
  924. out.s += '</code></pre>\n';
  925. }
  926. // void (*blockquote)(struct buf *ob, const struct buf *text, void *opaque);
  927. function cb_blockquote(out, text, options) {
  928. if (out.s.length) out.s += '\n';
  929. out.s += '<blockquote>\n';
  930. if (text) out.s += text.s;
  931. out.s += '</blockquote>\n';
  932. }
  933. // void (*blockhtml)(struct buf *ob,const struct buf *text, void *opaque);
  934. function cb_blockhtml(out, text, options) {
  935. var org, sz;
  936. if (!text) return;
  937. sz = text.s.length;
  938. while (sz > 0 && text.s[sz - 1] == '\n') sz--;
  939. org = 0;
  940. while (org < sz && text.s[org] == '\n') org++;
  941. if (org >= sz) return;
  942. if (out.s.length) out.s += '\n';
  943. out.s += text.s.slice(org, sz);
  944. out.s += '\n';
  945. }
  946. // header(Buffer out, Buffer text, int level, void *opaque);
  947. function cb_header(out, text, level, options) {
  948. if (out.s.length) out.s += '\n';
  949. if (options.flags & HTML_TOC) {
  950. out.s += '<h' + (+level) + ' id="';
  951. if (options.toc_id_prefix) out.s += options.toc_id_prefix;
  952. out.s += 'toc_' + (options.tocData.headerCount++) + '">';
  953. } else {
  954. out.s += '<h' + (+level) + '>';
  955. }
  956. if (text) out.s += text.s;
  957. out.s += '</h' + (+level) + '>\n';
  958. }
  959. // void (*hrule)(struct buf *ob, void *opaque);
  960. function cb_hrule(out, options) {
  961. if (out.s.length) out.s += '\n';
  962. out.s += (options.flags & HTML_USE_XHTML) ? '<hr/>\n' : '<hr>\n';
  963. }
  964. // void (*list)(struct buf *ob, const struct buf *text, int flags, void *opaque);
  965. function cb_list(out, text, flags, options) {
  966. if (out.s.length) out.s += '\n';
  967. out.s += (flags&MKD_LIST_ORDERED?'<ol>\n':'<ul>\n');
  968. if (text) out.s += text.s;
  969. out.s += (flags&MKD_LIST_ORDERED?'</ol>\n':'</ul>\n');
  970. }
  971. // void (*listitem)(struct buf *ob, const struct buf *text, int flags, void *opaque);
  972. function cb_listitem(out, text, flags, options) {
  973. out.s += '<li>';
  974. if (text) {
  975. var size = text.s.length;
  976. while (size && text.s[size - 1] == '\n') size--;
  977. out.s += text.s.slice(0, size);
  978. }
  979. out.s += '</li>\n';
  980. }
  981. // void (*paragraph)(struct buf *ob, const struct buf *text, void *opaque);
  982. function cb_paragraph(out, text, options) {
  983. var i = 0;
  984. if (out.s.length) out.s += '\n';
  985. if (!text || !text.s.length) return;
  986. while (i < text.s.length && isspace(text.s[i])) i++;
  987. if (i == text.s.length) return;
  988. out.s += '<p>';
  989. if (options.flags & HTML_HARD_WRAP) {
  990. var org;
  991. while (i < text.s.length) {
  992. org = i;
  993. while (i < text.s.length && text.data[i] != '\n')
  994. i++;
  995. if (i > org) out.s += text.s.slice(org, i);
  996. /*
  997. * do not insert a line break if this newline
  998. * is the last character on the paragraph
  999. */
  1000. if (i >= text.s.length - 1) break;
  1001. cb_linebreak(out, options);
  1002. i++;
  1003. }
  1004. } else {
  1005. out.s += text.s.slice(i);
  1006. }
  1007. out.s += '</p>\n';
  1008. }
  1009. // void (*table)(struct buf *ob, const struct buf *header, const struct buf *body, void *opaque);
  1010. function cb_table(out, header, body, options) {
  1011. if (out.s.length) out.s += '\n';
  1012. out.s += '<table><thead>\n';
  1013. if (header) out.s += header.s;
  1014. out.s += '</thead><tbody>\n';
  1015. if (body) out.s += body.s;
  1016. out.s += '</tbody></table>\n';
  1017. }
  1018. // void (*table_row)(struct buf *ob, const struct buf *text, void *opaque);
  1019. function cb_table_row(out, text, options) {
  1020. out.s += '<tr>\n';
  1021. if (text) out.s += text.s;
  1022. out.s += '</tr>\n';
  1023. }
  1024. // void (*table_cell)(struct buf *ob, const struct buf *text, int flags, void *opaque);
  1025. function cb_table_cell(out, text, flags, options) {
  1026. if (flags & MKD_TABLE_HEADER) {
  1027. out.s += '<th';
  1028. } else {
  1029. out.s += '<td';
  1030. }
  1031. switch (flags & MKD_TABLE_ALIGNMASK) {
  1032. case MKD_TABLE_ALIGN_CENTER:
  1033. out.s += ' align="center">';
  1034. break;
  1035. case MKD_TABLE_ALIGN_L:
  1036. out.s += ' align="left">';
  1037. break;
  1038. case MKD_TABLE_ALIGN_R:
  1039. out.s += ' align="right">';
  1040. break;
  1041. default:
  1042. out.s += '>';
  1043. }
  1044. if (text) out.s += text.s;
  1045. if (flags & MKD_TABLE_HEADER) {
  1046. out.s += '</th>\n';
  1047. } else {
  1048. out.s += '</td>\n';
  1049. }
  1050. }
  1051. /* span level callbacks - NULL or return 0 prints the span verbatim */
  1052. // int (*autolink)(struct buf *ob, const struct buf *link, enum mkd_autolink type, void *opaque);
  1053. function cb_autolink(out, link, type, options) {
  1054. var offset = 0;
  1055. if (!link || !link.s.length) return 0;
  1056. if ((options.flags & HTML_SAFELINK) != 0 &&
  1057. !sd_autolink_issafe(link.s) && type != MKDA_EMAIL)
  1058. return 0;
  1059. out.s += '<a href="';
  1060. if (type == MKDA_EMAIL) out.s += 'mailto:';
  1061. escape_href(out, link.s.slice(offset));
  1062. if (options.link_attributes) {
  1063. out.s += '"';
  1064. options.link_attributes(out, link, options);
  1065. out.s += '>';
  1066. } else {
  1067. out.s += '">';
  1068. }
  1069. /*
  1070. * Pretty printing: if we get an email address as
  1071. * an actual URI, e.g. `mailto:foo@bar.com`, we don't
  1072. * want to print the `mailto:` prefix
  1073. */
  1074. if (link.s.indexOf('mailto:')==0) {
  1075. escape_html(out, link.s.slice(7), false);
  1076. } else {
  1077. escape_html(out, link.s, false);
  1078. }
  1079. out.s += '</a>';
  1080. return 1;
  1081. }
  1082. // int (*codespan)(struct buf *ob, const struct buf *text, void *opaque);
  1083. function cb_codespan(out, text, options) {
  1084. out.s += '<code>';
  1085. if (text) escape_html(out, text.s, false);
  1086. out.s += '</code>';
  1087. return 1;
  1088. }
  1089. // int (*double_emphasis)(struct buf *ob, const struct buf *text, void *opaque);
  1090. function cb_double_emphasis(out, text, options) {
  1091. if (!text || !text.s.length) return 0;
  1092. out.s += '<strong>' + text.s + '</strong>';
  1093. return 1;
  1094. }
  1095. // int (*emphasis)(struct buf *ob, const struct buf *text, void *opaque);
  1096. function cb_emphasis(out, text, options) {
  1097. if (!text || !text.s.length) return 0;
  1098. out.s += '<em>' + text.s + '</em>';
  1099. return 1;
  1100. }
  1101. // int (*image)(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *alt, void *opaque);
  1102. function cb_image(out, link, title, alt, options) {
  1103. if (!link || !link.s.length) return 0;
  1104. out.s += '<img src="';
  1105. escape_href(out, link.s);
  1106. out.s += '" alt="';
  1107. if (alt && alt.s.length) escape_html(out, alt.s, false);
  1108. if (title && title.s.length) {
  1109. out.s += '" title="';
  1110. escape_html(out, title.s, false);
  1111. }
  1112. out.s += (options.flags&HTML_USE_XHTML?'"/>':'">');
  1113. return 1;
  1114. }
  1115. // int (*linebreak)(struct buf *ob, void *opaque);
  1116. function cb_linebreak(out, options) {
  1117. out.s += (options.flags&HTML_USE_XHTML?'<br/>\n':'<br>\n');
  1118. return 1;
  1119. }
  1120. // int (*link)(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque);
  1121. function cb_link(out, link, title, content, options) {
  1122. if (link != null && (options.flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link.s)) return 0;
  1123. out.s += '<a href="';
  1124. if (link && link.s.length) escape_href(out, link.s);
  1125. if (title && title.s.length) {
  1126. out.s += '" title="';
  1127. escape_html(out, title.s, false);
  1128. }
  1129. if (options.link_attributes) {
  1130. out.s += '"';
  1131. options.link_attributes(out, link, options);
  1132. out.s += '>';
  1133. } else {
  1134. out.s += '">';
  1135. }
  1136. if (content && content.s.length) out.s += content.s;
  1137. out.s += '</a>';
  1138. return 1;
  1139. }
  1140. // rndr_html_tag(struct buf *ob, const struct buf *text, void *opaque, char* tagname, char** whitelist, int tagtype)
  1141. //NOT A CALLBACK!
  1142. // rndr_html_tag(struct buf *ob, const struct buf *text, void *opaque, char* tagname, char** whitelist, int tagtype)
  1143. //NOT A CALLBACK!
  1144. function rndr_html_tag(out, text, options, tagname, whitelist, tagtype) {
  1145. var i, x, z, in_str = 0, seen_equals = 0, done = 0, done_attr = 0, reset = 0;
  1146. var attr;
  1147. var value;
  1148. var c;
  1149. out.s += '<';
  1150. if(tagtype == HTML_TAG_CLOSE) {
  1151. out.s += '/';
  1152. out.s += tagname;
  1153. out.s += '>';
  1154. return;
  1155. }
  1156. out.s += tagname;
  1157. i = 1 + tagname.length;
  1158. attr = new Buffer();
  1159. value = new Buffer();
  1160. var self_closed = false;
  1161. for(; i < text.s.length && !done; i++) {
  1162. c = text.s[i];
  1163. done = 0;
  1164. reset = 0;
  1165. done_attr = 0;
  1166. switch(c) {
  1167. case '/':
  1168. if(!seen_equals) {
  1169. self_closed = 1;
  1170. } else if(in_str) {
  1171. value.s += '/';
  1172. } else {
  1173. reset = 1;
  1174. }
  1175. break;
  1176. case '>':
  1177. done = 1;
  1178. break;
  1179. case '\'':
  1180. case '"':
  1181. self_closed = 0;
  1182. if(!seen_equals) {
  1183. reset = 1;
  1184. } else if(!in_str) {
  1185. in_str = c;
  1186. } else if(in_str == c) {
  1187. in_str = 0;
  1188. done_attr = 1;
  1189. } else {
  1190. value.s += c;
  1191. }
  1192. break;
  1193. case ' ':
  1194. if (in_str) {
  1195. value.s += ' ';
  1196. } else {
  1197. reset = 1;
  1198. }
  1199. break;
  1200. case '=':
  1201. self_closed = 0;
  1202. if(seen_equals) {
  1203. reset = 1;
  1204. break;
  1205. }
  1206. seen_equals = 1;
  1207. break;
  1208. default:
  1209. self_closed = 0;
  1210. if(seen_equals && in_str || !seen_equals) {
  1211. if(seen_equals)
  1212. value.s += c;
  1213. else
  1214. attr.s += c;
  1215. }
  1216. break;
  1217. }
  1218. if(done_attr) {
  1219. var valid = 0;
  1220. for(z = 0; z < whitelist.length; z++) {
  1221. if(whitelist[z].length != attr.s.length) {
  1222. continue;
  1223. }
  1224. for(x = 0; x < attr.s.length; x++) {
  1225. if(whitelist[z][x].toLowerCase() != attr.s[x].toLowerCase()) {
  1226. break;
  1227. }
  1228. }
  1229. if(x == attr.s.length) {
  1230. valid = 1;
  1231. break;
  1232. }
  1233. }
  1234. if(valid && value.s.length && attr.s.length) {
  1235. out.s += ' ';
  1236. escape_html(out, attr.s, true);
  1237. out.s += "=\"";
  1238. escape_html(out, value.s, true);
  1239. out.s += '"';
  1240. }
  1241. reset = 1;
  1242. }
  1243. if(reset) {
  1244. seen_equals = 0;
  1245. in_str = 0;
  1246. attr.s = '';
  1247. value.s = '';
  1248. }
  1249. }
  1250. // bufrelease(attr);
  1251. if(self_closed)
  1252. out.s += ' /';
  1253. out.s += '>';
  1254. }
  1255. // int (*raw_html_tag)(struct buf *ob, const struct buf *tag, void *opaque);
  1256. function cb_raw_html_tag(out, text, options) {
  1257. var whitelist = options.html_element_whitelist;
  1258. /* Items on the whitelist ignore all other flags and just output */
  1259. if (((options.flags & HTML_ALLOW_ELEMENT_WHITELIST) != 0) && whitelist) {
  1260. for(var i = 0; whitelist[i]; i++) {
  1261. var tagtype = sdhtml_is_tag(text.s, whitelist[i]);
  1262. if(tagtype != HTML_TAG_NONE) {
  1263. rndr_html_tag(out, text, options, whitelist[i], options.html_attr_whitelist, tagtype);
  1264. return 1;
  1265. }
  1266. }
  1267. }
  1268. /* HTML_ESCAPE overrides SKIP_HTML, SKIP_STYLE, SKIP_LINKS and SKIP_IMAGES
  1269. * It doens't see if there are any valid tags, just escape all of them. */
  1270. if((options.flags & HTML_ESCAPE) != 0) {
  1271. escape_html(out, text.s, false);
  1272. return 1;
  1273. }
  1274. if ((options.flags & HTML_SKIP_HTML) != 0) return 1;
  1275. if ((options.flags & HTML_SKIP_STYLE) != 0 &&
  1276. sdhtml_is_tag(text.s, "style"))
  1277. return 1;
  1278. if ((options.flags & HTML_SKIP_LINKS) != 0 &&
  1279. sdhtml_is_tag(text.s, "a"))
  1280. return 1;
  1281. if ((options.flags & HTML_SKIP_IMAGES) != 0 &&
  1282. sdhtml_is_tag(text.s, "img"))
  1283. return 1;
  1284. out.s += text.s;
  1285. return 1;
  1286. }
  1287. // int (*triple_emphasis)(struct buf *ob, const struct buf *text, void *opaque);
  1288. function cb_triple_emphasis(out, text, options) {
  1289. if (!text || !text.s.length) return 0;
  1290. out.s += '<strong><em>' + text.s + '</em></strong>';
  1291. return 1;
  1292. }
  1293. // int (*strikethrough)(struct buf *ob, const struct buf *text, void *opaque);
  1294. function cb_strikethrough(out, text, options) {
  1295. if (!text || !text.s.length) return 0;
  1296. out.s += '<del>' + text.s + '</del>';
  1297. return 1;
  1298. }
  1299. // int (*superscript)(struct buf *ob, const struct buf *text, void *opaque);
  1300. function cb_superscript(out, text, options) {
  1301. if (!text || !text.s.length) return 0;
  1302. out.s += '<sup>' + text.s + '</sup>';
  1303. return 1;
  1304. }
  1305. /* low level callbacks - NULL copies input directly into the output */
  1306. //do not use
  1307. // void (*entity)(struct buf *ob, const struct buf *entity, void *opaque);
  1308. // function cb_entity(out, entity, options) {}
  1309. // void (*normal_text)(struct buf *ob, const struct buf *text, void *opaque);
  1310. function cb_normal_text(out, text, options) {
  1311. if (text) escape_html(out, text.s, false);
  1312. }
  1313. // toc_header(struct buf *ob, const struct buf *text, int level, void *opaque)
  1314. function cb_toc_header(out, text, level, options) {
  1315. /* set the level offset if this is the first header
  1316. * we're parsing for the document */
  1317. if (options.tocData.currentLevel== 0) {
  1318. out.s += '<div class="toc">\n';
  1319. options.tocData.levelOffset = level - 1;
  1320. }
  1321. level -= options.tocData.levelOffset;
  1322. if (level > options.tocData.currentLevel) {
  1323. while (level > options.tocData.currentLevel) {
  1324. out.s += '<ul>\n<li>\n';
  1325. options.tocData.currentLevel++;
  1326. }
  1327. } else if (level < options.tocData.currentLevel) {
  1328. out.s += '</li>\n';
  1329. while (level < options.tocData.currentLevel) {
  1330. out.s += '</ul>\n</li>\n';
  1331. options.tocData.currentLevel--;
  1332. }
  1333. out.s += '<li>\n';
  1334. } else {
  1335. out.s += '</li>\n<li>\n';
  1336. }
  1337. out.s += '<a href="#';
  1338. if (options.toc_id_prefix) out.s += options.toc_id_prefix;
  1339. out.s += 'toc_' + (options.tocData.headerCount++) + '">';
  1340. if (text) escape_html(out, text.s, false);
  1341. out.s += '</a>\n';
  1342. }
  1343. //toc_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque)
  1344. function cb_toc_link(out, link, title, content, options) {
  1345. if (content && content.s)
  1346. out.s += content.s;
  1347. return 1;
  1348. }
  1349. // reset_toc(struct buf *ob, void *opaque)
  1350. function cb_reset_toc(out, options) {
  1351. options.tocData = {
  1352. headerCount: 0,
  1353. currentLevel: 0,
  1354. levelOffset: 0
  1355. };
  1356. }
  1357. //toc_finalize(struct buf *ob, void *opaque)
  1358. function cb_toc_finalize(out, options) {
  1359. var hasToc = false;
  1360. while (options.tocData.currentLevel > 0) {
  1361. out.s += '</li>\n</ul>\n';
  1362. options.tocData.currentLevel--;
  1363. hasToc = true;
  1364. }
  1365. if (hasToc) {
  1366. out.s += '</div>\n';
  1367. }
  1368. cb_reset_toc(out, options);
  1369. }
  1370. /* header and footer */
  1371. // doc_header(Buffer out}, context);
  1372. // doc_header: null,
  1373. // doc_footer(Buffer out, context);
  1374. // doc_footer: null
  1375. /* char_emphasis • single and double emphasis parsing */
  1376. //Buffer, md, str, int
  1377. function char_emphasis(out, md, data_, offset) {
  1378. var data = data_.slice(offset);
  1379. var size = data.length;
  1380. var c = data[0];
  1381. var ret;
  1382. if (size > 2 && data[1] != c) {
  1383. /* whitespace cannot follow an opening emphasis;
  1384. * strikethrough only takes two characters '~~' */
  1385. if (c == '~' || _isspace(data[1]) || (ret = parse_emph1(out, md, data, c)) == 0)
  1386. return 0;
  1387. return ret + 1;
  1388. }
  1389. if (data.length > 3 && data[1] == c && data[2] != c) {
  1390. if (_isspace(data[2]) || (ret = parse_emph2(out, md, data, c)) == 0)
  1391. return 0;
  1392. return ret + 2;
  1393. }
  1394. if (data.length > 4 && data[1] == c && data[2] == c && data[3] != c) {
  1395. if (c == '~' || _isspace(data[3]) || (ret = parse_emph3(out, md, data, c)) == 0)
  1396. return 0;
  1397. return ret + 3;
  1398. }
  1399. return 0;
  1400. }
  1401. /* char_codespan - '`' parsing a code span (assuming codespan != 0) */
  1402. function char_codespan(out, md, data_, offset) {
  1403. var data = data_.slice(offset);
  1404. var end, nb = 0, i, f_begin, f_end;
  1405. /* counting the number of backticks in the delimiter */
  1406. while (nb < data.length && data[nb] == '`')
  1407. nb++;
  1408. /* finding the next delimiter */
  1409. i = 0;
  1410. for (end = nb; end < data.length && i < nb; end++) {
  1411. if (data[end] == '`') i++;
  1412. else i = 0;
  1413. }
  1414. if (i < nb && end >= data.length)
  1415. return 0; /* no matching delimiter */
  1416. /* trimming outside whitespaces */
  1417. f_begin = nb;
  1418. while (f_begin < end && data[f_begin] == ' ') f_begin++;
  1419. f_end = end - nb;
  1420. while (f_end > nb && data[f_end-1] == ' ') f_end--;
  1421. /* real code span */
  1422. if (f_begin < f_end) {
  1423. var work = new Buffer(data.slice(f_begin, f_end));
  1424. if (!md.callbacks.codespan(out, work, md.context))
  1425. end = 0;
  1426. } else {
  1427. if (!md.callbacks.codespan(out, null, md.context))
  1428. end = 0;
  1429. }
  1430. return end;
  1431. }
  1432. /* char_linebreak - '\n' preceded by two spaces (assuming linebreak != 0) */
  1433. function char_linebreak(out, md, data_, offset) {
  1434. var data = data_.slice(offset);
  1435. if (offset < 2 || data_[offset-1] != ' ' || data_[offset-2] != ' ')
  1436. return 0;
  1437. /* removing the last space from ob and rendering */
  1438. var len = out.s.length;
  1439. while (len && out.s[len - 1] == ' ') len--;
  1440. out.s = out.s.slice(0, len);
  1441. return md.callbacks.linebreak(out, md.context) ? 1 : 0;
  1442. }
  1443. /* char_link - '[': parsing a link or an image */
  1444. function char_link(out, md, data_, offset) {
  1445. var data = data_.slice(offset);
  1446. var is_img = (offset && data_[offset - 1] == '!'), level;
  1447. var i = 1, txt_e, link_b = 0, link_e = 0, title_b = 0, title_e = 0;
  1448. //4 bufs
  1449. var content = null;
  1450. var link = null;
  1451. var title = null;
  1452. var u_link = null;
  1453. var org_work_size = md.spanStack.length;
  1454. var text_has_nl = 0, ret = 0;
  1455. var in_title = 0, qtype = 0;
  1456. function cleanup() {
  1457. md.spanStack.length = org_work_size;
  1458. return ret ? i : 0;
  1459. }
  1460. /* checking whether the correct renderer exists */
  1461. if ((is_img && !md.callbacks.image) || (!is_img && !md.callbacks.link))
  1462. return cleanup();
  1463. /* looking for the matching closing bracket */
  1464. for (level = 1; i < data.length; i++) {
  1465. if (data[i] == '\n') text_has_nl = 1;
  1466. else if (data[i - 1] == '\\') continue;
  1467. else if (data[i] == '[') level++;
  1468. else if (data[i] == ']') {
  1469. level--;
  1470. if (level <= 0) break;
  1471. }
  1472. }
  1473. if (i >= data.length) return cleanup();
  1474. txt_e = i;
  1475. i++;
  1476. /* skip any amount of whitespace or newline */
  1477. /* (this is much more laxist than original markdown syntax) */
  1478. while (i < data.length && _isspace(data[i])) i++;
  1479. /* inline style link */
  1480. if (i < data.length && data[i] == '(') {
  1481. /* skipping initial whitespace */
  1482. i++;
  1483. while (i < data.length && _isspace(data[i])) i++;
  1484. link_b = i;
  1485. /* looking for link end: ' " ) */
  1486. while (i < data.length) {
  1487. if (data[i] == '\\') i += 2;
  1488. else if (data[i] == ')') break;
  1489. else if (i >= 1 && _isspace(data[i-1]) && (data[i] == '\'' || data[i] == '"')) break;
  1490. else i++;
  1491. }
  1492. if (i >= data.length) return cleanup();
  1493. link_e = i;
  1494. /* looking for title end if present */
  1495. if (data[i] == '\'' || data[i] == '"') {
  1496. qtype = data[i];
  1497. in_title = 1;
  1498. i++;
  1499. title_b = i;
  1500. while (i < data.length) {
  1501. if (data[i] == '\\') i += 2;
  1502. else if (data[i] == qtype) {in_title = 0; i++;}
  1503. else if ((data[i] == ')') && !in_title) break;
  1504. else i++;
  1505. }
  1506. if (i >= data.length) return cleanup();
  1507. /* skipping whitespaces after title */
  1508. title_e = i - 1;
  1509. while (title_e > title_b && _isspace(data[title_e])) title_e--;
  1510. /* checking for closing quote presence */
  1511. if (data[title_e] != '\'' && data[title_e] != '"') {
  1512. title_b = title_e = 0;
  1513. link_e = i;
  1514. }
  1515. }
  1516. /* remove whitespace at the end of the link */
  1517. while (link_e > link_b && _isspace(data[link_e - 1])) link_e--;
  1518. /* remove optional angle brackets around the link */
  1519. if (data[link_b] == '<') link_b++;
  1520. if (data[link_e - 1] == '>') link_e--;
  1521. /* building escaped link and title */
  1522. if (link_e > link_b) {
  1523. link = new Buffer();
  1524. md.spanStack.push(link);
  1525. link.s += data.slice(link_b, link_e);
  1526. }
  1527. if (title_e > title_b) {
  1528. title = new Buffer();
  1529. md.spanStack.push(title);
  1530. title.s += data.slice(title_b, title_e);
  1531. }
  1532. i++;
  1533. }
  1534. /* reference style link */
  1535. else if (i < data.length && data[i] == '[') {
  1536. var id = new Buffer();
  1537. var lr = null;
  1538. /* looking for the id */
  1539. i++;
  1540. link_b = i;
  1541. while (i < data.length && data[i] != ']') i++;
  1542. if (i >= data.length) return cleanup();
  1543. link_e = i;
  1544. /* finding the link_ref */
  1545. if (link_b == link_e) {
  1546. if (text_has_nl) {
  1547. var b = new Buffer();
  1548. md.spanStack.push(b);
  1549. var j;
  1550. for (j = 1; j < txt_e; j++) {
  1551. if (data[j] != '\n')
  1552. b.s += data[j];
  1553. else if (data[j - 1] != ' ')
  1554. b.s += ' ';
  1555. }
  1556. id.s = b.s;
  1557. } else {
  1558. id.s = data.slice(1);
  1559. }
  1560. } else {
  1561. id.s = data.slice(link_b, link_e);
  1562. }
  1563. //TODO
  1564. lr = md.refs[id.s];
  1565. if (!lr) return cleanup();
  1566. /* keeping link and title from link_ref */
  1567. link = lr.link;
  1568. title = lr.title;
  1569. i++;
  1570. }
  1571. /* shortcut reference style link */
  1572. else {
  1573. var id = new Buffer();
  1574. var lr = null;
  1575. /* crafting the id */
  1576. if (text_has_nl) {
  1577. var b = new Buffer();
  1578. md.spanStack.push(b);
  1579. var j;
  1580. for (j = 1; j < txt_e; j++) {
  1581. if (data[j] != '\n') b.s += data[j];
  1582. else if (data[j - 1] != ' ') b.s += ' ';
  1583. }
  1584. id.s = b.s;
  1585. } else {
  1586. id.s = data.slice(1, txt_e);
  1587. }
  1588. /* finding the link_ref */
  1589. lr = md.refs[id.s];
  1590. if (!lr) return cleanup();
  1591. /* keeping link and title from link_ref */
  1592. link = lr.link;
  1593. title = lr.title;
  1594. /* rewinding the whitespace */
  1595. i = txt_e + 1;
  1596. }
  1597. /* building content: img alt is escaped, link content is parsed */
  1598. if (txt_e > 1) {
  1599. content = new Buffer();
  1600. md.spanStack.push(content);
  1601. if (is_img) {
  1602. content.s += data.slice(1, txt_e);
  1603. } else {
  1604. /* disable autolinking when parsing inline the
  1605. * content of a link */
  1606. md.inLinkBody = 1;
  1607. parse_inline(content, md, data.slice(1, txt_e));
  1608. md.inLinkBody = 0;
  1609. }
  1610. }
  1611. if (link) {
  1612. u_link = new Buffer();
  1613. md.spanStack.push(u_link);
  1614. unscape_text(u_link, link);
  1615. } else {
  1616. return cleanup();
  1617. }
  1618. /* calling the relevant rendering function */
  1619. if (is_img) {
  1620. if (out.s.length && out.s[out.s.length - 1] == '!')
  1621. out.s = out.s.slice(0, -1);
  1622. ret = md.callbacks.image(out, u_link, title, content, md.context);
  1623. } else {
  1624. ret = md.callbacks.link(out, u_link, title, content, md.context);
  1625. }
  1626. /* cleanup */
  1627. // cleanup:
  1628. // rndr->work_bufs[BUFFER_SPAN].size = (int)org_work_size;
  1629. // return ret ? i : 0;
  1630. return cleanup();
  1631. }
  1632. /* char_langle_tag - '<' when tags or autolinks are allowed */
  1633. function char_langle_tag(out, md, data_, offset) {
  1634. var data = data_.slice(offset);
  1635. var altype = {p:MKDA_NOT_AUTOLINK};
  1636. var end = tag_length(data, altype);
  1637. var work = new Buffer(data.slice(0, end));
  1638. var ret = 0;
  1639. if (end > 2) {
  1640. if (md.callbacks.autolink && altype.p != MKDA_NOT_AUTOLINK) {
  1641. var u_link = new Buffer();
  1642. md.spanStack.push(u_link);
  1643. work.s = data.substr(1 , end - 2);
  1644. unscape_text(u_link, work);
  1645. ret = md.callbacks.autolink(out, u_link, altype.p, md.context);
  1646. md.spanStack.pop();
  1647. }
  1648. else if (md.callbacks.raw_html_tag)
  1649. ret = md.callbacks.raw_html_tag(out, work, md.context);
  1650. }
  1651. if (!ret) return 0;
  1652. else return end;
  1653. }
  1654. /* char_escape - '\\' backslash escape */
  1655. function char_escape(out, md, data_, offset) {
  1656. var data = data_.slice(offset);
  1657. var escape_chars = "\\`*_{}[]()#+-.!:|&<>/^~";
  1658. var work = new Buffer();
  1659. if (data.length > 1) {
  1660. if (escape_chars.indexOf(data[1]) == -1) return 0;
  1661. if (md.callbacks.normal_text) {
  1662. work.s = data[1];
  1663. md.callbacks.normal_text(out, work, md.context);
  1664. }
  1665. else out.s += data[1];
  1666. } else if (data.length == 1) {
  1667. out.s += data[0];
  1668. }
  1669. return 2;
  1670. }
  1671. /* char_entity - '&' escaped when it doesn't belong to an entity */
  1672. /* valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; */
  1673. function char_entity(out, md, data_, offset) {
  1674. var data = data_.slice(offset);
  1675. var end = 1;
  1676. var work = new Buffer();
  1677. if (end < data.length && data[end] == '#') end++;
  1678. while (end < data.length && isalnum(data[end])) end++;
  1679. if (end < data.length && data[end] == ';') end++; /* real entity */
  1680. else return 0; /* lone '&' */
  1681. if (md.callbacks.entity) {
  1682. work.s = data.slice(0, end);
  1683. md.callbacks.entity(out, work, md.context);
  1684. }
  1685. else out.s += data.slice(0, end);
  1686. return end;
  1687. }
  1688. function char_autolink_url(out, md, data_, offset) {
  1689. var data = data_.slice(offset);
  1690. var link = null;
  1691. var link_len, rewind = {p: null};
  1692. if (!md.callbacks.autolink || md.inLinkBody) return 0;
  1693. link = new Buffer();
  1694. md.spanStack.push(link);
  1695. if ((link_len = sd_autolink__url(rewind, link, data_, offset, data.length, 0)) > 0) {
  1696. if (rewind.p > 0) out.s = out.s.slice(0, -rewind.p);
  1697. md.callbacks.autolink(out, link, MKDA_NORMAL, md.context);
  1698. }
  1699. md.spanStack.pop();
  1700. return link_len;
  1701. }
  1702. function char_autolink_email(out, md, data_, offset) {
  1703. var data = data_.slice(offset);
  1704. var link = null;
  1705. var link_len, rewind = {p: null};
  1706. if (!md.callbacks.autolink || md.inLinkBody) return 0;
  1707. link = new Buffer();
  1708. md.spanStack.push(link);
  1709. if ((link_len = sd_autolink__email(rewind, link, data_, offset, data.length, 0)) > 0) {
  1710. if (rewind.p > 0) out.s = out.s.slice(0, -rewind.p);
  1711. md.callbacks.autolink(out, link, MKDA_EMAIL, md.context);
  1712. }
  1713. md.spanStack.pop();
  1714. return link_len;
  1715. }
  1716. function char_autolink_www(out, md, data_, offset) {
  1717. var data = data_.slice(offset);
  1718. var link = null, link_url = null, link_text = null;
  1719. var link_len, rewind = {p: null};
  1720. if (!md.callbacks.link || md.inLinkBody) return 0;
  1721. link = new Buffer();
  1722. md.spanStack.push(link);
  1723. if ((link_len = sd_autolink__www(rewind, link, data_, offset, data.length, 0)) > 0) {
  1724. link_url = new Buffer();
  1725. md.spanStack.push(link_url);
  1726. link_url.s += 'http://';
  1727. link_url.s += link.s;
  1728. if (rewind.p > 0) out.s = out.s.slice(0, out.s.length-rewind.p);
  1729. if (md.callbacks.normal_text) {
  1730. link_text = new Buffer();
  1731. md.spanStack.push(link_text);
  1732. md.callbacks.normal_text(link_text, link, md.context);
  1733. md.callbacks.link(out, link_url, null, link_text, md.context);
  1734. md.spanStack.pop();
  1735. } else {
  1736. md.callbacks.link(out, link_url, null, link, md.context);
  1737. }
  1738. md.spanStack.pop();
  1739. }
  1740. md.spanStack.pop();
  1741. return link_len;
  1742. }
  1743. function char_autolink_subreddit_or_username(out, md, data_, offset) {
  1744. var data = data_.slice(offset);
  1745. var link = null;
  1746. var link_len, rewind = {p: null};
  1747. if (!md.callbacks.autolink || md.inLinkBody) return 0;
  1748. link = new Buffer();
  1749. md.spanStack.push(link);
  1750. if ((link_len = sd_autolink__subreddit(rewind, link, data_, offset, data.length)) > 0) {
  1751. //don't slice because the rewind pointer will always be 0
  1752. if (rewind.p > 0) out.s = out.s.slice(0, -rewind.p);
  1753. md.callbacks.autolink(out, link, MKDA_NORMAL, md.context);
  1754. } else if ((link_len = sd_autolink__username(rewind, link, data_, offset, data.length)) > 0) {
  1755. //don't slice because the rewind pointer will always be 0
  1756. if (rewind.p > 0) out.s = out.s.slice(0, -rewind.p);
  1757. md.callbacks.autolink(out, link, MKDA_NORMAL, md.context);
  1758. }
  1759. md.spanStack.pop();
  1760. return link_len;
  1761. }
  1762. function char_superscript(out, md, data_, offset) {
  1763. var data = data_.slice(offset);
  1764. var size = data.length;
  1765. var sup_start, sup_len;
  1766. var sup = null;
  1767. if (!md.callbacks.superscript) return 0;
  1768. if (size < 2) return 0;
  1769. if (data[1] == '(') {
  1770. sup_start = sup_len = 2;
  1771. while (sup_len < size && data[sup_len] != ')' && data[sup_len - 1] != '\\') sup_len++;
  1772. if (sup_len == size) return 0;
  1773. } else {
  1774. sup_start = sup_len = 1;
  1775. while (sup_len < size && !_isspace(data[sup_len])) sup_len++;
  1776. }
  1777. if (sup_len - sup_start == 0) return (sup_start == 2) ? 3 : 0;
  1778. sup = new Buffer();
  1779. md.spanStack.push(sup);
  1780. parse_inline(sup, md, data.slice(sup_start, sup_len));
  1781. md.callbacks.superscript(out, sup, md.context);
  1782. md.spanStack.pop();
  1783. return (sup_start == 2) ? sup_len + 1 : sup_len;
  1784. }
  1785. var markdown_char_ptrs = [
  1786. null,
  1787. char_emphasis,
  1788. char_codespan,
  1789. char_linebreak,
  1790. char_link,
  1791. char_langle_tag,
  1792. char_escape,
  1793. char_entity,
  1794. char_autolink_url,
  1795. char_autolink_email,
  1796. char_autolink_www,
  1797. char_autolink_subreddit_or_username,
  1798. char_superscript
  1799. ];
  1800. var MKD_LIST_ORDERED = 1;
  1801. var MKD_LI_BLOCK = 2; /* <li> containing block data */
  1802. var MKD_LI_END = 8; /* internal list flag */
  1803. var enumCounter = 0;
  1804. var MD_CHAR_NONE = enumCounter++;
  1805. var MD_CHAR_EMPHASIS = enumCounter++;
  1806. var MD_CHAR_CODESPAN = enumCounter++;
  1807. var MD_CHAR_LINEBREAK = enumCounter++;
  1808. var MD_CHAR_LINK = enumCounter++;
  1809. var MD_CHAR_LANGLE = enumCounter++;
  1810. var MD_CHAR_ESCAPE = enumCounter++;
  1811. var MD_CHAR_ENTITITY = enumCounter++;
  1812. var MD_CHAR_AUTOLINK_URL = enumCounter++;
  1813. var MD_CHAR_AUTOLINK_EMAIL = enumCounter++;
  1814. var MD_CHAR_AUTOLINK_WWW = enumCounter++;
  1815. var MD_CHAR_AUTOLINK_SUBREDDIT_OR_USERNAME = enumCounter++;
  1816. var MD_CHAR_SUPERSCRIPT = enumCounter++;
  1817. var SD_AUTOLINK_SHORT_DOMAINS = (1 << 0);
  1818. enumCounter = 0;
  1819. var MKDA_NOT_AUTOLINK = enumCounter++; /* used internally when it is not an autolink*/
  1820. var MKDA_NORMAL = enumCounter++; /* normal http/http/ftp/mailto/etc link */
  1821. var MKDA_EMAIL = enumCounter++; /* e-mail link without explit mailto: */
  1822. var MKDEXT_NO_INTRA_EMPHASIS = (1 << 0);
  1823. var MKDEXT_TABLES = (1 << 1);
  1824. var MKDEXT_FENCED_CODE = (1 << 2);
  1825. var MKDEXT_AUTOLINK = (1 << 3);
  1826. var MKDEXT_STRIKETHROUGH = (1 << 4);
  1827. // var MKDEXT_LAX_HTML_BLOCKS = (1 << 5);
  1828. var MKDEXT_SPACE_HEADERS = (1 << 6);
  1829. var MKDEXT_SUPERSCRIPT = (1 << 7);
  1830. var MKDEXT_LAX_SPACING = (1 << 8)
  1831. var HTML_SKIP_HTML = (1 << 0);
  1832. var HTML_SKIP_STYLE = (1 << 1);
  1833. var HTML_SKIP_IMAGES = (1 << 2);
  1834. var HTML_SKIP_LINKS = (1 << 3);
  1835. var HTML_EXPAND_TABS = (1 << 4);
  1836. var HTML_SAFELINK = (1 << 5);
  1837. var HTML_TOC = (1 << 6);
  1838. var HTML_HARD_WRAP = (1 << 7);
  1839. var HTML_USE_XHTML = (1 << 8);
  1840. var HTML_ESCAPE = (1 << 9);
  1841. var HTML_ALLOW_ELEMENT_WHITELIST = (1 << 10);
  1842. var MKD_TABLE_ALIGN_L = 1;
  1843. var MKD_TABLE_ALIGN_R = 2;
  1844. var MKD_TABLE_ALIGN_CENTER = 3;
  1845. var MKD_TABLE_ALIGNMASK = 3;
  1846. var MKD_TABLE_HEADER = 4
  1847. var HTML_TAG_NONE = 0;
  1848. var HTML_TAG_OPEN = 1;
  1849. var HTML_TAG_CLOSE = 2;
  1850. /**
  1851. * A string buffer wrapper because JavaScript doesn't have mutable strings.
  1852. * @constructor
  1853. * @param {string=} str Optional string to initialize the Buffer with.
  1854. */
  1855. function Buffer(str) {
  1856. this.s = str || "";
  1857. };
  1858. // Buffer.prototype.toString = function toString() {
  1859. // return this.s;
  1860. // };
  1861. // Buffer.prototype.slice
  1862. /**
  1863. * A Markdown parser object.
  1864. * @constructor
  1865. */
  1866. function Markdown() {
  1867. //Becase javascript strings are immutable they must be wrapped with Buffer()
  1868. this.spanStack = [];
  1869. this.blockStack = [];
  1870. this.extensions = MKDEXT_NO_INTRA_EMPHASIS | MKDEXT_SUPERSCRIPT | MKDEXT_AUTOLINK | MKDEXT_STRIKETHROUGH | MKDEXT_TABLES;
  1871. var renderer = getRedditRenderer();
  1872. this.context = renderer.context;
  1873. this.callbacks = renderer.callbacks;
  1874. this.inLinkBody = 0;
  1875. this.activeChars = {};
  1876. this.refs = {};
  1877. this.nestingLimit = 16;
  1878. };
  1879. /* is_empty - returns the line length when it is empty, 0 otherwise */
  1880. function is_empty(data) {
  1881. var i;
  1882. for (i = 0; i < data.length && data[i] != '\n'; i++)
  1883. if (data[i] != ' ') return 0;
  1884. return i + 1;
  1885. }
  1886. /* is_hrule - returns whether a line is a horizontal rule */
  1887. function is_hrule(data) {
  1888. var i = 0, n = 0;
  1889. var c;
  1890. /* skipping initial spaces */
  1891. if (data.length < 3) return 0;
  1892. if (data[0] == ' ') { i++;
  1893. if (data[1] == ' ') { i++;
  1894. if (data[2] == ' ') { i++; } } }
  1895. /* looking at the hrule uint8_t */
  1896. if (i + 2 >= data.length
  1897. || (data[i] != '*' && data[i] != '-' && data[i] != '_'))
  1898. return 0;
  1899. c = data[i];
  1900. /* the whole line must be the char or whitespace */
  1901. while (i < data.length && data[i] != '\n') {
  1902. if (data[i] == c) n++;
  1903. else if (data[i] != ' ')
  1904. return 0;
  1905. i++;
  1906. }
  1907. return n >= 3;
  1908. }
  1909. /* check if a line begins with a code fence; return the
  1910. * width if it is */
  1911. function prefix_codefence(data) {
  1912. var i = 0, n = 0;
  1913. var c;
  1914. /* skipping initial spaces */
  1915. if (data.length < 3) return 0;
  1916. if (data[0] == ' ') { i++;
  1917. if (data[1] == ' ') { i++;
  1918. if (data[2] == ' ') { i++; } } }
  1919. /* looking at the hrule uint8_t */
  1920. if (i + 2 >= data.length || !(data[i] == '~' || data[i] == '`'))
  1921. return 0;
  1922. c = data[i];
  1923. /* the whole line must be the uint8_t or whitespace */
  1924. while (i < data.length && data[i] == c) {
  1925. n++; i++;
  1926. }
  1927. if (n < 3) return 0;
  1928. return i;
  1929. }
  1930. /* check if a line is a code fence; return its size if it */
  1931. function is_codefence(data, syntax) {
  1932. var i = 0, syn_len = 0;
  1933. i = prefix_codefence(data);
  1934. if (i == 0) return 0;
  1935. while (i < data.length && data[i] == ' ') i++;
  1936. var syn_start;
  1937. //syn_start = data + i;
  1938. syn_start = i;
  1939. if (i < data.length && data[i] == '{') {
  1940. i++; syn_start++;
  1941. while (i < data.length && data[i] != '}' && data[i] != '\n') {
  1942. syn_len++; i++;
  1943. }
  1944. if (i == data.length || data[i] != '}')
  1945. return 0;
  1946. /* strip all whitespace at the beginning and the end
  1947. * of the {} block */
  1948. /*remember not to remove the +0, it helps me keep syncronised with snudown*/
  1949. while (syn_len > 0 && _isspace(data[syn_start+0])) {
  1950. syn_start++; syn_len--;
  1951. }
  1952. // while (syn_len > 0 && _isspace(syn_start[syn_len - 1]))
  1953. while (syn_len > 0 && _isspace(data[syn_start+syn_len - 1]))
  1954. syn_len--;
  1955. i++;
  1956. } else {
  1957. while (i < data.length && !_isspace(data[i])) {
  1958. syn_len++; i++;
  1959. }
  1960. }
  1961. if (syntax) syntax.s = data.substr(syn_start, syn_len);
  1962. // syntax->size = syn;
  1963. while (i < data.length && data[i] != '\n') {
  1964. if (!_isspace(data[i])) return 0;
  1965. i++;
  1966. }
  1967. return i + 1;
  1968. }
  1969. /* find_emph_char - looks for the next emph uint8_t, skipping other constructs */
  1970. function find_emph_char(data, c) {
  1971. var i = 1;
  1972. while (i < data.length) {
  1973. while (i < data.length && data[i] != c && data[i] != '`' && data[i] != '[')
  1974. i++;
  1975. if (i == data.length) return 0;
  1976. if (data[i] == c)
  1977. return i;
  1978. /* not counting escaped chars */
  1979. if (i && data[i - 1] == '\\') {
  1980. i++; continue;
  1981. }
  1982. if (data[i] == '`') {
  1983. var span_nb = 0, bt;
  1984. var tmp_i = 0;
  1985. /* counting the number of opening backticks */
  1986. while (i < data.length && data[i] == '`') {
  1987. i++; span_nb++;
  1988. }
  1989. if (i >= data.length) return 0;
  1990. /* finding the matching closing sequence */
  1991. bt = 0;
  1992. while (i < data.length && bt < span_nb) {
  1993. if (!tmp_i && data[i] == c) tmp_i = i;
  1994. if (data[i] == '`') bt++;
  1995. else bt = 0;
  1996. i++;
  1997. }
  1998. if (i >= data.length) return tmp_i;
  1999. }
  2000. /* skipping a link */
  2001. else if (data[i] == '[') {
  2002. var tmp_i = 0;
  2003. var cc;
  2004. i++;
  2005. while (i < data.length && data[i] != ']') {
  2006. if (!tmp_i && data[i] == c) tmp_i = i;
  2007. i++;
  2008. }
  2009. i++;
  2010. while (i < data.length && (data[i] == ' ' || data[i] == '\n'))
  2011. i++;
  2012. if (i >= data.length) return tmp_i;
  2013. switch (data[i]) {
  2014. case '[':
  2015. cc = ']'; break;
  2016. case '(':
  2017. cc = ')'; break;
  2018. default:
  2019. if (tmp_i)
  2020. return tmp_i;
  2021. else
  2022. continue;
  2023. }
  2024. i++;
  2025. while (i < data.length && data[i] != cc) {
  2026. if (!tmp_i && data[i] == c) tmp_i = i;
  2027. i++;
  2028. }
  2029. if (i >= data.length) return tmp_i;
  2030. i++;
  2031. }
  2032. }
  2033. return 0;
  2034. }
  2035. /* parse_emph1 - parsing single emphase */
  2036. /* closed by a symbol not preceded by whitespace and not followed by symbol */
  2037. function parse_emph1(out, md, data_, c) {
  2038. var data = data_.slice(1);
  2039. var i = 0, len;
  2040. var r;
  2041. if (!md.callbacks.emphasis) return 0;
  2042. /* skipping one symbol if coming from emph3 */
  2043. if (data.length > 1 && data[0] == c && data[1] == c) i = 1;
  2044. while (i < data.length) {
  2045. len = find_emph_char(data.slice(i), c);
  2046. if (!len) return 0;
  2047. i += len;
  2048. if (i >= data.length) return 0;
  2049. if (data[i] == c && !_isspace(data[i - 1])) {
  2050. if ((md.extensions & MKDEXT_NO_INTRA_EMPHASIS) && (c == '_')) {
  2051. if (!(i + 1 == data.length || _isspace(data[i + 1]) || ispunct(data[i + 1])))
  2052. continue;
  2053. }
  2054. var work = new Buffer();
  2055. md.spanStack.push(work);
  2056. parse_inline(work, md, data.slice(0, i));
  2057. r = md.callbacks.emphasis(out, work, md.context);
  2058. md.spanStack.pop();
  2059. return r ? i + 1 : 0;
  2060. }
  2061. }
  2062. return 0;
  2063. }
  2064. /* parse_emph2 - parsing single emphase */
  2065. function parse_emph2(out, md, data_, c) {
  2066. var data = data_.slice(2);
  2067. var i = 0, len;
  2068. var r;
  2069. var render_method = (c == '~') ? md.callbacks.strikethrough : md.callbacks.double_emphasis;
  2070. if (!render_method) return 0;
  2071. while (i < data.length) {
  2072. len = find_emph_char(data.slice(i), c);
  2073. if (!len) return 0;
  2074. i += len;
  2075. if (i + 1 < data.length && data[i] == c && data[i + 1] == c && i && !_isspace(data[i - 1])) {
  2076. var work = new Buffer();
  2077. md.spanStack.push(work);
  2078. parse_inline(work, md, data.slice(0, i));
  2079. r = render_method(out, work, md.context);
  2080. md.spanStack.pop();
  2081. return r ? i + 2 : 0;
  2082. }
  2083. i++;
  2084. }
  2085. return 0;
  2086. }
  2087. /* parse_emph3 • parsing single emphase */
  2088. /* finds the first closing tag, and delegates to the other emph */
  2089. function parse_emph3(out, md, data_, c) {
  2090. var data = data_.slice(3);
  2091. var i = 0, len;
  2092. var r;
  2093. while (i < data.length) {
  2094. len = find_emph_char(data.slice(i), c);
  2095. if (!len) return 0;
  2096. i += len;
  2097. /* skip whitespace preceded symbols */
  2098. if (data[i] != c || _isspace(data[i - 1])) continue;
  2099. if (i + 2 < data.length && data[i + 1] == c && data[i + 2] == c && md.callbacks.triple_emphasis) {
  2100. /* triple symbol found */
  2101. var work = new Buffer();
  2102. md.spanStack.push(work);
  2103. parse_inline(work, md, data.slice(0, i));
  2104. r = md.callbacks.triple_emphasis(out, work, md.context);
  2105. md.spanStack.pop();
  2106. return r ? i + 3 : 0;
  2107. } else if (i + 1 < data.length && data[i + 1] == c) {
  2108. /* double symbol found, handing over to emph1 */
  2109. len = parse_emph1(out, md, data_, c);
  2110. if (!len) return 0;
  2111. else return len - 2;
  2112. } else {
  2113. /* single symbol found, handing over to emph2 */
  2114. len = parse_emph2(out, md, data_, c);
  2115. if (!len) return 0;
  2116. else return len - 1;
  2117. }
  2118. }
  2119. return 0;
  2120. }
  2121. function is_atxheader(md, data) {
  2122. if (data[0] != '#') return false;
  2123. if (md.extensions & MKDEXT_SPACE_HEADERS) {
  2124. var level = 0;
  2125. while (level < data.length && level < 6 && data[level] == '#')
  2126. level++;
  2127. if (level < data.length && data[level] != ' ')
  2128. return false;
  2129. }
  2130. return true;
  2131. }
  2132. /* is_headerline . returns whether the line is a setext-style hdr underline */
  2133. function is_headerline(data) {
  2134. var i = 0;
  2135. var size = data.length;
  2136. /* test of level 1 header */
  2137. if (data[i] == '=') {
  2138. for (i = 1; i < size && data[i] == '='; i++) {}
  2139. while (i < size && data[i] == ' ') i++;
  2140. return (i >= size || data[i] == '\n') ? 1 : 0; }
  2141. /* test of level 2 header */
  2142. if (data[i] == '-') {
  2143. for (i = 1; i < size && data[i] == '-'; i++) {}
  2144. while (i < size && data[i] == ' ') i++;
  2145. return (i >= size || data[i] == '\n') ? 2 : 0; }
  2146. return 0;
  2147. }
  2148. function is_next_headerline(data) {
  2149. var size = data.length;
  2150. var i = 0;
  2151. while (i < size && data[i] != '\n') i++;
  2152. if (++i >= size) return 0;
  2153. return is_headerline(data.slice(i));
  2154. }
  2155. /* prefix_quote - returns blockquote prefix length */
  2156. function prefix_quote(data) {
  2157. var i = 0;
  2158. var size = data.length;
  2159. if (i < size && data[i] == ' ') i++;
  2160. if (i < size && data[i] == ' ') i++;
  2161. if (i < size && data[i] == ' ') i++;
  2162. if (i < size && data[i] == '>') {
  2163. if (i + 1 < size && data[i + 1] == ' ')
  2164. return i + 2;
  2165. return i + 1;
  2166. }
  2167. return 0;
  2168. }
  2169. /* prefix_code • returns prefix length for block code*/
  2170. function prefix_code(data) {
  2171. if (data.length > 3 && data[0] == ' ' && data[1] == ' '
  2172. && data[2] == ' ' && data[3] == ' ') return 4;
  2173. return 0;
  2174. }
  2175. /* prefix_oli - returns ordered list item prefix */
  2176. function prefix_oli(data) {
  2177. var size = data.length;
  2178. var i = 0;
  2179. if (i < size && data[i] == ' ') i++;
  2180. if (i < size && data[i] == ' ') i++;
  2181. if (i < size && data[i] == ' ') i++;
  2182. if (i >= size || data[i] < '0' || data[i] > '9') return 0;
  2183. while (i < size && data[i] >= '0' && data[i] <= '9') i++;
  2184. if (i + 1 >= size || data[i] != '.' || data[i + 1] != ' ') return 0;
  2185. if (is_next_headerline(data.slice(i))) return 0;
  2186. return i + 2;
  2187. }
  2188. /* prefix_uli - returns ordered list item prefix */
  2189. function prefix_uli(data) {
  2190. var size = data.length;
  2191. var i = 0;
  2192. if (i < size && data[i] == ' ') i++;
  2193. if (i < size && data[i] == ' ') i++;
  2194. if (i < size && data[i] == ' ') i++;
  2195. if (i + 1 >= size ||
  2196. (data[i] != '*' && data[i] != '+' && data[i] != '-') ||
  2197. data[i + 1] != ' ')
  2198. return 0;
  2199. if (is_next_headerline(data.slice(i))) return 0;
  2200. return i + 2;
  2201. }
  2202. /* is_mail_autolink - looks for the address part of a mail autolink and '>' */
  2203. /* this is less strict than the original markdown e-mail address matching */
  2204. function is_mail_autolink(data) {
  2205. var i = 0, nb = 0;
  2206. /* address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@' */
  2207. for (i = 0; i < data.length; ++i) {
  2208. if (isalnum(data[i]))
  2209. continue;
  2210. switch (data[i]) {
  2211. case '@':
  2212. nb++;
  2213. case '-':
  2214. case '.':
  2215. case '_':
  2216. break;
  2217. case '>':
  2218. return (nb == 1) ? i + 1 : 0;
  2219. default:
  2220. return 0;
  2221. }
  2222. }
  2223. return 0;
  2224. }
  2225. /* tag_length - returns the length of the given tag, or 0 is it's not valid */
  2226. function tag_length(data, autolink) {
  2227. var i, j;
  2228. /* a valid tag can't be shorter than 3 chars */
  2229. if (data.length < 3) return 0;
  2230. /* begins with a '<' optionally followed by '/', followed by letter or number */
  2231. if (data[0] != '<') return 0;
  2232. i = (data[1] == '/') ? 2 : 1;
  2233. if (!isalnum(data[i])) return 0;
  2234. /* scheme test */
  2235. autolink.p = MKDA_NOT_AUTOLINK;
  2236. /* try to find the beginning of an URI */
  2237. while (i < data.length && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-')) i++;
  2238. if (i > 1 && data[i] == '@') {
  2239. if ((j = is_mail_autolink(data.slice(i))) != 0) {
  2240. autolink.p = MKDA_EMAIL;
  2241. return i + j;
  2242. }
  2243. }
  2244. if (i > 2 && data[i] == ':') {
  2245. autolink.p = MKDA_NORMAL;
  2246. i++;
  2247. }
  2248. /* completing autolink test: no whitespace or ' or " */
  2249. if (i >= data.length) autolink.p = MKDA_NOT_AUTOLINK;
  2250. else if (autolink.p) {
  2251. j = i;
  2252. while (i < data.length) {
  2253. if (data[i] == '\\') i += 2;
  2254. else if (data[i] == '>' || data[i] == '\'' ||
  2255. data[i] == '"' || data[i] == ' ' || data[i] == '\n')
  2256. break;
  2257. else i++;
  2258. }
  2259. if (i >= data.length) return 0;
  2260. if (i > j && data[i] == '>') return i + 1;
  2261. /* one of the forbidden chars has been found */
  2262. autolink.p = MKDA_NOT_AUTOLINK;
  2263. }
  2264. /* looking for sometinhg looking like a tag end */
  2265. while (i < data.length && data[i] != '>') i++;
  2266. if (i >= data.length) return 0;
  2267. return i + 1;
  2268. }
  2269. // parse_inline - parses inline markdown elements
  2270. //Buffer, md, String
  2271. function parse_inline(out, md, data) {
  2272. var i = 0, end = 0;
  2273. var action = 0;
  2274. var work = new Buffer();
  2275. if (md.spanStack.length + md.blockStack.length > md.nestingLimit)
  2276. return;
  2277. while (i < data.length) {
  2278. /* copying inactive chars into the output */
  2279. while (end < data.length && !(action = md.activeChars[data[end]])) {
  2280. end++;
  2281. }
  2282. if (md.callbacks.normal_text) {
  2283. work.s = data.slice(i, end);
  2284. md.callbacks.normal_text(out, work, md.context);
  2285. }
  2286. else
  2287. out.s += data.slice(i, end);
  2288. if (end >= data.length) break;
  2289. i = end;
  2290. end = markdown_char_ptrs[action](out, md, data, i);
  2291. if (!end) /* no action from the callback */
  2292. end = i + 1;
  2293. else {
  2294. i += end;
  2295. end = i;
  2296. }
  2297. }
  2298. }
  2299. /* parse_atxheader - parsing of atx-style headers */
  2300. function parse_atxheader(out, md, data) {
  2301. var level = 0;
  2302. var i, end, skip;
  2303. while (level < data.length && level < 6 && data[level] == '#') level++;
  2304. for (i = level; i < data.length && data[i] == ' '; i++) {}
  2305. for (end = i; end < data.length && data[end] != '\n'; end++) {}
  2306. skip = end;
  2307. while (end && data[end - 1] == '#') end--;
  2308. while (end && data[end - 1] == ' ') end--;
  2309. if (end > i) {
  2310. var work = new Buffer();
  2311. md.spanStack.push(work);
  2312. parse_inline(work, md, data.slice(i, end));
  2313. if (md.callbacks.header)
  2314. md.callbacks.header(out, work, level, md.context);
  2315. md.spanStack.pop();
  2316. }
  2317. return skip;
  2318. }
  2319. /* htmlblock_end - checking end of HTML block : </tag>[ \t]*\n[ \t*]\n */
  2320. /* returns the length on match, 0 otherwise */
  2321. // htmlblock_end(const char *tag, size_t tag_len, struct sd_markdown *rndr, uint8_t *data, size_t size)
  2322. function htmlblock_end(tag, md, data) {
  2323. var i, w;
  2324. /* checking if tag is a match */
  2325. //tag should already be lowercase
  2326. if (tag.length + 3 >= data.length ||
  2327. data.slice(2).toLowerCase() != tag ||
  2328. data[tag.length + 2] != '>')
  2329. return 0;
  2330. /* checking white lines */
  2331. i = tag.length + 3;
  2332. w = 0;
  2333. if (i < data.length && (w = is_empty(data.slice(i))) == 0)
  2334. return 0; /* non-blank after tag */
  2335. i += w;
  2336. w = 0;
  2337. if (i < data.length) w = is_empty(data.slice(i));
  2338. return i + w;
  2339. }
  2340. /* parse_htmlblock - parsing of inline HTML block */
  2341. //TODO
  2342. function parse_htmlblock(out, md, data, do_render) {
  2343. var i, j = 0;
  2344. var curtag = null;
  2345. var found;
  2346. var work = new Buffer(data);
  2347. /* identification of the opening tag */
  2348. if (data.length < 2 || data[0] != '<') return 0;
  2349. i = 1;
  2350. while (i < data.length && data[i] != '>' && data[i] != ' ') i++;
  2351. if (i < data.length) curtag = find_block_tag(data.slice(1));
  2352. /* handling of special cases */
  2353. if (!curtag) {
  2354. /* HTML comment, laxist form */
  2355. if (data.length > 5 && data[1] == '!' && data[2] == '-' && data[3] == '-') {
  2356. i = 5;
  2357. while (i < data.length && !(data[i - 2] == '-' && data[i - 1] == '-' && data[i] == '>')) i++;
  2358. i++;
  2359. if (i < size)
  2360. j = is_empty(data.slice(i));
  2361. if (j) {
  2362. //TODO: HANDLE WORK!!!
  2363. // work.size = i + j;
  2364. work.s = data.slice(0, i + j);
  2365. if (do_render && md.callbacks.blockhtml)
  2366. md.callbacks.blockhtml(out, work, md.context);
  2367. return work.s.length;
  2368. }
  2369. }
  2370. /* HR, which is the only self-closing block tag considered */
  2371. if (data.length > 4 && (data[1] == 'h' || data[1] == 'H') && (data[2] == 'r' || data[2] == 'R')) {
  2372. i = 3;
  2373. while (i < data.length && data[i] != '>') i++;
  2374. if (i + 1 < data.length) {
  2375. i++;
  2376. j = is_empty(data.slice(i));
  2377. if (j) {
  2378. work.s = data.slice(0, i + j);
  2379. if (do_render && md.callbacks.blockhtml)
  2380. md.callbacks.blockhtml(out, work, md.context);
  2381. return work.s.length;
  2382. }
  2383. }
  2384. }
  2385. /* no special case recognised */
  2386. return 0;
  2387. }
  2388. /* looking for an unindented matching closing tag */
  2389. /* followed by a blank line */
  2390. i = 1;
  2391. found = 0;
  2392. /* if not found, trying a second pass looking for indented match */
  2393. /* but not if tag is "ins" or "del" (following original Markdown.pl) */
  2394. if (curtag != 'ins' && curtag != 'del') {
  2395. var tag_size = curtag.length;
  2396. i = 1;
  2397. while (i < data.length) {
  2398. i++;
  2399. while (i < data.length && !(data[i - 1] == '<' && data[i] == '/'))
  2400. i++;
  2401. if (i + 2 + tag_size >= data.length)
  2402. break;
  2403. // j = htmlblock_end(tag, md, data + i - 1, size - i + 1);
  2404. //TODO
  2405. j = htmlblock_end(tag, md, data.slice(i - 1));
  2406. if (j) {
  2407. i += j - 1;
  2408. found = 1;
  2409. break;
  2410. }
  2411. }
  2412. }
  2413. if (!found) return 0;
  2414. /* the end of the block has been found */
  2415. //TODO:
  2416. work.s = work.s.slice(0, i);
  2417. if (do_render && md.callbacks.blockhtml)
  2418. md.callbacks.blockhtml(out, work, md.context);
  2419. return i;
  2420. }
  2421. /* parse_blockquote - handles parsing of a blockquote fragment */
  2422. function parse_blockquote(out, md, data) {
  2423. var size = data.length;
  2424. var beg, end = 0, pre, work_size = 0;
  2425. // uint8_t *work_data = 0;
  2426. var work_data = "";
  2427. var work_data_cursor = 0;
  2428. var out_ = new Buffer();
  2429. md.blockStack.push(out_);
  2430. beg = 0;
  2431. while (beg < size) {
  2432. for (end = beg + 1; end < size && data[end - 1] != '\n'; end++) {}
  2433. pre = prefix_quote(data.slice(beg, end));
  2434. if (pre) beg += pre; /* skipping prefix */
  2435. /* empty line followed by non-quote line */
  2436. else if (is_empty(data.slice(beg, end)) &&
  2437. (end >= size || (prefix_quote(data.slice(end)) == 0 &&
  2438. !is_empty(data.slice(end)))))
  2439. break;
  2440. if (beg < end) { /* copy into the in-place working buffer */
  2441. /* bufput(work, data + beg, end - beg); */
  2442. //TODO:!!! FIX THIS!!!
  2443. // if (!work_data) work_data = data.slice(beg, end);
  2444. // work_data = data + beg;
  2445. work_data += data.slice(beg, end);
  2446. /*
  2447. if (!work_data) work_data_cursor = beg;
  2448. else if (beg != work_data_cursor + work_size)
  2449. work_data += data.slice(beg, end);
  2450. */
  2451. // memmove(work_data + work_size, data + beg, end - beg);
  2452. work_size += end - beg;
  2453. }
  2454. beg = end;
  2455. }
  2456. parse_block(out_, md, work_data);
  2457. if (md.callbacks.blockquote)
  2458. md.callbacks.blockquote(out, out_, md.context);
  2459. md.blockStack.pop();
  2460. return end;
  2461. }
  2462. /* parse_paragraph - handles parsing of a regular paragraph */
  2463. function parse_paragraph(out, md, data) {
  2464. var i = 0, end = 0;
  2465. var level = 0;
  2466. var size = data.length;
  2467. var work = new Buffer(data);
  2468. while (i < size) {
  2469. for (end = i + 1; end < size && data[end - 1] != '\n'; end++) {/* empty */}
  2470. if (prefix_quote(data.slice(i, end)) != 0) {
  2471. end = i;
  2472. break;
  2473. }
  2474. var tempdata = data.slice(i);
  2475. if (is_empty(tempdata) || (level = is_headerline(tempdata)) != 0) break;
  2476. if (is_empty(tempdata)) break;
  2477. if ((level = is_headerline(tempdata)) != 0) break;
  2478. if (is_atxheader(md, tempdata)
  2479. || is_hrule(tempdata)
  2480. || prefix_quote(tempdata)) {
  2481. end = i;
  2482. break;
  2483. }
  2484. /*
  2485. * Early termination of a paragraph with the same logic
  2486. * as markdown 1.0.0. If this logic is applied, the
  2487. * Markdown 1.0.3 test suite wont pass cleanly.
  2488. *
  2489. * :: If the first character in a new line is not a letter
  2490. * lets check to see if there's some kind of block starting here
  2491. */
  2492. if ((md.extensions & MKDEXT_LAX_SPACING) && !isalnum(data[i])) {
  2493. if (prefix_oli(tempdata)
  2494. || prefix_uli(tempdata)) {
  2495. end = i;
  2496. break;
  2497. }
  2498. /* see if an html block starts here */
  2499. if (data[i] == '<' && md.callbacks.blockhtml
  2500. && parse_htmlblock(out, md, tempdata, 0)) {
  2501. end = i;
  2502. break
  2503. }
  2504. /* see if a code fence starts here */
  2505. if ((md.extensions && MKDEXT_FENCED_CODE) != 0
  2506. && is_codefence(tempdata, null) != 0) {
  2507. end = i;
  2508. break;
  2509. }
  2510. }
  2511. i = end;
  2512. }
  2513. var work_size = i;
  2514. while (work_size && data[work_size - 1] == '\n') work_size--;
  2515. work.s = work.s.slice(0, work_size);
  2516. if (!level) {
  2517. var tmp = new Buffer();
  2518. md.blockStack.push(tmp);
  2519. parse_inline(tmp, md, work.s);
  2520. if (md.callbacks.paragraph)
  2521. md.callbacks.paragraph(out, tmp, md.context);
  2522. md.blockStack.pop();
  2523. } else {
  2524. var header_work = null;
  2525. if (work.size) {
  2526. var beg;
  2527. i = work.s.length;
  2528. // var work_size = work.s.length - 1;
  2529. // work.size -= 1;
  2530. while (work_size && data[work_size] != '\n')
  2531. work_size -= 1;
  2532. beg = work_size + 1;
  2533. while (work_size && data[work_size - 1] == '\n')
  2534. work_size -= 1;
  2535. work.s = work.s.slice(0, work_size);
  2536. if (work_size > 0) {
  2537. var tmp = new Buffer();
  2538. md.blockStack.push(tmp);
  2539. parse_inline(tmp, md, work.s);
  2540. if (md.callbacks.paragraph)
  2541. md.callbacks.paragraph(out, tmp, md.context);
  2542. md.blockStack.pop();
  2543. work.s = work.s.slice(beg, i);
  2544. }
  2545. else work.s = work.s.slice(0, i);
  2546. }
  2547. header_work = new Buffer();
  2548. md.spanStack.push(header_work);
  2549. parse_inline(header_work, md, work.s);
  2550. if (md.callbacks.header)
  2551. md.callbacks.header(out, header_work, level, md.context);
  2552. md.spanStack.pop();
  2553. }
  2554. return end;
  2555. }
  2556. /* parse_fencedcode - handles parsing of a block-level code fragment */
  2557. function parse_fencedcode(out, md, data) {
  2558. var beg, end;
  2559. var work = null;
  2560. var lang = new Buffer();
  2561. beg = is_codefence(data, lang);
  2562. if (beg == 0) return 0;
  2563. work = new Buffer();
  2564. md.blockStack.push(work);
  2565. while (beg < data.length) {
  2566. var fence_end;
  2567. var fence_trail = new Buffer();
  2568. fence_end = is_codefence(data.slice(beg), fence_trail);
  2569. if (fence_end != 0 && fence_trail.s.length == 0) {
  2570. beg += fence_end;
  2571. break;
  2572. }
  2573. for (end = beg + 1; end < data.length && data[end - 1] != '\n'; end++) {}
  2574. if (beg < end) {
  2575. /* verbatim copy to the working buffer,
  2576. escaping entities */
  2577. var tempData = data.slice(beg, end);
  2578. if (is_empty(tempData)) work.s += '\n';
  2579. else work.s += tempData;
  2580. }
  2581. beg = end;
  2582. }
  2583. if (work.s.length && work.s[work.s.length - 1] != '\n')
  2584. work.s += '\n';
  2585. if (md.callbacks.blockcode)
  2586. md.callbacks.blockcode(out, work, lang.s.length ? lang : null, md.context);
  2587. md.blockStack.pop();
  2588. return beg;
  2589. }
  2590. function parse_blockcode(out, md, data) {
  2591. var size = data.length;
  2592. var beg, end, pre;
  2593. var work = null;
  2594. md.blockStack.push(work = new Buffer());
  2595. beg = 0;
  2596. while (beg < size) {
  2597. for (end = beg + 1; end < size && data[end - 1] != '\n'; end++) {};
  2598. pre = prefix_code(data.slice(beg, end));
  2599. if (pre) beg += pre; /* skipping prefix */
  2600. else if (!is_empty(data.slice(beg, end)))
  2601. /* non-empty non-prefixed line breaks the pre */
  2602. break;
  2603. if (beg < end) {
  2604. /* verbatim copy to the working buffer,
  2605. escaping entities */
  2606. if (is_empty(data.slice(beg, end))) work.s += '\n';
  2607. else work.s += data.slice(beg, end);
  2608. }
  2609. beg = end;
  2610. }
  2611. var work_size = work.s.length;
  2612. while (work_size && work.s[work_size - 1] == '\n') work_size -= 1;
  2613. work.s = work.s.slice(0, work_size);
  2614. work.s += '\n';
  2615. if (md.callbacks.blockcode)
  2616. md.callbacks.blockcode(out, work, null, md.context);
  2617. md.blockStack.pop();
  2618. return beg;
  2619. }
  2620. /* parse_listitem - parsing of a single list item */
  2621. /* assuming initial prefix is already removed */
  2622. //FLAGS is pointer
  2623. function parse_listitem(out, md, data, flags) {
  2624. var size = data.length;
  2625. var work = null, inter = null;
  2626. var beg = 0, end, pre, sublist = 0, orgpre = 0, i;
  2627. var in_empty = 0, has_inside_empty = 0, in_fence = 0;
  2628. /* keeping track of the first indentation prefix */
  2629. while (orgpre < 3 && orgpre < size && data[orgpre] == ' ')
  2630. orgpre++;
  2631. //TODO
  2632. beg = prefix_uli(data);
  2633. if (!beg) beg = prefix_oli(data);
  2634. if (!beg) return 0;
  2635. /* skipping to the beginning of the following line */
  2636. end = beg;
  2637. while (end < size && data[end - 1] != '\n') end++;
  2638. /* getting working buffers */
  2639. md.spanStack.push(work = new Buffer());
  2640. md.spanStack.push(inter = new Buffer());
  2641. /* putting the first line into the working buffer */
  2642. work.s += data.slice(beg, end);
  2643. beg = end;
  2644. /* process the following lines */
  2645. while (beg < size) {
  2646. var has_next_uli, has_next_oli;
  2647. end++;
  2648. while (end < size && data[end - 1] != '\n') end++;
  2649. /* process an empty line */
  2650. if (is_empty(data.slice(beg, end))) {
  2651. in_empty = 1;
  2652. beg = end;
  2653. continue;
  2654. }
  2655. /* calculating the indentation */
  2656. i = 0;
  2657. while (i < 4 && beg + i < end && data[beg + i] == ' ') i++;
  2658. pre = i;
  2659. //TODO: Cache this slice?
  2660. if (md.flags & MKDEXT_FENCED_CODE) {
  2661. if (is_codefence(data.slice(beg+i, end), null) != 0) {
  2662. in_fence = !in_fence;
  2663. }
  2664. }
  2665. /* only check for new list items if we are **not** in a fenced code block */
  2666. if (!in_fence) {
  2667. has_next_uli = prefix_uli(data.slice(beg+i, end));
  2668. has_next_oli = prefix_oli(data.slice(beg+i, end));
  2669. }
  2670. /* checking for ul/ol switch */
  2671. if (in_empty && (
  2672. ((flags.p & MKD_LIST_ORDERED) && has_next_uli) ||
  2673. (!(flags.p & MKD_LIST_ORDERED) && has_next_oli))){
  2674. flags.p |= MKD_LI_END;
  2675. break; /* the following item must have same list type */
  2676. }
  2677. /* checking for a new item */
  2678. if ((has_next_uli && !is_hrule(data.slice(beg+i, end))) || has_next_oli) {
  2679. if (in_empty) has_inside_empty = 1;
  2680. if (pre == orgpre) /* the following item must have */
  2681. break; /* the same indentation */
  2682. if (!sublist) sublist = work.s.length;
  2683. }
  2684. /* joining only indented stuff after empty lines;
  2685. * note that now we only require 1 space of indentation
  2686. * to continue list */
  2687. else if (in_empty && pre == 0) {
  2688. flags.p |= MKD_LI_END;
  2689. break;
  2690. }
  2691. else if (in_empty) {
  2692. work.s += '\n';
  2693. has_inside_empty = 1;
  2694. }
  2695. in_empty = 0;
  2696. /* adding the line without prefix into the working buffer */
  2697. work.s += data.slice(beg + i, end);
  2698. beg = end;
  2699. }
  2700. /* render of li contents */
  2701. if (has_inside_empty) flags.p |= MKD_LI_BLOCK;
  2702. if (flags.p & MKD_LI_BLOCK) {
  2703. /* intermediate render of block li */
  2704. if (sublist && sublist < work.s.length) {
  2705. parse_block(inter, md, work.s.slice(0, sublist));
  2706. parse_block(inter, md, work.s.slice(sublist));
  2707. }
  2708. else
  2709. parse_block(inter, md, work.s);
  2710. } else {
  2711. //TODO:
  2712. /* intermediate render of inline li */
  2713. if (sublist && sublist < work.s.length) {
  2714. parse_inline(inter, md, work.s.slice(0, sublist));
  2715. parse_block(inter, md, work.s.slice(sublist));
  2716. }
  2717. else
  2718. parse_inline(inter, md, work.s);
  2719. }
  2720. /* render of li itself */
  2721. if (md.callbacks.listitem)
  2722. md.callbacks.listitem(out, inter, flags.p, md.context);
  2723. md.spanStack.pop();
  2724. md.spanStack.pop();
  2725. return beg;
  2726. }
  2727. /* parse_list - parsing ordered or unordered list block */
  2728. function parse_list(out, md, data, flags) {
  2729. var size = data.length;
  2730. var i = 0, j;
  2731. var work = null;
  2732. md.blockStack.push(work = new Buffer());
  2733. while (i < size) {
  2734. var flag_p = {p: flags};
  2735. j = parse_listitem(work, md, data.slice(i), flag_p);
  2736. flags = flag_p.p;
  2737. i += j;
  2738. if (!j || (flags & MKD_LI_END)) break;
  2739. }
  2740. if (md.callbacks.list)
  2741. md.callbacks.list(out, work, flags, md.context);
  2742. md.blockStack.pop();
  2743. return i;
  2744. }
  2745. function parse_table_row(out, md, data, columns, header_flag) {
  2746. var i = 0, col;
  2747. var row_work = null;
  2748. if (!md.callbacks.table_cell || !md.callbacks.table_row) return;
  2749. md.spanStack.push(row_work = new Buffer());
  2750. if (i < data.length && data[i] == '|') i++;
  2751. for (col = 0; col < columns.length && i < data.length; ++col) {
  2752. var cell_start, cell_end;
  2753. var cell_work;
  2754. md.spanStack.push(cell_work = new Buffer());
  2755. while (i < data.length && _isspace(data[i])) i++;
  2756. cell_start = i;
  2757. while (i < data.length && data[i] != '|') i++;
  2758. cell_end = i - 1;
  2759. while (cell_end > cell_start && _isspace(data[cell_end])) cell_end--;
  2760. // parse_inline(cell_work, rndr, data + cell_start, 1 + cell_end - cell_start);
  2761. parse_inline(cell_work, md, data.slice(cell_start, 1 + cell_end));
  2762. md.callbacks.table_cell(row_work, cell_work, columns[col] | header_flag, md.context);
  2763. md.spanStack.pop();
  2764. i++;
  2765. }
  2766. for (; col < columns.length; ++col) {
  2767. var empty_cell = null;
  2768. md.callbacks.table_cell(row_work, empty_cell, columns[col] | header_flag, md.context);
  2769. }
  2770. md.callbacks.table_row(out, row_work, md.context);
  2771. md.spanStack.pop();
  2772. }
  2773. function parse_table_header(out, md, data, columns) {
  2774. var i = 0, col, header_end, under_end;
  2775. var pipes = 0;
  2776. while (i < data.length && data[i] != '\n')
  2777. if (data[i++] == '|') pipes++;
  2778. if (i == data.length || pipes == 0)
  2779. return 0;
  2780. header_end = i;
  2781. while (header_end > 0 && _isspace(data[header_end - 1])) header_end--;
  2782. if (data[0] == '|') pipes--;
  2783. if (header_end && data[header_end - 1] == '|') pipes--;
  2784. // columns.p = pipes + 1;
  2785. // column_data.p = new Array(columns.p);
  2786. columns.p = new Array(pipes + 1);
  2787. for (var k = 0; k < columns.p.length; k++) columns.p[k] = 0;
  2788. /* Parse the header underline */
  2789. i++;
  2790. if (i < data.length && data[i] == '|') i++;
  2791. under_end = i;
  2792. while (under_end < data.length && data[under_end] != '\n') under_end++;
  2793. for (col = 0; col < columns.p.length && i < under_end; ++col) {
  2794. var dashes = 0;
  2795. while (i < under_end && data[i] == ' ') i++;
  2796. if (data[i] == ':') {
  2797. i++;
  2798. columns.p[col] |= MKD_TABLE_ALIGN_L;
  2799. dashes++;
  2800. }
  2801. while (i < under_end && data[i] == '-') {
  2802. i++; dashes++;
  2803. }
  2804. if (i < under_end && data[i] == ':') {
  2805. i++; columns.p[col] |= MKD_TABLE_ALIGN_R;
  2806. dashes++;
  2807. }
  2808. while (i < under_end && data[i] == ' ') i++;
  2809. if (i < under_end && data[i] != '|') break;
  2810. if (dashes < 1) break;
  2811. i++;
  2812. }
  2813. if (col < columns.p.length) return 0;
  2814. parse_table_row(out, md, data.slice(0, header_end), columns.p, MKD_TABLE_HEADER);
  2815. return under_end + 1;
  2816. }
  2817. function parse_table(out, md, data) {
  2818. var i;
  2819. var header_work, body_work;
  2820. var columns = {p: null};
  2821. md.spanStack.push(header_work = new Buffer());
  2822. md.blockStack.push(body_work = new Buffer());
  2823. i = parse_table_header(header_work, md, data, columns);
  2824. if (i > 0) {
  2825. while (i < data.length) {
  2826. var row_start;
  2827. var pipes = 0;
  2828. row_start = i;
  2829. while (i < data.length && data[i] != '\n')
  2830. if (data[i++] == '|')
  2831. pipes++;
  2832. if (pipes == 0 || i == data.length) {
  2833. i = row_start;
  2834. break;
  2835. }
  2836. parse_table_row(body_work, md, data.slice(row_start, i), columns.p, 0);
  2837. i++;
  2838. }
  2839. if (md.callbacks.table)
  2840. md.callbacks.table(out, header_work, body_work, md.context);
  2841. }
  2842. md.spanStack.pop();
  2843. md.blockStack.pop();
  2844. return i;
  2845. }
  2846. function parse_block(out, md, data) {
  2847. var beg = 0, end, i;
  2848. var textData;
  2849. if (md.spanStack.length +
  2850. md.blockStack.length > md.nestingLimit)
  2851. return;
  2852. while (beg < data.length) {
  2853. textData = data.slice(beg);
  2854. end = data.length - beg;
  2855. if (is_atxheader(md, textData))
  2856. beg += parse_atxheader(out, md, textData);
  2857. else if (data[beg] == '<' && md.callbacks.blockhtml &&
  2858. (i = parse_htmlblock(out, md, textData, 1)) != 0)
  2859. beg += i;
  2860. else if ((i = is_empty(textData)) != 0)
  2861. beg += i;
  2862. else if (is_hrule(textData)) {
  2863. if (md.callbacks.hrule)
  2864. md.callbacks.hrule(out, md.context);
  2865. while (beg < data.length && data[beg] != '\n') beg++;
  2866. beg++;
  2867. }
  2868. else if ((md.extensions & MKDEXT_FENCED_CODE) != 0 &&
  2869. (i = parse_fencedcode(out, md, textData)) != 0)
  2870. beg += i;
  2871. else if ((md.extensions & MKDEXT_TABLES) != 0 &&
  2872. (i = parse_table(out, md, textData)) != 0)
  2873. beg += i;
  2874. else if (prefix_quote(textData))
  2875. beg += parse_blockquote(out, md, textData);
  2876. else if (prefix_code(textData))
  2877. beg += parse_blockcode(out, md, textData);
  2878. else if (prefix_uli(textData))
  2879. beg += parse_list(out, md, textData, 0);
  2880. else if (prefix_oli(textData))
  2881. beg += parse_list(out, md, textData, MKD_LIST_ORDERED);
  2882. else {
  2883. beg += parse_paragraph(out, md, textData);
  2884. }
  2885. }
  2886. }
  2887. function is_ref(data, beg, end, md) {
  2888. /* int n; */
  2889. var i = 0;
  2890. var idOffset, idEnd;
  2891. var linkOffset, linkEnd;
  2892. var titleOffset, titleEnd;
  2893. var lineEnd;
  2894. /* up to 3 optional leading spaces */
  2895. if (beg + 3 >= end) return 0;
  2896. if (data[beg] == ' ') { i = 1;
  2897. if (data[beg + 1] == ' ') { i = 2;
  2898. if (data[beg + 2] == ' ') { i = 3;
  2899. if (data[beg + 3] == ' ') return 0; } } }
  2900. i += beg;
  2901. /* id part: anything but a newline between brackets */
  2902. if (data[i] != '[') return 0;
  2903. i++;
  2904. idOffset = i;
  2905. while (i < end && data[i] != '\n' && data[i] != '\r' && data[i] != ']') i++;
  2906. if (i >= end || data[i] != ']') return 0;
  2907. idEnd = i;
  2908. /* spacer: colon (space | tab)* newline? (space | tab)* */
  2909. i++;
  2910. if (i >= end || data[i] != ':') return 0;
  2911. i++;
  2912. while (i < end && data[i] == ' ') i++;
  2913. if (i < end && (data[i] == '\n' || data[i] == '\r')) {
  2914. i++;
  2915. if (i < end && data[i] == '\r' && data[i - 1] == '\n') i++; }
  2916. while (i < end && data[i] == ' ') i++;
  2917. if (i >= end) return 0;
  2918. /* link: whitespace-free sequence, optionally between angle brackets */
  2919. if (data[i] == '<') i++;
  2920. linkOffset = i;
  2921. while (i < end && data[i] != ' ' && data[i] != '\n' && data[i] != '\r') i++;
  2922. if (data[i - 1] == '>') linkEnd = i - 1; else linkEnd = i;
  2923. /* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */
  2924. while (i < end && data[i] == ' ') i++;
  2925. if (i < end && data[i] != '\n' && data[i] != '\r'
  2926. && data[i] != '\'' && data[i] != '"' && data[i] != '(')
  2927. return 0;
  2928. lineEnd = 0;
  2929. /* computing end-of-line */
  2930. if (i >= end || data[i] == '\r' || data[i] == '\n') lineEnd = i;
  2931. if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r')
  2932. lineEnd = i + 1;
  2933. /* optional (space|tab)* spacer after a newline */
  2934. if (lineEnd) {
  2935. i = lineEnd + 1;
  2936. while (i < end && data[i] == ' ') i++;
  2937. }
  2938. /* optional title: any non-newline sequence enclosed in '"()
  2939. alone on its line */
  2940. titleOffset = titleEnd = 0;
  2941. if (i + 1 < end && (data[i] == '\'' || data[i] == '"' || data[i] == '(')) {
  2942. i++;
  2943. titleOffset = i;
  2944. /* looking for EOL */
  2945. while (i < end && data[i] != '\n' && data[i] != '\r') i++;
  2946. if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r')
  2947. titleEnd = i + 1;
  2948. else titleEnd = i;
  2949. /* stepping back */
  2950. i -= 1;
  2951. while (i > titleOffset && data[i] == ' ')
  2952. i -= 1;
  2953. if (i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')')) {
  2954. lineEnd = titleEnd;
  2955. titleEnd = i;
  2956. }
  2957. }
  2958. if (!lineEnd || linkEnd == linkOffset)
  2959. return 0; /* garbage after the link empty link */
  2960. var id = data.slice(idOffset, idEnd);
  2961. var link = data.slice(linkOffset, linkEnd);
  2962. var title = null;
  2963. if (titleEnd > titleOffset) title = data.slice(titleOffset, titleEnd);
  2964. md.refs[id] = {
  2965. id: id,
  2966. link: new Buffer(link),
  2967. title: new Buffer(title)
  2968. };
  2969. return lineEnd;
  2970. }
  2971. function expand_tabs(out, line) {
  2972. var i = 0, tab = 0;
  2973. while (i < line.length) {
  2974. var org = i;
  2975. while (i < line.length && line[i] != '\t') {
  2976. i++; tab++;
  2977. }
  2978. if (i > org) out.s += line.slice(org, i);
  2979. if (i >= line.length) break;
  2980. do {
  2981. out.s += ' ';
  2982. tab++;
  2983. } while (tab % 4);
  2984. i++;
  2985. }
  2986. }
  2987. /**
  2988. Render markdown code to HTML.
  2989. @param {string} source Markdown code.
  2990. @returns {string} HTML code.
  2991. */
  2992. function render(source) {
  2993. var text = new Buffer();
  2994. var beg = 0, end;
  2995. this.refs = {};
  2996. while (beg < source.length) { /* iterating over lines */
  2997. if (end = is_ref(source, beg, source.length, this))
  2998. beg = end;
  2999. else { /* skipping to the next line */
  3000. end = beg;
  3001. while (end < source.length && source[end] != '\n' && source[end] != '\r') end++;
  3002. /* adding the line body if present */
  3003. if (end > beg) expand_tabs(text, source.slice(beg, end));
  3004. while (end < source.length && (source[end] == '\n' || source[end] == '\r')) {
  3005. /* add one \n per newline */
  3006. if (source[end] == '\n' || (end + 1 < source.length && source[end + 1] != '\n'))
  3007. text.s += '\n';
  3008. end++;
  3009. }
  3010. beg = end;
  3011. }
  3012. }
  3013. var out = new Buffer();
  3014. /* second pass: actual rendering */
  3015. if (this.callbacks.doc_header)
  3016. this.callbacks.doc_header(out, this.context);
  3017. if (text.s.length) {
  3018. /* adding a final newline if not already present */
  3019. if (text.s[text.s.length - 1] != '\n' && text.s[text.s.length - 1] != '\r')
  3020. text.s += '\n';
  3021. parse_block(out, this, text.s);
  3022. }
  3023. if (this.callbacks.doc_footer)
  3024. this.callbacks.doc_footer(out, this.context);
  3025. return out.s;
  3026. }
  3027. Markdown.prototype['render'] = render;
  3028. /**
  3029. Create a parser object using the given configuration parameters.
  3030. To get a Reddit equivelent configuration, pass no arguments.
  3031. @param {?Renderer=} renderer A renderer object.
  3032. @param {?Number=} extensions A series of OR'd extension flags. (Extension flags start with MKDEXT_)
  3033. @param {?Number=} nestingLimit The maximum depth to which inline elements can be nested.
  3034. @return {Markdown} A configured markdown object.
  3035. */
  3036. exports.getParser = function getParser(renderer, extensions, nestingLimit) {
  3037. var md = new Markdown();
  3038. if (renderer) md.callbacks = renderer.callbacks;
  3039. if (nestingLimit) md.nestingLimit = nestingLimit;
  3040. if (renderer) md.context = renderer.context;
  3041. if (extensions != undefined && extensions != null) md.extensions = extensions;
  3042. var cb = md.callbacks;
  3043. if (cb.emphasis || cb.double_emphasis || cb.triple_emphasis) {
  3044. md.activeChars['*'] = MD_CHAR_EMPHASIS;
  3045. md.activeChars['_'] = MD_CHAR_EMPHASIS;
  3046. if (md.extensions & MKDEXT_STRIKETHROUGH) md.activeChars['~'] = MD_CHAR_EMPHASIS;
  3047. }
  3048. if (cb.codespan) md.activeChars['`'] = MD_CHAR_CODESPAN;
  3049. if (cb.linebreak) md.activeChars['\n'] = MD_CHAR_LINEBREAK;
  3050. if (cb.image || cb.link) md.activeChars['['] = MD_CHAR_LINK;
  3051. md.activeChars['<'] = MD_CHAR_LANGLE;
  3052. md.activeChars['\\'] = MD_CHAR_ESCAPE;
  3053. md.activeChars['&'] = MD_CHAR_ENTITITY;
  3054. if (md.extensions & MKDEXT_AUTOLINK) {
  3055. md.activeChars[':'] = MD_CHAR_AUTOLINK_URL;
  3056. md.activeChars['@'] = MD_CHAR_AUTOLINK_EMAIL;
  3057. md.activeChars['w'] = MD_CHAR_AUTOLINK_WWW;
  3058. md.activeChars['/'] = MD_CHAR_AUTOLINK_SUBREDDIT_OR_USERNAME;
  3059. }
  3060. if (md.extensions & MKDEXT_SUPERSCRIPT) md.activeChars['^'] = MD_CHAR_SUPERSCRIPT;
  3061. return md;
  3062. }
  3063. var DEFAULT_BODY_FLAGS = HTML_SKIP_HTML | HTML_SKIP_IMAGES | HTML_SAFELINK | HTML_ESCAPE | HTML_USE_XHTML;
  3064. var DEFAULT_WIKI_FLAGS = HTML_SKIP_HTML | HTML_SAFELINK | HTML_ALLOW_ELEMENT_WHITELIST | HTML_ESCAPE | HTML_USE_XHTML;
  3065. var DEFAULT_HTML_ATTR_WHITELIST = ['colspan', 'rowspan', 'cellspacing', 'cellpadding', 'scope'];
  3066. var DEFAULT_HTML_ELEMENT_WHITELIST = ['tr', 'th', 'td', 'table', 'tbody', 'thead', 'tfoot', 'caption'];
  3067. exports.DEFAULT_HTML_ELEMENT_WHITELIST = DEFAULT_HTML_ELEMENT_WHITELIST;
  3068. exports.DEFAULT_HTML_ATTR_WHITELIST = DEFAULT_HTML_ATTR_WHITELIST;
  3069. exports.DEFAULT_BODY_FLAGS = DEFAULT_BODY_FLAGS;
  3070. exports.DEFAULT_WIKI_FLAGS = DEFAULT_WIKI_FLAGS;
  3071. exports.HTML_SKIP_HTML = HTML_SKIP_HTML;
  3072. exports.HTML_SKIP_STYLE = HTML_SKIP_STYLE;
  3073. exports.HTML_SKIP_IMAGES = HTML_SKIP_IMAGES;
  3074. exports.HTML_SKIP_LINKS = HTML_SKIP_LINKS;
  3075. exports.HTML_EXPAND_TABS = HTML_EXPAND_TABS;
  3076. exports.HTML_SAFELINK = HTML_SAFELINK;
  3077. exports.HTML_TOC = HTML_TOC;
  3078. exports.HTML_HARD_WRAP = HTML_HARD_WRAP;
  3079. exports.HTML_USE_XHTML = HTML_USE_XHTML;
  3080. exports.HTML_ESCAPE = HTML_ESCAPE;
  3081. exports.HTML_ALLOW_ELEMENT_WHITELIST = HTML_ALLOW_ELEMENT_WHITELIST;
  3082. exports.MKDEXT_NO_INTRA_EMPHASIS = MKDEXT_NO_INTRA_EMPHASIS;
  3083. exports.MKDEXT_TABLES = MKDEXT_TABLES;
  3084. exports.MKDEXT_FENCED_CODE = MKDEXT_FENCED_CODE;
  3085. exports.MKDEXT_AUTOLINK = MKDEXT_AUTOLINK;
  3086. exports.MKDEXT_STRIKETHROUGH = MKDEXT_STRIKETHROUGH;
  3087. exports.MKDEXT_SPACE_HEADERS = MKDEXT_SPACE_HEADERS;
  3088. exports.MKDEXT_SUPERSCRIPT = MKDEXT_SUPERSCRIPT;
  3089. exports.MKDEXT_LAX_SPACING = MKDEXT_LAX_SPACING;
  3090. exports['SD_AUTOLINK_SHORT_DOMAINS'] = SD_AUTOLINK_SHORT_DOMAINS;
  3091. exports.MKDA_NOT_AUTOLINK = MKDA_NOT_AUTOLINK;
  3092. exports.MKDA_NORMAL = MKDA_NORMAL;
  3093. exports.MKDA_EMAIL = MKDA_EMAIL;
  3094. if (typeof define === 'function') {
  3095. define('snuownd', [], exports);
  3096. }
  3097. })(typeof(exports)!=='undefined'?exports:typeof(window)!=='undefined'?window.SnuOwnd={}:{});