Formatting A String With Named Parameters

I usually try to blog about things that I haven’t really been able to find anywhere else. I realize that this may not quite be the case here – Phil Haack has an inspiring article here that sums up various methods of extending the usual positional formatting of String.Format.

However I wanted to be able to:

  • have the same formatting capabilities as String.Format, like{0:00}
  • have extensive validation between the string “template” and the parameters passed
  • not use Databinder.Eval, which seems a bit (too) exotic for my taste
  •  

    The use-cases – insert values from Dictionary<string,object> or from class looks like this:

    const string s = "Hello {name}, the value is {value:00} at {date:yyyy-MM-dd} {date:HHmmss}";
    
    var d = new Dictionary<string, object>();
    d.Add("date", DateTime.Now);
    d.Add("value", 5);
    d.Add("name", "MySelf");
    
    Console.WriteLine(s.FormatByName(d));
    
    Console.WriteLine(s.FormatByName(new {date=DateTime.Now, value=5, name="MySelf"}));

    My implementation of this follows here, and if you’d like to play with it you can download the full sample code here

    Please return any modifications or additions you come up with :).

    I’ll just skip the extension methods here and go straight into the meat of the formatting which is handled by a method with this signature:

    private static string KeyValueReplacer(string template, Dictionary<string, object> parametercollection)

    First the parameters need to be extracted, which is done by the black art of a RegEx (the origins of which I’m afraid I’ve forgotten)

    Regex m_regexmatch = new Regex(@"(?<start>\{)+(?<property>[\w\.\[\]]+)(?<format>:[^}]+)?(?<end>\})+",
        RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
    
    var matches = m_regexmatch.Matches(template).OfType<Match>();
    
    // Get the parameters { to } and index of any suffixed : formatting code
    var paramlist1 = matches.Select(s => new { Parameter = s.Value, FormatIndex = s.Value.IndexOf(':') });
    
    // Extract the key (parameter name) and formatting code from template
    var paramlist2 =
        paramlist1.Select(s =>
                          new
                          {
                              Key = s.Parameter.Substring(1, s.FormatIndex < 1 ? s.Parameter.Length - 2 : s.FormatIndex - 1),
                              Format = s.FormatIndex < 1 ? "" : s.Parameter.Substring(s.FormatIndex, s.Parameter.Length - s.FormatIndex - 1),
                              s.Parameter,
                          });

    As mentioned above I wanted extensive checking that the parameters correlate:

    // Perform existence checks from both sides
    var paramlist2unique = paramlist2.Select(s => s.Key).Distinct();
    foreach (var key in parametercollection.Keys)
        if (!paramlist2unique.Contains(key))
            throw new ArgumentException("Parameter [" + key + "] not found in template");
    
    foreach (var key in paramlist2unique)
        if (!parametercollection.ContainsKey(key))
            throw new ArgumentException("Template variable {" + key + "} not found in parameter collection");

    Now it’s time to join the template together with the values, and finally perform the actual replacing in the template string:

    // Join the template parameters and the values from parametercollection
    var paramlist3 =
        from p in paramlist2
        from v in parametercollection
        where p.Key == v.Key
        select new
        {
            p.Parameter,
            ValueString = String.Format("{0" + p.Format + "}", v.Value)
        };
    
    // Replace the values into the template
    var sb = new StringBuilder(template);
    foreach (var p in paramlist3)
        sb.Replace(p.Parameter, p.ValueString);
    
    return sb.ToString();

    Well, that’s about it – except that I also wanted the ability to substitute with the properties of a (possibly anonymous) class.

    This is done by first creating a dictionary of key/values from the class before calling the method above:

    private static Dictionary<string, object> PropertiesToDictionary(object o)
    {
        var t = o.GetType();
    
        if (!t.IsClass)
            throw new ArgumentException("Can only resolve properties in classes.");
    
        var p = t.GetProperties();
        var propcollection = p.ToDictionary(propertyInfo => propertyInfo.Name, propertyInfo => propertyInfo.GetValue(o, null));
        return propcollection;
    }

    Enjoy!

    Advertisements
    Posted in C#

    Leave a Reply

    Fill in your details below or click an icon to log in:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out / Change )

    Twitter picture

    You are commenting using your Twitter account. Log Out / Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out / Change )

    Google+ photo

    You are commenting using your Google+ account. Log Out / Change )

    Connecting to %s