/libs/snuownd.js
JavaScript | 3835 lines | 3128 code | 204 blank | 503 comment | 416 complexity | 8f54cc06d352d2d5ec12191843ae3072 MD5 | raw file
Possible License(s): Apache-2.0
Large files files are truncated, but you can click here to view the full file
- /**
- @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 = sd…
Large files files are truncated, but you can click here to view the full file