XRegExp.js (29440B)
1 // XRegExp 1.5.1 2 // (c) 2007-2012 Steven Levithan 3 // MIT License 4 // <http://xregexp.com> 5 // Provides an augmented, extensible, cross-browser implementation of regular expressions, 6 // including support for additional syntax, flags, and methods 7 8 var XRegExp; 9 10 if (XRegExp) { 11 // Avoid running twice, since that would break references to native globals 12 throw Error("can't load XRegExp twice in the same frame"); 13 } 14 15 // Run within an anonymous function to protect variables and avoid new globals 16 (function (undefined) { 17 18 //--------------------------------- 19 // Constructor 20 //--------------------------------- 21 22 // Accepts a pattern and flags; returns a new, extended `RegExp` object. Differs from a native 23 // regular expression in that additional syntax and flags are supported and cross-browser 24 // syntax inconsistencies are ameliorated. `XRegExp(/regex/)` clones an existing regex and 25 // converts to type XRegExp 26 XRegExp = function (pattern, flags) { 27 var output = [], 28 currScope = XRegExp.OUTSIDE_CLASS, 29 pos = 0, 30 context, tokenResult, match, chr, regex; 31 32 if (XRegExp.isRegExp(pattern)) { 33 if (flags !== undefined) 34 throw TypeError("can't supply flags when constructing one RegExp from another"); 35 return clone(pattern); 36 } 37 // Tokens become part of the regex construction process, so protect against infinite 38 // recursion when an XRegExp is constructed within a token handler or trigger 39 if (isInsideConstructor) 40 throw Error("can't call the XRegExp constructor within token definition functions"); 41 42 flags = flags || ""; 43 context = { // `this` object for custom tokens 44 hasNamedCapture: false, 45 captureNames: [], 46 hasFlag: function (flag) {return flags.indexOf(flag) > -1;}, 47 setFlag: function (flag) {flags += flag;} 48 }; 49 50 while (pos < pattern.length) { 51 // Check for custom tokens at the current position 52 tokenResult = runTokens(pattern, pos, currScope, context); 53 54 if (tokenResult) { 55 output.push(tokenResult.output); 56 pos += (tokenResult.match[0].length || 1); 57 } else { 58 // Check for native multicharacter metasequences (excluding character classes) at 59 // the current position 60 if (match = nativ.exec.call(nativeTokens[currScope], pattern.slice(pos))) { 61 output.push(match[0]); 62 pos += match[0].length; 63 } else { 64 chr = pattern.charAt(pos); 65 if (chr === "[") 66 currScope = XRegExp.INSIDE_CLASS; 67 else if (chr === "]") 68 currScope = XRegExp.OUTSIDE_CLASS; 69 // Advance position one character 70 output.push(chr); 71 pos++; 72 } 73 } 74 } 75 76 regex = RegExp(output.join(""), nativ.replace.call(flags, flagClip, "")); 77 regex._xregexp = { 78 source: pattern, 79 captureNames: context.hasNamedCapture ? context.captureNames : null 80 }; 81 return regex; 82 }; 83 84 85 //--------------------------------- 86 // Public properties 87 //--------------------------------- 88 89 XRegExp.version = "1.5.1"; 90 91 // Token scope bitflags 92 XRegExp.INSIDE_CLASS = 1; 93 XRegExp.OUTSIDE_CLASS = 2; 94 95 96 //--------------------------------- 97 // Private variables 98 //--------------------------------- 99 100 var replacementToken = /\$(?:(\d\d?|[$&`'])|{([$\w]+)})/g, 101 flagClip = /[^gimy]+|([\s\S])(?=[\s\S]*\1)/g, // Nonnative and duplicate flags 102 quantifier = /^(?:[?*+]|{\d+(?:,\d*)?})\??/, 103 isInsideConstructor = false, 104 tokens = [], 105 // Copy native globals for reference ("native" is an ES3 reserved keyword) 106 nativ = { 107 exec: RegExp.prototype.exec, 108 test: RegExp.prototype.test, 109 match: String.prototype.match, 110 replace: String.prototype.replace, 111 split: String.prototype.split 112 }, 113 compliantExecNpcg = nativ.exec.call(/()??/, "")[1] === undefined, // check `exec` handling of nonparticipating capturing groups 114 compliantLastIndexIncrement = function () { 115 var x = /^/g; 116 nativ.test.call(x, ""); 117 return !x.lastIndex; 118 }(), 119 hasNativeY = RegExp.prototype.sticky !== undefined, 120 nativeTokens = {}; 121 122 // `nativeTokens` match native multicharacter metasequences only (including deprecated octals, 123 // excluding character classes) 124 nativeTokens[XRegExp.INSIDE_CLASS] = /^(?:\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|c[A-Za-z]|[\s\S]))/; 125 nativeTokens[XRegExp.OUTSIDE_CLASS] = /^(?:\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9]\d*|x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|c[A-Za-z]|[\s\S])|\(\?[:=!]|[?*+]\?|{\d+(?:,\d*)?}\??)/; 126 127 128 //--------------------------------- 129 // Public methods 130 //--------------------------------- 131 132 // Lets you extend or change XRegExp syntax and create custom flags. This is used internally by 133 // the XRegExp library and can be used to create XRegExp plugins. This function is intended for 134 // users with advanced knowledge of JavaScript's regular expression syntax and behavior. It can 135 // be disabled by `XRegExp.freezeTokens` 136 XRegExp.addToken = function (regex, handler, scope, trigger) { 137 tokens.push({ 138 pattern: clone(regex, "g" + (hasNativeY ? "y" : "")), 139 handler: handler, 140 scope: scope || XRegExp.OUTSIDE_CLASS, 141 trigger: trigger || null 142 }); 143 }; 144 145 // Accepts a pattern and flags; returns an extended `RegExp` object. If the pattern and flag 146 // combination has previously been cached, the cached copy is returned; otherwise the newly 147 // created regex is cached 148 XRegExp.cache = function (pattern, flags) { 149 var key = pattern + "/" + (flags || ""); 150 return XRegExp.cache[key] || (XRegExp.cache[key] = XRegExp(pattern, flags)); 151 }; 152 153 // Accepts a `RegExp` instance; returns a copy with the `/g` flag set. The copy has a fresh 154 // `lastIndex` (set to zero). If you want to copy a regex without forcing the `global` 155 // property, use `XRegExp(regex)`. Do not use `RegExp(regex)` because it will not preserve 156 // special properties required for named capture 157 XRegExp.copyAsGlobal = function (regex) { 158 return clone(regex, "g"); 159 }; 160 161 // Accepts a string; returns the string with regex metacharacters escaped. The returned string 162 // can safely be used at any point within a regex to match the provided literal string. Escaped 163 // characters are [ ] { } ( ) * + ? - . , \ ^ $ | # and whitespace 164 XRegExp.escape = function (str) { 165 return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 166 }; 167 168 // Accepts a string to search, regex to search with, position to start the search within the 169 // string (default: 0), and an optional Boolean indicating whether matches must start at-or- 170 // after the position or at the specified position only. This function ignores the `lastIndex` 171 // of the provided regex in its own handling, but updates the property for compatibility 172 XRegExp.execAt = function (str, regex, pos, anchored) { 173 var r2 = clone(regex, "g" + ((anchored && hasNativeY) ? "y" : "")), 174 match; 175 r2.lastIndex = pos = pos || 0; 176 match = r2.exec(str); // Run the altered `exec` (required for `lastIndex` fix, etc.) 177 if (anchored && match && match.index !== pos) 178 match = null; 179 if (regex.global) 180 regex.lastIndex = match ? r2.lastIndex : 0; 181 return match; 182 }; 183 184 // Breaks the unrestorable link to XRegExp's private list of tokens, thereby preventing 185 // syntax and flag changes. Should be run after XRegExp and any plugins are loaded 186 XRegExp.freezeTokens = function () { 187 XRegExp.addToken = function () { 188 throw Error("can't run addToken after freezeTokens"); 189 }; 190 }; 191 192 // Accepts any value; returns a Boolean indicating whether the argument is a `RegExp` object. 193 // Note that this is also `true` for regex literals and regexes created by the `XRegExp` 194 // constructor. This works correctly for variables created in another frame, when `instanceof` 195 // and `constructor` checks would fail to work as intended 196 XRegExp.isRegExp = function (o) { 197 return Object.prototype.toString.call(o) === "[object RegExp]"; 198 }; 199 200 // Executes `callback` once per match within `str`. Provides a simpler and cleaner way to 201 // iterate over regex matches compared to the traditional approaches of subverting 202 // `String.prototype.replace` or repeatedly calling `exec` within a `while` loop 203 XRegExp.iterate = function (str, regex, callback, context) { 204 var r2 = clone(regex, "g"), 205 i = -1, match; 206 while (match = r2.exec(str)) { // Run the altered `exec` (required for `lastIndex` fix, etc.) 207 if (regex.global) 208 regex.lastIndex = r2.lastIndex; // Doing this to follow expectations if `lastIndex` is checked within `callback` 209 callback.call(context, match, ++i, str, regex); 210 if (r2.lastIndex === match.index) 211 r2.lastIndex++; 212 } 213 if (regex.global) 214 regex.lastIndex = 0; 215 }; 216 217 // Accepts a string and an array of regexes; returns the result of using each successive regex 218 // to search within the matches of the previous regex. The array of regexes can also contain 219 // objects with `regex` and `backref` properties, in which case the named or numbered back- 220 // references specified are passed forward to the next regex or returned. E.g.: 221 // var xregexpImgFileNames = XRegExp.matchChain(html, [ 222 // {regex: /<img\b([^>]+)>/i, backref: 1}, // <img> tag attributes 223 // {regex: XRegExp('(?ix) \\s src=" (?<src> [^"]+ )'), backref: "src"}, // src attribute values 224 // {regex: XRegExp("^http://xregexp\\.com(/[^#?]+)", "i"), backref: 1}, // xregexp.com paths 225 // /[^\/]+$/ // filenames (strip directory paths) 226 // ]); 227 XRegExp.matchChain = function (str, chain) { 228 return function recurseChain (values, level) { 229 var item = chain[level].regex ? chain[level] : {regex: chain[level]}, 230 regex = clone(item.regex, "g"), 231 matches = [], i; 232 for (i = 0; i < values.length; i++) { 233 XRegExp.iterate(values[i], regex, function (match) { 234 matches.push(item.backref ? (match[item.backref] || "") : match[0]); 235 }); 236 } 237 return ((level === chain.length - 1) || !matches.length) ? 238 matches : recurseChain(matches, level + 1); 239 }([str], 0); 240 }; 241 242 243 //--------------------------------- 244 // New RegExp prototype methods 245 //--------------------------------- 246 247 // Accepts a context object and arguments array; returns the result of calling `exec` with the 248 // first value in the arguments array. the context is ignored but is accepted for congruity 249 // with `Function.prototype.apply` 250 RegExp.prototype.apply = function (context, args) { 251 return this.exec(args[0]); 252 }; 253 254 // Accepts a context object and string; returns the result of calling `exec` with the provided 255 // string. the context is ignored but is accepted for congruity with `Function.prototype.call` 256 RegExp.prototype.call = function (context, str) { 257 return this.exec(str); 258 }; 259 260 261 //--------------------------------- 262 // Overriden native methods 263 //--------------------------------- 264 265 // Adds named capture support (with backreferences returned as `result.name`), and fixes two 266 // cross-browser issues per ES3: 267 // - Captured values for nonparticipating capturing groups should be returned as `undefined`, 268 // rather than the empty string. 269 // - `lastIndex` should not be incremented after zero-length matches. 270 RegExp.prototype.exec = function (str) { 271 var match, name, r2, origLastIndex; 272 if (!this.global) 273 origLastIndex = this.lastIndex; 274 match = nativ.exec.apply(this, arguments); 275 if (match) { 276 // Fix browsers whose `exec` methods don't consistently return `undefined` for 277 // nonparticipating capturing groups 278 if (!compliantExecNpcg && match.length > 1 && indexOf(match, "") > -1) { 279 r2 = RegExp(this.source, nativ.replace.call(getNativeFlags(this), "g", "")); 280 // Using `str.slice(match.index)` rather than `match[0]` in case lookahead allowed 281 // matching due to characters outside the match 282 nativ.replace.call((str + "").slice(match.index), r2, function () { 283 for (var i = 1; i < arguments.length - 2; i++) { 284 if (arguments[i] === undefined) 285 match[i] = undefined; 286 } 287 }); 288 } 289 // Attach named capture properties 290 if (this._xregexp && this._xregexp.captureNames) { 291 for (var i = 1; i < match.length; i++) { 292 name = this._xregexp.captureNames[i - 1]; 293 if (name) 294 match[name] = match[i]; 295 } 296 } 297 // Fix browsers that increment `lastIndex` after zero-length matches 298 if (!compliantLastIndexIncrement && this.global && !match[0].length && (this.lastIndex > match.index)) 299 this.lastIndex--; 300 } 301 if (!this.global) 302 this.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows) 303 return match; 304 }; 305 306 // Fix browser bugs in native method 307 RegExp.prototype.test = function (str) { 308 // Use the native `exec` to skip some processing overhead, even though the altered 309 // `exec` would take care of the `lastIndex` fixes 310 var match, origLastIndex; 311 if (!this.global) 312 origLastIndex = this.lastIndex; 313 match = nativ.exec.call(this, str); 314 // Fix browsers that increment `lastIndex` after zero-length matches 315 if (match && !compliantLastIndexIncrement && this.global && !match[0].length && (this.lastIndex > match.index)) 316 this.lastIndex--; 317 if (!this.global) 318 this.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows) 319 return !!match; 320 }; 321 322 // Adds named capture support and fixes browser bugs in native method 323 String.prototype.match = function (regex) { 324 if (!XRegExp.isRegExp(regex)) 325 regex = RegExp(regex); // Native `RegExp` 326 if (regex.global) { 327 var result = nativ.match.apply(this, arguments); 328 regex.lastIndex = 0; // Fix IE bug 329 return result; 330 } 331 return regex.exec(this); // Run the altered `exec` 332 }; 333 334 // Adds support for `${n}` tokens for named and numbered backreferences in replacement text, 335 // and provides named backreferences to replacement functions as `arguments[0].name`. Also 336 // fixes cross-browser differences in replacement text syntax when performing a replacement 337 // using a nonregex search value, and the value of replacement regexes' `lastIndex` property 338 // during replacement iterations. Note that this doesn't support SpiderMonkey's proprietary 339 // third (`flags`) parameter 340 String.prototype.replace = function (search, replacement) { 341 var isRegex = XRegExp.isRegExp(search), 342 captureNames, result, str, origLastIndex; 343 344 // There are too many combinations of search/replacement types/values and browser bugs that 345 // preclude passing to native `replace`, so don't try 346 //if (...) 347 // return nativ.replace.apply(this, arguments); 348 349 if (isRegex) { 350 if (search._xregexp) 351 captureNames = search._xregexp.captureNames; // Array or `null` 352 if (!search.global) 353 origLastIndex = search.lastIndex; 354 } else { 355 search = search + ""; // Type conversion 356 } 357 358 if (Object.prototype.toString.call(replacement) === "[object Function]") { 359 result = nativ.replace.call(this + "", search, function () { 360 if (captureNames) { 361 // Change the `arguments[0]` string primitive to a String object which can store properties 362 arguments[0] = new String(arguments[0]); 363 // Store named backreferences on `arguments[0]` 364 for (var i = 0; i < captureNames.length; i++) { 365 if (captureNames[i]) 366 arguments[0][captureNames[i]] = arguments[i + 1]; 367 } 368 } 369 // Update `lastIndex` before calling `replacement` (fix browsers) 370 if (isRegex && search.global) 371 search.lastIndex = arguments[arguments.length - 2] + arguments[0].length; 372 return replacement.apply(null, arguments); 373 }); 374 } else { 375 str = this + ""; // Type conversion, so `args[args.length - 1]` will be a string (given nonstring `this`) 376 result = nativ.replace.call(str, search, function () { 377 var args = arguments; // Keep this function's `arguments` available through closure 378 return nativ.replace.call(replacement + "", replacementToken, function ($0, $1, $2) { 379 // Numbered backreference (without delimiters) or special variable 380 if ($1) { 381 switch ($1) { 382 case "$": return "$"; 383 case "&": return args[0]; 384 case "`": return args[args.length - 1].slice(0, args[args.length - 2]); 385 case "'": return args[args.length - 1].slice(args[args.length - 2] + args[0].length); 386 // Numbered backreference 387 default: 388 // What does "$10" mean? 389 // - Backreference 10, if 10 or more capturing groups exist 390 // - Backreference 1 followed by "0", if 1-9 capturing groups exist 391 // - Otherwise, it's the string "$10" 392 // Also note: 393 // - Backreferences cannot be more than two digits (enforced by `replacementToken`) 394 // - "$01" is equivalent to "$1" if a capturing group exists, otherwise it's the string "$01" 395 // - There is no "$0" token ("$&" is the entire match) 396 var literalNumbers = ""; 397 $1 = +$1; // Type conversion; drop leading zero 398 if (!$1) // `$1` was "0" or "00" 399 return $0; 400 while ($1 > args.length - 3) { 401 literalNumbers = String.prototype.slice.call($1, -1) + literalNumbers; 402 $1 = Math.floor($1 / 10); // Drop the last digit 403 } 404 return ($1 ? args[$1] || "" : "$") + literalNumbers; 405 } 406 // Named backreference or delimited numbered backreference 407 } else { 408 // What does "${n}" mean? 409 // - Backreference to numbered capture n. Two differences from "$n": 410 // - n can be more than two digits 411 // - Backreference 0 is allowed, and is the entire match 412 // - Backreference to named capture n, if it exists and is not a number overridden by numbered capture 413 // - Otherwise, it's the string "${n}" 414 var n = +$2; // Type conversion; drop leading zeros 415 if (n <= args.length - 3) 416 return args[n]; 417 n = captureNames ? indexOf(captureNames, $2) : -1; 418 return n > -1 ? args[n + 1] : $0; 419 } 420 }); 421 }); 422 } 423 424 if (isRegex) { 425 if (search.global) 426 search.lastIndex = 0; // Fix IE, Safari bug (last tested IE 9.0.5, Safari 5.1.2 on Windows) 427 else 428 search.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows) 429 } 430 431 return result; 432 }; 433 434 // A consistent cross-browser, ES3 compliant `split` 435 String.prototype.split = function (s /* separator */, limit) { 436 // If separator `s` is not a regex, use the native `split` 437 if (!XRegExp.isRegExp(s)) 438 return nativ.split.apply(this, arguments); 439 440 var str = this + "", // Type conversion 441 output = [], 442 lastLastIndex = 0, 443 match, lastLength; 444 445 // Behavior for `limit`: if it's... 446 // - `undefined`: No limit 447 // - `NaN` or zero: Return an empty array 448 // - A positive number: Use `Math.floor(limit)` 449 // - A negative number: No limit 450 // - Other: Type-convert, then use the above rules 451 if (limit === undefined || +limit < 0) { 452 limit = Infinity; 453 } else { 454 limit = Math.floor(+limit); 455 if (!limit) 456 return []; 457 } 458 459 // This is required if not `s.global`, and it avoids needing to set `s.lastIndex` to zero 460 // and restore it to its original value when we're done using the regex 461 s = XRegExp.copyAsGlobal(s); 462 463 while (match = s.exec(str)) { // Run the altered `exec` (required for `lastIndex` fix, etc.) 464 if (s.lastIndex > lastLastIndex) { 465 output.push(str.slice(lastLastIndex, match.index)); 466 467 if (match.length > 1 && match.index < str.length) 468 Array.prototype.push.apply(output, match.slice(1)); 469 470 lastLength = match[0].length; 471 lastLastIndex = s.lastIndex; 472 473 if (output.length >= limit) 474 break; 475 } 476 477 if (s.lastIndex === match.index) 478 s.lastIndex++; 479 } 480 481 if (lastLastIndex === str.length) { 482 if (!nativ.test.call(s, "") || lastLength) 483 output.push(""); 484 } else { 485 output.push(str.slice(lastLastIndex)); 486 } 487 488 return output.length > limit ? output.slice(0, limit) : output; 489 }; 490 491 492 //--------------------------------- 493 // Private helper functions 494 //--------------------------------- 495 496 // Supporting function for `XRegExp`, `XRegExp.copyAsGlobal`, etc. Returns a copy of a `RegExp` 497 // instance with a fresh `lastIndex` (set to zero), preserving properties required for named 498 // capture. Also allows adding new flags in the process of copying the regex 499 function clone (regex, additionalFlags) { 500 if (!XRegExp.isRegExp(regex)) 501 throw TypeError("type RegExp expected"); 502 var x = regex._xregexp; 503 regex = XRegExp(regex.source, getNativeFlags(regex) + (additionalFlags || "")); 504 if (x) { 505 regex._xregexp = { 506 source: x.source, 507 captureNames: x.captureNames ? x.captureNames.slice(0) : null 508 }; 509 } 510 return regex; 511 } 512 513 function getNativeFlags (regex) { 514 return (regex.global ? "g" : "") + 515 (regex.ignoreCase ? "i" : "") + 516 (regex.multiline ? "m" : "") + 517 (regex.extended ? "x" : "") + // Proposed for ES4; included in AS3 518 (regex.sticky ? "y" : ""); 519 } 520 521 function runTokens (pattern, index, scope, context) { 522 var i = tokens.length, 523 result, match, t; 524 // Protect against constructing XRegExps within token handler and trigger functions 525 isInsideConstructor = true; 526 // Must reset `isInsideConstructor`, even if a `trigger` or `handler` throws 527 try { 528 while (i--) { // Run in reverse order 529 t = tokens[i]; 530 if ((scope & t.scope) && (!t.trigger || t.trigger.call(context))) { 531 t.pattern.lastIndex = index; 532 match = t.pattern.exec(pattern); // Running the altered `exec` here allows use of named backreferences, etc. 533 if (match && match.index === index) { 534 result = { 535 output: t.handler.call(context, match, scope), 536 match: match 537 }; 538 break; 539 } 540 } 541 } 542 } catch (err) { 543 throw err; 544 } finally { 545 isInsideConstructor = false; 546 } 547 return result; 548 } 549 550 function indexOf (array, item, from) { 551 if (Array.prototype.indexOf) // Use the native array method if available 552 return array.indexOf(item, from); 553 for (var i = from || 0; i < array.length; i++) { 554 if (array[i] === item) 555 return i; 556 } 557 return -1; 558 } 559 560 561 //--------------------------------- 562 // Built-in tokens 563 //--------------------------------- 564 565 // Augment XRegExp's regular expression syntax and flags. Note that when adding tokens, the 566 // third (`scope`) argument defaults to `XRegExp.OUTSIDE_CLASS` 567 568 // Comment pattern: (?# ) 569 XRegExp.addToken( 570 /\(\?#[^)]*\)/, 571 function (match) { 572 // Keep tokens separated unless the following token is a quantifier 573 return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)"; 574 } 575 ); 576 577 // Capturing group (match the opening parenthesis only). 578 // Required for support of named capturing groups 579 XRegExp.addToken( 580 /\((?!\?)/, 581 function () { 582 this.captureNames.push(null); 583 return "("; 584 } 585 ); 586 587 // Named capturing group (match the opening delimiter only): (?<name> 588 XRegExp.addToken( 589 /\(\?<([$\w]+)>/, 590 function (match) { 591 this.captureNames.push(match[1]); 592 this.hasNamedCapture = true; 593 return "("; 594 } 595 ); 596 597 // Named backreference: \k<name> 598 XRegExp.addToken( 599 /\\k<([\w$]+)>/, 600 function (match) { 601 var index = indexOf(this.captureNames, match[1]); 602 // Keep backreferences separate from subsequent literal numbers. Preserve back- 603 // references to named groups that are undefined at this point as literal strings 604 return index > -1 ? 605 "\\" + (index + 1) + (isNaN(match.input.charAt(match.index + match[0].length)) ? "" : "(?:)") : 606 match[0]; 607 } 608 ); 609 610 // Empty character class: [] or [^] 611 XRegExp.addToken( 612 /\[\^?]/, 613 function (match) { 614 // For cross-browser compatibility with ES3, convert [] to \b\B and [^] to [\s\S]. 615 // (?!) should work like \b\B, but is unreliable in Firefox 616 return match[0] === "[]" ? "\\b\\B" : "[\\s\\S]"; 617 } 618 ); 619 620 // Mode modifier at the start of the pattern only, with any combination of flags imsx: (?imsx) 621 // Does not support x(?i), (?-i), (?i-m), (?i: ), (?i)(?m), etc. 622 XRegExp.addToken( 623 /^\(\?([imsx]+)\)/, 624 function (match) { 625 this.setFlag(match[1]); 626 return ""; 627 } 628 ); 629 630 // Whitespace and comments, in free-spacing (aka extended) mode only 631 XRegExp.addToken( 632 /(?:\s+|#.*)+/, 633 function (match) { 634 // Keep tokens separated unless the following token is a quantifier 635 return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)"; 636 }, 637 XRegExp.OUTSIDE_CLASS, 638 function () {return this.hasFlag("x");} 639 ); 640 641 // Dot, in dotall (aka singleline) mode only 642 XRegExp.addToken( 643 /\./, 644 function () {return "[\\s\\S]";}, 645 XRegExp.OUTSIDE_CLASS, 646 function () {return this.hasFlag("s");} 647 ); 648 649 650 //--------------------------------- 651 // Backward compatibility 652 //--------------------------------- 653 654 // Uncomment the following block for compatibility with XRegExp 1.0-1.2: 655 /* 656 XRegExp.matchWithinChain = XRegExp.matchChain; 657 RegExp.prototype.addFlags = function (s) {return clone(this, s);}; 658 RegExp.prototype.execAll = function (s) {var r = []; XRegExp.iterate(s, this, function (m) {r.push(m);}); return r;}; 659 RegExp.prototype.forEachExec = function (s, f, c) {return XRegExp.iterate(s, this, f, c);}; 660 RegExp.prototype.validate = function (s) {var r = RegExp("^(?:" + this.source + ")$(?!\\s)", getNativeFlags(this)); if (this.global) this.lastIndex = 0; return s.search(r) === 0;}; 661 */ 662 663 })(); 664