├── .gitignore ├── .npmignore ├── package.json ├── lib ├── named-js-regexp.d.ts └── named-js-regexp.js ├── LICENSE ├── .vscode └── launch.json ├── README.md └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | app.js -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | app.js -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "named-js-regexp", 3 | "version": "1.3.5", 4 | "description": "Extends JavaScript RegExp with named groups, backreferences and replacement.", 5 | "main": "lib/named-js-regexp.js", 6 | "types": "lib/named-js-regexp.d.ts", 7 | "scripts": { 8 | "test": "mocha ./test" 9 | }, 10 | "author": "edvinv", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/edvinv/named-js-regexp.git" 14 | }, 15 | "license": "MIT", 16 | "dependencies": {}, 17 | "keywords": [ 18 | "named", 19 | "regexp", 20 | "regex", 21 | "capture", 22 | "group", 23 | "regular", 24 | "expression" 25 | ], 26 | "devDependencies": { 27 | "chai": "^4.2.0", 28 | "mocha": "^5.2.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/named-js-regexp.d.ts: -------------------------------------------------------------------------------- 1 | declare module "named-js-regexp" { 2 | 3 | interface NamedRegExpExecArray extends Pick> { 4 | groups(all?: boolean): { [key: string]: string } | null; 5 | group(name: string, all?: boolean): string; 6 | } 7 | 8 | interface NamedRegExp extends Pick> { 9 | exec(expression: string): NamedRegExpExecArray | null; 10 | execGroups(expression: string, all?: boolean): { [key: string]: string } | null; 11 | groupsIndices(): { [key: string]: number }; 12 | replace(text: string, replacement: string | ((this: NamedRegExpExecArray | null, match?: string, ...params: any[]) => string)): string; 13 | } 14 | 15 | let createNamedRegexp: (pattern: string | RegExp | boolean, flags?: string) => NamedRegExp; 16 | export = createNamedRegexp; 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2015, @edvinv 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | // List of configurations. Add new configurations or edit existing ones. 4 | "configurations": [ 5 | { 6 | "request": "launch", 7 | // Name of configuration; appears in the launch configuration drop down menu. 8 | "name": "Launch named-regex-groups.js", 9 | // Type of configuration. 10 | "type": "node", 11 | // Workspace relative or absolute path to the program. 12 | "program": "${workspaceRoot}/app.js", 13 | // Automatically stop program after launch. 14 | "stopOnEntry": false, 15 | // Command line arguments passed to the program. 16 | "args": [], 17 | // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. 18 | "cwd": "${workspaceRoot}/", 19 | // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. 20 | "runtimeExecutable": null, 21 | // Optional arguments passed to the runtime executable. 22 | "runtimeArgs": ["--nolazy"], 23 | // Environment variables passed to the program. 24 | "env": { 25 | "NODE_ENV": "development" 26 | }, 27 | // Use JavaScript source maps (if they exist). 28 | "sourceMaps": false, 29 | // If JavaScript source maps are enabled, the generated code is expected in this directory. 30 | "outDir": null 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## named-js-regexp 2 | Extends JavaScript RegExp with named groups, backreferences and replacement. 3 | All are converted to normal JavaScript RegExp so you will get the same speed 4 | except for initial parsing, which can also be eliminated with cache enabled. 5 | 6 | ### Syntax 7 | Named group: `(?expression)` or `(:expression)` 8 | Named backreference: `\k` 9 | Named group in replacement text: `${name}` 10 | 11 | 12 | ### Install 13 | ```sh 14 | npm install named-js-regexp --save 15 | ``` 16 | 17 | ### Using with regexp.execGroups 18 | ```javascript 19 | var namedRegexp = require("named-js-regexp"); 20 | 21 | var re=namedRegexp("(?\\d\\d?):(?\\d\\d?)(:(?\\d\\d?))?"); 22 | // or with regexp literal... NOTE: you must use (:) 23 | var re=namedRegexp(/(:\d\d?):(:\d\d?)(:(:\d\d?))?/); 24 | 25 | re.execGroups("1:2:33"); // => { hours:"1", minutes:"2", seconds:"33" } 26 | re.execGroups("1"); // => null 27 | ``` 28 | 29 | ### Using with regexp.exec 30 | ```javascript 31 | var re=namedRegexp("(?\\d\\d?):(?\\d\\d?)(:(?\\d\\d?))?"); 32 | var result=re.exec("1:2"); 33 | result.groups(); // => { hours:"1", minutes:"2", seconds:undefined } 34 | result.group("minutes"); // => "2" 35 | result.group("seconds"); // => undefined 36 | ``` 37 | 38 | ### Using named backreferences 39 | ```javascript 40 | var re=namedRegexp("(<(?\\w+)>).*<\/\\k>"); 41 | var result=re.exec("
hi
"); 42 | result.groups(); // => { elem: "div" } 43 | ``` 44 | 45 | ### Using named replacement 46 | ```javascript 47 | var re = namedRegexp("(?\\d+):(?\\d+):(?\\d+)"); 48 | re.replace('1:23:44', '${h}hour(s) ${m}minute(s) ${s}second(s)'); 49 | // => 1hour(s) 23minute(s) 44second(s) 50 | re.replace('1:23:44', function () { 51 | var g = this.groups(); 52 | return g.h + 'hour' + (g.h > 1 ? 's ' : ' ')+ g.m + 'minute' + (g.m > 1 ? 's ' : ' ')+ g.s + 'second' + (g.s > 1 ? 's' : ''); 53 | }); 54 | // => 1hour 23minutes 44seconds 55 | ``` 56 | 57 | ### Using with regexp.groupsIndices 58 | ```javascript 59 | var re = namedRegexp("(?\\d\\d?):(?\\d\\d?)(:(?\\d\\d?))?"); 60 | var matches = "1:2".match(re); 61 | matches[re.groupsIndices["hours"]]; // => "1" 62 | matches[re.groupsIndices["seconds"]]; // => undefined 63 | ``` 64 | 65 | ### Handling group name duplication 66 | ```javascript 67 | var re = namedRegexp("(?((?\\d):(?\\d)))|(?((?\\w):(?\\w)))"); 68 | re.groupsIndices; // => { digit: 1, a: [ 3, 7 ], b: [ 4, 8 ], char: 5 } 69 | 70 | var r = re.exec("a:b"); 71 | r.groups(); // => { a: "a", b: "b", digit: undefined, char: "a:b" } 72 | r.groups(true); // => { a: [undefined, "a"], b: [undefined, "b"], digit: undefined, char: "a:b" } 73 | r = re.exec("1:2"); 74 | r.groups(); // => { a: "1", b: "2", digit: "1:2", char: undefined } 75 | r.groups(true); // => { a: ["1", undefined], b: ["2", undefined], digit: "1:2", char: undefined } 76 | ``` 77 | 78 | ### Using with successive matches 79 | ```javascript 80 | var re = namedRegexp("(?\\d)(?\\w)", "g"); 81 | var r = re.exec("1a2b"); 82 | r.groups(); // => { x: '1', y: 'a' } 83 | r = re.exec("1a2b"); 84 | r.groups(); // => { x: '2', y: 'b' } 85 | r = re.exec("1a2b"); // => null 86 | ``` 87 | 88 | ### API 89 | `var namedRegexp = require("named-js-regexp");` 90 | `regexp=namedRegexp(value:string|RegExp|boolean, flags?:string)` 91 | Returns normal JavaScript RegExp object with some additional properties. Parameter value can be string expression or RegExp object. For 92 | latest you must use `(:expression)` syntax and flags parameter is ignored. 93 | Set value parameter to true to enable caching or set to false to clear and disable cache. 94 | 95 | ##### regexp 96 | `regexp.exec(expression:string)` 97 | Performs search for the matches and returns null if no match was found or matched (Array) result. 98 | 99 | `regexp.execGroups(expression:string, all?:boolean)` 100 | Performs search for the matches and returns null if no match was found or name/value dictionary, 101 | where name is group name and value is matched value. If same group name was defined multiple times and 102 | parameter all is false (default) then first (from left to right) not undefined value is returned. 103 | If parameter all is true then returned value is array of all matched values. 104 | 105 | `regexp.replace(text:string, replacement:string|function)` 106 | Works as String.prototype.replace. If parameter replacement is string you can also use named replace like `${name}` instead of `$1`. 107 | If replacement is function it receives same parameters as String.prototype.replace callback, but `this` is set to matched 108 | object, similar to one returned by exec. 109 | 110 | `regexp.groupsIndices` 111 | Returns name/value mapper where name is group name and value is index that can be used to access matched value by index. 112 | If same group name was defined multiple times then value is array of all matched indices ordered from left to right as defined in 113 | regular expression. 114 | 115 | ##### matched (returned by regexp.exec) 116 | `matched.groups(all?:boolean)` 117 | Returns name/value dictionary, where name is group name and value is matched value. Check regexp.execGroups 118 | for info about parameter all. 119 | 120 | `matched.group(name:string, all?:boolean)` 121 | Returns named group value or undefined if named group was not found. Check regexp.execGroups 122 | for info about parameter all. 123 | 124 | ### NOTES 125 | - Group name should start with '\_$a-zA-Z' and can contain only '\_$a-zA-Z0-9'. 126 | - Named backreference should refer to already defined named group, otherwise error is thrown. 127 | - Named backreference and named replacement should not refer to duplicated named group, otherwise error is thrown. 128 | 129 | ### LICENCE 130 | MIT 131 | -------------------------------------------------------------------------------- /lib/named-js-regexp.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | (function (window) { 3 | var validGroupName = /^[$_a-z][$_a-z0-9]*$/i; 4 | var cache; 5 | 6 | function parseRegex(text) { 7 | var i, c, c1, c12, nameEnd, name, item, 8 | mapper = {}, 9 | current = 0, 10 | regexText = "", 11 | inCharSet = false; 12 | 13 | for (i = 0; i < text.length; ++i) { 14 | c = text[i]; c1 = text[i + 1]; c12 = c1 + text[i + 2]; 15 | regexText += c; 16 | if (c === "\\") { 17 | if (!inCharSet && c12 === "k<") { 18 | // this is back reference 19 | nameEnd = text.indexOf(">", i + 3); 20 | if (nameEnd < 0) { 21 | throw new Error("'>' missing in named backreference."); 22 | } 23 | name = text.substring(i + 3, nameEnd); 24 | item = mapper[name]; 25 | if (!item) { 26 | throw new Error("Named group '" + name + "' is not defined in backreference."); 27 | } 28 | if (typeof item !== "number") { 29 | throw new Error("Named backreference referencing duplicate named group '" + name + "'."); 30 | } 31 | i = nameEnd; 32 | regexText += item; 33 | } else { 34 | regexText += c1; 35 | ++i; 36 | } 37 | } else if (inCharSet) { 38 | if (c === "]") { 39 | inCharSet = false; 40 | } 41 | } else if (c === "[") { 42 | inCharSet = true; 43 | } else { 44 | // if it starts with '(' and is neither non capturing group '(?:', nor positive lookahead "(?=" nor negative lookahead "(?!" then this is capturing group expression 45 | if (c === "(" && c12 !== "?:" && c12 !== "?=" && c12 !== "?!") { 46 | current++; 47 | // if it starts with '?<' or ':<' then this is capturing named group 48 | if (c12 === "?<" || c12 === ":<") { 49 | nameEnd = text.indexOf(">", i + 3); 50 | if (nameEnd < 0) { 51 | throw new Error("'>' missing in named group."); 52 | } 53 | name = text.substring(i + 3, nameEnd); 54 | if (!validGroupName.test(name)) { 55 | throw new Error("Invalide group name '" + name + "'. Regexp group name should start with '_$a-zA-Z' and can contain only '_$a-zA-Z0-9'."); 56 | } 57 | item = mapper[name]; 58 | if (item === undefined) { 59 | mapper[name] = current; 60 | } else if (typeof item === "number") { 61 | mapper[name] = [item, current]; 62 | } else { 63 | item.push(current); 64 | } 65 | i = nameEnd; 66 | } 67 | } 68 | } 69 | } 70 | return { mapper: mapper, regexText: regexText }; 71 | } 72 | 73 | function parseReplacement(text, groupsIndices) { 74 | var i, c, c1, name, nameEnd, groupIndex, 75 | result = ''; 76 | for (i = 0; i < text.length; ++i) { 77 | c = text[i]; c1 = text[i + 1]; 78 | result += c; 79 | if (c === '$') { 80 | if (c1 === '$') { 81 | result += c1; 82 | ++i; 83 | } else if (c1 === '{') { 84 | nameEnd = text.indexOf("}", i + 2); 85 | if (nameEnd < 0) { 86 | throw new Error("'>' missing in replacement named group."); 87 | } 88 | name = text.substring(i + 2, nameEnd); 89 | groupIndex = groupsIndices[name]; 90 | if (groupIndex === undefined) { 91 | throw new Error("Named group '" + name + "' is not defined in replacement text."); 92 | } 93 | if (typeof groupIndex !== "number") { 94 | throw new Error("Named replacement referencing duplicate named group '" + name + "'."); 95 | } 96 | result += groupIndex; 97 | i = nameEnd; 98 | } 99 | } 100 | } 101 | return result; 102 | } 103 | /** 104 | * Create regexp with additional properties or enable/disable cache 105 | * @param {(string|RegExp|boolean)} regexp string, regular literal or true/false for enable/disable cache 106 | * @param {string} flags 107 | */ 108 | function createNamedRegex(regexp, flags) { 109 | 110 | if (typeof regexp === "boolean") { 111 | // options parameter 112 | if (regexp === false) { 113 | cache = undefined; 114 | } else if (!cache) { 115 | cache = {}; 116 | } 117 | return; 118 | } 119 | 120 | if (typeof regexp !== "string") { 121 | // parameter is regexp literal 122 | flags = (regexp.global ? "g" : "") + 123 | (regexp.multiline ? "m" : "") + 124 | (regexp.ignoreCase ? "i" : ""); 125 | regexp = regexp.source; 126 | } 127 | 128 | var info = cache ? (cache[regexp] || (cache[regexp] = parseRegex(regexp))) : parseRegex(regexp); 129 | var regex = new RegExp(info.regexText, flags); 130 | 131 | regex.groupsIndices = info.mapper; 132 | 133 | function extendMatched(matched) { 134 | var mapper = info.mapper; 135 | /** 136 | * Returns value for group name or undefined. If same group name was defined multiple time, it returns first not undefined value. 137 | * If all is set to true, then it returns array of all matched values. 138 | */ 139 | matched.group = function (name, all) { 140 | var indices = mapper[name]; 141 | if (typeof indices === "number") { 142 | // name group is defined only once 143 | return matched[indices]; 144 | } else if (all) { 145 | // name group is defined multiple time and because all is true, return array of all matched values 146 | return indices.map(function (v) { return matched[v]; }); 147 | } else if (indices) { 148 | // name group is defined multiple time and because all is false, return first not undefined matched value 149 | for (var i = 0; i < indices.length; ++i) { 150 | var value = matched[indices[i]]; 151 | if (value !== undefined) { 152 | return value; 153 | } 154 | } 155 | } 156 | return undefined; 157 | }; 158 | var cachedGroups, cachedGroupsAll; 159 | matched.groups = function (all) { 160 | var cg = all ? cachedGroupsAll : cachedGroups; 161 | if (cg) { 162 | return cg; 163 | } 164 | cg = {}; 165 | for (var name in mapper) { 166 | cg[name] = matched.group(name, all); 167 | } 168 | return all ? cachedGroupsAll = cg : cachedGroups = cg; 169 | }; 170 | return matched; 171 | } 172 | 173 | regex.exec = function (text) { 174 | var matched = RegExp.prototype.exec.call(this, text); 175 | if (matched) { 176 | extendMatched(matched); 177 | } 178 | return matched; 179 | } 180 | regex.execGroups = function (text, all) { 181 | var matched = this.exec(text); 182 | if (!matched) { 183 | return null; 184 | } 185 | return matched.groups(all); 186 | } 187 | 188 | regex.replace = function (text, replacement) { 189 | if (typeof replacement === "function") { 190 | return text.replace(regex, function () { 191 | var matched = Array.prototype.slice.call(arguments, 0, arguments.length - 2); 192 | extendMatched(matched); 193 | return replacement.apply(matched, arguments); 194 | }); 195 | } else { 196 | var replacementText = parseReplacement(replacement, this.groupsIndices); 197 | return text.replace(this, replacementText); 198 | } 199 | } 200 | return regex; 201 | } 202 | 203 | if (typeof exports !== "undefined") { 204 | module.exports = createNamedRegex; 205 | } else if (typeof define === "function" && define.amd) { 206 | define(function () { 207 | return createNamedRegex; 208 | }) 209 | } else { 210 | window.createNamedRegex = createNamedRegex; 211 | } 212 | 213 | 214 | })(typeof window === "undefined" ? this : window); 215 | 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var expect = require("chai").expect; 2 | var namedRegexp = require("../lib/named-js-regexp"); 3 | 4 | describe("NamedRegExp must", function () { 5 | var regex = namedRegexp("(?\\d\\d?):(?\\d\\d?):(?\\d\\d?)"); 6 | it("be instance of RegExp.", function () { expect(regex).to.be.instanceof(RegExp); }); 7 | it("have execGroups function.", function () { expect(regex.execGroups).to.be.a("function"); }); 8 | it("have groupsIndices dictionary.", function () { expect(regex.groupsIndices).to.be.an("object"); }); 9 | }); 10 | 11 | describe("Result from exec() must be", function () { 12 | var regex = namedRegexp("(?\\d\\d?):(?\\d\\d?):(?\\d\\d?)"); 13 | var result = regex.exec("1:2:33"); 14 | it("valid Array.", function () { expect(result).to.be.an("array"); }); 15 | it("have group(name,all) function.", function () { expect(result.group).to.be.a("function"); }); 16 | it("have groups(all) function.", function () { expect(result.groups).to.be.a("function"); }); 17 | }); 18 | 19 | describe("Check", function () { 20 | it("that invalide expression throws an error.", function () { expect(function () { namedRegexp("?") }).to.throw(Error); }); 21 | it("that missing end '>' throws an error.", function () { expect(function () { namedRegexp("(?\\d\\d)").execGroups("(12").foo).to.be.equal("12"); }); 23 | it("'\\' escaping.", function () { expect(namedRegexp("\\(\\\\\\\\(\\\\(?\\d+))").execGroups("(\\\\\\1456u").foo).to.be.equal("1456"); }); 24 | it("that non-capturing parentheses '(?:)' works ok.", function () { expect(namedRegexp("(?:\\((?\\d\\d))").execGroups("(12").foo).to.be.equal("12"); }); 25 | it("that positive lookahead '(?=)' is not captured.", function () { expect(namedRegexp("(?<_>f(?=oo))oo(?<$>\\d\\d)").execGroups("(foo12")).to.be.deep.equal({ _: "f", $: "12" }); }); 26 | it("that negative lookahead '(?!)' is not captured.", function () { expect(namedRegexp("((?<_>f(?!(oo)))oA(?<$>\\d\\d))(?=x)").execGroups("(foA12x")).to.be.deep.equal({ _: "f", $: "12" }); }); 27 | it("that matched expression with no named groups returns groups={}.", function () { expect(namedRegexp("(\\d\\d)|(\\w)").execGroups("a")).to.be.deep.equal({}); }); 28 | it("deep expression nesting.", function () { expect(namedRegexp("(?:((((((?:(?\\d\\d\\d)))-((?\\d\\d))))-((((?\\d)))))))").execGroups("123-45-6")).to.be.deep.equal({ a: "123", b: "45", c: "6" }); }); 29 | var urlRegexp = namedRegexp("^((?http[s]?|ftp):\\\/)?\\\/?(?[^:\\\/\\s]+)(?(\\\/\\w+)*\\\/)(?[\\w\\-\\.]+[^#?\\s]+)(?.*)?$"); 30 | var urlParts = urlRegexp.execGroups("https://www.google.com/some/path/search.html?a=0&b=1"); 31 | it("url parsing.", function () { expect(urlParts).to.be.deep.equal({ schema: "https", domain: "www.google.com", path: "/some/path/", file: "search.html", query: "?a=0&b=1" }); }); 32 | }); 33 | 34 | describe("User group name ", function () { 35 | it("should start with ':$_a-zA-Z' .", function () { expect(function () { namedRegexp("(?<$>.)"); namedRegexp("(?<$>.)"); namedRegexp("(?.)"); namedRegexp("(?.)"); }).to.not.throw(); }); 36 | it("should not star with '.' .", function () { expect(function () { namedRegexp("(?<.>.)"); }).to.throw(/Invalide group name/); }); 37 | it("should not star with '1' .", function () { expect(function () { namedRegexp("(?<1>.)"); }).to.throw(/Invalide group name/); }); 38 | it("should not be empty '' .", function () { expect(function () { namedRegexp("(?<>.)"); }).to.throw(/Invalide group name/); }); 39 | }); 40 | 41 | describe("Using regexp with exec function", function () { 42 | var regex = namedRegexp("(?\\d\\d?):(?\\d\\d?):(?\\d\\d?)"); 43 | var result = regex.exec("1:2:33"); 44 | it("check groups() result.", function () { expect(result.groups()).to.be.deep.equal({ hours: "1", minutes: "2", seconds: "33" }); }); 45 | it("check group() with valid group name.", function () { expect(result.group("hours")).to.be.equal("1"); }); 46 | it("check group() with not defined group name.", function () { expect(result.group("hours1")).to.be.undefined; }); 47 | }); 48 | 49 | describe("Using regular expression literal", function () { 50 | var regex = namedRegexp(/(:\d\d?):(:\d\d?):(:\d\d?)/); 51 | var result = regex.exec("1:2:33"); 52 | it("check groups() result.", function () { expect(result.groups()).to.be.deep.equal({ hours: "1", minutes: "2", seconds: "33" }); }); 53 | it("check group() with valid group name.", function () { expect(result.group("hours")).to.be.equal("1"); }); 54 | it("check group() with not defined group name.", function () { expect(result.group("hours1")).to.be.undefined; }); 55 | 56 | var regex1 = namedRegexp(new RegExp("(:\\d\\d?):(:\\d\\d?):(:\\d\\d?)")); 57 | result = regex1.exec("1:2:33"); 58 | it("check groups() result.", function () { expect(result.groups()).to.be.deep.equal({ hours: "1", minutes: "2", seconds: "33" }); }); 59 | }); 60 | 61 | describe("Using regexp with execGroups function", function () { 62 | var regex = namedRegexp("(?\\d\\d?):(?\\d\\d?)(:(?\\d\\d?))?"); 63 | it("check result", function () { expect(regex.execGroups("1:2:33")).to.be.deep.equal({ hours: "1", minutes: "2", seconds: "33" }); }); 64 | it("group with unmatched name should be undefined.", function () { expect(regex.execGroups("1:2")).to.be.deep.equal({ hours: "1", minutes: "2", seconds: undefined }); }); 65 | it("if check fails, null should be returned.", function () { expect(regex.execGroups("1")).to.be.null; }); 66 | }); 67 | 68 | describe("Using regexp with groupsIndices property", function () { 69 | var regex = namedRegexp("(?\\d\\d?):(?\\d\\d?)(:(?\\d\\d?))?"); 70 | var matches = "1:2".match(regex); 71 | it("check hours.", function () { expect(matches[regex.groupsIndices["hours"]]).to.be.equal("1"); }); 72 | it("check minutes.", function () { expect(matches[regex.groupsIndices["minutes"]]).to.be.equal("2"); }); 73 | it("group with unmatched name should be undefined.", function () { expect(matches[regex.groupsIndices["seconds"]]).to.be.undefined; }); 74 | it("using not defined group name should be undefined.", function () { expect(matches[regex.groupsIndices["foo"]]).to.be.undefined; }); 75 | }); 76 | 77 | describe("NamedRegExp should behave just like normal RegExp", function () { 78 | var regex = namedRegexp("^((\\d\\d?):(\\d\\d?))$"); 79 | it("with test function", function () { expect(regex.test("1:2:33")).to.be.false; }); 80 | it("with test function", function () { expect(regex.test("1:2")).to.be.true; }); 81 | var result = regex.exec("1:3"); 82 | it("with exec function", function () { expect(result[2]).to.be.equal("1"); }); 83 | it("with exec function", function () { expect(result[3]).to.be.equal("3"); }); 84 | }); 85 | 86 | describe("Group names duplication", function () { 87 | var regex0 = namedRegexp("^(?\\d)(?\\d)((?\\d)(?\\d))$"); 88 | it("should return array of indices for duplicated group name .", function () { expect(regex0.groupsIndices).to.be.deep.equal({ first: [1, 4, 5], second: 2 }); }); 89 | 90 | var regex1 = namedRegexp("^(?\\d)(?\\d)$"); 91 | var res1 = regex1.exec("12"); 92 | it("should return first group value.", function () { expect(res1.groups().first).to.be.equal("1"); }); 93 | it("second call should return first group value from cache.", function () { expect(res1.groups().first).to.be.equal("1"); }); 94 | it("should return array if all is set to true.", function () { expect(res1.groups(true).first).to.be.deep.equal(["1", "2"]); }); 95 | it("second call should should return array if all is set to true from cache.", function () { expect(res1.groups(true).first).to.be.deep.equal(["1", "2"]); }); 96 | 97 | var regex2 = namedRegexp("(?((?\\d):(?\\d)))|(?((?\\w):(?\\w)))"); 98 | var res21 = regex2.exec("a:b"); 99 | it("with nested named groups.", function () { expect(res21.groups()).to.be.deep.equal({ a: "a", b: "b", digit: undefined, char: "a:b" }); }); 100 | it("with nested named groups (from cache).", function () { expect(res21.groups()).to.be.deep.equal({ a: "a", b: "b", digit: undefined, char: "a:b" }); }); 101 | it("with nested named groups.", function () { expect(regex2.execGroups("1:2")).to.be.deep.equal({ a: "1", b: "2", digit: "1:2", char: undefined }); }); 102 | it("with nested named groups (from cache).", function () { expect(regex2.execGroups("1:2")).to.be.deep.equal({ a: "1", b: "2", digit: "1:2", char: undefined }); }); 103 | 104 | it("with nested named groups.", function () { expect(res21.groups(true)).to.be.deep.equal({ a: [undefined, "a"], b: [undefined, "b"], digit: undefined, char: "a:b" }); }); 105 | it("with nested named groups (from cache).", function () { expect(res21.groups(true)).to.be.deep.equal({ a: [undefined, "a"], b: [undefined, "b"], digit: undefined, char: "a:b" }); }); 106 | it("with nested named groups.", function () { expect(regex2.execGroups("1:2", true)).to.be.deep.equal({ a: ["1", undefined], b: ["2", undefined], digit: "1:2", char: undefined }); }); 107 | it("with nested named groups (from cache).", function () { expect(regex2.execGroups("1:2", true)).to.be.deep.equal({ a: ["1", undefined], b: ["2", undefined], digit: "1:2", char: undefined }); }); 108 | 109 | it("with group(name).", function () { expect(res21.group("a")).to.be.equal("a"); }); 110 | it("with group(name,true).", function () { expect(res21.group("a", true)).to.be.deep.equal([undefined, "a"]); }); 111 | it("with group(name).", function () { expect(res21.group("char")).to.be.deep.equal("a:b"); }); 112 | it("with group(name,true).", function () { expect(res21.group("char", true)).to.be.deep.equal("a:b"); }); 113 | }); 114 | 115 | 116 | describe("Successive matched", function () { 117 | var regex = namedRegexp("(?\\d)(?\\w)", "g"); 118 | it("for first match.", function () { expect(regex.exec("1a2b").groups()).to.be.deep.equal({ x: "1", y: "a" }); }); 119 | it("for second match.", function () { expect(regex.exec("1a2b").groups()).to.be.deep.equal({ x: "2", y: "b" }); }); 120 | it("and null for last one.", function () { expect(regex.exec("1a2b")).to.be.null; }); 121 | }); 122 | 123 | describe("Named backreference", function () { 124 | var regex1 = namedRegexp("(?\\d)\\k"); 125 | it("simple test 1.", function () { expect(regex1.exec("a21b33h")[0]).to.be.equal("33"); }); 126 | var regex2 = namedRegexp("(((<(?\\w+)>).*<\/\\k>))"); 127 | it("simple test 2.", function () { expect(regex2.exec("
hi
").groups()).to.be.deep.equal({ elem: "div" }); }); 128 | it("to undefined group name, should throw exception.", function () { expect(function () { namedRegexp("<(?\\w+)>.*<\/\\k>") }).to.throw(/.*not defined in backreference.*/); }); 129 | it("to group defined after backreference, should throw exception.", function () { expect(function () { namedRegexp("\\k(?\\d)") }).to.throw(/.*not defined in backreference.*/); }); 130 | it("to duplicated group name, should throw exception.", function () { expect(function () { namedRegexp("(?\\w+)<(?\\w+)>.*<\/\\k>") }).to.throw(/.*Named backreference referencing duplicate named group*/); }); 131 | }); 132 | 133 | describe("Named replacement with replacement string", function () { 134 | var regex1 = namedRegexp("(?\\d+):(?\\d+):(?\\d+)"); 135 | it("simple test 1.", function () { expect(regex1.replace('1:23:44', '${h}hour(s) ${m}minute(s) ${s}second(s)')).to.be.equal("1hour(s) 23minute(s) 44second(s)"); }); 136 | it("simple test 2.", function () { 137 | expect(regex1.replace('1:23:44', function () { 138 | var g = this.groups(); 139 | return g.h + 'hour' + (g.h > 1 ? 's ' : ' ') + g.m + 'minute' + (g.m > 1 ? 's ' : ' ') + g.s + 'second' + (g.s > 1 ? 's' : ''); 140 | })).to.be.equal("1hour 23minutes 44seconds"); 141 | }); 142 | it("to undefined group name, should throw exception.", function () { expect(function () { regex1.replace('1:23:44', '${h1}hour(s) ${m}minute(s) ${s}second(s)') }).to.throw(/.*is not defined in replacement text.*/); }); 143 | 144 | var regex2 = namedRegexp("(?\\d+):(?\\d+):(?\\d+)"); 145 | it("to duplicated group name, should throw exception.", function () { expect(function () { regex2.replace('1:23:44', '${h}hour(s) ${m}minute(s) ${s}second(s)') }).to.throw(/.*Named replacement referencing duplicate named group.*/); }); 146 | 147 | it("check '$' escaping.", function () { expect(regex1.replace('1:23:44', '$$${h}hour(s) ${m}minute(s) ${s}second(s)$')).to.be.equal("$1hour(s) 23minute(s) 44second(s)$"); }); 148 | 149 | }); 150 | 151 | describe("Escape rules inside character sets are not so strict.", function () { 152 | var regex1 = namedRegexp("(([(\\[](?[abc])[\\)\\]]([(\\[](?[^a-z])[\\)\\]])))"); 153 | it("This should match", function () { expect(regex1.execGroups('(a)[1]')).to.be.deep.equal({ g1: "a", g2:"1"}); }); 154 | }); 155 | 156 | --------------------------------------------------------------------------------