xregexp-rails 1.5.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|
|