/libs/snuownd.js
JavaScript | 3835 lines | 3128 code | 204 blank | 503 comment | 416 complexity | 8f54cc06d352d2d5ec12191843ae3072 MD5 | raw file
Possible License(s): Apache-2.0
- /**
- @preserve snuownd.js - javascript port of reddit's "snudown" markdown parser
- https://github.com/gamefreak/snuownd
- */
- /**
- * @license Copyright (c) 2009, Natacha Porté
- * Copyright (c) 2011, Vicent Marti
- * Copyright (c) 2012, Scott McClaugherty
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
- // up to date with commit b6baacb79996cec794a20d3abcae51adec5cc3cd
- /**
- @module SnuOwnd
- */
- (function(exports){
- function _isspace(c) {return c == ' ' || c == '\n';}
- function isspace(c) {return /[\x09-\x0d ]/.test(c);}
- function isalnum(c) { return /[A-Za-z0-9]/.test(c); }
- function isalpha(c) { return /[A-Za-z]/.test(c); }
- function ispunct(c) {return /[\x20-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]/.test(c); }
- function urlHexCode(number) {
- var hex_str = '0123456789ABCDEF';
- return '%'+hex_str[(number&0xf0)>>4]+hex_str[(number&0x0f)>>0];
- }
- function escapeUTF8Char(char) {
- var code = char.charCodeAt(0);
- if (code < 0x80) {
- return urlHexCode(code);
- } else if((code > 0x7f) && (code < 0x0800)) {
- var seq = urlHexCode(code >> 6 & 0xff | 0xc0);
- seq += urlHexCode(code >> 0 & 0x3f | 0x80);
- return seq;
- } else {
- var seq = urlHexCode(code >> 12 & 0xff | 0xe0);
- seq += urlHexCode(code >> 6 & 0x3f | 0x80);
- seq += urlHexCode(code >> 0 & 0x3f | 0x80);
- return seq;
- }
- }
- function find_block_tag (str) {
- var wordList = [
- 'p', 'dl', 'div', 'math',
- 'table', 'ul', 'del', 'form',
- 'blockquote', 'figure', 'ol', 'fieldset',
- 'h1', 'h6', 'pre', 'script',
- 'h5', 'noscript', 'style', 'iframe',
- 'h4', 'ins', 'h3', 'h2'
- ];
- if (wordList.indexOf(str.toLowerCase()) != -1) {
- return str.toLowerCase();
- }
- return '';
- }
- function sdhtml_is_tag(tag_data, tagname) {
- var i;
- var closed = 0;
- var tag_size = tag_data.length;
- if (tag_size < 3 || tag_data[0] != '<') return HTML_TAG_NONE;
- i = 1;
- if (tag_data[i] == '/') {
- closed = 1;
- i++;
- }
- var tagname_c = 0;
- for (; i < tag_size; ++i, ++tagname_c) {
- if (tagname_c >= tagname.length) break;
- if (tag_data[i] != tagname[tagname_c]) return HTML_TAG_NONE;
- }
- if (i == tag_size) return HTML_TAG_NONE;
- if (isspace(tag_data[i]) || tag_data[i] == '>')
- return closed ? HTML_TAG_CLOSE : HTML_TAG_OPEN;
- return HTML_TAG_NONE;
- }
- function unscape_text(out, src) {
- var i = 0, org;
- while (i < src.s.length) {
- org = i;
- while (i < src.s.length && src.s[i] != '\\') i++;
- if (i > org) out.s += src.s.slice(org, i);
- if (i + 1 >= src.s.length) break;
- out.s += src.s[i + 1];
- i += 2;
- }
- }
- /**
- * According to the OWASP rules:
- *
- * & --> &
- * < --> <
- * > --> >
- * " --> "
- * ' --> ' ' is not recommended
- * / --> / forward slash is included as it helps end an HTML entity
- *
- */
- var HTML_ESCAPE_TABLE = [
- 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0, 7, 7, 0, 7, 7,
- 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 4,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- ];
- var HTML_ESCAPES = ["", """, "&", "'", "/", "<", ">", "" /* throw out control characters */ ];
- function escape_html(out, src, secure) {
- var i = 0, org, esc = 0;
- while (i < src.length) {
- org = i;
- while (i < src.length && !(esc = HTML_ESCAPE_TABLE[src.charCodeAt(i)]))
- i++;
- if (i > org) out.s += src.slice(org, i);
- /* escaping */
- if (i >= src.length) break;
- /* The forward slash is only escaped in secure mode */
- if (src[i] == '/' && !secure) {
- out.s += '/';
- } else if (HTML_ESCAPE_TABLE[src.charCodeAt(i)] == 7) {
- /* skip control characters */
- } else {
- out.s += HTML_ESCAPES[esc];
- }
- i++;
- }
- }
- /*
- * The following characters will not be escaped:
- *
- * -_.+!*'(),%#@?=;:/,+&$ alphanum
- *
- * Note that this character set is the addition of:
- *
- * - The characters which are safe to be in an URL
- * - The characters which are *not* safe to be in
- * an URL because they are RESERVED characters.
- *
- * We asume (lazily) that any RESERVED char that
- * appears inside an URL is actually meant to
- * have its native function (i.e. as an URL
- * component/separator) and hence needs no escaping.
- *
- * There are two exceptions: the chacters & (amp)
- * and ' (single quote) do not appear in the table.
- * They are meant to appear in the URL as components,
- * yet they require special HTML-entity escaping
- * to generate valid HTML markup.
- *
- * All other characters will be escaped to %XX.
- *
- */
- var HREF_SAFE = [
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
- 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- ];
- function escape_href(out, src) {
- var i = 0, org;
- while (i < src.length) {
- org = i;
- while (i < src.length && HREF_SAFE[src.charCodeAt(i)] != 0) i++;
- if (i > org) out.s += src.slice(org, i);
- /* escaping */
- if (i >= src.length) break;
- /* throw out control characters */
- if (HREF_SAFE[src.charCodeAt(i)] == 2) {
- i++;
- continue;
- }
- switch (src[i]) {
- /* amp appears all the time in URLs, but needs
- * HTML-entity escaping to be inside an href */
- case '&':
- out.s += '&';
- break;
- /* the single quote is a valid URL character
- * according to the standard; it needs HTML
- * entity escaping too */
- case '\'':
- out.s += ''';
- break;
- /* the space can be escaped to %20 or a plus
- * sign. we're going with the generic escape
- * for now. the plus thing is more commonly seen
- * when building GET strings */
- /*
- //This was disabled
- case ' ':
- out.s += '+'
- break;
- //*/
- /* every other character goes with a %XX escaping */
- default:
- out.s += escapeUTF8Char(src[i]);
- /*
- var cc = src.charCodeAt(i);
- hex_str[1] = hex_chars[(cc >> 4) & 0xF];
- hex_str[2] = hex_chars[cc & 0xF];
- out.s += hex_str.join('');
- */
- }
- i++;
- }
- }
- // function autolink_delim(uint8_t *data, size_t link_end, size_t offset, size_t size)
- function autolink_delim(data, link_end) {
- var cclose, copen = 0;
- var i;
- for (i = 0; i < link_end; ++i)
- if (data[i] == '<') {
- link_end = i;
- break;
- }
- while (link_end > 0) {
- if ('?!.,'.indexOf(data[link_end - 1]) != -1) link_end--;
- else if (data[link_end - 1] == ';') {
- var new_end = link_end - 2;
- while (new_end > 0 && isalpha(data[new_end])) new_end--;
- if (new_end < link_end - 2 && data[new_end] == '&')
- link_end = new_end;
- else link_end--;
- }
- else break;
- }
- if (link_end == 0) return 0;
- cclose = data[link_end - 1];
- switch (cclose) {
- case '"': copen = '"'; break;
- case '\'': copen = '\''; break;
- case ')': copen = '('; break;
- case ']': copen = '['; break;
- case '}': copen = '{'; break;
- }
- if (copen != 0) {
- var closing = 0;
- var opening = 0;
- var j = 0;
- /* Try to close the final punctuation sign in this same line;
- * if we managed to close it outside of the URL, that means that it's
- * not part of the URL. If it closes inside the URL, that means it
- * is part of the URL.
- *
- * Examples:
- *
- * foo http://www.pokemon.com/Pikachu_(Electric) bar
- * => http://www.pokemon.com/Pikachu_(Electric)
- *
- * foo (http://www.pokemon.com/Pikachu_(Electric)) bar
- * => http://www.pokemon.com/Pikachu_(Electric)
- *
- * foo http://www.pokemon.com/Pikachu_(Electric)) bar
- * => http://www.pokemon.com/Pikachu_(Electric))
- *
- * (foo http://www.pokemon.com/Pikachu_(Electric)) bar
- * => foo http://www.pokemon.com/Pikachu_(Electric)
- */
- while (j < link_end) {
- if (data[j] == copen) opening++;
- else if (data[j] == cclose) closing++;
- j++;
- }
- if (closing != opening) link_end--;
- }
- return link_end;
- }
- function check_domain(data, allow_short) {
- var i, np = 0;
- if (!isalnum(data[0])) return 0;
- for (i = 1; i < data.length - 1; ++i) {
- if (data[i] == '.') np++;
- else if (!isalnum(data[i]) && data[i] != '-') break;
- }
- /* a valid domain needs to have at least a dot.
- * that's as far as we get */
- if (allow_short) {
- /* We don't need a valid domain in the strict sence (with
- * at least one dot; so just make sure it's composed of valid
- * domain characters and return the length of the valid
- * sequence. */
- return i;
- } else {
- return np ? i : 0;
- }
- }
- function sd_autolink_issafe(link) {
- var valid_uris = [
- "http://", "https://", "ftp://", "mailto://",
- "/", "git://", "steam://", "irc://", "news://", "mumble://",
- "ssh://", "ircs://", "#"];
- var i;
- for (i = 0; i < valid_uris.length; ++i) {
- var len = valid_uris[i].length;
- if (link.length > len &&
- link.toLowerCase().indexOf(valid_uris[i]) == 0 &&
- /[A-Za-z0-9#\/?]/.test(link[len]))
- return 1;
- }
- return 0;
- }
- function sd_autolink__url(rewind_p, link, data_, offset, size, flags) {
- var data = data_.slice(offset);
- var link_end, rewind = 0, domain_len;
- if (size < 4 || data_[offset+1] != '/' || data_[offset+2] != '/') return 0;
- while (rewind < offset && isalpha(data_[offset-rewind - 1])) rewind++;
- if (!sd_autolink_issafe(data_.substr(offset-rewind, size+rewind))) return 0;
- link_end = "://".length;
- domain_len = check_domain(data.slice(link_end), flags & SD_AUTOLINK_SHORT_DOMAINS);
- if (domain_len == 0) return 0;
- link_end += domain_len;
- while (link_end < size && !isspace(data_[offset+link_end])) link_end++;
- link_end = autolink_delim(data, link_end);
- if (link_end == 0) return 0;
- //TODO
- link.s += data_.substr(offset-rewind, link_end+rewind);
- rewind_p.p = rewind;
- return link_end;
- }
- function sd_autolink__subreddit(rewind_p, link, data_, offset, size) {
- var data = data_.slice(offset);
- var link_end;
- var allMinus = false;
- if (size < 3) return 0;
- /* make sure this / is part of /r/ */
- if (data.indexOf('/r/') != 0) return 0;
- link_end = "/r/".length;
- if (data.substr(link_end-1, 4).toLowerCase() == "all-") {
- allMinus = true;
- }
- do {
- var start = link_end;
- var max_length = 24;
- /* special case: /r/reddit.com (the only subreddit with a '.') */
- if ( size >= link_end+10 && data.substr(link_end, 10).toLowerCase() == 'reddit.com') {
- link_end += 10;
- max_length = 10;
- } else {
- /* If not the special case make sure it starts with (t:)?[A-Za-z0-9] */
- /* support autolinking to timereddits, /r/t:when (1 April 2012) */
- if ( size > link_end+2 && data.substr(link_end, 2) == 't:')
- link_end += 2; /* Jump over the 't:' */
- /* the first character of a subreddit name must be a letter or digit */
- if (!isalnum(data[link_end]))
- return 0;
- link_end += 1;
- }
- /* consume valid characters ([A-Za-z0-9_]) until we run out */
- while (link_end < size && (isalnum(data[link_end]) ||
- data[link_end] == '_'))
- link_end++;
- /* valid subreddit names are between 3 and 21 characters, with
- * some subreddits having 2-character names. Don't bother with
- * autolinking for anything outside this length range.
- * (chksrname function in reddit/.../validator.py) */
- if ( link_end-start < 2 || link_end-start > max_length )
- return 0;
- /* If we are linking to a multireddit, continue */
- } while ( link_end < size && (data[link_end] == '+' || (allMinus && data[link_end] == '-')) && link_end++ );
- if (link_end < size && data[link_end] == '/') {
- while (link_end < size && (isalnum(data[link_end]) ||
- data[link_end] == '_' ||
- data[link_end] == '/' ||
- data[link_end] == '-')) {
- link_end++;
- }
- }
- /* make the link */
- link.s += data.slice(0, link_end);
- rewind_p.p = 0;
- return link_end;
- }
- function sd_autolink__username(rewind_p, link, data_, offset, size) {
- var data = data_.slice(offset);
- var link_end;
- if (size < 6) return 0;
- /* make sure this / is part of /u/ */
- if (data.indexOf('/u/') != 0) return 0;
- /* the first letter of a username must... well, be valid, we don't care otherwise */
- link_end = "/u/".length;
- if (!isalnum(data[link_end]) && data[link_end] != '_' && data[link_end] != '-')
- return 0;
- link_end += 1;
- /* consume valid characters ([A-Za-z0-9_-/]) until we run out */
- while (link_end < size && (isalnum(data[link_end]) ||
- data[link_end] == '_' ||
- data[link_end] == '/' ||
- data[link_end] == '-'))
- link_end++;
- /* make the link */
- link.s += data.slice(0, link_end);
- rewind_p.p = 0;
- return link_end;
- }
- function sd_autolink__email(rewind_p, link, data_, offset, size, flags) {
- var data = data_.slice(offset);
- var link_end, rewind;
- var nb = 0, np = 0;
- for (rewind = 0; rewind < offset; ++rewind) {
- var c = data_[offset-rewind - 1];
- if (isalnum(c)) continue;
- if (".+-_".indexOf(c) != -1) continue;
- break;
- }
- if (rewind == 0) return 0;
- for (link_end = 0; link_end < size; ++link_end) {
- var c = data_[offset+link_end];
- if (isalnum(c)) continue;
- if (c == '@') nb++;
- else if (c == '.' && link_end < size - 1) np++;
- else if (c != '-' && c != '_') break;
- }
- if (link_end < 2 || nb != 1 || np == 0) return 0;
- //TODO
- link_end = autolink_delim(data, link_end);
- if (link_end == 0) return 0;
- // link.s += data_.slice(offset - rewind, link_end + rewind
- link.s += data_.substr(offset - rewind, link_end + rewind);
- rewind_p.p = rewind;
- return link_end;
- }
- function sd_autolink__www(rewind_p, link, data_, offset, size, flags) {
- var data = data_.slice(offset);
- var link_end;
- if (offset > 0 && !ispunct(data_[offset-1]) && !isspace(data_[offset-1]))
- return 0;
- // if (size < 4 || memcmp(data, "www.", strlen("www.")) != 0)
- if (size < 4 || (data.slice(0,4) != 'www.')) return 0;
- link_end = check_domain(data, 0);
- if (link_end == 0)
- return 0;
- while (link_end < size && !isspace(data[link_end])) link_end++;
- link_end = autolink_delim(data, link_end);
- if (link_end == 0) return 0;
- link.s += data.slice(0, link_end);
- rewind_p.p = 0;
- return link_end;
- }
- /**
- Initialize a Callbacks object.
- @constructor
- @param {Object.<string, ?function>} callbacks A set of callbacks to use as the methods on this object.
- */
- function Callbacks(callbacks) {
- if (callbacks) {
- for (var name in callbacks) {
- if (name in this) this[name] = callbacks[name];
- }
- }
- }
- Callbacks.prototype = {
- /**
- Renders a code block.
- Syntax highlighting specific to lanugage may be performed here.
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The input text.
- @param {Buffer} language The name of the code langage.
- @param {?Object} context A renderer specific context object.
- */
- blockcode: null,
- /**
- Renders a blockquote.
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The input text.
- @param {?Object} context A renderer specific context object.
- */
- blockquote: null,
- /**
- Renders a block of HTML code.
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The input text.
- @param {?Object} context A renderer specific context object.
- */
- blockhtml: null,
- /**
- Renders a header.
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The input text.
- @param {Number} level The header level.
- @param {?Object} context A renderer specific context object.
- */
- header: null,
- /**
- Renders a horizontal rule.
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {?Object} context A renderer specific context object.
- */
- hrule: null,
- /**
- Renders a list.
- <p>
- This method handles the list wrapper, which in terms of HTML would be <ol> or <ul>.
- This method is not responsible for handling list elements, all such processing should
- already have occured on text pased to the method . All that it is intended
- to do is to wrap the text parameter in anything needed.
- </p>
- @example
- out.s += "<ul>" + text.s + "</ul>"
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The input that goes inside the list.
- @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
- @param {?Object} context A renderer specific context object.
- */
- list: null,
- /**
- Renders a list.
- <p>
- Wraps the text in a list element.
- </p>
- @example
- out.s += "<li>" + text.s + "</li>"
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The contents of the list element.
- @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.
- @param {?Object} context A renderer specific context object.
- */
- listitem: null,
- /**
- Renders a paragraph.
- @example
- out.s += "<p>" + text.s + "</p>";
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The input text.
- @param {?Object} context A renderer specific context object.
- */
- paragraph: null,
- /**
- Renders a table.
- @example
- out.s += "<table><thead>";
- out.s += header.s;
- out.s += "</thead><tbody>";
- out.s += body.s;
- out.s += "</tbody></table>";
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} head The table header.
- @param {Buffer} body The table body.
- @param {?Object} context A renderer specific context object.
- */
- table: null,
- /**
- Renders a table row.
- @example
- out.s += "<tr>" + text.s + "</tr>";
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The input text.
- @param {?Object} context A renderer specific context object.
- */
- table_row: null,
- /**
- Renders a table cell.
- @example
- out.s += "<td>" + text.s + "</td>";
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The input text.
- @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.
- @param {?Object} context A renderer specific context object.
- */
- table_cell: null,
- /**
- Renders a link that was autodetected.
- @example
- out.s += "<a href=\""+ text.s + "\">" + text.s + "</a>";
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The address being linked to.
- @param {Number} type Equal to MKDA_NORMAL or MKDA_EMAIL
- @param {?Object} context A renderer specific context object.
- @returns {Boolean} Whether or not the tag was rendered.
- */
- autolink: null,
- /**
- Renders inline code.
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The text being wrapped.
- @param {?Object} context A renderer specific context object.
- @returns {Boolean} Whether or not the tag was rendered.
- */
- codespan: null,
- /**
- Renders text with double emphasis. Default is equivalent to the HTML <strong> tag.
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The text being wrapped.
- @param {?Object} context A renderer specific context object.
- @returns {Boolean} Whether or not the tag was rendered.
- */
- double_emphasis: null,
- /**
- Renders text with single emphasis. Default is equivalent to the HTML <em> tag.
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The text being wrapped.
- @param {?Object} context A renderer specific context object.
- @returns {Boolean} Whether or not the tag was rendered.
- */
- emphasis: null,
- /**
- Renders an image.
- @example
- out.s = "<img src=\"" + link.s + "\" title=\"" + title.s + "\" alt=\"" + alt.s + "\"/>";"
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} link The address of the image.
- @param {Buffer} title Title text for the image
- @param {Buffer} alt Alt text for the image
- @param {?Object} context A renderer specific context object.
- @returns {Boolean} Whether or not the tag was rendered.
- */
- image: null,
- /**
- Renders line break.
- @example
- out.s += "<br/>";
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {?Object} context A renderer specific context object.
- @returns {Boolean} Whether or not the tag was rendered.
- */
- linebreak: null,
- /**
- Renders a link.
- @example
- out.s = "<a href=\"" + link.s + "\" title=\"" + title.s + "\">" + content.s + "</a>";
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} link The link address.
- @param {Buffer} title Title text for the link.
- @param {Buffer} content Link text.
- @param {?Object} context A renderer specific context object.
- @returns {Boolean} Whether or not the tag was rendered.
- */
- link: null,
- /**
- Copies and potentially escapes some HTML.
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The input text.
- @param {?Object} context A renderer specific context object.
- @returns {Boolean} Whether or not the tag was rendered.
- */
- raw_html_tag: null,
- /**
- Renders text with triple emphasis. Default is equivalent to both the <em> and <strong> HTML tags.
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The text being wrapped.
- @param {?Object} context A renderer specific context object.
- @returns {Boolean} Whether or not the tag was rendered.
- */
- triple_emphasis: null,
- /**
- Renders text crossd out.
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The text being wrapped.
- @param {?Object} context A renderer specific context object.
- @returns {Boolean} Whether or not the tag was rendered.
- */
- strikethrough: null,
- /**
- Renders text as superscript.
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The text being wrapped.
- @param {?Object} context A renderer specific context object.
- @returns {Boolean} Whether or not the tag was rendered.
- */
- superscript: null,
- /**
- Escapes an HTML entity.
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The text being wrapped.
- @param {?Object} context A renderer specific context object.
- */
- entity: null,
- /**
- Renders plain text.
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {Buffer} text The text being rendered.
- @param {?Object} context A renderer specific context object.
- */
- normal_text: null,
- /**
- Creates opening boilerplate for a table of contents.
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {?Object} context A renderer specific context object.
- */
- doc_header: null,
- /**
- Creates closing boilerplate for a table of contents.
- @method
- @param {Buffer} out The output string buffer to append to.
- @param {?Object} context A renderer specific context object.
- */
- doc_footer: null
- };
- /**
- A renderer object
- @constructor
- @param {Callbacks} callbacks The callbacks object to use for the renderer.
- @param {?Callbacks} context Renderer specific context information.
- */
- function Renderer(callbacks, context) {
- this.callbacks = callbacks;
- this.context = context;
- }
- /**
- Instantiates a custom Renderer object.
- @param {Callbacks} callbacks The callbacks object to use for the renderer.
- @param {?Callbacks} context Renderer specific context information.
- @returns {Renderer}
- */
- function createCustomRenderer(callbacks, context) {
- return new Renderer(callbacks, context)
- }
- exports.createCustomRenderer = createCustomRenderer;
- function defaultRenderState() {
- return {
- nofollow: 0,
- target: null,
- tocData: {
- headerCount: 0,
- currentLevel: 0,
- levelOffset: 0
- },
- toc_id_prefix: null,
- html_element_whitelist: DEFAULT_HTML_ELEMENT_WHITELIST,
- html_attr_whitelist: DEFAULT_HTML_ATTR_WHITELIST,
- flags: 0,
- //(flags != undefined?flags:HTML_SKIP_HTML | HTML_SKIP_IMAGES | HTML_SAFELINK | HTML_ESCAPE | HTML_USE_XHTML),
- /* extra callbacks */
- // void (*link_attributes)(struct buf *ob, const struct buf *url, void *self);
- link_attributes: function link_attributes(out, url, options) {
- if (options.nofollow) out.s += ' rel="nofollow"';
- if (options.target != null) {
- out.s += ' target="' + options.target + '"';
- }
- }
- };
- }
- exports.defaultRenderState = defaultRenderState;
- /**
- Produces a renderer object that will match Reddit's output.
- @param {?Number=} flags A bitfield containing flags specific to the reddit HTML renderer. Passing undefined, null, or null value will produce reddit exact output.
- @returns {Renderer} A renderer object that will match Reddit's output.
- */
- function getRedditRenderer(flags) {
- var state =defaultRenderState();
- if (flags == null) {
- state.flags = DEFAULT_BODY_FLAGS;
- } else {
- state.flags = flags;
- }
- var renderer = new Renderer(getRedditCallbacks() , state);
- if (renderer.context.flags & HTML_SKIP_IMAGES)
- renderer.callbacks.image = null;
- if (renderer.context.flags & HTML_SKIP_LINKS) {
- renderer.callbacks.link = null;
- renderer.callbacks.autolink = null;
- }
- if (renderer.context.flags & HTML_SKIP_HTML || renderer.context.flags & HTML_ESCAPE)
- renderer.callbacks.blockhtml = null;
- return renderer;
- }
- exports.getRedditRenderer = getRedditRenderer;
- /**
- Produces a renderer object that will match Reddit's for a table of contents.
- @returns {Renderer} A renderer object that will match Reddit's output.
- */
- function getTocRenderer() {
- var state = defaultRenderState();
- state.flags = HTML_TOC | HTML_SKIP_HTML;
- var renderer = new Renderer(getTocCallbacks(), state);
- return renderer;
- }
- exports.getTocRenderer = getTocRenderer;
- /**
- Create a Callbacks object with the given callback table.
- @param {Object.<string, function>} callbacks A table of callbacks to place int a callbacks object.
- @returns {Callbacks} A callbacks object holding the provided callbacks.
- */
- function createCustomCallbacks(callbacks) {
- return new Callbacks(callbacks);
- }
- exports.createCustomCallbacks = createCustomCallbacks;
- /**
- Produce a callbacks object that matches Reddit's output.
- @returns {Callbacks} A callbacks object that matches Reddit's output.
- */
- function getRedditCallbacks(){
- return new Callbacks({
- blockcode: cb_blockcode,
- blockquote: cb_blockquote,
- blockhtml: cb_blockhtml,
- header: cb_header,
- hrule: cb_hrule,
- list: cb_list,
- listitem: cb_listitem,
- paragraph: cb_paragraph,
- table: cb_table,
- table_row: cb_table_row,
- table_cell: cb_table_cell,
- autolink: cb_autolink,
- codespan: cb_codespan,
- double_emphasis: cb_double_emphasis,
- emphasis: cb_emphasis,
- image: cb_image,
- linebreak: cb_linebreak,
- link: cb_link,
- raw_html_tag: cb_raw_html_tag,
- triple_emphasis: cb_triple_emphasis,
- strikethrough: cb_strikethrough,
- superscript: cb_superscript,
- entity: null,
- normal_text: cb_normal_text,
- doc_header: null,
- doc_footer: cb_reset_toc
- });
- }
- exports.getRedditCallbacks = getRedditCallbacks;
- /**
- Produce a callbacks object for rendering a table of contents.
- @returns {Callbacks} A callbacks object for rendering a table of contents.
- */
- function getTocCallbacks() {
- return new Callbacks({
- blockcode: null,
- blockquote: null,
- blockhtml: null,
- header: cb_toc_header,
- hrule: null,
- list: null,
- listitem: null,
- paragraph: null,
- table: null,
- table_row: null,
- table_cell: null,
- autolink: null,
- codespan: cb_codespan,
- double_emphasis: cb_double_emphasis,
- emphasis: cb_emphasis,
- image: null,
- linebreak: null,
- link: cb_toc_link,
- raw_html_tag: null,
- triple_emphasis: cb_triple_emphasis,
- strikethrough: cb_strikethrough,
- superscript: cb_superscript,
- entity: null,
- normal_text: null,
- doc_header: null,
- doc_footer: cb_toc_finalize
- });
- }
- exports.getTocCallbacks = getTocCallbacks;
- /* block level callbacks - NULL skips the block */
- // void (*blockcode)(struct buf *ob, const struct buf *text, const struct buf *lang, void *opaque);
- function cb_blockcode(out, text, lang, options) {
- if (out.s.length) out.s += '\n';
- if (lang && lang.s.length) {
- var i, cls;
- out.s += '<pre><code class="';
- for (i = 0, cls = 0; i < lang.s.length; ++i, ++cls) {
- while (i < lang.s.length && isspace(lang.s[i]))
- i++;
- if (i < lang.s.length) {
- var org = i;
- while (i < lang.s.length && !isspace(lang.s[i])) i++;
- if (lang.s[org] == '.') org++;
- if (cls) out.s += ' ';
- escape_html(out, lang.s.slice(org, i), false);
- }
- }
- out.s += '">';
- } else
- out.s += '<pre><code>';
- if (text) escape_html(out, text.s, false);
- out.s += '</code></pre>\n';
- }
- // void (*blockquote)(struct buf *ob, const struct buf *text, void *opaque);
- function cb_blockquote(out, text, options) {
- if (out.s.length) out.s += '\n';
- out.s += '<blockquote>\n';
- if (text) out.s += text.s;
- out.s += '</blockquote>\n';
- }
- // void (*blockhtml)(struct buf *ob,const struct buf *text, void *opaque);
- function cb_blockhtml(out, text, options) {
- var org, sz;
- if (!text) return;
- sz = text.s.length;
- while (sz > 0 && text.s[sz - 1] == '\n') sz--;
- org = 0;
- while (org < sz && text.s[org] == '\n') org++;
- if (org >= sz) return;
- if (out.s.length) out.s += '\n';
- out.s += text.s.slice(org, sz);
- out.s += '\n';
- }
- // header(Buffer out, Buffer text, int level, void *opaque);
- function cb_header(out, text, level, options) {
- if (out.s.length) out.s += '\n';
- if (options.flags & HTML_TOC) {
- out.s += '<h' + (+level) + ' id="';
- if (options.toc_id_prefix) out.s += options.toc_id_prefix;
- out.s += 'toc_' + (options.tocData.headerCount++) + '">';
- } else {
- out.s += '<h' + (+level) + '>';
- }
- if (text) out.s += text.s;
- out.s += '</h' + (+level) + '>\n';
- }
- // void (*hrule)(struct buf *ob, void *opaque);
- function cb_hrule(out, options) {
- if (out.s.length) out.s += '\n';
- out.s += (options.flags & HTML_USE_XHTML) ? '<hr/>\n' : '<hr>\n';
- }
- // void (*list)(struct buf *ob, const struct buf *text, int flags, void *opaque);
- function cb_list(out, text, flags, options) {
- if (out.s.length) out.s += '\n';
- out.s += (flags&MKD_LIST_ORDERED?'<ol>\n':'<ul>\n');
- if (text) out.s += text.s;
- out.s += (flags&MKD_LIST_ORDERED?'</ol>\n':'</ul>\n');
- }
- // void (*listitem)(struct buf *ob, const struct buf *text, int flags, void *opaque);
- function cb_listitem(out, text, flags, options) {
- out.s += '<li>';
- if (text) {
- var size = text.s.length;
- while (size && text.s[size - 1] == '\n') size--;
- out.s += text.s.slice(0, size);
- }
- out.s += '</li>\n';
- }
- // void (*paragraph)(struct buf *ob, const struct buf *text, void *opaque);
- function cb_paragraph(out, text, options) {
- var i = 0;
- if (out.s.length) out.s += '\n';
- if (!text || !text.s.length) return;
- while (i < text.s.length && isspace(text.s[i])) i++;
- if (i == text.s.length) return;
- out.s += '<p>';
- if (options.flags & HTML_HARD_WRAP) {
- var org;
- while (i < text.s.length) {
- org = i;
- while (i < text.s.length && text.data[i] != '\n')
- i++;
- if (i > org) out.s += text.s.slice(org, i);
- /*
- * do not insert a line break if this newline
- * is the last character on the paragraph
- */
- if (i >= text.s.length - 1) break;
- cb_linebreak(out, options);
- i++;
- }
- } else {
- out.s += text.s.slice(i);
- }
- out.s += '</p>\n';
- }
- // void (*table)(struct buf *ob, const struct buf *header, const struct buf *body, void *opaque);
- function cb_table(out, header, body, options) {
- if (out.s.length) out.s += '\n';
- out.s += '<table><thead>\n';
- if (header) out.s += header.s;
- out.s += '</thead><tbody>\n';
- if (body) out.s += body.s;
- out.s += '</tbody></table>\n';
- }
- // void (*table_row)(struct buf *ob, const struct buf *text, void *opaque);
- function cb_table_row(out, text, options) {
- out.s += '<tr>\n';
- if (text) out.s += text.s;
- out.s += '</tr>\n';
- }
- // void (*table_cell)(struct buf *ob, const struct buf *text, int flags, void *opaque);
- function cb_table_cell(out, text, flags, options) {
- if (flags & MKD_TABLE_HEADER) {
- out.s += '<th';
- } else {
- out.s += '<td';
- }
- switch (flags & MKD_TABLE_ALIGNMASK) {
- case MKD_TABLE_ALIGN_CENTER:
- out.s += ' align="center">';
- break;
- case MKD_TABLE_ALIGN_L:
- out.s += ' align="left">';
- break;
- case MKD_TABLE_ALIGN_R:
- out.s += ' align="right">';
- break;
- default:
- out.s += '>';
- }
- if (text) out.s += text.s;
- if (flags & MKD_TABLE_HEADER) {
- out.s += '</th>\n';
- } else {
- out.s += '</td>\n';
- }
- }
- /* span level callbacks - NULL or return 0 prints the span verbatim */
- // int (*autolink)(struct buf *ob, const struct buf *link, enum mkd_autolink type, void *opaque);
- function cb_autolink(out, link, type, options) {
- var offset = 0;
- if (!link || !link.s.length) return 0;
- if ((options.flags & HTML_SAFELINK) != 0 &&
- !sd_autolink_issafe(link.s) && type != MKDA_EMAIL)
- return 0;
- out.s += '<a href="';
- if (type == MKDA_EMAIL) out.s += 'mailto:';
- escape_href(out, link.s.slice(offset));
- if (options.link_attributes) {
- out.s += '"';
- options.link_attributes(out, link, options);
- out.s += '>';
- } else {
- out.s += '">';
- }
- /*
- * Pretty printing: if we get an email address as
- * an actual URI, e.g. `mailto:foo@bar.com`, we don't
- * want to print the `mailto:` prefix
- */
- if (link.s.indexOf('mailto:')==0) {
- escape_html(out, link.s.slice(7), false);
- } else {
- escape_html(out, link.s, false);
- }
- out.s += '</a>';
- return 1;
- }
- // int (*codespan)(struct buf *ob, const struct buf *text, void *opaque);
- function cb_codespan(out, text, options) {
- out.s += '<code>';
- if (text) escape_html(out, text.s, false);
- out.s += '</code>';
- return 1;
- }
- // int (*double_emphasis)(struct buf *ob, const struct buf *text, void *opaque);
- function cb_double_emphasis(out, text, options) {
- if (!text || !text.s.length) return 0;
- out.s += '<strong>' + text.s + '</strong>';
- return 1;
- }
- // int (*emphasis)(struct buf *ob, const struct buf *text, void *opaque);
- function cb_emphasis(out, text, options) {
- if (!text || !text.s.length) return 0;
- out.s += '<em>' + text.s + '</em>';
- return 1;
- }
- // int (*image)(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *alt, void *opaque);
- function cb_image(out, link, title, alt, options) {
- if (!link || !link.s.length) return 0;
- out.s += '<img src="';
- escape_href(out, link.s);
- out.s += '" alt="';
- if (alt && alt.s.length) escape_html(out, alt.s, false);
- if (title && title.s.length) {
- out.s += '" title="';
- escape_html(out, title.s, false);
- }
- out.s += (options.flags&HTML_USE_XHTML?'"/>':'">');
- return 1;
- }
- // int (*linebreak)(struct buf *ob, void *opaque);
- function cb_linebreak(out, options) {
- out.s += (options.flags&HTML_USE_XHTML?'<br/>\n':'<br>\n');
- return 1;
- }
- // int (*link)(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque);
- function cb_link(out, link, title, content, options) {
- if (link != null && (options.flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link.s)) return 0;
- out.s += '<a href="';
- if (link && link.s.length) escape_href(out, link.s);
- if (title && title.s.length) {
- out.s += '" title="';
- escape_html(out, title.s, false);
- }
- if (options.link_attributes) {
- out.s += '"';
- options.link_attributes(out, link, options);
- out.s += '>';
- } else {
- out.s += '">';
- }
- if (content && content.s.length) out.s += content.s;
- out.s += '</a>';
- return 1;
- }
- // rndr_html_tag(struct buf *ob, const struct buf *text, void *opaque, char* tagname, char** whitelist, int tagtype)
- //NOT A CALLBACK!
- // rndr_html_tag(struct buf *ob, const struct buf *text, void *opaque, char* tagname, char** whitelist, int tagtype)
- //NOT A CALLBACK!
- function rndr_html_tag(out, text, options, tagname, whitelist, tagtype) {
- var i, x, z, in_str = 0, seen_equals = 0, done = 0, done_attr = 0, reset = 0;
- var attr;
- var value;
- var c;
- out.s += '<';
-
- if(tagtype == HTML_TAG_CLOSE) {
- out.s += '/';
- out.s += tagname;
- out.s += '>';
- return;
- }
-
- out.s += tagname;
- i = 1 + tagname.length;
-
- attr = new Buffer();
- value = new Buffer();
- var self_closed = false;
-
- for(; i < text.s.length && !done; i++) {
- c = text.s[i];
- done = 0;
- reset = 0;
- done_attr = 0;
-
- switch(c) {
- case '/':
- if(!seen_equals) {
- self_closed = 1;
- } else if(in_str) {
- value.s += '/';
- } else {
- reset = 1;
- }
- break;
- case '>':
- done = 1;
- break;
- case '\'':
- case '"':
- self_closed = 0;
- if(!seen_equals) {
- reset = 1;
- } else if(!in_str) {
- in_str = c;
- } else if(in_str == c) {
- in_str = 0;
- done_attr = 1;
- } else {
- value.s += c;
- }
- break;
- case ' ':
- if (in_str) {
- value.s += ' ';
- } else {
- reset = 1;
- }
- break;
- case '=':
- self_closed = 0;
- if(seen_equals) {
- reset = 1;
- break;
- }
- seen_equals = 1;
- break;
- default:
- self_closed = 0;
- if(seen_equals && in_str || !seen_equals) {
- if(seen_equals)
- value.s += c;
- else
- attr.s += c;
- }
- break;
- }
-
- if(done_attr) {
- var valid = 0;
- for(z = 0; z < whitelist.length; z++) {
- if(whitelist[z].length != attr.s.length) {
- continue;
- }
- for(x = 0; x < attr.s.length; x++) {
- if(whitelist[z][x].toLowerCase() != attr.s[x].toLowerCase()) {
- break;
- }
- }
- if(x == attr.s.length) {
- valid = 1;
- break;
- }
- }
- if(valid && value.s.length && attr.s.length) {
- out.s += ' ';
- escape_html(out, attr.s, true);
- out.s += "=\"";
- escape_html(out, value.s, true);
- out.s += '"';
- }
- reset = 1;
- }
-
- if(reset) {
- seen_equals = 0;
- in_str = 0;
- attr.s = '';
- value.s = '';
- }
- }
-
- // bufrelease(attr);
- if(self_closed)
- out.s += ' /';
- out.s += '>';
- }
- // int (*raw_html_tag)(struct buf *ob, const struct buf *tag, void *opaque);
- function cb_raw_html_tag(out, text, options) {
- var whitelist = options.html_element_whitelist;
- /* Items on the whitelist ignore all other flags and just output */
- if (((options.flags & HTML_ALLOW_ELEMENT_WHITELIST) != 0) && whitelist) {
- for(var i = 0; whitelist[i]; i++) {
- var tagtype = sdhtml_is_tag(text.s, whitelist[i]);
- if(tagtype != HTML_TAG_NONE) {
- rndr_html_tag(out, text, options, whitelist[i], options.html_attr_whitelist, tagtype);
- return 1;
- }
- }
- }
- /* HTML_ESCAPE overrides SKIP_HTML, SKIP_STYLE, SKIP_LINKS and SKIP_IMAGES
- * It doens't see if there are any valid tags, just escape all of them. */
- if((options.flags & HTML_ESCAPE) != 0) {
- escape_html(out, text.s, false);
- return 1;
- }
- if ((options.flags & HTML_SKIP_HTML) != 0) return 1;
- if ((options.flags & HTML_SKIP_STYLE) != 0 &&
- sdhtml_is_tag(text.s, "style"))
- return 1;
- if ((options.flags & HTML_SKIP_LINKS) != 0 &&
- sdhtml_is_tag(text.s, "a"))
- return 1;
- if ((options.flags & HTML_SKIP_IMAGES) != 0 &&
- sdhtml_is_tag(text.s, "img"))
- return 1;
- out.s += text.s;
- return 1;
- }
- // int (*triple_emphasis)(struct buf *ob, const struct buf *text, void *opaque);
- function cb_triple_emphasis(out, text, options) {
- if (!text || !text.s.length) return 0;
- out.s += '<strong><em>' + text.s + '</em></strong>';
- return 1;
- }
- // int (*strikethrough)(struct buf *ob, const struct buf *text, void *opaque);
- function cb_strikethrough(out, text, options) {
- if (!text || !text.s.length) return 0;
- out.s += '<del>' + text.s + '</del>';
- return 1;
- }
- // int (*superscript)(struct buf *ob, const struct buf *text, void *opaque);
- function cb_superscript(out, text, options) {
- if (!text || !text.s.length) return 0;
- out.s += '<sup>' + text.s + '</sup>';
- return 1;
- }
- /* low level callbacks - NULL copies input directly into the output */
- //do not use
- // void (*entity)(struct buf *ob, const struct buf *entity, void *opaque);
- // function cb_entity(out, entity, options) {}
- // void (*normal_text)(struct buf *ob, const struct buf *text, void *opaque);
- function cb_normal_text(out, text, options) {
- if (text) escape_html(out, text.s, false);
- }
- // toc_header(struct buf *ob, const struct buf *text, int level, void *opaque)
- function cb_toc_header(out, text, level, options) {
- /* set the level offset if this is the first header
- * we're parsing for the document */
- if (options.tocData.currentLevel== 0) {
- out.s += '<div class="toc">\n';
- options.tocData.levelOffset = level - 1;
- }
- level -= options.tocData.levelOffset;
- if (level > options.tocData.currentLevel) {
- while (level > options.tocData.currentLevel) {
- out.s += '<ul>\n<li>\n';
- options.tocData.currentLevel++;
- }
- } else if (level < options.tocData.currentLevel) {
- out.s += '</li>\n';
- while (level < options.tocData.currentLevel) {
- out.s += '</ul>\n</li>\n';
- options.tocData.currentLevel--;
- }
- out.s += '<li>\n';
- } else {
- out.s += '</li>\n<li>\n';
- }
- out.s += '<a href="#';
- if (options.toc_id_prefix) out.s += options.toc_id_prefix;
- out.s += 'toc_' + (options.tocData.headerCount++) + '">';
- if (text) escape_html(out, text.s, false);
- out.s += '</a>\n';
- }
- //toc_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque)
- function cb_toc_link(out, link, title, content, options) {
- if (content && content.s)
- out.s += content.s;
- return 1;
- }
- // reset_toc(struct buf *ob, void *opaque)
- function cb_reset_toc(out, options) {
- options.tocData = {
- headerCount: 0,
- currentLevel: 0,
- levelOffset: 0
- };
- }
- //toc_finalize(struct buf *ob, void *opaque)
- function cb_toc_finalize(out, options) {
- var hasToc = false;
- while (options.tocData.currentLevel > 0) {
- out.s += '</li>\n</ul>\n';
- options.tocData.currentLevel--;
- hasToc = true;
- }
- if (hasToc) {
- out.s += '</div>\n';
- }
- cb_reset_toc(out, options);
- }
- /* header and footer */
- // doc_header(Buffer out}, context);
- // doc_header: null,
- // doc_footer(Buffer out, context);
- // doc_footer: null
- /* char_emphasis • single and double emphasis parsing */
- //Buffer, md, str, int
- function char_emphasis(out, md, data_, offset) {
- var data = data_.slice(offset);
- var size = data.length;
- var c = data[0];
- var ret;
- if (size > 2 && data[1] != c) {
- /* whitespace cannot follow an opening emphasis;
- * strikethrough only takes two characters '~~' */
- if (c == '~' || _isspace(data[1]) || (ret = parse_emph1(out, md, data, c)) == 0)
- return 0;
- return ret + 1;
- }
- if (data.length > 3 && data[1] == c && data[2] != c) {
- if (_isspace(data[2]) || (ret = parse_emph2(out, md, data, c)) == 0)
- return 0;
- return ret + 2;
- }
- if (data.length > 4 && data[1] == c && data[2] == c && data[3] != c) {
- if (c == '~' || _isspace(data[3]) || (ret = parse_emph3(out, md, data, c)) == 0)
- return 0;
- return ret + 3;
- }
- return 0;
- }
- /* char_codespan - '`' parsing a code span (assuming codespan != 0) */
- function char_codespan(out, md, data_, offset) {
- var data = data_.slice(offset);
- var end, nb = 0, i, f_begin, f_end;
- /* counting the number of backticks in the delimiter */
- while (nb < data.length && data[nb] == '`')
- nb++;
- /* finding the next delimiter */
- i = 0;
- for (end = nb; end < data.length && i < nb; end++) {
- if (data[end] == '`') i++;
- else i = 0;
- }
- if (i < nb && end >= data.length)
- return 0; /* no matching delimiter */
- /* trimming outside whitespaces */
- f_begin = nb;
- while (f_begin < end && data[f_begin] == ' ') f_begin++;
- f_end = end - nb;
- while (f_end > nb && data[f_end-1] == ' ') f_end--;
- /* real code span */
- if (f_begin < f_end) {
- var work = new Buffer(data.slice(f_begin, f_end));
- if (!md.callbacks.codespan(out, work, md.context))
- end = 0;
- } else {
- if (!md.callbacks.codespan(out, null, md.context))
- end = 0;
- }
- return end;
- }
- /* char_linebreak - '\n' preceded by two spaces (assuming linebreak != 0) */
- function char_linebreak(out, md, data_, offset) {
- var data = data_.slice(offset);
- if (offset < 2 || data_[offset-1] != ' ' || data_[offset-2] != ' ')
- return 0;
- /* removing the last space from ob and rendering */
- var len = out.s.length;
- while (len && out.s[len - 1] == ' ') len--;
- out.s = out.s.slice(0, len);
- return md.callbacks.linebreak(out, md.context) ? 1 : 0;
- }
- /* char_link - '[': parsing a link or an image */
- function char_link(out, md, data_, offset) {
- var data = data_.slice(offset);
- var is_img = (offset && data_[offset - 1] == '!'), level;
- var i = 1, txt_e, link_b = 0, link_e = 0, title_b = 0, title_e = 0;
- //4 bufs
- var content = null;
- var link = null;
- var title = null;
- var u_link = null;
- var org_work_size = md.spanStack.length;
- var text_has_nl = 0, ret = 0;
- var in_title = 0, qtype = 0;
- function cleanup() {
- md.spanStack.length = org_work_size;
- return ret ? i : 0;
- }
- /* checking whether the correct renderer exists */
- if ((is_img && !md.callbacks.image) || (!is_img && !md.callbacks.link))
- return cleanup();
- /* looking for the matching closing bracket */
- for (level = 1; i < data.length; i++) {
- if (data[i] == '\n') text_has_nl = 1;
- else if (data[i - 1] == '\\') continue;
- else if (data[i] == '[') level++;
- else if (data[i] == ']') {
- level--;
- if (level <= 0) break;
- }
- }
- if (i >= data.length) return cleanup();
- txt_e = i;
- i++;
- /* skip any amount of whitespace or newline */
- /* (this is much more laxist than original markdown syntax) */
- while (i < data.length && _isspace(data[i])) i++;
- /* inline style link */
- if (i < data.length && data[i] == '(') {
- /* skipping initial whitespace */
- i++;
- while (i < data.length && _isspace(data[i])) i++;
- link_b = i;
- /* looking for link end: ' " ) */
- while (i < data.length) {
- if (data[i] == '\\') i += 2;
- else if (data[i] == ')') break;
- else if (i >= 1 && _isspace(data[i-1]) && (data[i] == '\'' || data[i] == '"')) break;
- else i++;
- }
- if (i >= data.length) return cleanup();
- link_e = i;
- /* looking for title end if present */
- if (data[i] == '\'' || data[i] == '"') {
- qtype = data[i];
- in_title = 1;
- i++;
- title_b = i;
- while (i < data.length) {
- if (data[i] == '\\') i += 2;
- else if (data[i] == qtype) {in_title = 0; i++;}
- else if ((data[i] == ')') && !in_title) break;
- else i++;
- }
- if (i >= data.length) return cleanup();
- /* skipping whitespaces after title */
- title_e = i - 1;
- while (title_e > title_b && _isspace(data[title_e])) title_e--;
- /* checking for closing quote presence */
- if (data[title_e] != '\'' && data[title_e] != '"') {
- title_b = title_e = 0;
- link_e = i;
- }
- }
- /* remove whitespace at the end of the link */
- while (link_e > link_b && _isspace(data[link_e - 1])) link_e--;
- /* remove optional angle brackets around the link */
- if (data[link_b] == '<') link_b++;
- if (data[link_e - 1] == '>') link_e--;
- /* building escaped link and title */
- if (link_e > link_b) {
- link = new Buffer();
- md.spanStack.push(link);
- link.s += data.slice(link_b, link_e);
- }
- if (title_e > title_b) {
- title = new Buffer();
- md.spanStack.push(title);
- title.s += data.slice(title_b, title_e);
- }
- i++;
- }
- /* reference style link */
- else if (i < data.length && data[i] == '[') {
- var id = new Buffer();
- var lr = null;
- /* looking for the id */
- i++;
- link_b = i;
- while (i < data.length && data[i] != ']') i++;
- if (i >= data.length) return cleanup();
- link_e = i;
- /* finding the link_ref */
- if (link_b == link_e) {
- if (text_has_nl) {
- var b = new Buffer();
- md.spanStack.push(b);
- var j;
- for (j = 1; j < txt_e; j++) {
- if (data[j] != '\n')
- b.s += data[j];
- else if (data[j - 1] != ' ')
- b.s += ' ';
- }
- id.s = b.s;
- } else {
- id.s = data.slice(1);
- }
- } else {
- id.s = data.slice(link_b, link_e);
- }
- //TODO
- lr = md.refs[id.s];
- if (!lr) return cleanup();
- /* keeping link and title from link_ref */
- link = lr.link;
- title = lr.title;
- i++;
- }
- /* shortcut reference style link */
- else {
- var id = new Buffer();
- var lr = null;
- /* crafting the id */
- if (text_has_nl) {
- var b = new Buffer();
- md.spanStack.push(b);
- var j;
- for (j = 1; j < txt_e; j++) {
- if (data[j] != '\n') b.s += data[j];
- else if (data[j - 1] != ' ') b.s += ' ';
- }
- id.s = b.s;
- } else {
- id.s = data.slice(1, txt_e);
- }
- /* finding the link_ref */
- lr = md.refs[id.s];
- if (!lr) return cleanup();
- /* keeping link and title from link_ref */
- link = lr.link;
- title = lr.title;
- /* rewinding the whitespace */
- i = txt_e + 1;
- }
- /* building content: img alt is escaped, link content is parsed */
- if (txt_e > 1) {
- content = new Buffer();
- md.spanStack.push(content);
- if (is_img) {
- content.s += data.slice(1, txt_e);
- } else {
- /* disable autolinking when parsing inline the
- * content of a link */
- md.inLinkBody = 1;
- parse_inline(content, md, data.slice(1, txt_e));
- md.inLinkBody = 0;
- }
- }
- if (link) {
- u_link = new Buffer();
- md.spanStack.push(u_link);
- unscape_text(u_link, link);
- } else {
- return cleanup();
- }
- /* calling the relevant rendering function */
- if (is_img) {
- if (out.s.length && out.s[out.s.length - 1] == '!')
- out.s = out.s.slice(0, -1);
- ret = md.callbacks.image(out, u_link, title, content, md.context);
- } else {
- ret = md.callbacks.link(out, u_link, title, content, md.context);
- }
- /* cleanup */
- // cleanup:
- // rndr->work_bufs[BUFFER_SPAN].size = (int)org_work_size;
- // return ret ? i : 0;
- return cleanup();
- }
- /* char_langle_tag - '<' when tags or autolinks are allowed */
- function char_langle_tag(out, md, data_, offset) {
- var data = data_.slice(offset);
- var altype = {p:MKDA_NOT_AUTOLINK};
- var end = tag_length(data, altype);
- var work = new Buffer(data.slice(0, end));
- var ret = 0;
- if (end > 2) {
- if (md.callbacks.autolink && altype.p != MKDA_NOT_AUTOLINK) {
- var u_link = new Buffer();
- md.spanStack.push(u_link);
- work.s = data.substr(1 , end - 2);
- unscape_text(u_link, work);
- ret = md.callbacks.autolink(out, u_link, altype.p, md.context);
- md.spanStack.pop();
- }
- else if (md.callbacks.raw_html_tag)
- ret = md.callbacks.raw_html_tag(out, work, md.context);
- }
- if (!ret) return 0;
- else return end;
- }
- /* char_escape - '\\' backslash escape */
- function char_escape(out, md, data_, offset) {
- var data = data_.slice(offset);
- var escape_chars = "\\`*_{}[]()#+-.!:|&<>/^~";
- var work = new Buffer();
- if (data.length > 1) {
- if (escape_chars.indexOf(data[1]) == -1) return 0;
- if (md.callbacks.normal_text) {
- work.s = data[1];
- md.callbacks.normal_text(out, work, md.context);
- }
- else out.s += data[1];
- } else if (data.length == 1) {
- out.s += data[0];
- }
- return 2;
- }
- /* char_entity - '&' escaped when it doesn't belong to an entity */
- /* valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; */
- function char_entity(out, md, data_, offset) {
- var data = data_.slice(offset);
- var end = 1;
- var work = new Buffer();
- if (end < data.length && data[end] == '#') end++;
- while (end < data.length && isalnum(data[end])) end++;
- if (end < data.length && data[end] == ';') end++; /* real entity */
- else return 0; /* lone '&' */
- if (md.callbacks.entity) {
- work.s = data.slice(0, end);
- md.callbacks.entity(out, work, md.context);
- }
- else out.s += data.slice(0, end);
- return end;
- }
- function char_autolink_url(out, md, data_, offset) {
- var data = data_.slice(offset);
- var link = null;
- var link_len, rewind = {p: null};
- if (!md.callbacks.autolink || md.inLinkBody) return 0;
- link = new Buffer();
- md.spanStack.push(link);
- if ((link_len = sd_autolink__url(rewind, link, data_, offset, data.length, 0)) > 0) {
- if (rewind.p > 0) out.s = out.s.slice(0, -rewind.p);
- md.callbacks.autolink(out, link, MKDA_NORMAL, md.context);
- }
- md.spanStack.pop();
- return link_len;
- }
- function char_autolink_email(out, md, data_, offset) {
- var data = data_.slice(offset);
- var link = null;
- var link_len, rewind = {p: null};
- if (!md.callbacks.autolink || md.inLinkBody) return 0;
- link = new Buffer();
- md.spanStack.push(link);
- if ((link_len = sd_autolink__email(rewind, link, data_, offset, data.length, 0)) > 0) {
- if (rewind.p > 0) out.s = out.s.slice(0, -rewind.p);
- md.callbacks.autolink(out, link, MKDA_EMAIL, md.context);
- }
- md.spanStack.pop();
- return link_len;
- }
- function char_autolink_www(out, md, data_, offset) {
- var data = data_.slice(offset);
- var link = null, link_url = null, link_text = null;
- var link_len, rewind = {p: null};
- if (!md.callbacks.link || md.inLinkBody) return 0;
- link = new Buffer();
- md.spanStack.push(link);
- if ((link_len = sd_autolink__www(rewind, link, data_, offset, data.length, 0)) > 0) {
- link_url = new Buffer();
- md.spanStack.push(link_url);
- link_url.s += 'http://';
- link_url.s += link.s;
- if (rewind.p > 0) out.s = out.s.slice(0, out.s.length-rewind.p);
- if (md.callbacks.normal_text) {
- link_text = new Buffer();
- md.spanStack.push(link_text);
- md.callbacks.normal_text(link_text, link, md.context);
- md.callbacks.link(out, link_url, null, link_text, md.context);
- md.spanStack.pop();
- } else {
- md.callbacks.link(out, link_url, null, link, md.context);
- }
- md.spanStack.pop();
- }
- md.spanStack.pop();
- return link_len;
- }
- function char_autolink_subreddit_or_username(out, md, data_, offset) {
- var data = data_.slice(offset);
- var link = null;
- var link_len, rewind = {p: null};
- if (!md.callbacks.autolink || md.inLinkBody) return 0;
- link = new Buffer();
- md.spanStack.push(link);
- if ((link_len = sd_autolink__subreddit(rewind, link, data_, offset, data.length)) > 0) {
- //don't slice because the rewind pointer will always be 0
- if (rewind.p > 0) out.s = out.s.slice(0, -rewind.p);
- md.callbacks.autolink(out, link, MKDA_NORMAL, md.context);
- } else if ((link_len = sd_autolink__username(rewind, link, data_, offset, data.length)) > 0) {
- //don't slice because the rewind pointer will always be 0
- if (rewind.p > 0) out.s = out.s.slice(0, -rewind.p);
- md.callbacks.autolink(out, link, MKDA_NORMAL, md.context);
- }
- md.spanStack.pop();
- return link_len;
- }
- function char_superscript(out, md, data_, offset) {
- var data = data_.slice(offset);
- var size = data.length;
- var sup_start, sup_len;
- var sup = null;
- if (!md.callbacks.superscript) return 0;
- if (size < 2) return 0;
- if (data[1] == '(') {
- sup_start = sup_len = 2;
- while (sup_len < size && data[sup_len] != ')' && data[sup_len - 1] != '\\') sup_len++;
- if (sup_len == size) return 0;
- } else {
- sup_start = sup_len = 1;
- while (sup_len < size && !_isspace(data[sup_len])) sup_len++;
- }
- if (sup_len - sup_start == 0) return (sup_start == 2) ? 3 : 0;
- sup = new Buffer();
- md.spanStack.push(sup);
- parse_inline(sup, md, data.slice(sup_start, sup_len));
- md.callbacks.superscript(out, sup, md.context);
- md.spanStack.pop();
- return (sup_start == 2) ? sup_len + 1 : sup_len;
- }
- var markdown_char_ptrs = [
- null,
- char_emphasis,
- char_codespan,
- char_linebreak,
- char_link,
- char_langle_tag,
- char_escape,
- char_entity,
- char_autolink_url,
- char_autolink_email,
- char_autolink_www,
- char_autolink_subreddit_or_username,
- char_superscript
- ];
- var MKD_LIST_ORDERED = 1;
- var MKD_LI_BLOCK = 2; /* <li> containing block data */
- var MKD_LI_END = 8; /* internal list flag */
- var enumCounter = 0;
- var MD_CHAR_NONE = enumCounter++;
- var MD_CHAR_EMPHASIS = enumCounter++;
- var MD_CHAR_CODESPAN = enumCounter++;
- var MD_CHAR_LINEBREAK = enumCounter++;
- var MD_CHAR_LINK = enumCounter++;
- var MD_CHAR_LANGLE = enumCounter++;
- var MD_CHAR_ESCAPE = enumCounter++;
- var MD_CHAR_ENTITITY = enumCounter++;
- var MD_CHAR_AUTOLINK_URL = enumCounter++;
- var MD_CHAR_AUTOLINK_EMAIL = enumCounter++;
- var MD_CHAR_AUTOLINK_WWW = enumCounter++;
- var MD_CHAR_AUTOLINK_SUBREDDIT_OR_USERNAME = enumCounter++;
- var MD_CHAR_SUPERSCRIPT = enumCounter++;
- var SD_AUTOLINK_SHORT_DOMAINS = (1 << 0);
- enumCounter = 0;
- var MKDA_NOT_AUTOLINK = enumCounter++; /* used internally when it is not an autolink*/
- var MKDA_NORMAL = enumCounter++; /* normal http/http/ftp/mailto/etc link */
- var MKDA_EMAIL = enumCounter++; /* e-mail link without explit mailto: */
- var MKDEXT_NO_INTRA_EMPHASIS = (1 << 0);
- var MKDEXT_TABLES = (1 << 1);
- var MKDEXT_FENCED_CODE = (1 << 2);
- var MKDEXT_AUTOLINK = (1 << 3);
- var MKDEXT_STRIKETHROUGH = (1 << 4);
- // var MKDEXT_LAX_HTML_BLOCKS = (1 << 5);
- var MKDEXT_SPACE_HEADERS = (1 << 6);
- var MKDEXT_SUPERSCRIPT = (1 << 7);
- var MKDEXT_LAX_SPACING = (1 << 8)
- var HTML_SKIP_HTML = (1 << 0);
- var HTML_SKIP_STYLE = (1 << 1);
- var HTML_SKIP_IMAGES = (1 << 2);
- var HTML_SKIP_LINKS = (1 << 3);
- var HTML_EXPAND_TABS = (1 << 4);
- var HTML_SAFELINK = (1 << 5);
- var HTML_TOC = (1 << 6);
- var HTML_HARD_WRAP = (1 << 7);
- var HTML_USE_XHTML = (1 << 8);
- var HTML_ESCAPE = (1 << 9);
- var HTML_ALLOW_ELEMENT_WHITELIST = (1 << 10);
- var MKD_TABLE_ALIGN_L = 1;
- var MKD_TABLE_ALIGN_R = 2;
- var MKD_TABLE_ALIGN_CENTER = 3;
- var MKD_TABLE_ALIGNMASK = 3;
- var MKD_TABLE_HEADER = 4
- var HTML_TAG_NONE = 0;
- var HTML_TAG_OPEN = 1;
- var HTML_TAG_CLOSE = 2;
- /**
- * A string buffer wrapper because JavaScript doesn't have mutable strings.
- * @constructor
- * @param {string=} str Optional string to initialize the Buffer with.
- */
- function Buffer(str) {
- this.s = str || "";
- };
- // Buffer.prototype.toString = function toString() {
- // return this.s;
- // };
- // Buffer.prototype.slice
- /**
- * A Markdown parser object.
- * @constructor
- */
- function Markdown() {
- //Becase javascript strings are immutable they must be wrapped with Buffer()
- this.spanStack = [];
- this.blockStack = [];
- this.extensions = MKDEXT_NO_INTRA_EMPHASIS | MKDEXT_SUPERSCRIPT | MKDEXT_AUTOLINK | MKDEXT_STRIKETHROUGH | MKDEXT_TABLES;
- var renderer = getRedditRenderer();
- this.context = renderer.context;
- this.callbacks = renderer.callbacks;
- this.inLinkBody = 0;
- this.activeChars = {};
- this.refs = {};
- this.nestingLimit = 16;
- };
- /* is_empty - returns the line length when it is empty, 0 otherwise */
- function is_empty(data) {
- var i;
- for (i = 0; i < data.length && data[i] != '\n'; i++)
- if (data[i] != ' ') return 0;
- return i + 1;
- }
- /* is_hrule - returns whether a line is a horizontal rule */
- function is_hrule(data) {
- var i = 0, n = 0;
- var c;
- /* skipping initial spaces */
- if (data.length < 3) return 0;
- if (data[0] == ' ') { i++;
- if (data[1] == ' ') { i++;
- if (data[2] == ' ') { i++; } } }
- /* looking at the hrule uint8_t */
- if (i + 2 >= data.length
- || (data[i] != '*' && data[i] != '-' && data[i] != '_'))
- return 0;
- c = data[i];
- /* the whole line must be the char or whitespace */
- while (i < data.length && data[i] != '\n') {
- if (data[i] == c) n++;
- else if (data[i] != ' ')
- return 0;
- i++;
- }
- return n >= 3;
- }
- /* check if a line begins with a code fence; return the
- * width if it is */
- function prefix_codefence(data) {
- var i = 0, n = 0;
- var c;
- /* skipping initial spaces */
- if (data.length < 3) return 0;
- if (data[0] == ' ') { i++;
- if (data[1] == ' ') { i++;
- if (data[2] == ' ') { i++; } } }
- /* looking at the hrule uint8_t */
- if (i + 2 >= data.length || !(data[i] == '~' || data[i] == '`'))
- return 0;
- c = data[i];
- /* the whole line must be the uint8_t or whitespace */
- while (i < data.length && data[i] == c) {
- n++; i++;
- }
- if (n < 3) return 0;
- return i;
- }
- /* check if a line is a code fence; return its size if it */
- function is_codefence(data, syntax) {
- var i = 0, syn_len = 0;
- i = prefix_codefence(data);
- if (i == 0) return 0;
- while (i < data.length && data[i] == ' ') i++;
- var syn_start;
- //syn_start = data + i;
- syn_start = i;
- if (i < data.length && data[i] == '{') {
- i++; syn_start++;
- while (i < data.length && data[i] != '}' && data[i] != '\n') {
- syn_len++; i++;
- }
- if (i == data.length || data[i] != '}')
- return 0;
- /* strip all whitespace at the beginning and the end
- * of the {} block */
- /*remember not to remove the +0, it helps me keep syncronised with snudown*/
- while (syn_len > 0 && _isspace(data[syn_start+0])) {
- syn_start++; syn_len--;
- }
- // while (syn_len > 0 && _isspace(syn_start[syn_len - 1]))
- while (syn_len > 0 && _isspace(data[syn_start+syn_len - 1]))
- syn_len--;
- i++;
- } else {
- while (i < data.length && !_isspace(data[i])) {
- syn_len++; i++;
- }
- }
- if (syntax) syntax.s = data.substr(syn_start, syn_len);
- // syntax->size = syn;
- while (i < data.length && data[i] != '\n') {
- if (!_isspace(data[i])) return 0;
- i++;
- }
- return i + 1;
- }
- /* find_emph_char - looks for the next emph uint8_t, skipping other constructs */
- function find_emph_char(data, c) {
- var i = 1;
- while (i < data.length) {
- while (i < data.length && data[i] != c && data[i] != '`' && data[i] != '[')
- i++;
- if (i == data.length) return 0;
- if (data[i] == c)
- return i;
- /* not counting escaped chars */
- if (i && data[i - 1] == '\\') {
- i++; continue;
- }
- if (data[i] == '`') {
- var span_nb = 0, bt;
- var tmp_i = 0;
- /* counting the number of opening backticks */
- while (i < data.length && data[i] == '`') {
- i++; span_nb++;
- }
- if (i >= data.length) return 0;
- /* finding the matching closing sequence */
- bt = 0;
- while (i < data.length && bt < span_nb) {
- if (!tmp_i && data[i] == c) tmp_i = i;
- if (data[i] == '`') bt++;
- else bt = 0;
- i++;
- }
- if (i >= data.length) return tmp_i;
- }
- /* skipping a link */
- else if (data[i] == '[') {
- var tmp_i = 0;
- var cc;
- i++;
- while (i < data.length && data[i] != ']') {
- if (!tmp_i && data[i] == c) tmp_i = i;
- i++;
- }
- i++;
- while (i < data.length && (data[i] == ' ' || data[i] == '\n'))
- i++;
- if (i >= data.length) return tmp_i;
- switch (data[i]) {
- case '[':
- cc = ']'; break;
- case '(':
- cc = ')'; break;
- default:
- if (tmp_i)
- return tmp_i;
- else
- continue;
- }
- i++;
- while (i < data.length && data[i] != cc) {
- if (!tmp_i && data[i] == c) tmp_i = i;
- i++;
- }
- if (i >= data.length) return tmp_i;
- i++;
- }
- }
- return 0;
- }
- /* parse_emph1 - parsing single emphase */
- /* closed by a symbol not preceded by whitespace and not followed by symbol */
- function parse_emph1(out, md, data_, c) {
- var data = data_.slice(1);
- var i = 0, len;
- var r;
- if (!md.callbacks.emphasis) return 0;
- /* skipping one symbol if coming from emph3 */
- if (data.length > 1 && data[0] == c && data[1] == c) i = 1;
- while (i < data.length) {
- len = find_emph_char(data.slice(i), c);
- if (!len) return 0;
- i += len;
- if (i >= data.length) return 0;
- if (data[i] == c && !_isspace(data[i - 1])) {
- if ((md.extensions & MKDEXT_NO_INTRA_EMPHASIS) && (c == '_')) {
- if (!(i + 1 == data.length || _isspace(data[i + 1]) || ispunct(data[i + 1])))
- continue;
- }
- var work = new Buffer();
- md.spanStack.push(work);
- parse_inline(work, md, data.slice(0, i));
- r = md.callbacks.emphasis(out, work, md.context);
- md.spanStack.pop();
- return r ? i + 1 : 0;
- }
- }
- return 0;
- }
- /* parse_emph2 - parsing single emphase */
- function parse_emph2(out, md, data_, c) {
- var data = data_.slice(2);
- var i = 0, len;
- var r;
- var render_method = (c == '~') ? md.callbacks.strikethrough : md.callbacks.double_emphasis;
- if (!render_method) return 0;
- while (i < data.length) {
- len = find_emph_char(data.slice(i), c);
- if (!len) return 0;
- i += len;
- if (i + 1 < data.length && data[i] == c && data[i + 1] == c && i && !_isspace(data[i - 1])) {
- var work = new Buffer();
- md.spanStack.push(work);
- parse_inline(work, md, data.slice(0, i));
- r = render_method(out, work, md.context);
- md.spanStack.pop();
- return r ? i + 2 : 0;
- }
- i++;
- }
- return 0;
- }
- /* parse_emph3 • parsing single emphase */
- /* finds the first closing tag, and delegates to the other emph */
- function parse_emph3(out, md, data_, c) {
- var data = data_.slice(3);
- var i = 0, len;
- var r;
- while (i < data.length) {
- len = find_emph_char(data.slice(i), c);
- if (!len) return 0;
- i += len;
- /* skip whitespace preceded symbols */
- if (data[i] != c || _isspace(data[i - 1])) continue;
- if (i + 2 < data.length && data[i + 1] == c && data[i + 2] == c && md.callbacks.triple_emphasis) {
- /* triple symbol found */
- var work = new Buffer();
- md.spanStack.push(work);
- parse_inline(work, md, data.slice(0, i));
- r = md.callbacks.triple_emphasis(out, work, md.context);
- md.spanStack.pop();
- return r ? i + 3 : 0;
- } else if (i + 1 < data.length && data[i + 1] == c) {
- /* double symbol found, handing over to emph1 */
- len = parse_emph1(out, md, data_, c);
- if (!len) return 0;
- else return len - 2;
- } else {
- /* single symbol found, handing over to emph2 */
- len = parse_emph2(out, md, data_, c);
- if (!len) return 0;
- else return len - 1;
- }
- }
- return 0;
- }
- function is_atxheader(md, data) {
- if (data[0] != '#') return false;
- if (md.extensions & MKDEXT_SPACE_HEADERS) {
- var level = 0;
- while (level < data.length && level < 6 && data[level] == '#')
- level++;
- if (level < data.length && data[level] != ' ')
- return false;
- }
- return true;
- }
- /* is_headerline . returns whether the line is a setext-style hdr underline */
- function is_headerline(data) {
- var i = 0;
- var size = data.length;
- /* test of level 1 header */
- if (data[i] == '=') {
- for (i = 1; i < size && data[i] == '='; i++) {}
- while (i < size && data[i] == ' ') i++;
- return (i >= size || data[i] == '\n') ? 1 : 0; }
- /* test of level 2 header */
- if (data[i] == '-') {
- for (i = 1; i < size && data[i] == '-'; i++) {}
- while (i < size && data[i] == ' ') i++;
- return (i >= size || data[i] == '\n') ? 2 : 0; }
- return 0;
- }
- function is_next_headerline(data) {
- var size = data.length;
- var i = 0;
- while (i < size && data[i] != '\n') i++;
- if (++i >= size) return 0;
- return is_headerline(data.slice(i));
- }
- /* prefix_quote - returns blockquote prefix length */
- function prefix_quote(data) {
- var i = 0;
- var size = data.length;
- if (i < size && data[i] == ' ') i++;
- if (i < size && data[i] == ' ') i++;
- if (i < size && data[i] == ' ') i++;
- if (i < size && data[i] == '>') {
- if (i + 1 < size && data[i + 1] == ' ')
- return i + 2;
- return i + 1;
- }
- return 0;
- }
- /* prefix_code • returns prefix length for block code*/
- function prefix_code(data) {
- if (data.length > 3 && data[0] == ' ' && data[1] == ' '
- && data[2] == ' ' && data[3] == ' ') return 4;
- return 0;
- }
- /* prefix_oli - returns ordered list item prefix */
- function prefix_oli(data) {
- var size = data.length;
- var i = 0;
- if (i < size && data[i] == ' ') i++;
- if (i < size && data[i] == ' ') i++;
- if (i < size && data[i] == ' ') i++;
- if (i >= size || data[i] < '0' || data[i] > '9') return 0;
- while (i < size && data[i] >= '0' && data[i] <= '9') i++;
- if (i + 1 >= size || data[i] != '.' || data[i + 1] != ' ') return 0;
- if (is_next_headerline(data.slice(i))) return 0;
- return i + 2;
- }
- /* prefix_uli - returns ordered list item prefix */
- function prefix_uli(data) {
- var size = data.length;
- var i = 0;
- if (i < size && data[i] == ' ') i++;
- if (i < size && data[i] == ' ') i++;
- if (i < size && data[i] == ' ') i++;
- if (i + 1 >= size ||
- (data[i] != '*' && data[i] != '+' && data[i] != '-') ||
- data[i + 1] != ' ')
- return 0;
- if (is_next_headerline(data.slice(i))) return 0;
- return i + 2;
- }
- /* is_mail_autolink - looks for the address part of a mail autolink and '>' */
- /* this is less strict than the original markdown e-mail address matching */
- function is_mail_autolink(data) {
- var i = 0, nb = 0;
- /* address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@' */
- for (i = 0; i < data.length; ++i) {
- if (isalnum(data[i]))
- continue;
- switch (data[i]) {
- case '@':
- nb++;
- case '-':
- case '.':
- case '_':
- break;
- case '>':
- return (nb == 1) ? i + 1 : 0;
- default:
- return 0;
- }
- }
- return 0;
- }
- /* tag_length - returns the length of the given tag, or 0 is it's not valid */
- function tag_length(data, autolink) {
- var i, j;
- /* a valid tag can't be shorter than 3 chars */
- if (data.length < 3) return 0;
- /* begins with a '<' optionally followed by '/', followed by letter or number */
- if (data[0] != '<') return 0;
- i = (data[1] == '/') ? 2 : 1;
- if (!isalnum(data[i])) return 0;
- /* scheme test */
- autolink.p = MKDA_NOT_AUTOLINK;
- /* try to find the beginning of an URI */
- while (i < data.length && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-')) i++;
- if (i > 1 && data[i] == '@') {
- if ((j = is_mail_autolink(data.slice(i))) != 0) {
- autolink.p = MKDA_EMAIL;
- return i + j;
- }
- }
- if (i > 2 && data[i] == ':') {
- autolink.p = MKDA_NORMAL;
- i++;
- }
- /* completing autolink test: no whitespace or ' or " */
- if (i >= data.length) autolink.p = MKDA_NOT_AUTOLINK;
- else if (autolink.p) {
- j = i;
- while (i < data.length) {
- if (data[i] == '\\') i += 2;
- else if (data[i] == '>' || data[i] == '\'' ||
- data[i] == '"' || data[i] == ' ' || data[i] == '\n')
- break;
- else i++;
- }
- if (i >= data.length) return 0;
- if (i > j && data[i] == '>') return i + 1;
- /* one of the forbidden chars has been found */
- autolink.p = MKDA_NOT_AUTOLINK;
- }
- /* looking for sometinhg looking like a tag end */
- while (i < data.length && data[i] != '>') i++;
- if (i >= data.length) return 0;
- return i + 1;
- }
- // parse_inline - parses inline markdown elements
- //Buffer, md, String
- function parse_inline(out, md, data) {
- var i = 0, end = 0;
- var action = 0;
- var work = new Buffer();
- if (md.spanStack.length + md.blockStack.length > md.nestingLimit)
- return;
- while (i < data.length) {
- /* copying inactive chars into the output */
- while (end < data.length && !(action = md.activeChars[data[end]])) {
- end++;
- }
- if (md.callbacks.normal_text) {
- work.s = data.slice(i, end);
- md.callbacks.normal_text(out, work, md.context);
- }
- else
- out.s += data.slice(i, end);
- if (end >= data.length) break;
- i = end;
- end = markdown_char_ptrs[action](out, md, data, i);
- if (!end) /* no action from the callback */
- end = i + 1;
- else {
- i += end;
- end = i;
- }
- }
- }
- /* parse_atxheader - parsing of atx-style headers */
- function parse_atxheader(out, md, data) {
- var level = 0;
- var i, end, skip;
- while (level < data.length && level < 6 && data[level] == '#') level++;
- for (i = level; i < data.length && data[i] == ' '; i++) {}
- for (end = i; end < data.length && data[end] != '\n'; end++) {}
- skip = end;
- while (end && data[end - 1] == '#') end--;
- while (end && data[end - 1] == ' ') end--;
- if (end > i) {
- var work = new Buffer();
- md.spanStack.push(work);
- parse_inline(work, md, data.slice(i, end));
- if (md.callbacks.header)
- md.callbacks.header(out, work, level, md.context);
- md.spanStack.pop();
- }
- return skip;
- }
- /* htmlblock_end - checking end of HTML block : </tag>[ \t]*\n[ \t*]\n */
- /* returns the length on match, 0 otherwise */
- // htmlblock_end(const char *tag, size_t tag_len, struct sd_markdown *rndr, uint8_t *data, size_t size)
- function htmlblock_end(tag, md, data) {
- var i, w;
- /* checking if tag is a match */
- //tag should already be lowercase
- if (tag.length + 3 >= data.length ||
- data.slice(2).toLowerCase() != tag ||
- data[tag.length + 2] != '>')
- return 0;
- /* checking white lines */
- i = tag.length + 3;
- w = 0;
- if (i < data.length && (w = is_empty(data.slice(i))) == 0)
- return 0; /* non-blank after tag */
- i += w;
- w = 0;
- if (i < data.length) w = is_empty(data.slice(i));
- return i + w;
- }
- /* parse_htmlblock - parsing of inline HTML block */
- //TODO
- function parse_htmlblock(out, md, data, do_render) {
- var i, j = 0;
- var curtag = null;
- var found;
- var work = new Buffer(data);
- /* identification of the opening tag */
- if (data.length < 2 || data[0] != '<') return 0;
- i = 1;
- while (i < data.length && data[i] != '>' && data[i] != ' ') i++;
- if (i < data.length) curtag = find_block_tag(data.slice(1));
- /* handling of special cases */
- if (!curtag) {
- /* HTML comment, laxist form */
- if (data.length > 5 && data[1] == '!' && data[2] == '-' && data[3] == '-') {
- i = 5;
- while (i < data.length && !(data[i - 2] == '-' && data[i - 1] == '-' && data[i] == '>')) i++;
- i++;
- if (i < size)
- j = is_empty(data.slice(i));
- if (j) {
- //TODO: HANDLE WORK!!!
- // work.size = i + j;
- work.s = data.slice(0, i + j);
- if (do_render && md.callbacks.blockhtml)
- md.callbacks.blockhtml(out, work, md.context);
- return work.s.length;
- }
- }
- /* HR, which is the only self-closing block tag considered */
- if (data.length > 4 && (data[1] == 'h' || data[1] == 'H') && (data[2] == 'r' || data[2] == 'R')) {
- i = 3;
- while (i < data.length && data[i] != '>') i++;
- if (i + 1 < data.length) {
- i++;
- j = is_empty(data.slice(i));
- if (j) {
- work.s = data.slice(0, i + j);
- if (do_render && md.callbacks.blockhtml)
- md.callbacks.blockhtml(out, work, md.context);
- return work.s.length;
- }
- }
- }
- /* no special case recognised */
- return 0;
- }
- /* looking for an unindented matching closing tag */
- /* followed by a blank line */
- i = 1;
- found = 0;
- /* if not found, trying a second pass looking for indented match */
- /* but not if tag is "ins" or "del" (following original Markdown.pl) */
- if (curtag != 'ins' && curtag != 'del') {
- var tag_size = curtag.length;
- i = 1;
- while (i < data.length) {
- i++;
- while (i < data.length && !(data[i - 1] == '<' && data[i] == '/'))
- i++;
- if (i + 2 + tag_size >= data.length)
- break;
- // j = htmlblock_end(tag, md, data + i - 1, size - i + 1);
- //TODO
- j = htmlblock_end(tag, md, data.slice(i - 1));
- if (j) {
- i += j - 1;
- found = 1;
- break;
- }
- }
- }
- if (!found) return 0;
- /* the end of the block has been found */
- //TODO:
- work.s = work.s.slice(0, i);
- if (do_render && md.callbacks.blockhtml)
- md.callbacks.blockhtml(out, work, md.context);
- return i;
- }
- /* parse_blockquote - handles parsing of a blockquote fragment */
- function parse_blockquote(out, md, data) {
- var size = data.length;
- var beg, end = 0, pre, work_size = 0;
- // uint8_t *work_data = 0;
- var work_data = "";
- var work_data_cursor = 0;
- var out_ = new Buffer();
- md.blockStack.push(out_);
- beg = 0;
- while (beg < size) {
- for (end = beg + 1; end < size && data[end - 1] != '\n'; end++) {}
- pre = prefix_quote(data.slice(beg, end));
- if (pre) beg += pre; /* skipping prefix */
- /* empty line followed by non-quote line */
- else if (is_empty(data.slice(beg, end)) &&
- (end >= size || (prefix_quote(data.slice(end)) == 0 &&
- !is_empty(data.slice(end)))))
- break;
- if (beg < end) { /* copy into the in-place working buffer */
- /* bufput(work, data + beg, end - beg); */
- //TODO:!!! FIX THIS!!!
- // if (!work_data) work_data = data.slice(beg, end);
- // work_data = data + beg;
- work_data += data.slice(beg, end);
- /*
- if (!work_data) work_data_cursor = beg;
- else if (beg != work_data_cursor + work_size)
- work_data += data.slice(beg, end);
- */
- // memmove(work_data + work_size, data + beg, end - beg);
- work_size += end - beg;
- }
- beg = end;
- }
- parse_block(out_, md, work_data);
- if (md.callbacks.blockquote)
- md.callbacks.blockquote(out, out_, md.context);
- md.blockStack.pop();
- return end;
- }
- /* parse_paragraph - handles parsing of a regular paragraph */
- function parse_paragraph(out, md, data) {
- var i = 0, end = 0;
- var level = 0;
- var size = data.length;
- var work = new Buffer(data);
- while (i < size) {
- for (end = i + 1; end < size && data[end - 1] != '\n'; end++) {/* empty */}
- if (prefix_quote(data.slice(i, end)) != 0) {
- end = i;
- break;
- }
- var tempdata = data.slice(i);
- if (is_empty(tempdata) || (level = is_headerline(tempdata)) != 0) break;
- if (is_empty(tempdata)) break;
- if ((level = is_headerline(tempdata)) != 0) break;
- if (is_atxheader(md, tempdata)
- || is_hrule(tempdata)
- || prefix_quote(tempdata)) {
- end = i;
- break;
- }
- /*
- * Early termination of a paragraph with the same logic
- * as markdown 1.0.0. If this logic is applied, the
- * Markdown 1.0.3 test suite wont pass cleanly.
- *
- * :: If the first character in a new line is not a letter
- * lets check to see if there's some kind of block starting here
- */
- if ((md.extensions & MKDEXT_LAX_SPACING) && !isalnum(data[i])) {
- if (prefix_oli(tempdata)
- || prefix_uli(tempdata)) {
- end = i;
- break;
- }
- /* see if an html block starts here */
- if (data[i] == '<' && md.callbacks.blockhtml
- && parse_htmlblock(out, md, tempdata, 0)) {
- end = i;
- break
- }
- /* see if a code fence starts here */
- if ((md.extensions && MKDEXT_FENCED_CODE) != 0
- && is_codefence(tempdata, null) != 0) {
- end = i;
- break;
- }
- }
- i = end;
- }
- var work_size = i;
- while (work_size && data[work_size - 1] == '\n') work_size--;
- work.s = work.s.slice(0, work_size);
- if (!level) {
- var tmp = new Buffer();
- md.blockStack.push(tmp);
- parse_inline(tmp, md, work.s);
- if (md.callbacks.paragraph)
- md.callbacks.paragraph(out, tmp, md.context);
- md.blockStack.pop();
- } else {
- var header_work = null;
- if (work.size) {
- var beg;
- i = work.s.length;
- // var work_size = work.s.length - 1;
- // work.size -= 1;
- while (work_size && data[work_size] != '\n')
- work_size -= 1;
- beg = work_size + 1;
- while (work_size && data[work_size - 1] == '\n')
- work_size -= 1;
- work.s = work.s.slice(0, work_size);
- if (work_size > 0) {
- var tmp = new Buffer();
- md.blockStack.push(tmp);
- parse_inline(tmp, md, work.s);
- if (md.callbacks.paragraph)
- md.callbacks.paragraph(out, tmp, md.context);
- md.blockStack.pop();
- work.s = work.s.slice(beg, i);
- }
- else work.s = work.s.slice(0, i);
- }
- header_work = new Buffer();
- md.spanStack.push(header_work);
- parse_inline(header_work, md, work.s);
- if (md.callbacks.header)
- md.callbacks.header(out, header_work, level, md.context);
- md.spanStack.pop();
- }
- return end;
- }
- /* parse_fencedcode - handles parsing of a block-level code fragment */
- function parse_fencedcode(out, md, data) {
- var beg, end;
- var work = null;
- var lang = new Buffer();
- beg = is_codefence(data, lang);
- if (beg == 0) return 0;
- work = new Buffer();
- md.blockStack.push(work);
- while (beg < data.length) {
- var fence_end;
- var fence_trail = new Buffer();
- fence_end = is_codefence(data.slice(beg), fence_trail);
- if (fence_end != 0 && fence_trail.s.length == 0) {
- beg += fence_end;
- break;
- }
- for (end = beg + 1; end < data.length && data[end - 1] != '\n'; end++) {}
- if (beg < end) {
- /* verbatim copy to the working buffer,
- escaping entities */
- var tempData = data.slice(beg, end);
- if (is_empty(tempData)) work.s += '\n';
- else work.s += tempData;
- }
- beg = end;
- }
- if (work.s.length && work.s[work.s.length - 1] != '\n')
- work.s += '\n';
- if (md.callbacks.blockcode)
- md.callbacks.blockcode(out, work, lang.s.length ? lang : null, md.context);
- md.blockStack.pop();
- return beg;
- }
- function parse_blockcode(out, md, data) {
- var size = data.length;
- var beg, end, pre;
- var work = null;
- md.blockStack.push(work = new Buffer());
- beg = 0;
- while (beg < size) {
- for (end = beg + 1; end < size && data[end - 1] != '\n'; end++) {};
- pre = prefix_code(data.slice(beg, end));
- if (pre) beg += pre; /* skipping prefix */
- else if (!is_empty(data.slice(beg, end)))
- /* non-empty non-prefixed line breaks the pre */
- break;
- if (beg < end) {
- /* verbatim copy to the working buffer,
- escaping entities */
- if (is_empty(data.slice(beg, end))) work.s += '\n';
- else work.s += data.slice(beg, end);
- }
- beg = end;
- }
- var work_size = work.s.length;
- while (work_size && work.s[work_size - 1] == '\n') work_size -= 1;
- work.s = work.s.slice(0, work_size);
- work.s += '\n';
- if (md.callbacks.blockcode)
- md.callbacks.blockcode(out, work, null, md.context);
- md.blockStack.pop();
- return beg;
- }
- /* parse_listitem - parsing of a single list item */
- /* assuming initial prefix is already removed */
- //FLAGS is pointer
- function parse_listitem(out, md, data, flags) {
- var size = data.length;
- var work = null, inter = null;
- var beg = 0, end, pre, sublist = 0, orgpre = 0, i;
- var in_empty = 0, has_inside_empty = 0, in_fence = 0;
- /* keeping track of the first indentation prefix */
- while (orgpre < 3 && orgpre < size && data[orgpre] == ' ')
- orgpre++;
- //TODO
- beg = prefix_uli(data);
- if (!beg) beg = prefix_oli(data);
- if (!beg) return 0;
- /* skipping to the beginning of the following line */
- end = beg;
- while (end < size && data[end - 1] != '\n') end++;
- /* getting working buffers */
- md.spanStack.push(work = new Buffer());
- md.spanStack.push(inter = new Buffer());
- /* putting the first line into the working buffer */
- work.s += data.slice(beg, end);
- beg = end;
- /* process the following lines */
- while (beg < size) {
- var has_next_uli, has_next_oli;
- end++;
- while (end < size && data[end - 1] != '\n') end++;
- /* process an empty line */
- if (is_empty(data.slice(beg, end))) {
- in_empty = 1;
- beg = end;
- continue;
- }
- /* calculating the indentation */
- i = 0;
- while (i < 4 && beg + i < end && data[beg + i] == ' ') i++;
- pre = i;
- //TODO: Cache this slice?
- if (md.flags & MKDEXT_FENCED_CODE) {
- if (is_codefence(data.slice(beg+i, end), null) != 0) {
- in_fence = !in_fence;
- }
- }
- /* only check for new list items if we are **not** in a fenced code block */
- if (!in_fence) {
- has_next_uli = prefix_uli(data.slice(beg+i, end));
- has_next_oli = prefix_oli(data.slice(beg+i, end));
- }
- /* checking for ul/ol switch */
- if (in_empty && (
- ((flags.p & MKD_LIST_ORDERED) && has_next_uli) ||
- (!(flags.p & MKD_LIST_ORDERED) && has_next_oli))){
- flags.p |= MKD_LI_END;
- break; /* the following item must have same list type */
- }
- /* checking for a new item */
- if ((has_next_uli && !is_hrule(data.slice(beg+i, end))) || has_next_oli) {
- if (in_empty) has_inside_empty = 1;
- if (pre == orgpre) /* the following item must have */
- break; /* the same indentation */
- if (!sublist) sublist = work.s.length;
- }
- /* joining only indented stuff after empty lines;
- * note that now we only require 1 space of indentation
- * to continue list */
- else if (in_empty && pre == 0) {
- flags.p |= MKD_LI_END;
- break;
- }
- else if (in_empty) {
- work.s += '\n';
- has_inside_empty = 1;
- }
- in_empty = 0;
- /* adding the line without prefix into the working buffer */
- work.s += data.slice(beg + i, end);
- beg = end;
- }
- /* render of li contents */
- if (has_inside_empty) flags.p |= MKD_LI_BLOCK;
- if (flags.p & MKD_LI_BLOCK) {
- /* intermediate render of block li */
- if (sublist && sublist < work.s.length) {
- parse_block(inter, md, work.s.slice(0, sublist));
- parse_block(inter, md, work.s.slice(sublist));
- }
- else
- parse_block(inter, md, work.s);
- } else {
- //TODO:
- /* intermediate render of inline li */
- if (sublist && sublist < work.s.length) {
- parse_inline(inter, md, work.s.slice(0, sublist));
- parse_block(inter, md, work.s.slice(sublist));
- }
- else
- parse_inline(inter, md, work.s);
- }
- /* render of li itself */
- if (md.callbacks.listitem)
- md.callbacks.listitem(out, inter, flags.p, md.context);
- md.spanStack.pop();
- md.spanStack.pop();
- return beg;
- }
- /* parse_list - parsing ordered or unordered list block */
- function parse_list(out, md, data, flags) {
- var size = data.length;
- var i = 0, j;
- var work = null;
- md.blockStack.push(work = new Buffer());
- while (i < size) {
- var flag_p = {p: flags};
- j = parse_listitem(work, md, data.slice(i), flag_p);
- flags = flag_p.p;
- i += j;
- if (!j || (flags & MKD_LI_END)) break;
- }
- if (md.callbacks.list)
- md.callbacks.list(out, work, flags, md.context);
- md.blockStack.pop();
- return i;
- }
- function parse_table_row(out, md, data, columns, header_flag) {
- var i = 0, col;
- var row_work = null;
- if (!md.callbacks.table_cell || !md.callbacks.table_row) return;
- md.spanStack.push(row_work = new Buffer());
- if (i < data.length && data[i] == '|') i++;
- for (col = 0; col < columns.length && i < data.length; ++col) {
- var cell_start, cell_end;
- var cell_work;
- md.spanStack.push(cell_work = new Buffer());
- while (i < data.length && _isspace(data[i])) i++;
- cell_start = i;
- while (i < data.length && data[i] != '|') i++;
- cell_end = i - 1;
- while (cell_end > cell_start && _isspace(data[cell_end])) cell_end--;
- // parse_inline(cell_work, rndr, data + cell_start, 1 + cell_end - cell_start);
- parse_inline(cell_work, md, data.slice(cell_start, 1 + cell_end));
- md.callbacks.table_cell(row_work, cell_work, columns[col] | header_flag, md.context);
- md.spanStack.pop();
- i++;
- }
- for (; col < columns.length; ++col) {
- var empty_cell = null;
- md.callbacks.table_cell(row_work, empty_cell, columns[col] | header_flag, md.context);
- }
- md.callbacks.table_row(out, row_work, md.context);
- md.spanStack.pop();
- }
- function parse_table_header(out, md, data, columns) {
- var i = 0, col, header_end, under_end;
- var pipes = 0;
- while (i < data.length && data[i] != '\n')
- if (data[i++] == '|') pipes++;
- if (i == data.length || pipes == 0)
- return 0;
- header_end = i;
- while (header_end > 0 && _isspace(data[header_end - 1])) header_end--;
- if (data[0] == '|') pipes--;
- if (header_end && data[header_end - 1] == '|') pipes--;
- // columns.p = pipes + 1;
- // column_data.p = new Array(columns.p);
- columns.p = new Array(pipes + 1);
- for (var k = 0; k < columns.p.length; k++) columns.p[k] = 0;
- /* Parse the header underline */
- i++;
- if (i < data.length && data[i] == '|') i++;
- under_end = i;
- while (under_end < data.length && data[under_end] != '\n') under_end++;
- for (col = 0; col < columns.p.length && i < under_end; ++col) {
- var dashes = 0;
- while (i < under_end && data[i] == ' ') i++;
- if (data[i] == ':') {
- i++;
- columns.p[col] |= MKD_TABLE_ALIGN_L;
- dashes++;
- }
- while (i < under_end && data[i] == '-') {
- i++; dashes++;
- }
- if (i < under_end && data[i] == ':') {
- i++; columns.p[col] |= MKD_TABLE_ALIGN_R;
- dashes++;
- }
- while (i < under_end && data[i] == ' ') i++;
- if (i < under_end && data[i] != '|') break;
- if (dashes < 1) break;
- i++;
- }
- if (col < columns.p.length) return 0;
- parse_table_row(out, md, data.slice(0, header_end), columns.p, MKD_TABLE_HEADER);
- return under_end + 1;
- }
- function parse_table(out, md, data) {
- var i;
- var header_work, body_work;
- var columns = {p: null};
- md.spanStack.push(header_work = new Buffer());
- md.blockStack.push(body_work = new Buffer());
- i = parse_table_header(header_work, md, data, columns);
- if (i > 0) {
- while (i < data.length) {
- var row_start;
- var pipes = 0;
- row_start = i;
- while (i < data.length && data[i] != '\n')
- if (data[i++] == '|')
- pipes++;
- if (pipes == 0 || i == data.length) {
- i = row_start;
- break;
- }
- parse_table_row(body_work, md, data.slice(row_start, i), columns.p, 0);
- i++;
- }
- if (md.callbacks.table)
- md.callbacks.table(out, header_work, body_work, md.context);
- }
- md.spanStack.pop();
- md.blockStack.pop();
- return i;
- }
- function parse_block(out, md, data) {
- var beg = 0, end, i;
- var textData;
- if (md.spanStack.length +
- md.blockStack.length > md.nestingLimit)
- return;
- while (beg < data.length) {
- textData = data.slice(beg);
- end = data.length - beg;
- if (is_atxheader(md, textData))
- beg += parse_atxheader(out, md, textData);
- else if (data[beg] == '<' && md.callbacks.blockhtml &&
- (i = parse_htmlblock(out, md, textData, 1)) != 0)
- beg += i;
- else if ((i = is_empty(textData)) != 0)
- beg += i;
- else if (is_hrule(textData)) {
- if (md.callbacks.hrule)
- md.callbacks.hrule(out, md.context);
- while (beg < data.length && data[beg] != '\n') beg++;
- beg++;
- }
- else if ((md.extensions & MKDEXT_FENCED_CODE) != 0 &&
- (i = parse_fencedcode(out, md, textData)) != 0)
- beg += i;
- else if ((md.extensions & MKDEXT_TABLES) != 0 &&
- (i = parse_table(out, md, textData)) != 0)
- beg += i;
- else if (prefix_quote(textData))
- beg += parse_blockquote(out, md, textData);
- else if (prefix_code(textData))
- beg += parse_blockcode(out, md, textData);
- else if (prefix_uli(textData))
- beg += parse_list(out, md, textData, 0);
- else if (prefix_oli(textData))
- beg += parse_list(out, md, textData, MKD_LIST_ORDERED);
- else {
- beg += parse_paragraph(out, md, textData);
- }
- }
- }
- function is_ref(data, beg, end, md) {
- /* int n; */
- var i = 0;
- var idOffset, idEnd;
- var linkOffset, linkEnd;
- var titleOffset, titleEnd;
- var lineEnd;
- /* up to 3 optional leading spaces */
- if (beg + 3 >= end) return 0;
- if (data[beg] == ' ') { i = 1;
- if (data[beg + 1] == ' ') { i = 2;
- if (data[beg + 2] == ' ') { i = 3;
- if (data[beg + 3] == ' ') return 0; } } }
- i += beg;
- /* id part: anything but a newline between brackets */
- if (data[i] != '[') return 0;
- i++;
- idOffset = i;
- while (i < end && data[i] != '\n' && data[i] != '\r' && data[i] != ']') i++;
- if (i >= end || data[i] != ']') return 0;
- idEnd = i;
- /* spacer: colon (space | tab)* newline? (space | tab)* */
- i++;
- if (i >= end || data[i] != ':') return 0;
- i++;
- while (i < end && data[i] == ' ') i++;
- if (i < end && (data[i] == '\n' || data[i] == '\r')) {
- i++;
- if (i < end && data[i] == '\r' && data[i - 1] == '\n') i++; }
- while (i < end && data[i] == ' ') i++;
- if (i >= end) return 0;
- /* link: whitespace-free sequence, optionally between angle brackets */
- if (data[i] == '<') i++;
- linkOffset = i;
- while (i < end && data[i] != ' ' && data[i] != '\n' && data[i] != '\r') i++;
- if (data[i - 1] == '>') linkEnd = i - 1; else linkEnd = i;
- /* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */
- while (i < end && data[i] == ' ') i++;
- if (i < end && data[i] != '\n' && data[i] != '\r'
- && data[i] != '\'' && data[i] != '"' && data[i] != '(')
- return 0;
- lineEnd = 0;
- /* computing end-of-line */
- if (i >= end || data[i] == '\r' || data[i] == '\n') lineEnd = i;
- if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r')
- lineEnd = i + 1;
- /* optional (space|tab)* spacer after a newline */
- if (lineEnd) {
- i = lineEnd + 1;
- while (i < end && data[i] == ' ') i++;
- }
- /* optional title: any non-newline sequence enclosed in '"()
- alone on its line */
- titleOffset = titleEnd = 0;
- if (i + 1 < end && (data[i] == '\'' || data[i] == '"' || data[i] == '(')) {
- i++;
- titleOffset = i;
- /* looking for EOL */
- while (i < end && data[i] != '\n' && data[i] != '\r') i++;
- if (i + 1 < end && data[i] == '\n' && data[i + 1] == '\r')
- titleEnd = i + 1;
- else titleEnd = i;
- /* stepping back */
- i -= 1;
- while (i > titleOffset && data[i] == ' ')
- i -= 1;
- if (i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')')) {
- lineEnd = titleEnd;
- titleEnd = i;
- }
- }
- if (!lineEnd || linkEnd == linkOffset)
- return 0; /* garbage after the link empty link */
- var id = data.slice(idOffset, idEnd);
- var link = data.slice(linkOffset, linkEnd);
- var title = null;
- if (titleEnd > titleOffset) title = data.slice(titleOffset, titleEnd);
- md.refs[id] = {
- id: id,
- link: new Buffer(link),
- title: new Buffer(title)
- };
- return lineEnd;
- }
- function expand_tabs(out, line) {
- var i = 0, tab = 0;
- while (i < line.length) {
- var org = i;
- while (i < line.length && line[i] != '\t') {
- i++; tab++;
- }
- if (i > org) out.s += line.slice(org, i);
- if (i >= line.length) break;
- do {
- out.s += ' ';
- tab++;
- } while (tab % 4);
- i++;
- }
- }
- /**
- Render markdown code to HTML.
- @param {string} source Markdown code.
- @returns {string} HTML code.
- */
- function render(source) {
- var text = new Buffer();
- var beg = 0, end;
- this.refs = {};
- while (beg < source.length) { /* iterating over lines */
- if (end = is_ref(source, beg, source.length, this))
- beg = end;
- else { /* skipping to the next line */
- end = beg;
- while (end < source.length && source[end] != '\n' && source[end] != '\r') end++;
- /* adding the line body if present */
- if (end > beg) expand_tabs(text, source.slice(beg, end));
- while (end < source.length && (source[end] == '\n' || source[end] == '\r')) {
- /* add one \n per newline */
- if (source[end] == '\n' || (end + 1 < source.length && source[end + 1] != '\n'))
- text.s += '\n';
- end++;
- }
- beg = end;
- }
- }
-
- var out = new Buffer();
- /* second pass: actual rendering */
- if (this.callbacks.doc_header)
- this.callbacks.doc_header(out, this.context);
- if (text.s.length) {
- /* adding a final newline if not already present */
- if (text.s[text.s.length - 1] != '\n' && text.s[text.s.length - 1] != '\r')
- text.s += '\n';
- parse_block(out, this, text.s);
- }
- if (this.callbacks.doc_footer)
- this.callbacks.doc_footer(out, this.context);
- return out.s;
- }
- Markdown.prototype['render'] = render;
- /**
- Create a parser object using the given configuration parameters.
- To get a Reddit equivelent configuration, pass no arguments.
- @param {?Renderer=} renderer A renderer object.
- @param {?Number=} extensions A series of OR'd extension flags. (Extension flags start with MKDEXT_)
- @param {?Number=} nestingLimit The maximum depth to which inline elements can be nested.
- @return {Markdown} A configured markdown object.
- */
- exports.getParser = function getParser(renderer, extensions, nestingLimit) {
- var md = new Markdown();
- if (renderer) md.callbacks = renderer.callbacks;
- if (nestingLimit) md.nestingLimit = nestingLimit;
- if (renderer) md.context = renderer.context;
- if (extensions != undefined && extensions != null) md.extensions = extensions;
- var cb = md.callbacks;
- if (cb.emphasis || cb.double_emphasis || cb.triple_emphasis) {
- md.activeChars['*'] = MD_CHAR_EMPHASIS;
- md.activeChars['_'] = MD_CHAR_EMPHASIS;
- if (md.extensions & MKDEXT_STRIKETHROUGH) md.activeChars['~'] = MD_CHAR_EMPHASIS;
- }
- if (cb.codespan) md.activeChars['`'] = MD_CHAR_CODESPAN;
- if (cb.linebreak) md.activeChars['\n'] = MD_CHAR_LINEBREAK;
- if (cb.image || cb.link) md.activeChars['['] = MD_CHAR_LINK;
- md.activeChars['<'] = MD_CHAR_LANGLE;
- md.activeChars['\\'] = MD_CHAR_ESCAPE;
- md.activeChars['&'] = MD_CHAR_ENTITITY;
- if (md.extensions & MKDEXT_AUTOLINK) {
- md.activeChars[':'] = MD_CHAR_AUTOLINK_URL;
- md.activeChars['@'] = MD_CHAR_AUTOLINK_EMAIL;
- md.activeChars['w'] = MD_CHAR_AUTOLINK_WWW;
- md.activeChars['/'] = MD_CHAR_AUTOLINK_SUBREDDIT_OR_USERNAME;
- }
- if (md.extensions & MKDEXT_SUPERSCRIPT) md.activeChars['^'] = MD_CHAR_SUPERSCRIPT;
- return md;
- }
- var DEFAULT_BODY_FLAGS = HTML_SKIP_HTML | HTML_SKIP_IMAGES | HTML_SAFELINK | HTML_ESCAPE | HTML_USE_XHTML;
- var DEFAULT_WIKI_FLAGS = HTML_SKIP_HTML | HTML_SAFELINK | HTML_ALLOW_ELEMENT_WHITELIST | HTML_ESCAPE | HTML_USE_XHTML;
- var DEFAULT_HTML_ATTR_WHITELIST = ['colspan', 'rowspan', 'cellspacing', 'cellpadding', 'scope'];
- var DEFAULT_HTML_ELEMENT_WHITELIST = ['tr', 'th', 'td', 'table', 'tbody', 'thead', 'tfoot', 'caption'];
- exports.DEFAULT_HTML_ELEMENT_WHITELIST = DEFAULT_HTML_ELEMENT_WHITELIST;
- exports.DEFAULT_HTML_ATTR_WHITELIST = DEFAULT_HTML_ATTR_WHITELIST;
- exports.DEFAULT_BODY_FLAGS = DEFAULT_BODY_FLAGS;
- exports.DEFAULT_WIKI_FLAGS = DEFAULT_WIKI_FLAGS;
- exports.HTML_SKIP_HTML = HTML_SKIP_HTML;
- exports.HTML_SKIP_STYLE = HTML_SKIP_STYLE;
- exports.HTML_SKIP_IMAGES = HTML_SKIP_IMAGES;
- exports.HTML_SKIP_LINKS = HTML_SKIP_LINKS;
- exports.HTML_EXPAND_TABS = HTML_EXPAND_TABS;
- exports.HTML_SAFELINK = HTML_SAFELINK;
- exports.HTML_TOC = HTML_TOC;
- exports.HTML_HARD_WRAP = HTML_HARD_WRAP;
- exports.HTML_USE_XHTML = HTML_USE_XHTML;
- exports.HTML_ESCAPE = HTML_ESCAPE;
- exports.HTML_ALLOW_ELEMENT_WHITELIST = HTML_ALLOW_ELEMENT_WHITELIST;
- exports.MKDEXT_NO_INTRA_EMPHASIS = MKDEXT_NO_INTRA_EMPHASIS;
- exports.MKDEXT_TABLES = MKDEXT_TABLES;
- exports.MKDEXT_FENCED_CODE = MKDEXT_FENCED_CODE;
- exports.MKDEXT_AUTOLINK = MKDEXT_AUTOLINK;
- exports.MKDEXT_STRIKETHROUGH = MKDEXT_STRIKETHROUGH;
- exports.MKDEXT_SPACE_HEADERS = MKDEXT_SPACE_HEADERS;
- exports.MKDEXT_SUPERSCRIPT = MKDEXT_SUPERSCRIPT;
- exports.MKDEXT_LAX_SPACING = MKDEXT_LAX_SPACING;
- exports['SD_AUTOLINK_SHORT_DOMAINS'] = SD_AUTOLINK_SHORT_DOMAINS;
- exports.MKDA_NOT_AUTOLINK = MKDA_NOT_AUTOLINK;
- exports.MKDA_NORMAL = MKDA_NORMAL;
- exports.MKDA_EMAIL = MKDA_EMAIL;
- if (typeof define === 'function') {
- define('snuownd', [], exports);
- }
- })(typeof(exports)!=='undefined'?exports:typeof(window)!=='undefined'?window.SnuOwnd={}:{});