d2-ui
Version:
187 lines (174 loc) • 7.77 kB
JavaScript
/*!
* XRegExp.build 3.1.1
* <xregexp.com>
* Steven Levithan (c) 2012-2016 MIT License
* Inspired by Lea Verou's RegExp.create <lea.verou.me>
*/
module.exports = function(XRegExp) {
'use strict';
var REGEX_DATA = 'xregexp';
var subParts = /(\()(?!\?)|\\([1-9]\d*)|\\[\s\S]|\[(?:[^\\\]]|\\[\s\S])*]/g;
var parts = XRegExp.union([/\({{([\w$]+)}}\)|{{([\w$]+)}}/, subParts], 'g');
/**
* Strips a leading `^` and trailing unescaped `$`, if both are present.
*
* @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.
*
* @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.
*
* @param {String} pattern XRegExp pattern using `{{name}}` for embedded subpatterns. Allows
* `({{name}})` as shorthand for `(?<name>{{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);
};
};