using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; #if !NETFX_CORE using System.Security.Cryptography; #endif using System.Text; using System.Text.RegularExpressions; using Developpez.Dotnet.Collections; using Developpez.Dotnet.Properties; using CH = Developpez.Dotnet.CompatibilityHelper; namespace Developpez.Dotnet { /// /// Fournit des méthodes d'extension pour les chaines de caractères /// public static class StringExtensions { /// /// Indique si une chaine est nulle ou vide /// /// la chaine à tester /// true si la chaine est nulle ou vide, false sinon public static bool IsNullOrEmpty(this string s) { return string.IsNullOrEmpty(s); } /// /// Indique si une chaîne est nulle, vide ou composée uniquement d'espaces blancs. /// /// la chaine à tester /// true si la chaine est nulle, vide ou composée uniquement d'espaces blancs, false sinon public static bool IsNullOrWhiteSpace(this string s) { if (string.IsNullOrEmpty(s)) return true; return string.IsNullOrEmpty(s.Trim()); } /// /// Formate la chaine avec les valeurs spécifiées, de la même façon que /// String.Format /// /// La chaine de format /// les valeurs à utiliser /// La chaine formatée public static string FormatWith(this string format, params object[] args) { return string.Format(format, args); } /// /// Inverse l'ordre des caractères d'une chaine /// /// La chaine à inverser /// La chaine inversée public static string Reverse(this string s) { s.CheckArgumentNull("s"); #if !SILVERLIGHT && !NETFX_CORE // La normalisation Unicode n'est pas supportée en SL ni en WinRT bool wasFormD = false; if (s.IsNormalized(NormalizationForm.FormD)) { wasFormD = true; s = s.Normalize(NormalizationForm.FormC); } #endif #if NETFX_CORE char[] array = s.ToCharArray(); array.Reverse(); s = new String(array); #else s = new String((s as IEnumerable).Reverse().ToArray()); #endif #if !SILVERLIGHT && !NETFX_CORE if (wasFormD) { s = s.Normalize(NormalizationForm.FormD); } #endif return s; } /// /// Concatène toutes les chaines de la liste en plaçant le séparateur /// spécifié entre chaque chaine /// /// La liste de chaines à concaténer /// Le séparateur à utiliser /// La concaténation des chaines de la liste public static string Join(this IEnumerable list, string separator) { list.CheckArgumentNull("list"); return string.Join(separator, list.ToArray()); } /// /// Renvoie une chaine construite à partir d'une séquence de caractères /// /// La séquence de caractères à transformer en chaine /// Une chaine constituée des caractères de la séquence public static string Join(this IEnumerable chars) { chars.CheckArgumentNull("chars"); return new string(chars.ToArray()); } /// /// Enumère les lignes d'une chaine de caractères /// /// La chaine à découper en lignes /// La liste des lignes de cette chaine public static IEnumerable ReadLines(this string s) { s.CheckArgumentNull("s"); return ReadLinesIterator(s); } private static IEnumerable ReadLinesIterator(string s) { using (StringReader reader = new StringReader(s)) { string line; while ((line = reader.ReadLine()) != null) { yield return line; } } } #if !SILVERLIGHT && !NETFX_CORE // MD5 non supporté en SL ni en WinRT private static MD5 _md5; /// /// Renvoie le hash MD5 de la chaîne sous forme d'une chaine hexadécimale, en /// se basant sur l'encodage UTF8 /// /// la chaine dont on veut obtenir le hash MD5 /// le hash MD5 sous forme d'une chaine hexadécimale public static string GetMD5Digest(this string s) { return s.GetMD5Digest(Encoding.UTF8); } /// /// Renvoie le hash MD5 de la chaîne sous forme d'une chaine hexadécimale, en /// se basant sur l'encodage spécifié /// /// la chaine dont on veut obtenir le hash MD5 /// L'encodage à utiliser /// le hash MD5 sous forme d'une chaine hexadécimale public static string GetMD5Digest(this string s, Encoding encoding) { s.CheckArgumentNull("s"); if (_md5 == null) _md5 = MD5.Create(); byte[] stringBytes = encoding.GetBytes(s); byte[] hashBytes = _md5.ComputeHash(stringBytes); return hashBytes.ToHexString(); } #endif #if !NETFX_CORE // SHA1 non supporté en WinRT private static SHA1 _sha1; /// /// Renvoie le hash SHA1 de la chaîne sous forme d'une chaîne hexadécimale, en se basant sur l'encodage UTF8 /// /// la chaine dont on veut obtenir le hash SHA1 /// le hash SHA1 sous forme d'une chaine hexadécimale public static string GetSHA1Digest(this string s) { return GetSHA1Digest(s, Encoding.UTF8); } /// /// Renvoie le hash SHA1 de la chaîne sous forme d'une chaîne hexadécimale, en se basant sur l'encodage spécifié /// /// la chaine dont on veut obtenir le hash SHA1 /// L'encodage à utiliser /// le hash SHA1 sous forme d'une chaine hexadécimale public static string GetSHA1Digest(this string s, Encoding encoding) { s.CheckArgumentNull("s"); if (_sha1 == null) #if SILVERLIGHT _sha1 = new SHA1Managed(); #else _sha1 = SHA1.Create(); #endif byte[] stringBytes = encoding.GetBytes(s); byte[] hashBytes = _sha1.ComputeHash(stringBytes); return hashBytes.ToHexString(); } #endif #if !SILVERLIGHT && !NETFX_CORE // La normalisation Unicode n'est pas supportée en SL ni en WinRT /// /// Enlève les caractères diacritiques (accents, cédilles...) d'une chaine en les remplaçant par le /// caractère de base. /// /// La chaine dont on veut enlever les diacritiques /// La chaine sans les diacritiques public static string RemoveDiacritics(this string s) { s.CheckArgumentNull("s"); string formD = s.Normalize(NormalizationForm.FormD); char[] chars = new char[formD.Length]; int count = 0; foreach (char c in formD) { if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark) { chars[count++] = c; } } string noDiacriticsFormD = new string(chars, 0, count); return noDiacriticsFormD.Normalize(NormalizationForm.FormC); } #endif /// /// Retourne une chaîne contenant un nombre spécifié de caractères en partant de la gauche d'une chaîne. /// /// chaine dont les caractères situés le plus à gauche sont retournés /// Nombre de caractères à retourner /// une chaîne contenant le nombre spécifié de caractères en partant de la gauche de s public static string Left(this string s, int count) { s.CheckArgumentNull("s"); count.CheckArgumentOutOfRange("count", 0, s.Length, ExceptionMessages.SubstringCountOutOfRange); return s.Substring(0, count); } /// /// Retourne une chaîne contenant un nombre spécifié de caractères en partant de la droite d'une chaîne. /// /// chaine dont les caractères situés le plus à droite sont retournés /// Nombre de caractères à retourner /// une chaîne contenant le nombre spécifié de caractères en partant de la droite de s public static string Right(this string s, int count) { s.CheckArgumentNull("s"); count.CheckArgumentOutOfRange("count", 0, s.Length, ExceptionMessages.SubstringCountOutOfRange); return s.Substring(s.Length - count, count); } /// /// Retourne une chaine tronquée au nombre de caractères spécifié. /// /// chaine à tronquer /// Nombre maximal de caractères à retourner /// une chaîne tronquée au nombre de caractères spécifié public static string Truncate(this string s, int count) { s.CheckArgumentNull("s"); count.CheckArgumentOutOfRange("count", 0, int.MaxValue, ExceptionMessages.NumberMustBePositiveOrZero.FormatWith("count")); if (count > s.Length) return s; return s.Substring(0, count); } #if !SILVERLIGHT && !NETFX_CORE // ToTitleCase n'est pas supporté en SL ni en WinRT /// /// Convertit la chaine spécifiée en initiales majuscules, selon les paramètres de la culture courante. /// /// La chaine à convertir en initiales majuscules /// La chaine spécifiée convertie en initiales majuscules public static string ToTitleCase(this string s) { return s.ToTitleCase(null); } /// /// Convertit la chaine spécifiée en initiales majuscules, selon les paramètres de la culture spécifiée. /// /// La chaine à convertir en initiales majuscules /// La culture à utiliser /// La chaine spécifiée convertie en initiales majuscules public static string ToTitleCase(this string s, CultureInfo culture) { s.CheckArgumentNull("s"); culture = culture ?? CultureInfo.CurrentCulture; return culture.TextInfo.ToTitleCase(s); } #endif /// /// Met en majuscule le premier caractère de la chaine spécifiée, selon les paramètres de la culture courante. /// /// La chaine dont le premier caractère est mis en majuscule /// La chaine spécifiée avec le premier caractère en majuscule public static string Capitalize(this string s) { return s.Capitalize(null); } /// /// Met en majuscule le premier caractère de la chaine spécifiée, selon les paramètres de la culture spécifiée. /// /// La chaine dont le premier caractère est mis en majuscule /// La culture à utiliser /// La chaine spécifiée avec le premier caractère en majuscule public static string Capitalize(this string s, CultureInfo culture) { s.CheckArgumentNull("s"); culture = culture ?? CultureInfo.CurrentCulture; if (s.Length == 0) return s; StringBuilder builder = new StringBuilder(s); builder[0] = culture.TextInfo.ToUpper(builder[0]); return builder.ToString(); } /// /// Renvoie un dictionnaire contenant le nombre d'occurences de chaque caractère de la chaine /// /// La chaine pour laquelle on veut compter les caractères /// Un dictionnaire contenant le nombre d'occurences de chaque caractère public static IDictionary GetCharFrequencies(this string s) { s.CheckArgumentNull("s"); var frequencies = new DefaultDictionary(); foreach (char c in s) frequencies[c]++; return frequencies; } /// /// Renvoie le caractère à la position spécifiée, ou le caractère nul (0) /// si la position spécifiée est la fin de la chaine. /// /// Chaine dont un caractère doit être renvoyé /// Position du caractère à renvoyer /// le caractère à la position spécifiée, ou le caractère nul (0) /// si la position spécifiée est la fin de la chaine. public static char CharAt(this string s, int index) { s.CheckArgumentNull("s"); index.CheckArgumentOutOfRange("index", 0, s.Length); if (index < s.Length) return s[index]; return '\0'; } /// /// Vérifie si une chaine correspond à un motif avec des caractères "joker" ('*' et '?') /// /// Chaine à vérifier /// Motif avec lequel comparer la chaine /// true si la chaine correspond au motif, false sinon. public static bool MatchesWildcard(this string text, string pattern) { text.CheckArgumentNull("text"); pattern.CheckArgumentNull("pattern"); int it = 0; while (text.CharAt(it) != 0 && pattern.CharAt(it) != '*') { if (pattern.CharAt(it) != text.CharAt(it) && pattern.CharAt(it) != '?') return false; it++; } int cp = 0; int mp = 0; int ip = it; while (text.CharAt(it) != 0) { if (pattern.CharAt(ip) == '*') { if (pattern.CharAt(++ip) == 0) return true; mp = ip; cp = it + 1; } else if (pattern.CharAt(ip) == text.CharAt(it) || pattern.CharAt(ip) == '?') { ip++; it++; } else { ip = mp; it = cp++; } } while (pattern.CharAt(ip) == '*') { ip++; } return pattern.CharAt(ip) == 0; } /// /// Tronque une chaine de caractères à la longueur spécifiée, en remplaçant les derniers /// caractères par des points de suspension le cas échéant. /// /// La chaine à tronquer /// La longueur maximale souhaitée /// La chaine tronquée public static string Ellipsis(this string s, int maxLength) { const string ellipsisString = "..."; if (maxLength < ellipsisString.Length) throw new ArgumentException(string.Format(ExceptionMessages.MaxLengthCantBeLessThan, ellipsisString.Length)); return s.Ellipsis(maxLength, ellipsisString); } /// /// Tronque une chaine de caractères à la longueur spécifiée, en remplaçant les derniers /// caractères par la chaine spécifiée le cas échéant. /// /// La chaine à tronquer /// La longueur maximale souhaitée /// La chaine à utiliser pour indiquer que la chaine est tronquée /// La chaine tronquée public static string Ellipsis(this string s, int maxLength, string ellipsisString) { s.CheckArgumentNull("s"); ellipsisString.CheckArgumentNull("ellipsisString"); if (maxLength < ellipsisString.Length) throw new ArgumentException(ExceptionMessages.MaxLengthCantBeLessThanLengthOfEllipsisString); if (s.Length <= maxLength) return s; return s.Substring(0, maxLength - ellipsisString.Length) + ellipsisString; } #region From/To/Take /// /// Renvoie une portion de chaine à partir de la position spécifiée /// /// Chaine dont on veut extraire une portion /// Position de début de la portion /// La portion de chaine demandée public static SubStringFrom From(this string s, int start) { return new SubStringFrom(s, start); } /// /// Termine une portion de chaine à la position spécifiée /// /// Portion de chaine à terminer /// Position de fin /// La portion de chaine demandée public static string To(this SubStringFrom subStringFrom, int end) { return subStringFrom.String.Substring(subStringFrom.Start, end - subStringFrom.Start + 1); } /// /// Prend le nombre de caractères spécifié à partir du début de la portion /// /// Portion de chaine à partir de laquelle prendre les caractères /// Nombre de caractères à prendre /// La portion de chaine demandée public static string Take(this SubStringFrom subStringFrom, int count) { return subStringFrom.String.Substring(subStringFrom.Start, count); } /// /// Renvoie une portion de chaine à partir de la sous-chaine spécifiée, sans inclure cette dernière /// /// Chaine dont on veut extraire une portion /// Sous-chaine de début de la portion /// La portion de chaine demandée public static SubStringFrom From(this string s, string start) { return s.From(start, false); } /// /// Renvoie une portion de chaine à partir de la sous-chaine spécifiée, en incluant éventuellement cette dernière /// /// Chaine dont on veut extraire une portion /// Sous-chaine de début de la portion /// true pour inclure la chaine de début spécifiée dans le résultat, false sinon /// La portion de chaine demandée public static SubStringFrom From(this string s, string start, bool includeBoundary) { int iStart = s.IndexOf(start); if (!includeBoundary && iStart >= 0) iStart += start.Length; return new SubStringFrom(s, iStart); } /// /// Termine une portion de chaine à la sous-chaine spécifiée, sans inclure cette dernière /// /// Portion de chaine à terminer /// Sous-chaine de fin /// La portion de chaine demandée public static string To(this SubStringFrom subStringFrom, string end) { return subStringFrom.To(end, false); } /// /// Termine une portion de chaine à la sous-chaine spécifiée, en incluant éventuellement cette dernière /// /// Portion de chaine à terminer /// Sous-chaine de fin /// true pour inclure la chaine de fin spécifiée, false sinon /// La portion de chaine demandée public static string To(this SubStringFrom subStringFrom, string end, bool includeBoundary) { string s = subStringFrom.String; int iStart = subStringFrom.Start; int iEnd = s.IndexOf(end, iStart + 1); if (includeBoundary && iEnd > 0) iEnd += end.Length; if (iStart < 0) return string.Empty; if (iEnd < 0) return s.Substring(iStart); return s.Substring(iStart, iEnd - iStart); } /// /// Représente une portion d'une chaine de caractères à partir d'une position donnée /// public struct SubStringFrom { internal SubStringFrom(string s, int start) : this() { this.String = s; this.Start = start; } /// /// La chaine dont cet objet représente une portion /// public string String { get; private set; } /// /// La position de départ de la portion de chaine /// public int Start { get; private set; } /// /// Convertit implicitement un SubStringForm en String /// /// SubStringForm à convertir /// La chaine correspondante public static implicit operator string(SubStringFrom subStringFrom) { return subStringFrom.ToString(); } /// /// Renvoie une chaine équivalente à la portion de chaine /// /// Une chaine équivalente à la portion de chaine public override string ToString() { if (this.Start < 0) return String.Empty; return this.String.Substring(this.Start); } } #endregion /// /// Vérifie qu'une chaine se termine par le suffixe spécifié et l'ajoute si ce n'est pas le cas. /// /// Chaine originale /// Suffixe à vérifier et éventuellement ajouter /// La chaine originale si elle se termine par le le suffixe spécifié, sinon la chaine originale suivie du suffixe public static string EnsureEndsWith(this string s, string suffix) { s.CheckArgumentNull("s"); if (!s.EndsWith(suffix)) return s + suffix; return s; } /// /// Vérifie qu'une chaine commence par le préfixe spécifié et l'ajoute si ce n'est pas le cas. /// /// Chaine originale /// Préfixe à vérifier et éventuellement ajouter /// La chaine originale si elle commence par le le préfixe spécifié, sinon la chaine originale précédée du préfixe public static string EnsureStartsWith(this string s, string prefix) { s.CheckArgumentNull("s"); if (!s.StartsWith(prefix)) return prefix + s; return s; } private static readonly Regex _emailRegex = new Regex(@"^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$", CH.RegexOptions.Compiled | RegexOptions.IgnoreCase); /// /// Vérifie si une chaine de caractères est une adresse email valide /// ///La chaine à vérifier ///true si la chaine est une adresse email valide, false sinon public static bool IsValidEmail(this string email) { email.CheckArgumentNull("email"); return _emailRegex.IsMatch(email); } /// /// Détermine si deux objets String ont la même valeur. Null et string.Empty sont considérés comme égaux /// /// chaîne servant de base à la comparaison /// chaîne à comparer /// booléen indiquant si les deux chaînes sont égales public static bool EqualsWithNullAsEmpty(this string baseString, string comparedString) { if (string.IsNullOrEmpty(baseString)) return string.IsNullOrEmpty(comparedString); return baseString.Equals(comparedString); } /// /// Détermine si deux objets String ont la même valeur. Null et string.Empty sont considérés comme égaux /// /// chaîne servant de base à la comparaison /// chaîne à comparer /// l'une des valeurs System.StringComparison /// booléen indiquant si les deux chaînes sont égales public static bool EqualsWithNullAsEmpty(this string baseString, string comparedString, StringComparison comparisonType) { if (string.IsNullOrEmpty(baseString)) return string.IsNullOrEmpty(comparedString); return baseString.Equals(comparedString, comparisonType); } /// /// Détermine si une chaine de caractères contient la sous-chaine spécifiée, en utilisant le type de comparison spécifié. /// ///chaine dans laquelle on recherche la sous-chaine ///sous-chaine recherchée ///l'une des valeurs System.StringComparison ///true si s contient subString, sinon false public static bool Contains(this string s, string subString, StringComparison comparisonType) { s.CheckArgumentNull("s"); subString.CheckArgumentNull("subString"); comparisonType.CheckArgumentInEnum("comparisonType"); return s.IndexOf(subString, comparisonType) >= 0; } /// /// Remplace le caractère à la position spécifiée d'une chaine par le caractère spécifié. /// /// Chaine dans laquelle remplacer un caractère /// Position à laquelle remplacer le caractère /// Caractère de remplacement /// La chaine modifiée. /// Tout comme la méthode Replace, cette méthode ne modifie pas /// la chaine d'origine, mais renvoie une nouvelle chaine qui contient la /// modification. public static string ReplaceAt(this string s, int index, char newChar) { s.CheckArgumentNull("s"); index.CheckArgumentOutOfRange("index", 0, s.Length - 1); var chars = s.ToCharArray(); chars[index] = newChar; return new string(chars); } } }