PageSourceSearch

https://theserverhost.com/blog/scripts/syntaxhighlighter/scripts/shCore.js

js theserverhost.com collected 2026-09-28 06:52:11 UTC 42,422 bytes, 1,714 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.