Скажем, у меня есть две строки: «Здесь текст» и «Здесь текст».

И у меня есть коллекция, содержащая слова, которые я хотел бы сопоставить с текстом в строках. «Некоторые», «Текст», «Здесь»

Если одно из слов соответствует определенному слову в строке (независимо от того, написано ли оно в верхнем или нижнем регистре), я хотел бы взять исходное слово из строки и добавить вокруг него некоторую разметку HTML, например <dfn title="Definition of word">Original word</dfn>.

Я играл с методом string.Replace (), но не знал, как добиться его соответствия независимо от регистра и как сохранить исходное слово без изменений (чтобы я не заменял слово на <dfn title="">Word</dfn или наоборот).

1
Frederik 25 Июн 2009 в 14:25

6 ответов

Лучший ответ

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

Вот функция.

Обновление:

  1. Теперь работает с Dictionary<string, string> вместо string[], что позволяет передавать определение функции вместе со словом.
  2. Теперь работает с произвольным упорядочением словаря определений.

...

public static string HtmlReplace(string value, Dictionary<string, string>
    definitions, Func<string, string, string> htmlWrapper)
{
    var sb = new StringBuilder(value.Length);

    int index = -1;
    int lastEndIndex = 0;
    KeyValuePair<string, string> def;
    while ((index = IndexOf(value, definitions, lastEndIndex,
        StringComparison.InvariantCultureIgnoreCase, out def)) != -1)
    {
        sb.Append(value.Substring(lastEndIndex, index - lastEndIndex));
        sb.Append(htmlWrapper(def.Key, def.Value));
        lastEndIndex = index + def.Key.Length;
    }
    sb.Append(value.Substring(lastEndIndex, value.Length - lastEndIndex));

    return sb.ToString();
}

private static int IndexOf(string text, Dictionary<string, string> values, int startIndex,
    StringComparison comparisonType, out KeyValuePair<string, string> foundEntry)
{
    var minEntry = default(KeyValuePair<string, string>);
    int minIndex = -1;
    int index;
    foreach (var entry in values)
    {
        if (((index = text.IndexOf(entry.Key, startIndex, comparisonType)) < minIndex
            && index != -1) || minIndex == -1)
        {
            minIndex = index;
            minEntry = entry;
        }
    }

    foundEntry = minEntry;
    return minIndex;
}

И небольшая тестовая программа. (Обратите внимание на использование лямбда-выражения для удобства.)

static void Main(string[] args)
{
    var str = "Definition foo; Definition bar; Definition baz";
    var definitions = new Dictionary<string, string>();
    definitions.Add("foo", "Definition 1");
    definitions.Add("bar", "Definition 2");
    definitions.Add("baz", "Definition 3");
    var output = HtmlReplace(str, definitions,
        (word, definition) => string.Format("<dfn title=\"{1}\">{0}</dfn>", 
            word, definition));
}

Выходной текст:

Определение foo ; Определение бар ; Определение baz

Надеюсь, это поможет.

5
Noldorin 25 Июн 2009 в 15:11

Вы можете использовать регулярное выражение:

class Program {

    static string ReplaceWord(Match m) {
        return string.Format("<dfn>{0}</dfn>",m.Value);
    }

    static void Main(string[] args) {

        Regex r = new Regex("some|text|here", RegexOptions.IgnoreCase);
        string input = "Some random text.";
        string replaced = r.Replace(input, ReplaceWord);
        Console.WriteLine(replaced);
    }
}

RegexOptions.IgnoreCase используется для поиска слов в списке независимо от их регистра.
Функция ReplaceWord возвращает совпавшую строку (в правильном регистре), окруженную открывающим и закрывающим тегами (обратите внимание, что вам все равно может потребоваться экранировать внутреннюю строку).

3
Paolo Tedesco 25 Июн 2009 в 10:34

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

Что произойдет, если у меня есть условия:

Web Browser
Browser History

И я сравниваю это с фразой:

Now, clean the web browser history by ...

Вы получаете

Now, clean the <dfn title="Definition of word">web <dfn title="Definition of word">browser</dfn> history</dfn> by ...

Недавно я боролся с той же проблемой, но не думаю, что мое решение вам поможет - http://github.com/jarofgreen/TaggedWiki/blob/d002997444c35cafecd85316280a896484a06511/taggedwikitest/taggedwiki/views.py, строка 47 и далее. В итоге я поставил маркер перед тегом и не обернул текст.

Однако у меня может быть одна часть ответа для вас: чтобы избежать перехвата слов в HTML (проблема в том, что происходит, если у вас есть тег «title», который вы указали в своем последнем абзаце), я сделал 2 прохода. В первом проходе поиска я сохранил расположение фраз для переноса, затем во втором проходе без поиска я вставил фактический HTML. Таким образом, в тексте не будет HTML, пока вы выполняете свой фактический поиск.

1
James 25 Июн 2009 в 11:23

Может я неправильно понял ваш вопрос. Но почему бы просто не использовать регулярные выражения?

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

Но обратите внимание, что вам придется использовать String.Insert () с позициями совпадения, и string .replace () не поможет.

Надеюсь это ответит на твой вопрос.

0
Prashanth 25 Июн 2009 в 10:45

Как вы сказали, самым простым способом было бы использовать String.Replace.

Я был удивлен, что не было возможности указать StringComparisonOptions в String.Replace.

Я написал для вас «не очень оптимизированный», но очень простой IgnoreCaseReplace:

static string IgnoreCaseReplace(string text, string oldValue, string newValue)
{
    int index = 0;
    while ((index = text.IndexOf(oldValue,
        index,
        StringComparison.InvariantCultureIgnoreCase)) >= 0)
    {
        text = text.Substring(0, index)
            + newValue
            + text.Substring(index + oldValue.Length);

        index += newValue.Length;
    }

    return text;
}

Чтобы сделать его более приятным, вы можете обернуть его в статический класс и сделать его методом расширения String:

static class MyStringUtilities
{
    public static string IgnoreCaseReplace(this string text, string oldValue, string newValue)
    {
        int index = 0;
        while ((index = text.IndexOf(oldValue,
            index,
            StringComparison.InvariantCultureIgnoreCase)) >= 0)
        {
            text = text.Substring(0, index)
                + newValue
                + text.Substring(index + oldValue.Length);

            index += newValue.Length;
        }

        return text;
    }
}
0
Maghis 25 Июн 2009 в 10:57

Код регулярного выражения:

/// <summary>
/// Converts the input string by formatting the words in the dict with their meanings
/// </summary>
/// <param name="input">Input string</param>
/// <param name="dict">Dictionary contains words as keys and meanings as values</param>
/// <returns>Formatted string</returns>
public static string FormatForDefns(string input, Dictionary<string,string> dict )
{
    string formatted = input;
    foreach (KeyValuePair<string, string> kv in dict)
    {
        string definition = "<dfn title=\"" + kv.Value + "\">" + kv.Key + "</dfn>.";
        string pattern = "(?<word>" + kv.Key + ")";
        formatted = Regex.Replace(formatted, pattern, definition, RegexOptions.IgnoreCase);
    }
    return formatted;
}

Это телефонный код

Dictionary<string, string> dict = new Dictionary<string, string>();
dict.Add("word", "meaning");
dict.Add("taciturn ", "Habitually silent; not inclined to talk");

string s = "word abase";
string formattedString = MyRegEx.FormatForDefns(s, dict);
0
SO User 25 Июн 2009 в 11:05