Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
Regular Expressions
CoderboyPB
myCSharp.de - Member



Dabei seit:
Beiträge: 327
Herkunft: Paderborn

Themenstarter:

Regular Expressions

beantworten | zitieren | melden

Ich habe folgenden String (Ausschnitt)
<tr><td>1.</td><td>&#x1f1e7;&#x1f1f7; Brasilien</td><td>1840.77</td><td>&#x2194;&#xfe0f;</td></tr><tr><td>2.</td><td>&#x1f1e6;&#x1f1f7; Argentinien</td><td>1838.38</td><td>&#x1f51d;</td></tr><tr><td>3.</td><td>&#x1f1eb;&#x1f1f7; Frankreich</td><td>1823.39</td><td>&#x1f51d;</td></tr><tr><td>4.</td><td>&#x1f1e7;&#x1f1ea; Belgien</td><td>1781.3</td><td>&#x1f53b;</td></tr><tr><td>5.</td><td>&#x1f3f4;&#xe0067;&#xe0062;&#xe0065;&#xe006e;&#xe0067;&#xe007f; England</td><td>1774.19</td><td>&#x2194;&#xfe0f;</td></tr><tr><td>6.</td><td>&#x1f1f3;&#x1f1f1; Niederlande</td><td>1740.92</td><td>&#x1f51d;</td></tr><tr><td>7.</td><td>&#x1f ...

Das Ziel ist den String in <tr>...</tr> Chunks aufzuteilen, also für jede Tabellenzeile ein Match.
Mit

<tr>[\s\S]*<\/tr> 

aber wird der ganze Text als 1 Gesamtmatch gefunden.
Wie bekomme ich den Ausdruck weniger 'gierig', dass der nach dem ersten </tr> endet?
private Nachricht | Beiträge des Benutzers
dannoe
myCSharp.de - Member



Dabei seit:
Beiträge: 235

beantworten | zitieren | melden

Fragezeichen hinter das Sternchen:


<tr>[\s\S]*?<\/tr>

private Nachricht | Beiträge des Benutzers
CoderboyPB
myCSharp.de - Member



Dabei seit:
Beiträge: 327
Herkunft: Paderborn

Themenstarter:

beantworten | zitieren | melden

Danke, das funktioniert, aber was genau macht die Kombination

*? 
?
private Nachricht | Beiträge des Benutzers
dannoe
myCSharp.de - Member



Dabei seit:
Beiträge: 235

beantworten | zitieren | melden

Zitat von https://ahkde.github.io/docs/v1/misc/RegEx-QuickRef.htm
Greed (Gier): Standardmäßig versuchen die Quantoren *, ?, +, und {min,max}, so viele Zeichen wie möglich einzubeziehen, um eine Übereinstimmung zu finden. Um dieses Verhalten auf so wenig Zeichen wie möglich zu begrenzen, fügen Sie nach den Quantoren ein Fragezeichen an. Zum Beispiel bedeutet das Suchmuster <.+> (das kein Fragezeichen enthält): "Suche nach einem <, gefolgt von 1 oder mehr Zeichen, gefolgt von einem >". Um zu verhindern, dass das Suchmuster die komplette Zeichenkette <em>text</em> findet, fügen Sie nach dem Pluszeichen ein Fragezeichen an: <.+?>. Dies führt dazu, dass die Übereinstimmung bereits beim ersten '>' endet und dementsprechend nur der erste HTML-Tag <em> gefunden wird.
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Experte

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1.879
Herkunft: Düsseldorf

beantworten | zitieren | melden

Du könntest auch einfach nach "</tr>" splitten, dürfte bedeutend einfacher sein:


var text = "<tr><td>1.</td><td>&#x1f1e7;&#x1f1f7; Brasilien</td><td>1840.77</td><td>&#x2194;&#xfe0f;</td></tr><tr><td>2.</td><td>&#x1f1e6;&#x1f1f7; Argentinien</td><td>1838.38</td><td>&#x1f51d;</td></tr><tr><td>3.</td><td>&#x1f1eb;&#x1f1f7; Frankreich</td><td>1823.39</td><td>&#x1f51d;</td></tr><tr><td>4.</td><td>&#x1f1e7;&#x1f1ea; Belgien</td><td>1781.3</td><td>&#x1f53b;</td></tr><tr><td>5.</td><td>&#x1f3f4;&#xe0067;&#xe0062;&#xe0065;&#xe006e;&#xe0067;&#xe007f; England</td><td>1774.19</td><td>&#x2194;&#xfe0f;</td></tr><tr><td>6.</td><td>&#x1f1f3;&#x1f1f1; Niederlande</td><td>1740.92</td><td>&#x1f51d;</td></tr>";

foreach (var part in text.Split("</tr>", StringSplitOptions.RemoveEmptyEntries))
    Console.WriteLine(part + "</tr>");

Oder Du parst es als XML - HTML ist ja auch nix anderes als XML.
Damit hättest Du dann mit Abstand die meisten Möglichkeiten.
private Nachricht | Beiträge des Benutzers
CoderboyPB
myCSharp.de - Member



Dabei seit:
Beiträge: 327
Herkunft: Paderborn

Themenstarter:

beantworten | zitieren | melden

Ja, xml und xpath war eigentlich auch mein erster Ansatz, aber das html Dokument ließ sich nicht in ein XDocument laden, weshalb ich es mit einer Library versucht hatte, mit
HtmlAgilityPack
Aber anstatt dem gewünschten Knoten erhielt ich nur NULL zurück, dabei war der xpath richtig, denn der stammte aus den Browsertools zur Inspektion.

Nachdem auch andere Bibliotheken keinen Erfolg brachten, musste dann eben die Fußgänger Methode über Regex die Kartoffeln aussem Feuer holen.

@dannoe: Danke :-)
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Experte

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1.879
Herkunft: Düsseldorf

beantworten | zitieren | melden

Wäre es dann nicht besser, den Fehler mit XML zu suchen, anstatt auf Regex zurückzugreifen?
Weil mit XML oder XPath geht es ganz sicher - Du hast also irgendwas falsch gemacht.

Wenn ich das z.B. in Notepad++ werfe, ein root-Element drum (damit es ein Document ist), kann ich problemlos alle Werte abrufen:
Attachments
private Nachricht | Beiträge des Benutzers
CoderboyPB
myCSharp.de - Member



Dabei seit:
Beiträge: 327
Herkunft: Paderborn

Themenstarter:

beantworten | zitieren | melden

Diese Option hatte ich auch noch in Bedacht gezogen:

Mit Regex den Teilbaum mit den Daten extrahieren und dann versuchen mit xpath drauf zuzugreifen, da ich mich aber mit xpath nicht auskenne, hatte ich dann Abstand davon genommen, ehe ich in noch weitere Fehler geraten wäre.
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Experte

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1.879
Herkunft: Düsseldorf

beantworten | zitieren | melden

Glaub mir: XPath ist einfacher.

Und du kannst auch das XML auf eine Klasse mappen oder Du liest es Manuell Element für Element.

Was nun die bessere Option ist, kann ich nicht sagen, ohne zu wissen, wo die Reise hin gehen soll.
private Nachricht | Beiträge des Benutzers
CoderboyPB
myCSharp.de - Member



Dabei seit:
Beiträge: 327
Herkunft: Paderborn

Themenstarter:

beantworten | zitieren | melden

Im Grunde genommen, ist das hier nur ein Nebenprojekt, das eigentliche Ziel ist ne App, wo man zwei Nationalmannschaften auswählt, und dann berechnet wird, wie viel FIFA Weltranglisten Punkte es für beide Teams (oder auch Abzüge) bei jeweiligen Spielausgängen gibt.

Aber da die FIFA leider keine API bereitstellt, muss ich halt selber eine Routine schreiben, die diese Info aus der Webseite parst.
Da die Webseite der FIFA selber aber nicht geeignet ist, weil der Seitensourcecode erst im Browser gerendert werden muss, entnehme ich die Daten hier:
FIFA Weltrangliste - Die aktuelle Fußball Rangliste

Hab jetzt sogar ne funktionierende Lösung, wahrscheinlich nicht die beste, aber eine, die funktioniert:


using System.Text.RegularExpressions;

List<Team> teams = new ();

HttpClient httpClient = new();
httpClient.BaseAddress = new Uri("https://www.fussball-wm.pro/fifa-weltrangliste/");
var html = await httpClient.GetStringAsync(httpClient.BaseAddress);

var sub = Regex.Match(html, "<tbody>.*<\\/tbody>");
html = sub.Value;

var rows = Regex.Matches(html, "<tr>.*?<\\/tr>");

foreach (var row in rows)
{
    string? rowHTML = row.ToString();
    var cells = Regex.Matches(rowHTML, "<td>.*?<\\/td>");

    string name = extractName(cells[1].Value);
    double points = extractPoints(cells[2].Value);

    if (string.IsNullOrEmpty(name))
    {
        name = "USA";
    }

    teams.Add(new Team { Name = name, Points = points }); 
}

foreach(var team in teams)
{
    Console.WriteLine($"{team.Name} : {team.Points}");
}

string extractName(string value)
{
    string trash = Regex.Match(value, "<td>.*\\s").Value;
    string name = RemoveStart(value, trash);    
    return name;
}

double extractPoints(string input)
{
    input = input.Substring(4);
    input = input.Substring(0, input.Length - 5);
    return Double.Parse(input) / 100;
}

string RemoveStart(string value, string trash)
{
    int length = trash.Length;
    value = value.Substring(length);
    return value.Substring(0, value.Length-5);
}

Würde das für ne weitere Verwendung noch in eine Serviceklasse kapseln, aber hier ging es erst mal nur um die Funktionalität.

So, muss jetzt los zur Dialyse, bis später dann :-)
private Nachricht | Beiträge des Benutzers
CoderboyPB
myCSharp.de - Member



Dabei seit:
Beiträge: 327
Herkunft: Paderborn

Themenstarter:

beantworten | zitieren | melden

Ich Depp, hatte nach der Vorschau vergesen, auf absenden zu klicken.
Das war heute morgen um 6:20, natürlich habe ich die Dialyse jetzt auch hinter mir :-)
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Experte

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1.879
Herkunft: Düsseldorf

beantworten | zitieren | melden

Zitat
Da die Webseite der FIFA selber aber nicht geeignet ist, weil der Seitensourcecode erst im Browser gerendert werden muss
Kann ich so nicht bestätigen.
Ich kann deine URL einfach als curl-command absetzen und bekomme alles zurück - inklusive der Daten.

Aber wenn Du eine vollständige Website hast, kannst Du dir auch das HTML Agility Pack anschauen.
Das kann CSS-Selektoren, dabei hilft dir dann der Browser, um den richtigen Selector zu finden.
private Nachricht | Beiträge des Benutzers
CoderboyPB
myCSharp.de - Member



Dabei seit:
Beiträge: 327
Herkunft: Paderborn

Themenstarter:

beantworten | zitieren | melden

Wenn ich die FIFA Seite in den Browsertools anschaue bekomme ich den Dokumenten Tree, aber wenn ich einfach nur auf Rechtsklick > Quelltext klicke, erscheint ein a) komplett für Menschen schwer zu lesener Code, der die gesuchten Daten (die Weltranglistenpunkte) nicht mal enthält.

Aber das Agillity Pack werde ich auf jeden Fall noch mal testen, diesen Fehlschlag beim ersten Versuch will ich so nicht hinnehmen :-)
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Experte

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1.879
Herkunft: Düsseldorf

beantworten | zitieren | melden

Zitat
komplett für Menschen schwer zu lesener Code
Der ist nur unformatiert, ist immer noch gutes altes HTML

Zitat
der die gesuchten Daten (die Weltranglistenpunkte) nicht mal enthält
Doch - Zeile 220 (zumindest bei dir)
Fängt mit figure und hat als erstes Child eine table.
Da stehen (vermutlich - hab's nicht abgeglichen) alle Daten drin.
Leider keine id, damit wäre es leicht gewesen, die Daten zu finden.
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.594

beantworten | zitieren | melden

Es gibt auch eine Menge von "HTML pretty print" (online) Tools, die die HTML-Struktur so darstellen, so daß man die Verschachtelung besser überblicken kann.
private Nachricht | Beiträge des Benutzers
CoderboyPB
myCSharp.de - Member



Dabei seit:
Beiträge: 327
Herkunft: Paderborn

Themenstarter:

beantworten | zitieren | melden

Wow, das ging ja wirklich deutlich einfacher, nur bei San Marino musste ich den Spezialfall beheben.

So, jetzt muss ich aber noch mal schnell was einkaufen, ehe die Geschäfte bis Dienstag schließen.
Wünsche euch allen ein frohes Fest :-)


using HtmlAgilityPack;

var url = "https://www.fussball-wm.pro/fifa-weltrangliste/";

HtmlWeb web = new();
var htmlDoc = web.Load(url);

var nodes = htmlDoc.DocumentNode.SelectNodes(@"//figure[@class='wp-block-table']/table/tbody/tr");

foreach (var node in nodes)
{
    var country = node.ChildNodes[1].InnerText
        .Split(' ')[1];

    country = (country == "San" ? "San Marino" : country);

    var points = node.ChildNodes[2].InnerText;

    Console.WriteLine($"{country} : {points}");
}
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.594

beantworten | zitieren | melden

Du solltest statt Split(' ')[1] besser den gesamten Text nach dem 1. Leerzeichen nehmen (dann brauchst du auch keine Spezialbehandlung - trifft ja auf andere Ländernamen wie "Costa Rica" oder "Burkina Faso" auch zu und ist m.E. sogar ein Fall von Coding Styles Horror).
private Nachricht | Beiträge des Benutzers
CoderboyPB
myCSharp.de - Member



Dabei seit:
Beiträge: 327
Herkunft: Paderborn

Themenstarter:

beantworten | zitieren | melden

Ich war selber noch am überlegen, sind das jetzt alle? Saudi Arabia, New Zealand? Aber ne, die werden im Deutschen ja als Ein Wort geschrieben.
Aber das ich nicht auf Costa Rica gekommen bin, obwohl wir vor 3 Wochen noch gegen die gespielt haben, kehr, was bin ich dämlich.

Naja dadurch jetzt heute mit dem Anzahl Parameter in der Split Funktion wieder was Neues gelernt. Ohne Witz Jahre lang die Funktion genutzt, aber dieser Parameter war mir nie aufgefallen.

Habe dadurch noch einen Fehler entdeckt: In Bosnien & Herzegowina stand das & in HTML Specialchars, die noch decodiert werden müssen.
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.594

beantworten | zitieren | melden

An die überladene Methode String.Split(Char, Int32) hatte ich dabei gar nicht gedacht, sondern einfach an IndexOf und Substring, aber schön, daß du selber diese elegante Methode gefunden hast.

Schöne Weihnachten!
private Nachricht | Beiträge des Benutzers
CoderboyPB
myCSharp.de - Member



Dabei seit:
Beiträge: 327
Herkunft: Paderborn

Themenstarter:

beantworten | zitieren | melden

Noch mal der Vollständigkeit halber:


using HtmlAgilityPack;
using System.Net;

var url = "https://www.fussball-wm.pro/fifa-weltrangliste/";

HtmlWeb web = new();
var htmlDoc = web.Load(url);

var nodes = htmlDoc.DocumentNode.SelectNodes(@"//figure[@class='wp-block-table']/table/tbody/tr");

foreach (var node in nodes)
{
    var country = node.ChildNodes[1].InnerText
        .Split(' ', 2)[1];

    country = WebUtility.HtmlDecode(country);

    var points = node.ChildNodes[2].InnerText;

    Console.WriteLine($"{country} : {points}");
}
private Nachricht | Beiträge des Benutzers