X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FTemplate.pm;h=3556dc49a6de3831ccb9e4427b92b9df88ad068a;hb=88e6dce2c094fa468e4397030d2f14289a265e62;hp=5763eee46e161ecd4387be4b7fd2a63661aac4dc;hpb=54e4131e091831e00a861fe2c4f53e344b87ddca;p=kivitendo-erp.git diff --git a/SL/Template.pm b/SL/Template.pm index 5763eee46..3556dc49a 100644 --- a/SL/Template.pm +++ b/SL/Template.pm @@ -5,9 +5,17 @@ # Web http://www.lx-office.org # #==================================================================== +# +# +# NOTE: strict checks are package global. don't check this file +# with perl -sc, it will only capture SimpleTemplate +# +# package SimpleTemplate; +use strict; + # Parameters: # 1. The template's file name # 2. A reference to the Form object @@ -28,12 +36,28 @@ sub new { sub _init { my $self = shift; - $self->{"source"} = shift; - $self->{"form"} = shift; - $self->{"myconfig"} = shift; - $self->{"userspath"} = shift; + $self->{source} = shift; + $self->{form} = shift; + $self->{myconfig} = shift; + $self->{userspath} = shift; + + $self->{error} = undef; + $self->{quot_re} = '"'; + + $self->set_tag_style('<%', '%>'); +} + +sub set_tag_style { + my $self = shift; + my $tag_start = shift; + my $tag_end = shift; + + $self->{tag_start} = $tag_start; + $self->{tag_end} = $tag_end; + $self->{tag_start_qm} = quotemeta $tag_start; + $self->{tag_end_qm} = quotemeta $tag_end; - $self->{"error"} = undef; + $self->{substitute_vars_re} = "$self->{tag_start_qm}(.+?)$self->{tag_end_qm}"; } sub cleanup { @@ -64,6 +88,133 @@ sub uses_temp_file { return 0; } +sub _get_loop_variable { + my $self = shift; + my $var = shift; + my $get_array = shift; + my @indices = @_; + + my $form = $self->{form}; + my $value; + + if (($get_array || @indices) && (ref $form->{TEMPLATE_ARRAYS} eq 'HASH') && (ref $form->{TEMPLATE_ARRAYS}->{$var} eq 'ARRAY')) { + $value = $form->{TEMPLATE_ARRAYS}->{$var}; + } else { + $value = $form->{$var}; + } + + for (my $i = 0; $i < scalar(@indices); $i++) { + last unless (ref($value) eq "ARRAY"); + $value = $value->[$indices[$i]]; + } + + return $value; +} + +sub substitute_vars { + my ($self, $text, @indices) = @_; + + my $form = $self->{"form"}; + + while ($text =~ /$self->{substitute_vars_re}/) { + my ($tag_pos, $tag_len) = ($-[0], $+[0] - $-[0]); + my ($var, @options) = split(/\s+/, $1); + + my $value = $self->_get_loop_variable($var, 0, @indices); + $value = $self->format_string($value) unless (grep(/^NOESCAPE$/, @options)); + + substr($text, $tag_pos, $tag_len, $value); + } + + return $text; +} + +sub _parse_block_if { + $main::lxdebug->enter_sub(); + + my $self = shift; + my $contents = shift; + my $new_contents = shift; + my $pos_if = shift; + my @indices = @_; + + $$new_contents .= $self->substitute_vars(substr($$contents, 0, $pos_if), @indices); + substr($$contents, 0, $pos_if) = ""; + + if ($$contents !~ m/^$self->{tag_start_qm}if + \s* + (not\b|\!)? # $1 -- Eventuelle Negierung + \s+ + (\b.+?\b) # $2 -- Name der zu überprüfenden Variablen + ( # $3 -- Beginn des optionalen Vergleiches + \s* + ([!=]) # $4 -- Negierung des Vergleiches speichern + ([=~]) # $5 -- Art des Vergleiches speichern + \s* + ( # $6 -- Gequoteter String oder Bareword + $self->{quot_re} + (.*?)(?{quot_re} + | + (\b.+?\b) # $8 -- Bareword -- als Index für $form benutzen + ) + )? + \s* + $self->{tag_end_qm} + /x) { + $self->{"error"} = "Malformed $self->{tag_start}if$self->{tag_end}."; + $main::lxdebug->leave_sub(); + return undef; + } + + my $not = $1; + my $var = $2; + my $operator_neg = $4; # '=' oder '!' oder undef, wenn kein Vergleich erkannt + my $operator_type = $5; # '=' oder '~' für Stringvergleich oder Regex + my $quoted_word = $7; # nur gültig, wenn quoted string angegeben (siehe unten); dann "value" aus <%if var == "value" %> + my $bareword = $8; # undef, falls quoted string angegeben wurde; andernfalls "othervar" aus <%if var == othervar %> + + $not = !$not if ($operator_neg && $operator_neg eq '!'); + + substr($$contents, 0, length($&)) = ""; + + my $block; + ($block, $$contents) = $self->find_end($$contents, 0, $var, $not); + if (!$block) { + $self->{"error"} = "Unclosed $self->{tag_start}if$self->{tag_end}." unless ($self->{"error"}); + $main::lxdebug->leave_sub(); + return undef; + } + + my $value = $self->_get_loop_variable($var, 0, @indices); + my $hit = 0; + + if ($operator_type) { + my $compare_to = $bareword ? $self->_get_loop_variable($bareword, 0, @indices) : $quoted_word; + if ($operator_type eq '=') { + $hit = ($not && !($value eq $compare_to)) || (!$not && ($value eq $compare_to)); + } else { + $hit = ($not && !($value =~ m/$compare_to/i)) || (!$not && ($value =~ m/$compare_to/i)); + } + + } else { + $hit = ($not && ! $value) || (!$not && $value); + } + + if ($hit) { + my $new_text = $self->parse_block($block, @indices); + if (!defined($new_text)) { + $main::lxdebug->leave_sub(); + return undef; + } + $$new_contents .= $new_text; + } + + $main::lxdebug->leave_sub(); + + return 1; +} + 1; #### @@ -76,39 +227,21 @@ use vars qw(@ISA); @ISA = qw(SimpleTemplate); +use strict; + sub new { my $type = shift; - return $type->SUPER::new(@_); + my $self = $type->SUPER::new(@_); + + return $self; } sub format_string { my ($self, $variable) = @_; my $form = $self->{"form"}; - my %replace = - ('order' => [ - '&', quotemeta("\n"), - '"', '\$', '%', '_', '#', quotemeta('^'), - '{', '}', '<', '>', '£', "\r" - ], - '"' => "''", - '&' => '\&', - '\$' => '\$', - '%' => '\%', - '_' => '\_', - '#' => '\#', - '{' => '\{', - '}' => '\}', - '<' => '$<$', - '>' => '$>$', - '£' => '\pounds ', - "\r" => "", - quotemeta('^') => '\^\\', - quotemeta("\n") => '\newline ' - ); - - map({ $variable =~ s/$_/$replace{$_}/g; } @{ $replace{"order"} }); + $variable = $main::locale->quote_special_chars('Template/LaTeX', $variable); # Allow some HTML markup to be converted into the output format's # corresponding markup code, e.g. bold or italic. @@ -121,231 +254,336 @@ sub format_string { $variable =~ s/\$\<\$${key}\$\>\$(.*?)\$<\$\/${key}\$>\$/\\${new}\{$1\}/gi; } + $variable =~ s/[\x00-\x1f]//g; + return $variable; } -sub parse { - my $self = $_[0]; - local *OUT = $_[1]; - my ($form, $myconfig) = ($self->{"form"}, $self->{"myconfig"}); +sub parse_foreach { + my ($self, $var, $text, $start_tag, $end_tag, @indices) = @_; - # Some variables used for page breaks - my ($chars_per_line, $lines_on_first_page, $lines_on_second_page) = - (0, 0, 0); - my ($current_page, $current_line, $current_row) = (1, 1, 0); - my ($pagebreak, $sum, $two_passes, $nodiscount_sum) = ("", 0, 0, 0); - my ($par, $var); + my ($form, $new_contents) = ($self->{"form"}, ""); - # Do we have to run LaTeX two times? This is needed if - # the template contains page references. - $two_passes = 0; + my $ary = $self->_get_loop_variable($var, 1, @indices); - if (!open(IN, "$form->{templates}/$form->{IN}")) { - $self->{"error"} = "$!"; - return 0; - } - @_ = ; - close(IN); + my $sum = 0; + my $current_page = 1; + my ($current_line, $corrent_row) = (0, 1); + my $description_array = $self->_get_loop_variable("description", 1); + my $longdescription_array = $self->_get_loop_variable("longdescription", 1); + my $linetotal_array = $self->_get_loop_variable("linetotal", 1); - # first we generate a tmpfile - # read file and replace <%variable%> - while ($_ = shift) { - $par = ""; - $var = $_; + $form->{TEMPLATE_ARRAYS}->{cumulatelinetotal} = []; - $two_passes = 1 if (/\\pageref/); + # forech block hasn't given us an array. ignore + return $new_contents unless ref $ary eq 'ARRAY'; + + for (my $i = 0; $i < scalar(@{$ary}); $i++) { + # do magic markers + $form->{"__first__"} = $i == 1; + $form->{"__last__"} = ($i + 1) == scalar(@{$ary}); + $form->{"__odd__"} = (($i + 1) % 2) == 1; + $form->{"__counter__"} = $i + 1; + + if ( ref $description_array eq 'ARRAY' + && scalar @{$description_array} == scalar @{$ary} + && $self->{"chars_per_line"} != 0) + { + my $lines = int(length($description_array->[$i]) / $self->{"chars_per_line"}); + my $lpp; + + $description_array->[$i] =~ s/(\\newline\s?)*$//; + $lines++ while ($description_array->[$i] =~ m/\\newline/g); + $lines++; + + if ($current_page == 1) { + $lpp = $self->{"lines_on_first_page"}; + } else { + $lpp = $self->{"lines_on_second_page"}; + } - # detect pagebreak block and its parameters - if (/\s*<%pagebreak ([0-9]+) ([0-9]+) ([0-9]+)%>/) { - $chars_per_line = $1; - $lines_on_first_page = $2; - $lines_on_second_page = $3; + # Yes we need a manual page break -- or the user has forced one + if ( (($current_line + $lines) > $lpp) + || ($description_array->[$i] =~ //) + || ( ref $longdescription_array eq 'ARRAY' + && $longdescription_array->[$i] =~ //)) { + my $pb = $self->{"pagebreak_block"}; - while ($_ = shift) { - last if (/\s*<%end pagebreak%>/); - $pagebreak .= $_; + # replace the special variables <%sumcarriedforward%> + # and <%lastpage%> + + my $psum = $form->format_amount($self->{"myconfig"}, $sum, 2); + $pb =~ s/$self->{tag_start_qm}sumcarriedforward$self->{tag_end_qm}/$psum/g; + $pb =~ s/$self->{tag_start_qm}lastpage$self->{tag_end_qm}/$current_page/g; + + my $new_text = $self->parse_block($pb, (@indices, $i)); + return undef unless (defined($new_text)); + $new_contents .= $new_text; + + $current_page++; + $current_line = 0; } + $current_line += $lines; } - if (/\s*<%foreach /) { + if ( ref $linetotal_array eq 'ARRAY' + && $i < scalar(@{$linetotal_array})) { + $sum += $form->parse_amount($self->{"myconfig"}, $linetotal_array->[$i]); + } - # this one we need for the count - chomp $var; - $var =~ s/\s*<%foreach (.+?)%>/$1/; - while ($_ = shift) { - last if (/\s*<%end /); + $form->{TEMPLATE_ARRAYS}->{cumulatelinetotal}->[$i] = $form->format_amount($self->{"myconfig"}, $sum, 2); - # store line in $par - $par .= $_; - } + my $new_text = $self->parse_block($text, (@indices, $i)); + return undef unless (defined($new_text)); + $new_contents .= $start_tag . $new_text . $end_tag; + } + map({ delete($form->{"__${_}__"}); } qw(first last odd counter)); - # Count the number of "lines" for our variable. Also find the forced pagebreak entries. - my $num_entries = scalar(@{$form->{$var}}); - my @forced_pagebreaks = (); - for (my $i = 0; $i < scalar(@{$form->{$var}}); $i++) { - if ($form->{$var}->[$i] =~ //) { - push(@forced_pagebreaks, $i); - } - } + return $new_contents; +} - $current_line = 1; - # display contents of $form->{number}[] array - for ($i = 0; $i < $num_entries; $i++) { - # Try to detect whether a manual page break is necessary - # but only if there was a <%pagebreak ...%> block before - - if ($chars_per_line) { - my $lines = - int(length($form->{"description"}->[$i]) / $chars_per_line + 0.95); - my $lpp; - - $form->{"description"}->[$i] =~ s/(\\newline\s?)*$//; - my $_description = $form->{"description"}->[$i]; - while ($_description =~ /\\newline/) { - $lines++; - $_description =~ s/\\newline//; - } - $lines++; +sub find_end { + my ($self, $text, $pos, $var, $not) = @_; - if ($current_page == 1) { - $lpp = $lines_on_first_page; - } else { - $lpp = $lines_on_second_page; - } + my $tag_start_len = length $self->{tag_start}; - # Yes we need a manual page break -- or the user has forced one - if ((($current_line + $lines) > $lpp) || - grep(/^${current_row}$/, @forced_pagebreaks)) { - my $pb = $pagebreak; + my $depth = 1; + $pos = 0 unless ($pos); - # replace the special variables <%sumcarriedforward%> - # and <%lastpage%> + while ($pos < length($text)) { + $pos++; - my $psum = $form->format_amount($myconfig, $sum, 2); - my $nodiscount_psum = $form->format_amount($myconfig, $nodiscount_sum, 2); - $pb =~ s/<%nodiscount_sumcarriedforward%>/$nodiscount_psum/g; - $pb =~ s/<%sumcarriedforward%>/$psum/g; - $pb =~ s/<%lastpage%>/$current_page/g; + next if (substr($text, $pos - 1, length($self->{tag_start})) ne $self->{tag_start}); - # only "normal" variables are supported here - # (no <%if, no <%foreach, no <%include) + my $keyword_pos = $pos - 1 + $tag_start_len; - while ($pb =~ /<%(.*?)%>/) { - substr($pb, $-[0], $+[0] - $-[0]) = - $self->format_string($form->{"$1"}->[$i]); - } + if ((substr($text, $keyword_pos, 2) eq 'if') || (substr($text, $keyword_pos, 3) eq 'for')) { + $depth++; - # page break block is ready to rock - print(OUT $pb); - $current_page++; - $current_line = 1; - } - $current_line += $lines; - $current_row++; - } - $sum += $form->parse_amount($myconfig, $form->{"linetotal"}->[$i]); - $nodiscount_sum += $form->parse_amount($myconfig, $form->{"nodiscount_linetotal"}->[$i]); - - # don't parse par, we need it for each line - $_ = $par; - while (/<%(.*?)%>/) { - substr($_, $-[0], $+[0] - $-[0]) = - $self->format_string($form->{"$1"}->[$i]); - } - print OUT; + } elsif ((substr($text, $keyword_pos, 4) eq 'else') && (1 == $depth)) { + if (!$var) { + $self->{"error"} = + "$self->{tag_start}else$self->{tag_end} outside of " + . "$self->{tag_start}if$self->{tag_end} / " + . "$self->{tag_start}ifnot$self->{tag_end}."; + return undef; + } + + my $block = substr($text, 0, $pos - 1); + substr($text, 0, $pos - 1) = ""; + $text =~ s!^$self->{tag_start_qm}.+?$self->{tag_end_qm}!!; + $text = $self->{tag_start} . 'if' . ($not ? " " : "not ") . $var . $self->{tag_end} . $text; + + return ($block, $text); + + } elsif (substr($text, $keyword_pos, 3) eq 'end') { + $depth--; + if ($depth == 0) { + my $block = substr($text, 0, $pos - 1); + substr($text, 0, $pos - 1) = ""; + $text =~ s!^$self->{tag_start_qm}.+?$self->{tag_end_qm}!!; + + return ($block, $text); } - next; } + } - # if not comes before if! - if (/\s*<%if not /) { + return undef; +} - # check if it is not set and display - chop; - s/\s*<%if not (.+?)%>/$1/; +sub parse_block { + $main::lxdebug->enter_sub(); - unless ($form->{$_}) { - while ($_ = shift) { - last if (/\s*<%end /); + my ($self, $contents, @indices) = @_; - # store line in $par - $par .= $_; - } + my $new_contents = ""; - $_ = $par; + while ($contents ne "") { + my $pos_if = index($contents, $self->{tag_start} . 'if'); + my $pos_foreach = index($contents, $self->{tag_start} . 'foreach'); - } else { - while ($_ = shift) { - last if (/\s*<%end /); - } - next; - } + if ((-1 == $pos_if) && (-1 == $pos_foreach)) { + $new_contents .= $self->substitute_vars($contents, @indices); + last; } - if (/\s*<%if /) { + if ((-1 == $pos_if) || ((-1 != $pos_foreach) && ($pos_if > $pos_foreach))) { + $new_contents .= $self->substitute_vars(substr($contents, 0, $pos_foreach), @indices); + substr($contents, 0, $pos_foreach) = ""; - # check if it is set and display - chop; - s/\s*<%if (.+?)%>/$1/; + if ($contents !~ m|^$self->{tag_start_qm}foreach (.+?)$self->{tag_end_qm}|) { + $self->{"error"} = "Malformed $self->{tag_start}foreach$self->{tag_end}."; + $main::lxdebug->leave_sub(); + return undef; + } - if ($form->{$_}) { - while ($_ = shift) { - last if (/\s*<%end /); + my $var = $1; - # store line in $par - $par .= $_; - } + substr($contents, 0, length($&)) = ""; - $_ = $par; + my $block; + ($block, $contents) = $self->find_end($contents); + if (!$block) { + $self->{"error"} = "Unclosed $self->{tag_start}foreach$self->{tag_end}." unless ($self->{"error"}); + $main::lxdebug->leave_sub(); + return undef; + } - } else { - while ($_ = shift) { - last if (/\s*<%end /); - } - next; + my $new_text = $self->parse_foreach($var, $block, "", "", @indices); + if (!defined($new_text)) { + $main::lxdebug->leave_sub(); + return undef; + } + $new_contents .= $new_text; + + } else { + if (!$self->_parse_block_if(\$contents, \$new_contents, $pos_if, @indices)) { + $main::lxdebug->leave_sub(); + return undef; } } + } + + $main::lxdebug->leave_sub(); + + return $new_contents; +} - # check for <%include filename%> - if (/\s*<%include /) { +sub parse_first_line { + my $self = shift; + my $line = shift || ""; - # get the filename - chomp $var; - $var =~ s/\s*<%include (.+?)%>/$1/; + if ($line =~ m/([^\s]+)set-tag-style([^\s]+)/) { + if ($1 eq $2) { + $self->{error} = "The tag start and end markers must not be equal."; + return 0; + } - # mangle filename - $var =~ s/(\/|\.\.)//g; + $self->set_tag_style($1, $2); + } - # prevent the infinite loop! - next if ($form->{"$var"}); + return 1; +} - open(INC, $form->{templates} . "/$var") - or $form->error($self->cleanup . $form->{templates} . "/$var : $!"); - unshift(@_, ); - close(INC); +sub _parse_config_option { + my $self = shift; + my $line = shift; - $form->{"$var"} = 1; + $line =~ s/^\s*//; + $line =~ s/\s*$//; + my ($key, $value) = split m/\s*=\s*/, $line, 2; + + if ($key eq 'tag-style') { + $self->set_tag_style(split(m/\s+/, $value, 2)); + } +} + +sub _parse_config_lines { + my $self = shift; + my $lines = shift; + + my ($comment_start, $comment_end) = ("", ""); + + if (ref $self eq 'LaTeXTemplate') { + $comment_start = '\s*%'; + } elsif (ref $self eq 'HTMLTemplate') { + $comment_start = '\s*