解析格式化的字符串

时间:2009-09-11 09:45:32

标签: c# regex string

我正在尝试创建一个通用的格式化程序/解析器组合。

示例场景:

  • 我有一个string.Format()的字符串,例如var format = "{0}-{1}"
  • 我有一个输入的对象(字符串)数组,例如var arr = new[] { "asdf", "qwer" }
  • 我使用格式字符串格式化数组,例如var res = string.Format(format, arr)

我要做的是将格式化的字符串恢复回对象数组(字符串)。像(伪代码):

var arr2 = string.Unformat(format, res)

// when: res = "asdf-qwer"    
// arr2 should be equal to arr

任何人都有这样的经历吗?我正在考虑使用正则表达式(修改原始格式字符串,然后将其传递给Regex.Matches以获取数组)并为格式字符串中的每个占位符运行它。这是可行的还是还有其他更有效的解决方案?

6 个答案:

答案 0 :(得分:15)

虽然有关丢失信息的注释有效,但有时您只想获取具有已知格式的字符串的字符串值。

我的一位朋友写的this blog post是一种方法。他实现了一种名为string[] ParseExact()的扩展方法,类似于DateTime.ParseExact()。数据以字符串数组的形式返回,但是如果你能接受它,它就非常方便。

public static class StringExtensions
{
    public static string[] ParseExact(
        this string data, 
        string format)
    {
        return ParseExact(data, format, false);
    }

    public static string[] ParseExact(
        this string data, 
        string format, 
        bool ignoreCase)
    {
        string[] values;

        if (TryParseExact(data, format, out values, ignoreCase))
            return values;
        else
            throw new ArgumentException("Format not compatible with value.");
    }

    public static bool TryExtract(
        this string data, 
        string format, 
        out string[] values)
    {
        return TryParseExact(data, format, out values, false);
    }

    public static bool TryParseExact(
        this string data, 
        string format, 
        out string[] values, 
        bool ignoreCase)
    {
        int tokenCount = 0;
        format = Regex.Escape(format).Replace("\\{", "{");

        for (tokenCount = 0; ; tokenCount++)
        {
            string token = string.Format("{{{0}}}", tokenCount);
            if (!format.Contains(token)) break;
            format = format.Replace(token,
                string.Format("(?'group{0}'.*)", tokenCount));
        }

        RegexOptions options = 
            ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None;

        Match match = new Regex(format, options).Match(data);

        if (tokenCount != (match.Groups.Count - 1))
        {
            values = new string[] { };
            return false;
        }
        else
        {
            values = new string[tokenCount];
            for (int index = 0; index < tokenCount; index++)
                values[index] = 
                    match.Groups[string.Format("group{0}", index)].Value;
            return true;
        }
    }
}

答案 1 :(得分:10)

您无法取消格式化,因为信息丢失了。 String.Format是一种“破坏性”算法,这意味着你不能(总是)回去。

创建一个继承自string的新类,您可以在其中添加跟踪"{0}-{1}"{ "asdf", "qwer" }的成员,覆盖ToString(),然后稍微修改一下代码。

如果它变得太棘手,只需创建相同的类,但不要继承string并修改一些代码。

IMO,这是最好的方法。

答案 2 :(得分:2)

在通用案例中根本不可能。 Format方法中的某些信息将“丢失”(字符串边界)。假设:

String.Format("{0}-{1}", "hello-world", "stack-overflow");

你怎么会“取消格式化”呢?

答案 3 :(得分:2)

假设“ - ”不在原始字符串中,你能不能只使用Split?

var arr2 = formattedString.Split('-');

请注意,这仅适用于带有假设的演示示例。任何反向算法都取决于所采用的格式类型;如其他答案所述,甚至可能无法进行逆运算。

答案 4 :(得分:1)

一个简单的解决方案可能是

  • 用(。*)
  • 替换所有格式标记
  • format
  • 中逃避所有其他特殊字符
  • 使正则表达式匹配非贪婪

这将解决最短可能匹配的模糊性。

(我不擅长RegEx,所以请纠正我,伙计们:))

答案 5 :(得分:0)

格式化后,您可以将生成的字符串和对象数组放入字典中,并将字符串作为键:

Dictionary<string,string []> unFormatLookup = new Dictionary<string,string []>
...
var arr = new string [] {"asdf", "qwer" };
var res = string.Format(format, arr);
unFormatLookup.Add(res,arr);

在Unformat方法中,您只需传递一个字符串并查找该字符串并返回使用的数组:

string [] Unformat(string res)
{
  string [] arr;
  unFormatLoopup.TryGetValue(res,out arr); //you can also check the return value of TryGetValue and throw an exception if the input string is not in.
  return arr; 
}