shCore.js 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950
  1. /**
  2. * SyntaxHighlighter
  3. * http://alexgorbatchev.com/
  4. *
  5. * SyntaxHighlighter is donationware. If you are using it, please donate.
  6. * http://alexgorbatchev.com/wiki/SyntaxHighlighter:Donate
  7. *
  8. * @version
  9. * 2.0.296 (March 01 2009)
  10. *
  11. * @copyright
  12. * Copyright (C) 2004-2009 Alex Gorbatchev.
  13. *
  14. * @license
  15. * This file is part of SyntaxHighlighter.
  16. *
  17. * SyntaxHighlighter is free software: you can redistribute it and/or modify
  18. * it under the terms of the GNU General Public License as published by
  19. * the Free Software Foundation, either version 3 of the License, or
  20. * (at your option) any later version.
  21. *
  22. * SyntaxHighlighter is distributed in the hope that it will be useful,
  23. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  25. * GNU General Public License for more details.
  26. *
  27. * You should have received a copy of the GNU General Public License
  28. * along with SyntaxHighlighter. If not, see <http://www.gnu.org/licenses/>.
  29. */
  30. //
  31. // Begin anonymous function. This is used to contain local scope variables without polutting global scope.
  32. //
  33. if (!window.SyntaxHighlighter) var SyntaxHighlighter = function() {
  34. // Shortcut object which will be assigned to the SyntaxHighlighter variable.
  35. // This is a shorthand for local reference in order to avoid long namespace
  36. // references to SyntaxHighlighter.whatever...
  37. var sh = {
  38. defaults : {
  39. /** Additional CSS class names to be added to highlighter elements. */
  40. 'class-name' : '',
  41. /** First line number. */
  42. 'first-line' : 1,
  43. /** Font size of the SyntaxHighlighter block. */
  44. 'font-size' : null,
  45. /** Lines to highlight. */
  46. 'highlight' : null,
  47. /** Enables or disables smart tabs. */
  48. 'smart-tabs' : true,
  49. /** Gets or sets tab size. */
  50. 'tab-size' : 4,
  51. /** Enables or disables ruler. */
  52. 'ruler' : false,
  53. /** Enables or disables gutter. */
  54. 'gutter' : true,
  55. /** Enables or disables toolbar. */
  56. 'toolbar' : true,
  57. /** Forces code view to be collapsed. */
  58. 'collapse' : false,
  59. /** Enables or disables automatic links. */
  60. 'auto-links' : true,
  61. /** Gets or sets light mode. Equavalent to turning off gutter and toolbar. */
  62. 'light' : false
  63. },
  64. config : {
  65. /** Path to the copy to clipboard SWF file. */
  66. clipboardSwf : null,
  67. /** Width of an item in the toolbar. */
  68. toolbarItemWidth : 16,
  69. /** Height of an item in the toolbar. */
  70. toolbarItemHeight : 16,
  71. /** Blogger mode flag. */
  72. bloggerMode : false,
  73. /** Name of the tag that SyntaxHighlighter will automatically look for. */
  74. tagName : 'pre',
  75. strings : {
  76. expandSource : 'expand source',
  77. viewSource : 'view source',
  78. copyToClipboard : 'copy to clipboard',
  79. copyToClipboardConfirmation : 'The code is in your clipboard now',
  80. print : 'print',
  81. help : '?',
  82. alert: 'SyntaxHighlighter\n\n',
  83. noBrush : 'Can\'t find brush for: ',
  84. brushNotHtmlScript : 'Brush wasn\'t configured for html-script option: ',
  85. // this is populated by the build script
  86. aboutDialog : '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>About SyntaxHighlighter</title></head><body style="font-family:Georgia,\'Times New Roman\',Times,serif;background-color:#fff;color:#000;font-size:1em;text-align:center;"><div style="text-align:center;margin-top:3em;"><div style="font-family:Geneva,Arial,Helvetica,sans-serif;font-size:xx-large;">SyntaxHighlighter</div><div style="font-size:.75em;margin-bottom:4em;"><div>version 2.0.296 (March 01 2009)</div><div><a href="http://alexgorbatchev.com" target="_blank" style="color:#0099FF;text-decoration:none;">http://alexgorbatchev.com</a></div></div><div>JavaScript code syntax highlighter.</div><div>Copyright 2004-2009 Alex Gorbatchev.</div></div></body></html>'
  87. },
  88. /** If true, output will show HTML produces instead. */
  89. debug : false
  90. },
  91. /** Internal 'global' variables. */
  92. vars : {
  93. discoveredBrushes : null,
  94. spaceWidth : null,
  95. printFrame : null,
  96. highlighters : {}
  97. },
  98. /** This object is populated by user included external brush files. */
  99. brushes : {},
  100. /** Common regular expressions. */
  101. regexLib : {
  102. multiLineCComments : /\/\*[\s\S]*?\*\//gm,
  103. singleLineCComments : /\/\/.*$/gm,
  104. singleLinePerlComments : /#.*$/gm,
  105. doubleQuotedString : /"(?:\.|(\\\")|[^\""\n])*"/g,
  106. singleQuotedString : /'(?:\.|(\\\')|[^\''\n])*'/g,
  107. multiLineDoubleQuotedString : /"(?:\.|(\\\")|[^\""])*"/g,
  108. multiLineSingleQuotedString : /'(?:\.|(\\\')|[^\''])*'/g,
  109. url : /\w+:\/\/[\w-.\/?%&=]*/g,
  110. /** <?= ?> tags. */
  111. phpScriptTags : { left: /(&lt;|<)\?=?/g, right: /\?(&gt;|>)/g },
  112. /** <%= %> tags. */
  113. aspScriptTags : { left: /(&lt;|<)%=?/g, right: /%(&gt;|>)/g },
  114. /** <script></script> tags. */
  115. scriptScriptTags : { left: /(&lt;|<)\s*script.*?(&gt;|>)/gi, right: /(&lt;|<)\/\s*script\s*(&gt;|>)/gi }
  116. },
  117. toolbar : {
  118. /**
  119. * Creates new toolbar for a highlighter.
  120. * @param {Highlighter} highlighter Target highlighter.
  121. */
  122. create : function(highlighter)
  123. {
  124. var div = document.createElement('DIV'),
  125. items = sh.toolbar.items
  126. ;
  127. div.className = 'toolbar';
  128. for (var name in items)
  129. {
  130. var constructor = items[name],
  131. command = new constructor(highlighter),
  132. element = command.create()
  133. ;
  134. highlighter.toolbarCommands[name] = command;
  135. if (element == null)
  136. continue;
  137. if (typeof(element) == 'string')
  138. element = sh.toolbar.createButton(element, highlighter.id, name);
  139. element.className += 'item ' + name;
  140. div.appendChild(element);
  141. }
  142. return div;
  143. },
  144. /**
  145. * Create a standard anchor button for the toolbar.
  146. * @param {String} label Label text to display.
  147. * @param {String} highlighterId Highlighter ID that this button would belong to.
  148. * @param {String} commandName Command name that would be executed.
  149. * @return {Element} Returns an 'A' element.
  150. */
  151. createButton : function(label, highlighterId, commandName)
  152. {
  153. var a = document.createElement('a'),
  154. style = a.style,
  155. config = sh.config,
  156. width = config.toolbarItemWidth,
  157. height = config.toolbarItemHeight
  158. ;
  159. a.href = '#' + commandName;
  160. a.title = label;
  161. a.highlighterId = highlighterId;
  162. a.commandName = commandName;
  163. a.innerHTML = label;
  164. if (isNaN(width) == false)
  165. style.width = width + 'px';
  166. if (isNaN(height) == false)
  167. style.height = height + 'px';
  168. a.onclick = function(e)
  169. {
  170. try
  171. {
  172. sh.toolbar.executeCommand(
  173. this,
  174. e || window.event,
  175. this.highlighterId,
  176. this.commandName
  177. );
  178. }
  179. catch(e)
  180. {
  181. sh.utils.alert(e.message);
  182. }
  183. return false;
  184. };
  185. return a;
  186. },
  187. /**
  188. * Executes a toolbar command.
  189. * @param {Element} sender Sender element.
  190. * @param {MouseEvent} event Original mouse event object.
  191. * @param {String} highlighterId Highlighter DIV element ID.
  192. * @param {String} commandName Name of the command to execute.
  193. * @return {Object} Passes out return value from command execution.
  194. */
  195. executeCommand : function(sender, event, highlighterId, commandName, args)
  196. {
  197. var highlighter = sh.vars.highlighters[highlighterId],
  198. command
  199. ;
  200. if (highlighter == null || (command = highlighter.toolbarCommands[commandName]) == null)
  201. return null;
  202. return command.execute(sender, event, args);
  203. },
  204. /** Collection of toolbar items. */
  205. items : {
  206. expandSource : function(highlighter)
  207. {
  208. this.create = function()
  209. {
  210. if (highlighter.getParam('collapse') != true)
  211. return;
  212. return sh.config.strings.expandSource;
  213. };
  214. this.execute = function(sender, event, args)
  215. {
  216. var div = highlighter.div;
  217. sender.parentNode.removeChild(sender);
  218. div.className = div.className.replace('collapsed', '');
  219. };
  220. },
  221. /**
  222. * Command to open a new window and display the original unformatted source code inside.
  223. */
  224. viewSource : function(highlighter)
  225. {
  226. this.create = function()
  227. {
  228. return sh.config.strings.viewSource;
  229. };
  230. this.execute = function(sender, event, args)
  231. {
  232. var code = sh.utils.fixForBlogger(highlighter.originalCode).replace(/</g, '&lt;'),
  233. wnd = sh.utils.popup('', '_blank', 750, 400, 'location=0, resizable=1, menubar=0, scrollbars=1')
  234. ;
  235. code = sh.utils.unindent(code);
  236. wnd.document.write('<pre>' + code + '</pre>');
  237. wnd.document.close();
  238. };
  239. },
  240. /**
  241. * Command to copy the original source code in to the clipboard.
  242. * Uses Flash method if <code>clipboardSwf</code> is configured.
  243. */
  244. copyToClipboard : function(highlighter)
  245. {
  246. var flashDiv, flashSwf,
  247. highlighterId = highlighter.id
  248. ;
  249. this.create = function()
  250. {
  251. var config = sh.config;
  252. // disable functionality if running locally
  253. if (config.clipboardSwf == null)
  254. return null;
  255. function params(list)
  256. {
  257. var result = '';
  258. for (var name in list)
  259. result += "<param name='" + name + "' value='" + list[name] + "'/>";
  260. return result;
  261. };
  262. function attributes(list)
  263. {
  264. var result = '';
  265. for (var name in list)
  266. result += " " + name + "='" + list[name] + "'";
  267. return result;
  268. };
  269. var args1 = {
  270. width : config.toolbarItemWidth,
  271. height : config.toolbarItemHeight,
  272. id : highlighterId + '_clipboard',
  273. type : 'application/x-shockwave-flash',
  274. title : sh.config.strings.copyToClipboard
  275. },
  276. // these arguments are used in IE's <param /> collection
  277. args2 = {
  278. allowScriptAccess : 'always',
  279. wmode : 'transparent',
  280. flashVars : 'highlighterId=' + highlighterId,
  281. menu : 'false'
  282. },
  283. swf = config.clipboardSwf,
  284. html
  285. ;
  286. if (/msie/i.test(navigator.userAgent))
  287. {
  288. html = '<object'
  289. + attributes({
  290. classid : 'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000',
  291. codebase : 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0'
  292. })
  293. + attributes(args1)
  294. + '>'
  295. + params(args2)
  296. + params({ movie : swf })
  297. + '</object>'
  298. ;
  299. }
  300. else
  301. {
  302. html = '<embed'
  303. + attributes(args1)
  304. + attributes(args2)
  305. + attributes({ src : swf })
  306. + '/>'
  307. ;
  308. }
  309. flashDiv = document.createElement('div');
  310. flashDiv.innerHTML = html;
  311. return flashDiv;
  312. };
  313. this.execute = function(sender, event, args)
  314. {
  315. var command = args.command;
  316. switch (command)
  317. {
  318. case 'get':
  319. var code = sh.utils.unindent(
  320. sh.utils.fixForBlogger(highlighter.originalCode)
  321. .replace(/&lt;/g, '<')
  322. .replace(/&gt;/g, '>')
  323. .replace(/&amp;/g, '&')
  324. );
  325. if(window.clipboardData)
  326. // will fall through to the confirmation because there isn't a break
  327. window.clipboardData.setData('text', code);
  328. else
  329. return sh.utils.unindent(code);
  330. case 'ok':
  331. sh.utils.alert(sh.config.strings.copyToClipboardConfirmation);
  332. break;
  333. case 'error':
  334. sh.utils.alert(args.message);
  335. break;
  336. }
  337. };
  338. },
  339. /** Command to print the colored source code. */
  340. printSource : function(highlighter)
  341. {
  342. this.create = function()
  343. {
  344. return sh.config.strings.print;
  345. };
  346. this.execute = function(sender, event, args)
  347. {
  348. var iframe = document.createElement('IFRAME'),
  349. doc = null
  350. ;
  351. // make sure there is never more than one hidden iframe created by SH
  352. if (sh.vars.printFrame != null)
  353. document.body.removeChild(sh.vars.printFrame);
  354. sh.vars.printFrame = iframe;
  355. // this hides the iframe
  356. iframe.style.cssText = 'position:absolute;width:0px;height:0px;left:-500px;top:-500px;';
  357. document.body.appendChild(iframe);
  358. doc = iframe.contentWindow.document;
  359. copyStyles(doc, window.document);
  360. doc.write('<div class="' + highlighter.div.className.replace('collapsed', '') + ' printing">' + highlighter.div.innerHTML + '</div>');
  361. doc.close();
  362. iframe.contentWindow.focus();
  363. iframe.contentWindow.print();
  364. function copyStyles(destDoc, sourceDoc)
  365. {
  366. var links = sourceDoc.getElementsByTagName('link');
  367. for(var i = 0; i < links.length; i++)
  368. if(links[i].rel.toLowerCase() == 'stylesheet' && /shCore\.css$/.test(links[i].href))
  369. destDoc.write('<link type="text/css" rel="stylesheet" href="' + links[i].href + '"></link>');
  370. };
  371. };
  372. },
  373. /** Command to display the about dialog window. */
  374. about : function(highlighter)
  375. {
  376. this.create = function()
  377. {
  378. return sh.config.strings.help;
  379. };
  380. this.execute = function(sender, event)
  381. {
  382. var wnd = sh.utils.popup('', '_blank', 500, 250, 'scrollbars=0'),
  383. doc = wnd.document
  384. ;
  385. doc.write(sh.config.strings.aboutDialog);
  386. doc.close();
  387. wnd.focus();
  388. };
  389. }
  390. }
  391. },
  392. utils : {
  393. guid : function(prefix)
  394. {
  395. return prefix + Math.round(Math.random() * 1000000).toString();
  396. },
  397. /**
  398. * Merges two objects. Values from obj2 override values in obj1.
  399. * Function is NOT recursive and works only for one dimensional objects.
  400. * @param {Object} obj1 First object.
  401. * @param {Object} obj2 Second object.
  402. * @return {Object} Returns combination of both objects.
  403. */
  404. merge: function(obj1, obj2)
  405. {
  406. var result = {}, name;
  407. for (name in obj1)
  408. result[name] = obj1[name];
  409. for (name in obj2)
  410. result[name] = obj2[name];
  411. return result;
  412. },
  413. /**
  414. * Attempts to convert string to boolean.
  415. * @param {String} value Input string.
  416. * @return {Boolean} Returns true if input was "true", false if input was "false" and value otherwise.
  417. */
  418. toBoolean: function(value)
  419. {
  420. switch (value)
  421. {
  422. case "true":
  423. return true;
  424. case "false":
  425. return false;
  426. }
  427. return value;
  428. },
  429. /**
  430. * Opens up a centered popup window.
  431. * @param {String} url URL to open in the window.
  432. * @param {String} name Popup name.
  433. * @param {int} width Popup width.
  434. * @param {int} height Popup height.
  435. * @param {String} options window.open() options.
  436. * @return {Window} Returns window instance.
  437. */
  438. popup: function(url, name, width, height, options)
  439. {
  440. var x = (screen.width - width) / 2,
  441. y = (screen.height - height) / 2
  442. ;
  443. options += ', left=' + x +
  444. ', top=' + y +
  445. ', width=' + width +
  446. ', height=' + height
  447. ;
  448. options = options.replace(/^,/, '');
  449. var win = window.open(url, name, options);
  450. win.focus();
  451. return win;
  452. },
  453. /**
  454. * Adds event handler to the target object.
  455. * @param {Object} obj Target object.
  456. * @param {String} type Name of the event.
  457. * @param {Function} func Handling function.
  458. */
  459. addEvent: function(obj, type, func)
  460. {
  461. if (obj.attachEvent)
  462. {
  463. obj['e' + type + func] = func;
  464. obj[type + func] = function()
  465. {
  466. obj['e' + type + func](window.event);
  467. }
  468. obj.attachEvent('on' + type, obj[type + func]);
  469. }
  470. else
  471. {
  472. obj.addEventListener(type, func, false);
  473. }
  474. },
  475. /**
  476. * Displays an alert.
  477. * @param {String} str String to display.
  478. */
  479. alert: function(str)
  480. {
  481. alert(sh.config.strings.alert + str)
  482. },
  483. /**
  484. * Finds a brush by its alias.
  485. *
  486. * @param {String} alias Brush alias.
  487. * @param {Boolean} alert Suppresses the alert if false.
  488. * @return {Brush} Returns bursh constructor if found, null otherwise.
  489. */
  490. findBrush: function(alias, alert)
  491. {
  492. var brushes = sh.vars.discoveredBrushes,
  493. result = null
  494. ;
  495. if (brushes == null)
  496. {
  497. brushes = {};
  498. // Find all brushes
  499. for (var brush in sh.brushes)
  500. {
  501. var aliases = sh.brushes[brush].aliases;
  502. if (aliases == null)
  503. continue;
  504. for (var i = 0; i < aliases.length; i++)
  505. brushes[aliases[i]] = brush;
  506. }
  507. sh.vars.discoveredBrushes = brushes;
  508. }
  509. result = sh.brushes[brushes[alias]];
  510. if (result == null && alert != false)
  511. sh.utils.alert(sh.config.strings.noBrush + alias);
  512. return result;
  513. },
  514. /**
  515. * Executes a callback on each line and replaces each line with result from the callback.
  516. * @param {Object} str Input string.
  517. * @param {Object} callback Callback function taking one string argument and returning a string.
  518. */
  519. eachLine: function(str, callback)
  520. {
  521. var lines = str.split('\n');
  522. for (var i = 0; i < lines.length; i++)
  523. lines[i] = callback(lines[i]);
  524. return lines.join('\n');
  525. },
  526. /**
  527. * Creates rules looking div.
  528. */
  529. createRuler: function()
  530. {
  531. var div = document.createElement('div'),
  532. ruler = document.createElement('div'),
  533. showEvery = 10,
  534. i = 1
  535. ;
  536. while (i <= 150)
  537. {
  538. if (i % showEvery === 0)
  539. {
  540. div.innerHTML += i;
  541. i += (i + '').length;
  542. }
  543. else
  544. {
  545. div.innerHTML += '&middot;';
  546. i++;
  547. }
  548. }
  549. ruler.className = 'ruler line';
  550. ruler.appendChild(div);
  551. return ruler;
  552. },
  553. /**
  554. * This is a special trim which only removes first and last empty lines
  555. * and doesn't affect valid leading space on the first line.
  556. *
  557. * @param {String} str Input string
  558. * @return {String} Returns string without empty first and last lines.
  559. */
  560. trimFirstAndLastLines: function(str)
  561. {
  562. return str.replace(/^[ ]*[\n]+|[\n]*[ ]*$/g, '');
  563. },
  564. /**
  565. * Parses key/value pairs into hash object.
  566. *
  567. * Understands the following formats:
  568. * - name: word;
  569. * - name: [word, word];
  570. * - name: "string";
  571. * - name: 'string';
  572. *
  573. * For example:
  574. * name1: value; name2: [value, value]; name3: 'value'
  575. *
  576. * @param {String} str Input string.
  577. * @return {Object} Returns deserialized object.
  578. */
  579. parseParams: function(str)
  580. {
  581. var match,
  582. result = {},
  583. arrayRegex = new XRegExp("^\\[(?<values>(.*?))\\]$"),
  584. regex = new XRegExp(
  585. "(?<name>[\\w-]+)" +
  586. "\\s*:\\s*" +
  587. "(?<value>" +
  588. "[\\w-%#]+|" + // word
  589. "\\[.*?\\]|" + // [] array
  590. '".*?"|' + // "" string
  591. "'.*?'" + // '' string
  592. ")\\s*;?",
  593. "g"
  594. )
  595. ;
  596. while ((match = regex.exec(str)) != null)
  597. {
  598. var value = match.value
  599. .replace(/^['"]|['"]$/g, '') // strip quotes from end of strings
  600. ;
  601. // try to parse array value
  602. if (value != null && arrayRegex.test(value))
  603. {
  604. var m = arrayRegex.exec(value);
  605. value = m.values.length > 0 ? m.values.split(/\s*,\s*/) : [];
  606. }
  607. result[match.name] = value;
  608. }
  609. return result;
  610. },
  611. /**
  612. * Wraps each line of the string into <code/> tag with given style applied to it.
  613. *
  614. * @param {String} str Input string.
  615. * @param {String} css Style name to apply to the string.
  616. * @return {String} Returns input string with each line surrounded by <span/> tag.
  617. */
  618. decorate: function(str, css)
  619. {
  620. if (str == null || str.length == 0 || str == '\n')
  621. return str;
  622. str = str.replace(/</g, '&lt;');
  623. // Replace two or more sequential spaces with &nbsp; leaving last space untouched.
  624. str = str.replace(/ {2,}/g, function(m)
  625. {
  626. var spaces = '';
  627. for (var i = 0; i < m.length - 1; i++)
  628. spaces += '&nbsp;';
  629. return spaces + ' ';
  630. });
  631. // Split each line and apply <span class="...">...</span> to them so that
  632. // leading spaces aren't included.
  633. if (css != null)
  634. str = sh.utils.eachLine(str, function(line)
  635. {
  636. if (line.length == 0)
  637. return '';
  638. var spaces = '';
  639. line = line.replace(/^(&nbsp;| )+/, function(s)
  640. {
  641. spaces = s;
  642. return '';
  643. });
  644. if (line.length == 0)
  645. return spaces;
  646. return spaces + '<code class="' + css + '">' + line + '</code>';
  647. });
  648. return str;
  649. },
  650. /**
  651. * Pads number with zeros until it's length is the same as given length.
  652. *
  653. * @param {Number} number Number to pad.
  654. * @param {Number} length Max string length with.
  655. * @return {String} Returns a string padded with proper amount of '0'.
  656. */
  657. padNumber : function(number, length)
  658. {
  659. var result = number.toString();
  660. while (result.length < length)
  661. result = '0' + result;
  662. return result;
  663. },
  664. /**
  665. * Measures width of a single space character.
  666. * @return {Number} Returns width of a single space character.
  667. */
  668. measureSpace : function()
  669. {
  670. var container = document.createElement('div'),
  671. span,
  672. result = 0,
  673. body = document.body,
  674. id = sh.utils.guid('measureSpace'),
  675. // variable names will be compressed, so it's better than a plain string
  676. divOpen = '<div class="',
  677. closeDiv = '</div>',
  678. closeSpan = '</span>'
  679. ;
  680. // we have to duplicate highlighter nested structure in order to get an acurate space measurment
  681. container.innerHTML =
  682. divOpen + 'syntaxhighlighter">'
  683. + divOpen + 'lines">'
  684. + divOpen + 'line">'
  685. + divOpen + 'content'
  686. + '"><span class="block"><span id="' + id + '">&nbsp;' + closeSpan + closeSpan
  687. + closeDiv
  688. + closeDiv
  689. + closeDiv
  690. + closeDiv
  691. ;
  692. body.appendChild(container);
  693. span = document.getElementById(id);
  694. if (/opera/i.test(navigator.userAgent))
  695. {
  696. var style = window.getComputedStyle(span, null);
  697. result = parseInt(style.getPropertyValue("width"));
  698. }
  699. else
  700. {
  701. result = span.offsetWidth;
  702. }
  703. body.removeChild(container);
  704. return result;
  705. },
  706. /**
  707. * Replaces tabs with spaces.
  708. *
  709. * @param {String} code Source code.
  710. * @param {Number} tabSize Size of the tab.
  711. * @return {String} Returns code with all tabs replaces by spaces.
  712. */
  713. processTabs : function(code, tabSize)
  714. {
  715. var tab = '';
  716. for (var i = 0; i < tabSize; i++)
  717. tab += ' ';
  718. return code.replace(/\t/g, tab);
  719. },
  720. /**
  721. * Replaces tabs with smart spaces.
  722. *
  723. * @param {String} code Code to fix the tabs in.
  724. * @param {Number} tabSize Number of spaces in a column.
  725. * @return {String} Returns code with all tabs replaces with roper amount of spaces.
  726. */
  727. processSmartTabs : function(code, tabSize)
  728. {
  729. var lines = code.split('\n'),
  730. tab = '\t',
  731. spaces = ''
  732. ;
  733. // Create a string with 1000 spaces to copy spaces from...
  734. // It's assumed that there would be no indentation longer than that.
  735. for (var i = 0; i < 50; i++)
  736. spaces += ' '; // 20 spaces * 50
  737. // This function inserts specified amount of spaces in the string
  738. // where a tab is while removing that given tab.
  739. function insertSpaces(line, pos, count)
  740. {
  741. return line.substr(0, pos)
  742. + spaces.substr(0, count)
  743. + line.substr(pos + 1, line.length) // pos + 1 will get rid of the tab
  744. ;
  745. };
  746. // Go through all the lines and do the 'smart tabs' magic.
  747. code = sh.utils.eachLine(code, function(line)
  748. {
  749. if (line.indexOf(tab) == -1)
  750. return line;
  751. var pos = 0;
  752. while ((pos = line.indexOf(tab)) != -1)
  753. {
  754. // This is pretty much all there is to the 'smart tabs' logic.
  755. // Based on the position within the line and size of a tab,
  756. // calculate the amount of spaces we need to insert.
  757. var spaces = tabSize - pos % tabSize;
  758. line = insertSpaces(line, pos, spaces);
  759. }
  760. return line;
  761. });
  762. return code;
  763. },
  764. fixForBlogger : function(str)
  765. {
  766. return (sh.config.bloggerMode == true) ? str.replace(/<br\s*\/?>|&lt;br\s*\/?&gt;/gi, '\n') : str;
  767. },
  768. /**
  769. * Removes all white space at the begining and end of a string.
  770. *
  771. * @param {String} str String to trim.
  772. * @return {String} Returns string without leading and following white space characters.
  773. */
  774. trim: function(str)
  775. {
  776. return str.replace(/\s*$/g, '').replace(/^\s*/, '');
  777. },
  778. /**
  779. * Unindents a block of text by the lowest common indent amount.
  780. * @param {String} str Text to unindent.
  781. * @return {String} Returns unindented text block.
  782. */
  783. unindent: function(str)
  784. {
  785. var lines = sh.utils.fixForBlogger(str).split('\n'),
  786. indents = new Array(),
  787. regex = /^\s*/,
  788. min = 1000
  789. ;
  790. // go through every line and check for common number of indents
  791. for (var i = 0; i < lines.length && min > 0; i++)
  792. {
  793. var line = lines[i];
  794. if (sh.utils.trim(line).length == 0)
  795. continue;
  796. var matches = regex.exec(line);
  797. // In the event that just one line doesn't have leading white space
  798. // we can't unindent anything, so bail completely.
  799. if (matches == null)
  800. return str;
  801. min = Math.min(matches[0].length, min);
  802. }
  803. // trim minimum common number of white space from the begining of every line
  804. if (min > 0)
  805. for (var i = 0; i < lines.length; i++)
  806. lines[i] = lines[i].substr(min);
  807. return lines.join('\n');
  808. },
  809. /**
  810. * Callback method for Array.sort() which sorts matches by
  811. * index position and then by length.
  812. *
  813. * @param {Match} m1 Left object.
  814. * @param {Match} m2 Right object.
  815. * @return {Number} Returns -1, 0 or -1 as a comparison result.
  816. */
  817. matchesSortCallback: function(m1, m2)
  818. {
  819. // sort matches by index first
  820. if(m1.index < m2.index)
  821. return -1;
  822. else if(m1.index > m2.index)
  823. return 1;
  824. else
  825. {
  826. // if index is the same, sort by length
  827. if(m1.length < m2.length)
  828. return -1;
  829. else if(m1.length > m2.length)
  830. return 1;
  831. }
  832. return 0;
  833. },
  834. /**
  835. * Executes given regular expression on provided code and returns all
  836. * matches that are found.
  837. *
  838. * @param {String} code Code to execute regular expression on.
  839. * @param {Object} regex Regular expression item info from <code>regexList</code> collection.
  840. * @return {Array} Returns a list of Match objects.
  841. */
  842. getMatches: function(code, regexInfo)
  843. {
  844. function defaultAdd(match, regexInfo)
  845. {
  846. return [new sh.Match(match[0], match.index, regexInfo.css)];
  847. };
  848. var index = 0,
  849. match = null,
  850. result = [],
  851. func = regexInfo.func ? regexInfo.func : defaultAdd
  852. ;
  853. while((match = regexInfo.regex.exec(code)) != null)
  854. result = result.concat(func(match, regexInfo));
  855. return result;
  856. },
  857. processUrls: function(code)
  858. {
  859. return code.replace(sh.regexLib.url, function(m)
  860. {
  861. return '<a href="' + m + '">' + m + '</a>';
  862. });
  863. }
  864. }, // end of utils
  865. /**
  866. * Shorthand to highlight all elements on the page that are marked as
  867. * SyntaxHighlighter source code.
  868. *
  869. * @param {Object} globalParams Optional parameters which override element's
  870. * parameters. Only used if element is specified.
  871. *
  872. * @param {Object} element Optional element to highlight. If none is
  873. * provided, all elements in the current document
  874. * are highlighted.
  875. */
  876. highlight : function(globalParams, element)
  877. {
  878. function toArray(source)
  879. {
  880. var result = [];
  881. for (var i = 0; i < source.length; i++)
  882. result.push(source[i]);
  883. return result;
  884. };
  885. var elements = element ? [element] : toArray(document.getElementsByTagName(sh.config.tagName)),
  886. propertyName = 'innerHTML',
  887. highlighter = null
  888. ;
  889. if (elements.length === 0)
  890. return;
  891. for (var i = 0; i < elements.length; i++)
  892. {
  893. var target = elements[i],
  894. params = sh.utils.parseParams(target.className),
  895. brushName
  896. ;
  897. // local params take precedence over globals
  898. params = sh.utils.merge(globalParams, params);
  899. brushName = params['brush'];
  900. if (brushName == null)
  901. continue;
  902. // Instantiate a brush
  903. if (params['html-script'] == 'true')
  904. {
  905. highlighter = new sh.HtmlScript(brushName);
  906. }
  907. else
  908. {
  909. var brush = sh.utils.findBrush(brushName);
  910. if (brush)
  911. highlighter = new brush();
  912. else
  913. continue;
  914. }
  915. highlighter.highlight(target[propertyName], params);
  916. var result = highlighter.div;
  917. if (sh.config.debug)
  918. {
  919. result = document.createElement('textarea');
  920. result.value = highlighter.div.innerHTML;
  921. result.style.width = '70em';
  922. result.style.height = '30em';
  923. }
  924. target.parentNode.replaceChild(result, target);
  925. }
  926. },
  927. /**
  928. * Main entry point for the SyntaxHighlighter.
  929. * @param {Object} params Optional params to apply to all highlighted elements.
  930. */
  931. all : function(params)
  932. {
  933. sh.utils.addEvent(
  934. window,
  935. 'load',
  936. function() { sh.highlight(params); }
  937. );
  938. }
  939. }; // end of sh
  940. /** Match object */
  941. sh.Match = function(value, index, css)
  942. {
  943. this.value = value;
  944. this.index = index;
  945. this.length = value.length;
  946. this.css = css;
  947. };
  948. sh.Match.prototype.toString = function()
  949. {
  950. return this.value;
  951. };
  952. /**
  953. * Simulates HTML code with a scripting language embedded.
  954. *
  955. * @param {String} scriptBrushName Brush name of the scripting language.
  956. */
  957. sh.HtmlScript = function(scriptBrushName)
  958. {
  959. var scriptBrush = sh.utils.findBrush(scriptBrushName),
  960. xmlBrush = new sh.brushes.Xml(),
  961. bracketsRegex = null
  962. ;
  963. if (scriptBrush == null)
  964. return;
  965. scriptBrush = new scriptBrush();
  966. this.xmlBrush = xmlBrush;
  967. if (scriptBrush.htmlScript == null)
  968. {
  969. sh.utils.alert(sh.config.strings.brushNotHtmlScript + scriptBrushName);
  970. return;
  971. }
  972. xmlBrush.regexList.push(
  973. { regex: scriptBrush.htmlScript.code, func: process }
  974. );
  975. function offsetMatches(matches, offset)
  976. {
  977. for (var j = 0; j < matches.length; j++)
  978. matches[j].index += offset;
  979. }
  980. function process(match, info)
  981. {
  982. var code = match.code,
  983. matches = [],
  984. regexList = scriptBrush.regexList,
  985. offset = match.index + match.left.length,
  986. htmlScript = scriptBrush.htmlScript,
  987. result
  988. ;
  989. for (var i = 0; i < regexList.length; i++)
  990. {
  991. result = sh.utils.getMatches(code, regexList[i]);
  992. offsetMatches(result, offset);
  993. matches = matches.concat(result);
  994. }
  995. if (htmlScript.left != null && match.left != null)
  996. {
  997. result = sh.utils.getMatches(match.left, htmlScript.left);
  998. offsetMatches(result, match.index);
  999. matches = matches.concat(result);
  1000. }
  1001. if (htmlScript.right != null && match.right != null)
  1002. {
  1003. result = sh.utils.getMatches(match.right, htmlScript.right);
  1004. offsetMatches(result, match.index + match[0].lastIndexOf(match.right));
  1005. matches = matches.concat(result);
  1006. }
  1007. return matches;
  1008. }
  1009. };
  1010. sh.HtmlScript.prototype.highlight = function(code, params)
  1011. {
  1012. this.xmlBrush.highlight(code, params);
  1013. this.div = this.xmlBrush.div;
  1014. }
  1015. /**
  1016. * Main Highlither class.
  1017. * @constructor
  1018. */
  1019. sh.Highlighter = function()
  1020. {
  1021. };
  1022. sh.Highlighter.prototype = {
  1023. /**
  1024. * Returns value of the parameter passed to the highlighter.
  1025. * @param {String} name Name of the parameter.
  1026. * @param {Object} defaultValue Default value.
  1027. * @return {Object} Returns found value or default value otherwise.
  1028. */
  1029. getParam : function(name, defaultValue)
  1030. {
  1031. var result = this.params[name];
  1032. return sh.utils.toBoolean(result == null ? defaultValue : result);
  1033. },
  1034. /**
  1035. * Shortcut to document.createElement().
  1036. * @param {String} name Name of the element to create (DIV, A, etc).
  1037. * @return {HTMLElement} Returns new HTML element.
  1038. */
  1039. create: function(name)
  1040. {
  1041. return document.createElement(name);
  1042. },
  1043. /**
  1044. * Checks if one match is inside another.
  1045. * @param {Match} match Match object to check.
  1046. * @return {Boolean} Returns true if given match was inside another, false otherwise.
  1047. */
  1048. isMatchNested: function(match)
  1049. {
  1050. for (var i = 0; i < this.matches.length; i++)
  1051. {
  1052. var item = this.matches[i];
  1053. if (item === null)
  1054. continue;
  1055. if ((match.index > item.index) && (match.index < item.index + item.length))
  1056. return true;
  1057. }
  1058. return false;
  1059. },
  1060. /**
  1061. * Applies all regular expression to the code and stores all found
  1062. * matches in the `this.matches` array.
  1063. * @param {Array} regexList List of regular expressions.
  1064. * @param {String} code Source code.
  1065. * @return {Array} Returns list of matches.
  1066. */
  1067. findMatches: function(regexList, code)
  1068. {
  1069. var result = [];
  1070. if (regexList != null)
  1071. for (var i = 0; i < regexList.length; i++)
  1072. result = result.concat(sh.utils.getMatches(code, regexList[i]));
  1073. // sort the matches
  1074. result = result.sort(sh.utils.matchesSortCallback);
  1075. return result;
  1076. },
  1077. /**
  1078. * Checks to see if any of the matches are inside of other matches.
  1079. * This process would get rid of highligted strings inside comments,
  1080. * keywords inside strings and so on.
  1081. */
  1082. removeNestedMatches: function()
  1083. {
  1084. for (var i = 0; i < this.matches.length; i++)
  1085. if (this.isMatchNested(this.matches[i]))
  1086. this.matches[i] = null;
  1087. },
  1088. /**
  1089. * Splits block of text into individual DIV lines.
  1090. * @param {String} code Code to highlight.
  1091. * @return {String} Returns highlighted code in HTML form.
  1092. */
  1093. createDisplayLines : function(code)
  1094. {
  1095. var lines = code.split(/\n/g),
  1096. firstLine = parseInt(this.getParam('first-line')),
  1097. padLength = (firstLine + lines.length).toString().length,
  1098. highlightedLines = this.getParam('highlight', [])
  1099. ;
  1100. code = '';
  1101. for (var i = 0; i < lines.length; i++)
  1102. {
  1103. var line = lines[i],
  1104. indent = /^(&nbsp;|\s)+/.exec(line),
  1105. lineClass = 'line alt' + (i % 2 == 0 ? 1 : 2),
  1106. lineNumber = sh.utils.padNumber(firstLine + i, padLength),
  1107. highlighted = highlightedLines.indexOf((firstLine + i).toString()) != -1,
  1108. spaces = null
  1109. ;
  1110. if (indent != null)
  1111. {
  1112. spaces = indent[0].toString();
  1113. line = line.substr(spaces.length);
  1114. spaces = spaces.replace(/&nbsp;/g, ' ');
  1115. indent = sh.vars.spaceWidth * spaces.length;
  1116. }
  1117. else
  1118. {
  1119. indent = 0;
  1120. }
  1121. line = sh.utils.trim(line);
  1122. if (line.length == 0)
  1123. line = '&nbsp;';
  1124. if (highlighted)
  1125. lineClass += ' highlighted';
  1126. code +=
  1127. '<div class="' + lineClass + '">'
  1128. + '<code class="number">' + lineNumber + '.</code>'
  1129. + '<span class="content">'
  1130. + (spaces != null ? '<code class="spaces">' + spaces.replace(/\s/g, '&nbsp;') + '</code>' : '')
  1131. + '<span class="block" style="margin-left: ' + indent + 'px !important;">' + line + '</span>'
  1132. + '</span>'
  1133. + '</div>'
  1134. ;
  1135. }
  1136. return code;
  1137. },
  1138. /**
  1139. * Finds all matches in the source code.
  1140. * @param {String} code Source code to process matches in.
  1141. * @param {Array} matches Discovered regex matches.
  1142. * @return {String} Returns formatted HTML with processed mathes.
  1143. */
  1144. processMatches: function(code, matches)
  1145. {
  1146. var pos = 0,
  1147. result = '',
  1148. decorate = sh.utils.decorate // make an alias to save some bytes
  1149. ;
  1150. // Finally, go through the final list of matches and pull the all
  1151. // together adding everything in between that isn't a match.
  1152. for (var i = 0; i < matches.length; i++)
  1153. {
  1154. var match = matches[i];
  1155. if (match === null || match.length === 0)
  1156. continue;
  1157. result += decorate(code.substr(pos, match.index - pos), 'plain')
  1158. + decorate(match.value, match.css)
  1159. ;
  1160. pos = match.index + match.length;
  1161. }
  1162. // don't forget to add whatever's remaining in the string
  1163. result += decorate(code.substr(pos), 'plain');
  1164. return result;
  1165. },
  1166. /**
  1167. * Highlights the code and returns complete HTML.
  1168. * @param {String} code Code to highlight.
  1169. * @param {Object} params Parameters object.
  1170. */
  1171. highlight: function(code, params)
  1172. {
  1173. var conf = sh.config,
  1174. vars = sh.vars,
  1175. div,
  1176. tabSize
  1177. ;
  1178. this.params = {};
  1179. this.div = null;
  1180. this.lines = null;
  1181. this.code = null;
  1182. this.bar = null;
  1183. this.toolbarCommands = {};
  1184. this.id = sh.utils.guid('highlighter_');
  1185. // register this instance in the highlighters list
  1186. vars.highlighters[this.id] = this;
  1187. if (code === null)
  1188. code = '';
  1189. // Measure width of a single space.
  1190. if (vars.spaceWidth === null)
  1191. vars.spaceWidth = sh.utils.measureSpace();
  1192. // local params take precedence over defaults
  1193. this.params = sh.utils.merge(sh.defaults, params || {});
  1194. // process light mode
  1195. if (this.getParam('light') == true)
  1196. this.params.toolbar = this.params.gutter = false;
  1197. this.div = div = this.create('DIV');
  1198. this.lines = this.create('DIV');
  1199. this.lines.className = 'lines';
  1200. div.className = 'syntaxhighlighter';
  1201. div.id = this.id;
  1202. if (this.getParam('collapse'))
  1203. div.className += ' collapsed';
  1204. if (this.getParam('gutter') == false)
  1205. div.className += ' nogutter';
  1206. div.className += ' ' + this.getParam('class-name');
  1207. div.style.fontSize = this.getParam('font-size', ''); // IE7 can't take null
  1208. this.originalCode = code;
  1209. this.code = sh.utils.trimFirstAndLastLines(code)
  1210. .replace(/\r/g, ' ') // IE lets these buggers through
  1211. ;
  1212. tabSize = this.getParam('tab-size');
  1213. // replace tabs with spaces
  1214. this.code = this.getParam('smart-tabs') == true
  1215. ? sh.utils.processSmartTabs(this.code, tabSize)
  1216. : sh.utils.processTabs(this.code, tabSize)
  1217. ;
  1218. this.code = sh.utils.unindent(this.code);
  1219. // add controls toolbar
  1220. if (this.getParam('toolbar'))
  1221. {
  1222. this.bar = this.create('DIV');
  1223. this.bar.className = 'bar';
  1224. this.bar.appendChild(sh.toolbar.create(this));
  1225. div.appendChild(this.bar);
  1226. }
  1227. // add columns ruler
  1228. if (this.getParam('ruler'))
  1229. div.appendChild(sh.utils.createRuler());
  1230. div.appendChild(this.lines);
  1231. this.matches = this.findMatches(this.regexList, this.code);
  1232. this.removeNestedMatches();
  1233. code = this.processMatches(this.code, this.matches);
  1234. // finally, split all lines so that they wrap well
  1235. code = this.createDisplayLines(sh.utils.trim(code));
  1236. // finally, process the links
  1237. if (this.getParam('auto-links'))
  1238. code = sh.utils.processUrls(code);
  1239. this.lines.innerHTML = code;
  1240. },
  1241. /**
  1242. * Converts space separated list of keywords into a regular expression string.
  1243. * @param {String} str Space separated keywords.
  1244. * @return {String} Returns regular expression string.
  1245. */
  1246. getKeywords: function(str)
  1247. {
  1248. str = str
  1249. .replace(/^\s+|\s+$/g, '')
  1250. .replace(/\s+/g, '\\b|\\b')
  1251. ;
  1252. return '\\b' + str + '\\b';
  1253. },
  1254. /**
  1255. * Makes a brush compatible with the `html-script` functionality.
  1256. * @param {Object} regexGroup Object containing `left` and `right` regular expressions.
  1257. */
  1258. forHtmlScript: function(regexGroup)
  1259. {
  1260. this.htmlScript = {
  1261. left : { regex: regexGroup.left, css: 'script' },
  1262. right : { regex: regexGroup.right, css: 'script' },
  1263. code : new XRegExp(
  1264. "(?<left>" + regexGroup.left.source + ")" +
  1265. "(?<code>.*?)" +
  1266. "(?<right>" + regexGroup.right.source + ")",
  1267. "sgi"
  1268. )
  1269. };
  1270. }
  1271. }; // end of Highlighter
  1272. return sh;
  1273. }(); // end of anonymous function
  1274. if (!Array.indexOf)
  1275. /**
  1276. * Finds an index of element in the array.
  1277. * @ignore
  1278. * @param {Object} searchElement
  1279. * @param {Number} fromIndex
  1280. * @return {Number} Returns index of element if found; -1 otherwise.
  1281. */
  1282. Array.prototype.indexOf = function (searchElement, fromIndex)
  1283. {
  1284. fromIndex = Math.max(fromIndex || 0, 0);
  1285. for (var i = fromIndex; i < this.length; i++)
  1286. if(this[i] == searchElement)
  1287. return i;
  1288. return -1;
  1289. };
  1290. /**
  1291. * XRegExp 0.6.1
  1292. * (c) 2007-2008 Steven Levithan
  1293. * <http://stevenlevithan.com/regex/xregexp/>
  1294. * MIT License
  1295. *
  1296. * provides an augmented, cross-browser implementation of regular expressions
  1297. * including support for additional modifiers and syntax. several convenience
  1298. * methods and a recursive-construct parser are also included.
  1299. */
  1300. // prevent running twice, which would break references to native globals
  1301. if (!window.XRegExp) {
  1302. // anonymous function to avoid global variables
  1303. (function () {
  1304. // copy various native globals for reference. can't use the name ``native``
  1305. // because it's a reserved JavaScript keyword.
  1306. var real = {
  1307. exec: RegExp.prototype.exec,
  1308. match: String.prototype.match,
  1309. replace: String.prototype.replace,
  1310. split: String.prototype.split
  1311. },
  1312. /* regex syntax parsing with support for all the necessary cross-
  1313. browser and context issues (escapings, character classes, etc.) */
  1314. lib = {
  1315. part: /(?:[^\\([#\s.]+|\\(?!k<[\w$]+>|[pP]{[^}]+})[\S\s]?|\((?=\?(?!#|<[\w$]+>)))+|(\()(?:\?(?:(#)[^)]*\)|<([$\w]+)>))?|\\(?:k<([\w$]+)>|[pP]{([^}]+)})|(\[\^?)|([\S\s])/g,
  1316. replaceVar: /(?:[^$]+|\$(?![1-9$&`']|{[$\w]+}))+|\$(?:([1-9]\d*|[$&`'])|{([$\w]+)})/g,
  1317. extended: /^(?:\s+|#.*)+/,
  1318. quantifier: /^(?:[?*+]|{\d+(?:,\d*)?})/,
  1319. classLeft: /&&\[\^?/g,
  1320. classRight: /]/g
  1321. },
  1322. indexOf = function (array, item, from) {
  1323. for (var i = from || 0; i < array.length; i++)
  1324. if (array[i] === item) return i;
  1325. return -1;
  1326. },
  1327. brokenExecUndef = /()??/.exec("")[1] !== undefined,
  1328. plugins = {};
  1329. /**
  1330. * Accepts a pattern and flags, returns a new, extended RegExp object.
  1331. * differs from a native regex in that additional flags and syntax are
  1332. * supported and browser inconsistencies are ameliorated.
  1333. * @ignore
  1334. */
  1335. XRegExp = function (pattern, flags) {
  1336. if (pattern instanceof RegExp) {
  1337. if (flags !== undefined)
  1338. throw TypeError("can't supply flags when constructing one RegExp from another");
  1339. return pattern.addFlags(); // new copy
  1340. }
  1341. var flags = flags || "",
  1342. singleline = flags.indexOf("s") > -1,
  1343. extended = flags.indexOf("x") > -1,
  1344. hasNamedCapture = false,
  1345. captureNames = [],
  1346. output = [],
  1347. part = lib.part,
  1348. match, cc, len, index, regex;
  1349. part.lastIndex = 0; // in case the last XRegExp compilation threw an error (unbalanced character class)
  1350. while (match = real.exec.call(part, pattern)) {
  1351. // comment pattern. this check must come before the capturing group check,
  1352. // because both match[1] and match[2] will be non-empty.
  1353. if (match[2]) {
  1354. // keep tokens separated unless the following token is a quantifier
  1355. if (!lib.quantifier.test(pattern.slice(part.lastIndex)))
  1356. output.push("(?:)");
  1357. // capturing group
  1358. } else if (match[1]) {
  1359. captureNames.push(match[3] || null);
  1360. if (match[3])
  1361. hasNamedCapture = true;
  1362. output.push("(");
  1363. // named backreference
  1364. } else if (match[4]) {
  1365. index = indexOf(captureNames, match[4]);
  1366. // keep backreferences separate from subsequent literal numbers
  1367. // preserve backreferences to named groups that are undefined at this point as literal strings
  1368. output.push(index > -1 ?
  1369. "\\" + (index + 1) + (isNaN(pattern.charAt(part.lastIndex)) ? "" : "(?:)") :
  1370. match[0]
  1371. );
  1372. // unicode element (requires plugin)
  1373. } else if (match[5]) {
  1374. output.push(plugins.unicode ?
  1375. plugins.unicode.get(match[5], match[0].charAt(1) === "P") :
  1376. match[0]
  1377. );
  1378. // character class opening delimiter ("[" or "[^")
  1379. // (non-native unicode elements are not supported within character classes)
  1380. } else if (match[6]) {
  1381. if (pattern.charAt(part.lastIndex) === "]") {
  1382. // for cross-browser compatibility with ECMA-262 v3 behavior,
  1383. // convert [] to (?!) and [^] to [\S\s].
  1384. output.push(match[6] === "[" ? "(?!)" : "[\\S\\s]");
  1385. part.lastIndex++;
  1386. } else {
  1387. // parse the character class with support for inner escapes and
  1388. // ES4's infinitely nesting intersection syntax ([&&[^&&[]]]).
  1389. cc = XRegExp.matchRecursive("&&" + pattern.slice(match.index), lib.classLeft, lib.classRight, "", {escapeChar: "\\"})[0];
  1390. output.push(match[6] + cc + "]");
  1391. part.lastIndex += cc.length + 1;
  1392. }
  1393. // dot ("."), pound sign ("#"), or whitespace character
  1394. } else if (match[7]) {
  1395. if (singleline && match[7] === ".") {
  1396. output.push("[\\S\\s]");
  1397. } else if (extended && lib.extended.test(match[7])) {
  1398. len = real.exec.call(lib.extended, pattern.slice(part.lastIndex - 1))[0].length;
  1399. // keep tokens separated unless the following token is a quantifier
  1400. if (!lib.quantifier.test(pattern.slice(part.lastIndex - 1 + len)))
  1401. output.push("(?:)");
  1402. part.lastIndex += len - 1;
  1403. } else {
  1404. output.push(match[7]);
  1405. }
  1406. } else {
  1407. output.push(match[0]);
  1408. }
  1409. }
  1410. regex = RegExp(output.join(""), real.replace.call(flags, /[sx]+/g, ""));
  1411. regex._x = {
  1412. source: pattern,
  1413. captureNames: hasNamedCapture ? captureNames : null
  1414. };
  1415. return regex;
  1416. };
  1417. /**
  1418. * Barebones plugin support for now (intentionally undocumented)
  1419. * @ignore
  1420. * @param {Object} name
  1421. * @param {Object} o
  1422. */
  1423. XRegExp.addPlugin = function (name, o) {
  1424. plugins[name] = o;
  1425. };
  1426. /**
  1427. * Adds named capture support, with values returned as ``result.name``.
  1428. *
  1429. * Also fixes two cross-browser issues, following the ECMA-262 v3 spec:
  1430. * - captured values for non-participating capturing groups should be returned
  1431. * as ``undefined``, rather than the empty string.
  1432. * - the regex's ``lastIndex`` should not be incremented after zero-length
  1433. * matches.
  1434. * @ignore
  1435. */
  1436. RegExp.prototype.exec = function (str) {
  1437. var match = real.exec.call(this, str),
  1438. name, i, r2;
  1439. if (match) {
  1440. // fix browsers whose exec methods don't consistently return
  1441. // undefined for non-participating capturing groups
  1442. if (brokenExecUndef && match.length > 1) {
  1443. // r2 doesn't need /g or /y, but they shouldn't hurt
  1444. r2 = new RegExp("^" + this.source + "$(?!\\s)", this.getNativeFlags());
  1445. real.replace.call(match[0], r2, function () {
  1446. for (i = 1; i < arguments.length - 2; i++) {
  1447. if (arguments[i] === undefined) match[i] = undefined;
  1448. }
  1449. });
  1450. }
  1451. // attach named capture properties
  1452. if (this._x && this._x.captureNames) {
  1453. for (i = 1; i < match.length; i++) {
  1454. name = this._x.captureNames[i - 1];
  1455. if (name) match[name] = match[i];
  1456. }
  1457. }
  1458. // fix browsers that increment lastIndex after zero-length matches
  1459. if (this.global && this.lastIndex > (match.index + match[0].length))
  1460. this.lastIndex--;
  1461. }
  1462. return match;
  1463. };
  1464. })(); // end anonymous function
  1465. } // end if(!window.XRegExp)
  1466. /**
  1467. * intentionally undocumented
  1468. * @ignore
  1469. */
  1470. RegExp.prototype.getNativeFlags = function () {
  1471. return (this.global ? "g" : "") +
  1472. (this.ignoreCase ? "i" : "") +
  1473. (this.multiline ? "m" : "") +
  1474. (this.extended ? "x" : "") +
  1475. (this.sticky ? "y" : "");
  1476. };
  1477. /**
  1478. * Accepts flags; returns a new XRegExp object generated by recompiling
  1479. * the regex with the additional flags (may include non-native flags).
  1480. * The original regex object is not altered.
  1481. * @ignore
  1482. */
  1483. RegExp.prototype.addFlags = function (flags) {
  1484. var regex = new XRegExp(this.source, (flags || "") + this.getNativeFlags());
  1485. if (this._x) {
  1486. regex._x = {
  1487. source: this._x.source,
  1488. captureNames: this._x.captureNames ? this._x.captureNames.slice(0) : null
  1489. };
  1490. }
  1491. return regex;
  1492. };
  1493. /**
  1494. * Accepts a context object and string; returns the result of calling
  1495. * ``exec`` with the provided string. the context is ignored but is
  1496. * accepted for congruity with ``Function.prototype.call``.
  1497. * @ignore
  1498. */
  1499. RegExp.prototype.call = function (context, str) {
  1500. return this.exec(str);
  1501. };
  1502. /**
  1503. * Accepts a context object and arguments array; returns the result of
  1504. * calling ``exec`` with the first value in the arguments array. the context
  1505. * is ignored but is accepted for congruity with ``Function.prototype.apply``.
  1506. * @ignore
  1507. */
  1508. RegExp.prototype.apply = function (context, args) {
  1509. return this.exec(args[0]);
  1510. };
  1511. /**
  1512. * Accepts a pattern and flags; returns an XRegExp object. if the pattern
  1513. * and flag combination has previously been cached, the cached copy is
  1514. * returned, otherwise the new object is cached.
  1515. * @ignore
  1516. */
  1517. XRegExp.cache = function (pattern, flags) {
  1518. var key = "/" + pattern + "/" + (flags || "");
  1519. return XRegExp.cache[key] || (XRegExp.cache[key] = new XRegExp(pattern, flags));
  1520. };
  1521. /**
  1522. * Accepts a string; returns the string with regex metacharacters escaped.
  1523. * the returned string can safely be used within a regex to match a literal
  1524. * string. escaped characters are [, ], {, }, (, ), -, *, +, ?, ., \, ^, $,
  1525. * |, #, [comma], and whitespace.
  1526. * @ignore
  1527. */
  1528. XRegExp.escape = function (str) {
  1529. return str.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, "\\$&");
  1530. };
  1531. /**
  1532. * Accepts a string to search, left and right delimiters as regex pattern
  1533. * strings, optional regex flags (may include non-native s, x, and y flags),
  1534. * and an options object which allows setting an escape character and changing
  1535. * the return format from an array of matches to a two-dimensional array of
  1536. * string parts with extended position data. returns an array of matches
  1537. * (optionally with extended data), allowing nested instances of left and right
  1538. * delimiters. use the g flag to return all matches, otherwise only the first
  1539. * is returned. if delimiters are unbalanced within the subject data, an error
  1540. * is thrown.
  1541. *
  1542. * This function admittedly pushes the boundaries of what can be accomplished
  1543. * sensibly without a "real" parser. however, by doing so it provides flexible
  1544. * and powerful recursive parsing capabilities with minimal code weight.
  1545. *
  1546. * Warning: the ``escapeChar`` option is considered experimental and might be
  1547. * changed or removed in future versions of XRegExp.
  1548. *
  1549. * unsupported features:
  1550. * - backreferences within delimiter patterns when using ``escapeChar``.
  1551. * - although providing delimiters as regex objects adds the minor feature of
  1552. * independent delimiter flags, it introduces other limitations and is only
  1553. * intended to be done by the ``XRegExp`` constructor (which can't call
  1554. * itself while building a regex).
  1555. *
  1556. * @ignore
  1557. */
  1558. XRegExp.matchRecursive = function (str, left, right, flags, options) {
  1559. var options = options || {},
  1560. escapeChar = options.escapeChar,
  1561. vN = options.valueNames,
  1562. flags = flags || "",
  1563. global = flags.indexOf("g") > -1,
  1564. ignoreCase = flags.indexOf("i") > -1,
  1565. multiline = flags.indexOf("m") > -1,
  1566. sticky = flags.indexOf("y") > -1,
  1567. /* sticky mode has its own handling in this function, which means you
  1568. can use flag "y" even in browsers which don't support it natively */
  1569. flags = flags.replace(/y/g, ""),
  1570. left = left instanceof RegExp ? (left.global ? left : left.addFlags("g")) : new XRegExp(left, "g" + flags),
  1571. right = right instanceof RegExp ? (right.global ? right : right.addFlags("g")) : new XRegExp(right, "g" + flags),
  1572. output = [],
  1573. openTokens = 0,
  1574. delimStart = 0,
  1575. delimEnd = 0,
  1576. lastOuterEnd = 0,
  1577. outerStart, innerStart, leftMatch, rightMatch, escaped, esc;
  1578. if (escapeChar) {
  1579. if (escapeChar.length > 1) throw SyntaxError("can't supply more than one escape character");
  1580. if (multiline) throw TypeError("can't supply escape character when using the multiline flag");
  1581. escaped = XRegExp.escape(escapeChar);
  1582. /* Escape pattern modifiers:
  1583. /g - not needed here
  1584. /i - included
  1585. /m - **unsupported**, throws error
  1586. /s - handled by XRegExp when delimiters are provided as strings
  1587. /x - handled by XRegExp when delimiters are provided as strings
  1588. /y - not needed here; supported by other handling in this function
  1589. */
  1590. esc = new RegExp(
  1591. "^(?:" + escaped + "[\\S\\s]|(?:(?!" + left.source + "|" + right.source + ")[^" + escaped + "])+)+",
  1592. ignoreCase ? "i" : ""
  1593. );
  1594. }
  1595. while (true) {
  1596. /* advance the starting search position to the end of the last delimiter match.
  1597. a couple special cases are also covered:
  1598. - if using an escape character, advance to the next delimiter's starting position,
  1599. skipping any escaped characters
  1600. - first time through, reset lastIndex in case delimiters were provided as regexes
  1601. */
  1602. left.lastIndex = right.lastIndex = delimEnd +
  1603. (escapeChar ? (esc.exec(str.slice(delimEnd)) || [""])[0].length : 0);
  1604. leftMatch = left.exec(str);
  1605. rightMatch = right.exec(str);
  1606. // only keep the result which matched earlier in the string
  1607. if (leftMatch && rightMatch) {
  1608. if (leftMatch.index <= rightMatch.index)
  1609. rightMatch = null;
  1610. else leftMatch = null;
  1611. }
  1612. /* paths*:
  1613. leftMatch | rightMatch | openTokens | result
  1614. 1 | 0 | 1 | ...
  1615. 1 | 0 | 0 | ...
  1616. 0 | 1 | 1 | ...
  1617. 0 | 1 | 0 | throw
  1618. 0 | 0 | 1 | throw
  1619. 0 | 0 | 0 | break
  1620. * - does not include the sticky mode special case
  1621. - the loop ends after the first completed match if not in global mode
  1622. */
  1623. if (leftMatch || rightMatch) {
  1624. delimStart = (leftMatch || rightMatch).index;
  1625. delimEnd = (leftMatch ? left : right).lastIndex;
  1626. } else if (!openTokens) {
  1627. break;
  1628. }
  1629. if (sticky && !openTokens && delimStart > lastOuterEnd)
  1630. break;
  1631. if (leftMatch) {
  1632. if (!openTokens++) {
  1633. outerStart = delimStart;
  1634. innerStart = delimEnd;
  1635. }
  1636. } else if (rightMatch && openTokens) {
  1637. if (!--openTokens) {
  1638. if (vN) {
  1639. if (vN[0] && outerStart > lastOuterEnd)
  1640. output.push([vN[0], str.slice(lastOuterEnd, outerStart), lastOuterEnd, outerStart]);
  1641. if (vN[1]) output.push([vN[1], str.slice(outerStart, innerStart), outerStart, innerStart]);
  1642. if (vN[2]) output.push([vN[2], str.slice(innerStart, delimStart), innerStart, delimStart]);
  1643. if (vN[3]) output.push([vN[3], str.slice(delimStart, delimEnd), delimStart, delimEnd]);
  1644. } else {
  1645. output.push(str.slice(innerStart, delimStart));
  1646. }
  1647. lastOuterEnd = delimEnd;
  1648. if (!global)
  1649. break;
  1650. }
  1651. } else {
  1652. // reset lastIndex in case delimiters were provided as regexes
  1653. left.lastIndex = right.lastIndex = 0;
  1654. throw Error("subject data contains unbalanced delimiters");
  1655. }
  1656. // if the delimiter matched an empty string, advance delimEnd to avoid an infinite loop
  1657. if (delimStart === delimEnd)
  1658. delimEnd++;
  1659. }
  1660. if (global && !sticky && vN && vN[0] && str.length > lastOuterEnd)
  1661. output.push([vN[0], str.slice(lastOuterEnd), lastOuterEnd, str.length]);
  1662. // reset lastIndex in case delimiters were provided as regexes
  1663. left.lastIndex = right.lastIndex = 0;
  1664. return output;
  1665. };