.net Regex.Replace中的错误?

时间:2014-01-24 14:04:40

标签: .net regex

以下代码......

using System;
using System.Text.RegularExpressions;

public class Program
{
    public static void Main()
    {
        var r = new Regex("(.*)");
        var c = "XYZ";
        var uc = r.Replace(c, "A $1 B");

        Console.WriteLine(uc);
    }
}

.Net Fiddle Link

产生以下输出......

  

XYZ BA B

你认为这是对的吗?

输出不应该......

  

XYZ B

我想我在这里做了些蠢事。如果您能帮助我理解这个问题,我将不胜感激。


这是有趣的事情......

using System;
using System.Text.RegularExpressions;

public class Program
{
    public static void Main()
    {
        var r = new Regex("(.*)");
        var c = "XYZ";
        var uc = r.Replace(c, "$1");

        Console.WriteLine(uc);
    }
}

.Net Fiddle

...输出

  

XYZ

5 个答案:

答案 0 :(得分:6)

至于为什么引擎返回2个匹配,这是由于.NET(也是Perl和Java)处理全局匹配的方式,即在输入字符串中找到与给定模式的所有匹配。

该过程可以描述如下(当前索引通常在搜索开始时设置为0,除非指定):

  1. 从当前索引中执行搜索。
  2. 如果没有匹配:
    1. 如果当前索引已经指向字符串的末尾(当前索引> = string.length),则返回到目前为止的结果。
    2. 将当前索引增加1,转到步骤1.
  3. 如果主匹配($0)非空(至少消耗了一个字符),请添加结果并将当前索引设置为主匹配($0)的结尾。然后转到第1步。
  4. 如果主匹配($0)为空:
    1. 如果上一个匹配项为非空,请添加结果并转到步骤1.
    2. 如果上一场比赛为空,请回溯并继续搜索。
    3. 如果回溯尝试找到非空匹配,请添加结果,将当前索引设置为匹配结束并转到步骤1.
    4. 否则,将当前索引增加1.转到步骤1.
  5. 引擎需要检查空匹配;否则,它将以无限循环结束。设计师会识别空匹配的使用(例如,将字符串拆分为字符),因此必须将引擎设计为避免永远卡在某个位置。

    此过程解释了为什么在结尾处存在空匹配:因为在(.*)匹配abc(.*)之后在字符串末尾(索引3)进行搜索可以匹配空字符串,找到空匹配。并且引擎不会产生无限数量的空匹配,因为最后已经找到了空匹配。

     a b c
    ^ ^ ^ ^
    0 1 2 3
    

    第一场比赛:

     a b c
    ^     ^
    0-----3
    

    第二场比赛:

     a b c
          ^
          3
    

    使用上面的全局匹配算法,从同一个索引开始最多只能有2个匹配,这种情况只有在第一个是空匹配时才会发生。

    请注意,如果主匹配为空,JavaScript只会将当前索引增加1,因此每个索引最多匹配1个匹配项。但是,在这种情况下(.*),如果使用全局标记g进行全局匹配,则会发生相同的结果:

    (下面的结果来自Firefox,请注意g标志)

    > "XYZ".replace(/(.*)/g, "A $1 B")
    "A XYZ BA  B"
    

答案 1 :(得分:4)

我将不得不考虑为什么会这样。我相信你错过了什么。虽然这解决了这个问题。只需锚定正则表达式。

var r = new Regex("^(.*)$");

这是.NetFiddle demo

答案 2 :(得分:4)

你的正则表达式有两个匹配,替换将替换它们。第一个是“XYZ”,第二个是空字符串。我不确定的是为什么它首先有两场比赛。您可以使用^(。*)$来修复它,以强制它考虑字符串的开头和结尾。

或者使用+代替*强制它匹配至少一个字符

.*匹配空字符串,因为它没有字符。

.+与空字符串不匹配,因为它需要至少一个字符。

有趣的是,在Javascript(在Chrome中):

var r = /(.*)/;
var s = "XYZ";
console.log(s.replace(r,"A $1 B");

将输出预期的A XYZ B而没有虚假的额外匹配。

编辑(感谢@nhahtdh):但是将g标志添加到Javascript正则表达式中,给出与.NET中相同的结果:

var r = /(.*)/g;
var s = "XYZ";
console.log(s.replace(r,"A $1 B");

答案 3 :(得分:4)

*量词匹配0或更多。这导致有2场比赛。 XYZ什么都没有。

尝试+量词,而不是匹配1或更多。

一个简单的解释是查看这样的字符串:XYZ<nothing>

  1. 我们有匹配XYZ<nothing>
  2. 每场比赛
    • 匹配1:将XYZ替换为A $1 B($ 1在这里XYZ)结果:A XYZ B
    • 匹配2:将<nothing>替换为A $1 B($ 1在这里<nothing>)结果:A B
  3. 最终结果:A XYZ BA B

    为什么<nothing>本身就是一个匹配很有趣,而且我并没有真正考虑过这个问题。 (为什么没有无限<nothing>个匹配?)

答案 4 :(得分:2)

正则表达式是一种特殊的语言。你必须准确理解什么(。*)将匹配。你还需要了解贪婪。

  • (。*)将贪婪地匹配0个或更多字符。因此,在字符串"XYZ"中,它会将整个字符串与其第一个匹配项匹配,并将其置于$ 1位置,为您提供:

    XYZ B. 然后它将继续尝试匹配并匹配字符串末尾的null,将$ 1设置为null,为您提供:

    A B. 导致您看到的字符串:

    XYZ BA B

  • 如果您想限制贪婪并匹配每个角色,您可以使用以下表达式:

    (。*?)
    这将分别匹配每个字符X,Y和Z,以及最后的null,结果如下:

    A BXA BYA BZA B

如果您不希望正则表达式超出给定字符串的范围,请使用^$标识符限制正则表达式。

为了让您更好地了解正在发生的事情,请考虑此测试以及生成的匹配组。

    [TestMethod()]
    public void TestMethod3()
    {
        var myText = "XYZ";
        var regex = new Regex("(.*)");
        var m = regex.Match(myText);
        var matchCount = 0;
        while (m.Success)
        {
            Console.WriteLine("Match" + (++matchCount));
            for (int i = 1; i <= 2; i++)
            {
                Group g = m.Groups[i];
                Console.WriteLine("Group" + i + "='" + g + "'");
                CaptureCollection cc = g.Captures;
                for (int j = 0; j < cc.Count; j++)
                {
                    Capture c = cc[j];
                    Console.WriteLine("Capture" + j + "='" + c + "', Position=" + c.Index);
                }
            }
            m = m.NextMatch();
        }

输出:

Match1
Group1='XYZ'
Capture0='XYZ', Position=0
Group2=''
Match2
Group1=''
Capture0='', Position=3
Group2=''

请注意,有两个匹配的组。第一个是整个组XYZ,第二个是空组。然而,有两组相匹配。因此,在第一种情况下,1美元换成XYZ,第二种情况换成null

另请注意,正斜杠/只是.net正则表达式引擎中考虑的另一个字符,没有特殊含义。 javascript解析器处理/的方式不同,因为它必须因为它存在于HTML解析器的框架中,其中</是一个特殊考虑因素。

最后,为了得到你真正想要的东西,请考虑这个测试:

    [TestMethod]
    public void TestMethod1()
    {
        var r = new Regex(@"^(.*)$");
        var c = "XYZ";
        var uc = r.Replace(c, "A $1 B");

        Assert.AreEqual("A XYZ B", uc);
    }