我的目标是创建一个正则表达式生成器函数,我将策略标准对象作为参数传递,并根据该参数生成正则表达式。
My attempts so far:
function generateRegexExpression(policy) {
let pattern = "";
if (policy.UppercaseLength > 0) pattern += `A-Z{${policy.UppercaseLength},}`;
if (policy.LowercaseLength > 0) pattern += `a-z{${policy.LowercaseLength},}`;
if (policy.NonAlphaLength > 0) pattern += `[!@#$%^&*()_+-={}\[\]\|;:'",.<>/?`~]{${policy.NonAlphaLength},}`;
if (policy.NumericLength > 0) pattern += `\d{${policy.NumericLength},}`;
if (policy.AlphaLength > 0) pattern += `[A-Za-z]{${policy.AlphaLength},}`;
pattern = `^[${pattern}]{${policy.MinimumLength},${policy.MaximumLength}}$`;
return new RegExp(pattern);
}
const policy = {
"MinimumLength": 8,
"MaximumLength": 20,
"UppercaseLength": 0,
"LowercaseLength": 0,
"NonAlphaLength": 0,
"NumericLength": 1,
"AlphaLength": 1
};
"PolicyRules": [
"Please choose a new password that is between 8 and 20 characters in length.",
"Must have at least 1 letter.",
"Must have at least 1 number.",
"Your new password should not be same as your username or as your last password.",
"Choose a password that is different from your last 5 passwords.",
"Do NOT share your password with anyone."
]
这不是对你问题的直接回答,但可能有助于解决最重要的问题。
与其构建复杂的正则表达式,还不如分别验证每个条件。这允许您向用户提供相关的错误消息。但如果你不需要,也可以自由地不使用正则表达式。
例如,要验证MinimumLength
,根本不需要使用正则表达式。你可以直接用input.length >= MinimumLength
下面是使用validate
函数的示例:
function validate(input, criteria) {
const errors = [];
for (const [isValid, errorMsg] of criteria) {
if (!isValid(input)) errors.push(errorMsg);
}
return [!errors.length, errors];
}
该函数期望criteria
,它是[isValid, errorMsg]
组合的列表。其中isValid
是一个接受input
作为参数的函数。然后,如果输入有效则返回真值,如果输入无效则返回假值。errorMsg
是在input
无效的情况下返回的错误。
使用这个函数,您可以相当简单地从策略中构建标准:
const criteria = [];
if ("MinimumLength" in policy) criteria.push([
input => input.length >= policy.MinimumLength,
`must be at least ${policy.MinimumLength} characters`
]);
// ...
根据构建的标准,您可以使用以下命令验证输入:
const [isValid, errors] = validate(input, criteria);
其中isValid
是true
/false
值,告诉您输入是否有效。errors
是criteria
中与当前输入相关的错误信息。
document.forms.validate.addEventListener("submit", (event) => {
event.preventDefault();
const form = event.target;
const inputs = form.elements;
const policy = JSON.parse(inputs.policy.value);
const criteria = generatePolicyCriteria(policy);
const input = inputs.testInput.value;
const [isValid, errors] = validate(input, criteria);
const output = document.getElementById("output");
output.innerHTML = "";
if (isValid) output.append("✓");
else output.append("✗", toUnorderedList(errors));
});
function validate(input, criteria) {
const errors = [];
for (const [isValid, errorMsg] of criteria) {
if (!isValid(input)) errors.push(errorMsg);
}
return [!errors.length, errors];
}
function generatePolicyCriteria(policy) {
const criteria = [];
if ("MinimumLength" in policy) criteria.push([
input => input.length >= policy.MinimumLength,
`must be at least ${policy.MinimumLength} characters`
]);
if ("MaximumLength" in policy) criteria.push([
input => input.length <= policy.MaximumLength,
`must be at least ${policy.MaximumLength} characters`
]);
if ("AlphaLength" in policy) criteria.push([
input => (input.match(/p{Letter}/gu) || []).length >= policy.AlphaLength,
`must contain at least ${policy.AlphaLength} letters`
]);
if ("UppercaseLength" in policy) criteria.push([
input => (input.match(/p{Uppercase_Letter}/gu) || []).length >= policy.UppercaseLength,
`must contain at least ${policy.UppercaseLength} uppercase letters`
]);
if ("LowercaseLength" in policy) criteria.push([
input => (input.match(/p{Lowercase_Letter}/gu) || []).length >= policy.LowercaseLength,
`must contain at least ${policy.LowercaseLength} lowercase letters`
]);
if ("NumericLength" in policy) criteria.push([
input => (input.match(/p{Decimal_Number}/gu) || []).length >= policy.NumericLength,
`must contain at least ${policy.NumericLength} digits`
]);
const specialChars = new Set("!@#$%^&*()_+-={}[]|;:'",.<>/?`~");
if ("NonAlphaLength" in policy) criteria.push([
input => Array.from(input).filter(char => specialChars.has(char)).length >= policy.NonAlphaLength,
`must contain at least ${policy.NonAlphaLength} special characters`
]);
return criteria;
}
function toUnorderedList(items, liContent = item => item) {
const ul = document.createElement("ul");
for (const item of items) {
const li = document.createElement("li");
li.append(liContent(item));
ul.append(li);
}
return ul;
}
<form id="validate">
<textarea name="policy" rows="9" cols="30">{
"MinimumLength": 8,
"MaximumLength": 20,
"UppercaseLength": 0,
"LowercaseLength": 0,
"NonAlphaLength": 0,
"NumericLength": 1,
"AlphaLength": 1
}</textarea>
<br />
<input name="testInput" type="text" />
<div id="output"></div>
<button>validate</button>
</form>
首先声明:我不认为使用单个正则表达式是完成此任务的最佳工具。我们可以使它工作,但现在我们都知道,我知道我们滥用regexp:-)
你当前的代码将产生奇怪的模式,不会做任何接近你想要的。因为它们都在同一条线上,让我们看一个例子,看看哪里出错了。
假设我们只应用一个大写策略(比如policy.UpperCaseLength = 4
)。加上一些空白来帮助查看发生了什么,结果是:
pattern = `^[A-Z{4,}]{8,20}$`
方括号之间的所有内容都被视为字符类的一部分。虽然您的意图是让{4,}
表示应该至少有四个字符与[A-Z]
匹配,但[A-Z{4,}]
被解释为"大写字符",或{
,或4
,或,
,或}
。
你真正想测试的是"忽略输入中的其他字符,一个大写字母是否有4个独立的实例?">
这实际上是一个非常简单的模式:我们可以搜索任何(.*
)之后的[A-Z]
,至少四次,即(.*[A-Z]){4,}
。事实上,我们可以稍微提高效率——只要有四个匹配,我们就不需要一直匹配,当我们在匹配的时候,我们可以使用一个非贪婪量词,和一个非捕获组。因此,要匹配至少包含policy.UpperCaseLength
个大写字母实例的字符串,我们可以使用模式:
`(?:.*?[A-Z]){${policy.UpperCaseLength}}`
我们可以用同样的思路为其他字符类创建模式,例如
`(?:.*?\d){${policy.NumericLength}}`
我们可以使用前瞻性断言将模式组合成一个正则表达式。断言可以有效地根据输入字符串检查模式,而不需要使用"up";
因此,如果我们添加预先断言语法((?= ... )
)中包装的规则,那么之前的模式看起来像:
pattern = `^(?=(?:.*?[A-Z]){4}){8,20}$`
这仍然是不完全正确的(事实上它不会匹配任何东西-前瞻组匹配0个字符,所以它只能匹配一个至少有四个大写字符的空字符串!)-我们真正想要做的是检查MinimumLength
和MaximumLength
策略是否有8到20个的实例,即.
字符。因此,将其添加到模式中:
pattern = `^(?=(?:.*?[A-Z]){4}).{8,20}$`
// ^^^^^^^ enforce length range
现在模式检查至少有四个大写字符,然后整个输入在8到20个字符之间。我们可以以同样的方式基于policy
构建其他模式:
function generateRegexExpression(policy) {
let pattern = "";
if (policy.UppercaseLength > 0) pattern += `(?=(?:.*?[A-Z]){${policy.UppercaseLength},})`;
if (policy.LowercaseLength > 0) pattern += `(?=(?:.*?[a-z]){${policy.LowercaseLength},})`;
if (policy.NonAlphaLength > 0) pattern += `(?=(?:.*?[!@#$%^&*()_+-={}\[\]\|;:'",.<>/?`~]){${policy.NonAlphaLength},})`;
if (policy.NumericLength > 0) pattern += `(?=(?:.*?\d){${policy.NumericLength},})`;
if (policy.AlphaLength > 0) pattern += `(?=(?:.*?[A-Za-z]){${policy.AlphaLength},})`;
pattern = `^${pattern}.{${policy.MinimumLength},${policy.MaximumLength}}$`;
return new RegExp(pattern);
}
const policy = {
"MinimumLength": 8,
"MaximumLength": 20,
"UppercaseLength": 0,
"LowercaseLength": 0,
"NonAlphaLength": 0,
"NumericLength": 1,
"AlphaLength": 1
};
const re = generateRegexExpression(policy);
// Test some inputs
for (const password of [
"aBcDeFgH1", // SUCCESS
"aBcDeFgH", // FAIL -- does not satisfy NumericLength
"aBcDeF1", // FAIL -- MinimumLength
"1abcDEFGhijklmNOPQrstuvwxyz", // FAIL -- MaximumLength
"12345678" // FAIL -- AlphaLength
]) {
console.log(password + " => " + re.test(password));
}