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