1 /* Unobtrustive Code Highlighter By Dan Webb 11/2005
\r
5 Add a script tag for this script and any stylesets you need to use
\r
6 to the page in question, add correct class names to CODE elements,
\r
7 define CSS styles for elements. That's it!
\r
11 Firefox/Mozilla PC/Mac
\r
15 Known to degrade gracefully on:
\r
18 Note: IE5.0 fails due to the use of lookahead in some stylesets. To avoid script errors
\r
19 in older browsers use expressions that use lookahead in string format when defining stylesets.
\r
21 This script is inspired by star-light by entirely cunning Dean Edwards
\r
22 http://dean.edwards.name/star-light/.
\r
25 // replace callback support for safari.
\r
26 if ("a".replace(/a/, function() {return "b"}) != "b") (function(){
\r
27 var default_replace = String.prototype.replace;
\r
28 String.prototype.replace = function(search,replace){
\r
29 // replace is not function
\r
30 if(typeof replace != "function"){
\r
31 return default_replace.apply(this,arguments)
\r
33 var str = "" + this;
\r
34 var callback = replace;
\r
35 // search string is not RegExp
\r
36 if(!(search instanceof RegExp)){
\r
37 var idx = str.indexOf(search);
\r
40 default_replace.apply(str,[search,callback(search, idx, str)])
\r
45 var lastidx = reg.lastIndex;
\r
47 while((re = reg.exec(str)) != null){
\r
49 var args = re.concat(idx, str);
\r
51 str.slice(lastidx,idx),
\r
52 callback.apply(null,args).toString()
\r
55 lastidx += RegExp.lastMatch.length;
\r
58 lastidx = reg.lastIndex;
\r
61 result.push(str.slice(lastidx));
\r
62 return result.join("")
\r
66 var CodeHighlighter = { styleSets : new Array };
\r
68 CodeHighlighter.addStyle = function(name, rules) {
\r
69 // using push test to disallow older browsers from adding styleSets
\r
70 if ([].push) this.styleSets.push({
\r
73 ignoreCase : arguments[2] || false
\r
76 function setEvent() {
\r
77 // set highlighter to run on load (use LowPro if present)
\r
78 if (typeof Event != 'undefined' && typeof Event.onReady == 'function')
\r
79 return Event.onReady(CodeHighlighter.init.bind(CodeHighlighter));
\r
81 var old = window.onload;
\r
83 if (typeof window.onload != 'function') {
\r
84 window.onload = function() { CodeHighlighter.init() };
\r
86 window.onload = function() {
\r
88 CodeHighlighter.init();
\r
93 // only set the event when the first style is added
\r
94 if (this.styleSets.length==1) setEvent();
\r
97 CodeHighlighter.init = function() {
\r
98 if (!document.getElementsByTagName) return;
\r
99 if ("a".replace(/a/, function() {return "b"}) != "b") return; // throw out Safari versions that don't support replace function
\r
100 // throw out older browsers
\r
102 var codeEls = document.getElementsByTagName("CODE");
\r
103 // collect array of all pre elements
\r
104 codeEls.filter = function(f) {
\r
106 for (var i = 0; i < this.length; i++) if (f(this[i])) a[a.length] = this[i];
\r
110 var rules = new Array;
\r
111 rules.toString = function() {
\r
112 // joins regexes into one big parallel regex
\r
113 var exps = new Array;
\r
114 for (var i = 0; i < this.length; i++) exps.push(this[i].exp);
\r
115 return exps.join("|");
\r
118 function addRule(className, rule) {
\r
119 // add a replace rule
\r
120 var exp = (typeof rule.exp != "string")?String(rule.exp).substr(1, String(rule.exp).length-2):rule.exp;
\r
121 // converts regex rules to strings and chops of the slashes
\r
123 className : className,
\r
124 exp : "(" + exp + ")",
\r
125 length : (exp.match(/(^|[^\\])\([^?]/g) || "").length + 1, // number of subexps in rule
\r
126 replacement : rule.replacement || null
\r
130 function parse(text, ignoreCase) {
\r
131 // main text parsing and replacement
\r
132 return text.replace(new RegExp(rules, (ignoreCase)?"gi":"g"), function() {
\r
133 var i = 0, j = 1, rule;
\r
134 while (rule = rules[i++]) {
\r
135 if (arguments[j]) {
\r
136 // if no custom replacement defined do the simple replacement
\r
137 if (!rule.replacement) return "<span class=\"" + rule.className + "\">" + arguments[0] + "</span>";
\r
139 // replace $0 with the className then do normal replaces
\r
140 var str = rule.replacement.replace("$0", rule.className);
\r
141 for (var k = 1; k <= rule.length - 1; k++) str = str.replace("$" + k, arguments[j + k]);
\r
144 } else j+= rule.length;
\r
149 function highlightCode(styleSet) {
\r
150 // clear rules array
\r
151 var parsed, clsRx = new RegExp("(\\s|^)" + styleSet.name + "(\\s|$)");
\r
154 // get stylable elements by filtering out all code elements without the correct className
\r
155 var stylableEls = codeEls.filter(function(item) { return clsRx.test(item.className) });
\r
157 // add style rules to parser
\r
158 for (var className in styleSet.rules) addRule(className, styleSet.rules[className]);
\r
161 // replace for all elements
\r
162 for (var i = 0; i < stylableEls.length; i++) {
\r
163 // EVIL hack to fix IE whitespace badness if it's inside a <pre>
\r
164 if (/MSIE/.test(navigator.appVersion) && stylableEls[i].parentNode.nodeName == 'PRE') {
\r
165 stylableEls[i] = stylableEls[i].parentNode;
\r
167 parsed = stylableEls[i].innerHTML.replace(/(<code[^>]*>)([^<]*)<\/code>/i, function() {
\r
168 return arguments[1] + parse(arguments[2], styleSet.ignoreCase) + "</code>"
\r
170 parsed = parsed.replace(/\n( *)/g, function() {
\r
172 for (var i = 0; i < arguments[1].length; i++) spaces+= " ";
\r
173 return "\n" + spaces;
\r
175 parsed = parsed.replace(/\t/g, " ");
\r
176 parsed = parsed.replace(/\n(<\/\w+>)?/g, "<br />$1").replace(/<br \/>[\n\r\s]*<br \/>/g, "<p><br></p>");
\r
178 } else parsed = parse(stylableEls[i].innerHTML, styleSet.ignoreCase);
\r
180 stylableEls[i].innerHTML = parsed;
\r
184 // run highlighter on all stylesets
\r
185 for (var i=0; i < this.styleSets.length; i++) {
\r
186 highlightCode(this.styleSets[i]);
\r