web-dev-qa-db-fra.com

Comment ignorer la casse dans String.replace

string sentence = "We know it contains 'camel' Word.";
// Camel can be in different cases:
string s1 = "CAMEL";
string s2 = "CaMEL";
string s3 = "CAMeL";
// ...
string s4 = "Camel";
// ...
string s5 = "camel";

Comment remplacer "chameau" dans la phrase par "cheval" malgré que string.Replace ne supporte pas ignoreCase sur la chaîne de gauche?

37
Xaqron

Utilisez une expression régulière:

var regex = new Regex( "camel", RegexOptions.IgnoreCase );
var newSentence = regex.Replace( sentence, "horse" );

Bien sûr, cela correspondra également aux mots contenant chameau, mais ce n'est pas clair si vous le voulez ou non.

Si vous avez besoin de correspondances exactes, vous pouvez utiliser un MatchEvaluator personnalisé.

public static class Evaluators
{
    public static string Wrap( Match m, string original, string format )
    {
        // doesn't match the entire string, otherwise it is a match
        if (m.Length != original.Length)
        {
            // has a preceding letter or digit (i.e., not a real match).
            if (m.Index != 0 && char.IsLetterOrDigit( original[m.Index - 1] ))
            {
                return m.Value;
            }
            // has a trailing letter or digit (i.e., not a real match).
            if (m.Index + m.Length != original.Length && char.IsLetterOrDigit( original[m.Index + m.Length] ))
            {
                return m.Value;
            }
        }
        // it is a match, apply the format
        return string.Format( format, m.Value );
    }
} 

Utilisé avec l'exemple précédent pour envelopper la correspondance dans une étendue en tant que:

var regex = new Regex( highlightedWord, RegexOptions.IgnoreCase );
foreach (var sentence in sentences)
{
    var evaluator = new MatchEvaluator( match => Evaluators.Wrap( match, sentence, "<span class='red'>{0}</span>" ) );
    Console.WriteLine( regex.Replace( sentence, evaluator ) );
}
51
tvanfosson

Ajoutez une méthode d'extension pour que string fasse l'affaire:

Usage:

string yourString = "TEXTTOREPLACE";
yourString.Replace("texttoreplace", "Look, I Got Replaced!", StringComparison.OrdinalIgnoreCase);

Code:

using System;
using System.Collections.Generic;
using System.IO;

public static class Extensions
{       
    public static string Replace(this string source, string oldString, string newString, StringComparison comp)
    {
        int index = source.IndexOf(oldString, comp);

        // Determine if we found a match
        bool MatchFound = index >= 0;

        if (MatchFound)
        {
            // Remove the old text
            source = source.Remove(index, oldString.Length);

            // Add the replacemenet text
            source = source.Insert(index, newString);
        }

        // recurse for multiple instances of the name
        if (source.IndexOf(oldString, comp) != -1)
        {
            source = Replace(source, oldString, newString, comp);
        }

        return source;
    }
}
17
Tom Beech

Voici une méthode d'extension utilisant StringComparison, à l'aide de string.IndexOf:

    [Pure]
    public static string Replace(this string source, string oldValue, string newValue, StringComparison comparisonType)
    {
        if (source.Length == 0 || oldValue.Length == 0)
            return source;

        var result = new System.Text.StringBuilder();
        int startingPos = 0;
        int nextMatch;
        while ((nextMatch = source.IndexOf(oldValue, startingPos, comparisonType)) > -1)
        {
            result.Append(source, startingPos, nextMatch - startingPos);
            result.Append(newValue);
            startingPos = nextMatch + oldValue.Length;
        }
        result.Append(source, startingPos, source.Length - startingPos);

        return result.ToString();
    }

Btw, voici également une méthode similaire Contains prenant également un StringComparison:

    [Pure]
    public static bool Contains(this string source, string value, StringComparison comparisonType)
    {
        return source.IndexOf(value, comparisonType) >= 0;
    }

Quelques tests:

[TestFixture]
public class ExternalTests
{
    private static string[] TestReplace_args =
        {
            "ab/B/c/ac",
            "HELLO World/Hello/Goodbye/Goodbye World",
            "Hello World/world/there!/Hello there!",
            "hello WoRlD/world/there!/hello there!",
            "///",
            "ab///ab",
            "/ab/cd/",
            "a|b|c|d|e|f/|//abcdef",
            "a|b|c|d|e|f|/|/:/a:b:c:d:e:f:",
        };

    [Test, TestCaseSource("TestReplace_args")]
    public void TestReplace(string teststring)
    {
        var split = teststring.Split("/");
        var source = split[0];
        var oldValue = split[1];
        var newValue = split[2];
        var result = split[3];
        Assert.That(source.Replace(oldValue, newValue, StringComparison.OrdinalIgnoreCase), Is.EqualTo(result));
    }
}
12
johv

Voici ma méthode d'extension, qui combine les actions de Tom Beech , avec le caractère récursif de sntbob , et une correction plus nette du bogue signalé par ksun .

Code:

public static string Replace(this string source, string oldString, 
                             string newString, StringComparison comparison)
{
    int index = source.IndexOf(oldString, comparison);

    while (index > -1)
    {
        source = source.Remove(index, oldString.Length);
        source = source.Insert(index, newString);

        index = source.IndexOf(oldString, index + newString.Length, comparison);
    }

    return source;
}

Usage:

string source = "banana";
Console.WriteLine(source.Replace("AN", "banana", StringComparison.OrdinalIgnoreCase));

Résultat:

bbananabananaa

Et si vous voulez toujours que la nature récursive soit optionnelle:

Code:

public static string Replace(this string source, string oldString, 
                             string newString, StringComparison comparison,
                             bool recursive = true)
{
    int index = source.IndexOf(oldString, comparison);

    while (index > -1)
    {
        source = source.Remove(index, oldString.Length);
        source = source.Insert(index, newString);

        if (!recursive)
        {
            return source;
        }
        index = source.IndexOf(oldString, index + newString.Length, comparison);
    }

    return source;
}

Usage:

string source = "banana";
Console.WriteLine(source.Replace("AN", "banana", StringComparison.OrdinalIgnoreCase, false));

Résultat:

bbananaana

6
Johnie Karr

Utiltize StringComparison en raison de sa facilité OrdinalIgnoreCase

    string sentence = "We know it contains 'camel' Word."; 
    string wordToFind = "camel";
    string replacementWord = "horse";

    int index = sentence.IndexOf(wordToFind , StringComparison.OrdinalIgnoreCase)
    // Did we match the Word regardless of case
    bool match = index >= 0;

    // perform the replace on the matched Word
    if(match) {
        sentence = sentence.Remove(index, wordToFind.Length)
        sentence = sentence.Insert(index, replacementWord)
    }

Ce serait bien si la classe C # String avait une méthode ignoreCase() comme Java.

3
whiteshooz

Vous pouvez également utiliser String.IndexOf 

http://msdn.Microsoft.com/en-us/library/system.string.indexof.aspx

Vous obtiendrez peut-être de meilleures performances en procédant de cette façon qu'avec RegExpressions (je les abhorre car elles ne sont pas intuitives et faciles à bousiller, bien que cet appel de fonction .Net abstraite le RegEx réellement désordonné, erreur), mais cela ne vous concerne probablement pas; les ordinateurs sont vraiment rapides ces jours-ci, non? :) La surcharge pour IndexOf qui prend un objet StringComparison vous permet d'ignorer éventuellement la casse et, comme IndexOf renvoie la première occurrence à une position spécifiée, vous devrez coder une boucle pour traiter une chaîne comportant plusieurs occurrences.

2
Quanta
    public static string CustomReplace(string srcText, string toFind, string toReplace, bool matchCase, bool replace0nce)
    {
        StringComparison sc = StringComparison.OrdinalIgnoreCase;
        if (matchCase)
            sc = StringComparison.Ordinal;

        int pos;
        while ((pos = srcText.IndexOf(toFind, sc)) > -1)
        {
            srcText = srcText.Remove(pos, toFind.Length);
            srcText = srcText.Insert(pos, toReplace);

            if (replace0nce)
                break;
        }

        return srcText;
    }
1
sntbob

Cela peut ne pas être aussi efficace que certaines des autres réponses, mais j'aime bien la fonction CustomReplace écrite par sntbob.

Cependant, il y a un défaut. Si le remplacement de texte est récursif, cela provoquera une boucle infinie. Par exemple, CustomReplace ("je mange des bananes!", "Une", "banane", false, false) provoquerait une boucle infinie et la chaîne continuerait de grossir . Par exemple, après la 4ème itération, la chaîne be "je mange bbbbbananaanaanaanaanas!"

Si vous souhaitez uniquement remplacer les deux instances de "une" banane "à l'intérieur", vous devrez alors adopter une autre approche. J'ai modifié le code de sntbob pour tenir compte de ce cas. J'admets que c'est beaucoup plus compliqué, mais ça gère les remplacements récursifs.

public static string CustomReplace(string srcText, string toFind, string toReplace, bool matchCase, bool replaceOnce)
    {
        StringComparison sc = StringComparison.OrdinalIgnoreCase;
        if (matchCase)
            sc = StringComparison.Ordinal;

        int pos;
        int previousProcessedLength = 0;
        string alreadyProcessedTxt = "";
        string remainingToProcessTxt = srcText;
        while ((pos = remainingToProcessTxt.IndexOf(toFind, sc)) > -1)
        {
            previousProcessedLength = alreadyProcessedTxt.Length;
            //Append processed text up until the end of the found string and perform replacement
            alreadyProcessedTxt += remainingToProcessTxt.Substring(0, pos + toFind.Length);
            alreadyProcessedTxt = alreadyProcessedTxt.Remove(previousProcessedLength + pos, toFind.Length);
            alreadyProcessedTxt = alreadyProcessedTxt.Insert(previousProcessedLength + pos, toReplace);

            //Remove processed text from remaining
            remainingToProcessTxt = remainingToProcessTxt.Substring(pos + toFind.Length);                

            if (replaceOnce)
                break;
        }

        return alreadyProcessedTxt + remainingToProcessTxt;
    }
1
ksun

Pourquoi ne pas simplement importer l'espace de noms Microsoft.VisualBasic et utiliser la méthode VB Strings.Replace?

https://msdn.Microsoft.com/en-us/library/Microsoft.visualbasic.strings.replace(v=vs.110).aspx

par exemple 

var newString = Strings.Replace(SourceString, FindTextValue, ReplacementTextValue, 1, -1, Constants.vbTextCompare);

vbTextCompare force un remplacement insensible à la casse. Travail accompli. 

D'accord, ce n'est pas du 'pur' C #, mais cela vous amène là où vous voulez aller avec beaucoup moins de complexité et de déconner.

0
Neil Haughton

Voici une autre alternative qui utilise StringComparison en tant que méthode d'extension. sur un objet StringBuilder. J'ai lu des articles indiquant qu'un StringBuilder pourrait être un peu plus efficace en mémoire que l'utilisation de chaînes. Vous pouvez facilement modifier cela pour travailler avec des chaînes si c'est ce dont vous avez besoin.

/// <summary>
/// Extension method to find/replace replaces text in a StringBuilder object
/// </summary>
/// <param name="original">Source StringBuilder object</param>
/// <param name="oldString">String to search for</param>
/// <param name="newString">String to replace each occurrance of oldString</param>
/// <param name="stringComparison">String comparison to use</param>
/// <returns>Original Stringbuilder with replacements made</returns>
public static StringBuilder Replace(this StringBuilder original,
                    string oldString, string newString, StringComparison stringComparison)
    {
        //If anything is null, or oldString is blank, exit with original value
        if ( newString == null || original == null || string.IsNullOrEmpty(oldString))
            return original;

        //Convert to a string and get starting position using
        //IndexOf which allows us to use StringComparison.
        int pos = original.ToString().IndexOf(oldString, 0, stringComparison);

        //Loop through until we find and replace all matches
        while ( pos >= 0 )
        {
            //Remove the old string and insert the new one.
            original.Remove(pos, oldString.Length).Insert(pos, newString);

            //Get the next match starting 1 character after last replacement (to avoid a possible infinite loop)
            pos = original.ToString().IndexOf(oldString, pos + newString.Length + 1, stringComparison);
        }
        return original;
    }
0
J.J. Willis