mo.notono.us

Tuesday, July 22, 2008

C#: String.Inject() - Format strings by key tokens

I generally prefer to use String.Format() instead of doing a bunch of string additions, but constantly find myself splitting the code onto multiple lines with an appended comment to keep track of the indices:

string myString = string.Format("{0} is {1} and {2} is {3}",
  o.foo, //0
  o.bar, //1
  o.yadi, //2
  o.yada //3
);

Wouldn't it be nice you could instead write something like this?:

string myString = "{foo} is {bar} and {yadi} is {yada}".Inject(o);

Well, now you can - see the string extension method Inject below; it accepts an object, IDictionary or HashTable and replaces the property name/key tokens with the instance values.  Since it uses string.Format internally, it also supports string.Format-like custom formatting:

   1:  using System;
   2:  using System.Text.RegularExpressions;
   3:  using System.Collections;
   4:  using System.Globalization;
   5:  using System.ComponentModel;
   6:   
   7:  [assembly: CLSCompliant(true)]
   8:  namespace StringInject
   9:  {
  10:    public static class StringInjectExtension
  11:    {
  12:      /// <summary>
  13:      /// Extension method that replaces keys in a string with the values of matching object properties.
  14:      /// <remarks>Uses <see cref="String.Format()"/> internally; custom formats should match those used for that method.</remarks>
  15:      /// </summary>
  16:      /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
  17:      /// <param name="injectionObject">The object whose properties should be injected in the string</param>
  18:      /// <returns>A version of the formatString string with keys replaced by (formatted) key values.</returns>
  19:      public static string Inject(this string formatString, object injectionObject)
  20:      {
  21:        return formatString.Inject(GetPropertyHash(injectionObject));
  22:      }
  23:   
  24:      /// <summary>
  25:      /// Extension method that replaces keys in a string with the values of matching dictionary entries.
  26:      /// <remarks>Uses <see cref="String.Format()"/> internally; custom formats should match those used for that method.</remarks>
  27:      /// </summary>
  28:      /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
  29:      /// <param name="dictionary">An <see cref="IDictionary"/> with keys and values to inject into the string</param>
  30:      /// <returns>A version of the formatString string with dictionary keys replaced by (formatted) key values.</returns>
  31:      public static string Inject(this string formatString, IDictionary dictionary)
  32:      {
  33:        return formatString.Inject(new Hashtable(dictionary));
  34:      }
  35:   
  36:      /// <summary>
  37:      /// Extension method that replaces keys in a string with the values of matching hashtable entries.
  38:      /// <remarks>Uses <see cref="String.Format()"/> internally; custom formats should match those used for that method.</remarks>
  39:      /// </summary>
  40:      /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
  41:      /// <param name="attributes">A <see cref="Hashtable"/> with keys and values to inject into the string</param>
  42:      /// <returns>A version of the formatString string with hastable keys replaced by (formatted) key values.</returns>
  43:      public static string Inject(this string formatString, Hashtable attributes)
  44:      {
  45:        string result = formatString;
  46:        if (attributes == null || formatString == null)
  47:          return result;
  48:   
  49:        foreach (string attributeKey in attributes.Keys)
  50:        {
  51:          result = result.InjectSingleValue(attributeKey, attributes[attributeKey]);
  52:        }
  53:        return result;
  54:      }
  55:   
  56:      /// <summary>
  57:      /// Replaces all instances of a 'key' (e.g. {foo} or {foo:SomeFormat}) in a string with an optionally formatted value, and returns the result.
  58:      /// </summary>
  59:      /// <param name="formatString">The string containing the key; unformatted ({foo}), or formatted ({foo:SomeFormat})</param>
  60:      /// <param name="key">The key name (foo)</param>
  61:      /// <param name="replacementValue">The replacement value; if null is replaced with an empty string</param>
  62:      /// <returns>The input string with any instances of the key replaced with the replacement value</returns>
  63:      public static string InjectSingleValue(this string formatString, string key, object replacementValue)
  64:      {
  65:        string result = formatString;
  66:        //regex replacement of key with value, where the generic key format is:
  67:        //Regex foo = new Regex("{(foo)(?:}|(?::(.[^}]*)}))");
  68:        Regex attributeRegex = new Regex("{(" + key + ")(?:}|(?::(.[^}]*)}))");  //for key = foo, matches {foo} and {foo:SomeFormat}
  69:        
  70:        //loop through matches, since each key may be used more than once (and with a different format string)
  71:        foreach (Match m in attributeRegex.Matches(formatString))
  72:        {
  73:          string replacement = m.ToString();
  74:          if (m.Groups[2].Length > 0) //matched {foo:SomeFormat}
  75:          {
  76:            //do a double string.Format - first to build the proper format string, and then to format the replacement value
  77:            string attributeFormatString = string.Format(CultureInfo.InvariantCulture, "{{0:{0}}}", m.Groups[2]);
  78:            replacement = string.Format(CultureInfo.CurrentCulture, attributeFormatString, replacementValue);
  79:          }
  80:          else //matched {foo}
  81:          {
  82:            replacement = (replacementValue ?? string.Empty).ToString();
  83:          }
  84:          //perform replacements, one match at a time
  85:          result = result.Replace(m.ToString(), replacement);  //attributeRegex.Replace(result, replacement, 1);
  86:        }
  87:        return result;
  88:   
  89:      }
  90:   
  91:   
  92:      /// <summary>
  93:      /// Creates a HashTable based on current object state.
  94:      /// <remarks>Copied from the MVCToolkit HtmlExtensionUtility class</remarks>
  95:      /// </summary>
  96:      /// <param name="properties">The object from which to get the properties</param>
  97:      /// <returns>A <see cref="Hashtable"/> containing the object instance's property names and their values</returns>
  98:      private static Hashtable GetPropertyHash(object properties)
  99:      {
 100:        Hashtable values = null;
 101:        if (properties != null)
 102:        {
 103:          values = new Hashtable();
 104:          PropertyDescriptorCollection props = TypeDescriptor.GetProperties(properties);
 105:          foreach (PropertyDescriptor prop in props)
 106:          {
 107:            values.Add(prop.Name, prop.GetValue(properties));
 108:          }
 109:        }
 110:        return values;
 111:      }
 112:   
 113:    }
 114:  }

File downloads:

Labels: , , , , , , , , ,

10 Comments:

Post a Comment

<< Home