简体   繁体   English

如何忽略String.replace中的大小写

[英]How to ignore case in 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";

How to replace 'camel' in sentence with 'horse' despite of string.Replace doesn't support ignoreCase on left string? 如何与“马”的句子替换“骆驼”尽管string.Replace不支持ignoreCase左弦?

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. 如果您需要完全匹配,可以使用自定义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: 与前一个示例一起使用以将匹配包装在span中:

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: 这是一个使用String.IndexOf的StringComparison的扩展方法:

    [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: 顺便说一句,这里也是一个类似的Contains方法,也采用了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));
    }
}

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. 这是我的扩展方法,它将Tom Beechsntbob的递归结合起来, 并对ksun指出的bug进行了更清晰的修复。

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 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 bbananaana

Utiltize StringComparison because of its handy OrdinalIgnoreCase 利用StringComparison因为它方便的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. 如果C#String类具有像Java这样的ignoreCase()方法,那肯定会很好。

You could also use String.IndexOf 您也可以使用String.IndexOf

http://msdn.microsoft.com/en-us/library/system.string.indexof.aspx 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; 与RegExpressions相比,你可以通过这种方式获得稍微好一点的表现(我厌恶它们,因为它们不直观且容易搞砸,虽然这个简单的.Net函数调用抽象了实际凌乱的RegEx,并没有提供太多空间错误),但这可能不是你的问题; 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. :)采用StringComparison对象的IndexOf的重载允许您可选地忽略大小写,并且因为IndexOf从指定位置返回第一个匹配项,所以您必须编写一个循环来处理具有多个匹配项的字符串。

    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. 它可能没有其他一些答案那么高效,但我有点像sntbob编写的CustomReplace函数。

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. 例如,CustomReplace(“我吃香蕉!”,“一个”,“香蕉”,假,假)将导致无限循环,字符串将继续变大。 For example, after the 4th iteration the string would be "I eat bbbbbananaanaanaanaanas!" 例如,在第4次迭代之后,字符串将是“我吃bbbbbananaanaanaanaanas!”

If you want to only replace the two instances of "an" inside "banana" then you'll have to take another approach. 如果你只想替换“香蕉”中的两个“an”实例,那么你将不得不采取另一种方法。 I modified sntbob's code to account for this case. 我修改了sntbob的代码来解释这种情况。 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;
    }

Here's one more alternative that uses StringComparison as an extension method. 这是使用StringComparison作为扩展方法的另一种替代方法。 on a StringBuilder object. 在StringBuilder对象上。 I've read some articles indicating that a StringBuilder might be a little more efficient with memory than using strings. 我已经阅读了一些文章,表明StringBuilder可能比使用字符串更有效。 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;
    }

Why not just import the Microsoft.VisualBasic namespace and use the VB Strings.Replace method? 为什么不直接导入Microsoft.VisualBasic命名空间并使用VB Strings.Replace方法?

https://msdn.microsoft.com/en-us/library/microsoft.visualbasic.strings.replace(v=vs.110).aspx 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. vbTextCompare强制不区分大小写的替换。 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. 好吧,它不是'纯粹的'C#,但是它可以让你到达你想去的地方,而且复杂程度更低,而且更糟糕。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM