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