Question

Je dois rechercher une chaîne et remplacer toutes les occurrences de % FirstName% et % PolicyAmount% par une valeur extraite d'une base de données. Le problème est que 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);

Toutefois, 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.

Y a-t-il une autre méthode que je peux utiliser qui n'implique pas de nettoyer l'entrée pour traiter les caractères spéciaux des expressions régulières?

Était-ce utile?

La solution

à partir de MSDN
$ 0 - "Remplace la dernière sous-chaîne trouvée par un numéro de groupe (décimal)."

Dans .NET Expressions régulières, le groupe 0 correspond toujours à la correspondance complète. Pour un $ littéral, vous devez

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

Autres conseils

On dirait que string.Replace devrait avoir une surcharge qui prend un argument StringComparison . 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();
}

Type de groupe de réponses prêtant à confusion, en partie parce que le titre de la question est en réalité beaucoup plus grand que la question spécifique posée. Après avoir lu le texte intégral, je ne suis pas sûr que toutes les réponses soient à 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, à mon avis, évite les pièges mentionnés ci-dessus 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]+", @"$
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.
"), RegexOptions.IgnoreCase); }

Alors ...

Malheureusement, @ Le commentaire de HA selon lequel vous devez Echap ne convient pas tous les trois . La valeur initiale et newValue ne doivent pas nécessairement l'être.

Remarque: vous devez toutefois échapper des $ dans la nouvelle valeur que vous insérez si elles font partie de ce qui apparaîtrait. être une "valeur capturée" marqueur . Ainsi, les trois signes dollar dans le Regex.Replace à l’intérieur du Regex.Replace [sic]. Sans cela, quelque chose comme ça casse ...

"C’est sa fourche, sa cuillère, son couteau" .ReplaceCaseInsensitiveFind ("his", "" il $ 0r ")

Voici l'erreur:

<*>

Vous savez quoi, je sais que les gens qui sont à l'aise avec Regex ont le sentiment que leur utilisation évite les erreurs, mais je suis encore souvent enclin à sniffer des chaînes (mais seulement après avoir lu Spolsky sur les codages ) pour être absolument sûr que vous obtenez ce que vous vouliez pour les cas d'utilisation importants. Cela me rappelle Crockford sur & insecure régulier expressions " un peu. Trop souvent, nous écrivons des expressions rationnelles qui autorisent ce que nous voulons (si nous avons de la chance), mais nous en autorisons involontairement davantage (par exemple, Est-ce que $ 10 est vraiment une chaîne valide de "valeur de capture" dans mon expression régulière newValue, ci-dessus ?) parce que nous n'étions pas assez attentionnés. Les deux méthodes ont de la valeur et encouragent différents types d’erreurs non intentionnelles. Il est souvent facile de sous-estimer la complexité.

Cet étrange $ échappé (et ce Regex.Escape n'a pas échappé aux modèles de valeur capturés comme $ 0 comme je l'aurais prévu en remplacement valeurs) m'a rendu fou pendant un moment. La programmation est difficile (c) 1842

Voici une méthode d'extension. Je ne sais 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;
    }

}

La méthode la plus simple consiste simplement à utiliser la méthode Replace fournie avec .Net et qui existe depuis .Net 1.0:

string res = Microsoft.VisualBasic.Strings.Replace(res, 
                                   "%PolicyAmount%", 
                                   "<*>", 
                                   Compare: Microsoft.VisualBasic.CompareMethod.Text);

Pour utiliser cette méthode, vous devez ajouter une référence à l'assembly Microsoft.VisualBasic. Cet assemblage est une pièce standard du runtime .Net. Ce n'est pas un téléchargement supplémentaire, ni 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 la réponse de cfeduke, j'ai créé cette fonction qui utilise IndexOf pour rechercher 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 bien 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;
}

Développement de C . Réponse populaire de Dragon 76 en faisant de son code une extension surchargeant 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();
     }
}

D'après 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's, mais si vous n'avez besoin que 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 les remplacements 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("<*>quot;, "[$]"), strReplaceWith, RegexOptions.IgnoreCase);

La méthode d'expression régulière devrait fonctionner. Cependant, vous pouvez également faire une minuscule de la chaîne de la base de données, une minuscule des% variables% dont vous disposez, puis localiser les positions et les longueurs dans la chaîne de casse inférieure de la base de données. N'oubliez pas que les positions dans une chaîne ne changent pas uniquement à cause de sa casse inférieure.

Ensuite, en utilisant une boucle inversée (c’est plus facile, sinon, vous devrez garder un compte permanent de la position des points suivants), supprimez de votre chaîne non casée inférieure 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 tente sa chance). Voici ma version (avec contrôles nuls, saisie correcte et é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("
var result = "This is a test".ReplaceIgnoreCase("IS", "was");
quot;, "$"), RegexOptions.IgnoreCase); } }

Utilisation:

<*>

Laisse-moi faire valoir mon point de vue et tu pourras ensuite me déchirer en lambeaux si tu veux.

Regex n’est pas la solution à ce problème - trop lent et gourmand en mémoire, relativement parlant.

StringBuilder est bien meilleur que le chagling string.

Puisqu'il s'agira d'une méthode d'extension pour compléter string.Replace , je pense qu'il est important de faire correspondre son fonctionnement. Il est donc important de lever des exceptions pour les mêmes problèmes d'argument que de renvoyer la chaîne d'origine si aucun remplacement n'a été fait.

Je pense 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 montrait 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.Length (2). Cela s'est manifesté en provoquant IndexOutOfRange dans certaines autres solutions lorsque oldValue.Length a été ajouté à la position de correspondance actuelle et que je ne pouvais pas en sortir. De toute façon, Regex ne correspondant pas à la casse, j’ai donc choisi la solution pragmatique consistant à n’utiliser que StringComparison.OrdinalIgnoreCase pour ma solution.

Mon code est similaire à d'autres réponses, mais je trouve que je cherche une correspondance avant de créer un StringBuilder . Si aucun n'est trouvé, une allocation potentiellement importante est évitée. Le code devient alors un do {...} tant que plutôt qu'un tant que {...}

J'ai effectué des tests approfondis par rapport à d'autres réponses, ce qui est sorti légèrement plus vite et utilise un peu moins de 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();
    }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top