正则表达式:否定角色类中的负面看法? (.NET风味)

时间:2010-02-01 21:28:08

标签: .net regex lookaround

我正在尝试做的事情:删除围绕特定未转义角色的最内层非转义方括号(\是逃脱)

输入:[\[x\]]\]\[[\[y\]]
在y周围寻找括号时的输出:[\[x\]]\]\[\[y\]
在x:\[x\]\]\[[\[y\]]

周围寻找括号时输出

简而言之,只删除特定字符周围的非转义括号。

我尝试了这个(对于y):Regex.Replace(input, @"(?<!\\)\[(.*?(?<!\\)y.*?)(?<!\\)\]",@"$1",但这似乎与第一个未转义的[(在x之前)与最后一个]相匹配。我想我可以用一个否定字符类替换.通配符来排除[],但我真正需要否定的是未转义版本的这些,当我试图在否定角色类中加入像(?<!\\)这样的负面后视时,我似乎根本没有任何匹配。

提前感谢您的时间和精力。

编辑:

为了澄清,未转义的方括号的内容可以是任何内容(除了另一个未转义的方括号),只要它们包含感兴趣的未转义字符(y)。括号的所有内容都应保留。

3 个答案:

答案 0 :(得分:2)

为此问题编写正则表达式可能过于复杂。虽然这个功能有点冗长,但它在概念上很简单,并且可以解决问题:

    string FixString(char x, string original)
    {
        int i = 0;
        string s = original;
        while (i < s.Length)
        {
            if (s[i] == x)
            {
                bool found = false;
                for (int j = i + 1; (j < s.Length) && !found; j++)
                {
                    if ((s[j] == ']') &&
                        (s[j-1] != '\\'))
                    {
                        s = s.Remove(j, 1);
                        found = true;
                    }
                }
                if (i > 0)
                {
                    found = false;
                    for (int j = i - 1; (j >= 0) && !found; j--)
                    {
                        if ((s[j] == '[') &&
                            ( (j == 0) ||
                              (s[j - 1] != '\\') ))
                        {
                            s = s.Remove(j, 1);
                            i--;
                            found = true;
                        }
                    }
                }
            }
            i++;
        }

        return s;
    }

答案 1 :(得分:2)

Lookbehind是这项工作的错误工具。试试这个:

Regex r = new Regex(
  @"\[((?>(?:[^y\[\]\\]|\\.)*)y(?>(?:[^\[\]\\]|\\.)*))\]");

string s1 = @"[\[x\]]\]\[[\[y\]]";
Console.WriteLine(s1);
Console.WriteLine(r.Replace(s1, @"%$1%"));

Console.WriteLine();

string s2 = @"[\[x\]]\]\[[1234(\[abcycba\]\y\y)]";
Console.WriteLine(s2);
Console.WriteLine(r.Replace(s2, @"%$1%"));

结果:

[\[x\]]\]\[[\[y\]]
[\[x\]]\]\[%\[y\]%

[\[x\]]\]\[[1234(\[abcycba\]\y\y)]
[\[x\]]\]\[%1234(\[abcycba\]\y\y)%

(我将括号替换为%而不是删除它们,以便更容易确切地看到被替换的内容。)

(?:\\.|[^y\[\]\\])*匹配零或多个(1)反斜杠后跟任何字符,或(2)任何不是'y',方括号或反斜杠的东西。如果下一个字符是'y',它将被消耗,(?:\\.|[^\[\]\\])*匹配任何剩余的字符,直到下一个未转义的括号。在否定字符类中包含两个括号(以及反斜杠)可确保您只匹配最内层的非转义括号。

使用atomic groups - 即。(?>...)也很重要;这可以防止我们知道无用的回溯,并且当正则表达式用于不包含匹配项的字符串时,可能会导致严重的性能问题。

另一种方法是使用前瞻来断言'y'的存在,然后使用更简单的(?>(?:\\.|[^\[\]\\])*)来消耗括号之间的字符。问题是你现在正在对字符串进行两次传递,并且确保前瞻看起来太远或者不够远可能会很棘手。一次完成所有工作可以更容易地跟踪您在匹配过程的每个阶段的位置。

答案 2 :(得分:1)

在编辑问题后编辑

Regex.Replace(input, @"((?<!\\)\[(?=((\\\[)|[^[])*((?<!\\)y)))|((?<=[^\\]y((\\\]|[^]]))*)(?<!\\)\])","");

我们希望匹配要删除的括号:

(?<!\\)\[ - Match is an unescaped left bracket
(?=((\\\[)|[^[])*((?<!\\)y)) - Match is followed by any number of (escaped left brackets or non-left brackets) followed by an unescaped y

| - OR

(?<=[^\\]y((\\\]|[^]]))*) - Match is preceded by unescaped y followed by any number of (escaped right brackets or non-right brackets)
(?<!\\)\] - Match is an unescaped right bracket