2c59ecdc82
merge salv's fix to remove keystone middleware: lp855151 one more 202->200 from tyler merge tylers additional 202 -> 200 changes merge additions to OVS readme describing running with multiple hosts merge brad's changes to make create API calls return 200, not 202 merge unit test for showing unset attachment merging API docs branch Merge: lp:~yinliu2/quantum/bug856564 Merge: lp:~bgh/quantum/bug850261 Change-Id: I56fe24c59f918737e57b562343c33ec6dcceac60
539 lines
15 KiB
JavaScript
539 lines
15 KiB
JavaScript
/*
|
|
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 sh_requests = {};
|
|
|
|
function sh_isEmailAddress(url) {
|
|
if (/^mailto:/.test(url)) {
|
|
return false;
|
|
}
|
|
return url.indexOf('@') !== -1;
|
|
}
|
|
|
|
function sh_setHref(tags, numTags, inputString) {
|
|
var url = inputString.substring(tags[numTags - 2].pos, tags[numTags - 1].pos);
|
|
if (url.length >= 2 && url.charAt(0) === '<' && url.charAt(url.length - 1) === '>') {
|
|
url = url.substr(1, url.length - 2);
|
|
}
|
|
if (sh_isEmailAddress(url)) {
|
|
url = 'mailto:' + url;
|
|
}
|
|
tags[numTags - 2].node.href = url;
|
|
}
|
|
|
|
/*
|
|
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
|
|
*/
|
|
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
|
|
*/
|
|
function sh_highlightString(inputString, language) {
|
|
if (/Konqueror/.test(navigator.userAgent)) {
|
|
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 = sh_konquerorExec;
|
|
}
|
|
}
|
|
}
|
|
language.konquered = true;
|
|
}
|
|
}
|
|
|
|
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 = [];
|
|
|
|
// 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 output = function(s, style) {
|
|
var length = s.length;
|
|
// this is more than just an optimization - we don't want to output empty <span></span> 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') {
|
|
sh_setHref(tags, numTags, inputString);
|
|
}
|
|
}
|
|
if (style) {
|
|
var clone;
|
|
if (style === 'sh_url') {
|
|
clone = a.cloneNode(false);
|
|
}
|
|
else {
|
|
clone = span.cloneNode(false);
|
|
}
|
|
clone.className = style;
|
|
tags[numTags++] = {node: clone, pos: pos};
|
|
}
|
|
}
|
|
pos += length;
|
|
currentStyle = style;
|
|
};
|
|
|
|
var endOfLinePattern = /\r\n|\r|\n/g;
|
|
endOfLinePattern.lastIndex = 0;
|
|
var inputStringLength = inputString.length;
|
|
while (pos < inputStringLength) {
|
|
var start = pos;
|
|
var end;
|
|
var startOfNextLine;
|
|
var endOfLineMatch = endOfLinePattern.exec(inputString);
|
|
if (endOfLineMatch === null) {
|
|
end = inputStringLength;
|
|
startOfNextLine = inputStringLength;
|
|
}
|
|
else {
|
|
end = endOfLineMatch.index;
|
|
startOfNextLine = endOfLinePattern.lastIndex;
|
|
}
|
|
|
|
var line = inputString.substring(start, end);
|
|
|
|
var matchCache = [];
|
|
for (;;) {
|
|
var posWithinLine = pos - start;
|
|
|
|
var stateIndex;
|
|
var stackLength = patternStack.length;
|
|
if (stackLength === 0) {
|
|
stateIndex = 0;
|
|
}
|
|
else {
|
|
// get the next state
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bestMatch === null) {
|
|
output(line.substring(posWithinLine), null);
|
|
break;
|
|
}
|
|
else {
|
|
// got a match
|
|
if (bestMatch.index > posWithinLine) {
|
|
output(line.substring(posWithinLine, bestMatch.index), null);
|
|
}
|
|
|
|
var pattern = state[bestPatternIndex];
|
|
|
|
var newStyle = pattern[1];
|
|
var matchedString;
|
|
if (newStyle instanceof Array) {
|
|
for (var subexpression = 0; subexpression < newStyle.length; subexpression++) {
|
|
matchedString = bestMatch[subexpression + 1];
|
|
output(matchedString, newStyle[subexpression]);
|
|
}
|
|
}
|
|
else {
|
|
matchedString = bestMatch[0];
|
|
output(matchedString, newStyle);
|
|
}
|
|
|
|
switch (pattern[2]) {
|
|
case -1:
|
|
// do nothing
|
|
break;
|
|
case -2:
|
|
// exit
|
|
patternStack.pop();
|
|
break;
|
|
case -3:
|
|
// exitall
|
|
patternStack.length = 0;
|
|
break;
|
|
default:
|
|
// this was the start of a delimited pattern or a state/environment
|
|
patternStack.push(pattern);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// end of the line
|
|
if (currentStyle) {
|
|
tags[numTags++] = {pos: pos};
|
|
if (currentStyle === 'sh_url') {
|
|
sh_setHref(tags, numTags, inputString);
|
|
}
|
|
currentStyle = null;
|
|
}
|
|
pos = startOfNextLine;
|
|
}
|
|
|
|
return tags;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// DOM-dependent functions
|
|
|
|
function sh_getClasses(element) {
|
|
var result = [];
|
|
var htmlClass = element.className;
|
|
if (htmlClass && htmlClass.length > 0) {
|
|
var htmlClasses = htmlClass.split(' ');
|
|
for (var i = 0; i < htmlClasses.length; i++) {
|
|
if (htmlClasses[i].length > 0) {
|
|
result.push(htmlClasses[i]);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function sh_addClass(element, name) {
|
|
var htmlClasses = sh_getClasses(element);
|
|
for (var i = 0; i < htmlClasses.length; i++) {
|
|
if (name.toLowerCase() === htmlClasses[i].toLowerCase()) {
|
|
return;
|
|
}
|
|
}
|
|
htmlClasses.push(name);
|
|
element.className = htmlClasses.join(' ');
|
|
}
|
|
|
|
/**
|
|
Extracts the tags from an HTML DOM NodeList.
|
|
@param nodeList a DOM NodeList
|
|
@param result an object with text, tags and pos properties
|
|
*/
|
|
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') {
|
|
var terminator;
|
|
if (/MSIE/.test(navigator.userAgent)) {
|
|
terminator = '\r';
|
|
}
|
|
else {
|
|
terminator = '\n';
|
|
}
|
|
result.text.push(terminator);
|
|
result.pos++;
|
|
}
|
|
else {
|
|
result.tags.push({node: node.cloneNode(false), pos: result.pos});
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
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
|
|
*/
|
|
function sh_extractTags(element, tags) {
|
|
var result = {};
|
|
result.text = [];
|
|
result.tags = tags;
|
|
result.pos = 0;
|
|
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
|
|
*/
|
|
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
|
|
*/
|
|
function sh_insertTags(tags, text) {
|
|
var doc = document;
|
|
|
|
var result = document.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;
|
|
}
|
|
|
|
/**
|
|
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 <pre> element containing the source code to be highlighted
|
|
@param language a language definition object
|
|
*/
|
|
function sh_highlightElement(element, language) {
|
|
sh_addClass(element, 'sh_sourceCode');
|
|
var originalTags = [];
|
|
var inputString = sh_extractTags(element, originalTags);
|
|
var highlightTags = sh_highlightString(inputString, language);
|
|
var tags = sh_mergeTags(originalTags, highlightTags);
|
|
var documentFragment = sh_insertTags(tags, inputString);
|
|
while (element.hasChildNodes()) {
|
|
element.removeChild(element.firstChild);
|
|
}
|
|
element.appendChild(documentFragment);
|
|
}
|
|
|
|
function sh_getXMLHttpRequest() {
|
|
if (window.ActiveXObject) {
|
|
return new ActiveXObject('Msxml2.XMLHTTP');
|
|
}
|
|
else if (window.XMLHttpRequest) {
|
|
return new XMLHttpRequest();
|
|
}
|
|
throw 'No XMLHttpRequest implementation available';
|
|
}
|
|
|
|
function sh_load(language, element, prefix, suffix) {
|
|
if (language in sh_requests) {
|
|
sh_requests[language].push(element);
|
|
return;
|
|
}
|
|
sh_requests[language] = [element];
|
|
var request = 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 = sh_requests[language];
|
|
for (var i = 0; i < elements.length; i++) {
|
|
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.
|
|
*/
|
|
function sh_highlightDocument(prefix, suffix) {
|
|
var nodeList = document.getElementsByTagName('pre');
|
|
for (var i = 0; i < nodeList.length; i++) {
|
|
var element = nodeList.item(i);
|
|
var htmlClasses = sh_getClasses(element);
|
|
for (var j = 0; j < htmlClasses.length; j++) {
|
|
var htmlClass = htmlClasses[j].toLowerCase();
|
|
if (htmlClass === 'sh_sourcecode') {
|
|
continue;
|
|
}
|
|
if (htmlClass.substr(0, 3) === 'sh_') {
|
|
var language = htmlClass.substring(3);
|
|
if (language in sh_languages) {
|
|
sh_highlightElement(element, sh_languages[language]);
|
|
}
|
|
else if (typeof(prefix) === 'string' && typeof(suffix) === 'string') {
|
|
sh_load(language, element, prefix, suffix);
|
|
}
|
|
else {
|
|
throw 'Found <pre> element with class="' + htmlClass + '", but no such language exists';
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|