Forcieren, dass bei LaTeX-Vorlagen das Paket "textcomp" eingebunden wird, das für...
[kivitendo-erp.git] / SL / Template.pm
1 #====================================================================
2 # LX-Office ERP
3 # Copyright (C) 2004
4 # Based on SQL-Ledger Version 2.1.9
5 # Web http://www.lx-office.org
6 #
7 #====================================================================
8
9 package SimpleTemplate;
10
11 # Parameters:
12 #   1. The template's file name
13 #   2. A reference to the Form object
14 #   3. A reference to the myconfig hash
15 #
16 # Returns:
17 #   A new template object
18 sub new {
19   my $type = shift;
20   my $self = {};
21
22   bless($self, $type);
23   $self->_init(@_);
24
25   return $self;
26 }
27
28 sub _init {
29   my $self = shift;
30
31   $self->{source}    = shift;
32   $self->{form}      = shift;
33   $self->{myconfig}  = shift;
34   $self->{userspath} = shift;
35
36   $self->{error}     = undef;
37
38   $self->set_tag_style('<%', '%>');
39 }
40
41 sub set_tag_style {
42   my $self              = shift;
43   my $tag_start         = shift;
44   my $tag_end           = shift;
45
46   $self->{tag_start}    = $tag_start;
47   $self->{tag_end}      = $tag_end;
48   $self->{tag_start_qm} = quotemeta $tag_start;
49   $self->{tag_end_qm}   = quotemeta $tag_end;
50 }
51
52 sub cleanup {
53   my ($self) = @_;
54 }
55
56 # Parameters:
57 #   1. A typeglob for the file handle. The output will be written
58 #      to this file handle.
59 #
60 # Returns:
61 #   1 on success and undef or 0 if there was an error. In the latter case
62 #   the calling function can retrieve the error message via $obj->get_error()
63 sub parse {
64   my $self = $_[0];
65   local *OUT = $_[1];
66
67   print(OUT "Hallo!\n");
68 }
69
70 sub get_error {
71   my $self = shift;
72
73   return $self->{"error"};
74 }
75
76 sub uses_temp_file {
77   return 0;
78 }
79
80 1;
81
82 ####
83 #### LaTeXTemplate
84 ####
85
86 package LaTeXTemplate;
87
88 use vars qw(@ISA);
89
90 @ISA = qw(SimpleTemplate);
91
92 sub new {
93   my $type = shift;
94
95   return $type->SUPER::new(@_);
96 }
97
98 sub format_string {
99   my ($self, $variable) = @_;
100   my $form = $self->{"form"};
101
102   $variable = $main::locale->quote_special_chars('Template/LaTeX', $variable);
103
104   # Allow some HTML markup to be converted into the output format's
105   # corresponding markup code, e.g. bold or italic.
106   my %markup_replace = ('b' => 'textbf',
107                         'i' => 'textit',
108                         'u' => 'underline');
109
110   foreach my $key (keys(%markup_replace)) {
111     my $new = $markup_replace{$key};
112     $variable =~ s/\$\<\$${key}\$\>\$(.*?)\$<\$\/${key}\$>\$/\\${new}\{$1\}/gi;
113   }
114
115   $variable =~ s/[\x00-\x1f]//g;
116
117   return $variable;
118 }
119
120 sub substitute_vars {
121   my ($self, $text, @indices) = @_;
122
123   my $form = $self->{"form"};
124
125   while ($text =~ /$self->{tag_start_qm}(.+?)$self->{tag_end_qm}/) {
126     my ($tag_pos, $tag_len) = ($-[0], $+[0] - $-[0]);
127     my ($var, @options) = split(/\s+/, $1);
128     my $value = $form->{$var};
129
130     for (my $i = 0; $i < scalar(@indices); $i++) {
131       last unless (ref($value) eq "ARRAY");
132       $value = $value->[$indices[$i]];
133     }
134     $value = $self->format_string($value) unless (grep(/^NOESCAPE$/, @options));
135     substr($text, $tag_pos, $tag_len) = $value;
136   }
137
138   return $text;
139 }
140
141 sub parse_foreach {
142   my ($self, $var, $text, $start_tag, $end_tag, @indices) = @_;
143
144   my ($form, $new_contents) = ($self->{"form"}, "");
145
146   my $ary = $form->{$var};
147   for (my $i = 0; $i < scalar(@indices); $i++) {
148     last unless (ref($ary) eq "ARRAY");
149     $ary = $ary->[$indices[$i]];
150   }
151
152   my $sum = 0;
153   my $current_page = 1;
154   my ($current_line, $corrent_row) = (0, 1);
155
156   for (my $i = 0; $i < scalar(@{$ary}); $i++) {
157     $form->{"__first__"} = $i == 0;
158     $form->{"__last__"} = ($i + 1) == scalar(@{$ary});
159     $form->{"__odd__"} = (($i + 1) % 2) == 1;
160     $form->{"__counter__"} = $i + 1;
161
162     if ((scalar(@{$form->{"description"}}) == scalar(@{$ary})) &&
163         $self->{"chars_per_line"}) {
164       my $lines =
165         int(length($form->{"description"}->[$i]) / $self->{"chars_per_line"});
166       my $lpp;
167
168       $form->{"description"}->[$i] =~ s/(\\newline\s?)*$//;
169       my $_description = $form->{"description"}->[$i];
170       while ($_description =~ /\\newline/) {
171         $lines++;
172         $_description =~ s/\\newline//;
173       }
174       $lines++;
175
176       if ($current_page == 1) {
177         $lpp = $self->{"lines_on_first_page"};
178       } else {
179         $lpp = $self->{"lines_on_second_page"};
180       }
181
182       # Yes we need a manual page break -- or the user has forced one
183       if ((($current_line + $lines) > $lpp) ||
184           ($form->{"description"}->[$i] =~ /<pagebreak>/)) {
185         my $pb = $self->{"pagebreak_block"};
186
187         # replace the special variables <%sumcarriedforward%>
188         # and <%lastpage%>
189
190         my $psum = $form->format_amount($self->{"myconfig"}, $sum, 2);
191         $pb =~ s/$self->{tag_start_qm}sumcarriedforward$self->{tag_end_qm}/$psum/g;
192         $pb =~ s/$self->{tag_start_qm}lastpage$self->{tag_end_qm}/$current_page/g;
193
194         my $new_text = $self->parse_block($pb, (@indices, $i));
195         return undef unless (defined($new_text));
196         $new_contents .= $new_text;
197
198         $current_page++;
199         $current_line = 0;
200       }
201       $current_line += $lines;
202     }
203     if ($i < scalar(@{$form->{"linetotal"}})) {
204       $sum += $form->parse_amount($self->{"myconfig"},
205                                   $form->{"linetotal"}->[$i]);
206     }
207     
208     $form->{"cumulatelinetotal"}[$i] = $form->format_amount($self->{"myconfig"}, $sum, 2);
209     
210     my $new_text = $self->parse_block($text, (@indices, $i));
211     return undef unless (defined($new_text));
212     $new_contents .= $start_tag . $new_text . $end_tag;
213   }
214   map({ delete($form->{"__${_}__"}); } qw(first last odd counter));
215
216   return $new_contents;
217 }
218
219 sub find_end {
220   my ($self, $text, $pos, $var, $not) = @_;
221
222   my $tag_start_len = length $self->{tag_start};
223
224   my $depth = 1;
225   $pos = 0 unless ($pos);
226
227   while ($pos < length($text)) {
228     $pos++;
229
230     next if (substr($text, $pos - 1, length($self->{tag_start})) ne $self->{tag_start});
231
232     my $keyword_pos = $pos - 1 + $tag_start_len;
233
234     if ((substr($text, $keyword_pos, 2) eq 'if') || (substr($text, $keyword_pos, 3) eq 'for')) {
235       $depth++;
236
237     } elsif ((substr($text, $keyword_pos, 4) eq 'else') && (1 == $depth)) {
238       if (!$var) {
239         $self->{"error"} =
240             "$self->{tag_start}else$self->{tag_end} outside of "
241           . "$self->{tag_start}if$self->{tag_end} / "
242           . "$self->{tag_start}ifnot$self->{tag_end}.";
243         return undef;
244       }
245
246       my $block = substr($text, 0, $pos - 1);
247       substr($text, 0, $pos - 1) = "";
248       $text =~ s!^$self->{tag_start_qm}.+?$self->{tag_end_qm}!!;
249       $text =  $self->{tag_start} . 'if' . ($not ?  " " : "not ") . $var . $self->{tag_end} . $text;
250
251       return ($block, $text);
252
253     } elsif (substr($text, $keyword_pos, 3) eq 'end') {
254       $depth--;
255       if ($depth == 0) {
256         my $block = substr($text, 0, $pos - 1);
257         substr($text, 0, $pos - 1) = "";
258         $text =~ s!^$self->{tag_start_qm}.+?$self->{tag_end_qm}!!;
259
260         return ($block, $text);
261       }
262     }
263   }
264
265   return undef;
266 }
267
268 sub parse_block {
269   $main::lxdebug->enter_sub();
270
271   my ($self, $contents, @indices) = @_;
272
273   my $new_contents = "";
274
275   while ($contents ne "") {
276     my $pos_if      = index($contents, $self->{tag_start} . 'if');
277     my $pos_foreach = index($contents, $self->{tag_start} . 'foreach');
278
279     if ((-1 == $pos_if) && (-1 == $pos_foreach)) {
280       $new_contents .= $self->substitute_vars($contents, @indices);
281       last;
282     }
283
284     if ((-1 == $pos_if) || ((-1 != $pos_foreach) && ($pos_if > $pos_foreach))) {
285       $new_contents .= $self->substitute_vars(substr($contents, 0, $pos_foreach), @indices);
286       substr($contents, 0, $pos_foreach) = "";
287
288       if ($contents !~ m|^$self->{tag_start_qm}foreach (.+?)$self->{tag_end_qm}|) {
289         $self->{"error"} = "Malformed $self->{tag_start}foreach$self->{tag_end}.";
290         $main::lxdebug->leave_sub();
291         return undef;
292       }
293
294       my $var = $1;
295
296       substr($contents, 0, length($&)) = "";
297
298       my $block;
299       ($block, $contents) = $self->find_end($contents);
300       if (!$block) {
301         $self->{"error"} = "Unclosed $self->{tag_start}foreach$self->{tag_end}." unless ($self->{"error"});
302         $main::lxdebug->leave_sub();
303         return undef;
304       }
305
306       my $new_text = $self->parse_foreach($var, $block, "", "", @indices);
307       if (!defined($new_text)) {
308         $main::lxdebug->leave_sub();
309         return undef;
310       }
311       $new_contents .= $new_text;
312
313     } else {
314       $new_contents .= $self->substitute_vars(substr($contents, 0, $pos_if), @indices);
315       substr($contents, 0, $pos_if) = "";
316
317       if ($contents !~ m|^$self->{tag_start_qm}if\s*(not)?\s+(.*?)$self->{tag_end_qm}|) {
318         $self->{"error"} = "Malformed $self->{tag_start}if$self->{tag_end}.";
319         $main::lxdebug->leave_sub();
320         return undef;
321       }
322
323       my ($not, $var) = ($1, $2);
324
325       substr($contents, 0, length($&)) = "";
326
327       ($block, $contents) = $self->find_end($contents, 0, $var, $not);
328       if (!$block) {
329         $self->{"error"} = "Unclosed $self->{tag_start}if${not}$self->{tag_end}." unless ($self->{"error"});
330         $main::lxdebug->leave_sub();
331         return undef;
332       }
333
334       my $value = $self->{"form"}->{$var};
335       for (my $i = 0; $i < scalar(@indices); $i++) {
336         last unless (ref($value) eq "ARRAY");
337         $value = $value->[$indices[$i]];
338       }
339
340       if (($not && !$value) || (!$not && $value)) {
341         my $new_text = $self->parse_block($block, @indices);
342         if (!defined($new_text)) {
343           $main::lxdebug->leave_sub();
344           return undef;
345         }
346         $new_contents .= $new_text;
347       }
348     }
349   }
350
351   $main::lxdebug->leave_sub();
352
353   return $new_contents;
354 }
355
356 sub parse_first_line {
357   my $self = shift;
358   my $line = shift || "";
359
360   if ($line =~ m/([^\s]+)set-tag-style([^\s]+)/) {
361     if ($1 eq $2) {
362       $self->{error} = "The tag start and end markers must not be equal.";
363       return 0;
364     }
365
366     $self->set_tag_style($1, $2);
367   }
368
369   return 1;
370 }
371
372 sub _parse_config_option {
373   my $self = shift;
374   my $line = shift;
375
376   $line =~ s/^\s*//;
377   $line =~ s/\s*$//;
378
379   my ($key, $value) = split m/\s*=\s*/, $line, 2;
380
381   if ($key eq 'tag-style') {
382     $self->set_tag_style(split(m/\s+/, $value, 2));
383   }
384 }
385
386 sub _parse_config_lines {
387   my $self  = shift;
388   my $lines = shift;
389
390   my ($comment_start, $comment_end) = ("", "");
391
392   if (ref $self eq 'LaTeXTemplate') {
393     $comment_start = '\s*%';
394   } elsif (ref $self eq 'HTMLTemplate') {
395     $comment_start = '\s*<!--';
396     $comment_end   = '>\s*';
397   } else {
398     $comment_start = '\s*\#';
399   }
400
401   my $num_lines = scalar @{ $lines };
402   my $i         = 0;
403
404   while ($i < $num_lines) {
405     my $line = $lines->[$i];
406
407     if ($line !~ m/^${comment_start}\s*config\s*:(.*)${comment_end}$/i) {
408       $i++;
409       next;
410     }
411
412     $self->_parse_config_option($1);
413     splice @{ $lines }, $i, 1;
414     $num_lines--;
415   }
416 }
417
418 sub _force_mandatory_packages {
419   my $self  = shift;
420   my $lines = shift;
421
422   my (%used_packages, $document_start_line);
423
424   foreach my $i (0 .. scalar @{ $lines } - 1) {
425     if ($lines->[$i] =~ m/\\usepackage[^{]*{(.*?)}/) {
426       $used_packages{$1} = 1;
427
428     } elsif ($lines->[$i] =~ m/\\begin{document}/) {
429       $document_start_line = $i;
430       last;
431
432     }
433   }
434
435   $document_start_line = scalar @{ $lines } - 1 if (!defined $document_start_line);
436
437   if (!$used_packages{textcomp}) {
438     splice @{ $lines }, $document_start_line, 0, "\\usepackage{textcomp}\n";
439     $document_start_line++;
440   }
441 }
442
443 sub parse {
444   my $self = $_[0];
445   local *OUT = $_[1];
446   my $form = $self->{"form"};
447
448   if (!open(IN, "$form->{templates}/$form->{IN}")) {
449     $self->{"error"} = "$!";
450     return 0;
451   }
452   my @lines = <IN>;
453   close(IN);
454
455   $self->_parse_config_lines(\@lines);
456   $self->_force_mandatory_packages(\@lines);
457
458   my $contents = join("", @lines);
459
460   # detect pagebreak block and its parameters
461   if ($contents =~ /$self->{tag_start_qm}pagebreak\s+(\d+)\s+(\d+)\s+(\d+)\s*$self->{tag_end_qm}(.*?)$self->{tag_start_qm}end(\s*pagebreak)?$self->{tag_end_qm}/s) {
462     $self->{"chars_per_line"} = $1;
463     $self->{"lines_on_first_page"} = $2;
464     $self->{"lines_on_second_page"} = $3;
465     $self->{"pagebreak_block"} = $4;
466
467     substr($contents, length($`), length($&)) = "";
468   }
469
470   $self->{"forced_pagebreaks"} = [];
471
472   my $new_contents = $self->parse_block($contents);
473   if (!defined($new_contents)) {
474     $main::lxdebug->leave_sub();
475     return 0;
476   }
477
478   print(OUT $new_contents);
479
480   if ($form->{"format"} =~ /postscript/i) {
481     return $self->convert_to_postscript();
482   } elsif ($form->{"format"} =~ /pdf/i) {
483     return $self->convert_to_pdf();
484   } else {
485     return 1;
486   }
487 }
488
489 sub convert_to_postscript {
490   my ($self) = @_;
491   my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"});
492
493   # Convert the tex file to postscript
494
495   if (!chdir("$userspath")) {
496     $self->{"error"} = "chdir : $!";
497     $self->cleanup();
498     return 0;
499   }
500
501   $form->{tmpfile} =~ s/\Q$userspath\E\///g;
502
503   for (my $run = 1; $run <= 2; $run++) {
504     system("latex --interaction=nonstopmode $form->{tmpfile} " .
505            "> $form->{tmpfile}.err");
506     if ($?) {
507       $self->{"error"} = $form->cleanup();
508       $self->cleanup();
509       return 0;
510     }
511   }
512
513   $form->{tmpfile} =~ s/tex$/dvi/;
514
515   system("dvips $form->{tmpfile} -o -q > /dev/null");
516   if ($?) {
517     $self->{"error"} = "dvips : $!";
518     $self->cleanup();
519     return 0;
520   }
521   $form->{tmpfile} =~ s/dvi$/ps/;
522
523   $self->cleanup();
524
525   return 1;
526 }
527
528 sub convert_to_pdf {
529   my ($self) = @_;
530   my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"});
531
532   # Convert the tex file to PDF
533
534   if (!chdir("$userspath")) {
535     $self->{"error"} = "chdir : $!";
536     $self->cleanup();
537     return 0;
538   }
539
540   $form->{tmpfile} =~ s/\Q$userspath\E\///g;
541
542   for (my $run = 1; $run <= 2; $run++) {
543     system("pdflatex --interaction=nonstopmode $form->{tmpfile} " .
544            "> $form->{tmpfile}.err");
545     if ($?) {
546       $self->{"error"} = $form->cleanup();
547       $self->cleanup();
548       return 0;
549     }
550   }
551
552   $form->{tmpfile} =~ s/tex$/pdf/;
553
554   $self->cleanup();
555 }
556
557 sub get_mime_type() {
558   my ($self) = @_;
559
560   if ($self->{"form"}->{"format"} =~ /postscript/i) {
561     return "application/postscript";
562   } else {
563     return "application/pdf";
564   }
565 }
566
567 sub uses_temp_file {
568   return 1;
569 }
570
571
572 ####
573 #### HTMLTemplate
574 ####
575
576 package HTMLTemplate;
577
578 use vars qw(@ISA);
579
580 @ISA = qw(LaTeXTemplate);
581
582 sub new {
583   my $type = shift;
584
585   return $type->SUPER::new(@_);
586 }
587
588 sub format_string {
589   my ($self, $variable) = @_;
590   my $form = $self->{"form"};
591
592   $variable = $main::locale->quote_special_chars('Template/HTML', $variable);
593
594   # Allow some HTML markup to be converted into the output format's
595   # corresponding markup code, e.g. bold or italic.
596   my @markup_replace = ('b', 'i', 's', 'u', 'sub', 'sup');
597
598   foreach my $key (@markup_replace) {
599     $variable =~ s/\&lt;(\/?)${key}\&gt;/<$1${key}>/g;
600   }
601
602   return $variable;
603 }
604
605 sub get_mime_type() {
606   my ($self) = @_;
607
608   if ($self->{"form"}->{"format"} =~ /postscript/i) {
609     return "application/postscript";
610   } elsif ($self->{"form"}->{"format"} =~ /pdf/i) {
611     return "application/pdf";
612   } else {
613     return "text/html";
614   }
615 }
616
617 sub uses_temp_file {
618   my ($self) = @_;
619
620   if ($self->{"form"}->{"format"} =~ /postscript/i) {
621     return 1;
622   } elsif ($self->{"form"}->{"format"} =~ /pdf/i) {
623     return 1;
624   } else {
625     return 0;
626   }
627 }
628
629 sub convert_to_postscript {
630   my ($self) = @_;
631   my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"});
632
633   # Convert the HTML file to postscript
634
635   if (!chdir("$userspath")) {
636     $self->{"error"} = "chdir : $!";
637     $self->cleanup();
638     return 0;
639   }
640
641   $form->{"tmpfile"} =~ s/\Q$userspath\E\///g;
642   my $psfile = $form->{"tmpfile"};
643   $psfile =~ s/.html/.ps/;
644   if ($psfile eq $form->{"tmpfile"}) {
645     $psfile .= ".ps";
646   }
647
648   system("html2ps -f html2ps-config < $form->{tmpfile} > $psfile");
649   if ($?) {
650     $self->{"error"} = $form->cleanup();
651     $self->cleanup();
652     return 0;
653   }
654
655   $form->{"tmpfile"} = $psfile;
656
657   $self->cleanup();
658
659   return 1;
660 }
661
662 sub convert_to_pdf {
663   my ($self) = @_;
664   my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"});
665
666   # Convert the HTML file to PDF
667
668   if (!chdir("$userspath")) {
669     $self->{"error"} = "chdir : $!";
670     $self->cleanup();
671     return 0;
672   }
673
674   $form->{"tmpfile"} =~ s/\Q$userspath\E\///g;
675   my $pdffile = $form->{"tmpfile"};
676   $pdffile =~ s/.html/.pdf/;
677   if ($pdffile eq $form->{"tmpfile"}) {
678     $pdffile .= ".pdf";
679   }
680
681   system("html2ps -f html2ps-config < $form->{tmpfile} | ps2pdf - $pdffile");
682   if ($?) {
683     $self->{"error"} = $form->cleanup();
684     $self->cleanup();
685     return 0;
686   }
687
688   $form->{"tmpfile"} = $pdffile;
689
690   $self->cleanup();
691
692   return 1;
693 }
694
695
696 ####
697 #### PlainTextTemplate
698 ####
699
700 package PlainTextTemplate;
701
702 use vars qw(@ISA);
703
704 @ISA = qw(LaTeXTemplate);
705
706 sub new {
707   my $type = shift;
708
709   return $type->SUPER::new(@_);
710 }
711
712 sub format_string {
713   my ($self, $variable) = @_;
714
715   return $variable;
716 }
717
718 sub get_mime_type {
719   return "text/plain";
720 }
721
722 sub parse {
723 }
724
725 1;
726
727 ####
728 #### OpenDocumentTemplate
729 ####
730
731 package OpenDocumentTemplate;
732
733 use POSIX 'setsid';
734 use vars qw(@ISA);
735
736 use Cwd;
737 # use File::Copy;
738 # use File::Spec;
739 # use File::Temp qw(:mktemp);
740 use IO::File;
741
742 @ISA = qw(SimpleTemplate);
743
744 sub new {
745   my $type = shift;
746
747   $self = $type->SUPER::new(@_);
748
749   foreach my $module (qw(Archive::Zip Text::Iconv)) {
750     eval("use ${module};");
751     if ($@) {
752       $self->{"form"}->error("The Perl module '${module}' could not be " .
753                              "loaded. Support for OpenDocument templates " .
754                              "does not work without it. Please install your " .
755                              "distribution's package or get the module from " .
756                              "CPAN ( http://www.cpan.org ).");
757     }
758   }
759
760   $self->{"rnd"} = int(rand(1000000));
761   $self->{"iconv"} = Text::Iconv->new($main::dbcharset, "UTF-8");
762
763   return $self;
764 }
765
766 sub substitute_vars {
767   my ($self, $text, @indices) = @_;
768
769   my $form = $self->{"form"};
770
771   while ($text =~ /\&lt;\%(.*?)\%\&gt;/) {
772     my $value = $form->{$1};
773
774     for (my $i = 0; $i < scalar(@indices); $i++) {
775       last unless (ref($value) eq "ARRAY");
776       $value = $value->[$indices[$i]];
777     }
778     substr($text, $-[0], $+[0] - $-[0]) = $self->format_string($value);
779   }
780
781   return $text;
782 }
783
784 sub parse_foreach {
785   my ($self, $var, $text, $start_tag, $end_tag, @indices) = @_;
786
787   my ($form, $new_contents) = ($self->{"form"}, "");
788
789   my $ary = $form->{$var};
790   for (my $i = 0; $i < scalar(@indices); $i++) {
791     last unless (ref($ary) eq "ARRAY");
792     $ary = $ary->[$indices[$i]];
793   }
794
795   for (my $i = 0; $i < scalar(@{$ary}); $i++) {
796     $form->{"__first__"} = $i == 0;
797     $form->{"__last__"} = ($i + 1) == scalar(@{$ary});
798     $form->{"__odd__"} = (($i + 1) % 2) == 1;
799     $form->{"__counter__"} = $i + 1;
800     my $new_text = $self->parse_block($text, (@indices, $i));
801     return undef unless (defined($new_text));
802     $new_contents .= $start_tag . $new_text . $end_tag;
803   }
804   map({ delete($form->{"__${_}__"}); } qw(first last odd counter));
805
806   return $new_contents;
807 }
808
809 sub find_end {
810   my ($self, $text, $pos, $var, $not) = @_;
811
812   my $depth = 1;
813   $pos = 0 unless ($pos);
814
815   while ($pos < length($text)) {
816     $pos++;
817
818     next if (substr($text, $pos - 1, 5) ne '&lt;%');
819
820     if ((substr($text, $pos + 4, 2) eq 'if') || (substr($text, $pos + 4, 3) eq 'for')) {
821       $depth++;
822
823     } elsif ((substr($text, $pos + 4, 4) eq 'else') && (1 == $depth)) {
824       if (!$var) {
825         $self->{"error"} = '<%else%> outside of <%if%> / <%ifnot%>.';
826         return undef;
827       }
828
829       my $block = substr($text, 0, $pos - 1);
830       substr($text, 0, $pos - 1) = "";
831       $text =~ s!^\&lt;\%[^\%]+\%\&gt;!!;
832       $text = '&lt;%if' . ($not ?  " " : "not ") . $var . '%&gt;' . $text;
833
834       return ($block, $text);
835
836     } elsif (substr($text, $pos + 4, 3) eq 'end') {
837       $depth--;
838       if ($depth == 0) {
839         my $block = substr($text, 0, $pos - 1);
840         substr($text, 0, $pos - 1) = "";
841         $text =~ s!^\&lt;\%[^\%]+\%\&gt;!!;
842
843         return ($block, $text);
844       }
845     }
846   }
847
848   return undef;
849 }
850
851 sub parse_block {
852   $main::lxdebug->enter_sub();
853
854   my ($self, $contents, @indices) = @_;
855
856   my $new_contents = "";
857
858   while ($contents ne "") {
859     if (substr($contents, 0, 1) eq "<") {
860       $contents =~ m|^<[^>]+>|;
861       my $tag = $&;
862       substr($contents, 0, length($&)) = "";
863
864       if ($tag =~ m|<table:table-row|) {
865         $contents =~ m|^(.*?)(</table:table-row[^>]*>)|;
866         my $table_row = $1;
867         my $end_tag = $2;
868         substr($contents, 0, length($1) + length($end_tag)) = "";
869
870         if ($table_row =~ m|\&lt;\%foreachrow\s+(.*?)\%\&gt;|) {
871           my $var = $1;
872
873           substr($table_row, length($`), length($&)) = "";
874
875           my ($t1, $t2) = $self->find_end($table_row, length($`));
876           if (!$t1) {
877             $self->{"error"} = "Unclosed <\%foreachrow\%>." unless ($self->{"error"});
878             $main::lxdebug->leave_sub();
879             return undef;
880           }
881
882           my $new_text = $self->parse_foreach($var, $t1 . $t2, $tag, $end_tag, @indices);
883           if (!defined($new_text)) {
884             $main::lxdebug->leave_sub();
885             return undef;
886           }
887           $new_contents .= $new_text;
888
889         } else {
890           my $new_text = $self->parse_block($table_row, @indices);
891           if (!defined($new_text)) {
892             $main::lxdebug->leave_sub();
893             return undef;
894           }
895           $new_contents .= $tag . $new_text . $end_tag;
896         }
897
898       } else {
899         $new_contents .= $tag;
900       }
901
902     } else {
903       $contents =~ /^[^<]+/;
904       my $text = $&;
905
906       my $pos_if = index($text, '&lt;%if');
907       my $pos_foreach = index($text, '&lt;%foreach');
908
909       if ((-1 == $pos_if) && (-1 == $pos_foreach)) {
910         substr($contents, 0, length($text)) = "";
911         $new_contents .= $self->substitute_vars($text, @indices);
912         next;
913       }
914
915       if ((-1 == $pos_if) || ((-1 != $pos_foreach) && ($pos_if > $pos_foreach))) {
916         $new_contents .= $self->substitute_vars(substr($contents, 0, $pos_foreach), @indices);
917         substr($contents, 0, $pos_foreach) = "";
918
919         if ($contents !~ m|^\&lt;\%foreach (.*?)\%\&gt;|) {
920           $self->{"error"} = "Malformed <\%foreach\%>.";
921           $main::lxdebug->leave_sub();
922           return undef;
923         }
924
925         my $var = $1;
926
927         substr($contents, 0, length($&)) = "";
928
929         my $block;
930         ($block, $contents) = $self->find_end($contents);
931         if (!$block) {
932           $self->{"error"} = "Unclosed <\%foreach\%>." unless ($self->{"error"});
933           $main::lxdebug->leave_sub();
934           return undef;
935         }
936
937         my $new_text = $self->parse_foreach($var, $block, "", "", @indices);
938         if (!defined($new_text)) {
939           $main::lxdebug->leave_sub();
940           return undef;
941         }
942         $new_contents .= $new_text;
943
944       } else {
945         $new_contents .= $self->substitute_vars(substr($contents, 0, $pos_if), @indices);
946         substr($contents, 0, $pos_if) = "";
947
948         if ($contents !~ m|^\&lt;\%if\s*(not)?\s+(.*?)\%\&gt;|) {
949           $self->{"error"} = "Malformed <\%if\%>.";
950           $main::lxdebug->leave_sub();
951           return undef;
952         }
953
954         my ($not, $var) = ($1, $2);
955
956         substr($contents, 0, length($&)) = "";
957
958         ($block, $contents) = $self->find_end($contents, 0, $var, $not);
959         if (!$block) {
960           $self->{"error"} = "Unclosed <\%if${not}\%>." unless ($self->{"error"});
961           $main::lxdebug->leave_sub();
962           return undef;
963         }
964
965         my $value = $self->{"form"}->{$var};
966         for (my $i = 0; $i < scalar(@indices); $i++) {
967           last unless (ref($value) eq "ARRAY");
968           $value = $value->[$indices[$i]];
969         }
970
971         if (($not && !$value) || (!$not && $value)) {
972           my $new_text = $self->parse_block($block, @indices);
973           if (!defined($new_text)) {
974             $main::lxdebug->leave_sub();
975             return undef;
976           }
977           $new_contents .= $new_text;
978         }
979       }
980     }
981   }
982
983   $main::lxdebug->leave_sub();
984
985   return $new_contents;
986 }
987
988 sub parse {
989   $main::lxdebug->enter_sub();
990
991   my $self = $_[0];
992   local *OUT = $_[1];
993   my $form = $self->{"form"};
994
995   close(OUT);
996
997   my $file_name;
998   if ($form->{"IN"} =~ m|^/|) {
999     $file_name = $form->{"IN"};
1000   } else {
1001     $file_name = $form->{"templates"} . "/" . $form->{"IN"};
1002   }
1003
1004   my $zip = Archive::Zip->new();
1005   if (Archive::Zip::AZ_OK != $zip->read($file_name)) {
1006     $self->{"error"} = "File not found/is not a OpenDocument file.";
1007     $main::lxdebug->leave_sub();
1008     return 0;
1009   }
1010
1011   my $contents = $zip->contents("content.xml");
1012   if (!$contents) {
1013     $self->{"error"} = "File is not a OpenDocument file.";
1014     $main::lxdebug->leave_sub();
1015     return 0;
1016   }
1017
1018   my $rnd = $self->{"rnd"};
1019   my $new_styles = qq|<style:style style:name="TLXO${rnd}BOLD" style:family="text">
1020 <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
1021 </style:style>
1022 <style:style style:name="TLXO${rnd}ITALIC" style:family="text">
1023 <style:text-properties fo:font-style="italic" style:font-style-asian="italic" style:font-style-complex="italic"/>
1024 </style:style>
1025 <style:style style:name="TLXO${rnd}UNDERLINE" style:family="text">
1026 <style:text-properties style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color"/>
1027 </style:style>
1028 <style:style style:name="TLXO${rnd}STRIKETHROUGH" style:family="text">
1029 <style:text-properties style:text-line-through-style="solid"/>
1030 </style:style>
1031 <style:style style:name="TLXO${rnd}SUPER" style:family="text">
1032 <style:text-properties style:text-position="super 58%"/>
1033 </style:style>
1034 <style:style style:name="TLXO${rnd}SUB" style:family="text">
1035 <style:text-properties style:text-position="sub 58%"/>
1036 </style:style>
1037 |;
1038
1039   $contents =~ s|</office:automatic-styles>|${new_styles}</office:automatic-styles>|;
1040   $contents =~ s|[\n\r]||gm;
1041
1042   my $new_contents = $self->parse_block($contents);
1043   if (!defined($new_contents)) {
1044     $main::lxdebug->leave_sub();
1045     return 0;
1046   }
1047
1048 #   $new_contents =~ s|>|>\n|g;
1049
1050   $zip->contents("content.xml", $new_contents);
1051
1052   my $styles = $zip->contents("styles.xml");
1053   if ($contents) {
1054     my $new_styles = $self->parse_block($styles);
1055     if (!defined($new_contents)) {
1056       $main::lxdebug->leave_sub();
1057       return 0;
1058     }
1059     $zip->contents("styles.xml", $new_styles);
1060   }
1061
1062   $zip->writeToFileNamed($form->{"tmpfile"}, 1);
1063
1064   my $res = 1;
1065   if ($form->{"format"} =~ /pdf/) {
1066     $res = $self->convert_to_pdf();
1067   }
1068
1069   $main::lxdebug->leave_sub();
1070   return $res;
1071 }
1072
1073 sub is_xvfb_running {
1074   $main::lxdebug->enter_sub();
1075
1076   my ($self) = @_;
1077
1078   local *IN;
1079   my $dfname = $self->{"userspath"} . "/xvfb_display";
1080   my $display;
1081
1082   $main::lxdebug->message(LXDebug::DEBUG2, "    Looking for $dfname\n");
1083   if ((-f $dfname) && open(IN, $dfname)) {
1084     my $pid = <IN>;
1085     chomp($pid);
1086     $display = <IN>;
1087     chomp($display);
1088     my $xauthority = <IN>;
1089     chomp($xauthority);
1090     close(IN);
1091
1092     $main::lxdebug->message(LXDebug::DEBUG2, "      found with $pid and $display\n");
1093
1094     if ((! -d "/proc/$pid") || !open(IN, "/proc/$pid/cmdline")) {
1095       $main::lxdebug->message(LXDebug::DEBUG2, "  no/wrong process #1\n");
1096       unlink($dfname, $xauthority);
1097       $main::lxdebug->leave_sub();
1098       return undef;
1099     }
1100     my $line = <IN>;
1101     close(IN);
1102     if ($line !~ /xvfb/i) {
1103       $main::lxdebug->message(LXDebug::DEBUG2, "      no/wrong process #2\n");
1104       unlink($dfname, $xauthority);
1105       $main::lxdebug->leave_sub();
1106       return undef;
1107     }
1108
1109     $ENV{"XAUTHORITY"} = $xauthority;
1110     $ENV{"DISPLAY"} = $display;
1111   } else {
1112     $main::lxdebug->message(LXDebug::DEBUG2, "      not found\n");
1113   }
1114
1115   $main::lxdebug->leave_sub();
1116
1117   return $display;
1118 }
1119
1120 sub spawn_xvfb {
1121   $main::lxdebug->enter_sub();
1122
1123   my ($self) = @_;
1124
1125   $main::lxdebug->message(LXDebug::DEBUG2, "spawn_xvfb()\n");
1126
1127   my $display = $self->is_xvfb_running();
1128
1129   if ($display) {
1130     $main::lxdebug->leave_sub();
1131     return $display;
1132   }
1133
1134   $display = 99;
1135   while ( -f "/tmp/.X${display}-lock") {
1136     $display++;
1137   }
1138   $display = ":${display}";
1139   $main::lxdebug->message(LXDebug::DEBUG2, "  display $display\n");
1140
1141   my $mcookie = `mcookie`;
1142   die("Installation error: mcookie not found.") if ($? != 0);
1143   chomp($mcookie);
1144
1145   $main::lxdebug->message(LXDebug::DEBUG2, "  mcookie $mcookie\n");
1146
1147   my $xauthority = "/tmp/.Xauthority-" . $$ . "-" . time() . "-" . int(rand(9999999));
1148   $ENV{"XAUTHORITY"} = $xauthority;
1149
1150   $main::lxdebug->message(LXDebug::DEBUG2, "  xauthority $xauthority\n");
1151
1152   system("xauth add \"${display}\" . \"${mcookie}\"");
1153   if ($? != 0) {
1154     $self->{"error"} = "Conversion to PDF failed because OpenOffice could not be started (xauth: $!)";
1155     $main::lxdebug->leave_sub();
1156     return undef;
1157   }
1158
1159   $main::lxdebug->message(LXDebug::DEBUG2, "  about to fork()\n");
1160
1161   my $pid = fork();
1162   if (0 == $pid) {
1163     $main::lxdebug->message(LXDebug::DEBUG2, "  Child execing\n");
1164     exec($main::xvfb_bin, $display, "-screen", "0", "640x480x8", "-nolisten", "tcp");
1165   }
1166   sleep(3);
1167   $main::lxdebug->message(LXDebug::DEBUG2, "  parent dont sleeping\n");
1168
1169   local *OUT;
1170   my $dfname = $self->{"userspath"} . "/xvfb_display";
1171   if (!open(OUT, ">$dfname")) {
1172     $self->{"error"} = "Conversion to PDF failed because OpenOffice could not be started ($dfname: $!)";
1173     unlink($xauthority);
1174     kill($pid);
1175     $main::lxdebug->leave_sub();
1176     return undef;
1177   }
1178   print(OUT "$pid\n$display\n$xauthority\n");
1179   close(OUT);
1180
1181   $main::lxdebug->message(LXDebug::DEBUG2, "  parent re-testing\n");
1182
1183   if (!$self->is_xvfb_running()) {
1184     $self->{"error"} = "Conversion to PDF failed because OpenOffice could not be started.";
1185     unlink($xauthority, $dfname);
1186     kill($pid);
1187     $main::lxdebug->leave_sub();
1188     return undef;
1189   }
1190
1191   $main::lxdebug->message(LXDebug::DEBUG2, "  spawn OK\n");
1192
1193   $main::lxdebug->leave_sub();
1194
1195   return $display;
1196 }
1197
1198 sub is_openoffice_running {
1199   $main::lxdebug->enter_sub();
1200
1201   system("./scripts/oo-uno-test-conn.py $main::openofficeorg_daemon_port " .
1202          "> /dev/null 2> /dev/null");
1203   my $res = $? == 0;
1204   $main::lxdebug->message(LXDebug::DEBUG2, "  is_openoffice_running(): $?\n");
1205
1206   $main::lxdebug->leave_sub();
1207
1208   return $res;
1209 }
1210
1211 sub spawn_openoffice {
1212   $main::lxdebug->enter_sub();
1213
1214   my ($self) = @_;
1215
1216   $main::lxdebug->message(LXDebug::DEBUG2, "spawn_openoffice()\n");
1217
1218   my ($try, $spawned_oo, $res);
1219
1220   $res = 0;
1221   for ($try = 0; $try < 15; $try++) {
1222     if ($self->is_openoffice_running()) {
1223       $res = 1;
1224       last;
1225     }
1226
1227     if (!$spawned_oo) {
1228       my $pid = fork();
1229       if (0 == $pid) {
1230         $main::lxdebug->message(LXDebug::DEBUG2, "  Child daemonizing\n");
1231         chdir('/');
1232         open(STDIN, '/dev/null');
1233         open(STDOUT, '>/dev/null');
1234         my $new_pid = fork();
1235         exit if ($new_pid);
1236         my $ssres = setsid();
1237         $main::lxdebug->message(LXDebug::DEBUG2, "  Child execing\n");
1238         my @cmdline = ($main::openofficeorg_writer_bin,
1239                        "-minimized", "-norestore", "-nologo", "-nolockcheck",
1240                        "-headless",
1241                        "-accept=socket,host=localhost,port=" .
1242                        $main::openofficeorg_daemon_port . ";urp;");
1243         exec(@cmdline);
1244       }
1245
1246       $main::lxdebug->message(LXDebug::DEBUG2, "  Parent after fork\n");
1247       $spawned_oo = 1;
1248       sleep(3);
1249     }
1250
1251     sleep($try >= 5 ? 2 : 1);
1252   }
1253
1254   if (!$res) {
1255     $self->{"error"} = "Conversion from OpenDocument to PDF failed because " .
1256       "OpenOffice could not be started.";
1257   }
1258
1259   $main::lxdebug->leave_sub();
1260
1261   return $res;
1262 }
1263
1264 sub convert_to_pdf {
1265   $main::lxdebug->enter_sub();
1266
1267   my ($self) = @_;
1268
1269   my $form = $self->{"form"};
1270
1271   my $filename = $form->{"tmpfile"};
1272   $filename =~ s/.odt$//;
1273   if (substr($filename, 0, 1) ne "/") {
1274     $filename = getcwd() . "/${filename}";
1275   }
1276
1277   if (substr($self->{"userspath"}, 0, 1) eq "/") {
1278     $ENV{'HOME'} = $self->{"userspath"};
1279   } else {
1280     $ENV{'HOME'} = getcwd() . "/" . $self->{"userspath"};
1281   }
1282
1283   if (!$self->spawn_xvfb()) {
1284     $main::lxdebug->leave_sub();
1285     return 0;
1286   }
1287
1288   my @cmdline;
1289   if (!$main::openofficeorg_daemon) {
1290     @cmdline = ($main::openofficeorg_writer_bin,
1291                 "-minimized", "-norestore", "-nologo", "-nolockcheck",
1292                 "-headless",
1293                 "file:${filename}.odt",
1294                 "macro://" . (split('/', $filename))[-1] .
1295                 "/Standard.Conversion.ConvertSelfToPDF()");
1296   } else {
1297     if (!$self->spawn_openoffice()) {
1298       $main::lxdebug->leave_sub();
1299       return 0;
1300     }
1301
1302     @cmdline = ("./scripts/oo-uno-convert-pdf.py",
1303                 $main::openofficeorg_daemon_port,
1304                 "${filename}.odt");
1305   }
1306
1307   system(@cmdline);
1308
1309   my $res = $?;
1310   if (0 == $?) {
1311     $form->{"tmpfile"} =~ s/odt$/pdf/;
1312
1313     unlink($filename . ".odt");
1314
1315     $main::lxdebug->leave_sub();
1316     return 1;
1317
1318   }
1319
1320   unlink($filename . ".odt", $filename . ".pdf");
1321   $self->{"error"} = "Conversion from OpenDocument to PDF failed. " .
1322     "Exit code: $res";
1323
1324   $main::lxdebug->leave_sub();
1325   return 0;
1326 }
1327
1328 sub format_string {
1329   my ($self, $variable) = @_;
1330   my $form = $self->{"form"};
1331   my $iconv = $self->{"iconv"};
1332
1333   $variable = $main::locale->quote_special_chars('Template/OpenDocument', $variable);
1334
1335   # Allow some HTML markup to be converted into the output format's
1336   # corresponding markup code, e.g. bold or italic.
1337   my $rnd = $self->{"rnd"};
1338   my %markup_replace = ("b" => "BOLD", "i" => "ITALIC", "s" => "STRIKETHROUGH",
1339                         "u" => "UNDERLINE", "sup" => "SUPER", "sub" => "SUB");
1340
1341   foreach my $key (keys(%markup_replace)) {
1342     my $value = $markup_replace{$key};
1343     $variable =~ s|\&lt;${key}\&gt;|<text:span text:style-name=\"TLXO${rnd}${value}\">|gi; #"
1344     $variable =~ s|\&lt;/${key}\&gt;|</text:span>|gi;
1345   }
1346
1347   return $iconv->convert($variable);
1348 }
1349
1350 sub get_mime_type() {
1351   if ($self->{"form"}->{"format"} =~ /pdf/) {
1352     return "application/pdf";
1353   } else {
1354     return "application/vnd.oasis.opendocument.text";
1355   }
1356 }
1357
1358 sub uses_temp_file {
1359   return 1;
1360 }
1361
1362
1363 ##########################################################
1364 ####
1365 #### XMLTemplate
1366 ####
1367 ##########################################################
1368
1369 package XMLTemplate; 
1370
1371 use vars qw(@ISA);
1372
1373 @ISA = qw(HTMLTemplate);
1374
1375 sub new {
1376   #evtl auskommentieren
1377   my $type = shift;
1378
1379   return $type->SUPER::new(@_);
1380 }
1381
1382 sub format_string {
1383   my ($self, $variable) = @_;
1384   my $form = $self->{"form"};
1385
1386   $variable = $main::locale->quote_special_chars('Template/XML', $variable);
1387
1388   # Allow no markup to be converted into the output format
1389   my @markup_replace = ('b', 'i', 's', 'u', 'sub', 'sup');
1390
1391   foreach my $key (@markup_replace) {
1392     $variable =~ s/\&lt;(\/?)${key}\&gt;//g;
1393   }
1394
1395   return $variable;
1396 }
1397
1398 sub get_mime_type() {
1399   my ($self) = @_;
1400
1401   if ($self->{"form"}->{"format"} =~ /elsterwinston/i) {
1402     return "application/xml ";
1403   } elsif ($self->{"form"}->{"format"} =~ /elstertaxbird/i) {
1404     return "application/x-taxbird";
1405   } else {
1406     return "text";
1407   }
1408 }
1409
1410 sub uses_temp_file {
1411   # tempfile needet for XML Output
1412   return 1;
1413 }
1414
1415 1;