Есть ли альтернатива string.Replace без учета регистра? - PullRequest
297 голосов
/ 28 октября 2008

Мне нужно найти строку и заменить все вхождения %FirstName% и %PolicyAmount% значением, извлеченным из базы данных. Проблема в том, что заглавные буквы FirstName меняются. Это мешает мне использовать метод String.Replace(). Я видел веб-страницы на эту тему, которые предлагают

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

Однако по какой-то причине, когда я пытаюсь заменить %PolicyAmount% на $0, замена никогда не происходит. Я предполагаю, что это как-то связано с тем, что знак доллара является зарезервированным символом в регулярном выражении.

Есть ли другой метод, который я могу использовать, который не предусматривает дезинфекции ввода для работы со специальными символами регулярных выражений?

Ответы [ 15 ]

293 голосов
/ 29 октября 2008

Похоже, 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();
}
127 голосов
/ 28 октября 2008

Из MSDN
$ 0 - «Подставляет последнюю подстроку, совпадающую с номером группы (десятичным).»

В .NET Группа регулярных выражений 0 всегда полностью совпадает. Для буквального $ вам нужно

string value = Regex.Replace("%PolicyAmount%", "%PolicyAmount%", @"$$0", RegexOptions.IgnoreCase);
41 голосов
/ 05 июля 2014

Вид сбивающей с толку группы ответов, отчасти потому, что название вопроса на самом деле намного больше, чем конкретный задаваемый вопрос. После прочтения, я не уверен, что какой-либо ответ будет в нескольких редакциях от усвоения всех хороших вещей здесь, поэтому я решил, что я попытаюсь подвести итог.

Вот метод расширения, который, я думаю, позволяет избежать ловушек, упомянутых здесь, и обеспечивает наиболее широкое применение.

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 внутри Regex.Replace [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.

Скажите, что, я знаю, что люди, которым комфортно с Regex, чувствуют, что их использование позволяет избежать ошибок, но я часто все еще неравнодушен к байт-анализирующим строкам (но только после прочтения Spolsky о кодировках ) в быть абсолютно уверенным, что вы получаете то, что вы рассчитывали на важные варианты использования. Немного напоминает мне Крокфорда о « небезопасных регулярных выражениях ». Слишком часто мы пишем регулярные выражения, которые разрешают то, что мы хотим (если нам повезет), но непреднамеренно допускают больше в (например, действительно ли $10 является действительной строкой «значения захвата» в моем новом регулярном выражении newValue, выше?), Потому что мы не ' Я достаточно вдумчивый. Оба метода имеют ценность, и оба поощряют различные типы непреднамеренных ошибок. Часто легко недооценить сложность.

Этот странный $ выход (и то, что Regex.Escape не избежал шаблонов с захваченными значениями, таких как $0, как я и ожидал в значениях замены), на какое-то время сводил меня с ума. Программирование сложно (с) 1842

30 голосов
/ 20 июля 2011

Вот метод расширения. Не уверен, где я его нашел.

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 голосов
/ 23 августа 2010

Кажется, что самый простой метод - это просто использовать метод Replace, который поставляется с .Net и существует с .Net 1.0:

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

Чтобы использовать этот метод, необходимо добавить ссылку на сборку Microsoft.VisualBasic. Эта сборка является стандартной частью среды выполнения .Net, она не является дополнительной загрузкой или помечена как устаревшая.

10 голосов
/ 21 августа 2012
    /// <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);
    }
8 голосов
/ 21 августа 2014

Вдохновленный ответом cfeduke, я создал эту функцию, которая использует IndexOf для поиска старого значения в строке, а затем заменяет его новым значением. Я использовал это в скрипте SSIS, обрабатывающем миллионы строк, и метод регулярных выражений был намного медленнее, чем этот.

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;
}
6 голосов
/ 30 сентября 2016

Расширение на 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();
     }
}
3 голосов
/ 26 сентября 2016

На основании ответа Джеффа Редди с некоторыми оптимизациями и проверками:

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();
}
2 голосов
/ 07 января 2011

версия, аналогичная версии C. Dragon, но если вам нужна только одна замена:

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