¿Cómo accedo a grupos de captura con nombre en .NET Regex?

255

Me está costando encontrar un buen recurso que explique cómo usar Grupos de captura con nombre en C #. Este es el código que tengo hasta ahora:

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

Sin embargo, esto siempre muestra la línea completa:

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

He experimentado con varios otros "métodos" que he encontrado en varios sitios web, pero sigo obteniendo el mismo resultado.

¿Cómo puedo acceder a los grupos de captura con nombre que se especifican en mi expresión regular?

UnkwnTech
fuente
3
La referencia inversa debe estar en el formato (? <link>. *) Y no (? <link>. *?)
Usuario SO
11
FYI: Si está tratando de almacenar un grupo de captura con nombre dentro de un archivo xml, entonces <>lo romperá. Puede usar (?'link'.*)en su lugar en este caso. No es del todo relevante para esta pregunta, pero llegué aquí desde una búsqueda en Google de "grupos de captura con nombre .net", así que estoy seguro de que otras personas también lo están ...
rtpHarry
1
Enlace de StackOverflow con un buen ejemplo: stackoverflow.com/a/1381163/463206 Además, @rtpHarry, No <>, no lo romperá. Pude usar la myRegex.GetGroupNames()colección como nombres de elementos XML.
radarbob

Respuestas:

263

Utilice la colección de grupo del objeto Match, indizándolo con el nombre del grupo de captura, p. Ej.

foreach (Match m in mc){
    MessageBox.Show(m.Groups["link"].Value);
}
Paolo Tedesco
fuente
10
No lo uses var m, ya que eso sería un object.
Thomas Weller
111

Puede especificar la cadena del grupo de captura con nombre pasándola al indizador de la Groupspropiedad de un Matchobjeto resultante .

Aquí hay un pequeño ejemplo:

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
fuente
10

El siguiente ejemplo de código coincidirá con el patrón incluso en el caso de caracteres de espacio en el medio. es decir:

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

tanto como:

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

El método devuelve verdadero o falso, dependiendo de si la cadena de entrada htmlTd coincide con el patrón o no. Si coincide, los parámetros de salida contienen el enlace y el nombre 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;
}

He probado esto y funciona correctamente.

Usuario SO
fuente
1
Gracias por recordarme que las llaves pueden acceder a los grupos. Prefiero seguir ${1}para mantener las cosas aún más simples.
Magnus Smith
Esto responde completamente a la pregunta, pero tiene algunos problemas que son demasiado largos para explicar aquí, pero expliqué y corregí los de mi respuesta a continuación
Mariano Desanze
1

Además, si alguien tiene un caso de uso donde necesita nombres de grupo antes de ejecutar la búsqueda en el objeto Regex, puede usar:

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

Esta respuesta mejora con la respuesta de Rashmi Pandit , que es de alguna manera mejor que el resto porque parece resolver completamente el problema exacto detallado en la pregunta.

Lo malo es que es ineficiente y no usa la opción IgnoreCase de manera consistente.

La parte ineficiente se debe a que la expresión regular puede ser costosa de construir y ejecutar, y en esa respuesta podría haber sido construida solo una vez (la llamada Regex.IsMatchfue simplemente construir la expresión regular de nuevo detrás de la escena). Y Matchmétodo podría haber sido llamado una sola vez y se almacena en una variable y, a continuación link, y namedebe llamar Resultde esa variable.

Y la opción IgnoreCase solo se usó en la Matchparte pero no en la Regex.IsMatchparte.

También moví la definición de Regex fuera del método para construirla solo una vez (creo que es el enfoque sensato si estamos almacenando el ensamblaje con la RegexOptions.Compiledopción).

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
fuente