Revision: 24048
Initial Code
Initial URL
Initial Description
Initial Title
Initial Tags
Initial Language
at February 19, 2010 12:16 by jmcd
Initial Code
class Program { static void Main() { var someObject = new { FirstName = "John", LastName = "McDowall", Dob = new DateTime(1976, 1, 14) }; var format = new StringFormatCompiler().Compile("{FirstName} {LastName}, born {Dob:D}"); var output = new Formatter().Process(format, someObject); Console.WriteLine(output); } } public class Formatter { public string Process(StringFormatWithNamedArguments formatWithNamedArguments, object valueProvider) { var valueList = new object[formatWithNamedArguments.ArgumentNames.Count]; for (var index = 0; index < valueList.Length; index++) { var argumentName = formatWithNamedArguments.ArgumentNames[index]; var value = GetValue(valueProvider, argumentName); valueList[index] = value; } return string.Format(formatWithNamedArguments.Format, valueList); } private static object GetValue(object valueProvider, string name) { var type = valueProvider.GetType(); var propertyInfoOrDefault = type.GetProperties().Where(x => x.Name == name).SingleOrDefault(); if (propertyInfoOrDefault != null) { var methodInfo = propertyInfoOrDefault.GetGetMethod(true); if (methodInfo != null) { return methodInfo.Invoke(valueProvider, new object[] {}); } } var field = type.GetFields().Where(x => x.Name == name).SingleOrDefault(); if (field != null) { return field.GetValue(valueProvider); } throw new ArgumentException("Could not find get property or field for " + name + " on type " + type); } } public class StringFormatWithNamedArguments { public string Format { get; private set; } public List<string> ArgumentNames { get; private set; } public StringFormatWithNamedArguments(string format, List<string> argumentNames) { Format = format; ArgumentNames = argumentNames; } } public class StringFormatCompiler { public StringFormatWithNamedArguments Compile(string format) { var tokens = Tokenize(format); var stringBuilder = new StringBuilder(); var argumentNames = new List<string>(); var argumentIndex = 0; foreach (var token in tokens) { if (token is Literal) { stringBuilder.Append(token); } else { var argument = (Argument) token; stringBuilder.Append('{').Append(argumentIndex).Append(argument.GetSpecification()).Append('}'); argumentIndex++; argumentNames.Add(argument.GetName()); } } return new StringFormatWithNamedArguments(stringBuilder.ToString(), argumentNames); } public IEnumerable<Token> Tokenize(string format) { var result = new List<Token> {new Literal()}; foreach (var c in format.ToCharArray()) { if (result.Last() is Literal) if (c == '{') result.Add(new Argument()); else result.Last().Add(c); else if (c == '}') result.Add(new Literal()); else result.Last().Add(c); } return result.Where(x => x.HasData()); } } public abstract class Token { protected readonly StringBuilder Data = new StringBuilder(); public void Add(char c) { Data.Append(c); } public bool HasData() { return Data.Length > 0; } public override string ToString() { return Data.ToString(); } } public class Literal : Token { } public class Argument : Token { public bool HasSpecification() { return IndexOfSpecification() != -1; } public string GetSpecification() { var indexOfSpecification = IndexOfSpecification(); return indexOfSpecification == -1 ? null : Data.ToString().Substring(indexOfSpecification); } private int IndexOfSpecification() { for (var index = 0; index < Data.Length; index++) { var c = Data[index]; if (c == ':') { return index; } } return -1; } public string GetName() { var indexOfSpecification = IndexOfSpecification(); return indexOfSpecification == -1 ? Data.ToString() : Data.ToString().Substring(0, indexOfSpecification); } }
Initial URL
Initial Description
Initial Title
String Format with Named Arguments
Initial Tags
format
Initial Language
C#