UNPKG

npm-creole

Version:

npm package for parsing wiki creole. fork of https://github.com/codeholic/jscreole/network

466 lines (406 loc) 16.7 kB
/*! * Copyright (c) 2011 Ivan Fomichev * http://github.com/codeholic/jscreole * Licensed under MIT license, see http://www.opensource.org/licenses/mit-license.php * npm module by zibx. zibx.mail@gmail.com */ var doc; if(typeof document === 'undefined'){ doc = (function(){ var Node = function(type){ this.type = type; this.childNodes = []; this.attrs = {}; }; Node.prototype = { appendChild: function(child){ this.childNodes.push(child); }, setAttribute: function(k, v){ this.attrs[k] = v; }, getAttribute: function(k){ return this.attrs[k]; } }; var noClose = { br: 1, hr: 1, img: 1 }; Object.defineProperty(Node.prototype, 'innerHTML', { get: function () { if('_innerHTML' in this){ return this._innerHTML; } if('_innerText' in this){ } return this.childNodes.map(function(node){ return node.type === 'TextNode' ? node.innerText : node.outerHTML; }).join(''); }, set: function (value) { this._innerHTML = value; } }); var attr = function(name){ return { set: function(val){ return this.setAttribute(name, val); }, get: function(val){ return this.getAttribute(val); } }; }; 'href,src,alt'.split(',').forEach(function(name){ Object.defineProperty(Node.prototype, name, attr(name)); }); Object.defineProperty(Node.prototype, 'outerHTML', { get: function () { var node = this, attrs = [], i; for( i in this.attrs ) attrs.push({name: i, val: i+'="'+this.getAttribute(i)+'"'}); attrs.sort(function(a, b){ return a.name < b.name ? -1 : 1; }); attrs = attrs.map(function(attr){ return attr.val; }); return noClose[node.type] ? '<'+node.type+(attrs.length?' '+attrs.join(' ')+'/':' /')+'>': '<'+node.type+(attrs.length?' '+attrs.join(' '):'')+'>'+node.innerHTML+'</'+node.type+'>'; } }); Object.defineProperty(Node.prototype, 'innerText', { get: function () { if('_innerText' in this){ return this._innerText; } }, set: function (value) { this._innerText = value .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") //.replace(/"/g, "&quot;") .replace(/'/g, "&#039;"); } }); return { createElement: function(type){ return new Node(type); }, createTextNode: function(val){ var textNode = new Node('TextNode'); textNode.innerText = val; return textNode; } }; })(); }else{ doc = document; } var creole = function(options) { var rx = {}; rx._link = '[^\\]|~\\n]*(?:(?:\\](?!\\])|~.)[^\\]|~\\n]*)*'; rx._linkText = '[^\\]~\\n]*(?:(?:\\](?!\\])|~.)[^\\]~\\n]*)*'; rx._uriPrefix = '\\b(?:(?:https?|ftp)://|mailto:)'; rx._uri = rx._uriPrefix + rx._link; rx._rawUri = rx._uriPrefix + '\\S*[^\\s!"\',.:;?]'; rx._interwikiLink = '[\\w.]+:' + rx._link; rx._img = '\\{\\{((?!\\{)[^|}\\n]*(?:}(?!})[^|}\\n]*)*)' + (options && options.strict ? '' : '(?:') + '\\|([^}~\\n]*((}(?!})|~.)[^}~\\n]*)*)' + (options && options.strict ? '' : ')?') + '}}'; var formatLink = function(link, format) { if (format instanceof Function) { return format(link); } format = format instanceof Array ? format : [ format ]; if (typeof format[1] === 'undefined') { format[1] = ''; } return format[0] + link + format[1]; }; var g = { _hr: { _tag: 'hr', _regex: /(^|\n)\s*----\s*(\n|$)/ }, _br: { _tag: 'br', _regex: /\\\\/ }, _preBlock: { _tag: 'pre', _capture: 2, _regex: /(^|\n)\{\{\{\n((.*\n)*?)\}\}\}(\n|$)/, _replaceRegex: /^ ([ \t]*\}\}\})/gm, _replaceString: '$1' }, _tt: { _tag: 'tt', _regex: /\{\{\{(.*?\}\}\}+)/, _capture: 1, _replaceRegex: /\}\}\}$/, _replaceString: '' }, _ulist: { _tag: 'ul', _capture: 0, _regex: /(^|\n)([ \t]*\*[^*#].*(\n|$)([ \t]*[^\s*#].*(\n|$))*([ \t]*[*#]{2}.*(\n|$))*)+/ }, _olist: { _tag: 'ol', _capture: 0, _regex: /(^|\n)([ \t]*#[^*#].*(\n|$)([ \t]*[^\s*#].*(\n|$))*([ \t]*[*#]{2}.*(\n|$))*)+/ }, _li: { _tag: 'li', _capture: 0, _regex: /[ \t]*([*#]).+(\n[ \t]*[^*#\s].*)*(\n[ \t]*[*#]{2}.+)*/, _replaceRegex: /(^|\n)[ \t]*[*#]/g, _replaceString: '$1' }, _table: { _tag: 'table', _capture: 0, _regex: /(^|\n)(\|.*?[ \t]*(\n|$))+/ }, _tr: { _tag: 'tr', _capture: 2, _regex: /(^|\n)(\|.*?)\|?[ \t]*(\n|$)/ }, _th: { _tag: 'th', _regex: /\|+=([^|]*)/, _capture: 1 }, _td: { _tag: 'td', _capture: 1, _regex: '\\|+([^|~\\[{]*((~(.|(?=\\n)|$)|' + '\\[\\[' + rx._link + '(\\|' + rx._linkText + ')?\\]\\]' + (options && options.strict ? '' : '|' + rx._img) + '|[\\[{])[^|~]*)*)' }, _singleLine: { _regex: /.+/, _capture: 0 }, _paragraph: { _tag: 'p', _capture: 0, _regex: /(^|\n)([ \t]*\S.*(\n|$))+/ }, _text: { _capture: 0, _regex: /(^|\n)([ \t]*[^\s].*(\n|$))+/ }, _strong: { _tag: 'strong', _capture: 1, _regex: /\*\*([^*~]*((\*(?!\*)|~(.|(?=\n)|$))[^*~]*)*)(\*\*|\n|$)/ }, _underline: { _tag: 'u', _capture: 1, _regex: /__([^_~]*((_(?!_)|~(.|(?=\n)|$))[^_~]*)*)(__|\n|$)/ }, _em: { _tag: 'em', _capture: 1, _regex: '\\/\\/(((?!' + rx._uriPrefix + ')[^\\/~])*' + '((' + rx._rawUri + '|\\/(?!\\/)|~(.|(?=\\n)|$))' + '((?!' + rx._uriPrefix + ')[^\\/~])*)*)(\\/\\/|\\n|$)' }, _highlight: { _regex: /!!([^!~]*((!(?!!)|!!!|~(.|(?=\n)|$))[^!~]*)*)(!!|\n|$)/, _build: function(node, r, options){ var span = doc.createElement('span'); var cls = ['highlight'], maybeCls; r[1] = r[0].substr(2, r[0].length - 4); if(r[1].indexOf('!!!')>-1){ maybeCls = r[1].split('!!!'); cls.push(maybeCls[0]); r[1] = maybeCls.slice(1).join('!!!'); } span.setAttribute('class', cls.join(' ')); span.innerHTML = r[1]; node.appendChild(span); } }, _img: { _regex: rx._img, _build: function(node, r, options) { var img = doc.createElement('img'); img.src = r[1]; img.alt = r[2] === undefined ? (options && options.defaultImageText ? options.defaultImageText : '') : r[2].replace(/~(.)/g, '$1'); node.appendChild(img); } }, _namedUri: { _regex: '\\[\\[(' + rx._uri + ')\\|(' + rx._linkText + ')\\]\\]', _build: function(node, r, options) { var link = doc.createElement('a'); link.href = r[1]; if (options && options.isPlainUri) { link.appendChild(doc.createTextNode(r[2])); } else { this._apply(link, r[2], options); } node.appendChild(link); } }, _namedLink: { _regex: '\\[\\[(' + rx._link + ')\\|(' + rx._linkText + ')\\]\\]', _build: function(node, r, options) { var link = doc.createElement('a'); link.href = options && options.linkFormat ? formatLink(r[1].replace(/~(.)/g, '$1'), options.linkFormat) : r[1].replace(/~(.)/g, '$1'); this._apply(link, r[2], options); node.appendChild(link); } }, _unnamedUri: { _regex: '\\[\\[(' + rx._uri + ')\\]\\]' }, _unnamedLink: { _regex: '\\[\\[(' + rx._link + ')\\]\\]' }, _unnamedInterwikiLink: { _regex: '\\[\\[(' + rx._interwikiLink + ')\\]\\]' }, _rawUri: { _regex: '(' + rx._rawUri + ')' }, _escapedSequence: { _regex: '~(' + rx._rawUri + '|.)', _capture: 1, _tag: 'span', _attrs: { 'class': 'escaped' } }, _escapedSymbol: { _regex: /~(.)/, _capture: 1, _tag: 'span', _attrs: { 'class': 'escaped' } } }; g._unnamedUri._build = g._rawUri._build = function(node, r, options) { if (!options) { options = {}; } options.isPlainUri = true; g._namedUri._build.call(this, node, [r[0], r[1], r[1]], options); }; g._unnamedLink._build = function(node, r, options) { g._namedLink._build.call(this, node, [r[0], r[1], r[1]], options); }; g._namedInterwikiLink = { _regex: '\\[\\[(' + rx._interwikiLink + ')\\|(' + rx._linkText + ')\\]\\]', _build: function(node, r, options) { var link = doc.createElement('a'); var m, f; if (options && options.interwiki) { m = r[1].match(/(.*?):(.*)/); f = options.interwiki[m[1]]; } if (typeof f === 'undefined') { if (!g._namedLink._apply) { g._namedLink = new this.constructor(g._namedLink); } return g._namedLink._build.call(g._namedLink, node, r, options); } link.href = formatLink(m[2].replace(/~(.)/g, '$1'), f); this._apply(link, r[2], options); node.appendChild(link); } }; g._unnamedInterwikiLink._build = function(node, r, options) { g._namedInterwikiLink._build.call(this, node, [r[0], r[1], r[1]], options); }; g._namedUri._children = g._unnamedUri._children = g._rawUri._children = g._namedLink._children = g._unnamedLink._children = g._namedInterwikiLink._children = g._unnamedInterwikiLink._children = [ g._escapedSymbol, g._img ]; for (var i = 1; i <= 6; i++) { g['h' + i] = { _tag: 'h' + i, _capture: 2, _regex: '(^|\\n)[ \\t]*={' + i + '}[ \\t]*' + '([^\\n=][^~]*?(~(.|(?=\\n)|$))*)[ \\t]*=*\\s*(\\n|$)' }; } g._ulist._children = g._olist._children = [ g._li ]; g._li._children = [ g._ulist, g._olist ]; g._li._fallback = g._text; g._table._children = [ g._tr ]; g._tr._children = [ g._th, g._td ]; g._td._children = [ g._singleLine ]; g._th._children = [ g._singleLine ]; g.h1._children = g.h2._children = g.h3._children = g.h4._children = g.h5._children = g.h6._children = g._singleLine._children = g._paragraph._children = g._text._children = g._strong._children = g._highlight._children = g._em._children = g._underline._children = [ g._escapedSequence, g._strong, g._highlight, g._underline, g._em, g._br, g._rawUri, g._namedUri, g._namedInterwikiLink, g._namedLink, g._unnamedUri, g._unnamedInterwikiLink, g._unnamedLink, g._tt, g._img ]; g._root = { _children: [ g.h1, g.h2, g.h3, g.h4, g.h5, g.h6, g._hr, g._ulist, g._olist, g._preBlock, g._table ], _fallback: { _children: [ g._paragraph ] } }; creole._base.call(this, g, options); }; creole._base = function(grammar, options) { if (!arguments.length) { return; } this._grammar = grammar; this._grammar._root = new this._ruleConstructor(this._grammar._root); this._options = options; }; creole._rule = function(params) { if (!arguments.length) { return; } Object.assign(this, params); if (!this._children) { this._children = []; } }; creole._base.prototype = { _ruleConstructor: null, _grammar: null, _options: null, parse: function(node, data, options) { if(arguments.length === 1 || typeof data === 'object'){ options = data; data = node; node = doc.createElement('div'); } if (options) { Object.assign(options, this._options); } else { options = this._options; } data = data.replace(/\r\n?/g, '\n'); this._grammar._root._apply(node, data, options); if (options && options.forIE) { node.innerHTML = node.innerHTML.replace(/\r?\n/g, '\r\n'); } return node.innerHTML; } }; creole._base.prototype.constructor = creole._base; creole._base.prototype._ruleConstructor = creole._rule; creole._rule.prototype = { _match: function(data) { return data.match(this._regex); }, _build: function(node, r, options) { var data; if (this._capture !== null) { data = r[this._capture]; } var target; if (this._tag) { target = doc.createElement(this._tag); node.appendChild(target); } else { target = node; } if (data) { if (this._replaceRegex) { data = data.replace(this._replaceRegex, this._replaceString); } this._apply(target, data, options); } if (this._attrs) { for (var i in this._attrs) { if (this._attrs.hasOwnProperty(i)) { target.setAttribute(i, this._attrs[i]); if (options && options.forIE && i === 'class') { target.className = this._attrs[i]; } } } } return this; }, _apply: function(node, data, options) { var tail = '' + data; var matches = []; if (!this._fallback._apply) { this._fallback = new this.constructor(this._fallback); } while (true) { var best = false; var rule = false; var i; for (i = 0; i < this._children.length; i++) { if (typeof matches[i] === 'undefined') { if (!this._children[i]._match) { this._children[i] = new this.constructor(this._children[i]); } matches[i] = this._children[i]._match(tail, options); } if (matches[i] && (!best || best.index > matches[i].index)) { best = matches[i]; rule = this._children[i]; if (best.index === 0) { break; } } } var pos = best ? best.index : tail.length; if (pos > 0) { this._fallback._apply(node, tail.substring(0, pos), options); } if (!best) { break; } if (!rule._build) { rule = new this.constructor(rule); } rule._build(node, best, options); var chopped = best.index + best[0].length; tail = tail.substring(chopped); for (i = 0; i < this._children.length; i++) { if (matches[i]) { if (matches[i].index >= chopped) { matches[i].index -= chopped; } else { matches[i] = void 0; } } } } return this; }, _fallback: { _apply: function(node, data, options) { if (options && options.forIE) { // workaround for bad IE data = data.replace(/\n/g, ' \r'); } node.appendChild(doc.createTextNode(data)); } } }; creole._rule.prototype.constructor = creole._rule; creole.prototype = new creole._base(); creole.prototype.constructor = creole; if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { module.exports = creole; }