Как получить доступ к именованным группам захвата в .NET Regex?

255

Мне трудно найти хороший ресурс, который объясняет, как использовать именованные группы захвата в 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> 

Я экспериментировал с несколькими другими «методами», которые я нашел на различных сайтах, но я продолжаю получать тот же результат.

Как я могу получить доступ к именованным группам захвата, которые указаны в моем регулярном выражении?

UnkwnTech
источник
3
Обратные ссылки должны быть в формате (? <Link>. *), А не (? <Link>. *?)
SO User
11
К вашему сведению: если вы пытаетесь сохранить именованную группу захвата в XML-файле, то <>она сломается. Вы можете использовать (?'link'.*)вместо этого в этом случае. Не совсем уместно в этом вопросе, но я попал сюда из поиска в Google «.net именованных групп захвата», так что я уверен, что другие люди тоже ...
rtpHarry
1
Ссылка на StackOverflow с хорошим примером: stackoverflow.com/a/1381163/463206 Кроме того, @rtpHarry, нет, это <>не сломает. Я смог использовать myRegex.GetGroupNames()коллекцию в качестве имен элементов XML.
радар Боб

Ответы:

263

Используйте коллекцию групп объекта Match, проиндексировав его с помощью имени группы захвата, например

foreach (Match m in mc){
    MessageBox.Show(m.Groups["link"].Value);
}
Паоло Тедеско
источник
10
Не используйте var m, так как это будет object.
Томас Веллер
111

Вы указываете именованную строку группы захвата, передавая ее индексатору Groupsсвойства результирующего Matchобъекта.

Вот небольшой пример:

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);
        }
    }
}
Эндрю Хэйр
источник
10

Следующий пример кода будет соответствовать шаблону даже в случае пробелов между ними. т.е.

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

так же как:

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

Метод возвращает true или false, в зависимости от того, соответствует ли введенная строка htmlTd шаблону или нет. Если это соответствует, выходные параметры содержат ссылку и имя соответственно.

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

Я проверил это, и он работает правильно.

SO User
источник
1
Спасибо, что напомнили мне, что фигурные скобки могут получить доступ к группам. Я предпочитаю придерживаться, ${1}чтобы держать вещи еще проще.
Магнус Смит
Это полностью отвечает на вопрос, но есть некоторые проблемы, которые здесь слишком долго объяснять, но я объяснил и исправил их в своем ответе ниже
Мариано Дезанце
1

Кроме того, если у кого-то есть сценарий использования, где ему нужны имена групп перед выполнением поиска по объекту Regex, он может использовать:

var regex = new Regex(pattern); // initialized somewhere
// ...
var groupNames = regex.GetGroupNames();
тинаму
источник
1

Этот ответ улучшает ответ Рашми Пандита , который несколько лучше, чем остальные, потому что кажется, что он полностью решает точную проблему, подробно описанную в вопросе.

Плохая часть заключается в том, что он неэффективен и не использует опцию IgnoreCase последовательно.

Неэффективная часть заключается в том, что регулярное выражение может быть дорогим для создания и выполнения, и в этом ответе оно могло быть Regex.IsMatchсоздано только один раз (вызов просто создавал регулярное выражение снова за сценой). И Matchметод мог быть вызван только один раз и сохранен в переменной, а затем linkи nameдолжен вызываться Resultиз этой переменной.

И опция IgnoreCase использовалась только в Matchчасти, но не в Regex.IsMatchчасти.

Я также переместил определение Regex за пределы метода, чтобы создать его только один раз (я думаю, это разумный подход, если мы храним эту сборку с 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;
    }
}
Мариано Дезанце
источник