How to ignore case in String.replace

余生长醉 提交于 2019-11-28 20:06:42

Use a regular expression:

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

Of course, this will also match words containing camel, but it's not clear if you want that or not.

If you need exact matches you can use a custom MatchEvaluator.

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

Used with the previous example to wrap the match in a span as:

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

Add an extension method for string to do the trick:

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

Here's an extension method taking a StringComparison, using 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, here's also a similar Contains-method also taking a StringComparison:

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

Some 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));
    }
}
Johnie Karr

Here is my extension method, which combines Tom Beech's, with the recursiveness of sntbob's, and a cleaner fix to the bug that ksun pointed out.

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

Result:

bbananabananaa

And, if you still want the recursive nature to be optional:

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

Result:

bbananaana

Utiltize StringComparison because of its handy 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)
    }

Sure would be nice if the C# String class had an ignoreCase() method like Java.

You could also use String.IndexOf

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

You may get slightly better performance doing it this way than with RegExpressions (I abhor them because they're not intuitive and easy to screw up, although this simple .Net function call abstracts the actual messy RegEx, and doesn't provide much room for error), but that's probably not a concern for you; computers are REALLY fast these days, right? :) The overload for IndexOf that takes a StringComparison object allows you to optionally ignore case, and because IndexOf returns the first occurrence from at a specified position, you'll have to code a loop to process a string having multiple occurrences.

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

It may not be as efficient as some of the other answers, but I kind of like the CustomReplace function written by sntbob.

However, there is a flaw in it. If the text replacement is recursive it will cause an infinite loop. For example, CustomReplace("I eat bananas!","an","banana",false,false) would cause an infinite loop and the string would continue growing larger. For example, after the 4th iteration the string would be "I eat bbbbbananaanaanaanaanas!"

If you want to only replace the two instances of "an" inside "banana" then you'll have to take another approach. I modified sntbob's code to account for this case. I admit that it's much more convoluted, but it handles recursive replacements.

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

Why not just import the Microsoft.VisualBasic namespace and use the VB Strings.Replace method?

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

eg

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

vbTextCompare forces a case-insensitive replacement. Job done.

Okay, it's not 'pure' C#, but it gets you to where you want to go with much less complexity and messing around.

Here's one more alternative that uses StringComparison as an extension method. on a StringBuilder object. I've read some articles indicating that a StringBuilder might be a little more efficient with memory than using strings. You can easily alter this to work with strings if that's what you need.

/// <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;
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!