【问题标题】:Parsing formatted string解析格式化字符串
【发布时间】:2009-09-11 09:45:32
【问题描述】:

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

示例场景:

  • 我有一个 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 以获取数组)并为格式字符串中的每个占位符运行它。这是可行的还是有其他更有效的解决方案?

【问题讨论】:

  • 未格式化的字符串有多长?
  • @Chris:在合理范围内。 ATM,我只在文件名上使用它。
  • 请注意,给出的一般性,结果可能是模棱两可的 - 例如format='{0}-{1}'arr = { "as-df", "qw-er" }。可以以三种不同的方式取消格式化。您需要定义如何处理歧义,或者限制格式字符串和值的内容。
  • 您可以使用正则表达式捕获组轻松实现此目的:stackoverflow.com/questions/906493/…

标签: c# regex string


【解决方案1】:

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

一种方法是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;
        }
    }
}

【讨论】:

  • 这种情况下返回什么:"a-b-c".ParseExact("{0}-{1}-{0}")?
  • 建议 - 将 format = format.Replace(token, string.Format("(?'group{0}'.*)", tokenCount)); 替换为 format = format.ReplaceFirst(token, string.Format("(?'group{0}'.*)", tokenCount)); format = format.Replace(token, string.Format("\\{0}", tokenCount));。这应该更好地处理多次使用输入参数的格式字符串。 ReplaceFirst 来自:stackoverflow.com/questions/141045/…
  • 不喜欢 "abc".ParseExact("{0}{1}{2}") 和 @"a$--\&*b^c".ParseExact(@"{ 0}$--\\&*{1}^{2}")
  • 这非常适合从 Route Maps 中解析参数。尽管我很惊讶,但我找不到任何关于重用框架本身为此目的使用的任何代码的参考。
【解决方案2】:

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

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

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

IMO,这是最好的方法。

【讨论】:

    【解决方案3】:

    在一般情况下根本不可能。 Format 方法中的某些信息将“丢失”(字符串边界)。假设:

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

    你会如何“取消格式化”它?

    【讨论】:

    • 好点。如果假设对象数组中不存在格式中的字符,那么创建一个小于通用的解决方案怎么样?
    • Adrian:在某些情况下这也很模糊:String.Format("{0}{1}", "12", "3") 将返回“123”,但您无法从格式字符串推断它是“12”、“3”或“12” "、"3" 或 ...
    • 您将返回一个结果数组并让客户端处理它。
    【解决方案4】:

    假设原始字符串中没有“-”,可以不直接使用Split吗?

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

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

    【讨论】:

    • 格式可以是任何东西。但是,是的,我们必须同意格式中的任何内容都不应出现在正在格式化的数组中。
    • 为答案添加了一些说明。
    【解决方案5】:

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

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

    这会将歧义解决为最短的匹配。

    (我不擅长正则表达式,所以请纠正我,伙计们:))

    【讨论】:

      【解决方案6】:

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

      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; 
      }
      

      【讨论】:

        猜你喜欢
        • 2015-04-10
        • 1970-01-01
        • 1970-01-01
        • 2012-03-03
        • 1970-01-01
        • 1970-01-01
        • 2019-06-23
        • 2021-09-17
        相关资源
        最近更新 更多