UNPKG

named-regexp-groups

Version:

Regular expressions with named capture groups and named back-references

370 lines (318 loc) 9.99 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); 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); exports["default"] = NamedRegExp; module.exports = exports.default;