csharp/0x0ade/CelesteNet/CelesteNet.Shared/StringInjectExtension.cs

StringInjectExtension.cs
// Taken from http://mo.notono.us/2008/07/c-stringinject-format-strings-by-key.html
using System;
using System.Text.RegularExpressions;
using System.Collections;
using System.Globalization;
using System.ComponentModel;

namespace Celeste.Mod.CelesteNet {
    public static clast StringInjectExtension {
        /// 
        /// Extension method that replaces keys in a string with the values of matching object properties.
        /// Uses  internally; custom formats should match those used for that method.
        /// 
        /// The format string, containing keys like {foo} and {foo:SomeFormat}.
        /// The object whose properties should be injected in the string
        /// A version of the formatString string with keys replaced by (formatted) key values.
        public static string Inject(this string formatString, object injectionObject) {
            if (injectionObject is IDictionary dictionary)
                return formatString.Inject(new(dictionary));
            return formatString.Inject(GetPropertyHash(injectionObject));
        }

        /// 
        /// Extension method that replaces keys in a string with the values of matching dictionary entries.
        /// Uses  internally; custom formats should match those used for that method.
        /// 
        /// The format string, containing keys like {foo} and {foo:SomeFormat}.
        /// An  with keys and values to inject into the string
        /// A version of the formatString string with dictionary keys replaced by (formatted) key values.
        public static string Inject(this string formatString, IDictionary dictionary) {
            return formatString.Inject(new(dictionary));
        }

        /// 
        /// Extension method that replaces keys in a string with the values of matching hashtable entries.
        /// Uses  internally; custom formats should match those used for that method.
        /// 
        /// The format string, containing keys like {foo} and {foo:SomeFormat}.
        /// A  with keys and values to inject into the string
        /// A version of the formatString string with hastable keys replaced by (formatted) key values.
        public static string Inject(this string formatString, Hashtable attributes) {
            string result = formatString;
            if (attributes == null || formatString == null)
                return result;

            foreach (string? attributeKey in attributes.Keys) {
                if (attributeKey == null)
                    continue;
                result = result.InjectSingleValue(attributeKey, attributes[attributeKey] ?? string.Empty);
            }
            return result;
        }

        /// 
        /// Replaces all instances of a 'key' (e.g. {foo} or {foo:SomeFormat}) in a string with an optionally formatted value, and returns the result.
        /// 
        /// The string containing the key; unformatted ({foo}), or formatted ({foo:SomeFormat})
        /// The key name (foo)
        /// The replacement value; if null is replaced with an empty string
        /// The input string with any instances of the key replaced with the replacement value
        public static string InjectSingleValue(this string formatString, string key, object replacementValue) {
            string result = formatString;
            //regex replacement of key with value, where the generic key format is:
            //Regex foo = new("{(foo)(?:}|(?::(.[^}]*)}))");
            Regex attributeRegex = new("{(" + key + ")(?:}|(?::(.[^}]*)}))");  //for key = foo, matches {foo} and {foo:SomeFormat}

            //loop through matches, since each key may be used more than once (and with a different format string)
            foreach (Match? m in attributeRegex.Matches(formatString)) {
                if (m == null)
                    continue;

                string replacement = m.ToString();
                if (m.Groups[2].Length > 0) {
                    //matched {foo:SomeFormat}
                    //do a double string.Format - first to build the proper format string, and then to format the replacement value
                    string attributeFormatString = string.Format(CultureInfo.InvariantCulture, "{{0:{0}}}", m.Groups[2]);
                    replacement = string.Format(CultureInfo.CurrentCulture, attributeFormatString, replacementValue);
                } else {
                    //matched {foo}
                    replacement = replacementValue.ToString() ?? string.Empty;
                }
                //perform replacements, one match at a time
                result = result.Replace(m.ToString(), replacement);  //attributeRegex.Replace(result, replacement, 1);
            }
            return result;

        }


        /// 
        /// Creates a HashTable based on current object state.
        /// Copied from the MVCToolkit HtmlExtensionUtility clast
        /// 
        /// The object from which to get the properties
        /// A  containing the object instance's property names and their values
        private static Hashtable GetPropertyHash(object properties) {
            Hashtable values = new();
            if (properties != null) {
                PropertyDescriptorCollection props = TypeDescriptor.GetProperties(properties);
                foreach (PropertyDescriptor? prop in props) {
                    if (prop == null)
                        continue;
                    values.Add(prop.Name, prop.GetValue(properties));
                }
            }
            return values;
        }

    }
}