/* [MODIFIED] ds14050@vvvvvv.sakura.ne.jp [ORIGINAL] SHJS - Syntax Highlighting in JavaScript Copyright (C) 2007, 2008 gnombat@users.sourceforge.net License: http://shjs.sourceforge.net/doc/gplv3.html */ if (! this.sh_languages) { this.sh_languages = {}; } var SHJS = function() { var self = { sh_requests: {}, sh_isEmailAddress: function sh_isEmailAddress(url) { return ! /^(?:mailto:|[^@]*$)/.test(url); }, sh_cloneArray: function sh_cloneArray(arr) { var clone = []; for (var i in arr) { clone[i] = (arr[i] instanceof Array) ? self.sh_cloneArray(arr[i]) : arr[i]; } return clone; }, /* Konqueror has a bug where the regular expression /$/g will not match at the end of a line more than once: var regex = /$/g; var match; var line = '1234567890'; regex.lastIndex = 10; match = regex.exec(line); var line2 = 'abcde'; regex.lastIndex = 5; match = regex.exec(line2); // fails */ sh_konquerorExec: function sh_konquerorExec(s) { var result = ['']; result.index = s.length; result.input = s; return result; }, /** Highlights all elements containing source code in a text string. The return value is an array of objects, each representing an HTML start or end tag. Each object has a property named pos, which is an integer representing the text offset of the tag. Every start tag also has a property named node, which is the DOM element started by the tag. End tags do not have this property. @param inputString a text string @param language a language definition object @return an array of tag objects */ sh_highlightString: function sh_highlightString(inputString, language) { var a = document.createElement('a'); var span = document.createElement('span'); // the result var tags = []; var numTags = 0; // each element is a pattern object from language var patternStack = []; patternStack[-1] = [null,null,0]; var machineStateStack = []; // the current position within inputString var pos = 0; // the name of the current style, or null if there is no current style var currentStyle = null; var setHref = function setHref() { var url = inputString.substring(tags[numTags - 2].pos, tags[numTags - 1].pos); url = url.replace(/^<|>$/g, ''); if (self.sh_isEmailAddress(url)) { url = 'mailto:' + url; } tags[numTags - 2].node.href = url; }; var output = function output(s, style) { var length = s.length; // this is more than just an optimization - we don't want to output empty elements if (length === 0) { return; } if (! style) { var stackLength = patternStack.length; if (stackLength !== 0) { var pattern = patternStack[stackLength - 1]; // check whether this is a state or an environment if (! pattern[3]) { // it's not a state - it's an environment; use the style for this environment style = pattern[1]; } } } if (currentStyle !== style) { if (currentStyle) { tags[numTags++] = {pos: pos}; if (currentStyle === 'sh_url') { setHref(); } } if (style) { var clone = (style === 'sh_url' ? a : span).cloneNode(false); clone.className = style; tags[numTags++] = {node: clone, pos: pos}; } } pos += length; currentStyle = style; }; var endOfLinePattern = /\r\n?|\n|$/g; var startOfNextLine = 0; var inputStringLength = inputString.length; while (pos < inputStringLength) { endOfLinePattern.lastIndex = startOfNextLine; var endOfLineMatch = endOfLinePattern.exec(inputString); var start = pos; var end = endOfLineMatch.index; startOfNextLine = endOfLinePattern.lastIndex; var line = inputString.substring(start, end); var matchCache = []; BestMatchFindingLoop: for (;;) { var posWithinLine = pos - start; var stackLength = patternStack.length; // get the next state var stateIndex = patternStack[stackLength - 1][2]; var state = language[stateIndex]; var numPatterns = state.length; var mc = matchCache[stateIndex]; if (! mc) { mc = matchCache[stateIndex] = []; } var bestMatch = null; var bestPatternIndex = -1; for (var i = 0; i < numPatterns; i++) { var match; if (i < mc.length && (mc[i] === null || posWithinLine <= mc[i].index)) { match = mc[i]; } else { var regex = state[i][0]; regex.lastIndex = posWithinLine; match = regex.exec(line); mc[i] = match; } if (match !== null && (bestMatch === null || match.index < bestMatch.index)) { bestMatch = match; bestPatternIndex = i; if (match.index === posWithinLine) { break; } } } var saveBestMatchFindingState = function saveBestMatchFindingState() { return [ numTags, self.sh_cloneArray(patternStack), pos, currentStyle, endOfLineMatch, start, end, startOfNextLine, self.sh_cloneArray(matchCache), bestMatch, bestPatternIndex ]; }; var loadBestMatchFindingState = function loadBestMatchFindingState(machineState) { numTags = machineState[0]; patternStack = machineState[1]; pos = machineState[2]; currentStyle = machineState[3]; endOfLineMatch = machineState[4]; start = machineState[5]; end = machineState[6]; startOfNextLine = machineState[7]; matchCache = machineState[8]; bestMatch = machineState[9]; bestPatternIndex = machineState[10]; tags.length = numTags; endOfLinePattern.lastIndex = startOfNextLine; line = inputString.substring(start, end); }; var invalidateBestMatch = function invalidateBestMatch() { stateIndex = patternStack[patternStack.length - 1][2]; regex = language[stateIndex][bestPatternIndex][0]; regex.lastIndex = bestMatch.index + 1; matchCache[stateIndex][bestPatternIndex] = regex.exec(line); }; if (bestMatch === null) { output(line.substring(posWithinLine), null); if (inputStringLength <= startOfNextLine && patternStack.length !== 0) { loadBestMatchFindingState(machineStateStack.pop()); invalidateBestMatch(); continue BestMatchFindingLoop; } else { break; } } else { // got a match // save current bestMatch finding state for future backtracking. var machineState = saveBestMatchFindingState(); if (bestMatch.index > posWithinLine) { output(line.substring(posWithinLine, bestMatch.index), null); } var pattern = state[bestPatternIndex]; var newStyle = pattern[1]; if (newStyle instanceof Array) { for (var subexpression = 0; subexpression < newStyle.length; subexpression++) { output(bestMatch[subexpression + 1], newStyle[subexpression]); } } else { output(bestMatch[0], newStyle); } switch (pattern[2]) { case -1: // do nothing break; case -2: // exit patternStack.pop(); if (patternStack.length === 0) { // moving to default state is special. machineStateStack.length = 0; } else { machineStateStack.push(machineState); } break; case -3: // exitall patternStack.length = machineStateStack.length = 0; break; case -4: loadBestMatchFindingState(machineStateStack.pop()); invalidateBestMatch(); continue BestMatchFindingLoop; default: // this was the start of a delimited pattern or a state/environment patternStack.push(pattern); machineStateStack.push(machineState); break; } } } // end of the line if (currentStyle) { tags[numTags++] = {pos: pos}; if (currentStyle === 'sh_url') { setHref(); } currentStyle = null; } pos = startOfNextLine; } return tags; }, //////////////////////////////////////////////////////////////////////////////// // DOM-dependent functions sh_hasClass: function sh_hasClass(element, name) { var className = (' '+ element.className +' ').toLowerCase(); return -1 !== className.indexOf((' '+ name +' ').toLowerCase()); }, sh_getClasses: function sh_getClasses(element) { return element.className.split(/^$|\s+/); }, sh_addClass: function sh_addClass(element, name) { if (self.sh_hasClass(element, name)) { return false; } element.className += ' '+ name; return true; }, /** Extracts the tags from an HTML DOM NodeList. @param nodeList a DOM NodeList @param result an object with text, tags and pos properties */ sh_extractTagsFromNodeList: function(terminator) { return function sh_extractTagsFromNodeList(nodeList, result) { var length = nodeList.length; for (var i = 0; i < length; i++) { var node = nodeList.item(i); switch (node.nodeType) { case 1: if (node.nodeName.toLowerCase() === 'br') { result.text.push(terminator); result.pos++; } else { result.tags.push({node: node.cloneNode(false), pos: result.pos}); self.sh_extractTagsFromNodeList(node.childNodes, result); result.tags.push({pos: result.pos}); } break; case 3: case 4: result.text.push(node.data); result.pos += node.length; break; } } }; }(-1 !== navigator.userAgent.indexOf('MSIE') ? '\r' : '\n'), /** Extracts the tags from the text of an HTML element. The extracted tags will be returned as an array of tag objects. See sh_highlightString for the format of the tag objects. @param element a DOM element @param tags an empty array; the extracted tag objects will be returned in it @return the text of the element @see sh_highlightString */ sh_extractTags: function sh_extractTags(element, tags) { var result = {}; result.text = []; result.tags = tags; result.pos = 0; self.sh_extractTagsFromNodeList(element.childNodes, result); return result.text.join(''); }, /** Merges the original tags from an element with the tags produced by highlighting. @param originalTags an array containing the original tags @param highlightTags an array containing the highlighting tags - these must not overlap @result an array containing the merged tags */ sh_mergeTags: function sh_mergeTags(originalTags, highlightTags) { var numOriginalTags = originalTags.length; if (numOriginalTags === 0) { return highlightTags; } var numHighlightTags = highlightTags.length; if (numHighlightTags === 0) { return originalTags; } var result = []; var originalIndex = 0; var highlightIndex = 0; while (originalIndex < numOriginalTags && highlightIndex < numHighlightTags) { var originalTag = originalTags[originalIndex]; var highlightTag = highlightTags[highlightIndex]; if (originalTag.pos <= highlightTag.pos) { result.push(originalTag); originalIndex++; } else { result.push(highlightTag); if (highlightTags[highlightIndex + 1].pos <= originalTag.pos) { highlightIndex++; result.push(highlightTags[highlightIndex]); highlightIndex++; } else { // new end tag result.push({pos: originalTag.pos}); // new start tag highlightTags[highlightIndex] = {node: highlightTag.node.cloneNode(false), pos: originalTag.pos}; } } } while (originalIndex < numOriginalTags) { result.push(originalTags[originalIndex]); originalIndex++; } while (highlightIndex < numHighlightTags) { result.push(highlightTags[highlightIndex]); highlightIndex++; } return result; }, /** Inserts tags into text. @param tags an array of tag objects @param text a string representing the text @return a DOM DocumentFragment representing the resulting HTML */ sh_insertTags: function sh_insertTags(tags, text) { var doc = document; var result = doc.createDocumentFragment(); var tagIndex = 0; var numTags = tags.length; var textPos = 0; var textLength = text.length; var currentNode = result; // output one tag or text node every iteration while (textPos < textLength || tagIndex < numTags) { var tag; var tagPos; if (tagIndex < numTags) { tag = tags[tagIndex]; tagPos = tag.pos; } else { tagPos = textLength; } if (tagPos <= textPos) { // output the tag if (tag.node) { // start tag var newNode = tag.node; currentNode.appendChild(newNode); currentNode = newNode; } else { // end tag currentNode = currentNode.parentNode; } tagIndex++; } else { // output text currentNode.appendChild(doc.createTextNode(text.substring(textPos, tagPos))); textPos = tagPos; } } return result; }, sh_putLinenumber: function sh_putLinenumber(element, param, inputString) { var startline = parseInt(param, 10); var opt = /^([-+]?)(0*)(\d+)/.exec(param); var opt_explicit_sign = (opt[1] === '+') ? '+' : ''; var opt_zero_padding = (0 !== opt[2].length) ? new Array(opt[2].length + opt[3].length + 1).join('0') : ''; var re_zero_padding = new RegExp('^0+(?=\\d{' + opt_zero_padding.length + '})'); var nums = inputString.match(/(?:\r\n?|\n)(?!$)|$/g); if (0 !== opt_explicit_sign.length || 0 !== opt_zero_padding.length) { for (var i = 0; i !== nums.length; ++i) { nums[i] = (0 < startline + i ? opt_explicit_sign : startline + i < 0 ? '-' : '') + (opt_zero_padding + Math.abs(startline + i)).replace(re_zero_padding, '') + nums[i]; } } else { for (var i = 0; i !== nums.length; ++i) { nums[i] = '' + (startline + i) + nums[i]; } } var d = element.ownerDocument; var e = {table:'table', tbody:'tbody', tr:'tr', tdLeft:'td', pre:'pre', tdRight:'td'}; for (var p in e) { e[p] = d.createElement(e[p]); } element.parentNode.replaceChild(e['table'], element); e['table'].appendChild(e['tbody']).appendChild(e['tr']); e['tr'].appendChild(e['tdLeft']).appendChild(e['pre']).appendChild(d.createTextNode(nums.join(''))); e['tr'].appendChild(e['tdRight']).appendChild(element); e['table'].className = 'sh_sourceTable'; e['pre'].className = 'sh_sourceCode sh_numbers'; return element; }, /** Highlights an element containing source code. Upon completion of this function, the element will have been placed in the "sh_sourceCode" class. @param element a DOM
element containing the source code to be highlighted @param language a language definition object */ sh_highlightElement: function sh_highlightElement(element, language, firstline) { self.sh_addClass(element, 'sh_sourceCode'); var originalTags = []; var inputString = self.sh_extractTags(element, originalTags); var highlightTags = self.sh_highlightString(inputString, language); var tags = self.sh_mergeTags(originalTags, highlightTags); var documentFragment = self.sh_insertTags(tags, inputString); if (firstline !== null && ! isNaN(firstline)) { self.sh_putLinenumber(element, firstline, inputString); } while (element.hasChildNodes()) { element.removeChild(element.firstChild); } element.appendChild(documentFragment); }, sh_getXMLHttpRequest: function sh_getXMLHttpRequest() { if (window.XMLHttpRequest) { return new XMLHttpRequest(); } if (window.ActiveXObject) { return new ActiveXObject('Msxml2.XMLHTTP'); } throw 'No XMLHttpRequest implementation available'; }, sh_load: function sh_load(language, element, prefix, suffix) { if (language in self.sh_requests) { self.sh_requests[language].push(element); return; } self.sh_requests[language] = [element]; var request = self.sh_getXMLHttpRequest(); var url = prefix + 'sh_' + language + suffix; request.open('GET', url, true); request.onreadystatechange = function() { if (request.readyState === 4) { try { if (! request.status || request.status === 200) { eval(request.responseText); var elements = self.sh_requests[language]; for (var i = 0; i < elements.length; i++) { self.sh_highlightElement(elements[i], sh_languages[language]); } } else { throw 'HTTP error: status ' + request.status; } } finally { request = null; } } }; request.send(null); }, /** Highlights all elements containing source code on the current page. Elements containing source code must be "pre" elements with a "class" attribute of "sh_LANGUAGE", where LANGUAGE is a valid language identifier; e.g., "sh_java" identifies the element as containing "java" language source code. */ sh_highlightDocument: function sh_highlightDocument(prefix, suffix) { var nodeList = document.getElementsByTagName('pre'); for (var i = 0; i < nodeList.length; i++) { var element = nodeList.item(i); var language = null; var htmlClasses = self.sh_getClasses(element); for (var j = 0; j < htmlClasses.length; j++) { var htmlClass = htmlClasses[j].toLowerCase(); if (htmlClass === 'sh_sourcecode') { language = null; break; } if (htmlClass.substr(0, 3) === 'sh_') { language = htmlClass.substring(3); } } if (language) { if (language in sh_languages) { self.sh_highlightElement(element, sh_languages[language], element.getAttribute('firstline')); } else if (prefix != null && suffix != null) { self.sh_load(language, element, prefix, suffix); } else { throw 'Foundelement with class="sh_' + language + '", but no such language exists'; } } } } }; if (-1 !== navigator.userAgent.indexOf('Konqueror')) { self.sh_highlightString = function(sh_highlightString) { return function sh_highlightString(inputString, language) { if (! language.konquered) { for (var s = 0; s < language.length; s++) { for (var p = 0; p < language[s].length; p++) { var r = language[s][p][0]; if (r.source === '$') { r.exec = self.sh_konquerorExec; } } } language.konquered = true; } return sh_highlightString(inputString, language); }; }(self.sh_highlightString); } return self; }(); var sh_highlightDocument = SHJS.sh_highlightDocument;