ReverseStringFormat更好的正则表达式

时间:2015-02-19 18:11:53

标签: c# regex

我一直在使用这个简洁的功能在SO上找到here

    private List<string> ReverseStringFormat(string template, string str)
    {
        string pattern = "^" + Regex.Replace(template, @"\{[0-9]+\}", "(.*?)") + "$";
        Regex r = new Regex(pattern);
        Match m = r.Match(str);

        List<string> ret = new List<string>();
        for (int i = 1; i < m.Groups.Count; i++)
            ret.Add(m.Groups[i].Value);

        return ret;
    }

此功能可以正确处理以下模板:

My name is {0} and I'm {1} years old

虽然它失败的模式如下:

My name is {0} and I'm {1:00} years old

我想处理这个失败的场景并添加固定长度解析。 该函数将(第一个)模板转换为:

My name is (.*?) and I'm (.*?) years old

我一直在尝试编写上面的正则表达式来限制为第二组捕获的字符数而没有成功。这是我(可怕)的尝试:

My name is (.*?) and I'm (.{2}) years old

我一直在尝试处理如下输入,但下面的PATTERN不起作用:

PATTERN: My name is (.*?) (.{3})(.{5})
INPUT: My name is John 123ABCDE
EXPECTED OUTPUT: John, 123, ABCDE

每个建议都受到高度赞赏

2 个答案:

答案 0 :(得分:1)

您极不可能在相同的Regex替换中测量捕获组的长度。

我强烈建议您查看以下状态机实现。 请注意,此实现还解决了string.Format的多个大括号转义功能。

首先你需要一个状态枚举,非常像这个:

public enum State {
    Outside,
    OutsideAfterCurly,
    Inside,
    InsideAfterColon
}

然后,您将需要一种很好的方法来迭代字符串中的每个字符。 string chars参数代表您的template参数,而返回的IEnumerable<string>代表结果模式的连续部分:

public static IEnumerable<string> InnerTransmogrify(string chars) {
    State state = State.Outside;
    int counter = 0;

    foreach (var @char in chars) {
        switch (state) {
            case State.Outside:
                switch (@char) {
                    case '{':
                        state = State.OutsideAfterCurly;
                        break;
                    default:
                        yield return @char.ToString();
                        break;
                }
                break;
            case State.OutsideAfterCurly:
                switch (@char) {
                    case '{':
                        state = State.Outside;
                        break;
                    default:
                        state = State.Inside;
                        counter = 0;
                        yield return "(.";
                        break;
                }
                break;
            case State.Inside:
                switch (@char) {
                    case '}':
                        state = State.Outside;
                        yield return "*?)";
                        break;
                    case ':':
                        state = State.InsideAfterColon;
                        break;
                    default:
                        break;
                }
                break;
            case State.InsideAfterColon:
                switch (@char) {
                    case '}':
                        state = State.Outside;
                        yield return "{" + counter + "})";
                        break;
                    default:
                        counter++;
                        break;
                }
                break;
        }

    }

}

你可以加入这样的部分:

public static string Transmogrify(string chars) {
    var parts = InnerTransmogrify(chars);
    var result = string.Join("", parts);
    return result;
}

然后像你原定的那样把所有东西都包起来:

private List<string> ReverseStringFormat(string template, string str) {
    string pattern = <<SOME_PLACE>> .Transmogrify(template);
    Regex r = new Regex(pattern);
    Match m = r.Match(str);

    List<string> ret = new List<string>();
    for (int i = 1; i < m.Groups.Count; i++)
        ret.Add(m.Groups[i].Value);

    return ret;
}

希望您理解为什么正则表达式语言对于这类工作不够表达(至少就我的理解而言)。

答案 1 :(得分:1)

使用正则表达式解决问题的唯一方法是使用自定义匹配器替换组捕获长度。

下面的代码在您的示例中执行此操作:

private static string PatternFromStringFormat(string template)
{
    // replaces only elements like {0}
    string firstPass = Regex.Replace(template, @"\{[0-9]+\}", "(.*?)");
    // replaces elements like {0:000} using a custom matcher
    string secondPass = Regex.Replace(firstPass, @"\{[0-9]+\:(?<len>[0-9]+)\}",
        (match) =>
        {
            var len = match.Groups["len"].Value.Length;
            return "(.{" + len + "*})";
        });

    return "^" + secondPass + "$";
}

private static List<string> ReverseStringFormat(string template, string str)
{
    string pattern = PatternFromStringFormat(template);

    Regex r = new Regex(pattern);
    Match m = r.Match(str);

    List<string> ret = new List<string>();
    for (int i = 1; i < m.Groups.Count; i++)
        ret.Add(m.Groups[i].Value);

    return ret;
}