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
}
}