Come posso accedere ai gruppi di acquisizione con nome in un .NET Regex?


255

Sto facendo fatica a trovare una buona risorsa che spieghi come usare Named Capturing Groups in C #. Questo è il codice che ho finora:

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

Tuttavia, questo mostra sempre solo la linea completa:

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

Ho sperimentato diversi altri "metodi" che ho trovato su vari siti Web, ma continuo a ottenere lo stesso risultato.

Come posso accedere ai gruppi di acquisizione nominati specificati nella mia regex?


3
Il backreference deve essere nel formato (? <link>. *) E non (? <link>. *?)
SO Utente

11
Cordiali saluti: Se si sta tentando di memorizzare un gruppo di acquisizione denominato all'interno di un file XML, <>lo interromperà. Puoi usare (?'link'.*)invece in questo caso. Non del tutto rilevante per questa domanda, ma sono arrivato qui da una ricerca su Google di "gruppi di cattura nominati .net", quindi sono sicuro che anche altre persone ...
rtpHarry

1
Link StackOverflow con bell'esempio: stackoverflow.com/a/1381163/463206 Inoltre, @rtpHarry, No il <>non sarà romperlo. Sono stato in grado di utilizzare la myRegex.GetGroupNames()raccolta come nomi di elementi XML.
radarbob,

Risposte:


263

Utilizzare la raccolta di gruppi dell'oggetto Match, indicizzandola con il nome del gruppo di acquisizione, ad es

foreach (Match m in mc){
    MessageBox.Show(m.Groups["link"].Value);
}

10
Non usare var m, dal momento che sarebbe un object.
Thomas Weller,

111

È possibile specificare la stringa del gruppo di acquisizione denominata passandola all'indicizzatore della Groupsproprietà di un Matchoggetto risultante .

Ecco un piccolo esempio:

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

L'esempio di codice seguente corrisponderà al modello anche in caso di spazi tra i caratteri. cioè:

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

così come:

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

Il metodo restituisce vero o falso, a seconda che la stringa di input htmlTd corrisponda al modello o no. Se corrisponde, i parametri out contengono rispettivamente il collegamento e il nome.

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

Ho provato questo e funziona correttamente.


1
Grazie per avermi ricordato che le parentesi graffe possono accedere ai gruppi. Preferisco attenermi ${1}a rendere le cose ancora più semplici.
Magnus Smith,

Questo risponde completamente alla domanda, ma presenta alcuni problemi che sono troppo lunghi per essere spiegati qui, ma ho spiegato e corretto quelli nella mia risposta di seguito
Mariano Desanze,

1

Inoltre, se qualcuno ha un caso d'uso in cui ha bisogno di nomi di gruppo prima di eseguire la ricerca sull'oggetto Regex, può usare:

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

1

Questa risposta migliora sulla risposta di Rashmi Pandit , che è in qualche modo migliore delle altre perché sembra che risolva completamente il problema esatto dettagliato nella domanda.

La parte negativa è che è inefficiente e non utilizza costantemente l'opzione IgnoreCase.

La parte inefficiente è perché regex può essere costoso da costruire ed eseguire, e in quella risposta avrebbe potuto essere costruito solo una volta (la chiamata Regex.IsMatchstava solo costruendo di nuovo la regex dietro la scena). E il Matchmetodo avrebbe potuto essere chiamato solo una volta e archiviato in una variabile e quindi linke namedovrebbe chiamare Resultda quella variabile.

E l'opzione IgnoreCase è stata utilizzata solo nella Matchparte ma non nella Regex.IsMatchparte.

Ho anche spostato la definizione di Regex al di fuori del metodo per costruirla solo una volta (penso che sia l'approccio sensato se stiamo memorizzando l'assembly con l' RegexOptions.Compiledopzione).

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;
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.