MediaWiki:Highlight-js.js

/** * @module     Highlight-JS * @description Syntax highlighting with language autodetection. * @see        https://highlightjs.org/ */   // Convenience variables for build-in objects var ArrayProto = [], objectKeys = Object.keys;
 * (function (hljs, window, $, mw) {

// Global internal variables used within the highlight.js library. var languages = {}, aliases = {};

// Regular expressions used throughout the highlight.js library. var noHighlightRe = /^(no-?highlight|plain|text)$/i, languagePrefixRe = /\blang(?:uage)?-([\w-]+)\b/i, fixMarkupRe = /((^(<[^>]+>|\t|)+|(?:\n)))/gm;

var spanEndTag = ' ';

// Global options used when within external APIs. This is modified when // calling the `hljs.configure` function. var options = { classPrefix: 'hljs-', tabReplace: null, useBR: false, languages: undefined };

/* Utility functions */ function escape(value) { return value.replace(/&/g, '&amp;').replace(//g, '&gt;'); }

function tag(node) { return node.nodeName.toLowerCase; }

function testRe(re, lexeme) { var match = re && re.exec(lexeme); return match && match.index === 0; }

function isNotHighlighted(language) { return noHighlightRe.test(language); }

function blockLanguage(block) { var i, match, length, _class; var classes = block.className + ' ';

classes += block.parentNode ? block.parentNode.className : '';

// language-* takes precedence over non-prefixed class names. match = languagePrefixRe.exec(classes); if (match) { return hljs.getLanguage(match[1]) ? match[1] : 'no-highlight'; }

classes = classes.split(/\s+/);

for (i = 0, length = classes.length; i < length; i++) { _class = classes[i];

if (isNotHighlighted(_class) || hljs.getLanguage(_class)) { return _class; }       }    }

// inherit(parent, override_obj, override_obj, ...) hljs.inherit = function(parent) { var key; var result = {}; var objects = ArrayProto.slice.call(arguments, 1); for (key in parent) result[key] = parent[key]; objects.forEach(function (obj) {           for (key in obj)                result[key] = obj[key];        }); return result; };

/* Stream merging */ function nodeStream(node) { var result = []; (function _nodeStream(node, offset) {           for (var child = node.firstChild; child; child = child.nextSibling) {                if (child.nodeType === 3)                    offset += child.nodeValue.length;                else if (child.nodeType === 1) {                    result.push({ event: 'start', offset: offset, node: child });                   offset = _nodeStream(child, offset);                    // Prevent void elements from having an end tag that would actually                    // double them in the output. There are more void elements in HTML                    // but we list only those realistically expected in code display.                    if (!tag(child).match(/br|hr|img|input/)) {                        result.push({ event: 'stop', offset: offset, node: child });                   }                }            }            return offset;        })(node, 0); return result; }

function mergeStreams(original, highlighted, value) { var processed = 0; var result = ''; var nodeStack = [];

function selectStream { if (!original.length || !highlighted.length) { return original.length ? original : highlighted; }           if (original[0].offset !== highlighted[0].offset) { return (original[0].offset < highlighted[0].offset) ? original : highlighted; }

/**            * To avoid starting the stream just before it should stop the order is             * ensured that original always starts first and closes last:

* if (event1 == 'start' && event2 == 'start') *  return original; * if (event1 == 'start' && event2 == 'stop') *  return highlighted; * if (event1 == 'stop' && event2 == 'start') *  return original; * if (event1 == 'stop' && event2 == 'stop') *  return highlighted;

* ... which is collapsed to: */           return highlighted[0].event === 'start' ? original : highlighted; }

function open(node) { function attr_str(a) { return ' ' + a.nodeName + '="' + escape(a.value).replace('"', '&quot;') + '"';           }            result += '<' + tag(node) + ArrayProto.map.call(node.attributes, attr_str).join('') + '>';        }

function close(node) { result += ''; }

function render(event) { (event.event === 'start' ? open : close)(event.node); }

while (original.length || highlighted.length) { var stream = selectStream; result += escape(value.substring(processed, stream[0].offset)); processed = stream[0].offset; if (stream === original) { /**                * On any opening or closing tag of the original markup we first close * the entire highlighted node stack, then render the original tag along * with all the following original tags at the same offset and then * reopen all the tags on the highlighted stack. */               nodeStack.reverse.forEach(close); do { render(stream.splice(0, 1)[0]); stream = selectStream; } while (stream === original && stream.length && stream[0].offset === processed); nodeStack.reverse.forEach(open); } else { if (stream[0].event === 'start') { nodeStack.push(stream[0].node); } else { nodeStack.pop; }               render(stream.splice(0, 1)[0]); }       }        return result + escape(value.substr(processed)); }

/* Initialization */ function expand_mode(mode) { if (mode.variants && !mode.cached_variants) { mode.cached_variants = mode.variants.map(function (variant) {               return hljs.inherit(mode, { variants: null }, variant);            }); }       return mode.cached_variants || (mode.endsWithParent && [hljs.inherit(mode)]) || [mode]; }

function compileLanguage(language) {

function reStr(re) { return (re && re.source) || re; }

function langRe(value, global) { return new RegExp(               reStr(value),                'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '')           ); }

function compileMode(mode, parent) { if (mode.compiled) return; mode.compiled = true;

mode.keywords = mode.keywords || mode.beginKeywords; if (mode.keywords) { var compiled_keywords = {};

var flatten = function (className, str) { if (language.case_insensitive) { str = str.toLowerCase; }                   str.split(' ').forEach(function (kw) {                        var pair = kw.split('|');                        compiled_keywords[pair[0]] = [className, pair[1] ? Number(pair[1]) : 1];                    }); };

if (typeof mode.keywords === 'string') { // string flatten('keyword', mode.keywords); } else { objectKeys(mode.keywords).forEach(function (className) {                       flatten(className, mode.keywords[className]);                    }); }               mode.keywords = compiled_keywords; }           mode.lexemesRe = langRe(mode.lexemes || /\w+/, true);

if (parent) { if (mode.beginKeywords) { mode.begin = '\\b(' + mode.beginKeywords.split(' ').join('|') + ')\\b'; }               if (!mode.begin) mode.begin = /\B|\b/; mode.beginRe = langRe(mode.begin); if (mode.endSameAsBegin) mode.end = mode.begin; if (!mode.end && !mode.endsWithParent) mode.end = /\B|\b/; if (mode.end) mode.endRe = langRe(mode.end); mode.terminator_end = reStr(mode.end) || ''; if (mode.endsWithParent && parent.terminator_end) mode.terminator_end += (mode.end ? '|' : '') + parent.terminator_end; }           if (mode.illegal) mode.illegalRe = langRe(mode.illegal); if (mode.relevance == null) mode.relevance = 1; if (!mode.contains) { mode.contains = []; }           mode.contains = ArrayProto.concat.apply([], mode.contains.map(function (c) { return expand_mode(c === 'self' ? mode : c); }));           mode.contains.forEach(function (c) { compileMode(c, mode); });

if (mode.starts) { compileMode(mode.starts, parent); }

var terminators = mode.contains.map(function (c) {                   return c.beginKeywords ? '\\.?(' + c.begin + ')\\.?' : c.begin;                }) .concat([mode.terminator_end, mode.illegal]) .map(reStr) .filter(Boolean); mode.terminators = terminators.length ? langRe(terminators.join('|'), true) : { exec: function (/*s*/) { return null; } }; }

compileMode(language); }

/**    * Core highlighting function. Accepts a language name, or an alias, and a    * string with the code to highlight. Returns an object with the following * properties:

- relevance (int) - value (an HTML string with highlighting markup)

*/   hljs.highlight = function(name, value, ignore_illegals, continuation) {

function escapeRe(value) { return new RegExp(value.replace(/[-\/\\^$*+?.|[\]{}]/g, '\\$&'), 'm'); }

function subMode(lexeme, mode) { var i, length;

for (i = 0, length = mode.contains.length; i < length; i++) { if (testRe(mode.contains[i].beginRe, lexeme)) { if (mode.contains[i].endSameAsBegin) { mode.contains[i].endRe = escapeRe(mode.contains[i].beginRe.exec(lexeme)[0]); }                   return mode.contains[i]; }           }        }

function endOfMode(mode, lexeme) { if (testRe(mode.endRe, lexeme)) { while (mode.endsParent && mode.parent) { mode = mode.parent; }               return mode; }           if (mode.endsWithParent) { return endOfMode(mode.parent, lexeme); }       }

function isIllegal(lexeme, mode) { return !ignore_illegals && testRe(mode.illegalRe, lexeme); }

function keywordMatch(mode, match) { var match_str = language.case_insensitive ? match[0].toLowerCase : match[0]; return mode.keywords.hasOwnProperty(match_str) && mode.keywords[match_str]; }

function buildSpan(classname, insideSpan, leaveOpen, noPrefix) { var classPrefix = noPrefix ? '' : options.classPrefix, openSpan = '';

return openSpan + insideSpan + closeSpan; }

function processKeywords { var keyword_match, last_index, match, result;

if (!top.keywords) return escape(mode_buffer);

result = ''; last_index = 0; top.lexemesRe.lastIndex = 0; match = top.lexemesRe.exec(mode_buffer);

while (match) { result += escape(mode_buffer.substring(last_index, match.index)); keyword_match = keywordMatch(top, match); if (keyword_match) { relevance += keyword_match[1]; result += buildSpan(keyword_match[0], escape(match[0])); } else { result += escape(match[0]); }               last_index = top.lexemesRe.lastIndex; match = top.lexemesRe.exec(mode_buffer); }           return result + escape(mode_buffer.substr(last_index)); }

function processSubLanguage { var explicit = typeof top.subLanguage === 'string'; if (explicit && !languages[top.subLanguage]) { return escape(mode_buffer); }

var result = explicit ? hljs.highlight(top.subLanguage, mode_buffer, true, continuations[top.subLanguage]) : hljs.highlightAuto(mode_buffer, top.subLanguage.length ? top.subLanguage : undefined);

// Counting embedded language score towards the host language may be disabled // with zeroing the containing mode relevance. Usecase in point is Markdown that // allows XML everywhere and makes every XML snippet to have a much larger Markdown // score. if (top.relevance > 0) { relevance += result.relevance; }           if (explicit) { continuations[top.subLanguage] = result.top; }           return buildSpan(result.language, result.value, false, true); }

function processBuffer { result += (top.subLanguage != null ? processSubLanguage : processKeywords); mode_buffer = ''; }

function startNewMode(mode) { result += mode.className ? buildSpan(mode.className, , true) : ; top = Object.create(mode, { parent: { value: top } }); }

function processLexeme(buffer, lexeme) {

mode_buffer += buffer;

if (lexeme == null) { processBuffer; return 0; }

var new_mode = subMode(lexeme, top); if (new_mode) { if (new_mode.skip) { mode_buffer += lexeme; } else { if (new_mode.excludeBegin) { mode_buffer += lexeme; }                   processBuffer; if (!new_mode.returnBegin && !new_mode.excludeBegin) { mode_buffer = lexeme; }               }                startNewMode(new_mode, lexeme); return new_mode.returnBegin ? 0 : lexeme.length; }

var end_mode = endOfMode(top, lexeme); if (end_mode) { var origin = top; if (origin.skip) { mode_buffer += lexeme; } else { if (!(origin.returnEnd || origin.excludeEnd)) { mode_buffer += lexeme; }                   processBuffer; if (origin.excludeEnd) { mode_buffer = lexeme; }               }                do { if (top.className) { result += spanEndTag; }                   if (!top.skip && !top.subLanguage) { relevance += top.relevance; }                   top = top.parent; } while (top !== end_mode.parent); if (end_mode.starts) { if (end_mode.endSameAsBegin) { end_mode.starts.endRe = end_mode.endRe; }                   startNewMode(end_mode.starts, ''); }               return origin.returnEnd ? 0 : lexeme.length; }

if (isIllegal(lexeme, top)) throw new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.className || ' ') + '"');

/**            *Parser should not reach this point as all types of lexemes should be caught *earlier, but if it does due to some bug make sure it advances at least one *character forward to prevent infinite looping. */           mode_buffer += lexeme; return lexeme.length || 1; }

var language = hljs.getLanguage(name); if (!language) { throw new Error('Unknown language: "' + name + '"'); }

compileLanguage(language); var top = continuation || language; var continuations = {}; // keep continuations for sub-languages var result = '', current; for (current = top; current !== language; current = current.parent) { if (current.className) { result = buildSpan(current.className, '', true) + result; }       }        var mode_buffer = ''; var relevance = 0; try { var match, count, index = 0; while (true) { top.terminators.lastIndex = index; match = top.terminators.exec(value); if (!match) break; count = processLexeme(value.substring(index, match.index), match[0]); index = match.index + count; }           processLexeme(value.substr(index)); for (current = top; current.parent; current = current.parent) { // close dangling modes if (current.className) { result += spanEndTag; }           }            return { relevance: relevance, value: result, language: name, top: top };       } catch (e) { if (e.message && e.message.indexOf('Illegal') !== -1) { return { relevance: 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) - value (an HTML string with highlighting markup) - second_best (object with the same structure for second-best heuristically       detected language, may be absent)

*/   hljs.highlightAuto = function(text, languageSubset) { languageSubset = languageSubset || options.languages || objectKeys(languages); var result = { relevance: 0, value: escape(text) };       var second_best = result; languageSubset.filter(hljs.getLanguage).filter(hljs.autoDetection).forEach(function (name) {           var current = hljs.highlight(name, text, false);            current.language = name;            if (current.relevance > second_best.relevance) {                second_best = current;            }            if (current.relevance > 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

*/   hljs.fixMarkup = function(value) { return !(options.tabReplace || options.useBR) ? value : value.replace(fixMarkupRe, function (match, p1) {               if (options.useBR && match === '\n') {                    return ' ';                } else if (options.tabReplace) {                    return p1.replace(/\t/g, options.tabReplace);                }                return '';            }); };

function buildClassName(prevClassName, currentLang, resultLang) { var language = currentLang ? aliases[currentLang] : resultLang, result = [prevClassName.trim];

if (!prevClassName.match(/\bhljs\b/)) { result.push('hljs'); }

if (prevClassName.indexOf(language) === -1) { result.push(language); }

return result.join(' ').trim; }

/**    * Applies highlighting to a DOM node containing code. Accepts a DOM node and * two optional parameters for fixMarkup. */   hljs.highlightBlock = function(block) { var node, originalStream, result, resultNode, text; var language = blockLanguage(block);

if (isNotHighlighted(language)) return;

if (options.useBR) { node = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); node.innerHTML = block.innerHTML.replace(/\n/g, '').replace(//g, '\n'); } else { node = block; }       text = node.textContent; result = language ? hljs.highlight(language, text, true) : hljs.highlightAuto(text);

originalStream = nodeStream(node); if (originalStream.length) { resultNode = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); resultNode.innerHTML = result.value; result.value = mergeStreams(originalStream, nodeStream(resultNode), text); }       result.value = hljs.fixMarkup(result.value);

block.innerHTML = result.value; block.className = buildClassName(block.className, language, result.language); block.result = { language: result.language, re: result.relevance };       if (result.second_best) { block.second_best = { language: result.second_best.language, re: result.second_best.relevance };       }    };

/**    * Updates highlight.js global options with values passed in the form of an object. */   hljs.configure = function(user_options) { options = hljs.inherit(options, user_options); };

/**    * Applies highlighting to all  blocks on a page. */   hljs.initHighlighting = function { if (hljs.initHighlighting.called) return; hljs.initHighlighting.called = true;

var blocks = document.querySelectorAll('pre code'); ArrayProto.forEach.call(blocks, hljs.highlightBlock); };

/**    * Attaches highlighting to the page load event. */   hljs.initHighlightingOnLoad = function { document.addEventListener('DOMContentLoaded', hljs.initHighlighting, false); document.addEventListener('load', hljs.initHighlighting, false); };

hljs.registerLanguage = function(name, language) { var lang = languages[name] = language(hljs); if (lang.aliases) { lang.aliases.forEach(function (alias) { aliases[alias] = name; }); }   };

hljs.listLanguages = function { return objectKeys(languages); };

hljs.getLanguage = function(name) { name = (name || '').toLowerCase; return languages[name] || languages[aliases[name]]; };

hljs.autoDetection = function(name) { name = (name || '').toLowerCase; return !languages[name].disableAutodetect; };

// Common regexps hljs.IDENT_RE = '[a-zA-Z]\\w*'; hljs.UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\w*'; hljs.NUMBER_RE = '\\b\\d+(\\.\\d+)?'; hljs.C_NUMBER_RE = '(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float hljs.BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b... hljs.RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~';

// Common modes hljs.BACKSLASH_ESCAPE = { begin: '\\\\[\\s\\S]', relevance: 0 };   hljs.APOS_STRING_MODE = { className: 'string', begin: '\, end: '\, illegal: '\\n', contains: [hljs.BACKSLASH_ESCAPE] };   hljs.QUOTE_STRING_MODE = { className: 'string', begin: '"', end: '"', illegal: '\\n', contains: [hljs.BACKSLASH_ESCAPE] };   hljs.PHRASAL_WORDS_MODE = { begin: /\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ };   hljs.COMMENT = function (begin, end, inherits) { var mode = hljs.inherit(           {                className: 'comment',                begin: begin, end: end,                contains: []            },            inherits || {}        ); mode.contains.push(hljs.PHRASAL_WORDS_MODE); mode.contains.push({           className: 'doctag',            begin: '(?:TODO|FIXME|NOTE|BUG|XXX):',            relevance: 0        }); return mode; };   hljs.C_LINE_COMMENT_MODE = hljs.COMMENT('//', '$'); hljs.C_BLOCK_COMMENT_MODE = hljs.COMMENT('/\\*', '\\*/'); hljs.HASH_COMMENT_MODE = hljs.COMMENT('#', '$'); hljs.NUMBER_MODE = { className: 'number', begin: hljs.NUMBER_RE, relevance: 0 };   hljs.C_NUMBER_MODE = { className: 'number', begin: hljs.C_NUMBER_RE, relevance: 0 };   hljs.BINARY_NUMBER_MODE = { className: 'number', begin: hljs.BINARY_NUMBER_RE, relevance: 0 };   hljs.CSS_NUMBER_MODE = { className: 'number', begin: hljs.NUMBER_RE + '(' +           '%|em|ex|ch|rem' +            '|vw|vh|vmin|vmax' +            '|cm|mm|in|pt|pc|px' +            '|deg|grad|rad|turn' +            '|s|ms' +            '|Hz|kHz' +            '|dpi|dpcm|dppx' +            ')?', relevance: 0 };   hljs.REGEXP_MODE = { className: 'regexp', begin: /\//, end: /\/[gimuy]*/, illegal: /\n/, contains: [ hljs.BACKSLASH_ESCAPE, {               begin: /\[/, end: /\]/, relevance: 0, contains: [hljs.BACKSLASH_ESCAPE] }       ]    };    hljs.TITLE_MODE = { className: 'title', begin: hljs.IDENT_RE, relevance: 0 };   hljs.UNDERSCORE_TITLE_MODE = { className: 'title', begin: hljs.UNDERSCORE_IDENT_RE, relevance: 0 };   hljs.METHOD_GUARD = { // excludes method names from keyword processing begin: '\\.\\s*' + hljs.UNDERSCORE_IDENT_RE, relevance: 0 };

mw.hook('dev.highlight').fire(hljs);

}(((window.dev = window.dev || {}).highlight = {}), window, jQuery, mediaWiki));