Die Ausgabe der Zeile "\usepackage{textcomp}" darf nur erfolgen, wenn LaTeX-Vorlagen...
[kivitendo-erp.git] / SL / Template.pm
index 8693580..6da93d9 100644 (file)
@@ -28,12 +28,25 @@ 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;
 }
 
 sub cleanup {
@@ -86,34 +99,7 @@ sub format_string {
   my ($self, $variable) = @_;
   my $form = $self->{"form"};
 
-  my %replace =
-    ('order' => [quotemeta("\\"),
-                 '<pagebreak>',
-                 '&', quotemeta("\n"),
-                 '"', '\$', '%', '_', '#', quotemeta('^'),
-                 '{', '}',  '<', '>', '£', "\r", '±', '\xe1',
-                 ],
-     quotemeta("\\") => '\\textbackslash ',
-     '<pagebreak>'   => '',
-     '"'             => "''",
-     '&'             => '\&',
-     '\$'            => '\$',
-     '%'             => '\%',
-     '_'             => '\_',
-     '#'             => '\#',
-     '{'             => '\{',
-     '}'             => '\}',
-     '<'             => '$<$',
-     '>'             => '$>$',
-     '£'             => '\pounds ',
-     "\r"            => "",
-     '±'             => '$\pm$',
-     '\xe1'          => '$\bullet$',
-     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.
@@ -136,7 +122,8 @@ sub substitute_vars {
 
   my $form = $self->{"form"};
 
-  while ($text =~ /<\%(.*?)\%>/) {
+  while ($text =~ /$self->{tag_start_qm}(.+?)$self->{tag_end_qm}/) {
+    my ($tag_pos, $tag_len) = ($-[0], $+[0] - $-[0]);
     my ($var, @options) = split(/\s+/, $1);
     my $value = $form->{$var};
 
@@ -145,7 +132,7 @@ sub substitute_vars {
       $value = $value->[$indices[$i]];
     }
     $value = $self->format_string($value) unless (grep(/^NOESCAPE$/, @options));
-    substr($text, $-[0], $+[0] - $-[0]) = $value;
+    substr($text, $tag_pos, $tag_len) = $value;
   }
 
   return $text;
@@ -201,8 +188,8 @@ sub parse_foreach {
         # and <%lastpage%>
 
         my $psum = $form->format_amount($self->{"myconfig"}, $sum, 2);
-        $pb =~ s/<%sumcarriedforward%>/$psum/g;
-        $pb =~ s/<%lastpage%>/$current_page/g;
+        $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));
@@ -232,36 +219,43 @@ sub parse_foreach {
 sub find_end {
   my ($self, $text, $pos, $var, $not) = @_;
 
+  my $tag_start_len = length $self->{tag_start};
+
   my $depth = 1;
   $pos = 0 unless ($pos);
 
   while ($pos < length($text)) {
     $pos++;
 
-    next if (substr($text, $pos - 1, 2) ne '<%');
+    next if (substr($text, $pos - 1, length($self->{tag_start})) ne $self->{tag_start});
+
+    my $keyword_pos = $pos - 1 + $tag_start_len;
 
-    if ((substr($text, $pos + 1, 2) eq 'if') || (substr($text, $pos + 1, 3) eq 'for')) {
+    if ((substr($text, $keyword_pos, 2) eq 'if') || (substr($text, $keyword_pos, 3) eq 'for')) {
       $depth++;
 
-    } elsif ((substr($text, $pos + 1, 4) eq 'else') && (1 == $depth)) {
+    } elsif ((substr($text, $keyword_pos, 4) eq 'else') && (1 == $depth)) {
       if (!$var) {
-        $self->{"error"} = '<%else%> outside of <%if%> / <%ifnot%>.';
+        $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!^<\%[^\%]+\%>!!;
-      $text = '<%if' . ($not ?  " " : "not ") . $var . '%>' . $text;
+      $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, $pos + 1, 3) eq 'end') {
+    } 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!^<\%[^\%]+\%>!!;
+        $text =~ s!^$self->{tag_start_qm}.+?$self->{tag_end_qm}!!;
 
         return ($block, $text);
       }
@@ -279,8 +273,8 @@ sub parse_block {
   my $new_contents = "";
 
   while ($contents ne "") {
-    my $pos_if = index($contents, '<%if');
-    my $pos_foreach = index($contents, '<%foreach');
+    my $pos_if      = index($contents, $self->{tag_start} . 'if');
+    my $pos_foreach = index($contents, $self->{tag_start} . 'foreach');
 
     if ((-1 == $pos_if) && (-1 == $pos_foreach)) {
       $new_contents .= $self->substitute_vars($contents, @indices);
@@ -291,8 +285,8 @@ sub parse_block {
       $new_contents .= $self->substitute_vars(substr($contents, 0, $pos_foreach), @indices);
       substr($contents, 0, $pos_foreach) = "";
 
-      if ($contents !~ m|^<\%foreach (.*?)\%>|) {
-        $self->{"error"} = "Malformed <\%foreach\%>.";
+      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;
       }
@@ -304,7 +298,7 @@ sub parse_block {
       my $block;
       ($block, $contents) = $self->find_end($contents);
       if (!$block) {
-        $self->{"error"} = "Unclosed <\%foreach\%>." unless ($self->{"error"});
+        $self->{"error"} = "Unclosed $self->{tag_start}foreach$self->{tag_end}." unless ($self->{"error"});
         $main::lxdebug->leave_sub();
         return undef;
       }
@@ -320,8 +314,8 @@ sub parse_block {
       $new_contents .= $self->substitute_vars(substr($contents, 0, $pos_if), @indices);
       substr($contents, 0, $pos_if) = "";
 
-      if ($contents !~ m|^<\%if\s*(not)?\s+(.*?)\%>|) {
-        $self->{"error"} = "Malformed <\%if\%>.";
+      if ($contents !~ m|^$self->{tag_start_qm}if\s*(not)?\s+(.*?)$self->{tag_end_qm}|) {
+        $self->{"error"} = "Malformed $self->{tag_start}if$self->{tag_end}.";
         $main::lxdebug->leave_sub();
         return undef;
       }
@@ -332,7 +326,7 @@ sub parse_block {
 
       ($block, $contents) = $self->find_end($contents, 0, $var, $not);
       if (!$block) {
-        $self->{"error"} = "Unclosed <\%if${not}\%>." unless ($self->{"error"});
+        $self->{"error"} = "Unclosed $self->{tag_start}if${not}$self->{tag_end}." unless ($self->{"error"});
         $main::lxdebug->leave_sub();
         return undef;
       }
@@ -359,6 +353,93 @@ sub parse_block {
   return $new_contents;
 }
 
+sub parse_first_line {
+  my $self = shift;
+  my $line = shift || "";
+
+  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);
+  }
+
+  return 1;
+}
+
+sub _parse_config_option {
+  my $self = shift;
+  my $line = shift;
+
+  $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;
+    }
+
+    $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;
+
+    }
+  }
+
+  $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];
@@ -368,13 +449,16 @@ sub parse {
     $self->{"error"} = "$!";
     return 0;
   }
-  @_ = <IN>;
+  my @lines = <IN>;
   close(IN);
 
-  my $contents = join("", @_);
+  $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 =~ /<%pagebreak\s+(\d+)\s+(\d+)\s+(\d+)\s*%>(.*?)<%end(\s*pagebreak)?%>/s) {
+  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;
@@ -414,7 +498,7 @@ sub convert_to_postscript {
     return 0;
   }
 
-  $form->{tmpfile} =~ s/$userspath\///g;
+  $form->{tmpfile} =~ s/\Q$userspath\E\///g;
 
   for (my $run = 1; $run <= 2; $run++) {
     system("latex --interaction=nonstopmode $form->{tmpfile} " .
@@ -453,7 +537,7 @@ sub convert_to_pdf {
     return 0;
   }
 
-  $form->{tmpfile} =~ s/$userspath\///g;
+  $form->{tmpfile} =~ s/\Q$userspath\E\///g;
 
   for (my $run = 1; $run <= 2; $run++) {
     system("pdflatex --interaction=nonstopmode $form->{tmpfile} " .
@@ -505,14 +589,7 @@ 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.
@@ -561,7 +638,7 @@ sub convert_to_postscript {
     return 0;
   }
 
-  $form->{"tmpfile"} =~ s/$userspath\///g;
+  $form->{"tmpfile"} =~ s/\Q$userspath\E\///g;
   my $psfile = $form->{"tmpfile"};
   $psfile =~ s/.html/.ps/;
   if ($psfile eq $form->{"tmpfile"}) {
@@ -594,7 +671,7 @@ sub convert_to_pdf {
     return 0;
   }
 
-  $form->{"tmpfile"} =~ s/$userspath\///g;
+  $form->{"tmpfile"} =~ s/\Q$userspath\E\///g;
   my $pdffile = $form->{"tmpfile"};
   $pdffile =~ s/.html/.pdf/;
   if ($pdffile eq $form->{"tmpfile"}) {
@@ -1253,21 +1330,7 @@ sub format_string {
   my $form = $self->{"form"};
   my $iconv = $self->{"iconv"};
 
-  my %replace =
-    ('order' => ['&', '<', '>', '"', "'",
-                 '\x80',        # Euro
-                 quotemeta("\n"), quotemeta("\r")],
-     '<'             => '&lt;',
-     '>'             => '&gt;',
-     '"'             => '&quot;',
-     "'"             => '&apos;',
-     '&'             => '&amp;',
-     '\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.
@@ -1277,7 +1340,7 @@ sub format_string {
 
   foreach my $key (keys(%markup_replace)) {
     my $value = $markup_replace{$key};
-    $variable =~ s|\&lt;${key}\&gt;|<text:span text:style-name=\"TLXO${rnd}${value}\">|gi;
+    $variable =~ s|\&lt;${key}\&gt;|<text:span text:style-name=\"TLXO${rnd}${value}\">|gi; #"
     $variable =~ s|\&lt;/${key}\&gt;|</text:span>|gi;
   }
 
@@ -1320,14 +1383,7 @@ 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/XML', $variable);
 
   # Allow no markup to be converted into the output format
   my @markup_replace = ('b', 'i', 's', 'u', 'sub', 'sup');