我有一个奇怪的问题,我试图用一些优雅的正则表达式解决。
我正在处理的系统最初设计为接受传入的字符串,并通过模式匹配方法,改变它返回的字符串。一个非常简单的例子是:
传入字符串:
The dog & I went to the park and had a great time...
传出字符串:
The dog {&} I went to the park and had a great time {...}
标点符号映射器包装关键字符或短语,并用大括号包装它们。最初的实现是单向的,并且从来没有意味着它当前是如何被应用的,因此,如果它被错误地调用,系统很容易“双”包裹一个字符串,因为它只是在做简单的字符串替换。
我今天早上旋转了Regex Hero,并开始进行一些模式匹配,并且近一年没有写出正则表达式,很快就撞上了墙。
我的第一个想法是匹配一个角色(即&
),但前提是它没有用括号括起来并提出[^\{]&[^\}]
,这很棒,但当然会抓住任何一个&符号只要它没有大括号,包括空格,并且在背靠背有两个&符号的情况下不起作用(即&&
在传出中需要{&}{&}
为了使问题更复杂,它并不总是单个字符,因为省略号(...
)也是映射值之一。
我面临的每一个解决方案都遇到障碍,因为字符串中存在未知数量的特定值,或者捕获组要么过于贪婪,要么最终不能反复补偿多个值(即单个句点.
vs省略号...
),原始开发人员首先处理省略号,覆盖字符串替换实现中的句点。
那里有没有关于我如何能够检测字符串中未修饰(未包装)值然后以不同方式执行替换的任何正则表达式专家来处理多个重复字符?
我正在使用的数据源是一个简单的键值对,它包含要搜索的值和要替换它的值。
更新了示例字符串:
未修饰:
Show Details...
Default Server:
"Smart" 2-Way
Show Lender's Information
Black & White
饰:
Show Details{...}
Default Server{:}
{"}Smart{"} 2-Way
Show Lender{'}s Information
Black {&} White
更新了更多具体示例和数据源
数据源(SQL表,可以随时增长):
TaggedValue UntaggedValue
{:}:
断字符串: This is a string that already has stuff {&} other stuff{!} and {...} with {_} and {@} as well{.} and here are the same characters without it & follow by ! and ... _ & . &&&
需要修饰的字符串: Show Details... Default Server: "Smart" 2-Way Show Lender's Information Black & White
通过该方法未触及的字符串(因为它已经过装饰): The dog {&} I went to the park and had a great time {...}
转向正则表达式的另一个“问题”是需要处理转义,尤其是反斜杠,因为它们在正则表达式中的功能。
更新了@Ethan Brown的输出
@Ethan Brown,
我开始认为正则表达式虽然优雅可能不是这里的方式。您提供的更新代码虽然更接近但仍未产生正确的结果,并且涉及的变量数量可能超过正则表达式逻辑功能。
使用上面的示例:
'This is a string that already has stuff {&} other stuff{!} and {...} with {_} and {@} as well{.} and here are the same characters without it & follow by ! and ... _ & . &&&'
产量
This is a string that already has stuff {&} other stuff{!} and {...} with {_} and {@} as well{.} and here are the same characters without it {&} follow by {!} and {...} {_} {&} . {&&}&
最后一组&符号{&} {&} {&}实际上出现在{&&}&}的位置。
这里有很多变化(即需要处理来自远东语言的省略号和宽省略号),并且需要利用数据库作为数据源是最重要的。
我想我只是要写一个自定义评估器,我可以很容易地编写它来执行这种类型的验证并暂时搁置正则表达式路由。一旦我走到桌面浏览器前,我会认可您的答案和工作。
答案 0 :(得分:1)
这种问题真的很难,但是让我给你一些可能会有所帮助的想法。真正令你头疼的一件事是处理标点符号出现在字符串开头或结尾的情况。当然,使用类似(^|[^{])&($|[^}])
的构造可以处理正则表达式,但除了难以阅读之外,它还存在效率问题。但是,有一种简单的方法可以“欺骗”并解决这个问题:只需用两端的空格填充输入字符串:
var input = " " + originalInput + " ";
当你完成后,你可以修剪。当然,如果你关心在开头或结尾保留输入,你必须更加聪明,但我会假设你不这样做。
现在谈谈问题的关键。当然,我们可以提出一些精心设计的正则表达式来完成我们正在寻找的东西,但是如果你使用多个正则表达式,答案往往要简单得多。
由于您已使用更多字符和更多问题输入更新了答案,因此我已将此答案更新为更灵活:希望随着更多字符的添加,它会更好地满足您的需求。
查看输入空间和需要引用的表达式,实际上有三种情况:
由于期间包含在单字符替换中,因此订单很重要:如果您先替换所有句点,那么您将错过省略号。
因为我发现C#正则表达式库有点笨重,所以我使用以下扩展方法使它更“流畅”:
public static class StringExtensions {
public static string RegexReplace( this string s, string regex, string replacement ) {
return Regex.Replace( s, regex, replacement );
}
}
现在我可以涵盖所有案例:
// putting this into a const will make it easier to add new
// characters in the future
const string normalQuotedChars = @"\!_\\:&<\$'>""%:`";
var output = s
.RegexReplace( "(?<=[^{])\\.\\.\\.(?=[^}])", "{$&}" )
.RegexReplace( "(?<=[^{])[" + normalQuotedChars + "](?=[^}])", "{$&}" )
.RegexReplace( "\\\\", "{}" );
让我们打破这个解决方案:
首先我们处理省略号(这将使我们在以后的句点中遇到麻烦)。请注意,我们在表达式的开头和结尾使用zero-width assertions来排除已引用的表达式。零宽度断言是必要的,因为没有它们,我们就会遇到与引用字符紧挨着的问题。例如,如果您具有正则表达式([^{])!([^}])
,并且输入字符串为foo !! bar
,则匹配将包括第一个感叹号之前的空格和第二个感叹号。因此,$1!$2
的天真替换将产生foo {!}! bar
,因为第二个感叹号将作为匹配的一部分被消耗。您必须最终进行详尽的匹配,并且使用零宽度断言要容易得多,而这些断言不会消耗掉。
然后我们处理所有正常引用的字符。请注意,我们在此使用零宽度断言的原因与上述相同。
最后,我们可以找到单独的斜杠(注意我们必须两次转义它:一次用于C#字符串,再用于正则表达式元字符)并用空的大括号替换它。
我通过这一系列匹配运行了所有测试用例(以及我自己的一些发明),这一切都按预期工作。
答案 1 :(得分:0)
我不是正则表达的上帝,所以一个简单的方法:
答案 2 :(得分:0)
忽略原始输入字符串具有{
或}
字符的情况,避免将正则表达式重新应用于已经转义的字符串的常用方法是查找转义序列和在将正则表达式应用于余数之前将其从字符串中删除。这是一个示例正则表达式,用于查找已经转义的内容:
Regex escapedPattern = new Regex(@"\{[^{}]*\}"); // consider adding RegexOptions.Compiled
这种负面角色类模式的基本思想来自regular-expressions.info,这是一个非常有用的网站,适用于所有正则表达式。该模式有效,因为对于任何最内部的大括号,必须有{
后跟非{}
,然后是}
在输入字符串上运行escapedPattern
,查找每个Match
获取原始字符串中的开始和结束索引并将其子串出来,然后使用最终清理后的字符串再次运行原始模式匹配或使用以下内容:
Regex punctPattern = new Regex(@"[^\w\d\s]+"); // this assumes all non-word,
// digit or space chars are punctuation, which may not be a correct
//assumption
并为每个匹配替换Match.Groups[1].Value
(组是基于0的数组,其中0是整个匹配,1是第一组括号,2是下一个等),"{" + Match.Groups[1].Value + "}"