c# - Cómo ignorar caso en String.replace
ignore-case (10)
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";
Cómo reemplazar ''camel'' en la oración con ''caballo'' a pesar de la string.Replace
¿ string.Replace
no admite ignoreCase
de la izquierda?
¿Por qué no importar el espacio de nombres Microsoft.VisualBasic y usar el método VB Strings.Replace?
https://msdn.microsoft.com/en-us/library/microsoft.visualbasic.strings.replace(v=vs.110).aspx
p.ej
var newString = Strings.Replace(SourceString, FindTextValue, ReplacementTextValue, 1, -1, Constants.vbTextCompare);
vbTextCompare fuerza un reemplazo insensible a mayúsculas y minúsculas. Trabajo hecho.
De acuerdo, no es C # puro, pero te lleva a donde quieres ir con mucha menos complejidad y problemas.
Agregue un método de extensión para la cadena para hacer el truco:
Uso:
string yourString = "TEXTTOREPLACE";
yourString.Replace("texttoreplace", "Look, I Got Replaced!", StringComparison.OrdinalIgnoreCase);
Código:
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);
}
return source;
}
}
Aquí está mi método de extensión, que combina Tom Beech , con la recursividad de sntbob''s , y una solución más limpia para el error que ksun señaló.
Código:
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;
}
Uso:
string source = "banana";
Console.WriteLine(source.Replace("AN", "banana", StringComparison.OrdinalIgnoreCase));
Resultado:
bbananabananaa
Y, si aún desea que la naturaleza recursiva sea opcional:
Código:
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;
}
Uso:
string source = "banana";
Console.WriteLine(source.Replace("AN", "banana", StringComparison.OrdinalIgnoreCase, false));
Resultado:
bbananaana
Aquí hay un método de extensión que toma un StringComparison, usando 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();
}
Por cierto, aquí también hay un método Contiene similar que también toma un StringComparison:
[Pure]
public static bool Contains(this string source, string value, StringComparison comparisonType)
{
return source.IndexOf(value, comparisonType) >= 0;
}
Algunas pruebas:
[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));
}
}
Aquí hay una alternativa más que usa StringComparison como método de extensión. en un objeto StringBuilder. He leído algunos artículos que indican que un StringBuilder podría ser un poco más eficiente con la memoria que usar cadenas. Puede modificar esto fácilmente para trabajar con cadenas si eso es lo que necesita.
/// <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;
}
Puede que no sea tan eficiente como algunas de las otras respuestas, pero me gusta la función CustomReplace escrita por sntbob.
Sin embargo, hay un defecto en eso. Si el reemplazo de texto es recursivo, producirá un bucle infinito. Por ejemplo, CustomReplace ("¡Como plátanos!", "An", "banana", falso, falso) causaría un ciclo infinito y la cadena continuaría creciendo. Por ejemplo, después de la cuarta iteración, la cadena sería "¡como bbbbbananaanaanaanaanas!"
Si solo quieres reemplazar las dos instancias de "an" dentro de "banana", entonces tendrás que tomar otro enfoque. Modifiqué el código de sntbob para dar cuenta de este caso. Admito que es mucho más intrincado, pero maneja reemplazos recursivos.
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;
}
También podría usar String.IndexOf
http://msdn.microsoft.com/en-us/library/system.string.indexof.aspx
Puede obtener un rendimiento un poco mejor haciéndolo de esta manera que con RegExpressions (los aborrezco porque no son intuitivos y fáciles de arruinar, aunque esta simple llamada de función .Net abstrae el RegEx desordenado real, y no proporciona mucho espacio para error), pero eso probablemente no es una preocupación para ti; las computadoras son REALMENTE rápidas estos días, ¿verdad? :) La sobrecarga de IndexOf que toma un objeto StringComparison le permite opcionalmente ignorar mayúsculas y minúsculas, y dado que IndexOf devuelve la primera ocurrencia desde una posición específica, tendrá que codificar un bucle para procesar una cadena que tenga varias ocurrencias.
Use una expresión regular:
var regex = new Regex( "camel", RegexOptions.IgnoreCase );
var newSentence = regex.Replace( sentence, "horse" );
Por supuesto, esto también coincidirá con las palabras que contienen camello, pero no está claro si quieres eso o no.
Si necesita coincidencias exactas, puede utilizar un MatchEvaluator personalizado.
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 );
}
}
Se usó con el ejemplo anterior para ajustar la coincidencia en un lapso como:
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 ) );
}
Utilizar StringComparison
debido a su práctica 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)
}
Claro que sería bueno si la clase C # String tuviera un método ignoreCase()
como Java.
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;
}