Gibt es eine Alternative zu string.Replace, bei der die Groß- und Kleinschreibung nicht berücksichtigt wird?

306

Ich muss eine Zeichenfolge suchen und alle Vorkommen von %FirstName%und %PolicyAmount%durch einen Wert aus einer Datenbank ersetzen . Das Problem ist, dass die Großschreibung von Vorname unterschiedlich ist. Das hindert mich daran, die String.Replace()Methode anzuwenden. Ich habe Webseiten zu diesem Thema gesehen, die darauf hindeuten

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

Aber aus irgendeinem Grund , wenn ich versuche und ersetzen %PolicyAmount%mit $0, nimmt der Ersatz nie statt. Ich gehe davon aus, dass es etwas damit zu tun hat, dass das Dollarzeichen ein reserviertes Zeichen in Regex ist.

Gibt es eine andere Methode, die ich verwenden kann, bei der die Eingabe nicht bereinigt wird, um mit Regex-Sonderzeichen umzugehen?

Aheho
quelle
1
Wenn "$ 0" die Variable ist, die eingegeben wird, hat dies keinerlei Auswirkungen auf den regulären Ausdruck.
cfeduke

Antworten:

132

Von MSDN
$ 0 - "Ersetzt den letzten Teilstring, der mit der Gruppennummernnummer (dezimal) übereinstimmt."

In regulären .NET-Ausdrücken ist Gruppe 0 immer die gesamte Übereinstimmung. Für ein buchstäbliches $ müssen Sie

string value = Regex.Replace("%PolicyAmount%", "%PolicyAmount%", @"$$0", RegexOptions.IgnoreCase);
Todd White
quelle
16
In diesem speziellen Fall ist dies in Ordnung, aber in Fällen, in denen die Zeichenfolgen von außen eingegeben werden, kann man nicht sicher sein, dass sie keine Zeichen enthalten, die in regulären Ausdrücken etwas Besonderes bedeuten
Allanrbo
23
Sie sollten Sonderzeichen wie diese maskieren: string value = Regex.Replace ("% PolicyAmount%", Regex.Escape ("% PolicyAmount%"), Regex.Escape ("$ 0"), RegexOptions.IgnoreCase);
Helge Klein
8
Bitte beachten Sie, wenn Sie Regex.Escape in Regex.Replace verwenden. Sie müssen alle drei übergebenen Zeichenfolgen umgehen und Regex.Unescape für das Ergebnis aufrufen!
Holger Adam
4
Laut msdn: "Zeichen-Escapezeichen werden in Mustern mit regulären Ausdrücken erkannt, jedoch nicht in Ersatzmustern." ( msdn.microsoft.com/en-us/library/4edbef7e.aspx )
Bronek
1
Am besten verwenden Sie: string value = Regex.Replace ("% PolicyAmount%", Regex.Escape ("% PolicyAmount%"), "$ 0" .Replace ("$", "$$"), RegexOptions.IgnoreCase); als Ersatz erkennt nur Dolarzeichen.
Skorek
295

Scheint, als string.Replace hätte es eine Überladung, die ein StringComparisonArgument braucht . Da dies nicht der Fall ist, können Sie Folgendes ausprobieren:

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();
}
C. Drache 76
quelle
9
Nett. Ich würde ändern ReplaceStringzu Replace.
AMissico
41
Stimmen Sie den obigen Kommentaren zu. Dies kann zu einer Erweiterungsmethode mit demselben Methodennamen gemacht werden. Fügen Sie es einfach in eine statische Klasse mit der Methodensignatur ein: public static string Replace (dieser String str, String oldValue, String newValue, StringComparison-Vergleich)
Mark Robinson
8
@Helge, im Allgemeinen mag das in Ordnung sein, aber ich muss dem Benutzer beliebige Zeichenfolgen abnehmen und kann nicht riskieren, dass die Eingabe für Regex von Bedeutung ist. Natürlich könnte ich eine Schleife schreiben und vor jedes einzelne Zeichen einen Backslash setzen ... An diesem Punkt könnte ich genauso gut das Obige tun (IMHO).
Jim
9
Während des Unit-Tests stieß ich auf den Fall, dass es nie zurückkehren würde, wenn oldValue == newValue == "".
Ishmael
10
Das ist fehlerhaft; ReplaceString("œ", "oe", "", StringComparison.InvariantCulture)wirft ArgumentOutOfRangeException.
Michael Liu
45

Eine Art verwirrende Gruppe von Antworten, zum Teil, weil der Titel der Frage tatsächlich viel größer ist als die spezifische Frage, die gestellt wird. Nach dem Durchlesen bin ich mir nicht sicher, ob eine Antwort ein paar Änderungen davon entfernt ist, all die guten Sachen hier zu assimilieren, also dachte ich mir, ich würde versuchen, es zusammenzufassen.

Hier ist eine Erweiterungsmethode, die meiner Meinung nach die hier genannten Fallstricke vermeidet und die am weitesten verbreitete Lösung bietet.

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

So...

Leider ist der Kommentar von @HA, den Sie zu Escapeallen drei haben, nicht korrekt . Der Anfangswert undnewValue muss nicht sein.

Hinweis: Sie müssen jedoch $s in dem neuen Wert, den Sie einfügen, maskieren, wenn sie Teil eines Markers sind, der als "erfasster Wert" erscheint . Somit sind die drei Dollarzeichen im Regex.Replace im Regex.Replace enthalten. Ohne das bricht so etwas ...

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

Hier ist der Fehler:

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.

Ich weiß, dass Leute, die mit Regex vertraut sind, das Gefühl haben, dass ihre Verwendung Fehler vermeidet, aber ich bin oft immer noch daran interessiert, Zeichenfolgen zu schnüffeln (aber erst, nachdem ich Spolsky über Codierungen gelesen habe ), um absolut sicher zu sein, dass Sie das bekommen, was Sie haben bestimmt für wichtige Anwendungsfälle. Erinnert mich ein wenig an Crockford über " unsichere reguläre Ausdrücke ". Zu oft schreiben wir reguläre Ausdrücke, die zulassen, was wir wollen (wenn wir Glück haben), aber ungewollt mehr zulassen (z. B. Is$10 in meinem neuen regulären Ausdruck oben wirklich ein gültiger "Capture Value" -String?), Weil wir nicht nachdenklich genug waren . Beide Methoden haben Wert und beide fördern unterschiedliche Arten von unbeabsichtigten Fehlern. Komplexität ist oft leicht zu unterschätzen.

Dieses seltsame $Entkommen (und das entkam Regex.Escapenicht den erfassten Wertemustern, wie $0ich es bei Ersatzwerten erwartet hätte) machte mich für eine Weile verrückt. Die Programmierung ist schwierig (c) 1842

Ruffin
quelle
32

Hier ist eine Erweiterungsmethode. Ich bin mir nicht sicher, wo ich es gefunden habe.

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

}
Rboarman
quelle
Möglicherweise müssen Sie Fälle mit leeren / Null-Zeichenfolgen behandeln.
Vad
2
Mehrere Fehler in dieser Lösung: 1. Überprüfen Sie originalString, oldValue und newValue auf null. 2. Geben Sie orginalString nicht zurück (funktioniert nicht, einfache Typen werden nicht als Referenz übergeben), sondern weisen Sie den Wert von orginalValue zuerst einer neuen Zeichenfolge zu, ändern Sie ihn und geben Sie ihn zurück.
RWC
31

Die einfachste Methode scheint einfach die Ersetzungsmethode zu verwenden, die im Lieferumfang von .Net enthalten ist und seit .Net 1.0 verfügbar ist:

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

Um diese Methode verwenden zu können, müssen Sie der Assembly Microsoft.VisualBasic einen Verweis hinzufügen. Diese Assembly ist ein Standardbestandteil der .NET-Laufzeit, kein zusätzlicher Download oder als veraltet markiert.

CleverPatrick
quelle
4
Es klappt. Sie müssen einen Verweis auf die Microsoft.VisualBasic-Assembly hinzufügen.
CleverPatrick
Seltsam, dass diese Methode einige Probleme hatte, als ich sie verwendete (Zeichen am Zeilenanfang gingen verloren). Die beliebteste Antwort hier von C. Dragon 76hat wie erwartet funktioniert.
Jeremy Thompson
1
Das Problem dabei ist, dass eine NEUE Zeichenfolge zurückgegeben wird, auch wenn keine Ersetzung erfolgt, wobei string.replace () einen Zeiger auf dieselbe Zeichenfolge zurückgibt. Kann ineffizient werden, wenn Sie so etwas wie eine Serienbriefzusammenführung durchführen.
Brain2000
4
Brain2000, du liegst falsch. Alle Zeichenfolgen in .NET sind unveränderlich.
Der_Meister
Der_Meister, obwohl das, was Sie sagen, richtig ist, macht das nicht das, was Brain2000 gesagt hat, falsch.
Simon Hewitt
11
    /// <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);
    }
Karl Glennon
quelle
Welcher ist besser? Was ist mit stackoverflow.com/a/244933/206730 ? bessere Leistung?
Kiquenet
8

Inspiriert von der Antwort von cfeduke habe ich diese Funktion erstellt, die IndexOf verwendet, um den alten Wert in der Zeichenfolge zu finden und ihn dann durch den neuen Wert zu ersetzen. Ich habe dies in einem SSIS-Skript verwendet, das Millionen von Zeilen verarbeitet, und die Regex-Methode war viel langsamer als diese.

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;
}
JeroenV
quelle
+1 für die Nichtverwendung von Regex, wenn dies nicht erforderlich ist. Sicher, Sie verwenden ein paar weitere Codezeilen, aber es ist viel effizienter als das Ersetzen auf Regex-Basis, es sei denn, Sie benötigen die $ -Funktionalität.
ChrisG
6

Erweitern Sie die beliebte Antwort von C. Dragon 76 , indem Sie seinen Code in eine Erweiterung verwandeln, die die Standardmethode überlastet 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();
     }
}
Chad Kuehn
quelle
3

Basierend auf Jeff Reddys Antwort mit einigen Optimierungen und Validierungen:

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();
}
Mark Cranness
quelle
2

eine Version ähnlich der von C. Dragon, aber wenn Sie nur einen einzigen Ersatz benötigen:

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

Hier ist eine weitere Option zum Ausführen von Regex-Ersetzungen, da nicht viele Leute zu bemerken scheinen, dass die Übereinstimmungen die Position innerhalb der Zeichenfolge enthalten:

    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();
    }
Brandon
quelle
Können Sie erklären, warum Sie mit MatchNo multiplizieren?
Aheho
Wenn es einen Längenunterschied zwischen dem alten und dem neuen Wert gibt, wird die Zeichenfolge länger oder kürzer, wenn Sie Werte ersetzen. match.Index bezieht sich auf die ursprüngliche Position innerhalb der Zeichenfolge. Wir müssen die Bewegung dieser Positionen aufgrund unseres Ersatzes anpassen. Ein anderer Ansatz wäre, das Entfernen / Einfügen von rechts nach links auszuführen.
Brandon
Ich verstehe das. Dafür ist die Variable "Offset" gedacht. Was ich nicht verstehe ist, warum Sie mit matchNo multiplizieren. Meine Intuition sagt mir, dass die Position einer Übereinstimmung innerhalb einer Zeichenfolge keine Beziehung zur tatsächlichen Anzahl früherer Vorkommen haben würde.
Aheho
Egal, ich verstehe es jetzt. Der Versatz muss basierend auf der Anzahl der Vorkommen skaliert werden. Wenn Sie jedes Mal 2 Zeichen verlieren, wenn Sie eine Ersetzung durchführen müssen, müssen Sie dies berücksichtigen, wenn Sie die Parameter für die Entfernungsmethode berechnen
Aheho
0
Regex.Replace(strInput, strToken.Replace("$", "[$]"), strReplaceWith, RegexOptions.IgnoreCase);
Joel Coehoorn
quelle
3
Das funktioniert nicht. Das $ ist nicht im Token. Es ist im strReplace With string.
Aheho
9
Und dafür können Sie es nicht anpassen?
Joel Coehoorn
18
Diese Seite soll ein Aufbewahrungsort für korrekte Antworten sein. Keine Antworten, die fast richtig sind.
Aheho
0

Die Methode für reguläre Ausdrücke sollte funktionieren. Sie können jedoch auch die Zeichenfolge aus der Datenbank in Kleinbuchstaben, die% -Variablen% in Kleinbuchstaben und dann die Positionen und Längen in der Zeichenfolge in Kleinbuchstaben aus der Datenbank suchen. Denken Sie daran, dass sich die Positionen in einer Zeichenfolge nicht ändern, nur weil sie in einem niedrigeren Gehäuse angeordnet sind.

Wenn Sie dann eine Schleife verwenden, die in umgekehrter Reihenfolge abläuft (es ist einfacher, wenn Sie dies nicht tun, müssen Sie die Anzahl der Punkte, zu denen sich spätere Punkte bewegen, laufen lassen), entfernen Sie die% -Variablen% nach ihrer Position und aus Ihrer nicht untergeordneten Zeichenfolge aus der Datenbank Länge und geben Sie die Ersatzwerte ein.

cfeduke
quelle
Mit "umgekehrt" meine ich, die gefundenen Positionen in umgekehrter Reihenfolge vom weitesten zum kürzesten zu verarbeiten und nicht die Zeichenfolge aus der Datenbank in umgekehrter Reihenfolge zu durchlaufen.
cfeduke
Sie könnten, oder Sie könnten einfach den Regex verwenden :)
Ray
0

(Da jeder einen Versuch macht). Hier ist meine Version (mit Nullprüfungen und korrekter Eingabe und Ersetzung) ** Inspiriert aus dem Internet und anderen Versionen:

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

Verwendungszweck:

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

Lassen Sie mich meinen Fall machen und dann können Sie mich in Stücke reißen, wenn Sie möchten.

Regex ist nicht die Antwort auf dieses Problem - relativ gesehen zu langsam und speicherhungrig.

StringBuilder ist viel besser als String Mangling.

Da dies eine ergänzende Erweiterungsmethode sein wird, halte string.Replaceich es für wichtig, die Funktionsweise anzupassen. Daher ist es wichtig, Ausnahmen für dieselben Argumentprobleme auszulösen, wie die Rückgabe der ursprünglichen Zeichenfolge, wenn keine Ersetzung vorgenommen wurde.

Ich glaube, dass ein StringComparison-Parameter keine gute Idee ist. Ich habe es versucht, aber der ursprünglich von Michael-Liu erwähnte Testfall zeigte ein Problem:

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

Während IndexOf übereinstimmt, besteht eine Nichtübereinstimmung zwischen der Länge der Übereinstimmung in der Quellzeichenfolge (1) und oldValue.Length (2). Dies manifestierte sich darin, dass IndexOutOfRange in einigen anderen Lösungen verursacht wurde, als oldValue.Length zur aktuellen Übereinstimmungsposition hinzugefügt wurde und ich keinen Weg fand, dies zu umgehen. Da Regex ohnehin nicht mit dem Fall übereinstimmt, habe ich die pragmatische Lösung gewählt, nur StringComparison.OrdinalIgnoreCasefür meine Lösung zu verwenden.

Mein Code ähnelt anderen Antworten, aber meine Wendung ist, dass ich nach einer Übereinstimmung suche, bevor ich mir die Mühe mache, eine zu erstellen StringBuilder. Wenn keine gefunden wird, wird eine möglicherweise große Zuordnung vermieden. Der Code wird dann do{...}whileeher zu einem als zu einemwhile{...}

Ich habe einige umfangreiche Tests mit anderen Antworten durchgeführt und diese kamen etwas schneller heraus und verbrauchten etwas weniger Speicher.

    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();
    }
Simon Hewitt
quelle