Est-il une alternative à la chaîne.Remplacer qui est insensible à la casse?
J'ai besoin de rechercher une chaîne de caractères et remplacer toutes les occurrences de %FirstName%
et %PolicyAmount%
avec une valeur tirée d'une base de données. Le problème est la capitalisation de FirstName varie. Cela m'empêche d'utiliser la méthode String.Replace()
. J'ai vu des pages web sur le sujet qui suggèrent
Regex.Replace(strInput, strToken, strReplaceWith, RegexOptions.IgnoreCase);
Cependant, pour une raison quelconque, lorsque j'essaie de remplacer %PolicyAmount%
par $0
, Le remplacement n'a jamais lieu. Je suppose que cela a quelque chose à voir avec le signe dollar étant un caractère réservé dans regex.
Est il y a une autre méthode que je peux utiliser qui n'implique pas de désinfecter l'entrée pour traiter les caractères spéciaux regex?
15 réponses
À Partir de MSDN
$0 - " remplace la dernière sous-chaîne correspondant au numéro de groupe (décimal)."
Dans les expressions régulières. net, le groupe 0 correspond toujours à la correspondance entière. Pour un $ littéral, vous devez
string value = Regex.Replace("%PolicyAmount%", "%PolicyAmount%", @"$$0", RegexOptions.IgnoreCase);
Semble string.Replace
devrait - ont une surcharge qui prend un StringComparison
argument. Comme ce n'est pas le cas, vous pouvez essayer quelque chose comme ceci:
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();
}
Une sorte de groupe de réponses confus, en partie parce que le titre de la question Est en fait beaucoup plus grand que la question spécifique posée. Après avoir lu, je ne suis pas sûr qu'une réponse soit à quelques modifications d'assimiler toutes les bonnes choses ici, alors j'ai pensé que j'essaierais de résumer.
Voici une méthode d'extension qui, je pense, évite les pièges mentionnés ici et fournit la solution la plus largement applicable.
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);
}
Donc...
- C'est une méthode d'extension @MarkRobinson
- Ce n'essaie pas D'ignorer Regex @Helge (vous devez vraiment faire octet par octet si vous voulez renifler comme ça en dehors de Regex)
- passe l ' excellent cas de test de @ MichaelLiu ,
"œ".ReplaceCaseInsensitiveFind("oe", "")
, bien qu'il ait peut-être eu un comportement légèrement différent à l'esprit.
Malheureusement, le commentaire de @HA que vous devez Escape
Tous les trois n'est pas correct . La valeur initiale et newValue
n'a pas besoin de être.
Remarque: vous devez cependant échapper $
s dans la nouvelle valeur que vous insérez si elles font partie de ce qui semblerait être un marqueur "valeur capturée" . Ainsi, les trois signes de dollar dans la Regex.Remplacer à l'intérieur de L'expression rationnelle.Remplacer [sic]. Sans cela, quelque chose comme ça se brise...
"This is HIS fork, hIs spoon, hissssssss knife.".ReplaceCaseInsensitiveFind("his", @"he$0r")
Voici l'erreur:
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.
Vous dire quoi, je sais que les gens qui sont à l'aise avec Regex ont l'impression que leur utilisation évite les erreurs, mais je suis souvent toujours partiel aux chaînes de reniflage d'octets (mais seulement après avoir lu Spolsky sur les encodages ) pour être absolument sûr que vous obtenez ce que vous avez prévu pour des cas d'utilisation importants. Cela me rappelle Crockford sur "expressions régulières non sécurisées " un peu. Trop souvent, nous écrivons des expressions rationnelles qui permettent ce que nous voulons (si nous avons de la chance), mais permettent involontairement plus (par exemple, est $10
vraiment une chaîne de "valeur de capture" valide dans mon expression rationnelle newValue, ci-dessus?) parce que nous n'étions pas assez attentionnés. Les deux méthodes ont de la valeur, et les deux encouragent différents types d'erreurs involontaires. Il est souvent facile de sous-estimer la complexité.
Cet étrange $
échappement (et que Regex.Escape
n'a pas échappé aux modèles de valeurs capturées comme $0
comme je l'aurais attendu dans les valeurs de remplacement) m'a rendu fou pendant un moment. La programmation est difficile (c) 1842
Voici une méthode d'extension. Vous ne savez pas où je l'ai trouvé.
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;
}
}
Semble que la méthode la plus simple consiste simplement à utiliser la méthode Replace qui est livrée avec. net et existe depuis. net 1.0:
string res = Microsoft.VisualBasic.Strings.Replace(res,
"%PolicyAmount%",
"$0",
Compare: Microsoft.VisualBasic.CompareMethod.Text);
Pour utiliser cette méthode, vous devez ajouter une Référence à Microsoft.VisualBasic assemblly. Cet assembly est une partie standard du runtime. Net, ce n'est pas un téléchargement supplémentaire ou marqué comme obsolète.
/// <summary>
/// A case insenstive replace function.
/// </summary>
/// <param name="originalString">The string to examine.(HayStack)</param>
/// <param name="oldValue">The value to replace.(Needle)</param>
/// <param name="newValue">The new value to be inserted</param>
/// <returns>A string</returns>
public static string CaseInsenstiveReplace(string originalString, string oldValue, string newValue)
{
Regex regEx = new Regex(oldValue,
RegexOptions.IgnoreCase | RegexOptions.Multiline);
return regEx.Replace(originalString, newValue);
}
Inspiré par cfeduke réponse, j'ai fait une fonction qui utilise IndexOf pour trouver l'ancienne valeur dans la chaîne, puis la remplace par la nouvelle valeur. Je l'ai utilisé dans un script SSIS traitant des millions de lignes, et la méthode regex était beaucoup plus lente que cela.
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;
}
En développant la réponse populaire de C. Dragon 76 en transformant son code en une extension qui surcharge la méthode par défaut 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();
}
}
Basé sur la réponse de Jeff Reddy, avec quelques optimisations et validations:
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();
}
Une version similaire à C. Dragon, mais pour si vous avez seulement besoin d'un seul remplacement:
int n = myText.IndexOf(oldValue, System.StringComparison.InvariantCultureIgnoreCase);
if (n >= 0)
{
myText = myText.Substring(0, n)
+ newValue
+ myText.Substring(n + oldValue.Length);
}
Voici une autre option pour exécuter des remplacements de Regex, car peu de gens semblent remarquer que les correspondances contiennent l'emplacement dans la chaîne:
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);
La méthode d'expression régulière devrait fonctionner. Cependant, ce que vous pouvez également faire est de minuscules la chaîne de la base de données, minuscules les %variables% que vous avez, puis recherchez les positions et les longueurs dans la chaîne inférieure de la base de données. Rappelez-vous, les positions dans une chaîne ne changent pas simplement parce que son boîtier inférieur.
Ensuite, en utilisant une boucle qui va en sens inverse (c'est plus facile, si vous ne le faites pas, vous devrez garder un nombre en cours d'exécution de l'endroit où les points ultérieurs se déplacent) Supprimer de votre non-inférieur chaîne cased de la base de données les variables % % par leur position et leur longueur et insérez les valeurs de remplacement.
(puisque tout le monde prend un coup de feu à cela). Voici ma version (avec des vérifications nulles, et une entrée correcte et un échappement de remplacement) * * inspiré d'internet et d'autres versions:
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);
}
}
Utilisation:
var result = "This is a test".ReplaceIgnoreCase("IS", "was");
Laissez-moi faire mon cas et ensuite vous pouvez me déchirer en lambeaux si vous voulez.
Regex n'est pas la réponse à ce problème - trop lent et affamé de mémoire, relativement parlant.
StringBuilder est beaucoup mieux que le mangling de chaînes.
Puisque ce sera une méthode d'extension pour compléter string.Replace
, je crois qu'il est important de faire correspondre comment cela fonctionne - donc lancer des exceptions pour les mêmes problèmes d'argument est important que de retourner la chaîne d'origine si un remplacement n'a pas été faire.
Je crois qu'avoir un paramètre StringComparison n'est pas une bonne idée. Je l'ai essayé mais le cas de test mentionné à l'origine par michael-liu a montré un problème: -
[TestCase("œ", "oe", "", StringComparison.InvariantCultureIgnoreCase, Result = "")]
Alors que IndexOf correspondra, il y a une discordance entre la longueur de la correspondance dans la chaîne source (1) et oldValue.Longueur (2). Cela s'est manifesté en provoquant IndexOutOfRange dans d'autres solutions quand oldValue.La longueur a été ajoutée à la position actuelle du match et je ne pouvais pas trouver un moyen de contourner ce.
Regex ne parvient pas à correspondre au cas de toute façon, donc j'ai pris la solution pragmatique de n'utiliser que StringComparison.OrdinalIgnoreCase
pour ma solution.
Mon code est similaire aux autres réponses mais ma torsion est que je cherche un match avant d'aller à la peine de créer un StringBuilder
. Si aucun n'est trouvé, une allocation potentiellement importante est évitée. Le code devient alors un do{...}while
plutôt que while{...}
J'ai fait des tests approfondis contre d'autres réponses et cela est sorti fractionnellement plus vite et utilisé un peu moins mémoire.
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();
}