Jak uzyskać dostęp do nazwanych grup przechwytywania w .NET Regex?

255

Trudno mi znaleźć dobry zasób, który wyjaśnia, jak używać nazwanych grup przechwytywania w języku C #. Oto kod, który mam do tej pory:

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

Jednak zawsze pokazuje to tylko pełną linię:

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

Eksperymentowałem z kilkoma innymi „metodami”, które znalazłem na różnych stronach internetowych, ale ciągle otrzymuję ten sam rezultat.

Jak mogę uzyskać dostęp do nazwanych grup przechwytywania, które są określone w moim wyrażeniu regularnym?

UnkwnTech
źródło
3
Odsyłanie wsteczne powinno mieć format (? <link>. *), A nie (? <link>. *?)
SO Użytkownik
11
FYI: Jeśli próbujesz zapisać nazwaną grupę przechwytywania w pliku xml, <>spowoduje to jej uszkodzenie. W (?'link'.*)takim przypadku możesz użyć . Nie do końca związane z tym pytaniem, ale wylądowałem tutaj podczas wyszukiwania w Google „.net nazwanych grup przechwytywania”, więc jestem pewien, że inni ludzie też…
rtpHarry
1
Link StackOverflow z ładnym przykładem: stackoverflow.com/a/1381163/463206 Ponadto, @rtpHarry, No the <>will go nie złamie. Udało mi się użyć myRegex.GetGroupNames()kolekcji jako nazw elementów XML.
radarbob

Odpowiedzi:

263

Użyj kolekcji grupowej obiektu Dopasuj, indeksując go nazwą grupy przechwytywania, np

foreach (Match m in mc){
    MessageBox.Show(m.Groups["link"].Value);
}
Paolo Tedesco
źródło
10
Nie używaj var m, ponieważ byłoby to object.
Thomas Weller
111

Określony ciąg grupy przechwytywania określa się, przekazując go do indeksu Groupswłaściwości Matchobiektu wynikowego .

Oto mały przykład:

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
źródło
10

Poniższy przykładowy kod będzie pasował do wzorca nawet w przypadku spacji pomiędzy nimi. tj .:

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

jak również:

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

Metoda zwraca true lub false, w zależności od tego, czy wejściowy ciąg htmlTd pasuje do wzorca, czy nie. Jeśli pasuje, parametry wyjściowe zawierają odpowiednio link i nazwę.

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

Przetestowałem to i działa poprawnie.

Użytkownik SO
źródło
1
Dzięki za przypomnienie, że nawiasy klamrowe mają dostęp do grup. Wolę trzymać się, ${1}aby wszystko było jeszcze prostsze.
Magnus Smith
To całkowicie odpowiada na pytanie, ale ma pewne problemy, które są zbyt długie, aby je tutaj wyjaśnić, ale wyjaśniłem i poprawiłem je w mojej odpowiedzi poniżej
Mariano Desanze
1

Dodatkowo, jeśli ktoś ma przypadek użycia, w którym potrzebuje nazw grup przed wykonaniem wyszukiwania na obiekcie Regex, może użyć:

var regex = new Regex(pattern); // initialized somewhere
// ...
var groupNames = regex.GetGroupNames();
tinamou
źródło
1

Te odpowiedzi poprawiają odpowiedź Rashmi Pandit , która jest w pewnym sensie lepsza niż reszta, ponieważ wydaje się, że całkowicie rozwiązuje dokładny problem opisany w pytaniu.

Złą stroną jest to, że jest nieefektywne i nie korzysta konsekwentnie z opcji IgnoreCase.

Nieefektywna część polega na tym, że wyrażenie regularne może być kosztowne w budowie i wykonaniu, a w tej odpowiedzi można go było zbudować tylko raz (wywołanie Regex.IsMatchpolegało na ponownym utworzeniu wyrażenia regularnego za sceną). I Matchmetoda mogła zostać wywołana tylko raz i zapisana w zmiennej, a następnie powinna linki namepowinna zostać wywołana Resultz tej zmiennej.

I opcja IgnoreCase była używana tylko w Matchczęści, ale nie w Regex.IsMatchczęści.

Przesunąłem również definicję Regex poza metodę, aby skonstruować ją tylko raz (myślę, że to rozsądne podejście, jeśli przechowujemy ten zestaw z RegexOptions.Compiledopcją).

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
źródło