using System; using System.Collections.Generic; using System.Reflection; using System.Text; using JD.Kernel.StaticHelpers; namespace JD.StringParserService { /// /// A string parser is an object that handles the parsing of ${xyz} tags. /// All environment variables are available under the name ${env:[NAME]} /// where [NAME] represents the string under which it is avaiable in /// the environment. /// public class StringParser { const string TagStart = "${"; #region Variables private readonly Dictionary m_properties = new Dictionary(StringComparer.InvariantCultureIgnoreCase); private readonly Dictionary m_propertyObjects = new Dictionary(StringComparer.InvariantCultureIgnoreCase); private readonly Dictionary m_tagProviders = new Dictionary(StringComparer.InvariantCultureIgnoreCase); private readonly Dictionary m_transforms = new Dictionary(StringComparer.InvariantCultureIgnoreCase); #endregion #region Constructor #endregion #region Properties /// /// Name-Value property pairs, used in the parsing process. /// public Dictionary Properties { get { return m_properties; } } /// /// Name-value property pairs to objects, used in the parsing process. /// /// /// If some instance MyInstance has a property called 'Length', you can assign the propertyObject /// StringParser.PropertyObjects["myObj"] = MyInstance. If you then call StringParser.Parse("${myObj:Length}"), /// you will get the value of this property at the time that Parse was called. /// public Dictionary PropertyObjects { get { return m_propertyObjects; } } #endregion #region Methods #region Misc /// /// Clears the StringParserService (Removes all properties and custom tag providers). /// public void Clear() { m_properties.Clear(); m_propertyObjects.Clear(); m_tagProviders.Clear(); } #endregion #region Custom Tag Providers /// /// Gets a custom tag provider corresponding to the given . /// /// /// public IStringTagProvider GetCustomTagProvider(string tag) { IStringTagProvider provider; m_tagProviders.TryGetValue(tag, out provider); return provider; } /// /// Registers a custom tag provider. /// /// public void RegisterCustomTagProvider(IStringTagProvider provider) { Throw.IfNull(provider, "provider"); foreach (string tag in provider.Tags) { m_tagProviders.Add(tag, provider); } } #endregion #region Transforms /// /// Gets a transform corresponding to the given . /// /// /// public IStringParserTransform GetTransform(string tag) { IStringParserTransform transform; m_transforms.TryGetValue(tag, out transform); return transform; } /// /// Registers a transform. /// /// public void RegisterTransform(IStringParserTransform transform) { Throw.IfNull(transform, "transform"); foreach (string tag in transform.Tags) { m_transforms.Add(tag, transform); } } #endregion #region Parse /// /// Check if the string should be parsed /// /// /// private static bool ShouldParse(string input) { return input != null && input.Contains(TagStart); } /// /// Parses an array of strings. The result is put back directly into the array. /// /// /// String array to parse public void Parse(string[] inputs) { if (inputs != null) { for (int i = 0; i < inputs.Length; ++i) { inputs[i] = Parse(inputs[i], null); } } } /// /// Parses an array of strings, using a set of custom tags. The result is put back directly into the array. /// /// /// String array to parse /// Custom Tags to use in the parsing process public void Parse(string[] inputs, string[,] customTags) { if (inputs != null) { for (int i = 0; i < inputs.Length; ++i) { inputs[i] = Parse(inputs[i], customTags); } } } /// /// Parses a string, replacing the tags it contains /// /// The text to parse /// public string Parse(string input) { return Parse(input, null); } /// /// Parses a string, replacing the tags it contains. This overload also uses the custom tags that are passed with this method. /// /// The text to parse /// Custom Tags to use in the parsing process, in a 'Name-Value pairs' format /// public string Parse(string input, string[,] customTags) { if (!ShouldParse(input)) { return input; } string output = input; do { input = output; output = ParseImpl(output, customTags); } while (output != input); return output; } private string ParseImpl(string input, string[,] customTags) { if (input == null) return null; int pos = 0; StringBuilder output = null; // don't use StringBuilder if input is a single property do { int oldPos = pos; pos = input.IndexOf(TagStart, pos); if (pos < 0) { if (output == null) { // No tags, return the original string return input; } else { if (oldPos < input.Length) { // normal text after last property output.Append(input, oldPos, input.Length - oldPos); } return output.ToString(); } } if (output == null) { output = pos == 0 ? new StringBuilder() : new StringBuilder(input, 0, pos, pos + 16); } else { if (pos > oldPos) { // normal text between two properties output.Append(input, oldPos, pos - oldPos); } } int end = input.IndexOf('}', pos + 1); int nextTag = input.IndexOf(TagStart, pos + 1); if (end < 0 || (nextTag != -1 && nextTag < end)) { output.Append(TagStart); pos += 2; } else { string property = input.Substring(pos + 2, end - pos - 2); ConvertProperty(output, property, customTags); pos = end + 1; } } while (pos < input.Length); return output.ToString(); } private void ConvertProperty(StringBuilder output, string property, string[,] customTags) { string[] transformTokens = property.Split(new char[] { '@' }, StringSplitOptions.RemoveEmptyEntries); string val = GetValue(transformTokens[0], customTags); if (val == null && transformTokens.Length > 1) { val = transformTokens[0]; } for (int i = 1; i < transformTokens.Length; i++) { if (val == null) { break; } string transform = transformTokens[i]; val = m_transforms.ContainsKey(transform) ? m_transforms[transform].Transform(val, transform) : null; } if (val == null) { // Could not replace the tag, leave it as is. output.Append(TagStart); output.Append(property); output.Append('}'); } else { output.Append(val); } } private string GetValue(string propertyName, string[,] customTags) { if (propertyName.Equals("DATE", StringComparison.OrdinalIgnoreCase)) return DateTime.Today.ToShortDateString(); if (propertyName.Equals("TIME", StringComparison.OrdinalIgnoreCase)) return DateTime.Now.ToShortTimeString(); if (customTags != null) { for (int j = 0; j < customTags.GetLength(0); ++j) { if (propertyName.Equals(customTags[j, 0], StringComparison.OrdinalIgnoreCase)) { return customTags[j, 1]; } } } if (m_properties.ContainsKey(propertyName)) { return m_properties[propertyName]; } if (m_tagProviders.ContainsKey(propertyName)) { return m_tagProviders[propertyName].Convert(propertyName); } int k = propertyName.IndexOf(':'); if (k <= 0) { IStringTagProvider fallbackProvider; if (m_tagProviders.TryGetValue(string.Empty, out fallbackProvider)) { // Use fallback provider return fallbackProvider.Convert(propertyName); } else { return null; } } return TryConvertWithPrefix(propertyName, k); } private string TryConvertWithPrefix(string propertyName, int k) { string prefix = propertyName.Substring(0, k); propertyName = propertyName.Substring(k + 1); if (m_tagProviders.ContainsKey(prefix)) { return m_tagProviders[prefix].Convert(prefix, propertyName); } switch (prefix.ToUpperInvariant()) { case "ENV": return Environment.GetEnvironmentVariable(propertyName); default: if (m_propertyObjects.ContainsKey(prefix)) { return GetProperty(m_propertyObjects[prefix], propertyName); } break; } return null; } private string GetProperty(object obj, string name) { Type type = obj.GetType(); PropertyInfo prop = type.GetProperty(name); if (prop != null) { return prop.GetValue(obj, null).ToString(); } FieldInfo field = type.GetField(name); if (field != null) { return field.GetValue(obj).ToString(); } return null; } #endregion #endregion } }