Apakah ada alternatif untuk string. Ganti yang peka huruf besar kecil?

306

Saya perlu mencari string dan mengganti semua kemunculan %FirstName%dan %PolicyAmount%dengan nilai yang diambil dari database. Masalahnya adalah kapitalisasi FirstName bervariasi. Itu mencegah saya menggunakan String.Replace()metode ini. Saya telah melihat halaman web pada subjek yang menyarankan

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

Namun untuk beberapa alasan ketika saya mencoba dan mengganti %PolicyAmount%dengan $0, penggantian tidak pernah terjadi. Saya berasumsi bahwa itu ada hubungannya dengan tanda dolar menjadi karakter yang dipesan di regex.

Apakah ada metode lain yang bisa saya gunakan yang tidak melibatkan sanitasi input untuk berurusan dengan karakter khusus regex?

Aheho
sumber
1
Jika "$ 0" adalah variabel yang masuk tidak mempengaruhi regex sama sekali.
cfeduke

Jawaban:

132

Dari MSDN
$ 0 - "Pengganti substring terakhir cocok dengan nomor kelompok (desimal)."

Dalam .NET Regular expressions group 0 selalu cocok. Untuk $ literal Anda perlu

string value = Regex.Replace("%PolicyAmount%", "%PolicyAmount%", @"$$0", RegexOptions.IgnoreCase);
Todd White
sumber
16
dalam kasus khusus ini baik-baik saja, tetapi dalam kasus di mana string adalah input dari luar, orang tidak dapat memastikan bahwa mereka tidak mengandung karakter yang berarti sesuatu yang istimewa dalam ekspresi reguler
Allanrbo
23
Anda harus keluar dari karakter khusus seperti ini: string value = Regex.Replace ("% PolicyAmount%", Regex.Escape ("% PolicyAmount%"), Regex.Escape ("$ 0"), RegexOptions.IgnoreCase);
Helge Klein
8
Harap diperhatikan saat menggunakan Regex.Escape di Regex.Replace. Anda harus melarikan diri dari ketiga string yang telah dilewati dan hubungi Regex.Unescape pada hasilnya!
Holger Adam
4
Menurut msdn: "Karakter lolos diakui dalam pola ekspresi reguler tetapi tidak dalam pola penggantian." ( msdn.microsoft.com/en-us/library/4edbef7e.aspx )
Bronek
1
Yang terbaik adalah menggunakan: string value = Regex.Replace ("% PolicyAmount%", Regex.Escape ("% PolicyAmount%"), "$ 0". Ganti ("$", "$$"), RegexOptions.IgnoreCase); sebagai pengganti hanya mengenali tanda-tanda dolar.
Skorek
295

Sepertinya string.Replace harus memiliki kelebihan yang membutuhkan StringComparisonargumen. Karena tidak, Anda dapat mencoba sesuatu seperti ini:

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. Dragon 76
sumber
9
Bagus. Saya akan berubah ReplaceStringmenjadi Replace.
AMissico
41
Setuju dengan komentar di atas. Ini dapat dibuat menjadi metode ekstensi dengan nama metode yang sama. Cukup masukkan dalam kelas statis dengan metode tanda tangan: public static string Replace (string ini String, string oldValue, string newValue, perbandingan StringComparison)
Mark Robinson
8
@Helge, secara umum, itu mungkin baik-baik saja, tapi saya harus mengambil string sewenang-wenang dari pengguna dan tidak dapat mengambil risiko input menjadi bermakna untuk regex. Tentu saja, saya kira saya bisa menulis satu lingkaran dan meletakkan garis miring terbalik di depan masing-masing dan setiap karakter ... Pada saat itu, saya mungkin juga melakukan yang di atas (IMHO).
Jim
9
Sementara unit yang menguji ini saya berlari ke dalam kasus di mana ia tidak akan pernah kembali kapan oldValue == newValue == "".
Ismael
10
Ini buggy; ReplaceString("œ", "oe", "", StringComparison.InvariantCulture)melempar ArgumentOutOfRangeException.
Michael Liu
45

Semacam kelompok jawaban yang membingungkan, sebagian karena judul pertanyaannya sebenarnya jauh lebih besar daripada pertanyaan spesifik yang diajukan. Setelah membaca, saya tidak yakin ada jawaban beberapa suntingan dari mengasimilasi semua hal baik di sini, jadi saya pikir saya akan mencoba untuk menjumlahkan.

Berikut adalah metode ekstensi yang menurut saya menghindari jebakan yang disebutkan di sini dan memberikan solusi yang paling luas berlaku.

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

Begitu...

  • Ini adalah metode ekstensi @MarkRobinson
  • Ini tidak mencoba melewati Regex @Helge (Anda benar-benar harus melakukan byte-by-byte jika Anda ingin merangkai sniff seperti ini di luar Regex)
  • Melewati @MichaelLiu 's kasus uji yang sangat baik , "œ".ReplaceCaseInsensitiveFind("oe", "")meskipun ia mungkin memiliki perilaku yang sedikit berbeda dalam pikiran.

Sayangnya, komentar @HA bahwa Anda harus Escapeketiganya tidak benar . Nilai awal dannewValue tidak harus.

Catatan: Anda harus melepaskan $nilai baru yang Anda sisipkan jika itu bagian dari apa yang kelihatannya merupakan penanda "nilai yang ditangkap" . Demikianlah tiga tanda dolar di Regex. Ganti di dalam Regex. Ganti [sic]. Tanpa itu, sesuatu seperti ini pecah ...

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

Inilah kesalahannya:

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.

Begini saja, saya tahu orang-orang yang nyaman dengan Regex merasa seperti penggunaannya menghindari kesalahan, tapi saya sering masih parsial untuk byte sniffing string (tetapi hanya setelah membaca Spolsky pada pengkodean ) untuk benar-benar yakin Anda mendapatkan apa yang Anda dimaksudkan untuk kasus penggunaan penting. Mengingatkan saya pada Crockford tentang " ekspresi reguler tidak aman " sedikit. Terlalu sering kita menulis regexps yang memungkinkan apa yang kita inginkan (jika kita beruntung), tetapi secara tidak sengaja memperbolehkan lebih banyak (misalnya, Apakah$10 benar-benar string "nilai tangkapan" yang valid di regValue newValue saya, di atas?) Karena kami tidak cukup bijaksana . Kedua metode memiliki nilai, dan keduanya mendorong berbagai jenis kesalahan yang tidak disengaja. Seringkali mudah untuk meremehkan kompleksitas.

Pelarian aneh itu $(dan itu Regex.Escapetidak luput dari pola nilai yang ditangkap seperti yang $0saya harapkan dalam nilai-nilai penggantian) membuat saya marah untuk sementara waktu. Pemrograman Sulit (c) 1842

ruffin
sumber
32

Inilah metode ekstensi. Tidak yakin di mana saya menemukannya.

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
sumber
Anda mungkin perlu menangani case string kosong / null.
Vad
2
Mutiple error dalam solusi ini: 1. Periksa originalString, oldValue dan newValue untuk null. 2. Jangan memberikan orginalString kembali (tidak berfungsi, tipe sederhana tidak lulus dengan referensi), tetapi tetapkan nilai orginalValue terlebih dahulu ke string baru dan memodifikasinya dan mengembalikannya.
RWC
31

Tampaknya metode termudah adalah dengan menggunakan metode Ganti yang dikirimkan bersama .Net dan telah ada sejak .Net 1.0:

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

Untuk menggunakan metode ini, Anda harus menambahkan Referensi ke kumpulan Microsoft.VisualBasic. Perakitan ini adalah bagian standar dari runtime .Net, ini bukan unduhan tambahan atau ditandai sebagai usang.

CleverPatrick
sumber
4
Berhasil. Anda perlu menambahkan referensi ke perakitan Microsoft.VisualBasic.
CleverPatrick
Aneh bahwa metode ini memiliki beberapa masalah ketika saya menggunakannya (karakter di awal baris hilang). Jawaban paling populer di sini dari C. Dragon 76berfungsi seperti yang diharapkan.
Jeremy Thompson
1
Masalahnya adalah ini mengembalikan string BARU bahkan jika pengganti tidak dibuat, di mana string.replace () mengembalikan pointer ke string yang sama. Dapat menjadi tidak efisien jika Anda melakukan sesuatu seperti penggabungan surat formulir.
Brain2000
4
Brain2000, Anda salah. Semua string dalam. NET tidak dapat diubah.
Der_Meister
Der_Meister, sementara apa yang Anda katakan itu benar, itu tidak membuat apa yang dikatakan Brain2000 salah.
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
sumber
Mana cara yang lebih baik? bagaimana dengan stackoverflow.com/a/244933/206730 ? kinerja yang lebih baik?
Kiquenet
8

Terinspirasi oleh jawaban cfeduke, saya membuat fungsi ini yang menggunakan IndexOf untuk menemukan nilai lama dalam string dan kemudian menggantinya dengan nilai baru. Saya menggunakan ini dalam skrip SSIS yang memproses jutaan baris, dan metode regex jauh lebih lambat dari ini.

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
sumber
+1 karena tidak menggunakan regex saat tidak diperlukan. Tentu, Anda menggunakan beberapa baris kode lagi, tetapi jauh lebih efisien daripada penggantian berbasis-regex kecuali Anda membutuhkan $ fungsionalitas.
ChrisG
6

Memperluas jawaban populer C. Dragon 76 dengan membuat kode menjadi ekstensi yang membebani Replacemetode default .

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
sumber
3

Berdasarkan jawaban Jeff Reddy, dengan beberapa optimisasi dan validasi:

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
sumber
2

versi yang mirip dengan C. Dragon, tetapi untuk jika Anda hanya perlu satu penggantian:

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

Berikut adalah opsi lain untuk menjalankan penggantian Regex, karena tidak banyak orang yang memperhatikan bahwa kecocokan memuat lokasi di dalam string:

    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
sumber
Bisakah Anda menjelaskan mengapa Anda mengalikan dengan MatchNo?
Aheho
Jika ada perbedaan panjang antara oldValue dan newValue, string akan menjadi lebih panjang atau lebih pendek saat Anda mengganti nilai. match.Index mengacu pada lokasi asli dalam string, kita perlu menyesuaikan untuk pergerakan posisi karena penggantian kami. Pendekatan lain adalah dengan menjalankan Remove / Insert dari kanan ke kiri.
Brandon
Saya mengerti. Itulah gunanya variabel "offset". Yang tidak saya mengerti adalah mengapa Anda mengalikannya dengan matchNo. Intuisi saya memberi tahu saya bahwa lokasi kecocokan dalam string tidak akan ada kaitannya dengan jumlah aktual kejadian sebelumnya.
Aheho
Sudahlah, saya mengerti sekarang. Offset perlu diskalakan berdasarkan # kejadian. Jika Anda kehilangan 2 karakter setiap kali Anda perlu mengganti, Anda harus memperhitungkannya saat menghitung parameter ke metode penghapusan
Aheho
0
Regex.Replace(strInput, strToken.Replace("$", "[$]"), strReplaceWith, RegexOptions.IgnoreCase);
Joel Coehoorn
sumber
3
Ini tidak berfungsi. $ Tidak ada dalam token. Ada di strReplace With string.
Aheho
9
Dan Anda tidak dapat mengadaptasinya untuk itu?
Joel Coehoorn
18
Situs ini seharusnya menjadi tempat penyimpanan untuk jawaban yang benar. Bukan jawaban yang hampir benar.
Aheho
0

Metode ekspresi reguler harus bekerja. Namun apa yang Anda juga dapat lakukan adalah huruf kecil string dari database, huruf kecil% variabel% yang Anda miliki, dan kemudian cari posisi dan panjang dalam string cased lebih rendah dari database. Ingat, posisi dalam string tidak berubah hanya karena cased lebih rendah

Kemudian menggunakan loop yang berjalan terbalik (lebih mudah, jika tidak, Anda harus tetap menghitung kemana titik kemudian pindah) hapus dari string cased yang tidak lebih rendah dari database% variabel% berdasarkan posisi dan panjang dan masukkan nilai penggantian.

cfeduke
sumber
Secara terbalik, maksud saya memproses lokasi yang ditemukan secara terbalik dari yang terjauh ke yang terpendek, bukan melintasi string dari database secara terbalik.
cfeduke
Anda bisa, atau Anda bisa menggunakan Regex :)
Ray
0

(Karena semua orang mengambil kesempatan ini). Inilah versi saya (dengan cek nol, dan input dan penggantian yang benar hilang) ** Terinspirasi dari seluruh internet dan versi lain:

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

Pemakaian:

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

Biarkan saya membuat kasing saya dan kemudian Anda dapat mencabik-cabik saya jika Anda mau.

Regex bukan jawaban untuk masalah ini - terlalu lambat dan memori haus, relatif berbicara.

StringBuilder jauh lebih baik daripada string mangling.

Karena ini akan menjadi metode ekstensi untuk melengkapi string.Replace, saya percaya penting untuk mencocokkan cara kerjanya - karena itu melempar pengecualian untuk masalah argumen yang sama adalah penting seperti mengembalikan string asli jika penggantian tidak dilakukan.

Saya percaya bahwa memiliki parameter StringComparison bukanlah ide yang baik. Saya memang mencobanya tetapi test case yang awalnya disebutkan oleh michael-liu menunjukkan masalah: -

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

Sementara IndexOf akan cocok, ada ketidakcocokan antara panjang pertandingan dalam string sumber (1) dan oldValue.Length (2). Ini memanifestasikan dirinya dengan menyebabkan IndexOutOfRange dalam beberapa solusi lain ketika oldValue.Length ditambahkan ke posisi pertandingan saat ini dan saya tidak dapat menemukan cara untuk mengatasi ini. Lagipula Regex gagal untuk mencocokkan case, jadi saya mengambil solusi pragmatis dari hanya menggunakan StringComparison.OrdinalIgnoreCaseuntuk solusi saya.

Kode saya mirip dengan jawaban lain tetapi putaran saya adalah bahwa saya mencari kecocokan sebelum kesulitan membuat StringBuilder. Jika tidak ada yang ditemukan maka alokasi yang berpotensi besar dihindari. Kode kemudian menjadi a do{...}whiledaripada awhile{...}

Saya telah melakukan beberapa pengujian ekstensif terhadap Jawaban lain dan ini keluar lebih cepat dan menggunakan sedikit memori.

    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
sumber