PageRenderTime 91ms CodeModel.GetById 29ms app.highlight 49ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/syntaxhighlighter_3.0.83/src/shCore.js

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