Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Javascript porting from libtext-creolize-perl
/*!
* WikiCreole JavaScript Library v0.1.0 experimental
*
* Copyright 2011, MIZUTANI Tociyuki
* licensed under the GPL Version 2 licenses.
*
* 2011-12-24
*/
function WikiCreole() {}
(function(){
var escape_htmlall = function(s) {
return s.replace(/[\x3c\x3e\x26\x22\x27\x5c]/g, function(c){
return c == '\x3c' ? '\x26lt;'
: c == '\x3e' ? '\x26gt;'
: c == '\x22' ? '\x26quot;'
: c == '\x26' ? '\x26amp;'
: '\x26\x23' + c.charCodeAt(0) + ';';
});
};
var escape_html = function(s) {
return s.replace(/(?:[\x3c\x3e\x22\x27\x5c]|\x26(?:(?:[a-zA-z]\w*|\x23(?:\d{1,10}|x[0-9a-fA-F]{2,8}));)?)/g, function(c){
return c == '\x3c' ? '\x26lt;'
: c == '\x3e' ? '\x26gt;'
: c == '\x22' ? '\x26quot;'
: c == '\x26' ? '\x26amp;'
: c.charAt(0) == '\x26' ? c
: '\x26\x23' + c.charCodeAt(0) + ';';
});
};
var HTMARKUP = {
'<hr>': '<hr />', '</hr>': '\n',
'<table>': '<table>\n<tr>', '</table>': '</tr>\n</table>\n',
'</table><table>': '</tr>\n<tr>',
'<|>': '<td>', '</|>': '</td>', '<|=>': '<th>', '</|=>': '</th>',
'<blockquote>': '<blockquote>\n', '</blockquote>': '</blockquote>\n',
'<ul>': '<ul>\n<li>', '</ul>': '</li>\n</ul>\n',
'</ul><ul>': '</li>\n<li>',
'<ol>': '<ol>\n<li>', '</ol>': '</li>\n</ol>\n',
'</ol><ol>': '</li>\n<li>',
'<dl>': '<dl>\n<dt>', '</dl>': '</dd>\n</dl>\n',
'</dl><dl>': '</dd>\n<dt>',
'<**>': '<strong>', '</**>': '</strong>',
'<//>': '<em>', '<///>': '</em>',
'<##>': '<tt>', '</##>': '</tt>',
'<^^>': '<sup>', '</^^>': '</sup>',
'<,,>': '<sub>', '</,,>': '</sub>',
'<__>': '<span class="underline">', '</__>': '</span>',
};
var markup = function(prev, sign) {
var e = '</' + prev + '>';
var s = '<' + sign + '>';
var joint = e + s;
if (HTMARKUP.hasOwnProperty(joint))
return HTMARKUP[joint];
var result = '';
if (prev)
result += HTMARKUP.hasOwnProperty(e) ? HTMARKUP[e] : (e + '\n');
if (sign)
result += HTMARKUP.hasOwnProperty(s) ? HTMARKUP[s] : s;
return result;
};
var StrScanner = function(s) {
this.input = s;
this.pos = 0;
this.epos = s.length;
};
StrScanner.prototype = {
'eos': function() {
return this.pos >= this.epos;
},
'bol': function() {
return this.pos == 0 || this.input.charAt(this.pos - 1) == '\n';
},
'eol': function() {
return this.eos() || this.input.charAt(this.pos) == '\n';
},
'peek': function(n) {
return this.input.substr(this.pos, n);
},
'check_char': function(s) {
return s.indexOf(this.input.charAt(this.pos));
},
'check': function(s) {
return this.peek(s.length) == s;
},
'check_until': function(s) {
return this.input.indexOf(s, this.pos);
},
'scan': function(s) {
var i = this.peek(s.length) == s ? this.pos : -1;
if (i != -1)
this.pos = i + s.length;
return i;
},
'scan_until': function(s) {
var i = this.check_until(s);
if (i != -1)
this.pos = i + s.length;
return i;
},
'skip': function(ch) {
var i = this.pos;
while (this.input.charAt(this.pos) == ch)
++this.pos;
return this.input.slice(i, this.pos);
}
};
var _WikiCreole = {
'to_html': function(text_creole) {
this.yyblock(text_creole);
this.pretty_tag();
return this.result;
},
'yyblock': function(s) {
if (s.length > 0 && s.substr(s.length - 1, 1) == '\n')
s += '\n';
else
s += '\n\n';
var sc = new StrScanner(s);
this.init();
while (this.state)
this.state = this.cycle[this.state].apply(this, [sc]);
},
'init': function() {
this[1] = {'sign': 'p', 'mark': '', 'start': 0, 'end': 0};
this[2] = {'sign': '', 'mark': '', 'start': 0, 'end': 0};
this.state = 'S0';
this.result = '';
this.code = [];
},
'emit_capture1': function(s) {
if (this[1].start >= this[1].end)
return;
if (this[1].sign == 'p')
this.emit_block('p', '');
var data = s.slice(this[1].start, this[1].end);
if (this[1].sign == 'blockquote') {
data = data.replace(/^[ ]*[:>][ ]?/gm, '');
this.result += (new WikiCreole()).to_html(data);
}
else {
this.yyinline(data, this[1].sign);
}
},
'emit_pre': function(s) {
this.emit_block(this[2].sign, this[2].mark);
var data = s.slice(this[2].start, this[2].end);
data = data.replace(/^[ ]\}\}\}$/gm, '}}}');
this.result += escape_htmlall(data);
},
'emit_heading': function(s) {
var n = this[2].mark.length;
n = n > 6 ? 6 : n;
this.emit_block('h' + n, '');
var data = s.slice(this[2].start, this[2].end);
this.yyinline(data, this[2].sign);
},
'emit_block': function(sign, mark) {
var level = mark.length;
var result = '';
var code = this.code;
if (level > 0 && code.length > 0 && code[0][0] == 0)
this.emit_block('', '');
while (code.length >= 2 && level < code[0][0]) {
if (code[1][0] < level) {
code[0][0] = level;
break;
}
var prev = code.shift()[1];
if (prev)
result += markup(prev, '');
}
if (code.length == 0) {
result += markup('', sign);
code.unshift([level, sign]);
}
else if (code[0][1] && code[0][0] < level) {
result += '\n' + markup('', sign);
code.unshift([level, sign]);
}
else {
result += markup(code[0][1], sign);
code[0] = [level, sign];
}
this.result += result;
},
'cycle': {
'S0': function(sc) {
this[1].start = this[1].end = sc.pos;
if (sc.eos())
return null;
return 'S1';
},
'S1': function(sc){
this[2].sign = '';
this[2].mark = '';
if (sc.eos())
return 'S3';
if (! sc.bol())
sc.scan_until('\n');
this[1].end = sc.pos;
if (sc.check('{{{\n'))
return this.capture_maybe_pre(sc);
sc.skip(' ');
if (sc.scan('\n') != -1)
return 'S3';
if (sc.scan('----') != -1)
return this.capture_maybe_hr(sc);
if (sc.check('='))
return this.capture_heading(sc);
if (sc.check('|'))
return this.capture_table(sc);
if (sc.check('>') || (this[1].sign != 'dl' && sc.check(':')))
return this.capture_blockquote(sc);
if (sc.check_char('*#;') != -1)
return this.capture_maybe_list(sc);
return 'S2';
},
'S2': function(sc) {
sc.scan_until('\n');
return 'S1';
},
'S3': function(sc) {
this.emit_capture1(sc.input);
this.emit_block(this[2].sign, this[2].mark);
return 'S8';
},
'S4': function(sc) {
this.emit_capture1(sc.input);
this.emit_block(this[2].sign, this[2].mark);
this[1].sign = this[2].sign;
return 'S0';
},
'S5': function(sc) {
this.emit_capture1(sc.input);
this.emit_pre(sc.input);
return 'S8';
},
'S6': function(sc) {
this.emit_capture1(sc.input);
this.emit_heading(sc.input);
return 'S8';
},
'S7': function(sc) {
this.emit_capture1(sc.input);
this.emit_block(this[2].sign, this[2].mark);
var data = sc.input.slice(this[2].start, this[2].end);
this.yyinline(data, this[2].sign);
return 'S8';
},
'S8': function(sc) {
this[1].sign = 'p';
return 'S0';
}
},
'capture_maybe_pre': function(sc) {
this[2].sign = 'pre';
this[2].start = sc.pos + 4;
this[2].end = sc.scan_until('\n}}}\n');
if (this[2].end == -1)
return 'S2';
return 'S5';
},
'capture_maybe_hr': function(sc) {
this[2].sign = 'hr';
sc.skip('-');
sc.skip(' ');
if (sc.scan('\n') == -1)
return 'S2';
return 'S3';
},
'capture_heading': function(sc) {
this[2].sign = 'heading';
this[2].mark = sc.skip('=');
sc.skip(' ');
this[2].start = sc.pos;
this[2].end = sc.scan_until('\n');
return 'S6';
},
'capture_table': function(sc) {
this[2].sign = 'table';
this[2].start = sc.pos;
this[2].end = sc.scan_until('\n');
return 'S7';
},
'capture_blockquote': function(sc) {
this[2].sign = 'blockquote';
if (this[1].sign == 'blockquote')
return 'S2';
return 'S4';
},
'capture_maybe_list': function(sc) {
var mark = sc.peek(1);
this[2].mark = sc.skip(mark);
if (this[2].mark == '**' || this[2].mark == '##')
if (this[1].sign != 'ul' && this[1].sign != 'ol')
return 'S2';
this[2].sign = mark == '*' ? 'ul' : mark == '#' ? 'ol' : 'dl';
sc.skip(' ');
return 'S4';
},
'yyinline': function(s, blk) {
var inline = /(?:\[\[[^\]]*(?:\][^\]]+)*\]\]|\{\{\{[^\}]*(?:\}\}?[^\}]+)*\}\}\}+|\{\{[^\}]*(?:\}[^\}]+)*\}\}|<<<[^>]*(?:>>?[^>]+)*>>>|<<[^>]*(?:>[^>]+)*>>|\b(?:f|ht)tps?:\x2f\x2f(?:[A-Za-z0-9\-_~&*+=\x2f.!$\x27\(\),;:@?#]|%[0-9A-Fa-f]{2})+(?:[A-Za-z0-9\-_~&*+=\x2f$@#]|%[0-9A-Fa-f]{2})+|\\\\[\n ]*|[*][*]|\x2f\x2f|##|\^\^|,,|__|~(?:\[\[[^\]]*(?:\][^\]]+)*\]\]|\[+|\{\{\{[^\}]*(?:\}\}?[^\}]+)*\}\}\}+|\{\{[^\}]*(?:\}[^\}]+)*\}\}|\{+|<<<[^>]*(?:>>?[^>]+)*>>>|<<[^>]*(?:>[^>]+)*>>|<+|\b(?:f|ht)tps?:\x2f\x2f(?:[A-Za-z0-9\-_~&*+=\x2f.!$\x27\(\),;:@?#]|%[0-9A-Fa-f]{2})+(?:[A-Za-z0-9\-_~&*+=\x2f$@#]|%[0-9A-Fa-f]{2})+|\\\\|[*][*]|\x2f\x2f|##|\^\^|,,|__|\|=?|=+|[^\n ]?)|:|\|=?|=+)/gm;
s = s.replace(/[ \n]+$/, '');
var c = {'pos': 0, 'epos': s.length, 'blk': blk, 'phrase_member': {}, 'phrase_stack': []};
for (;;) {
var a = inline.exec(s);
if (! a) {
this.result += escape_html(s.slice(c.pos));
break;
}
this.result += escape_html(s.slice(c.pos, a.index));
c.pos = inline.lastIndex;
var sign = a[0];
if ( this.emit_nowiki(c, sign)
|| this.emit_bracked_img(c, sign)
|| this.emit_brackted_link(c, sign)
|| this.emit_freestand_link(c, sign)
|| this.emit_br(c, sign)
|| this.ignore_heading_lastmark(c, sign)
|| this.emit_dtdd(c, sign)
|| this.emit_table_data(c, sign)
|| this.emit_phrase(c, sign)
)
continue;
if (sign.substr(0, 3) == '<<<') {
sign = this.trim(sign, 3);
}
else if (sign.substr(0, 2) == '<<') {
sign = this.trim(sign, 2);
}
else if (sign.substr(0, 1) == '~') {
if (sign != '~')
sign = sign.slice(1);
}
this.result += escape_html(sign);
}
this.flush_phrase(c);
if (c.blk == '|' || c.blk == '|=')
this.result += HTMARKUP['</' + c.blk + '>'];
if (c.blk == 'dl')
this.result += '</dt><dd>';
},
'trim': function(s, margin) {
return s.slice(margin, -margin).replace(/^[ ]+/, '').replace(/[ ]+$/, '');
},
'emit_nowiki': function(c, sign) {
if (sign.substr(0, 3) != '{{{')
return false;
this.result += escape_htmlall(this.trim(sign, 3));
return true;
},
'emit_bracked_img': function(c, sign) {
var m = /^\{\{[ ]*([^ \|]*(?:[ ]+[^ \|]+)*)[ ]*(?:\|[ ]*([^ ]*(?:[ ]+[^ ]+)*)[ ]*)?\}\}$/.exec(sign);
if (m == null)
return false;
var src = encodeURI(decodeURI(m[1]));
var alt = m[2] === undefined ? '' : m[2];
this.result += '<img src="' + escape_html(src) + '" alt="'
+ escape_html(alt) + '" />';
return true;
},
'emit_brackted_link': function(c, sign) {
var m = /^\[\[[ ]*([^ \|]*(?:[ ]+[^ \|]+)*)[ ]*(?:\|[ ]*([^ ]*(?:[ ]+[^ ]+)*)[ ]*)?\]\]$/.exec(sign);
if (m == null)
return false;
return this.emit_link(m[1], m[2] === undefined ? m[1] : m[2]);
},
'emit_freestand_link': function(c, sign) {
if (/^(?:ht|f)tp:\x2f\x2f/.test(sign))
return this.emit_link(sign, sign);
else
return false;
},
'emit_link': function(href, text) {
if (! (/^(?:ht|f)tps?:\x2f\x2f/.test(href)))
href = 'http://example.net/wiki/' + href;
href = encodeURI(decodeURI(href));
this.result += '<a href="' + escape_html(href) + '">'
+ escape_html(text) + '</a>';
return true;
},
'emit_br': function(c, sign) {
if (sign.substr(0, 2) != '\\\\')
return false;
this.result += '<br />\n';
return true;
},
'ignore_heading_lastmark' : function(c, sign) {
return sign.charAt(0) == '=' && c.blk == 'heading' && c.pos >= c.epos;
},
'emit_dtdd': function(c, sign) {
if (sign != ':' || c.blk != 'dl')
return false;
this.flush_phrase(c);
this.result += '</dt><dd>';
c.blk = '';
return true;
},
'emit_table_data': function(c, sign) {
if (sign != '|' && sign != '|=')
return false;
if (c.blk != 'table' && c.blk != '|' && c.blk != '|=')
return false;
this.flush_phrase(c);
if (c.blk == '|' || c.blk == '|=') {
this.result += HTMARKUP['</' + c.blk + '>'];
}
if (c.blk != 'table' && c.pos >= c.epos && sign == '|')
c.blk = '';
else {
this.result += HTMARKUP['<' + sign + '>'];
c.blk = sign;
}
return true;
},
'emit_phrase': function(c, sign) {
switch(sign) {
case '**':
case '//':
case '##':
case '^^':
case ',,':
case '__':
if (! c.phrase_member[sign]) {
c.phrase_member[sign] = 1;
c.phrase_stack.unshift(sign);
this.result += HTMARKUP['<' + sign + '>'];
return true;
}
else if (c.phrase_stack[0] == sign) {
c.phrase_member[sign] = 0;
c.phrase_stack.shift();
this.result += HTMARKUP['</' + sign + '>'];
return true;
}
}
return false;
},
'flush_phrase': function(c) {
this.result += c.phrase_stack.map(function(x){
return HTMARKUP['</' + x + '>'];
}).join('');
c.phrase_stack = [];
c.phrase_member = {};
},
'pretty_tag': function() {
this.result = this.result.replace(/(<(?!img|pre)\w[^>]*>)[ ]+/g, '$1');
this.result = this.result.replace(/[ ]+(<\x2f(?!pre)[^>]*>)/g, '$1');
}
};
for (x in _WikiCreole) {
WikiCreole.prototype[x] = _WikiCreole[x];
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.