C# 解析格式化字符串

C# 解析格式化字符串,c#,regex,string,C#,Regex,String,我正在尝试创建一个通用格式化程序/解析器组合 示例场景: 我有一个string.Format()的字符串,例如var Format=“{0}-{1}” 我有一个用于输入的对象(字符串)数组,例如var arr=new[]{“asdf”,“qwer”} 我正在使用格式字符串格式化数组,例如var res=string.format(format,arr) 我试图做的是将格式化的字符串恢复到object(string)数组中。类似于(伪代码): 有人做过这样的事吗?我正在考虑使用正则表达式(修改

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

示例场景:

  • 我有一个string.Format()的字符串,例如
    var Format=“{0}-{1}”
  • 我有一个用于输入的对象(字符串)数组,例如
    var arr=new[]{“asdf”,“qwer”}
  • 我正在使用格式字符串格式化数组,例如
    var res=string.format(format,arr)
我试图做的是将格式化的字符串恢复到object(string)数组中。类似于(伪代码):


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

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

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

您将如何“取消格式化”它?

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

创建一个从
string
继承的新类,在该类中添加一个跟踪
“{0}-{1}”
{asdf”,“qwer”}
的成员,重写
ToString()
,并稍微修改代码

如果变得太棘手,只需创建相同的类,但不要从
string
继承,然后再修改一点代码

依我看,这是最好的方法。

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

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

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

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

  • 将所有格式标记替换为(.*)
  • 格式转义所有其他特殊字符
  • 使正则表达式匹配为非贪婪
这将把模糊性解决为尽可能短的匹配


(我不擅长正则表达式,所以请纠正我,各位:)

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

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

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

一个方法是我的一个朋友写的。他实现了一个名为
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;
        }
    }
}
公共静态类StringExtensions
{
公共静态字符串[]ParseExact(
这个字符串包含数据,
(字符串格式)
{
返回ParseExact(数据、格式、false);
}
公共静态字符串[]ParseExact(
这个字符串包含数据,
字符串格式,
布尔格诺尔案)
{
字符串[]值;
if(TryParseExact(数据、格式、输出值、ignoreCase))
返回值;
其他的
抛出新ArgumentException(“格式与值不兼容”);
}
公共静态bool TryExtract(
这个字符串包含数据,
字符串格式,
输出字符串[]值)
{
返回TryParseExact(数据、格式、输出值、false);
}
公共静态bool TryParseExact(
这个字符串包含数据,
字符串格式,
输出字符串[]值,
布尔格诺尔案)
{
int-tokenCount=0;
format=Regex.Escape(format.Replace(“\\{”,“{”);
对于(tokenCount=0;tokenCount++)
{
string token=string.Format(“{{{0}}}”,tokenCount);
如果(!format.Contains(token))中断;
format=format.Replace(令牌,
格式((?'group{0}.*)”,tokenCount));
}
RegexOptions选项=
ignoreCase?RegexOptions.ignoreCase:RegexOptions.None;
Match Match=新的正则表达式(格式、选项)。Match(数据);
if(tokenCount!=(match.Groups.Count-1))
{
值=新字符串[]{};
返回false;
}
其他的
{
值=新字符串[tokenCount];
for(int index=0;index
未格式化的字符串有多长?@Chris:在合理的范围内。ATM,我只在文件名上使用它。注意,根据给定的一般性,结果可能是模糊的-例如,
格式='{0}-{1}'
arr={“as df”,“qw er”}
。可以用三种不同的方式取消格式化。您需要定义如何处理歧义,或限制格式字符串的内容和值。使用正则表达式捕获组可以很容易地实现这一点:好的。创建一个比一般解决方案小的解决方案,假设格式中没有字符,如何e是否存在于对象数组中?阿德里安:在某些情况下,这也是不明确的:
String.Format(“{0}{1}”、“12”、“3”)
将返回“123”,但不能从格式字符串推断它是“12”、“3”或“12”、“3”或者…您将返回一个结果数组,并让客户端处理它。格式可以是任何内容。但是,是的,我们必须同意格式中的任何内容都不应出现在正在格式化的数组上。为答案添加了一些澄清。在这种情况下返回的内容:
“a-b-c”.ParseExact(“{0}-{1}-{0}”)
?建议-replace
format=format.replace(令牌,string.format((?'group{0}.*)”,tokenCount));
替换为
format=format.ReplaceFirst(令牌,string.format)
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; 
}
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;
        }
    }
}