HTML Dokumente mit Perl verwalten, HTML::TagReader

ArticleCategory: [Choose a category, do not translate this]

Webdesign

AuthorImage:[Here we need a little image from you]

[Photo of the Author]

TranslationInfo:[Author + translation history. mailto: or http://homepage]

original in en Guido Socher

en to de Guido Socher

AboutTheAuthor:[A small biography about the author]

Guido liebt Perl, weil es eine flexible und schnelle Script-Sprache ist. Das Motto von Perl, "Es gibt mehr als nur einen Weg", reflektiert die Freiheit und die M�glichkeiten, die man hat, wenn man Open Source Software benutzt.

Abstract:[Here you write a little summary]

Jemand, der eine Webseite mit mehr als 10 HTML Seiten verwalten will, wird schnell merken, dass man einige Programme zur Unterst�tzung dieser Aufgabe braucht.
Aus historischen Gr�nden bietet die meiste Software Funktionen, um Dateien Zeile f�r Zeile oder Zeichen f�r Zeichen zu lesen. In SGML/XML/HTML Dateien haben Zeilen leider keine Bedeutung. SGML/XML/HTML Dateien sind Tag-basiert (Sprich "T�g", nicht zu verwechseln mit Tag oder Nacht). HTML::TagReader ist ein effizientes Modul, um eine Datei Tag f�r Tag zu lesen.

Dieser Artikel nimmt an, dass du Perl schon sehr gut beherrscht. Du kannst z.B. meine Perl Tutorien, (Januar 2000, LinuxFocus) lesen um Perl zu lernen.

ArticleIllustration:[This is the title picture for your article]

[Illustration]

ArticleBody:[The article body]

Einf�hrung

Historisch gesehen sind fast alle Dateien Zeilen basiert. Beispiele sind die Unix Konfigurationsdateien wie /etc/hosts, /etc/passwd .... Es gibt sogar noch Betriebssysteme, in denen das Betriebssystem selbst Funktionen hat, um Daten zeilenweise zu lesen. SGML/XML/HTML Dateien basieren auf Tags. Zeilen haben hier keine Bedeutung. Texteditoren und Menschen funktionieren aber irgendwie immer noch zeilenweise.

Speziell gr��ere HTML Dateien werden im allgemeinen aus mehreren Zeilen HTML-Code bestehen. Es gibt sogar Programme wie z.B. "Tidy", die HTML einr�cken und in Zeilen umbrechen, damit der HTML-Code lesbar wird. Wir benutzen Zeilen, obwohl HTML auf Tags basiert und nicht auf Zeilen. Man kann das mit C-Code vergleichen. Theoretisch k�nnte man den gesamten C-Code in eine einzige Zeile schreiben. Niemand macht das. Es w�re unlesbar.
Man erwartet daher, dass ein HTML-Syntaxchecker schreibt "Fehler in Zeile ..." und nicht "Fehler nach Tag 4123". Das ist so, weil man mit einem Texteditor ganz einfach zu einer bestimmten Zeile gehen kann.

Was man also braucht, ist ein einfaches Verfahren um eine HTML-Datei Tag f�r Tag zu lesen und gleichzeitig mitzuhalten, in welcher Zeile man sich befindet.

Eine m�gliche L�sung

Die �bliche Methode, eine Datei in Perl zu lesen, ist, den while(<FILEHANDLE>) Operator zu benutzen. Das wird die Datei zeilenweise lesen und jede Zeile an $_ �bergeben. Warum macht Perl das? Perl hat eine interne Variable namens INPUT_RECORD_SEPARATOR ($RS oder $/), in der definiert ist, dass "\n" das Ende einer Zeile ist. Wenn man $/=">" setzt, dann wird Perl ">" als "Zeilenende" benutzen. Der folgende Befehl wird HTML Text umformatieren, so dass er immer in ">" endet:

perl -ne 'sub BEGIN{$/=">";} s/\s+/ /g; print "$_\n";' file.html

Eine HTML Datei, die so aussieht

<html><p>some text here</p></html>
wird zu:
<html>
<p>
some text here</p>
</html>
Wichtig ist hier nicht die Lesbarkeit! F�r den Softwareentwickler ist es wichtig, dass die Daten Tag f�r Tag an die Funktionen �bergeben werden. Damit wird es z.B. einfach, nach "<a href= ..." zu suchen, selbst wenn der Original HTML-Code "a" und "href" auf zwei verschiedenen Zeilen hatte.

Das Ver�ndern von "$/" (INPUT_RECORD_SEPARATOR) verursacht keinen zus�tzlichen Verarbeitungsaufwand und ist sehr schnell. Es ist auch m�glich, den Match-Operator und Regular Expressions als Iterator zu benutzen. Das ist etwas komplizierter und langsamer, wird aber auch gerne benutzt.

Wo ist das Problem?? Im Titel dieses Artikels steht HTML::TagReader aber jetzt haben wir die ganze Zeit �ber eine viel einfachere L�sung gesprochen, die keine zus�tzlichen Module erfordert. Irgendetwas muss faul an der bisherigen L�sung sein: Mit anderen Worten es ist nur in Spezialf�llen m�glich "$/" (INPUT_RECORD_SEPARATOR) zu benutzen.

Trotzdem habe ich hier ein n�tzliches Beispielprogramm, das genau das benutzt, was wir bisher besprochen haben. Es setzt jedoch "$/" auf "<" weil die meisten Browser ein fehlplaziertes "<" nicht so gut handhaben k�nne wie ein ">". Deshalb gibt es viel weniger Seiten, in denen ein "<" an der falschen Stelle sitzt. Das Programm nennt sich tr_tagcontentgrep (Klick zum Ansehen). In dem Code kann man auch sehen, wie man die Zeilennummer beim Lesen der Datei mithalten kann. tr_tagcontentgrep kann man benutzen, um nach einem String innerhalb eines Tags zu "grep-en". Z.B. nach "img". Das funktioniert dann auch, wenn der Tag sich �ber viele Zeilen erstreckt. So etwas wie:

tr_tagcontentgrep -l img file.html
index.html:53: <IMG src="../images/transpix.gif" alt="">
index.html:257: <IMG SRC="../Logo.gif" width=128 height=53>

HTML::TagReader

HTML::TagReader l�st die Probleme mit dem �berschreiben des INPUT_RECORD_SEPARATOR und bietet eine viel sch�nere Schnittstelle, um Tags von Text zu unterscheiden. Es ist nicht so schwergewichtig wie ein vollst�ndiger HTML::Parser und bietet genau das, was man zum Verarbeiten von HTML-Code braucht: Eine Methode, um Tag f�r Tag zu lesen.

Genug Worte. So benutzt man es. Zuerst schreibt man
use HTML::TagReader;
, um das Modul zu laden. Danach ruft man
my $p=new HTML::TagReader "filename";
auf, um die Datei "filename" zu �ffnen und erh�lt eine Objektreferenz zur�ck in $p. Nun kann man $p->gettag(0) oder $p->getbytoken(0) aufrufen, um den n�chsten Tag zu lesen. gettag gibt nur Tags zur�ck (Das Zeug zwischen < und >). getbytoken hingegen gibt uns auch den Text zwischen den Tags und sagt uns, ob das jetzt ein Tag ist oder Text. Mit diesen Funktionen ist es ganz einfach, HTML Dateien zu lesen. Das ist essenziell, um eine gr��ere Website zu unterhalten. Eine vollst�ndige Beschreibung der Syntax findet sich in der man-Page von HTML::TagReader.

Hier ist ein echtes Beispielprogramm. Es gibt den Titel von HTML Dokumenten aus:
#!/usr/bin/perl -w
use strict;
use HTML::TagReader;
#
die "USAGE: htmltitle file.html [file2.html...]\n" unless($ARGV[0]);
my $printnow=0;
my ($tagOrText,$tagtype,$linenumber,$column);
#
for my $file (@ARGV){
  my $p=new HTML::TagReader "$file";
  # read the file with getbytoken:
  while(($tagOrText,$tagtype,$linenumber,$column) = $p->getbytoken(0)){
  if ($tagtype eq "title"){
    $printnow=1;
    print "${file}:${linenumber}:${column}: ";
    next;
  }
  next unless($printnow);
  if ($tagtype eq "/title" || $tagtype eq "/head" ){
    $printnow=0;
    print "\n";
    next;
  }
  $tagOrText=~s/\s+/ /; #kill newline, double space and tabs
  print $tagOrText;
  }
}
# vim: set sw=4 ts=4 si et:
Wie es funktioniert? Wir lesen die HTML Dateien mit $p->getbytoken(0) und wenn wir ein <title> oder <Title> oder <TITLE> (sie werden alle als $tagtype eq "title" erkannt) haben, dann setzen wir ein Flag ($printnow), um mit der Ausgabe anzufangen. Wenn wir </title> finden, h�ren wir mit dem Drucken auf.
Man benutzt das Programm so:

htmltitle file.html somedir/index.html
file.html:4: the cool perl page
somedir/index.html:9: joe's homepage

Nat�rlich ist es m�glich, den tr_tagcontentgrep von oben mit HTML::TagReader zu implementieren. Es ist ein bisschen k�rzer und einfacher zu schreiben:

#!/usr/bin/perl -w
use HTML::TagReader;
die "USAGE: taggrep.pl searchexpr file.html\n" unless ($ARGV[1]);
my $expression = shift;
my @tag;
for my $file (@ARGV){
  my $p=new HTML::TagReader "$file";
  while(@tag = $p->gettag(0)){
    # $tag[0] is the tag (e.g <a href=...>)
    # $tag[1]=linenumber $tag[2]=column
    if ($tag[0]=~/$expression/io){
      print "$file:$tag[1]:$tag[2]: $tag[0]\n";
    }
  }
}
Das Script ist sehr kurz und hat nicht viel Fehlerbehandlung, aber ansonsten ist es voll funktionsf�hig. Um nach Tags zu suchen, die den String (regexp) "gif" enthalten, benutzt man:

taggrep.pl gif file.html
file.html:135:15: <img src="images/2doc.gif" width=34 height=22>
file.html:140:1: <img src="images/tst.gif" height="164" width="173">

Noch ein Beispiel? Hier ist ein Programm, das all die <font...> und </font> Tags aus HTML Seiten entfernt. Diese font Tags werden manchmal in Unmengen von schlecht entwickelten grafischen HTML Editoren benutzt und verursachen Probleme, wenn man die Seiten mit unterschiedlichen Webbrowsern anschaut. Dieses einfache Script entfernt alle font Tags. Man kann es �ndern, so dass es nur die entfernt, die fontface oder size setzen und color unver�ndert lassen.
#!/usr/bin/perl -w
use strict;
use HTML::TagReader;
# strip all font tags from html code but leave the rest of the
# code un-changed.
die "USAGE: delfont file.html > newfile.html\n" unless ($ARGV[0]);
my $file = $ARGV[0];
my ($tagOrText,$tagtype,$linenumber,$column);
#
my $p=new HTML::TagReader "$file";
# read the file with getbytoken:
while(($tagOrText,$tagtype,$linenumber,$column) = $p->getbytoken(0)){
  if ($tagtype eq "font" || $tagtype eq "/font"){
    print STDERR "${file}:${linenumber}:${column}: deleting $tagtype\n";
    next;
  }
  print $tagOrText;
}
# vim: set sw=4 ts=4 si et:
Wie du sehen kannst, ist es sehr einfach, ein n�tzliches Programm mit nur wenigen Zeilen zu schreiben.
Der Quellcode von HTML::TagReader (siehe Referenzen) enth�lt einige Applikationen von HTML::TagReader: tr_xlnk und tr_staticssi sind sehr n�tzlich, wenn man eine CD-ROM von einer Webseite machen m�chte. Ein Webserver liefert z.B. http://www.linuxfocus.org/index.html, selbst wenn man nur http://www.linuxfocus.org/ (ohne index.html) getippt hat. Wenn man jedoch einfach alle Verzeichnisse und Dateien auf CD brennt und mit dem Browser dann nach (file:/mnt/cdrom/) geht, dann sieht man ein Verzeichnis und das passiert nicht nur beim ersten Mal, sondern jedes Mal, wenn man auf einen Link klickt, der nicht auf eine HTML Datei zeigt. Die Firma, die die ersten LinuxFocus CDs gemacht hat, hatte diesen Fehler begangen und es war furchtbar, die CD zu benutzen. Nun erhalten sie die Daten expandiert mit tr_xlnk und die CD funktioniert super.

Ich bin sicher, dir wird HTML::TagReader gefallen. Frohes Programmieren!

Referenzen