Geeks With Blogs

News
Impossibility is not an option! My blog about .Net programming, what is possible and what (sometimes) drives me crazy!
As you might have seen, there has been some blogging about named format strings in the last days.

If you didn't came across it, the general idea is to use something like:
"foo '{foo}' bar: {bar}".Format(new {foo = "abc", bar= 123})

Since I already had an analog solution for this problem I thought it would be fun to run it in 'competition' with the other implementations.
After I adopted it the the same syntax (I used \{ and \} as escape sequences), a bit of optimisation and make it pass the Phil's test suite, it turned out to run quite fast.

These are the results (output slightly changed from Phil's implementation), including an ordinary string.Format with a object array in comparison:
Hanselformat took 0.21862 ms / 00:00:10.9318279
OskarFormat took 0.30212 ms / 00:00:15.1064949
JamesFormat took 0.17788 ms / 00:00:08.8948007
HenriFormat took 0.14302 ms / 00:00:07.1513749
HaackFormat took 0.1572 ms / 00:00:07.8600728
HartFormat took 0.13706 ms / 00:00:06.8530890

Mine:
RobotoFormat took 0.12632 ms / 00:00:06.3163623

Standard string.Format:
str.Format took 0.00786 ms / 00:00:00.3935486

Find my code below. If you find any bugs, please let me know!


using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace StringLib
{
    public static class RobotoFormatter
    {
        //------------------------------------------------------------------
        /// <summary>
        /// Returns a formated string.
        /// The format may contain references to properties of this object.
        /// To add properties to an output, the format is:
        /// "Random Text={PropertyName}"
        /// If you like to add the characters '{' or '}', you have to mask them
        /// with '{{'/'}}'.
        /// </summary>
        /// <param name="format"></param>
        /// <returns></returns>
        /// <exception cref="FormatException"><c>FormatException</c> in case of invalid format.</exception>
        /// <exception cref="ArgumentNullException"><c>format</c> is null.</exception>
        public static string RobotoFormat(this string format, object @object)
        {
            if (format == null) { throw new ArgumentNullException("format");}
            if (format.Trim().Length == 0) { return format; }

            Descriptor[] descriptors = GetDescriptors(format);

            string result = format;
            Descriptor descriptor;
            object descriptorValue;

            //could be done with foreach, altough 'for' could be faster.
            for (int i = 0; i < descriptors.Length; i++)
            {
                descriptor = descriptors[i];
                descriptorValue = GetDescriptorValue(@object, descriptor);
                result = result.Replace(
                    string.Concat("{", descriptor.FullDescriptor,"}"), 
                    descriptor.ValueToString(descriptorValue));
            }
            return result.Replace(@"{{", "{").Replace(@"}}", "}");
        }

        //---------------------------------------------------------
        private static object GetDescriptorValue(object @object, Descriptor descriptor)
        {
            object result = @object;
            foreach (var name in descriptor.Names)
            {
                PropertyInfo descriptionInfo = result.GetType().GetProperty(name);
                if (descriptionInfo == null)
                {
                    throw new FormatException(string.Format("No property named '{0}' found. Invalid format!", descriptor));
                }
                result = descriptionInfo.GetValue(result, null);
            }
            return result;
        }


        //------------------------------------------------------------------
        /// <summary>
        /// Function to 
        /// </summary>
        /// <param name="format"></param>
        /// <returns></returns>
        private static Descriptor[] GetDescriptors(string format)
        {
            var list = new List<Descriptor>();
            StringBuilder descriptorBuilder = new StringBuilder(format.Length);

            bool isDescriptor = false;
            char character;
            for (int i = 0; i < format.Length; i++ )
            {
                character = format[i];
                //ignore escaped opening/closing braces
                if (( character == '{' && GetNextChar(i, format) == '{' ) ||
                    ( character == '}' && GetNextChar(i, format) == '}'))
                {
                    i++;
                    continue;
                }

                if (isDescriptor || character == '{') //we found beginning
                {
                    isDescriptor = true;
                    if (character != '}')
                    {
                        if (character != '{') 
                        {
                            descriptorBuilder.Append(character);
                        }
                    }
                    else if (GetNextChar(i, format) != '}') //found an end (ignoring escaped closing)
                    {
                        var descriptor = new Descriptor(descriptorBuilder.ToString());
                        if (!list.Contains(descriptor)) { list.Add(descriptor); }

                        descriptorBuilder.Remove(0, descriptorBuilder.Length);
                        isDescriptor = false;
                    }
                }
            }
            if (isDescriptor) //found no end
            {
                throw new FormatException("No end for descriptor found. Please use {{ to encode a { in your format.");
            }
            return list.ToArray();
        }

        //---------------------------------------------------------
        private static char GetNextChar(int i, string format)
        {
            if (((i + 1) < format.Length))
            {
                return format[i + 1];
            }
            return ' ';
        }


        //---------------------------------------------------------
        /// <summary>
        /// This class encapsulates the handling of a descriptor.
        /// </summary>
        private class Descriptor : IEquatable<Descriptor>
        {
            public Descriptor(string descriptorValue)
            {
                if (descriptorValue == null) { throw new ArgumentNullException("descriptorValue");}
                if ( descriptorValue.Trim().Length == 0 ) { throw new ArgumentException("Empty descriptor not allowed.", "descriptorValue");}

                Format = string.Empty;
                FullDescriptor = descriptorValue;

                string[] values = descriptorValue.Split(':');
                if (values.Length > 0)
                {
                    Names = values[0].Split('.');
                }
                if (values.Length > 1)
                {
                    Format = values[1];
                }
            }

            //---------------------------------------------------------
            public string[] Names { get; private set; }
            public string Format { get; private set; }
            public string FullDescriptor { get; private set; }

            //---------------------------------------------------------
            public bool HasFormat
            {
                get { return !string.IsNullOrEmpty(Format); }
            }

            //---------------------------------------------------------
            public string ValueToString(object value)
            {
                return string.Format(ToFormatString(), value);
            }


            //---------------------------------------------------------
            private string ToFormatString()
            {
                string formatString = "{0}";
                if (HasFormat)
                {
                    formatString = string.Concat("{0:", Format, "}");
                }
                return formatString;
            }

            //---------------------------------------------------------
            public bool Equals(Descriptor other)
            {
                return FullDescriptor.Equals(other.FullDescriptor);
            }
        }
    }
}



Nothing Impossible
roboto Posted on Monday, January 19, 2009 12:06 PM C# , .Net | Back to top


Comments on this post: Named Format Strings

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © NotImpossible | Powered by: GeeksWithBlogs.net