2

我有一个正则表达式,它GroupCollection在它的捕获中使用 s 来捕获一组项目 ID(可以用逗号分隔,也说明最后一个有“和”这个词):

(\bItem #(?<ITEMID>\d+))|(,\s?(?<ITEMID>\d+))|(,?\sand\s(?<ITEMID>\d+))

有没有一种简单的方法使用 C# 的Regex类来用 url 替换 ITEMID 数字?现在,我有以下内容:

foreach (Match match in matches)
{
    var group = match.Groups["ITEMID"];
    var address = String.Format(UnformattedAddress, group.Value);

    CustomReplace(ref myString, group.Value, address,
        group.Index, (group.Index + group.Length));
}

public static int CustomReplace(ref string source, string org, string replace,
    int start, int max)
{
    if (start < 0) throw new System.ArgumentOutOfRangeException("start");
    if (max <= 0) return 0;

    start = source.IndexOf(org, start);

    if (start < 0) return 0;

    var sb = new StringBuilder(source, 0, start, source.Length);

    var found = 0;
    while (max-- > 0)
    {
        var index = source.IndexOf(org, start);

        if (index < 0) break;

        sb.Append(source, start, index - start).Append(replace);
        start = index + org.Length;
        found++;
    }

    sb.Append(source, start, source.Length - start);
    source = sb.ToString();

    return found;
}

我在网上找到的CustomReplace方法是在字符串源中用另一个字符串替换一个字符串的简单方法。问题是我确信可能有一种更简单的方法,可能会根据需要使用Regex类来替换GroupCollections 。我只是想不通那是什么。谢谢!

示例文本:

Hello the items you are looking for are Item #25, 38, and 45. They total 100 dollars.

25, 38, 并且45应该替换为我正在创建的 URL 字符串(这是一个 HTML 字符串)。

4

3 回答 3

2

您的模式适用于您的输入,但它确实有一个错误。具体来说,它将匹配您输入中出现在逗号或单词" 和 "之后的任何数字。

我继续重写你的模式以避免这个问题。为了实现这一点,我实际上使用了两种正则表达式模式。可以使用一种模式来实现这一点,但它比我选择分享的方法相当复杂且可读性差。

主要模式是:\bItem #\d+(?:,? \d+)*(?:,? and \d+)? 这里没有使用捕获组,因为我只对匹配项目感兴趣。该(?: ... )位是非捕获组。的用法(?:,? \d+)*是匹配字符串中间部分的多个逗号分隔值。

一旦项目匹配,我使用Regex.Replace格式化项目,然后重建字符串以用格式化项目交换原始项目。

这是一个带有几个不同输入的示例:

string[] inputs =
{
    "Hello the items you are looking for are Item #25, 38, 22, and 45. They total 100 dollars.",
    "... Item #25, 38 and 45. Other numbers 100, 20, and 30 untouched.",
    "Item #25, and 45",
    "Item #25 and 45",
    "Item #25"
};

string pattern = @"\bItem #\d+(?:,? \d+)*(?:,? and \d+)?";
string digitPattern = @"(\d+)";
// $1 refers to the first (and only) group in digitPattern
string replacement = @"<a href=""http://url/$1.html"">$1</a>";

foreach (var input in inputs)
{
    Match m = Regex.Match(input, pattern);
    string formatted = Regex.Replace(m.Value, digitPattern, replacement);
    var builder = new StringBuilder(input)
                        .Remove(m.Index, m.Length)
                        .Insert(m.Index, formatted);
    Console.WriteLine(builder.ToString());
}

如果您需要使用现有方法来格式化 URL,而不是使用正则表达式替换模式,您可以使用Regex.Replace接受MatchEvaluator. 这可以使用 lambda 实现,并且比 MSDN 文档中显示的繁琐方法更好。

例如,假设您有一个FormatItem接受字符串并返回格式化字符串的方法:

public string FormatItem(string item)
{
    return String.Format("-- {0} --", item);
}

要使用FormatItem,您可以使用以下代码更改Regex.Replace前面代码示例中使用的方法:

string formatted = Regex.Replace(m.Value, digitPattern,
                       d => FormatItem(d.Value));
于 2012-06-30T05:47:31.307 回答
0

这是所需语法的示例,还表明您可以通过回调在替换中回退到 C#。

Regex.Replace 中的 MatchEvaluator 如何工作?

于 2012-06-29T19:09:47.770 回答
0

你似乎是同时从两个方向来的。一方面,您有一个包含三个捕获组的正则表达式,因此您希望解决方案涉及 GroupCollection。另一方面,所有三个组都具有相同的名称,因此您可能必须将它们视为同一组的单独捕获 - 即 CaptureCollection。实际上,您可能不需要它们中的任何一个。这是你的正则表达式(经过一点美学调整):

string source = @"Total cost for Item #25, 38, and 45 is 100 dollars.";

Regex regex1 = new Regex(
    @"\bItem #(?<ITEMID>\d+)|,\s*(?<ITEMID>\d+)|,?\s+and\s+(?<ITEMID>\d+)",
    RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);

foreach (Match m in regex1.Matches(source)) {
  Console.WriteLine(m.Groups["ITEMID"].Value);
}

它按预期输出25, 3845每个备选方案都有自己的捕获组副本,但只有其中一个将参加每场比赛。这是 .NET 正则表达式风格的一个显着特征;其他一些提供了允许您重用组名的特殊设置或组构造,但它们都不像 .NET 那样简单。但是,在这种情况下您并不需要它。您可以合并替代方案,如下所示:

@"(\bItem #|,\s*|,?\s+and\s+)(?<ITEMID>\d+)"

但是,您的正则表达式存在问题,如果您将源字符串更改为以下内容,则会显示该问题:

@"Total cost for Item #25, 38, and 45 is 1,500 dollars and 42 cents."

现在的输出是25, 38, 45, 500, 42。为了防止这些误报,您需要确保每个不以Item #, 开头的匹配都从最后一个匹配结束的地方开始。为此,您可以使用\G

@"(\bItem #|\G,?\s+and\s+|\G,\s*)(?<ITEMID>\d+)"

(为了提高效率,我还交换了最后两个选项的顺序。)把所有这些放在一起,我们只有另一个正则表达式替换。

string source =
   @"Total cost for Item #25, 38, and 45 is 1,500 dollars and 42 cents.";
Regex regex2 = new Regex(
    @"(?<TEXT>\bItem #|\G,?\s+and\s+|\G,\s*)(?<ITEMID>\d+)",
    RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
string result = regex2.Replace(source, 
    @"${TEXT}<a href='URL_${ITEMID}'>${ITEMID}</a>");
Console.WriteLine(result);

不需要显式使用 GroupCollections 或 CaptureCollections,除非您的替换比这复杂得多,否则可能也不需要 MatchEvaluator。

于 2012-07-01T04:55:56.657 回答