簡體   English   中英

是否有不區分大小寫的 string.Replace 替代方法?

[英]Is there an alternative to string.Replace that is case-insensitive?

我需要搜索一個字符串並將所有出現的%FirstName%%PolicyAmount%替換為從數據庫中提取的值。 問題是 FirstName 的大小寫不同。 這阻止了我使用String.Replace()方法。 我已經看到有關該主題的網頁建議

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

但是由於某種原因,當我嘗試用$0替換%PolicyAmount%時,替換永遠不會發生。 我認為這與美元符號是正則表達式中的保留字符有關。

我可以使用另一種方法來處理輸入以處理正則表達式特殊字符嗎?

看起來像string.Replace 應該有一個帶有StringComparison參數的重載。 既然沒有,你可以嘗試這樣的事情:

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

來自MSDN
$ 0 - “替換與組號(十進制)匹配的最后一個子串。”

在.NET正則表達式中,組0始終是整個匹配。 對於文字$,你需要

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

混亂組答案,部分原因是由於問題的標題實際上是遠遠大於被問的具體問題的種類。 閱讀完之后,我不確定任何答案是否能夠吸收所有好東西的一些編輯,所以我想我會嘗試總結。

這是一種擴展方法,我認為可以避免這里提到的陷阱,並提供最廣泛適用的解決方案。

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

所以...

不幸的是, @ HA的評論說你必須Escape這三個是不正確的 初始值和newValue不需要。

注意:但是, 如果它們是看似“捕獲值”標記的一部分 ,則必須在新插入的值中轉義$ s。 因此Regex.Replace里面的三個美元符號.Replace里面的內容。[原文如此]。 沒有它,這樣的事情會破壞......

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

這是錯誤:

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.

告訴你什么,我知道那些對Regex感到滿意的人覺得他們的使用可以避免錯誤,但是我經常仍然偏向字節嗅探字符串(但只有在編碼后閱讀Spolsky )才能確保你得到的是什么用於重要用例。 讓我想起克羅克福德對“ 不安全的正則表達 ”的看法。 我們經常編寫允許我們想要的正則表達式(如果我們很幸運),但無意中允許更多(例如,在我的newValue正則表達式中, $10真的是一個有效的“捕獲值”字符串嗎?)因為我們並不周到足夠。 這兩種方法都有價值,並且都鼓勵不同類型的無意識錯誤。 通常很容易低估復雜性。

奇怪的$逃避(並且Regex.Escape沒有像我在預期的替換價值中那樣逃避被捕獲的價值模式,如$0 )讓我瘋了一會兒。 編程很難(c)1842

似乎最簡單的方法就是使用.Net附帶的Replace方法,並且自.Net 1.0以來一直存在:

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

要使用此方法,您必須添加對Microsoft.VisualBasic組件的引用。 此程序集是.Net運行時的標准部分,它不是額外的下載或標記為過時。

這是一種擴展方法。 不確定我在哪里找到它。

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

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

受cfeduke的回答啟發,我創建了這個函數,它使用IndexOf在字符串中查找舊值,然后用新值替換它。 我在處理數百萬行的SSIS腳本中使用了這個,而regex方法比這慢。

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

擴展C. Dragon 76的流行答案,將他的代碼變成一個擴展,重載默認的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();
     }
}

根據Jeff Reddy的回答,進行了一些優化和驗證:

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

類似於C. Dragon的版本,但是如果你只需要一個替換:

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

這是執行正則表達式替換的另一個選項,因為似乎沒有多少人注意到匹配包含字符串中的位置:

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

從 .NET Core 2.0 或 .NET Standard 2.1 開始,這被烘焙到 .NET 運行時 [1]:

"hello world".Replace("World", "csharp", StringComparison.CurrentCultureIgnoreCase); // "hello csharp"

[1] https://docs.microsoft.com/en-us/dotnet/api/system.string.replace#System_String_Replace_System_String_System_String_System_StringComparison _

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

正則表達式方法應該有效。 然而,您還可以做的是小寫數據庫中的字符串,小寫%變量%,然后從數據庫中找到下部字符串中的位置和長度。 請記住,字符串中的位置不會因為較低的情況而改變。

然后使用一個反向循環(它更容易,如果你不這樣做,你將不得不保持后續點移動到的位置的運行計數)從數據庫中刪除非低位字符串的%變量%由它們的位置和長度並插入替換值。

(因為每個人都在考慮這個)。 這是我的版本(使用空檢查,正確輸入和替換轉義)**靈感來自互聯網和其他版本:

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

用法:

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

讓我說出我的情況,如果你願意,你可以把我撕成碎片。

相對來說,正則表達式不是這個問題的答案 - 太慢和內存飢餓。

StringBuilder比字符串重整更好。

因為這將是一個補充string.Replace的擴展方法,我認為重要的是匹配它的工作方式 - 因此拋出相同參數問題的異常很重要,因為如果沒有替換,則返回原始字符串。

我相信擁有StringComparison參數並不是一個好主意。 我確實嘗試過但是michael-liu最初提到的測試用例顯示了一個問題: -

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

雖然IndexOf將匹配,但源字符串(1)中的匹配長度與oldValue.Length(2)之間存在不匹配。 當oldValue.Length被添加到當前匹配位置並且我無法找到解決方法時,這表現為在其他一些解決方案中引入IndexOutOfRange。 Regex無論如何都無法匹配案例,因此我采用了僅使用StringComparison.OrdinalIgnoreCase的實用解決方案作為我的解決方案。

我的代碼與其他答案類似,但我的轉折是我在找到創建StringBuilder的麻煩之前尋找匹配。 如果沒有找到,則避免潛在的大分配。 然后代碼變為do{...}while而不是一段while{...}

我已經針對其他Answers進行了一些廣泛的測試,這種測試速度更快,使用的內存略少。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM