using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
using Developpez.Dotnet.Properties;
namespace Developpez.Dotnet.Text
{
///
/// Représente un modèle de chaine de caractères, où on peut utiliser des noms plutôt que des index
/// pour les valeurs à remplacer.
///
public class StringTemplate
{
private static readonly Regex _regex = new Regex(@"(?{+)(?\w+)(?:[^}]+)?(?}+)", RegexOptions.Compiled);
private readonly string _template;
///
/// Initialise une nouvelle instance de StringTemplate avec le template à utiliser
///
/// Le template à utiliser. Le format est identique à celui accepté par String.Format,
/// mais avec des noms à la place des index pour les valeurs à remplacer
public StringTemplate(string template)
{
template.CheckArgumentNull("template");
_template = template;
ParseTemplate();
}
private string _templateWithIndexes;
private List _placeholders;
private void ParseTemplate()
{
_placeholders = new List();
MatchEvaluator evaluator = m =>
{
if (m.Success)
{
string open = m.Groups["open"].Value;
string close = m.Groups["close"].Value;
string key = m.Groups["key"].Value;
string format = m.Groups["format"].Value;
if (open.Length % 2 == 0)
return m.Value;
open = RemoveLastChar(open);
close = RemoveLastChar(close);
if (!_placeholders.Contains(key))
{
_placeholders.Add(key);
}
int index = _placeholders.IndexOf(key);
return string.Format("{0}{{{1}{2}}}{3}", open, index, format, close);
}
return m.Value;
};
_templateWithIndexes = _regex.Replace(_template, evaluator);
}
private static string RemoveLastChar(string str)
{
if (str.Length > 1)
return str.Substring(0, str.Length - 1);
return string.Empty;
}
///
/// Convertit une chaine en StringTemplate
///
/// La chaine à convertir
/// Un StringTemplate utilisant la chaine convertie comme template
public static implicit operator StringTemplate(string s)
{
return GetTemplateFromCache(s);
}
///
/// Renvoie une chaine représentant cette instance de StringTemplate.
///
/// Le template utilisé par ce StringTemplate
public override string ToString()
{
return _template;
}
///
/// Remplace les placeholders du template par les valeurs fournies dans le dictionnaire spécifié
///
/// Le dictionnaire contenant les valeurs pour les placeholders
/// La chaine formatée
/// Aucune valeur n'a été trouvée pour
/// un placeholder du template
public string Format(IDictionary values)
{
return Format(values, true);
}
///
/// Remplace les placeholders du template par les valeurs fournies dans le dictionnaire spécifié
///
/// Le dictionnaire contenant les valeurs pour les placeholders
/// Indique si une exception doit être levée quand aucune valeur
/// n'est trouvée pour un placeholder du template. Si ce paramètre vaut false, le placeholder est laissé
/// tel quel dans la chaine formatée.
/// La chaine formatée
/// Le paramètre throwOnMissingValue
/// vaut true et aucune valeur n'a été trouvée pour un placeholder du template
public string Format(IDictionary values, bool throwOnMissingValue)
{
values.CheckArgumentNull("values");
object[] array = new object[_placeholders.Count];
for(int i = 0; i < _placeholders.Count; i++)
{
string key = _placeholders[i];
object value;
if (!values.TryGetValue(key, out value))
{
if (throwOnMissingValue)
throw new KeyNotFoundException(string.Format(ExceptionMessages.TemplateKeyNotFound, key));
value = string.Format("{{{0}}}", key);
}
array[i] = value;
}
return string.Format(_templateWithIndexes, array);
}
///
/// Remplace les placeholders du template par les valeurs fournies dans l'objet spécifié
///
/// L'objet contenant les valeurs pour les placeholders. Chaque propriété de
/// l'objet correspond à un placeholder du template
/// La chaine formatée
/// Aucune valeur n'a été trouvée pour
/// un placeholder du template
/// Cette méthode s'utilise typiquement avec un objet de type anonyme, créé avec la syntaxe
/// new { nom1 = valeur1, nom2 = valeur2 }
public string Format(object values)
{
return Format(values, true);
}
///
/// Remplace les placeholders du template par les valeurs fournies dans l'objet spécifié
///
/// L'objet contenant les valeurs pour les placeholders. Chaque propriété de
/// l'objet correspond à un placeholder du template
/// Indique si une exception doit être levée quand aucune valeur
/// n'est trouvée pour un placeholder du template. Si ce paramètre vaut false, le placeholder est laissé
/// tel quel dans la chaine formatée.
/// La chaine formatée
/// Le paramètre throwOnMissingValue
/// vaut true et aucune valeur n'a été trouvée pour un placeholder du template
/// Cette méthode s'utilise typiquement avec un objet de type anonyme, créé avec la syntaxe
/// new { nom1 = valeur1, nom2 = valeur2 }
public string Format(object values, bool throwOnMissingValue)
{
values.CheckArgumentNull("values");
return Format(MakeDictionary(values), throwOnMissingValue);
}
private IDictionary MakeDictionary(object obj)
{
Dictionary dict = new Dictionary();
foreach (string name in _placeholders)
{
object value;
if (TryGetMemberValue(obj, name, out value))
dict.Add(name, value);
}
return dict;
}
private static bool TryGetMemberValue(object obj, string memberName, out object value)
{
Type type = obj.GetType();
while (type != null)
{
var prop = type.GetProperty(memberName, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance);
if (prop != null && prop.CanRead)
{
value = prop.GetValue(obj, null);
return true;
}
var field = type.GetField(memberName, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance);
if (field != null)
{
value = field.GetValue(obj);
return true;
}
type = type.BaseType;
}
value = null;
return false;
}
///
/// Remplace les placeholders du template spécifié par les valeurs fournies dans le dictionnaire spécifié
///
/// Le template à utiliser
/// Le dictionnaire contenant les valeurs pour les placeholders
/// La chaine formatée
/// Aucune valeur n'a été trouvée pour
/// un placeholder du template
public static string Format(string template, IDictionary values)
{
return GetTemplateFromCache(template).Format(values);
}
///
/// Remplace les placeholders du template spécifié par les valeurs fournies dans le dictionnaire spécifié
///
/// Le template à utiliser
/// Le dictionnaire contenant les valeurs pour les placeholders
/// Indique si une exception doit être levée quand aucune valeur
/// n'est trouvée pour un placeholder du template. Si ce paramètre vaut false, le placeholder est laissé
/// tel quel dans la chaine formatée.
/// La chaine formatée
/// Le paramètre throwOnMissingValue
/// vaut true et aucune valeur n'a été trouvée pour un placeholder du template
public static string Format(string template, IDictionary values, bool throwOnMissingValue)
{
return GetTemplateFromCache(template).Format(values, throwOnMissingValue);
}
///
/// Remplace les placeholders du template par les valeurs fournies dans l'objet spécifié
///
/// Le template à utiliser
/// L'objet contenant les valeurs pour les placeholders. Chaque placeholder du
/// template est remplacé par la valeur de la propriété ou du champ de même nom
/// La chaine formatée
/// Cette méthode s'utilise typiquement avec un objet de type anonyme, créé avec la syntaxe
/// new { nom1 = valeur1, nom2 = valeur2 }
/// Aucune valeur n'a été trouvée pour
/// un placeholder du template
public static string Format(string template, object values)
{
return GetTemplateFromCache(template).Format(values);
}
///
/// Remplace les placeholders du template par les valeurs fournies dans l'objet spécifié
///
/// Le template à utiliser
/// L'objet contenant les valeurs pour les placeholders. Chaque placeholder du
/// template est remplacé par la valeur de la propriété ou du champ de même nom
/// Indique si une exception doit être levée quand aucune valeur
/// n'est trouvée pour un placeholder du template. Si ce paramètre vaut false, le placeholder est laissé
/// tel quel dans la chaine formatée.
/// La chaine formatée
/// Cette méthode s'utilise typiquement avec un objet de type anonyme, créé avec la syntaxe
/// new { nom1 = valeur1, nom2 = valeur2 }
/// Le paramètre throwOnMissingValue
/// vaut true et aucune valeur n'a été trouvée pour un placeholder du template
public static string Format(string template, object values, bool throwOnMissingValue)
{
return GetTemplateFromCache(template).Format(values, throwOnMissingValue);
}
private static readonly Dictionary _templateCache =
new Dictionary();
private static StringTemplate GetTemplateFromCache(string templateString)
{
StringTemplate template;
if (!_templateCache.TryGetValue(templateString, out template))
{
template = new StringTemplate(templateString);
_templateCache[templateString] = template;
}
return template;
}
}
}