xregexp-rails 1.5.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 03158c84d5dcf174e189bb3118fe869518a4152d
4
- data.tar.gz: 3976d36f6b6d0f442863957fcdbc4d31ca81f6b9
3
+ metadata.gz: e67303d4e75a553268b4ba2e7e74d6a1c4deeae2
4
+ data.tar.gz: 5beab616181a0eb9bb9e9296f4978e29c50940c0
5
5
  SHA512:
6
- metadata.gz: 78179f849fb86da314a80379297c5a21c6f8118d101b23698b5cb832f1702a0e2500af4d7159e6620192ac1aff18936510c3fd331edfbc2e17776a34533abf1f
7
- data.tar.gz: 649c7c40a58db4c66f367a09ad77538a1bf7eff8464a3892262cb0da539979f780ef93f3f96c9af8e1dd8e823f9bfbd4fa8e3be43f99122632d8857a7ca03361
6
+ metadata.gz: beaa60d69b87c2e1f52684a5da83ca741105caea91d8648ae8cb857b42f368966db047dd67cb158fc4d5ddc29b6814cc7de72016f9f64aca48fee086d8451d90
7
+ data.tar.gz: 7f5d5804ba1095c575796db0348548a51f33ba295fdb5fbfddcfaa090257cc5f74c2ee5dff8c8539fbf36baaef93624dcc27caa743d49005305919775a1d8147
@@ -1,664 +1,1265 @@
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
-
1
+ /*!
2
+ * XRegExp v2.0.0
3
+ * (c) 2007-2012 Steven Levithan <http://xregexp.com/>
4
+ * MIT License
5
+ */
6
+
7
+ /**
8
+ * XRegExp provides augmented, extensible JavaScript regular expressions. You get new syntax,
9
+ * flags, and methods beyond what browsers support natively. XRegExp is also a regex utility belt
10
+ * with tools to make your client-side grepping simpler and more powerful, while freeing you from
11
+ * worrying about pesky cross-browser inconsistencies and the dubious `lastIndex` property. See
12
+ * XRegExp's documentation (http://xregexp.com/) for more details.
13
+ * @module xregexp
14
+ * @requires N/A
15
+ */
8
16
  var XRegExp;
9
17
 
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
- }
18
+ // Avoid running twice; that would reset tokens and could break references to native globals
19
+ XRegExp = XRegExp || (function (undef) {
20
+ "use strict";
21
+
22
+ /*--------------------------------------
23
+ * Private variables
24
+ *------------------------------------*/
25
+
26
+ var self,
27
+ addToken,
28
+ add,
29
+
30
+ // Optional features; can be installed and uninstalled
31
+ features = {
32
+ natives: false,
33
+ extensibility: false
34
+ },
35
+
36
+ // Store native methods to use and restore ("native" is an ES3 reserved keyword)
37
+ nativ = {
38
+ exec: RegExp.prototype.exec,
39
+ test: RegExp.prototype.test,
40
+ match: String.prototype.match,
41
+ replace: String.prototype.replace,
42
+ split: String.prototype.split
43
+ },
44
+
45
+ // Storage for fixed/extended native methods
46
+ fixed = {},
47
+
48
+ // Storage for cached regexes
49
+ cache = {},
50
+
51
+ // Storage for addon tokens
52
+ tokens = [],
53
+
54
+ // Token scopes
55
+ defaultScope = "default",
56
+ classScope = "class",
57
+
58
+ // Regexes that match native regex syntax
59
+ nativeTokens = {
60
+ // Any native multicharacter token in default scope (includes octals, excludes character classes)
61
+ "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*)?}\??)/,
62
+ // Any native multicharacter token in character class scope (includes octals)
63
+ "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]))/
64
+ },
65
+
66
+ // Any backreference in replacement strings
67
+ replacementToken = /\$(?:{([\w$]+)}|(\d\d?|[\s\S]))/g,
68
+
69
+ // Any character with a later instance in the string
70
+ duplicateFlags = /([\s\S])(?=[\s\S]*\1)/g,
71
+
72
+ // Any greedy/lazy quantifier
73
+ quantifier = /^(?:[?*+]|{\d+(?:,\d*)?})\??/,
74
+
75
+ // Check for correct `exec` handling of nonparticipating capturing groups
76
+ compliantExecNpcg = nativ.exec.call(/()??/, "")[1] === undef,
77
+
78
+ // Check for flag y support (Firefox 3+)
79
+ hasNativeY = RegExp.prototype.sticky !== undef,
80
+
81
+ // Used to kill infinite recursion during XRegExp construction
82
+ isInsideConstructor = false,
83
+
84
+ // Storage for known flags, including addon flags
85
+ registeredFlags = "gim" + (hasNativeY ? "y" : "");
86
+
87
+ /*--------------------------------------
88
+ * Private helper functions
89
+ *------------------------------------*/
90
+
91
+ /**
92
+ * Attaches XRegExp.prototype properties and named capture supporting data to a regex object.
93
+ * @private
94
+ * @param {RegExp} regex Regex to augment.
95
+ * @param {Array} captureNames Array with capture names, or null.
96
+ * @param {Boolean} [isNative] Whether the regex was created by `RegExp` rather than `XRegExp`.
97
+ * @returns {RegExp} Augmented regex.
98
+ */
99
+ function augment(regex, captureNames, isNative) {
100
+ var p;
101
+ // Can't auto-inherit these since the XRegExp constructor returns a nonprimitive value
102
+ for (p in self.prototype) {
103
+ if (self.prototype.hasOwnProperty(p)) {
104
+ regex[p] = self.prototype[p];
105
+ }
106
+ }
107
+ regex.xregexp = {captureNames: captureNames, isNative: !!isNative};
108
+ return regex;
109
+ }
110
+
111
+ /**
112
+ * Returns native `RegExp` flags used by a regex object.
113
+ * @private
114
+ * @param {RegExp} regex Regex to check.
115
+ * @returns {String} Native flags in use.
116
+ */
117
+ function getNativeFlags(regex) {
118
+ //return nativ.exec.call(/\/([a-z]*)$/i, String(regex))[1];
119
+ return (regex.global ? "g" : "") +
120
+ (regex.ignoreCase ? "i" : "") +
121
+ (regex.multiline ? "m" : "") +
122
+ (regex.extended ? "x" : "") + // Proposed for ES6, included in AS3
123
+ (regex.sticky ? "y" : ""); // Proposed for ES6, included in Firefox 3+
124
+ }
125
+
126
+ /**
127
+ * Copies a regex object while preserving special properties for named capture and augmenting with
128
+ * `XRegExp.prototype` methods. The copy has a fresh `lastIndex` property (set to zero). Allows
129
+ * adding and removing flags while copying the regex.
130
+ * @private
131
+ * @param {RegExp} regex Regex to copy.
132
+ * @param {String} [addFlags] Flags to be added while copying the regex.
133
+ * @param {String} [removeFlags] Flags to be removed while copying the regex.
134
+ * @returns {RegExp} Copy of the provided regex, possibly with modified flags.
135
+ */
136
+ function copy(regex, addFlags, removeFlags) {
137
+ if (!self.isRegExp(regex)) {
138
+ throw new TypeError("type RegExp expected");
139
+ }
140
+ var flags = nativ.replace.call(getNativeFlags(regex) + (addFlags || ""), duplicateFlags, "");
141
+ if (removeFlags) {
142
+ // Would need to escape `removeFlags` if this was public
143
+ flags = nativ.replace.call(flags, new RegExp("[" + removeFlags + "]+", "g"), "");
144
+ }
145
+ if (regex.xregexp && !regex.xregexp.isNative) {
146
+ // Compiling the current (rather than precompilation) source preserves the effects of nonnative source flags
147
+ regex = augment(self(regex.source, flags),
148
+ regex.xregexp.captureNames ? regex.xregexp.captureNames.slice(0) : null);
149
+ } else {
150
+ // Augment with `XRegExp.prototype` methods, but use native `RegExp` (avoid searching for special tokens)
151
+ regex = augment(new RegExp(regex.source, flags), null, true);
152
+ }
153
+ return regex;
154
+ }
155
+
156
+ /*
157
+ * Returns the last index at which a given value can be found in an array, or `-1` if it's not
158
+ * present. The array is searched backwards.
159
+ * @private
160
+ * @param {Array} array Array to search.
161
+ * @param {*} value Value to locate in the array.
162
+ * @returns {Number} Last zero-based index at which the item is found, or -1.
163
+ */
164
+ function lastIndexOf(array, value) {
165
+ var i = array.length;
166
+ if (Array.prototype.lastIndexOf) {
167
+ return array.lastIndexOf(value); // Use the native method if available
168
+ }
169
+ while (i--) {
170
+ if (array[i] === value) {
171
+ return i;
172
+ }
173
+ }
174
+ return -1;
175
+ }
176
+
177
+ /**
178
+ * Determines whether an object is of the specified type.
179
+ * @private
180
+ * @param {*} value Object to check.
181
+ * @param {String} type Type to check for, in lowercase.
182
+ * @returns {Boolean} Whether the object matches the type.
183
+ */
184
+ function isType(value, type) {
185
+ return Object.prototype.toString.call(value).toLowerCase() === "[object " + type + "]";
186
+ }
187
+
188
+ /**
189
+ * Prepares an options object from the given value.
190
+ * @private
191
+ * @param {String|Object} value Value to convert to an options object.
192
+ * @returns {Object} Options object.
193
+ */
194
+ function prepareOptions(value) {
195
+ value = value || {};
196
+ if (value === "all" || value.all) {
197
+ value = {natives: true, extensibility: true};
198
+ } else if (isType(value, "string")) {
199
+ value = self.forEach(value, /[^\s,]+/, function (m) {
200
+ this[m] = true;
201
+ }, {});
202
+ }
203
+ return value;
204
+ }
205
+
206
+ /**
207
+ * Runs built-in/custom tokens in reverse insertion order, until a match is found.
208
+ * @private
209
+ * @param {String} pattern Original pattern from which an XRegExp object is being built.
210
+ * @param {Number} pos Position to search for tokens within `pattern`.
211
+ * @param {Number} scope Current regex scope.
212
+ * @param {Object} context Context object assigned to token handler functions.
213
+ * @returns {Object} Object with properties `output` (the substitution string returned by the
214
+ * successful token handler) and `match` (the token's match array), or null.
215
+ */
216
+ function runTokens(pattern, pos, scope, context) {
217
+ var i = tokens.length,
218
+ result = null,
219
+ match,
220
+ t;
221
+ // Protect against constructing XRegExps within token handler and trigger functions
222
+ isInsideConstructor = true;
223
+ // Must reset `isInsideConstructor`, even if a `trigger` or `handler` throws
224
+ try {
225
+ while (i--) { // Run in reverse order
226
+ t = tokens[i];
227
+ if ((t.scope === "all" || t.scope === scope) && (!t.trigger || t.trigger.call(context))) {
228
+ t.pattern.lastIndex = pos;
229
+ match = fixed.exec.call(t.pattern, pattern); // Fixed `exec` here allows use of named backreferences, etc.
230
+ if (match && match.index === pos) {
231
+ result = {
232
+ output: t.handler.call(context, match, scope),
233
+ match: match
234
+ };
235
+ break;
236
+ }
237
+ }
238
+ }
239
+ } catch (err) {
240
+ throw err;
241
+ } finally {
242
+ isInsideConstructor = false;
243
+ }
244
+ return result;
245
+ }
246
+
247
+ /**
248
+ * Enables or disables XRegExp syntax and flag extensibility.
249
+ * @private
250
+ * @param {Boolean} on `true` to enable; `false` to disable.
251
+ */
252
+ function setExtensibility(on) {
253
+ self.addToken = addToken[on ? "on" : "off"];
254
+ features.extensibility = on;
255
+ }
14
256
 
15
- // Run within an anonymous function to protect variables and avoid new globals
16
- (function (undefined) {
257
+ /**
258
+ * Enables or disables native method overrides.
259
+ * @private
260
+ * @param {Boolean} on `true` to enable; `false` to disable.
261
+ */
262
+ function setNatives(on) {
263
+ RegExp.prototype.exec = (on ? fixed : nativ).exec;
264
+ RegExp.prototype.test = (on ? fixed : nativ).test;
265
+ String.prototype.match = (on ? fixed : nativ).match;
266
+ String.prototype.replace = (on ? fixed : nativ).replace;
267
+ String.prototype.split = (on ? fixed : nativ).split;
268
+ features.natives = on;
269
+ }
17
270
 
18
- //---------------------------------
19
- // Constructor
20
- //---------------------------------
271
+ /*--------------------------------------
272
+ * Constructor
273
+ *------------------------------------*/
274
+
275
+ /**
276
+ * Creates an extended regular expression object for matching text with a pattern. Differs from a
277
+ * native regular expression in that additional syntax and flags are supported. The returned object
278
+ * is in fact a native `RegExp` and works with all native methods.
279
+ * @class XRegExp
280
+ * @constructor
281
+ * @param {String|RegExp} pattern Regex pattern string, or an existing `RegExp` object to copy.
282
+ * @param {String} [flags] Any combination of flags:
283
+ * <li>`g` - global
284
+ * <li>`i` - ignore case
285
+ * <li>`m` - multiline anchors
286
+ * <li>`n` - explicit capture
287
+ * <li>`s` - dot matches all (aka singleline)
288
+ * <li>`x` - free-spacing and line comments (aka extended)
289
+ * <li>`y` - sticky (Firefox 3+ only)
290
+ * Flags cannot be provided when constructing one `RegExp` from another.
291
+ * @returns {RegExp} Extended regular expression object.
292
+ * @example
293
+ *
294
+ * // With named capture and flag x
295
+ * date = XRegExp('(?<year> [0-9]{4}) -? # year \n\
296
+ * (?<month> [0-9]{2}) -? # month \n\
297
+ * (?<day> [0-9]{2}) # day ', 'x');
298
+ *
299
+ * // Passing a regex object to copy it. The copy maintains special properties for named capture,
300
+ * // is augmented with `XRegExp.prototype` methods, and has a fresh `lastIndex` property (set to
301
+ * // zero). Native regexes are not recompiled using XRegExp syntax.
302
+ * XRegExp(/regex/);
303
+ */
304
+ self = function (pattern, flags) {
305
+ if (self.isRegExp(pattern)) {
306
+ if (flags !== undef) {
307
+ throw new TypeError("can't supply flags when constructing one RegExp from another");
308
+ }
309
+ return copy(pattern);
310
+ }
311
+ // Tokens become part of the regex construction process, so protect against infinite recursion
312
+ // when an XRegExp is constructed within a token handler function
313
+ if (isInsideConstructor) {
314
+ throw new Error("can't call the XRegExp constructor within token definition functions");
315
+ }
21
316
 
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
317
  var output = [],
28
- currScope = XRegExp.OUTSIDE_CLASS,
318
+ scope = defaultScope,
319
+ tokenContext = {
320
+ hasNamedCapture: false,
321
+ captureNames: [],
322
+ hasFlag: function (flag) {
323
+ return flags.indexOf(flag) > -1;
324
+ }
325
+ },
29
326
  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
- };
327
+ tokenResult,
328
+ match,
329
+ chr;
330
+ pattern = pattern === undef ? "" : String(pattern);
331
+ flags = flags === undef ? "" : String(flags);
332
+
333
+ if (nativ.match.call(flags, duplicateFlags)) { // Don't use test/exec because they would update lastIndex
334
+ throw new SyntaxError("invalid duplicate regular expression flag");
335
+ }
336
+ // Strip/apply leading mode modifier with any combination of flags except g or y: (?imnsx)
337
+ pattern = nativ.replace.call(pattern, /^\(\?([\w$]+)\)/, function ($0, $1) {
338
+ if (nativ.test.call(/[gy]/, $1)) {
339
+ throw new SyntaxError("can't use flag g or y in mode modifier");
340
+ }
341
+ flags = nativ.replace.call(flags + $1, duplicateFlags, "");
342
+ return "";
343
+ });
344
+ self.forEach(flags, /[\s\S]/, function (m) {
345
+ if (registeredFlags.indexOf(m[0]) < 0) {
346
+ throw new SyntaxError("invalid regular expression flag " + m[0]);
347
+ }
348
+ });
49
349
 
50
350
  while (pos < pattern.length) {
51
351
  // Check for custom tokens at the current position
52
- tokenResult = runTokens(pattern, pos, currScope, context);
53
-
352
+ tokenResult = runTokens(pattern, pos, scope, tokenContext);
54
353
  if (tokenResult) {
55
354
  output.push(tokenResult.output);
56
355
  pos += (tokenResult.match[0].length || 1);
57
356
  } 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))) {
357
+ // Check for native tokens (except character classes) at the current position
358
+ match = nativ.exec.call(nativeTokens[scope], pattern.slice(pos));
359
+ if (match) {
61
360
  output.push(match[0]);
62
361
  pos += match[0].length;
63
362
  } else {
64
363
  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
364
+ if (chr === "[") {
365
+ scope = classScope;
366
+ } else if (chr === "]") {
367
+ scope = defaultScope;
368
+ }
369
+ // Advance position by one character
70
370
  output.push(chr);
71
- pos++;
371
+ ++pos;
72
372
  }
73
373
  }
74
374
  }
75
375
 
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;
376
+ return augment(new RegExp(output.join(""), nativ.replace.call(flags, /[^gimy]+/g, "")),
377
+ tokenContext.hasNamedCapture ? tokenContext.captureNames : null);
82
378
  };
83
379
 
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
380
+ /*--------------------------------------
381
+ * Public methods/properties
382
+ *------------------------------------*/
383
+
384
+ // Installed and uninstalled states for `XRegExp.addToken`
385
+ addToken = {
386
+ on: function (regex, handler, options) {
387
+ options = options || {};
388
+ if (regex) {
389
+ tokens.push({
390
+ pattern: copy(regex, "g" + (hasNativeY ? "y" : "")),
391
+ handler: handler,
392
+ scope: options.scope || defaultScope,
393
+ trigger: options.trigger || null
394
+ });
395
+ }
396
+ // Providing `customFlags` with null `regex` and `handler` allows adding flags that do
397
+ // nothing, but don't throw an error
398
+ if (options.customFlags) {
399
+ registeredFlags = nativ.replace.call(registeredFlags + options.customFlags, duplicateFlags, "");
400
+ }
112
401
  },
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
- });
402
+ off: function () {
403
+ throw new Error("extensibility must be installed before using addToken");
404
+ }
143
405
  };
144
406
 
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) {
407
+ /**
408
+ * Extends or changes XRegExp syntax and allows custom flags. This is used internally and can be
409
+ * used to create XRegExp addons. `XRegExp.install('extensibility')` must be run before calling
410
+ * this function, or an error is thrown. If more than one token can match the same string, the last
411
+ * added wins.
412
+ * @memberOf XRegExp
413
+ * @param {RegExp} regex Regex object that matches the new token.
414
+ * @param {Function} handler Function that returns a new pattern string (using native regex syntax)
415
+ * to replace the matched token within all future XRegExp regexes. Has access to persistent
416
+ * properties of the regex being built, through `this`. Invoked with two arguments:
417
+ * <li>The match array, with named backreference properties.
418
+ * <li>The regex scope where the match was found.
419
+ * @param {Object} [options] Options object with optional properties:
420
+ * <li>`scope` {String} Scopes where the token applies: 'default', 'class', or 'all'.
421
+ * <li>`trigger` {Function} Function that returns `true` when the token should be applied; e.g.,
422
+ * if a flag is set. If `false` is returned, the matched string can be matched by other tokens.
423
+ * Has access to persistent properties of the regex being built, through `this` (including
424
+ * function `this.hasFlag`).
425
+ * <li>`customFlags` {String} Nonnative flags used by the token's handler or trigger functions.
426
+ * Prevents XRegExp from throwing an invalid flag error when the specified flags are used.
427
+ * @example
428
+ *
429
+ * // Basic usage: Adds \a for ALERT character
430
+ * XRegExp.addToken(
431
+ * /\\a/,
432
+ * function () {return '\\x07';},
433
+ * {scope: 'all'}
434
+ * );
435
+ * XRegExp('\\a[\\a-\\n]+').test('\x07\n\x07'); // -> true
436
+ */
437
+ self.addToken = addToken.off;
438
+
439
+ /**
440
+ * Caches and returns the result of calling `XRegExp(pattern, flags)`. On any subsequent call with
441
+ * the same pattern and flag combination, the cached copy is returned.
442
+ * @memberOf XRegExp
443
+ * @param {String} pattern Regex pattern string.
444
+ * @param {String} [flags] Any combination of XRegExp flags.
445
+ * @returns {RegExp} Cached XRegExp object.
446
+ * @example
447
+ *
448
+ * while (match = XRegExp.cache('.', 'gs').exec(str)) {
449
+ * // The regex is compiled once only
450
+ * }
451
+ */
452
+ self.cache = function (pattern, flags) {
149
453
  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");
454
+ return cache[key] || (cache[key] = self(pattern, flags));
159
455
  };
160
456
 
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, "\\$&");
457
+ /**
458
+ * Escapes any regular expression metacharacters, for use when matching literal strings. The result
459
+ * can safely be used at any point within a regex that uses any flags.
460
+ * @memberOf XRegExp
461
+ * @param {String} str String to escape.
462
+ * @returns {String} String with regex metacharacters escaped.
463
+ * @example
464
+ *
465
+ * XRegExp.escape('Escaped? <.>');
466
+ * // -> 'Escaped\?\ <\.>'
467
+ */
468
+ self.escape = function (str) {
469
+ return nativ.replace.call(str, /[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
166
470
  };
167
471
 
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" : "")),
472
+ /**
473
+ * Executes a regex search in a specified string. Returns a match array or `null`. If the provided
474
+ * regex uses named capture, named backreference properties are included on the match array.
475
+ * Optional `pos` and `sticky` arguments specify the search start position, and whether the match
476
+ * must start at the specified position only. The `lastIndex` property of the provided regex is not
477
+ * used, but is updated for compatibility. Also fixes browser bugs compared to the native
478
+ * `RegExp.prototype.exec` and can be used reliably cross-browser.
479
+ * @memberOf XRegExp
480
+ * @param {String} str String to search.
481
+ * @param {RegExp} regex Regex to search with.
482
+ * @param {Number} [pos=0] Zero-based index at which to start the search.
483
+ * @param {Boolean|String} [sticky=false] Whether the match must start at the specified position
484
+ * only. The string `'sticky'` is accepted as an alternative to `true`.
485
+ * @returns {Array} Match array with named backreference properties, or null.
486
+ * @example
487
+ *
488
+ * // Basic use, with named backreference
489
+ * var match = XRegExp.exec('U+2620', XRegExp('U\\+(?<hex>[0-9A-F]{4})'));
490
+ * match.hex; // -> '2620'
491
+ *
492
+ * // With pos and sticky, in a loop
493
+ * var pos = 2, result = [], match;
494
+ * while (match = XRegExp.exec('<1><2><3><4>5<6>', /<(\d)>/, pos, 'sticky')) {
495
+ * result.push(match[1]);
496
+ * pos = match.index + match[0].length;
497
+ * }
498
+ * // result -> ['2', '3', '4']
499
+ */
500
+ self.exec = function (str, regex, pos, sticky) {
501
+ var r2 = copy(regex, "g" + (sticky && hasNativeY ? "y" : ""), (sticky === false ? "y" : "")),
174
502
  match;
175
503
  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)
504
+ match = fixed.exec.call(r2, str); // Fixed `exec` required for `lastIndex` fix, etc.
505
+ if (sticky && match && match.index !== pos) {
178
506
  match = null;
179
- if (regex.global)
507
+ }
508
+ if (regex.global) {
180
509
  regex.lastIndex = match ? r2.lastIndex : 0;
510
+ }
181
511
  return match;
182
512
  };
183
513
 
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
- };
514
+ /**
515
+ * Executes a provided function once per regex match.
516
+ * @memberOf XRegExp
517
+ * @param {String} str String to search.
518
+ * @param {RegExp} regex Regex to search with.
519
+ * @param {Function} callback Function to execute for each match. Invoked with four arguments:
520
+ * <li>The match array, with named backreference properties.
521
+ * <li>The zero-based match index.
522
+ * <li>The string being traversed.
523
+ * <li>The regex object being used to traverse the string.
524
+ * @param {*} [context] Object to use as `this` when executing `callback`.
525
+ * @returns {*} Provided `context` object.
526
+ * @example
527
+ *
528
+ * // Extracts every other digit from a string
529
+ * XRegExp.forEach('1a2345', /\d/, function (match, i) {
530
+ * if (i % 2) this.push(+match[0]);
531
+ * }, []);
532
+ * // -> [2, 4]
533
+ */
534
+ self.forEach = function (str, regex, callback, context) {
535
+ var pos = 0,
536
+ i = -1,
537
+ match;
538
+ while ((match = self.exec(str, regex, pos))) {
539
+ callback.call(context, match, ++i, str, regex);
540
+ pos = match.index + (match[0].length || 1);
541
+ }
542
+ return context;
190
543
  };
191
544
 
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]";
545
+ /**
546
+ * Copies a regex object and adds flag `g`. The copy maintains special properties for named
547
+ * capture, is augmented with `XRegExp.prototype` methods, and has a fresh `lastIndex` property
548
+ * (set to zero). Native regexes are not recompiled using XRegExp syntax.
549
+ * @memberOf XRegExp
550
+ * @param {RegExp} regex Regex to globalize.
551
+ * @returns {RegExp} Copy of the provided regex with flag `g` added.
552
+ * @example
553
+ *
554
+ * var globalCopy = XRegExp.globalize(/regex/);
555
+ * globalCopy.global; // -> true
556
+ */
557
+ self.globalize = function (regex) {
558
+ return copy(regex, "g");
198
559
  };
199
560
 
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++;
561
+ /**
562
+ * Installs optional features according to the specified options.
563
+ * @memberOf XRegExp
564
+ * @param {Object|String} options Options object or string.
565
+ * @example
566
+ *
567
+ * // With an options object
568
+ * XRegExp.install({
569
+ * // Overrides native regex methods with fixed/extended versions that support named
570
+ * // backreferences and fix numerous cross-browser bugs
571
+ * natives: true,
572
+ *
573
+ * // Enables extensibility of XRegExp syntax and flags
574
+ * extensibility: true
575
+ * });
576
+ *
577
+ * // With an options string
578
+ * XRegExp.install('natives extensibility');
579
+ *
580
+ * // Using a shortcut to install all optional features
581
+ * XRegExp.install('all');
582
+ */
583
+ self.install = function (options) {
584
+ options = prepareOptions(options);
585
+ if (!features.natives && options.natives) {
586
+ setNatives(true);
587
+ }
588
+ if (!features.extensibility && options.extensibility) {
589
+ setExtensibility(true);
212
590
  }
213
- if (regex.global)
214
- regex.lastIndex = 0;
215
591
  };
216
592
 
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) {
593
+ /**
594
+ * Checks whether an individual optional feature is installed.
595
+ * @memberOf XRegExp
596
+ * @param {String} feature Name of the feature to check. One of:
597
+ * <li>`natives`
598
+ * <li>`extensibility`
599
+ * @returns {Boolean} Whether the feature is installed.
600
+ * @example
601
+ *
602
+ * XRegExp.isInstalled('natives');
603
+ */
604
+ self.isInstalled = function (feature) {
605
+ return !!(features[feature]);
606
+ };
607
+
608
+ /**
609
+ * Returns `true` if an object is a regex; `false` if it isn't. This works correctly for regexes
610
+ * created in another frame, when `instanceof` and `constructor` checks would fail.
611
+ * @memberOf XRegExp
612
+ * @param {*} value Object to check.
613
+ * @returns {Boolean} Whether the object is a `RegExp` object.
614
+ * @example
615
+ *
616
+ * XRegExp.isRegExp('string'); // -> false
617
+ * XRegExp.isRegExp(/regex/i); // -> true
618
+ * XRegExp.isRegExp(RegExp('^', 'm')); // -> true
619
+ * XRegExp.isRegExp(XRegExp('(?s).')); // -> true
620
+ */
621
+ self.isRegExp = function (value) {
622
+ return isType(value, "regexp");
623
+ };
624
+
625
+ /**
626
+ * Retrieves the matches from searching a string using a chain of regexes that successively search
627
+ * within previous matches. The provided `chain` array can contain regexes and objects with `regex`
628
+ * and `backref` properties. When a backreference is specified, the named or numbered backreference
629
+ * is passed forward to the next regex or returned.
630
+ * @memberOf XRegExp
631
+ * @param {String} str String to search.
632
+ * @param {Array} chain Regexes that each search for matches within preceding results.
633
+ * @returns {Array} Matches by the last regex in the chain, or an empty array.
634
+ * @example
635
+ *
636
+ * // Basic usage; matches numbers within <b> tags
637
+ * XRegExp.matchChain('1 <b>2</b> 3 <b>4 a 56</b>', [
638
+ * XRegExp('(?is)<b>.*?</b>'),
639
+ * /\d+/
640
+ * ]);
641
+ * // -> ['2', '4', '56']
642
+ *
643
+ * // Passing forward and returning specific backreferences
644
+ * html = '<a href="http://xregexp.com/api/">XRegExp</a>\
645
+ * <a href="http://www.google.com/">Google</a>';
646
+ * XRegExp.matchChain(html, [
647
+ * {regex: /<a href="([^"]+)">/i, backref: 1},
648
+ * {regex: XRegExp('(?i)^https?://(?<domain>[^/?#]+)'), backref: 'domain'}
649
+ * ]);
650
+ * // -> ['xregexp.com', 'www.google.com']
651
+ */
652
+ self.matchChain = function (str, chain) {
653
+ return (function recurseChain(values, level) {
229
654
  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) {
655
+ matches = [],
656
+ addMatch = function (match) {
234
657
  matches.push(item.backref ? (match[item.backref] || "") : match[0]);
235
- });
658
+ },
659
+ i;
660
+ for (i = 0; i < values.length; ++i) {
661
+ self.forEach(values[i], item.regex, addMatch);
236
662
  }
237
663
  return ((level === chain.length - 1) || !matches.length) ?
238
- matches : recurseChain(matches, level + 1);
239
- }([str], 0);
664
+ matches :
665
+ recurseChain(matches, level + 1);
666
+ }([str], 0));
240
667
  };
241
668
 
669
+ /**
670
+ * Returns a new string with one or all matches of a pattern replaced. The pattern can be a string
671
+ * or regex, and the replacement can be a string or a function to be called for each match. To
672
+ * perform a global search and replace, use the optional `scope` argument or include flag `g` if
673
+ * using a regex. Replacement strings can use `${n}` for named and numbered backreferences.
674
+ * Replacement functions can use named backreferences via `arguments[0].name`. Also fixes browser
675
+ * bugs compared to the native `String.prototype.replace` and can be used reliably cross-browser.
676
+ * @memberOf XRegExp
677
+ * @param {String} str String to search.
678
+ * @param {RegExp|String} search Search pattern to be replaced.
679
+ * @param {String|Function} replacement Replacement string or a function invoked to create it.
680
+ * Replacement strings can include special replacement syntax:
681
+ * <li>$$ - Inserts a literal '$'.
682
+ * <li>$&, $0 - Inserts the matched substring.
683
+ * <li>$` - Inserts the string that precedes the matched substring (left context).
684
+ * <li>$' - Inserts the string that follows the matched substring (right context).
685
+ * <li>$n, $nn - Where n/nn are digits referencing an existent capturing group, inserts
686
+ * backreference n/nn.
687
+ * <li>${n} - Where n is a name or any number of digits that reference an existent capturing
688
+ * group, inserts backreference n.
689
+ * Replacement functions are invoked with three or more arguments:
690
+ * <li>The matched substring (corresponds to $& above). Named backreferences are accessible as
691
+ * properties of this first argument.
692
+ * <li>0..n arguments, one for each backreference (corresponding to $1, $2, etc. above).
693
+ * <li>The zero-based index of the match within the total search string.
694
+ * <li>The total string being searched.
695
+ * @param {String} [scope='one'] Use 'one' to replace the first match only, or 'all'. If not
696
+ * explicitly specified and using a regex with flag `g`, `scope` is 'all'.
697
+ * @returns {String} New string with one or all matches replaced.
698
+ * @example
699
+ *
700
+ * // Regex search, using named backreferences in replacement string
701
+ * var name = XRegExp('(?<first>\\w+) (?<last>\\w+)');
702
+ * XRegExp.replace('John Smith', name, '${last}, ${first}');
703
+ * // -> 'Smith, John'
704
+ *
705
+ * // Regex search, using named backreferences in replacement function
706
+ * XRegExp.replace('John Smith', name, function (match) {
707
+ * return match.last + ', ' + match.first;
708
+ * });
709
+ * // -> 'Smith, John'
710
+ *
711
+ * // Global string search/replacement
712
+ * XRegExp.replace('RegExp builds RegExps', 'RegExp', 'XRegExp', 'all');
713
+ * // -> 'XRegExp builds XRegExps'
714
+ */
715
+ self.replace = function (str, search, replacement, scope) {
716
+ var isRegex = self.isRegExp(search),
717
+ search2 = search,
718
+ result;
719
+ if (isRegex) {
720
+ if (scope === undef && search.global) {
721
+ scope = "all"; // Follow flag g when `scope` isn't explicit
722
+ }
723
+ // Note that since a copy is used, `search`'s `lastIndex` isn't updated *during* replacement iterations
724
+ search2 = copy(search, scope === "all" ? "g" : "", scope === "all" ? "" : "g");
725
+ } else if (scope === "all") {
726
+ search2 = new RegExp(self.escape(String(search)), "g");
727
+ }
728
+ result = fixed.replace.call(String(str), search2, replacement); // Fixed `replace` required for named backreferences, etc.
729
+ if (isRegex && search.global) {
730
+ search.lastIndex = 0; // Fixes IE, Safari bug (last tested IE 9, Safari 5.1)
731
+ }
732
+ return result;
733
+ };
242
734
 
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]);
735
+ /**
736
+ * Splits a string into an array of strings using a regex or string separator. Matches of the
737
+ * separator are not included in the result array. However, if `separator` is a regex that contains
738
+ * capturing groups, backreferences are spliced into the result each time `separator` is matched.
739
+ * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably
740
+ * cross-browser.
741
+ * @memberOf XRegExp
742
+ * @param {String} str String to split.
743
+ * @param {RegExp|String} separator Regex or string to use for separating the string.
744
+ * @param {Number} [limit] Maximum number of items to include in the result array.
745
+ * @returns {Array} Array of substrings.
746
+ * @example
747
+ *
748
+ * // Basic use
749
+ * XRegExp.split('a b c', ' ');
750
+ * // -> ['a', 'b', 'c']
751
+ *
752
+ * // With limit
753
+ * XRegExp.split('a b c', ' ', 2);
754
+ * // -> ['a', 'b']
755
+ *
756
+ * // Backreferences in result array
757
+ * XRegExp.split('..word1..', /([a-z]+)(\d+)/i);
758
+ * // -> ['..', 'word', '1', '..']
759
+ */
760
+ self.split = function (str, separator, limit) {
761
+ return fixed.split.call(str, separator, limit);
252
762
  };
253
763
 
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);
764
+ /**
765
+ * Executes a regex search in a specified string. Returns `true` or `false`. Optional `pos` and
766
+ * `sticky` arguments specify the search start position, and whether the match must start at the
767
+ * specified position only. The `lastIndex` property of the provided regex is not used, but is
768
+ * updated for compatibility. Also fixes browser bugs compared to the native
769
+ * `RegExp.prototype.test` and can be used reliably cross-browser.
770
+ * @memberOf XRegExp
771
+ * @param {String} str String to search.
772
+ * @param {RegExp} regex Regex to search with.
773
+ * @param {Number} [pos=0] Zero-based index at which to start the search.
774
+ * @param {Boolean|String} [sticky=false] Whether the match must start at the specified position
775
+ * only. The string `'sticky'` is accepted as an alternative to `true`.
776
+ * @returns {Boolean} Whether the regex matched the provided value.
777
+ * @example
778
+ *
779
+ * // Basic use
780
+ * XRegExp.test('abc', /c/); // -> true
781
+ *
782
+ * // With pos and sticky
783
+ * XRegExp.test('abc', /c/, 0, 'sticky'); // -> false
784
+ */
785
+ self.test = function (str, regex, pos, sticky) {
786
+ // Do this the easy way :-)
787
+ return !!self.exec(str, regex, pos, sticky);
258
788
  };
259
789
 
790
+ /**
791
+ * Uninstalls optional features according to the specified options.
792
+ * @memberOf XRegExp
793
+ * @param {Object|String} options Options object or string.
794
+ * @example
795
+ *
796
+ * // With an options object
797
+ * XRegExp.uninstall({
798
+ * // Restores native regex methods
799
+ * natives: true,
800
+ *
801
+ * // Disables additional syntax and flag extensions
802
+ * extensibility: true
803
+ * });
804
+ *
805
+ * // With an options string
806
+ * XRegExp.uninstall('natives extensibility');
807
+ *
808
+ * // Using a shortcut to uninstall all optional features
809
+ * XRegExp.uninstall('all');
810
+ */
811
+ self.uninstall = function (options) {
812
+ options = prepareOptions(options);
813
+ if (features.natives && options.natives) {
814
+ setNatives(false);
815
+ }
816
+ if (features.extensibility && options.extensibility) {
817
+ setExtensibility(false);
818
+ }
819
+ };
260
820
 
261
- //---------------------------------
262
- // Overriden native methods
263
- //---------------------------------
821
+ /**
822
+ * Returns an XRegExp object that is the union of the given patterns. Patterns can be provided as
823
+ * regex objects or strings. Metacharacters are escaped in patterns provided as strings.
824
+ * Backreferences in provided regex objects are automatically renumbered to work correctly. Native
825
+ * flags used by provided regexes are ignored in favor of the `flags` argument.
826
+ * @memberOf XRegExp
827
+ * @param {Array} patterns Regexes and strings to combine.
828
+ * @param {String} [flags] Any combination of XRegExp flags.
829
+ * @returns {RegExp} Union of the provided regexes and strings.
830
+ * @example
831
+ *
832
+ * XRegExp.union(['a+b*c', /(dogs)\1/, /(cats)\1/], 'i');
833
+ * // -> /a\+b\*c|(dogs)\1|(cats)\2/i
834
+ *
835
+ * XRegExp.union([XRegExp('(?<pet>dogs)\\k<pet>'), XRegExp('(?<pet>cats)\\k<pet>')]);
836
+ * // -> XRegExp('(?<pet>dogs)\\k<pet>|(?<pet>cats)\\k<pet>')
837
+ */
838
+ self.union = function (patterns, flags) {
839
+ var parts = /(\()(?!\?)|\\([1-9]\d*)|\\[\s\S]|\[(?:[^\\\]]|\\[\s\S])*]/g,
840
+ numCaptures = 0,
841
+ numPriorCaptures,
842
+ captureNames,
843
+ rewrite = function (match, paren, backref) {
844
+ var name = captureNames[numCaptures - numPriorCaptures];
845
+ if (paren) { // Capturing group
846
+ ++numCaptures;
847
+ if (name) { // If the current capture has a name
848
+ return "(?<" + name + ">";
849
+ }
850
+ } else if (backref) { // Backreference
851
+ return "\\" + (+backref + numPriorCaptures);
852
+ }
853
+ return match;
854
+ },
855
+ output = [],
856
+ pattern,
857
+ i;
858
+ if (!(isType(patterns, "array") && patterns.length)) {
859
+ throw new TypeError("patterns must be a nonempty array");
860
+ }
861
+ for (i = 0; i < patterns.length; ++i) {
862
+ pattern = patterns[i];
863
+ if (self.isRegExp(pattern)) {
864
+ numPriorCaptures = numCaptures;
865
+ captureNames = (pattern.xregexp && pattern.xregexp.captureNames) || [];
866
+ // Rewrite backreferences. Passing to XRegExp dies on octals and ensures patterns
867
+ // are independently valid; helps keep this simple. Named captures are put back
868
+ output.push(self(pattern.source).source.replace(parts, rewrite));
869
+ } else {
870
+ output.push(self.escape(pattern));
871
+ }
872
+ }
873
+ return self(output.join("|"), flags);
874
+ };
264
875
 
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)
876
+ /**
877
+ * The XRegExp version number.
878
+ * @static
879
+ * @memberOf XRegExp
880
+ * @type String
881
+ */
882
+ self.version = "2.0.0";
883
+
884
+ /*--------------------------------------
885
+ * Fixed/extended native methods
886
+ *------------------------------------*/
887
+
888
+ /**
889
+ * Adds named capture support (with backreferences returned as `result.name`), and fixes browser
890
+ * bugs in the native `RegExp.prototype.exec`. Calling `XRegExp.install('natives')` uses this to
891
+ * override the native method. Use via `XRegExp.exec` without overriding natives.
892
+ * @private
893
+ * @param {String} str String to search.
894
+ * @returns {Array} Match array with named backreference properties, or null.
895
+ */
896
+ fixed.exec = function (str) {
897
+ var match, name, r2, origLastIndex, i;
898
+ if (!this.global) {
273
899
  origLastIndex = this.lastIndex;
900
+ }
274
901
  match = nativ.exec.apply(this, arguments);
275
902
  if (match) {
276
903
  // Fix browsers whose `exec` methods don't consistently return `undefined` for
277
904
  // nonparticipating capturing groups
278
- if (!compliantExecNpcg && match.length > 1 && indexOf(match, "") > -1) {
279
- r2 = RegExp(this.source, nativ.replace.call(getNativeFlags(this), "g", ""));
905
+ if (!compliantExecNpcg && match.length > 1 && lastIndexOf(match, "") > -1) {
906
+ r2 = new RegExp(this.source, nativ.replace.call(getNativeFlags(this), "g", ""));
280
907
  // Using `str.slice(match.index)` rather than `match[0]` in case lookahead allowed
281
908
  // 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;
909
+ nativ.replace.call(String(str).slice(match.index), r2, function () {
910
+ var i;
911
+ for (i = 1; i < arguments.length - 2; ++i) {
912
+ if (arguments[i] === undef) {
913
+ match[i] = undef;
914
+ }
286
915
  }
287
916
  });
288
917
  }
289
918
  // 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];
919
+ if (this.xregexp && this.xregexp.captureNames) {
920
+ for (i = 1; i < match.length; ++i) {
921
+ name = this.xregexp.captureNames[i - 1];
922
+ if (name) {
923
+ match[name] = match[i];
924
+ }
295
925
  }
296
926
  }
297
927
  // Fix browsers that increment `lastIndex` after zero-length matches
298
- if (!compliantLastIndexIncrement && this.global && !match[0].length && (this.lastIndex > match.index))
299
- this.lastIndex--;
928
+ if (this.global && !match[0].length && (this.lastIndex > match.index)) {
929
+ this.lastIndex = match.index;
930
+ }
931
+ }
932
+ if (!this.global) {
933
+ this.lastIndex = origLastIndex; // Fixes IE, Opera bug (last tested IE 9, Opera 11.6)
300
934
  }
301
- if (!this.global)
302
- this.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows)
303
935
  return match;
304
936
  };
305
937
 
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;
938
+ /**
939
+ * Fixes browser bugs in the native `RegExp.prototype.test`. Calling `XRegExp.install('natives')`
940
+ * uses this to override the native method.
941
+ * @private
942
+ * @param {String} str String to search.
943
+ * @returns {Boolean} Whether the regex matched the provided value.
944
+ */
945
+ fixed.test = function (str) {
946
+ // Do this the easy way :-)
947
+ return !!fixed.exec.call(this, str);
320
948
  };
321
949
 
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) {
950
+ /**
951
+ * Adds named capture support (with backreferences returned as `result.name`), and fixes browser
952
+ * bugs in the native `String.prototype.match`. Calling `XRegExp.install('natives')` uses this to
953
+ * override the native method.
954
+ * @private
955
+ * @param {RegExp} regex Regex to search with.
956
+ * @returns {Array} If `regex` uses flag g, an array of match strings or null. Without flag g, the
957
+ * result of calling `regex.exec(this)`.
958
+ */
959
+ fixed.match = function (regex) {
960
+ if (!self.isRegExp(regex)) {
961
+ regex = new RegExp(regex); // Use native `RegExp`
962
+ } else if (regex.global) {
327
963
  var result = nativ.match.apply(this, arguments);
328
- regex.lastIndex = 0; // Fix IE bug
964
+ regex.lastIndex = 0; // Fixes IE bug
329
965
  return result;
330
966
  }
331
- return regex.exec(this); // Run the altered `exec`
967
+ return fixed.exec.call(regex, this);
332
968
  };
333
969
 
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
-
970
+ /**
971
+ * Adds support for `${n}` tokens for named and numbered backreferences in replacement text, and
972
+ * provides named backreferences to replacement functions as `arguments[0].name`. Also fixes
973
+ * browser bugs in replacement text syntax when performing a replacement using a nonregex search
974
+ * value, and the value of a replacement regex's `lastIndex` property during replacement iterations
975
+ * and upon completion. Note that this doesn't support SpiderMonkey's proprietary third (`flags`)
976
+ * argument. Calling `XRegExp.install('natives')` uses this to override the native method. Use via
977
+ * `XRegExp.replace` without overriding natives.
978
+ * @private
979
+ * @param {RegExp|String} search Search pattern to be replaced.
980
+ * @param {String|Function} replacement Replacement string or a function invoked to create it.
981
+ * @returns {String} New string with one or all matches replaced.
982
+ */
983
+ fixed.replace = function (search, replacement) {
984
+ var isRegex = self.isRegExp(search), captureNames, result, str, origLastIndex;
349
985
  if (isRegex) {
350
- if (search._xregexp)
351
- captureNames = search._xregexp.captureNames; // Array or `null`
352
- if (!search.global)
986
+ if (search.xregexp) {
987
+ captureNames = search.xregexp.captureNames;
988
+ }
989
+ if (!search.global) {
353
990
  origLastIndex = search.lastIndex;
991
+ }
354
992
  } else {
355
- search = search + ""; // Type conversion
993
+ search += "";
356
994
  }
357
-
358
- if (Object.prototype.toString.call(replacement) === "[object Function]") {
359
- result = nativ.replace.call(this + "", search, function () {
995
+ if (isType(replacement, "function")) {
996
+ result = nativ.replace.call(String(this), search, function () {
997
+ var args = arguments, i;
360
998
  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];
999
+ // Change the `arguments[0]` string primitive to a `String` object that can store properties
1000
+ args[0] = new String(args[0]);
1001
+ // Store named backreferences on the first argument
1002
+ for (i = 0; i < captureNames.length; ++i) {
1003
+ if (captureNames[i]) {
1004
+ args[0][captureNames[i]] = args[i + 1];
1005
+ }
367
1006
  }
368
1007
  }
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);
1008
+ // Update `lastIndex` before calling `replacement`.
1009
+ // Fixes IE, Chrome, Firefox, Safari bug (last tested IE 9, Chrome 17, Firefox 11, Safari 5.1)
1010
+ if (isRegex && search.global) {
1011
+ search.lastIndex = args[args.length - 2] + args[0].length;
1012
+ }
1013
+ return replacement.apply(null, args);
373
1014
  });
374
1015
  } else {
375
- str = this + ""; // Type conversion, so `args[args.length - 1]` will be a string (given nonstring `this`)
1016
+ str = String(this); // Ensure `args[args.length - 1]` will be a string when given nonstring `this`
376
1017
  result = nativ.replace.call(str, search, function () {
377
1018
  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
1019
+ return nativ.replace.call(String(replacement), replacementToken, function ($0, $1, $2) {
1020
+ var n;
1021
+ // Named or numbered backreference with curly brackets
380
1022
  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;
1023
+ /* XRegExp behavior for `${n}`:
1024
+ * 1. Backreference to numbered capture, where `n` is 1+ digits. `0`, `00`, etc. is the entire match.
1025
+ * 2. Backreference to named capture `n`, if it exists and is not a number overridden by numbered capture.
1026
+ * 3. Otherwise, it's an error.
1027
+ */
1028
+ n = +$1; // Type-convert; drop leading zeros
1029
+ if (n <= args.length - 3) {
1030
+ return args[n] || "";
1031
+ }
1032
+ n = captureNames ? lastIndexOf(captureNames, $1) : -1;
1033
+ if (n < 0) {
1034
+ throw new SyntaxError("backreference to undefined group " + $0);
1035
+ }
1036
+ return args[n + 1] || "";
1037
+ }
1038
+ // Else, special variable or numbered backreference (without curly brackets)
1039
+ if ($2 === "$") return "$";
1040
+ if ($2 === "&" || +$2 === 0) return args[0]; // $&, $0 (not followed by 1-9), $00
1041
+ if ($2 === "`") return args[args.length - 1].slice(0, args[args.length - 2]);
1042
+ if ($2 === "'") return args[args.length - 1].slice(args[args.length - 2] + args[0].length);
1043
+ // Else, numbered backreference (without curly brackets)
1044
+ $2 = +$2; // Type-convert; drop leading zero
1045
+ /* XRegExp behavior:
1046
+ * - Backreferences without curly brackets end after 1 or 2 digits. Use `${..}` for more digits.
1047
+ * - `$1` is an error if there are no capturing groups.
1048
+ * - `$10` is an error if there are less than 10 capturing groups. Use `${1}0` instead.
1049
+ * - `$01` is equivalent to `$1` if a capturing group exists, otherwise it's an error.
1050
+ * - `$0` (not followed by 1-9), `$00`, and `$&` are the entire match.
1051
+ * Native behavior, for comparison:
1052
+ * - Backreferences end after 1 or 2 digits. Cannot use backreference to capturing group 100+.
1053
+ * - `$1` is a literal `$1` if there are no capturing groups.
1054
+ * - `$10` is `$1` followed by a literal `0` if there are less than 10 capturing groups.
1055
+ * - `$01` is equivalent to `$1` if a capturing group exists, otherwise it's a literal `$01`.
1056
+ * - `$0` is a literal `$0`. `$&` is the entire match.
1057
+ */
1058
+ if (!isNaN($2)) {
1059
+ if ($2 > args.length - 3) {
1060
+ throw new SyntaxError("backreference to undefined group " + $0);
405
1061
  }
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;
1062
+ return args[$2] || "";
419
1063
  }
1064
+ throw new SyntaxError("invalid token " + $0);
420
1065
  });
421
1066
  });
422
1067
  }
423
-
424
1068
  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)
1069
+ if (search.global) {
1070
+ search.lastIndex = 0; // Fixes IE, Safari bug (last tested IE 9, Safari 5.1)
1071
+ } else {
1072
+ search.lastIndex = origLastIndex; // Fixes IE, Opera bug (last tested IE 9, Opera 11.6)
1073
+ }
429
1074
  }
430
-
431
1075
  return result;
432
1076
  };
433
1077
 
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
1078
+ /**
1079
+ * Fixes browser bugs in the native `String.prototype.split`. Calling `XRegExp.install('natives')`
1080
+ * uses this to override the native method. Use via `XRegExp.split` without overriding natives.
1081
+ * @private
1082
+ * @param {RegExp|String} separator Regex or string to use for separating the string.
1083
+ * @param {Number} [limit] Maximum number of items to include in the result array.
1084
+ * @returns {Array} Array of substrings.
1085
+ */
1086
+ fixed.split = function (separator, limit) {
1087
+ if (!self.isRegExp(separator)) {
1088
+ return nativ.split.apply(this, arguments); // use faster native method
1089
+ }
1090
+ var str = String(this),
1091
+ origLastIndex = separator.lastIndex,
441
1092
  output = [],
442
1093
  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) {
1094
+ lastLength;
1095
+ /* Values for `limit`, per the spec:
1096
+ * If undefined: pow(2,32) - 1
1097
+ * If 0, Infinity, or NaN: 0
1098
+ * If positive number: limit = floor(limit); if (limit >= pow(2,32)) limit -= pow(2,32);
1099
+ * If negative number: pow(2,32) - floor(abs(limit))
1100
+ * If other: Type-convert, then use the above rules
1101
+ */
1102
+ limit = (limit === undef ? -1 : limit) >>> 0;
1103
+ self.forEach(str, separator, function (match) {
1104
+ if ((match.index + match[0].length) > lastLastIndex) { // != `if (match[0].length)`
465
1105
  output.push(str.slice(lastLastIndex, match.index));
466
-
467
- if (match.length > 1 && match.index < str.length)
1106
+ if (match.length > 1 && match.index < str.length) {
468
1107
  Array.prototype.push.apply(output, match.slice(1));
469
-
1108
+ }
470
1109
  lastLength = match[0].length;
471
- lastLastIndex = s.lastIndex;
472
-
473
- if (output.length >= limit)
474
- break;
1110
+ lastLastIndex = match.index + lastLength;
475
1111
  }
476
-
477
- if (s.lastIndex === match.index)
478
- s.lastIndex++;
479
- }
480
-
1112
+ });
481
1113
  if (lastLastIndex === str.length) {
482
- if (!nativ.test.call(s, "") || lastLength)
1114
+ if (!nativ.test.call(separator, "") || lastLength) {
483
1115
  output.push("");
1116
+ }
484
1117
  } else {
485
1118
  output.push(str.slice(lastLastIndex));
486
1119
  }
487
-
1120
+ separator.lastIndex = origLastIndex;
488
1121
  return output.length > limit ? output.slice(0, limit) : output;
489
1122
  };
490
1123
 
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
- }
1124
+ /*--------------------------------------
1125
+ * Built-in tokens
1126
+ *------------------------------------*/
1127
+
1128
+ // Shortcut
1129
+ add = addToken.on;
1130
+
1131
+ /* Letter identity escapes that natively match literal characters: \p, \P, etc.
1132
+ * Should be SyntaxErrors but are allowed in web reality. XRegExp makes them errors for cross-
1133
+ * browser consistency and to reserve their syntax, but lets them be superseded by XRegExp addons.
1134
+ */
1135
+ add(/\\([ABCE-RTUVXYZaeg-mopqyz]|c(?![A-Za-z])|u(?![\dA-Fa-f]{4})|x(?![\dA-Fa-f]{2}))/,
1136
+ function (match, scope) {
1137
+ // \B is allowed in default scope only
1138
+ if (match[1] === "B" && scope === defaultScope) {
1139
+ return match[0];
541
1140
  }
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
- }
1141
+ throw new SyntaxError("invalid escape " + match[0]);
1142
+ },
1143
+ {scope: "all"});
559
1144
 
1145
+ /* Empty character class: [] or [^]
1146
+ * Fixes a critical cross-browser syntax inconsistency. Unless this is standardized (per the spec),
1147
+ * regex syntax can't be accurately parsed because character class endings can't be determined.
1148
+ */
1149
+ add(/\[(\^?)]/,
1150
+ function (match) {
1151
+ // For cross-browser compatibility with ES3, convert [] to \b\B and [^] to [\s\S].
1152
+ // (?!) should work like \b\B, but is unreliable in Firefox
1153
+ return match[1] ? "[\\s\\S]" : "\\b\\B";
1154
+ });
560
1155
 
561
- //---------------------------------
562
- // Built-in tokens
563
- //---------------------------------
1156
+ /* Comment pattern: (?# )
1157
+ * Inline comments are an alternative to the line comments allowed in free-spacing mode (flag x).
1158
+ */
1159
+ add(/(?:\(\?#[^)]*\))+/,
1160
+ function (match) {
1161
+ // Keep tokens separated unless the following token is a quantifier
1162
+ return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)";
1163
+ });
564
1164
 
565
- // Augment XRegExp's regular expression syntax and flags. Note that when adding tokens, the
566
- // third (`scope`) argument defaults to `XRegExp.OUTSIDE_CLASS`
1165
+ /* Named backreference: \k<name>
1166
+ * Backreference names can use the characters A-Z, a-z, 0-9, _, and $ only.
1167
+ */
1168
+ add(/\\k<([\w$]+)>/,
1169
+ function (match) {
1170
+ var index = isNaN(match[1]) ? (lastIndexOf(this.captureNames, match[1]) + 1) : +match[1],
1171
+ endIndex = match.index + match[0].length;
1172
+ if (!index || index > this.captureNames.length) {
1173
+ throw new SyntaxError("backreference to undefined group " + match[0]);
1174
+ }
1175
+ // Keep backreferences separate from subsequent literal numbers
1176
+ return "\\" + index + (
1177
+ endIndex === match.input.length || isNaN(match.input.charAt(endIndex)) ? "" : "(?:)"
1178
+ );
1179
+ });
567
1180
 
568
- // Comment pattern: (?# )
569
- XRegExp.addToken(
570
- /\(\?#[^)]*\)/,
1181
+ /* Whitespace and line comments, in free-spacing mode (aka extended mode, flag x) only.
1182
+ */
1183
+ add(/(?:\s+|#.*)+/,
571
1184
  function (match) {
572
1185
  // Keep tokens separated unless the following token is a quantifier
573
1186
  return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? "" : "(?:)";
574
- }
575
- );
1187
+ },
1188
+ {
1189
+ trigger: function () {
1190
+ return this.hasFlag("x");
1191
+ },
1192
+ customFlags: "x"
1193
+ });
576
1194
 
577
- // Capturing group (match the opening parenthesis only).
578
- // Required for support of named capturing groups
579
- XRegExp.addToken(
580
- /\((?!\?)/,
1195
+ /* Dot, in dotall mode (aka singleline mode, flag s) only.
1196
+ */
1197
+ add(/\./,
581
1198
  function () {
582
- this.captureNames.push(null);
583
- return "(";
584
- }
585
- );
1199
+ return "[\\s\\S]";
1200
+ },
1201
+ {
1202
+ trigger: function () {
1203
+ return this.hasFlag("s");
1204
+ },
1205
+ customFlags: "s"
1206
+ });
586
1207
 
587
- // Named capturing group (match the opening delimiter only): (?<name>
588
- XRegExp.addToken(
589
- /\(\?<([$\w]+)>/,
1208
+ /* Named capturing group; match the opening delimiter only: (?<name>
1209
+ * Capture names can use the characters A-Z, a-z, 0-9, _, and $ only. Names can't be integers.
1210
+ * Supports Python-style (?P<name> as an alternate syntax to avoid issues in recent Opera (which
1211
+ * natively supports the Python-style syntax). Otherwise, XRegExp might treat numbered
1212
+ * backreferences to Python-style named capture as octals.
1213
+ */
1214
+ add(/\(\?P?<([\w$]+)>/,
590
1215
  function (match) {
1216
+ if (!isNaN(match[1])) {
1217
+ // Avoid incorrect lookups, since named backreferences are added to match arrays
1218
+ throw new SyntaxError("can't use integer as capture name " + match[0]);
1219
+ }
591
1220
  this.captureNames.push(match[1]);
592
1221
  this.hasNamedCapture = true;
593
1222
  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
- );
1223
+ });
619
1224
 
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
- );
1225
+ /* Numbered backreference or octal, plus any following digits: \0, \11, etc.
1226
+ * Octals except \0 not followed by 0-9 and backreferences to unopened capture groups throw an
1227
+ * error. Other matches are returned unaltered. IE <= 8 doesn't support backreferences greater than
1228
+ * \99 in regex syntax.
1229
+ */
1230
+ add(/\\(\d+)/,
1231
+ function (match, scope) {
1232
+ if (!(scope === defaultScope && /^[1-9]/.test(match[1]) && +match[1] <= this.captureNames.length) &&
1233
+ match[1] !== "0") {
1234
+ throw new SyntaxError("can't use octal escape or backreference to undefined group " + match[0]);
1235
+ }
1236
+ return match[0];
1237
+ },
1238
+ {scope: "all"});
629
1239
 
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)) ? "" : "(?:)";
1240
+ /* Capturing group; match the opening parenthesis only.
1241
+ * Required for support of named capturing groups. Also adds explicit capture mode (flag n).
1242
+ */
1243
+ add(/\((?!\?)/,
1244
+ function () {
1245
+ if (this.hasFlag("n")) {
1246
+ return "(?:";
1247
+ }
1248
+ this.captureNames.push(null);
1249
+ return "(";
636
1250
  },
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
- })();
1251
+ {customFlags: "n"});
1252
+
1253
+ /*--------------------------------------
1254
+ * Expose XRegExp
1255
+ *------------------------------------*/
1256
+
1257
+ // For CommonJS enviroments
1258
+ if (typeof exports !== "undefined") {
1259
+ exports.XRegExp = self;
1260
+ }
1261
+
1262
+ return self;
1263
+
1264
+ }());
664
1265