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, '&amp;').replace(/</gm, '&lt;').replace(/>/gm, '&gt;');
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