=head1 NAME LedgerSMB::Template::ODS Template support module for LedgerSMB =head1 SYNOPSIS OpenDocument Spreadsheet output. =head1 METHODS =over =item get_template ($name) Returns the appropriate template filename for this format. '.xlst' is the extension that was chosen for the templates. =item preprocess ($vars) Returns $vars. =item process ($parent, $cleanvars) Processes the template for text. =item postprocess ($parent) Returns the output filename. =back =head1 Copyright (C) 2007, The LedgerSMB core team. This work contains copyrighted information from a number of sources all used with permission. It is released under the GNU General Public License Version 2 or, at your option, any later version. See COPYRIGHT file for details. For a full list including contact information of contributors, maintainers, and copyright holders, see the CONTRIBUTORS file. =cut package LedgerSMB::Template::ODS; use strict; use warnings; use Error qw(:try); use Data::Dumper; use CGI::Simple::Standard qw(:html); use Template; use XML::Twig; use OpenOffice::OODoc; use LedgerSMB::Template::TTI18N; # SC: The ODS handlers need these vars in common my $ods; my $rowcount; my $currcol; my %celltype; # SC: The elements of the style table for regular styles and stack are # arrays where the stack name is the first element and the style # properties are the second. The name is used for setting styles, # while the properties are used in handling nested styles. my @style_stack; # stack of styles, 0 is active style my %style_table; # hash table for created styles # SC: Subtract 8 from the attribute to get the index # http://search.cpan.org/src/JMCNAMARA/Spreadsheet-WriteExcel-2.11/doc/palette.html my @colour = (odfColor(0, 0, 0), odfColor(255, 255, 255), odfColor(255, 0, 0), odfColor(0, 255, 0), odfColor(0, 0, 255), odfColor(255, 255, 0), odfColor(255, 0, 255), odfColor(0, 255, 255), odfColor(128, 0, 0), odfColor(0, 128, 0), odfColor(0, 0, 128), odfColor(128, 128, 0), odfColor(128, 0, 128), odfColor(0, 128, 128), odfColor(192, 192, 192), odfColor(128, 128, 128), odfColor(153, 153, 255), odfColor(153, 51, 102), odfColor(255, 255, 204), odfColor(204, 255, 255), odfColor(102, 0, 102), odfColor(255, 128, 128), odfColor(0, 102, 204), odfColor(204, 204, 255), odfColor(0, 0, 128), odfColor(255, 0, 255), odfColor(255, 255, 0), odfColor(0, 255, 255), odfColor(128, 0, 128), odfColor(120, 0, 0), odfColor(0, 128, 128), odfColor(0, 0, 255), odfColor(0, 204, 255), odfColor(204, 255, 255), odfColor(204, 255, 204), odfColor(255, 255, 153), odfColor(153, 204, 255), odfColor(255, 153, 204), odfColor(204, 153, 255), odfColor(192, 192, 192), odfColor(51, 102, 255), odfColor(51, 204, 204), odfColor(153, 204, 0), odfColor(255, 204, 0), odfColor(255, 153, 0), odfColor(255, 102, 0), odfColor(102, 102, 153), odfColor(150, 150, 150), odfColor(0, 51, 102), odfColor(51, 153, 102), odfColor(0, 51, 0), odfColor(51, 51, 0), odfColor(153, 51, 0), odfColor(153, 51, 102), odfColor(51, 51, 153), odfColor(51, 51, 51), ); my %colour_name = ('black' => $colour[0], 'white' => $colour[1], 'red' => $colour[2], 'lime' => $colour[3], 'blue' => $colour[4], 'yellow' => $colour[5], 'magenta' => $colour[6], 'cyan' => $colour[7], 'brown' => $colour[8], 'green' => $colour[9], 'navy' => $colour[10], 'purple' => $colour[12], 'silver' => $colour[14], 'gray' => $colour[15], 'grey' => $colour[15], 'orange' => $colour[45], ); my @line_width = ('none', '0.018cm solid', '0.035cm solid', '0.018cm dashed', '0.018cm dotted', '0.141cm solid', '0.039cm double', '0.002cm solid' ); sub _worksheet_handler { $rowcount = -1; $currcol = 0; my $rows = $_->{att}->{rows}; my $columns = $_->{att}->{columns}; $rows ||= 1000; $columns ||= 52; my $sheet; if ($_->is_first_child) { $sheet = $ods->getTable(0, $rows, $columns); $ods->renameTable($sheet, $_->{att}->{name}); } else { $sheet = $ods->appendTable($_->{att}->{name}, $rows, $columns); } } sub _row_handler { $rowcount++; $currcol = 0; } sub _cell_handler { my $cell = $ods->getCell(-1, $rowcount, $currcol); if (@style_stack and $celltype{$style_stack[0][0]}) { $ods->cellValueType($cell, $celltype{$style_stack[0][0]}[0]); } elsif ($_->{att}->{type}) { my $type = $_->{att}->{type}; if ($type =~ /^(string|blank|url)$/i) { $ods->cellValueType($cell, 'string'); } elsif ($type =~ /^(number|formula)$/i) { $ods->cellValueType($cell, 'float'); } } $ods->cellValue($cell, $_->{att}->{text}); if (@style_stack) { $ods->cellStyle($cell, $style_stack[0][0]); } $currcol++; } sub _formula_handler { my $cell = $ods->getCell(-1, $rowcount, $currcol); if (@style_stack and $celltype{$style_stack[0][0]}) { $ods->cellValueType($cell, $celltype{$style_stack[0][0]}[0]); } elsif ($_->{att}->{type}) { my $type = $_->{att}->{type}; if ($type =~ /^(string|blank|url)$/i) { $ods->cellValueType($cell, 'string'); } elsif ($type =~ /^(number|formula)$/i) { $ods->cellValueType($cell, 'float'); } } $ods->cellFormula($cell, "oooc:=$_->{att}->{text}"); if (@style_stack) { $ods->cellStyle($cell, $style_stack[0][0]); } $currcol++; } sub _border_set { my ($format, $properties, $border) = @_; my $edge = $border; $edge =~ s/^border-?//; my $val; if ($edge) { $val = $format->{att}{$edge}; } else { $val = $format->{att}{'border'}; } if ($properties->{cell}{"fo:$border"}){ $properties->{cell}{"fo:$border"} =~ s/^.* (\#......)$/$val $1/; } else { $properties->{cell}{"fo:$border"} = "$line_width[$val] #000000"; } if ($edge and $format->{att}->{"${edge}_color"}) { my $colour = $format->{att}->{"${edge}_color"}; if ($colour =~ /^\d+$/) { $colour = $colour[$colour]; } elsif ($colour !~ /^\#......$/) { $colour = $colour_name{$colour}; } $properties->{cell}{"fo:$border"} =~ s/^(.*) \#......$/$1 $colour/; } elsif ($format->{att}->{border_color}) { my $colour = $format->{att}->{"${edge}_color"}; if ($colour =~ /^\d+$/) { $colour = $colour[$colour]; } elsif ($colour !~ /^\#......$/) { $colour = $colour_name{$colour}; } $properties->{cell}{"fo:$border"} =~ s/^(.*) \#......$/$1 $colour/; } } sub _prepare_float { my ($style) = shift; my %properties; my @sides = split /\./, $style; if ($#sides == 1) { # decimal places $properties{'number:decimal-places'} = length $sides[1]; } else { $properties{'number:decimal-places'} = 0; } $properties{'number:min-integer-digits'} = length($sides[0] =~ /0+$/); $properties{'number:grouping'} = 'true' if $sides[0] =~ /.,...$/; \%properties; } sub _prepare_fraction { my ($style) = shift; my %properties; my @sides = split /[ \/]/, $style; $properties{'number:min-integer-digits'} = length($sides[0] =~ /0+$/); $properties{'number:min-numerator-digits'} = length($sides[1]); $properties{'number:min-denominator-digits'} = length($sides[2]); \%properties; } sub _create_positive_style { my ($name, $type, $base) = @_; my $pstyle = $ods->createStyle( $name, namespace => 'number', type => $type, properties => $base, references => { 'style:volatile' => 'true', }, ); $pstyle->insert_new_elt('last-child', 'number:text', {}, ' '); } sub _format_handler { my ($t, $format) = @_; my $style = sprintf "ce%d", (scalar (keys %style_table) + 1); my @extras; # SC: There are multiple types of properties that can be associated # with a style. However, the OO::OOD style creation code appears # to only allow for a single type to be added to the style at a # time. As a result, %properties is split into property groupings # to allow for each group to get the correct type. my %properties; if (@style_stack) { %properties = %{$style_stack[0][1]}; if ($celltype{$style_stack[0][0]}) { $celltype{$style} = $celltype{$style_stack[0][0]}; @extras = ('references', { 'style:data-style-name' => $celltype{$style}[1] }); } } &_border_set(\%properties, $format, 'border') if $format->{att}->{border}; while (my ($attr, $val) = each %{$format->{att}}) { if ($attr eq 'bottom') { &_border_set($format, \%properties, 'border-bottom'); } elsif ($attr eq 'top') { &_border_set($format, \%properties, 'border-top'); } elsif ($attr eq 'left') { &_border_set($format, \%properties, 'border-left'); } elsif ($attr eq 'right') { &_border_set($format, \%properties, 'border-right'); } elsif ($attr eq 'bg_color' or $attr eq 'bg_colour') { if ($val =~ /^\d+$/) { $properties{cell}{'fo:background-color'} = $colour[$val - 8]; } elsif ($val =~ /^\#[0-9A-Fa-f]{6}$/) { $properties{cell}{'fo:background-color'} = $val; } else { $properties{cell}{'fo:background-color'} = $colour_name{$val}; } } elsif ($attr eq 'color' or $attr eq 'colour') { if ($val =~ /^\d+$/) { $properties{text}{'fo:color'} = $colour[$val - 8]; } elsif ($val =~ /^\#[0-9A-Fa-f]{6}$/) { $properties{text}{'fo:color'} = $val; } else { $properties{text}{'fo:color'} = $colour_name{$val}; } } elsif ($attr eq 'align') { if (lc $val eq 'right') { $properties{paragraph}{'fo:text-align'} = 'end'; } elsif (lc $val eq 'left') { $properties{paragraph}{'fo:text-align'} = 'start'; } else { $properties{paragraph}{'fo:text-align'} = $val; } } elsif ($attr eq 'valign') { # takes top, vcenter, bottom, or vjustify # needs top, middle, or bottom if ($val =~ /^v/i) { $properties{paragraph}{'style:vertical-align'} = 'middle'; } else { $properties{paragraph}{'style:vertical-align'} = $val; } } elsif ($attr eq 'hidden') { if ($properties{cell}{'style:cell-protect'} and !$val) { delete $properties{cell}{'style:cell-protect'}; } elsif ($val) { $properties{cell}{'style:cell-protect'} = 'formula-hidden'; } } elsif ($attr eq 'font') { $properties{text}{'style:font-name'} = $val; } elsif ($attr eq 'size') { $properties{text}{'fo:font-size'} = "${val}pt"; } elsif ($attr eq 'bold') { if ($properties{text}{'fo:font-weight'} and !$val) { delete $properties{text}{'fo:font-weight'}; } elsif ($val) { $properties{text}{'fo:font-weight'} = 'bold'; } } elsif ($attr eq 'italic') { if ($properties{text}{'fo:font-style'} and !$val) { delete $properties{text}{'fo:font-style'}; } elsif ($val) { $properties{text}{'fo:font-style'} = 'italic'; } } elsif ($attr eq 'font_strikeout') { if (!$val) { $properties{text}{'style:text-line-through-type'} = 'none'; } elsif ($val) { $properties{text}{'style:text-line-through-type'} = 'single'; } } elsif ($attr eq 'font_shadow') { if ($properties{text}{'fo:text-shadow'} and !$val) { delete $properties{text}{'fo:text-shadow'}; } elsif ($val) { $properties{text}{'fo:text-shadow'} = '2pt'; } } elsif ($attr eq 'font_outline') { if ($properties{text}{'style:text-outline'} and !$val) { delete $properties{text}{'style:text-outline'}; } elsif ($val) { $properties{text}{'style:text-outline'} = 'true'; } } elsif ($attr eq 'shrink') { if ($properties{cell}{'style:shrink-to-fit'} and !$val) { delete $properties{cell}{'style:shrink-to-fit'}; } elsif ($val) { $properties{cell}{'style:shrink-to-fit'} = 'true'; } } elsif ($attr eq 'text_wrap') { if (!$val) { $properties{cell}{'style:wrap-option'} = 'no-wrap'; } else { $properties{text}{'style:wrap-option'} = 'wrap'; } } elsif ($attr eq 'text_justlast') { if ($properties{paragraph}{'fo:text-align-last'} and !$val) { delete $properties{paragraph}{'fo:text-align-last'}; } elsif ($val) { $properties{paragraph}{'fo:text-align-last'} = 'justify'; } } elsif ($attr eq 'num_format') { #SC: Number formatting is when I hit the point of, # "Screw the OO::OOD API, XML::Twig is simpler". # @children's elements are passed right into the # style via XML::Twig::Elt's insert_new_elt. The # OO:OOD API, while decent enough for the text # styles, is not so pleasant with complex number # styles. my @children; my %nproperties; my @nextras; my $nstyle; my $fval = sprintf 'N%02d', $val; @extras = ('references', {'style:data-style-name' => $fval}); if ($style_table{$fval}) { # pass through } elsif ($val == 0) { $celltype{$style} = 'float'; } elsif ($val == 1) { $celltype{$style} = ['float', 'N01']; $nstyle = 'number-style'; %nproperties = %{&_prepare_float('0')} } elsif ($val == 2) { $celltype{$style} = ['float', 'N02']; $nstyle = 'number-style'; %nproperties = %{&_prepare_float('0.00')} } elsif ($val == 3) { $celltype{$style} = ['float', 'N03']; $nstyle = 'number-style'; %nproperties = %{&_prepare_float('#,##0')} } elsif ($val == 4) { $celltype{$style} = ['float', "N04"]; $nstyle = 'number-style'; %nproperties = %{&_prepare_float('#,##0.00')} } elsif ($val == 5) { ## ($#,##0_);($#,##0) } elsif ($val == 6) { $celltype{$style} = 'currency'; } elsif ($val == 7) { $celltype{$style} = 'currency'; } elsif ($val == 8) { $celltype{$style} = 'currency'; } elsif ($val == 9) { ## 0% $celltype{$style} = ['percentage', "N09"]; $nstyle = 'percentage-style'; %nproperties = %{&_prepare_float('0')}; push @children, ['number:text', {}, '%']; } elsif ($val == 10) { ## 0.00% $celltype{$style} = ['percentage', "N10"]; $nstyle = 'percentage-style'; %nproperties = %{&_prepare_float('0.00')}; push @children, ['number:text', {}, '%']; } elsif ($val == 11) { ## 0.00E+00 $celltype{$style} = ['float', "N11"]; $nstyle = 'number-style'; push @children, ['number:scientific-number', { %{&_prepare_float('0.00')}, 'number:min-exponent-digits' => 2 }]; } elsif ($val == 12) { ## # ?/? $celltype{$style} = ['float', "N12"]; $nstyle = 'number-style'; push @children, ['number:fraction', %{&_prepare_fraction('# ?/?')}]; } elsif ($val == 13) { ## # ??/?? $celltype{$style} = ['float', "N13"]; $nstyle = 'number-style'; push @children, ['number:fraction', %{&_prepare_fraction('# ??/??')}]; } elsif ($val == 14) { ## m/d/yy $celltype{$style} = ['date', "N14"]; $nstyle = 'date-style'; @nextras = ('references' => { 'number:automatic-order' => 'true'}); @children = ( ['number:month'], ['number:text', {}, '/'], ['number:day'], ['number:text', {}, '/'], ['number:year'], ); } elsif ($val == 15) { ## d-mmm-yy $celltype{$style} = ['date', "N15"]; $nstyle = 'date-style'; @nextras = ('references' => { 'number:automatic-order' => 'true'}); @children = ( ['number:day'], ['number:text', {}, '-'], ['number:month', { 'number:textual' => 'true'}], ['number:text', {}, '-'], ['number:year'], ); } elsif ($val == 16) { ## d-mmm $celltype{$style} = ['date', "N16"]; $nstyle = 'date-style'; @nextras = ('references' => { 'number:automatic-order' => 'true'}); @children = ( ['number:day'], ['number:text', {}, '-'], ['number:month', { 'number:textual' => 'true'}], ); } elsif ($val == 17) { ## mmm-yy $celltype{$style} = ['date', "N17"]; $nstyle = 'date-style'; @nextras = ('references' => { 'number:automatic-order' => 'true'}); @children = ( ['number:month', { 'number:textual' => 'true'}], ['number:text', {}, '-'], ['number:year'], ); } elsif ($val == 18) { ## h:mm AM/PM $celltype{$style} = ['time', "N18"]; $nstyle = 'time-style'; @nextras = ('references' => { 'number:automatic-order' => 'true'}); @children = ( ['number:hours'], ['number:text', {}, ':'], ['number:minutes', {'number:style' => 'long'}], ['number:text', {}, ' '], ['number:am-pm'] ); } elsif ($val == 19) { ## h:mm:ss AM/PM $celltype{$style} = ['time', "N19"]; $nstyle = 'time-style'; @nextras = ('references' => { 'number:automatic-order' => 'true'}); @children = ( ['number:hours'], ['number:text', {}, ':'], ['number:minutes', {'number:style' => 'long'}], ['number:text', {}, ':'], ['number:seconds', {'number:style' => 'long'}], ['number:text', {}, ' '], ['number:am-pm'] ); } elsif ($val == 20) { ## h:mm $celltype{$style} = ['time', "N20"]; $nstyle = 'time-style'; @nextras = ('references' => { 'number:automatic-order' => 'true'}); @children = ( ['number:hours'], ['number:text', {}, ':'], ['number:minutes', {'number:style' => 'long'}], ); } elsif ($val == 21) { ## h:mm:ss $celltype{$style} = ['time', "N21"]; $nstyle = 'time-style'; @nextras = ('references' => { 'number:automatic-order' => 'true'}); @children = ( ['number:hours'], ['number:text', {}, ':'], ['number:minutes', {'number:style' => 'long'}], ['number:text', {}, ':'], ['number:seconds', {'number:style' => 'long'}], ); } elsif ($val == 22) { ## m/d/yy h:mm $celltype{$style} = ['date', "N22"]; $nstyle = 'date-style'; @nextras = ('references' => { 'number:automatic-order' => 'true'}); @children = ( ['number:month'], ['number:text', {}, '/'], ['number:day'], ['number:text', {}, '/'], ['number:year'], ['number:text', {}, ' '], ['number:hours'], ['number:text', {}, ':'], ['number:minutes', {'number:style' => 'long'}], ); } elsif ($val == 37) { ## (#,##0_);(#,##0) $celltype{$style} = ['float', "N37"]; $nstyle = 'number-style'; my %base = ( 'number:min-integer-digits' => 1, 'number:grouping' => 'true', ); @children = ( ['number:text', {}, '('], ['number:number', \%base], ['number:text', {}, ')'], ['style:map', { 'style:condition' => 'value()>=0', 'style:apply-style-name' => "NP37", }], ); &_create_positive_style("NP37", $nstyle, \%base); } elsif ($val == 38) { ## (#,##0_);[Red](#,##0) $celltype{$style} = ['float', "N38"]; $nstyle = 'number-style'; my %base = %{&_prepare_float('#,##0')}; @children = ( ['style:text-properties', {'fo:color' => '#ff0000'}], ['number:text', {}, '('], ['number:number', \%base], ['number:text', {}, ')'], ['style:map',{ 'style:condition' => 'value()>=0', 'style:apply-style-name' => "NP38", }] ); &_create_positive_style("NP38", $nstyle, \%base); } elsif ($val == 39) { ## (#,##0.00_);(#,##0.00) $celltype{$style} = ['float', "N39"]; $nstyle = 'number-style'; my %base = %{&_prepare_float('#,##0.00')}; @children = ( ['number:text', {}, '('], ['number:number', \%base], ['number:text', {}, ')'], ['style:map',{ 'style:condition' => 'value()>=0', 'style:apply-style-name' => "NP39", }] ); &_create_positive_style("NP39", $nstyle, \%base); } elsif ($val == 40) { ## (#,##0.00_);[Red](#,##0.00) $celltype{$style} = ['float', "N40"]; $nstyle = 'number-style'; my %base = %{&_prepare_float('#,##0.00')}; @children = ( ['style:text-properties', {'fo:color' => '#ff0000'}], ['number:text', {}, '('], ['number:number', \%base], ['number:text', {}, ')'], ['style:map', { 'style:condition' => 'value()>=0', 'style:apply-style-name' => "NP40", }], ); &_create_positive_style("NP40", $nstyle, \%base); } elsif ($val == 41) { $celltype{$style} = 'float'; } elsif ($val == 42) { $celltype{$style} = 'currency'; } elsif ($val == 43) { $celltype{$style} = 'float'; } elsif ($val == 44) { $celltype{$style} = 'currency'; } elsif ($val == 45) { ## mm:ss $celltype{$style} = ['time', "N45"]; $nstyle = 'time-style'; @nextras = ('references' => { 'number:automatic-order' => 'true'}); @children = ( ['number:minutes', {'number:style' => 'long'}], ['number:text', {}, ':'], ['number:seconds', {'number:style' => 'long'}], ); } elsif ($val == 46) { ## [h]:mm:ss $celltype{$style} = ['time', "N46"]; $nstyle = 'time-style'; @nextras = ('references' => { 'number:automatic-order' => 'true', 'number:truncate-on-overflow' => 'false'}); @children = ( ['number:hours', {}], ['number:text', {}, ':'], ['number:minutes', {'number:style' => 'long'}], ['number:text', {}, ':'], ['number:seconds', {'number:style' => 'long'}], ); } elsif ($val == 47) { ## mm:ss.0 $celltype{$style} = ['time', "N47"]; $nstyle = 'time-style'; @nextras = ('references' => { 'number:automatic-order' => 'true'}); @children = ( ['number:minutes', {'number:style' => 'long'}], ['number:text', {}, ':'], ['number:seconds', {'number:style' => 'long', 'number:decimal-places' => 1}], ); } elsif ($val == 48) { ## ##0.0E+0 $celltype{$style} = ['float', "N48"]; $nstyle = 'number-style'; %nproperties = (); push @children, ['number:scientific-number', {%{&_prepare_float(0.0)}, 'number:min-exponent-digits' => 1 }]; } elsif ($val == 49) { $celltype{$style} = 'string'; } # $nstyle is set on new styles if ($nstyle) { my $cstyle = $ods->createStyle( $celltype{$style}[1], namespace => 'number', type => $nstyle, properties => \%nproperties, @nextras, ); for my $child (@children) { $cstyle->insert_new_elt('last_child', @$child); } $style_table{$fval} = 1; } } } # Maintain a hash table to keep the final style list size down $Data::Dumper::Sortkeys = 1; my $mystyle = Digest::MD5::md5_hex(Dumper(\%properties, \@extras)); if (!$style_table{$mystyle}) { $ods->createStyle( $style, family => 'table-cell', properties => $properties{cell}, @extras, ); $ods->updateStyle( $style, properties => { -area => 'text', %{$properties{text}} } ) if $properties{text}; $ods->updateStyle( $style, properties => { -area => 'paragraph', %{$properties{paragraph}} } ) if $properties{paragraph}; $style_table{$mystyle} = [$style, \%properties]; } unshift @style_stack, $style_table{$mystyle}; } sub _named_format { my ($name, $t, $format) = @_; $format->{att}{$name} = 1; &_format_handler($t, $format); } sub _format_cleanup_handler { my ($t, $format) = @_; shift @style_stack; } sub _ods_process { my ($filename, $template) = @_; $ods = ooDocument(file => $filename, create => 'spreadsheet'); my $parser = XML::Twig->new( start_tag_handlers => { worksheet => \&_worksheet_handler, row => \&_row_handler, cell => \&_cell_handler, formula => \&_formula_handler, format => \&_format_handler, bold => sub { &_named_format('bold', @_) }, hidden => sub { &_named_format('hidden', @_) }, italic => sub { &_named_format('italic', @_) }, shadow => sub { &_named_format('shadow', @_) }, strikeout => sub { &_named_format('strikeout', @_) }, }, twig_handlers => { format => \&_format_cleanup_handler, bold => \&_format_cleanup_handler, hidden => \&_format_cleanup_handler, italic => \&_format_cleanup_handler, shadow => \&_format_cleanup_handler, strikeout => \&_format_cleanup_handler, } ); $parser->parse($template); $parser->purge; $ods->save; } sub get_template { my $name = shift; return "${name}.odst"; } sub preprocess { my $rawvars = shift; my $vars; my $type = ref $rawvars; #XXX fix escaping function return $rawvars if $type =~ /^LedgerSMB::Locale/; return unless defined $rawvars; if ( $type eq 'ARRAY' ) { for (@{$rawvars}) { push @{$vars}, preprocess( $_ ); } } elsif (!$type) { return escapeHTML($rawvars); } elsif ($type eq 'SCALAR' or $type eq 'Math::BigInt::GMP') { return escapeHTML($$rawvars); } else { # Hashes and objects for ( keys %{$rawvars} ) { $vars->{preprocess($_)} = preprocess( $rawvars->{$_} ); } } return $vars; } sub process { my $parent = shift; my $cleanvars = shift; my $template; my $source; my $tempdir = ${LedgerSMB::Sysconfig::tempdir}; my $output = ''; $parent->{outputfile} ||= "$tempdir/$parent->{template}-output-$$"; if (ref $parent->{template} eq 'SCALAR') { $source = $parent->{template}; } elsif (ref $parent->{template} eq 'ARRAY') { $source = join "\n", @{$parent->{template}}; } else { $source = get_template($parent->{template}); } $template = Template->new({ INCLUDE_PATH => $parent->{include_path}, START_TAG => quotemeta(' quotemeta('?>'), DELIMITER => ';', DEBUG => ($parent->{debug})? 'dirs': undef, DEBUG_FORMAT => '', }) || throw Error::Simple Template->error(); if (not $template->process( $source, {%$cleanvars, %$LedgerSMB::Template::TTI18N::ttfuncs, 'escape' => \&preprocess}, \$output, binmode => ':utf8')) { throw Error::Simple $template->error(); } &_ods_process("$parent->{outputfile}.ods", $output); $parent->{mimetype} = 'application/vnd.oasis.opendocument.spreadsheet'; } sub postprocess { my $parent = shift; $parent->{rendered} = "$parent->{outputfile}.ods"; return $parent->{rendered}; } 1;