Return to Snippet

Revision: 24048
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#