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