Domanda

Devo cercare una stringa e sostituire tutte le occorrenze di % FirstName% e % PolicyAmount% con un valore estratto da un database. Il problema è che la capitalizzazione di FirstName varia. Ciò mi impedisce di utilizzare il metodo String.Replace () . Ho visto pagine web sull'argomento che suggeriscono

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

Tuttavia, per qualche motivo, quando provo a sostituire % PolicyAmount% con $ 0 , la sostituzione non ha mai luogo. Presumo che abbia qualcosa a che fare con il simbolo del dollaro che è un personaggio riservato in regex.

Esiste un altro metodo che posso usare che non comporta la sanificazione dell'input per gestire i caratteri speciali di regex?

È stato utile?

Soluzione

Da MSDN
$ 0 - " Sostituisce l'ultima sottostringa corrispondente al numero di gruppo (decimale). & Quot;

Nel gruppo di espressioni regolari .NET il gruppo 0 è sempre l'intera corrispondenza. Per $ letterali devi

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

Altri suggerimenti

Sembra string.Replace dovrebbe avere un sovraccarico che accetta un argomento StringComparison . Dal momento che non lo è, potresti provare qualcosa del genere:

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 di confuso gruppo di risposte, in parte perché il titolo della domanda è in realtà molto più grande della domanda specifica che viene posta. Dopo aver letto, non sono sicuro che nessuna risposta sia a poche modifiche dall'assimilazione di tutte le cose buone qui, quindi ho pensato di provare a riassumere.

Ecco un metodo di estensione che penso evita le insidie ??menzionate qui e fornisce la soluzione più ampiamente applicabile.

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

...

Sfortunatamente, @ Il commento di HA secondo cui devi Escape non è corretto . Il valore iniziale e newValue non devono essere.

Nota: devi, tuttavia, scappare da $ s nel nuovo valore che stai inserendo se fanno parte di ciò che apparirebbe essere un "valore acquisito" marcatore . Quindi i tre simboli del dollaro nel Regex.Replace all'interno del Regex.Replace [sic]. Senza quello, qualcosa del genere si rompe ...

" Questa è la SUA forchetta, il suo cucchiaio, il coltello hissssssss. " .ReplaceCaseInsensitiveFind (" his " ;, @ " he $ 0r ")

Ecco l'errore:

<*>

Ti dico una cosa, so che le persone che si sentono a proprio agio con Regex sentono che il loro uso evita errori, ma spesso sono ancora parziale alle stringhe di sniffer di byte (ma solo dopo aver letto Spolsky sulle codifiche ) per essere assolutamente sicuri di ottenere ciò che intendevi per importanti casi d'uso. Mi ricorda Crockford su " regolare insicuro espressioni " un po. Troppo spesso scriviamo regexps che consentono ciò che vogliamo (se siamo fortunati), ma consentiamo involontariamente di più in (ad esempio, $ 10 è davvero una stringa di valore di acquisizione " valida nel mio nuovo valore regexp, sopra ?) perché non eravamo abbastanza premurosi. Entrambi i metodi hanno valore ed entrambi incoraggiano diversi tipi di errori involontari. Spesso è facile sottovalutare la complessità.

Quel strano $ in fuga (e quel Regex.Escape non è sfuggito a modelli di valore acquisiti come $ 0 come mi sarei aspettato in sostituzione valori) mi ha fatto impazzire per un po '. La programmazione è difficile (c) 1842

Ecco un metodo di estensione. Non sono sicuro di dove l'ho trovato.

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

}

Sembra che il metodo più semplice sia semplicemente usare il metodo Sostituisci fornito con .Net ed è in circolazione da .Net 1.0:

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

Per utilizzare questo metodo, è necessario aggiungere un riferimento al gruppo Microsoft.VisualBasic. Questo assembly è una parte standard del runtime .Net, non è un download aggiuntivo o contrassegnato come obsoleto.

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

Ispirato dalla risposta di cfeduke, ho creato questa funzione che utilizza IndexOf per trovare il vecchio valore nella stringa e poi lo sostituisce con il nuovo valore. L'ho usato in uno script SSIS che elaborava milioni di righe e il metodo regex era molto più lento di così.

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

Espansione su C . La risposta popolare di Dragon 76 trasformando il suo codice in un'estensione che sovraccarica il metodo predefinito Sostituisci .

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

Basato sulla risposta di Jeff Reddy, con alcune ottimizzazioni e convalide:

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 versione simile a C. Dragon's, ma se hai bisogno di una sola sostituzione:

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

Ecco un'altra opzione per eseguire sostituzioni Regex, poiché non molte persone sembrano notare che le corrispondenze contengono la posizione all'interno della stringa:

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

Il metodo di espressione regolare dovrebbe funzionare. Tuttavia, ciò che puoi anche fare è minuscola la stringa dal database, minuscole le variabili%% che hai, e quindi individuare le posizioni e le lunghezze nella stringa maiuscola dal database. Ricorda, le posizioni in una stringa non cambiano solo perché ha il case inferiore.

Quindi usando un ciclo che va al contrario (è più facile, se non lo fai dovrai tenere un conteggio corrente di dove si spostano i punti successivi) rimuovi dalla tua stringa non inferiore dal database le variabili% da la loro posizione e lunghezza e inserire i valori di sostituzione.

(Dal momento che tutti ci stanno provando). Ecco la mia versione (con controlli nulli e escape di input e sostituzione corretti) ** Ispirato da Internet e da altre versioni:

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

Utilizzo:

<*>

Lasciami fare il mio caso e poi puoi farmi a brandelli se vuoi.

Regex non è la risposta a questo problema: troppo lento e affamato di memoria, relativamente parlando.

StringBuilder è molto meglio della manipolazione delle stringhe.

Poiché si tratterà di un metodo di estensione per integrare string.Replace , ritengo sia importante abbinare il modo in cui funziona, pertanto generare eccezioni per gli stessi argomenti è importante come restituire la stringa originale se una sostituzione non è stata effettuata.

Credo che avere un parametro StringComparison non sia una buona idea. L'ho provato ma il test case originariamente menzionato da michael-liu ha mostrato un problema: -

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

Sebbene IndexOf corrisponderà, esiste una discrepanza tra la lunghezza della corrispondenza nella stringa di origine (1) e oldValue.Length (2). Ciò si è manifestato causando IndexOutOfRange in alcune altre soluzioni quando oldValue.Length è stato aggiunto alla posizione di corrispondenza corrente e non sono riuscito a trovare un modo per aggirare questo. Regex non riesce comunque ad abbinare il caso, quindi ho preso la soluzione pragmatica di usare solo StringComparison.OrdinalIgnoreCase per la mia soluzione.

Il mio codice è simile ad altre risposte, ma la mia svolta è che cerco una corrispondenza prima di andare nel guaio della creazione di un StringBuilder . Se non viene trovato nessuno, viene evitata un'allocazione potenzialmente grande. Il codice diventa quindi un do {...} mentre anziché un while{...}

Ho fatto alcuni test approfonditi contro altre Risposte e questo è risultato leggermente più veloce e ha usato un po 'meno 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();
    }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top