2 // (c) 2007-2012 Steven Levithan
4 // <http://xregexp.com>
5 // Provides an augmented, extensible, cross-browser implementation of regular expressions,
6 // including support for additional syntax, flags, and methods
11 // Avoid running twice, since that would break references to native globals
12 throw Error("can't load XRegExp twice in the same frame");
15 // Run within an anonymous function to protect variables and avoid new globals
16 (function (undefined) {
18 //---------------------------------
20 //---------------------------------
22 // Accepts a pattern and flags; returns a new, extended `RegExp` object. Differs from a native
23 // regular expression in that additional syntax and flags are supported and cross-browser
24 // syntax inconsistencies are ameliorated. `XRegExp(/regex/)` clones an existing regex and
25 // converts to type XRegExp
26 XRegExp = function (pattern, flags) {
28 currScope = XRegExp.OUTSIDE_CLASS,
30 context, tokenResult, match, chr, regex;
32 if (XRegExp.isRegExp(pattern)) {
33 if (flags !== undefined)
34 throw TypeError("can't supply flags when constructing one RegExp from another");
35 return clone(pattern);
37 // Tokens become part of the regex construction process, so protect against infinite
38 // recursion when an XRegExp is constructed within a token handler or trigger
39 if (isInsideConstructor)
40 throw Error("can't call the XRegExp constructor within token definition functions");
43 context = { // `this` object for custom tokens
44 hasNamedCapture: false,
46 hasFlag: function (flag) {return flags.indexOf(flag) > -1;},
47 setFlag: function (flag) {flags += flag;}
50 while (pos < pattern.length) {
51 // Check for custom tokens at the current position
52 tokenResult = runTokens(pattern, pos, currScope, context);
55 output.push(tokenResult.output);
56 pos += (tokenResult.match[0].length || 1);
58 // Check for native multicharacter metasequences (excluding character classes) at
59 // the current position
60 if (match = nativ.exec.call(nativeTokens[currScope], pattern.slice(pos))) {
61 output.push(match[0]);
62 pos += match[0].length;
64 chr = pattern.charAt(pos);
66 currScope = XRegExp.INSIDE_CLASS;
68 currScope = XRegExp.OUTSIDE_CLASS;
69 // Advance position one character
76 regex = RegExp(output.join(""), nativ.replace.call(flags, flagClip, ""));
79 captureNames: context.hasNamedCapture ? context.captureNames : null
85 //---------------------------------
87 //---------------------------------
89 XRegExp.version = "1.5.1";
91 // Token scope bitflags
92 XRegExp.INSIDE_CLASS = 1;
93 XRegExp.OUTSIDE_CLASS = 2;
96 //---------------------------------
98 //---------------------------------
100 var replacementToken = /\$(?:(\d\d?|[$&`'])|{([$\w]+)})/g,
101 flagClip = /[^gimy]+|([\s\S])(?=[\s\S]*\1)/g, // Nonnative and duplicate flags
102 quantifier = /^(?:[?*+]|{\d+(?:,\d*)?})\??/,
103 isInsideConstructor = false,
105 // Copy native globals for reference ("native" is an ES3 reserved keyword)
107 exec: RegExp.prototype.exec,
108 test: RegExp.prototype.test,
109 match: String.prototype.match,
110 replace: String.prototype.replace,
111 split: String.prototype.split
113 compliantExecNpcg = nativ.exec.call(/()??/, "")[1] === undefined, // check `exec` handling of nonparticipating capturing groups
114 compliantLastIndexIncrement = function () {
116 nativ.test.call(x, "");
119 hasNativeY = RegExp.prototype.sticky !== undefined,
122 // `nativeTokens` match native multicharacter metasequences only (including deprecated octals,
123 // excluding character classes)
124 nativeTokens[XRegExp.INSIDE_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]))/;
125 nativeTokens[XRegExp.OUTSIDE_CLASS] = /^(?:\\(?: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*)?}\??)/;
128 //---------------------------------
130 //---------------------------------
132 // Lets you extend or change XRegExp syntax and create custom flags. This is used internally by
133 // the XRegExp library and can be used to create XRegExp plugins. This function is intended for
134 // users with advanced knowledge of JavaScript's regular expression syntax and behavior. It can
135 // be disabled by `XRegExp.freezeTokens`
136 XRegExp.addToken = function (regex, handler, scope, trigger) {
138 pattern: clone(regex, "g" + (hasNativeY ? "y" : "")),
140 scope: scope || XRegExp.OUTSIDE_CLASS,
141 trigger: trigger || null
145 // Accepts a pattern and flags; returns an extended `RegExp` object. If the pattern and flag
146 // combination has previously been cached, the cached copy is returned; otherwise the newly
147 // created regex is cached
148 XRegExp.cache = function (pattern, flags) {
149 var key = pattern + "/" + (flags || "");
150 return XRegExp.cache[key] || (XRegExp.cache[key] = XRegExp(pattern, flags));
153 // Accepts a `RegExp` instance; returns a copy with the `/g` flag set. The copy has a fresh
154 // `lastIndex` (set to zero). If you want to copy a regex without forcing the `global`
155 // property, use `XRegExp(regex)`. Do not use `RegExp(regex)` because it will not preserve
156 // special properties required for named capture
157 XRegExp.copyAsGlobal = function (regex) {
158 return clone(regex, "g");
161 // Accepts a string; returns the string with regex metacharacters escaped. The returned string
162 // can safely be used at any point within a regex to match the provided literal string. Escaped
163 // characters are [ ] { } ( ) * + ? - . , \ ^ $ | # and whitespace
164 XRegExp.escape = function (str) {
165 return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
168 // Accepts a string to search, regex to search with, position to start the search within the
169 // string (default: 0), and an optional Boolean indicating whether matches must start at-or-
170 // after the position or at the specified position only. This function ignores the `lastIndex`
171 // of the provided regex in its own handling, but updates the property for compatibility
172 XRegExp.execAt = function (str, regex, pos, anchored) {
173 var r2 = clone(regex, "g" + ((anchored && hasNativeY) ? "y" : "")),
175 r2.lastIndex = pos = pos || 0;
176 match = r2.exec(str); // Run the altered `exec` (required for `lastIndex` fix, etc.)
177 if (anchored && match && match.index !== pos)
180 regex.lastIndex = match ? r2.lastIndex : 0;
184 // Breaks the unrestorable link to XRegExp's private list of tokens, thereby preventing
185 // syntax and flag changes. Should be run after XRegExp and any plugins are loaded
186 XRegExp.freezeTokens = function () {
187 XRegExp.addToken = function () {
188 throw Error("can't run addToken after freezeTokens");
192 // Accepts any value; returns a Boolean indicating whether the argument is a `RegExp` object.
193 // Note that this is also `true` for regex literals and regexes created by the `XRegExp`
194 // constructor. This works correctly for variables created in another frame, when `instanceof`
195 // and `constructor` checks would fail to work as intended
196 XRegExp.isRegExp = function (o) {
197 return Object.prototype.toString.call(o) === "[object RegExp]";
200 // Executes `callback` once per match within `str`. Provides a simpler and cleaner way to
201 // iterate over regex matches compared to the traditional approaches of subverting
202 // `String.prototype.replace` or repeatedly calling `exec` within a `while` loop
203 XRegExp.iterate = function (str, regex, callback, context) {
204 var r2 = clone(regex, "g"),
206 while (match = r2.exec(str)) { // Run the altered `exec` (required for `lastIndex` fix, etc.)
208 regex.lastIndex = r2.lastIndex; // Doing this to follow expectations if `lastIndex` is checked within `callback`
209 callback.call(context, match, ++i, str, regex);
210 if (r2.lastIndex === match.index)
217 // Accepts a string and an array of regexes; returns the result of using each successive regex
218 // to search within the matches of the previous regex. The array of regexes can also contain
219 // objects with `regex` and `backref` properties, in which case the named or numbered back-
220 // references specified are passed forward to the next regex or returned. E.g.:
221 // var xregexpImgFileNames = XRegExp.matchChain(html, [
222 // {regex: /<img\b([^>]+)>/i, backref: 1}, // <img> tag attributes
223 // {regex: XRegExp('(?ix) \\s src=" (?<src> [^"]+ )'), backref: "src"}, // src attribute values
224 // {regex: XRegExp("^http://xregexp\\.com(/[^#?]+)", "i"), backref: 1}, // xregexp.com paths
225 // /[^\/]+$/ // filenames (strip directory paths)
227 XRegExp.matchChain = function (str, chain) {
228 return function recurseChain (values, level) {
229 var item = chain[level].regex ? chain[level] : {regex: chain[level]},
230 regex = clone(item.regex, "g"),
232 for (i = 0; i < values.length; i++) {
233 XRegExp.iterate(values[i], regex, function (match) {
234 matches.push(item.backref ? (match[item.backref] || "") : match[0]);
237 return ((level === chain.length - 1) || !matches.length) ?
238 matches : recurseChain(matches, level + 1);
243 //---------------------------------
244 // New RegExp prototype methods
245 //---------------------------------
247 // Accepts a context object and arguments array; returns the result of calling `exec` with the
248 // first value in the arguments array. the context is ignored but is accepted for congruity
249 // with `Function.prototype.apply`
250 RegExp.prototype.apply = function (context, args) {
251 return this.exec(args[0]);
254 // Accepts a context object and string; returns the result of calling `exec` with the provided
255 // string. the context is ignored but is accepted for congruity with `Function.prototype.call`
256 RegExp.prototype.call = function (context, str) {
257 return this.exec(str);
261 //---------------------------------
262 // Overriden native methods
263 //---------------------------------
265 // Adds named capture support (with backreferences returned as `result.name`), and fixes two
266 // cross-browser issues per ES3:
267 // - Captured values for nonparticipating capturing groups should be returned as `undefined`,
268 // rather than the empty string.
269 // - `lastIndex` should not be incremented after zero-length matches.
270 RegExp.prototype.exec = function (str) {
271 var match, name, r2, origLastIndex;
273 origLastIndex = this.lastIndex;
274 match = nativ.exec.apply(this, arguments);
276 // Fix browsers whose `exec` methods don't consistently return `undefined` for
277 // nonparticipating capturing groups
278 if (!compliantExecNpcg && match.length > 1 && indexOf(match, "") > -1) {
279 r2 = RegExp(this.source, nativ.replace.call(getNativeFlags(this), "g", ""));
280 // Using `str.slice(match.index)` rather than `match[0]` in case lookahead allowed
281 // matching due to characters outside the match
282 nativ.replace.call((str + "").slice(match.index), r2, function () {
283 for (var i = 1; i < arguments.length - 2; i++) {
284 if (arguments[i] === undefined)
285 match[i] = undefined;
289 // Attach named capture properties
290 if (this._xregexp && this._xregexp.captureNames) {
291 for (var i = 1; i < match.length; i++) {
292 name = this._xregexp.captureNames[i - 1];
294 match[name] = match[i];
297 // Fix browsers that increment `lastIndex` after zero-length matches
298 if (!compliantLastIndexIncrement && this.global && !match[0].length && (this.lastIndex > match.index))
302 this.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows)
306 // Fix browser bugs in native method
307 RegExp.prototype.test = function (str) {
308 // Use the native `exec` to skip some processing overhead, even though the altered
309 // `exec` would take care of the `lastIndex` fixes
310 var match, origLastIndex;
312 origLastIndex = this.lastIndex;
313 match = nativ.exec.call(this, str);
314 // Fix browsers that increment `lastIndex` after zero-length matches
315 if (match && !compliantLastIndexIncrement && this.global && !match[0].length && (this.lastIndex > match.index))
318 this.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows)
322 // Adds named capture support and fixes browser bugs in native method
323 String.prototype.match = function (regex) {
324 if (!XRegExp.isRegExp(regex))
325 regex = RegExp(regex); // Native `RegExp`
327 var result = nativ.match.apply(this, arguments);
328 regex.lastIndex = 0; // Fix IE bug
331 return regex.exec(this); // Run the altered `exec`
334 // Adds support for `${n}` tokens for named and numbered backreferences in replacement text,
335 // and provides named backreferences to replacement functions as `arguments[0].name`. Also
336 // fixes cross-browser differences in replacement text syntax when performing a replacement
337 // using a nonregex search value, and the value of replacement regexes' `lastIndex` property
338 // during replacement iterations. Note that this doesn't support SpiderMonkey's proprietary
339 // third (`flags`) parameter
340 String.prototype.replace = function (search, replacement) {
341 var isRegex = XRegExp.isRegExp(search),
342 captureNames, result, str, origLastIndex;
344 // There are too many combinations of search/replacement types/values and browser bugs that
345 // preclude passing to native `replace`, so don't try
347 // return nativ.replace.apply(this, arguments);
351 captureNames = search._xregexp.captureNames; // Array or `null`
353 origLastIndex = search.lastIndex;
355 search = search + ""; // Type conversion
358 if (Object.prototype.toString.call(replacement) === "[object Function]") {
359 result = nativ.replace.call(this + "", search, function () {
361 // Change the `arguments[0]` string primitive to a String object which can store properties
362 arguments[0] = new String(arguments[0]);
363 // Store named backreferences on `arguments[0]`
364 for (var i = 0; i < captureNames.length; i++) {
366 arguments[0][captureNames[i]] = arguments[i + 1];
369 // Update `lastIndex` before calling `replacement` (fix browsers)
370 if (isRegex && search.global)
371 search.lastIndex = arguments[arguments.length - 2] + arguments[0].length;
372 return replacement.apply(null, arguments);
375 str = this + ""; // Type conversion, so `args[args.length - 1]` will be a string (given nonstring `this`)
376 result = nativ.replace.call(str, search, function () {
377 var args = arguments; // Keep this function's `arguments` available through closure
378 return nativ.replace.call(replacement + "", replacementToken, function ($0, $1, $2) {
379 // Numbered backreference (without delimiters) or special variable
382 case "$": return "$";
383 case "&": return args[0];
384 case "`": return args[args.length - 1].slice(0, args[args.length - 2]);
385 case "'": return args[args.length - 1].slice(args[args.length - 2] + args[0].length);
386 // Numbered backreference
388 // What does "$10" mean?
389 // - Backreference 10, if 10 or more capturing groups exist
390 // - Backreference 1 followed by "0", if 1-9 capturing groups exist
391 // - Otherwise, it's the string "$10"
393 // - Backreferences cannot be more than two digits (enforced by `replacementToken`)
394 // - "$01" is equivalent to "$1" if a capturing group exists, otherwise it's the string "$01"
395 // - There is no "$0" token ("$&" is the entire match)
396 var literalNumbers = "";
397 $1 = +$1; // Type conversion; drop leading zero
398 if (!$1) // `$1` was "0" or "00"
400 while ($1 > args.length - 3) {
401 literalNumbers = String.prototype.slice.call($1, -1) + literalNumbers;
402 $1 = Math.floor($1 / 10); // Drop the last digit
404 return ($1 ? args[$1] || "" : "$") + literalNumbers;
406 // Named backreference or delimited numbered backreference
408 // What does "${n}" mean?
409 // - Backreference to numbered capture n. Two differences from "$n":
410 // - n can be more than two digits
411 // - Backreference 0 is allowed, and is the entire match
412 // - Backreference to named capture n, if it exists and is not a number overridden by numbered capture
413 // - Otherwise, it's the string "${n}"
414 var n = +$2; // Type conversion; drop leading zeros
415 if (n <= args.length - 3)
417 n = captureNames ? indexOf(captureNames, $2) : -1;
418 return n > -1 ? args[n + 1] : $0;
426 search.lastIndex = 0; // Fix IE, Safari bug (last tested IE 9.0.5, Safari 5.1.2 on Windows)
428 search.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows)
434 // A consistent cross-browser, ES3 compliant `split`
435 String.prototype.split = function (s /* separator */, limit) {
436 // If separator `s` is not a regex, use the native `split`
437 if (!XRegExp.isRegExp(s))
438 return nativ.split.apply(this, arguments);
440 var str = this + "", // Type conversion
445 // Behavior for `limit`: if it's...
446 // - `undefined`: No limit
447 // - `NaN` or zero: Return an empty array
448 // - A positive number: Use `Math.floor(limit)`
449 // - A negative number: No limit
450 // - Other: Type-convert, then use the above rules
451 if (limit === undefined || +limit < 0) {
454 limit = Math.floor(+limit);
459 // This is required if not `s.global`, and it avoids needing to set `s.lastIndex` to zero
460 // and restore it to its original value when we're done using the regex
461 s = XRegExp.copyAsGlobal(s);
463 while (match = s.exec(str)) { // Run the altered `exec` (required for `lastIndex` fix, etc.)
464 if (s.lastIndex > lastLastIndex) {
465 output.push(str.slice(lastLastIndex, match.index));
467 if (match.length > 1 && match.index < str.length)
468 Array.prototype.push.apply(output, match.slice(1));
470 lastLength = match[0].length;
471 lastLastIndex = s.lastIndex;
473 if (output.length >= limit)
477 if (s.lastIndex === match.index)
481 if (lastLastIndex === str.length) {
482 if (!nativ.test.call(s, "") || lastLength)
485 output.push(str.slice(lastLastIndex));
488 return output.length > limit ? output.slice(0, limit) : output;
492 //---------------------------------
493 // Private helper functions
494 //---------------------------------
496 // Supporting function for `XRegExp`, `XRegExp.copyAsGlobal`, etc. Returns a copy of a `RegExp`
497 // instance with a fresh `lastIndex` (set to zero), preserving properties required for named
498 // capture. Also allows adding new flags in the process of copying the regex
499 function clone (regex, additionalFlags) {
500 if (!XRegExp.isRegExp(regex))
501 throw TypeError("type RegExp expected");
502 var x = regex._xregexp;
503 regex = XRegExp(regex.source, getNativeFlags(regex) + (additionalFlags || ""));
507 captureNames: x.captureNames ? x.captureNames.slice(0) : null
513 function getNativeFlags (regex) {
514 return (regex.global ? "g" : "") +
515 (regex.ignoreCase ? "i" : "") +
516 (regex.multiline ? "m" : "") +
517 (regex.extended ? "x" : "") + // Proposed for ES4; included in AS3
518 (regex.sticky ? "y" : "");
521 function runTokens (pattern, index, scope, context) {
522 var i = tokens.length,
524 // Protect against constructing XRegExps within token handler and trigger functions
525 isInsideConstructor = true;
526 // Must reset `isInsideConstructor`, even if a `trigger` or `handler` throws
528 while (i--) { // Run in reverse order
530 if ((scope & t.scope) && (!t.trigger || t.trigger.call(context))) {
531 t.pattern.lastIndex = index;
532 match = t.pattern.exec(pattern); // Running the altered `exec` here allows use of named backreferences, etc.
533 if (match && match.index === index) {
535 output: t.handler.call(context, match, scope),
545 isInsideConstructor = false;
550 function indexOf (array, item, from) {
551 if (Array.prototype.indexOf) // Use the native array method if available
552 return array.indexOf(item, from);
553 for (var i = from || 0; i < array.length; i++) {
554 if (array[i] === item)
561 //---------------------------------
563 //---------------------------------
565 // Augment XRegExp's regular expression syntax and flags. Note that when adding tokens, the
566 // third (`scope`) argument defaults to `XRegExp.OUTSIDE_CLASS`
568 // Comment pattern: (?# )
572 // Keep tokens separated unless the following token is a quantifier
573 return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)";
577 // Capturing group (match the opening parenthesis only).
578 // Required for support of named capturing groups
582 this.captureNames.push(null);
587 // Named capturing group (match the opening delimiter only): (?<name>
591 this.captureNames.push(match[1]);
592 this.hasNamedCapture = true;
597 // Named backreference: \k<name>
601 var index = indexOf(this.captureNames, match[1]);
602 // Keep backreferences separate from subsequent literal numbers. Preserve back-
603 // references to named groups that are undefined at this point as literal strings
605 "\\" + (index + 1) + (isNaN(match.input.charAt(match.index + match[0].length)) ? "" : "(?:)") :
610 // Empty character class: [] or [^]
614 // For cross-browser compatibility with ES3, convert [] to \b\B and [^] to [\s\S].
615 // (?!) should work like \b\B, but is unreliable in Firefox
616 return match[0] === "[]" ? "\\b\\B" : "[\\s\\S]";
620 // Mode modifier at the start of the pattern only, with any combination of flags imsx: (?imsx)
621 // Does not support x(?i), (?-i), (?i-m), (?i: ), (?i)(?m), etc.
625 this.setFlag(match[1]);
630 // Whitespace and comments, in free-spacing (aka extended) mode only
634 // Keep tokens separated unless the following token is a quantifier
635 return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)";
637 XRegExp.OUTSIDE_CLASS,
638 function () {return this.hasFlag("x");}
641 // Dot, in dotall (aka singleline) mode only
644 function () {return "[\\s\\S]";},
645 XRegExp.OUTSIDE_CLASS,
646 function () {return this.hasFlag("s");}
650 //---------------------------------
651 // Backward compatibility
652 //---------------------------------
654 // Uncomment the following block for compatibility with XRegExp 1.0-1.2:
656 XRegExp.matchWithinChain = XRegExp.matchChain;
657 RegExp.prototype.addFlags = function (s) {return clone(this, s);};
658 RegExp.prototype.execAll = function (s) {var r = []; XRegExp.iterate(s, this, function (m) {r.push(m);}); return r;};
659 RegExp.prototype.forEachExec = function (s, f, c) {return XRegExp.iterate(s, this, f, c);};
660 RegExp.prototype.validate = function (s) {var r = RegExp("^(?:" + this.source + ")$(?!\\s)", getNativeFlags(this)); if (this.global) this.lastIndex = 0; return s.search(r) === 0;};
666 // Begin anonymous function. This is used to contain local scope variables without polutting global scope.
668 if (typeof(SyntaxHighlighter) == 'undefined') var SyntaxHighlighter = function() {
671 if (typeof(require) != 'undefined' && typeof(XRegExp) == 'undefined')
673 XRegExp = require('XRegExp').XRegExp;
676 // Shortcut object which will be assigned to the SyntaxHighlighter variable.
677 // This is a shorthand for local reference in order to avoid long namespace
678 // references to SyntaxHighlighter.whatever...
681 /** Additional CSS class names to be added to highlighter elements. */
684 /** First line number. */
688 * Pads line numbers. Possible values are:
690 * false - don't pad line numbers.
691 * true - automaticaly pad numbers with minimum required number of leading zeroes.
692 * [int] - length up to which pad line numbers.
694 'pad-line-numbers' : false,
696 /** Lines to highlight. */
699 /** Title to be displayed above the code block. */
702 /** Enables or disables smart tabs. */
705 /** Gets or sets tab size. */
708 /** Enables or disables gutter. */
711 /** Enables or disables toolbar. */
714 /** Enables quick code copy and paste from double click. */
717 /** Forces code view to be collapsed. */
720 /** Enables or disables automatic links. */
723 /** Gets or sets light mode. Equavalent to turning off gutter and toolbar. */
728 'html-script' : false
734 /** Enables use of <SCRIPT type="syntaxhighlighter" /> tags. */
735 useScriptTags : true,
737 /** Blogger mode flag. */
742 /** Name of the tag that SyntaxHighlighter will automatically look for. */
746 expandSource : 'expand source',
748 alert: 'SyntaxHighlighter\n\n',
749 noBrush : 'Can\'t find brush for: ',
750 brushNotHtmlScript : 'Brush wasn\'t configured for html-script option: ',
752 // this is populated by the build script
753 aboutDialog : '@ABOUT@'
757 /** Internal 'global' variables. */
759 discoveredBrushes : null,
763 /** This object is populated by user included external brush files. */
766 /** Common regular expressions. */
768 multiLineCComments : /\/\*[\s\S]*?\*\//gm,
769 singleLineCComments : /\/\/.*$/gm,
770 singleLinePerlComments : /#.*$/gm,
771 doubleQuotedString : /"([^\\"\n]|\\.)*"/g,
772 singleQuotedString : /'([^\\'\n]|\\.)*'/g,
773 multiLineDoubleQuotedString : new XRegExp('"([^\\\\"]|\\\\.)*"', 'gs'),
774 multiLineSingleQuotedString : new XRegExp("'([^\\\\']|\\\\.)*'", 'gs'),
775 xmlComments : /(<|<)!--[\s\S]*?--(>|>)/gm,
776 url : /\w+:\/\/[\w-.\/?%&=:@;#]*/g,
779 phpScriptTags : { left: /(<|<)\?(?:=|php)?/g, right: /\?(>|>)/g, 'eof' : true },
782 aspScriptTags : { left: /(<|<)%=?/g, right: /%(>|>)/g },
784 /** <script> tags. */
785 scriptScriptTags : { left: /(<|<)\s*script.*?(>|>)/gi, right: /(<|<)\/\s*script\s*(>|>)/gi }
790 * Generates HTML markup for the toolbar.
791 * @param {Highlighter} highlighter Highlighter instance.
792 * @return {String} Returns HTML markup.
794 getHtml: function(highlighter)
796 var html = '<div class="toolbar">',
797 items = sh.toolbar.items,
801 function defaultGetHtml(highlighter, name)
803 return sh.toolbar.getButtonHtml(highlighter, name, sh.config.strings[name]);
806 for (var i = 0; i < list.length; i++)
807 html += (items[list[i]].getHtml || defaultGetHtml)(highlighter, list[i]);
815 * Generates HTML markup for a regular button in the toolbar.
816 * @param {Highlighter} highlighter Highlighter instance.
817 * @param {String} commandName Command name that would be executed.
818 * @param {String} label Label text to display.
819 * @return {String} Returns HTML markup.
821 getButtonHtml: function(highlighter, commandName, label)
823 return '<span><a href="#" class="toolbar_item'
824 + ' command_' + commandName
826 + '">' + label + '</a></span>'
831 * Event handler for a toolbar anchor.
835 var target = e.target,
836 className = target.className || ''
839 function getValue(name)
841 var r = new RegExp(name + '_(\\w+)'),
842 match = r.exec(className)
845 return match ? match[1] : null;
848 var highlighter = getHighlighterById(findParentElement(target, '.syntaxhighlighter').id),
849 commandName = getValue('command')
852 // execute the toolbar command
853 if (highlighter && commandName)
854 sh.toolbar.items[commandName].execute(highlighter);
856 // disable default A click behaviour
860 /** Collection of toolbar items. */
862 // Ordered lis of items in the toolbar. Can't expect `for (var n in items)` to be consistent.
863 list: ['expandSource', 'help'],
866 getHtml: function(highlighter)
868 if (highlighter.getParam('collapse') != true)
871 var title = highlighter.getParam('title');
872 return sh.toolbar.getButtonHtml(highlighter, 'expandSource', title ? title : sh.config.strings.expandSource);
875 execute: function(highlighter)
877 var div = getHighlighterDivById(highlighter.id);
878 removeClass(div, 'collapsed');
882 /** Command to display the about dialog window. */
884 execute: function(highlighter)
886 var wnd = popup('', '_blank', 500, 250, 'scrollbars=0'),
890 doc.write(sh.config.strings.aboutDialog);
899 * Finds all elements on the page which should be processes by SyntaxHighlighter.
901 * @param {Object} globalParams Optional parameters which override element's
902 * parameters. Only used if element is specified.
904 * @param {Object} element Optional element to highlight. If none is
905 * provided, all elements in the current document
906 * are returned which qualify.
908 * @return {Array} Returns list of <code>{ target: DOMElement, params: Object }</code> objects.
910 findElements: function(globalParams, element)
912 var elements = element ? [element] : toArray(document.getElementsByTagName(sh.config.tagName)),
917 // support for <SCRIPT TYPE="syntaxhighlighter" /> feature
918 if (conf.useScriptTags)
919 elements = elements.concat(getSyntaxHighlighterScriptTags());
921 if (elements.length === 0)
924 for (var i = 0; i < elements.length; i++)
928 // local params take precedence over globals
929 params: merge(globalParams, parseParams(elements[i].className))
932 if (item.params['brush'] == null)
942 * Shorthand to highlight all elements on the page that are marked as
943 * SyntaxHighlighter source code.
945 * @param {Object} globalParams Optional parameters which override element's
946 * parameters. Only used if element is specified.
948 * @param {Object} element Optional element to highlight. If none is
949 * provided, all elements in the current document
952 highlight: function(globalParams, element)
954 var elements = this.findElements(globalParams, element),
955 propertyName = 'innerHTML',
960 if (elements.length === 0)
963 for (var i = 0; i < elements.length; i++)
965 var element = elements[i],
966 target = element.target,
967 params = element.params,
968 brushName = params.brush,
972 if (brushName == null)
975 // Instantiate a brush
976 if (params['html-script'] == 'true' || sh.defaults['html-script'] == true)
978 highlighter = new sh.HtmlScript(brushName);
979 brushName = 'htmlscript';
983 var brush = findBrush(brushName);
986 highlighter = new brush();
991 code = target[propertyName];
993 // remove CDATA from <SCRIPT/> tags if it's present
994 if (conf.useScriptTags)
995 code = stripCData(code);
997 // Inject title if the attribute is present
998 if ((target.title || '') != '')
999 params.title = target.title;
1001 params['brush'] = brushName;
1002 highlighter.init(params);
1003 element = highlighter.getDiv(code);
1006 if ((target.id || '') != '')
1007 element.id = target.id;
1009 target.parentNode.replaceChild(element, target);
1014 * Main entry point for the SyntaxHighlighter.
1015 * @param {Object} params Optional params to apply to all highlighted elements.
1017 all: function(params)
1022 function() { sh.highlight(params); }
1028 * Checks if target DOM elements has specified CSS class.
1029 * @param {DOMElement} target Target DOM element to check.
1030 * @param {String} className Name of the CSS class to check for.
1031 * @return {Boolean} Returns true if class name is present, false otherwise.
1033 function hasClass(target, className)
1035 return target.className.indexOf(className) != -1;
1039 * Adds CSS class name to the target DOM element.
1040 * @param {DOMElement} target Target DOM element.
1041 * @param {String} className New CSS class to add.
1043 function addClass(target, className)
1045 if (!hasClass(target, className))
1046 target.className += ' ' + className;
1050 * Removes CSS class name from the target DOM element.
1051 * @param {DOMElement} target Target DOM element.
1052 * @param {String} className CSS class to remove.
1054 function removeClass(target, className)
1056 target.className = target.className.replace(className, '');
1060 * Converts the source to array object. Mostly used for function arguments and
1061 * lists returned by getElementsByTagName() which aren't Array objects.
1062 * @param {List} source Source list.
1063 * @return {Array} Returns array.
1065 function toArray(source)
1069 for (var i = 0; i < source.length; i++)
1070 result.push(source[i]);
1076 * Splits block of text into lines.
1077 * @param {String} block Block of text.
1078 * @return {Array} Returns array of lines.
1080 function splitLines(block)
1082 return block.split(/\r?\n/);
1086 * Generates HTML ID for the highlighter.
1087 * @param {String} highlighterId Highlighter ID.
1088 * @return {String} Returns HTML ID.
1090 function getHighlighterId(id)
1092 var prefix = 'highlighter_';
1093 return id.indexOf(prefix) == 0 ? id : prefix + id;
1097 * Finds Highlighter instance by ID.
1098 * @param {String} highlighterId Highlighter ID.
1099 * @return {Highlighter} Returns instance of the highlighter.
1101 function getHighlighterById(id)
1103 return sh.vars.highlighters[getHighlighterId(id)];
1107 * Finds highlighter's DIV container.
1108 * @param {String} highlighterId Highlighter ID.
1109 * @return {Element} Returns highlighter's DIV element.
1111 function getHighlighterDivById(id)
1113 return document.getElementById(getHighlighterId(id));
1117 * Stores highlighter so that getHighlighterById() can do its thing. Each
1118 * highlighter must call this method to preserve itself.
1119 * @param {Highilghter} highlighter Highlighter instance.
1121 function storeHighlighter(highlighter)
1123 sh.vars.highlighters[getHighlighterId(highlighter.id)] = highlighter;
1127 * Looks for a child or parent node which has specified classname.
1128 * Equivalent to jQuery's $(container).find(".className")
1129 * @param {Element} target Target element.
1130 * @param {String} search Class name or node name to look for.
1131 * @param {Boolean} reverse If set to true, will go up the node tree instead of down.
1132 * @return {Element} Returns found child or parent element on null.
1134 function findElement(target, search, reverse /* optional */)
1139 var nodes = reverse != true ? target.childNodes : [ target.parentNode ],
1140 propertyToFind = { '#' : 'id', '.' : 'className' }[search.substr(0, 1)] || 'nodeName',
1145 expectedValue = propertyToFind != 'nodeName'
1147 : search.toUpperCase()
1150 // main return of the found node
1151 if ((target[propertyToFind] || '').indexOf(expectedValue) != -1)
1154 for (var i = 0; nodes && i < nodes.length && found == null; i++)
1155 found = findElement(nodes[i], search, reverse);
1161 * Looks for a parent node which has specified classname.
1162 * This is an alias to <code>findElement(container, className, true)</code>.
1163 * @param {Element} target Target element.
1164 * @param {String} className Class name to look for.
1165 * @return {Element} Returns found parent element on null.
1167 function findParentElement(target, className)
1169 return findElement(target, className, true);
1173 * Finds an index of element in the array.
1175 * @param {Object} searchElement
1176 * @param {Number} fromIndex
1177 * @return {Number} Returns index of element if found; -1 otherwise.
1179 function indexOf(array, searchElement, fromIndex)
1181 fromIndex = Math.max(fromIndex || 0, 0);
1183 for (var i = fromIndex; i < array.length; i++)
1184 if(array[i] == searchElement)
1191 * Generates a unique element ID.
1193 function guid(prefix)
1195 return (prefix || '') + Math.round(Math.random() * 1000000).toString();
1199 * Merges two objects. Values from obj2 override values in obj1.
1200 * Function is NOT recursive and works only for one dimensional objects.
1201 * @param {Object} obj1 First object.
1202 * @param {Object} obj2 Second object.
1203 * @return {Object} Returns combination of both objects.
1205 function merge(obj1, obj2)
1207 var result = {}, name;
1210 result[name] = obj1[name];
1213 result[name] = obj2[name];
1219 * Attempts to convert string to boolean.
1220 * @param {String} value Input string.
1221 * @return {Boolean} Returns true if input was "true", false if input was "false" and value otherwise.
1223 function toBoolean(value)
1225 var result = { "true" : true, "false" : false }[value];
1226 return result == null ? value : result;
1230 * Opens up a centered popup window.
1231 * @param {String} url URL to open in the window.
1232 * @param {String} name Popup name.
1233 * @param {int} width Popup width.
1234 * @param {int} height Popup height.
1235 * @param {String} options window.open() options.
1236 * @return {Window} Returns window instance.
1238 function popup(url, name, width, height, options)
1240 var x = (screen.width - width) / 2,
1241 y = (screen.height - height) / 2
1244 options += ', left=' + x +
1246 ', width=' + width +
1247 ', height=' + height
1249 options = options.replace(/^,/, '');
1251 var win = window.open(url, name, options);
1257 * Adds event handler to the target object.
1258 * @param {Object} obj Target object.
1259 * @param {String} type Name of the event.
1260 * @param {Function} func Handling function.
1262 function attachEvent(obj, type, func, scope)
1266 e = e || window.event;
1270 e.target = e.srcElement;
1271 e.preventDefault = function()
1273 this.returnValue = false;
1277 func.call(scope || window, e);
1280 if (obj.attachEvent)
1282 obj.attachEvent('on' + type, handler);
1286 obj.addEventListener(type, handler, false);
1291 * Displays an alert.
1292 * @param {String} str String to display.
1296 window.alert(sh.config.strings.alert + str);
1300 * Finds a brush by its alias.
1302 * @param {String} alias Brush alias.
1303 * @param {Boolean} showAlert Suppresses the alert if false.
1304 * @return {Brush} Returns bursh constructor if found, null otherwise.
1306 function findBrush(alias, showAlert)
1308 var brushes = sh.vars.discoveredBrushes,
1312 if (brushes == null)
1317 for (var brush in sh.brushes)
1319 var info = sh.brushes[brush],
1320 aliases = info.aliases
1323 if (aliases == null)
1326 // keep the brush name
1327 info.brushName = brush.toLowerCase();
1329 for (var i = 0; i < aliases.length; i++)
1330 brushes[aliases[i]] = brush;
1333 sh.vars.discoveredBrushes = brushes;
1336 result = sh.brushes[brushes[alias]];
1338 if (result == null && showAlert)
1339 alert(sh.config.strings.noBrush + alias);
1345 * Executes a callback on each line and replaces each line with result from the callback.
1346 * @param {Object} str Input string.
1347 * @param {Object} callback Callback function taking one string argument and returning a string.
1349 function eachLine(str, callback)
1351 var lines = splitLines(str);
1353 for (var i = 0; i < lines.length; i++)
1354 lines[i] = callback(lines[i], i);
1356 // include \r to enable copy-paste on windows (ie8) without getting everything on one line
1357 return lines.join('\r\n');
1361 * This is a special trim which only removes first and last empty lines
1362 * and doesn't affect valid leading space on the first line.
1364 * @param {String} str Input string
1365 * @return {String} Returns string without empty first and last lines.
1367 function trimFirstAndLastLines(str)
1369 return str.replace(/^[ ]*[\n]+|[\n]*[ ]*$/g, '');
1373 * Parses key/value pairs into hash object.
1375 * Understands the following formats:
1377 * - name: [word, word];
1382 * name1: value; name2: [value, value]; name3: 'value'
1384 * @param {String} str Input string.
1385 * @return {Object} Returns deserialized object.
1387 function parseParams(str)
1391 arrayRegex = new XRegExp("^\\[(?<values>(.*?))\\]$"),
1392 regex = new XRegExp(
1393 "(?<name>[\\w-]+)" +
1396 "[\\w-%#]+|" + // word
1397 "\\[.*?\\]|" + // [] array
1398 '".*?"|' + // "" string
1399 "'.*?'" + // '' string
1405 while ((match = regex.exec(str)) != null)
1407 var value = match.value
1408 .replace(/^['"]|['"]$/g, '') // strip quotes from end of strings
1411 // try to parse array value
1412 if (value != null && arrayRegex.test(value))
1414 var m = arrayRegex.exec(value);
1415 value = m.values.length > 0 ? m.values.split(/\s*,\s*/) : [];
1418 result[match.name] = value;
1425 * Wraps each line of the string into <code/> tag with given style applied to it.
1427 * @param {String} str Input string.
1428 * @param {String} css Style name to apply to the string.
1429 * @return {String} Returns input string with each line surrounded by <span/> tag.
1431 function wrapLinesWithCode(str, css)
1433 if (str == null || str.length == 0 || str == '\n')
1436 str = str.replace(/</g, '<');
1438 // Replace two or more sequential spaces with leaving last space untouched.
1439 str = str.replace(/ {2,}/g, function(m)
1443 for (var i = 0; i < m.length - 1; i++)
1444 spaces += sh.config.space;
1446 return spaces + ' ';
1449 // Split each line and apply <span class="...">...</span> to them so that
1450 // leading spaces aren't included.
1452 str = eachLine(str, function(line)
1454 if (line.length == 0)
1459 line = line.replace(/^( | )+/, function(s)
1465 if (line.length == 0)
1468 return spaces + '<code class="' + css + '">' + line + '</code>';
1475 * Pads number with zeros until it's length is the same as given length.
1477 * @param {Number} number Number to pad.
1478 * @param {Number} length Max string length with.
1479 * @return {String} Returns a string padded with proper amount of '0'.
1481 function padNumber(number, length)
1483 var result = number.toString();
1485 while (result.length < length)
1486 result = '0' + result;
1492 * Replaces tabs with spaces.
1494 * @param {String} code Source code.
1495 * @param {Number} tabSize Size of the tab.
1496 * @return {String} Returns code with all tabs replaces by spaces.
1498 function processTabs(code, tabSize)
1502 for (var i = 0; i < tabSize; i++)
1505 return code.replace(/\t/g, tab);
1509 * Replaces tabs with smart spaces.
1511 * @param {String} code Code to fix the tabs in.
1512 * @param {Number} tabSize Number of spaces in a column.
1513 * @return {String} Returns code with all tabs replaces with roper amount of spaces.
1515 function processSmartTabs(code, tabSize)
1517 var lines = splitLines(code),
1522 // Create a string with 1000 spaces to copy spaces from...
1523 // It's assumed that there would be no indentation longer than that.
1524 for (var i = 0; i < 50; i++)
1525 spaces += ' '; // 20 spaces * 50
1527 // This function inserts specified amount of spaces in the string
1528 // where a tab is while removing that given tab.
1529 function insertSpaces(line, pos, count)
1531 return line.substr(0, pos)
1532 + spaces.substr(0, count)
1533 + line.substr(pos + 1, line.length) // pos + 1 will get rid of the tab
1537 // Go through all the lines and do the 'smart tabs' magic.
1538 code = eachLine(code, function(line)
1540 if (line.indexOf(tab) == -1)
1545 while ((pos = line.indexOf(tab)) != -1)
1547 // This is pretty much all there is to the 'smart tabs' logic.
1548 // Based on the position within the line and size of a tab,
1549 // calculate the amount of spaces we need to insert.
1550 var spaces = tabSize - pos % tabSize;
1551 line = insertSpaces(line, pos, spaces);
1561 * Performs various string fixes based on configuration.
1563 function fixInputString(str)
1565 var br = /<br\s*\/?>|<br\s*\/?>/gi;
1567 if (sh.config.bloggerMode == true)
1568 str = str.replace(br, '\n');
1570 if (sh.config.stripBrs == true)
1571 str = str.replace(br, '');
1577 * Removes all white space at the begining and end of a string.
1579 * @param {String} str String to trim.
1580 * @return {String} Returns string without leading and following white space characters.
1584 return str.replace(/^\s+|\s+$/g, '');
1588 * Unindents a block of text by the lowest common indent amount.
1589 * @param {String} str Text to unindent.
1590 * @return {String} Returns unindented text block.
1592 function unindent(str)
1594 var lines = splitLines(fixInputString(str)),
1595 indents = new Array(),
1600 // go through every line and check for common number of indents
1601 for (var i = 0; i < lines.length && min > 0; i++)
1603 var line = lines[i];
1605 if (trim(line).length == 0)
1608 var matches = regex.exec(line);
1610 // In the event that just one line doesn't have leading white space
1611 // we can't unindent anything, so bail completely.
1612 if (matches == null)
1615 min = Math.min(matches[0].length, min);
1618 // trim minimum common number of white space from the begining of every line
1620 for (var i = 0; i < lines.length; i++)
1621 lines[i] = lines[i].substr(min);
1623 return lines.join('\n');
1627 * Callback method for Array.sort() which sorts matches by
1628 * index position and then by length.
1630 * @param {Match} m1 Left object.
1631 * @param {Match} m2 Right object.
1632 * @return {Number} Returns -1, 0 or -1 as a comparison result.
1634 function matchesSortCallback(m1, m2)
1636 // sort matches by index first
1637 if(m1.index < m2.index)
1639 else if(m1.index > m2.index)
1643 // if index is the same, sort by length
1644 if(m1.length < m2.length)
1646 else if(m1.length > m2.length)
1654 * Executes given regular expression on provided code and returns all
1655 * matches that are found.
1657 * @param {String} code Code to execute regular expression on.
1658 * @param {Object} regex Regular expression item info from <code>regexList</code> collection.
1659 * @return {Array} Returns a list of Match objects.
1661 function getMatches(code, regexInfo)
1663 function defaultAdd(match, regexInfo)
1671 func = regexInfo.func ? regexInfo.func : defaultAdd
1674 while((match = regexInfo.regex.exec(code)) != null)
1676 var resultMatch = func(match, regexInfo);
1678 if (typeof(resultMatch) == 'string')
1679 resultMatch = [new sh.Match(resultMatch, match.index, regexInfo.css)];
1681 matches = matches.concat(resultMatch);
1688 * Turns all URLs in the code into <a/> tags.
1689 * @param {String} code Input code.
1690 * @return {String} Returns code with </a> tags.
1692 function processUrls(code)
1694 var gt = /(.*)((>|<).*)/;
1696 return code.replace(sh.regexLib.url, function(m)
1702 // We include < and > in the URL for the common cases like <http://google.com>
1703 // The problem is that they get transformed into <http://google.com>
1704 // Where as > easily looks like part of the URL string.
1706 if (match = gt.exec(m))
1712 return '<a href="' + m + '">' + m + '</a>' + suffix;
1717 * Finds all <SCRIPT TYPE="syntaxhighlighter" /> elementss.
1718 * @return {Array} Returns array of all found SyntaxHighlighter tags.
1720 function getSyntaxHighlighterScriptTags()
1722 var tags = document.getElementsByTagName('script'),
1726 for (var i = 0; i < tags.length; i++)
1727 if (tags[i].type == 'syntaxhighlighter')
1728 result.push(tags[i]);
1734 * Strips <![CDATA[]]> from <SCRIPT /> content because it should be used
1735 * there in most cases for XHTML compliance.
1736 * @param {String} original Input code.
1737 * @return {String} Returns code without leading <![CDATA[]]> tags.
1739 function stripCData(original)
1741 var left = '<![CDATA[',
1743 // for some reason IE inserts some leading blanks here
1744 copy = trim(original),
1746 leftLength = left.length,
1747 rightLength = right.length
1750 if (copy.indexOf(left) == 0)
1752 copy = copy.substring(leftLength);
1756 var copyLength = copy.length;
1758 if (copy.indexOf(right) == copyLength - rightLength)
1760 copy = copy.substring(0, copyLength - rightLength);
1764 return changed ? copy : original;
1769 * Quick code mouse double click handler.
1771 function quickCodeHandler(e)
1773 var target = e.target,
1774 highlighterDiv = findParentElement(target, '.syntaxhighlighter'),
1775 container = findParentElement(target, '.container'),
1776 textarea = document.createElement('textarea'),
1780 if (!container || !highlighterDiv || findElement(container, 'textarea'))
1783 highlighter = getHighlighterById(highlighterDiv.id);
1785 // add source class name
1786 addClass(highlighterDiv, 'source');
1788 // Have to go over each line and grab it's text, can't just do it on the
1789 // container because Firefox loses all \n where as Webkit doesn't.
1790 var lines = container.childNodes,
1794 for (var i = 0; i < lines.length; i++)
1795 code.push(lines[i].innerText || lines[i].textContent);
1797 // using \r instead of \r or \r\n makes this work equally well on IE, FF and Webkit
1798 code = code.join('\r');
1800 // For Webkit browsers, replace nbsp with a breaking space
1801 code = code.replace(/\u00a0/g, " ");
1803 // inject <textarea/> tag
1804 textarea.appendChild(document.createTextNode(code));
1805 container.appendChild(textarea);
1807 // preselect all text
1811 // set up handler for lost focus
1812 attachEvent(textarea, 'blur', function(e)
1814 textarea.parentNode.removeChild(textarea);
1815 removeClass(highlighterDiv, 'source');
1822 sh.Match = function(value, index, css)
1826 this.length = value.length;
1828 this.brushName = null;
1831 sh.Match.prototype.toString = function()
1837 * Simulates HTML code with a scripting language embedded.
1839 * @param {String} scriptBrushName Brush name of the scripting language.
1841 sh.HtmlScript = function(scriptBrushName)
1843 var brushClass = findBrush(scriptBrushName),
1845 xmlBrush = new sh.brushes.Xml(),
1846 bracketsRegex = null,
1848 methodsToExpose = 'getDiv getHtml init'.split(' ')
1851 if (brushClass == null)
1854 scriptBrush = new brushClass();
1856 for(var i = 0; i < methodsToExpose.length; i++)
1857 // make a closure so we don't lose the name after i changes
1859 var name = methodsToExpose[i];
1861 ref[name] = function()
1863 return xmlBrush[name].apply(xmlBrush, arguments);
1867 if (scriptBrush.htmlScript == null)
1869 alert(sh.config.strings.brushNotHtmlScript + scriptBrushName);
1873 xmlBrush.regexList.push(
1874 { regex: scriptBrush.htmlScript.code, func: process }
1877 function offsetMatches(matches, offset)
1879 for (var j = 0; j < matches.length; j++)
1880 matches[j].index += offset;
1883 function process(match, info)
1885 var code = match.code,
1887 regexList = scriptBrush.regexList,
1888 offset = match.index + match.left.length,
1889 htmlScript = scriptBrush.htmlScript,
1893 // add all matches from the code
1894 for (var i = 0; i < regexList.length; i++)
1896 result = getMatches(code, regexList[i]);
1897 offsetMatches(result, offset);
1898 matches = matches.concat(result);
1901 // add left script bracket
1902 if (htmlScript.left != null && match.left != null)
1904 result = getMatches(match.left, htmlScript.left);
1905 offsetMatches(result, match.index);
1906 matches = matches.concat(result);
1909 // add right script bracket
1910 if (htmlScript.right != null && match.right != null)
1912 result = getMatches(match.right, htmlScript.right);
1913 offsetMatches(result, match.index + match[0].lastIndexOf(match.right));
1914 matches = matches.concat(result);
1917 for (var j = 0; j < matches.length; j++)
1918 matches[j].brushName = brushClass.brushName;
1925 * Main Highlither class.
1928 sh.Highlighter = function()
1930 // not putting any code in here because of the prototype inheritance
1933 sh.Highlighter.prototype = {
1935 * Returns value of the parameter passed to the highlighter.
1936 * @param {String} name Name of the parameter.
1937 * @param {Object} defaultValue Default value.
1938 * @return {Object} Returns found value or default value otherwise.
1940 getParam: function(name, defaultValue)
1942 var result = this.params[name];
1943 return toBoolean(result == null ? defaultValue : result);
1947 * Shortcut to document.createElement().
1948 * @param {String} name Name of the element to create (DIV, A, etc).
1949 * @return {HTMLElement} Returns new HTML element.
1951 create: function(name)
1953 return document.createElement(name);
1957 * Applies all regular expression to the code and stores all found
1958 * matches in the `this.matches` array.
1959 * @param {Array} regexList List of regular expressions.
1960 * @param {String} code Source code.
1961 * @return {Array} Returns list of matches.
1963 findMatches: function(regexList, code)
1967 if (regexList != null)
1968 for (var i = 0; i < regexList.length; i++)
1969 // BUG: length returns len+1 for array if methods added to prototype chain (oising@gmail.com)
1970 if (typeof (regexList[i]) == "object")
1971 result = result.concat(getMatches(code, regexList[i]));
1973 // sort and remove nested the matches
1974 return this.removeNestedMatches(result.sort(matchesSortCallback));
1978 * Checks to see if any of the matches are inside of other matches.
1979 * This process would get rid of highligted strings inside comments,
1980 * keywords inside strings and so on.
1982 removeNestedMatches: function(matches)
1984 // Optimized by Jose Prado (http://joseprado.com)
1985 for (var i = 0; i < matches.length; i++)
1987 if (matches[i] === null)
1990 var itemI = matches[i],
1991 itemIEndPos = itemI.index + itemI.length
1994 for (var j = i + 1; j < matches.length && matches[i] !== null; j++)
1996 var itemJ = matches[j];
2000 else if (itemJ.index > itemIEndPos)
2002 else if (itemJ.index == itemI.index && itemJ.length > itemI.length)
2004 else if (itemJ.index >= itemI.index && itemJ.index < itemIEndPos)
2013 * Creates an array containing integer line numbers starting from the 'first-line' param.
2014 * @return {Array} Returns array of integers.
2016 figureOutLineNumbers: function(code)
2019 firstLine = parseInt(this.getParam('first-line'))
2022 eachLine(code, function(line, index)
2024 lines.push(index + firstLine);
2031 * Determines if specified line number is in the highlighted list.
2033 isLineHighlighted: function(lineNumber)
2035 var list = this.getParam('highlight', []);
2037 if (typeof(list) != 'object' && list.push == null)
2040 return indexOf(list, lineNumber.toString()) != -1;
2044 * Generates HTML markup for a single line of code while determining alternating line style.
2045 * @param {Integer} lineNumber Line number.
2046 * @param {String} code Line HTML markup.
2047 * @return {String} Returns HTML markup.
2049 getLineHtml: function(lineIndex, lineNumber, code)
2053 'number' + lineNumber,
2054 'index' + lineIndex,
2055 'alt' + (lineNumber % 2 == 0 ? 1 : 2).toString()
2058 if (this.isLineHighlighted(lineNumber))
2059 classes.push('highlighted');
2061 if (lineNumber == 0)
2062 classes.push('break');
2064 return '<div class="' + classes.join(' ') + '">' + code + '</div>';
2068 * Generates HTML markup for line number column.
2069 * @param {String} code Complete code HTML markup.
2070 * @param {Array} lineNumbers Calculated line numbers.
2071 * @return {String} Returns HTML markup.
2073 getLineNumbersHtml: function(code, lineNumbers)
2076 count = splitLines(code).length,
2077 firstLine = parseInt(this.getParam('first-line')),
2078 pad = this.getParam('pad-line-numbers')
2082 pad = (firstLine + count - 1).toString().length;
2083 else if (isNaN(pad) == true)
2086 for (var i = 0; i < count; i++)
2088 var lineNumber = lineNumbers ? lineNumbers[i] : firstLine + i,
2089 code = lineNumber == 0 ? sh.config.space : padNumber(lineNumber, pad)
2092 html += this.getLineHtml(i, lineNumber, code);
2099 * Splits block of text into individual DIV lines.
2100 * @param {String} code Code to highlight.
2101 * @param {Array} lineNumbers Calculated line numbers.
2102 * @return {String} Returns highlighted code in HTML form.
2104 getCodeLinesHtml: function(html, lineNumbers)
2108 var lines = splitLines(html),
2109 padLength = this.getParam('pad-line-numbers'),
2110 firstLine = parseInt(this.getParam('first-line')),
2112 brushName = this.getParam('brush')
2115 for (var i = 0; i < lines.length; i++)
2117 var line = lines[i],
2118 indent = /^( |\s)+/.exec(line),
2120 lineNumber = lineNumbers ? lineNumbers[i] : firstLine + i;
2125 spaces = indent[0].toString();
2126 line = line.substr(spaces.length);
2127 spaces = spaces.replace(' ', sh.config.space);
2132 if (line.length == 0)
2133 line = sh.config.space;
2135 html += this.getLineHtml(
2138 (spaces != null ? '<code class="' + brushName + ' spaces">' + spaces + '</code>' : '') + line
2146 * Returns HTML for the table title or empty string if title is null.
2148 getTitleHtml: function(title)
2150 return title ? '<caption>' + title + '</caption>' : '';
2154 * Finds all matches in the source code.
2155 * @param {String} code Source code to process matches in.
2156 * @param {Array} matches Discovered regex matches.
2157 * @return {String} Returns formatted HTML with processed mathes.
2159 getMatchesHtml: function(code, matches)
2163 brushName = this.getParam('brush', '')
2166 function getBrushNameCss(match)
2168 var result = match ? (match.brushName || brushName) : brushName;
2169 return result ? result + ' ' : '';
2172 // Finally, go through the final list of matches and pull the all
2173 // together adding everything in between that isn't a match.
2174 for (var i = 0; i < matches.length; i++)
2176 var match = matches[i],
2180 if (match === null || match.length === 0)
2183 matchBrushName = getBrushNameCss(match);
2185 result += wrapLinesWithCode(code.substr(pos, match.index - pos), matchBrushName + 'plain')
2186 + wrapLinesWithCode(match.value, matchBrushName + match.css)
2189 pos = match.index + match.length + (match.offset || 0);
2192 // don't forget to add whatever's remaining in the string
2193 result += wrapLinesWithCode(code.substr(pos), getBrushNameCss() + 'plain');
2199 * Generates HTML markup for the whole syntax highlighter.
2200 * @param {String} code Source code.
2201 * @return {String} Returns HTML markup.
2203 getHtml: function(code)
2206 classes = [ 'syntaxhighlighter' ],
2212 // process light mode
2213 if (this.getParam('light') == true)
2214 this.params.toolbar = this.params.gutter = false;
2216 className = 'syntaxhighlighter';
2218 if (this.getParam('collapse') == true)
2219 classes.push('collapsed');
2221 if ((gutter = this.getParam('gutter')) == false)
2222 classes.push('nogutter');
2224 // add custom user style name
2225 classes.push(this.getParam('class-name'));
2227 // add brush alias to the class name for custom CSS
2228 classes.push(this.getParam('brush'));
2230 code = trimFirstAndLastLines(code)
2231 .replace(/\r/g, ' ') // IE lets these buggers through
2234 tabSize = this.getParam('tab-size');
2236 // replace tabs with spaces
2237 code = this.getParam('smart-tabs') == true
2238 ? processSmartTabs(code, tabSize)
2239 : processTabs(code, tabSize)
2242 // unindent code by the common indentation
2243 if (this.getParam('unindent'))
2244 code = unindent(code);
2247 lineNumbers = this.figureOutLineNumbers(code);
2249 // find matches in the code using brushes regex list
2250 matches = this.findMatches(this.regexList, code);
2251 // processes found matches into the html
2252 html = this.getMatchesHtml(code, matches);
2253 // finally, split all lines so that they wrap well
2254 html = this.getCodeLinesHtml(html, lineNumbers);
2256 // finally, process the links
2257 if (this.getParam('auto-links'))
2258 html = processUrls(html);
2260 if (typeof(navigator) != 'undefined' && navigator.userAgent && navigator.userAgent.match(/MSIE/))
2264 '<div id="' + getHighlighterId(this.id) + '" class="' + classes.join(' ') + '">'
2265 + (this.getParam('toolbar') ? sh.toolbar.getHtml(this) : '')
2266 + '<table border="0" cellpadding="0" cellspacing="0">'
2267 + this.getTitleHtml(this.getParam('title'))
2270 + (gutter ? '<td class="gutter">' + this.getLineNumbersHtml(code) + '</td>' : '')
2271 + '<td class="code">'
2272 + '<div class="container">'
2286 * Highlights the code and returns complete HTML.
2287 * @param {String} code Code to highlight.
2288 * @return {Element} Returns container DIV element with all markup.
2290 getDiv: function(code)
2297 var div = this.create('div');
2300 div.innerHTML = this.getHtml(code);
2302 // set up click handlers
2303 if (this.getParam('toolbar'))
2304 attachEvent(findElement(div, '.toolbar'), 'click', sh.toolbar.handler);
2306 if (this.getParam('quick-code'))
2307 attachEvent(findElement(div, '.code'), 'dblclick', quickCodeHandler);
2313 * Initializes the highlighter/brush.
2315 * Constructor isn't used for initialization so that nothing executes during necessary
2316 * `new SyntaxHighlighter.Highlighter()` call when setting up brush inheritence.
2318 * @param {Hash} params Highlighter parameters.
2320 init: function(params)
2324 // register this instance in the highlighters list
2325 storeHighlighter(this);
2327 // local params take precedence over defaults
2328 this.params = merge(sh.defaults, params || {})
2330 // process light mode
2331 if (this.getParam('light') == true)
2332 this.params.toolbar = this.params.gutter = false;
2336 * Converts space separated list of keywords into a regular expression string.
2337 * @param {String} str Space separated keywords.
2338 * @return {String} Returns regular expression string.
2340 getKeywords: function(str)
2343 .replace(/^\s+|\s+$/g, '')
2344 .replace(/\s+/g, '|')
2347 return '\\b(?:' + str + ')\\b';
2351 * Makes a brush compatible with the `html-script` functionality.
2352 * @param {Object} regexGroup Object containing `left` and `right` regular expressions.
2354 forHtmlScript: function(regexGroup)
2356 var regex = { 'end' : regexGroup.right.source };
2359 regex.end = "(?:(?:" + regex.end + ")|$)";
2362 left : { regex: regexGroup.left, css: 'script' },
2363 right : { regex: regexGroup.right, css: 'script' },
2365 "(?<left>" + regexGroup.left.source + ")" +
2367 "(?<right>" + regex.end + ")",
2372 }; // end of Highlighter
2375 }(); // end of anonymous function
2378 typeof(exports) != 'undefined' ? exports.SyntaxHighlighter = SyntaxHighlighter : null;/**
2380 * http://alexgorbatchev.com/SyntaxHighlighter
2382 * SyntaxHighlighter is donationware. If you are using it, please donate.
2383 * http://alexgorbatchev.com/SyntaxHighlighter/donate.html
2386 * 3.0.83 (July 02 2010)
2389 * Copyright (C) 2004-2010 Alex Gorbatchev.
2392 * Dual licensed under the MIT and GPL licenses.
2397 typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;
2401 var keywords = 'break case catch continue ' +
2402 'default delete do else false ' +
2403 'for function if in instanceof ' +
2404 'new null return super switch ' +
2405 'this throw true try typeof var while with'
2409 'fnSettings fnDraw fnFilter fnSort fnAddData fnDeleteRow fnClearTable fnGetData '+
2410 'fnGetPosition fnGetNodes fnOpen fnClose fnUpdate fnSetColumnVis fnVersionCheck '+
2411 'fnPageChange fnSortListener fnDestroy fnAdjustColumnSizing';
2413 var r = SyntaxHighlighter.regexLib;
2416 { regex: r.multiLineDoubleQuotedString, css: 'string' }, // double quoted strings
2417 { regex: r.multiLineSingleQuotedString, css: 'string' }, // single quoted strings
2418 { regex: r.singleLineCComments, css: 'comments' }, // one line comments
2419 { regex: r.multiLineCComments, css: 'comments' }, // multiline comments
2420 { regex: /\s*#.*/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion
2421 { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // keywords
2422 { regex: new RegExp(this.getKeywords(datatablesAPI), 'gm'), css: 'dtapi' } // DataTables API methods
2425 this.forHtmlScript(r.scriptScriptTags);
2428 Brush.prototype = new SyntaxHighlighter.Highlighter();
2429 Brush.aliases = ['js', 'jscript', 'javascript'];
2431 SyntaxHighlighter.brushes.JScript = Brush;
2434 typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
2438 http://www.JSON.org/json2.js
2442 var JSON;JSON||(JSON={});
2443 (function(){function k(a){return a<10?"0"+a:a}function o(a){p.lastIndex=0;return p.test(a)?'"'+a.replace(p,function(a){var c=r[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function l(a,j){var c,d,h,m,g=e,f,b=j[a];b&&typeof b==="object"&&typeof b.toJSON==="function"&&(b=b.toJSON(a));typeof i==="function"&&(b=i.call(j,a,b));switch(typeof b){case "string":return o(b);case "number":return isFinite(b)?String(b):"null";case "boolean":case "null":return String(b);case "object":if(!b)return"null";
2444 e+=n;f=[];if(Object.prototype.toString.apply(b)==="[object Array]"){m=b.length;for(c=0;c<m;c+=1)f[c]=l(c,b)||"null";h=f.length===0?"[]":e?"[\n"+e+f.join(",\n"+e)+"\n"+g+"]":"["+f.join(",")+"]";e=g;return h}if(i&&typeof i==="object"){m=i.length;for(c=0;c<m;c+=1)typeof i[c]==="string"&&(d=i[c],(h=l(d,b))&&f.push(o(d)+(e?": ":":")+h))}else for(d in b)Object.prototype.hasOwnProperty.call(b,d)&&(h=l(d,b))&&f.push(o(d)+(e?": ":":")+h);h=f.length===0?"{}":e?"{\n"+e+f.join(",\n"+e)+"\n"+g+"}":"{"+f.join(",")+
2445 "}";e=g;return h}}if(typeof Date.prototype.toJSON!=="function")Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+k(this.getUTCMonth()+1)+"-"+k(this.getUTCDate())+"T"+k(this.getUTCHours())+":"+k(this.getUTCMinutes())+":"+k(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()};var q=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
2446 p=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,e,n,r={"\u0008":"\\b","\t":"\\t","\n":"\\n","\u000c":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},i;if(typeof JSON.stringify!=="function")JSON.stringify=function(a,j,c){var d;n=e="";if(typeof c==="number")for(d=0;d<c;d+=1)n+=" ";else typeof c==="string"&&(n=c);if((i=j)&&typeof j!=="function"&&(typeof j!=="object"||typeof j.length!=="number"))throw Error("JSON.stringify");return l("",
2447 {"":a})};if(typeof JSON.parse!=="function")JSON.parse=function(a,e){function c(a,d){var g,f,b=a[d];if(b&&typeof b==="object")for(g in b)Object.prototype.hasOwnProperty.call(b,g)&&(f=c(b,g),f!==void 0?b[g]=f:delete b[g]);return e.call(a,d,b)}var d,a=String(a);q.lastIndex=0;q.test(a)&&(a=a.replace(q,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
2448 "]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return d=eval("("+a+")"),typeof e==="function"?c({"":d},""):d;throw new SyntaxError("JSON.parse");}})();
2451 /* Self initialise */
2452 SyntaxHighlighter.all();
2454 $(window).load( function() {
2474 'fnAdjustColumnSizing',
2533 'fnStateLoadCallback',
2534 'fnStateSaveCallback',
2542 'fnPreDrawCallback',
2584 'fnAdjustColumnSizing',
2643 'fnStateLoadCallback',
2644 'fnStateSaveCallback',
2652 'fnPreDrawCallback',
2655 'oLanguage.sLoadingRecords',
2656 'oLanguage.sProcessing',
2657 'oLanguage.sLengthMenu',
2658 'oLanguage.sZeroRecords',
2660 'oLanguage.sInfoEmpty',
2661 'oLanguage.sInfoFiltered',
2662 'oLanguage.sInfoPostFix',
2663 'oLanguage.sSearch',
2665 'oLanguage.oPaginate.sFirst',
2666 'oLanguage.oPaginate.sPrevious',
2667 'oLanguage.oPaginate.sNext',
2668 'oLanguage.oPaginate.sLast',
2669 'oLanguage.sEmptyTable',
2670 'oLanguage.sInfoThousands',
2675 /* Add a class to all the strings which are DataTables init options or API methods */
2676 var dtList = '"'+ dtOptions.join( '" "' ) +'"';
2677 $('code.string').each( function () {
2678 if ( dtList.indexOf( this.innerHTML ) !== -1 ) {
2679 $(this).addClass( 'datatables_ref' );
2683 $('code.dtapi').each( function () {
2684 if ( dtList.indexOf( this.innerHTML ) !== -1 ) {
2685 $(this).addClass( 'datatables_ref' );
2689 /* Click handler to redirect to the documentation */
2690 $('code.datatables_ref').live('click', function () {
2691 var i = $.inArray( this.innerHTML.replace(/"/g,''), dtOptions );
2693 window.location.href = "http://datatables.net/ref#"+dtLinks[i];
2698 /* Show and syntax highlight XHR returns from the server */
2699 $(document).ready( function () {
2700 if ( $.fn.dataTableSettings.length >= 1 ) {
2701 $('#example').bind('xhr', function ( e, oSettings, json ) {
2702 var n = document.getElementById('latest_xhr');
2704 n.innerHTML = JSON.stringify( json, null, 2 );
2705 n.className = "brush: js;"
2706 SyntaxHighlighter.highlight({}, n);