web-dev-qa-db-ja.com

大文字と小文字を区別しないstring.Replaceの代替手段はありますか?

文字列を検索し、%FirstName%%PolicyAmount%のすべての出現をデータベースから取得した値に置き換える必要があります。問題は、FirstNameの大文字化が異なることです。そのため、String.Replace()メソッドを使用できません。私は提案する主題のウェブページを見ました

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

しかし、何らかの理由で%PolicyAmount%$0で置き換えようとすると、置き換えは行われません。正規表現の予約文字であるドル記号と関係があると思います。

正規表現の特殊文字を処理するための入力のサニタイズを伴わない別の方法を使用できますか?

297
Aheho

MSDNから
$ 0-「グループ番号番号と一致する最後の部分文字列を置き換えます(10進数)。」

.NET正規表現では、グループ0が常に完全に一致します。文字通りの$には

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

string.Replaceshouldのように思えますが、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();
}
293
C. Dragon 76

ある種の質問のタイトルが実際にmuch質問されている特定の質問よりも大きいため、一種の混乱した回答グループ。読んだ後、ここですべての良いものを同化することからいくつかの編集が何の答えかわからないので、合計しようと思いました。

ここに記載されている落とし穴を回避し、最も広く適用可能なソリューションを提供する拡張方法を次に示します。

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にコメントする必要があるというコメントは3つとも正しくありません です。初期値とnewValueは必須ではありません。

注:ただし、挿入する新しい値で$sをエスケープする必要があります 「キャプチャされた値」マーカーのように見えるものの一部。したがって、Regex.Replace内のRegex.Replaceの3つのドル記号[sic]。それがなければ、このようなものは壊れます...

"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.

教えてください、正規表現に慣れている人はエラーを避けることができると感じていますが、多くの場合、バイトスニッフィング文字列にはまだ部分的です(ただし、 エンコーディングのSpolsky を読んだ後にのみ)あなたが重要なユースケースのために意図したものを得ていることを確認してください「 安全でない正規表現 」に関するCrockfordを思い出します。頻繁に、(幸運なら)望むものを許可する正規表現を記述しますが、意図せずに(たとえば、上記のnewValue正規表現の$10は本当に有効な「キャプチャ値」文字列ですか?)十分な。どちらの方法にも価値があり、両方とも異なるタイプの意図しないエラーを助長します。多くの場合、複雑さを過小評価するのは簡単です。

その奇妙な$のエスケープ(そして、Regex.Escapeは、置換値で期待していたように、$0のようなキャプチャーされた値のパターンをエスケープしませんでした)はしばらく私を怒らせました。プログラミングは難しい(c)1842

41
ruffin

最も簡単な方法は、.Netに同梱され、.Net 1.0から使用されているReplaceメソッドを使用することです。

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

このメソッドを使用するには、Microsoft.VisualBasicアセンブリへの参照を追加する必要があります。このアセンブリは.Netランタイムの標準的な部分であり、追加のダウンロードではなく、古いものとしてマークされています。

30
Clever Human

これが拡張メソッドです。どこで見つけたかわかりません。

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

}
30
rboarman
    /// <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);
    }
10
Karl Glennon

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

デフォルトのReplaceメソッドをオーバーロードする拡張機能にコードを作成することにより、 C。Dragon 76 の一般的な答えを拡張します。

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

ジェフレディの回答に基づき、最適化と検証を行いました。

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

c. Dragonのバージョンに似ていますが、単一の交換のみが必要な場合:

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

正規表現の置換を実行するための別のオプションは、文字列内の場所が一致に含まれていることに気付かない人があまりいないためです。

    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();
    }
1
Brandon
Regex.Replace(strInput, strToken.Replace("$", "[$]"), strReplaceWith, RegexOptions.IgnoreCase);
0
Joel Coehoorn

(誰もがこれを撃っているので)。ここに私のバージョンがあります(nullチェック、および正しい入力と置換のエスケープ)**インターネットや他のバージョンからインスピレーションを受けています:

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

正規表現メソッドが機能するはずです。ただし、データベースからの文字列を小文字にし、使用している%variables%を小文字にしてから、データベースから小文字の文字列の位置と長さを特定することもできます。文字列内の位置は、小文字であるという理由だけで変化しないことを忘れないでください。

次に、逆のループを使用して(後のポイントが移動する場所の実行カウントを保持する必要がない場合は簡単です)データベースから小文字以外の文字列から、位置によって%variables%を削除します。長さと置換値を挿入します。

0
cfeduke

私の主張を聞かせてください。そして、あなたが望むなら、あなたは私を引き裂きます。

正規表現は、この問題に対する答えではありません-遅すぎると、比較的メモリが空いています。

StringBuilderは、文字列のマングリングよりもはるかに優れています。

これはstring.Replaceを補完する拡張メソッドであるため、その動作を一致させることが重要だと考えます。したがって、同じ引数の問題に対して例外をスローすることは、置換が行われなかった場合に元の文字列を返すのと同様に重要です。

StringComparisonパラメーターを使用することはお勧めできません。私はそれを試してみましたが、マイケル・リューが元々言及したテストケースは問題を示しました:-

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

IndexOfは一致しますが、ソース文字列の一致の長さ(1)とoldValue.Length(2)の間に不一致があります。これは、oldValue.Lengthが現在の一致位置に追加されたときに他のいくつかのソリューションでIndexOutOfRangeを引き起こすことによって現れ、これを回避する方法が見つかりませんでした。とにかく正規表現は大文字と小文字を一致させることができないため、ソリューションにStringComparison.OrdinalIgnoreCaseのみを使用するという実用的なソリューションを採用しました。

私のコードは他の答えに似ていますが、私のひねりは、StringBuilderを作成する手間をかける前に一致を探すことです。何も見つからない場合、潜在的に大きな割り当てが回避されます。その後、コードはdo{...}whileではなくwhile{...}になります

私は他のアンサーに対して広範なテストを行いましたが、これはわずかに高速になり、使用メモリがわずかに少なくなりました。

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