Comment accéder aux groupes de capture nommés dans une expression régulière .NET?

255

J'ai du mal à trouver une bonne ressource qui explique comment utiliser les groupes de capture nommés en C #. Voici le code que j'ai jusqu'à présent:

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

Cependant, cela montre toujours juste la ligne complète:

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

J'ai expérimenté plusieurs autres "méthodes" que j'ai trouvées sur divers sites Web mais j'obtiens toujours le même résultat.

Comment puis-je accéder aux groupes de capture nommés qui sont spécifiés dans mon expression régulière?

UnkwnTech
la source
3
La référence arrière doit être au format (? <link>. *) Et non (? <link>. *?)
SO User
11
FYI: Si vous essayez de stocker un groupe de capture nommé dans un fichier xml, alors il le <>cassera. Vous pouvez utiliser à la (?'link'.*)place dans ce cas. Pas tout à fait pertinent pour cette question, mais j'ai atterri ici à partir d'une recherche Google sur les "groupes de capture nommés .net", donc je suis sûr que d'autres personnes le sont aussi ...
rtpHarry
1
Lien StackOverflow avec un bel exemple: stackoverflow.com/a/1381163/463206 Aussi, @rtpHarry, Non, <>cela ne le cassera pas. J'ai pu utiliser la myRegex.GetGroupNames()collection comme noms d'élément XML.
radarbob

Réponses:

263

Utilisez la collection de groupes de l'objet Match, en l'indexant avec le nom du groupe de capture, par exemple

foreach (Match m in mc){
    MessageBox.Show(m.Groups["link"].Value);
}
Paolo Tedesco
la source
10
N'utilisez pas var m, car ce serait un object.
Thomas Weller
111

Vous spécifiez la chaîne de groupe de capture nommée en la transmettant à l'indexeur de la Groupspropriété d'un Matchobjet résultant .

Voici un petit exemple:

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
la source
10

L'exemple de code suivant correspondra au modèle même en cas d'espace entre les caractères. c'est à dire :

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

aussi bien que:

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

La méthode renvoie true ou false, selon que la chaîne htmlTd d'entrée correspond au modèle ou non. S'il correspond, les paramètres de sortie contiennent respectivement le lien et le nom.

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

J'ai testé cela et cela fonctionne correctement.

Utilisateur SO
la source
1
Merci de me rappeler que les accolades peuvent accéder aux groupes. Je préfère m'en ${1}tenir à garder les choses encore plus simples.
Magnus Smith
Cela répond complètement à la question, mais a quelques problèmes qui sont trop longs à expliquer ici, mais j'ai expliqué et corrigé ceux dans ma réponse ci
Mariano Desanze
1

De plus, si quelqu'un a un cas d'utilisation où il a besoin de noms de groupe avant d'exécuter la recherche sur un objet Regex, il peut utiliser:

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

Cette réponse améliore la réponse de Rashmi Pandit , qui est en quelque sorte meilleure que les autres car elle semble résoudre complètement le problème exact détaillé dans la question.

La mauvaise partie est qu'elle est inefficace et n'utilise pas systématiquement l'option IgnoreCase.

La partie inefficace est parce que l'expression régulière peut être coûteuse à construire et à exécuter, et dans cette réponse, elle aurait pu être construite une seule fois (l'appel ne Regex.IsMatchfaisait que reconstruire l'expression régulière derrière la scène). Et Matchméthode aurait pu être appelé une seule fois et stocké dans une variable puis linket namedoit appeler Resultde cette variable.

Et l'option IgnoreCase n'était utilisée que dans la Matchpièce mais pas dans la Regex.IsMatchpièce.

J'ai également déplacé la définition Regex en dehors de la méthode afin de la construire une seule fois (je pense que c'est l'approche raisonnable si nous stockons cet assemblage avec l' RegexOptions.Compiledoption).

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
la source