【问题标题】:Regex - Escape escape characters正则表达式 - 转义转义字符
【发布时间】:2014-07-11 09:21:14
【问题描述】:

我的问题很复杂,但可以归结为一个简单的例子。

我正在编写一种自定义查询语言,用户可以在其中输入我解析为 LinQ 表达式的字符串。

我想做的是用* 字符分割字符串,除非它被正确转义。

Input         Output                          Query Description
"*\\*"    --> { "*", "\\", "*" }       -- contains a '\'
"*\\\**"  --> { "*", "\\\*", "*" }     -- contains '\*'
"*\**"    --> { "*", "\*", "*" }       -- contains '*' (works now)

我不介意 Regex.Split 返回空字符串,但我最终得到了这个:

Regex.Split(@"*\\*", @"(?<!\\)(\*)")  --> {"", "*", "\\*"}

如您所见,我尝试了消极的后视,它适用于我的所有情况,除了这个。我也试过Regex.Escape,但没有运气。

显然,我的问题是我正在寻找 \*,与 \\* 匹配。但在这种情况下, \\ 是另一个转义序列。

任何解决方案都不必涉及正则表达式。

【问题讨论】:

  • +1 将问题的本质提炼成一个很好的问题。
  • 在本例中,您可以匹配 (\*)(\\\\)(\*) 之类的内容以在匹配中获得三个组。这是在现实世界场景中使用拆分的可接受替代方案吗?
  • 你能告诉我们一些不同的情况以及如何解析它们吗?所以我们不会搞砸你原来的(和有效的)解决方案?
  • 我已经用另一个例子更新了这个问题。
  • 如果你想用(未转义的)* 字符分割字符串,你的例子#2不应该是这样吗? "*\\\**" --&gt; { "*", "\\\*", "*" }

标签: c# regex


【解决方案1】:

我认为匹配比拆分容易得多,尤其是因为您没有从初始字符串中删除任何内容。那么要搭配什么呢?除了未转义的 * 之外的所有内容。

如何做到这一点?使用以下正则表达式:

@"(?:[^*\\]+|\\.)+|\*"

(?:[^*\\]+|\\.)+ 匹配不是* 或任何转义字符的所有内容。无需任何环顾。

\* 将匹配分隔符。

在代码中:

using System;
using System.Text.RegularExpressions;
using System.Linq;
public class Test
{
    public static void Main()
    {   
        string[] tests = new string[]{
            @"*\\*",
            @"*\\\**",
            @"*\**",
        };

        Regex re = new Regex(@"(?:[^*\\]+|\\.)+|\*");

        foreach (string s in tests) {
            var parts = re.Matches(s)
             .OfType<Match>()
             .Select(m => m.Value)
             .ToList();

            Console.WriteLine(string.Join(", ", parts.ToArray()));
        }
    }
}

输出:

*, \\, *
*, \\\*, *
*, \*, *

ideone demo

【讨论】:

  • 两个答案都有效,但是,考虑到我离开后将维护此代码的可怜草皮:较短的正则表达式获胜:D
  • 不错的解决方案! @TroelsLarsen - 可以稍微缩短为 @"(?:[^*\\]|\\.)+|\*"。外部+ 使内部+ 变得多余。
  • 有点。 @groverboy 让你的速度略有下降
【解决方案2】:

我想出了这个正则表达式(?&lt;=(?:^|[^\\])(?:\\\\)*)(\*)

说明:

您只需将* 之前可能发生的情况列入白名单,这些情况是:

  • 字符串的开头^
  • 不是\ - [^\\]
  • (不是\ 或字符串的开头)然后是偶数个\ - (^|[^\\])(\\\\)*

测试代码及示例:

string[] tests = new string[]{
    @"*\\*",
    @"*\\\**",
    @"*\**",
    @"test\**test2",
};

Regex re = new Regex(@"(?<=(?:^|[^\\])(?:\\\\)*)(\*)");

foreach (string s in tests) {
    string[] m = re.Split( s );
    Console.WriteLine(String.Format("{0,-20} {1}", s, String.Join(", ",
       m.Where(x => !String.IsNullOrEmpty(x)))));
}

结果:

*\\*                 *, \\, *
*\\\**               *, \\\*, *
*\**                 *, \*, *
test\**test2         test\*, *, test2

【讨论】:

  • 现在在我稍微复杂的环境中进行测试。通过了 36/36 个测试用例。非常感谢你!我相信我会发现更多例外,但这个答案帮助我能够自己修复它们。
【解决方案3】:

我认为一个纯解析、非正则表达式的解决方案将是对这个问题的一个很好的补充。

我阅读这篇文章的速度明显快于我理解任何这些正则表达式的速度。这也使得修复意外的角落案例变得容易。逻辑直接布局。

public static String[] splitOnDelimiterWithEscape(String toSplit, char delimiter, char escape) {
    List<String> strings = new ArrayList<>();

    char[] chars = toSplit.toCharArray();
    String sub = "";

    for(int i = 0 ; i < chars.length ; i++) {
        if(chars[i] == escape) {
            sub += (i+1 < chars.length) ? chars[++i] : ""; //assign whatever char is after the escape to the string. This essentially makes single escape character non-existent. It just forces the next character to be literal. If the escape is at end, then we just ignore it

            //this is the simplest implementation of the escape. If escaping certain characters should have
            //special behaviour it should be implemented here.

            //You could even pass a Map mapping escape characters, to literal characters to make this even 
            //more general.

        } else if(chars[i] == delimiter) {
            strings.add(sub); //Found delimiter. So we split.
            sub = "";
        } else {
            sub += chars[i]; //nothing special. Just append to current string.
        }
    }

    strings.add(sub); //end of string is a boundary. Must include.

    return strings.toArray(new String[strings.size()]);
}

更新:我现在对这个问题有点困惑。正如我一直知道的那样,拆分不包括分隔符(但看起来像您的示例一样)。如果您希望分隔符存在于数组中,在它们自己的插槽中,那么对此的修改相当简单。 (我将把它作为练习留给读者作为代码可维护性的证据)

【讨论】:

  • 这也让我感到惊讶。 Regex.Split 会,而 String.Split 不会。有点奇怪。就我而言,我不需要 *
  • @TroelsLarsen 这个解决方案对你有意义吗?
  • 感谢您的建议。是的,我理解这个想法但是,它没有返回正确的结果,而是`, *, *`而不是\`, \\*, *`。它似乎去掉了转义字符,我稍后需要实际执行查询。这可能是我在 .NET 中快速实现的一个问题。
  • @TroelsLarsen 转义序列的一般概念是,如果您想要实际的转义字符,则需要转义自身。在这里仍然有效。无论如何,这很容易编辑
  • @TroelsLarsen 正则表达式拆分确实是因为您将 * 包装在括号之间。否则你不会得到它。我以为您需要所需输出中的星号。如果你真的不需要它,那么我的正则表达式会变得更短!
猜你喜欢
  • 2020-02-05
  • 2010-12-22
  • 2010-09-21
  • 2020-07-24
  • 1970-01-01
相关资源
最近更新 更多