![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
||||||||
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|||||||
![]() |
|||||||
![]() |
|
||||||
![]() |
Obwohl meine neue Website http://perlmeister.com nur ein paar Seiten hat, zeigen sich schon typische Probleme: Jede Seite führt oben einen Navigations-Balken und unten eine Fußnote mit dem Hinweis, wohin man sich wenden kann, falls etwas nicht funktioniert. Ändert sich irgendetwas, ist der Teufel los: Soll nur ein neues Datum in die Fußzeile, muß man sämtliche Seiten editieren. Das treibt mich nicht nur zum Wahnsinn, sondern ist zudem auch sehr fehleranfällig. Hier kommt die Lösung: Da bestimmte HTML-Elemente auf vielen Seiten wiederkehren, liegt der Ansatz nahe, in den eigentlichen Seiten nur jeweils einen Tag (Täg!) im Format
<!-- include /german/foot.ger --> <!-- /include -->
abzulegen, den sich ein Spezialprogramm (bevor die Seiten "live" gehen) schnappt, die referenzierte Fußzeile, die angeblich in der Datei /german/foot.ger liegt, holt und sie, wie z.B. in
<!-- include /german/foot.ger --> Hier ist die Fußzeile! <!-- /include -->
zwischen die Tags preßt. Der Browser zeigt die <!-- include ... --> Spezial-Tags nicht an, da es in einen HTML-Kommentar verpackt ist. Ändert sich die Fußzeile ein weiteres Mal, wiederholt der Tag-Ersetzer einfach seine Tätigkeit -- schließlich ist die Pfad-Information trotz ersetzten Inhalts immer noch da.
include.production/ english/ foot.eng head.eng german/ foot.ger head.ger
Es gibt also Navigations-Balken (head) und Fußnoten (foot) für deutsche und englische Seiten. Steht in einer deutschen Seite im Verzeichnis HTML/index.html also
<!-- include /german/head.ger --> <!-- /include --> <H1>Hier ist der Seitentext</H1> <!-- include /german/foot.ger --> <!-- /include -->
stopft includer.pl mit dem Aufruf
includer.pl -i include.production HTML
die Navigationsbalken und Fußnoten in alle Seiten unterhalb des HTML-Verzeichnisses. Wandern die Seiten danach nicht auf den endgültigen Web-Server, sondern zunächst auf eine Testmaschine, sehen die Links im Navigationsbalken unter Umständen anders aus - kein Problem: Einfach ein zweites Include-Verzeichnis, beispielsweise include.test anlegen, die HTML-Stückchen darunter entsprechend modifizieren und
includer.pl -i include.test HTML
aufrufen, schon generiert includer.pl die Seiten für eine andere Konfiguration, denn die Dateien unterhalb von HTML referenzieren die HTML-Stückchen relativ zum include-Verzeichnis, so bezieht sich beispielsweise ein Tag, das /english/foot.eng enthält, auf include.test/english/foot.eng, falls die Option -i include.test des Includers gesetzt ist. Der Includer zeigt für jede Seite an, wieviele Ersetzungen er durchführen konnte:
HTML/index.html: 2 subs HTML/resume.html: 2 subs HTML/german/index.html: 2 subs HTML/german/perl/index.html: 2 subs HTML/german/perl/gotoperl/index.html: 2 subs
Vorsichtige Naturen starten den Includer zunächst mit der Option -r, die bewirkt, daß er zwar alle Dateien analysiert, bei eventuell nicht gefundenen Referenzen meckert, aber keine Ersetzungen durchführt. Der Includer arbeitet natürlich offline, entweder erzeugt man den HTML-Seiten-Baum auf einer anderen Maschine, um Ihn nach Vollendung auf den Webserver zu spielen, oder aber man installiert includer.pl und das include-Verzeichnis mit den HTML-Stückchen der Einfachheit halber auf dem Webserver selbst, in einem Verzeichnis oberhalb der Baumwurzel und läßt ihn nach jeder Änderung einmal durch die Original-Seiten rattern, die Ausfallzeit ist gering.
Listing includer.pl zieht in Zeile 6 das Getopt::Std-Modul, dessen Funktion getopts in Zeile 14 die Kommandozeilen-Parameter -r und -i setzt und, falls vorhanden, die Einträge in $opt{r} und $opt_i entsprechend setzt. Bei fehlender -i-Option nutzt includer.pl das Verzeichis include im gegenwärtigen Verzeichnis. Um aus einer absoluten Angabe wie mydir/include eine relative zu formen, springt das Skript in den Zeilen 20-24 einfach schnell ins fragliche Verzeichnis, ermittelt mit cwd() aus dem Cwd-Modul den relativen Namen und springt wieder zurück.
In den Zeilen 31 und 32 folgen dann zwei Aufrufe der find-Funktion aus dem File::Find-Modul. Erst bekommt scan_include die Dateinamen aus dem Include-Verzeichnis zu fressen, wobei laut File::Find-Konvention der angesprungene Callback immer im gerade abgearbeiteten Verzeichnis steht, man also einfach mit $_ auf die aktuell angesprungene Datei zugreifen kann. Ändert man absichtlich oder unabsichtlich den Wert von $_ besteht File::Find ärgerlicherweise darauf, daß $_ seinen Wert am Ende des Callbacks wieder zurück erhält, sonst kracht's. scan_include liest also die einzelnen Dateien unterhalb des Include-Verzeichnisses aus und speichert deren Inhalt als Strings unter dem Pfadnamen im Hash %INCLUDE_MAP ab.
Schickt sich dann Zeile 32 an, die zu korrigierenden HTML-Seiten abzuklappern, öffnet der Callback process_file jeweils die Datei, liest sie in einen String $lines ein, führt in einer gewaltigen Anweisung zum Suchen und Ersetzen die ganze Transformation durch, und überschreibt, falls nicht gerade das Read-Only Flag -r gesetzt ist, die jeweilige Datei mit dem neuen Inhalt.
Die Anweisung aus den Zeilen 71-77 ersetzt alles zwischen den beiden gesuchten Spezialtags durch den Rückgabewert der Funktion include_replace() - der Modifikator e für evaluate macht's möglich. Die anderen Modifikatoren der Substitutionsanweisung (die statt "/" das Zeichen "@" als Trenner benutzt) sind g, i, x und s die für globale Bearbeitung (alle vorkommenden Tags werden ersetzt), ignore case (Groß-/Kleinschreibung ignorieren), eXtended (erlaubt Kommentare und Leerzeichen zur besseren Strukturierung) und single line (.* paßt über mehrere Zeilen hinweg) stehen.
include_replace kriegt für jeden Treffer den Namen der aktuell bearbeiteten HTML-Datei und den Namen der gesuchten Include-Datei mit - und prüft mit dem Hash %INCLUDE_MAP, ob diese vorher gefunden wurde. Falls nicht, bricht das Programm mit einer Fehlermeldung ab, falls ja, liefert include_replace einfach den im Hash ge-cache-ten Inhalt der Include-Datei zurück, mit dem die Substitutions-Anweisung in process_file dann endlich den Tag ersetzt. So einfach und doch so kompliziert!
Zurück zum Alltag: Ändert sich nun ein Objekt, das in mehreren HTML-Seiten vertreten ist (z.B. Navigationsbalken), wird es einfach im Include-Verzeichnis einmal geändert und includer.pl aufgerufen - ratz-fatz erscheint die ganze Website in neuem Gewand. Die Webseiten selbst dürfen nach Herzenslust editiert werden, nur die Bereiche zwischen <!-- include ... --> und <!-- /include --> werden bekanntlich automatisch ersetzt.
Über die zahlreichen Zuschriften wegen meines September-Aufrufs zur Beifallsbekundung habe ich mich sehr gefreut, meine lieben Leser und Leserinnen, vielen Dank dafür! Deswegen lass' ich mich auch nicht lange bitten und mache weiter ... see ya in Perl land!
Listing: includer.pl |
1 #!/usr/bin/perl -w 2 ################################################## 3 # Syntax: includer [-i includedir] directory 4 ################################################## 5 6 use Getopt::Std; 7 use File::Find; 8 use Cwd; 9 use strict; 10 11 my (%INCLUDE_MAP, $INCLUDE_ROOT); # Globals 12 13 my %opt; 14 getopts('ri:', \%opt) || usage("Argument Error"); 15 16 print "READONLY MODE\n" if $opt{r}; 17 18 my $include_dir = $opt{i} || "include"; 19 20 my $now = cwd(); # Get absolute path 21 chdir($include_dir) || 22 usage("Cannot include from $include_dir"); 23 $INCLUDE_ROOT = cwd(); 24 chdir($now); 25 26 usage("No start directory given") if $#ARGV < 0; 27 28 usage("Start directory doesn't exist: $ARGV[0]") 29 unless -d $ARGV[0]; 30 31 File::Find::find(\&scan_include, $INCLUDE_ROOT); 32 File::Find::find(\&process_file, $ARGV[0]); 33 34 ################################################## 35 sub scan_include { # Scan include files 36 ################################################## 37 my $file = $_; # Save $_ 38 39 return unless -f $_; # No directories 40 41 open(FILE, "<$file") || 42 die "Cannot open $file (read)"; 43 44 # relative path name 45 (my $rel = $File::Find::name) =~ 46 s#^$INCLUDE_ROOT/*##g; 47 48 # read and store 49 my $data = join('', <FILE>); 50 chomp($data); 51 $INCLUDE_MAP{"/$rel"} = $data; 52 53 close(FILE); 54 55 $_ = $file; # reset $_ 56 } 57 58 ################################################## 59 sub process_file { 60 ################################################## 61 my $file = $_; 62 63 return if -d $file; 64 return unless $file =~ /\.html$/; 65 66 open(FILE, "<$file") || # Read file 67 die "Cannot open $file (read)"; 68 my $lines = join('', <FILE>); 69 close(FILE); 70 71 my $subs = ($lines =~ # Replace includes 72 s@<!-- \s* include # Intro tag 73 \s+ # Whitespace 74 ([^\s]+) # include file 75 .*?--> # end of tag 76 .*?<!--\s*/include\s*--> 77 @include_replace($file, $1)@gsexi); 78 # replace function 79 80 if($subs) { 81 print "$File::Find::name: $subs subs\n"; 82 83 if(!$opt{r}) { 84 open(FILE, ">$file") || 85 die "Cannot open $file (write)"; 86 print FILE $lines; 87 close(FILE); 88 } 89 } 90 91 $_ = $file; 92 } 93 94 ################################################## 95 sub include_replace { 96 ################################################## 97 my ($file, $tag) = @_; 98 99 # Check if tag defined 100 if(exists $INCLUDE_MAP{$tag}) { 101 # ... and return replacement 102 return "<!-- include $tag -->" . 103 "$INCLUDE_MAP{$tag}" . 104 "<!-- /include -->"; 105 } else { 106 die "Cannot resolve include '$tag' " . 107 "in file ", cwd(), "/$file"; 108 } 109 } 110 111 ################################################## 112 sub usage { 113 ################################################## 114 $0 =~ s#.*/##g; 115 print "$0: @_.\n"; 116 print "usage: $0 " . 117 "[-r] [-i includedir] directory\n" . 118 "-r: read only\n" . 119 "-i: include file directory\n"; 120 exit 1; 121 } |
Der Autor |
Michael Schilli arbeitet als Web-Engineer für AOL/Netscape in Mountain View, Kalifornien. Er ist Autor des 1998 bei Addison-Wesley erschienenen (und 1999 für den englischsprachigen Markt als "Perl Power" herausgekommenen) Buches "GoTo Perl 5" und unter michael@perlmeister.com oder http://perlmeister.com zu erreichen. |
Copyright © 1998 Linux-Magazin Verlag