¿Hay alguna alternativa a la cadena? ¿Reemplazar que no distingue entre mayúsculas y minúsculas?

Necesito buscar una cadena y reemplazar todas las apariciones de %FirstName% y %PolicyAmount% con un valor extraído de una base de datos. El problema es que la capitalización de FirstName varía. Eso me impide usar el método String.Replace() . He visto páginas web sobre el tema que sugieren

 Regex.Replace(strInput, strToken, strReplaceWith, RegexOptions.IgnoreCase); 

Sin embargo, por alguna razón cuando trato de reemplazar %PolicyAmount% con $0 , el reemplazo nunca ocurre. Supongo que tiene algo que ver con que el signo de dólar sea un personaje reservado en expresiones regulares.

¿Hay algún otro método que pueda usar que no implique sanitizar la entrada para tratar con los caracteres especiales de expresiones regulares?

Desde MSDN
$ 0 – “Sustituye la última subcadena por el número de grupo (decimal)”.

En .NET, el grupo de expresiones regulares 0 siempre es la coincidencia completa. Por un $ literal necesitas

 string value = Regex.Replace("%PolicyAmount%", "%PolicyAmount%", @"$$0", RegexOptions.IgnoreCase); 

Parece string.Replace debe tener una sobrecarga que tome un argumento StringComparison . Como no es así, puedes intentar algo como esto:

 public static string ReplaceString(string str, string oldValue, string newValue, StringComparison comparison) { StringBuilder sb = new StringBuilder(); int previousIndex = 0; int index = str.IndexOf(oldValue, comparison); while (index != -1) { sb.Append(str.Substring(previousIndex, index - previousIndex)); sb.Append(newValue); index += oldValue.Length; previousIndex = index; index = str.IndexOf(oldValue, index, comparison); } sb.Append(str.Substring(previousIndex)); return sb.ToString(); } 

Tipo de un grupo confuso de respuestas, en parte porque el título de la pregunta es en realidad mucho más grande que la pregunta específica que se hace. Después de leerlo, no estoy seguro de que haya alguna edición de asimilar todas las cosas buenas aquí, así que pensé que trataría de resumir.

Este es un método de extensión que creo que evita las trampas mencionadas aquí y proporciona la solución más ampliamente aplicable.

 public static string ReplaceCaseInsensitiveFind(this string str, string findMe, string newValue) { return Regex.Replace(str, Regex.Escape(findMe), Regex.Replace(newValue, "\\$[0-9]+", @"$$$0"), RegexOptions.IgnoreCase); } 

Asi que…

  • Este es un método de extensión @MarkRobinson
  • Esto no intenta omitir Regex @Helge (lo que realmente tiene que hacer byte a byte si desea enrutar el sniff así fuera de Regex)
  • Aprueba el excelente caso de prueba de @MichaelLiu, "œ".ReplaceCaseInsensitiveFind("oe", "") , aunque puede haber tenido un comportamiento ligeramente diferente en mente.

Desafortunadamente, el comentario de @HA de que tienes que Escape los tres no es correcto . El valor inicial y newValue no necesitan serlo.

Nota: Sin embargo, debe evitar $ s en el nuevo valor que está insertando si forman parte de lo que parecería ser un marcador de “valor capturado” . Por lo tanto, los tres signos de dólar en Regex.Replace dentro de Regex.Replace [sic]. Sin eso, algo como esto se rompe …

"This is HIS fork, hIs spoon, hissssssss knife.".ReplaceCaseInsensitiveFind("his", @"he$0r")

Aquí está el error:

 An unhandled exception of type 'System.ArgumentException' occurred in System.dll Additional information: parsing "The\hisr\ is\ he\HISr\ fork,\ he\hIsr\ spoon,\ he\hisrsssssss\ knife\." - Unrecognized escape sequence \h. 

Te digo algo, sé que las personas que se sienten cómodas con Regex sienten que su uso evita errores, pero a menudo sigo siendo pariente de oler cadenas de bytes (pero solo después de haber leído las codificaciones de Spolsky ) para estar absolutamente seguro de que estás obteniendo lo que destinado para casos de uso importantes. Me recuerda un poco a Crockford sobre ” expresiones regulares inseguras “. Con demasiada frecuencia escribimos expresiones regulares que permiten lo que deseamos (si tenemos suerte), pero sin querer permite más (por ejemplo, ¿es realmente $10 una cadena válida de “valor de captura” en mi nueva expresión regular de Valor, más arriba?) Porque no estábamos pensativos suficiente. Ambos métodos tienen valor, y ambos fomentan diferentes tipos de errores involuntarios. A menudo es fácil subestimar la complejidad.

Ese raro $ escaping (y que Regex.Escape no escapó patrones de valor capturados como $0 como lo hubiera esperado en los valores de reemplazo) me volvió loco por un tiempo. La progtwigción es difícil (c) 1842

Aquí hay un método de extensión. No estoy seguro de dónde lo encontré.

 public static class StringExtensions { public static string Replace(this string originalString, string oldValue, string newValue, StringComparison comparisonType) { int startIndex = 0; while (true) { startIndex = originalString.IndexOf(oldValue, startIndex, comparisonType); if (startIndex == -1) break; originalString = originalString.Substring(0, startIndex) + newValue + originalString.Substring(startIndex + oldValue.Length); startIndex += newValue.Length; } return originalString; } } 

Parece que el método más fácil es simplemente usar el método Replace que viene con .Net y ha estado disponible desde .Net 1.0:

 string res = Microsoft.VisualBasic.Strings.Replace(res, "%PolicyAmount%", "$0", Compare: Microsoft.VisualBasic.CompareMethod.Text); 

Para utilizar este método, debe agregar una Referencia a Microsoft.VisualBasic ensamblada. Este ensamblaje es una parte estándar del tiempo de ejecución .Net, no es una descarga adicional ni está marcado como obsoleto.

  ///  /// A case insenstive replace function. ///  /// The string to examine.(HayStack) /// The value to replace.(Needle) /// The new value to be inserted /// A string public static string CaseInsenstiveReplace(string originalString, string oldValue, string newValue) { Regex regEx = new Regex(oldValue, RegexOptions.IgnoreCase | RegexOptions.Multiline); return regEx.Replace(originalString, newValue); } 

Inspirado por la respuesta de cfeduke, hice esta función que utiliza IndexOf para encontrar el valor antiguo en la cadena y luego lo reemplaza con el nuevo valor. Usé esto en un script SSIS procesando millones de filas, y el método de expresión regular fue mucho más lento que esto.

 public static string ReplaceCaseInsensitive(this string str, string oldValue, string newValue) { int prevPos = 0; string retval = str; // find the first occurence of oldValue int pos = retval.IndexOf(oldValue, StringComparison.InvariantCultureIgnoreCase); while (pos > -1) { // remove oldValue from the string retval = retval.Remove(pos, oldValue.Length); // insert newValue in it's place retval = retval.Insert(pos, newValue); // check if oldValue is found further down prevPos = pos + newValue.Length; pos = retval.IndexOf(oldValue, prevPos, StringComparison.InvariantCultureIgnoreCase); } return retval; } 

Ampliando la popular respuesta de C. Dragon 76 al hacer que su código se convierta en una extensión que sobrecarga el método predeterminado Replace .

 public static class StringExtensions { public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison) { StringBuilder sb = new StringBuilder(); int previousIndex = 0; int index = str.IndexOf(oldValue, comparison); while (index != -1) { sb.Append(str.Substring(previousIndex, index - previousIndex)); sb.Append(newValue); index += oldValue.Length; previousIndex = index; index = str.IndexOf(oldValue, index, comparison); } sb.Append(str.Substring(previousIndex)); return sb.ToString(); } } 

Basado en la respuesta de Jeff Reddy, con algunas optimizaciones y validaciones:

 public static string Replace(string str, string oldValue, string newValue, StringComparison comparison) { if (oldValue == null) throw new ArgumentNullException("oldValue"); if (oldValue.Length == 0) throw new ArgumentException("String cannot be of zero length.", "oldValue"); StringBuilder sb = null; int startIndex = 0; int foundIndex = str.IndexOf(oldValue, comparison); while (foundIndex != -1) { if (sb == null) sb = new StringBuilder(str.Length + (newValue != null ? Math.Max(0, 5 * (newValue.Length - oldValue.Length)) : 0)); sb.Append(str, startIndex, foundIndex - startIndex); sb.Append(newValue); startIndex = foundIndex + oldValue.Length; foundIndex = str.IndexOf(oldValue, startIndex, comparison); } if (startIndex == 0) return str; sb.Append(str, startIndex, str.Length - startIndex); return sb.ToString(); } 

una versión similar a la de C. Dragon, pero si solo necesitas un reemplazo único:

 int n = myText.IndexOf(oldValue, System.StringComparison.InvariantCultureIgnoreCase); if (n >= 0) { myText = myText.Substring(0, n) + newValue + myText.Substring(n + oldValue.Length); } 

Aquí hay otra opción para ejecutar reemplazos Regex, ya que no mucha gente parece notar que las coincidencias contienen la ubicación dentro de la cadena:

  public static string ReplaceCaseInsensative( this string s, string oldValue, string newValue ) { var sb = new StringBuilder(s); int offset = oldValue.Length - newValue.Length; int matchNo = 0; foreach (Match match in Regex.Matches(s, Regex.Escape(oldValue), RegexOptions.IgnoreCase)) { sb.Remove(match.Index - (offset * matchNo), match.Length).Insert(match.Index - (offset * matchNo), newValue); matchNo++; } return sb.ToString(); } 
 Regex.Replace(strInput, strToken.Replace("$", "[$]"), strReplaceWith, RegexOptions.IgnoreCase); 

El método de expresión regular debería funcionar. Sin embargo, lo que también puede hacer en minúsculas es la cadena de la base de datos, en minúsculas el% de% de las variables que tiene, y luego ubicar las posiciones y longitudes en la cadena inferior de la base de datos. Recuerde, las posiciones en una cuerda no cambian solo porque su carcasa es inferior.

Luego, usando un bucle que vaya en reversa (es más fácil, si no lo hace, deberá mantener un recuento continuo de los puntos posteriores) elimine de la base de datos el% de variables% de la base de datos% variables% por su posición y longitud e inserte los valores de reemplazo.

(Dado que todos están probando esto). Aquí está mi versión (con comprobaciones nulas, entrada correcta y escape de reemplazo) ** Inspirada en Internet y otras versiones:

 using System; using System.Text.RegularExpressions; public static class MyExtensions { public static string ReplaceIgnoreCase(this string search, string find, string replace) { return Regex.Replace(search ?? "", Regex.Escape(find ?? ""), (replace ?? "").Replace("$", "$$"), RegexOptions.IgnoreCase); } } 

Uso:

 var result = "This is a test".ReplaceIgnoreCase("IS", "was"); 

Déjame hacer mi caso y luego puedes hacerme trizas si quieres.

Regex no es la respuesta para este problema: demasiado lento y con falta de memoria, en términos relativos.

StringBuilder es mucho mejor que la manipulación de cadenas.

Dado que este será un método de extensión para complementar la string.Replace . string.Replace , creo que es importante hacer coincidir cómo funciona eso, por lo tanto, lanzar excepciones para los mismos problemas de argumento es importante, así como devolver la cadena original si no se realizó un reemplazo.

Creo que tener un parámetro StringComparison no es una buena idea. Lo probé, pero el caso de prueba mencionado originalmente por Michael-Liu mostró un problema:

 [TestCase("œ", "oe", "", StringComparison.InvariantCultureIgnoreCase, Result = "")] 

Mientras que IndexOf coincidirá, hay una falta de coincidencia entre la duración de la coincidencia en la cadena fuente (1) y oldValue.Length (2). Esto se manifestó al causar IndexOutOfRange en algunas otras soluciones cuando oldValue.Length se agregó a la posición de coincidencia actual y no pude encontrar una forma de evitar esto. Regex no coincide con el caso de todos modos, así que tomé la solución pragmática de usar únicamente StringComparison.OrdinalIgnoreCase para mi solución.

Mi código es similar a otras respuestas, pero mi giro es que busco una coincidencia antes de tomar la molestia de crear un StringBuilder . Si no se encuentra ninguno, se evita una asignación potencialmente grande. El código se convierte en un do{...}while vez de un while{...}

He hecho algunas pruebas extensas contra otras respuestas y esto salió fraccionalmente más rápido y usé un poco menos de memoria.

  public static string ReplaceCaseInsensitive(this string str, string oldValue, string newValue) { if (str == null) throw new ArgumentNullException(nameof(str)); if (oldValue == null) throw new ArgumentNullException(nameof(oldValue)); if (oldValue.Length == 0) throw new ArgumentException("String cannot be of zero length.", nameof(oldValue)); var position = str.IndexOf(oldValue, 0, StringComparison.OrdinalIgnoreCase); if (position == -1) return str; var sb = new StringBuilder(str.Length); var lastPosition = 0; do { sb.Append(str, lastPosition, position - lastPosition); sb.Append(newValue); } while ((position = str.IndexOf(oldValue, lastPosition = position + oldValue.Length, StringComparison.OrdinalIgnoreCase)) != -1); sb.Append(str, lastPosition, str.Length - lastPosition); return sb.ToString(); }