Textdateien verarbeiten für Einsteiger
Gelesen bei AboutWebDesign.de
URL: http://www.aboutwebdesign.de/awd/content/1038575412.shtml
Textdateien sind auf den ersten Blick eine ziemlich armselige Technologie. Im Vergleich mit Datenbanksystemen können sie nicht mithalten. Und dennoch gibt es Situationen, in denen Sie Textdateien verarbeiten können wollen. So z.B.:
- Ihr Kunde gibt Ihnen eine Textdatei mit dem Kommentar "Bau das in die Website ein". Leider jedoch kann die Datenbank die Datei nicht direkt einlesen.
- Sie wollen ein simples Tool programmieren und wollen dafür nicht extra eine Datenbank anlegen.
- Ihnen steht keine Datenbank zur Verfügung.
Was ist "Verarbeitung"?
Verarbeitung, auch als "Parsen" bezeichnet, heißt zunächst einmal, dass eine Textdatei irgendwie auf eine in Ihrem Programm vorhandene Struktur abgebildet wird. Konkret: Ihr Programm liest die Datei, erkennt bestimmte Stellen anhand bestimmter Charakteristika und macht etwas mit diesen Stellen.
So könnte ein Programm z.B. alle HTML-Tags aus einer Textdatei entfernen. Der Arbeitsablauf wäre:
- Datei in temporären Speicher einlesen
- HTML-Tags erkennen
- HTML-Tags entfernen
- Datei neu schreiben
An dieser Stelle schreien erfahrene Programmierer vermutlich laut auf: es ist nicht gerade die effizienteste Methode, eine Datei komplett einzulesen, um sie dann komplett neu zu schreiben. In diesem Artikel wollen wir jedoch keinen allzu großen Wert auf Effizienz legen. Es geht vielmehr um grundsätzliche Arbeitstechniken, speziell, um den zweiten Schritt, die Erkennung von Strukturen, zu bewerkstelligen.
Für unsere Beispiele haben wir die Programmiersprache Perl gewählt. Die zugrundeliegenden Prinzipien lassen sich aber ohne weiteres auf jede andere moderne Sprache übertragen.
Vorüberlegungen
Zunächst: sind Sie in der glücklichen Situation, die Art und Weise, in der Sie Ihre Dateien organisieren wollen, selbst festzulegen?
Wenn ja: wir empfehlen, alle Datendateien in einen Ordner zu legen. Alle diese Dateien sollten die gleiche Endung haben, um später schnell von anderen Dateien unterscheidbar zu sein.
Schlechte Daten...
Zur Struktur der Daten selbst: Sie müssen eine Möglichkeit finden, die Daten später möglichst einfach zu verarbeiten. Sie könnten z.B. festlegen, dass die Daten eine andere Bedeutung haben, je nachdem, in welcher Zeile sie stehen:
Vorname
Nachname
Straße
Vorname
Nachname
Straße
Vorname
...
Um eine bessere optische Trennung zu haben, würde sich eine Leerzeile zwischen zwei Adress-Datensätzen anbieten.
Doch optimal sind solche Lösungen nicht. Sie sind unflexibel. Wenn Sie jetzt noch eine Telefonnummer speichern wollen, müssen Sie wahrscheinlich weite Teile Ihres Scripts umbauen.
Gute Daten...
Besser geeignet dagegen ist XML - oder zumindest etwas XML-artiges. Wenn Sie unser Tutorial (dazu können Sie dem voranstehenden Link folgen :) gelesen haben, wissen Sie, dass XML eine Menge Formalismen mitbringt, die für eine einfache Anwendung nicht unbedingt nötig sind, so z.B. die Angabe der XML-Version in der ersten Zeile oder die Maskierung von Sonderzeichen.
Wenn Sie darauf verzichten, halten Sie Ihr Verarbeitungs-Programm zunächst einmal kleiner.
Wenn Sie jedoch wohlgeformtes/gültiges XML einsetzen, können Sie die Daten später auch mit anderen Programmen weiterverarbeiten.
Diese Entscheidung können wir Ihnen also nicht abnehmen. Bedenken Sie dazu, was Sie später noch alles mit den Daten anstellen wollen.
Ein Beispiel für so ein Pseudo-XML:
<kunde>
<vorname>Arnold</vorname>
<nachname>Schwarenbär</nachname>
<adresse>Fitnessclub am Silbersee</adresse>
</kunde>
<kunde>
<vorname>Heinz</vorname>
<nachname>Müller</nachname>
<adresse>Musterstr. 21</adresse>
</kunde>
Dateien finden
Nun steigen wir in die eigentliche Datenverarbeitung. Daher: mit welchen Dateien müssen wir uns beschäftigen?
Wenn alle in einem Verzeichnis liegen und die gleiche Endung haben, ist das einfach:
chdir 'C:/temp/parser' || die "Kann nicht ins Verzeichnis wechseln";
my @files = (<*.txt>);
Ansonsten müssen Sie die Dateien halt per Hand angeben:
my @files=('C:/temp/abc.dat', 'C:/abc/dhdh.dat')
Dateiinhalte verarbeiten
Gute Daten...
Öffnen Sie die Dateien und tun Sie etwas mit den Inhalten. Z.B. ein Beispiel, bezogen auf unser Pseudo-XML von eben:
chdir 'C:/temp/parser' || die "Kann nicht ins Verzeichnis wechseln";
my @files = (<*.txt>);
foreach my $file (@files)
{
open(DATEI, '<'.$file) || die "Kann $file nicht öffnen";
my $inhalt = join('', <DATEI>);
close(DATEI) || die "Kann $file nicht schließen";
my $zaehler = 0;
while ($inhalt =~ s/<kunde>(.+?)<\/kunde>//s)
{
$zaehler++;
my $kunde = $1;
my ($vorname, $nachname, $adresse);
if ($kunde =~ s/<vorname>(.+?)<\/vorname>//s)
{
$vorname = $1;
}
else
{
die "Kein Vorname in Datei $file bei Datensatz $zaehler!";
}
if ($kunde =~ s/<nachname>(.+?)<\/nachname>//s)
{
$nachname = $1;
}
else
{
die "Kein Nachname in Datei $file bei Datensatz $zaehler!";
}
if ($kunde =~ s/<adresse>(.+?)<\/adresse>//s)
{
$adresse = $1;
}
else
{
die "Keine Adresse in Datei $file bei Datensatz $zaehler!";
}
print qq(
Kunde #$zaehler
Name: $vorname $nachname
Adresse: $adresse
);
}
}
Wichtig dabei: überprüfen, überprüfen und nochmals überprüfen! Checken Sie immer, ob Sie auch die richtigen Daten in der richtigen Form erhalten. Sonst sind Sie später auf endloser Fehlersuche, weil sich ein Mitarbeiter im Lager bei einem Produkt-Code vertippt hat.
Schlechte Daten...
Nicht immer können Sie sich aussuchen, welche Daten Sie verarbeiten müssen - gegessen wird, was auf den Tisch kommt! Deshalb wollen wir Ihnen auch zeigen, wie man z.B. eine solche Datei wie die oben als "schlechtes Beispiel" angeführte verarbeitet. Hier die Datendatei:
Arnold
Nachnamix
Am Fitnesswald 1
Hein
Müller
Gretchenstr. 10
Max
Mustermann
Normalstraße 90
Und hier der passende Algorithmus:
chdir 'C:/temp/parser' || die "Kann nicht ins Verzeichnis wechseln";
my @files = (<*.txt>);
foreach my $file (@files)
{
open(DATEI, '<'.$file) || die "Kann $file nicht öffnen";
my %kunde;
my $zeilencounter = 0;
while (<DATEI>)
{
# \n entfernen
chomp;
# Zeilen zählen
$zeilencounter++;
if ($zeilencounter == 1)
{
$kunde{vorname} = $_;
}
elsif ($zeilencounter == 2)
{
$kunde{nachname} = $_;
}
elsif ($zeilencounter == 3)
{
$kunde{adresse} = $_;
# Ein Kunde ist nun vollständig
# eingelesen - Ausgabe!
print qq(
Name: $kunde{vorname} $kunde{nachname}
Adresse: $kunde{adresse}
);
# Variablen zurücksetzen
%kunde = ();
$zeilencounter = 0;
}
}
close(DATEI) || die "Kann $file nicht schließen";
}
Andere Daten...
Unsere Beispiele sind insofern ein wenig unrealistisch, als dass sie die Kunden-Daten immer nur direkt ausgeben. Besser könnte es sein, sie in eine komplexe Datenstruktur zu verpacken, dann können wir später flexibler mit ihnen umgehen. Das demonstrieren wir im nächsten Beispiel. Wenn Sie den Code nicht verstehen, sollten Sie sich perlreftut in der Perl-Dokumentation durchlesen. Zusätzlich dazu verwenden wir hier nun auch ein anderes Datenformat: ein Datensatz pro Zeile, Datenfelder mit Kommata getrennt.
chdir 'C:/temp/parser' || die "Kann nicht ins Verzeichnis wechseln";
my @files = (<*.txt>);
my @kunden;
foreach my $file (@files)
{
open(DATEI, '<'.$file) || die "Kann $file nicht öffnen";
my $zeilencounter = 0;
while (<DATEI>)
{
# \n entfernen
chomp;
my ($vorname, $nachname, $adresse, $telefon) = split(/,/, $_);
# Diverse Tests...
# sind die Felder alle angegeben?
unless($vorname)
{
die "Vorname angeben: Datei $file Zeile $.";
}
unless($nachname)
{
die "Nachname angeben: Datei $file Zeile $.";
}
unless($adresse)
{
die "Adresse angeben: Datei $file Zeile $.";
}
unless($telefon)
{
die "Telefon angeben: Datei $file Zeile $.";
}
# Besteht die Telefonnummer nur aus Zahlen,
# Leerzeichen und Schrägstrichen (Trennung
# Vorwahl / eigentliche Nummer)
unless ($telefon =~ /^[\d \/]+$/)
{
die "Telefon in Datei $file Zeile $. enthält ungültige Zeichen";
}
# Hash aufbauen
my %kunde;
$kunde{vname} = $vorname;
$kunde{nname} = $nachname;
$kunde{adresse} = $adresse;
$kunde{telefon} = $telefon;
# Hash_Referenz in Kunden-Array packen
push(@kunden, \%kunde);
}
close(DATEI) || die "Kann $file nicht schließen";
}
# Jetzt: eine schöne HTML-Tabelle aus den Daten erzeugen
my $html;
$html = qq(
<html>
<body>
<h1>Die Kundendatenbank:</h1>
<table>
<tr>
<th>Vorname</th>
<th>Nachname</th>
<th>Adresse</th>
<th>Telefonnummer</th>
</tr>);
foreach my $kunde (@kunden)
{
$html .= qq(
<tr>
<td>$kunde->{vname}</td>
<td>$kunde->{nname}</td>
<td>$kunde->{adresse}</td>
<td>$kunde->{telefon}</td>
</tr>
);
}
$html .= qq(
</table>
</body>
</html>
);
my $ausgabedatei = "output.html";
open (DATEI, '>'.$ausgabedatei) || die "Kann $ausgabedatei nicht zum Schreiben öffnen";
print DATEI $html;
close (DATEI);
Was ist hier interessant?
- Die Hashes, die per Referenz in Arrays gespeichert werden. Eine wichtige Technik, die Sie beherrschen sollten.
- Die Tests. Denken Sie daran, so viel wie möglich abzuprüfen. Bei der Telefonnummer wird hier z.B. ein regulärer Ausdruck verwendet:
^[\d \/]+$.
Zur Erklärung: ^ steht für den Anfang einer Zeichenkette, $ für das Ende. Eckige Klammern leiten eine Zeichenklasse ein ( = verschiedene Optionen). Das Plus-Zeichen dahinter bedeutet, dass alle in der Klasse enthaltenen Zeichen beliebig oft vorkommen dürfen, aber eines davon mindestens einmal vorkommen muss. Die Start- und Ende-Zeichen sorgen in Kombination damit dafür, dass die gesamte Zeichenkette nur aus Zeichen bestehen darf, die in der Zeichenklasse enthalten sind.
Zur Klasse selbst: \d steht für Zahlen, das Leerzeichen für sich selbst und \/ für den Slash. / allein würde nicht funktionieren, da / gleichzeitig auch den regulären Ausdruck begrenzt.
- Zum Schluss wird schließlich noch eine HTML-Datei ausgegeben. Sie lesen also Daten ein, um daraus eine neue Datei zu erzeugen. Eine sehr gebräuchliche Methode.
Ausblick
Sie können nun die Strukturen einfacher Dateien verarbeiten. Wenn Sie tiefer einsteigen wollen, sollten Sie sich intensiver mit regulären Ausdrücken beschäftigen. In der Perl-Dokumentation finden Sie unter dem Begriff perlrequick mehr Informationen dazu.
Außerdem gibt es natürlich noch die Möglichkeit, eine echte Datenbank aus Textdateien zu erstellen, d.h. dass die Dateien auch selbst modifiziert werden. In diesen Fällen lohnt es sich jedoch fast immer, auf eine echte Datenbank zu setzen. Wenn Sie dies dennoch nicht tun wollen, vergessen Sie nicht, die zu bearbeitenden Dateien mit flock() zu sperren!