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.90 (Sat, 18 Jun 2016 21:01:41 GMT)
10 *
11 * @copyright
12 * Copyright (C) 2004-2013 Alex Gorbatchev.
13 *
14 * @license
15 * Dual licensed under the MIT and GPL licenses.
16 */
17/*!
18 * XRegExp v2.0.0
19 * (c) 2007-2012 Steven Levithan <http://xregexp.com/>
20 * MIT License
21 */
22
23/**
24 * XRegExp provides augmented, extensible JavaScript regular expressions. You get new syntax,
25 * flags, and methods beyond what browsers support natively. XRegExp is also a regex utility belt
26 * with tools to make your client-side grepping simpler and more powerful, while freeing you from
27 * worrying about pesky cross-browser inconsistencies and the dubious `lastIndex` property. See
28 * XRegExp's documentation (http://xregexp.com/) for more details.
29 * @module xregexp
30 * @requires N/A
31 */
32var XRegExp;
33
34// Avoid running twice; that would reset tokens and could break references to native globals
35XRegExp = XRegExp || (function (undef) {
36    "use strict";
37
38/*--------------------------------------
39 *  Private variables
40 *------------------------------------*/
41
42    var self,
43        addToken,
44        add,
45
46// Optional features; can be installed and uninstalled
47        features = {
48            natives: false,
49            extensibility: false
50        },
51
52// Store native methods to use and restore ("native" is an ES3 reserved keyword)
53        nativ = {
54            exec: RegExp.prototype.exec,
55            test: RegExp.prototype.test,
56            match: String.prototype.match,
57            replace: String.prototype.replace,
58            split: String.prototype.split
59        },
60
61// Storage for fixed/extended native methods
62        fixed = {},
63
64// Storage for cached regexes
65        cache = {},
66
67// Storage for addon tokens
68        tokens = [],
69
70// Token scopes
71        defaultScope = "default",
72        classScope = "class",
73
74// Regexes that match native regex syntax
75        nativeTokens = {
76            // Any native multicharacter token in default scope (includes octals, excludes character classes)
77            "default": /^(?:\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9]\d*|x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|c[A-Za-z]|[\s\S])|\(\?[:=!]|[?*+]\?|{\d+(?:,\d*)?}\??)/,
78            // Any native multicharacter token in character class scope (includes octals)
79            "class": /^(?:\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|c[A-Za-z]|[\s\S]))/
80        },
81
82// Any backreference in replacement strings
83        replacementToken = /\$(?:{([\w$]+)}|(\d\d?|[\s\S]))/g,
84
85// Any character with a later instance in the string
86        duplicateFlags = /([\s\S])(?=[\s\S]*\1)/g,
87
88// Any greedy/lazy quantifier
89        quantifier = /^(?:[?*+]|{\d+(?:,\d*)?})\??/,
90
91// Check for correct `exec` handling of nonparticipating capturing groups
92        compliantExecNpcg = nativ.exec.call(/()??/, "")[1] === undef,
93
94// Check for flag y support (Firefox 3+)
95        hasNativeY = RegExp.prototype.sticky !== undef,
96
97// Used to kill infinite recursion during XRegExp construction
98        isInsideConstructor = false,
99
100// Storage for known flags, including addon flags
101        registeredFlags = "gim" + (hasNativeY ? "y" : "");
102
103/*--------------------------------------
104 *  Private helper functions
105 *------------------------------------*/
106
107/**
108 * Attaches XRegExp.prototype properties and named capture supporting data to a regex object.
109 * @private
110 * @param {RegExp} regex Regex to augment.
111 * @param {Array} captureNames Array with capture names, or null.
112 * @param {Boolean} [isNative] Whether the regex was created by `RegExp` rather than `XRegExp`.
113 * @returns {RegExp} Augmented regex.
114 */
115    function augment(regex, captureNames, isNative) {
116        var p;
117        // Can't auto-inherit these since the XRegExp constructor returns a nonprimitive value
118        for (p in self.prototype) {
119            if (self.prototype.hasOwnProperty(p)) {
120                regex[p] = self.prototype[p];
121            }
122        }
123        regex.xregexp = {captureNames: captureNames, isNative: !!isNative};
124        return regex;
125    }
126
127/**
128 * Returns native `RegExp` flags used by a regex object.
129 * @private
130 * @param {RegExp} regex Regex to check.
131 * @returns {String} Native flags in use.
132 */
133    function getNativeFlags(regex) {
134        //return nativ.exec.call(/\/([a-z]*)$/i, String(regex))[1];
135        return (regex.global     ? "g" : "") +
136               (regex.ignoreCase ? "i" : "") +
137               (regex.multiline  ? "m" : "") +
138               (regex.extended   ? "x" : "") + // Proposed for ES6, included in AS3
139               (regex.sticky     ? "y" : ""); // Proposed for ES6, included in Firefox 3+
140    }
141
142/**
143 * Copies a regex object while preserving special properties for named capture and augmenting with
144 * `XRegExp.prototype` methods. The copy has a fresh `lastIndex` property (set to zero). Allows
145 * adding and removing flags while copying the regex.
146 * @private
147 * @param {RegExp} regex Regex to copy.
148 * @param {String} [addFlags] Flags to be added while copying the regex.
149 * @param {String} [removeFlags] Flags to be removed while copying the regex.
150 * @returns {RegExp} Copy of the provided regex, possibly with modified flags.
151 */
152    function copy(regex, addFlags, removeFlags) {
153        if (!self.isRegExp(regex)) {
154            throw new TypeError("type RegExp expected");
155        }
156        var flags = nativ.replace.call(getNativeFlags(regex) + (addFlags || ""), duplicateFlags, "");
157        if (removeFlags) {
158            // Would need to escape `removeFlags` if this was public
159            flags = nativ.replace.call(flags, new RegExp("[" + removeFlags + "]+", "g"), "");
160        }
161        if (regex.xregexp && !regex.xregexp.isNative) {
162            // Compiling the current (rather than precompilation) source preserves the effects of nonnative source flags
163            regex = augment(self(regex.source, flags),
164                            regex.xregexp.captureNames ? regex.xregexp.captureNames.slice(0) : null);
165        } else {
166            // Augment with `XRegExp.prototype` methods, but use native `RegExp` (avoid searching for special tokens)
167            regex = augment(new RegExp(regex.source, flags), null, true);
168        }
169        return regex;
170    }
171
172/*
173 * Returns the last index at which a given value can be found in an array, or `-1` if it's not
174 * present. The array is searched backwards.
175 * @private
176 * @param {Array} array Array to search.
177 * @param {*} value Value to locate in the array.
178 * @returns {Number} Last zero-based index at which the item is found, or -1.
179 */
180    function lastIndexOf(array, value) {
181        var i = array.length;
182        if (Array.prototype.lastIndexOf) {
183            return array.lastIndexOf(value); // Use the native method if available
184        }
185        while (i--) {
186            if (array[i] === value) {
187                return i;
188            }
189        }
190        return -1;
191    }
192
193/**
194 * Determines whether an object is of the specified type.
195 * @private
196 * @param {*} value Object to check.
197 * @param {String} type Type to check for, in lowercase.
198 * @returns {Boolean} Whether the object matches the type.
199 */
200    function isType(value, type) {
201        return Object.prototype.toString.call(value).toLowerCase() === "[object " + type + "]";
202    }
203
204/**
205 * Prepares an options object from the given value.
206 * @private
207 * @param {String|Object} value Value to convert to an options object.
208 * @returns {Object} Options object.
209 */
210    function prepareOptions(value) {
211        value = value || {};
212        if (value === "all" || value.all) {
213            value = {natives: true, extensibility: true};
214        } else if (isType(value, "string")) {
215            value = self.forEach(value, /[^\s,]+/, function (m) {
216                this[m] = true;
217            }, {});
218        }
219        return value;
220    }
221
222/**
223 * Runs built-in/custom tokens in reverse insertion order, until a match is found.
224 * @private
225 * @param {String} pattern Original pattern from which an XRegExp object is being built.
226 * @param {Number} pos Position to search for tokens within `pattern`.
227 * @param {Number} scope Current regex scope.
228 * @param {Object} context Context object assigned to token handler functions.
229 * @returns {Object} Object with properties `output` (the substitution string returned by the
230 *   successful token handler) and `match` (the token's match array), or null.
231 */
232    function runTokens(pattern, pos, scope, context) {
233        var i = tokens.length,
234            result = null,
235            match,
236            t;
237        // Protect against constructing XRegExps within token handler and trigger functions
238        isInsideConstructor = true;
239        // Must reset `isInsideConstructor`, even if a `trigger` or `handler` throws
240        try {
241            while (i--) { // Run in reverse order
242                t = tokens[i];
243                if ((t.scope === "all" || t.scope === scope) && (!t.trigger || t.trigger.call(context))) {
244                    t.pattern.lastIndex = pos;
245                    match = fixed.exec.call(t.pattern, pattern); // Fixed `exec` here allows use of named backreferences, etc.
246                    if (match && match.index === pos) {
247                        result = {
248                            output: t.handler.call(context, match, scope),
249                            match: match
250                        };
251                        break;
252                    }
253                }
254            }
255        } catch (err) {
256            throw err;
257        } finally {
258            isInsideConstructor = false;
259        }
260        return result;
261    }
262
263/**
264 * Enables or disables XRegExp syntax and flag extensibility.
265 * @private
266 * @param {Boolean} on `true` to enable; `false` to disable.
267 */
268    function setExtensibility(on) {
269        self.addToken = addToken[on ? "on" : "off"];
270        features.extensibility = on;
271    }
272
273/**
274 * Enables or disables native method overrides.
275 * @private
276 * @param {Boolean} on `true` to enable; `false` to disable.
277 */
278    function setNatives(on) {
279        RegExp.prototype.exec = (on ? fixed : nativ).exec;
280        RegExp.prototype.test = (on ? fixed : nativ).test;
281        String.prototype.match = (on ? fixed : nativ).match;
282        String.prototype.replace = (on ? fixed : nativ).replace;
283        String.prototype.split = (on ? fixed : nativ).split;
284        features.natives = on;
285    }
286
287/*--------------------------------------
288 *  Constructor
289 *------------------------------------*/
290
291/**
292 * Creates an extended regular expression object for matching text with a pattern. Differs from a
293 * native regular expression in that additional syntax and flags are supported. The returned object
294 * is in fact a native `RegExp` and works with all native methods.
295 * @class XRegExp
296 * @constructor
297 * @param {String|RegExp} pattern Regex pattern string, or an existing `RegExp` object to copy.
298 * @param {String} [flags] Any combination of flags:
299 *   <li>`g` - global
300 *   <li>`i` - ignore case
301 *   <li>`m` - multiline anchors
302 *   <li>`n` - explicit capture
303 *   <li>`s` - dot matches all (aka singleline)
304 *   <li>`x` - free-spacing and line comments (aka extended)
305 *   <li>`y` - sticky (Firefox 3+ only)
306 *   Flags cannot be provided when constructing one `RegExp` from another.
307 * @returns {RegExp} Extended regular expression object.
308 * @example
309 *
310 * // With named capture and flag x
311 * date = XRegExp('(?<year>  [0-9]{4}) -?  # year  \n\
312 *                 (?<month> [0-9]{2}) -?  # month \n\
313 *                 (?<day>   [0-9]{2})     # day   ', 'x');
314 *
315 * // Passing a regex object to copy it. The copy maintains special properties for named capture,
316 * // is augmented with `XRegExp.prototype` methods, and has a fresh `lastIndex` property (set to
317 * // zero). Native regexes are not recompiled using XRegExp syntax.
318 * XRegExp(/regex/);
319 */
320    self = function (pattern, flags) {
321        if (self.isRegExp(pattern)) {
322            if (flags !== undef) {
323                throw new TypeError("can't supply flags when constructing one RegExp from another");
324            }
325            return copy(pattern);
326        }
327        // Tokens become part of the regex construction process, so protect against infinite recursion
328        // when an XRegExp is constructed within a token handler function
329        if (isInsideConstructor) {
330            throw new Error("can't call the XRegExp constructor within token definition functions");
331        }
332
333        var output = [],
334            scope = defaultScope,
335            tokenContext = {
336                hasNamedCapture: false,
337                captureNames: [],
338                hasFlag: function (flag) {
339                    return flags.indexOf(flag) > -1;
340                }
341            },
342            pos = 0,
343            tokenResult,
344            match,
345            chr;
346        pattern = pattern === undef ? "" : String(pattern);
347        flags = flags === undef ? "" : String(flags);
348
349        if (nativ.match.call(flags, duplicateFlags)) { // Don't use test/exec because they would update lastIndex
350            throw new SyntaxError("invalid duplicate regular expression flag");
351        }
352        // Strip/apply leading mode modifier with any combination of flags except g or y: (?imnsx)
353        pattern = nativ.replace.call(pattern, /^\(\?([\w$]+)\)/, function ($0, $1) {
354            if (nativ.test.call(/[gy]/, $1)) {
355                throw new SyntaxError("can't use flag g or y in mode modifier");
356            }
357            flags = nativ.replace.call(flags + $1, duplicateFlags, "");
358            return "";
359        });
360        self.forEach(flags, /[\s\S]/, function (m) {
361            if (registeredFlags.indexOf(m[0]) < 0) {
362                throw new SyntaxError("invalid regular expression flag " + m[0]);
363            }
364        });
365
366        while (pos < pattern.length) {
367            // Check for custom tokens at the current position
368            tokenResult = runTokens(pattern, pos, scope, tokenContext);
369            if (tokenResult) {
370                output.push(tokenResult.output);
371                pos += (tokenResult.match[0].length || 1);
372            } else {
373                // Check for native tokens (except character classes) at the current position
374                match = nativ.exec.call(nativeTokens[scope], pattern.slice(pos));
375                if (match) {
376                    output.push(match[0]);
377                    pos += match[0].length;
378                } else {
379                    chr = pattern.charAt(pos);
380                    if (chr === "[") {
381                        scope = classScope;
382                    } else if (chr === "]") {
383                        scope = defaultScope;
384                    }
385                    // Advance position by one character
386                    output.push(chr);
387                    ++pos;
388                }
389            }
390        }
391
392        return augment(new RegExp(output.join(""), nativ.replace.call(flags, /[^gimy]+/g, "")),
393                       tokenContext.hasNamedCapture ? tokenContext.captureNames : null);
394    };
395
396/*--------------------------------------
397 *  Public methods/properties
398 *------------------------------------*/
399
400// Installed and uninstalled states for `XRegExp.addToken`
401    addToken = {
402        on: function (regex, handler, options) {
403            options = options || {};
404            if (regex) {
405                tokens.push({
406                    pattern: copy(regex, "g" + (hasNativeY ? "y" : "")),
407                    handler: handler,
408                    scope: options.scope || defaultScope,
409                    trigger: options.trigger || null
410                });
411            }
412            // Providing `customFlags` with null `regex` and `handler` allows adding flags that do
413            // nothing, but don't throw an error
414            if (options.customFlags) {
415                registeredFlags = nativ.replace.call(registeredFlags + options.customFlags, duplicateFlags, "");
416            }
417        },
418        off: function () {
419            throw new Error("extensibility must be installed before using addToken");
420        }
421    };
422
423/**
424 * Extends or changes XRegExp syntax and allows custom flags. This is used internally and can be
425 * used to create XRegExp addons. `XRegExp.install('extensibility')` must be run before calling
426 * this function, or an error is thrown. If more than one token can match the same string, the last
427 * added wins.
428 * @memberOf XRegExp
429 * @param {RegExp} regex Regex object that matches the new token.
430 * @param {Function} handler Function that returns a new pattern string (using native regex syntax)
431 *   to replace the matched token within all future XRegExp regexes. Has access to persistent
432 *   properties of the regex being built, through `this`. Invoked with two arguments:
433 *   <li>The match array, with named backreference properties.
434 *   <li>The regex scope where the match was found.
435 * @param {Object} [options] Options object with optional properties:
436 *   <li>`scope` {String} Scopes where the token applies: 'default', 'class', or 'all'.
437 *   <li>`trigger` {Function} Function that returns `true` when the token should be applied; e.g.,
438 *     if a flag is set. If `false` is returned, the matched string can be matched by other tokens.
439 *     Has access to persistent properties of the regex being built, through `this` (including
440 *     function `this.hasFlag`).
441 *   <li>`customFlags` {String} Nonnative flags used by the token's handler or trigger functions.
442 *     Prevents XRegExp from throwing an invalid flag error when the specified flags are used.
443 * @example
444 *
445 * // Basic usage: Adds \a for ALERT character
446 * XRegExp.addToken(
447 *   /\\a/,
448 *   function () {return '\\x07';},
449 *   {scope: 'all'}
450 * );
451 * XRegExp('\\a[\\a-\\n]+').test('\x07\n\x07'); // -> true
452 */
453    self.addToken = addToken.off;
454
455/**
456 * Caches and returns the result of calling `XRegExp(pattern, flags)`. On any subsequent call with
457 * the same pattern and flag combination, the cached copy is returned.
458 * @memberOf XRegExp
459 * @param {String} pattern Regex pattern string.
460 * @param {String} [flags] Any combination of XRegExp flags.
461 * @returns {RegExp} Cached XRegExp object.
462 * @example
463 *
464 * while (match = XRegExp.cache('.', 'gs').exec(str)) {
465 *   // The regex is compiled once only
466 * }
467 */
468    self.cache = function (pattern, flags) {
469        var key = pattern + "/" + (flags || "");
470        return cache[key] || (cache[key] = self(pattern, flags));
471    };
472
473/**
474 * Escapes any regular expression metacharacters, for use when matching literal strings. The result
475 * can safely be used at any point within a regex that uses any flags.
476 * @memberOf XRegExp
477 * @param {String} str String to escape.
478 * @returns {String} String with regex metacharacters escaped.
479 * @example
480 *
481 * XRegExp.escape('Escaped? <.>');
482 * // -> 'Escaped\?\ <\.>'
483 */
484    self.escape = function (str) {
485        return nativ.replace.call(str, /[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
486    };
487
488/**
489 * Executes a regex search in a specified string. Returns a match array or `null`. If the provided
490 * regex uses named capture, named backreference properties are included on the match array.
491 * Optional `pos` and `sticky` arguments specify the search start position, and whether the match
492 * must start at the specified position only. The `lastIndex` property of the provided regex is not
493 * used, but is updated for compatibility. Also fixes browser bugs compared to the native
494 * `RegExp.prototype.exec` and can be used reliably cross-browser.
495 * @memberOf XRegExp
496 * @param {String} str String to search.
497 * @param {RegExp} regex Regex to search with.
498 * @param {Number} [pos=0] Zero-based index at which to start the search.
499 * @param {Boolean|String} [sticky=false] Whether the match must start at the specified position
500 *   only. The string `'sticky'` is accepted as an alternative to `true`.
501 * @returns {Array} Match array with named backreference properties, or null.
502 * @example
503 *
504 * // Basic use, with named backreference
505 * var match = XRegExp.exec('U+2620', XRegExp('U\\+(?<hex>[0-9A-F]{4})'));
506 * match.hex; // -> '2620'
507 *
508 * // With pos and sticky, in a loop
509 * var pos = 2, result = [], match;
510 * while (match = XRegExp.exec('<1><2><3><4>5<6>', /<(\d)>/, pos, 'sticky')) {
511 *   result.push(match[1]);
512 *   pos = match.index + match[0].length;
513 * }
514 * // result -> ['2', '3', '4']
515 */
516    self.exec = function (str, regex, pos, sticky) {
517        var r2 = copy(regex, "g" + (sticky && hasNativeY ? "y" : ""), (sticky === false ? "y" : "")),
518            match;
519        r2.lastIndex = pos = pos || 0;
520        match = fixed.exec.call(r2, str); // Fixed `exec` required for `lastIndex` fix, etc.
521        if (sticky && match && match.index !== pos) {
522            match = null;
523        }
524        if (regex.global) {
525            regex.lastIndex = match ? r2.lastIndex : 0;
526        }
527        return match;
528    };
529
530/**
531 * Executes a provided function once per regex match.
532 * @memberOf XRegExp
533 * @param {String} str String to search.
534 * @param {RegExp} regex Regex to search with.
535 * @param {Function} callback Function to execute for each match. Invoked with four arguments:
536 *   <li>The match array, with named backreference properties.
537 *   <li>The zero-based match index.
538 *   <li>The string being traversed.
539 *   <li>The regex object being used to traverse the string.
540 * @param {*} [context] Object to use as `this` when executing `callback`.
541 * @returns {*} Provided `context` object.
542 * @example
543 *
544 * // Extracts every other digit from a string
545 * XRegExp.forEach('1a2345', /\d/, function (match, i) {
546 *   if (i % 2) this.push(+match[0]);
547 * }, []);
548 * // -> [2, 4]
549 */
550    self.forEach = function (str, regex, callback, context) {
551        var pos = 0,
552            i = -1,
553            match;
554        while ((match = self.exec(str, regex, pos))) {
555            callback.call(context, match, ++i, str, regex);
556            pos = match.index + (match[0].length || 1);
557        }
558        return context;
559    };
560
561/**
562 * Copies a regex object and adds flag `g`. The copy maintains special properties for named
563 * capture, is augmented with `XRegExp.prototype` methods, and has a fresh `lastIndex` property
564 * (set to zero). Native regexes are not recompiled using XRegExp syntax.
565 * @memberOf XRegExp
566 * @param {RegExp} regex Regex to globalize.
567 * @returns {RegExp} Copy of the provided regex with flag `g` added.
568 * @example
569 *
570 * var globalCopy = XRegExp.globalize(/regex/);
571 * globalCopy.global; // -> true
572 */
573    self.globalize = function (regex) {
574        return copy(regex, "g");
575    };
576
577/**
578 * Installs optional features according to the specified options.
579 * @memberOf XRegExp
580 * @param {Object|String} options Options object or string.
581 * @example
582 *
583 * // With an options object
584 * XRegExp.install({
585 *   // Overrides native regex methods with fixed/extended versions that support named
586 *   // backreferences and fix numerous cross-browser bugs
587 *   natives: true,
588 *
589 *   // Enables extensibility of XRegExp syntax and flags
590 *   extensibility: true
591 * });
592 *
593 * // With an options string
594 * XRegExp.install('natives extensibility');
595 *
596 * // Using a shortcut to install all optional features
597 * XRegExp.install('all');
598 */
599    self.install = function (options) {
600        options = prepareOptions(options);
601        if (!features.natives && options.natives) {
602            setNatives(true);
603        }
604        if (!features.extensibility && options.extensibility) {
605            setExtensibility(true);
606        }
607    };
608
609/**
610 * Checks whether an individual optional feature is installed.
611 * @memberOf XRegExp
612 * @param {String} feature Name of the feature to check. One of:
613 *   <li>`natives`
614 *   <li>`extensibility`
615 * @returns {Boolean} Whether the feature is installed.
616 * @example
617 *
618 * XRegExp.isInstalled('natives');
619 */
620    self.isInstalled = function (feature) {
621        return !!(features[feature]);
622    };
623
624/**
625 * Returns `true` if an object is a regex; `false` if it isn't. This works correctly for regexes
626 * created in another frame, when `instanceof` and `constructor` checks would fail.
627 * @memberOf XRegExp
628 * @param {*} value Object to check.
629 * @returns {Boolean} Whether the object is a `RegExp` object.
630 * @example
631 *
632 * XRegExp.isRegExp('string'); // -> false
633 * XRegExp.isRegExp(/regex/i); // -> true
634 * XRegExp.isRegExp(RegExp('^', 'm')); // -> true
635 * XRegExp.isRegExp(XRegExp('(?s).')); // -> true
636 */
637    self.isRegExp = function (value) {
638        return isType(value, "regexp");
639    };
640
641/**
642 * Retrieves the matches from searching a string using a chain of regexes that successively search
643 * within previous matches. The provided `chain` array can contain regexes and objects with `regex`
644 * and `backref` properties. When a backreference is specified, the named or numbered backreference
645 * is passed forward to the next regex or returned.
646 * @memberOf XRegExp
647 * @param {String} str String to search.
648 * @param {Array} chain Regexes that each search for matches within preceding results.
649 * @returns {Array} Matches by the last regex in the chain, or an empty array.
650 * @example
651 *
652 * // Basic usage; matches numbers within <b> tags
653 * XRegExp.matchChain('1 <b>2</b> 3 <b>4 a 56</b>', [
654 *   XRegExp('(?is)<b>.*?</b>'),
655 *   /\d+/
656 * ]);
657 * // -> ['2', '4', '56']
658 *
659 * // Passing forward and returning specific backreferences
660 * html = '<a href="http://xregexp.com/api/">XRegExp</a>\
661 *         <a href="http://www.google.com/">Google</a>';
662 * XRegExp.matchChain(html, [
663 *   {regex: /<a href="([^"]+)">/i, backref: 1},
664 *   {regex: XRegExp('(?i)^https?://(?<domain>[^/?#]+)'), backref: 'domain'}
665 * ]);
666 * // -> ['xregexp.com', 'www.google.com']
667 */
668    self.matchChain = function (str, chain) {
669        return (function recurseChain(values, level) {
670            var item = chain[level].regex ? chain[level] : {regex: chain[level]},
671                matches = [],
672                addMatch = function (match) {
673                    matches.push(item.backref ? (match[item.backref] || "") : match[0]);
674                },
675                i;
676            for (i = 0; i < values.length; ++i) {
677                self.forEach(values[i], item.regex, addMatch);
678            }
679            return ((level === chain.length - 1) || !matches.length) ?
680                    matches :
681                    recurseChain(matches, level + 1);
682        }([str], 0));
683    };
684
685/**
686 * Returns a new string with one or all matches of a pattern replaced. The pattern can be a string
687 * or regex, and the replacement can be a string or a function to be called for each match. To
688 * perform a global search and replace, use the optional `scope` argument or include flag `g` if
689 * using a regex. Replacement strings can use `${n}` for named and numbered backreferences.
690 * Replacement functions can use named backreferences via `arguments[0].name`. Also fixes browser
691 * bugs compared to the native `String.prototype.replace` and can be used reliably cross-browser.
692 * @memberOf XRegExp
693 * @param {String} str String to search.
694 * @param {RegExp|String} search Search pattern to be replaced.
695 * @param {String|Function} replacement Replacement string or a function invoked to create it.
696 *   Replacement strings can include special replacement syntax:
697 *     <li>$$ - Inserts a literal '$'.
698 *     <li>$&, $0 - Inserts the matched substring.
699 *     <li>$` - Inserts the string that precedes the matched substring (left context).
700 *     <li>$' - Inserts the string that follows the matched substring (right context).
701 *     <li>$n, $nn - Where n/nn are digits referencing an existent capturing group, inserts
702 *       backreference n/nn.
703 *     <li>${n} - Where n is a name or any number of digits that reference an existent capturing
704 *       group, inserts backreference n.
705 *   Replacement functions are invoked with three or more arguments:
706 *     <li>The matched substring (corresponds to $& above). Named backreferences are accessible as
707 *       properties of this first argument.
708 *     <li>0..n arguments, one for each backreference (corresponding to $1, $2, etc. above).
709 *     <li>The zero-based index of the match within the total search string.
710 *     <li>The total string being searched.
711 * @param {String} [scope='one'] Use 'one' to replace the first match only, or 'all'. If not
712 *   explicitly specified and using a regex with flag `g`, `scope` is 'all'.
713 * @returns {String} New string with one or all matches replaced.
714 * @example
715 *
716 * // Regex search, using named backreferences in replacement string
717 * var name = XRegExp('(?<first>\\w+) (?<last>\\w+)');
718 * XRegExp.replace('John Smith', name, '${last}, ${first}');
719 * // -> 'Smith, John'
720 *
721 * // Regex search, using named backreferences in replacement function
722 * XRegExp.replace('John Smith', name, function (match) {
723 *   return match.last + ', ' + match.first;
724 * });
725 * // -> 'Smith, John'
726 *
727 * // Global string search/replacement
728 * XRegExp.replace('RegExp builds RegExps', 'RegExp', 'XRegExp', 'all');
729 * // -> 'XRegExp builds XRegExps'
730 */
731    self.replace = function (str, search, replacement, scope) {
732        var isRegex = self.isRegExp(search),
733            search2 = search,
734            result;
735        if (isRegex) {
736            if (scope === undef && search.global) {
737                scope = "all"; // Follow flag g when `scope` isn't explicit
738            }
739            // Note that since a copy is used, `search`'s `lastIndex` isn't updated *during* replacement iterations
740            search2 = copy(search, scope === "all" ? "g" : "", scope === "all" ? "" : "g");
741        } else if (scope === "all") {
742            search2 = new RegExp(self.escape(String(search)), "g");
743        }
744        result = fixed.replace.call(String(str), search2, replacement); // Fixed `replace` required for named backreferences, etc.
745        if (isRegex && search.global) {
746            search.lastIndex = 0; // Fixes IE, Safari bug (last tested IE 9, Safari 5.1)
747        }
748        return result;
749    };
750
751/**
752 * Splits a string into an array of strings using a regex or string separator. Matches of the
753 * separator are not included in the result array. However, if `separator` is a regex that contains
754 * capturing groups, backreferences are spliced into the result each time `separator` is matched.
755 * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably
756 * cross-browser.
757 * @memberOf XRegExp
758 * @param {String} str String to split.
759 * @param {RegExp|String} separator Regex or string to use for separating the string.
760 * @param {Number} [limit] Maximum number of items to include in the result array.
761 * @returns {Array} Array of substrings.
762 * @example
763 *
764 * // Basic use
765 * XRegExp.split('a b c', ' ');
766 * // -> ['a', 'b', 'c']
767 *
768 * // With limit
769 * XRegExp.split('a b c', ' ', 2);
770 * // -> ['a', 'b']
771 *
772 * // Backreferences in result array
773 * XRegExp.split('..word1..', /([a-z]+)(\d+)/i);
774 * // -> ['..', 'word', '1', '..']
775 */
776    self.split = function (str, separator, limit) {
777        return fixed.split.call(str, separator, limit);
778    };
779
780/**
781 * Executes a regex search in a specified string. Returns `true` or `false`. Optional `pos` and
782 * `sticky` arguments specify the search start position, and whether the match must start at the
783 * specified position only. The `lastIndex` property of the provided regex is not used, but is
784 * updated for compatibility. Also fixes browser bugs compared to the native
785 * `RegExp.prototype.test` and can be used reliably cross-browser.
786 * @memberOf XRegExp
787 * @param {String} str String to search.
788 * @param {RegExp} regex Regex to search with.
789 * @param {Number} [pos=0] Zero-based index at which to start the search.
790 * @param {Boolean|String} [sticky=false] Whether the match must start at the specified position
791 *   only. The string `'sticky'` is accepted as an alternative to `true`.
792 * @returns {Boolean} Whether the regex matched the provided value.
793 * @example
794 *
795 * // Basic use
796 * XRegExp.test('abc', /c/); // -> true
797 *
798 * // With pos and sticky
799 * XRegExp.test('abc', /c/, 0, 'sticky'); // -> false
800 */
801    self.test = function (str, regex, pos, sticky) {
802        // Do this the easy way :-)
803        return !!self.exec(str, regex, pos, sticky);
804    };
805
806/**
807 * Uninstalls optional features according to the specified options.
808 * @memberOf XRegExp
809 * @param {Object|String} options Options object or string.
810 * @example
811 *
812 * // With an options object
813 * XRegExp.uninstall({
814 *   // Restores native regex methods
815 *   natives: true,
816 *
817 *   // Disables additional syntax and flag extensions
818 *   extensibility: true
819 * });
820 *
821 * // With an options string
822 * XRegExp.uninstall('natives extensibility');
823 *
824 * // Using a shortcut to uninstall all optional features
825 * XRegExp.uninstall('all');
826 */
827    self.uninstall = function (options) {
828        options = prepareOptions(options);
829        if (features.natives && options.natives) {
830            setNatives(false);
831        }
832        if (features.extensibility && options.extensibility) {
833            setExtensibility(false);
834        }
835    };
836
837/**
838 * Returns an XRegExp object that is the union of the given patterns. Patterns can be provided as
839 * regex objects or strings. Metacharacters are escaped in patterns provided as strings.
840 * Backreferences in provided regex objects are automatically renumbered to work correctly. Native
841 * flags used by provided regexes are ignored in favor of the `flags` argument.
842 * @memberOf XRegExp
843 * @param {Array} patterns Regexes and strings to combine.
844 * @param {String} [flags] Any combination of XRegExp flags.
845 * @returns {RegExp} Union of the provided regexes and strings.
846 * @example
847 *
848 * XRegExp.union(['a+b*c', /(dogs)\1/, /(cats)\1/], 'i');
849 * // -> /a\+b\*c|(dogs)\1|(cats)\2/i
850 *
851 * XRegExp.union([XRegExp('(?<pet>dogs)\\k<pet>'), XRegExp('(?<pet>cats)\\k<pet>')]);
852 * // -> XRegExp('(?<pet>dogs)\\k<pet>|(?<pet>cats)\\k<pet>')
853 */
854    self.union = function (patterns, flags) {
855        var parts = /(\()(?!\?)|\\([1-9]\d*)|\\[\s\S]|\[(?:[^\\\]]|\\[\s\S])*]/g,
856            numCaptures = 0,
857            numPriorCaptures,
858            captureNames,
859            rewrite = function (match, paren, backref) {
860                var name = captureNames[numCaptures - numPriorCaptures];
861                if (paren) { // Capturing group
862                    ++numCaptures;
863                    if (name) { // If the current capture has a name
864                        return "(?<" + name + ">";
865                    }
866                } else if (backref) { // Backreference
867                    return "\\" + (+backref + numPriorCaptures);
868                }
869                return match;
870            },
871            output = [],
872            pattern,
873            i;
874        if (!(isType(patterns, "array") && patterns.length)) {
875            throw new TypeError("patterns must be a nonempty array");
876        }
877        for (i = 0; i < patterns.length; ++i) {
878            pattern = patterns[i];
879            if (self.isRegExp(pattern)) {
880                numPriorCaptures = numCaptures;
881                captureNames = (pattern.xregexp && pattern.xregexp.captureNames) || [];
882                // Rewrite backreferences. Passing to XRegExp dies on octals and ensures patterns
883                // are independently valid; helps keep this simple. Named captures are put back
884                output.push(self(pattern.source).source.replace(parts, rewrite));
885            } else {
886                output.push(self.escape(pattern));
887            }
888        }
889        return self(output.join("|"), flags);
890    };
891
892/**
893 * The XRegExp version number.
894 * @static
895 * @memberOf XRegExp
896 * @type String
897 */
898    self.version = "2.0.0";
899
900/*--------------------------------------
901 *  Fixed/extended native methods
902 *------------------------------------*/
903
904/**
905 * Adds named capture support (with backreferences returned as `result.name`), and fixes browser
906 * bugs in the native `RegExp.prototype.exec`. Calling `XRegExp.install('natives')` uses this to
907 * override the native method. Use via `XRegExp.exec` without overriding natives.
908 * @private
909 * @param {String} str String to search.
910 * @returns {Array} Match array with named backreference properties, or null.
911 */
912    fixed.exec = function (str) {
913        var match, name, r2, origLastIndex, i;
914        if (!this.global) {
915            origLastIndex = this.lastIndex;
916        }
917        match = nativ.exec.apply(this, arguments);
918        if (match) {
919            // Fix browsers whose `exec` methods don't consistently return `undefined` for
920            // nonparticipating capturing groups
921            if (!compliantExecNpcg && match.length > 1 && lastIndexOf(match, "") > -1) {
922                r2 = new RegExp(this.source, nativ.replace.call(getNativeFlags(this), "g", ""));
923                // Using `str.slice(match.index)` rather than `match[0]` in case lookahead allowed
924                // matching due to characters outside the match
925                nativ.replace.call(String(str).slice(match.index), r2, function () {
926                    var i;
927                    for (i = 1; i < arguments.length - 2; ++i) {
928                        if (arguments[i] === undef) {
929                            match[i] = undef;
930                        }
931                    }
932                });
933            }
934            // Attach named capture properties
935            if (this.xregexp && this.xregexp.captureNames) {
936                for (i = 1; i < match.length; ++i) {
937                    name = this.xregexp.captureNames[i - 1];
938                    if (name) {
939                        match[name] = match[i];
940                    }
941                }
942            }
943            // Fix browsers that increment `lastIndex` after zero-length matches
944            if (this.global && !match[0].length && (this.lastIndex > match.index)) {
945                this.lastIndex = match.index;
946            }
947        }
948        if (!this.global) {
949            this.lastIndex = origLastIndex; // Fixes IE, Opera bug (last tested IE 9, Opera 11.6)
950        }
951        return match;
952    };
953
954/**
955 * Fixes browser bugs in the native `RegExp.prototype.test`. Calling `XRegExp.install('natives')`
956 * uses this to override the native method.
957 * @private
958 * @param {String} str String to search.
959 * @returns {Boolean} Whether the regex matched the provided value.
960 */
961    fixed.test = function (str) {
962        // Do this the easy way :-)
963        return !!fixed.exec.call(this, str);
964    };
965
966/**
967 * Adds named capture support (with backreferences returned as `result.name`), and fixes browser
968 * bugs in the native `String.prototype.match`. Calling `XRegExp.install('natives')` uses this to
969 * override the native method.
970 * @private
971 * @param {RegExp} regex Regex to search with.
972 * @returns {Array} If `regex` uses flag g, an array of match strings or null. Without flag g, the
973 *   result of calling `regex.exec(this)`.
974 */
975    fixed.match = function (regex) {
976        if (!self.isRegExp(regex)) {
977            regex = new RegExp(regex); // Use native `RegExp`
978        } else if (regex.global) {
979            var result = nativ.match.apply(this, arguments);
980            regex.lastIndex = 0; // Fixes IE bug
981            return result;
982        }
983        return fixed.exec.call(regex, this);
984    };
985
986/**
987 * Adds support for `${n}` tokens for named and numbered backreferences in replacement text, and
988 * provides named backreferences to replacement functions as `arguments[0].name`. Also fixes
989 * browser bugs in replacement text syntax when performing a replacement using a nonregex search
990 * value, and the value of a replacement regex's `lastIndex` property during replacement iterations
991 * and upon completion. Note that this doesn't support SpiderMonkey's proprietary third (`flags`)
992 * argument. Calling `XRegExp.install('natives')` uses this to override the native method. Use via
993 * `XRegExp.replace` without overriding natives.
994 * @private
995 * @param {RegExp|String} search Search pattern to be replaced.
996 * @param {String|Function} replacement Replacement string or a function invoked to create it.
997 * @returns {String} New string with one or all matches replaced.
998 */
999    fixed.replace = function (search, replacement) {
1000        var isRegex = self.isRegExp(search), captureNames, result, str, origLastIndex;
1001        if (isRegex) {
1002            if (search.xregexp) {
1003                captureNames = search.xregexp.captureNames;
1004            }
1005            if (!search.global) {
1006                origLastIndex = search.lastIndex;
1007            }
1008        } else {
1009            search += "";
1010        }
1011        if (isType(replacement, "function")) {
1012            result = nativ.replace.call(String(this), search, function () {
1013                var args = arguments, i;
1014                if (captureNames) {
1015                    // Change the `arguments[0]` string primitive to a `String` object that can store properties
1016                    args[0] = new String(args[0]);
1017                    // Store named backreferences on the first argument
1018                    for (i = 0; i < captureNames.length; ++i) {
1019                        if (captureNames[i]) {
1020                            args[0][captureNames[i]] = args[i + 1];
1021                        }
1022                    }
1023                }
1024                // Update `lastIndex` before calling `replacement`.
1025                // Fixes IE, Chrome, Firefox, Safari bug (last tested IE 9, Chrome 17, Firefox 11, Safari 5.1)
1026                if (isRegex && search.global) {
1027                    search.lastIndex = args[args.length - 2] + args[0].length;
1028                }
1029                return replacement.apply(null, args);
1030            });
1031        } else {
1032            str = String(this); // Ensure `args[args.length - 1]` will be a string when given nonstring `this`
1033            result = nativ.replace.call(str, search, function () {
1034                var args = arguments; // Keep this function's `arguments` available through closure
1035                return nativ.replace.call(String(replacement), replacementToken, function ($0, $1, $2) {
1036                    var n;
1037                    // Named or numbered backreference with curly brackets
1038                    if ($1) {
1039                        /* XRegExp behavior for `${n}`:
1040                         * 1. Backreference to numbered capture, where `n` is 1+ digits. `0`, `00`, etc. is the entire match.
1041                         * 2. Backreference to named capture `n`, if it exists and is not a number overridden by numbered capture.
1042                         * 3. Otherwise, it's an error.
1043                         */
1044                        n = +$1; // Type-convert; drop leading zeros
1045                        if (n <= args.length - 3) {
1046                            return args[n] || "";
1047                        }
1048                        n = captureNames ? lastIndexOf(captureNames, $1) : -1;
1049                        if (n < 0) {
1050                            throw new SyntaxError("backreference to undefined group " + $0);
1051                        }
1052                        return args[n + 1] || "";
1053                    }
1054                    // Else, special variable or numbered backreference (without curly brackets)
1055                    if ($2 === "$") return "$";
1056                    if ($2 === "&" || +$2 === 0) return args[0]; // $&, $0 (not followed by 1-9), $00
1057                    if ($2 === "`") return args[args.length - 1].slice(0, args[args.length - 2]);
1058                    if ($2 === "'") return args[args.length - 1].slice(args[args.length - 2] + args[0].length);
1059                    // Else, numbered backreference (without curly brackets)
1060                    $2 = +$2; // Type-convert; drop leading zero
1061                    /* XRegExp behavior:
1062                     * - Backreferences without curly brackets end after 1 or 2 digits. Use `${..}` for more digits.
1063                     * - `$1` is an error if there are no capturing groups.
1064                     * - `$10` is an error if there are less than 10 capturing groups. Use `${1}0` instead.
1065                     * - `$01` is equivalent to `$1` if a capturing group exists, otherwise it's an error.
1066                     * - `$0` (not followed by 1-9), `$00`, and `$&` are the entire match.
1067                     * Native behavior, for comparison:
1068                     * - Backreferences end after 1 or 2 digits. Cannot use backreference to capturing group 100+.
1069                     * - `$1` is a literal `$1` if there are no capturing groups.
1070                     * - `$10` is `$1` followed by a literal `0` if there are less than 10 capturing groups.
1071                     * - `$01` is equivalent to `$1` if a capturing group exists, otherwise it's a literal `$01`.
1072                     * - `$0` is a literal `$0`. `$&` is the entire match.
1073                     */
1074                    if (!isNaN($2)) {
1075                        if ($2 > args.length - 3) {
1076                            throw new SyntaxError("backreference to undefined group " + $0);
1077                        }
1078                        return args[$2] || "";
1079                    }
1080                    throw new SyntaxError("invalid token " + $0);
1081                });
1082            });
1083        }
1084        if (isRegex) {
1085            if (search.global) {
1086                search.lastIndex = 0; // Fixes IE, Safari bug (last tested IE 9, Safari 5.1)
1087            } else {
1088                search.lastIndex = origLastIndex; // Fixes IE, Opera bug (last tested IE 9, Opera 11.6)
1089            }
1090        }
1091        return result;
1092    };
1093
1094/**
1095 * Fixes browser bugs in the native `String.prototype.split`. Calling `XRegExp.install('natives')`
1096 * uses this to override the native method. Use via `XRegExp.split` without overriding natives.
1097 * @private
1098 * @param {RegExp|String} separator Regex or string to use for separating the string.
1099 * @param {Number} [limit] Maximum number of items to include in the result array.
1100 * @returns {Array} Array of substrings.
1101 */
1102    fixed.split = function (separator, limit) {
1103        if (!self.isRegExp(separator)) {
1104            return nativ.split.apply(this, arguments); // use faster native method
1105        }
1106        var str = String(this),
1107            origLastIndex = separator.lastIndex,
1108            output = [],
1109            lastLastIndex = 0,
1110            lastLength;
1111        /* Values for `limit`, per the spec:
1112         * If undefined: pow(2,32) - 1
1113         * If 0, Infinity, or NaN: 0
1114         * If positive number: limit = floor(limit); if (limit >= pow(2,32)) limit -= pow(2,32);
1115         * If negative number: pow(2,32) - floor(abs(limit))
1116         * If other: Type-convert, then use the above rules
1117         */
1118        limit = (limit === undef ? -1 : limit) >>> 0;
1119        self.forEach(str, separator, function (match) {
1120            if ((match.index + match[0].length) > lastLastIndex) { // != `if (match[0].length)`
1121                output.push(str.slice(lastLastIndex, match.index));
1122                if (match.length > 1 && match.index < str.length) {
1123                    Array.prototype.push.apply(output, match.slice(1));
1124                }
1125                lastLength = match[0].length;
1126                lastLastIndex = match.index + lastLength;
1127            }
1128        });
1129        if (lastLastIndex === str.length) {
1130            if (!nativ.test.call(separator, "") || lastLength) {
1131                output.push("");
1132            }
1133        } else {
1134            output.push(str.slice(lastLastIndex));
1135        }
1136        separator.lastIndex = origLastIndex;
1137        return output.length > limit ? output.slice(0, limit) : output;
1138    };
1139
1140/*--------------------------------------
1141 *  Built-in tokens
1142 *------------------------------------*/
1143
1144// Shortcut
1145    add = addToken.on;
1146
1147/* Letter identity escapes that natively match literal characters: \p, \P, etc.
1148 * Should be SyntaxErrors but are allowed in web reality. XRegExp makes them errors for cross-
1149 * browser consistency and to reserve their syntax, but lets them be superseded by XRegExp addons.
1150 */
1151    add(/\\([ABCE-RTUVXYZaeg-mopqyz]|c(?![A-Za-z])|u(?![\dA-Fa-f]{4})|x(?![\dA-Fa-f]{2}))/,
1152        function (match, scope) {
1153            // \B is allowed in default scope only
1154            if (match[1] === "B" && scope === defaultScope) {
1155                return match[0];
1156            }
1157            throw new SyntaxError("invalid escape " + match[0]);
1158        },
1159        {scope: "all"});
1160
1161/* Empty character class: [] or [^]
1162 * Fixes a critical cross-browser syntax inconsistency. Unless this is standardized (per the spec),
1163 * regex syntax can't be accurately parsed because character class endings can't be determined.
1164 */
1165    add(/\[(\^?)]/,
1166        function (match) {
1167            // For cross-browser compatibility with ES3, convert [] to \b\B and [^] to [\s\S].
1168            // (?!) should work like \b\B, but is unreliable in Firefox
1169            return match[1] ? "[\\s\\S]" : "\\b\\B";
1170        });
1171
1172/* Comment pattern: (?# )
1173 * Inline comments are an alternative to the line comments allowed in free-spacing mode (flag x).
1174 */
1175    add(/(?:\(\?#[^)]*\))+/,
1176        function (match) {
1177            // Keep tokens separated unless the following token is a quantifier
1178            return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)";
1179        });
1180
1181/* Named backreference: \k<name>
1182 * Backreference names can use the characters A-Z, a-z, 0-9, _, and $ only.
1183 */
1184    add(/\\k<([\w$]+)>/,
1185        function (match) {
1186            var index = isNaN(match[1]) ? (lastIndexOf(this.captureNames, match[1]) + 1) : +match[1],
1187                endIndex = match.index + match[0].length;
1188            if (!index || index > this.captureNames.length) {
1189                throw new SyntaxError("backreference to undefined group " + match[0]);
1190            }
1191            // Keep backreferences separate from subsequent literal numbers
1192            return "\\" + index + (
1193                endIndex === match.input.length || isNaN(match.input.charAt(endIndex)) ? "" : "(?:)"
1194            );
1195        });
1196
1197/* Whitespace and line comments, in free-spacing mode (aka extended mode, flag x) only.
1198 */
1199    add(/(?:\s+|#.*)+/,
1200        function (match) {
1201            // Keep tokens separated unless the following token is a quantifier
1202            return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)";
1203        },
1204        {
1205            trigger: function () {
1206                return this.hasFlag("x");
1207            },
1208            customFlags: "x"
1209        });
1210
1211/* Dot, in dotall mode (aka singleline mode, flag s) only.
1212 */
1213    add(/\./,
1214        function () {
1215            return "[\\s\\S]";
1216        },
1217        {
1218            trigger: function () {
1219                return this.hasFlag("s");
1220            },
1221            customFlags: "s"
1222        });
1223
1224/* Named capturing group; match the opening delimiter only: (?<name>
1225 * Capture names can use the characters A-Z, a-z, 0-9, _, and $ only. Names can't be integers.
1226 * Supports Python-style (?P<name> as an alternate syntax to avoid issues in recent Opera (which
1227 * natively supports the Python-style syntax). Otherwise, XRegExp might treat numbered
1228 * backreferences to Python-style named capture as octals.
1229 */
1230    add(/\(\?P?<([\w$]+)>/,
1231        function (match) {
1232            if (!isNaN(match[1])) {
1233                // Avoid incorrect lookups, since named backreferences are added to match arrays
1234                throw new SyntaxError("can't use integer as capture name " + match[0]);
1235            }
1236            this.captureNames.push(match[1]);
1237            this.hasNamedCapture = true;
1238            return "(";
1239        });
1240
1241/* Numbered backreference or octal, plus any following digits: \0, \11, etc.
1242 * Octals except \0 not followed by 0-9 and backreferences to unopened capture groups throw an
1243 * error. Other matches are returned unaltered. IE <= 8 doesn't support backreferences greater than
1244 * \99 in regex syntax.
1245 */
1246    add(/\\(\d+)/,
1247        function (match, scope) {
1248            if (!(scope === defaultScope && /^[1-9]/.test(match[1]) && +match[1] <= this.captureNames.length) &&
1249                    match[1] !== "0") {
1250                throw new SyntaxError("can't use octal escape or backreference to undefined group " + match[0]);
1251            }
1252            return match[0];
1253        },
1254        {scope: "all"});
1255
1256/* Capturing group; match the opening parenthesis only.
1257 * Required for support of named capturing groups. Also adds explicit capture mode (flag n).
1258 */
1259    add(/\((?!\?)/,
1260        function () {
1261            if (this.hasFlag("n")) {
1262                return "(?:";
1263            }
1264            this.captureNames.push(null);
1265            return "(";
1266        },
1267        {customFlags: "n"});
1268
1269/*--------------------------------------
1270 *  Expose XRegExp
1271 *------------------------------------*/
1272
1273// For CommonJS enviroments
1274    if (typeof exports !== "undefined") {
1275        exports.XRegExp = self;
1276    }
1277
1278    return self;
1279
1280}());
1281
1282//
1283// Begin anonymous function. This is used to contain local scope variables without polutting global scope.
1284//
1285if (typeof(SyntaxHighlighter) == 'undefined') var SyntaxHighlighter = function() {
1286
1287// CommonJS
1288if (typeof(require) != 'undefined' && typeof(XRegExp) == 'undefined')
1289{
1290	XRegExp = require('xregexp').XRegExp;
1291}
1292
1293// Shortcut object which will be assigned to the SyntaxHighlighter variable.
1294// This is a shorthand for local reference in order to avoid long namespace
1295// references to SyntaxHighlighter.whatever...
1296var sh = {
1297	defaults : {
1298		/** Additional CSS class names to be added to highlighter elements. */
1299		'class-name' : '',
1300
1301		/** First line number. */
1302		'first-line' : 1,
1303
1304		/**
1305		 * Pads line numbers. Possible values are:
1306		 *
1307		 *   false - don't pad line numbers.
1308		 *   true  - automaticaly pad numbers with minimum required number of leading zeroes.
1309		 *   [int] - length up to which pad line numbers.
1310		 */
1311		'pad-line-numbers' : false,
1312
1313		/** Lines to highlight. */
1314		'highlight' : null,
1315
1316		/** Title to be displayed above the code block. */
1317		'title' : null,
1318
1319		/** Enables or disables smart tabs. */
1320		'smart-tabs' : true,
1321
1322		/** Gets or sets tab size. */
1323		'tab-size' : 4,
1324
1325		/** Enables or disables gutter. */
1326		'gutter' : true,
1327
1328		/** Enables or disables toolbar. */
1329		'toolbar' : true,
1330
1331		/** Enables quick code copy and paste from double click. */
1332		'quick-code' : true,
1333
1334		/** Forces code view to be collapsed. */
1335		'collapse' : false,
1336
1337		/** Enables or disables automatic links. */
1338		'auto-links' : true,
1339
1340		/** Gets or sets light mode. Equavalent to turning off gutter and toolbar. */
1341		'light' : false,
1342
1343		'unindent' : true,
1344
1345		'html-script' : false
1346	},
1347
1348	config : {
1349		space : '&nbsp;',
1350
1351		/** Enables use of <SCRIPT type="syntaxhighlighter" /> tags. */
1352		useScriptTags : true,
1353
1354		/** Blogger mode flag. */
1355		bloggerMode : false,
1356
1357		stripBrs : false,
1358
1359		/** Name of the tag that SyntaxHighlighter will automatically look for. */
1360		tagName : 'pre',
1361
1362		strings : {
1363			expandSource : 'expand source',
1364			help : '?',
1365			alert: 'SyntaxHighlighter\n\n',
1366			noBrush : 'Can\'t find brush for: ',
1367			brushNotHtmlScript : 'Brush wasn\'t configured for html-script option: ',
1368
1369			// this is populated by the build script
1370			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.90 (Sat, 18 Jun 2016 21:01:41 GMT)</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-2013 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>'
1371		}
1372	},
1373
1374	/** Internal 'global' variables. */
1375	vars : {
1376		discoveredBrushes : null,
1377		highlighters : {}
1378	},
1379
1380	/** This object is populated by user included external brush files. */
1381	brushes : {},
1382
1383	/** Common regular expressions. */
1384	regexLib : {
1385		multiLineCComments			: XRegExp('/\\*.*?\\*/', 'gs'),
1386		singleLineCComments			: /\/\/.*$/gm,
1387		singleLinePerlComments		: /#.*$/gm,
1388		doubleQuotedString			: /"([^\\"\n]|\\.)*"/g,
1389		singleQuotedString			: /'([^\\'\n]|\\.)*'/g,
1390		multiLineDoubleQuotedString	: XRegExp('"([^\\\\"]|\\\\.)*"', 'gs'),
1391		multiLineSingleQuotedString	: XRegExp("'([^\\\\']|\\\\.)*'", 'gs'),
1392		xmlComments					: XRegExp('(&lt;|<)!--.*?--(&gt;|>)', 'gs'),
1393		url							: /\w+:\/\/[\w-.\/?%&=:@;#]*/g,
1394		phpScriptTags 				: { left: /(&lt;|<)\?(?:=|php)?/g, right: /\?(&gt;|>)/g, 'eof' : true },
1395		aspScriptTags				: { left: /(&lt;|<)%=?/g, right: /%(&gt;|>)/g },
1396		scriptScriptTags			: { left: /(&lt;|<)\s*script.*?(&gt;|>)/gi, right: /(&lt;|<)\/\s*script\s*(&gt;|>)/gi }
1397	},
1398
1399	toolbar: {
1400		/**
1401		 * Generates HTML markup for the toolbar.
1402		 * @param {Highlighter} highlighter Highlighter instance.
1403		 * @return {String} Returns HTML markup.
1404		 */
1405		getHtml: function(highlighter)
1406		{
1407			var html = '<div class="toolbar">',
1408				items = sh.toolbar.items,
1409				list = items.list
1410				;
1411
1412			function defaultGetHtml(highlighter, name)
1413			{
1414				return sh.toolbar.getButtonHtml(highlighter, name, sh.config.strings[name]);
1415			}
1416
1417			for (var i = 0, l = list.length; i < l; i++)
1418			{
1419				html += (items[list[i]].getHtml || defaultGetHtml)(highlighter, list[i]);
1420			}
1421
1422			html += '</div>';
1423
1424			return html;
1425		},
1426
1427		/**
1428		 * Generates HTML markup for a regular button in the toolbar.
1429		 * @param {Highlighter} highlighter Highlighter instance.
1430		 * @param {String} commandName		Command name that would be executed.
1431		 * @param {String} label			Label text to display.
1432		 * @return {String}					Returns HTML markup.
1433		 */
1434		getButtonHtml: function(highlighter, commandName, label)
1435		{
1436			commandName = escapeHtml(commandName);
1437
1438			return '<span><a href="#" class="toolbar_item'
1439				+ ' command_' + commandName
1440				+ ' ' + commandName
1441				+ '">' + escapeHtml(label) + '</a></span>'
1442				;
1443		},
1444
1445		/**
1446		 * Event handler for a toolbar anchor.
1447		 */
1448		handler: function(e)
1449		{
1450			var target = e.target,
1451				className = target.className || ''
1452				;
1453
1454			function getValue(name)
1455			{
1456				var r = new RegExp(name + '_(\\w+)'),
1457					match = r.exec(className)
1458					;
1459
1460				return match ? match[1] : null;
1461			}
1462
1463			var highlighter = getHighlighterById(findParentElement(target, '.syntaxhighlighter').id),
1464				commandName = getValue('command')
1465				;
1466
1467			// execute the toolbar command
1468			if (highlighter && commandName)
1469				sh.toolbar.items[commandName].execute(highlighter);
1470
1471			// disable default A click behaviour
1472			e.preventDefault();
1473		},
1474
1475		/** Collection of toolbar items. */
1476		items : {
1477			// Ordered lis of items in the toolbar. Can't expect `for (var n in items)` to be consistent.
1478			list: ['expandSource', 'help'],
1479
1480			expandSource: {
1481				getHtml: function(highlighter)
1482				{
1483					if (highlighter.getParam('collapse') != true)
1484						return '';
1485
1486					var title = highlighter.getParam('title');
1487					return sh.toolbar.getButtonHtml(highlighter, 'expandSource', title ? title : sh.config.strings.expandSource);
1488				},
1489
1490				execute: function(highlighter)
1491				{
1492					var div = getHighlighterDivById(highlighter.id);
1493					removeClass(div, 'collapsed');
1494				}
1495			},
1496
1497			/** Command to display the about dialog window. */
1498			help: {
1499				execute: function(highlighter)
1500				{
1501					var wnd = popup('', '_blank', 500, 250, 'scrollbars=0'),
1502						doc = wnd.document
1503						;
1504
1505					doc.write(sh.config.strings.aboutDialog);
1506					doc.close();
1507					wnd.focus();
1508				}
1509			}
1510		}
1511	},
1512
1513	/**
1514	 * Finds all elements on the page which should be processes by SyntaxHighlighter.
1515	 *
1516	 * @param {Object} globalParams		Optional parameters which override element's
1517	 * 									parameters. Only used if element is specified.
1518	 *
1519	 * @param {Object} element	Optional element to highlight. If none is
1520	 * 							provided, all elements in the current document
1521	 * 							are returned which qualify.
1522	 *
1523	 * @return {Array}	Returns list of <code>{ target: DOMElement, params: Object }</code> objects.
1524	 */
1525	findElements: function(globalParams, element)
1526	{
1527		var elements = element ? [element] : toArray(document.getElementsByTagName(sh.config.tagName)),
1528			conf = sh.config,
1529			result = []
1530			;
1531
1532		// support for <SCRIPT TYPE="syntaxhighlighter" /> feature
1533		if (conf.useScriptTags)
1534			elements = elements.concat(getSyntaxHighlighterScriptTags());
1535
1536		if (elements.length === 0)
1537			return result;
1538
1539		for (var i = 0, l = elements.length; i < l; i++)
1540		{
1541			var item = {
1542				target: elements[i],
1543				// local params take precedence over globals
1544				params: merge(globalParams, parseParams(elements[i].className))
1545			};
1546
1547			if (item.params['brush'] == null)
1548				continue;
1549
1550			result.push(item);
1551		}
1552
1553		return result;
1554	},
1555
1556	/**
1557	 * Shorthand to highlight all elements on the page that are marked as
1558	 * SyntaxHighlighter source code.
1559	 *
1560	 * @param {Object} globalParams		Optional parameters which override element's
1561	 * 									parameters. Only used if element is specified.
1562	 *
1563	 * @param {Object} element	Optional element to highlight. If none is
1564	 * 							provided, all elements in the current document
1565	 * 							are highlighted.
1566	 */
1567	highlight: function(globalParams, element)
1568	{
1569		var elements = this.findElements(globalParams, element),
1570			propertyName = 'innerHTML',
1571			highlighter = null,
1572			conf = sh.config
1573			;
1574
1575		if (elements.length === 0)
1576			return;
1577
1578		for (var i = 0, l = elements.length; i < l; i++)
1579		{
1580			var element = elements[i],
1581				target = element.target,
1582				params = element.params,
1583				brushName = params.brush,
1584				code
1585				;
1586
1587			if (brushName == null)
1588				continue;
1589
1590			// Instantiate a brush
1591			if (params['html-script'] == 'true' || sh.defaults['html-script'] == true)
1592			{
1593				highlighter = new sh.HtmlScript(brushName);
1594				brushName = 'htmlscript';
1595			}
1596			else
1597			{
1598				var brush = findBrush(brushName);
1599
1600				if (brush)
1601					highlighter = new brush();
1602				else
1603					continue;
1604			}
1605
1606			code = target[propertyName];
1607
1608			// remove CDATA from <SCRIPT/> tags if it's present
1609			if (conf.useScriptTags)
1610				code = stripCData(code);
1611
1612			// Inject title if the attribute is present
1613			if ((target.title || '') != '')
1614				params.title = target.title;
1615
1616			params['brush'] = brushName;
1617			highlighter.init(params);
1618			element = highlighter.getDiv(code);
1619
1620			// carry over ID
1621			if ((target.id || '') != '')
1622				element.id = target.id;
1623
1624			target.parentNode.replaceChild(element, target);
1625		}
1626	},
1627
1628	/**
1629	 * Main entry point for the SyntaxHighlighter.
1630	 * @param {Object} params Optional params to apply to all highlighted elements.
1631	 */
1632	all: function(params)
1633	{
1634		attachEvent(
1635			window,
1636			'load',
1637			function() { sh.highlight(params); }
1638		);
1639	}
1640}; // end of sh
1641
1642function escapeHtml(html)
1643{
1644	return document.createElement('div').appendChild(document.createTextNode(html)).parentNode.innerHTML.replace(/"/g, '&quot;');
1645};
1646
1647/**
1648 * Checks if target DOM elements has specified CSS class.
1649 * @param {DOMElement} target Target DOM element to check.
1650 * @param {String} className Name of the CSS class to check for.
1651 * @return {Boolean} Returns true if class name is present, false otherwise.
1652 */
1653function hasClass(target, className)
1654{
1655	return target.className.indexOf(className) != -1;
1656};
1657
1658/**
1659 * Adds CSS class name to the target DOM element.
1660 * @param {DOMElement} target Target DOM element.
1661 * @param {String} className New CSS class to add.
1662 */
1663function addClass(target, className)
1664{
1665	if (!hasClass(target, className))
1666		target.className += ' ' + className;
1667};
1668
1669/**
1670 * Removes CSS class name from the target DOM element.
1671 * @param {DOMElement} target Target DOM element.
1672 * @param {String} className CSS class to remove.
1673 */
1674function removeClass(target, className)
1675{
1676	target.className = target.className.replace(className, '');
1677};
1678
1679/**
1680 * Converts the source to array object. Mostly used for function arguments and
1681 * lists returned by getElementsByTagName() which aren't Array objects.
1682 * @param {List} source Source list.
1683 * @return {Array} Returns array.
1684 */
1685function toArray(source)
1686{
1687	var result = [];
1688
1689	for (var i = 0, l = source.length; i < l; i++)
1690		result.push(source[i]);
1691
1692	return result;
1693};
1694
1695/**
1696 * Splits block of text into lines.
1697 * @param {String} block Block of text.
1698 * @return {Array} Returns array of lines.
1699 */
1700function splitLines(block)
1701{
1702	return block.split(/\r?\n/);
1703}
1704
1705/**
1706 * Generates HTML ID for the highlighter.
1707 * @param {String} highlighterId Highlighter ID.
1708 * @return {String} Returns HTML ID.
1709 */
1710function getHighlighterId(id)
1711{
1712	var prefix = 'highlighter_';
1713	return id.indexOf(prefix) == 0 ? id : prefix + id;
1714};
1715
1716/**
1717 * Finds Highlighter instance by ID.
1718 * @param {String} highlighterId Highlighter ID.
1719 * @return {Highlighter} Returns instance of the highlighter.
1720 */
1721function getHighlighterById(id)
1722{
1723	return sh.vars.highlighters[getHighlighterId(id)];
1724};
1725
1726/**
1727 * Finds highlighter's DIV container.
1728 * @param {String} highlighterId Highlighter ID.
1729 * @return {Element} Returns highlighter's DIV element.
1730 */
1731function getHighlighterDivById(id)
1732{
1733	return document.getElementById(getHighlighterId(id));
1734};
1735
1736/**
1737 * Stores highlighter so that getHighlighterById() can do its thing. Each
1738 * highlighter must call this method to preserve itself.
1739 * @param {Highilghter} highlighter Highlighter instance.
1740 */
1741function storeHighlighter(highlighter)
1742{
1743	sh.vars.highlighters[getHighlighterId(highlighter.id)] = highlighter;
1744};
1745
1746/**
1747 * Looks for a child or parent node which has specified classname.
1748 * Equivalent to jQuery's $(container).find(".className")
1749 * @param {Element} target Target element.
1750 * @param {String} search Class name or node name to look for.
1751 * @param {Boolean} reverse If set to true, will go up the node tree instead of down.
1752 * @return {Element} Returns found child or parent element on null.
1753 */
1754function findElement(target, search, reverse /* optional */)
1755{
1756	if (target == null)
1757		return null;
1758
1759	var nodes			= reverse != true ? target.childNodes : [ target.parentNode ],
1760		propertyToFind	= { '#' : 'id', '.' : 'className' }[search.substr(0, 1)] || 'nodeName',
1761		expectedValue,
1762		found
1763		;
1764
1765	expectedValue = propertyToFind != 'nodeName'
1766		? search.substr(1)
1767		: search.toUpperCase()
1768		;
1769
1770	// main return of the found node
1771	if ((target[propertyToFind] || '').indexOf(expectedValue) != -1)
1772		return target;
1773
1774	for (var i = 0, l = nodes.length; nodes && i < l && found == null; i++)
1775		found = findElement(nodes[i], search, reverse);
1776
1777	return found;
1778};
1779
1780/**
1781 * Looks for a parent node which has specified classname.
1782 * This is an alias to <code>findElement(container, className, true)</code>.
1783 * @param {Element} target Target element.
1784 * @param {String} className Class name to look for.
1785 * @return {Element} Returns found parent element on null.
1786 */
1787function findParentElement(target, className)
1788{
1789	return findElement(target, className, true);
1790};
1791
1792/**
1793 * Finds an index of element in the array.
1794 * @ignore
1795 * @param {Object} searchElement
1796 * @param {Number} fromIndex
1797 * @return {Number} Returns index of element if found; -1 otherwise.
1798 */
1799function indexOf(array, searchElement, fromIndex)
1800{
1801	fromIndex = Math.max(fromIndex || 0, 0);
1802
1803	for (var i = fromIndex, l = array.length; i < l; i++)
1804		if(array[i] == searchElement)
1805			return i;
1806
1807	return -1;
1808};
1809
1810/**
1811 * Generates a unique element ID.
1812 */
1813function guid(prefix)
1814{
1815	return (prefix || '') + Math.round(Math.random() * 1000000).toString();
1816};
1817
1818/**
1819 * Merges two objects. Values from obj2 override values in obj1.
1820 * Function is NOT recursive and works only for one dimensional objects.
1821 * @param {Object} obj1 First object.
1822 * @param {Object} obj2 Second object.
1823 * @return {Object} Returns combination of both objects.
1824 */
1825function merge(obj1, obj2)
1826{
1827	var result = {}, name;
1828
1829	for (name in obj1)
1830		result[name] = obj1[name];
1831
1832	for (name in obj2)
1833		result[name] = obj2[name];
1834
1835	return result;
1836};
1837
1838/**
1839 * Attempts to convert string to boolean.
1840 * @param {String} value Input string.
1841 * @return {Boolean} Returns true if input was "true", false if input was "false" and value otherwise.
1842 */
1843function toBoolean(value)
1844{
1845	var result = { "true" : true, "false" : false }[value];
1846	return result == null ? value : result;
1847};
1848
1849/**
1850 * Opens up a centered popup window.
1851 * @param {String} url		URL to open in the window.
1852 * @param {String} name		Popup name.
1853 * @param {int} width		Popup width.
1854 * @param {int} height		Popup height.
1855 * @param {String} options	window.open() options.
1856 * @return {Window}			Returns window instance.
1857 */
1858function popup(url, name, width, height, options)
1859{
1860	var x = (screen.width - width) / 2,
1861		y = (screen.height - height) / 2
1862		;
1863
1864	options +=	', left=' + x +
1865				', top=' + y +
1866				', width=' + width +
1867				', height=' + height
1868		;
1869	options = options.replace(/^,/, '');
1870
1871	var win = window.open(url, name, options);
1872	win.focus();
1873	return win;
1874};
1875
1876/**
1877 * Adds event handler to the target object.
1878 * @param {Object} obj		Target object.
1879 * @param {String} type		Name of the event.
1880 * @param {Function} func	Handling function.
1881 */
1882function attachEvent(obj, type, func, scope)
1883{
1884	function handler(e)
1885	{
1886		e = e || window.event;
1887
1888		if (!e.target)
1889		{
1890			e.target = e.srcElement;
1891			e.preventDefault = function()
1892			{
1893				this.returnValue = false;
1894			};
1895		}
1896
1897		func.call(scope || window, e);
1898	};
1899
1900	if (obj.attachEvent)
1901	{
1902		obj.attachEvent('on' + type, handler);
1903	}
1904	else
1905	{
1906		obj.addEventListener(type, handler, false);
1907	}
1908};
1909
1910/**
1911 * Displays an alert.
1912 * @param {String} str String to display.
1913 */
1914function alert(str)
1915{
1916	window.alert(sh.config.strings.alert + str);
1917};
1918
1919/**
1920 * Finds a brush by its alias.
1921 *
1922 * @param {String} alias		Brush alias.
1923 * @param {Boolean} showAlert	Suppresses the alert if false.
1924 * @return {Brush}				Returns bursh constructor if found, null otherwise.
1925 */
1926function findBrush(alias, showAlert)
1927{
1928	var brushes = sh.vars.discoveredBrushes,
1929		result = null
1930		;
1931
1932	if (brushes == null)
1933	{
1934		brushes = {};
1935
1936		// Find all brushes
1937		for (var brush in sh.brushes)
1938		{
1939			var info = sh.brushes[brush],
1940				aliases = info.aliases
1941				;
1942
1943			if (aliases == null)
1944				continue;
1945
1946			// keep the brush name
1947			info.brushName = brush.toLowerCase();
1948
1949			for (var i = 0, l = aliases.length; i < l; i++)
1950				brushes[aliases[i]] = brush;
1951		}
1952
1953		sh.vars.discoveredBrushes = brushes;
1954	}
1955
1956	result = sh.brushes[brushes[alias]];
1957
1958	if (result == null && showAlert)
1959		alert(sh.config.strings.noBrush + alias);
1960
1961	return result;
1962};
1963
1964/**
1965 * Executes a callback on each line and replaces each line with result from the callback.
1966 * @param {Object} str			Input string.
1967 * @param {Object} callback		Callback function taking one string argument and returning a string.
1968 */
1969function eachLine(str, callback)
1970{
1971	var lines = splitLines(str);
1972
1973	for (var i = 0, l = lines.length; i < l; i++)
1974		lines[i] = callback(lines[i], i);
1975
1976	// include \r to enable copy-paste on windows (ie8) without getting everything on one line
1977	return lines.join('\r\n');
1978};
1979
1980/**
1981 * This is a special trim which only removes first and last empty lines
1982 * and doesn't affect valid leading space on the first line.
1983 *
1984 * @param {String} str   Input string
1985 * @return {String}      Returns string without empty first and last lines.
1986 */
1987function trimFirstAndLastLines(str)
1988{
1989	return str.replace(/^[ ]*[\n]+|[\n]*[ ]*$/g, '');
1990};
1991
1992/**
1993 * Parses key/value pairs into hash object.
1994 *
1995 * Understands the following formats:
1996 * - name: word;
1997 * - name: [word, word];
1998 * - name: "string";
1999 * - name: 'string';
2000 *
2001 * For example:
2002 *   name1: value; name2: [value, value]; name3: 'value'
2003 *
2004 * @param {String} str    Input string.
2005 * @return {Object}       Returns deserialized object.
2006 */
2007function parseParams(str)
2008{
2009	var match,
2010		result = {},
2011		arrayRegex = XRegExp("^\\[(?<values>(.*?))\\]$"),
2012		pos = 0,
2013		regex = XRegExp(
2014			"(?<name>[\\w-]+)" +
2015			"\\s*:\\s*" +
2016			"(?<value>" +
2017				"[\\w%#-]+|" +		// word
2018				"\\[.*?\\]|" +		// [] array
2019				'".*?"|' +			// "" string
2020				"'.*?'" +			// '' string
2021			")\\s*;?",
2022			"g"
2023		)
2024		;
2025
2026	while ((match = XRegExp.exec(str, regex, pos)) != null)
2027	{
2028		var value = match.value
2029			.replace(/^['"]|['"]$/g, '') // strip quotes from end of strings
2030			;
2031
2032		// try to parse array value
2033		if (value != null && arrayRegex.test(value))
2034		{
2035			var m = XRegExp.exec(value, arrayRegex);
2036			value = m.values.length > 0 ? m.values.split(/\s*,\s*/) : [];
2037		}
2038
2039		result[match.name] = value;
2040		pos = match.index + match[0].length;
2041	}
2042
2043	return result;
2044};
2045
2046/**
2047 * Wraps each line of the string into <code/> tag with given style applied to it.
2048 *
2049 * @param {String} str   Input string.
2050 * @param {String} css   Style name to apply to the string.
2051 * @return {String}      Returns input string with each line surrounded by <span/> tag.
2052 */
2053function wrapLinesWithCode(str, css)
2054{
2055	if (str == null || str.length == 0 || str == '\n')
2056		return str;
2057
2058	str = str.replace(/</g, '&lt;');
2059
2060	// Replace two or more sequential spaces with &nbsp; leaving last space untouched.
2061	str = str.replace(/ {2,}/g, function(m)
2062	{
2063		var spaces = '';
2064
2065		for (var i = 0, l = m.length; i < l - 1; i++)
2066			spaces += sh.config.space;
2067
2068		return spaces + ' ';
2069	});
2070
2071	// Split each line and apply <span class="...">...</span> to them so that
2072	// leading spaces aren't included.
2073	if (css != null)
2074		str = eachLine(str, function(line)
2075		{
2076			if (line.length == 0)
2077				return '';
2078
2079			var spaces = '';
2080
2081			line = line.replace(/^(&nbsp;| )+/, function(s)
2082			{
2083				spaces = s;
2084				return '';
2085			});
2086
2087			if (line.length == 0)
2088				return spaces;
2089
2090			return spaces + '<code class="' + css + '">' + line + '</code>';
2091		});
2092
2093	return str;
2094};
2095
2096/**
2097 * Pads number with zeros until it's length is the same as given length.
2098 *
2099 * @param {Number} number	Number to pad.
2100 * @param {Number} length	Max string length with.
2101 * @return {String}			Returns a string padded with proper amount of '0'.
2102 */
2103function padNumber(number, length)
2104{
2105	var result = number.toString();
2106
2107	while (result.length < length)
2108		result = '0' + result;
2109
2110	return result;
2111};
2112
2113/**
2114 * Replaces tabs with spaces.
2115 *
2116 * @param {String} code		Source code.
2117 * @param {Number} tabSize	Size of the tab.
2118 * @return {String}			Returns code with all tabs replaces by spaces.
2119 */
2120function processTabs(code, tabSize)
2121{
2122	var tab = '';
2123
2124	for (var i = 0; i < tabSize; i++)
2125		tab += ' ';
2126
2127	return code.replace(/\t/g, tab);
2128};
2129
2130/**
2131 * Replaces tabs with smart spaces.
2132 *
2133 * @param {String} code    Code to fix the tabs in.
2134 * @param {Number} tabSize Number of spaces in a column.
2135 * @return {String}        Returns code with all tabs replaces with roper amount of spaces.
2136 */
2137function processSmartTabs(code, tabSize)
2138{
2139	var lines = splitLines(code),
2140		tab = '\t',
2141		spaces = ''
2142		;
2143
2144	// Create a string with 1000 spaces to copy spaces from...
2145	// It's assumed that there would be no indentation longer than that.
2146	for (var i = 0; i < 50; i++)
2147		spaces += '                    '; // 20 spaces * 50
2148
2149	// This function inserts specified amount of spaces in the string
2150	// where a tab is while removing that given tab.
2151	function insertSpaces(line, pos, count)
2152	{
2153		return line.substr(0, pos)
2154			+ spaces.substr(0, count)
2155			+ line.substr(pos + 1, line.length) // pos + 1 will get rid of the tab
2156			;
2157	};
2158
2159	// Go through all the lines and do the 'smart tabs' magic.
2160	code = eachLine(code, function(line)
2161	{
2162		if (line.indexOf(tab) == -1)
2163			return line;
2164
2165		var pos = 0;
2166
2167		while ((pos = line.indexOf(tab)) != -1)
2168		{
2169			// This is pretty much all there is to the 'smart tabs' logic.
2170			// Based on the position within the line and size of a tab,
2171			// calculate the amount of spaces we need to insert.
2172			var spaces = tabSize - pos % tabSize;
2173			line = insertSpaces(line, pos, spaces);
2174		}
2175
2176		return line;
2177	});
2178
2179	return code;
2180};
2181
2182/**
2183 * Performs various string fixes based on configuration.
2184 */
2185function fixInputString(str)
2186{
2187	var br = /<br\s*\/?>|&lt;br\s*\/?&gt;/gi;
2188
2189	if (sh.config.bloggerMode == true)
2190		str = str.replace(br, '\n');
2191
2192	if (sh.config.stripBrs == true)
2193		str = str.replace(br, '');
2194
2195	return str;
2196};
2197
2198/**
2199 * Removes all white space at the begining and end of a string.
2200 *
2201 * @param {String} str   String to trim.
2202 * @return {String}      Returns string without leading and following white space characters.
2203 */
2204function trim(str)
2205{
2206	return str.replace(/^\s+|\s+$/g, '');
2207};
2208
2209/**
2210 * Unindents a block of text by the lowest common indent amount.
2211 * @param {String} str   Text to unindent.
2212 * @return {String}      Returns unindented text block.
2213 */
2214function unindent(str)
2215{
2216	var lines = splitLines(fixInputString(str)),
2217		indents = new Array(),
2218		regex = /^\s*/,
2219		min = 1000
2220		;
2221
2222	// go through every line and check for common number of indents
2223	for (var i = 0, l = lines.length; i < l && min > 0; i++)
2224	{
2225		var line = lines[i];
2226
2227		if (trim(line).length == 0)
2228			continue;
2229
2230		var matches = regex.exec(line);
2231
2232		// In the event that just one line doesn't have leading white space
2233		// we can't unindent anything, so bail completely.
2234		if (matches == null)
2235			return str;
2236
2237		min = Math.min(matches[0].length, min);
2238	}
2239
2240	// trim minimum common number of white space from the begining of every line
2241	if (min > 0)
2242		for (var i = 0, l = lines.length; i < l; i++)
2243			lines[i] = lines[i].substr(min);
2244
2245	return lines.join('\n');
2246};
2247
2248/**
2249 * Callback method for Array.sort() which sorts matches by
2250 * index position and then by length.
2251 *
2252 * @param {Match} m1	Left object.
2253 * @param {Match} m2    Right object.
2254 * @return {Number}     Returns -1, 0 or -1 as a comparison result.
2255 */
2256function matchesSortCallback(m1, m2)
2257{
2258	// sort matches by index first
2259	if(m1.index < m2.index)
2260		return -1;
2261	else if(m1.index > m2.index)
2262		return 1;
2263	else
2264	{
2265		// if index is the same, sort by length
2266		if(m1.length < m2.length)
2267			return -1;
2268		else if(m1.length > m2.length)
2269			return 1;
2270	}
2271
2272	return 0;
2273};
2274
2275/**
2276 * Executes given regular expression on provided code and returns all
2277 * matches that are found.
2278 *
2279 * @param {String} code    Code to execute regular expression on.
2280 * @param {Object} regex   Regular expression item info from <code>regexList</code> collection.
2281 * @return {Array}         Returns a list of Match objects.
2282 */
2283function getMatches(code, regexInfo)
2284{
2285	function defaultAdd(match, regexInfo)
2286	{
2287		return match[0];
2288	};
2289
2290	var index = 0,
2291		match = null,
2292		matches = [],
2293		func = regexInfo.func ? regexInfo.func : defaultAdd
2294		pos = 0
2295		;
2296
2297	while((match = XRegExp.exec(code, regexInfo.regex, pos)) != null)
2298	{
2299		var resultMatch = func(match, regexInfo);
2300
2301		if (typeof(resultMatch) == 'string')
2302			resultMatch = [new sh.Match(resultMatch, match.index, regexInfo.css)];
2303
2304		matches = matches.concat(resultMatch);
2305		pos = match.index + match[0].length;
2306	}
2307
2308	return matches;
2309};
2310
2311/**
2312 * Turns all URLs in the code into <a/> tags.
2313 * @param {String} code Input code.
2314 * @return {String} Returns code with </a> tags.
2315 */
2316function processUrls(code)
2317{
2318	var gt = /(.*)((&gt;|&lt;).*)/;
2319
2320	return code.replace(sh.regexLib.url, function(m)
2321	{
2322		var suffix = '',
2323			match = null
2324			;
2325
2326		// We include &lt; and &gt; in the URL for the common cases like <http://google.com>
2327		// The problem is that they get transformed into &lt;http://google.com&gt;
2328		// Where as &gt; easily looks like part of the URL string.
2329
2330		if (match = gt.exec(m))
2331		{
2332			m = match[1];
2333			suffix = match[2];
2334		}
2335
2336		return '<a href="' + m + '">' + m + '</a>' + suffix;
2337	});
2338};
2339
2340/**
2341 * Finds all <SCRIPT TYPE="syntaxhighlighter" /> elementss.
2342 * @return {Array} Returns array of all found SyntaxHighlighter tags.
2343 */
2344function getSyntaxHighlighterScriptTags()
2345{
2346	var tags = document.getElementsByTagName('script'),
2347		result = []
2348		;
2349
2350	for (var i = 0, l = tags.length; i < l; i++)
2351		if (tags[i].type == 'syntaxhighlighter')
2352			result.push(tags[i]);
2353
2354	return result;
2355};
2356
2357/**
2358 * Strips <![CDATA[]]> from <SCRIPT /> content because it should be used
2359 * there in most cases for XHTML compliance.
2360 * @param {String} original	Input code.
2361 * @return {String} Returns code without leading <![CDATA[]]> tags.
2362 */
2363function stripCData(original)
2364{
2365	var left = '<![CDATA[',
2366		right = ']]>',
2367		// for some reason IE inserts some leading blanks here
2368		copy = trim(original),
2369		changed = false,
2370		leftLength = left.length,
2371		rightLength = right.length
2372		;
2373
2374	if (copy.indexOf(left) == 0)
2375	{
2376		copy = copy.substring(leftLength);
2377		changed = true;
2378	}
2379
2380	var copyLength = copy.length;
2381
2382	if (copy.indexOf(right) == copyLength - rightLength)
2383	{
2384		copy = copy.substring(0, copyLength - rightLength);
2385		changed = true;
2386	}
2387
2388	return changed ? copy : original;
2389};
2390
2391
2392/**
2393 * Quick code mouse double click handler.
2394 */
2395function quickCodeHandler(e)
2396{
2397	var target = e.target,
2398		highlighterDiv = findParentElement(target, '.syntaxhighlighter'),
2399		container = findParentElement(target, '.container'),
2400		textarea = document.createElement('textarea'),
2401		highlighter
2402		;
2403
2404	if (!container || !highlighterDiv || findElement(container, 'textarea'))
2405		return;
2406
2407	highlighter = getHighlighterById(highlighterDiv.id);
2408
2409	// add source class name
2410	addClass(highlighterDiv, 'source');
2411
2412	// Have to go over each line and grab it's text, can't just do it on the
2413	// container because Firefox loses all \n where as Webkit doesn't.
2414	var lines = container.childNodes,
2415		code = []
2416		;
2417
2418	for (var i = 0, l = lines.length; i < l; i++)
2419		code.push(lines[i].innerText || lines[i].textContent);
2420
2421	// using \r instead of \r or \r\n makes this work equally well on IE, FF and Webkit
2422	code = code.join('\r');
2423
2424    // For Webkit browsers, replace nbsp with a breaking space
2425    code = code.replace(/\u00a0/g, " ");
2426
2427	// inject <textarea/> tag
2428	textarea.appendChild(document.createTextNode(code));
2429	container.appendChild(textarea);
2430
2431	// preselect all text
2432	textarea.focus();
2433	textarea.select();
2434
2435	// set up handler for lost focus
2436	attachEvent(textarea, 'blur', function(e)
2437	{
2438		textarea.parentNode.removeChild(textarea);
2439		removeClass(highlighterDiv, 'source');
2440	});
2441};
2442
2443/**
2444 * Match object.
2445 */
2446sh.Match = function(value, index, css)
2447{
2448	this.value = value;
2449	this.index = index;
2450	this.length = value.length;
2451	this.css = css;
2452	this.brushName = null;
2453};
2454
2455sh.Match.prototype.toString = function()
2456{
2457	return this.value;
2458};
2459
2460/**
2461 * Simulates HTML code with a scripting language embedded.
2462 *
2463 * @param {String} scriptBrushName Brush name of the scripting language.
2464 */
2465sh.HtmlScript = function(scriptBrushName)
2466{
2467	var brushClass = findBrush(scriptBrushName),
2468		scriptBrush,
2469		xmlBrush = new sh.brushes.Xml(),
2470		bracketsRegex = null,
2471		ref = this,
2472		methodsToExpose = 'getDiv getHtml init'.split(' ')
2473		;
2474
2475	if (brushClass == null)
2476		return;
2477
2478	scriptBrush = new brushClass();
2479
2480	for(var i = 0, l = methodsToExpose.length; i < l; i++)
2481		// make a closure so we don't lose the name after i changes
2482		(function() {
2483			var name = methodsToExpose[i];
2484
2485			ref[name] = function()
2486			{
2487				return xmlBrush[name].apply(xmlBrush, arguments);
2488			};
2489		})();
2490
2491	if (scriptBrush.htmlScript == null)
2492	{
2493		alert(sh.config.strings.brushNotHtmlScript + scriptBrushName);
2494		return;
2495	}
2496
2497	xmlBrush.regexList.push(
2498		{ regex: scriptBrush.htmlScript.code, func: process }
2499	);
2500
2501	function offsetMatches(matches, offset)
2502	{
2503		for (var j = 0, l = matches.length; j < l; j++)
2504			matches[j].index += offset;
2505	}
2506
2507	function process(match, info)
2508	{
2509		var code = match.code,
2510			matches = [],
2511			regexList = scriptBrush.regexList,
2512			offset = match.index + match.left.length,
2513			htmlScript = scriptBrush.htmlScript,
2514			result
2515			;
2516
2517		// add all matches from the code
2518		for (var i = 0, l = regexList.length; i < l; i++)
2519		{
2520			result = getMatches(code, regexList[i]);
2521			offsetMatches(result, offset);
2522			matches = matches.concat(result);
2523		}
2524
2525		// add left script bracket
2526		if (htmlScript.left != null && match.left != null)
2527		{
2528			result = getMatches(match.left, htmlScript.left);
2529			offsetMatches(result, match.index);
2530			matches = matches.concat(result);
2531		}
2532
2533		// add right script bracket
2534		if (htmlScript.right != null && match.right != null)
2535		{
2536			result = getMatches(match.right, htmlScript.right);
2537			offsetMatches(result, match.index + match[0].lastIndexOf(match.right));
2538			matches = matches.concat(result);
2539		}
2540
2541		for (var j = 0, l = matches.length; j < l; j++)
2542			matches[j].brushName = brushClass.brushName;
2543
2544		return matches;
2545	}
2546};
2547
2548/**
2549 * Main Highlither class.
2550 * @constructor
2551 */
2552sh.Highlighter = function()
2553{
2554	// not putting any code in here because of the prototype inheritance
2555};
2556
2557sh.Highlighter.prototype = {
2558	/**
2559	 * Returns value of the parameter passed to the highlighter.
2560	 * @param {String} name				Name of the parameter.
2561	 * @param {Object} defaultValue		Default value.
2562	 * @return {Object}					Returns found value or default value otherwise.
2563	 */
2564	getParam: function(name, defaultValue)
2565	{
2566		var result = this.params[name];
2567		return toBoolean(result == null ? defaultValue : result);
2568	},
2569
2570	/**
2571	 * Shortcut to document.createElement().
2572	 * @param {String} name		Name of the element to create (DIV, A, etc).
2573	 * @return {HTMLElement}	Returns new HTML element.
2574	 */
2575	create: function(name)
2576	{
2577		return document.createElement(name);
2578	},
2579
2580	/**
2581	 * Applies all regular expression to the code and stores all found
2582	 * matches in the `this.matches` array.
2583	 * @param {Array} regexList		List of regular expressions.
2584	 * @param {String} code			Source code.
2585	 * @return {Array}				Returns list of matches.
2586	 */
2587	findMatches: function(regexList, code)
2588	{
2589		var result = [];
2590
2591		if (regexList != null)
2592			for (var i = 0, l = regexList.length; i < l; i++)
2593				// BUG: length returns len+1 for array if methods added to prototype chain (oising@gmail.com)
2594				if (typeof (regexList[i]) == "object")
2595					result = result.concat(getMatches(code, regexList[i]));
2596
2597		// sort and remove nested the matches
2598		return this.removeNestedMatches(result.sort(matchesSortCallback));
2599	},
2600
2601	/**
2602	 * Checks to see if any of the matches are inside of other matches.
2603	 * This process would get rid of highligted strings inside comments,
2604	 * keywords inside strings and so on.
2605	 */
2606	removeNestedMatches: function(matches)
2607	{
2608		// Optimized by Jose Prado (http://joseprado.com)
2609		for (var i = 0, l = matches.length; i < l; i++)
2610		{
2611			if (matches[i] === null)
2612				continue;
2613
2614			var itemI = matches[i],
2615				itemIEndPos = itemI.index + itemI.length
2616				;
2617
2618			for (var j = i + 1, l = matches.length; j < l && matches[i] !== null; j++)
2619			{
2620				var itemJ = matches[j];
2621
2622				if (itemJ === null)
2623					continue;
2624				else if (itemJ.index > itemIEndPos)
2625					break;
2626				else if (itemJ.index == itemI.index && itemJ.length > itemI.length)
2627					matches[i] = null;
2628				else if (itemJ.index >= itemI.index && itemJ.index < itemIEndPos)
2629					matches[j] = null;
2630			}
2631		}
2632
2633		return matches;
2634	},
2635
2636	/**
2637	 * Creates an array containing integer line numbers starting from the 'first-line' param.
2638	 * @return {Array} Returns array of integers.
2639	 */
2640	figureOutLineNumbers: function(code)
2641	{
2642		var lines = [],
2643			firstLine = parseInt(this.getParam('first-line'))
2644			;
2645
2646		eachLine(code, function(line, index)
2647		{
2648			lines.push(index + firstLine);
2649		});
2650
2651		return lines;
2652	},
2653
2654	/**
2655	 * Determines if specified line number is in the highlighted list.
2656	 */
2657	isLineHighlighted: function(lineNumber)
2658	{
2659		var list = this.getParam('highlight', []);
2660
2661		if (typeof(list) != 'object' && list.push == null)
2662			list = [ list ];
2663
2664		return indexOf(list, lineNumber.toString()) != -1;
2665	},
2666
2667	/**
2668	 * Generates HTML markup for a single line of code while determining alternating line style.
2669	 * @param {Integer} lineNumber	Line number.
2670	 * @param {String} code Line	HTML markup.
2671	 * @return {String}				Returns HTML markup.
2672	 */
2673	getLineHtml: function(lineIndex, lineNumber, code)
2674	{
2675		var classes = [
2676			'line',
2677			'number' + lineNumber,
2678			'index' + lineIndex,
2679			'alt' + (lineNumber % 2 == 0 ? 1 : 2).toString()
2680		];
2681
2682		if (this.isLineHighlighted(lineNumber))
2683		 	classes.push('highlighted');
2684
2685		if (lineNumber == 0)
2686			classes.push('break');
2687
2688		return '<div class="' + classes.join(' ') + '">' + code + '</div>';
2689	},
2690
2691	/**
2692	 * Generates HTML markup for line number column.
2693	 * @param {String} code			Complete code HTML markup.
2694	 * @param {Array} lineNumbers	Calculated line numbers.
2695	 * @return {String}				Returns HTML markup.
2696	 */
2697	getLineNumbersHtml: function(code, lineNumbers)
2698	{
2699		var html = '',
2700			count = splitLines(code).length,
2701			firstLine = parseInt(this.getParam('first-line')),
2702			pad = this.getParam('pad-line-numbers')
2703			;
2704
2705		if (pad == true)
2706			pad = (firstLine + count - 1).toString().length;
2707		else if (isNaN(pad) == true)
2708			pad = 0;
2709
2710		for (var i = 0; i < count; i++)
2711		{
2712			var lineNumber = lineNumbers ? lineNumbers[i] : firstLine + i,
2713				code = lineNumber == 0 ? sh.config.space : padNumber(lineNumber, pad)
2714				;
2715
2716			html += this.getLineHtml(i, lineNumber, code);
2717		}
2718
2719		return html;
2720	},
2721
2722	/**
2723	 * Splits block of text into individual DIV lines.
2724	 * @param {String} code			Code to highlight.
2725	 * @param {Array} lineNumbers	Calculated line numbers.
2726	 * @return {String}				Returns highlighted code in HTML form.
2727	 */
2728	getCodeLinesHtml: function(html, lineNumbers)
2729	{
2730		html = trim(html);
2731
2732		var lines = splitLines(html),
2733			padLength = this.getParam('pad-line-numbers'),
2734			firstLine = parseInt(this.getParam('first-line')),
2735			html = '',
2736			brushName = this.getParam('brush')
2737			;
2738
2739		for (var i = 0, l = lines.length; i < l; i++)
2740		{
2741			var line = lines[i],
2742				indent = /^(&nbsp;|\s)+/.exec(line),
2743				spaces = null,
2744				lineNumber = lineNumbers ? lineNumbers[i] : firstLine + i;
2745				;
2746
2747			if (indent != null)
2748			{
2749				spaces = indent[0].toString();
2750				line = line.substr(spaces.length);
2751				spaces = spaces.replace(' ', sh.config.space);
2752			}
2753
2754			line = trim(line);
2755
2756			if (line.length == 0)
2757				line = sh.config.space;
2758
2759			html += this.getLineHtml(
2760				i,
2761				lineNumber,
2762				(spaces != null ? '<code class="' + brushName + ' spaces">' + spaces + '</code>' : '') + line
2763			);
2764		}
2765
2766		return html;
2767	},
2768
2769	/**
2770	 * Returns HTML for the table title or empty string if title is null.
2771	 */
2772	getTitleHtml: function(title)
2773	{
2774		return title ? '<caption>' + escapeHtml(title) + '</caption>' : '';
2775	},
2776
2777	/**
2778	 * Finds all matches in the source code.
2779	 * @param {String} code		Source code to process matches in.
2780	 * @param {Array} matches	Discovered regex matches.
2781	 * @return {String} Returns formatted HTML with processed mathes.
2782	 */
2783	getMatchesHtml: function(code, matches)
2784	{
2785		var pos = 0,
2786			result = '',
2787			brushName = this.getParam('brush', '')
2788			;
2789
2790		function getBrushNameCss(match)
2791		{
2792			var result = match ? (match.brushName || brushName) : brushName;
2793			return result ? result + ' ' : '';
2794		};
2795
2796		// Finally, go through the final list of matches and pull the all
2797		// together adding everything in between that isn't a match.
2798		for (var i = 0, l = matches.length; i < l; i++)
2799		{
2800			var match = matches[i],
2801				matchBrushName
2802				;
2803
2804			if (match === null || match.length === 0)
2805				continue;
2806
2807			matchBrushName = getBrushNameCss(match);
2808
2809			result += wrapLinesWithCode(code.substr(pos, match.index - pos), matchBrushName + 'plain')
2810					+ wrapLinesWithCode(match.value, matchBrushName + match.css)
2811					;
2812
2813			pos = match.index + match.length + (match.offset || 0);
2814		}
2815
2816		// don't forget to add whatever's remaining in the string
2817		result += wrapLinesWithCode(code.substr(pos), getBrushNameCss() + 'plain');
2818
2819		return result;
2820	},
2821
2822	/**
2823	 * Generates HTML markup for the whole syntax highlighter.
2824	 * @param {String} code Source code.
2825	 * @return {String} Returns HTML markup.
2826	 */
2827	getHtml: function(code)
2828	{
2829		var html = '',
2830			classes = [ 'syntaxhighlighter' ],
2831			tabSize,
2832			matches,
2833			lineNumbers
2834			;
2835
2836		// process light mode
2837		if (this.getParam('light') == true)
2838			this.params.toolbar = this.params.gutter = false;
2839
2840		className = 'syntaxhighlighter';
2841
2842		if (this.getParam('collapse') == true)
2843			classes.push('collapsed');
2844
2845		if ((gutter = this.getParam('gutter')) == false)
2846			classes.push('nogutter');
2847
2848		// add custom user style name
2849		classes.push(this.getParam('class-name'));
2850
2851		// add brush alias to the class name for custom CSS
2852		classes.push(this.getParam('brush'));
2853
2854		code = trimFirstAndLastLines(code)
2855			.replace(/\r/g, ' ') // IE lets these buggers through
2856			;
2857
2858		tabSize = this.getParam('tab-size');
2859
2860		// replace tabs with spaces
2861		code = this.getParam('smart-tabs') == true
2862			? processSmartTabs(code, tabSize)
2863			: processTabs(code, tabSize)
2864			;
2865
2866		// unindent code by the common indentation
2867		if (this.getParam('unindent'))
2868			code = unindent(code);
2869
2870		if (gutter)
2871			lineNumbers = this.figureOutLineNumbers(code);
2872
2873		// find matches in the code using brushes regex list
2874		matches = this.findMatches(this.regexList, code);
2875		// processes found matches into the html
2876		html = this.getMatchesHtml(code, matches);
2877		// finally, split all lines so that they wrap well
2878		html = this.getCodeLinesHtml(html, lineNumbers);
2879
2880		// finally, process the links
2881		if (this.getParam('auto-links'))
2882			html = processUrls(html);
2883
2884		if (typeof(navigator) != 'undefined' && navigator.userAgent && navigator.userAgent.match(/MSIE/))
2885			classes.push('ie');
2886
2887		html =
2888			'<div id="' + getHighlighterId(this.id) + '" class="' + escapeHtml(classes.join(' ')) + '">'
2889				+ (this.getParam('toolbar') ? sh.toolbar.getHtml(this) : '')
2890				+ '<table border="0" cellpadding="0" cellspacing="0">'
2891					+ this.getTitleHtml(this.getParam('title'))
2892					+ '<tbody>'
2893						+ '<tr>'
2894							+ (gutter ? '<td class="gutter">' + this.getLineNumbersHtml(code) + '</td>' : '')
2895							+ '<td class="code">'
2896								+ '<div class="container">'
2897									+ html
2898								+ '</div>'
2899							+ '</td>'
2900						+ '</tr>'
2901					+ '</tbody>'
2902				+ '</table>'
2903			+ '</div>'
2904			;
2905
2906		return html;
2907	},
2908
2909	/**
2910	 * Highlights the code and returns complete HTML.
2911	 * @param {String} code     Code to highlight.
2912	 * @return {Element}        Returns container DIV element with all markup.
2913	 */
2914	getDiv: function(code)
2915	{
2916		if (code === null)
2917			code = '';
2918
2919		this.code = code;
2920
2921		var div = this.create('div');
2922
2923		// create main HTML
2924		div.innerHTML = this.getHtml(code);
2925
2926		// set up click handlers
2927		if (this.getParam('toolbar'))
2928			attachEvent(findElement(div, '.toolbar'), 'click', sh.toolbar.handler);
2929
2930		if (this.getParam('quick-code'))
2931			attachEvent(findElement(div, '.code'), 'dblclick', quickCodeHandler);
2932
2933		return div;
2934	},
2935
2936	/**
2937	 * Initializes the highlighter/brush.
2938	 *
2939	 * Constructor isn't used for initialization so that nothing executes during necessary
2940	 * `new SyntaxHighlighter.Highlighter()` call when setting up brush inheritence.
2941	 *
2942	 * @param {Hash} params Highlighter parameters.
2943	 */
2944	init: function(params)
2945	{
2946		this.id = guid();
2947
2948		// register this instance in the highlighters list
2949		storeHighlighter(this);
2950
2951		// local params take precedence over defaults
2952		this.params = merge(sh.defaults, params || {})
2953
2954		// process light mode
2955		if (this.getParam('light') == true)
2956			this.params.toolbar = this.params.gutter = false;
2957	},
2958
2959	/**
2960	 * Converts space separated list of keywords into a regular expression string.
2961	 * @param {String} str    Space separated keywords.
2962	 * @return {String}       Returns regular expression string.
2963	 */
2964	getKeywords: function(str)
2965	{
2966		str = str
2967			.replace(/^\s+|\s+$/g, '')
2968			.replace(/\s+/g, '|')
2969			;
2970
2971		return '\\b(?:' + str + ')\\b';
2972	},
2973
2974	/**
2975	 * Makes a brush compatible with the `html-script` functionality.
2976	 * @param {Object} regexGroup Object containing `left` and `right` regular expressions.
2977	 */
2978	forHtmlScript: function(regexGroup)
2979	{
2980		var regex = { 'end' : regexGroup.right.source };
2981
2982		if(regexGroup.eof)
2983			regex.end = "(?:(?:" + regex.end + ")|$)";
2984
2985		this.htmlScript = {
2986			left : { regex: regexGroup.left, css: 'script' },
2987			right : { regex: regexGroup.right, css: 'script' },
2988			code : XRegExp(
2989				"(?<left>" + regexGroup.left.source + ")" +
2990				"(?<code>.*?)" +
2991				"(?<right>" + regex.end + ")",
2992				"sgi"
2993				)
2994		};
2995	}
2996}; // end of Highlighter
2997
2998return sh;
2999}(); // end of anonymous function
3000
3001// CommonJS
3002typeof(exports) != 'undefined' ? exports.SyntaxHighlighter = SyntaxHighlighter : null;
3003