|
| 1 | +/* Unobtrustive Code Highlighter By Dan Webb 11/2005 |
| 2 | + Version: 0.4 |
| 3 | +
|
| 4 | + Usage: |
| 5 | + Add a script tag for this script and any stylesets you need to use |
| 6 | + to the page in question, add correct class names to CODE elements, |
| 7 | + define CSS styles for elements. That's it! |
| 8 | +
|
| 9 | + Known to work on: |
| 10 | + IE 5.5+ PC |
| 11 | + Firefox/Mozilla PC/Mac |
| 12 | + Opera 7.23 + PC |
| 13 | + Safari 2 |
| 14 | +
|
| 15 | + Known to degrade gracefully on: |
| 16 | + IE5.0 PC |
| 17 | +
|
| 18 | + Note: IE5.0 fails due to the use of lookahead in some stylesets. To avoid script errors |
| 19 | + in older browsers use expressions that use lookahead in string format when defining stylesets. |
| 20 | +
|
| 21 | + This script is inspired by star-light by entirely cunning Dean Edwards |
| 22 | + http://dean.edwards.name/star-light/. |
| 23 | +*/ |
| 24 | + |
| 25 | +// replace callback support for safari. |
| 26 | +if ("a".replace(/a/, function() {return "b"}) != "b") (function(){ |
| 27 | + var default_replace = String.prototype.replace; |
| 28 | + String.prototype.replace = function(search,replace){ |
| 29 | + // replace is not function |
| 30 | + if(typeof replace != "function"){ |
| 31 | + return default_replace.apply(this,arguments) |
| 32 | + } |
| 33 | + var str = "" + this; |
| 34 | + var callback = replace; |
| 35 | + // search string is not RegExp |
| 36 | + if(!(search instanceof RegExp)){ |
| 37 | + var idx = str.indexOf(search); |
| 38 | + return ( |
| 39 | + idx == -1 ? str : |
| 40 | + default_replace.apply(str,[search,callback(search, idx, str)]) |
| 41 | + ) |
| 42 | + } |
| 43 | + var reg = search; |
| 44 | + var result = []; |
| 45 | + var lastidx = reg.lastIndex; |
| 46 | + var re; |
| 47 | + while((re = reg.exec(str)) != null){ |
| 48 | + var idx = re.index; |
| 49 | + var args = re.concat(idx, str); |
| 50 | + result.push( |
| 51 | + str.slice(lastidx,idx), |
| 52 | + callback.apply(null,args).toString() |
| 53 | + ); |
| 54 | + if(!reg.global){ |
| 55 | + lastidx += RegExp.lastMatch.length; |
| 56 | + break |
| 57 | + }else{ |
| 58 | + lastidx = reg.lastIndex; |
| 59 | + } |
| 60 | + } |
| 61 | + result.push(str.slice(lastidx)); |
| 62 | + return result.join("") |
| 63 | + } |
| 64 | +})(); |
| 65 | + |
| 66 | +var CodeHighlighter = { styleSets : new Array }; |
| 67 | + |
| 68 | +CodeHighlighter.addStyle = function(name, rules) { |
| 69 | + // using push test to disallow older browsers from adding styleSets |
| 70 | + if ([].push) this.styleSets.push({ |
| 71 | + name : name, |
| 72 | + rules : rules, |
| 73 | + ignoreCase : arguments[2] || false |
| 74 | + }) |
| 75 | + |
| 76 | + function setEvent() { |
| 77 | + // set highlighter to run on load (use LowPro if present) |
| 78 | + if (typeof Event != 'undefined' && typeof Event.onReady == 'function') |
| 79 | + return Event.onReady(CodeHighlighter.init.bind(CodeHighlighter)); |
| 80 | + |
| 81 | + var old = window.onload; |
| 82 | + |
| 83 | + if (typeof window.onload != 'function') { |
| 84 | + window.onload = function() { CodeHighlighter.init() }; |
| 85 | + } else { |
| 86 | + window.onload = function() { |
| 87 | + old(); |
| 88 | + CodeHighlighter.init(); |
| 89 | + } |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + // only set the event when the first style is added |
| 94 | + if (this.styleSets.length==1) setEvent(); |
| 95 | +} |
| 96 | + |
| 97 | +CodeHighlighter.init = function() { |
| 98 | + if (!document.getElementsByTagName) return; |
| 99 | + if ("a".replace(/a/, function() {return "b"}) != "b") return; // throw out Safari versions that don't support replace function |
| 100 | + // throw out older browsers |
| 101 | + |
| 102 | + var codeEls = document.getElementsByTagName("CODE"); |
| 103 | + // collect array of all pre elements |
| 104 | + codeEls.filter = function(f) { |
| 105 | + var a = new Array; |
| 106 | + for (var i = 0; i < this.length; i++) if (f(this[i])) a[a.length] = this[i]; |
| 107 | + return a; |
| 108 | + } |
| 109 | + |
| 110 | + var rules = new Array; |
| 111 | + rules.toString = function() { |
| 112 | + // joins regexes into one big parallel regex |
| 113 | + var exps = new Array; |
| 114 | + for (var i = 0; i < this.length; i++) exps.push(this[i].exp); |
| 115 | + return exps.join("|"); |
| 116 | + } |
| 117 | + |
| 118 | + function addRule(className, rule) { |
| 119 | + // add a replace rule |
| 120 | + var exp = (typeof rule.exp != "string")?String(rule.exp).substr(1, String(rule.exp).length-2):rule.exp; |
| 121 | + // converts regex rules to strings and chops of the slashes |
| 122 | + rules.push({ |
| 123 | + className : className, |
| 124 | + exp : "(" + exp + ")", |
| 125 | + length : (exp.match(/(^|[^\\])\([^?]/g) || "").length + 1, // number of subexps in rule |
| 126 | + replacement : rule.replacement || null |
| 127 | + }); |
| 128 | + } |
| 129 | + |
| 130 | + function parse(text, ignoreCase) { |
| 131 | + // main text parsing and replacement |
| 132 | + return text.replace(new RegExp(rules, (ignoreCase)?"gi":"g"), function() { |
| 133 | + var i = 0, j = 1, rule; |
| 134 | + while (rule = rules[i++]) { |
| 135 | + if (arguments[j]) { |
| 136 | + // if no custom replacement defined do the simple replacement |
| 137 | + if (!rule.replacement) return "<span class=\"" + rule.className + "\">" + arguments[0] + "</span>"; |
| 138 | + else { |
| 139 | + // replace $0 with the className then do normal replaces |
| 140 | + var str = rule.replacement.replace("$0", rule.className); |
| 141 | + for (var k = 1; k <= rule.length - 1; k++) str = str.replace("$" + k, arguments[j + k]); |
| 142 | + return str; |
| 143 | + } |
| 144 | + } else j+= rule.length; |
| 145 | + } |
| 146 | + }); |
| 147 | + } |
| 148 | + |
| 149 | + function highlightCode(styleSet) { |
| 150 | + // clear rules array |
| 151 | + var parsed, clsRx = new RegExp("(\\s|^)" + styleSet.name + "(\\s|$)"); |
| 152 | + rules.length = 0; |
| 153 | + |
| 154 | + // get stylable elements by filtering out all code elements without the correct className |
| 155 | + var stylableEls = codeEls.filter(function(item) { return clsRx.test(item.className) }); |
| 156 | + |
| 157 | + // add style rules to parser |
| 158 | + for (var className in styleSet.rules) addRule(className, styleSet.rules[className]); |
| 159 | + |
| 160 | + |
| 161 | + // replace for all elements |
| 162 | + for (var i = 0; i < stylableEls.length; i++) { |
| 163 | + // EVIL hack to fix IE whitespace badness if it's inside a <pre> |
| 164 | + if (/MSIE/.test(navigator.appVersion) && stylableEls[i].parentNode.nodeName == 'PRE') { |
| 165 | + stylableEls[i] = stylableEls[i].parentNode; |
| 166 | + |
| 167 | + parsed = stylableEls[i].innerHTML.replace(/(<code[^>]*>)([^<]*)<\/code>/i, function() { |
| 168 | + return arguments[1] + parse(arguments[2], styleSet.ignoreCase) + "</code>" |
| 169 | + }); |
| 170 | + parsed = parsed.replace(/\n( *)/g, function() { |
| 171 | + var spaces = ""; |
| 172 | + for (var i = 0; i < arguments[1].length; i++) spaces+= " "; |
| 173 | + return "\n" + spaces; |
| 174 | + }); |
| 175 | + parsed = parsed.replace(/\t/g, " "); |
| 176 | + parsed = parsed.replace(/\n(<\/\w+>)?/g, "<br />$1").replace(/<br \/>[\n\r\s]*<br \/>/g, "<p><br></p>"); |
| 177 | + |
| 178 | + } else parsed = parse(stylableEls[i].innerHTML, styleSet.ignoreCase); |
| 179 | + |
| 180 | + stylableEls[i].innerHTML = parsed; |
| 181 | + } |
| 182 | + } |
| 183 | + |
| 184 | + // run highlighter on all stylesets |
| 185 | + for (var i=0; i < this.styleSets.length; i++) { |
| 186 | + highlightCode(this.styleSets[i]); |
| 187 | + } |
| 188 | +} |
0 commit comments