/*! * XRegExp.build 3.1.0 * * Steven Levithan (c) 2012-2016 MIT License * Inspired by Lea Verou's RegExp.create */ module.exports = function(XRegExp) { 'use strict'; var REGEX_DATA = 'xregexp', subParts = /(\()(?!\?)|\\([1-9]\d*)|\\[\s\S]|\[(?:[^\\\]]|\\[\s\S])*]/g, parts = XRegExp.union([/\({{([\w$]+)}}\)|{{([\w$]+)}}/, subParts], 'g'); /** * Strips a leading `^` and trailing unescaped `$`, if both are present. * * @private * @param {String} pattern Pattern to process. * @returns {String} Pattern with edge anchors removed. */ function deanchor(pattern) { // Allow any number of empty noncapturing groups before/after anchors, because regexes // built/generated by XRegExp sometimes include them var leadingAnchor = /^(?:\(\?:\))*\^/, trailingAnchor = /\$(?:\(\?:\))*$/; if ( leadingAnchor.test(pattern) && trailingAnchor.test(pattern) && // Ensure that the trailing `$` isn't escaped trailingAnchor.test(pattern.replace(/\\[\s\S]/g, '')) ) { return pattern.replace(leadingAnchor, '').replace(trailingAnchor, ''); } return pattern; } /** * Converts the provided value to an XRegExp. Native RegExp flags are not preserved. * * @private * @param {String|RegExp} value Value to convert. * @returns {RegExp} XRegExp object with XRegExp syntax applied. */ function asXRegExp(value) { return XRegExp.isRegExp(value) ? (value[REGEX_DATA] && value[REGEX_DATA].captureNames ? // Don't recompile, to preserve capture names value : // Recompile as XRegExp XRegExp(value.source) ) : // Compile string as XRegExp XRegExp(value); } /** * Builds regexes using named subpatterns, for readability and pattern reuse. Backreferences in the * outer pattern and provided subpatterns are automatically renumbered to work correctly. Native * flags used by provided subpatterns are ignored in favor of the `flags` argument. * * @memberOf XRegExp * @param {String} pattern XRegExp pattern using `{{name}}` for embedded subpatterns. Allows * `({{name}})` as shorthand for `(?{{name}})`. Patterns cannot be embedded within * character classes. * @param {Object} subs Lookup object for named subpatterns. Values can be strings or regexes. A * leading `^` and trailing unescaped `$` are stripped from subpatterns, if both are present. * @param {String} [flags] Any combination of XRegExp flags. * @returns {RegExp} Regex with interpolated subpatterns. * @example * * var time = XRegExp.build('(?x)^ {{hours}} ({{minutes}}) $', { * hours: XRegExp.build('{{h12}} : | {{h24}}', { * h12: /1[0-2]|0?[1-9]/, * h24: /2[0-3]|[01][0-9]/ * }, 'x'), * minutes: /^[0-5][0-9]$/ * }); * time.test('10:59'); // -> true * XRegExp.exec('10:59', time).minutes; // -> '59' */ XRegExp.build = function(pattern, subs, flags) { var inlineFlags = /^\(\?([\w$]+)\)/.exec(pattern), data = {}, numCaps = 0, // 'Caps' is short for captures numPriorCaps, numOuterCaps = 0, outerCapsMap = [0], outerCapNames, sub, p; // Add flags within a leading mode modifier to the overall pattern's flags if (inlineFlags) { flags = flags || ''; inlineFlags[1].replace(/./g, function(flag) { // Don't add duplicates flags += (flags.indexOf(flag) > -1 ? '' : flag); }); } for (p in subs) { if (subs.hasOwnProperty(p)) { // Passing to XRegExp enables extended syntax and ensures independent validity, // lest an unescaped `(`, `)`, `[`, or trailing `\` breaks the `(?:)` wrapper. For // subpatterns provided as native regexes, it dies on octals and adds the property // used to hold extended regex instance data, for simplicity sub = asXRegExp(subs[p]); data[p] = { // Deanchoring allows embedding independently useful anchored regexes. If you // really need to keep your anchors, double them (i.e., `^^...$$`) pattern: deanchor(sub.source), names: sub[REGEX_DATA].captureNames || [] }; } } // Passing to XRegExp dies on octals and ensures the outer pattern is independently valid; // helps keep this simple. Named captures will be put back pattern = asXRegExp(pattern); outerCapNames = pattern[REGEX_DATA].captureNames || []; pattern = pattern.source.replace(parts, function($0, $1, $2, $3, $4) { var subName = $1 || $2, capName, intro, localCapIndex; // Named subpattern if (subName) { if (!data.hasOwnProperty(subName)) { throw new ReferenceError('Undefined property ' + $0); } // Named subpattern was wrapped in a capturing group if ($1) { capName = outerCapNames[numOuterCaps]; outerCapsMap[++numOuterCaps] = ++numCaps; // If it's a named group, preserve the name. Otherwise, use the subpattern name // as the capture name intro = '(?<' + (capName || subName) + '>'; } else { intro = '(?:'; } numPriorCaps = numCaps; return intro + data[subName].pattern.replace(subParts, function(match, paren, backref) { // Capturing group if (paren) { capName = data[subName].names[numCaps - numPriorCaps]; ++numCaps; // If the current capture has a name, preserve the name if (capName) { return '(?<' + capName + '>'; } // Backreference } else if (backref) { localCapIndex = +backref - 1; // Rewrite the backreference return data[subName].names[localCapIndex] ? // Need to preserve the backreference name in case using flag `n` '\\k<' + data[subName].names[localCapIndex] + '>' : '\\' + (+backref + numPriorCaps); } return match; }) + ')'; } // Capturing group if ($3) { capName = outerCapNames[numOuterCaps]; outerCapsMap[++numOuterCaps] = ++numCaps; // If the current capture has a name, preserve the name if (capName) { return '(?<' + capName + '>'; } // Backreference } else if ($4) { localCapIndex = +$4 - 1; // Rewrite the backreference return outerCapNames[localCapIndex] ? // Need to preserve the backreference name in case using flag `n` '\\k<' + outerCapNames[localCapIndex] + '>' : '\\' + outerCapsMap[+$4]; } return $0; }); return XRegExp(pattern, flags); }; };