You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1792 lines
68 KiB

  1. 'use strict';
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. /*!
  6. * XRegExp 4.0.0
  7. * <xregexp.com>
  8. * Steven Levithan (c) 2007-2017 MIT License
  9. */
  10. /**
  11. * XRegExp provides augmented, extensible regular expressions. You get additional regex syntax and
  12. * flags, beyond what browsers support natively. XRegExp is also a regex utility belt with tools to
  13. * make your client-side grepping simpler and more powerful, while freeing you from related
  14. * cross-browser inconsistencies.
  15. */
  16. // ==--------------------------==
  17. // Private stuff
  18. // ==--------------------------==
  19. // Property name used for extended regex instance data
  20. var REGEX_DATA = 'xregexp';
  21. // Optional features that can be installed and uninstalled
  22. var features = {
  23. astral: false
  24. };
  25. // Native methods to use and restore ('native' is an ES3 reserved keyword)
  26. var nativ = {
  27. exec: RegExp.prototype.exec,
  28. test: RegExp.prototype.test,
  29. match: String.prototype.match,
  30. replace: String.prototype.replace,
  31. split: String.prototype.split
  32. };
  33. // Storage for fixed/extended native methods
  34. var fixed = {};
  35. // Storage for regexes cached by `XRegExp.cache`
  36. var regexCache = {};
  37. // Storage for pattern details cached by the `XRegExp` constructor
  38. var patternCache = {};
  39. // Storage for regex syntax tokens added internally or by `XRegExp.addToken`
  40. var tokens = [];
  41. // Token scopes
  42. var defaultScope = 'default';
  43. var classScope = 'class';
  44. // Regexes that match native regex syntax, including octals
  45. var nativeTokens = {
  46. // Any native multicharacter token in default scope, or any single character
  47. '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}|{[\dA-Fa-f]+})|c[A-Za-z]|[\s\S])|\(\?(?:[:=!]|<[=!])|[?*+]\?|{\d+(?:,\d*)?}\??|[\s\S]/,
  48. // Any native multicharacter token in character class scope, or any single character
  49. 'class': /\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[\dA-Fa-f]{2}|u(?:[\dA-Fa-f]{4}|{[\dA-Fa-f]+})|c[A-Za-z]|[\s\S])|[\s\S]/
  50. };
  51. // Any backreference or dollar-prefixed character in replacement strings
  52. var replacementToken = /\$(?:{([\w$]+)}|<([\w$]+)>|(\d\d?|[\s\S]))/g;
  53. // Check for correct `exec` handling of nonparticipating capturing groups
  54. var correctExecNpcg = nativ.exec.call(/()??/, '')[1] === undefined;
  55. // Check for ES6 `flags` prop support
  56. var hasFlagsProp = /x/.flags !== undefined;
  57. // Shortcut to `Object.prototype.toString`
  58. var toString = {}.toString;
  59. function hasNativeFlag(flag) {
  60. // Can't check based on the presence of properties/getters since browsers might support such
  61. // properties even when they don't support the corresponding flag in regex construction (tested
  62. // in Chrome 48, where `'unicode' in /x/` is true but trying to construct a regex with flag `u`
  63. // throws an error)
  64. var isSupported = true;
  65. try {
  66. // Can't use regex literals for testing even in a `try` because regex literals with
  67. // unsupported flags cause a compilation error in IE
  68. new RegExp('', flag);
  69. } catch (exception) {
  70. isSupported = false;
  71. }
  72. return isSupported;
  73. }
  74. // Check for ES6 `u` flag support
  75. var hasNativeU = hasNativeFlag('u');
  76. // Check for ES6 `y` flag support
  77. var hasNativeY = hasNativeFlag('y');
  78. // Tracker for known flags, including addon flags
  79. var registeredFlags = {
  80. g: true,
  81. i: true,
  82. m: true,
  83. u: hasNativeU,
  84. y: hasNativeY
  85. };
  86. /**
  87. * Attaches extended data and `XRegExp.prototype` properties to a regex object.
  88. *
  89. * @private
  90. * @param {RegExp} regex Regex to augment.
  91. * @param {Array} captureNames Array with capture names, or `null`.
  92. * @param {String} xSource XRegExp pattern used to generate `regex`, or `null` if N/A.
  93. * @param {String} xFlags XRegExp flags used to generate `regex`, or `null` if N/A.
  94. * @param {Boolean} [isInternalOnly=false] Whether the regex will be used only for internal
  95. * operations, and never exposed to users. For internal-only regexes, we can improve perf by
  96. * skipping some operations like attaching `XRegExp.prototype` properties.
  97. * @returns {RegExp} Augmented regex.
  98. */
  99. function augment(regex, captureNames, xSource, xFlags, isInternalOnly) {
  100. var p = void 0;
  101. regex[REGEX_DATA] = {
  102. captureNames: captureNames
  103. };
  104. if (isInternalOnly) {
  105. return regex;
  106. }
  107. // Can't auto-inherit these since the XRegExp constructor returns a nonprimitive value
  108. if (regex.__proto__) {
  109. regex.__proto__ = XRegExp.prototype;
  110. } else {
  111. for (p in XRegExp.prototype) {
  112. // An `XRegExp.prototype.hasOwnProperty(p)` check wouldn't be worth it here, since this
  113. // is performance sensitive, and enumerable `Object.prototype` or `RegExp.prototype`
  114. // extensions exist on `regex.prototype` anyway
  115. regex[p] = XRegExp.prototype[p];
  116. }
  117. }
  118. regex[REGEX_DATA].source = xSource;
  119. // Emulate the ES6 `flags` prop by ensuring flags are in alphabetical order
  120. regex[REGEX_DATA].flags = xFlags ? xFlags.split('').sort().join('') : xFlags;
  121. return regex;
  122. }
  123. /**
  124. * Removes any duplicate characters from the provided string.
  125. *
  126. * @private
  127. * @param {String} str String to remove duplicate characters from.
  128. * @returns {String} String with any duplicate characters removed.
  129. */
  130. function clipDuplicates(str) {
  131. return nativ.replace.call(str, /([\s\S])(?=[\s\S]*\1)/g, '');
  132. }
  133. /**
  134. * Copies a regex object while preserving extended data and augmenting with `XRegExp.prototype`
  135. * properties. The copy has a fresh `lastIndex` property (set to zero). Allows adding and removing
  136. * flags g and y while copying the regex.
  137. *
  138. * @private
  139. * @param {RegExp} regex Regex to copy.
  140. * @param {Object} [options] Options object with optional properties:
  141. * - `addG` {Boolean} Add flag g while copying the regex.
  142. * - `addY` {Boolean} Add flag y while copying the regex.
  143. * - `removeG` {Boolean} Remove flag g while copying the regex.
  144. * - `removeY` {Boolean} Remove flag y while copying the regex.
  145. * - `isInternalOnly` {Boolean} Whether the copied regex will be used only for internal
  146. * operations, and never exposed to users. For internal-only regexes, we can improve perf by
  147. * skipping some operations like attaching `XRegExp.prototype` properties.
  148. * - `source` {String} Overrides `<regex>.source`, for special cases.
  149. * @returns {RegExp} Copy of the provided regex, possibly with modified flags.
  150. */
  151. function copyRegex(regex, options) {
  152. if (!XRegExp.isRegExp(regex)) {
  153. throw new TypeError('Type RegExp expected');
  154. }
  155. var xData = regex[REGEX_DATA] || {};
  156. var flags = getNativeFlags(regex);
  157. var flagsToAdd = '';
  158. var flagsToRemove = '';
  159. var xregexpSource = null;
  160. var xregexpFlags = null;
  161. options = options || {};
  162. if (options.removeG) {
  163. flagsToRemove += 'g';
  164. }
  165. if (options.removeY) {
  166. flagsToRemove += 'y';
  167. }
  168. if (flagsToRemove) {
  169. flags = nativ.replace.call(flags, new RegExp('[' + flagsToRemove + ']+', 'g'), '');
  170. }
  171. if (options.addG) {
  172. flagsToAdd += 'g';
  173. }
  174. if (options.addY) {
  175. flagsToAdd += 'y';
  176. }
  177. if (flagsToAdd) {
  178. flags = clipDuplicates(flags + flagsToAdd);
  179. }
  180. if (!options.isInternalOnly) {
  181. if (xData.source !== undefined) {
  182. xregexpSource = xData.source;
  183. }
  184. // null or undefined; don't want to add to `flags` if the previous value was null, since
  185. // that indicates we're not tracking original precompilation flags
  186. if (xData.flags != null) {
  187. // Flags are only added for non-internal regexes by `XRegExp.globalize`. Flags are never
  188. // removed for non-internal regexes, so don't need to handle it
  189. xregexpFlags = flagsToAdd ? clipDuplicates(xData.flags + flagsToAdd) : xData.flags;
  190. }
  191. }
  192. // Augment with `XRegExp.prototype` properties, but use the native `RegExp` constructor to avoid
  193. // searching for special tokens. That would be wrong for regexes constructed by `RegExp`, and
  194. // unnecessary for regexes constructed by `XRegExp` because the regex has already undergone the
  195. // translation to native regex syntax
  196. regex = augment(new RegExp(options.source || regex.source, flags), hasNamedCapture(regex) ? xData.captureNames.slice(0) : null, xregexpSource, xregexpFlags, options.isInternalOnly);
  197. return regex;
  198. }
  199. /**
  200. * Converts hexadecimal to decimal.
  201. *
  202. * @private
  203. * @param {String} hex
  204. * @returns {Number}
  205. */
  206. function dec(hex) {
  207. return parseInt(hex, 16);
  208. }
  209. /**
  210. * Returns a pattern that can be used in a native RegExp in place of an ignorable token such as an
  211. * inline comment or whitespace with flag x. This is used directly as a token handler function
  212. * passed to `XRegExp.addToken`.
  213. *
  214. * @private
  215. * @param {String} match Match arg of `XRegExp.addToken` handler
  216. * @param {String} scope Scope arg of `XRegExp.addToken` handler
  217. * @param {String} flags Flags arg of `XRegExp.addToken` handler
  218. * @returns {String} Either '' or '(?:)', depending on which is needed in the context of the match.
  219. */
  220. function getContextualTokenSeparator(match, scope, flags) {
  221. if (
  222. // No need to separate tokens if at the beginning or end of a group
  223. match.input[match.index - 1] === '(' || match.input[match.index + match[0].length] === ')' ||
  224. // Avoid separating tokens when the following token is a quantifier
  225. isQuantifierNext(match.input, match.index + match[0].length, flags)) {
  226. return '';
  227. }
  228. // Keep tokens separated. This avoids e.g. inadvertedly changing `\1 1` or `\1(?#)1` to `\11`.
  229. // This also ensures all tokens remain as discrete atoms, e.g. it avoids converting the syntax
  230. // error `(? :` into `(?:`.
  231. return '(?:)';
  232. }
  233. /**
  234. * Returns native `RegExp` flags used by a regex object.
  235. *
  236. * @private
  237. * @param {RegExp} regex Regex to check.
  238. * @returns {String} Native flags in use.
  239. */
  240. function getNativeFlags(regex) {
  241. return hasFlagsProp ? regex.flags :
  242. // Explicitly using `RegExp.prototype.toString` (rather than e.g. `String` or concatenation
  243. // with an empty string) allows this to continue working predictably when
  244. // `XRegExp.proptotype.toString` is overridden
  245. nativ.exec.call(/\/([a-z]*)$/i, RegExp.prototype.toString.call(regex))[1];
  246. }
  247. /**
  248. * Determines whether a regex has extended instance data used to track capture names.
  249. *
  250. * @private
  251. * @param {RegExp} regex Regex to check.
  252. * @returns {Boolean} Whether the regex uses named capture.
  253. */
  254. function hasNamedCapture(regex) {
  255. return !!(regex[REGEX_DATA] && regex[REGEX_DATA].captureNames);
  256. }
  257. /**
  258. * Converts decimal to hexadecimal.
  259. *
  260. * @private
  261. * @param {Number|String} dec
  262. * @returns {String}
  263. */
  264. function hex(dec) {
  265. return parseInt(dec, 10).toString(16);
  266. }
  267. /**
  268. * Checks whether the next nonignorable token after the specified position is a quantifier.
  269. *
  270. * @private
  271. * @param {String} pattern Pattern to search within.
  272. * @param {Number} pos Index in `pattern` to search at.
  273. * @param {String} flags Flags used by the pattern.
  274. * @returns {Boolean} Whether the next nonignorable token is a quantifier.
  275. */
  276. function isQuantifierNext(pattern, pos, flags) {
  277. var inlineCommentPattern = '\\(\\?#[^)]*\\)';
  278. var lineCommentPattern = '#[^#\\n]*';
  279. var quantifierPattern = '[?*+]|{\\d+(?:,\\d*)?}';
  280. return nativ.test.call(flags.indexOf('x') !== -1 ?
  281. // Ignore any leading whitespace, line comments, and inline comments
  282. /^(?:\s|#[^#\n]*|\(\?#[^)]*\))*(?:[?*+]|{\d+(?:,\d*)?})/ :
  283. // Ignore any leading inline comments
  284. /^(?:\(\?#[^)]*\))*(?:[?*+]|{\d+(?:,\d*)?})/, pattern.slice(pos));
  285. }
  286. /**
  287. * Determines whether a value is of the specified type, by resolving its internal [[Class]].
  288. *
  289. * @private
  290. * @param {*} value Object to check.
  291. * @param {String} type Type to check for, in TitleCase.
  292. * @returns {Boolean} Whether the object matches the type.
  293. */
  294. function isType(value, type) {
  295. return toString.call(value) === '[object ' + type + ']';
  296. }
  297. /**
  298. * Adds leading zeros if shorter than four characters. Used for fixed-length hexadecimal values.
  299. *
  300. * @private
  301. * @param {String} str
  302. * @returns {String}
  303. */
  304. function pad4(str) {
  305. while (str.length < 4) {
  306. str = '0' + str;
  307. }
  308. return str;
  309. }
  310. /**
  311. * Checks for flag-related errors, and strips/applies flags in a leading mode modifier. Offloads
  312. * the flag preparation logic from the `XRegExp` constructor.
  313. *
  314. * @private
  315. * @param {String} pattern Regex pattern, possibly with a leading mode modifier.
  316. * @param {String} flags Any combination of flags.
  317. * @returns {Object} Object with properties `pattern` and `flags`.
  318. */
  319. function prepareFlags(pattern, flags) {
  320. var i = void 0;
  321. // Recent browsers throw on duplicate flags, so copy this behavior for nonnative flags
  322. if (clipDuplicates(flags) !== flags) {
  323. throw new SyntaxError('Invalid duplicate regex flag ' + flags);
  324. }
  325. // Strip and apply a leading mode modifier with any combination of flags except g or y
  326. pattern = nativ.replace.call(pattern, /^\(\?([\w$]+)\)/, function ($0, $1) {
  327. if (nativ.test.call(/[gy]/, $1)) {
  328. throw new SyntaxError('Cannot use flag g or y in mode modifier ' + $0);
  329. }
  330. // Allow duplicate flags within the mode modifier
  331. flags = clipDuplicates(flags + $1);
  332. return '';
  333. });
  334. // Throw on unknown native or nonnative flags
  335. for (i = 0; i < flags.length; ++i) {
  336. if (!registeredFlags[flags[i]]) {
  337. throw new SyntaxError('Unknown regex flag ' + flags[i]);
  338. }
  339. }
  340. return {
  341. pattern: pattern,
  342. flags: flags
  343. };
  344. }
  345. /**
  346. * Prepares an options object from the given value.
  347. *
  348. * @private
  349. * @param {String|Object} value Value to convert to an options object.
  350. * @returns {Object} Options object.
  351. */
  352. function prepareOptions(value) {
  353. var options = {};
  354. if (isType(value, 'String')) {
  355. XRegExp.forEach(value, /[^\s,]+/, function (match) {
  356. options[match] = true;
  357. });
  358. return options;
  359. }
  360. return value;
  361. }
  362. /**
  363. * Registers a flag so it doesn't throw an 'unknown flag' error.
  364. *
  365. * @private
  366. * @param {String} flag Single-character flag to register.
  367. */
  368. function registerFlag(flag) {
  369. if (!/^[\w$]$/.test(flag)) {
  370. throw new Error('Flag must be a single character A-Za-z0-9_$');
  371. }
  372. registeredFlags[flag] = true;
  373. }
  374. /**
  375. * Runs built-in and custom regex syntax tokens in reverse insertion order at the specified
  376. * position, until a match is found.
  377. *
  378. * @private
  379. * @param {String} pattern Original pattern from which an XRegExp object is being built.
  380. * @param {String} flags Flags being used to construct the regex.
  381. * @param {Number} pos Position to search for tokens within `pattern`.
  382. * @param {Number} scope Regex scope to apply: 'default' or 'class'.
  383. * @param {Object} context Context object to use for token handler functions.
  384. * @returns {Object} Object with properties `matchLength`, `output`, and `reparse`; or `null`.
  385. */
  386. function runTokens(pattern, flags, pos, scope, context) {
  387. var i = tokens.length;
  388. var leadChar = pattern[pos];
  389. var result = null;
  390. var match = void 0;
  391. var t = void 0;
  392. // Run in reverse insertion order
  393. while (i--) {
  394. t = tokens[i];
  395. if (t.leadChar && t.leadChar !== leadChar || t.scope !== scope && t.scope !== 'all' || t.flag && !(flags.indexOf(t.flag) !== -1)) {
  396. continue;
  397. }
  398. match = XRegExp.exec(pattern, t.regex, pos, 'sticky');
  399. if (match) {
  400. result = {
  401. matchLength: match[0].length,
  402. output: t.handler.call(context, match, scope, flags),
  403. reparse: t.reparse
  404. };
  405. // Finished with token tests
  406. break;
  407. }
  408. }
  409. return result;
  410. }
  411. /**
  412. * Enables or disables implicit astral mode opt-in. When enabled, flag A is automatically added to
  413. * all new regexes created by XRegExp. This causes an error to be thrown when creating regexes if
  414. * the Unicode Base addon is not available, since flag A is registered by that addon.
  415. *
  416. * @private
  417. * @param {Boolean} on `true` to enable; `false` to disable.
  418. */
  419. function setAstral(on) {
  420. features.astral = on;
  421. }
  422. /**
  423. * Returns the object, or throws an error if it is `null` or `undefined`. This is used to follow
  424. * the ES5 abstract operation `ToObject`.
  425. *
  426. * @private
  427. * @param {*} value Object to check and return.
  428. * @returns {*} The provided object.
  429. */
  430. function toObject(value) {
  431. // null or undefined
  432. if (value == null) {
  433. throw new TypeError('Cannot convert null or undefined to object');
  434. }
  435. return value;
  436. }
  437. // ==--------------------------==
  438. // Constructor
  439. // ==--------------------------==
  440. /**
  441. * Creates an extended regular expression object for matching text with a pattern. Differs from a
  442. * native regular expression in that additional syntax and flags are supported. The returned object
  443. * is in fact a native `RegExp` and works with all native methods.
  444. *
  445. * @class XRegExp
  446. * @constructor
  447. * @param {String|RegExp} pattern Regex pattern string, or an existing regex object to copy.
  448. * @param {String} [flags] Any combination of flags.
  449. * Native flags:
  450. * - `g` - global
  451. * - `i` - ignore case
  452. * - `m` - multiline anchors
  453. * - `u` - unicode (ES6)
  454. * - `y` - sticky (Firefox 3+, ES6)
  455. * Additional XRegExp flags:
  456. * - `n` - explicit capture
  457. * - `s` - dot matches all (aka singleline)
  458. * - `x` - free-spacing and line comments (aka extended)
  459. * - `A` - astral (requires the Unicode Base addon)
  460. * Flags cannot be provided when constructing one `RegExp` from another.
  461. * @returns {RegExp} Extended regular expression object.
  462. * @example
  463. *
  464. * // With named capture and flag x
  465. * XRegExp(`(?<year> [0-9]{4} ) -? # year
  466. * (?<month> [0-9]{2} ) -? # month
  467. * (?<day> [0-9]{2} ) # day`, 'x');
  468. *
  469. * // Providing a regex object copies it. Native regexes are recompiled using native (not XRegExp)
  470. * // syntax. Copies maintain extended data, are augmented with `XRegExp.prototype` properties, and
  471. * // have fresh `lastIndex` properties (set to zero).
  472. * XRegExp(/regex/);
  473. */
  474. function XRegExp(pattern, flags) {
  475. if (XRegExp.isRegExp(pattern)) {
  476. if (flags !== undefined) {
  477. throw new TypeError('Cannot supply flags when copying a RegExp');
  478. }
  479. return copyRegex(pattern);
  480. }
  481. // Copy the argument behavior of `RegExp`
  482. pattern = pattern === undefined ? '' : String(pattern);
  483. flags = flags === undefined ? '' : String(flags);
  484. if (XRegExp.isInstalled('astral') && !(flags.indexOf('A') !== -1)) {
  485. // This causes an error to be thrown if the Unicode Base addon is not available
  486. flags += 'A';
  487. }
  488. if (!patternCache[pattern]) {
  489. patternCache[pattern] = {};
  490. }
  491. if (!patternCache[pattern][flags]) {
  492. var context = {
  493. hasNamedCapture: false,
  494. captureNames: []
  495. };
  496. var scope = defaultScope;
  497. var output = '';
  498. var pos = 0;
  499. var result = void 0;
  500. // Check for flag-related errors, and strip/apply flags in a leading mode modifier
  501. var applied = prepareFlags(pattern, flags);
  502. var appliedPattern = applied.pattern;
  503. var appliedFlags = applied.flags;
  504. // Use XRegExp's tokens to translate the pattern to a native regex pattern.
  505. // `appliedPattern.length` may change on each iteration if tokens use `reparse`
  506. while (pos < appliedPattern.length) {
  507. do {
  508. // Check for custom tokens at the current position
  509. result = runTokens(appliedPattern, appliedFlags, pos, scope, context);
  510. // If the matched token used the `reparse` option, splice its output into the
  511. // pattern before running tokens again at the same position
  512. if (result && result.reparse) {
  513. appliedPattern = appliedPattern.slice(0, pos) + result.output + appliedPattern.slice(pos + result.matchLength);
  514. }
  515. } while (result && result.reparse);
  516. if (result) {
  517. output += result.output;
  518. pos += result.matchLength || 1;
  519. } else {
  520. // Get the native token at the current position
  521. var token = XRegExp.exec(appliedPattern, nativeTokens[scope], pos, 'sticky')[0];
  522. output += token;
  523. pos += token.length;
  524. if (token === '[' && scope === defaultScope) {
  525. scope = classScope;
  526. } else if (token === ']' && scope === classScope) {
  527. scope = defaultScope;
  528. }
  529. }
  530. }
  531. patternCache[pattern][flags] = {
  532. // Use basic cleanup to collapse repeated empty groups like `(?:)(?:)` to `(?:)`. Empty
  533. // groups are sometimes inserted during regex transpilation in order to keep tokens
  534. // separated. However, more than one empty group in a row is never needed.
  535. pattern: nativ.replace.call(output, /(?:\(\?:\))+/g, '(?:)'),
  536. // Strip all but native flags
  537. flags: nativ.replace.call(appliedFlags, /[^gimuy]+/g, ''),
  538. // `context.captureNames` has an item for each capturing group, even if unnamed
  539. captures: context.hasNamedCapture ? context.captureNames : null
  540. };
  541. }
  542. var generated = patternCache[pattern][flags];
  543. return augment(new RegExp(generated.pattern, generated.flags), generated.captures, pattern, flags);
  544. }
  545. // Add `RegExp.prototype` to the prototype chain
  546. XRegExp.prototype = /(?:)/;
  547. // ==--------------------------==
  548. // Public properties
  549. // ==--------------------------==
  550. /**
  551. * The XRegExp version number as a string containing three dot-separated parts. For example,
  552. * '2.0.0-beta-3'.
  553. *
  554. * @static
  555. * @memberOf XRegExp
  556. * @type String
  557. */
  558. XRegExp.version = '4.0.0';
  559. // ==--------------------------==
  560. // Public methods
  561. // ==--------------------------==
  562. // Intentionally undocumented; used in tests and addons
  563. XRegExp._clipDuplicates = clipDuplicates;
  564. XRegExp._hasNativeFlag = hasNativeFlag;
  565. XRegExp._dec = dec;
  566. XRegExp._hex = hex;
  567. XRegExp._pad4 = pad4;
  568. /**
  569. * Extends XRegExp syntax and allows custom flags. This is used internally and can be used to
  570. * create XRegExp addons. If more than one token can match the same string, the last added wins.
  571. *
  572. * @memberOf XRegExp
  573. * @param {RegExp} regex Regex object that matches the new token.
  574. * @param {Function} handler Function that returns a new pattern string (using native regex syntax)
  575. * to replace the matched token within all future XRegExp regexes. Has access to persistent
  576. * properties of the regex being built, through `this`. Invoked with three arguments:
  577. * - The match array, with named backreference properties.
  578. * - The regex scope where the match was found: 'default' or 'class'.
  579. * - The flags used by the regex, including any flags in a leading mode modifier.
  580. * The handler function becomes part of the XRegExp construction process, so be careful not to
  581. * construct XRegExps within the function or you will trigger infinite recursion.
  582. * @param {Object} [options] Options object with optional properties:
  583. * - `scope` {String} Scope where the token applies: 'default', 'class', or 'all'.
  584. * - `flag` {String} Single-character flag that triggers the token. This also registers the
  585. * flag, which prevents XRegExp from throwing an 'unknown flag' error when the flag is used.
  586. * - `optionalFlags` {String} Any custom flags checked for within the token `handler` that are
  587. * not required to trigger the token. This registers the flags, to prevent XRegExp from
  588. * throwing an 'unknown flag' error when any of the flags are used.
  589. * - `reparse` {Boolean} Whether the `handler` function's output should not be treated as
  590. * final, and instead be reparseable by other tokens (including the current token). Allows
  591. * token chaining or deferring.
  592. * - `leadChar` {String} Single character that occurs at the beginning of any successful match
  593. * of the token (not always applicable). This doesn't change the behavior of the token unless
  594. * you provide an erroneous value. However, providing it can increase the token's performance
  595. * since the token can be skipped at any positions where this character doesn't appear.
  596. * @example
  597. *
  598. * // Basic usage: Add \a for the ALERT control code
  599. * XRegExp.addToken(
  600. * /\\a/,
  601. * () => '\\x07',
  602. * {scope: 'all'}
  603. * );
  604. * XRegExp('\\a[\\a-\\n]+').test('\x07\n\x07'); // -> true
  605. *
  606. * // Add the U (ungreedy) flag from PCRE and RE2, which reverses greedy and lazy quantifiers.
  607. * // Since `scope` is not specified, it uses 'default' (i.e., transformations apply outside of
  608. * // character classes only)
  609. * XRegExp.addToken(
  610. * /([?*+]|{\d+(?:,\d*)?})(\??)/,
  611. * (match) => `${match[1]}${match[2] ? '' : '?'}`,
  612. * {flag: 'U'}
  613. * );
  614. * XRegExp('a+', 'U').exec('aaa')[0]; // -> 'a'
  615. * XRegExp('a+?', 'U').exec('aaa')[0]; // -> 'aaa'
  616. */
  617. XRegExp.addToken = function (regex, handler, options) {
  618. options = options || {};
  619. var optionalFlags = options.optionalFlags;
  620. var i = void 0;
  621. if (options.flag) {
  622. registerFlag(options.flag);
  623. }
  624. if (optionalFlags) {
  625. optionalFlags = nativ.split.call(optionalFlags, '');
  626. for (i = 0; i < optionalFlags.length; ++i) {
  627. registerFlag(optionalFlags[i]);
  628. }
  629. }
  630. // Add to the private list of syntax tokens
  631. tokens.push({
  632. regex: copyRegex(regex, {
  633. addG: true,
  634. addY: hasNativeY,
  635. isInternalOnly: true
  636. }),
  637. handler: handler,
  638. scope: options.scope || defaultScope,
  639. flag: options.flag,
  640. reparse: options.reparse,
  641. leadChar: options.leadChar
  642. });
  643. // Reset the pattern cache used by the `XRegExp` constructor, since the same pattern and flags
  644. // might now produce different results
  645. XRegExp.cache.flush('patterns');
  646. };
  647. /**
  648. * Caches and returns the result of calling `XRegExp(pattern, flags)`. On any subsequent call with
  649. * the same pattern and flag combination, the cached copy of the regex is returned.
  650. *
  651. * @memberOf XRegExp
  652. * @param {String} pattern Regex pattern string.
  653. * @param {String} [flags] Any combination of XRegExp flags.
  654. * @returns {RegExp} Cached XRegExp object.
  655. * @example
  656. *
  657. * while (match = XRegExp.cache('.', 'gs').exec(str)) {
  658. * // The regex is compiled once only
  659. * }
  660. */
  661. XRegExp.cache = function (pattern, flags) {
  662. if (!regexCache[pattern]) {
  663. regexCache[pattern] = {};
  664. }
  665. return regexCache[pattern][flags] || (regexCache[pattern][flags] = XRegExp(pattern, flags));
  666. };
  667. // Intentionally undocumented; used in tests
  668. XRegExp.cache.flush = function (cacheName) {
  669. if (cacheName === 'patterns') {
  670. // Flush the pattern cache used by the `XRegExp` constructor
  671. patternCache = {};
  672. } else {
  673. // Flush the regex cache populated by `XRegExp.cache`
  674. regexCache = {};
  675. }
  676. };
  677. /**
  678. * Escapes any regular expression metacharacters, for use when matching literal strings. The result
  679. * can safely be used at any point within a regex that uses any flags.
  680. *
  681. * @memberOf XRegExp
  682. * @param {String} str String to escape.
  683. * @returns {String} String with regex metacharacters escaped.
  684. * @example
  685. *
  686. * XRegExp.escape('Escaped? <.>');
  687. * // -> 'Escaped\?\ <\.>'
  688. */
  689. XRegExp.escape = function (str) {
  690. return nativ.replace.call(toObject(str), /[-\[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
  691. };
  692. /**
  693. * Executes a regex search in a specified string. Returns a match array or `null`. If the provided
  694. * regex uses named capture, named backreference properties are included on the match array.
  695. * Optional `pos` and `sticky` arguments specify the search start position, and whether the match
  696. * must start at the specified position only. The `lastIndex` property of the provided regex is not
  697. * used, but is updated for compatibility. Also fixes browser bugs compared to the native
  698. * `RegExp.prototype.exec` and can be used reliably cross-browser.
  699. *
  700. * @memberOf XRegExp
  701. * @param {String} str String to search.
  702. * @param {RegExp} regex Regex to search with.
  703. * @param {Number} [pos=0] Zero-based index at which to start the search.
  704. * @param {Boolean|String} [sticky=false] Whether the match must start at the specified position
  705. * only. The string `'sticky'` is accepted as an alternative to `true`.
  706. * @returns {Array} Match array with named backreference properties, or `null`.
  707. * @example
  708. *
  709. * // Basic use, with named backreference
  710. * let match = XRegExp.exec('U+2620', XRegExp('U\\+(?<hex>[0-9A-F]{4})'));
  711. * match.hex; // -> '2620'
  712. *
  713. * // With pos and sticky, in a loop
  714. * let pos = 2, result = [], match;
  715. * while (match = XRegExp.exec('<1><2><3><4>5<6>', /<(\d)>/, pos, 'sticky')) {
  716. * result.push(match[1]);
  717. * pos = match.index + match[0].length;
  718. * }
  719. * // result -> ['2', '3', '4']
  720. */
  721. XRegExp.exec = function (str, regex, pos, sticky) {
  722. var cacheKey = 'g';
  723. var addY = false;
  724. var fakeY = false;
  725. var match = void 0;
  726. addY = hasNativeY && !!(sticky || regex.sticky && sticky !== false);
  727. if (addY) {
  728. cacheKey += 'y';
  729. } else if (sticky) {
  730. // Simulate sticky matching by appending an empty capture to the original regex. The
  731. // resulting regex will succeed no matter what at the current index (set with `lastIndex`),
  732. // and will not search the rest of the subject string. We'll know that the original regex
  733. // has failed if that last capture is `''` rather than `undefined` (i.e., if that last
  734. // capture participated in the match).
  735. fakeY = true;
  736. cacheKey += 'FakeY';
  737. }
  738. regex[REGEX_DATA] = regex[REGEX_DATA] || {};
  739. // Shares cached copies with `XRegExp.match`/`replace`
  740. var r2 = regex[REGEX_DATA][cacheKey] || (regex[REGEX_DATA][cacheKey] = copyRegex(regex, {
  741. addG: true,
  742. addY: addY,
  743. source: fakeY ? regex.source + '|()' : undefined,
  744. removeY: sticky === false,
  745. isInternalOnly: true
  746. }));
  747. pos = pos || 0;
  748. r2.lastIndex = pos;
  749. // Fixed `exec` required for `lastIndex` fix, named backreferences, etc.
  750. match = fixed.exec.call(r2, str);
  751. // Get rid of the capture added by the pseudo-sticky matcher if needed. An empty string means
  752. // the original regexp failed (see above).
  753. if (fakeY && match && match.pop() === '') {
  754. match = null;
  755. }
  756. if (regex.global) {
  757. regex.lastIndex = match ? r2.lastIndex : 0;
  758. }
  759. return match;
  760. };
  761. /**
  762. * Executes a provided function once per regex match. Searches always start at the beginning of the
  763. * string and continue until the end, regardless of the state of the regex's `global` property and
  764. * initial `lastIndex`.
  765. *
  766. * @memberOf XRegExp
  767. * @param {String} str String to search.
  768. * @param {RegExp} regex Regex to search with.
  769. * @param {Function} callback Function to execute for each match. Invoked with four arguments:
  770. * - The match array, with named backreference properties.
  771. * - The zero-based match index.
  772. * - The string being traversed.
  773. * - The regex object being used to traverse the string.
  774. * @example
  775. *
  776. * // Extracts every other digit from a string
  777. * const evens = [];
  778. * XRegExp.forEach('1a2345', /\d/, (match, i) => {
  779. * if (i % 2) evens.push(+match[0]);
  780. * });
  781. * // evens -> [2, 4]
  782. */
  783. XRegExp.forEach = function (str, regex, callback) {
  784. var pos = 0;
  785. var i = -1;
  786. var match = void 0;
  787. while (match = XRegExp.exec(str, regex, pos)) {
  788. // Because `regex` is provided to `callback`, the function could use the deprecated/
  789. // nonstandard `RegExp.prototype.compile` to mutate the regex. However, since `XRegExp.exec`
  790. // doesn't use `lastIndex` to set the search position, this can't lead to an infinite loop,
  791. // at least. Actually, because of the way `XRegExp.exec` caches globalized versions of
  792. // regexes, mutating the regex will not have any effect on the iteration or matched strings,
  793. // which is a nice side effect that brings extra safety.
  794. callback(match, ++i, str, regex);
  795. pos = match.index + (match[0].length || 1);
  796. }
  797. };
  798. /**
  799. * Copies a regex object and adds flag `g`. The copy maintains extended data, is augmented with
  800. * `XRegExp.prototype` properties, and has a fresh `lastIndex` property (set to zero). Native
  801. * regexes are not recompiled using XRegExp syntax.
  802. *
  803. * @memberOf XRegExp
  804. * @param {RegExp} regex Regex to globalize.
  805. * @returns {RegExp} Copy of the provided regex with flag `g` added.
  806. * @example
  807. *
  808. * const globalCopy = XRegExp.globalize(/regex/);
  809. * globalCopy.global; // -> true
  810. */
  811. XRegExp.globalize = function (regex) {
  812. return copyRegex(regex, { addG: true });
  813. };
  814. /**
  815. * Installs optional features according to the specified options. Can be undone using
  816. * `XRegExp.uninstall`.
  817. *
  818. * @memberOf XRegExp
  819. * @param {Object|String} options Options object or string.
  820. * @example
  821. *
  822. * // With an options object
  823. * XRegExp.install({
  824. * // Enables support for astral code points in Unicode addons (implicitly sets flag A)
  825. * astral: true
  826. * });
  827. *
  828. * // With an options string
  829. * XRegExp.install('astral');
  830. */
  831. XRegExp.install = function (options) {
  832. options = prepareOptions(options);
  833. if (!features.astral && options.astral) {
  834. setAstral(true);
  835. }
  836. };
  837. /**
  838. * Checks whether an individual optional feature is installed.
  839. *
  840. * @memberOf XRegExp
  841. * @param {String} feature Name of the feature to check. One of:
  842. * - `astral`
  843. * @returns {Boolean} Whether the feature is installed.
  844. * @example
  845. *
  846. * XRegExp.isInstalled('astral');
  847. */
  848. XRegExp.isInstalled = function (feature) {
  849. return !!features[feature];
  850. };
  851. /**
  852. * Returns `true` if an object is a regex; `false` if it isn't. This works correctly for regexes
  853. * created in another frame, when `instanceof` and `constructor` checks would fail.
  854. *
  855. * @memberOf XRegExp
  856. * @param {*} value Object to check.
  857. * @returns {Boolean} Whether the object is a `RegExp` object.
  858. * @example
  859. *
  860. * XRegExp.isRegExp('string'); // -> false
  861. * XRegExp.isRegExp(/regex/i); // -> true
  862. * XRegExp.isRegExp(RegExp('^', 'm')); // -> true
  863. * XRegExp.isRegExp(XRegExp('(?s).')); // -> true
  864. */
  865. XRegExp.isRegExp = function (value) {
  866. return toString.call(value) === '[object RegExp]';
  867. }; // isType(value, 'RegExp');
  868. /**
  869. * Returns the first matched string, or in global mode, an array containing all matched strings.
  870. * This is essentially a more convenient re-implementation of `String.prototype.match` that gives
  871. * the result types you actually want (string instead of `exec`-style array in match-first mode,
  872. * and an empty array instead of `null` when no matches are found in match-all mode). It also lets
  873. * you override flag g and ignore `lastIndex`, and fixes browser bugs.
  874. *
  875. * @memberOf XRegExp
  876. * @param {String} str String to search.
  877. * @param {RegExp} regex Regex to search with.
  878. * @param {String} [scope='one'] Use 'one' to return the first match as a string. Use 'all' to
  879. * return an array of all matched strings. If not explicitly specified and `regex` uses flag g,
  880. * `scope` is 'all'.
  881. * @returns {String|Array} In match-first mode: First match as a string, or `null`. In match-all
  882. * mode: Array of all matched strings, or an empty array.
  883. * @example
  884. *
  885. * // Match first
  886. * XRegExp.match('abc', /\w/); // -> 'a'
  887. * XRegExp.match('abc', /\w/g, 'one'); // -> 'a'
  888. * XRegExp.match('abc', /x/g, 'one'); // -> null
  889. *
  890. * // Match all
  891. * XRegExp.match('abc', /\w/g); // -> ['a', 'b', 'c']
  892. * XRegExp.match('abc', /\w/, 'all'); // -> ['a', 'b', 'c']
  893. * XRegExp.match('abc', /x/, 'all'); // -> []
  894. */
  895. XRegExp.match = function (str, regex, scope) {
  896. var global = regex.global && scope !== 'one' || scope === 'all';
  897. var cacheKey = (global ? 'g' : '') + (regex.sticky ? 'y' : '') || 'noGY';
  898. regex[REGEX_DATA] = regex[REGEX_DATA] || {};
  899. // Shares cached copies with `XRegExp.exec`/`replace`
  900. var r2 = regex[REGEX_DATA][cacheKey] || (regex[REGEX_DATA][cacheKey] = copyRegex(regex, {
  901. addG: !!global,
  902. removeG: scope === 'one',
  903. isInternalOnly: true
  904. }));
  905. var result = nativ.match.call(toObject(str), r2);
  906. if (regex.global) {
  907. regex.lastIndex = scope === 'one' && result ?
  908. // Can't use `r2.lastIndex` since `r2` is nonglobal in this case
  909. result.index + result[0].length : 0;
  910. }
  911. return global ? result || [] : result && result[0];
  912. };
  913. /**
  914. * Retrieves the matches from searching a string using a chain of regexes that successively search
  915. * within previous matches. The provided `chain` array can contain regexes and or objects with
  916. * `regex` and `backref` properties. When a backreference is specified, the named or numbered
  917. * backreference is passed forward to the next regex or returned.
  918. *
  919. * @memberOf XRegExp
  920. * @param {String} str String to search.
  921. * @param {Array} chain Regexes that each search for matches within preceding results.
  922. * @returns {Array} Matches by the last regex in the chain, or an empty array.
  923. * @example
  924. *
  925. * // Basic usage; matches numbers within <b> tags
  926. * XRegExp.matchChain('1 <b>2</b> 3 <b>4 a 56</b>', [
  927. * XRegExp('(?is)<b>.*?</b>'),
  928. * /\d+/
  929. * ]);
  930. * // -> ['2', '4', '56']
  931. *
  932. * // Passing forward and returning specific backreferences
  933. * html = '<a href="http://xregexp.com/api/">XRegExp</a>\
  934. * <a href="http://www.google.com/">Google</a>';
  935. * XRegExp.matchChain(html, [
  936. * {regex: /<a href="([^"]+)">/i, backref: 1},
  937. * {regex: XRegExp('(?i)^https?://(?<domain>[^/?#]+)'), backref: 'domain'}
  938. * ]);
  939. * // -> ['xregexp.com', 'www.google.com']
  940. */
  941. XRegExp.matchChain = function (str, chain) {
  942. return function recurseChain(values, level) {
  943. var item = chain[level].regex ? chain[level] : { regex: chain[level] };
  944. var matches = [];
  945. function addMatch(match) {
  946. if (item.backref) {
  947. // Safari 4.0.5 (but not 5.0.5+) inappropriately uses sparse arrays to hold the
  948. // `undefined`s for backreferences to nonparticipating capturing groups. In such
  949. // cases, a `hasOwnProperty` or `in` check on its own would inappropriately throw
  950. // the exception, so also check if the backreference is a number that is within the
  951. // bounds of the array.
  952. if (!(match.hasOwnProperty(item.backref) || +item.backref < match.length)) {
  953. throw new ReferenceError('Backreference to undefined group: ' + item.backref);
  954. }
  955. matches.push(match[item.backref] || '');
  956. } else {
  957. matches.push(match[0]);
  958. }
  959. }
  960. for (var i = 0; i < values.length; ++i) {
  961. XRegExp.forEach(values[i], item.regex, addMatch);
  962. }
  963. return level === chain.length - 1 || !matches.length ? matches : recurseChain(matches, level + 1);
  964. }([str], 0);
  965. };
  966. /**
  967. * Returns a new string with one or all matches of a pattern replaced. The pattern can be a string
  968. * or regex, and the replacement can be a string or a function to be called for each match. To
  969. * perform a global search and replace, use the optional `scope` argument or include flag g if using
  970. * a regex. Replacement strings can use `${n}` or `$<n>` for named and numbered backreferences.
  971. * Replacement functions can use named backreferences via `arguments[0].name`. Also fixes browser
  972. * bugs compared to the native `String.prototype.replace` and can be used reliably cross-browser.
  973. *
  974. * @memberOf XRegExp
  975. * @param {String} str String to search.
  976. * @param {RegExp|String} search Search pattern to be replaced.
  977. * @param {String|Function} replacement Replacement string or a function invoked to create it.
  978. * Replacement strings can include special replacement syntax:
  979. * - $$ - Inserts a literal $ character.
  980. * - $&, $0 - Inserts the matched substring.
  981. * - $` - Inserts the string that precedes the matched substring (left context).
  982. * - $' - Inserts the string that follows the matched substring (right context).
  983. * - $n, $nn - Where n/nn are digits referencing an existent capturing group, inserts
  984. * backreference n/nn.
  985. * - ${n}, $<n> - Where n is a name or any number of digits that reference an existent capturing
  986. * group, inserts backreference n.
  987. * Replacement functions are invoked with three or more arguments:
  988. * - The matched substring (corresponds to $& above). Named backreferences are accessible as
  989. * properties of this first argument.
  990. * - 0..n arguments, one for each backreference (corresponding to $1, $2, etc. above).
  991. * - The zero-based index of the match within the total search string.
  992. * - The total string being searched.
  993. * @param {String} [scope='one'] Use 'one' to replace the first match only, or 'all'. If not
  994. * explicitly specified and using a regex with flag g, `scope` is 'all'.
  995. * @returns {String} New string with one or all matches replaced.
  996. * @example
  997. *
  998. * // Regex search, using named backreferences in replacement string
  999. * const name = XRegExp('(?<first>\\w+) (?<last>\\w+)');
  1000. * XRegExp.replace('John Smith', name, '$<last>, $<first>');
  1001. * // -> 'Smith, John'
  1002. *
  1003. * // Regex search, using named backreferences in replacement function
  1004. * XRegExp.replace('John Smith', name, (match) => `${match.last}, ${match.first}`);
  1005. * // -> 'Smith, John'
  1006. *
  1007. * // String search, with replace-all
  1008. * XRegExp.replace('RegExp builds RegExps', 'RegExp', 'XRegExp', 'all');
  1009. * // -> 'XRegExp builds XRegExps'
  1010. */
  1011. XRegExp.replace = function (str, search, replacement, scope) {
  1012. var isRegex = XRegExp.isRegExp(search);
  1013. var global = search.global && scope !== 'one' || scope === 'all';
  1014. var cacheKey = (global ? 'g' : '') + (search.sticky ? 'y' : '') || 'noGY';
  1015. var s2 = search;
  1016. if (isRegex) {
  1017. search[REGEX_DATA] = search[REGEX_DATA] || {};
  1018. // Shares cached copies with `XRegExp.exec`/`match`. Since a copy is used, `search`'s
  1019. // `lastIndex` isn't updated *during* replacement iterations
  1020. s2 = search[REGEX_DATA][cacheKey] || (search[REGEX_DATA][cacheKey] = copyRegex(search, {
  1021. addG: !!global,
  1022. removeG: scope === 'one',
  1023. isInternalOnly: true
  1024. }));
  1025. } else if (global) {
  1026. s2 = new RegExp(XRegExp.escape(String(search)), 'g');
  1027. }
  1028. // Fixed `replace` required for named backreferences, etc.
  1029. var result = fixed.replace.call(toObject(str), s2, replacement);
  1030. if (isRegex && search.global) {
  1031. // Fixes IE, Safari bug (last tested IE 9, Safari 5.1)
  1032. search.lastIndex = 0;
  1033. }
  1034. return result;
  1035. };
  1036. /**
  1037. * Performs batch processing of string replacements. Used like `XRegExp.replace`, but accepts an
  1038. * array of replacement details. Later replacements operate on the output of earlier replacements.
  1039. * Replacement details are accepted as an array with a regex or string to search for, the
  1040. * replacement string or function, and an optional scope of 'one' or 'all'. Uses the XRegExp
  1041. * replacement text syntax, which supports named backreference properties via `${name}` or
  1042. * `$<name>`.
  1043. *
  1044. * @memberOf XRegExp
  1045. * @param {String} str String to search.
  1046. * @param {Array} replacements Array of replacement detail arrays.
  1047. * @returns {String} New string with all replacements.
  1048. * @example
  1049. *
  1050. * str = XRegExp.replaceEach(str, [
  1051. * [XRegExp('(?<name>a)'), 'z${name}'],
  1052. * [/b/gi, 'y'],
  1053. * [/c/g, 'x', 'one'], // scope 'one' overrides /g
  1054. * [/d/, 'w', 'all'], // scope 'all' overrides lack of /g
  1055. * ['e', 'v', 'all'], // scope 'all' allows replace-all for strings
  1056. * [/f/g, ($0) => $0.toUpperCase()]
  1057. * ]);
  1058. */
  1059. XRegExp.replaceEach = function (str, replacements) {
  1060. var i = void 0;
  1061. var r = void 0;
  1062. for (i = 0; i < replacements.length; ++i) {
  1063. r = replacements[i];
  1064. str = XRegExp.replace(str, r[0], r[1], r[2]);
  1065. }
  1066. return str;
  1067. };
  1068. /**
  1069. * Splits a string into an array of strings using a regex or string separator. Matches of the
  1070. * separator are not included in the result array. However, if `separator` is a regex that contains
  1071. * capturing groups, backreferences are spliced into the result each time `separator` is matched.
  1072. * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably
  1073. * cross-browser.
  1074. *
  1075. * @memberOf XRegExp
  1076. * @param {String} str String to split.
  1077. * @param {RegExp|String} separator Regex or string to use for separating the string.
  1078. * @param {Number} [limit] Maximum number of items to include in the result array.
  1079. * @returns {Array} Array of substrings.
  1080. * @example
  1081. *
  1082. * // Basic use
  1083. * XRegExp.split('a b c', ' ');
  1084. * // -> ['a', 'b', 'c']
  1085. *
  1086. * // With limit
  1087. * XRegExp.split('a b c', ' ', 2);
  1088. * // -> ['a', 'b']
  1089. *
  1090. * // Backreferences in result array
  1091. * XRegExp.split('..word1..', /([a-z]+)(\d+)/i);
  1092. * // -> ['..', 'word', '1', '..']
  1093. */
  1094. XRegExp.split = function (str, separator, limit) {
  1095. return fixed.split.call(toObject(str), separator, limit);
  1096. };
  1097. /**
  1098. * Executes a regex search in a specified string. Returns `true` or `false`. Optional `pos` and
  1099. * `sticky` arguments specify the search start position, and whether the match must start at the
  1100. * specified position only. The `lastIndex` property of the provided regex is not used, but is
  1101. * updated for compatibility. Also fixes browser bugs compared to the native
  1102. * `RegExp.prototype.test` and can be used reliably cross-browser.
  1103. *
  1104. * @memberOf XRegExp
  1105. * @param {String} str String to search.
  1106. * @param {RegExp} regex Regex to search with.
  1107. * @param {Number} [pos=0] Zero-based index at which to start the search.
  1108. * @param {Boolean|String} [sticky=false] Whether the match must start at the specified position
  1109. * only. The string `'sticky'` is accepted as an alternative to `true`.
  1110. * @returns {Boolean} Whether the regex matched the provided value.
  1111. * @example
  1112. *
  1113. * // Basic use
  1114. * XRegExp.test('abc', /c/); // -> true
  1115. *
  1116. * // With pos and sticky
  1117. * XRegExp.test('abc', /c/, 0, 'sticky'); // -> false
  1118. * XRegExp.test('abc', /c/, 2, 'sticky'); // -> true
  1119. */
  1120. // Do this the easy way :-)
  1121. XRegExp.test = function (str, regex, pos, sticky) {
  1122. return !!XRegExp.exec(str, regex, pos, sticky);
  1123. };
  1124. /**
  1125. * Uninstalls optional features according to the specified options. All optional features start out
  1126. * uninstalled, so this is used to undo the actions of `XRegExp.install`.
  1127. *
  1128. * @memberOf XRegExp
  1129. * @param {Object|String} options Options object or string.
  1130. * @example
  1131. *
  1132. * // With an options object
  1133. * XRegExp.uninstall({
  1134. * // Disables support for astral code points in Unicode addons
  1135. * astral: true
  1136. * });
  1137. *
  1138. * // With an options string
  1139. * XRegExp.uninstall('astral');
  1140. */
  1141. XRegExp.uninstall = function (options) {
  1142. options = prepareOptions(options);
  1143. if (features.astral && options.astral) {
  1144. setAstral(false);
  1145. }
  1146. };
  1147. /**
  1148. * Returns an XRegExp object that is the union of the given patterns. Patterns can be provided as
  1149. * regex objects or strings. Metacharacters are escaped in patterns provided as strings.
  1150. * Backreferences in provided regex objects are automatically renumbered to work correctly within
  1151. * the larger combined pattern. Native flags used by provided regexes are ignored in favor of the
  1152. * `flags` argument.
  1153. *
  1154. * @memberOf XRegExp
  1155. * @param {Array} patterns Regexes and strings to combine.
  1156. * @param {String} [flags] Any combination of XRegExp flags.
  1157. * @param {Object} [options] Options object with optional properties:
  1158. * - `conjunction` {String} Type of conjunction to use: 'or' (default) or 'none'.
  1159. * @returns {RegExp} Union of the provided regexes and strings.
  1160. * @example
  1161. *
  1162. * XRegExp.union(['a+b*c', /(dogs)\1/, /(cats)\1/], 'i');
  1163. * // -> /a\+b\*c|(dogs)\1|(cats)\2/i
  1164. *
  1165. * XRegExp.union([/man/, /bear/, /pig/], 'i', {conjunction: 'none'});
  1166. * // -> /manbearpig/i
  1167. */
  1168. XRegExp.union = function (patterns, flags, options) {
  1169. options = options || {};
  1170. var conjunction = options.conjunction || 'or';
  1171. var numCaptures = 0;
  1172. var numPriorCaptures = void 0;
  1173. var captureNames = void 0;
  1174. function rewrite(match, paren, backref) {
  1175. var name = captureNames[numCaptures - numPriorCaptures];
  1176. // Capturing group
  1177. if (paren) {
  1178. ++numCaptures;
  1179. // If the current capture has a name, preserve the name
  1180. if (name) {
  1181. return '(?<' + name + '>';
  1182. }
  1183. // Backreference
  1184. } else if (backref) {
  1185. // Rewrite the backreference
  1186. return '\\' + (+backref + numPriorCaptures);
  1187. }
  1188. return match;
  1189. }
  1190. if (!(isType(patterns, 'Array') && patterns.length)) {
  1191. throw new TypeError('Must provide a nonempty array of patterns to merge');
  1192. }
  1193. var parts = /(\()(?!\?)|\\([1-9]\d*)|\\[\s\S]|\[(?:[^\\\]]|\\[\s\S])*\]/g;
  1194. var output = [];
  1195. var pattern = void 0;
  1196. for (var i = 0; i < patterns.length; ++i) {
  1197. pattern = patterns[i];
  1198. if (XRegExp.isRegExp(pattern)) {
  1199. numPriorCaptures = numCaptures;
  1200. captureNames = pattern[REGEX_DATA] && pattern[REGEX_DATA].captureNames || [];
  1201. // Rewrite backreferences. Passing to XRegExp dies on octals and ensures patterns are
  1202. // independently valid; helps keep this simple. Named captures are put back
  1203. output.push(nativ.replace.call(XRegExp(pattern.source).source, parts, rewrite));
  1204. } else {
  1205. output.push(XRegExp.escape(pattern));
  1206. }
  1207. }
  1208. var separator = conjunction === 'none' ? '' : '|';
  1209. return XRegExp(output.join(separator), flags);
  1210. };
  1211. // ==--------------------------==
  1212. // Fixed/extended native methods
  1213. // ==--------------------------==
  1214. /**
  1215. * Adds named capture support (with backreferences returned as `result.name`), and fixes browser
  1216. * bugs in the native `RegExp.prototype.exec`. Use via `XRegExp.exec`.
  1217. *
  1218. * @memberOf RegExp
  1219. * @param {String} str String to search.
  1220. * @returns {Array} Match array with named backreference properties, or `null`.
  1221. */
  1222. fixed.exec = function (str) {
  1223. var origLastIndex = this.lastIndex;
  1224. var match = nativ.exec.apply(this, arguments);
  1225. if (match) {
  1226. // Fix browsers whose `exec` methods don't return `undefined` for nonparticipating capturing
  1227. // groups. This fixes IE 5.5-8, but not IE 9's quirks mode or emulation of older IEs. IE 9
  1228. // in standards mode follows the spec.
  1229. if (!correctExecNpcg && match.length > 1 && match.indexOf('') !== -1) {
  1230. var r2 = copyRegex(this, {
  1231. removeG: true,
  1232. isInternalOnly: true
  1233. });
  1234. // Using `str.slice(match.index)` rather than `match[0]` in case lookahead allowed
  1235. // matching due to characters outside the match
  1236. nativ.replace.call(String(str).slice(match.index), r2, function () {
  1237. for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
  1238. args[_key] = arguments[_key];
  1239. }
  1240. var len = args.length;
  1241. // Skip index 0 and the last 2
  1242. for (var i = 1; i < len - 2; ++i) {
  1243. if (args[i] === undefined) {
  1244. match[i] = undefined;
  1245. }
  1246. }
  1247. });
  1248. }
  1249. // Attach named capture properties
  1250. if (this[REGEX_DATA] && this[REGEX_DATA].captureNames) {
  1251. // Skip index 0
  1252. for (var i = 1; i < match.length; ++i) {
  1253. var name = this[REGEX_DATA].captureNames[i - 1];
  1254. if (name) {
  1255. match[name] = match[i];
  1256. }
  1257. }
  1258. }
  1259. // Fix browsers that increment `lastIndex` after zero-length matches
  1260. if (this.global && !match[0].length && this.lastIndex > match.index) {
  1261. this.lastIndex = match.index;
  1262. }
  1263. }
  1264. if (!this.global) {
  1265. // Fixes IE, Opera bug (last tested IE 9, Opera 11.6)
  1266. this.lastIndex = origLastIndex;
  1267. }
  1268. return match;
  1269. };
  1270. /**
  1271. * Fixes browser bugs in the native `RegExp.prototype.test`.
  1272. *
  1273. * @memberOf RegExp
  1274. * @param {String} str String to search.
  1275. * @returns {Boolean} Whether the regex matched the provided value.
  1276. */
  1277. fixed.test = function (str) {
  1278. // Do this the easy way :-)
  1279. return !!fixed.exec.call(this, str);
  1280. };
  1281. /**
  1282. * Adds named capture support (with backreferences returned as `result.name`), and fixes browser
  1283. * bugs in the native `String.prototype.match`.
  1284. *
  1285. * @memberOf String
  1286. * @param {RegExp|*} regex Regex to search with. If not a regex object, it is passed to `RegExp`.
  1287. * @returns {Array} If `regex` uses flag g, an array of match strings or `null`. Without flag g,
  1288. * the result of calling `regex.exec(this)`.
  1289. */
  1290. fixed.match = function (regex) {
  1291. if (!XRegExp.isRegExp(regex)) {
  1292. // Use the native `RegExp` rather than `XRegExp`
  1293. regex = new RegExp(regex);
  1294. } else if (regex.global) {
  1295. var result = nativ.match.apply(this, arguments);
  1296. // Fixes IE bug
  1297. regex.lastIndex = 0;
  1298. return result;
  1299. }
  1300. return fixed.exec.call(regex, toObject(this));
  1301. };
  1302. /**
  1303. * Adds support for `${n}` (or `$<n>`) tokens for named and numbered backreferences in replacement
  1304. * text, and provides named backreferences to replacement functions as `arguments[0].name`. Also
  1305. * fixes browser bugs in replacement text syntax when performing a replacement using a nonregex
  1306. * search value, and the value of a replacement regex's `lastIndex` property during replacement
  1307. * iterations and upon completion. Note that this doesn't support SpiderMonkey's proprietary third
  1308. * (`flags`) argument. Use via `XRegExp.replace`.
  1309. *
  1310. * @memberOf String
  1311. * @param {RegExp|String} search Search pattern to be replaced.
  1312. * @param {String|Function} replacement Replacement string or a function invoked to create it.
  1313. * @returns {String} New string with one or all matches replaced.
  1314. */
  1315. fixed.replace = function (search, replacement) {
  1316. var isRegex = XRegExp.isRegExp(search);
  1317. var origLastIndex = void 0;
  1318. var captureNames = void 0;
  1319. var result = void 0;
  1320. if (isRegex) {
  1321. if (search[REGEX_DATA]) {
  1322. captureNames = search[REGEX_DATA].captureNames;
  1323. }
  1324. // Only needed if `search` is nonglobal
  1325. origLastIndex = search.lastIndex;
  1326. } else {
  1327. search += ''; // Type-convert
  1328. }
  1329. // Don't use `typeof`; some older browsers return 'function' for regex objects
  1330. if (isType(replacement, 'Function')) {
  1331. // Stringifying `this` fixes a bug in IE < 9 where the last argument in replacement
  1332. // functions isn't type-converted to a string
  1333. result = nativ.replace.call(String(this), search, function () {
  1334. for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  1335. args[_key2] = arguments[_key2];
  1336. }
  1337. if (captureNames) {
  1338. // Change the `args[0]` string primitive to a `String` object that can store
  1339. // properties. This really does need to use `String` as a constructor
  1340. args[0] = new String(args[0]);
  1341. // Store named backreferences on the first argument
  1342. for (var i = 0; i < captureNames.length; ++i) {
  1343. if (captureNames[i]) {
  1344. args[0][captureNames[i]] = args[i + 1];
  1345. }
  1346. }
  1347. }
  1348. // Update `lastIndex` before calling `replacement`. Fixes IE, Chrome, Firefox, Safari
  1349. // bug (last tested IE 9, Chrome 17, Firefox 11, Safari 5.1)
  1350. if (isRegex && search.global) {
  1351. search.lastIndex = args[args.length - 2] + args[0].length;
  1352. }
  1353. // ES6 specs the context for replacement functions as `undefined`
  1354. return replacement.apply(undefined, args);
  1355. });
  1356. } else {
  1357. // Ensure that the last value of `args` will be a string when given nonstring `this`,
  1358. // while still throwing on null or undefined context
  1359. result = nativ.replace.call(this == null ? this : String(this), search, function () {
  1360. for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
  1361. args[_key3] = arguments[_key3];
  1362. }
  1363. return nativ.replace.call(String(replacement), replacementToken, replacer);
  1364. function replacer($0, bracketed, angled, dollarToken) {
  1365. bracketed = bracketed || angled;
  1366. // Named or numbered backreference with curly or angled braces
  1367. if (bracketed) {
  1368. // XRegExp behavior for `${n}` or `$<n>`:
  1369. // 1. Backreference to numbered capture, if `n` is an integer. Use `0` for the
  1370. // entire match. Any number of leading zeros may be used.
  1371. // 2. Backreference to named capture `n`, if it exists and is not an integer
  1372. // overridden by numbered capture. In practice, this does not overlap with
  1373. // numbered capture since XRegExp does not allow named capture to use a bare
  1374. // integer as the name.
  1375. // 3. If the name or number does not refer to an existing capturing group, it's
  1376. // an error.
  1377. var n = +bracketed; // Type-convert; drop leading zeros
  1378. if (n <= args.length - 3) {
  1379. return args[n] || '';
  1380. }
  1381. // Groups with the same name is an error, else would need `lastIndexOf`
  1382. n = captureNames ? captureNames.indexOf(bracketed) : -1;
  1383. if (n < 0) {
  1384. throw new SyntaxError('Backreference to undefined group ' + $0);
  1385. }
  1386. return args[n + 1] || '';
  1387. }
  1388. // Else, special variable or numbered backreference without curly braces
  1389. if (dollarToken === '$') {
  1390. // $$
  1391. return '$';
  1392. }
  1393. if (dollarToken === '&' || +dollarToken === 0) {
  1394. // $&, $0 (not followed by 1-9), $00
  1395. return args[0];
  1396. }
  1397. if (dollarToken === '`') {
  1398. // $` (left context)
  1399. return args[args.length - 1].slice(0, args[args.length - 2]);
  1400. }
  1401. if (dollarToken === "'") {
  1402. // $' (right context)
  1403. return args[args.length - 1].slice(args[args.length - 2] + args[0].length);
  1404. }
  1405. // Else, numbered backreference without braces
  1406. dollarToken = +dollarToken; // Type-convert; drop leading zero
  1407. // XRegExp behavior for `$n` and `$nn`:
  1408. // - Backrefs end after 1 or 2 digits. Use `${..}` or `$<..>` for more digits.
  1409. // - `$1` is an error if no capturing groups.
  1410. // - `$10` is an error if less than 10 capturing groups. Use `${1}0` or `$<1>0`
  1411. // instead.
  1412. // - `$01` is `$1` if at least one capturing group, else it's an error.
  1413. // - `$0` (not followed by 1-9) and `$00` are the entire match.
  1414. // Native behavior, for comparison:
  1415. // - Backrefs end after 1 or 2 digits. Cannot reference capturing group 100+.
  1416. // - `$1` is a literal `$1` if no capturing groups.
  1417. // - `$10` is `$1` followed by a literal `0` if less than 10 capturing groups.
  1418. // - `$01` is `$1` if at least one capturing group, else it's a literal `$01`.
  1419. // - `$0` is a literal `$0`.
  1420. if (!isNaN(dollarToken)) {
  1421. if (dollarToken > args.length - 3) {
  1422. throw new SyntaxError('Backreference to undefined group ' + $0);
  1423. }
  1424. return args[dollarToken] || '';
  1425. }
  1426. // `$` followed by an unsupported char is an error, unlike native JS
  1427. throw new SyntaxError('Invalid token ' + $0);
  1428. }
  1429. });
  1430. }
  1431. if (isRegex) {
  1432. if (search.global) {
  1433. // Fixes IE, Safari bug (last tested IE 9, Safari 5.1)
  1434. search.lastIndex = 0;
  1435. } else {
  1436. // Fixes IE, Opera bug (last tested IE 9, Opera 11.6)
  1437. search.lastIndex = origLastIndex;
  1438. }
  1439. }
  1440. return result;
  1441. };
  1442. /**
  1443. * Fixes browser bugs in the native `String.prototype.split`. Use via `XRegExp.split`.
  1444. *
  1445. * @memberOf String
  1446. * @param {RegExp|String} separator Regex or string to use for separating the string.
  1447. * @param {Number} [limit] Maximum number of items to include in the result array.
  1448. * @returns {Array} Array of substrings.
  1449. */
  1450. fixed.split = function (separator, limit) {
  1451. if (!XRegExp.isRegExp(separator)) {
  1452. // Browsers handle nonregex split correctly, so use the faster native method
  1453. return nativ.split.apply(this, arguments);
  1454. }
  1455. var str = String(this);
  1456. var output = [];
  1457. var origLastIndex = separator.lastIndex;
  1458. var lastLastIndex = 0;
  1459. var lastLength = void 0;
  1460. // Values for `limit`, per the spec:
  1461. // If undefined: pow(2,32) - 1
  1462. // If 0, Infinity, or NaN: 0
  1463. // If positive number: limit = floor(limit); if (limit >= pow(2,32)) limit -= pow(2,32);
  1464. // If negative number: pow(2,32) - floor(abs(limit))
  1465. // If other: Type-convert, then use the above rules
  1466. // This line fails in very strange ways for some values of `limit` in Opera 10.5-10.63, unless
  1467. // Opera Dragonfly is open (go figure). It works in at least Opera 9.5-10.1 and 11+
  1468. limit = (limit === undefined ? -1 : limit) >>> 0;
  1469. XRegExp.forEach(str, separator, function (match) {
  1470. // This condition is not the same as `if (match[0].length)`
  1471. if (match.index + match[0].length > lastLastIndex) {
  1472. output.push(str.slice(lastLastIndex, match.index));
  1473. if (match.length > 1 && match.index < str.length) {
  1474. Array.prototype.push.apply(output, match.slice(1));
  1475. }
  1476. lastLength = match[0].length;
  1477. lastLastIndex = match.index + lastLength;
  1478. }
  1479. });
  1480. if (lastLastIndex === str.length) {
  1481. if (!nativ.test.call(separator, '') || lastLength) {
  1482. output.push('');
  1483. }
  1484. } else {
  1485. output.push(str.slice(lastLastIndex));
  1486. }
  1487. separator.lastIndex = origLastIndex;
  1488. return output.length > limit ? output.slice(0, limit) : output;
  1489. };
  1490. // ==--------------------------==
  1491. // Built-in syntax/flag tokens
  1492. // ==--------------------------==
  1493. /*
  1494. * Letter escapes that natively match literal characters: `\a`, `\A`, etc. These should be
  1495. * SyntaxErrors but are allowed in web reality. XRegExp makes them errors for cross-browser
  1496. * consistency and to reserve their syntax, but lets them be superseded by addons.
  1497. */
  1498. XRegExp.addToken(/\\([ABCE-RTUVXYZaeg-mopqyz]|c(?![A-Za-z])|u(?![\dA-Fa-f]{4}|{[\dA-Fa-f]+})|x(?![\dA-Fa-f]{2}))/, function (match, scope) {
  1499. // \B is allowed in default scope only
  1500. if (match[1] === 'B' && scope === defaultScope) {
  1501. return match[0];
  1502. }
  1503. throw new SyntaxError('Invalid escape ' + match[0]);
  1504. }, {
  1505. scope: 'all',
  1506. leadChar: '\\'
  1507. });
  1508. /*
  1509. * Unicode code point escape with curly braces: `\u{N..}`. `N..` is any one or more digit
  1510. * hexadecimal number from 0-10FFFF, and can include leading zeros. Requires the native ES6 `u` flag
  1511. * to support code points greater than U+FFFF. Avoids converting code points above U+FFFF to
  1512. * surrogate pairs (which could be done without flag `u`), since that could lead to broken behavior
  1513. * if you follow a `\u{N..}` token that references a code point above U+FFFF with a quantifier, or
  1514. * if you use the same in a character class.
  1515. */
  1516. XRegExp.addToken(/\\u{([\dA-Fa-f]+)}/, function (match, scope, flags) {
  1517. var code = dec(match[1]);
  1518. if (code > 0x10FFFF) {
  1519. throw new SyntaxError('Invalid Unicode code point ' + match[0]);
  1520. }
  1521. if (code <= 0xFFFF) {
  1522. // Converting to \uNNNN avoids needing to escape the literal character and keep it
  1523. // separate from preceding tokens
  1524. return '\\u' + pad4(hex(code));
  1525. }
  1526. // If `code` is between 0xFFFF and 0x10FFFF, require and defer to native handling
  1527. if (hasNativeU && flags.indexOf('u') !== -1) {
  1528. return match[0];
  1529. }
  1530. throw new SyntaxError('Cannot use Unicode code point above \\u{FFFF} without flag u');
  1531. }, {
  1532. scope: 'all',
  1533. leadChar: '\\'
  1534. });
  1535. /*
  1536. * Empty character class: `[]` or `[^]`. This fixes a critical cross-browser syntax inconsistency.
  1537. * Unless this is standardized (per the ES spec), regex syntax can't be accurately parsed because
  1538. * character class endings can't be determined.
  1539. */
  1540. XRegExp.addToken(/\[(\^?)\]/,
  1541. // For cross-browser compatibility with ES3, convert [] to \b\B and [^] to [\s\S].
  1542. // (?!) should work like \b\B, but is unreliable in some versions of Firefox
  1543. /* eslint-disable no-confusing-arrow */
  1544. function (match) {
  1545. return match[1] ? '[\\s\\S]' : '\\b\\B';
  1546. },
  1547. /* eslint-enable no-confusing-arrow */
  1548. { leadChar: '[' });
  1549. /*
  1550. * Comment pattern: `(?# )`. Inline comments are an alternative to the line comments allowed in
  1551. * free-spacing mode (flag x).
  1552. */
  1553. XRegExp.addToken(/\(\?#[^)]*\)/, getContextualTokenSeparator, { leadChar: '(' });
  1554. /*
  1555. * Whitespace and line comments, in free-spacing mode (aka extended mode, flag x) only.
  1556. */
  1557. XRegExp.addToken(/\s+|#[^\n]*\n?/, getContextualTokenSeparator, { flag: 'x' });
  1558. /*
  1559. * Dot, in dotall mode (aka singleline mode, flag s) only.
  1560. */
  1561. XRegExp.addToken(/\./, function () {
  1562. return '[\\s\\S]';
  1563. }, {
  1564. flag: 's',
  1565. leadChar: '.'
  1566. });
  1567. /*
  1568. * Named backreference: `\k<name>`. Backreference names can use the characters A-Z, a-z, 0-9, _,
  1569. * and $ only. Also allows numbered backreferences as `\k<n>`.
  1570. */
  1571. XRegExp.addToken(/\\k<([\w$]+)>/, function (match) {
  1572. // Groups with the same name is an error, else would need `lastIndexOf`
  1573. var index = isNaN(match[1]) ? this.captureNames.indexOf(match[1]) + 1 : +match[1];
  1574. var endIndex = match.index + match[0].length;
  1575. if (!index || index > this.captureNames.length) {
  1576. throw new SyntaxError('Backreference to undefined group ' + match[0]);
  1577. }
  1578. // Keep backreferences separate from subsequent literal numbers. This avoids e.g.
  1579. // inadvertedly changing `(?<n>)\k<n>1` to `()\11`.
  1580. return '\\' + index + (endIndex === match.input.length || isNaN(match.input[endIndex]) ? '' : '(?:)');
  1581. }, { leadChar: '\\' });
  1582. /*
  1583. * Numbered backreference or octal, plus any following digits: `\0`, `\11`, etc. Octals except `\0`
  1584. * not followed by 0-9 and backreferences to unopened capture groups throw an error. Other matches
  1585. * are returned unaltered. IE < 9 doesn't support backreferences above `\99` in regex syntax.
  1586. */
  1587. XRegExp.addToken(/\\(\d+)/, function (match, scope) {
  1588. if (!(scope === defaultScope && /^[1-9]/.test(match[1]) && +match[1] <= this.captureNames.length) && match[1] !== '0') {
  1589. throw new SyntaxError('Cannot use octal escape or backreference to undefined group ' + match[0]);
  1590. }
  1591. return match[0];
  1592. }, {
  1593. scope: 'all',
  1594. leadChar: '\\'
  1595. });
  1596. /*
  1597. * Named capturing group; match the opening delimiter only: `(?<name>`. Capture names can use the
  1598. * characters A-Z, a-z, 0-9, _, and $ only. Names can't be integers. Supports Python-style
  1599. * `(?P<name>` as an alternate syntax to avoid issues in some older versions of Opera which natively
  1600. * supported the Python-style syntax. Otherwise, XRegExp might treat numbered backreferences to
  1601. * Python-style named capture as octals.
  1602. */
  1603. XRegExp.addToken(/\(\?P?<([\w$]+)>/, function (match) {
  1604. // Disallow bare integers as names because named backreferences are added to match arrays
  1605. // and therefore numeric properties may lead to incorrect lookups
  1606. if (!isNaN(match[1])) {
  1607. throw new SyntaxError('Cannot use integer as capture name ' + match[0]);
  1608. }
  1609. if (match[1] === 'length' || match[1] === '__proto__') {
  1610. throw new SyntaxError('Cannot use reserved word as capture name ' + match[0]);
  1611. }
  1612. if (this.captureNames.indexOf(match[1]) !== -1) {
  1613. throw new SyntaxError('Cannot use same name for multiple groups ' + match[0]);
  1614. }
  1615. this.captureNames.push(match[1]);
  1616. this.hasNamedCapture = true;
  1617. return '(';
  1618. }, { leadChar: '(' });
  1619. /*
  1620. * Capturing group; match the opening parenthesis only. Required for support of named capturing
  1621. * groups. Also adds explicit capture mode (flag n).
  1622. */
  1623. XRegExp.addToken(/\((?!\?)/, function (match, scope, flags) {
  1624. if (flags.indexOf('n') !== -1) {
  1625. return '(?:';
  1626. }
  1627. this.captureNames.push(null);
  1628. return '(';
  1629. }, {
  1630. optionalFlags: 'n',
  1631. leadChar: '('
  1632. });
  1633. exports.default = XRegExp;
  1634. module.exports = exports['default'];