匹配标准和离群值输入的正则表达式
Regex to match standard and outlier input
我使用正则表达式已经有一段时间了 jQuery validation to ensure my users input valid strings for drawing names. In the past week we've added the ability to use 3rd-party devices whose strings are somewhat different. I'm tasked with allowing those strings as well as the previous set as valid input. I send them to this validator adapted from this SO answer:
$.validator.addMethod("accept", function (value, element, param)
{
return value.match(new RegExp("^" + param + "$"));
});
请注意,我正在添加 ^
和 $
个字符。
像这样:
drawingName: {
required: true,
accept: "[0-9]{4,5}[\.?\w*]{0,4}"
},
双反斜杠转义单反斜杠以供在验证器中使用。如果您在 http://www.rubular.com/ 之类的地方进行测试,您需要使用单反斜杠。
上一组(我为此做了通用表示,其中 "X" 代表字母,“0”代表数字,小数点是它们的原样)由以下有效可能性组成:
00000
00000.0
00000.00
00000.X
000000
000000X
00000X
00000X.0
00000X.0X
00000X0.0
00000XX.0
0000X.0
我的数据中有数万个这样的变体,不可能更改它们。公司正在努力制定标准化的命名法,但我们卖给客户的设备可以回来维修、维护和校准几十年:所以我们永远无法摆脱旧系统。
新的字符串变体如下所示:
XXX-0000
XXX-00000
XXX-000000
我修改了原始正则表达式以适应更改:[\w{3}-]*\d{4,5}[\.?\w*]{0,4}
我也试过 \p{L}{3}?-?\d{4,6}\.?\w{0,2}\.?\w{0,2}
,但出现了同样的问题(见下文)。
两者都有效,但在我的测试中,我注意到它们允许将看似无限数量的额外字符添加到有效可能性的末尾。 (我很确定旧的正则表达式允许相同类型的错误输入。)
因此,要捕获新字符串,我需要查找三个字母后跟一个破折号,再后跟四到六个数字(类似这样:[\w{3}-]?\d{4,6}?
或 [/p{L}{3}-]?\d{4,6}?
)...并且还包含以前的绘图名称,四到五个数字可能后跟一个字母、数字或一个小数点,可能后跟一个字母或数字,可能后跟一个数字或字母(令人困惑,嗯?)——像这样:\d{4,5}[\.\w*]{0,4}
我认为这部分的问题在于 w
后面的星号,但我不确定如何修复它或正确连接两个不同的部分正则表达式在一起。
我正在寻找的是一个单一的正则表达式,它允许我使用上面的所有字符串变体来筛选有效输入,但阻止无效输入。我知道我可以简单地添加另一个验证规则,这可能是我必须做的,但我想看看是否可以在一个正则表达式中完成它。
编辑:
这是我在我的代码中使用的 Lucas 建议的最终解决方案,经过一些修改以不使用 \w
,正如他在下面的回答中指出的那样:
(?:\d{4,5}[0-9a-zA-Z]{0,2}(?:\.[0-9a-zA-Z]{1,2})?|[a-zA-Z]{3}-\d{6})
你可以这样做:
^(?:\d{4,5}\w{0,2}(?:\.\w{1,2})?|\w{3}-\d{6})$
我刚刚使用了 替代运算符 (|
) 在旧格式和新格式之间进行拆分。
请注意,您的原始正则表达式 ([0-9]{4,5}[\.?\w*]{0,4}
) 可能 有问题:[\.?\w*]
表示 .
或 ?
或一个 字字符 或 *
,这似乎不是你想要的。我根据您的示例使其更加严格,但您可能需要对其进行调整。
此外,请注意 \w
在 JS 中的意思是 [0-9a-zA-Z_]
- 这可能不是您想要的(尤其是下划线)。
单个正则表达式
这是一个简单的(相对于您的问题的规模)单个正则表达式,它解释了所提供的所有样本输入——但不 "allow a seemingly infinite number of extra characters to be tacked onto the end of the valid possibilities":
^(\d{4}(\d([\dA-Z]|(\.(\d{1,2}|[A-Z]))|\d[A-Z]|[A-Z](\.\d[A-Z]?|[\dA-Z]\.\d))?|[A-Z]\.\d)|[A-Z]{3}-\d{4,6})$
下面是使用提供的示例输入测试正则表达式的片段:
var regex = new RegExp("^(\d{4}(\d([\dA-Z]|(\.(\d{1,2}|[A-Z]))|\d[A-Z]|[A-Z](\.\d[A-Z]?|[\dA-Z]\.\d))?|[A-Z]\.\d)|[A-Z]{3}-\d{4,6})$");
// tests for the sample inputs provided that are all expected to match
var tests = [
'12345',
'12345.6',
'12345.67',
'12345.A',
'123456',
'123456A',
'12345A',
'12345A.6',
'12345A.6B',
'12345A6.7',
'12345AB.6',
'1234A.5',
'ABC-1234',
'ABC-12345',
'ABC-123456'
];
for (var i = 0; i < tests.length; i++) {
var result = "'" + tests[i] + "' => ";
if (regex.test(tests[i])) {
result += 'Yup!';
} else {
result += 'Nope...';
}
console.log(result);
}
理由
显然 top-level 交替 (|
) 是 old-variants/new-variant 分裂的关键;但我认为旧变体的 \d{4,5}
(特别是 {4,5}
量词)是过度匹配问题的根源。
而是从 所有旧变体的共同之处开始 – 四个开位数字(即 \d{4}
):这样您就可以直接使用 follow-on, 内部交替以匹配旧变体的前四个字符之外的差异。
详情
结果如下:
^
– 输入字符串的开始
(
– 旧变体和新变体的一级交替开始
\d{4}
– 所有旧变体开头的四位数字
(
– 旧变体主要分歧的二级交替开始
\d
– 第五个数字
(
– 可选分歧的第 3 级交替开始,其中前五个字符是数字
[\dA-Z]
– 一个数字或字母
|
– 或(3级)
(\.(\d{1,2}|[A-Z]))
– 一个句点和 1) 一个或两个数字 OR 2)一封信
|
– 或(3级)
\d[A-Z]
– 一个数字和一个字母
|
– 或(3级)
[A-Z](\.\d[A-Z]?|[\dA-Z]\.\d)
– 一个字母和 1) 一个句点、一个数字和可选的字母 OR 2)一个数字或字母,一个句点,一个数字
)?
– 前五个字符为数字的可选分歧的第三级交替结束
|
– 或(二级)
[A-Z]\.\d
– 一个字母、一个句点和一个数字
)
– 旧变体主要分歧的二级交替结束
|
– 或(一级)
[A-Z]{3}-\d{4,6}
– 新变体
)
– 旧变体和新变体的一级交替结束
$
- 输入字符串的结尾
上面的 Debuggex 图形以图表方式解释了所有这些,但是(其中一些)可能更容易理解。
可维护性
使用一个正则表达式来处理如此多的变体自然会给可维护性带来挑战。
支持可维护性:
- 像我在上面的详细信息细分中显示的那样,将子模式分配给不同的字符串变量。
- 然后通过连接子模式的字符串构建单一模式。
这样做的好处是允许您评论每个子模式的字符串变量(就像我在 详细信息 细分中评论每个项目符号)。
我使用正则表达式已经有一段时间了 jQuery validation to ensure my users input valid strings for drawing names. In the past week we've added the ability to use 3rd-party devices whose strings are somewhat different. I'm tasked with allowing those strings as well as the previous set as valid input. I send them to this validator adapted from this SO answer:
$.validator.addMethod("accept", function (value, element, param)
{
return value.match(new RegExp("^" + param + "$"));
});
请注意,我正在添加 ^
和 $
个字符。
像这样:
drawingName: {
required: true,
accept: "[0-9]{4,5}[\.?\w*]{0,4}"
},
双反斜杠转义单反斜杠以供在验证器中使用。如果您在 http://www.rubular.com/ 之类的地方进行测试,您需要使用单反斜杠。
上一组(我为此做了通用表示,其中 "X" 代表字母,“0”代表数字,小数点是它们的原样)由以下有效可能性组成:
00000
00000.0
00000.00
00000.X
000000
000000X
00000X
00000X.0
00000X.0X
00000X0.0
00000XX.0
0000X.0
我的数据中有数万个这样的变体,不可能更改它们。公司正在努力制定标准化的命名法,但我们卖给客户的设备可以回来维修、维护和校准几十年:所以我们永远无法摆脱旧系统。
新的字符串变体如下所示:
XXX-0000
XXX-00000
XXX-000000
我修改了原始正则表达式以适应更改:[\w{3}-]*\d{4,5}[\.?\w*]{0,4}
我也试过 \p{L}{3}?-?\d{4,6}\.?\w{0,2}\.?\w{0,2}
,但出现了同样的问题(见下文)。
两者都有效,但在我的测试中,我注意到它们允许将看似无限数量的额外字符添加到有效可能性的末尾。 (我很确定旧的正则表达式允许相同类型的错误输入。)
因此,要捕获新字符串,我需要查找三个字母后跟一个破折号,再后跟四到六个数字(类似这样:[\w{3}-]?\d{4,6}?
或 [/p{L}{3}-]?\d{4,6}?
)...并且还包含以前的绘图名称,四到五个数字可能后跟一个字母、数字或一个小数点,可能后跟一个字母或数字,可能后跟一个数字或字母(令人困惑,嗯?)——像这样:\d{4,5}[\.\w*]{0,4}
我认为这部分的问题在于 w
后面的星号,但我不确定如何修复它或正确连接两个不同的部分正则表达式在一起。
我正在寻找的是一个单一的正则表达式,它允许我使用上面的所有字符串变体来筛选有效输入,但阻止无效输入。我知道我可以简单地添加另一个验证规则,这可能是我必须做的,但我想看看是否可以在一个正则表达式中完成它。
编辑:
这是我在我的代码中使用的 Lucas 建议的最终解决方案,经过一些修改以不使用 \w
,正如他在下面的回答中指出的那样:
(?:\d{4,5}[0-9a-zA-Z]{0,2}(?:\.[0-9a-zA-Z]{1,2})?|[a-zA-Z]{3}-\d{6})
你可以这样做:
^(?:\d{4,5}\w{0,2}(?:\.\w{1,2})?|\w{3}-\d{6})$
我刚刚使用了 替代运算符 (|
) 在旧格式和新格式之间进行拆分。
请注意,您的原始正则表达式 ([0-9]{4,5}[\.?\w*]{0,4}
) 可能 有问题:[\.?\w*]
表示 .
或 ?
或一个 字字符 或 *
,这似乎不是你想要的。我根据您的示例使其更加严格,但您可能需要对其进行调整。
此外,请注意 \w
在 JS 中的意思是 [0-9a-zA-Z_]
- 这可能不是您想要的(尤其是下划线)。
单个正则表达式
这是一个简单的(相对于您的问题的规模)单个正则表达式,它解释了所提供的所有样本输入——但不 "allow a seemingly infinite number of extra characters to be tacked onto the end of the valid possibilities":
^(\d{4}(\d([\dA-Z]|(\.(\d{1,2}|[A-Z]))|\d[A-Z]|[A-Z](\.\d[A-Z]?|[\dA-Z]\.\d))?|[A-Z]\.\d)|[A-Z]{3}-\d{4,6})$
下面是使用提供的示例输入测试正则表达式的片段:
var regex = new RegExp("^(\d{4}(\d([\dA-Z]|(\.(\d{1,2}|[A-Z]))|\d[A-Z]|[A-Z](\.\d[A-Z]?|[\dA-Z]\.\d))?|[A-Z]\.\d)|[A-Z]{3}-\d{4,6})$");
// tests for the sample inputs provided that are all expected to match
var tests = [
'12345',
'12345.6',
'12345.67',
'12345.A',
'123456',
'123456A',
'12345A',
'12345A.6',
'12345A.6B',
'12345A6.7',
'12345AB.6',
'1234A.5',
'ABC-1234',
'ABC-12345',
'ABC-123456'
];
for (var i = 0; i < tests.length; i++) {
var result = "'" + tests[i] + "' => ";
if (regex.test(tests[i])) {
result += 'Yup!';
} else {
result += 'Nope...';
}
console.log(result);
}
理由
显然 top-level 交替 (|
) 是 old-variants/new-variant 分裂的关键;但我认为旧变体的 \d{4,5}
(特别是 {4,5}
量词)是过度匹配问题的根源。
而是从 所有旧变体的共同之处开始 – 四个开位数字(即 \d{4}
):这样您就可以直接使用 follow-on, 内部交替以匹配旧变体的前四个字符之外的差异。
详情
结果如下:
^
– 输入字符串的开始(
– 旧变体和新变体的一级交替开始\d{4}
– 所有旧变体开头的四位数字(
– 旧变体主要分歧的二级交替开始\d
– 第五个数字(
– 可选分歧的第 3 级交替开始,其中前五个字符是数字[\dA-Z]
– 一个数字或字母|
– 或(3级)(\.(\d{1,2}|[A-Z]))
– 一个句点和 1) 一个或两个数字 OR 2)一封信|
– 或(3级)\d[A-Z]
– 一个数字和一个字母|
– 或(3级)[A-Z](\.\d[A-Z]?|[\dA-Z]\.\d)
– 一个字母和 1) 一个句点、一个数字和可选的字母 OR 2)一个数字或字母,一个句点,一个数字)?
– 前五个字符为数字的可选分歧的第三级交替结束
|
– 或(二级)[A-Z]\.\d
– 一个字母、一个句点和一个数字)
– 旧变体主要分歧的二级交替结束
|
– 或(一级)[A-Z]{3}-\d{4,6}
– 新变体)
– 旧变体和新变体的一级交替结束$
- 输入字符串的结尾
上面的 Debuggex 图形以图表方式解释了所有这些,但是(其中一些)可能更容易理解。
可维护性
使用一个正则表达式来处理如此多的变体自然会给可维护性带来挑战。
支持可维护性:
- 像我在上面的详细信息细分中显示的那样,将子模式分配给不同的字符串变量。
- 然后通过连接子模式的字符串构建单一模式。
这样做的好处是允许您评论每个子模式的字符串变量(就像我在 详细信息 细分中评论每个项目符号)。