407 lines
13 KiB
C#
407 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using JD.Kernel.StaticHelpers;
|
|
|
|
namespace JD.StringParserService
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public class StringParser
|
|
{
|
|
const string TagStart = "${";
|
|
|
|
#region Variables
|
|
|
|
private readonly Dictionary<string, string> m_properties = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
|
|
private readonly Dictionary<string, object> m_propertyObjects = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
|
|
private readonly Dictionary<string, IStringTagProvider> m_tagProviders = new Dictionary<string, IStringTagProvider>(StringComparer.InvariantCultureIgnoreCase);
|
|
private readonly Dictionary<string, IStringParserTransform> m_transforms = new Dictionary<string, IStringParserTransform>(StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Name-Value property pairs, used in the parsing process.
|
|
/// </summary>
|
|
public Dictionary<string, string> Properties
|
|
{
|
|
get
|
|
{
|
|
return m_properties;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Name-value property pairs to objects, used in the parsing process.
|
|
/// </summary>
|
|
/// <example>
|
|
/// 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.
|
|
/// </example>
|
|
public Dictionary<string, object> PropertyObjects
|
|
{
|
|
get
|
|
{
|
|
return m_propertyObjects;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
#region Misc
|
|
|
|
/// <summary>
|
|
/// Clears the StringParserService (Removes all properties and custom tag providers).
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
m_properties.Clear();
|
|
m_propertyObjects.Clear();
|
|
m_tagProviders.Clear();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Custom Tag Providers
|
|
|
|
/// <summary>
|
|
/// Gets a custom tag provider corresponding to the given <paramref name="tag"/>.
|
|
/// </summary>
|
|
/// <param name="tag"></param>
|
|
/// <returns></returns>
|
|
public IStringTagProvider GetCustomTagProvider(string tag)
|
|
{
|
|
IStringTagProvider provider;
|
|
m_tagProviders.TryGetValue(tag, out provider);
|
|
return provider;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers a custom tag provider.
|
|
/// </summary>
|
|
/// <param name="provider"></param>
|
|
public void RegisterCustomTagProvider(IStringTagProvider provider)
|
|
{
|
|
Throw.IfNull(provider, "provider");
|
|
|
|
foreach (string tag in provider.Tags)
|
|
{
|
|
m_tagProviders.Add(tag, provider);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Transforms
|
|
|
|
/// <summary>
|
|
/// Gets a transform corresponding to the given <paramref name="tag"/>.
|
|
/// </summary>
|
|
/// <param name="tag"></param>
|
|
/// <returns></returns>
|
|
public IStringParserTransform GetTransform(string tag)
|
|
{
|
|
IStringParserTransform transform;
|
|
m_transforms.TryGetValue(tag, out transform);
|
|
return transform;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers a transform.
|
|
/// </summary>
|
|
/// <param name="transform"></param>
|
|
public void RegisterTransform(IStringParserTransform transform)
|
|
{
|
|
Throw.IfNull(transform, "transform");
|
|
|
|
foreach (string tag in transform.Tags)
|
|
{
|
|
m_transforms.Add(tag, transform);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Parse
|
|
|
|
/// <summary>
|
|
/// Check if the string should be parsed
|
|
/// </summary>
|
|
/// <param name="input"></param>
|
|
/// <returns></returns>
|
|
private static bool ShouldParse(string input)
|
|
{
|
|
return input != null && input.Contains(TagStart);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses an array of strings. The result is put back directly into the array.
|
|
/// </summary>
|
|
/// <see cref="Parse(string)"/>
|
|
/// <param name="inputs">String array to parse</param>
|
|
public void Parse(string[] inputs)
|
|
{
|
|
if (inputs != null)
|
|
{
|
|
for (int i = 0; i < inputs.Length; ++i)
|
|
{
|
|
inputs[i] = Parse(inputs[i], null);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses an array of strings, using a set of custom tags. The result is put back directly into the array.
|
|
/// </summary>
|
|
/// <see cref="Parse(string, string[,])"/>
|
|
/// <param name="inputs">String array to parse</param>
|
|
/// <param name="customTags">Custom Tags to use in the parsing process</param>
|
|
public void Parse(string[] inputs, string[,] customTags)
|
|
{
|
|
if (inputs != null)
|
|
{
|
|
for (int i = 0; i < inputs.Length; ++i)
|
|
{
|
|
inputs[i] = Parse(inputs[i], customTags);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a string, replacing the tags it contains
|
|
/// </summary>
|
|
/// <param name="input">The text to parse</param>
|
|
/// <returns></returns>
|
|
public string Parse(string input)
|
|
{
|
|
return Parse(input, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a string, replacing the tags it contains. This overload also uses the custom tags that are passed with this method.
|
|
/// </summary>
|
|
/// <param name="input">The text to parse</param>
|
|
/// <param name="customTags">Custom Tags to use in the parsing process, in a 'Name-Value pairs' format</param>
|
|
/// <returns></returns>
|
|
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
|
|
}
|
|
}
|