1 #====================================================================
4 # Based on SQL-Ledger Version 2.1.9
5 # Web http://www.lx-office.org
7 #====================================================================
9 package SimpleTemplate;
12 # 1. The template's file name
13 # 2. A reference to the Form object
14 # 3. A reference to the myconfig hash
17 # A new template object
31 $self->{"source"} = shift;
32 $self->{"form"} = shift;
33 $self->{"myconfig"} = shift;
34 $self->{"userspath"} = shift;
36 $self->{"error"} = undef;
44 # 1. A typeglob for the file handle. The output will be written
45 # to this file handle.
48 # 1 on success and undef or 0 if there was an error. In the latter case
49 # the calling function can retrieve the error message via $obj->get_error()
54 print(OUT "Hallo!\n");
60 return $self->{"error"};
73 package LaTeXTemplate;
77 @ISA = qw(SimpleTemplate);
82 return $type->SUPER::new(@_);
86 my ($self, $variable) = @_;
87 my $form = $self->{"form"};
92 '"', '\$', '%', '_', '#', quotemeta('^'),
93 '{', '}', '<', '>', '£', "\r"
107 quotemeta('^') => '\^\\',
108 quotemeta("\n") => '\newline '
111 map({ $variable =~ s/$_/$replace{$_}/g; } @{ $replace{"order"} });
113 # Allow some HTML markup to be converted into the output format's
114 # corresponding markup code, e.g. bold or italic.
115 my %markup_replace = ('b' => 'textbf',
119 foreach my $key (keys(%markup_replace)) {
120 my $new = $markup_replace{$key};
121 $variable =~ s/\$\<\$${key}\$\>\$(.*?)\$<\$\/${key}\$>\$/\\${new}\{$1\}/gi;
130 my ($form, $myconfig) = ($self->{"form"}, $self->{"myconfig"});
132 # Some variables used for page breaks
133 my ($chars_per_line, $lines_on_first_page, $lines_on_second_page) =
135 my ($current_page, $current_line, $current_row) = (1, 1, 0);
136 my ($pagebreak, $sum, $two_passes, $nodiscount_sum) = ("", 0, 0, 0);
139 # Do we have to run LaTeX two times? This is needed if
140 # the template contains page references.
143 if (!open(IN, "$form->{templates}/$form->{IN}")) {
144 $self->{"error"} = "$!";
150 # first we generate a tmpfile
151 # read file and replace <%variable%>
156 $two_passes = 1 if (/\\pageref/);
158 # detect pagebreak block and its parameters
159 if (/\s*<%pagebreak ([0-9]+) ([0-9]+) ([0-9]+)%>/) {
160 $chars_per_line = $1;
161 $lines_on_first_page = $2;
162 $lines_on_second_page = $3;
165 last if (/\s*<%end pagebreak%>/);
170 if (/\s*<%foreach /) {
172 # this one we need for the count
174 $var =~ s/\s*<%foreach (.+?)%>/$1/;
176 last if (/\s*<%end /);
182 # Count the number of "lines" for our variable. Also find the forced pagebreak entries.
183 my $num_entries = scalar(@{$form->{$var}});
184 my @forced_pagebreaks = ();
185 for (my $i = 0; $i < scalar(@{$form->{$var}}); $i++) {
186 if ($form->{$var}->[$i] =~ /<pagebreak>/) {
187 push(@forced_pagebreaks, $i);
192 # display contents of $form->{number}[] array
193 for ($i = 0; $i < $num_entries; $i++) {
194 # Try to detect whether a manual page break is necessary
195 # but only if there was a <%pagebreak ...%> block before
197 if ($chars_per_line) {
199 int(length($form->{"description"}->[$i]) / $chars_per_line + 0.95);
202 $form->{"description"}->[$i] =~ s/(\\newline\s?)*$//;
203 my $_description = $form->{"description"}->[$i];
204 while ($_description =~ /\\newline/) {
206 $_description =~ s/\\newline//;
210 if ($current_page == 1) {
211 $lpp = $lines_on_first_page;
213 $lpp = $lines_on_second_page;
216 # Yes we need a manual page break -- or the user has forced one
217 if ((($current_line + $lines) > $lpp) ||
218 grep(/^${current_row}$/, @forced_pagebreaks)) {
221 # replace the special variables <%sumcarriedforward%>
224 my $psum = $form->format_amount($myconfig, $sum, 2);
225 my $nodiscount_psum = $form->format_amount($myconfig, $nodiscount_sum, 2);
226 $pb =~ s/<%nodiscount_sumcarriedforward%>/$nodiscount_psum/g;
227 $pb =~ s/<%sumcarriedforward%>/$psum/g;
228 $pb =~ s/<%lastpage%>/$current_page/g;
230 # only "normal" variables are supported here
231 # (no <%if, no <%foreach, no <%include)
233 while ($pb =~ /<%(.*?)%>/) {
234 substr($pb, $-[0], $+[0] - $-[0]) =
235 $self->format_string($form->{"$1"}->[$i]);
238 # page break block is ready to rock
243 $current_line += $lines;
246 $sum += $form->parse_amount($myconfig, $form->{"linetotal"}->[$i]);
247 $nodiscount_sum += $form->parse_amount($myconfig, $form->{"nodiscount_linetotal"}->[$i]);
249 # don't parse par, we need it for each line
251 while (/<%(.*?)%>/) {
252 substr($_, $-[0], $+[0] - $-[0]) =
253 $self->format_string($form->{"$1"}->[$i]);
260 # if not comes before if!
261 if (/\s*<%if not /) {
263 # check if it is not set and display
265 s/\s*<%if not (.+?)%>/$1/;
267 unless ($form->{$_}) {
269 last if (/\s*<%end /);
279 last if (/\s*<%end /);
287 # check if it is set and display
289 s/\s*<%if (.+?)%>/$1/;
293 last if (/\s*<%end /);
303 last if (/\s*<%end /);
309 # check for <%include filename%>
310 if (/\s*<%include /) {
314 $var =~ s/\s*<%include (.+?)%>/$1/;
317 $var =~ s/(\/|\.\.)//g;
319 # prevent the infinite loop!
320 next if ($form->{"$var"});
322 open(INC, $form->{templates} . "/$var")
323 or $form->error($self->cleanup . $form->{templates} . "/$var : $!");
332 while (/<%(.*?)%>/) {
333 substr($_, $-[0], $+[0] - $-[0]) = $self->format_string($form->{$1});
338 if ($form->{"format"} =~ /postscript/i) {
339 return $self->convert_to_postscript($two_passes);
340 } elsif ($form->{"format"} =~ /pdf/i) {
341 return $self->convert_to_pdf($two_passes);
347 sub convert_to_postscript {
348 my ($self, $two_passes) = @_;
349 my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"});
351 # Convert the tex file to postscript
353 if (!chdir("$userspath")) {
354 $self->{"error"} = "chdir : $!";
359 $form->{tmpfile} =~ s/$userspath\///g;
361 system("latex --interaction=nonstopmode $form->{tmpfile} " .
362 "> $form->{tmpfile}.err");
364 $self->{"error"} = $form->cleanup();
369 system("latex --interaction=nonstopmode $form->{tmpfile} " .
370 "> $form->{tmpfile}.err");
372 $self->{"error"} = $form->cleanup();
378 $form->{tmpfile} =~ s/tex$/dvi/;
380 system("dvips $form->{tmpfile} -o -q > /dev/null");
382 $self->{"error"} = "dvips : $!";
386 $form->{tmpfile} =~ s/dvi$/ps/;
394 my ($self, $two_passes) = @_;
395 my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"});
397 # Convert the tex file to PDF
399 if (!chdir("$userspath")) {
400 $self->{"error"} = "chdir : $!";
405 $form->{tmpfile} =~ s/$userspath\///g;
407 system("pdflatex --interaction=nonstopmode $form->{tmpfile} " .
408 "> $form->{tmpfile}.err");
410 $self->{"error"} = $form->cleanup();
416 system("pdflatex --interaction=nonstopmode $form->{tmpfile} " .
417 "> $form->{tmpfile}.err");
419 $self->{"error"} = $form->cleanup();
425 $form->{tmpfile} =~ s/tex$/pdf/;
430 sub get_mime_type() {
433 if ($self->{"form"}->{"format"} =~ /postscript/i) {
434 return "application/postscript";
436 return "application/pdf";
449 package HTMLTemplate;
453 @ISA = qw(LaTeXTemplate);
458 return $type->SUPER::new(@_);
462 my ($self, $variable) = @_;
463 my $form = $self->{"form"};
466 ('order' => ['<', '>', quotemeta("\n")],
469 quotemeta("\n") => '<br>',
472 map({ $variable =~ s/$_/$replace{$_}/g; } @{ $replace{"order"} });
474 # Allow some HTML markup to be converted into the output format's
475 # corresponding markup code, e.g. bold or italic.
476 my @markup_replace = ('b', 'i', 's', 'u');
478 foreach my $key (@markup_replace) {
479 $variable =~ s/\<(\/?)${key}\>/<$1${key}>/g;
485 sub get_mime_type() {
499 package OpenDocumentTemplate;
506 # use File::Temp qw(:mktemp);
509 @ISA = qw(SimpleTemplate);
514 $self = $type->SUPER::new(@_);
516 foreach my $module (qw(Archive::Zip Text::Iconv)) {
517 eval("use ${module};");
519 $self->{"form"}->error("The Perl module '${module}' could not be " .
520 "loaded. Support for OpenDocument templates " .
521 "does not work without it. Please install your " .
522 "distribution's package or get the module from " .
523 "CPAN ( http://www.cpan.org ).");
527 $self->{"rnd"} = int(rand(1000000));
528 $self->{"iconv"} = Text::Iconv->new($main::dbcharset, "UTF-8");
533 sub substitute_vars {
534 my ($self, $text, @indices) = @_;
536 my $form = $self->{"form"};
538 while ($text =~ /\<\%(.*?)\%\>/) {
539 my $value = $form->{$1};
541 for (my $i = 0; $i < scalar(@indices); $i++) {
542 last unless (ref($value) eq "ARRAY");
543 $value = $value->[$indices[$i]];
545 substr($text, $-[0], $+[0] - $-[0]) = $self->format_string($value);
552 my ($self, $var, $text, $start_tag, $end_tag, @indices) = @_;
554 my ($form, $new_contents) = ($self->{"form"}, "");
556 my $ary = $form->{$var};
557 for (my $i = 0; $i < scalar(@indices); $i++) {
558 last unless (ref($ary) eq "ARRAY");
559 $ary = $ary->[$indices[$i]];
562 for (my $i = 0; $i < scalar(@{$ary}); $i++) {
563 my $new_text = $self->parse_block($text, (@indices, $i));
564 return undef unless (defined($new_text));
565 $new_contents .= $start_tag . $new_text . $end_tag;
568 return $new_contents;
572 $main::lxdebug->enter_sub();
574 my ($self, $contents, @indices) = @_;
576 my $new_contents = "";
578 while ($contents ne "") {
579 if (substr($contents, 0, 1) eq "<") {
580 $contents =~ m|^<[^>]+>|;
582 substr($contents, 0, length($&)) = "";
584 if ($tag =~ m|<table:table-row|) {
585 $contents =~ m|^(.*?)(</table:table-row[^>]*>)|;
588 substr($contents, 0, length($1) + length($end_tag)) = "";
590 if ($table_row =~ m|\<\%foreachrow\s+(.*?)\%\>|) {
593 $table_row =~ s|\<\%foreachrow .*?\%\>||g;
594 $table_row =~ s!\<\%end(for|foreach)?row\s+${var}\%\>!!g;
596 my $new_text = $self->parse_foreach($var, $table_row, $tag, $end_tag, @indices);
597 return undef unless (defined($new_text));
598 $new_contents .= $new_text;
601 my $new_text = $self->parse_block($table_row, @indices);
602 return undef unless (defined($new_text));
603 $new_contents .= $tag . $new_text . $end_tag;
607 $new_contents .= $tag;
611 $contents =~ /^[^<]+/;
614 my $pos_if = index($text, '<%if');
615 my $pos_foreach = index($text, '<%foreach');
617 if ((-1 == $pos_if) && (-1 == $pos_foreach)) {
618 substr($contents, 0, length($text)) = "";
619 $new_contents .= $self->substitute_vars($text, @indices);
623 if ((-1 == $pos_if) || ((-1 != $pos_foreach) && ($pos_if > $pos_foreach))) {
624 $new_contents .= $self->substitute_vars(substr($contents, 0, $pos_foreach), @indices);
625 substr($contents, 0, $pos_foreach) = "";
627 if ($contents !~ m|^\<\%foreach (.*?)\%\>|) {
628 $self->{"error"} = "Malformed <\%foreach\%>.";
629 $main::lxdebug->leave_sub();
635 substr($contents, 0, length($&)) = "";
637 if ($contents !~ m!\<\%end\s*?(for)?\s+${var}\%\>!) {
638 $self->{"error"} = "Unclosed <\%foreach\%>.";
639 $main::lxdebug->leave_sub();
643 substr($contents, 0, length($`) + length($&)) = "";
644 my $new_text = $self->parse_foreach($var, $`, "", "", @indices);
645 return undef unless (defined($new_text));
646 $new_contents .= $new_text;
649 $new_contents .= $self->substitute_vars(substr($contents, 0, $pos_if), @indices);
650 substr($contents, 0, $pos_if) = "";
652 if ($contents !~ m|^\<\%if(not)?\s+(.*?)\%\>|) {
653 $self->{"error"} = "Malformed <\%if\%>.";
654 $main::lxdebug->leave_sub();
658 my ($not, $var) = ($1, $2);
660 substr($contents, 0, length($&)) = "";
662 if ($contents !~ m!\<\%endif${not}\s+${var}\%\>!) {
663 $self->{"error"} = "Unclosed <\%if${not}\%>.";
664 $main::lxdebug->leave_sub();
668 substr($contents, 0, length($`) + length($&)) = "";
670 my $value = $self->{"form"}->{$var};
671 for (my $i = 0; $i < scalar(@indices); $i++) {
672 last unless (ref($value) eq "ARRAY");
673 $value = $value->[$indices[$i]];
676 if (($not && !$value) || (!$not && $value)) {
677 my $new_text = $self->parse_block($`, @indices);
678 return undef unless (defined($new_text));
679 $new_contents .= $new_text;
685 return $new_contents;
689 $main::lxdebug->enter_sub();
693 my $form = $self->{"form"};
697 my $zip = Archive::Zip->new();
698 if (Archive::Zip::AZ_OK != $zip->read("$form->{templates}/$form->{IN}")) {
699 $self->{"error"} = "File not found/is not a OpenDocument file.";
700 $main::lxdebug->leave_sub();
704 my $contents = $zip->contents("content.xml");
706 $self->{"error"} = "File is not a OpenDocument file.";
707 $main::lxdebug->leave_sub();
711 my $rnd = $self->{"rnd"};
712 my $new_styles = qq|<style:style style:name="TLXO${rnd}BOLD" style:family="text">
713 <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
715 <style:style style:name="TLXO${rnd}ITALIC" style:family="text">
716 <style:text-properties fo:font-style="italic" style:font-style-asian="italic" style:font-style-complex="italic"/>
718 <style:style style:name="TLXO${rnd}UNDERLINE" style:family="text">
719 <style:text-properties style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color"/>
721 <style:style style:name="TLXO${rnd}STRIKETHROUGH" style:family="text">
722 <style:text-properties style:text-line-through-style="solid"/>
725 $contents =~ s|</office:automatic-styles>|${new_styles}</office:automatic-styles>|;
726 $contents =~ s|[\n\r]||gm;
728 my $new_contents = $self->parse_block($contents);
729 return 0 unless (defined($new_contents));
731 # $new_contents =~ s|>|>\n|g;
733 $zip->contents("content.xml", $new_contents);
734 $zip->writeToFileNamed($form->{"tmpfile"}, 1);
737 if ($form->{"format"} =~ /pdf/) {
738 $res = $self->convert_to_pdf();
741 $main::lxdebug->leave_sub();
748 my $form = $self->{"form"};
750 my $filename = $form->{"tmpfile"};
751 $filename =~ s/.odt$//;
752 if (substr($filename, 0, 1) ne "/") {
753 $filename = getcwd() . "/${filename}";
756 if (substr($self->{"userspath"}, 0, 1) eq "/") {
757 $ENV{'HOME'} = $self->{"userspath"};
759 $ENV{'HOME'} = getcwd() . "/" . $self->{"userspath"};
762 my @cmdline = ($main::xvfb_run_bin, $main::openofficeorg_writer_bin,
763 "-minimized", "-norestore", "-nologo", "-nolockcheck",
765 "file:${filename}.odt",
766 "macro://" . (split('/', $filename))[-1] .
767 "/Standard.Conversion.ConvertSelfToPDF()");
773 $form->{"tmpfile"} =~ s/odt$/pdf/;
775 unlink($filename . ".odt");
777 $main::lxdebug->leave_sub();
782 unlink($filename . ".odt", $filename . ".pdf");
783 $self->{"error"} = "Conversion from OpenDocument to PDF failed. " .
786 $main::lxdebug->leave_sub();
791 my ($self, $variable) = @_;
792 my $form = $self->{"form"};
793 my $iconv = $self->{"iconv"};
796 ('order' => ['<', '>', '"', "'",
798 quotemeta("\n"), quotemeta("\r"), '&'],
804 '\x80' => chr(0xa4), # Euro
805 quotemeta("\n") => '<text:line-break/>',
806 quotemeta("\r") => '',
809 map({ $variable =~ s/$_/$replace{$_}/g; } @{ $replace{"order"} });
811 # Allow some HTML markup to be converted into the output format's
812 # corresponding markup code, e.g. bold or italic.
813 my $rnd = $self->{"rnd"};
814 my %markup_replace = ("b" => "BOLD", "i" => "ITALIC", "s" => "STRIKETHROUGH",
817 foreach my $key (keys(%markup_replace)) {
818 my $value = $markup_replace{$key};
819 $variable =~ s|\<${key}\>|<text:span text:style-name=\"TLXO${rnd}${value}\">|g;
820 $variable =~ s|\</${key}\>|</text:span>|g;
823 return $iconv->convert($variable);
826 sub get_mime_type() {
827 if ($self->{"form"}->{"format"} =~ /pdf/) {
828 return "application/pdf";
830 return "application/vnd.oasis.opendocument.text";