189 lines
5.8 KiB
JavaScript
189 lines
5.8 KiB
JavaScript
|
/* Unobtrustive Code Highlighter By Dan Webb 11/2005
|
||
|
Version: 0.4
|
||
|
|
||
|
Usage:
|
||
|
Add a script tag for this script and any stylesets you need to use
|
||
|
to the page in question, add correct class names to CODE elements,
|
||
|
define CSS styles for elements. That's it!
|
||
|
|
||
|
Known to work on:
|
||
|
IE 5.5+ PC
|
||
|
Firefox/Mozilla PC/Mac
|
||
|
Opera 7.23 + PC
|
||
|
Safari 2
|
||
|
|
||
|
Known to degrade gracefully on:
|
||
|
IE5.0 PC
|
||
|
|
||
|
Note: IE5.0 fails due to the use of lookahead in some stylesets. To avoid script errors
|
||
|
in older browsers use expressions that use lookahead in string format when defining stylesets.
|
||
|
|
||
|
This script is inspired by star-light by entirely cunning Dean Edwards
|
||
|
http://dean.edwards.name/star-light/.
|
||
|
*/
|
||
|
|
||
|
// replace callback support for safari.
|
||
|
if ("a".replace(/a/, function() {return "b"}) != "b") (function(){
|
||
|
var default_replace = String.prototype.replace;
|
||
|
String.prototype.replace = function(search,replace){
|
||
|
// replace is not function
|
||
|
if(typeof replace != "function"){
|
||
|
return default_replace.apply(this,arguments)
|
||
|
}
|
||
|
var str = "" + this;
|
||
|
var callback = replace;
|
||
|
// search string is not RegExp
|
||
|
if(!(search instanceof RegExp)){
|
||
|
var idx = str.indexOf(search);
|
||
|
return (
|
||
|
idx == -1 ? str :
|
||
|
default_replace.apply(str,[search,callback(search, idx, str)])
|
||
|
)
|
||
|
}
|
||
|
var reg = search;
|
||
|
var result = [];
|
||
|
var lastidx = reg.lastIndex;
|
||
|
var re;
|
||
|
while((re = reg.exec(str)) != null){
|
||
|
var idx = re.index;
|
||
|
var args = re.concat(idx, str);
|
||
|
result.push(
|
||
|
str.slice(lastidx,idx),
|
||
|
callback.apply(null,args).toString()
|
||
|
);
|
||
|
if(!reg.global){
|
||
|
lastidx += RegExp.lastMatch.length;
|
||
|
break
|
||
|
}else{
|
||
|
lastidx = reg.lastIndex;
|
||
|
}
|
||
|
}
|
||
|
result.push(str.slice(lastidx));
|
||
|
return result.join("")
|
||
|
}
|
||
|
})();
|
||
|
|
||
|
var CodeHighlighter = { styleSets : new Array };
|
||
|
|
||
|
CodeHighlighter.addStyle = function(name, rules) {
|
||
|
// using push test to disallow older browsers from adding styleSets
|
||
|
if ([].push) this.styleSets.push({
|
||
|
name : name,
|
||
|
rules : rules,
|
||
|
ignoreCase : arguments[2] || false
|
||
|
})
|
||
|
|
||
|
function setEvent() {
|
||
|
// set highlighter to run on load (use LowPro if present)
|
||
|
if (typeof Event != 'undefined' && typeof Event.onReady == 'function')
|
||
|
return Event.onReady(CodeHighlighter.init.bind(CodeHighlighter));
|
||
|
|
||
|
var old = window.onload;
|
||
|
|
||
|
if (typeof window.onload != 'function') {
|
||
|
window.onload = function() { CodeHighlighter.init() };
|
||
|
} else {
|
||
|
window.onload = function() {
|
||
|
old();
|
||
|
CodeHighlighter.init();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// only set the event when the first style is added
|
||
|
if (this.styleSets.length==1) setEvent();
|
||
|
}
|
||
|
|
||
|
CodeHighlighter.init = function() {
|
||
|
if (!document.getElementsByTagName) return;
|
||
|
if ("a".replace(/a/, function() {return "b"}) != "b") return; // throw out Safari versions that don't support replace function
|
||
|
// throw out older browsers
|
||
|
|
||
|
var codeEls = document.getElementsByTagName("CODE");
|
||
|
// collect array of all pre elements
|
||
|
codeEls.filter = function(f) {
|
||
|
var a = new Array;
|
||
|
for (var i = 0; i < this.length; i++) if (f(this[i])) a[a.length] = this[i];
|
||
|
return a;
|
||
|
}
|
||
|
|
||
|
var rules = new Array;
|
||
|
rules.toString = function() {
|
||
|
// joins regexes into one big parallel regex
|
||
|
var exps = new Array;
|
||
|
for (var i = 0; i < this.length; i++) exps.push(this[i].exp);
|
||
|
return exps.join("|");
|
||
|
}
|
||
|
|
||
|
function addRule(className, rule) {
|
||
|
// add a replace rule
|
||
|
var exp = (typeof rule.exp != "string")?String(rule.exp).substr(1, String(rule.exp).length-2):rule.exp;
|
||
|
// converts regex rules to strings and chops of the slashes
|
||
|
rules.push({
|
||
|
className : className,
|
||
|
exp : "(" + exp + ")",
|
||
|
length : (exp.match(/(^|[^\\])\([^?]/g) || "").length + 1, // number of subexps in rule
|
||
|
replacement : rule.replacement || null
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function parse(text, ignoreCase) {
|
||
|
// main text parsing and replacement
|
||
|
return text.replace(new RegExp(rules, (ignoreCase)?"gi":"g"), function() {
|
||
|
var i = 0, j = 1, rule;
|
||
|
while (rule = rules[i++]) {
|
||
|
if (arguments[j]) {
|
||
|
// if no custom replacement defined do the simple replacement
|
||
|
if (!rule.replacement) return "<span class=\"" + rule.className + "\">" + arguments[0] + "</span>";
|
||
|
else {
|
||
|
// replace $0 with the className then do normal replaces
|
||
|
var str = rule.replacement.replace("$0", rule.className);
|
||
|
for (var k = 1; k <= rule.length - 1; k++) str = str.replace("$" + k, arguments[j + k]);
|
||
|
return str;
|
||
|
}
|
||
|
} else j+= rule.length;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function highlightCode(styleSet) {
|
||
|
// clear rules array
|
||
|
var parsed, clsRx = new RegExp("(\\s|^)" + styleSet.name + "(\\s|$)");
|
||
|
rules.length = 0;
|
||
|
|
||
|
// get stylable elements by filtering out all code elements without the correct className
|
||
|
var stylableEls = codeEls.filter(function(item) { return clsRx.test(item.className) });
|
||
|
|
||
|
// add style rules to parser
|
||
|
for (var className in styleSet.rules) addRule(className, styleSet.rules[className]);
|
||
|
|
||
|
|
||
|
// replace for all elements
|
||
|
for (var i = 0; i < stylableEls.length; i++) {
|
||
|
// EVIL hack to fix IE whitespace badness if it's inside a <pre>
|
||
|
if (/MSIE/.test(navigator.appVersion) && stylableEls[i].parentNode.nodeName == 'PRE') {
|
||
|
stylableEls[i] = stylableEls[i].parentNode;
|
||
|
|
||
|
parsed = stylableEls[i].innerHTML.replace(/(<code[^>]*>)([^<]*)<\/code>/i, function() {
|
||
|
return arguments[1] + parse(arguments[2], styleSet.ignoreCase) + "</code>"
|
||
|
});
|
||
|
parsed = parsed.replace(/\n( *)/g, function() {
|
||
|
var spaces = "";
|
||
|
for (var i = 0; i < arguments[1].length; i++) spaces+= " ";
|
||
|
return "\n" + spaces;
|
||
|
});
|
||
|
parsed = parsed.replace(/\t/g, " ");
|
||
|
parsed = parsed.replace(/\n(<\/\w+>)?/g, "<br />$1").replace(/<br \/>[\n\r\s]*<br \/>/g, "<p><br></p>");
|
||
|
|
||
|
} else parsed = parse(stylableEls[i].innerHTML, styleSet.ignoreCase);
|
||
|
|
||
|
stylableEls[i].innerHTML = parsed;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// run highlighter on all stylesets
|
||
|
for (var i=0; i < this.styleSets.length; i++) {
|
||
|
highlightCode(this.styleSets[i]);
|
||
|
}
|
||
|
}
|