/* 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

		

	Known to degrade gracefully on:

		IE5.0 PC

		Safari (until it supports functions in replace statements)

	

	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/.  

*/



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

		var old = window.onload;

		

		if (typeof window.onload != 'function') {

			window.onload = function() { CodeHighlighter.init() }

		} else {

			window.onload = function() {

				oldonload();

				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;

		rules.length = 0;

		

		// get stylable elements by filtering out all code elements without the correct className	

		var stylableEls = codeEls.filter(function(item) {return (item.className.indexOf(styleSet.name)>=0)});

		

		// 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+= "&nbsp;";

					return "\n" + spaces;  

				});

				parsed = parsed.replace(/\t/g, "&nbsp;&nbsp;&nbsp;&nbsp;");

				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 in this.styleSets) highlightCode(this.styleSets[i]);

}