XRegExp.js 29 KB

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