Есть ли альтернатива string.Replace, не учитывающая регистр?

StackOverflow https://stackoverflow.com/questions/244531

Вопрос

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

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

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

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

Это было полезно?

Решение

из MSDN
$ 0 - " Заменяет последнюю подстроку, соответствующую номеру группы (десятичное число). & Quot;

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

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

Другие советы

Похоже, что 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();
}

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

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

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 [так в оригинале].Без этого что-то подобное ломается...

"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, считают, что их использование позволяет избежать ошибок, но я часто все еще неравнодушен к байтовым перехватам строк (но только после прочтения Спольский о кодировках), чтобы быть абсолютно уверенным, что вы получаете то, что предназначено для важных случаев использования.Напоминает мне Крокфорда из "небезопасные регулярные выражения" немного.Слишком часто мы пишем регулярные выражения, которые допускают то, что мы хотим (если нам повезет), но непреднамеренно допускают больше (например, Is $10 действительно ли это действительная строка «захватываемого значения» в моем регулярном выражении newValue, приведенном выше?), потому что мы не были достаточно вдумчивы.Оба метода имеют ценность, и оба поощряют различные типы непреднамеренных ошибок.Часто легко недооценить сложность.

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

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

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

}

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

string res = Microsoft.VisualBasic.Strings.Replace(res, 
                                   "%PolicyAmount%", 
                                   "<*>", 
                                   Compare: Microsoft.VisualBasic.CompareMethod.Text);

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

    /// <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, обрабатывающем миллионы строк, и метод регулярных выражений был намного медленнее, чем этот.

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

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

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

Вот еще один вариант выполнения замен Regex, так как не многие люди замечают, что совпадения содержат расположение в строке:

    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();
    }
Regex.Replace(strInput, strToken.Replace("<*>quot;, "[$]"), strReplaceWith, RegexOptions.IgnoreCase);

Метод регулярного выражения должен работать. Однако то, что вы также можете сделать, это нижний регистр строки из базы данных, нижний регистр% переменных%, который у вас есть, а затем найти позиции и длины в строчной строке из базы данных. Помните, что позиции в строке не меняются только потому, что ее нижний регистр.

Затем, используя цикл, который идет в обратном направлении (проще, если вы этого не сделаете, вам придется вести текущий подсчет того, куда переходят более поздние точки), удалите из вашей строки без нижестоящего элемента из базы данных% variable% by их положение и длину и вставьте значения замены.

(Поскольку все стремятся к этому).Вот моя версия (с нулевыми проверками, правильным вводом и экранированием замен) ** Вдохновлена ​​Интернетом и другими версиями:

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

Позвольте мне сделать мое дело, и тогда вы можете разорвать меня на куски, если хотите.

Regex не является решением этой проблемы - слишком медленно и требует много памяти.

StringBuilder намного лучше, чем искажение строк.

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

Я считаю, что наличие параметра StringComparison не очень хорошая идея. Я попробовал, но тестовый пример, упомянутый Майклом-Лю, показал проблему: -

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

Несмотря на совпадение IndexOf, существует несоответствие между длиной совпадения в исходной строке (1) и oldValue.Length (2). Это проявилось в появлении IndexOutOfRange в некоторых других решениях, когда oldValue.Length был добавлен к текущей позиции совпадения, и я не смог найти способ обойти это. В любом случае, Regex не соответствует этому случаю, поэтому я принял прагматическое решение - использовать только 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();
    }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top