xregexp-rails 1.5.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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