Test auf Übereinstimmung mit regulären Ausdrücken in <%if...%>-Blöcken ohne Berücksic...
[kivitendo-erp.git] / SL / Template.pm
index 5763eee..6dce41b 100644 (file)
@@ -28,12 +28,27 @@ 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->{error}     = undef;
+
+  $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->{substitute_vars_re} = "$self->{tag_start_qm}(.+?)$self->{tag_end_qm}";
 }
 
 sub cleanup {
@@ -64,6 +79,47 @@ 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;
+}
+
 1;
 
 ####
@@ -79,36 +135,16 @@ use vars qw(@ISA);
 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 +157,393 @@ 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;
-  }
-  @_ = <IN>;
-  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/);
+  for (my $i = 0; $i < scalar(@{$ary}); $i++) {
+    $form->{"__first__"}   = $i == 1;
+    $form->{"__last__"}    = ($i + 1) == scalar(@{$ary});
+    $form->{"__odd__"}     = (($i + 1) % 2) == 1;
+    $form->{"__counter__"} = $i + 1;
+
+    if (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?)*$//;
+      my $_description = $description_array->[$i];
+      while ($_description =~ /\\newline/) {
+        $lines++;
+        $_description =~ s/\\newline//;
+      }
+      $lines++;
 
-    # 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;
+      if ($current_page == 1) {
+        $lpp = $self->{"lines_on_first_page"};
+      } else {
+        $lpp = $self->{"lines_on_second_page"};
+      }
+
+      # Yes we need a manual page break -- or the user has forced one
+      if ((($current_line + $lines) > $lpp) || ($description_array->[$i] =~ /<pagebreak>/) || ($longdescription_array->[$i] =~ /<pagebreak>/)) {
+        my $pb = $self->{"pagebreak_block"};
+
+        # 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;
 
-      while ($_ = shift) {
-        last if (/\s*<%end pagebreak%>/);
-        $pagebreak .= $_;
+        $current_page++;
+        $current_line = 0;
       }
+      $current_line += $lines;
     }
 
-    if (/\s*<%foreach /) {
+    if ($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] =~ /<pagebreak>/) {
-          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($&)) = "";
+
+      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;
+      }
+
+      my $new_text = $self->parse_foreach($var, $block, "", "", @indices);
+      if (!defined($new_text)) {
+        $main::lxdebug->leave_sub();
+        return undef;
+      }
+      $new_contents .= $new_text;
+
+    } else {
+      $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
+                             "(.*?)(?<!\\)"  # $7 -- Gequoteter String -- direkter Vergleich mit eq bzw. ne oder Patternmatching; Escapete Anführungs als Teil des Strings belassen
+                           |
+                             (\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($&)) = "";
+
+      ($block, $contents) = $self->find_end($contents, 0, $var, $not);
+      if (!$block) {
+        $self->{"error"} = "Unclosed $self->{tag_start}if${not}$self->{tag_end}." unless ($self->{"error"});
+        $main::lxdebug->leave_sub();
+        return undef;
+      }
 
-        $_ = $par;
+      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 {
-        while ($_ = shift) {
-          last if (/\s*<%end /);
+        $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;
         }
-        next;
+        $new_contents .= $new_text;
       }
     }
+  }
 
-    # check for <%include filename%>
-    if (/\s*<%include /) {
+  $main::lxdebug->leave_sub();
 
-      # get the filename
-      chomp $var;
-      $var =~ s/\s*<%include (.+?)%>/$1/;
+  return $new_contents;
+}
 
-      # mangle filename
-      $var =~ s/(\/|\.\.)//g;
+sub parse_first_line {
+  my $self = shift;
+  my $line = shift || "";
 
-      # prevent the infinite loop!
-      next if ($form->{"$var"});
+  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;
+    }
+
+    $self->set_tag_style($1, $2);
+  }
 
-      open(INC, $form->{templates} . "/$var")
-        or $form->error($self->cleanup . $form->{templates} . "/$var : $!");
-      unshift(@_, <INC>);
-      close(INC);
+  return 1;
+}
+
+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*<!--';
+    $comment_end   = '>\s*';
+  } else {
+    $comment_start = '\s*\#';
+  }
+
+  my $num_lines = scalar @{ $lines };
+  my $i         = 0;
+
+  while ($i < $num_lines) {
+    my $line = $lines->[$i];
+
+    if ($line !~ m/^${comment_start}\s*config\s*:(.*)${comment_end}$/i) {
+      $i++;
       next;
     }
 
-    while (/<%(.*?)%>/) {
-      substr($_, $-[0], $+[0] - $-[0]) = $self->format_string($form->{$1});
+    $self->_parse_config_option($1);
+    splice @{ $lines }, $i, 1;
+    $num_lines--;
+  }
+}
+
+sub _force_mandatory_packages {
+  my $self  = shift;
+  my $lines = shift;
+
+  my (%used_packages, $document_start_line);
+
+  foreach my $i (0 .. scalar @{ $lines } - 1) {
+    if ($lines->[$i] =~ m/\\usepackage[^{]*{(.*?)}/) {
+      $used_packages{$1} = 1;
+
+    } elsif ($lines->[$i] =~ m/\\begin{document}/) {
+      $document_start_line = $i;
+      last;
+
     }
-    print OUT;
   }
 
+  $document_start_line = scalar @{ $lines } - 1 if (!defined $document_start_line);
+
+  if (!$used_packages{textcomp}) {
+    splice @{ $lines }, $document_start_line, 0, "\\usepackage{textcomp}\n";
+    $document_start_line++;
+  }
+}
+
+sub parse {
+  my $self = $_[0];
+  local *OUT = $_[1];
+  my $form = $self->{"form"};
+
+  if (!open(IN, "$form->{templates}/$form->{IN}")) {
+    $self->{"error"} = "$!";
+    return 0;
+  }
+  my @lines = <IN>;
+  close(IN);
+
+  $self->_parse_config_lines(\@lines);
+  $self->_force_mandatory_packages(\@lines) if (ref $self eq 'LaTeXTemplate');
+
+  my $contents = join("", @lines);
+
+  # detect pagebreak block and its parameters
+  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) {
+    $self->{"chars_per_line"} = $1;
+    $self->{"lines_on_first_page"} = $2;
+    $self->{"lines_on_second_page"} = $3;
+    $self->{"pagebreak_block"} = $4;
+
+    substr($contents, length($`), length($&)) = "";
+  }
+
+  $self->{"forced_pagebreaks"} = [];
+
+  my $new_contents = $self->parse_block($contents);
+  if (!defined($new_contents)) {
+    $main::lxdebug->leave_sub();
+    return 0;
+  }
+
+  print(OUT $new_contents);
+
   if ($form->{"format"} =~ /postscript/i) {
-    return $self->convert_to_postscript($two_passes);
+    return $self->convert_to_postscript();
   } elsif ($form->{"format"} =~ /pdf/i) {
-    return $self->convert_to_pdf($two_passes);
+    return $self->convert_to_pdf();
   } else {
     return 1;
   }
 }
 
 sub convert_to_postscript {
-  my ($self, $two_passes) = @_;
+  my ($self) = @_;
   my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"});
 
   # Convert the tex file to postscript
@@ -356,17 +554,12 @@ sub convert_to_postscript {
     return 0;
   }
 
-  $form->{tmpfile} =~ s/$userspath\///g;
+  $form->{tmpfile} =~ s/\Q$userspath\E\///g;
 
-  system("latex --interaction=nonstopmode $form->{tmpfile} " .
-         "> $form->{tmpfile}.err");
-  if ($?) {
-    $self->{"error"} = $form->cleanup();
-    $self->cleanup();
-    return 0;
-  }
-  if ($two_passes) {
-    system("latex --interaction=nonstopmode $form->{tmpfile} " .
+  my $latex = $self->_get_latex_path();
+
+  for (my $run = 1; $run <= 2; $run++) {
+    system("${latex} --interaction=nonstopmode $form->{tmpfile} " .
            "> $form->{tmpfile}.err");
     if ($?) {
       $self->{"error"} = $form->cleanup();
@@ -391,7 +584,7 @@ sub convert_to_postscript {
 }
 
 sub convert_to_pdf {
-  my ($self, $two_passes) = @_;
+  my ($self) = @_;
   my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"});
 
   # Convert the tex file to PDF
@@ -402,18 +595,12 @@ sub convert_to_pdf {
     return 0;
   }
 
-  $form->{tmpfile} =~ s/$userspath\///g;
+  $form->{tmpfile} =~ s/\Q$userspath\E\///g;
 
-  system("pdflatex --interaction=nonstopmode $form->{tmpfile} " .
-         "> $form->{tmpfile}.err");
-  if ($?) {
-    $self->{"error"} = $form->cleanup();
-    $self->cleanup();
-    return 0;
-  }
+  my $latex = $self->_get_latex_path();
 
-  if ($two_passes) {
-    system("pdflatex --interaction=nonstopmode $form->{tmpfile} " .
+  for (my $run = 1; $run <= 2; $run++) {
+    system("${latex} --interaction=nonstopmode $form->{tmpfile} " .
            "> $form->{tmpfile}.err");
     if ($?) {
       $self->{"error"} = $form->cleanup();
@@ -427,6 +614,10 @@ sub convert_to_pdf {
   $self->cleanup();
 }
 
+sub _get_latex_path {
+  return $main::latex_bin || 'pdflatex';
+}
+
 sub get_mime_type() {
   my ($self) = @_;
 
@@ -462,18 +653,11 @@ sub format_string {
   my ($self, $variable) = @_;
   my $form = $self->{"form"};
 
-  my %replace =
-    ('order' => ['<', '>', quotemeta("\n")],
-     '<'             => '&lt;',
-     '>'             => '&gt;',
-     quotemeta("\n") => '<br>',
-     );
-
-  map({ $variable =~ s/$_/$replace{$_}/g; } @{ $replace{"order"} });
+  $variable = $main::locale->quote_special_chars('Template/HTML', $variable);
 
   # Allow some HTML markup to be converted into the output format's
   # corresponding markup code, e.g. bold or italic.
-  my @markup_replace = ('b', 'i', 's', 'u');
+  my @markup_replace = ('b', 'i', 's', 'u', 'sub', 'sup');
 
   foreach my $key (@markup_replace) {
     $variable =~ s/\&lt;(\/?)${key}\&gt;/<$1${key}>/g;
@@ -483,21 +667,134 @@ sub format_string {
 }
 
 sub get_mime_type() {
-  return "text/html";
+  my ($self) = @_;
+
+  if ($self->{"form"}->{"format"} =~ /postscript/i) {
+    return "application/postscript";
+  } elsif ($self->{"form"}->{"format"} =~ /pdf/i) {
+    return "application/pdf";
+  } else {
+    return "text/html";
+  }
 }
 
 sub uses_temp_file {
-  return 0;
+  my ($self) = @_;
+
+  if ($self->{"form"}->{"format"} =~ /postscript/i) {
+    return 1;
+  } elsif ($self->{"form"}->{"format"} =~ /pdf/i) {
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+sub convert_to_postscript {
+  my ($self) = @_;
+  my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"});
+
+  # Convert the HTML file to postscript
+
+  if (!chdir("$userspath")) {
+    $self->{"error"} = "chdir : $!";
+    $self->cleanup();
+    return 0;
+  }
+
+  $form->{"tmpfile"} =~ s/\Q$userspath\E\///g;
+  my $psfile = $form->{"tmpfile"};
+  $psfile =~ s/.html/.ps/;
+  if ($psfile eq $form->{"tmpfile"}) {
+    $psfile .= ".ps";
+  }
+
+  system("html2ps -f html2ps-config < $form->{tmpfile} > $psfile");
+  if ($?) {
+    $self->{"error"} = $form->cleanup();
+    $self->cleanup();
+    return 0;
+  }
+
+  $form->{"tmpfile"} = $psfile;
+
+  $self->cleanup();
+
+  return 1;
+}
+
+sub convert_to_pdf {
+  my ($self) = @_;
+  my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"});
+
+  # Convert the HTML file to PDF
+
+  if (!chdir("$userspath")) {
+    $self->{"error"} = "chdir : $!";
+    $self->cleanup();
+    return 0;
+  }
+
+  $form->{"tmpfile"} =~ s/\Q$userspath\E\///g;
+  my $pdffile = $form->{"tmpfile"};
+  $pdffile =~ s/.html/.pdf/;
+  if ($pdffile eq $form->{"tmpfile"}) {
+    $pdffile .= ".pdf";
+  }
+
+  system("html2ps -f html2ps-config < $form->{tmpfile} | ps2pdf - $pdffile");
+  if ($?) {
+    $self->{"error"} = $form->cleanup();
+    $self->cleanup();
+    return 0;
+  }
+
+  $form->{"tmpfile"} = $pdffile;
+
+  $self->cleanup();
+
+  return 1;
+}
+
+
+####
+#### PlainTextTemplate
+####
+
+package PlainTextTemplate;
+
+use vars qw(@ISA);
+
+@ISA = qw(LaTeXTemplate);
+
+sub new {
+  my $type = shift;
+
+  return $type->SUPER::new(@_);
 }
 
+sub format_string {
+  my ($self, $variable) = @_;
 
+  return $variable;
+}
+
+sub get_mime_type {
+  return "text/plain";
+}
+
+sub parse {
+}
+
+1;
 
 ####
-#### HTMLTemplate
+#### OpenDocumentTemplate
 ####
 
 package OpenDocumentTemplate;
 
+use POSIX 'setsid';
 use vars qw(@ISA);
 
 use Cwd;
@@ -524,28 +821,12 @@ sub new {
     }
   }
 
-  $self->{"rnd"} = int(rand(1000000));
+  $self->{"rnd"}   = int(rand(1000000));
   $self->{"iconv"} = Text::Iconv->new($main::dbcharset, "UTF-8");
 
-  return $self;
-}
-
-sub substitute_vars {
-  my ($self, $text, @indices) = @_;
-
-  my $form = $self->{"form"};
+  $self->set_tag_style('&lt;%', '%&gt;');
 
-  while ($text =~ /\&lt;\%(.*?)\%\&gt;/) {
-    my $value = $form->{$1};
-
-    for (my $i = 0; $i < scalar(@indices); $i++) {
-      last unless (ref($value) eq "ARRAY");
-      $value = $value->[$indices[$i]];
-    }
-    substr($text, $-[0], $+[0] - $-[0]) = $self->format_string($value);
-  }
-
-  return $text;
+  return $self;
 }
 
 sub parse_foreach {
@@ -553,21 +834,64 @@ sub parse_foreach {
 
   my ($form, $new_contents) = ($self->{"form"}, "");
 
-  my $ary = $form->{$var};
-  for (my $i = 0; $i < scalar(@indices); $i++) {
-    last unless (ref($ary) eq "ARRAY");
-    $ary = $ary->[$indices[$i]];
-  }
+  my $ary = $self->_get_loop_variable($var, 1, @indices);
 
   for (my $i = 0; $i < scalar(@{$ary}); $i++) {
+    $form->{"__first__"} = $i == 0;
+    $form->{"__last__"} = ($i + 1) == scalar(@{$ary});
+    $form->{"__odd__"} = (($i + 1) % 2) == 1;
+    $form->{"__counter__"} = $i + 1;
     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));
 
   return $new_contents;
 }
 
+sub find_end {
+  my ($self, $text, $pos, $var, $not) = @_;
+
+  my $depth = 1;
+  $pos = 0 unless ($pos);
+
+  while ($pos < length($text)) {
+    $pos++;
+
+    next if (substr($text, $pos - 1, 5) ne '&lt;%');
+
+    if ((substr($text, $pos + 4, 2) eq 'if') || (substr($text, $pos + 4, 3) eq 'for')) {
+      $depth++;
+
+    } elsif ((substr($text, $pos + 4, 4) eq 'else') && (1 == $depth)) {
+      if (!$var) {
+        $self->{"error"} = '<%else%> outside of <%if%> / <%ifnot%>.';
+        return undef;
+      }
+
+      my $block = substr($text, 0, $pos - 1);
+      substr($text, 0, $pos - 1) = "";
+      $text =~ s!^\&lt;\%[^\%]+\%\&gt;!!;
+      $text = '&lt;%if' . ($not ?  " " : "not ") . $var . '%&gt;' . $text;
+
+      return ($block, $text);
+
+    } elsif (substr($text, $pos + 4, 3) eq 'end') {
+      $depth--;
+      if ($depth == 0) {
+        my $block = substr($text, 0, $pos - 1);
+        substr($text, 0, $pos - 1) = "";
+        $text =~ s!^\&lt;\%[^\%]+\%\&gt;!!;
+
+        return ($block, $text);
+      }
+    }
+  }
+
+  return undef;
+}
+
 sub parse_block {
   $main::lxdebug->enter_sub();
 
@@ -590,16 +914,28 @@ sub parse_block {
         if ($table_row =~ m|\&lt;\%foreachrow\s+(.*?)\%\&gt;|) {
           my $var = $1;
 
-          $table_row =~ s|\&lt;\%foreachrow .*?\%\&gt;||g;
-          $table_row =~ s!\&lt;\%end(for|foreach)?row\s+${var}\%\&gt;!!g;
+          substr($table_row, length($`), length($&)) = "";
+
+          my ($t1, $t2) = $self->find_end($table_row, length($`));
+          if (!$t1) {
+            $self->{"error"} = "Unclosed <\%foreachrow\%>." unless ($self->{"error"});
+            $main::lxdebug->leave_sub();
+            return undef;
+          }
 
-          my $new_text = $self->parse_foreach($var, $table_row, $tag, $end_tag, @indices);
-          return undef unless (defined($new_text));
+          my $new_text = $self->parse_foreach($var, $t1 . $t2, $tag, $end_tag, @indices);
+          if (!defined($new_text)) {
+            $main::lxdebug->leave_sub();
+            return undef;
+          }
           $new_contents .= $new_text;
 
         } else {
           my $new_text = $self->parse_block($table_row, @indices);
-          return undef unless (defined($new_text));
+          if (!defined($new_text)) {
+            $main::lxdebug->leave_sub();
+            return undef;
+          }
           $new_contents .= $tag . $new_text . $end_tag;
         }
 
@@ -634,22 +970,26 @@ sub parse_block {
 
         substr($contents, 0, length($&)) = "";
 
-        if ($contents !~ m!\&lt;\%end\s*?(for)?\s+${var}\%\&gt;!) {
-          $self->{"error"} = "Unclosed <\%foreach\%>.";
+        my $block;
+        ($block, $contents) = $self->find_end($contents);
+        if (!$block) {
+          $self->{"error"} = "Unclosed <\%foreach\%>." unless ($self->{"error"});
           $main::lxdebug->leave_sub();
           return undef;
         }
 
-        substr($contents, 0, length($`) + length($&)) = "";
-        my $new_text = $self->parse_foreach($var, $`, "", "", @indices);
-        return undef unless (defined($new_text));
+        my $new_text = $self->parse_foreach($var, $block, "", "", @indices);
+        if (!defined($new_text)) {
+          $main::lxdebug->leave_sub();
+          return undef;
+        }
         $new_contents .= $new_text;
 
       } else {
         $new_contents .= $self->substitute_vars(substr($contents, 0, $pos_if), @indices);
         substr($contents, 0, $pos_if) = "";
 
-        if ($contents !~ m|^\&lt;\%if(not)?\s+(.*?)\%\&gt;|) {
+        if ($contents !~ m|^\&lt;\%if\s*(not)?\s+(.*?)\%\&gt;|) {
           $self->{"error"} = "Malformed <\%if\%>.";
           $main::lxdebug->leave_sub();
           return undef;
@@ -659,14 +999,13 @@ sub parse_block {
 
         substr($contents, 0, length($&)) = "";
 
-        if ($contents !~ m!\&lt;\%endif${not}\s+${var}\%\&gt;!) {
-          $self->{"error"} = "Unclosed <\%if${not}\%>.";
+        ($block, $contents) = $self->find_end($contents, 0, $var, $not);
+        if (!$block) {
+          $self->{"error"} = "Unclosed <\%if${not}\%>." unless ($self->{"error"});
           $main::lxdebug->leave_sub();
           return undef;
         }
 
-        substr($contents, 0, length($`) + length($&)) = "";
-
         my $value = $self->{"form"}->{$var};
         for (my $i = 0; $i < scalar(@indices); $i++) {
           last unless (ref($value) eq "ARRAY");
@@ -674,14 +1013,19 @@ sub parse_block {
         }
 
         if (($not && !$value) || (!$not && $value)) {
-          my $new_text = $self->parse_block($`, @indices);
-          return undef unless (defined($new_text));
+          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 $new_contents;
 }
 
@@ -694,8 +1038,15 @@ sub parse {
 
   close(OUT);
 
+  my $file_name;
+  if ($form->{"IN"} =~ m|^/|) {
+    $file_name = $form->{"IN"};
+  } else {
+    $file_name = $form->{"templates"} . "/" . $form->{"IN"};
+  }
+
   my $zip = Archive::Zip->new();
-  if (Archive::Zip::AZ_OK != $zip->read("$form->{templates}/$form->{IN}")) {
+  if (Archive::Zip::AZ_OK != $zip->read($file_name)) {
     $self->{"error"} = "File not found/is not a OpenDocument file.";
     $main::lxdebug->leave_sub();
     return 0;
@@ -720,17 +1071,38 @@ sub parse {
 </style:style>
 <style:style style:name="TLXO${rnd}STRIKETHROUGH" style:family="text">
 <style:text-properties style:text-line-through-style="solid"/>
-</style:style>|;
+</style:style>
+<style:style style:name="TLXO${rnd}SUPER" style:family="text">
+<style:text-properties style:text-position="super 58%"/>
+</style:style>
+<style:style style:name="TLXO${rnd}SUB" style:family="text">
+<style:text-properties style:text-position="sub 58%"/>
+</style:style>
+|;
 
   $contents =~ s|</office:automatic-styles>|${new_styles}</office:automatic-styles>|;
   $contents =~ s|[\n\r]||gm;
 
   my $new_contents = $self->parse_block($contents);
-  return 0 unless (defined($new_contents));
+  if (!defined($new_contents)) {
+    $main::lxdebug->leave_sub();
+    return 0;
+  }
 
 #   $new_contents =~ s|>|>\n|g;
 
   $zip->contents("content.xml", $new_contents);
+
+  my $styles = $zip->contents("styles.xml");
+  if ($contents) {
+    my $new_styles = $self->parse_block($styles);
+    if (!defined($new_contents)) {
+      $main::lxdebug->leave_sub();
+      return 0;
+    }
+    $zip->contents("styles.xml", $new_styles);
+  }
+
   $zip->writeToFileNamed($form->{"tmpfile"}, 1);
 
   my $res = 1;
@@ -742,7 +1114,200 @@ sub parse {
   return $res;
 }
 
+sub is_xvfb_running {
+  $main::lxdebug->enter_sub();
+
+  my ($self) = @_;
+
+  local *IN;
+  my $dfname = $self->{"userspath"} . "/xvfb_display";
+  my $display;
+
+  $main::lxdebug->message(LXDebug::DEBUG2, "    Looking for $dfname\n");
+  if ((-f $dfname) && open(IN, $dfname)) {
+    my $pid = <IN>;
+    chomp($pid);
+    $display = <IN>;
+    chomp($display);
+    my $xauthority = <IN>;
+    chomp($xauthority);
+    close(IN);
+
+    $main::lxdebug->message(LXDebug::DEBUG2, "      found with $pid and $display\n");
+
+    if ((! -d "/proc/$pid") || !open(IN, "/proc/$pid/cmdline")) {
+      $main::lxdebug->message(LXDebug::DEBUG2, "  no/wrong process #1\n");
+      unlink($dfname, $xauthority);
+      $main::lxdebug->leave_sub();
+      return undef;
+    }
+    my $line = <IN>;
+    close(IN);
+    if ($line !~ /xvfb/i) {
+      $main::lxdebug->message(LXDebug::DEBUG2, "      no/wrong process #2\n");
+      unlink($dfname, $xauthority);
+      $main::lxdebug->leave_sub();
+      return undef;
+    }
+
+    $ENV{"XAUTHORITY"} = $xauthority;
+    $ENV{"DISPLAY"} = $display;
+  } else {
+    $main::lxdebug->message(LXDebug::DEBUG2, "      not found\n");
+  }
+
+  $main::lxdebug->leave_sub();
+
+  return $display;
+}
+
+sub spawn_xvfb {
+  $main::lxdebug->enter_sub();
+
+  my ($self) = @_;
+
+  $main::lxdebug->message(LXDebug::DEBUG2, "spawn_xvfb()\n");
+
+  my $display = $self->is_xvfb_running();
+
+  if ($display) {
+    $main::lxdebug->leave_sub();
+    return $display;
+  }
+
+  $display = 99;
+  while ( -f "/tmp/.X${display}-lock") {
+    $display++;
+  }
+  $display = ":${display}";
+  $main::lxdebug->message(LXDebug::DEBUG2, "  display $display\n");
+
+  my $mcookie = `mcookie`;
+  die("Installation error: mcookie not found.") if ($? != 0);
+  chomp($mcookie);
+
+  $main::lxdebug->message(LXDebug::DEBUG2, "  mcookie $mcookie\n");
+
+  my $xauthority = "/tmp/.Xauthority-" . $$ . "-" . time() . "-" . int(rand(9999999));
+  $ENV{"XAUTHORITY"} = $xauthority;
+
+  $main::lxdebug->message(LXDebug::DEBUG2, "  xauthority $xauthority\n");
+
+  system("xauth add \"${display}\" . \"${mcookie}\"");
+  if ($? != 0) {
+    $self->{"error"} = "Conversion to PDF failed because OpenOffice could not be started (xauth: $!)";
+    $main::lxdebug->leave_sub();
+    return undef;
+  }
+
+  $main::lxdebug->message(LXDebug::DEBUG2, "  about to fork()\n");
+
+  my $pid = fork();
+  if (0 == $pid) {
+    $main::lxdebug->message(LXDebug::DEBUG2, "  Child execing\n");
+    exec($main::xvfb_bin, $display, "-screen", "0", "640x480x8", "-nolisten", "tcp");
+  }
+  sleep(3);
+  $main::lxdebug->message(LXDebug::DEBUG2, "  parent dont sleeping\n");
+
+  local *OUT;
+  my $dfname = $self->{"userspath"} . "/xvfb_display";
+  if (!open(OUT, ">$dfname")) {
+    $self->{"error"} = "Conversion to PDF failed because OpenOffice could not be started ($dfname: $!)";
+    unlink($xauthority);
+    kill($pid);
+    $main::lxdebug->leave_sub();
+    return undef;
+  }
+  print(OUT "$pid\n$display\n$xauthority\n");
+  close(OUT);
+
+  $main::lxdebug->message(LXDebug::DEBUG2, "  parent re-testing\n");
+
+  if (!$self->is_xvfb_running()) {
+    $self->{"error"} = "Conversion to PDF failed because OpenOffice could not be started.";
+    unlink($xauthority, $dfname);
+    kill($pid);
+    $main::lxdebug->leave_sub();
+    return undef;
+  }
+
+  $main::lxdebug->message(LXDebug::DEBUG2, "  spawn OK\n");
+
+  $main::lxdebug->leave_sub();
+
+  return $display;
+}
+
+sub is_openoffice_running {
+  $main::lxdebug->enter_sub();
+
+  system("./scripts/oo-uno-test-conn.py $main::openofficeorg_daemon_port " .
+         "> /dev/null 2> /dev/null");
+  my $res = $? == 0;
+  $main::lxdebug->message(LXDebug::DEBUG2, "  is_openoffice_running(): $?\n");
+
+  $main::lxdebug->leave_sub();
+
+  return $res;
+}
+
+sub spawn_openoffice {
+  $main::lxdebug->enter_sub();
+
+  my ($self) = @_;
+
+  $main::lxdebug->message(LXDebug::DEBUG2, "spawn_openoffice()\n");
+
+  my ($try, $spawned_oo, $res);
+
+  $res = 0;
+  for ($try = 0; $try < 15; $try++) {
+    if ($self->is_openoffice_running()) {
+      $res = 1;
+      last;
+    }
+
+    if (!$spawned_oo) {
+      my $pid = fork();
+      if (0 == $pid) {
+        $main::lxdebug->message(LXDebug::DEBUG2, "  Child daemonizing\n");
+        chdir('/');
+        open(STDIN, '/dev/null');
+        open(STDOUT, '>/dev/null');
+        my $new_pid = fork();
+        exit if ($new_pid);
+        my $ssres = setsid();
+        $main::lxdebug->message(LXDebug::DEBUG2, "  Child execing\n");
+        my @cmdline = ($main::openofficeorg_writer_bin,
+                       "-minimized", "-norestore", "-nologo", "-nolockcheck",
+                       "-headless",
+                       "-accept=socket,host=localhost,port=" .
+                       $main::openofficeorg_daemon_port . ";urp;");
+        exec(@cmdline);
+      }
+
+      $main::lxdebug->message(LXDebug::DEBUG2, "  Parent after fork\n");
+      $spawned_oo = 1;
+      sleep(3);
+    }
+
+    sleep($try >= 5 ? 2 : 1);
+  }
+
+  if (!$res) {
+    $self->{"error"} = "Conversion from OpenDocument to PDF failed because " .
+      "OpenOffice could not be started.";
+  }
+
+  $main::lxdebug->leave_sub();
+
+  return $res;
+}
+
 sub convert_to_pdf {
+  $main::lxdebug->enter_sub();
+
   my ($self) = @_;
 
   my $form = $self->{"form"};
@@ -759,12 +1324,29 @@ sub convert_to_pdf {
     $ENV{'HOME'} = getcwd() . "/" . $self->{"userspath"};
   }
 
-  my @cmdline = ($main::xvfb_run_bin, $main::openofficeorg_writer_bin,
-                 "-minimized", "-norestore", "-nologo", "-nolockcheck",
-                 "-headless",
-                 "file:${filename}.odt",
-                 "macro://" . (split('/', $filename))[-1] .
-                 "/Standard.Conversion.ConvertSelfToPDF()");
+  if (!$self->spawn_xvfb()) {
+    $main::lxdebug->leave_sub();
+    return 0;
+  }
+
+  my @cmdline;
+  if (!$main::openofficeorg_daemon) {
+    @cmdline = ($main::openofficeorg_writer_bin,
+                "-minimized", "-norestore", "-nologo", "-nolockcheck",
+                "-headless",
+                "file:${filename}.odt",
+                "macro://" . (split('/', $filename))[-1] .
+                "/Standard.Conversion.ConvertSelfToPDF()");
+  } else {
+    if (!$self->spawn_openoffice()) {
+      $main::lxdebug->leave_sub();
+      return 0;
+    }
+
+    @cmdline = ("./scripts/oo-uno-convert-pdf.py",
+                $main::openofficeorg_daemon_port,
+                "${filename}.odt");
+  }
 
   system(@cmdline);
 
@@ -792,32 +1374,18 @@ sub format_string {
   my $form = $self->{"form"};
   my $iconv = $self->{"iconv"};
 
-  my %replace =
-    ('order' => ['<', '>', '"', "'",
-                 '\x80',        # Euro
-                 quotemeta("\n"), quotemeta("\r"), '&'],
-     '<'             => '&lt;',
-     '>'             => '&gt;',
-     '"'             => '&quot;',
-     "'"             => '&apos;',
-     '&'             => '&quot;',
-     '\x80'          => chr(0xa4), # Euro
-     quotemeta("\n") => '<text:line-break/>',
-     quotemeta("\r") => '',
-     );
-
-  map({ $variable =~ s/$_/$replace{$_}/g; } @{ $replace{"order"} });
+  $variable = $main::locale->quote_special_chars('Template/OpenDocument', $variable);
 
   # Allow some HTML markup to be converted into the output format's
   # corresponding markup code, e.g. bold or italic.
   my $rnd = $self->{"rnd"};
   my %markup_replace = ("b" => "BOLD", "i" => "ITALIC", "s" => "STRIKETHROUGH",
-                        "u" => "UNDERLINE");
+                        "u" => "UNDERLINE", "sup" => "SUPER", "sub" => "SUB");
 
   foreach my $key (keys(%markup_replace)) {
     my $value = $markup_replace{$key};
-    $variable =~ s|\&lt;${key}\&gt;|<text:span text:style-name=\"TLXO${rnd}${value}\">|g;
-    $variable =~ s|\&lt;/${key}\&gt;|</text:span>|g;
+    $variable =~ s|\&lt;${key}\&gt;|<text:span text:style-name=\"TLXO${rnd}${value}\">|gi; #"
+    $variable =~ s|\&lt;/${key}\&gt;|</text:span>|gi;
   }
 
   return $iconv->convert($variable);
@@ -835,4 +1403,57 @@ sub uses_temp_file {
   return 1;
 }
 
+
+##########################################################
+####
+#### XMLTemplate
+####
+##########################################################
+
+package XMLTemplate;
+
+use vars qw(@ISA);
+
+@ISA = qw(HTMLTemplate);
+
+sub new {
+  #evtl auskommentieren
+  my $type = shift;
+
+  return $type->SUPER::new(@_);
+}
+
+sub format_string {
+  my ($self, $variable) = @_;
+  my $form = $self->{"form"};
+
+  $variable = $main::locale->quote_special_chars('Template/XML', $variable);
+
+  # Allow no markup to be converted into the output format
+  my @markup_replace = ('b', 'i', 's', 'u', 'sub', 'sup');
+
+  foreach my $key (@markup_replace) {
+    $variable =~ s/\&lt;(\/?)${key}\&gt;//g;
+  }
+
+  return $variable;
+}
+
+sub get_mime_type() {
+  my ($self) = @_;
+
+  if ($self->{"form"}->{"format"} =~ /elsterwinston/i) {
+    return "application/xml ";
+  } elsif ($self->{"form"}->{"format"} =~ /elstertaxbird/i) {
+    return "application/x-taxbird";
+  } else {
+    return "text";
+  }
+}
+
+sub uses_temp_file {
+  # tempfile needet for XML Output
+  return 1;
+}
+
 1;