_base.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. define("dojox/highlight/_base", ["dojo", "dojox/main"], function(dojo, dojox){
  2. /*=====
  3. dojox.highlight = {
  4. // summary:
  5. // Syntax highlighting with language auto-detection package
  6. //
  7. // description:
  8. //
  9. // Syntax highlighting with language auto-detection package.
  10. // Released under CLA by the Dojo Toolkit, original BSD release
  11. // available from: http://softwaremaniacs.org/soft/highlight/
  12. //
  13. //
  14. };
  15. =====*/
  16. var dh = dojo.getObject("dojox.highlight", true),
  17. C_NUMBER_RE = '\\b(0x[A-Za-z0-9]+|\\d+(\\.\\d+)?)'
  18. ;
  19. dh.languages = dh.languages || {};
  20. // constants
  21. dh.constants = {
  22. IDENT_RE: '[a-zA-Z][a-zA-Z0-9_]*',
  23. UNDERSCORE_IDENT_RE: '[a-zA-Z_][a-zA-Z0-9_]*',
  24. NUMBER_RE: '\\b\\d+(\\.\\d+)?',
  25. C_NUMBER_RE: C_NUMBER_RE,
  26. // Common modes
  27. APOS_STRING_MODE: {
  28. className: 'string',
  29. begin: '\'', end: '\'',
  30. illegal: '\\n',
  31. contains: ['escape'],
  32. relevance: 0
  33. },
  34. QUOTE_STRING_MODE: {
  35. className: 'string',
  36. begin: '"',
  37. end: '"',
  38. illegal: '\\n',
  39. contains: ['escape'],
  40. relevance: 0
  41. },
  42. BACKSLASH_ESCAPE: {
  43. className: 'escape',
  44. begin: '\\\\.', end: '^',
  45. relevance: 0
  46. },
  47. C_LINE_COMMENT_MODE: {
  48. className: 'comment',
  49. begin: '//', end: '$',
  50. relevance: 0
  51. },
  52. C_BLOCK_COMMENT_MODE: {
  53. className: 'comment',
  54. begin: '/\\*', end: '\\*/'
  55. },
  56. HASH_COMMENT_MODE: {
  57. className: 'comment',
  58. begin: '#', end: '$'
  59. },
  60. C_NUMBER_MODE: {
  61. className: 'number',
  62. begin: C_NUMBER_RE, end: '^',
  63. relevance: 0
  64. }
  65. };
  66. // utilities
  67. function esc(value){
  68. return value.replace(/&/gm, '&amp;').replace(/</gm, '&lt;').replace(/>/gm, '&gt;');
  69. }
  70. function verifyText(block){
  71. return dojo.every(block.childNodes, function(node){
  72. return node.nodeType == 3 || String(node.nodeName).toLowerCase() == 'br';
  73. });
  74. }
  75. function blockText(block){
  76. var result = [];
  77. dojo.forEach(block.childNodes, function(node){
  78. if(node.nodeType == 3){
  79. result.push(node.nodeValue);
  80. }else if(String(node.nodeName).toLowerCase() == 'br'){
  81. result.push("\n");
  82. }else{
  83. throw 'Complex markup';
  84. }
  85. });
  86. return result.join("");
  87. }
  88. function buildKeywordGroups(mode){
  89. if(!mode.keywordGroups){
  90. for(var key in mode.keywords){
  91. var kw = mode.keywords[key];
  92. if(kw instanceof Object){ // dojo.isObject?
  93. mode.keywordGroups = mode.keywords;
  94. }else{
  95. mode.keywordGroups = {keyword: mode.keywords};
  96. }
  97. break;
  98. }
  99. }
  100. }
  101. function buildKeywords(lang){
  102. if(lang.defaultMode && lang.modes){
  103. buildKeywordGroups(lang.defaultMode);
  104. dojo.forEach(lang.modes, buildKeywordGroups);
  105. }
  106. }
  107. // main object
  108. var Highlighter = function(langName, textBlock){
  109. // initialize the state
  110. this.langName = langName;
  111. this.lang = dh.languages[langName];
  112. this.modes = [this.lang.defaultMode];
  113. this.relevance = 0;
  114. this.keywordCount = 0;
  115. this.result = [];
  116. // build resources lazily
  117. if(!this.lang.defaultMode.illegalRe){
  118. this.buildRes();
  119. buildKeywords(this.lang);
  120. }
  121. // run the algorithm
  122. try{
  123. this.highlight(textBlock);
  124. this.result = this.result.join("");
  125. }catch(e){
  126. if(e == 'Illegal'){
  127. this.relevance = 0;
  128. this.keywordCount = 0;
  129. this.partialResult = this.result.join("");
  130. this.result = esc(textBlock);
  131. }else{
  132. throw e;
  133. }
  134. }
  135. };
  136. dojo.extend(Highlighter, {
  137. buildRes: function(){
  138. dojo.forEach(this.lang.modes, function(mode){
  139. if(mode.begin){
  140. mode.beginRe = this.langRe('^' + mode.begin);
  141. }
  142. if(mode.end){
  143. mode.endRe = this.langRe('^' + mode.end);
  144. }
  145. if(mode.illegal){
  146. mode.illegalRe = this.langRe('^(?:' + mode.illegal + ')');
  147. }
  148. }, this);
  149. this.lang.defaultMode.illegalRe = this.langRe('^(?:' + this.lang.defaultMode.illegal + ')');
  150. },
  151. subMode: function(lexeme){
  152. var classes = this.modes[this.modes.length - 1].contains;
  153. if(classes){
  154. var modes = this.lang.modes;
  155. for(var i = 0; i < classes.length; ++i){
  156. var className = classes[i];
  157. for(var j = 0; j < modes.length; ++j){
  158. var mode = modes[j];
  159. if(mode.className == className && mode.beginRe.test(lexeme)){ return mode; }
  160. }
  161. }
  162. }
  163. return null;
  164. },
  165. endOfMode: function(lexeme){
  166. for(var i = this.modes.length - 1; i >= 0; --i){
  167. var mode = this.modes[i];
  168. if(mode.end && mode.endRe.test(lexeme)){ return this.modes.length - i; }
  169. if(!mode.endsWithParent){ break; }
  170. }
  171. return 0;
  172. },
  173. isIllegal: function(lexeme){
  174. var illegalRe = this.modes[this.modes.length - 1].illegalRe;
  175. return illegalRe && illegalRe.test(lexeme);
  176. },
  177. langRe: function(value, global){
  178. var mode = 'm' + (this.lang.case_insensitive ? 'i' : '') + (global ? 'g' : '');
  179. return new RegExp(value, mode);
  180. },
  181. buildTerminators: function(){
  182. var mode = this.modes[this.modes.length - 1],
  183. terminators = {};
  184. if(mode.contains){
  185. dojo.forEach(this.lang.modes, function(lmode){
  186. if(dojo.indexOf(mode.contains, lmode.className) >= 0){
  187. terminators[lmode.begin] = 1;
  188. }
  189. });
  190. }
  191. for(var i = this.modes.length - 1; i >= 0; --i){
  192. var m = this.modes[i];
  193. if(m.end){ terminators[m.end] = 1; }
  194. if(!m.endsWithParent){ break; }
  195. }
  196. if(mode.illegal){ terminators[mode.illegal] = 1; }
  197. var t = [];
  198. for(i in terminators){ t.push(i); }
  199. mode.terminatorsRe = this.langRe("(" + t.join("|") + ")");
  200. },
  201. eatModeChunk: function(value, index){
  202. var mode = this.modes[this.modes.length - 1];
  203. // create terminators lazily
  204. if(!mode.terminatorsRe){
  205. this.buildTerminators();
  206. }
  207. value = value.substr(index);
  208. var match = mode.terminatorsRe.exec(value);
  209. if(!match){
  210. return {
  211. buffer: value,
  212. lexeme: "",
  213. end: true
  214. };
  215. }
  216. return {
  217. buffer: match.index ? value.substr(0, match.index) : "",
  218. lexeme: match[0],
  219. end: false
  220. };
  221. },
  222. keywordMatch: function(mode, match){
  223. var matchStr = match[0];
  224. if(this.lang.case_insensitive){ matchStr = matchStr.toLowerCase(); }
  225. for(var className in mode.keywordGroups){
  226. if(matchStr in mode.keywordGroups[className]){ return className; }
  227. }
  228. return "";
  229. },
  230. buildLexemes: function(mode){
  231. var lexemes = {};
  232. dojo.forEach(mode.lexems, function(lexeme){
  233. lexemes[lexeme] = 1;
  234. });
  235. var t = [];
  236. for(var i in lexemes){ t.push(i); }
  237. mode.lexemsRe = this.langRe("(" + t.join("|") + ")", true);
  238. },
  239. processKeywords: function(buffer){
  240. var mode = this.modes[this.modes.length - 1];
  241. if(!mode.keywords || !mode.lexems){
  242. return esc(buffer);
  243. }
  244. // create lexemes lazily
  245. if(!mode.lexemsRe){
  246. this.buildLexemes(mode);
  247. }
  248. mode.lexemsRe.lastIndex = 0;
  249. var result = [], lastIndex = 0,
  250. match = mode.lexemsRe.exec(buffer);
  251. while(match){
  252. result.push(esc(buffer.substr(lastIndex, match.index - lastIndex)));
  253. var keywordM = this.keywordMatch(mode, match);
  254. if(keywordM){
  255. ++this.keywordCount;
  256. result.push('<span class="'+ keywordM +'">' + esc(match[0]) + '</span>');
  257. }else{
  258. result.push(esc(match[0]));
  259. }
  260. lastIndex = mode.lexemsRe.lastIndex;
  261. match = mode.lexemsRe.exec(buffer);
  262. }
  263. result.push(esc(buffer.substr(lastIndex, buffer.length - lastIndex)));
  264. return result.join("");
  265. },
  266. processModeInfo: function(buffer, lexeme, end) {
  267. var mode = this.modes[this.modes.length - 1];
  268. if(end){
  269. this.result.push(this.processKeywords(mode.buffer + buffer));
  270. return;
  271. }
  272. if(this.isIllegal(lexeme)){ throw 'Illegal'; }
  273. var newMode = this.subMode(lexeme);
  274. if(newMode){
  275. mode.buffer += buffer;
  276. this.result.push(this.processKeywords(mode.buffer));
  277. if(newMode.excludeBegin){
  278. this.result.push(lexeme + '<span class="' + newMode.className + '">');
  279. newMode.buffer = '';
  280. }else{
  281. this.result.push('<span class="' + newMode.className + '">');
  282. newMode.buffer = lexeme;
  283. }
  284. this.modes.push(newMode);
  285. this.relevance += typeof newMode.relevance == "number" ? newMode.relevance : 1;
  286. return;
  287. }
  288. var endLevel = this.endOfMode(lexeme);
  289. if(endLevel){
  290. mode.buffer += buffer;
  291. if(mode.excludeEnd){
  292. this.result.push(this.processKeywords(mode.buffer) + '</span>' + lexeme);
  293. }else{
  294. this.result.push(this.processKeywords(mode.buffer + lexeme) + '</span>');
  295. }
  296. while(endLevel > 1){
  297. this.result.push('</span>');
  298. --endLevel;
  299. this.modes.pop();
  300. }
  301. this.modes.pop();
  302. this.modes[this.modes.length - 1].buffer = '';
  303. return;
  304. }
  305. },
  306. highlight: function(value){
  307. var index = 0;
  308. this.lang.defaultMode.buffer = '';
  309. do{
  310. var modeInfo = this.eatModeChunk(value, index);
  311. this.processModeInfo(modeInfo.buffer, modeInfo.lexeme, modeInfo.end);
  312. index += modeInfo.buffer.length + modeInfo.lexeme.length;
  313. }while(!modeInfo.end);
  314. if(this.modes.length > 1){
  315. throw 'Illegal';
  316. }
  317. }
  318. });
  319. // more utilities
  320. function replaceText(node, className, text){
  321. if(String(node.tagName).toLowerCase() == "code" && String(node.parentNode.tagName).toLowerCase() == "pre"){
  322. // See these 4 lines? This is IE's notion of "node.innerHTML = text". Love this browser :-/
  323. var container = document.createElement('div'),
  324. environment = node.parentNode.parentNode;
  325. container.innerHTML = '<pre><code class="' + className + '">' + text + '</code></pre>';
  326. environment.replaceChild(container.firstChild, node.parentNode);
  327. }else{
  328. node.className = className;
  329. node.innerHTML = text;
  330. }
  331. }
  332. function highlightStringLanguage(lang, str){
  333. var highlight = new Highlighter(lang, str);
  334. return {result:highlight.result, langName:lang, partialResult:highlight.partialResult};
  335. }
  336. function highlightLanguage(block, lang){
  337. var result = highlightStringLanguage(lang, blockText(block));
  338. replaceText(block, block.className, result.result);
  339. }
  340. function highlightStringAuto(str){
  341. var result = "", langName = "", bestRelevance = 2,
  342. textBlock = str;
  343. for(var key in dh.languages){
  344. if(!dh.languages[key].defaultMode){ continue; } // skip internal members
  345. var highlight = new Highlighter(key, textBlock),
  346. relevance = highlight.keywordCount + highlight.relevance, relevanceMax = 0;
  347. if(!result || relevance > relevanceMax){
  348. relevanceMax = relevance;
  349. result = highlight.result;
  350. langName = highlight.langName;
  351. }
  352. }
  353. return {result:result, langName:langName};
  354. }
  355. function highlightAuto(block){
  356. var result = highlightStringAuto(blockText(block));
  357. if(result.result){
  358. replaceText(block, result.langName, result.result);
  359. }
  360. }
  361. // the public API
  362. dojox.highlight.processString = function(/* String */ str, /* String? */lang){
  363. // summary: highlight a string of text
  364. // returns: Object containing:
  365. // result - string of html with spans to apply formatting
  366. // partialResult - if the formating failed: string of html
  367. // up to the point of the failure, otherwise: undefined
  368. // langName - the language used to do the formatting
  369. return lang ? highlightStringLanguage(lang, str) : highlightStringAuto(str);
  370. };
  371. dojox.highlight.init = function(/* String|DomNode */ node){
  372. // summary: Highlight a passed node
  373. //
  374. // description:
  375. //
  376. // Syntax highlight a passed DomNode or String ID of a DomNode
  377. //
  378. //
  379. // example:
  380. // | dojox.highlight.init("someId");
  381. //
  382. node = dojo.byId(node);
  383. if(dojo.hasClass(node, "no-highlight")){ return; }
  384. if(!verifyText(node)){ return; }
  385. var classes = node.className.split(/\s+/),
  386. flag = dojo.some(classes, function(className){
  387. if(className.charAt(0) != "_" && dh.languages[className]){
  388. highlightLanguage(node, className);
  389. return true; // stop iterations
  390. }
  391. return false; // continue iterations
  392. });
  393. if(!flag){
  394. highlightAuto(node);
  395. }
  396. };
  397. /*=====
  398. dojox.highlight.Code = function(props, node){
  399. // summary: A Class object to allow for dojoType usage with the highlight engine. This is
  400. // NOT a Widget in the conventional sense, and does not have any member functions for
  401. // the instance. This is provided as a convenience. You likely should be calling
  402. // `dojox.highlight.init` directly.
  403. //
  404. // props: Object?
  405. // Unused. Pass 'null' or {}. Positional usage to allow `dojo.parser` to instantiate
  406. // this class as other Widgets would be.
  407. //
  408. // node: String|DomNode
  409. // A String ID or DomNode reference to use as the root node of this instance.
  410. //
  411. // example:
  412. // | <pre><code dojoType="dojox.highlight.Code">for(var i in obj){ ... }</code></pre>
  413. //
  414. // example:
  415. // | var inst = new dojox.highlight.Code({}, "someId");
  416. //
  417. this.node = dojo.byId(node);
  418. };
  419. =====*/
  420. dh.Code = function(props, node){ dh.init(node); };
  421. return dh;
  422. });