RegEx的词汇表功能

时间:2013-10-11 15:07:25

标签: c# .net regex

我正在开发一个基于网络的帮助系统,该系统会自动将链接插入到解释性文本中,将用户带到帮助中的其他主题。我有数百个应该联系的术语,即

“手册和标签”(一般描述这些概念) “删除手册和标签”(描述此具体操作) “了解有关添加手册和标签的详细信息”(再次,更具体的操作)

我有一个RegEx来查找/替换整个单词(好的'\ b'),除了在其他链接的术语中找到的链接术语外,效果很好。而不是:

<a href="#">Learn more about manuals and labels</a>

我最终得到了

<a href="#">Learn more about <a href="#">manuals and labels</a></a>

这让每个人都哭了一下。更改术语被替换的顺序(最短到最长)意味着我会得到:

Learn more about <a href="#">manuals and labels</a>

没有我真正需要的外链接。

进一步的复杂性是搜索词的大小写可能会有所不同,我需要保留原始大小写。如果我可以做这样的事情,我会全力以赴:

Regex _regex = new Regex("\\b" + termToFind + "(|s)" + "\\b", RegexOptions.IgnoreCase);

string resultingText = _regex.Replace(textThatNeedsLinksInserted, "<a>" + "$&".Replace(" ", "_") + "</a>));

然后在完成所有条款之后,删除“_”,这将是完美的。 “Learn_more_about_manuals_and_labels”与“手册和标签”不匹配,一切都很好。

在编写文本时,帮助作者很难界定需要替换的术语 - 他们不习惯编码。此外,这将限制以后添加新术语的灵活性,因为我们必须返回并为所有以前写入的文本添加分隔符。

是否有RegEx可以让我在原始匹配中用“_”替换空格?或者是否有一个不同的解决方案让我望而却步?

3 个答案:

答案 0 :(得分:1)

在您使用嵌套链接的示例中,您可能会对这些字词进行单独传递并执行多次Regex.Replace调用。因为你正在使用一个正则表达式,所以你应该让它完成繁重的工作并将一个漂亮的模式放在一起,以便利用交替。

换句话说,您可能想要这样的模式:\b(term1|term2|termN)\b

var input = "Having trouble with your manuals and labels? Learn more about adding manuals and labels. Need to get rid of them? Try to delete manuals and labels.";
var terms = new[] 
{
    "Learn more about adding manuals and labels",
    "Delete Manuals and Labels",
    "manuals and labels"
};

var pattern = @"\b(" + String.Join("|", terms) + @")\b";
var replacement = @"<a href=""#"">$1</a>";
var result = Regex.Replace(input, pattern, replacement, RegexOptions.IgnoreCase);
Console.WriteLine(result);

现在,为了解决每个术语的相应href值的问题,您可以使用字典并更改正则表达式以使用将返回自定义格式并从字典中查找值的MatchEvaluator。字典也通过传递StringComparer.OrdinalIgnoreCase来忽略大小写。我通过在组的开头添加?:来稍微调整模式,使其成为非捕获组,因为我不再像第一个示例中那样引用捕获的项目。

var terms = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
    { "Learn more about adding manuals and labels", "2.html" },
    { "Delete Manuals and Labels", "3.html" },
    { "manuals and labels", "1.html" }
};

var pattern = @"\b(?:" + String.Join("|", terms.Select(t => t.Key)) + @")\b";
var result = Regex.Replace(input, pattern,
    m => String.Format(@"<a href=""{0}"">{1}</a>", terms[m.Value], m.Value),
    RegexOptions.IgnoreCase);

Console.WriteLine(result);

答案 1 :(得分:1)

我会使用这样的有序字典,确保最小的术语是最后一个:

using System;
using System.Text.RegularExpressions;
using System.Collections.Specialized;

public class Test
{
    public static void Main()
    {
        OrderedDictionary Links = new OrderedDictionary();
        Links.Add("Learn more about adding manuals and labels", "2");
        Links.Add("Delete Manuals and Labels", "3");
        Links.Add("manuals and labels", "1");

        string text = "Having trouble with your manuals and labels? Learn more about adding manuals and labels. Need to get rid of them? Try to delete manuals and labels.";

        foreach (string termToFind in Links.Keys)
        {
            Regex _regex = new Regex(@"\b" + termToFind + @"s?\b(?![^<>]*</)", RegexOptions.IgnoreCase);
            text = _regex.Replace(text, @"<a href=""" + Links[termToFind] + @".html"">$&</a>");
        }
        Console.WriteLine(text);
    }
}

ideone demo

我添加的否定前瞻((?![^<>]*</))会阻止替换之前已经替换过的锚标记之间的部分。

答案 2 :(得分:0)

首先,您可以阻止manuals and labels的正则表达式使用lookbehind查找Learn more about manuals and labels。修改你的正则表达式如下:

(?<!Learn more about )(manuals and labels)

但是根据您的具体要求,我会提出一个不同的解决方案。您应该为正则表达式或两者定义规则或优先级列表。可能的规则可能是“始终首先搜索与大多数字符匹配的正则表达式”。但是,这需要您的正则表达式始终是固定长度。并且它不会阻止一个正则表达式使用和替换将由不同的正则表达式匹配的字符(甚至可能具有相同的大小)。

当然,您需要为每个正则表达式添加额外的lookbehind和lookahead,以防止替换替换元素中的字符串