1/* 2Syntax highlighting with language autodetection. 3http://softwaremaniacs.org/soft/highlight/ 4*/ 5 6var hljs = new function() { 7 var LANGUAGES = {} 8 var selected_languages = {}; 9 10 function escape(value) { 11 return value.replace(/&/gm, '&').replace(/</gm, '<').replace(/>/gm, '>'); 12 } 13 14 function contains(array, item) { 15 if (!array) 16 return false; 17 for (var i = 0; i < array.length; i++) 18 if (array[i] == item) 19 return true; 20 return false; 21 } 22 23 function highlight(language_name, value) { 24 function compileSubModes(mode, language) { 25 mode.sub_modes = []; 26 for (var i = 0; i < mode.contains.length; i++) { 27 for (var j = 0; j < language.modes.length; j++) { 28 if (language.modes[j].className == mode.contains[i]) { 29 mode.sub_modes[mode.sub_modes.length] = language.modes[j]; 30 } 31 } 32 } 33 } 34 35 function subMode(lexem, mode) { 36 if (!mode.contains) { 37 return null; 38 } 39 if (!mode.sub_modes) { 40 compileSubModes(mode, language); 41 } 42 for (var i = 0; i < mode.sub_modes.length; i++) { 43 if (mode.sub_modes[i].beginRe.test(lexem)) { 44 return mode.sub_modes[i]; 45 } 46 } 47 return null; 48 } 49 50 function endOfMode(mode_index, lexem) { 51 if (modes[mode_index].end && modes[mode_index].endRe.test(lexem)) 52 return 1; 53 if (modes[mode_index].endsWithParent) { 54 var level = endOfMode(mode_index - 1, lexem); 55 return level ? level + 1 : 0; 56 } 57 return 0; 58 } 59 60 function isIllegal(lexem, mode) { 61 return mode.illegalRe && mode.illegalRe.test(lexem); 62 } 63 64 function compileTerminators(mode, language) { 65 var terminators = []; 66 67 function addTerminator(re) { 68 if (!contains(terminators, re)) { 69 terminators[terminators.length] = re; 70 } 71 } 72 73 if (mode.contains) 74 for (var i = 0; i < language.modes.length; i++) { 75 if (contains(mode.contains, language.modes[i].className)) { 76 addTerminator(language.modes[i].begin); 77 } 78 } 79 80 var index = modes.length - 1; 81 do { 82 if (modes[index].end) { 83 addTerminator(modes[index].end); 84 } 85 index--; 86 } while (modes[index + 1].endsWithParent); 87 88 if (mode.illegal) { 89 addTerminator(mode.illegal); 90 } 91 92 var terminator_re = '(' + terminators[0]; 93 for (var i = 0; i < terminators.length; i++) 94 terminator_re += '|' + terminators[i]; 95 terminator_re += ')'; 96 return langRe(language, terminator_re); 97 } 98 99 function eatModeChunk(value, index) { 100 var mode = modes[modes.length - 1]; 101 if (!mode.terminators) { 102 mode.terminators = compileTerminators(mode, language); 103 } 104 value = value.substr(index); 105 var match = mode.terminators.exec(value); 106 if (!match) 107 return [value, '', true]; 108 if (match.index == 0) 109 return ['', match[0], false]; 110 else 111 return [value.substr(0, match.index), match[0], false]; 112 } 113 114 function keywordMatch(mode, match) { 115 var match_str = language.case_insensitive ? match[0].toLowerCase() : match[0] 116 for (var className in mode.keywordGroups) { 117 if (!mode.keywordGroups.hasOwnProperty(className)) 118 continue; 119 var value = mode.keywordGroups[className].hasOwnProperty(match_str); 120 if (value) 121 return [className, value]; 122 } 123 return false; 124 } 125 126 function processKeywords(buffer, mode) { 127 if (!mode.keywords || !mode.lexems) 128 return escape(buffer); 129 if (!mode.lexemsRe) { 130 var lexems_re = '(' + mode.lexems[0]; 131 for (var i = 1; i < mode.lexems.length; i++) 132 lexems_re += '|' + mode.lexems[i]; 133 lexems_re += ')'; 134 mode.lexemsRe = langRe(language, lexems_re, true); 135 } 136 var result = ''; 137 var last_index = 0; 138 mode.lexemsRe.lastIndex = 0; 139 var match = mode.lexemsRe.exec(buffer); 140 while (match) { 141 result += escape(buffer.substr(last_index, match.index - last_index)); 142 var keyword_match = keywordMatch(mode, match); 143 if (keyword_match) { 144 keyword_count += keyword_match[1]; 145 result += '<span class="'+ keyword_match[0] +'">' + escape(match[0]) + '</span>'; 146 } else { 147 result += escape(match[0]); 148 } 149 last_index = mode.lexemsRe.lastIndex; 150 match = mode.lexemsRe.exec(buffer); 151 } 152 result += escape(buffer.substr(last_index, buffer.length - last_index)); 153 return result; 154 } 155 156 function processBuffer(buffer, mode) { 157 if (mode.subLanguage && selected_languages[mode.subLanguage]) { 158 var result = highlight(mode.subLanguage, buffer); 159 keyword_count += result.keyword_count; 160 relevance += result.relevance; 161 return result.value; 162 } else { 163 return processKeywords(buffer, mode); 164 } 165 } 166 167 function startNewMode(mode, lexem) { 168 var markup = mode.noMarkup?'':'<span class="' + mode.className + '">'; 169 if (mode.returnBegin) { 170 result += markup; 171 mode.buffer = ''; 172 } else if (mode.excludeBegin) { 173 result += escape(lexem) + markup; 174 mode.buffer = ''; 175 } else { 176 result += markup; 177 mode.buffer = lexem; 178 } 179 modes[modes.length] = mode; 180 } 181 182 function processModeInfo(buffer, lexem, end) { 183 var current_mode = modes[modes.length - 1]; 184 if (end) { 185 result += processBuffer(current_mode.buffer + buffer, current_mode); 186 return false; 187 } 188 189 var new_mode = subMode(lexem, current_mode); 190 if (new_mode) { 191 result += processBuffer(current_mode.buffer + buffer, current_mode); 192 startNewMode(new_mode, lexem); 193 relevance += new_mode.relevance; 194 return new_mode.returnBegin; 195 } 196 197 var end_level = endOfMode(modes.length - 1, lexem); 198 if (end_level) { 199 var markup = current_mode.noMarkup?'':'</span>'; 200 if (current_mode.returnEnd) { 201 result += processBuffer(current_mode.buffer + buffer, current_mode) + markup; 202 } else if (current_mode.excludeEnd) { 203 result += processBuffer(current_mode.buffer + buffer, current_mode) + markup + escape(lexem); 204 } else { 205 result += processBuffer(current_mode.buffer + buffer + lexem, current_mode) + markup; 206 } 207 while (end_level > 1) { 208 markup = modes[modes.length - 2].noMarkup?'':'</span>'; 209 result += markup; 210 end_level--; 211 modes.length--; 212 } 213 modes.length--; 214 modes[modes.length - 1].buffer = ''; 215 if (current_mode.starts) { 216 for (var i = 0; i < language.modes.length; i++) { 217 if (language.modes[i].className == current_mode.starts) { 218 startNewMode(language.modes[i], ''); 219 break; 220 } 221 } 222 } 223 return current_mode.returnEnd; 224 } 225 226 if (isIllegal(lexem, current_mode)) 227 throw 'Illegal'; 228 } 229 230 var language = LANGUAGES[language_name]; 231 var modes = [language.defaultMode]; 232 var relevance = 0; 233 var keyword_count = 0; 234 var result = ''; 235 try { 236 var index = 0; 237 language.defaultMode.buffer = ''; 238 do { 239 var mode_info = eatModeChunk(value, index); 240 var return_lexem = processModeInfo(mode_info[0], mode_info[1], mode_info[2]); 241 index += mode_info[0].length; 242 if (!return_lexem) { 243 index += mode_info[1].length; 244 } 245 } while (!mode_info[2]); 246 if(modes.length > 1) 247 throw 'Illegal'; 248 return { 249 relevance: relevance, 250 keyword_count: keyword_count, 251 value: result 252 } 253 } catch (e) { 254 if (e == 'Illegal') { 255 return { 256 relevance: 0, 257 keyword_count: 0, 258 value: escape(value) 259 } 260 } else { 261 throw e; 262 } 263 } 264 } 265 266 function blockText(block) { 267 var result = ''; 268 for (var i = 0; i < block.childNodes.length; i++) 269 if (block.childNodes[i].nodeType == 3) 270 result += block.childNodes[i].nodeValue; 271 else if (block.childNodes[i].nodeName == 'BR') 272 result += '\n'; 273 else 274 result += blockText(block.childNodes[i]); 275 return result; 276 } 277 278 function blockLanguage(block) { 279 var classes = block.className.split(/\s+/) 280 classes = classes.concat(block.parentNode.className.split(/\s+/)); 281 for (var i = 0; i < classes.length; i++) { 282 var class_ = classes[i].replace(/^language-/, ''); 283 if (class_ == 'no-highlight') { 284 throw 'No highlight' 285 } 286 if (LANGUAGES[class_]) { 287 return class_; 288 } 289 } 290 } 291 292 function nodeStream(node) { 293 var result = []; 294 (function (node, offset) { 295 for (var i = 0; i < node.childNodes.length; i++) { 296 if (node.childNodes[i].nodeType == 3) 297 offset += node.childNodes[i].nodeValue.length; 298 else if (node.childNodes[i].nodeName == 'BR') 299 offset += 1 300 else { 301 result.push({ 302 event: 'start', 303 offset: offset, 304 node: node.childNodes[i] 305 }); 306 offset = arguments.callee(node.childNodes[i], offset) 307 result.push({ 308 event: 'stop', 309 offset: offset, 310 node: node.childNodes[i] 311 }); 312 } 313 } 314 return offset; 315 })(node, 0); 316 return result; 317 } 318 319 function mergeStreams(stream1, stream2, value) { 320 var processed = 0; 321 var result = ''; 322 var nodeStack = []; 323 324 function selectStream() { 325 if (stream1.length && stream2.length) { 326 if (stream1[0].offset != stream2[0].offset) 327 return (stream1[0].offset < stream2[0].offset) ? stream1 : stream2; 328 else 329 return (stream1[0].event == 'start' && stream2[0].event == 'stop') ? stream2 : stream1; 330 } else { 331 return stream1.length ? stream1 : stream2; 332 } 333 } 334 335 function open(node) { 336 var result = '<' + node.nodeName.toLowerCase(); 337 for (var i = 0; i < node.attributes.length; i++) { 338 result += ' ' + node.attributes[i].nodeName.toLowerCase() + '="' + escape(node.attributes[i].nodeValue) + '"'; 339 } 340 return result + '>'; 341 } 342 343 function close(node) { 344 return '</' + node.nodeName.toLowerCase() + '>'; 345 } 346 347 while (stream1.length || stream2.length) { 348 var current = selectStream().splice(0, 1)[0]; 349 result += escape(value.substr(processed, current.offset - processed)); 350 processed = current.offset; 351 if ( current.event == 'start') { 352 result += open(current.node); 353 nodeStack.push(current.node); 354 } else if (current.event == 'stop') { 355 var i = nodeStack.length; 356 do { 357 i--; 358 var node = nodeStack[i]; 359 result += close(node); 360 } while (node != current.node); 361 nodeStack.splice(i, 1); 362 while (i < nodeStack.length) { 363 result += open(nodeStack[i]); 364 i++; 365 } 366 } 367 } 368 result += value.substr(processed); 369 return result; 370 } 371 372 function highlightBlock(block, tabReplace) { 373 try { 374 var text = blockText(block); 375 var language = blockLanguage(block); 376 } catch (e) { 377 if (e == 'No highlight') 378 return; 379 } 380 381 if (language) { 382 var result = highlight(language, text).value; 383 } else { 384 var max_relevance = 0; 385 for (var key in selected_languages) { 386 if (!selected_languages.hasOwnProperty(key)) 387 continue; 388 var lang_result = highlight(key, text); 389 var relevance = lang_result.keyword_count + lang_result.relevance; 390 if (relevance > max_relevance) { 391 max_relevance = relevance; 392 var result = lang_result.value; 393 language = key; 394 } 395 } 396 } 397 398 if (result) { 399 if (tabReplace) { 400 result = result.replace(/^(\t+)/gm, function(match, p1, offset, s) { 401 return p1.replace(/\t/g, tabReplace); 402 }) 403 } 404 var class_name = block.className; 405 if (!class_name.match(language)) { 406 class_name += ' ' + language; 407 } 408 var original = nodeStream(block); 409 if (original.length) { 410 var pre = document.createElement('pre'); 411 pre.innerHTML = result; 412 result = mergeStreams(original, nodeStream(pre), text); 413 } 414 // See these 4 lines? This is IE's notion of "block.innerHTML = result". Love this browser :-/ 415 var container = document.createElement('div'); 416 container.innerHTML = '<pre><code class="' + class_name + '">' + result + '</code></pre>'; 417 var environment = block.parentNode.parentNode; 418 environment.replaceChild(container.firstChild, block.parentNode); 419 } 420 } 421 422 function langRe(language, value, global) { 423 var mode = 'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : ''); 424 return new RegExp(value, mode); 425 } 426 427 function compileModes() { 428 for (var i in LANGUAGES) { 429 if (!LANGUAGES.hasOwnProperty(i)) 430 continue; 431 var language = LANGUAGES[i]; 432 for (var j = 0; j < language.modes.length; j++) { 433 if (language.modes[j].begin) 434 language.modes[j].beginRe = langRe(language, '^' + language.modes[j].begin); 435 if (language.modes[j].end) 436 language.modes[j].endRe = langRe(language, '^' + language.modes[j].end); 437 if (language.modes[j].illegal) 438 language.modes[j].illegalRe = langRe(language, '^(?:' + language.modes[j].illegal + ')'); 439 language.defaultMode.illegalRe = langRe(language, '^(?:' + language.defaultMode.illegal + ')'); 440 if (language.modes[j].relevance == undefined) { 441 language.modes[j].relevance = 1; 442 } 443 } 444 } 445 } 446 447 function compileKeywords() { 448 449 function compileModeKeywords(mode) { 450 if (!mode.keywordGroups) { 451 for (var key in mode.keywords) { 452 if (!mode.keywords.hasOwnProperty(key)) 453 continue; 454 if (mode.keywords[key] instanceof Object) 455 mode.keywordGroups = mode.keywords; 456 else 457 mode.keywordGroups = {'keyword': mode.keywords}; 458 break; 459 } 460 } 461 } 462 463 for (var i in LANGUAGES) { 464 if (!LANGUAGES.hasOwnProperty(i)) 465 continue; 466 var language = LANGUAGES[i]; 467 compileModeKeywords(language.defaultMode); 468 for (var j = 0; j < language.modes.length; j++) { 469 compileModeKeywords(language.modes[j]); 470 } 471 } 472 } 473 474 function findCode(pre) { 475 for (var i = 0; i < pre.childNodes.length; i++) { 476 node = pre.childNodes[i]; 477 if (node.nodeName == 'CODE') 478 return node; 479 if (!(node.nodeType == 3 && node.nodeValue.match(/\s+/))) 480 return null; 481 } 482 } 483 484 function initHighlighting() { 485 if (initHighlighting.called) 486 return; 487 initHighlighting.called = true; 488 compileModes(); 489 compileKeywords(); 490 if (arguments.length) { 491 for (var i = 0; i < arguments.length; i++) { 492 if (LANGUAGES[arguments[i]]) { 493 selected_languages[arguments[i]] = LANGUAGES[arguments[i]]; 494 } 495 } 496 } else 497 selected_languages = LANGUAGES; 498 var pres = document.getElementsByTagName('code'); 499 for (var i = 0; i < pres.length; i++) { 500 var code = findCode(pres[i]); 501 if (code) 502 highlightBlock(code, hljs.tabReplace); 503 } 504 } 505 506 function initHighlightingOnLoad() { 507 var original_arguments = arguments; 508 var handler = function(){initHighlighting.apply(null, original_arguments)}; 509 if (window.addEventListener) { 510 window.addEventListener('DOMContentLoaded', handler, false); 511 window.addEventListener('load', handler, false); 512 } else if (window.attachEvent) 513 window.attachEvent('onload', handler); 514 else 515 window.onload = handler; 516 } 517 518 this.LANGUAGES = LANGUAGES; 519 this.initHighlightingOnLoad = initHighlightingOnLoad; 520 this.highlightBlock = highlightBlock; 521 this.initHighlighting = initHighlighting; 522 523 // Common regexps 524 this.IDENT_RE = '[a-zA-Z][a-zA-Z0-9_]*'; 525 this.UNDERSCORE_IDENT_RE = '[a-zA-Z_][a-zA-Z0-9_]*'; 526 this.NUMBER_RE = '\\b\\d+(\\.\\d+)?'; 527 this.C_NUMBER_RE = '\\b(0x[A-Za-z0-9]+|\\d+(\\.\\d+)?)'; 528 this.RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|\\.|-|-=|/|/=|:|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~'; 529 530 // Common modes 531 this.APOS_STRING_MODE = { 532 className: 'string', 533 begin: '\'', end: '\'', 534 illegal: '\\n', 535 contains: ['escape'], 536 relevance: 0 537 }; 538 this.QUOTE_STRING_MODE = { 539 className: 'string', 540 begin: '"', end: '"', 541 illegal: '\\n', 542 contains: ['escape'], 543 relevance: 0 544 }; 545 this.BACKSLASH_ESCAPE = { 546 className: 'escape', 547 begin: '\\\\.', end: '^', noMarkup: true, 548 relevance: 0 549 }; 550 this.C_LINE_COMMENT_MODE = { 551 className: 'comment', 552 begin: '//', end: '$', 553 relevance: 0 554 }; 555 this.C_BLOCK_COMMENT_MODE = { 556 className: 'comment', 557 begin: '/\\*', end: '\\*/' 558 }; 559 this.HASH_COMMENT_MODE = { 560 className: 'comment', 561 begin: '#', end: '$' 562 }; 563 this.C_NUMBER_MODE = { 564 className: 'number', 565 begin: this.C_NUMBER_RE, end: '^', 566 relevance: 0 567 }; 568}(); 569 570var initHighlightingOnLoad = hljs.initHighlightingOnLoad; 571