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 : ' ', 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('(<|<)!--.*?--(>|>)', 'gs'), 1393 url : /\w+:\/\/[\w-.\/?%&=:@;#]*/g, 1394 phpScriptTags : { left: /(<|<)\?(?:=|php)?/g, right: /\?(>|>)/g, 'eof' : true }, 1395 aspScriptTags : { left: /(<|<)%=?/g, right: /%(>|>)/g }, 1396 scriptScriptTags : { left: /(<|<)\s*script.*?(>|>)/gi, right: /(<|<)\/\s*script\s*(>|>)/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, '"'); 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, '<'); 2059 2060 // Replace two or more sequential spaces with 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(/^( | )+/, 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*\/?>|<br\s*\/?>/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 = /(.*)((>|<).*)/; 2319 2320 return code.replace(sh.regexLib.url, function(m) 2321 { 2322 var suffix = '', 2323 match = null 2324 ; 2325 2326 // We include < and > in the URL for the common cases like <http://google.com> 2327 // The problem is that they get transformed into <http://google.com> 2328 // Where as > 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 = /^( |\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