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 +4 -4
- data/app/assets/javascripts/xregexp.js +1125 -524
- data/app/assets/javascripts/xregexp/build.js +146 -0
- data/app/assets/javascripts/xregexp/matchrecursive.js +181 -0
- data/app/assets/javascripts/xregexp/prototypes.js +115 -0
- data/app/assets/javascripts/xregexp/unicode-base.js +152 -0
- data/app/assets/javascripts/xregexp/unicode-blocks.js +183 -0
- data/app/assets/javascripts/xregexp/unicode-categories.js +102 -0
- data/app/assets/javascripts/xregexp/unicode-properties.js +39 -0
- data/app/assets/javascripts/xregexp/unicode-scripts.js +98 -0
- data/lib/xregexp-rails/version.rb +1 -1
- metadata +9 -6
- data/app/assets/javascripts/xregexp-matchrecursive.js +0 -151
- data/app/assets/javascripts/xregexp-unicode-base.js +0 -73
- data/app/assets/javascripts/xregexp-unicode-blocks.js +0 -187
- data/app/assets/javascripts/xregexp-unicode-categories.js +0 -102
- data/app/assets/javascripts/xregexp-unicode-scripts.js +0 -102
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e67303d4e75a553268b4ba2e7e74d6a1c4deeae2
|
4
|
+
data.tar.gz: 5beab616181a0eb9bb9e9296f4978e29c50940c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: beaa60d69b87c2e1f52684a5da83ca741105caea91d8648ae8cb857b42f368966db047dd67cb158fc4d5ddc29b6814cc7de72016f9f64aca48fee086d8451d90
|
7
|
+
data.tar.gz: 7f5d5804ba1095c575796db0348548a51f33ba295fdb5fbfddcfaa090257cc5f74c2ee5dff8c8539fbf36baaef93624dcc27caa743d49005305919775a1d8147
|
@@ -1,664 +1,1265 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
16
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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,
|
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
|
59
|
-
|
60
|
-
if (match
|
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
|
-
|
67
|
-
else if (chr === "]")
|
68
|
-
|
69
|
-
|
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
|
-
|
77
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
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
|
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
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
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
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
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 =
|
177
|
-
if (
|
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
|
-
|
507
|
+
}
|
508
|
+
if (regex.global) {
|
180
509
|
regex.lastIndex = match ? r2.lastIndex : 0;
|
510
|
+
}
|
181
511
|
return match;
|
182
512
|
};
|
183
513
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
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
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
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
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
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
|
-
|
231
|
-
|
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
|
-
|
239
|
-
|
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
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
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
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
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
|
-
|
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
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
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 &&
|
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
|
283
|
-
|
284
|
-
|
285
|
-
|
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.
|
291
|
-
for (
|
292
|
-
name = this.
|
293
|
-
if (name)
|
294
|
-
|
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 (
|
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
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
//
|
315
|
-
|
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
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
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; //
|
964
|
+
regex.lastIndex = 0; // Fixes IE bug
|
329
965
|
return result;
|
330
966
|
}
|
331
|
-
return
|
967
|
+
return fixed.exec.call(regex, this);
|
332
968
|
};
|
333
969
|
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
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.
|
351
|
-
captureNames = search.
|
352
|
-
|
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
|
993
|
+
search += "";
|
356
994
|
}
|
357
|
-
|
358
|
-
|
359
|
-
|
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
|
362
|
-
|
363
|
-
// Store named backreferences on
|
364
|
-
for (
|
365
|
-
if (captureNames[i])
|
366
|
-
|
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
|
370
|
-
|
371
|
-
|
372
|
-
|
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
|
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
|
379
|
-
|
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
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
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
|
-
|
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; //
|
427
|
-
else
|
428
|
-
search.lastIndex = origLastIndex; //
|
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
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
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
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
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 =
|
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(
|
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
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
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
|
-
|
543
|
-
|
544
|
-
}
|
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
|
-
|
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
|
-
|
566
|
-
|
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
|
-
|
569
|
-
|
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
|
-
|
578
|
-
|
579
|
-
|
580
|
-
/\((?!\?)/,
|
1195
|
+
/* Dot, in dotall mode (aka singleline mode, flag s) only.
|
1196
|
+
*/
|
1197
|
+
add(/\./,
|
581
1198
|
function () {
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
1199
|
+
return "[\\s\\S]";
|
1200
|
+
},
|
1201
|
+
{
|
1202
|
+
trigger: function () {
|
1203
|
+
return this.hasFlag("s");
|
1204
|
+
},
|
1205
|
+
customFlags: "s"
|
1206
|
+
});
|
586
1207
|
|
587
|
-
|
588
|
-
|
589
|
-
|
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
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
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
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
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
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
XRegExp
|
646
|
-
|
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
|
|