554 lines
15 KiB
JavaScript
554 lines
15 KiB
JavaScript
/**************\
|
|
* ____ _____
|
|
* DlHighlight -- a JavaScript-based syntax highlighting engine. \ /_ / /
|
|
* \ / / /
|
|
* Author: Mihai Bazon, http://mihai.bazon.net/blog \/ /_ /
|
|
* Copyright: (c) Dynarch.com 2007. All rights reserved. \ / /
|
|
* http://www.dynarch.com/ / /
|
|
* \/
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
\******************************************************************************/
|
|
|
|
|
|
/*
|
|
*
|
|
* This thing only cares to colorize a piece of text. It has nothing to do
|
|
* with the DOM, with reading existing text from the DOM and to insert the
|
|
* formatted code back. This facility is present in helpers.js.
|
|
*
|
|
* Assuming the unformatted code is in the "code" variable, use DlHighlight
|
|
* this way:
|
|
*
|
|
* var hl = new DlHighlight({ lang : "js",
|
|
* lineNumbers : true });
|
|
*
|
|
* formatted = hl.doItNow(code);
|
|
*
|
|
* Now you have in "formatted" the colored version.
|
|
*
|
|
* Supported parameters are:
|
|
*
|
|
* - "lang" (required) to declare the language
|
|
* - "lineNumbers" (optional) if you want line numbers
|
|
* - "showWhitespace" (optional) if you want to display whitespace
|
|
* in strings as underscores
|
|
* - "noTrim" (optional) pass *true* if you want not to ignore empty
|
|
* newlines at the end of the code
|
|
*
|
|
*/
|
|
|
|
var DlHighlight = (function(){
|
|
|
|
var H = function(args) {
|
|
var self = this;
|
|
this.args = {};
|
|
function D(name, val) {
|
|
if (name in args)
|
|
val = args[name];
|
|
self.args[name] = val;
|
|
}
|
|
D("replaceTabs", null);
|
|
D("lineNumbers", false);
|
|
D("noTrim", false);
|
|
D("showWhitespace", false);
|
|
var lang = this.lang = H.LANG[args.lang];
|
|
this.tokenParsers = lang.tokens.slice(0).reverse();
|
|
if (this.args.replaceTabs != null) {
|
|
var tab = " ";
|
|
while (--this.args.replaceTabs > 0)
|
|
tab += " ";
|
|
this.args.replaceTabs = tab;
|
|
}
|
|
};
|
|
|
|
H.is_ie = /MSIE/.test(navigator.userAgent) && !/Gecko|KHTML|Opera/.test(navigator.userAgent);
|
|
|
|
// definitions useful for most languages out there
|
|
H.BASE = {
|
|
|
|
COMMENT_CPP : function(txt) {
|
|
if (txt.charAt(0) == "/" && txt.charAt(1) == "/") {
|
|
var nl = txt.indexOf("\n");
|
|
if (nl == -1)
|
|
nl = txt.length;
|
|
var c = this.lang.onComment.call(this, this._he(txt.substring(2, nl)));
|
|
return {
|
|
content : { escaped: c },
|
|
style : "comment comment-line",
|
|
type : "comment",
|
|
index : nl,
|
|
before : "//"
|
|
};
|
|
}
|
|
},
|
|
|
|
COMMENT_C : function(txt) {
|
|
if (txt.charAt(0) == "/" && txt.charAt(1) == "*") {
|
|
var nl = txt.indexOf("*/"), c, index = nl;
|
|
if (nl == -1)
|
|
nl = index = txt.length;
|
|
else
|
|
index += 2;
|
|
c = this.lang.onComment.call(this, this._he(txt.substring(2, nl)));
|
|
c = c.replace(/^\s*[*\\|]+/mg, function(s) {
|
|
return "<span class='before'>" + s + "</span>";
|
|
});
|
|
return {
|
|
content : { escaped: c },
|
|
before : "/*",
|
|
after : "*/",
|
|
index : index,
|
|
style : "comment comment-multiline",
|
|
type : "comment"
|
|
};
|
|
|
|
}
|
|
},
|
|
|
|
STRING : {
|
|
regexp : /^(\x22(\\.|[^\x22\\])*\x22|\x27(\\.|[^\x27\\])*\x27)/g,
|
|
content : function(m) {
|
|
m = m[1];
|
|
m = m.substr(1, m.length - 2);
|
|
if (this.args.showWhitespace)
|
|
m = m.replace(/\x20/g, "_");
|
|
return m;
|
|
},
|
|
before : function(m) { return m[1].charAt(0); },
|
|
after : function(m) { return m[1].charAt(0); },
|
|
type : "string",
|
|
style : "string"
|
|
},
|
|
|
|
PAREN : {
|
|
regexp : /^[\](){}\[]/g,
|
|
content : 0,
|
|
type : "paren",
|
|
style : "paren"
|
|
},
|
|
|
|
OPERATOR : function(txt) {
|
|
var m = /^[<>!+=%&*\x2f|?:-]+/.exec(txt);
|
|
if (m && m[0] != "!/") return {
|
|
content : m[0],
|
|
index : m.lastIndex,
|
|
type : "operator",
|
|
style : "operator"
|
|
};
|
|
}
|
|
|
|
};
|
|
|
|
H.prototype = {
|
|
|
|
formatToken : function(tok) {
|
|
var cls = tok.style, html = buffer();
|
|
if (cls instanceof Array)
|
|
cls = cls.join(" ");
|
|
html("<span class='", cls, "'>");
|
|
if (tok.before)
|
|
html("<span class='before'>", this._he(tok.before), "</span>");
|
|
html(this._he(tok.content));
|
|
if (tok.after)
|
|
html("<span class='after'>", this._he(tok.after), "</span>");
|
|
html("</span>");
|
|
return html.get();
|
|
},
|
|
|
|
formatUnknown : function(txt) {
|
|
return this._he(txt);
|
|
},
|
|
|
|
getLastToken : function(pos) {
|
|
return this.tokens[this.tokens.length - (pos || 0) - 1];
|
|
},
|
|
|
|
lastTokenType : function(re) {
|
|
var t = this.getLastToken();
|
|
if (t)
|
|
return re.test(t.type);
|
|
return false;
|
|
},
|
|
|
|
parseToken : function(test, code) {
|
|
var m, tok;
|
|
if (test.regexp) {
|
|
test.regexp.lastIndex = 0;
|
|
m = test.regexp.exec(code);
|
|
if (m) {
|
|
tok = { type : test.type,
|
|
style : test.style,
|
|
index : test.regexp.lastIndex
|
|
};
|
|
reAdd(this, "before", m, test, tok);
|
|
reAdd(this, "after", m, test, tok);
|
|
reAdd(this, "content", m, test, tok);
|
|
}
|
|
} else {
|
|
tok = test.call(this, code);
|
|
}
|
|
return tok;
|
|
},
|
|
|
|
doItNow : function(code) {
|
|
this.lang.start.call(this, code);
|
|
if (!this.args.noTrim)
|
|
code = code.replace(/\s+$/, "");
|
|
var formatted = [], T = this.tokenParsers, m, unknown, tok, i, f = 0, tokens;
|
|
unknown = "";
|
|
tokens = this.tokens = [];
|
|
while (code.length > 0) {
|
|
// jumping whitespace one character at a time
|
|
// might eat a lot of time, let's skip it
|
|
// quickly
|
|
m = /^\s+/.exec(code);
|
|
if (m) {
|
|
unknown += m[0];
|
|
code = code.substr(m[0].length);
|
|
}
|
|
for (i = T.length; --i >= 0;) {
|
|
tok = this.parseToken(T[i], code);
|
|
if (tok)
|
|
break;
|
|
}
|
|
if (tok) {
|
|
if (unknown)
|
|
formatted[f++] = unknown;
|
|
unknown = "";
|
|
if (!(tok instanceof Array))
|
|
tok = [ tok ];
|
|
var index = 0;
|
|
tokens.push.apply(tokens, tok);
|
|
for (var j = 0; j < tok.length; ++j) {
|
|
var t = tok[j];
|
|
formatted[f++] = t;
|
|
index += getNextIndex(t);
|
|
}
|
|
code = code.substr(index);
|
|
} else {
|
|
unknown += code.charAt(0);
|
|
code = code.substr(1);
|
|
}
|
|
}
|
|
if (unknown)
|
|
formatted[f++] = unknown;
|
|
for (i = formatted.length; --i >= 0;) {
|
|
f = formatted[i];
|
|
if (typeof f == "string")
|
|
formatted[i] = this.formatUnknown(f);
|
|
else
|
|
formatted[i] = this.formatToken(f);
|
|
}
|
|
var html = formatted.join("");
|
|
i = this.args.lineNumbers;
|
|
if (i) {
|
|
if (typeof i != "number")
|
|
i = 0;
|
|
html = html.replace(/^/mg, function() {
|
|
return "<span class='line-numbers'>" + (++i) + "</span>";
|
|
});
|
|
this.args.lineNumbers = i;
|
|
}
|
|
// html = html.replace(/\n/g, "<br />");
|
|
this.lang.stop.call(this);
|
|
return html;
|
|
},
|
|
|
|
_he : function(str) {
|
|
if (str.escaped)
|
|
return str.escaped;
|
|
str = str.replace(he_re, function(c) {
|
|
return he_re_val[c];
|
|
});
|
|
if (this.args.replaceTabs)
|
|
str = str.replace(/\t/g, this.args.replaceTabs);
|
|
return str;
|
|
}
|
|
|
|
};
|
|
|
|
var he_re = /[&<>]/g, he_re_val = {
|
|
"&" : "&",
|
|
"<" : "<",
|
|
">" : ">"
|
|
};
|
|
|
|
H.LANG = function(id, tokens) {
|
|
if (arguments.length > 0) {
|
|
H.LANG[id] = this;
|
|
this.tokens = tokens;
|
|
}
|
|
};
|
|
|
|
H.registerLang = function(type, tokens) {
|
|
F.prototype = new H.LANG;
|
|
F.prototype.constructor = F;
|
|
function F() { H.LANG.call(this, type, tokens); };
|
|
return new F();
|
|
};
|
|
|
|
var P = H.LANG.prototype;
|
|
P.start = P.stop = function(){};
|
|
|
|
P.onComment = function(c) {
|
|
return makeUrls(c);
|
|
};
|
|
|
|
function makeUrls(s) {
|
|
return s.replace(/\b((https?|ftp):\x2f\x2f[^\s\x22]+)/g, function(url) {
|
|
return "<a href='" + url + "'>" + url + "</a>";
|
|
});
|
|
};
|
|
|
|
function reAdd(self, c, m, test, tok) {
|
|
if (test[c] != null) {
|
|
if (typeof test[c] == "number") {
|
|
tok[c] = m[test[c]];
|
|
} else if (typeof test[c] == "function") {
|
|
tok[c] = test[c].call(self, m);
|
|
} else {
|
|
tok[c] = test[c];
|
|
}
|
|
}
|
|
}
|
|
|
|
function getNextIndex(tok) {
|
|
var index = tok.index || 0;
|
|
if (!index) {
|
|
// console.log("No index in %s", tok.style);
|
|
if (tok.before)
|
|
index += tok.before.length;
|
|
if (tok.content)
|
|
index += tok.content.length;
|
|
if (tok.after)
|
|
index += tok.after.length;
|
|
}
|
|
return index;
|
|
}
|
|
|
|
var buffer = H.is_ie
|
|
? function() {
|
|
var a = [], idx = 0, f = function() {
|
|
for (var i = 0; i < arguments.length; ++i)
|
|
a[idx++] = arguments[i];
|
|
};
|
|
f.get = function() { return a.join(""); };
|
|
return f;
|
|
} : function() {
|
|
var str = "", f = function() {
|
|
str = str.concat.apply(str, arguments);
|
|
};
|
|
f.get = function() { return str; };
|
|
return f;
|
|
};
|
|
|
|
/**************\
|
|
* ____ _____
|
|
* DlHighlight -- a JavaScript-based syntax highlighting engine. \ /_ / /
|
|
* \ / / /
|
|
* Author: Mihai Bazon, http://mihai.bazon.net/blog \/ /_ /
|
|
* Copyright: (c) Dynarch.com 2007. All rights reserved. \ / /
|
|
* http://www.dynarch.com/ / /
|
|
* \/
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
\******************************************************************************/
|
|
|
|
var T = {
|
|
|
|
COMMENT: function(txt) {
|
|
if (txt.indexOf("<!--") == 0) {
|
|
var nl = txt.indexOf("--", 4);
|
|
if (nl == -1)
|
|
nl = txt.length;
|
|
return {
|
|
before : "<!--",
|
|
after : "-->",
|
|
content : txt.substring(4, nl),
|
|
index : nl + 3,
|
|
type : "comment",
|
|
style : "comment"
|
|
}
|
|
}
|
|
},
|
|
|
|
STRING: function(txt) {
|
|
if (this.inXmlTag)
|
|
return this.parseToken(H.BASE.STRING, txt);
|
|
},
|
|
|
|
ATTRIBUTE: function(txt) {
|
|
var r = null;
|
|
if (this.inXmlTag) {
|
|
var m = /^([a-z0-9_-]+)(\s*)=/i.exec(txt);
|
|
if (m) {
|
|
return [ { content : m[1],
|
|
style : "builtin xml-attribute" },
|
|
{ content : m[2] }, // whitespace
|
|
{ content : "=",
|
|
style : "operator" }
|
|
];
|
|
}
|
|
}
|
|
return r;
|
|
},
|
|
|
|
ENTITY: {
|
|
regexp : /^&(\w+);/g,
|
|
before : "&",
|
|
after : ";",
|
|
content : 1,
|
|
type : "builtin",
|
|
style : "builtin xml-entity"
|
|
},
|
|
|
|
START_TAG: function(txt) {
|
|
var m = /^<([a-z0-9_-]+)/i.exec(txt);
|
|
if (m) {
|
|
this.inXmlTag = m[1];
|
|
return [ { content : "<",
|
|
style : "paren xml-tagangle" },
|
|
{ content : m[1],
|
|
style : "keyword xml-tag xml-tag-open" } ];
|
|
}
|
|
},
|
|
|
|
END_TAG: function(txt) {
|
|
var m = /^<\x2f([a-z0-9_-]+)(\s*>)/i.exec(txt);
|
|
if (m) {
|
|
return [ { content : "</",
|
|
style : "paren xml-tagangle" },
|
|
{ content : m[1],
|
|
style : "keyword xml-tag xml-tag-close" },
|
|
{ content : m[2],
|
|
style : "paren xml-tagangle" } ];
|
|
}
|
|
},
|
|
|
|
END_ANGLE: function(txt) {
|
|
var m = /^\x2f?>/.exec(txt);
|
|
if (m) {
|
|
this.inXmlTag = false;
|
|
return {
|
|
content : m[0],
|
|
style : "paren xml-tagangle"
|
|
};
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
var lang = H.registerLang(
|
|
"xml", [ T.COMMENT,
|
|
T.STRING,
|
|
T.ATTRIBUTE,
|
|
T.ENTITY,
|
|
T.START_TAG,
|
|
T.END_TAG,
|
|
T.END_ANGLE ]);
|
|
|
|
lang.T = T;
|
|
|
|
lang.start = function() {
|
|
this.inXmlTag = false;
|
|
};
|
|
|
|
/**************\
|
|
* ____ _____
|
|
* DlHighlight -- a JavaScript-based syntax highlighting engine. \ /_ / /
|
|
* \ / / /
|
|
* Author: Mihai Bazon, http://mihai.bazon.net/blog \/ /_ /
|
|
* Copyright: (c) Dynarch.com 2007. All rights reserved. \ / /
|
|
* http://www.dynarch.com/ / /
|
|
* \/
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
\******************************************************************************/
|
|
|
|
// Inherits most parsers from XML, but modifies END_ANGLE to highlight SCRIPT tags
|
|
|
|
var re_get_script = /([^\0]*?)<\x2fscript>/gi;
|
|
|
|
var xml = H.LANG.xml;
|
|
|
|
function END_ANGLE(txt) {
|
|
var m = /^\x2f?>/.exec(txt);
|
|
if (m) {
|
|
var tag = this.inXmlTag;
|
|
this.inXmlTag = false;
|
|
var tok = [{ content : m[0],
|
|
style : "paren xml-tagangle" }];
|
|
if (/^script$/i.test(tag) && !/><\x2fscript>/i.test(txt)) {
|
|
re_get_script.lastIndex = 1;
|
|
var m = re_get_script.exec(txt);
|
|
if (m && m[1] && m.index == 1) {
|
|
var code = m[1];
|
|
var index = re_get_script.lastIndex - 10;
|
|
var js = new H({ lang: "js",
|
|
noTrim: true }).doItNow(code);
|
|
var jstok = {
|
|
content : { escaped: js },
|
|
style : "xml-inline-script",
|
|
index : index
|
|
};
|
|
tok.push(jstok);
|
|
}
|
|
}
|
|
return tok;
|
|
}
|
|
}
|
|
|
|
H.registerLang("html", [ xml.T.COMMENT,
|
|
xml.T.STRING,
|
|
xml.T.ATTRIBUTE,
|
|
xml.T.ENTITY,
|
|
xml.T.START_TAG,
|
|
xml.T.END_TAG,
|
|
END_ANGLE
|
|
]);
|
|
|
|
return H;
|
|
|
|
})();
|
|
|
|
export default DlHighlight; |