time-to-botec

Benchmark sampling in different programming languages
Log | Files | Refs | README

sh_main.js (15298B)


      1 /*
      2 SHJS - Syntax Highlighting in JavaScript
      3 Copyright (C) 2007, 2008 gnombat@users.sourceforge.net
      4 License: http://shjs.sourceforge.net/doc/gplv3.html
      5 */
      6 
      7 if (! this.sh_languages) {
      8   this.sh_languages = {};
      9 }
     10 var sh_requests = {};
     11 
     12 function sh_isEmailAddress(url) {
     13   if (/^mailto:/.test(url)) {
     14     return false;
     15   }
     16   return url.indexOf('@') !== -1;
     17 }
     18 
     19 function sh_setHref(tags, numTags, inputString) {
     20   var url = inputString.substring(tags[numTags - 2].pos, tags[numTags - 1].pos);
     21   if (url.length >= 2 && url.charAt(0) === '<' && url.charAt(url.length - 1) === '>') {
     22     url = url.substr(1, url.length - 2);
     23   }
     24   if (sh_isEmailAddress(url)) {
     25     url = 'mailto:' + url;
     26   }
     27   tags[numTags - 2].node.href = url;
     28 }
     29 
     30 /*
     31 Konqueror has a bug where the regular expression /$/g will not match at the end
     32 of a line more than once:
     33 
     34   var regex = /$/g;
     35   var match;
     36 
     37   var line = '1234567890';
     38   regex.lastIndex = 10;
     39   match = regex.exec(line);
     40 
     41   var line2 = 'abcde';
     42   regex.lastIndex = 5;
     43   match = regex.exec(line2);  // fails
     44 */
     45 function sh_konquerorExec(s) {
     46   var result = [''];
     47   result.index = s.length;
     48   result.input = s;
     49   return result;
     50 }
     51 
     52 /**
     53 Highlights all elements containing source code in a text string.  The return
     54 value is an array of objects, each representing an HTML start or end tag.  Each
     55 object has a property named pos, which is an integer representing the text
     56 offset of the tag. Every start tag also has a property named node, which is the
     57 DOM element started by the tag. End tags do not have this property.
     58 @param  inputString  a text string
     59 @param  language  a language definition object
     60 @return  an array of tag objects
     61 */
     62 function sh_highlightString(inputString, language) {
     63   if (/Konqueror/.test(navigator.userAgent)) {
     64     if (! language.konquered) {
     65       for (var s = 0; s < language.length; s++) {
     66         for (var p = 0; p < language[s].length; p++) {
     67           var r = language[s][p][0];
     68           if (r.source === '$') {
     69             r.exec = sh_konquerorExec;
     70           }
     71         }
     72       }
     73       language.konquered = true;
     74     }
     75   }
     76 
     77   var a = document.createElement('a');
     78   var span = document.createElement('span');
     79 
     80   // the result
     81   var tags = [];
     82   var numTags = 0;
     83 
     84   // each element is a pattern object from language
     85   var patternStack = [];
     86 
     87   // the current position within inputString
     88   var pos = 0;
     89 
     90   // the name of the current style, or null if there is no current style
     91   var currentStyle = null;
     92 
     93   var output = function(s, style) {
     94     var length = s.length;
     95     // this is more than just an optimization - we don't want to output empty <span></span> elements
     96     if (length === 0) {
     97       return;
     98     }
     99     if (! style) {
    100       var stackLength = patternStack.length;
    101       if (stackLength !== 0) {
    102         var pattern = patternStack[stackLength - 1];
    103         // check whether this is a state or an environment
    104         if (! pattern[3]) {
    105           // it's not a state - it's an environment; use the style for this environment
    106           style = pattern[1];
    107         }
    108       }
    109     }
    110     if (currentStyle !== style) {
    111       if (currentStyle) {
    112         tags[numTags++] = {pos: pos};
    113         if (currentStyle === 'sh_url') {
    114           sh_setHref(tags, numTags, inputString);
    115         }
    116       }
    117       if (style) {
    118         var clone;
    119         if (style === 'sh_url') {
    120           clone = a.cloneNode(false);
    121         }
    122         else {
    123           clone = span.cloneNode(false);
    124         }
    125         clone.className = style;
    126         tags[numTags++] = {node: clone, pos: pos};
    127       }
    128     }
    129     pos += length;
    130     currentStyle = style;
    131   };
    132 
    133   var endOfLinePattern = /\r\n|\r|\n/g;
    134   endOfLinePattern.lastIndex = 0;
    135   var inputStringLength = inputString.length;
    136   while (pos < inputStringLength) {
    137     var start = pos;
    138     var end;
    139     var startOfNextLine;
    140     var endOfLineMatch = endOfLinePattern.exec(inputString);
    141     if (endOfLineMatch === null) {
    142       end = inputStringLength;
    143       startOfNextLine = inputStringLength;
    144     }
    145     else {
    146       end = endOfLineMatch.index;
    147       startOfNextLine = endOfLinePattern.lastIndex;
    148     }
    149 
    150     var line = inputString.substring(start, end);
    151 
    152     var matchCache = [];
    153     for (;;) {
    154       var posWithinLine = pos - start;
    155 
    156       var stateIndex;
    157       var stackLength = patternStack.length;
    158       if (stackLength === 0) {
    159         stateIndex = 0;
    160       }
    161       else {
    162         // get the next state
    163         stateIndex = patternStack[stackLength - 1][2];
    164       }
    165 
    166       var state = language[stateIndex];
    167       var numPatterns = state.length;
    168       var mc = matchCache[stateIndex];
    169       if (! mc) {
    170         mc = matchCache[stateIndex] = [];
    171       }
    172       var bestMatch = null;
    173       var bestPatternIndex = -1;
    174       for (var i = 0; i < numPatterns; i++) {
    175         var match;
    176         if (i < mc.length && (mc[i] === null || posWithinLine <= mc[i].index)) {
    177           match = mc[i];
    178         }
    179         else {
    180           var regex = state[i][0];
    181           regex.lastIndex = posWithinLine;
    182           match = regex.exec(line);
    183           mc[i] = match;
    184         }
    185         if (match !== null && (bestMatch === null || match.index < bestMatch.index)) {
    186           bestMatch = match;
    187           bestPatternIndex = i;
    188           if (match.index === posWithinLine) {
    189             break;
    190           }
    191         }
    192       }
    193 
    194       if (bestMatch === null) {
    195         output(line.substring(posWithinLine), null);
    196         break;
    197       }
    198       else {
    199         // got a match
    200         if (bestMatch.index > posWithinLine) {
    201           output(line.substring(posWithinLine, bestMatch.index), null);
    202         }
    203 
    204         var pattern = state[bestPatternIndex];
    205 
    206         var newStyle = pattern[1];
    207         var matchedString;
    208         if (newStyle instanceof Array) {
    209           for (var subexpression = 0; subexpression < newStyle.length; subexpression++) {
    210             matchedString = bestMatch[subexpression + 1];
    211             output(matchedString, newStyle[subexpression]);
    212           }
    213         }
    214         else {
    215           matchedString = bestMatch[0];
    216           output(matchedString, newStyle);
    217         }
    218 
    219         switch (pattern[2]) {
    220         case -1:
    221           // do nothing
    222           break;
    223         case -2:
    224           // exit
    225           patternStack.pop();
    226           break;
    227         case -3:
    228           // exitall
    229           patternStack.length = 0;
    230           break;
    231         default:
    232           // this was the start of a delimited pattern or a state/environment
    233           patternStack.push(pattern);
    234           break;
    235         }
    236       }
    237     }
    238 
    239     // end of the line
    240     if (currentStyle) {
    241       tags[numTags++] = {pos: pos};
    242       if (currentStyle === 'sh_url') {
    243         sh_setHref(tags, numTags, inputString);
    244       }
    245       currentStyle = null;
    246     }
    247     pos = startOfNextLine;
    248   }
    249 
    250   return tags;
    251 }
    252 
    253 ////////////////////////////////////////////////////////////////////////////////
    254 // DOM-dependent functions
    255 
    256 function sh_getClasses(element) {
    257   var result = [];
    258   var htmlClass = element.className;
    259   if (htmlClass && htmlClass.length > 0) {
    260     var htmlClasses = htmlClass.split(' ');
    261     for (var i = 0; i < htmlClasses.length; i++) {
    262       if (htmlClasses[i].length > 0) {
    263         result.push(htmlClasses[i]);
    264       }
    265     }
    266   }
    267   return result;
    268 }
    269 
    270 function sh_addClass(element, name) {
    271   var htmlClasses = sh_getClasses(element);
    272   for (var i = 0; i < htmlClasses.length; i++) {
    273     if (name.toLowerCase() === htmlClasses[i].toLowerCase()) {
    274       return;
    275     }
    276   }
    277   htmlClasses.push(name);
    278   element.className = htmlClasses.join(' ');
    279 }
    280 
    281 /**
    282 Extracts the tags from an HTML DOM NodeList.
    283 @param  nodeList  a DOM NodeList
    284 @param  result  an object with text, tags and pos properties
    285 */
    286 function sh_extractTagsFromNodeList(nodeList, result) {
    287   var length = nodeList.length;
    288   for (var i = 0; i < length; i++) {
    289     var node = nodeList.item(i);
    290     switch (node.nodeType) {
    291     case 1:
    292       if (node.nodeName.toLowerCase() === 'br') {
    293         var terminator;
    294         if (/MSIE/.test(navigator.userAgent)) {
    295           terminator = '\r';
    296         }
    297         else {
    298           terminator = '\n';
    299         }
    300         result.text.push(terminator);
    301         result.pos++;
    302       }
    303       else {
    304         result.tags.push({node: node.cloneNode(false), pos: result.pos});
    305         sh_extractTagsFromNodeList(node.childNodes, result);
    306         result.tags.push({pos: result.pos});
    307       }
    308       break;
    309     case 3:
    310     case 4:
    311       result.text.push(node.data);
    312       result.pos += node.length;
    313       break;
    314     }
    315   }
    316 }
    317 
    318 /**
    319 Extracts the tags from the text of an HTML element. The extracted tags will be
    320 returned as an array of tag objects. See sh_highlightString for the format of
    321 the tag objects.
    322 @param  element  a DOM element
    323 @param  tags  an empty array; the extracted tag objects will be returned in it
    324 @return  the text of the element
    325 @see  sh_highlightString
    326 */
    327 function sh_extractTags(element, tags) {
    328   var result = {};
    329   result.text = [];
    330   result.tags = tags;
    331   result.pos = 0;
    332   sh_extractTagsFromNodeList(element.childNodes, result);
    333   return result.text.join('');
    334 }
    335 
    336 /**
    337 Merges the original tags from an element with the tags produced by highlighting.
    338 @param  originalTags  an array containing the original tags
    339 @param  highlightTags  an array containing the highlighting tags - these must not overlap
    340 @result  an array containing the merged tags
    341 */
    342 function sh_mergeTags(originalTags, highlightTags) {
    343   var numOriginalTags = originalTags.length;
    344   if (numOriginalTags === 0) {
    345     return highlightTags;
    346   }
    347 
    348   var numHighlightTags = highlightTags.length;
    349   if (numHighlightTags === 0) {
    350     return originalTags;
    351   }
    352 
    353   var result = [];
    354   var originalIndex = 0;
    355   var highlightIndex = 0;
    356 
    357   while (originalIndex < numOriginalTags && highlightIndex < numHighlightTags) {
    358     var originalTag = originalTags[originalIndex];
    359     var highlightTag = highlightTags[highlightIndex];
    360 
    361     if (originalTag.pos <= highlightTag.pos) {
    362       result.push(originalTag);
    363       originalIndex++;
    364     }
    365     else {
    366       result.push(highlightTag);
    367       if (highlightTags[highlightIndex + 1].pos <= originalTag.pos) {
    368         highlightIndex++;
    369         result.push(highlightTags[highlightIndex]);
    370         highlightIndex++;
    371       }
    372       else {
    373         // new end tag
    374         result.push({pos: originalTag.pos});
    375 
    376         // new start tag
    377         highlightTags[highlightIndex] = {node: highlightTag.node.cloneNode(false), pos: originalTag.pos};
    378       }
    379     }
    380   }
    381 
    382   while (originalIndex < numOriginalTags) {
    383     result.push(originalTags[originalIndex]);
    384     originalIndex++;
    385   }
    386 
    387   while (highlightIndex < numHighlightTags) {
    388     result.push(highlightTags[highlightIndex]);
    389     highlightIndex++;
    390   }
    391 
    392   return result;
    393 }
    394 
    395 /**
    396 Inserts tags into text.
    397 @param  tags  an array of tag objects
    398 @param  text  a string representing the text
    399 @return  a DOM DocumentFragment representing the resulting HTML
    400 */
    401 function sh_insertTags(tags, text) {
    402   var doc = document;
    403 
    404   var result = document.createDocumentFragment();
    405   var tagIndex = 0;
    406   var numTags = tags.length;
    407   var textPos = 0;
    408   var textLength = text.length;
    409   var currentNode = result;
    410 
    411   // output one tag or text node every iteration
    412   while (textPos < textLength || tagIndex < numTags) {
    413     var tag;
    414     var tagPos;
    415     if (tagIndex < numTags) {
    416       tag = tags[tagIndex];
    417       tagPos = tag.pos;
    418     }
    419     else {
    420       tagPos = textLength;
    421     }
    422 
    423     if (tagPos <= textPos) {
    424       // output the tag
    425       if (tag.node) {
    426         // start tag
    427         var newNode = tag.node;
    428         currentNode.appendChild(newNode);
    429         currentNode = newNode;
    430       }
    431       else {
    432         // end tag
    433         currentNode = currentNode.parentNode;
    434       }
    435       tagIndex++;
    436     }
    437     else {
    438       // output text
    439       currentNode.appendChild(doc.createTextNode(text.substring(textPos, tagPos)));
    440       textPos = tagPos;
    441     }
    442   }
    443 
    444   return result;
    445 }
    446 
    447 /**
    448 Highlights an element containing source code.  Upon completion of this function,
    449 the element will have been placed in the "sh_sourceCode" class.
    450 @param  element  a DOM <pre> element containing the source code to be highlighted
    451 @param  language  a language definition object
    452 */
    453 function sh_highlightElement(element, language) {
    454   sh_addClass(element, 'sh_sourceCode');
    455   var originalTags = [];
    456   var inputString = sh_extractTags(element, originalTags);
    457   var highlightTags = sh_highlightString(inputString, language);
    458   var tags = sh_mergeTags(originalTags, highlightTags);
    459   var documentFragment = sh_insertTags(tags, inputString);
    460   while (element.hasChildNodes()) {
    461     element.removeChild(element.firstChild);
    462   }
    463   element.appendChild(documentFragment);
    464 }
    465 
    466 function sh_getXMLHttpRequest() {
    467   if (window.ActiveXObject) {
    468     return new ActiveXObject('Msxml2.XMLHTTP');
    469   }
    470   else if (window.XMLHttpRequest) {
    471     return new XMLHttpRequest();
    472   }
    473   throw 'No XMLHttpRequest implementation available';
    474 }
    475 
    476 function sh_load(language, element, prefix, suffix) {
    477   if (language in sh_requests) {
    478     sh_requests[language].push(element);
    479     return;
    480   }
    481   sh_requests[language] = [element];
    482   var request = sh_getXMLHttpRequest();
    483   var url = prefix + 'sh_' + language + suffix;
    484   request.open('GET', url, true);
    485   request.onreadystatechange = function () {
    486     if (request.readyState === 4) {
    487       try {
    488         if (! request.status || request.status === 200) {
    489           eval(request.responseText);
    490           var elements = sh_requests[language];
    491           for (var i = 0; i < elements.length; i++) {
    492             sh_highlightElement(elements[i], sh_languages[language]);
    493           }
    494         }
    495         else {
    496           throw 'HTTP error: status ' + request.status;
    497         }
    498       }
    499       finally {
    500         request = null;
    501       }
    502     }
    503   };
    504   request.send(null);
    505 }
    506 
    507 /**
    508 Highlights all elements containing source code on the current page. Elements
    509 containing source code must be "pre" elements with a "class" attribute of
    510 "sh_LANGUAGE", where LANGUAGE is a valid language identifier; e.g., "sh_java"
    511 identifies the element as containing "java" language source code.
    512 */
    513 function highlight(prefix, suffix, tag) {
    514   var nodeList = document.getElementsByTagName(tag);
    515   for (var i = 0; i < nodeList.length; i++) {
    516     var element = nodeList.item(i);
    517     var htmlClasses = sh_getClasses(element);
    518     var highlighted = false;
    519     var donthighlight = false;
    520     for (var j = 0; j < htmlClasses.length; j++) {
    521       var htmlClass = htmlClasses[j].toLowerCase();
    522       if (htmlClass === 'sh_none') {
    523         donthighlight = true
    524         continue;
    525       }
    526       if (htmlClass.substr(0, 3) === 'sh_') {
    527         var language = htmlClass.substring(3);
    528         if (language in sh_languages) {
    529           sh_highlightElement(element, sh_languages[language]);
    530           highlighted = true;
    531         }
    532         else if (typeof(prefix) === 'string' && typeof(suffix) === 'string') {
    533           sh_load(language, element, prefix, suffix);
    534         }
    535         else {
    536           throw 'Found <' + tag + '> element with class="' + htmlClass + '", but no such language exists';
    537         }
    538         break;
    539       }
    540     }
    541     if (highlighted === false && donthighlight == false) {
    542       sh_highlightElement(element, sh_languages["javascript"]);
    543     }
    544   }
    545 }
    546 
    547 
    548 
    549 function sh_highlightDocument(prefix, suffix) {
    550   highlight(prefix, suffix, 'tt');
    551   highlight(prefix, suffix, 'code');
    552   highlight(prefix, suffix, 'pre');
    553 }