如何在.NET Regex中访问命名捕获组?

285

我很难找到一个好的资源来解释如何在C#中使用命名捕获组。这是迄今为止我所拥有的代码:

string page = Encoding.ASCII.GetString(bytePage);
Regex qariRegex = new Regex("<td><a href=\"(?<link>.*?)\">(?<name>.*?)</a></td>");
MatchCollection mc = qariRegex.Matches(page);
CaptureCollection cc = mc[0].Captures;
MessageBox.Show(cc[0].ToString());

然而,这总是显示整行:

<td><a href="/path/to/file">Name of File</a></td> 

我尝试过几种从不同网站上找到的“方法”,但结果一直相同。

如何访问在正则表达式中指定的命名捕获组?


3
回溯引用应该按照格式(?<link>.),而不是(?<link>.?)。 - Rashmi Pandit
14
注意:如果您试图在xml文件中存储一个命名捕获组,则尖括号<>会破坏它。在这种情况下,您可以使用(?'link'.*)代替。虽然与此问题不完全相关,但我是通过Google搜索“.net命名捕获组”而来的,所以我相信其他人也是如此... - rtpHarry
1
StackOverflow链接,带有很好的示例:https://dev59.com/eHM_5IYBdhLWcg3wcCnc#1381163。此外,@rtpHarry,`<>不会破坏它。我能够使用myRegex.GetGroupNames()`集合作为XML元素名称。 - radarbob
5个回答

291
使用Match对象的group集合,并使用捕获组名进行索引,例如:
foreach (Match m in mc){
    MessageBox.Show(m.Groups["link"].Value);
}

12
不要使用 var m,因为它将是一个“对象”。 - Thomas Weller

124
您可以通过将命名捕获组字符串传递给结果Match对象的Groups属性的索引器来指定它。以下是一个小例子:
using System;
using System.Text.RegularExpressions;

class Program
{
    static void Main()
    {
        String sample = "hello-world-";
        Regex regex = new Regex("-(?<test>[^-]*)-");

        Match match = regex.Match(sample);

        if (match.Success)
        {
            Console.WriteLine(match.Groups["test"].Value);
        }
    }
}

11
下面的代码示例,即使在空格字符之间也会匹配模式。 即:
<td><a href='/path/to/file'>Name of File</a></td>

以及:

<td> <a      href='/path/to/file' >Name of File</a>  </td>

该方法根据输入的htmlTd字符串是否与模式匹配返回true或false。如果匹配成功,则out参数分别包含链接和名称。

/// <summary>
/// Assigns proper values to link and name, if the htmlId matches the pattern
/// </summary>
/// <returns>true if success, false otherwise</returns>
public static bool TryGetHrefDetails(string htmlTd, out string link, out string name)
{
    link = null;
    name = null;

    string pattern = "<td>\\s*<a\\s*href\\s*=\\s*(?:\"(?<link>[^\"]*)\"|(?<link>\\S+))\\s*>(?<name>.*)\\s*</a>\\s*</td>";

    if (Regex.IsMatch(htmlTd, pattern))
    {
        Regex r = new Regex(pattern,  RegexOptions.IgnoreCase | RegexOptions.Compiled);
        link = r.Match(htmlTd).Result("${link}");
        name = r.Match(htmlTd).Result("${name}");
        return true;
    }
    else
        return false;
}

我已经测试过了,它可以正常工作。


1
谢谢你提醒我花括号可以访问组。我更喜欢坚持使用${1},以保持简单。 - Magnus Smith
这完全回答了问题,但有一些问题太长无法在此解释,但我在下面的答案中进行了说明和更正。 - Mariano Desanze

3
此外,如果有人在对正则表达式对象执行搜索之前需要组名称的用例,可以使用以下方法:
var regex = new Regex(pattern); // initialized somewhere
// ...
var groupNames = regex.GetGroupNames();

2
这个答案对Rashmi Pandit的答案进行了改进,这种方式比其他方式更好,因为它似乎完全解决了问题中详细描述的确切问题。
不好的地方在于它效率低下,并且没有始终使用IgnoreCase选项。
效率低下是因为正则表达式的构造和执行可能很昂贵,在那个答案中,它只需要构造一次(调用Regex.IsMatch只是在幕后再次构造正则表达式)。Match方法可以只调用一次并存储在一个变量中,然后linkname应该从该变量的Result调用。
IgnoreCase选项仅在Match部分中使用,但未在Regex.IsMatch部分中使用。
我还将正则表达式的定义移出了方法,以便只需构造一次(如果我们使用RegexOptions.Compiled选项存储该程序集,则我认为这是明智的方法)。
private static Regex hrefRegex = new Regex("<td>\\s*<a\\s*href\\s*=\\s*(?:\"(?<link>[^\"]*)\"|(?<link>\\S+))\\s*>(?<name>.*)\\s*</a>\\s*</td>",  RegexOptions.IgnoreCase | RegexOptions.Compiled);

public static bool TryGetHrefDetails(string htmlTd, out string link, out string name)
{
    var matches = hrefRegex.Match(htmlTd);
    if (matches.Success)
    {
        link = matches.Result("${link}");
        name = matches.Result("${name}");
        return true;
    }
    else
    {
        link = null;
        name = null;
        return false;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接