【问题标题】:Inject HTML markup around certain words in a string在字符串中的某些单词周围注入 HTML 标记
【发布时间】:2009-06-25 10:25:41
【问题描述】:

假设我有这两个字符串: “这里有一些文字”和“这里有一些文字”

我有一个集合,其中包含我想与字符串中的文本匹配的单词。 “一些”、“文本”、“这里”

如果其中一个词与字符串中的某个词匹配(无论它是大写还是小写),我想从字符串中获取原始词并在其周围添加一些 HTML 标记,例如 <dfn title="Definition of word">Original word</dfn> .

我在玩 string.Replace() 方法,但不知道如何让它匹配而不考虑大小写以及如何仍然保持原始单词完整(这样我就不会用 @987654322 替换“单词” @ 或反之亦然)。

【问题讨论】:

  • 您是否正在创建将某些词链接到广告的网站之一(例如 Word 浏览器链接到 IE8)?

标签: c# html regex


【解决方案1】:

确实,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;
}

还有一个小测试程序。 (为方便起见,请注意使用 lambda 表达式。)

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;定义 bar;定义 baz

希望对您有所帮助。

【讨论】:

  • 在将单词数组更改为字典集合后遇到了一些问题。除了在 string.format 方法(lambda 表达式)中检索要作为定义文本发送的值之外,我一切正常。感谢您的帮助。
  • @Frederik:没问题...您实际上可以在以前版本的 lambda 表达式中使用 switch 语句,但我更新了帖子以显示使用 Dictionary 的版本。选择你喜欢的。
  • 太棒了!最后一件小事,您现在使用 word.Key 和 word.Value 但我想使用原始单词而不是 word.Key。再次感谢!
  • 可以使用:Func, string, string> sb.Append(htmlWrapper(def, value.Substring(index, def.Key.Length)));和 var output = HtmlReplace(str, dict, (word, value) => string.Format("{0}", value, word.Value)) ;
  • 不确定你的意思——你只是想在 lambda 表达式中使用 word 吗?您可以更改 htmlWrapepr 函数的定义来执行此操作。
【解决方案2】:

你可以使用正则表达式:

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】:

    首先,我会很刻薄,并提供一个反答案:一个测试用例给你,它是一个代码的虫子。

    如果我有条款会怎样:

    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 中捕获单词(如果您在上一段中确定了“标题”标签会发生什么问题),我做了 2 遍.在第一个搜索过程中,我存储了要换行的短语的位置,然后在第二个非搜索过程中,我输入了实际的 HTML。这样,在您进行实际搜索时,文本中就没有 HTML。

    【讨论】:

      【解决方案4】:

      可能是我错误地理解了你的问题。但是为什么不直接使用正则表达式呢?

      如果您的正则表达式正确,那么它们会更快、更简单,并在原始字符串上提供索引,从而为您提供匹配单词的确切位置,以便您可以在所需位置准确插入标记。

      但请注意,您必须将 String.Insert() 与匹配位置一起使用,而字符串 .replace() 将无济于事。

      希望能回答你的问题。

      【讨论】:

        【解决方案5】:

        如您所说,最简单的方法是使用 String.Replace。

        我很惊讶在 String.Replace 中没有指定 StringComparisonOptions 的选项。

        我为你写了一个“没有那么优化”但很简单的 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;
            }
        }
        

        【讨论】:

          【解决方案6】:

          正则表达式代码:

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

          【讨论】:

          • 多次进行这样的正则表达式替换(对于每个字典条目)将非常低效。
          • 您还冒着让您的正则表达式错误地匹配由较早的 Replace() 添加到字符串中的文本的风险。例如,如果其中一个关键字是“title”,您最终会替换任何已存在的 dfn 元素中的“title”属性名称。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-09-15
          • 1970-01-01
          • 1970-01-01
          • 2013-03-09
          • 1970-01-01
          • 2021-06-22
          • 2013-09-20
          相关资源
          最近更新 更多