JD2022-TU1/main/tools/framework/JD.StringParserService/StringParser.cs

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