dojo.provide("dojox.highlight._base"); /*===== dojox.highlight = { // summary: // Syntax highlighting with language auto-detection package // // description: // // Syntax highlighting with language auto-detection package. // Released under CLA by the Dojo Toolkit, original BSD release // available from: http://softwaremaniacs.org/soft/highlight/ // // }; =====*/ ;(function(){ var dh = dojox.highlight, C_NUMBER_RE = '\\b(0x[A-Za-z0-9]+|\\d+(\\.\\d+)?)'; // constants dh.constants = { IDENT_RE: '[a-zA-Z][a-zA-Z0-9_]*', UNDERSCORE_IDENT_RE: '[a-zA-Z_][a-zA-Z0-9_]*', NUMBER_RE: '\\b\\d+(\\.\\d+)?', C_NUMBER_RE: C_NUMBER_RE, // Common modes APOS_STRING_MODE: { className: 'string', begin: '\'', end: '\'', illegal: '\\n', contains: ['escape'], relevance: 0 }, QUOTE_STRING_MODE: { className: 'string', begin: '"', end: '"', illegal: '\\n', contains: ['escape'], relevance: 0 }, BACKSLASH_ESCAPE: { className: 'escape', begin: '\\\\.', end: '^', relevance: 0 }, C_LINE_COMMENT_MODE: { className: 'comment', begin: '//', end: '$', relevance: 0 }, C_BLOCK_COMMENT_MODE: { className: 'comment', begin: '/\\*', end: '\\*/' }, HASH_COMMENT_MODE: { className: 'comment', begin: '#', end: '$' }, C_NUMBER_MODE: { className: 'number', begin: C_NUMBER_RE, end: '^', relevance: 0 } }; // utilities function esc(value){ return value.replace(/&/gm, '&').replace(//gm, '>'); } function verifyText(block){ return dojo.every(block.childNodes, function(node){ return node.nodeType == 3 || String(node.nodeName).toLowerCase() == 'br'; }); } function blockText(block){ var result = []; dojo.forEach(block.childNodes, function(node){ if(node.nodeType == 3){ result.push(node.nodeValue); }else if(String(node.nodeName).toLowerCase() == 'br'){ result.push("\n"); }else{ throw 'Complex markup'; } }); return result.join(""); } function buildKeywordGroups(mode){ if(!mode.keywordGroups){ for(var key in mode.keywords){ var kw = mode.keywords[key]; if(kw instanceof Object){ // dojo.isObject? mode.keywordGroups = mode.keywords; }else{ mode.keywordGroups = {keyword: mode.keywords}; } break; } } } function buildKeywords(lang){ if(lang.defaultMode && lang.modes){ buildKeywordGroups(lang.defaultMode); dojo.forEach(lang.modes, buildKeywordGroups); } } // main object var Highlighter = function(langName, textBlock){ // initialize the state this.langName = langName; this.lang = dh.languages[langName]; this.modes = [this.lang.defaultMode]; this.relevance = 0; this.keywordCount = 0; this.result = []; // build resources lazily if(!this.lang.defaultMode.illegalRe){ this.buildRes(); buildKeywords(this.lang); } // run the algorithm try{ this.highlight(textBlock); this.result = this.result.join(""); }catch(e){ if(e == 'Illegal'){ this.relevance = 0; this.keywordCount = 0; this.partialResult = this.result.join(""); this.result = esc(textBlock); }else{ throw e; } } }; dojo.extend(Highlighter, { buildRes: function(){ dojo.forEach(this.lang.modes, function(mode){ if(mode.begin){ mode.beginRe = this.langRe('^' + mode.begin); } if(mode.end){ mode.endRe = this.langRe('^' + mode.end); } if(mode.illegal){ mode.illegalRe = this.langRe('^(?:' + mode.illegal + ')'); } }, this); this.lang.defaultMode.illegalRe = this.langRe('^(?:' + this.lang.defaultMode.illegal + ')'); }, subMode: function(lexeme){ var classes = this.modes[this.modes.length - 1].contains; if(classes){ var modes = this.lang.modes; for(var i = 0; i < classes.length; ++i){ var className = classes[i]; for(var j = 0; j < modes.length; ++j){ var mode = modes[j]; if(mode.className == className && mode.beginRe.test(lexeme)){ return mode; } } } } return null; }, endOfMode: function(lexeme){ for(var i = this.modes.length - 1; i >= 0; --i){ var mode = this.modes[i]; if(mode.end && mode.endRe.test(lexeme)){ return this.modes.length - i; } if(!mode.endsWithParent){ break; } } return 0; }, isIllegal: function(lexeme){ var illegalRe = this.modes[this.modes.length - 1].illegalRe; return illegalRe && illegalRe.test(lexeme); }, langRe: function(value, global){ var mode = 'm' + (this.lang.case_insensitive ? 'i' : '') + (global ? 'g' : ''); return new RegExp(value, mode); }, buildTerminators: function(){ var mode = this.modes[this.modes.length - 1], terminators = {}; if(mode.contains){ dojo.forEach(this.lang.modes, function(lmode){ if(dojo.indexOf(mode.contains, lmode.className) >= 0){ terminators[lmode.begin] = 1; } }); } for(var i = this.modes.length - 1; i >= 0; --i){ var m = this.modes[i]; if(m.end){ terminators[m.end] = 1; } if(!m.endsWithParent){ break; } } if(mode.illegal){ terminators[mode.illegal] = 1; } var t = []; for(i in terminators){ t.push(i); } mode.terminatorsRe = this.langRe("(" + t.join("|") + ")"); }, eatModeChunk: function(value, index){ var mode = this.modes[this.modes.length - 1]; // create terminators lazily if(!mode.terminatorsRe){ this.buildTerminators(); } value = value.substr(index); var match = mode.terminatorsRe.exec(value); if(!match){ return { buffer: value, lexeme: "", end: true }; } return { buffer: match.index ? value.substr(0, match.index) : "", lexeme: match[0], end: false }; }, keywordMatch: function(mode, match){ var matchStr = match[0]; if(this.lang.case_insensitive){ matchStr = matchStr.toLowerCase(); } for(var className in mode.keywordGroups){ if(matchStr in mode.keywordGroups[className]){ return className; } } return ""; }, buildLexemes: function(mode){ var lexemes = {}; dojo.forEach(mode.lexems, function(lexeme){ lexemes[lexeme] = 1; }); var t = []; for(var i in lexemes){ t.push(i); } mode.lexemsRe = this.langRe("(" + t.join("|") + ")", true); }, processKeywords: function(buffer){ var mode = this.modes[this.modes.length - 1]; if(!mode.keywords || !mode.lexems){ return esc(buffer); } // create lexemes lazily if(!mode.lexemsRe){ this.buildLexemes(mode); } mode.lexemsRe.lastIndex = 0; var result = [], lastIndex = 0, match = mode.lexemsRe.exec(buffer); while(match){ result.push(esc(buffer.substr(lastIndex, match.index - lastIndex))); var keywordM = this.keywordMatch(mode, match); if(keywordM){ ++this.keywordCount; result.push('' + esc(match[0]) + ''); }else{ result.push(esc(match[0])); } lastIndex = mode.lexemsRe.lastIndex; match = mode.lexemsRe.exec(buffer); } result.push(esc(buffer.substr(lastIndex, buffer.length - lastIndex))); return result.join(""); }, processModeInfo: function(buffer, lexeme, end) { var mode = this.modes[this.modes.length - 1]; if(end){ this.result.push(this.processKeywords(mode.buffer + buffer)); return; } if(this.isIllegal(lexeme)){ throw 'Illegal'; } var newMode = this.subMode(lexeme); if(newMode){ mode.buffer += buffer; this.result.push(this.processKeywords(mode.buffer)); if(newMode.excludeBegin){ this.result.push(lexeme + ''); newMode.buffer = ''; }else{ this.result.push(''); newMode.buffer = lexeme; } this.modes.push(newMode); this.relevance += typeof newMode.relevance == "number" ? newMode.relevance : 1; return; } var endLevel = this.endOfMode(lexeme); if(endLevel){ mode.buffer += buffer; if(mode.excludeEnd){ this.result.push(this.processKeywords(mode.buffer) + '' + lexeme); }else{ this.result.push(this.processKeywords(mode.buffer + lexeme) + ''); } while(endLevel > 1){ this.result.push(''); --endLevel; this.modes.pop(); } this.modes.pop(); this.modes[this.modes.length - 1].buffer = ''; return; } }, highlight: function(value){ var index = 0; this.lang.defaultMode.buffer = ''; do{ var modeInfo = this.eatModeChunk(value, index); this.processModeInfo(modeInfo.buffer, modeInfo.lexeme, modeInfo.end); index += modeInfo.buffer.length + modeInfo.lexeme.length; }while(!modeInfo.end); if(this.modes.length > 1){ throw 'Illegal'; } } }); // more utilities function replaceText(node, className, text){ if(String(node.tagName).toLowerCase() == "code" && String(node.parentNode.tagName).toLowerCase() == "pre"){ // See these 4 lines? This is IE's notion of "node.innerHTML = text". Love this browser :-/ var container = document.createElement('div'), environment = node.parentNode.parentNode; container.innerHTML = '
' + text + '
';
environment.replaceChild(container.firstChild, node.parentNode);
}else{
node.className = className;
node.innerHTML = text;
}
}
function highlightStringLanguage(lang, str){
var highlight = new Highlighter(lang, str);
return {result:highlight.result, langName:lang, partialResult:highlight.partialResult};
}
function highlightLanguage(block, lang){
var result = highlightStringLanguage(lang, blockText(block));
replaceText(block, block.className, result.result);
}
function highlightStringAuto(str){
var result = "", langName = "", bestRelevance = 2,
textBlock = str;
for(var key in dh.languages){
if(!dh.languages[key].defaultMode){ continue; } // skip internal members
var highlight = new Highlighter(key, textBlock),
relevance = highlight.keywordCount + highlight.relevance, relevanceMax = 0;
if(!result || relevance > relevanceMax){
relevanceMax = relevance;
result = highlight.result;
langName = highlight.langName;
}
}
return {result:result, langName:langName};
}
function highlightAuto(block){
var result = highlightStringAuto(blockText(block));
if(result.result){
replaceText(block, result.langName, result.result);
}
}
// the public API
dojox.highlight.processString = function(/* String */ str, /* String? */lang){
// summary: highlight a string of text
// returns: Object containing:
// result - string of html with spans to apply formatting
// partialResult - if the formating failed: string of html
// up to the point of the failure, otherwise: undefined
// langName - the language used to do the formatting
return lang ? highlightStringLanguage(lang, str) : highlightStringAuto(str);
};
dojox.highlight.init = function(/* String|DomNode */ node){
// summary: Highlight a passed node
//
// description:
//
// Syntax highlight a passed DomNode or String ID of a DomNode
//
//
// example:
// | dojox.highlight.init("someId");
//
node = dojo.byId(node);
if(dojo.hasClass(node, "no-highlight")){ return; }
if(!verifyText(node)){ return; }
var classes = node.className.split(/\s+/),
flag = dojo.some(classes, function(className){
if(className.charAt(0) != "_" && dh.languages[className]){
highlightLanguage(node, className);
return true; // stop iterations
}
return false; // continue iterations
});
if(!flag){
highlightAuto(node);
}
};
/*=====
dojox.highlight.Code = function(props, node){
// summary: A Class object to allow for dojoType usage with the highlight engine. This is
// NOT a Widget in the conventional sense, and does not have any member functions for
// the instance. This is provided as a convenience. You likely should be calling
// `dojox.highlight.init` directly.
//
// props: Object?
// Unused. Pass 'null' or {}. Positional usage to allow `dojo.parser` to instantiate
// this class as other Widgets would be.
//
// node: String|DomNode
// A String ID or DomNode reference to use as the root node of this instance.
//
// example:
// | for(var i in obj){ ... }
//
// example:
// | var inst = new dojox.highlight.Code({}, "someId");
//
this.node = dojo.byId(node);
};
=====*/
dh.Code = function(p, n){ dh.init(n); };
})();