Einen HTML-Link erkennen

(Auszug aus "Reguläre Ausdrücke" von Jeffrey E. F. Friedl)

Wir wollen aus einer HTML-Datei zu jedem Link jeweils die URL und den dazugehörigen Link-Text extrahieren, also die hier unterstrichenen Teile:

...<a href="http://www.oreilly.de">O'Reilly Verlag</a>...

Weil der Inhalt eines <A>-Tags sehr vielgestaltig sein kann, gehe ich das Problem in zwei Schritten an. Im ersten operiere ich die »Innereien« des <A> und den Link-Text heraus, im zweiten pflücke ich aus diesen <A>-Innereien die URL heraus.

In einem ersten Ansatz verwende ich eine Mustersuche ohne Rücksicht auf Groß- und Kleinschreibung mit dem Modus »Punkt passt auf alles« und einem nicht-gierigen Stern: ˹<a\b([^>]+)>(.*?)</a>˼. Die »Innereien« landen in $1 und der Link-Text in $2. Natürlich sollte ich statt ˹[^>]+˼ den im letzten Abschnitt aufgebauten Ausdruck benutzen; ich verwende hier die einfache Form nur, damit das Beispiel übersichtlich bleibt.

Wenn wir den Inhalt des <A>-Tags in einem String haben, können wir ihn mit einer weiteren Regex untersuchen. Die URL kommt darin im Attribut href=Wert vor. In HTML ist auf beiden Seiten des Gleichheitszeichens Whitespace erlaubt, und der Wert kann in Anführungszeichen stehen oder auch nicht, wie wir das im letzten Abschnitt gesehen haben. Das folgende Perl-Programm gibt die URLs und die Link-Texte aller Links in der Variablen $Html aus:

# Die Regex in der while-Bedingung ist zu einfach -- siehe Text
while ($Html =~ m{<a\b([^>]+)>(.*?)</a>}ig)
{
  my $Innereien = $1; # Resultate aus dem ersten Matching werden in separate ...
  my $LinkText  = $2; # ... Variablen abgespeichert.
  if ($Innereien =~ m{
                      \b HREF       # "href"-Attribut.
                      \s* = \s*     # Whitespace zu beiden Seiten des = erlaubt.
                      (?:           # Der Wert ist ...
                        "([^"]*)"   #   ein String in Anführungszeichen
                        |           #   oder ...
                        '([^']*)'   #   ein String in Hochkommas
                        |           #   oder ...
                        ([^'">\s]+) #   "anderes Zeug".
                      )             #
                     }xi)
  {
    my $Url = $+; # Der gefundene Klammerinhalt mit der höchsten Nummer von $1, $2 usw.
    print "URL $Url mit Link-Text: $LinkText\n";
  }
}

Dazu ein paar Anmerkungen:

  • Diesmal ist jede Alternative eingeklammert, weil wir den Wert daraus benötigen.
  • Wo wir den eingefangenen Text nicht brauchen – bei der Klammer, die die Alternation umschließt –, verwenden wir dagegen nicht-einfangende Klammern, das ist klarer und effizienter.
  • In der Alternative »anderes Zeug« ist diesmal außer Hochkomma, Anführungszeichen und ›>‹ auch kein Whitespace erlaubt, denn dieser trennt die »Attribut=Wert«-Paare.
  • Diesmal wird bei der »Anderes Zeug«-Klasse wirklich der Quantor ˹+˼ benutzt. Damit wird der gesamte Wert des href-Attributs erfasst. Handeln wir uns damit nicht die genannte »böse Überraschung« ein wie in der »Anderes Zeug«-Alternative aus HTML-Tags erkennen? Nein, denn diesmal gibt es keinen äußeren Quantor, der mit dem Quantor der Zeichenklasse in Konkurrenz steht. Auch dieses Beispiel werden wir unter Die Kunst, reguläre Ausdrücke zu schreiben wiederfinden.

Je nach Art des HTML-Texts wird die URL in $1, $2 oder $3 abgespeichert, die anderen sind leer oder undefiniert. Perl hat genau für diesen Zweck eine besondere Variable, $+, die den Wert enthält, der vom letzten Klammerpaar aufgefangen wurde, das tatsächlich Text abgekriegt hat. In diesem Fall ist das gerade die URL.

In Perl wird man $+ benutzen, in anderen Programmiersprachen gibt es andere Möglichkeiten. Man kann mit den normalen Mitteln der Programmiersprache die eingefangenen Textgruppen untersuchen und die letzte nehmen, die nicht leer ist. Falls benannte Klammerausdrücke (siehe Benannte Unterausdrücke) unterstützt werden, sind sie hier ideal geeignet, wie beim folgenden Programmstück in VB.NET. (Glücklicherweise unterstützen die .NET-Sprachen benannte Klammerausdrücke, denn das $+ funktioniert bei ihnen nicht richtig, siehe den Kasten Spezielle »Dollar-Sequenzen« im Ersatztext.)


Ein Link-Checker in VB.NET

Dieses Programm gibt die URLs und Link-Texte aus der Variablen Html aus:

Imports System.Text.RegularExpressions
   ' Reguläre Ausdrücke definieren, die später in der Schleife verwendet werden.
Dim A_Regex as Regex = New Regex("<a\b(?<Innerei>[^>]+)>(?<Link>.*?)</a>", _
                                 RegexOptions.IgnoreCase)

Dim InnereiRegex as Regex = New Regex( _
   "\b HREF                (?#  'href'-Attribut                       )" & _
   "\s* = \s*              (?#  '=' mit Whitespace                    )" & _
   "(?:                    (?#  Der Wert ist ...                      )" & _
   "  ""(?<url>[^""]*)""   (?#    ein String in Anführungszeichen     )" & _
   "  |                    (?#    oder ...                            )" & _
   "  '(?<url>[^']*)'      (?#    ein String in Hochkommas            )" & _
   "  |                    (?#    oder ...                            )" & _
   "  (?<url>[^'"">\s]+)   (?#    'anderes Zeug'.                     )" & _
   ")                      (?#                                        )",  _
   RegexOptions.IgnoreCase OR RegexOptions.IgnorePatternWhitespace)

' Die ›Html‹-Variable absuchen ...
Dim CheckA as Match = A_Regex.Match(Html)

' Für jeden gefundenen Treffer ...
While CheckA.Success
   ' <a>-Tag gefunden, jetzt extrahieren wir daraus die URL.
   Dim UrlCheck as Match = InnereiRegex.Match(CheckA.Groups("Innerei").Value)
   If UrlCheck.Success
      ' Treffer, also haben wir ein URL/Link-Paar.
      Console.WriteLine("Url " & UrlCheck.Groups("url").Value & _
                        " mit Link-Text: " & CheckA.Groups("Link").Value)

   End If
   CheckA = CheckA.NextMatch
End While

Dazu ein paar Bemerkungen:

  • In VB.NET muss die Regex-Bibliothek zuerst mit Imports eingebunden werden.
  • Ich benutze in der Regex Kommentare vom Typ ˹(?#...)˼, weil es ziemlich schwierig ist, in einen VB.NET-String ein Newline einzubauen. Normale Kommentare mit ›#‹ reichen entweder bis zum nächsten Newline oder bis ans Ende des Strings; damit würde der ganze Rest der Regex auskommentiert. Für normale Kommentare mit ˹#...˼ benötigte man ein &chr(10) am Ende jeder Zeile (siehe Regex-Optionen).
  • Jedes ›"‹ in der Regex erfordert ein ›""‹ im literalen String (siehe Strings in VB.NET).
  • In beiden regulären Ausdrücken werden benannte Unterausdrücke eingesetzt, das ergibt aussagekräftigere Namen als bloß Groups(1), Groups(2) usw.

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema Reguläre Ausdrücke bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:
   

Copyright der deutschen Ausgabe © 2008 by O’Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Reguläre Ausdrücke" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O’Reilly Verlag GmbH & Co. KG, Balthasarstr. 81, 50670 Köln