1 #----------------------------------------------------------------------
3 #----------------------------------------------------------------------
7 UVtemplate - Templateverarbeitung und String-Formatierungen
13 $plate = UVtemplate->new([%keys]);
15 $plate->setKey(%keys);
16 $item = $plate->addListItem($name, %keys);
18 $string = $plate->processTemplate($file);
22 Mit Hilfe von UVtemplate, wird die komplette Aufbereitung und
23 Formatierung der Programmausgaben nicht nur ausgelagert sondern
24 auch so flexibiliert, dass sie jederzeit angepasst werden kann,
25 ohne das im eigentlichen Programmcode veraendert werden muss.
27 Auf Programmseite wird eine Datenstruktur mit Schluessel-Wert
28 Paaren erzeugt. In den Template-Dateien werden dann spaeter die
29 jeweiligen Schluessel, durch ihre im Programm festgelegten
30 Werte ersetzt. Zusaetzlich ist es moeglich Schluessel zu Listen
33 Da es sich bei den Templates um Ascii-Texte handelt, gibt es
34 zusaetzlich die Moeglichkeit die Werte der Schluessel zuformatieren
35 um eine einheitliche Ausgabe zu ermoeglichen. D.h. es kann z.B. durch
36 das Anhaengen von Leerzeichen dafuer gesorgt werden, das ein Schluessel
37 einer Liste immer 60 Zeichen lang ist um ansehnliche Tabellen auszugeben.
45 #----------------------------------------------------------------------
48 use vars qw( $VERSION $functions @dirs);
53 #----------------------------------------------------------------------
57 Eine neues Objekt vom Typ UVtemplate anlegen.
59 my $plate = UVtemplate->new();
61 Als Parameter koennen gleich beliebig viele Schluessel-Wert-Paare
71 $self->{FATHER} = $class;
72 bless($self, ref($class));
85 Schluessel und zugehoerige Werte im Objekt speichern.
87 $plate->setKey( vote-addr => 'to-vote@dom.ain' );
88 $plate->setKey( datenschutz => 1);
90 Ist der zu speichernde Schluessel bereits vorhanden, wird er
91 durch den neuen Wert ueberschrieben.
99 foreach my $key (keys(%param)){
100 $self->{KEYS}->{$key} = $param{$key};
106 Erzeugt ein neues Objekt vom Typ UVtemplate und fuegt es der
107 angebenen Liste hinzu.
109 $plate->addListItem(name => 'Musterman', email => 'em@il');
111 Da sich Listen wie normale Schluessel-Wert Paare verhalten,
112 wird die Liste als Array ueber UVtemplate-Objekte unter dem
113 definiertem Schluessel abgelegt. Ist dieser Schluessel bereits
114 gesetzt und enthaehlt keinen Array, so bricht die Funktion ab.
122 # pruefen ob key angegeben ist und falls key vorhanden
123 # eine liste vorliegt
124 return unless ($list && (not($self->{KEYS}->{$list}) ||
125 UNIVERSAL::isa($self->{KEYS}->{$list}, 'ARRAY')));
127 # neues Element erzeugen
128 my $new = $self->new( @_ );
130 # an listen anhaengen
131 push(@{$self->{KEYS}->{$list}}, $new);
133 # referenz zurueckgeben
139 Den Wert eines Schluessel ermitteln.
141 my $value = $plate->getKey('email');
143 Ist der Wert im Objekt nicht gesetzt, wird - falls es sich um ein
144 Element einer Liste handelt - rekursiv beim Vater weiter gesucht.
146 So stehen allen Kindern auch die Schluessel-Wert Paare ihrer Eltern
149 Zum Schluss wird noch geprueft, ob der Schluessel in usevote.cfg
150 gesetzt wurde. Dadurch sind alle Konfigurationsoptionen direkt
151 in Templates nutzbar.
162 $value = $self->{KEYS}->{$key};
163 $self = $self->{FATHER};
165 }while(!defined($value) && $self);
167 if (!defined($value) && defined($config{$key})) {
168 $value = $config{$key};
174 #----------------------------------------------------------------------
180 return $self->{RULES} if ($self->{RULES});
181 $self = $self->{FATHER};
189 Einen Format ermitteln.
191 my $value = $plate->getConvKey('email-adresse');
193 Diese Funktion ueberprueft ob eine Formatierung mit den entsprechenden
194 Schluessel definiert ist und ruft dementsprechend die dort definierten
195 Funktionen ueber der Datenstruktur auf.
197 Ist kein solches Format definiert, wird der Wert des Schluessel mit
198 einem solchen Namen zurueckgegeben. (Es wird intern getKey aufgerufen).
204 my $key = $_[0] || return;
206 my $rules = $self->getRules();
207 my $value = $self->getKey($key);
209 $value = '' unless (defined($value));
211 if ($rules && ($rules->{$key})){
212 my @funcs = @{$rules->{$key}};
214 foreach my $func (@funcs){
215 my ($name, @params) = @$func;
217 if ($functions->{$name}){
218 $value = $functions->{$name}->($self, $value, @params);
221 print STDERR "format function '$name' not found!\n";
229 #----------------------------------------------------------------------
231 =item processTemplate
233 Daten des Objekts in ein Template einfuegen.
235 my $string = $plate->processTemplate('template/info.txt');
237 Die angebene Datei wird eingelesen, zerlegt und danach
238 die enstprechenden Platzhalter durch die (formatierten)
239 Werte aus der Datenstruktur ersetzt.
245 my $file = $_[0] || return;
247 my ($rules, $body) = _split_file($file);
249 # konvertierungsregeln parsen
250 $self->{RULES} = _parse_rules($rules);
252 # template zerlegen (zuerst fuehrende leerzeilen entfernen!)
254 my $token = UVtemplate::scan->new(string => $body);
257 return $token->processData($self);
261 my $file = $_[0] || return;
263 my $fname = _complete_filename($file);
266 print STDERR "couldnt find '$file'\n";
278 foreach my $line (@lines){
279 if ($line =~ m/^== TEMPLATE/){
292 # falls kein Separator definiert war, wird der komplette Text
293 # als Body interpretiert. Es gibt keine Regeln!
300 # und nun wieder zu Strings zusammenpappen
301 return (join('', @rules), join('', @body));
304 sub _complete_filename{
305 my $file = $_[0] || return;
307 my $dirs = $UVconfig::config{templatedir};
308 @dirs = split(/\s*,\s*/, $dirs) if $dirs;
312 foreach my $dir (@dirs, '.'){
313 $fname = "$dir/$file";
314 return $fname if (-r $fname);
318 #----------------------------------------------------------------------
319 # Konvertierungs-Regeln
320 #----------------------------------------------------------------------
323 my $string = $_[0] || return;
330 while (length($string) > 0){
331 _strip_chars(\$string);
333 my $token = _parse_token(\$string);
336 push(@stack, $token);
338 _strip_chars(\$string);
340 if ($string =~ s/^:=//){
341 # neuen Schluessel vom Stack holen
342 my $key = pop(@stack);
344 # restlichen Stack auf alten Schluessel packen
345 push(@$this, [ @stack ]);
348 # neuen Schluessel anlegen
349 $rules->{$key} = $this = [];
351 }elsif($string =~ s/^\|//){
352 # stack auf schluessel packen
353 push(@$this, [ @stack ]);
358 # fehlermeldung ausgeben (nacharbeiten!)
359 print STDERR "Syntaxerror in Definition\n";
364 # den Rest vom Stack abarbeiten
365 push(@$this, [ @stack ]) if @stack;
371 my $line = $_[0] || return;
373 # führenden whitespace entfernen
376 # kommentare bis zum nächsten Zeilenumbruch entfernen
377 $$line =~ s/^#.*$//m;
384 if ($$string =~ s/^(["'])//){
385 return _parse_string($string, $1);
388 return _parse_ident($string);
394 my ($string, $limit) = @_;
399 if ($$string =~ s/^$limit//){
400 $$string =~ s/^\s*//;
403 }elsif($$string =~ s/^\\(.)//){
407 $$string =~ s/^[^$limit\\]*//;
420 if ($$string =~ s/^([A-Za-z0-9-]+)\s*//){
427 #----------------------------------------------------------------------
430 $functions = \%UVconfig::functions;
433 #----------------------------------------------------------------------
434 #----------------------------------------------------------------------
435 package UVtemplate::scan;
436 #----------------------------------------------------------------------
437 #----------------------------------------------------------------------
444 bless($self, $class);
446 $self->parseFile($param{file}) if defined($param{file});
447 $self->parseString($param{string}) if defined($param{string});
452 #----------------------------------------------------------------------
458 return _process_data($self->{toks}, $data);
462 my ($toref, $data) = @_;
468 foreach my $token (@$toref){
470 my $before = length($string);
473 if ($token->[0] eq 'VAR'){
474 my $value = $data->getConvKey(_process_data($token->[1], $data));
476 if (defined($value) && length($value)){
480 $string .= _process_data($token->[2], $data);
483 }elsif($token->[0] eq 'IF'){
484 if ($data->getConvKey(_process_data($token->[1], $data))){
485 $string .= _process_data($token->[2], $data);
488 $string .= _process_data($token->[3], $data);
491 }elsif($token->[0] eq 'LOOP'){
492 my $nodes = $data->getConvKey(_process_data($token->[1], $data));
495 if ($nodes && (UNIVERSAL::isa($nodes, 'ARRAY'))){
496 foreach my $node (@$nodes){
497 push(@block, _process_data($token->[2], $node));
500 $string .= join(_process_data($token->[3], $data), @block);
504 $length = length($string);
505 $empty = 1 if ($before == $length);
508 if ($empty && ($string =~ m/(\n|^)$/s)){
509 $empty = 0; # Falls die letzte Zeile nur aus einem Token
510 $token =~ s/^\n//s; # ohne Inhalt bestand, wird die Zeile entfernt
520 #----------------------------------------------------------------------
521 # Den String in einen Syntaxbaum abbilden
523 sub _parse_token_string{
525 my ($string, $intern) = @_;
531 if ($intern && $string =~ m/^(\]|\|)/){
534 }elsif ($string =~ s/^\[//){
537 ($toref, $string) = $self->_parse_token($string);
540 push (@token, $data) if $data;
546 if ($string !~ s/^\]//){
547 my $pos = $self->{lines} - _count_lines($orig) + 1;
549 print STDERR "Scanner: [$pos] missing right bracket\n";
550 return (\@token, $string);
553 }elsif($string =~ s/^\\n//s){
556 }elsif($string =~ s/^\\(.)//s){
560 $string =~ s/^([^\]\[\|\\]+)//s;
564 $string =~ s/^([^\[\\]+)//s;
569 push (@token, $data) if length($data);
570 return (\@token, $string)
580 if ($string =~ s/^\$//s){
581 # Variablen - Syntax: [$key[|<else>]]
582 push (@token, 'VAR');
584 }elsif ($string =~ s/^\?//s){
585 # Bedingung - Syntax: [?if|<then>[|<else>]]
588 }elsif ($string =~ s/^\@//s){
589 # Schleifen - Syntax: [@key|<block>[|<sep>]]
590 push (@token, 'LOOP');
592 }elsif ($string =~ s/^#//s){
593 # Kommentare - Syntax: [# ... ]
594 $string = _parse_comment($string);
596 return (\@token, $string);
599 print STDERR "unknown token in template\n";
604 ($toref, $string) = $self->_parse_token_string($string, 1);
605 push(@token, $toref);
607 while ($string =~ s/^\|//){
608 ($toref, $string) = $self->_parse_token_string($string, 1);
609 push(@token, $toref);
612 return (\@token, $string);
620 while($string && $count) {
621 $string =~ s/^[^\[\]\\]+//s; # alles außer Klammern und Backslash wegwerfen
622 $string =~ s/^\\.//; # alles gesperrte löschen
624 $count++ if $string =~ s/^\[//;
625 $count-- if $string =~ s/^\]//;
628 $string = ']'.$string if !$count;
632 #----------------------------------------------------------------------
638 $self->{lines} = _count_lines($text);
639 my ($toref, $rest) = $self->_parse_token_string($text);
641 $self->{toks} = $toref;
646 return 0 unless defined($_[0]);
648 my ($string, $count) = ($_[0], 1);
649 $count++ while($string =~ m/\n/sg);
654 #----------------------------------------------------------------------
655 #----------------------------------------------------------------------
656 #----------------------------------------------------------------------
664 Eine Templatedatei besteht aus zwei Teilen. Am Anfang werden die
665 Formatierungen bestimmter Schluessel definiert und nach einem
666 Trenner folgt der eigentlich Template-Koerper, der dann von Programm
667 bearbeitet und ausgegeben wird.
669 format-key := function1 param | function2 param
671 == TEMPLATE ====================================
673 Ich bin nun das eigentliche Template:
675 format-key: [$format-key]
677 Der Trenner beginnt mit den Zeichen '== TEMPLATE' danach koennen
678 beliebige Zeichen folgen um die beiden Sektionen optisch voneinander
681 Wenn es keine Formatierungsanweisungen gibt, kann der Trenner auch
682 weggelassen werden. D.h. wenn kein Trenner gefunden wird, wird der
683 komplette Text als Template-Koerper betrachtet.
685 =head2 Template-Koerper
687 Im Template-Koerper werden die zu ersetzenden Token durch eckige
688 Klammern abgegrenzt. Sollen eckige Klammern im Text ausgegeben werden
689 muessen diese durch einen Backslash freigestellt werden.
691 [$termersetzung] [@schleife] nur eine \[ Klammer
695 =item $ - Termersetzung
697 Ersetzt den Token durch den Wert des angegeben Schluessels.
699 [$formatierung] [$schluessel]
701 Es wird zuerst nach einer Formatierung mit den entsprechenden
702 Bezeichner gesucht. Ist dies der Fall werden die entsprechenden
703 Funktionen ausgefuehrt.
705 Kann kein Format gefunden, wird direkt in der Datenstruktur
706 nach einem Schhluessel mit dem angegeben Bezeichner gesucht
707 und sein Wert eingesetzt.
709 Schlussendlich ist es noch moeglich einen default-Wert zu
710 definieren, der eingesetzt wird, wenn keiner der obigen Wege
713 Hallo [$name|Unbekannter]!
715 =item ? - bedingte Verzeigung
717 Ueberprueft ob der Wert des angegebenen Formats/Schluessel
718 boolsch WAHR ist. Dementsprechend wird der then oder else
721 [?if|then|else] oder auch nur [?if|then]
723 Die then/else Bloecke werden natuerlich auch auf Tokens
724 geparst und diese dementsprechend ersetzt.
726 =item @ - Schleifen/Listen
728 Der nachfolgende Textblock wird fuer alle Elemente des durch
729 den Schluessel bezeichneten Arrays ausgefuehrt und eingefuegt.
731 [@schluessel|block] oder [@schluessel|block|sep]
733 Als zweiter Parameter kann ein Separtor definiert werden, mit
734 dem sich z.B. kommaseparierte Listen erzeugen lassen, da der
735 Separator eben nur zwischen den Element eingefuegt wird.
737 Auch fuer Schleifen koennen Formatierungen genutzt werden.
738 Allerdings darf kein String zurueckgegeben werden, sondern
739 ein Array mit einer Menge von UVtemplate-Objekten.
743 Token die nach der Bearbeitungen entfernt werden.
745 [# mich sieht man nicht]
749 Um in Listen einen Zeilenumbruch zu erzwingen, muss
750 lediglich ein '\n' eingefuegt werden, falls eine kompakte
751 Definition der Liste erfolgen soll.
753 [@names|[name] [email]\n]
757 =head2 Formatierungen
759 Eine Formatierung besteht eigentlich nur aus dem entsprechenden
760 Namen und einer beliebigen Anzahl von Funktionsaufrufen:
762 format := funktion param1 "param 2" | funktion param
764 Aehnlich der Unix-Shell-Funktionalitaet, wird dabei die Ausgabe
765 einer Funktion an die folgende weitergeleitet. So ist es moeglich
766 verschiedenste simple Formatierungen zu kombinieren um nicht fuer
767 jeden Spezialfall eine neue Funktion schreiben zu muessen.
769 Die jeweilige Formatierungsfunktion erhaelt als Input die Datenstruktur,
770 den Output der vorherigen Funktion und die definierten Parameter in der
771 entsprechenden Reihenfolge.
773 Zahlen und einfache Bezeichner koennen direkt definiert werden. Sollen
774 Sonderzeichen oder Leerzeichen uebergeben werden muessen diese gequotet
775 werden. Dazu kann ' also auch " verwendet werden.
777 Die Funktionen geben im Allgemeinen einen String zurueck. Im Rahmen
778 von Listen können auch Arrays uebergeben werden.
780 Die erste Funktion duerfte ueblicherweise 'value' sein. Sie gibt den
781 des angegeben Schluessel zurueck, der dann von den folgenden Funktionen
784 name-60 := value name | fill-right 60
786 Das Format "name-60" definiert also den Wert des Schluessel "name" der
787 um Leerzeichen aufgefuellt wird, bis eine Laenge von 60 Zeichen
790 name-email := value name | justify-behind mail 72
792 "name-email" resultiert in einem String, der zwischen den Werten
793 von "name" und "email" genau so viele Leerzeichen enthaelt, damit
794 der gesamte String 72 Zeichen lang ist.
796 Wird dieses Format in einer Liste angewandt, erhaelt man eine Tabelle
797 in der die linke Spalte linksbuendig und die rechte Spalte entsprechend
800 Soweit ein kleiner Ueberblick ueber die Formatierungen.
801 Ausfuehrliche Funktionsbeschreibungen und weitere Beispiele finden
802 sich in der Dokumentation des Moduls UVformat.
810 Cornell Binder <cobi@dex.de>