【问题标题】:How to ignore case in String.replace如何在 String.replace 中忽略大小写
【发布时间】:2011-08-26 21:39:24
【问题描述】:
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";

尽管string.Replace 不支持左侧字符串上的ignoreCase,如何将句子中的“骆驼”替换为“马”?

【问题讨论】:

标签: c# string replace ignore-case


【解决方案1】:

使用正则表达式:

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

当然,这也会匹配包含骆驼的单词,但不清楚你是否想要。

如果您需要完全匹配,您可以使用自定义 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 );
    }
} 

与前面的示例一起使用,将匹配项包装在一个跨度中:

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

【讨论】:

  • 如果我只匹配那个特定的词怎么办?我需要匹配文本中的一些关键字,并用 关键字 替换 thme(以及它们),以使它们变为红色。
  • @Luca - 我可能会使用自定义 MatchEvaluator 来检查匹配的子字符串是否在其前后有一个字母或数字,并且仅在没有发生时返回替换字符串。我将添加一个示例(不完整、可能且经过最低限度的测试)。
  • @minitech 好吧,如果单词边界确实有效,当然,但在他的示例中,他想替换引号内的单词。我认为这种情况下的单词边界是引号字符,而不是第一个“c”。自定义匹配评估器允许使用简化的正则表达式进行任意逻辑。
  • 啊。 Lookbehinds 也可能有用,但我知道其中任何一个都值得!
  • 您还应该在正则表达式的模式上使用 Regex.Escape()。
【解决方案2】:

为字符串添加一个扩展方法来解决这个问题:

用法:

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

代码:

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

【讨论】:

  • 这仅替换source 中第一次出现的oldString,与核心Replace 不同
  • 谢谢!才看到这个,我已经更新了答案以包括递归。
【解决方案3】:

这是一个采用 StringComparison 的扩展方法,使用 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();
    }

顺便说一句,这里还有一个类似的 Contains 方法,也采用 StringComparison:

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

一些测试:

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

【讨论】:

    【解决方案4】:

    这是我的扩展方法,它结合了Tom Beech'ssntbob's 的递归性,以及对ksun 指出的错误的更清晰的修复。

    代码:

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

    用法:

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

    结果:

    bbananabananaa

    而且,如果您仍然希望递归性质是可选的:

    代码:

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

    用法:

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

    结果:

    香蕉

    【讨论】:

      【解决方案5】:

      利用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)
          }
      

      如果 C# String 类有像 Java 这样的 ignoreCase() 方法当然会很好。

      【讨论】:

        【解决方案6】:

        你也可以使用 String.IndexOf

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

        这样做可能会比使用 RegExpressions 获得更好的性能(我讨厌它们,因为它们不直观且容易搞砸,尽管这个简单的 .Net 函数调用抽象了实际混乱的 RegEx,并且不提供有很大的错误空间),但这可能不是你关心的问题;这些天电脑真的很快,对吧? :) 采用 StringComparison 对象的 IndexOf 重载允许您有选择地忽略大小写,并且因为 IndexOf 从指定位置返回第一个匹配项,您必须编写一个循环来处理具有多个匹配项的字符串。

        【讨论】:

        • 在库和框架的时代,需要大量检查和调试的性能是大多数时候的受害者。
        • 这就是 Stack Overflow 的用途。 ;)
        【解决方案7】:
            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;
            }
        

        【讨论】:

          【解决方案8】:

          它可能没有其他一些答案那么有效,但我有点喜欢 sntbob 编写的 CustomReplace 函数。

          但是,它有一个缺陷。如果文本替换是递归的,它将导致无限循环。例如,CustomReplace("I eat bananas!","an","banana",false,false) 会导致无限循环,字符串会继续变大。 例如,在第 4 次迭代之后,字符串将是“我吃 bbbbbananaanaanaanaanas!”

          如果您只想替换“banana”中的两个“an”实例,那么您将不得不采取另一种方法。我修改了 sntbob 的代码来解决这种情况。我承认它更复杂,但它处理递归替换。

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

          【讨论】:

            【解决方案9】:

            为什么不直接导入 Microsoft.VisualBasic 命名空间并使用 VB Strings.Replace 方法?

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

            例如

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

            vbTextCompare 强制进行不区分大小写的替换。任务完成。

            好吧,它不是“纯”C#,但它可以让您以更少的复杂性和混乱程度到达您想去的地方。

            【讨论】:

            • 仅仅是因为您不能仅针对这种简单的情况从 Visual Basic 导入命名空间。
            • 认真的吗?为我工作。
            • 使用 System.Collections.Generic;使用 System.Linq;使用 System.Text;使用 System.Threading.Tasks;使用 Microsoft.VisualBasic; namespace stringreplace { class Program { static void Main(string[] args) { var x = Microsoft.VisualBasic.Strings.Replace("a String string", "string", "strong", 1, -1, CompareMethod.Text) ;变量 y = x; } } }
            • 没有比这更简单的案例了!
            【解决方案10】:

            这是使用 StringComparison 作为扩展方法的另一种选择。在 StringBuilder 对象上。我读过一些文章,表明 StringBuilder 在内存方面可能比使用字符串更有效。如果您需要的话,您可以轻松地将其更改为使用字符串。

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

            【讨论】:

            • 能否添加 cmets 使其更易于理解?
            猜你喜欢
            • 2012-11-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多