X-Git-Url: https://git.p6c8.net/devedit.git/blobdiff_plain/03d83644ce2d958fe3e0466fa272c67f0566f4ab..d8d635a1b1d6d9ffa05f3cfaaa6d8bf9d2b9bf81:/modules/Template.pm?ds=sidebyside diff --git a/modules/Template.pm b/modules/Template.pm index e3fbb98..cf46d09 100644 --- a/modules/Template.pm +++ b/modules/Template.pm @@ -1,17 +1,18 @@ package Template; # -# Template (Version 1.2a) +# Template (Version 2.0) # # Klasse zum Parsen von Templates # -# Autor: Patrick Canterino -# Letzte Aenderung: 12.9.2003 +# Autor: Patrick Canterino +# Letzte Aenderung: 31.7.2006 # use strict; use Carp qw(croak); +use File::Spec; # new() # @@ -24,7 +25,7 @@ use Carp qw(croak); sub new { my $class = shift; - my $self = {template => ''}; + my $self = {file => '', template => '', original => '', vars => {}, defined_vars => [], loop_vars => {}}; return bless($self,$class); } @@ -51,8 +52,6 @@ sub get_template sub set_template($) { - # Geht nur so... - my ($self,$template) = @_; $self->{'template'} = $template; } @@ -73,23 +72,178 @@ sub add_text($) # read_file() # -# Einlesen einer Vorlagendatei -# (Inhalt wird an bereits vorhandenen Text angehaengt) +# Einlesen einer Vorlagendatei und {INCLUDE}-Anweisungen ggf. verarbeiten +# (Text wird an bereits vorhandenen Text angehaengt) # -# Parameter: Datei zum Einlesen +# Parameter: 1. Datei zum Einlesen +# 2. Status-Code (Boolean): +# true => {INCLUDE}-Anweisungen nicht verarbeiten +# false => {INCLUDE}-Anweisungen verarbeiten (Standard) # # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) -sub read_file($) +sub read_file($;$) { - my ($self,$tfile) = @_; + my ($self,$file,$not_include) = @_; local *FILE; - open(FILE,"<$tfile") or croak "Open $tfile: $!"; - read(FILE, my $content, -s $tfile); - close(FILE) or croak "Closing $tfile: $!"; + $self->{'file'} = $file; + + open(FILE,'<'.$file) or croak "Open $file: $!"; + read(FILE, my $content, -s $file); + close(FILE) or croak "Closing $file: $!"; $self->add_text($content); + $self->save_state; + + $self->parse_includes unless($not_include); +} + +# set_var() +# +# Wert einer Variable setzen +# +# Parameter: 1. Name der Variable +# 2. Wert, den die Variable erhalten soll +# +# Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + +sub set_var($$) +{ + my ($self,$var,$content) = @_; + $self->{'vars'}->{$var} = $content; +} + +# get_var() +# +# Wert einer Variable zurueckgeben +# +# Parameter: (optional) Variablenname +# +# Rueckgabe: Wert der Variable; +# wenn die Variable nicht existiert, false; +# wenn kein Variablenname angegeben wurde, wird ein +# Array mit den Variablennamen zurueckgegeben + +sub get_var(;$) +{ + my ($self,$var) = @_; + + if(defined $var) + { + if($self->{'vars'}->{$var}) + { + return $self->{'vars'}->{$var}; + } + else + { + return undef; + } + } + else + { + return keys %{$self->{'vars'}}; + } +} + +# set_loop_data() +# +# Daten fuer eine Schleife setzen +# +# Parameter: 1. Name der Schleife +# 2. Array-Referenz mit den Hash-Referenzen mit +# den Variablen fuer die Schleifendurchgaenge +# +# Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + +sub set_loop_data($$) +{ + my ($self,$loop,$data) = @_; + $self->{'loop_vars'}->{$loop} = $data; +} + +# add_loop_data() +# +# Daten fuer einen Schleifendurchgang hinzufuegen +# +# Parameter: 1. Name der Schleife +# 2. Hash-Referenz mit den Variablen fuer den +# Schleifendurchgang +# +# Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + +sub add_loop_data($$) +{ + my ($self,$loop,$data) = @_; + + if($self->{'loop_vars'}->{$loop} && ref($self->{'loop_vars'}->{$loop}) eq 'ARRAY') + { + push(@{$self->{'loop_vars'}->{$loop}},$data); + } + else + { + $self->{'loop_vars'}->{$loop} = [$data]; + } +} + +# parse() +# +# In der Template definierte Variablen auslesen, Variablen +# ersetzen, {IF}- und {TRIM}-Bloecke parsen +# +# Parameter: -nichts- +# +# Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + +sub parse +{ + my $self = shift; + + # Zuerst die Schleifen parsen + + if($self->{'loop_vars'} && (my @loops = keys(%{$self->{'loop_vars'}}))) + { + foreach my $loop(@loops) + { + $self->parse_loop($loop); + } + } + + # Normale Variablen durchgehen + + foreach my $var($self->get_var) + { + my $val = $self->get_var($var); + + $self->parse_if_block($var,$val); + + if(ref($val) eq 'ARRAY') + { + $self->fillin_array($var,$val); + } + else + { + $self->fillin($var,$val); + } + } + + # Jetzt dasselbe mit denen, die direkt in der Template-Datei definiert + # sind, machen. Ich weiss, dass das eine ziemlich unsaubere Loesung ist, + # aber es funktioniert + + $self->get_defined_vars; + + foreach my $var(@{$self->{'defined_vars'}}) + { + my $val = $self->get_var($var); + + $self->parse_if_block($var,$val); + $self->fillin($var,$val); + } + + # {TRIM}-Bloecke entfernen + + $self->parse_trim_blocks; } # fillin() @@ -105,11 +259,10 @@ sub fillin($$) { my ($self,$var,$text) = @_; - $var = quotemeta($var); - $text = "" unless defined $text; # Um Fehler zu vermeiden + $text = '' unless defined $text; # Um Fehler zu vermeiden my $template = $self->get_template; - $template =~ s/\{$var\}/$text/g; + $template = str_replace('{'.$var.'}',$text,$template); $self->set_template($template); } @@ -121,7 +274,7 @@ sub fillin($$) # Parameter: 1. Variable zum Ersetzen # 2. Array-Referenz, durch die die Variable ersetzt werden soll # 3. Zeichenkette, mit der das Array verbunden werden soll -# (Standard: "") +# (Standard: '') # # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) @@ -133,40 +286,325 @@ sub fillin_array($$;$) $self->fillin($var,join($glue,@$array)); } +# to_file() +# +# Template in Datei schreiben +# +# Parameter: Datei-Handle +# +# Rueckgabe: Status-Code (Boolean) + +sub to_file($) +{ + my ($self,$handle) = @_; + return print $handle $self->get_template; +} + +# reset() +# +# Den gesicherten Stand des Template-Textes sichern +# +# Parameter: -nichts- +# +# Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + +sub reset +{ + my $self = shift; + $self->{'template'} = $self->{'original'}; +} + +# save_state() +# +# Aktuellen Stand des Template-Textes sichern +# (alte Sicherung wird ueberschrieben) +# +# Parameter: -nichts- +# +# Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + +sub save_state +{ + my $self = shift; + $self->{'original'} = $self->{'template'}; +} + +# parse_loop() +# +# Eine Schleife parsen +# +# Parameter: Name der Schleife +# +# Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + +sub parse_loop($) +{ + my ($self,$name) = @_; + + my $template = $self->get_template; + return if(index($template,'{LOOP '.$name.'}') == -1); + + my $offset = 0; + my $name_len = length($name); + + while((my $begin = index($template,'{LOOP '.$name.'}',$offset)) != -1) + { + if((my $end = index($template,'{ENDLOOP}',$begin+6+$name_len)) != -1) + { + my $block = substr($template,$begin,$end+9-$begin); + my $content = substr($block,$name_len+7,-9); + + my $parsed_block = ''; + + for(my $x=0;$x{'loop_vars'}->{$name}};$x++) + { + my $loop_data = $self->{'loop_vars'}->{$name}->[$x]; + my @loop_vars = keys(%$loop_data); + + my $ctpl = new Template; + $ctpl->set_template($content); + + foreach my $loop_var(@loop_vars) + { + $ctpl->set_var($name.'.'.$loop_var,$loop_data->{$loop_var}); + } + + $ctpl->parse; + $parsed_block .= $ctpl->get_template; + + undef($ctpl); + } + + $template = str_replace($block,$parsed_block,$template); + $offset = $begin+length($parsed_block); + } + else + { + last; + } + } + + $self->set_template($template); +} + +# get_defined_vars() +# +# In der Template-Datei definierte Variablen auslesen +# +# Parameter: -nichts- +# +# Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + +sub get_defined_vars +{ + my $self = shift; + + my $template = $self->get_template; + return if(index($template,'{DEFINE ') == -1); + + my $offset = 0; + + while(index($template,'{DEFINE ',$offset) != -1) + { + my $begin = index($template,'{DEFINE ',$offset)+8; + $offset = $begin; + + my $name = ''; + my $content = ''; + + my $var_open = 0; + my $name_found = 0; + my $define_block = 0; + + for(my $x=$begin;$xget_var($name)) + { + # Die Variable wird nur gesetzt, wenn sie nicht bereits gesetzt ist + + $self->set_var($name,$content); + push(@{$self->{'defined_vars'}},$name); + } + + # {DEFINE}-Block entfernen + + my $pre = substr($template,0,$begin-8); + my $post = substr($template,$x+2); + + $template = $pre.$post; + + # Fertig! + + $offset = length($pre); + last; + } + } + elsif(substr($template,$x,1) eq '\\') + { + # Ein Backslash wurde gefunden, er dient zum Escapen von Zeichen + + if(substr($template,$x+1,1) eq 'n') + { + # "\n" in Zeilenumbrueche umwandeln + + $content .= "\n"; + } + else + { + $content .= substr($template,$x+1,1); + } + + $x++; + } + else + { + $content .= substr($template,$x,1); + } + } + else + { + if($name_found == 1) + { + if($var_open == 0) + { + if(substr($template,$x,1) eq '"') + { + $var_open = 1; + } + else + { + last; + } + } + } + else + { + # Variablennamen auslesen + + if(substr($template,$x,1) eq '}' && $name ne '') + { + # Wir haben einen {DEFINE}-Block + + $name_found = 1; + $define_block = 1; + + # Alles ab hier sollte mit dem Teil verbunden werden, der das + # {DEFINE} in einer Zeile verarbeitet + + # Der Parser fuer {DEFINE}-Bloecke ist nicht rekursiv, was auch + # nicht noetig sein sollte + + if((my $end = index($template,'{ENDDEFINE}',$x)) != -1) + { + $x++; + + $content = substr($template,$x,$end-$x); + + if(not defined $self->get_var($name)) + { + # Die Variable wird nur gesetzt, wenn sie nicht bereits gesetzt ist + + $self->set_var($name,$content); + push(@{$self->{'defined_vars'}},$name); + } + + my $pre = substr($template,0,$begin-8); + my $post = substr($template,$end+11); + + $template = $pre.$post; + + # Fertig! + + $offset = length($pre); + last; + } + else + { + last; + } + } + elsif(substr($template,$x,1) ne ' ') + { + $name .= substr($template,$x,1); + } + elsif(substr($template,$x,1) ne '') + { + $name_found = 1; + } + else + { + last; + } + } + } + } + } + + $self->set_template($template); +} + # parse_if_block() # # IF-Bloecke verarbeiten # # Parameter: 1. Name des IF-Blocks (das, was nach dem IF steht) -# 2. Statuscode (true => Inhalt anzeigen -# false => Inhalt nicht anzeigen +# 2. Status-Code (true => Inhalt anzeigen +# false => Inhalt nicht anzeigen +# 3. true => Verneinten Block nicht parsen +# false => Verneinten Block parsen (Standard) # # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) -sub parse_if_block($$) +sub parse_if_block($$;$) { - my ($self,$name,$state) = @_; - my $template = $self->get_template; + my ($self,$name,$state,$no_negate) = @_; + my $template = $self->get_template; + + my $count = 0; - while(index($template,"{IF ".$name."}") >= 0) + while(index($template,'{IF '.$name.'}') >= 0) { # Das alles hier ist nicht wirklich elegant geloest... # ... aber solange es funktioniert... ;-) - my $start = index($template,"{IF ".$name."}"); + $count++; + + my $start = index($template,'{IF '.$name.'}'); my $tpl_tmp = substr($template,$start); - my @splitted = split(/\{ENDIF\}/,$tpl_tmp); + my @splitted = explode('{ENDIF}',$tpl_tmp); - my $block = ""; # Kompletter bedingter Block + my $block = ''; # Kompletter bedingter Block my $ifs = 0; # IF-Zaehler (wird fuer jedes IF erhoeht und fuer jedes ENDIF erniedrigt) # {IF} for(my $x=0;$x<@splitted;$x++) { - $ifs += substr_count($splitted[$x],"{IF"); # Zum Zaehler jedes Vorkommen von IF hinzuzaehlen - $ifs--; # Zaehler um 1 erniedrigen - $block .= $splitted[$x]."{ENDIF}"; # Daten zum Block hinzufuegen + croak 'Nesting error found while parsing IF block "'.$name.'" nr. '.$count.' in template file "'.$self->{'file'}.'"' if($x == $#splitted); + + $ifs += substr_count($splitted[$x],'{IF '); # Zum Zaehler jedes Vorkommen von IF hinzuzaehlen + $ifs--; # Zaehler um 1 erniedrigen + $block .= $splitted[$x].'{ENDIF}'; # Daten zum Block hinzufuegen if($ifs == 0) { @@ -180,15 +618,15 @@ sub parse_if_block($$) # {ELSE} - my $else_block = ""; # Alles ab {ELSE} + my $else_block = ''; # Alles ab {ELSE} $ifs = 0; # IF-Zaehler - @splitted = split(/\{ELSE\}/,$if_block); + @splitted = explode('{ELSE}',$if_block); for(my $x=0;$x<@splitted;$x++) { - $ifs += substr_count($splitted[$x],"{IF"); # Zum Zaehler jedes Vorkommen von IF hinzuzaehlen - $ifs -= substr_count($splitted[$x],"{ENDIF}"); # Vom Zaehler jedes Vorkommen von ENDIF abziehen + $ifs += substr_count($splitted[$x],'{IF '); # Zum Zaehler jedes Vorkommen von IF hinzuzaehlen + $ifs -= substr_count($splitted[$x],'{ENDIF}'); # Vom Zaehler jedes Vorkommen von ENDIF abziehen if($ifs == 0) { @@ -198,13 +636,13 @@ sub parse_if_block($$) for(my $y=$x+1;$y<@splitted;$y++) { - $else_block .= "{ELSE}".$splitted[$y]; + $else_block .= '{ELSE}'.$splitted[$y]; } if($else_block) { $if_block = substr($if_block,0,length($if_block)-length($else_block)); - $else_block = (length($else_block) > 6) ? substr($else_block,6) : ""; # Ansonsten gibt es Fehler + $else_block = (length($else_block) > 6) ? substr($else_block,6) : ''; # Ansonsten gibt es Fehler } last; @@ -213,9 +651,58 @@ sub parse_if_block($$) my $replacement = ($state) ? $if_block : $else_block; - my $qmblock = quotemeta($block); + $template = str_replace($block,$replacement,$template); + } + + $self->set_template($template); + + # Evtl. verneinte Form parsen - $template =~ s/$qmblock/$replacement/; + unless($no_negate) + { + $self->parse_if_block('!'.$name,not($state),1); + } +} + +# parse_trim_blocks() +# +# {TRIM}-Bloecke parsen +# +# Dieser Parser ist nicht rekursiv, was auch nicht +# noetig sein sollte. +# +# Parameter: -nichts- +# +# Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + +sub parse_trim_blocks +{ + my $self = shift; + + my $template = $self->get_template; + return if(index($template,'{TRIM}') == -1); + + my $offset = 0; + + while((my $begin = index($template,'{TRIM}')) >= 0) + { + if((my $end = index($template,'{ENDTRIM}',$begin+6)) >= 0) + { + my $block = substr($template,$begin,$end+9-$begin); + my $content = substr($block,6,-9); + + my $trimmed = $content; + $trimmed =~ s/^\s+//s; + $trimmed =~ s/\s+$//s; + + $template = str_replace($block,$content,$template); + + $offset = $begin+length($trimmed); + } + else + { + last; + } } $self->set_template($template); @@ -226,8 +713,8 @@ sub parse_if_block($$) # Bedingungstags in einem Vorlagentext verarbeiten # # Parameter: 1. Tagname -# 2. Statuscode (true => Tag-Inhalt anzeigen -# false => Tag-Inhalt nicht anzeigen +# 2. Status-Code (true => Tag-Inhalt anzeigen +# false => Tag-Inhalt nicht anzeigen # # Rueckgabe: -nichts- (Template-Objekt wird modifiziert) @@ -237,19 +724,118 @@ sub parse_condtag($$) my $template = $self->get_template; - while(index($template,"<$condtag>") >= 0) + while(index($template,'<'.$condtag.'>') >= 0) + { + my $start = index($template,'<'.$condtag.'>'); # Beginn des Blocks + my $end = index($template,'')+length($condtag)+3; # Ende des Blocks + + my $extract = substr($template,$start,$end-$start); # Kompletten Bedingungsblock extrahieren... + + my $replacement = ($state) ? substr($extract,length($condtag)+2,0-length($condtag)-3) : ''; + + $template = str_replace($extract,$replacement,$template); # Block durch neue Daten ersetzen + } + $self->set_template($template); +} + +# parse_includes() +# +# {INCLUDE}-Anweisungen verarbeiten +# +# Parameter: -nichts- +# +# Rueckgabe: -nichts- (Template-Objekt wird modifiziert) + +sub parse_includes +{ + my $self = shift; + + my $template = $self->get_template; + return if(index($template,'{INCLUDE ') == -1); + + my $offset = 0; + + my $y = 0; + + while((my $begin = index($template,'{INCLUDE ',$offset)) != -1) { - my $start = index($template,"<$condtag>"); # Beginn des Blocks - my $end = index($template,"")+length($condtag)+3; # Ende des Blocks + $y++; + + my $start = $begin+9; + $offset = $start; + my $long = 0; - my $extract = substr($template,$start,$end-$start); # Kompletten Bedingungsblock extrahieren... + if(substr($template,$start,1) eq '"') + { + $long = 1; + $start++; + } - my $replacement = ($state) ? substr($extract,length($condtag)+2,0-length($condtag)-3) : ""; + my $file = ''; + my $skip = 0; - $extract = quotemeta($extract); + for(my $x=$start;$xfile_name_is_absolute($file)) + { + my $dir = (File::Spec->splitpath($self->{'file'}))[1]; + $dir = '.' unless($dir); + $filepath = File::Spec->catfile($dir,$file); + } + + if(-f $filepath) + { + my $inc = new Template; + $inc->read_file($filepath); + + my $end = ($long == 1) + ? $start + length($file) + 2 + : $start + length($file) + 1; + + my $pre = substr($template,0,$begin); + my $post = substr($template,$end); + + $template = $pre.$inc->get_template.$post; + $offset = length($pre)+length($inc->get_template); + + undef($inc); + } + } } + $self->set_template($template); } @@ -257,6 +843,61 @@ sub parse_condtag($$) # Private Funktion # ================== +# explode() +# +# Eine Zeichenkette ohne regulaere Ausdruecke auftrennen +# (split() hat einen Bug, deswegen verwende ich es nicht) +# +# Parameter: 1. Trennzeichenkette +# 2. Zeichenkette, die aufgetrennt werden soll +# 3. Maximale Zahl von Teilen +# +# Rueckgabe: Aufgetrennte Zeichenkette (Array) + +sub explode($$;$) +{ + my ($separator,$string,$limit) = @_; + my @splitted; + + my $x = 1; + my $offset = 0; + my $sep_len = length($separator); + + while((my $pos = index($string,$separator,$offset)) >= 0 && (!$limit || $x < $limit)) + { + my $part = substr($string,$offset,$pos-$offset); + push(@splitted,$part); + + $offset = $pos+$sep_len; + + $x++; + } + + push(@splitted,substr($string,$offset,length($string)-$offset)); + + return @splitted; +} + +# str_replace() +# +# Zeichenkette durch andere ersetzen +# +# Parameter: 1. Zu ersetzender Text +# 2. Ersetzender Text +# 3. Zeichenkette, in der ersetzt werden soll +# +# Rueckgabe: Bearbeitete Zeichenkette (String) + +sub str_replace($$$) +{ + my ($search,$replace,$subject) = @_; + $search = quotemeta($search); + + $subject =~ s/$search/$replace/gs; + + return $subject; +} + # substr_count() # # Zaehlt, wie oft ein String in einem String vorkommt @@ -279,30 +920,6 @@ sub substr_count($$) return $count; } -# ================== -# Alias-Funktionen -# ================== - -sub addtext($) -{ - shift->add_text(shift); -} - -sub as_string -{ - return shift->get_template; -} - -sub condtag($$) -{ - shift->parse_condtag(@_); -} - -sub readin($) -{ - shift->read_file(shift); -} - # it's true, baby ;-) 1;