2008-10-28 21 views
281

Muszę przeszukać ciąg znaków i zastąpić wszystkie wystąpienia %FirstName% i %PolicyAmount% wartością wyciągniętą z bazy danych. Problem polega na tym, że wielkość liter FirstName jest różna. To uniemożliwia mi użycie metody String.Replace(). Widziałem stron internetowych na ten temat, które sugerująCzy istnieje alternatywa dla ciągu.Rozpak nie ma znaczenia wielkość liter?

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

Jednak z jakiegoś powodu, gdy próbuję i zastąpić %PolicyAmount% z $0 nigdy wymiana odbywa. Zakładam, że ma to coś wspólnego ze znakiem dolara będącym zastrzeżoną postacią w regex.

Czy istnieje inna metoda, której mogę użyć, która nie wymaga odkażania danych wejściowych do obsługi znaków specjalnych regex?

+1

Jeśli „$ 0” jest zmienna będzie w to nie ma wpływu na wyrażenie regularne. – cfeduke

Odpowiedz

125

From MSDN
$ 0 - "Zastępuje ostatni podciąg dopasowany przez numer porządkowy grupy (dziesiętny)."

W .NET Wyrażenia regularne grupa 0 jest zawsze zgodna. Dla dosłownym $ trzeba

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

w tym konkretnym przypadku jest to w porządku, ale w przypadkach, w których łańcuchy są wprowadzane z zewnątrz, nie można mieć pewności, że nie zawierają znaków, które oznaczają coś specjalnego w wyrażeniach regularnych – Allanrbo

+23

Powinieneś unikać specjalnych znaków, takich jak to: string value = Regex.Replace ("% PolicyAmount%", Regex.Escape ("% PolicyAmount%"), Regex.Escape ("$ 0"), RegexOptions.IgnoreCase); –

+0

Właściwie regex - ucieczkę z drugiego ciągu nie będzie miało żadnego wpływu na dodatkowe \ przed zamianą. Aby zignorować znaki specjalne w łańcuchu zamiennym, lepiej napisać matchevaluator, który zwraca sam napis. –

0
Regex.Replace(strInput, strToken.Replace("$", "[$]"), strReplaceWith, RegexOptions.IgnoreCase); 
+3

To nie działa. Znak $ nie znajduje się w tokenie. Jest w łańcuchu strReplace With. – Aheho

+9

I nie możesz tego zaadaptować? –

+16

Ta strona ma być repozytorium dla prawidłowych odpowiedzi. Nie odpowiedzi, które są prawie poprawne. – Aheho

0

Metoda wyrażenia regularnego powinna działać. Jednak można również zrobić małe litery z bazy danych, małe litery% zmienne% masz, a następnie zlokalizować pozycje i długości w łańcuchu z małymi literami z bazy danych. Pamiętaj, że pozycje w ciągu nie zmieniają się tylko dlatego, że mają małe litery.

Następnie za pomocą pętli, która przechodzi w odwrotność (jest to łatwiejsze, jeśli nie będziesz musiała utrzymywać liczby, z której będą później przenoszone punkty), usuń z bazy danych ciąg znaków o wartości mniejszej niż% zmienne% według ich pozycji i długości i wstaw wartości zastępcze.

+0

Odwrotnie, mam na myśli przetwarzanie znalezionych lokalizacji w kierunku odwrotnym od najdalszego do najkrótszego, nie przechodź ciągu znaków z bazy danych w odwrotnej kolejności. – cfeduke

+0

Możesz, lub możesz po prostu użyć Regeksu :) – Ray

285

Wygląda string.replace powinny mieć przeciążenie, że trwa argumentem StringComparison. Ponieważ nie można było spróbować czegoś takiego:

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

Metody rozszerzeń działają tylko w 3+, prawda? +1 Tak samo, ponieważ OP nie był specyficzny, ale warto o tym wspomnieć. –

+3

Ponadto będzie to szybsze niż wyrażenie regularne. –

+8

Nice. Chciałbym zmienić 'ReplaceString' na' Replace'. – AMissico

29

Wydaje się, że najprostszym sposobem jest po prostu użyć metody Replace, że statki z .NET i istnieje od .NET 1.0:

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

Aby skorzystać z tej metody, należy dodać odniesienie do zestawu Microsoft.VisualBasic. Ten zespół jest standardową częścią środowiska wykonawczego .Net, nie jest dodatkowym plikiem do pobrania ani oznaczony jako przestarzały.

+4

To działa. Musisz dodać odniesienie do zestawu Microsoft.VisualBasic. –

+0

Dziwne, że ta metoda miała pewne problemy, gdy jej użyłem (postacie na początku linii zniknęły). Najpopularniejsza odpowiedź tutaj z C. Dragon 76' działał zgodnie z oczekiwaniami. –

+1

Problem z tym, że zwraca NOWY łańcuch, nawet jeśli nie jest dokonywany zamiennik, gdzie string.replace() zwraca wskaźnik do tego samego ciągu znaków. Może stać się nieefektywny, jeśli robisz coś w rodzaju scalania listów. – Brain2000

2

wersja podobna do C smok, ale na razie trzeba tylko jeden Zamiennik:

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

To nie zadziała, jeśli chcesz zastąpić wiele dopasowań. – hspain

29

Oto metoda rozszerzenie. Nie jestem pewien, gdzie go znalazłem.

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

} 
+0

Co to jest http://stackoverflow.com/a/244933/206730? który jest lepszy sposób? – Kiquenet

+0

Może zajść konieczność obsługi pustych/pustych skrzynek ciągów. – Vad

+2

Powtarzaj błędy w tym rozwiązaniu: 1. Sprawdź wartość originalString, oldValue i newVallue dla wartości null. 2. Nie przywracaj orginalString (nie działa, typy proste nie są przekazywane przez referencje), ale najpierw przypisz wartość parametru orginalValue do nowego łańcucha, a następnie zmodyfikuj i zwróć. – RWC

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

Który jest lepszy sposób? co jest o http://stackoverflow.com/a/244933/206730? lepsza wydajność? – Kiquenet

29

Trochę mylące grupy odpowiedzi, częściowo dlatego, że tytuł pytanie jest faktycznie znacznie większy niż konkretne pytanie jest zadawane. Po przeczytaniu, nie jestem pewna, czy jakakolwiek odpowiedź to tylko kilka zmian od przyswojenia wszystkich dobrych rzeczy tutaj, więc pomyślałem, że spróbuję podsumować.

Oto metoda rozszerzenia, która pozwala uniknąć opisanych tu pułapek i zapewnia najbardziej odpowiednie rozwiązanie.

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

Więc ...

  • To an extension method @MarkRobinson
  • Ten doesn't try to skip Regex @Helge (naprawdę trzeba zrobić bajt po bajcie, jeśli chcesz powąchać jak ten ciąg zewnątrz Regex)
  • Przepuszcza @MichaelLiu's excellent test case, "œ".ReplaceCaseInsensitiveFind("oe", ""), chociaż mógł mieć nieco inne zachowanie na uwadze.

Niestety, @HA 's comment that you have to Escape all three isn't correct. Wartość początkowa i newValue nie musi być.

Uwaga: to zrobić, muszą jednak uciec $ S w nowej wartości, że jesteś wstawianie jeśli są częścią tego, co wydaje się być „złapany wartość” marker. Tak więc trzy znaki dolara w Regex.Replace wewnątrz Regex.Replace [sic]. Bez tego coś takiego łamie ...

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

Oto błąd:

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. 

Wiesz co, wiem, że ludzie są wygodne z Regex ochoty ich stosowanie pozwala uniknąć błędów, ale Często jestem nadal stronniczy od tego, że bajtowe sygnały sniffujące (ale dopiero po przeczytaniu Spolsky on encodings) są absolutnie pewne, że otrzymujesz to, co zamierzałeś dla ważnych przypadków użycia. Przypomina mi trochę Crockforda o "insecure regular expressions". Zbyt często piszemy wyrażenia regularne, które pozwalają na to, co chcemy (jeśli mamy szczęście), ale nieumyślnie dopuszczamy więcej w (np. Czy $10 naprawdę jest prawidłowym ciągiem "wartości przechwytywania" w moim nowym wyrażeniu regularnym, powyżej?), Ponieważ nie byliśmy przemyślani dość. Obie metody mają wartość i oba zachęcają do różnych rodzajów niezamierzonych błędów. Często trudno jest nie docenić złożoności.

To dziwne $ uciekającego (i Regex.Escape nie uciec uchwycone wzorców wartości jak $0 jak by się było spodziewać w wartościach zamiennych) zawiózł mnie do szału przez chwilę. Programowanie jest trudne (c) 1842

+0

Naprawdę zasługuje na więcej głosów. Plus 1842, lol. :) – ewbi

1

Oto kolejna opcja do wykonywania regex zamienników, ponieważ wiele osób nie wydaje się zauważyć mecze zawierać lokalizację w obrębie łańcucha:

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

Czy możesz wyjaśnić, dlaczego mnożysz przez MatchNo? – Aheho

+0

Jeśli różnica między starą wartością a wartością nową jest różna, ciąg znaków zostanie wydłużony lub skręcony w miarę zastępowania wartości. match.Index odnosi się do oryginalnej lokalizacji w ciągu znaków, musimy dostosować się do tego ruchu pozycji z powodu naszej wymiany. Innym podejściem byłoby wykonanie Usuń/Wstaw od prawej do lewej. – Brandon

+0

Rozumiem. Do tego służy zmienna "offset". Czego nie rozumiem, to dlaczego mnożysz przez matchNo.Moja intuicja podpowiada mi, że lokalizacja dopasowania w ciągu nie ma związku z rzeczywistą liczbą poprzednich zdarzeń. – Aheho

8

Zainspirowany odpowiedź cfeduke za zrobiłem ta funkcja używa IndexOf do znalezienia starej wartości w ciągu znaków, a następnie zastępuje ją nową wartością. Użyłem tego w skrypcie SSIS przetwarzającym miliony wierszy, a metoda regex była znacznie wolniejsza.

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

+1, aby nie używać wyrażenia regularnego, gdy nie jest to konieczne. Oczywiście, używasz kilku dodatkowych linii kodu, ale jest to o wiele bardziej wydajne niż zastąpienie oparte na regex, chyba że potrzebujesz funkcjonalności $. – ChrisG

3

podstawie odpowiedzi Jeff Reddy, w niektórych optymalizacji i walidacji:

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

Rozszerzając popularnej odpowiedź C. Dragon 76 „s poprzez swój kod na przedłużenie że przeciąża domyślny Replace metody.

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

(Ponieważ wszyscy robią zdjęcia). Oto moja wersja (z kontroli zerowych i prawidłowe wejście i ucieczki zamiennik) ** Zainspirowany z całego internetu i innych wersjach:

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

Zastosowanie:

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