使用正则表达式匹配文本中由引号包围的字符串 C#

最近我有一个想法,制作一个小型解释器,因为需要分析代码,所以我去学了点正则表达式。
当我学成归来,开始动手时,我遇到的第一个问题就难倒我了——字符串的匹配

代码中要用到字符串常量,所以需要匹配像C#这样的,用引号包围字符串,我的第一个想法是,字符串的首位和末尾都是一个引号,只要匹配这两个符号就行了,然后我的第一个代码是这样的:

//字符串(无转义) "hello world"
var str = "\"hello world\"";
//最终匹配的结果为"hello world"
var mathc = Regex.Match(str, "\".*\"");

直接匹配首尾的双引号,当然上文用来测试的str变量的匹配是没问题的,但是,当代码中出现多个引号时,就会出现问题了:

//字符串(无转义) "hello","world"
var str = "\"hello\",\"world\"";
//预想的结果是匹配两个字符串,"hello"和"world"
var mathces = Regex.Matches(str, "\".*\"");

预想匹配str的结果是"hello""world"两个字符串,但是当打印匹配的结果时,却只匹配了一个字符串,即"hello","world"。匹配并没有在中间逗号处隔开,而是将中间的引号一并包括在同一个字符串内,也就是“黏在一起”了,这样的字符串在C#的代码中是不会出现的。

这是因为正则表达式的贪婪模式(各翻译可能不同)会尽可能匹配多的字符串。
这个问题很快就被我解决了,只需要在正则表达式字符串中的‘*’符号后面加上一个‘?’以关闭贪婪模式,这样便能保证在匹配时不会去尽可能匹配多的字符串而导致出现字符串黏在一起的问题:

var str = "\"hello\",\"world\"";
//此处用于匹配的字符串"\".?\""和原来相比在'*'号后面多了一个问号 表示关闭了贪婪模式
//此时匹配的结果与预想的结果相同,是 "hello"和"world"两个字符串
var mathces = Regex.Matches(str, "\".*?\"");

解决了贪婪模式导致的字符串黏在一起的问题后,接下来又出现了一个新的问题:当字符串中出现了转义的引号,那么匹配将会被这个引号影响:

//字符串(无转义) A:"say:\"hello world\"", B:"say:\"something\""
var str = "A:\"say:\\\"hello world\\\"\", B:\"say:\\\"something\\\"\"";
//预想的匹配结果是"say:\"hello world\""与"say:\"something\""
var mathces = Regex.Matches(str, "\".*?\"");

但事实上如果匹配上文代码中的字符串str的话,会出现4个莫名其妙的结果:
此为控制台消息截图
可以看到,正则表达式在遇到的第一个引号处就结束了匹配——即使这个引号在字符串中是一个转义的存在。如果在C#的代码中,这个情况是不会存在的,转义的引号 任何转义字符都将被视为字符串内的内容。

事实上这个问题我研究了很久,我的第一个解决方法是:将正则匹配模式中的"."匹配符换成 "(\\\\"|.)",即:

var str = "A:\"say:\\\"hello world\\\"\", B:\"say:\\\"something\\\"\"";
//将匹配模式中的"."换成"(\\\\\"|.)"
//最终的结果将和预想的一样,匹配到了字符串"say:\"hello world\""以及"say:\"something\""
//匹配不会在转义的引号位置被结束
var mathces = Regex.Matches(str, "\"(\\\\\"|.)*?\"");

这样便能够解决匹配过程中在转义的引号处完成匹配的问题,因为在正则表达式对于字符串内容的匹配中,会优先将带有反斜杠的引号,将带有转义符的引号匹配到字符串的内容中,即将转义的引号归类到字符串的内容中而不是作为字符串的结尾,但这样子之后一个新问题又来了,假设匹配遇到了这样的字符串:

//字符串(无转义) "hello\\", "world"
var str = "\"hello\\\\\", \"world\"";
//预想的匹配结果为两个字符串 "hello\\"和 "world"
var mathces = Regex.Matches(str, "\"(\\\\\"|.)*?\"");

可以看到,在字符串的hello的位置后面是一个转义的反斜杠,紧跟着的是一个没有转义的,用于包围字符串的引号。但如果匹配str的话,正则表达式会略过hello\末尾的引号,而选择匹配world前面的引号作为字符串末尾的引号。因为在正则表达式看来,hello\末尾的引号的前面有一个反斜杠转义符——即使这是一个转义过后的反斜杠。

为了解决这个问题,我想了很久,我上网查了很多方法,但大多数的方法都和我上文中的"\"\.*?""匹配一样,无法匹配转义的字符,在我苦思多日(真的是多日,因为我脑子不开窍)无果后,我放弃了正则表达式选择了循环遍历字符串:

        var list = new List<string>();
        var str = "\"hello\\\\\", \"world\"";
        for (int i = 0; i < str.Length; i++)
        {
            if (str[i] == '"')
            {
                var start = i;
                for (i++; i < str.Length; i++)
                {
                    //在遇到转义字符(反斜杠)时
                    //将下一个字符视作转义字符 索引位置推移 不处理
                    if (str[i] == '\\') i++;
                    else if (str[i] == '"') break;
                }
                var length = i - start + 1;
                list.Add(str.Substring(start, length));
            }
        }

在索引位置遇到转义字符(反斜杠)时,将索引位置的下一个字符视作转义字符,索引位置向前推移,不处理,这样可以避免因为检查到转义的引号而导致字符串结束,也不会因为检查到转义的反斜杠而误判其他字符。

等等,标题不是说使用正则表达式匹配字符串吗?
没错,当我写完上文代码后,我的脑子突然开窍了,我不必执着于去判断引号是否是为转义的引号,而是直接去匹配任何转义的字符即可。将之前的匹配模式中的"\""更改为".",即:

var str = "\"hello\\\\\", \"world\"";
//与上上文对比,将括号的第一个匹配模式中的"\""改为"."
//最终的匹配结果与预想的一样 匹配到了 "hello\\"和"world"
var matches = Regex.Matches(str, "\"(\\\\.|.)*?\"");

使用上文的匹配模式,就能够匹配匹配字符串而不会因为转义的缘故而出现意料之外的匹配。这和正则表达式的匹配原理有关,当正则表达式在匹配过程中匹配到了转义字符反斜杠,则连同这个转义字符与下一个任意字符(换行符除外)一同匹配到字符串的内容中吗,而正则表达式匹配过的内容不会再匹配,所以不会出现上文中将已经转义过后的反斜杠认作转义符,而忽略用来结束字符串的引号

(文中用到的Regex类位于System.dll库内,需要引用命名空间System.Text.RegularExpressions)

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值