named-regexp-groups
Version:
Regular expressions with named capture groups and named back-references
365 lines (315 loc) • 9.88 kB
JavaScript
function _typeof(obj) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
}, _typeof(obj);
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
Object.defineProperty(Constructor, "prototype", {
writable: false
});
return Constructor;
}
/*!
* @copyright 2017- Commenthol
* @license
*/
var R_NAME = /([a-zA-Z_$][a-zA-Z_$0-9]{0,50})/;
var R_NAME_REPLACE = new RegExp("\\$\\+{".concat(R_NAME.source, "}"), 'g');
var R_NAMED_BACKREF = new RegExp("^[?:]&".concat(R_NAME.source));
var R_GROUP = new RegExp("^[?:]<".concat(R_NAME.source, ">([^]*)"));
var R_GROUPS = /([\\]?[()])/g;
var R_EMPTY_GROUPS = /\(\)/g;
function generate(str, flags) {
str = str || '';
var groups = {};
var named = {};
var source = '';
/* istanbul ignore else */
if (str instanceof RegExp) {
flags = flags || str.flags || '';
/* istanbul ignore if */
if (!flags) {
// No RegExp.flags for node < v6.
if (str.ignoreCase) flags += 'i';
if (str.multiline) flags += 'm';
if (str.global) flags += 'g';
}
str = str.source;
}
var store = {
count: 0,
// counter for unnamed matching group
groups: [''],
// store for named pattern
names: [] // store for names of capture groups
};
var index = 0;
var arr = str.split(R_GROUPS);
source = arr.map(function (s, i) {
var name;
var block;
var isGroup = false;
switch (s) {
case '(':
store.groups.push('');
store.names.push('');
break;
case ')':
block = store.groups.pop();
name = store.names.pop();
/* istanbul ignore else */
if (name) {
named[name] = block.substr(1);
}
break;
default:
// is it a real group, not a cluster (?:...), or assertion (?=...), (?!...)
isGroup = arr[i - 1] === '(' && !/^\?[:!=]/.test(s);
if (isGroup) {
index++; // named capture group check
name = R_GROUP.exec(s);
if (name && name[1]) {
if (!groups[name[1]]) {
store.names[store.names.length - 1] = name[1];
groups[name[1]] = index;
} else {
groups[store.count++] = index;
}
s = name[2] || '';
if (arr[i + 1] === ')' && !name[2]) {
s = '[^]+';
}
} else {
// is not a cluster, assertion or named capture group
groups[store.count++] = index;
} // named backreference check
name = R_NAMED_BACKREF.exec(s);
if (name && name[1]) {
s = named[name[1]] || '';
}
}
break;
}
store.groups = store.groups.map(function (group) {
return group + s;
});
return s;
}).join('').replace(R_EMPTY_GROUPS, ''); // remove any empty groups
return {
source: source,
flags: flags,
groups: groups,
named: named
};
}
/**
* Creates a regular expression with named capture groups
* @class NamedRegExp
* @note Would have loved to extend class from RegExp but that's not possible.
*/
var NamedRegExp = /*#__PURE__*/function (_Symbol$replace, _Symbol$match, _Symbol$split, _Symbol$search) {
/**
* Creates a regular expression with named capture groups
*
* Create a named capture group with `(?<name>.*)` or `(:<name>.*)` when using
* a RegExp.
* Named backreferences using `(?&name)` can be used either.
*
* For nodejs < v5.0 core-js polyfills are required.
* Use `npm i -S core-js` in your project and add:
*
* ```js
* // for node < v0.11
* require('core-js/es6/object')
* // for node < v5.0
* require('core-js/es6/string')
* require('core-js/es6/symbol')
* ```
*
* @param {String|RegExp} pattern - string or regex with named groups
* @param {String} [flags] - regex flags 'igm' if `regex` is a String
* @example
* import NamedRegExp from 'named-regexp-groups'
* //or
* const NamedRegExp = require('named-regexp-groups')
*
* // as string
* var r = new NamedRegExp('(?<foo>foo)(?<bar>)(-)(?:wat)(?<na>(?:na)+)(?&na)')
* // or as regex
* var r = new NamedRegExp(/(:<foo>foo)(:<bar>)(-)(?:wat)(:<na>(?:na)+)(:&na)/)
*
* r.source
* // => r.source === '(foo)([^]+)(-)(?:wat)((?:na)+)((?:na)+)'
*/
function NamedRegExp(pattern, flags) {
_classCallCheck(this, NamedRegExp);
var g = generate(pattern, flags);
this.regex = new RegExp(g.source, g.flags);
this.source = this.regex.source;
this.groups = g.groups;
}
/**
* Execute a search with `str`
* @param {String} str
* @return {Array} matching array with additional property `groups`
* @example
* var r = new NamedRegExp('(?<foo>foo)(bar)(?:waah)')
* r.exec('nanafoobarwaah')
* // => [ 'foobarwaah', 'foo', 'bar',
* // index: 4, input: 'nanafoobarwaah',
* // groups: { '0': 'bar', foo: 'foo' } ]
*/
_createClass(NamedRegExp, [{
key: "exec",
value: function exec(str) {
var _this = this;
var res = this.regex.exec(str);
if (res) {
res.groups = {};
Object.keys(this.groups).forEach(function (name) {
res.groups[name] = res[_this.groups[name]];
});
}
return res;
}
/**
* test for `str`
* @param {String} str
* @return {Boolean} matching array with additional property `groups`
* @example
* var r = new NamedRegExp('(?<foo>foo)(bar)(?:waah)')
* r.test('nanafoobarwaah')
* // => true
*/
}, {
key: "test",
value: function test(str) {
return this.regex.test(str);
}
/**
* outputs regex as String
*/
}, {
key: "toString",
value: function toString() {
return this.regex.toString();
}
/**
* Replace `str` by `replacement` using named capture groups
*
* If using a string use `$+{name}` to define the placeholder for the capture group.
* This follows the Syntax of {@link http://perldoc.perl.org/perlretut.html#Named-backreferences|PCRE Named backreferences}.
*
* @name replace
* @param {String} str
* @param {String|Function} replacement
* @return {String} matching array with additional property `groups`
* @example
* var r = new NamedRegExp(/(:<year>\d+)-(:<month>\d+)-(:<day>\d+)/)
*
* // ---- using strings
* '2017-01-02'.replace(r, 'day: $+{day}, month: $+{month}, year: $+{year}')
* // => 'day: 02, month: 01, year: 2017')
*
* // ---- using function
* '2016-11-22'.replace(r, function () { // take care of NOT using an arrow function here!
* var args = [].slice.call(arguments)
* var g = this.groups
* return `day: ${args[g.day]}, month: ${args[g.month]}, year: ${args[g.year]}`
* })
* // => 'day: 22, month: 11, year: 2017')
*/
}, {
key: _Symbol$replace,
value: function value(str, replacement) {
var _this2 = this;
var repl = replacement;
/* istanbul ignore next */
switch (_typeof(repl)) {
case 'string':
repl = repl.replace(R_NAME_REPLACE, function (m, name) {
var idx = _this2.groups[name];
if (idx === undefined || idx === null) {
return '';
}
return '$' + _this2.groups[name];
});
break;
case 'function':
repl = replacement.bind(this);
break;
default:
return String(repl);
}
return str.replace(this.regex, repl);
}
/**
* Search for a match in `str`
* @name match
* @param {String} str
* @return {Array} matching array with additional property `groups`
* @example
* var r = new NamedRegExp('(?<foo>foo)(bar)(?:waah)')
* 'nanafoobarwaah'.match(r)
* // => [ 'foobarwaah', 'foo', 'bar',
* // index: 4, input: 'nanafoobarwaah',
* // groups: { '0': 'bar', foo: 'foo' } ]
*/
}, {
key: _Symbol$match,
value: function value(str) {
return this.exec(str);
}
/**
* split `str`
* @name split
* @param {String} str
* @return {Array} matching array with additional property `groups`
* @example
* var r = new NamedRegExp('(?<foo>foo)')
* 'nanafoobarwaah'.split(r)
* // => [ 'nana', 'foo', 'barwaah' ]
*/
}, {
key: _Symbol$split,
value: function value(str) {
return str.split(this.regex);
}
/**
* search `str`
* @name search
* @param {String} str
* @return {Number} position of index or -1
* @example
* var r = new NamedRegExp('(?<foo>foo)')
* 'nanafoobarwaah'.search(r)
* // => 4
*/
}, {
key: _Symbol$search,
value: function value(str) {
return str.search(this.regex);
}
}]);
return NamedRegExp;
}(Symbol.replace, Symbol.match, Symbol.split, Symbol.search);
export { NamedRegExp as default };