PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/jsspec/highlight.js

https://github.com/thepian/jsspec
JavaScript | 429 lines | 390 code | 32 blank | 7 comment | 95 complexity | 8c7160bf0d163cb773d20cd6e3c57606 MD5 | raw file
  1. /*
  2. Syntax highlighting with language autodetection.
  3. http://softwaremaniacs.org/soft/highlight/
  4. */
  5. var DEFAULT_LANGUAGES = ['python', 'ruby', 'perl', 'php', 'css', 'xml', 'html', 'django', 'javascript', 'java', 'cpp', 'sql', 'smalltalk'];
  6. var ALL_LANGUAGES = (DEFAULT_LANGUAGES.join(',') + ',' + ['1c', 'axapta', 'delphi', 'rib', 'rsl', 'vbscript'].join(',')).split(',');
  7. var LANGUAGE_GROUPS = {
  8. 'xml': 'www',
  9. 'html': 'www',
  10. 'css': 'www',
  11. 'django': 'www',
  12. 'python': 'dynamic',
  13. 'perl': 'dynamic',
  14. 'php': 'dynamic',
  15. 'ruby': 'dynamic',
  16. 'cpp': 'static',
  17. 'java': 'static',
  18. 'delphi': 'static'
  19. }
  20. var IDENT_RE = '[a-zA-Z][a-zA-Z0-9_]*';
  21. var UNDERSCORE_IDENT_RE = '[a-zA-Z_][a-zA-Z0-9_]*';
  22. var NUMBER_RE = '\\b\\d+(\\.\\d+)?';
  23. var C_NUMBER_RE = '\\b(0x[A-Za-z0-9]+|\\d+(\\.\\d+)?)';
  24. // Common modes
  25. var APOS_STRING_MODE = {
  26. className: 'string',
  27. begin: '\'', end: '\'',
  28. illegal: '\\n',
  29. contains: ['escape'],
  30. relevance: 0
  31. }
  32. var QUOTE_STRING_MODE = {
  33. className: 'string',
  34. begin: '"', end: '"',
  35. illegal: '\\n',
  36. contains: ['escape'],
  37. relevance: 0
  38. }
  39. var BACKSLASH_ESCAPE = {
  40. className: 'escape',
  41. begin: '\\\\.', end: '^',
  42. relevance: 0
  43. }
  44. var C_LINE_COMMENT_MODE = {
  45. className: 'comment',
  46. begin: '//', end: '$',
  47. relevance: 0
  48. }
  49. var C_BLOCK_COMMENT_MODE = {
  50. className: 'comment',
  51. begin: '/\\*', end: '\\*/'
  52. }
  53. var HASH_COMMENT_MODE = {
  54. className: 'comment',
  55. begin: '#', end: '$'
  56. }
  57. var C_NUMBER_MODE = {
  58. className: 'number',
  59. begin: C_NUMBER_RE, end: '^',
  60. relevance: 0
  61. }
  62. var LANGUAGES = {}
  63. var selected_languages = {};
  64. function Highlighter(language_name, value) {
  65. function subMode(lexem) {
  66. if (!modes[modes.length - 1].contains)
  67. return null;
  68. for (var i in modes[modes.length - 1].contains) {
  69. var className = modes[modes.length - 1].contains[i];
  70. for (var key in language.modes)
  71. if (language.modes[key].className == className && language.modes[key].beginRe.test(lexem))
  72. return language.modes[key];
  73. }//for
  74. return null;
  75. }//subMode
  76. function endOfMode(mode_index, lexem) {
  77. if (modes[mode_index].end && modes[mode_index].endRe.test(lexem))
  78. return 1;
  79. if (modes[mode_index].endsWithParent) {
  80. var level = endOfMode(mode_index - 1, lexem);
  81. return level ? level + 1 : 0;
  82. }//if
  83. return 0;
  84. }//endOfMode
  85. function isIllegal(lexem) {
  86. if (!modes[modes.length - 1].illegalRe)
  87. return false;
  88. return modes[modes.length - 1].illegalRe.test(lexem);
  89. }//isIllegal
  90. function eatModeChunk(value, index) {
  91. if (!modes[modes.length - 1].terminators) {
  92. var terminators = [];
  93. if (modes[modes.length - 1].contains)
  94. for (var key in language.modes) {
  95. if (contains(modes[modes.length - 1].contains, language.modes[key].className) &&
  96. !contains(terminators, language.modes[key].begin))
  97. terminators[terminators.length] = language.modes[key].begin;
  98. }//for
  99. var mode_index = modes.length - 1;
  100. do {
  101. if (modes[mode_index].end && !contains(terminators, modes[mode_index].end))
  102. terminators[terminators.length] = modes[mode_index].end;
  103. mode_index--;
  104. } while (modes[mode_index + 1].endsWithParent);
  105. if (modes[modes.length - 1].illegal)
  106. if (!contains(terminators, modes[modes.length - 1].illegal))
  107. terminators[terminators.length] = modes[modes.length - 1].illegal;
  108. var terminator_re = '(' + terminators[0];
  109. for (var i = 0; i < terminators.length; i++)
  110. terminator_re += '|' + terminators[i];
  111. terminator_re += ')';
  112. modes[modes.length - 1].terminators = langRe(language, terminator_re);
  113. }//if
  114. value = value.substr(index);
  115. var match = modes[modes.length - 1].terminators.exec(value);
  116. if (!match)
  117. return [value, '', true];
  118. if (match.index == 0)
  119. return ['', match[0], false];
  120. else
  121. return [value.substr(0, match.index), match[0], false];
  122. }//eatModeChunk
  123. function escape(value) {
  124. return value.replace(/&/gm, '&amp;').replace(/</gm, '&lt;').replace(/>/gm, '&gt;');
  125. }//escape
  126. function keywordMatch(mode, match) {
  127. var match_str = language.case_insensitive ? match[0].toLowerCase() : match[0]
  128. for (var className in mode.keywordGroups) {
  129. var value = mode.keywordGroups[className].hasOwnProperty(match_str);
  130. if (value)
  131. return [className, value];
  132. }//for
  133. return false;
  134. }//keywordMatch
  135. function processKeywords(buffer) {
  136. var mode = modes[modes.length - 1];
  137. if (!mode.keywords || !mode.lexems)
  138. return escape(buffer);
  139. if (!mode.lexemsRe) {
  140. var lexems = [];
  141. for (var key in mode.lexems)
  142. if (!contains(lexems, mode.lexems[key]))
  143. lexems[lexems.length] = mode.lexems[key];
  144. var lexems_re = '(' + lexems[0];
  145. for (var i = 1; i < lexems.length; i++)
  146. lexems_re += '|' + lexems[i];
  147. lexems_re += ')';
  148. mode.lexemsRe = langRe(language, lexems_re, true);
  149. }//if
  150. var result = '';
  151. var last_index = 0;
  152. mode.lexemsRe.lastIndex = 0;
  153. var match = mode.lexemsRe.exec(buffer);
  154. while (match) {
  155. result += escape(buffer.substr(last_index, match.index - last_index));
  156. keyword_match = keywordMatch(mode, match);
  157. if (keyword_match) {
  158. keyword_count += keyword_match[1];
  159. result += '<span class="'+ keyword_match[0] +'">' + escape(match[0]) + '</span>';
  160. } else {
  161. result += escape(match[0]);
  162. }//if
  163. last_index = mode.lexemsRe.lastIndex;
  164. match = mode.lexemsRe.exec(buffer);
  165. }//while
  166. result += escape(buffer.substr(last_index, buffer.length - last_index));
  167. return result;
  168. }//processKeywords
  169. function processModeInfo(buffer, lexem, end) {
  170. if (end) {
  171. result += processKeywords(modes[modes.length - 1].buffer + buffer);
  172. return;
  173. }//if
  174. if (isIllegal(lexem))
  175. throw 'Illegal';
  176. var new_mode = subMode(lexem);
  177. if (new_mode) {
  178. modes[modes.length - 1].buffer += buffer;
  179. result += processKeywords(modes[modes.length - 1].buffer);
  180. if (new_mode.excludeBegin) {
  181. result += lexem + '<span class="' + new_mode.className + '">';
  182. new_mode.buffer = '';
  183. } else {
  184. result += '<span class="' + new_mode.className + '">';
  185. new_mode.buffer = lexem;
  186. }//if
  187. modes[modes.length] = new_mode;
  188. relevance += modes[modes.length - 1].relevance != undefined ? modes[modes.length - 1].relevance : 1;
  189. return;
  190. }//if
  191. var end_level = endOfMode(modes.length - 1, lexem);
  192. if (end_level) {
  193. modes[modes.length - 1].buffer += buffer;
  194. if (modes[modes.length - 1].excludeEnd) {
  195. result += processKeywords(modes[modes.length - 1].buffer) + '</span>' + lexem;
  196. } else {
  197. result += processKeywords(modes[modes.length - 1].buffer + lexem) + '</span>';
  198. }
  199. while (end_level > 1) {
  200. result += '</span>';
  201. end_level--;
  202. modes.length--;
  203. }//while
  204. modes.length--;
  205. modes[modes.length - 1].buffer = '';
  206. return;
  207. }//if
  208. }//processModeInfo
  209. function highlight(value) {
  210. var index = 0;
  211. language.defaultMode.buffer = '';
  212. do {
  213. var mode_info = eatModeChunk(value, index);
  214. processModeInfo(mode_info[0], mode_info[1], mode_info[2]);
  215. index += mode_info[0].length + mode_info[1].length;
  216. } while (!mode_info[2]);
  217. if(modes.length > 1)
  218. throw 'Illegal';
  219. }//highlight
  220. this.language_name = language_name;
  221. var language = LANGUAGES[language_name];
  222. var modes = [language.defaultMode];
  223. var relevance = 0;
  224. var keyword_count = 0;
  225. var result = '';
  226. try {
  227. highlight(value);
  228. this.relevance = relevance;
  229. this.keyword_count = keyword_count;
  230. this.result = result;
  231. } catch (e) {
  232. if (e == 'Illegal') {
  233. this.relevance = 0;
  234. this.keyword_count = 0;
  235. this.result = escape(value);
  236. } else {
  237. throw e;
  238. }//if
  239. }//try
  240. }//Highlighter
  241. function contains(array, item) {
  242. if (!array)
  243. return false;
  244. for (var key in array)
  245. if (array[key] == item)
  246. return true;
  247. return false;
  248. }//contains
  249. function blockText(block) {
  250. var result = '';
  251. for (var i = 0; i < block.childNodes.length; i++)
  252. if (block.childNodes[i].nodeType == 3)
  253. result += block.childNodes[i].nodeValue;
  254. else if (block.childNodes[i].nodeName == 'BR')
  255. result += '\n';
  256. else
  257. throw 'Complex markup';
  258. return result;
  259. }//blockText
  260. function initHighlight(block) {
  261. if (block.className.search(/\bno\-highlight\b/) != -1)
  262. return;
  263. try {
  264. blockText(block);
  265. } catch (e) {
  266. if (e == 'Complex markup')
  267. return;
  268. }//try
  269. var classes = block.className.split(/\s+/);
  270. for (var i = 0; i < classes.length; i++) {
  271. if (LANGUAGES[classes[i]]) {
  272. highlightLanguage(block, classes[i]);
  273. return;
  274. }//if
  275. }//for
  276. highlightAuto(block);
  277. }//initHighlight
  278. function highlightLanguage(block, language) {
  279. var highlight = new Highlighter(language, blockText(block));
  280. // See these 4 lines? This is IE's notion of "block.innerHTML = result". Love this browser :-/
  281. var container = document.createElement('div');
  282. container.innerHTML = '<pre><code class="' + block.className + '">' + highlight.result + '</code></pre>';
  283. var environment = block.parentNode.parentNode;
  284. environment.replaceChild(container.firstChild, block.parentNode);
  285. }//highlightLanguage
  286. function highlightAuto(block) {
  287. var result = null;
  288. var language = '';
  289. var max_relevance = 2;
  290. var relevance = 0;
  291. var block_text = blockText(block);
  292. for (var key in selected_languages) {
  293. var highlight = new Highlighter(key, block_text);
  294. relevance = highlight.keyword_count + highlight.relevance;
  295. if (relevance > max_relevance) {
  296. max_relevance = relevance;
  297. result = highlight;
  298. }//if
  299. }//for
  300. if(result) {
  301. // See these 4 lines? This is IE's notion of "block.innerHTML = result". Love this browser :-/
  302. var container = document.createElement('div');
  303. container.innerHTML = '<pre><code class="' + result.language_name + '">' + result.result + '</code></pre>';
  304. var environment = block.parentNode.parentNode;
  305. environment.replaceChild(container.firstChild, block.parentNode);
  306. }//if
  307. }//highlightAuto
  308. function langRe(language, value, global) {
  309. var mode = 'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '');
  310. return new RegExp(value, mode);
  311. }//re
  312. function compileRes() {
  313. for (var i in LANGUAGES) {
  314. var language = LANGUAGES[i];
  315. for (var key in language.modes) {
  316. if (language.modes[key].begin)
  317. language.modes[key].beginRe = langRe(language, '^' + language.modes[key].begin);
  318. if (language.modes[key].end)
  319. language.modes[key].endRe = langRe(language, '^' + language.modes[key].end);
  320. if (language.modes[key].illegal)
  321. language.modes[key].illegalRe = langRe(language, '^(?:' + language.modes[key].illegal + ')');
  322. language.defaultMode.illegalRe = langRe(language, '^(?:' + language.defaultMode.illegal + ')');
  323. }//for
  324. }//for
  325. }//compileRes
  326. function compileKeywords() {
  327. function compileModeKeywords(mode) {
  328. if (!mode.keywordGroups) {
  329. for (var key in mode.keywords) {
  330. if (mode.keywords[key] instanceof Object)
  331. mode.keywordGroups = mode.keywords;
  332. else
  333. mode.keywordGroups = {'keyword': mode.keywords};
  334. break;
  335. }//for
  336. }//if
  337. }//compileModeKeywords
  338. for (var i in LANGUAGES) {
  339. var language = LANGUAGES[i];
  340. compileModeKeywords(language.defaultMode);
  341. for (var key in language.modes) {
  342. compileModeKeywords(language.modes[key]);
  343. }//for
  344. }//for
  345. }//compileKeywords
  346. function initHighlighting() {
  347. if (initHighlighting.called)
  348. return;
  349. initHighlighting.called = true;
  350. compileRes();
  351. compileKeywords();
  352. if (arguments.length) {
  353. for (var i = 0; i < arguments.length; i++) {
  354. if (LANGUAGES[arguments[i]]) {
  355. selected_languages[arguments[i]] = LANGUAGES[arguments[i]];
  356. }//if
  357. }//for
  358. } else
  359. selected_languages = LANGUAGES;
  360. var pres = document.getElementsByTagName('pre');
  361. for (var i = 0; i < pres.length; i++) {
  362. if (pres[i].firstChild && pres[i].firstChild.nodeName == 'CODE')
  363. initHighlight(pres[i].firstChild);
  364. }//for
  365. }//initHighlighting
  366. function injectScripts(languages) {
  367. var scripts = document.getElementsByTagName('SCRIPT');
  368. for (var i=0; i < scripts.length; i++) {
  369. if (scripts[i].src.match(/highlight\.js(\?.+)?$/)) {
  370. var path = scripts[i].src.replace(/highlight\.js(\?.+)?$/, '');
  371. break;
  372. }//if
  373. }//for
  374. if (languages.length == 0) {
  375. languages = DEFAULT_LANGUAGES;
  376. }//if
  377. var injected = {}
  378. for (var i=0; i < languages.length; i++) {
  379. var filename = LANGUAGE_GROUPS[languages[i]] ? LANGUAGE_GROUPS[languages[i]] : languages[i];
  380. if (!injected[filename]) {
  381. document.write('<script type="text/javascript" src="' + path + 'languages/' + filename + '.js"></script>');
  382. injected[filename] = true;
  383. }//if
  384. }//for
  385. }//injectScripts
  386. function initHighlightingOnLoad() {
  387. var original_arguments = arguments;
  388. injectScripts(arguments);
  389. var handler = function(){initHighlighting.apply(null, original_arguments)};
  390. if (window.addEventListener) {
  391. window.addEventListener('DOMContentLoaded', handler, false);
  392. window.addEventListener('load', handler, false);
  393. } else if (window.attachEvent)
  394. window.attachEvent('onload', handler);
  395. else
  396. window.onload = handler;
  397. }//initHighlightingOnLoad