Como acesso grupos de captura nomeados em um .NET Regex?

255

Estou com dificuldade para encontrar um bom recurso que explique como usar Grupos de Captura Nomeados em C #. Este é o código que eu tenho até agora:

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

No entanto, isso sempre mostra apenas a linha completa:

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

Eu experimentei vários outros "métodos" que encontrei em vários sites, mas continuo obtendo o mesmo resultado.

Como posso acessar os grupos de captura nomeados especificados na minha regex?

UnkwnTech
fonte
3
A referência anterior deve estar no formato (? <link>. *) E não (? <link>. *?)
SO User
11
FYI: Se você estiver tentando armazenar um grupo de captura nomeado dentro de um arquivo xml, ele <>será interrompido. Você pode usar (?'link'.*)neste caso. Não inteiramente relevante para esta pergunta, mas eu aterrei aqui de uma pesquisa no Google de ".net chamados grupos de captura" Então, eu tenho certeza que outras pessoas são bem ...
rtpHarry
1
Link StackOverflow com um bom exemplo: stackoverflow.com/a/1381163/463206 Além disso, @rtpHarry, Não, <>isso não será interrompido. Consegui usar a myRegex.GetGroupNames()coleção como os nomes dos elementos XML.
Radarbob

Respostas:

263

Use a coleção de grupo do objeto Match, indexando-o com o nome do grupo de captura, por exemplo

foreach (Match m in mc){
    MessageBox.Show(m.Groups["link"].Value);
}
Paolo Tedesco
fonte
10
Não use var m, pois isso seria um object.
Thomas Weller
111

Você especifica a sequência do grupo de captura nomeado passando-a para o indexador da Groupspropriedade de um Matchobjeto resultante .

Aqui está um pequeno exemplo:

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);
        }
    }
}
Andrew Hare
fonte
10

O exemplo de código a seguir corresponderá ao padrão mesmo no caso de caracteres de espaço no meio. ou seja:

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

assim como:

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

O método retorna verdadeiro ou falso, dependendo se a sequência htmlTd de entrada corresponde ao padrão ou não. Se corresponder, os parâmetros de saída conterão o link e o nome, respectivamente.

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

Eu testei isso e funciona corretamente.

Usuário SO
fonte
1
Obrigado por me lembrar que o aparelho pode acessar os grupos. Eu prefiro ${1}manter as coisas ainda mais simples.
Magnus Smith
Isso responde completamente a pergunta, mas tem alguns problemas que são muito longos para explicar aqui, mas eu expliquei e corrigiu aqueles em minha resposta abaixo
Mariano Desanze
1

Além disso, se alguém tiver um caso de uso em que ele precisa de nomes de grupos antes de executar a pesquisa no objeto Regex, ele poderá usar:

var regex = new Regex(pattern); // initialized somewhere
// ...
var groupNames = regex.GetGroupNames();
tinamou
fonte
1

Essas respostas melhoram a resposta de Rashmi Pandit , que é um pouco melhor que as demais, porque parece resolver completamente o problema exato detalhado na pergunta.

A parte ruim é que é ineficiente e não usa a opção IgnoreCase de forma consistente.

Parte ineficiente é porque o regex pode ser caro para construir e executar, e nessa resposta ele poderia ter sido construído apenas uma vez (chamar Regex.IsMatchestava apenas construindo o regex novamente nos bastidores). E Matchmétodo poderia ter sido chamado apenas uma vez e armazenados em uma variável e, em seguida, linke namedeve chamar Resulta partir dessa variável.

E a opção IgnoreCase foi usada apenas na Matchparte, mas não na Regex.IsMatchparte.

Também mudei a definição Regex para fora do método, a fim de construí-la apenas uma vez (acho que é a abordagem sensata se estivermos armazenando esse assembly com a RegexOptions.Compiledopção).

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;
    }
}
Mariano Desanze
fonte