shCore.js 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714
  1. //
  2. // Begin anonymous function. This is used to contain local scope variables without polutting global scope.
  3. //
  4. if (typeof(SyntaxHighlighter) == 'undefined') var SyntaxHighlighter = function() {
  5. // CommonJS
  6. if (typeof(require) != 'undefined' && typeof(XRegExp) == 'undefined')
  7. {
  8. XRegExp = require('XRegExp').XRegExp;
  9. }
  10. // Shortcut object which will be assigned to the SyntaxHighlighter variable.
  11. // This is a shorthand for local reference in order to avoid long namespace
  12. // references to SyntaxHighlighter.whatever...
  13. var sh = {
  14. defaults : {
  15. /** Additional CSS class names to be added to highlighter elements. */
  16. 'class-name' : '',
  17. /** First line number. */
  18. 'first-line' : 1,
  19. /**
  20. * Pads line numbers. Possible values are:
  21. *
  22. * false - don't pad line numbers.
  23. * true - automaticaly pad numbers with minimum required number of leading zeroes.
  24. * [int] - length up to which pad line numbers.
  25. */
  26. 'pad-line-numbers' : false,
  27. /** Lines to highlight. */
  28. 'highlight' : null,
  29. /** Title to be displayed above the code block. */
  30. 'title' : null,
  31. /** Enables or disables smart tabs. */
  32. 'smart-tabs' : true,
  33. /** Gets or sets tab size. */
  34. 'tab-size' : 4,
  35. /** Enables or disables gutter. */
  36. 'gutter' : true,
  37. /** Enables or disables toolbar. */
  38. 'toolbar' : true,
  39. /** Enables quick code copy and paste from double click. */
  40. 'quick-code' : true,
  41. /** Forces code view to be collapsed. */
  42. 'collapse' : false,
  43. /** Enables or disables automatic links. */
  44. 'auto-links' : true,
  45. /** Gets or sets light mode. Equavalent to turning off gutter and toolbar. */
  46. 'light' : false,
  47. 'unindent' : true,
  48. 'html-script' : false
  49. },
  50. config : {
  51. space : ' ',
  52. /** Enables use of <SCRIPT type="syntaxhighlighter" /> tags. */
  53. useScriptTags : true,
  54. /** Blogger mode flag. */
  55. bloggerMode : false,
  56. stripBrs : false,
  57. /** Name of the tag that SyntaxHighlighter will automatically look for. */
  58. tagName : 'pre',
  59. strings : {
  60. expandSource : 'expand source',
  61. help : '?',
  62. alert: 'SyntaxHighlighter\n\n',
  63. noBrush : 'Can\'t find brush for: ',
  64. brushNotHtmlScript : 'Brush wasn\'t configured for html-script option: ',
  65. // this is populated by the build script
  66. aboutDialog : '@ABOUT@'
  67. }
  68. },
  69. /** Internal 'global' variables. */
  70. vars : {
  71. discoveredBrushes : null,
  72. highlighters : {}
  73. },
  74. /** This object is populated by user included external brush files. */
  75. brushes : {},
  76. /** Common regular expressions. */
  77. regexLib : {
  78. multiLineCComments : /\/\*[\s\S]*?\*\//gm,
  79. singleLineCComments : /\/\/.*$/gm,
  80. singleLinePerlComments : /#.*$/gm,
  81. doubleQuotedString : /"([^\\"\n]|\\.)*"/g,
  82. singleQuotedString : /'([^\\'\n]|\\.)*'/g,
  83. multiLineDoubleQuotedString : new XRegExp('"([^\\\\"]|\\\\.)*"', 'gs'),
  84. multiLineSingleQuotedString : new XRegExp("'([^\\\\']|\\\\.)*'", 'gs'),
  85. xmlComments : /(&lt;|<)!--[\s\S]*?--(&gt;|>)/gm,
  86. url : /\w+:\/\/[\w-.\/?%&=:@;#]*/g,
  87. /** <?= ?> tags. */
  88. phpScriptTags : { left: /(&lt;|<)\?(?:=|php)?/g, right: /\?(&gt;|>)/g, 'eof' : true },
  89. /** <%= %> tags. */
  90. aspScriptTags : { left: /(&lt;|<)%=?/g, right: /%(&gt;|>)/g },
  91. /** <script> tags. */
  92. scriptScriptTags : { left: /(&lt;|<)\s*script.*?(&gt;|>)/gi, right: /(&lt;|<)\/\s*script\s*(&gt;|>)/gi }
  93. },
  94. toolbar: {
  95. /**
  96. * Generates HTML markup for the toolbar.
  97. * @param {Highlighter} highlighter Highlighter instance.
  98. * @return {String} Returns HTML markup.
  99. */
  100. getHtml: function(highlighter)
  101. {
  102. var html = '<div class="toolbar">',
  103. items = sh.toolbar.items,
  104. list = items.list
  105. ;
  106. function defaultGetHtml(highlighter, name)
  107. {
  108. return sh.toolbar.getButtonHtml(highlighter, name, sh.config.strings[name]);
  109. };
  110. for (var i = 0; i < list.length; i++)
  111. html += (items[list[i]].getHtml || defaultGetHtml)(highlighter, list[i]);
  112. html += '</div>';
  113. return html;
  114. },
  115. /**
  116. * Generates HTML markup for a regular button in the toolbar.
  117. * @param {Highlighter} highlighter Highlighter instance.
  118. * @param {String} commandName Command name that would be executed.
  119. * @param {String} label Label text to display.
  120. * @return {String} Returns HTML markup.
  121. */
  122. getButtonHtml: function(highlighter, commandName, label)
  123. {
  124. return '<span><a href="#" class="toolbar_item'
  125. + ' command_' + commandName
  126. + ' ' + commandName
  127. + '">' + label + '</a></span>'
  128. ;
  129. },
  130. /**
  131. * Event handler for a toolbar anchor.
  132. */
  133. handler: function(e)
  134. {
  135. var target = e.target,
  136. className = target.className || ''
  137. ;
  138. function getValue(name)
  139. {
  140. var r = new RegExp(name + '_(\\w+)'),
  141. match = r.exec(className)
  142. ;
  143. return match ? match[1] : null;
  144. };
  145. var highlighter = getHighlighterById(findParentElement(target, '.syntaxhighlighter').id),
  146. commandName = getValue('command')
  147. ;
  148. // execute the toolbar command
  149. if (highlighter && commandName)
  150. sh.toolbar.items[commandName].execute(highlighter);
  151. // disable default A click behaviour
  152. e.preventDefault();
  153. },
  154. /** Collection of toolbar items. */
  155. items : {
  156. // Ordered lis of items in the toolbar. Can't expect `for (var n in items)` to be consistent.
  157. list: ['expandSource', 'help'],
  158. expandSource: {
  159. getHtml: function(highlighter)
  160. {
  161. if (highlighter.getParam('collapse') != true)
  162. return '';
  163. var title = highlighter.getParam('title');
  164. return sh.toolbar.getButtonHtml(highlighter, 'expandSource', title ? title : sh.config.strings.expandSource);
  165. },
  166. execute: function(highlighter)
  167. {
  168. var div = getHighlighterDivById(highlighter.id);
  169. removeClass(div, 'collapsed');
  170. }
  171. },
  172. /** Command to display the about dialog window. */
  173. help: {
  174. execute: function(highlighter)
  175. {
  176. var wnd = popup('', '_blank', 500, 250, 'scrollbars=0'),
  177. doc = wnd.document
  178. ;
  179. doc.write(sh.config.strings.aboutDialog);
  180. doc.close();
  181. wnd.focus();
  182. }
  183. }
  184. }
  185. },
  186. /**
  187. * Finds all elements on the page which should be processes by SyntaxHighlighter.
  188. *
  189. * @param {Object} globalParams Optional parameters which override element's
  190. * parameters. Only used if element is specified.
  191. *
  192. * @param {Object} element Optional element to highlight. If none is
  193. * provided, all elements in the current document
  194. * are returned which qualify.
  195. *
  196. * @return {Array} Returns list of <code>{ target: DOMElement, params: Object }</code> objects.
  197. */
  198. findElements: function(globalParams, element)
  199. {
  200. var elements = element ? [element] : toArray(document.getElementsByTagName(sh.config.tagName)),
  201. conf = sh.config,
  202. result = []
  203. ;
  204. // support for <SCRIPT TYPE="syntaxhighlighter" /> feature
  205. if (conf.useScriptTags)
  206. elements = elements.concat(getSyntaxHighlighterScriptTags());
  207. if (elements.length === 0)
  208. return result;
  209. for (var i = 0; i < elements.length; i++)
  210. {
  211. var item = {
  212. target: elements[i],
  213. // local params take precedence over globals
  214. params: merge(globalParams, parseParams(elements[i].className))
  215. };
  216. if (item.params['brush'] == null)
  217. continue;
  218. result.push(item);
  219. }
  220. return result;
  221. },
  222. /**
  223. * Shorthand to highlight all elements on the page that are marked as
  224. * SyntaxHighlighter source code.
  225. *
  226. * @param {Object} globalParams Optional parameters which override element's
  227. * parameters. Only used if element is specified.
  228. *
  229. * @param {Object} element Optional element to highlight. If none is
  230. * provided, all elements in the current document
  231. * are highlighted.
  232. */
  233. highlight: function(globalParams, element)
  234. {
  235. var elements = this.findElements(globalParams, element),
  236. propertyName = 'innerHTML',
  237. highlighter = null,
  238. conf = sh.config
  239. ;
  240. if (elements.length === 0)
  241. return;
  242. for (var i = 0; i < elements.length; i++)
  243. {
  244. var element = elements[i],
  245. target = element.target,
  246. params = element.params,
  247. brushName = params.brush,
  248. code
  249. ;
  250. if (brushName == null)
  251. continue;
  252. // Instantiate a brush
  253. if (params['html-script'] == 'true' || sh.defaults['html-script'] == true)
  254. {
  255. highlighter = new sh.HtmlScript(brushName);
  256. brushName = 'htmlscript';
  257. }
  258. else
  259. {
  260. var brush = findBrush(brushName);
  261. if (brush)
  262. highlighter = new brush();
  263. else
  264. continue;
  265. }
  266. code = target[propertyName];
  267. // remove CDATA from <SCRIPT/> tags if it's present
  268. if (conf.useScriptTags)
  269. code = stripCData(code);
  270. // Inject title if the attribute is present
  271. if ((target.title || '') != '')
  272. params.title = target.title;
  273. params['brush'] = brushName;
  274. highlighter.init(params);
  275. element = highlighter.getDiv(code);
  276. // carry over ID
  277. if ((target.id || '') != '')
  278. element.id = target.id;
  279. target.parentNode.replaceChild(element, target);
  280. }
  281. },
  282. /**
  283. * Main entry point for the SyntaxHighlighter.
  284. * @param {Object} params Optional params to apply to all highlighted elements.
  285. */
  286. all: function(params)
  287. {
  288. attachEvent(
  289. window,
  290. 'load',
  291. function() { sh.highlight(params); }
  292. );
  293. }
  294. }; // end of sh
  295. /**
  296. * Checks if target DOM elements has specified CSS class.
  297. * @param {DOMElement} target Target DOM element to check.
  298. * @param {String} className Name of the CSS class to check for.
  299. * @return {Boolean} Returns true if class name is present, false otherwise.
  300. */
  301. function hasClass(target, className)
  302. {
  303. return target.className.indexOf(className) != -1;
  304. };
  305. /**
  306. * Adds CSS class name to the target DOM element.
  307. * @param {DOMElement} target Target DOM element.
  308. * @param {String} className New CSS class to add.
  309. */
  310. function addClass(target, className)
  311. {
  312. if (!hasClass(target, className))
  313. target.className += ' ' + className;
  314. };
  315. /**
  316. * Removes CSS class name from the target DOM element.
  317. * @param {DOMElement} target Target DOM element.
  318. * @param {String} className CSS class to remove.
  319. */
  320. function removeClass(target, className)
  321. {
  322. target.className = target.className.replace(className, '');
  323. };
  324. /**
  325. * Converts the source to array object. Mostly used for function arguments and
  326. * lists returned by getElementsByTagName() which aren't Array objects.
  327. * @param {List} source Source list.
  328. * @return {Array} Returns array.
  329. */
  330. function toArray(source)
  331. {
  332. var result = [];
  333. for (var i = 0; i < source.length; i++)
  334. result.push(source[i]);
  335. return result;
  336. };
  337. /**
  338. * Splits block of text into lines.
  339. * @param {String} block Block of text.
  340. * @return {Array} Returns array of lines.
  341. */
  342. function splitLines(block)
  343. {
  344. return block.split(/\r?\n/);
  345. }
  346. /**
  347. * Generates HTML ID for the highlighter.
  348. * @param {String} highlighterId Highlighter ID.
  349. * @return {String} Returns HTML ID.
  350. */
  351. function getHighlighterId(id)
  352. {
  353. var prefix = 'highlighter_';
  354. return id.indexOf(prefix) == 0 ? id : prefix + id;
  355. };
  356. /**
  357. * Finds Highlighter instance by ID.
  358. * @param {String} highlighterId Highlighter ID.
  359. * @return {Highlighter} Returns instance of the highlighter.
  360. */
  361. function getHighlighterById(id)
  362. {
  363. return sh.vars.highlighters[getHighlighterId(id)];
  364. };
  365. /**
  366. * Finds highlighter's DIV container.
  367. * @param {String} highlighterId Highlighter ID.
  368. * @return {Element} Returns highlighter's DIV element.
  369. */
  370. function getHighlighterDivById(id)
  371. {
  372. return document.getElementById(getHighlighterId(id));
  373. };
  374. /**
  375. * Stores highlighter so that getHighlighterById() can do its thing. Each
  376. * highlighter must call this method to preserve itself.
  377. * @param {Highilghter} highlighter Highlighter instance.
  378. */
  379. function storeHighlighter(highlighter)
  380. {
  381. sh.vars.highlighters[getHighlighterId(highlighter.id)] = highlighter;
  382. };
  383. /**
  384. * Looks for a child or parent node which has specified classname.
  385. * Equivalent to jQuery's $(container).find(".className")
  386. * @param {Element} target Target element.
  387. * @param {String} search Class name or node name to look for.
  388. * @param {Boolean} reverse If set to true, will go up the node tree instead of down.
  389. * @return {Element} Returns found child or parent element on null.
  390. */
  391. function findElement(target, search, reverse /* optional */)
  392. {
  393. if (target == null)
  394. return null;
  395. var nodes = reverse != true ? target.childNodes : [ target.parentNode ],
  396. propertyToFind = { '#' : 'id', '.' : 'className' }[search.substr(0, 1)] || 'nodeName',
  397. expectedValue,
  398. found
  399. ;
  400. expectedValue = propertyToFind != 'nodeName'
  401. ? search.substr(1)
  402. : search.toUpperCase()
  403. ;
  404. // main return of the found node
  405. if ((target[propertyToFind] || '').indexOf(expectedValue) != -1)
  406. return target;
  407. for (var i = 0; nodes && i < nodes.length && found == null; i++)
  408. found = findElement(nodes[i], search, reverse);
  409. return found;
  410. };
  411. /**
  412. * Looks for a parent node which has specified classname.
  413. * This is an alias to <code>findElement(container, className, true)</code>.
  414. * @param {Element} target Target element.
  415. * @param {String} className Class name to look for.
  416. * @return {Element} Returns found parent element on null.
  417. */
  418. function findParentElement(target, className)
  419. {
  420. return findElement(target, className, true);
  421. };
  422. /**
  423. * Finds an index of element in the array.
  424. * @ignore
  425. * @param {Object} searchElement
  426. * @param {Number} fromIndex
  427. * @return {Number} Returns index of element if found; -1 otherwise.
  428. */
  429. function indexOf(array, searchElement, fromIndex)
  430. {
  431. fromIndex = Math.max(fromIndex || 0, 0);
  432. for (var i = fromIndex; i < array.length; i++)
  433. if(array[i] == searchElement)
  434. return i;
  435. return -1;
  436. };
  437. /**
  438. * Generates a unique element ID.
  439. */
  440. function guid(prefix)
  441. {
  442. return (prefix || '') + Math.round(Math.random() * 1000000).toString();
  443. };
  444. /**
  445. * Merges two objects. Values from obj2 override values in obj1.
  446. * Function is NOT recursive and works only for one dimensional objects.
  447. * @param {Object} obj1 First object.
  448. * @param {Object} obj2 Second object.
  449. * @return {Object} Returns combination of both objects.
  450. */
  451. function merge(obj1, obj2)
  452. {
  453. var result = {}, name;
  454. for (name in obj1)
  455. result[name] = obj1[name];
  456. for (name in obj2)
  457. result[name] = obj2[name];
  458. return result;
  459. };
  460. /**
  461. * Attempts to convert string to boolean.
  462. * @param {String} value Input string.
  463. * @return {Boolean} Returns true if input was "true", false if input was "false" and value otherwise.
  464. */
  465. function toBoolean(value)
  466. {
  467. var result = { "true" : true, "false" : false }[value];
  468. return result == null ? value : result;
  469. };
  470. /**
  471. * Opens up a centered popup window.
  472. * @param {String} url URL to open in the window.
  473. * @param {String} name Popup name.
  474. * @param {int} width Popup width.
  475. * @param {int} height Popup height.
  476. * @param {String} options window.open() options.
  477. * @return {Window} Returns window instance.
  478. */
  479. function popup(url, name, width, height, options)
  480. {
  481. var x = (screen.width - width) / 2,
  482. y = (screen.height - height) / 2
  483. ;
  484. options += ', left=' + x +
  485. ', top=' + y +
  486. ', width=' + width +
  487. ', height=' + height
  488. ;
  489. options = options.replace(/^,/, '');
  490. var win = window.open(url, name, options);
  491. win.focus();
  492. return win;
  493. };
  494. /**
  495. * Adds event handler to the target object.
  496. * @param {Object} obj Target object.
  497. * @param {String} type Name of the event.
  498. * @param {Function} func Handling function.
  499. */
  500. function attachEvent(obj, type, func, scope)
  501. {
  502. function handler(e)
  503. {
  504. e = e || window.event;
  505. if (!e.target)
  506. {
  507. e.target = e.srcElement;
  508. e.preventDefault = function()
  509. {
  510. this.returnValue = false;
  511. };
  512. }
  513. func.call(scope || window, e);
  514. };
  515. if (obj.attachEvent)
  516. {
  517. obj.attachEvent('on' + type, handler);
  518. }
  519. else
  520. {
  521. obj.addEventListener(type, handler, false);
  522. }
  523. };
  524. /**
  525. * Displays an alert.
  526. * @param {String} str String to display.
  527. */
  528. function alert(str)
  529. {
  530. window.alert(sh.config.strings.alert + str);
  531. };
  532. /**
  533. * Finds a brush by its alias.
  534. *
  535. * @param {String} alias Brush alias.
  536. * @param {Boolean} showAlert Suppresses the alert if false.
  537. * @return {Brush} Returns bursh constructor if found, null otherwise.
  538. */
  539. function findBrush(alias, showAlert)
  540. {
  541. var brushes = sh.vars.discoveredBrushes,
  542. result = null
  543. ;
  544. if (brushes == null)
  545. {
  546. brushes = {};
  547. // Find all brushes
  548. for (var brush in sh.brushes)
  549. {
  550. var info = sh.brushes[brush],
  551. aliases = info.aliases
  552. ;
  553. if (aliases == null)
  554. continue;
  555. // keep the brush name
  556. info.brushName = brush.toLowerCase();
  557. for (var i = 0; i < aliases.length; i++)
  558. brushes[aliases[i]] = brush;
  559. }
  560. sh.vars.discoveredBrushes = brushes;
  561. }
  562. result = sh.brushes[brushes[alias]];
  563. if (result == null && showAlert)
  564. alert(sh.config.strings.noBrush + alias);
  565. return result;
  566. };
  567. /**
  568. * Executes a callback on each line and replaces each line with result from the callback.
  569. * @param {Object} str Input string.
  570. * @param {Object} callback Callback function taking one string argument and returning a string.
  571. */
  572. function eachLine(str, callback)
  573. {
  574. var lines = splitLines(str);
  575. for (var i = 0; i < lines.length; i++)
  576. lines[i] = callback(lines[i], i);
  577. // include \r to enable copy-paste on windows (ie8) without getting everything on one line
  578. return lines.join('\r\n');
  579. };
  580. /**
  581. * This is a special trim which only removes first and last empty lines
  582. * and doesn't affect valid leading space on the first line.
  583. *
  584. * @param {String} str Input string
  585. * @return {String} Returns string without empty first and last lines.
  586. */
  587. function trimFirstAndLastLines(str)
  588. {
  589. return str.replace(/^[ ]*[\n]+|[\n]*[ ]*$/g, '');
  590. };
  591. /**
  592. * Parses key/value pairs into hash object.
  593. *
  594. * Understands the following formats:
  595. * - name: word;
  596. * - name: [word, word];
  597. * - name: "string";
  598. * - name: 'string';
  599. *
  600. * For example:
  601. * name1: value; name2: [value, value]; name3: 'value'
  602. *
  603. * @param {String} str Input string.
  604. * @return {Object} Returns deserialized object.
  605. */
  606. function parseParams(str)
  607. {
  608. var match,
  609. result = {},
  610. arrayRegex = new XRegExp("^\\[(?<values>(.*?))\\]$"),
  611. regex = new XRegExp(
  612. "(?<name>[\\w-]+)" +
  613. "\\s*:\\s*" +
  614. "(?<value>" +
  615. "[\\w-%#]+|" + // word
  616. "\\[.*?\\]|" + // [] array
  617. '".*?"|' + // "" string
  618. "'.*?'" + // '' string
  619. ")\\s*;?",
  620. "g"
  621. )
  622. ;
  623. while ((match = regex.exec(str)) != null)
  624. {
  625. var value = match.value
  626. .replace(/^['"]|['"]$/g, '') // strip quotes from end of strings
  627. ;
  628. // try to parse array value
  629. if (value != null && arrayRegex.test(value))
  630. {
  631. var m = arrayRegex.exec(value);
  632. value = m.values.length > 0 ? m.values.split(/\s*,\s*/) : [];
  633. }
  634. result[match.name] = value;
  635. }
  636. return result;
  637. };
  638. /**
  639. * Wraps each line of the string into <code/> tag with given style applied to it.
  640. *
  641. * @param {String} str Input string.
  642. * @param {String} css Style name to apply to the string.
  643. * @return {String} Returns input string with each line surrounded by <span/> tag.
  644. */
  645. function wrapLinesWithCode(str, css)
  646. {
  647. if (str == null || str.length == 0 || str == '\n')
  648. return str;
  649. str = str.replace(/</g, '&lt;');
  650. // Replace two or more sequential spaces with &nbsp; leaving last space untouched.
  651. str = str.replace(/ {2,}/g, function(m)
  652. {
  653. var spaces = '';
  654. for (var i = 0; i < m.length - 1; i++)
  655. spaces += sh.config.space;
  656. return spaces + ' ';
  657. });
  658. // Split each line and apply <span class="...">...</span> to them so that
  659. // leading spaces aren't included.
  660. if (css != null)
  661. str = eachLine(str, function(line)
  662. {
  663. if (line.length == 0)
  664. return '';
  665. var spaces = '';
  666. line = line.replace(/^(&nbsp;| )+/, function(s)
  667. {
  668. spaces = s;
  669. return '';
  670. });
  671. if (line.length == 0)
  672. return spaces;
  673. return spaces + '<code class="' + css + '">' + line + '</code>';
  674. });
  675. return str;
  676. };
  677. /**
  678. * Pads number with zeros until it's length is the same as given length.
  679. *
  680. * @param {Number} number Number to pad.
  681. * @param {Number} length Max string length with.
  682. * @return {String} Returns a string padded with proper amount of '0'.
  683. */
  684. function padNumber(number, length)
  685. {
  686. var result = number.toString();
  687. while (result.length < length)
  688. result = '0' + result;
  689. return result;
  690. };
  691. /**
  692. * Replaces tabs with spaces.
  693. *
  694. * @param {String} code Source code.
  695. * @param {Number} tabSize Size of the tab.
  696. * @return {String} Returns code with all tabs replaces by spaces.
  697. */
  698. function processTabs(code, tabSize)
  699. {
  700. var tab = '';
  701. for (var i = 0; i < tabSize; i++)
  702. tab += ' ';
  703. return code.replace(/\t/g, tab);
  704. };
  705. /**
  706. * Replaces tabs with smart spaces.
  707. *
  708. * @param {String} code Code to fix the tabs in.
  709. * @param {Number} tabSize Number of spaces in a column.
  710. * @return {String} Returns code with all tabs replaces with roper amount of spaces.
  711. */
  712. function processSmartTabs(code, tabSize)
  713. {
  714. var lines = splitLines(code),
  715. tab = '\t',
  716. spaces = ''
  717. ;
  718. // Create a string with 1000 spaces to copy spaces from...
  719. // It's assumed that there would be no indentation longer than that.
  720. for (var i = 0; i < 50; i++)
  721. spaces += ' '; // 20 spaces * 50
  722. // This function inserts specified amount of spaces in the string
  723. // where a tab is while removing that given tab.
  724. function insertSpaces(line, pos, count)
  725. {
  726. return line.substr(0, pos)
  727. + spaces.substr(0, count)
  728. + line.substr(pos + 1, line.length) // pos + 1 will get rid of the tab
  729. ;
  730. };
  731. // Go through all the lines and do the 'smart tabs' magic.
  732. code = eachLine(code, function(line)
  733. {
  734. if (line.indexOf(tab) == -1)
  735. return line;
  736. var pos = 0;
  737. while ((pos = line.indexOf(tab)) != -1)
  738. {
  739. // This is pretty much all there is to the 'smart tabs' logic.
  740. // Based on the position within the line and size of a tab,
  741. // calculate the amount of spaces we need to insert.
  742. var spaces = tabSize - pos % tabSize;
  743. line = insertSpaces(line, pos, spaces);
  744. }
  745. return line;
  746. });
  747. return code;
  748. };
  749. /**
  750. * Performs various string fixes based on configuration.
  751. */
  752. function fixInputString(str)
  753. {
  754. var br = /<br\s*\/?>|&lt;br\s*\/?&gt;/gi;
  755. if (sh.config.bloggerMode == true)
  756. str = str.replace(br, '\n');
  757. if (sh.config.stripBrs == true)
  758. str = str.replace(br, '');
  759. return str;
  760. };
  761. /**
  762. * Removes all white space at the begining and end of a string.
  763. *
  764. * @param {String} str String to trim.
  765. * @return {String} Returns string without leading and following white space characters.
  766. */
  767. function trim(str)
  768. {
  769. return str.replace(/^\s+|\s+$/g, '');
  770. };
  771. /**
  772. * Unindents a block of text by the lowest common indent amount.
  773. * @param {String} str Text to unindent.
  774. * @return {String} Returns unindented text block.
  775. */
  776. function unindent(str)
  777. {
  778. var lines = splitLines(fixInputString(str)),
  779. indents = new Array(),
  780. regex = /^\s*/,
  781. min = 1000
  782. ;
  783. // go through every line and check for common number of indents
  784. for (var i = 0; i < lines.length && min > 0; i++)
  785. {
  786. var line = lines[i];
  787. if (trim(line).length == 0)
  788. continue;
  789. var matches = regex.exec(line);
  790. // In the event that just one line doesn't have leading white space
  791. // we can't unindent anything, so bail completely.
  792. if (matches == null)
  793. return str;
  794. min = Math.min(matches[0].length, min);
  795. }
  796. // trim minimum common number of white space from the begining of every line
  797. if (min > 0)
  798. for (var i = 0; i < lines.length; i++)
  799. lines[i] = lines[i].substr(min);
  800. return lines.join('\n');
  801. };
  802. /**
  803. * Callback method for Array.sort() which sorts matches by
  804. * index position and then by length.
  805. *
  806. * @param {Match} m1 Left object.
  807. * @param {Match} m2 Right object.
  808. * @return {Number} Returns -1, 0 or -1 as a comparison result.
  809. */
  810. function matchesSortCallback(m1, m2)
  811. {
  812. // sort matches by index first
  813. if(m1.index < m2.index)
  814. return -1;
  815. else if(m1.index > m2.index)
  816. return 1;
  817. else
  818. {
  819. // if index is the same, sort by length
  820. if(m1.length < m2.length)
  821. return -1;
  822. else if(m1.length > m2.length)
  823. return 1;
  824. }
  825. return 0;
  826. };
  827. /**
  828. * Executes given regular expression on provided code and returns all
  829. * matches that are found.
  830. *
  831. * @param {String} code Code to execute regular expression on.
  832. * @param {Object} regex Regular expression item info from <code>regexList</code> collection.
  833. * @return {Array} Returns a list of Match objects.
  834. */
  835. function getMatches(code, regexInfo)
  836. {
  837. function defaultAdd(match, regexInfo)
  838. {
  839. return match[0];
  840. };
  841. var index = 0,
  842. match = null,
  843. matches = [],
  844. func = regexInfo.func ? regexInfo.func : defaultAdd
  845. ;
  846. while((match = regexInfo.regex.exec(code)) != null)
  847. {
  848. var resultMatch = func(match, regexInfo);
  849. if (typeof(resultMatch) == 'string')
  850. resultMatch = [new sh.Match(resultMatch, match.index, regexInfo.css)];
  851. matches = matches.concat(resultMatch);
  852. }
  853. return matches;
  854. };
  855. /**
  856. * Turns all URLs in the code into <a/> tags.
  857. * @param {String} code Input code.
  858. * @return {String} Returns code with </a> tags.
  859. */
  860. function processUrls(code)
  861. {
  862. var gt = /(.*)((&gt;|&lt;).*)/;
  863. return code.replace(sh.regexLib.url, function(m)
  864. {
  865. var suffix = '',
  866. match = null
  867. ;
  868. // We include &lt; and &gt; in the URL for the common cases like <http://google.com>
  869. // The problem is that they get transformed into &lt;http://google.com&gt;
  870. // Where as &gt; easily looks like part of the URL string.
  871. if (match = gt.exec(m))
  872. {
  873. m = match[1];
  874. suffix = match[2];
  875. }
  876. return '<a href="' + m + '">' + m + '</a>' + suffix;
  877. });
  878. };
  879. /**
  880. * Finds all <SCRIPT TYPE="syntaxhighlighter" /> elementss.
  881. * @return {Array} Returns array of all found SyntaxHighlighter tags.
  882. */
  883. function getSyntaxHighlighterScriptTags()
  884. {
  885. var tags = document.getElementsByTagName('script'),
  886. result = []
  887. ;
  888. for (var i = 0; i < tags.length; i++)
  889. if (tags[i].type == 'syntaxhighlighter')
  890. result.push(tags[i]);
  891. return result;
  892. };
  893. /**
  894. * Strips <![CDATA[]]> from <SCRIPT /> content because it should be used
  895. * there in most cases for XHTML compliance.
  896. * @param {String} original Input code.
  897. * @return {String} Returns code without leading <![CDATA[]]> tags.
  898. */
  899. function stripCData(original)
  900. {
  901. var left = '<![CDATA[',
  902. right = ']]>',
  903. // for some reason IE inserts some leading blanks here
  904. copy = trim(original),
  905. changed = false,
  906. leftLength = left.length,
  907. rightLength = right.length
  908. ;
  909. if (copy.indexOf(left) == 0)
  910. {
  911. copy = copy.substring(leftLength);
  912. changed = true;
  913. }
  914. var copyLength = copy.length;
  915. if (copy.indexOf(right) == copyLength - rightLength)
  916. {
  917. copy = copy.substring(0, copyLength - rightLength);
  918. changed = true;
  919. }
  920. return changed ? copy : original;
  921. };
  922. /**
  923. * Quick code mouse double click handler.
  924. */
  925. function quickCodeHandler(e)
  926. {
  927. var target = e.target,
  928. highlighterDiv = findParentElement(target, '.syntaxhighlighter'),
  929. container = findParentElement(target, '.container'),
  930. textarea = document.createElement('textarea'),
  931. highlighter
  932. ;
  933. if (!container || !highlighterDiv || findElement(container, 'textarea'))
  934. return;
  935. highlighter = getHighlighterById(highlighterDiv.id);
  936. // add source class name
  937. addClass(highlighterDiv, 'source');
  938. // Have to go over each line and grab it's text, can't just do it on the
  939. // container because Firefox loses all \n where as Webkit doesn't.
  940. var lines = container.childNodes,
  941. code = []
  942. ;
  943. for (var i = 0; i < lines.length; i++)
  944. code.push(lines[i].innerText || lines[i].textContent);
  945. // using \r instead of \r or \r\n makes this work equally well on IE, FF and Webkit
  946. code = code.join('\r');
  947. // For Webkit browsers, replace nbsp with a breaking space
  948. code = code.replace(/\u00a0/g, " ");
  949. // inject <textarea/> tag
  950. textarea.appendChild(document.createTextNode(code));
  951. container.appendChild(textarea);
  952. // preselect all text
  953. textarea.focus();
  954. textarea.select();
  955. // set up handler for lost focus
  956. attachEvent(textarea, 'blur', function(e)
  957. {
  958. textarea.parentNode.removeChild(textarea);
  959. removeClass(highlighterDiv, 'source');
  960. });
  961. };
  962. /**
  963. * Match object.
  964. */
  965. sh.Match = function(value, index, css)
  966. {
  967. this.value = value;
  968. this.index = index;
  969. this.length = value.length;
  970. this.css = css;
  971. this.brushName = null;
  972. };
  973. sh.Match.prototype.toString = function()
  974. {
  975. return this.value;
  976. };
  977. /**
  978. * Simulates HTML code with a scripting language embedded.
  979. *
  980. * @param {String} scriptBrushName Brush name of the scripting language.
  981. */
  982. sh.HtmlScript = function(scriptBrushName)
  983. {
  984. var brushClass = findBrush(scriptBrushName),
  985. scriptBrush,
  986. xmlBrush = new sh.brushes.Xml(),
  987. bracketsRegex = null,
  988. ref = this,
  989. methodsToExpose = 'getDiv getHtml init'.split(' ')
  990. ;
  991. if (brushClass == null)
  992. return;
  993. scriptBrush = new brushClass();
  994. for(var i = 0; i < methodsToExpose.length; i++)
  995. // make a closure so we don't lose the name after i changes
  996. (function() {
  997. var name = methodsToExpose[i];
  998. ref[name] = function()
  999. {
  1000. return xmlBrush[name].apply(xmlBrush, arguments);
  1001. };
  1002. })();
  1003. if (scriptBrush.htmlScript == null)
  1004. {
  1005. alert(sh.config.strings.brushNotHtmlScript + scriptBrushName);
  1006. return;
  1007. }
  1008. xmlBrush.regexList.push(
  1009. { regex: scriptBrush.htmlScript.code, func: process }
  1010. );
  1011. function offsetMatches(matches, offset)
  1012. {
  1013. for (var j = 0; j < matches.length; j++)
  1014. matches[j].index += offset;
  1015. }
  1016. function process(match, info)
  1017. {
  1018. var code = match.code,
  1019. matches = [],
  1020. regexList = scriptBrush.regexList,
  1021. offset = match.index + match.left.length,
  1022. htmlScript = scriptBrush.htmlScript,
  1023. result
  1024. ;
  1025. // add all matches from the code
  1026. for (var i = 0; i < regexList.length; i++)
  1027. {
  1028. result = getMatches(code, regexList[i]);
  1029. offsetMatches(result, offset);
  1030. matches = matches.concat(result);
  1031. }
  1032. // add left script bracket
  1033. if (htmlScript.left != null && match.left != null)
  1034. {
  1035. result = getMatches(match.left, htmlScript.left);
  1036. offsetMatches(result, match.index);
  1037. matches = matches.concat(result);
  1038. }
  1039. // add right script bracket
  1040. if (htmlScript.right != null && match.right != null)
  1041. {
  1042. result = getMatches(match.right, htmlScript.right);
  1043. offsetMatches(result, match.index + match[0].lastIndexOf(match.right));
  1044. matches = matches.concat(result);
  1045. }
  1046. for (var j = 0; j < matches.length; j++)
  1047. matches[j].brushName = brushClass.brushName;
  1048. return matches;
  1049. }
  1050. };
  1051. /**
  1052. * Main Highlither class.
  1053. * @constructor
  1054. */
  1055. sh.Highlighter = function()
  1056. {
  1057. // not putting any code in here because of the prototype inheritance
  1058. };
  1059. sh.Highlighter.prototype = {
  1060. /**
  1061. * Returns value of the parameter passed to the highlighter.
  1062. * @param {String} name Name of the parameter.
  1063. * @param {Object} defaultValue Default value.
  1064. * @return {Object} Returns found value or default value otherwise.
  1065. */
  1066. getParam: function(name, defaultValue)
  1067. {
  1068. var result = this.params[name];
  1069. return toBoolean(result == null ? defaultValue : result);
  1070. },
  1071. /**
  1072. * Shortcut to document.createElement().
  1073. * @param {String} name Name of the element to create (DIV, A, etc).
  1074. * @return {HTMLElement} Returns new HTML element.
  1075. */
  1076. create: function(name)
  1077. {
  1078. return document.createElement(name);
  1079. },
  1080. /**
  1081. * Applies all regular expression to the code and stores all found
  1082. * matches in the `this.matches` array.
  1083. * @param {Array} regexList List of regular expressions.
  1084. * @param {String} code Source code.
  1085. * @return {Array} Returns list of matches.
  1086. */
  1087. findMatches: function(regexList, code)
  1088. {
  1089. var result = [];
  1090. if (regexList != null)
  1091. for (var i = 0; i < regexList.length; i++)
  1092. // BUG: length returns len+1 for array if methods added to prototype chain (oising@gmail.com)
  1093. if (typeof (regexList[i]) == "object")
  1094. result = result.concat(getMatches(code, regexList[i]));
  1095. // sort and remove nested the matches
  1096. return this.removeNestedMatches(result.sort(matchesSortCallback));
  1097. },
  1098. /**
  1099. * Checks to see if any of the matches are inside of other matches.
  1100. * This process would get rid of highligted strings inside comments,
  1101. * keywords inside strings and so on.
  1102. */
  1103. removeNestedMatches: function(matches)
  1104. {
  1105. // Optimized by Jose Prado (http://joseprado.com)
  1106. for (var i = 0; i < matches.length; i++)
  1107. {
  1108. if (matches[i] === null)
  1109. continue;
  1110. var itemI = matches[i],
  1111. itemIEndPos = itemI.index + itemI.length
  1112. ;
  1113. for (var j = i + 1; j < matches.length && matches[i] !== null; j++)
  1114. {
  1115. var itemJ = matches[j];
  1116. if (itemJ === null)
  1117. continue;
  1118. else if (itemJ.index > itemIEndPos)
  1119. break;
  1120. else if (itemJ.index == itemI.index && itemJ.length > itemI.length)
  1121. matches[i] = null;
  1122. else if (itemJ.index >= itemI.index && itemJ.index < itemIEndPos)
  1123. matches[j] = null;
  1124. }
  1125. }
  1126. return matches;
  1127. },
  1128. /**
  1129. * Creates an array containing integer line numbers starting from the 'first-line' param.
  1130. * @return {Array} Returns array of integers.
  1131. */
  1132. figureOutLineNumbers: function(code)
  1133. {
  1134. var lines = [],
  1135. firstLine = parseInt(this.getParam('first-line'))
  1136. ;
  1137. eachLine(code, function(line, index)
  1138. {
  1139. lines.push(index + firstLine);
  1140. });
  1141. return lines;
  1142. },
  1143. /**
  1144. * Determines if specified line number is in the highlighted list.
  1145. */
  1146. isLineHighlighted: function(lineNumber)
  1147. {
  1148. var list = this.getParam('highlight', []);
  1149. if (typeof(list) != 'object' && list.push == null)
  1150. list = [ list ];
  1151. return indexOf(list, lineNumber.toString()) != -1;
  1152. },
  1153. /**
  1154. * Generates HTML markup for a single line of code while determining alternating line style.
  1155. * @param {Integer} lineNumber Line number.
  1156. * @param {String} code Line HTML markup.
  1157. * @return {String} Returns HTML markup.
  1158. */
  1159. getLineHtml: function(lineIndex, lineNumber, code)
  1160. {
  1161. var classes = [
  1162. 'line',
  1163. 'number' + lineNumber,
  1164. 'index' + lineIndex,
  1165. 'alt' + (lineNumber % 2 == 0 ? 1 : 2).toString()
  1166. ];
  1167. if (this.isLineHighlighted(lineNumber))
  1168. classes.push('highlighted');
  1169. if (lineNumber == 0)
  1170. classes.push('break');
  1171. return '<div class="' + classes.join(' ') + '">' + code + '</div>';
  1172. },
  1173. /**
  1174. * Generates HTML markup for line number column.
  1175. * @param {String} code Complete code HTML markup.
  1176. * @param {Array} lineNumbers Calculated line numbers.
  1177. * @return {String} Returns HTML markup.
  1178. */
  1179. getLineNumbersHtml: function(code, lineNumbers)
  1180. {
  1181. var html = '',
  1182. count = splitLines(code).length,
  1183. firstLine = parseInt(this.getParam('first-line')),
  1184. pad = this.getParam('pad-line-numbers')
  1185. ;
  1186. if (pad == true)
  1187. pad = (firstLine + count - 1).toString().length;
  1188. else if (isNaN(pad) == true)
  1189. pad = 0;
  1190. for (var i = 0; i < count; i++)
  1191. {
  1192. var lineNumber = lineNumbers ? lineNumbers[i] : firstLine + i,
  1193. code = lineNumber == 0 ? sh.config.space : padNumber(lineNumber, pad)
  1194. ;
  1195. html += this.getLineHtml(i, lineNumber, code);
  1196. }
  1197. return html;
  1198. },
  1199. /**
  1200. * Splits block of text into individual DIV lines.
  1201. * @param {String} code Code to highlight.
  1202. * @param {Array} lineNumbers Calculated line numbers.
  1203. * @return {String} Returns highlighted code in HTML form.
  1204. */
  1205. getCodeLinesHtml: function(html, lineNumbers)
  1206. {
  1207. html = trim(html);
  1208. var lines = splitLines(html),
  1209. padLength = this.getParam('pad-line-numbers'),
  1210. firstLine = parseInt(this.getParam('first-line')),
  1211. html = '',
  1212. brushName = this.getParam('brush')
  1213. ;
  1214. for (var i = 0; i < lines.length; i++)
  1215. {
  1216. var line = lines[i],
  1217. indent = /^(&nbsp;|\s)+/.exec(line),
  1218. spaces = null,
  1219. lineNumber = lineNumbers ? lineNumbers[i] : firstLine + i;
  1220. ;
  1221. if (indent != null)
  1222. {
  1223. spaces = indent[0].toString();
  1224. line = line.substr(spaces.length);
  1225. spaces = spaces.replace(' ', sh.config.space);
  1226. }
  1227. line = trim(line);
  1228. if (line.length == 0)
  1229. line = sh.config.space;
  1230. html += this.getLineHtml(
  1231. i,
  1232. lineNumber,
  1233. (spaces != null ? '<code class="' + brushName + ' spaces">' + spaces + '</code>' : '') + line
  1234. );
  1235. }
  1236. return html;
  1237. },
  1238. /**
  1239. * Returns HTML for the table title or empty string if title is null.
  1240. */
  1241. getTitleHtml: function(title)
  1242. {
  1243. return title ? '<caption>' + title + '</caption>' : '';
  1244. },
  1245. /**
  1246. * Finds all matches in the source code.
  1247. * @param {String} code Source code to process matches in.
  1248. * @param {Array} matches Discovered regex matches.
  1249. * @return {String} Returns formatted HTML with processed mathes.
  1250. */
  1251. getMatchesHtml: function(code, matches)
  1252. {
  1253. var pos = 0,
  1254. result = '',
  1255. brushName = this.getParam('brush', '')
  1256. ;
  1257. function getBrushNameCss(match)
  1258. {
  1259. var result = match ? (match.brushName || brushName) : brushName;
  1260. return result ? result + ' ' : '';
  1261. };
  1262. // Finally, go through the final list of matches and pull the all
  1263. // together adding everything in between that isn't a match.
  1264. for (var i = 0; i < matches.length; i++)
  1265. {
  1266. var match = matches[i],
  1267. matchBrushName
  1268. ;
  1269. if (match === null || match.length === 0)
  1270. continue;
  1271. matchBrushName = getBrushNameCss(match);
  1272. result += wrapLinesWithCode(code.substr(pos, match.index - pos), matchBrushName + 'plain')
  1273. + wrapLinesWithCode(match.value, matchBrushName + match.css)
  1274. ;
  1275. pos = match.index + match.length + (match.offset || 0);
  1276. }
  1277. // don't forget to add whatever's remaining in the string
  1278. result += wrapLinesWithCode(code.substr(pos), getBrushNameCss() + 'plain');
  1279. return result;
  1280. },
  1281. /**
  1282. * Generates HTML markup for the whole syntax highlighter.
  1283. * @param {String} code Source code.
  1284. * @return {String} Returns HTML markup.
  1285. */
  1286. getHtml: function(code)
  1287. {
  1288. var html = '',
  1289. classes = [ 'syntaxhighlighter' ],
  1290. tabSize,
  1291. matches,
  1292. lineNumbers
  1293. ;
  1294. // process light mode
  1295. if (this.getParam('light') == true)
  1296. this.params.toolbar = this.params.gutter = false;
  1297. className = 'syntaxhighlighter';
  1298. if (this.getParam('collapse') == true)
  1299. classes.push('collapsed');
  1300. if ((gutter = this.getParam('gutter')) == false)
  1301. classes.push('nogutter');
  1302. // add custom user style name
  1303. classes.push(this.getParam('class-name'));
  1304. // add brush alias to the class name for custom CSS
  1305. classes.push(this.getParam('brush'));
  1306. code = trimFirstAndLastLines(code)
  1307. .replace(/\r/g, ' ') // IE lets these buggers through
  1308. ;
  1309. tabSize = this.getParam('tab-size');
  1310. // replace tabs with spaces
  1311. code = this.getParam('smart-tabs') == true
  1312. ? processSmartTabs(code, tabSize)
  1313. : processTabs(code, tabSize)
  1314. ;
  1315. // unindent code by the common indentation
  1316. if (this.getParam('unindent'))
  1317. code = unindent(code);
  1318. if (gutter)
  1319. lineNumbers = this.figureOutLineNumbers(code);
  1320. // find matches in the code using brushes regex list
  1321. matches = this.findMatches(this.regexList, code);
  1322. // processes found matches into the html
  1323. html = this.getMatchesHtml(code, matches);
  1324. // finally, split all lines so that they wrap well
  1325. html = this.getCodeLinesHtml(html, lineNumbers);
  1326. // finally, process the links
  1327. if (this.getParam('auto-links'))
  1328. html = processUrls(html);
  1329. if (typeof(navigator) != 'undefined' && navigator.userAgent && navigator.userAgent.match(/MSIE/))
  1330. classes.push('ie');
  1331. html =
  1332. '<div id="' + getHighlighterId(this.id) + '" class="' + classes.join(' ') + '">'
  1333. + (this.getParam('toolbar') ? sh.toolbar.getHtml(this) : '')
  1334. + '<table border="0" cellpadding="0" cellspacing="0">'
  1335. + this.getTitleHtml(this.getParam('title'))
  1336. + '<tbody>'
  1337. + '<tr>'
  1338. + (gutter ? '<td class="gutter">' + this.getLineNumbersHtml(code) + '</td>' : '')
  1339. + '<td class="code">'
  1340. + '<div class="container">'
  1341. + html
  1342. + '</div>'
  1343. + '</td>'
  1344. + '</tr>'
  1345. + '</tbody>'
  1346. + '</table>'
  1347. + '</div>'
  1348. ;
  1349. return html;
  1350. },
  1351. /**
  1352. * Highlights the code and returns complete HTML.
  1353. * @param {String} code Code to highlight.
  1354. * @return {Element} Returns container DIV element with all markup.
  1355. */
  1356. getDiv: function(code)
  1357. {
  1358. if (code === null)
  1359. code = '';
  1360. this.code = code;
  1361. var div = this.create('div');
  1362. // create main HTML
  1363. div.innerHTML = this.getHtml(code);
  1364. // set up click handlers
  1365. if (this.getParam('toolbar'))
  1366. attachEvent(findElement(div, '.toolbar'), 'click', sh.toolbar.handler);
  1367. if (this.getParam('quick-code'))
  1368. attachEvent(findElement(div, '.code'), 'dblclick', quickCodeHandler);
  1369. return div;
  1370. },
  1371. /**
  1372. * Initializes the highlighter/brush.
  1373. *
  1374. * Constructor isn't used for initialization so that nothing executes during necessary
  1375. * `new SyntaxHighlighter.Highlighter()` call when setting up brush inheritence.
  1376. *
  1377. * @param {Hash} params Highlighter parameters.
  1378. */
  1379. init: function(params)
  1380. {
  1381. this.id = guid();
  1382. // register this instance in the highlighters list
  1383. storeHighlighter(this);
  1384. // local params take precedence over defaults
  1385. this.params = merge(sh.defaults, params || {})
  1386. // process light mode
  1387. if (this.getParam('light') == true)
  1388. this.params.toolbar = this.params.gutter = false;
  1389. },
  1390. /**
  1391. * Converts space separated list of keywords into a regular expression string.
  1392. * @param {String} str Space separated keywords.
  1393. * @return {String} Returns regular expression string.
  1394. */
  1395. getKeywords: function(str)
  1396. {
  1397. str = str
  1398. .replace(/^\s+|\s+$/g, '')
  1399. .replace(/\s+/g, '|')
  1400. ;
  1401. return '\\b(?:' + str + ')\\b';
  1402. },
  1403. /**
  1404. * Makes a brush compatible with the `html-script` functionality.
  1405. * @param {Object} regexGroup Object containing `left` and `right` regular expressions.
  1406. */
  1407. forHtmlScript: function(regexGroup)
  1408. {
  1409. var regex = { 'end' : regexGroup.right.source };
  1410. if(regexGroup.eof)
  1411. regex.end = "(?:(?:" + regex.end + ")|$)";
  1412. this.htmlScript = {
  1413. left : { regex: regexGroup.left, css: 'script' },
  1414. right : { regex: regexGroup.right, css: 'script' },
  1415. code : new XRegExp(
  1416. "(?<left>" + regexGroup.left.source + ")" +
  1417. "(?<code>.*?)" +
  1418. "(?<right>" + regex.end + ")",
  1419. "sgi"
  1420. )
  1421. };
  1422. }
  1423. }; // end of Highlighter
  1424. return sh;
  1425. }(); // end of anonymous function
  1426. // CommonJS
  1427. typeof(exports) != 'undefined' ? exports.SyntaxHighlighter = SyntaxHighlighter : null;