web-dev-qa-db-fra.com

Existe-t-il une alternative à string.Replace insensible à la casse?

Je dois rechercher une chaîne et remplacer toutes les occurrences de %FirstName% et %PolicyAmount% par une valeur extraite d'une base de données. Le problème est que la capitalisation de FirstName varie. Cela m'empêche d'utiliser la méthode String.Replace(). J'ai vu des pages Web sur le sujet qui suggèrent

Regex.Replace(strInput, strToken, strReplaceWith, RegexOptions.IgnoreCase);

Cependant, pour une raison quelconque, lorsque j'essaie de remplacer %PolicyAmount% par $0, le remplacement n'a jamais lieu. Je suppose que cela a quelque chose à voir avec le signe dollar étant un caractère réservé dans regex. 

Existe-t-il une autre méthode que je peux utiliser qui n'implique pas de nettoyer l'entrée pour traiter les caractères spéciaux de regex?

293
Aheho

De MSDN
$ 0 - "Remplace la dernière sous-chaîne trouvée par un numéro de groupe (décimal)."

Dans le groupe d'expressions régulières .NET, le groupe 0 correspond toujours à l'intégralité de la correspondance. Pour un $ littéral, vous devez

string value = Regex.Replace("%PolicyAmount%", "%PolicyAmount%", @"$$0", RegexOptions.IgnoreCase);
127
Todd White

On dirait que string.Replace devrait avoir une surcharge qui prend un argument StringComparison. Comme ce n'est pas le cas, vous pouvez essayer quelque chose comme ceci:

public static string ReplaceString(string str, string oldValue, string newValue, StringComparison comparison)
{
    StringBuilder sb = new StringBuilder();

    int previousIndex = 0;
    int index = str.IndexOf(oldValue, comparison);
    while (index != -1)
    {
        sb.Append(str.Substring(previousIndex, index - previousIndex));
        sb.Append(newValue);
        index += oldValue.Length;

        previousIndex = index;
        index = str.IndexOf(oldValue, index, comparison);
    }
    sb.Append(str.Substring(previousIndex));

    return sb.ToString();
}
291
C. Dragon 76

Il semble que la méthode la plus simple consiste simplement à utiliser la méthode Replace fournie avec .Net et existante depuis .Net 1.0:

string res = Microsoft.VisualBasic.Strings.Replace(res, 
                                   "%PolicyAmount%", 
                                   "$0", 
                                   Compare: Microsoft.VisualBasic.CompareMethod.Text);

Pour utiliser cette méthode, vous devez ajouter une référence à l'assembly Microsoft.VisualBasic. Cet assemblage est une partie standard du runtime .Net, ce n'est pas un téléchargement supplémentaire ou considéré comme obsolète.

30
Clever Human

Voici une méthode d'extension. Je ne sais pas où je l'ai trouvé.

public static class StringExtensions
{
    public static string Replace(this string originalString, string oldValue, string newValue, StringComparison comparisonType)
    {
        int startIndex = 0;
        while (true)
        {
            startIndex = originalString.IndexOf(oldValue, startIndex, comparisonType);
            if (startIndex == -1)
                break;

            originalString = originalString.Substring(0, startIndex) + newValue + originalString.Substring(startIndex + oldValue.Length);

            startIndex += newValue.Length;
        }

        return originalString;
    }

}
30
rboarman
    /// <summary>
    /// A case insenstive replace function.
    /// </summary>
    /// <param name="originalString">The string to examine.(HayStack)</param>
    /// <param name="oldValue">The value to replace.(Needle)</param>
    /// <param name="newValue">The new value to be inserted</param>
    /// <returns>A string</returns>
    public static string CaseInsenstiveReplace(string originalString, string oldValue, string newValue)
    {
        Regex regEx = new Regex(oldValue,
           RegexOptions.IgnoreCase | RegexOptions.Multiline);
        return regEx.Replace(originalString, newValue);
    }
10
Karl Glennon

Inspiré par la réponse de cfeduke, j'ai créé cette fonction qui utilise IndexOf pour rechercher l'ancienne valeur dans la chaîne, puis la remplace par la nouvelle valeur. Je l'ai utilisé dans un script SSIS traitant des millions de lignes, et la méthode regex était bien plus lente que cela.

public static string ReplaceCaseInsensitive(this string str, string oldValue, string newValue)
{
    int prevPos = 0;
    string retval = str;
    // find the first occurence of oldValue
    int pos = retval.IndexOf(oldValue, StringComparison.InvariantCultureIgnoreCase);

    while (pos > -1)
    {
        // remove oldValue from the string
        retval = retval.Remove(pos, oldValue.Length);

        // insert newValue in it's place
        retval = retval.Insert(pos, newValue);

        // check if oldValue is found further down
        prevPos = pos + newValue.Length;
        pos = retval.IndexOf(oldValue, prevPos, StringComparison.InvariantCultureIgnoreCase);
    }

    return retval;
}
8
JeroenV

Développer la réponse populaire de C. Dragon 76 en transformant son code en une extension surchargeant la méthode Replace par défaut.

public static class StringExtensions
{
    public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison)
    {
        StringBuilder sb = new StringBuilder();

        int previousIndex = 0;
        int index = str.IndexOf(oldValue, comparison);
        while (index != -1)
        {
            sb.Append(str.Substring(previousIndex, index - previousIndex));
            sb.Append(newValue);
            index += oldValue.Length;

            previousIndex = index;
            index = str.IndexOf(oldValue, index, comparison);
        }
        sb.Append(str.Substring(previousIndex));
        return sb.ToString();
     }
}
6
Chad Kuehn

Basé sur la réponse de Jeff Reddy, avec quelques optimisations et validations:

public static string Replace(string str, string oldValue, string newValue, StringComparison comparison)
{
    if (oldValue == null)
        throw new ArgumentNullException("oldValue");
    if (oldValue.Length == 0)
        throw new ArgumentException("String cannot be of zero length.", "oldValue");

    StringBuilder sb = null;

    int startIndex = 0;
    int foundIndex = str.IndexOf(oldValue, comparison);
    while (foundIndex != -1)
    {
        if (sb == null)
            sb = new StringBuilder(str.Length + (newValue != null ? Math.Max(0, 5 * (newValue.Length - oldValue.Length)) : 0));
        sb.Append(str, startIndex, foundIndex - startIndex);
        sb.Append(newValue);

        startIndex = foundIndex + oldValue.Length;
        foundIndex = str.IndexOf(oldValue, startIndex, comparison);
    }

    if (startIndex == 0)
        return str;
    sb.Append(str, startIndex, str.Length - startIndex);
    return sb.ToString();
}
3
Mark Cranness

une version similaire à C. Dragon's, mais si vous n'avez besoin que d'un seul remplacement:

int n = myText.IndexOf(oldValue, System.StringComparison.InvariantCultureIgnoreCase);
if (n >= 0)
{
    myText = myText.Substring(0, n)
        + newValue
        + myText.Substring(n + oldValue.Length);
}
2
Allanrbo

Voici une autre option pour exécuter les remplacements Regex, car peu de gens semblent remarquer que les correspondances contiennent l’emplacement dans la chaîne:

    public static string ReplaceCaseInsensative( this string s, string oldValue, string newValue ) {
        var sb = new StringBuilder(s);
        int offset = oldValue.Length - newValue.Length;
        int matchNo = 0;
        foreach (Match match in Regex.Matches(s, Regex.Escape(oldValue), RegexOptions.IgnoreCase))
        {
            sb.Remove(match.Index - (offset * matchNo), match.Length).Insert(match.Index - (offset * matchNo), newValue);
            matchNo++;
        }
        return sb.ToString();
    }
1
Brandon
Regex.Replace(strInput, strToken.Replace("$", "[$]"), strReplaceWith, RegexOptions.IgnoreCase);
0
Joel Coehoorn

(Puisque tout le monde essaie de ça). Voici ma version (avec contrôles nuls, et saisie correcte et échappement de remplacement) ** Inspiré d'Internet et d'autres versions:

using System;
using System.Text.RegularExpressions;

public static class MyExtensions {
    public static string ReplaceIgnoreCase(this string search, string find, string replace) {
        return Regex.Replace(search ?? "", Regex.Escape(find ?? ""), (replace ?? "").Replace("$", "$$"), RegexOptions.IgnoreCase);          
    }
}

Usage:

var result = "This is a test".ReplaceIgnoreCase("IS", "was");
0
Fredrik Johansson

La méthode d'expression régulière devrait fonctionner. Cependant, vous pouvez également faire une minuscule de la chaîne de la base de données, une minuscule des% variables% dont vous disposez, puis localiser les positions et les longueurs dans la chaîne de casse inférieure de la base de données. Rappelez-vous que les positions dans une chaîne ne changent pas uniquement à cause de sa casse inférieure.

Ensuite, en utilisant une boucle inversée (c’est plus facile, sinon, vous devrez garder un compte permanent de la position des points ultérieurs), supprimez de votre chaîne non majuscule de la base de données les% variables% par leur position longueur et insérez les valeurs de remplacement.

0
cfeduke

Laisse-moi faire mon cas et tu pourras alors me déchirer si tu veux.

Regex n'est pas la solution à ce problème - trop lent et gourmand en mémoire, relativement parlant.

StringBuilder est bien meilleur que le brassage de chaînes.

Etant donné que ce sera une méthode d’extension pour compléter string.Replace, j’estime important de faire correspondre ce principe. Par conséquent, il est important de prévoir des exceptions pour les mêmes arguments, tout comme le renvoi de la chaîne originale si aucun remplacement n’a été effectué.

Je pense qu'avoir un paramètre StringComparison n'est pas une bonne idée ..__ Je l'ai essayé mais le cas de test mentionné à l'origine par michael-liu a montré un problème: -

[TestCase("œ", "oe", "", StringComparison.InvariantCultureIgnoreCase, Result = "")]

Bien que IndexOf corresponde, il existe une discordance entre la longueur de la correspondance dans la chaîne source (1) et oldValue.Length (2). Cela s'est manifesté en provoquant IndexOutOfRange dans certaines autres solutions lorsque oldValue.Length a été ajouté à la position de match en cours et je ne pouvais pas trouver un moyen de contourner cette . Regex ne correspond pas à la casse de toute façon, alors j'ai pris la solution pragmatique de seulement en utilisant StringComparison.OrdinalIgnoreCase pour ma solution.

Mon code est similaire à d’autres réponses, mais j’ai un problème: je cherche une correspondance avant de créer une StringBuilder. Si aucun n'est trouvé, une allocation potentiellement importante est évitée. Le code devient alors un do{...}while plutôt qu'un while{...}

J'ai fait des tests approfondis contre d'autres réponses et cela est sorti légèrement plus vite et a utilisé un peu moins de mémoire.

    public static string ReplaceCaseInsensitive(this string str, string oldValue, string newValue)
    {
        if (str == null) throw new ArgumentNullException(nameof(str));
        if (oldValue == null) throw new ArgumentNullException(nameof(oldValue));
        if (oldValue.Length == 0) throw new ArgumentException("String cannot be of zero length.", nameof(oldValue));

        var position = str.IndexOf(oldValue, 0, StringComparison.OrdinalIgnoreCase);
        if (position == -1) return str;

        var sb = new StringBuilder(str.Length);

        var lastPosition = 0;

        do
        {
            sb.Append(str, lastPosition, position - lastPosition);

            sb.Append(newValue);

        } while ((position = str.IndexOf(oldValue, lastPosition = position + oldValue.Length, StringComparison.OrdinalIgnoreCase)) != -1);

        sb.Append(str, lastPosition, str.Length - lastPosition);

        return sb.ToString();
    }
0
Simon Hewitt