简体   繁体   English

查找.NET字符串结果的格式化部分

[英]Finding formatted parts of result of .NET's string.Format

I'm using string.Format in a data-driven fashion - I know how many objects to format there are, but nothing else - and I'd like to find out which parts of the result are formatted objects and which parts come verbatim from the format string. 我正在以数据驱动的方式使用string.Format我知道要格式化的对象有多少,但别无其他-我想找出结果的哪些部分是格式化对象,哪些部分是逐字逐句地格式字符串。 (I intend to display the formatted result in the UI with the formatted parts "hot", so that they can be hovered over and clicked on to activate some UI related to the object that produced them.) (我打算在带有格式化部分“ hot”的UI中显示格式化结果,以便可以将其悬停并单击以激活一些与生成它们的对象相关的UI。)

For example, suppose I call this hypothetical formatting function, passing in a particular format string, and (string)"fred" as object 0, and (int)50 as object 1. And suppose the result is (fred) . 例如,假设我调用此假设的格式化函数,传入一个特定的格式字符串,并且将(string)"fred"作为对象0,将(int)50作为对象1,并将结果为(fred) I'd like to be able to determine that the 4 chars starting at index 1 are the result of formatting object 0, and that object 1 wasn't formatted. 我希望能够确定从索引1开始的4个字符是格式化对象0的结果,而对象1尚未格式化。 (Clearly the format string in this case was something like "{0}" .) (显然,这种情况下的格式字符串类似于"{0}" 。)

Is there some built-in way of doing this? 有内置的方法吗?

(This feels like a generic .NET/CLR question - but in case it's relevant, I'm using C#.) (这感觉像是一个通用的.NET / CLR问题-但是,如果有问题,我将使用C#。)

If you know just the format string and the resulting string, but not the parameters which were formatted, it is not possible to find them in the resulting string. 如果仅知道格式字符串和结果字符串,但不知道已格式化的参数,则无法在结果字符串中找到它们。

For instance, the following lines produce the same result: 例如,以下几行产生相同的结果:

string.Format("{0}{1}", "a", "bc")
string.Format("{0}{1}", "ab", "c")

You could also work with regular expressions, more specifically using a MatchEvaluator , so you could keep track of those indexes. 您还可以使用正则表达式,更具体地说,使用MatchEvaluator ,以便跟踪这些索引。 I made an example, which you can customize for your application: 我举了一个例子,您可以为您的应用程序自定义它:

static void Main(string[] args)
{
    var arg0 = (string)"fred";
    var arg1 = (int)50;
    var format = "{0}";

    var result = Format(format, arg0, arg1);
    for(int index = 0; index < result.Arguments.Length; index++)
    {
        if(String.IsNullOrEmpty(result.Arguments[index].Capture))
        {
            Console.WriteLine(
                "Argument {0} with value {1} was unused", 
                index, result.Arguments[index].Value);
        }
        else
        {
            Console.WriteLine(
                "Argument {0} with value {1} was used, starting at index {2}", 
                index, result.Arguments[index].Value,
                result.Arguments[index].Index);
        }
    }
}

static Transformation Format(string format, params object[] args)
{
    var value = new Transformation
    {
        Format    = format,
        Arguments = (args ?? new object[]{})
            .Select (o => new Argument{ Value = o })
            .ToArray()
    };

    value.Result = Regex.Replace(format, @"{(\d+)}", (match) =>
    {
        int index = Convert.ToInt32(match.Groups[1].Value);
        if (index > args.Length) return "";

        var @this = args[index];
        var result = @this == null ? "" : @this.ToString();

        value.Arguments[index].Index   = match.Index;
        value.Arguments[index].Capture = match.Value;
        value.Arguments[index].Length  = result.Length;

        return result;
    });

    return value;
}

class Transformation
{
    public string Format        { get; set; }
    public string Result        { get; set; }
    public Argument[] Arguments { get; set; }
}

class Argument
{
    public object Value         { get; set; }
    public int Index            { get; set; }
    public int Length           { get; set; }
    public string Capture       { get; set; }
}

In the end I wrote my own thing, as it sounded like there wasn't any built-in way of getting what I wanted, whether from an existing function or by hooking into something somewhere. 最后,我写了自己的东西,听起来好像没有任何内置的方式可以获取我想要的东西,无论是从现有函数中还是通过挂接到某处。

Firstly, an object to store the location in the result where each object's string was inserted: 首先,一个对象,用于存储插入每个对象的字符串的结果中的位置:

    public class FormattedStringPart
    {
        public int ObjectIndex { get; private set; }
        public int StartIndex { get; private set; }
        public int Length { get; private set; }

        public FormattedStringPart(int objectIndex, int startIndex, int length)
        {
            ObjectIndex = objectIndex;
            StartIndex = startIndex;
            Length = length;
        }
    }

Then the function itself works through the format string, building up a StringBuilder is it goes. 然后,函数本身通过格式字符串工作,就可以构建一个StringBuilder了。 Either it adds chars verbatim, or it finds a format part, which it formats with string.Format keeping track of the indexes so it can make a new FormattedStringPart for the insertion. 它要么逐字添加字符,要么找到一个格式部分,使用string.Format格式化string.Format跟踪索引,以便可以为插入创建新的FormattedStringPart (The key thing that makes this quite easy is that you can just hand off the format part and the entire array of object to string.Format - so there's no need to look carefully at the format part to check for validity. Just pass it to string.Format and see what happens.) (使这一过程变得很容易的关键是,您可以将格式部分和整个object数组交出给string.Format因此,无需仔细查看格式部分以检查其有效性。只需将其传递给string.Format ,看看会发生什么。)

    public static string FormatString(string format, 
                                      object[] args, 
                                      out FormattedStringPart[] formattedParts)
    {
        var parts = new List<FormattedStringPart>();
        var result = new StringBuilder();

        int i = 0;
        while (i < format.Length)
        {
            char c = format[i];

            if (c == '{')
            {
                int j = format.IndexOf('}', i);
                if (j < 0)
                    throw new FormatException("Missing '}'");

                int startIndex = result.Length;

                result.AppendFormat(format.Substring(i, (j - i) + 1), args);

                ++i;

                // the AppendFormat call should have ensured there's a
                // valid number following...
                int objectIndex = 0;
                while (format[i] >= '0' && format[i] <= '9')
                {
                    objectIndex *= 10;
                    objectIndex += (int)(format[i] - '0');

                    ++i;
                }

                parts.Add(new FormattedStringPart(objectIndex, 
                                                  startIndex, 
                                                  result.Length - startIndex));

                i = j + 1;
            }
            else
            {
                result.Append(c);
                ++i;
            }
        }

        if (parts.Count == 0)
            formattedParts = null;
        else
            formattedParts = parts.ToArray();

        return result.ToString();
    }

Eagle-eyed string.Format fans will note that this isn't quite exactly like string.Format - this is not (yet?) important in my situation. 老鹰眼的string.Format爱好者会注意到,这与string.Format并不完全一样-在我的情况下,这还不重要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM