/* Syntax highlighting with language autodetection. http://softwaremaniacs.org/soft/highlight/ */ var hljs = new function() { /* Utility functions */ function escape(value) { return value.replace(/&/gm, '&').replace(/'; } while (stream1.length || stream2.length) { var current = selectStream().splice(0, 1)[0]; result += escape(value.substr(processed, current.offset - processed)); processed = current.offset; if ( current.event == 'start') { result += open(current.node); nodeStack.push(current.node); } else if (current.event == 'stop') { var node, i = nodeStack.length; do { i--; node = nodeStack[i]; result += (''); } while (node != current.node); nodeStack.splice(i, 1); while (i < nodeStack.length) { result += open(nodeStack[i]); i++; } } } return result + escape(value.substr(processed)); } /* Initialization */ function compileModes() { function compileMode(mode, language, is_default) { if (mode.compiled) return; var group; if (!is_default) { mode.beginRe = langRe(language, mode.begin ? mode.begin : '\\B|\\b'); if (!mode.end && !mode.endsWithParent) mode.end = '\\B|\\b'; if (mode.end) mode.endRe = langRe(language, mode.end); } if (mode.illegal) mode.illegalRe = langRe(language, mode.illegal); if (mode.relevance === undefined) mode.relevance = 1; if (mode.keywords) { mode.lexemsRe = langRe(language, mode.lexems || hljs.IDENT_RE, true); for (var className in mode.keywords) { if (!mode.keywords.hasOwnProperty(className)) continue; if (mode.keywords[className] instanceof Object) { group = mode.keywords[className]; } else { group = mode.keywords; className = 'keyword'; } for (var keyword in group) { if (!group.hasOwnProperty(keyword)) continue; mode.keywords[keyword] = [className, group[keyword]]; } } } if (!mode.contains) { mode.contains = []; } // compiled flag is set before compiling submodes to avoid self-recursion // (see lisp where quoted_list contains quoted_list) mode.compiled = true; for (var i = 0; i < mode.contains.length; i++) { if (mode.contains[i] == 'self') { mode.contains[i] = mode; } compileMode(mode.contains[i], language, false); } if (mode.starts) { compileMode(mode.starts, language, false); } } for (var i in languages) { if (!languages.hasOwnProperty(i)) continue; compileMode(languages[i].defaultMode, languages[i], true); } } /* Core highlighting function. Accepts a language name and a string with the code to highlight. Returns an object with the following properties: - relevance (int) - keyword_count (int) - value (an HTML string with highlighting markup) */ function highlight(language_name, value) { if (!compileModes.called) { compileModes(); compileModes.called = true; } function subMode(lexem, mode) { for (var i = 0; i < mode.contains.length; i++) { if (mode.contains[i].beginRe.test(lexem)) { return mode.contains[i]; } } } function endOfMode(mode_index, lexem) { if (modes[mode_index].end && modes[mode_index].endRe.test(lexem)) return 1; if (modes[mode_index].endsWithParent) { var level = endOfMode(mode_index - 1, lexem); return level ? level + 1 : 0; } return 0; } function isIllegal(lexem, mode) { return mode.illegal && mode.illegalRe.test(lexem); } function compileTerminators(mode, language) { var terminators = []; for (var i = 0; i < mode.contains.length; i++) { terminators.push(mode.contains[i].begin); } var index = modes.length - 1; do { if (modes[index].end) { terminators.push(modes[index].end); } index--; } while (modes[index + 1].endsWithParent); if (mode.illegal) { terminators.push(mode.illegal); } return langRe(language, '(' + terminators.join('|') + ')', true); } function eatModeChunk(value, index) { var mode = modes[modes.length - 1]; if (!mode.terminators) { mode.terminators = compileTerminators(mode, language); } mode.terminators.lastIndex = index; var match = mode.terminators.exec(value); if (match) return [value.substr(index, match.index - index), match[0], false]; else return [value.substr(index), '', true]; } function keywordMatch(mode, match) { var match_str = language.case_insensitive ? match[0].toLowerCase() : match[0]; var value = mode.keywords[match_str]; if (value && value instanceof Array) return value; return false; } function processKeywords(buffer, mode) { buffer = escape(buffer); if (!mode.keywords) return buffer; var result = ''; var last_index = 0; mode.lexemsRe.lastIndex = 0; var match = mode.lexemsRe.exec(buffer); while (match) { result += buffer.substr(last_index, match.index - last_index); var keyword_match = keywordMatch(mode, match); if (keyword_match) { keyword_count += keyword_match[1]; result += '' + match[0] + ''; } else { result += match[0]; } last_index = mode.lexemsRe.lastIndex; match = mode.lexemsRe.exec(buffer); } return result + buffer.substr(last_index, buffer.length - last_index); } function processBuffer(buffer, mode) { if (mode.subLanguage && languages[mode.subLanguage]) { var result = highlight(mode.subLanguage, buffer); keyword_count += result.keyword_count; return result.value; } else { return processKeywords(buffer, mode); } } function startNewMode(mode, lexem) { var markup = mode.className?'':''; if (mode.returnBegin) { result += markup; mode.buffer = ''; } else if (mode.excludeBegin) { result += escape(lexem) + markup; mode.buffer = ''; } else { result += markup; mode.buffer = lexem; } modes.push(mode); relevance += mode.relevance; } function processModeInfo(buffer, lexem, end) { var current_mode = modes[modes.length - 1]; if (end) { result += processBuffer(current_mode.buffer + buffer, current_mode); return false; } var new_mode = subMode(lexem, current_mode); if (new_mode) { result += processBuffer(current_mode.buffer + buffer, current_mode); startNewMode(new_mode, lexem); return new_mode.returnBegin; } var end_level = endOfMode(modes.length - 1, lexem); if (end_level) { var markup = current_mode.className?'':''; if (current_mode.returnEnd) { result += processBuffer(current_mode.buffer + buffer, current_mode) + markup; } else if (current_mode.excludeEnd) { result += processBuffer(current_mode.buffer + buffer, current_mode) + markup + escape(lexem); } else { result += processBuffer(current_mode.buffer + buffer + lexem, current_mode) + markup; } while (end_level > 1) { markup = modes[modes.length - 2].className?'':''; result += markup; end_level--; modes.length--; } var last_ended_mode = modes[modes.length - 1]; modes.length--; modes[modes.length - 1].buffer = ''; if (last_ended_mode.starts) { startNewMode(last_ended_mode.starts, ''); } return current_mode.returnEnd; } if (isIllegal(lexem, current_mode)) throw 'Illegal'; } var language = languages[language_name]; var modes = [language.defaultMode]; var relevance = 0; var keyword_count = 0; var result = ''; try { var mode_info, index = 0; language.defaultMode.buffer = ''; do { mode_info = eatModeChunk(value, index); var return_lexem = processModeInfo(mode_info[0], mode_info[1], mode_info[2]); index += mode_info[0].length; if (!return_lexem) { index += mode_info[1].length; } } while (!mode_info[2]); if(modes.length > 1) throw 'Illegal'; return { relevance: relevance, keyword_count: keyword_count, value: result }; } catch (e) { if (e == 'Illegal') { return { relevance: 0, keyword_count: 0, value: escape(value) }; } else { throw e; } } } /* Highlighting with language detection. Accepts a string with the code to highlight. Returns an object with the following properties: - language (detected language) - relevance (int) - keyword_count (int) - value (an HTML string with highlighting markup) - second_best (object with the same structure for second-best heuristically detected language, may be absent) */ function highlightAuto(text) { var result = { keyword_count: 0, relevance: 0, value: escape(text) }; var second_best = result; for (var key in languages) { if (!languages.hasOwnProperty(key)) continue; var current = highlight(key, text); current.language = key; if (current.keyword_count + current.relevance > second_best.keyword_count + second_best.relevance) { second_best = current; } if (current.keyword_count + current.relevance > result.keyword_count + result.relevance) { second_best = result; result = current; } } if (second_best.language) { result.second_best = second_best; } return result; } /* Post-processing of the highlighted markup: - replace TABs with something more useful - replace real line-breaks with '
' for non-pre containers */ function fixMarkup(value, tabReplace, useBR) { if (tabReplace) { value = value.replace(/^((<[^>]+>|\t)+)/gm, function(match, p1, offset, s) { return p1.replace(/\t/g, tabReplace); }); } if (useBR) { value = value.replace(/\n/g, '
'); } return value; } /* Applies highlighting to a DOM node containing code. Accepts a DOM node and two optional parameters for fixMarkup. */ function highlightBlock(block, tabReplace, useBR) { var text = blockText(block, useBR); var language = blockLanguage(block); var result, pre; if (language == 'no-highlight') return; if (language) { result = highlight(language, text); } else { result = highlightAuto(text); language = result.language; } var original = nodeStream(block); if (original.length) { pre = document.createElement('pre'); pre.innerHTML = result.value; result.value = mergeStreams(original, nodeStream(pre), text); } result.value = fixMarkup(result.value, tabReplace, useBR); var class_name = block.className; if (!class_name.match('(\\s|^)(language-)?' + language + '(\\s|$)')) { class_name = class_name ? (class_name + ' ' + language) : language; } if (/MSIE [678]/.test(navigator.userAgent) && block.tagName == 'CODE' && block.parentNode.tagName == 'PRE') { // This is for backwards compatibility only. IE needs this strange // hack becasue it cannot just cleanly replace block contents. pre = block.parentNode; var container = document.createElement('div'); container.innerHTML = '
' + result.value + '
'; block = container.firstChild.firstChild; container.firstChild.className = pre.className; pre.parentNode.replaceChild(container.firstChild, pre); } else { block.innerHTML = result.value; } block.className = class_name; block.result = { language: language, kw: result.keyword_count, re: result.relevance }; if (result.second_best) { block.second_best = { language: result.second_best.language, kw: result.second_best.keyword_count, re: result.second_best.relevance }; } } /* Applies highlighting to all
..
blocks on a page. */ function initHighlighting() { if (initHighlighting.called) return; initHighlighting.called = true; var pres = document.getElementsByTagName('pre'); for (var i = 0; i < pres.length; i++) { var code = findCode(pres[i]); if (code) highlightBlock(code, hljs.tabReplace); } } /* Attaches highlighting to the page load event. */ function initHighlightingOnLoad() { if (window.addEventListener) { window.addEventListener('DOMContentLoaded', initHighlighting, false); window.addEventListener('load', initHighlighting, false); } else if (window.attachEvent) window.attachEvent('onload', initHighlighting); else window.onload = initHighlighting; } var languages = {}; // a shortcut to avoid writing "this." everywhere /* Interface definition */ this.LANGUAGES = languages; this.highlight = highlight; this.highlightAuto = highlightAuto; this.fixMarkup = fixMarkup; this.highlightBlock = highlightBlock; this.initHighlighting = initHighlighting; this.initHighlightingOnLoad = initHighlightingOnLoad; // Common regexps this.IDENT_RE = '[a-zA-Z][a-zA-Z0-9_]*'; this.UNDERSCORE_IDENT_RE = '[a-zA-Z_][a-zA-Z0-9_]*'; this.NUMBER_RE = '\\b\\d+(\\.\\d+)?'; this.C_NUMBER_RE = '\\b(0[xX][a-fA-F0-9]+|(\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float this.BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b... this.RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|\\.|-|-=|/|/=|:|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~'; // Common modes this.BACKSLASH_ESCAPE = { begin: '\\\\.', relevance: 0 }; this.APOS_STRING_MODE = { className: 'string', begin: '\'', end: '\'', illegal: '\\n', contains: [this.BACKSLASH_ESCAPE], relevance: 0 }; this.QUOTE_STRING_MODE = { className: 'string', begin: '"', end: '"', illegal: '\\n', contains: [this.BACKSLASH_ESCAPE], relevance: 0 }; this.C_LINE_COMMENT_MODE = { className: 'comment', begin: '//', end: '$' }; this.C_BLOCK_COMMENT_MODE = { className: 'comment', begin: '/\\*', end: '\\*/' }; this.HASH_COMMENT_MODE = { className: 'comment', begin: '#', end: '$' }; this.NUMBER_MODE = { className: 'number', begin: this.NUMBER_RE, relevance: 0 }; this.C_NUMBER_MODE = { className: 'number', begin: this.C_NUMBER_RE, relevance: 0 }; this.BINARY_NUMBER_MODE = { className: 'number', begin: this.BINARY_NUMBER_RE, relevance: 0 }; // Utility functions this.inherit = function(parent, obj) { var result = {} for (var key in parent) result[key] = parent[key]; if (obj) for (var key in obj) result[key] = obj[key]; return result; } }();