Funktion zum direkten Ausdrucken von PDF-Dokumenten aus dem ReportGenerator heraus...
[kivitendo-erp.git] / SL / ReportGenerator.pm
1 package SL::ReportGenerator;
2
3 use IO::Wrap;
4 use List::Util qw(max);
5 use Text::CSV_XS;
6 use Text::Iconv;
7
8 use SL::Form;
9
10 # Cause locales.pl to parse these files:
11 # parse_html_template('report_generator/html_report')
12 # parse_html_template('report_generator/pdf_report')
13
14 sub new {
15   my $type = shift;
16
17   my $self = { };
18
19   $self->{myconfig} = shift;
20   $self->{form}     = shift;
21
22   $self->{data}     = [];
23   $self->{options}  = {
24     'std_column_visibility' => 0,
25     'output_format'         => 'HTML',
26     'allow_pdf_export'      => 1,
27     'allow_csv_export'      => 1,
28     'html_template'         => 'report_generator/html_report',
29     'pdf_template'          => 'report_generator/pdf_report',
30     'pdf_export'            => {
31       'paper_size'          => 'a4',
32       'orientation'         => 'landscape',
33       'font_name'           => 'Verdana',
34       'font_size'           => '7',
35       'margin_top'          => 1.5,
36       'margin_left'         => 1.5,
37       'margin_bottom'       => 1.5,
38       'margin_right'        => 1.5,
39       'number'              => 1,
40       'print'               => 0,
41       'printer_id'          => 0,
42       'copies'              => 1,
43     },
44     'csv_export'            => {
45       'quote_char'          => '"',
46       'sep_char'            => ';',
47       'escape_char'         => '"',
48       'eol_style'           => 'Unix',
49       'headers'             => 1,
50     },
51   };
52   $self->{export}   = {
53     'nextsub'       => '',
54     'variable_list' => [],
55   };
56
57   $self->{data_present} = 0;
58
59   bless $self, $type;
60
61   $self->set_options(@_) if (@_);
62
63   $self->_init_escaped_strings_map();
64
65   return $self;
66 }
67
68 sub _init_escaped_strings_map {
69   my $self = shift;
70
71   $self->{escaped_strings_map} = {
72     'ä'  => 'ä',
73     'ö'  => 'ö',
74     'ü'  => 'ü',
75     'Ä'  => 'Ä',
76     'Ö'  => 'Ö',
77     'Ü'  => 'Ü',
78     'ß' => 'ß',
79     '>'    => '>',
80      '&lt;'    => '<',
81     '&quot;'  => '"',
82   };
83
84   my $iconv = $main::locale->{iconv_iso8859};
85
86   if ($iconv) {
87     map { $self->{escaped_strings_map}->{$_} = $iconv->convert($self->{escaped_strings_map}->{$_}) } keys %{ $self->{escaped_strings_map} };
88   }
89 }
90
91 sub set_columns {
92   my $self    = shift;
93   my %columns = @_;
94
95   $self->{columns} = \%columns;
96
97   foreach my $column (values %{ $self->{columns} }) {
98     $column->{visible} = $self->{options}->{std_column_visibility} unless defined $column->{visible};
99   }
100
101   $self->set_column_order(sort keys %{ $self->{columns} });
102 }
103
104 sub set_column_order {
105   my $self    = shift;
106
107   my $order   = 0;
108   my %columns = map { $order++; ($_, $order) } @_;
109
110   foreach my $column (sort keys %{ $self->{columns} }) {
111     next if $columns{$column};
112
113     $order++;
114     $columns{$column} = $order;
115   }
116
117   $self->{column_order} = [ sort { $columns{$a} <=> $columns{$b} } keys %columns ];
118 }
119
120 sub set_sort_indicator {
121   my $self = shift;
122
123   $self->{options}->{sort_indicator_column}    = shift;
124   $self->{options}->{sort_indicator_direction} = shift;
125 }
126
127 sub add_data {
128   my $self = shift;
129
130   my $last_row_set;
131
132   while (my $arg = shift) {
133     my $row_set;
134
135     if ('ARRAY' eq ref $arg) {
136       $row_set = $arg;
137
138     } elsif ('HASH' eq ref $arg) {
139       $row_set = [ $arg ];
140
141     } else {
142       $self->{form}->error('Incorrect usage -- expecting hash or array ref');
143     }
144
145     my @columns_with_default_alignment = grep { defined $self->{columns}->{$_}->{align} } keys %{ $self->{columns} };
146
147     foreach my $row (@{ $row_set }) {
148       foreach my $column (@columns_with_default_alignment) {
149         $row->{$column}          ||= { };
150         $row->{$column}->{align}   = $self->{columns}->{$column}->{align} unless (defined $row->{$column}->{align});
151       }
152
153       foreach my $field (qw(data link)) {
154         map { $row->{$_}->{$field} = [ $row->{$_}->{$field} ] if (ref $row->{$_}->{$field} ne 'ARRAY') } keys %{ $row };
155       }
156     }
157
158     push @{ $self->{data} }, $row_set;
159     $last_row_set = $row_set;
160
161     $self->{data_present} = 1;
162   }
163
164   return $last_row_set;
165 }
166
167 sub add_separator {
168   my $self = shift;
169
170   push @{ $self->{data} }, { 'type' => 'separator' };
171 }
172
173 sub add_control {
174   my $self = shift;
175   my $data = shift;
176
177   push @{ $self->{data} }, $data;
178 }
179
180 sub clear_data {
181   my $self = shift;
182
183   $self->{data}         = [];
184   $self->{data_present} = 0;
185 }
186
187 sub set_options {
188   my $self    = shift;
189   my %options = @_;
190
191   while (my ($key, $value) = each %options) {
192     if ($key eq 'pdf_export') {
193       map { $self->{options}->{pdf_export}->{$_} = $value->{$_} } keys %{ $value };
194     } else {
195       $self->{options}->{$key} = $value;
196     }
197   }
198 }
199
200 sub set_options_from_form {
201   my $self     = shift;
202
203   my $form     = $self->{form};
204   my $myconfig = $self->{myconfig};
205
206   foreach my $key (qw(output_format)) {
207     my $full_key = "report_generator_${key}";
208     $self->{options}->{$key} = $form->{$full_key} if (defined $form->{$full_key});
209   }
210
211   foreach my $format (qw(pdf csv)) {
212     my $opts = $self->{options}->{"${format}_export"};
213     foreach my $key (keys %{ $opts }) {
214       my $full_key = "report_generator_${format}_options_${key}";
215       $opts->{$key} = $key =~ /^margin/ ? $form->parse_amount($myconfig, $form->{$full_key}) : $form->{$full_key};
216     }
217   }
218 }
219
220 sub set_export_options {
221   my $self        = shift;
222
223   $self->{export} = {
224     'nextsub'       => shift,
225     'variable_list' => [ @_ ],
226   };
227 }
228
229 sub get_attachment_basename {
230   my $self     = shift;
231   my $filename =  $self->{options}->{attachment_basename} || 'report';
232   $filename    =~ s|.*\\||;
233   $filename    =~ s|.*/||;
234
235   return $filename;
236 }
237
238 sub generate_with_headers {
239   my $self   = shift;
240   my $format = lc $self->{options}->{output_format};
241   my $form   = $self->{form};
242
243   if (!$self->{columns}) {
244     $form->error('Incorrect usage -- no columns specified');
245   }
246
247   if ($format eq 'html') {
248     my $title      = $form->{title};
249     $form->{title} = $self->{title} if ($self->{title});
250     $form->header();
251     $form->{title} = $title;
252
253     print $self->generate_html_content();
254
255   } elsif ($format eq 'csv') {
256     my $filename = $self->get_attachment_basename();
257     print qq|content-type: text/csv\n|;
258     print qq|content-disposition: attachment; filename=${filename}.csv\n\n|;
259     $self->generate_csv_content();
260
261   } elsif ($format eq 'pdf') {
262     $self->generate_pdf_content();
263
264   } else {
265     $form->error('Incorrect usage -- unknown format (supported are HTML, CSV, PDF)');
266   }
267 }
268
269 sub get_visible_columns {
270   my $self   = shift;
271   my $format = shift;
272
273   return grep { my $c = $self->{columns}->{$_}; $c && $c->{visible} && (($c->{visible} == 1) || ($c->{visible} =~ /\Q${format}\E/i)) } @{ $self->{column_order} };
274 }
275
276 sub html_format {
277   my $self  = shift;
278   my $value = shift;
279
280   $value =  $self->{form}->quote_html($value);
281   $value =~ s/\r//g;
282   $value =~ s/\n/<br>/g;
283
284   return $value;
285 }
286
287 sub prepare_html_content {
288   my $self = shift;
289
290   my ($column, $name, @column_headers);
291
292   my $opts            = $self->{options};
293   my @visible_columns = $self->get_visible_columns('HTML');
294
295   foreach $name (@visible_columns) {
296     $column = $self->{columns}->{$name};
297
298     my $header = {
299       'name'                     => $name,
300       'link'                     => $column->{link},
301       'text'                     => $column->{text},
302       'show_sort_indicator'      => $name eq $opts->{sort_indicator_column},
303       'sort_indicator_direction' => $opts->{sort_indicator_direction},
304     };
305
306     push @column_headers, $header;
307   }
308
309   my ($outer_idx, $inner_idx) = (0, 0);
310   my $next_border_top;
311   my @rows;
312
313   foreach my $row_set (@{ $self->{data} }) {
314     if ('HASH' eq ref $row_set) {
315       if ($row_set->{type} eq 'separator') {
316         if (! scalar @rows) {
317           $next_border_top = 1;
318         } else {
319           $rows[-1]->{BORDER_BOTTOM} = 1;
320         }
321
322         next;
323       }
324
325       my $row_data = {
326         'IS_CONTROL'      => 1,
327         'IS_COLSPAN_DATA' => $row_set->{type} eq 'colspan_data',
328         'NUM_COLUMNS'     => scalar @visible_columns,
329         'BORDER_TOP'      => $next_border_top,
330         'data'            => $row_set->{data},
331       };
332
333       push @rows, $row_data;
334
335       $next_border_top = 0;
336
337       next;
338     }
339
340     $outer_idx++;
341
342     foreach my $row (@{ $row_set }) {
343       $inner_idx++;
344
345       foreach my $col_name (@visible_columns) {
346         my $col = $row->{$col_name};
347         $col->{CELL_ROWS} = [ ];
348         foreach my $i (0 .. scalar(@{ $col->{data} }) - 1) {
349           push @{ $col->{CELL_ROWS} }, {
350             'data' => $self->html_format($col->{data}->[$i]),
351             'link' => $col->{link}->[$i],
352           };
353         }
354
355         # Force at least a &nbsp; to be displayed so that browsers
356         # will format the table cell (e.g. borders etc).
357         if (!scalar @{ $col->{CELL_ROWS} }) {
358           push @{ $col->{CELL_ROWS} }, { 'data' => '&nbsp;' };
359         } elsif ((1 == scalar @{ $col->{CELL_ROWS} }) && (!defined $col->{CELL_ROWS}->[0]->{data} || ($col->{CELL_ROWS}->[0]->{data} eq ''))) {
360           $col->{CELL_ROWS}->[0]->{data} = '&nbsp;';
361         }
362       }
363
364       my $row_data = {
365         'COLUMNS'       => [ map { $row->{$_} } @visible_columns ],
366         'outer_idx'     => $outer_idx,
367         'outer_idx_odd' => $outer_idx % 2,
368         'inner_idx'     => $inner_idx,
369         'BORDER_TOP'    => $next_border_top,
370       };
371
372       push @rows, $row_data;
373
374       $next_border_top = 0;
375     }
376   }
377
378   my @export_variables = $self->{form}->flatten_variables(@{ $self->{export}->{variable_list} });
379
380   my $allow_pdf_export = $opts->{allow_pdf_export} && (-x $main::html2ps_bin) && (-x $main::ghostscript_bin);
381
382   eval { require PDF::API2; require PDF::Table; };
383   $allow_pdf_export |= 1 if (! $@);
384
385   my $variables = {
386     'TITLE'                => $opts->{title},
387     'TOP_INFO_TEXT'        => $self->html_format($opts->{top_info_text}),
388     'RAW_TOP_INFO_TEXT'    => $opts->{raw_top_info_text},
389     'BOTTOM_INFO_TEXT'     => $self->html_format($opts->{bottom_info_text}),
390     'RAW_BOTTOM_INFO_TEXT' => $opts->{raw_bottom_info_text},
391     'ALLOW_PDF_EXPORT'     => $allow_pdf_export,
392     'ALLOW_CSV_EXPORT'     => $opts->{allow_csv_export},
393     'SHOW_EXPORT_BUTTONS'  => ($allow_pdf_export || $opts->{allow_csv_export}) && $self->{data_present},
394     'COLUMN_HEADERS'       => \@column_headers,
395     'NUM_COLUMNS'          => scalar @column_headers,
396     'ROWS'                 => \@rows,
397     'EXPORT_VARIABLES'     => \@export_variables,
398     'EXPORT_VARIABLE_LIST' => join(' ', @{ $self->{export}->{variable_list} }),
399     'EXPORT_NEXTSUB'       => $self->{export}->{nextsub},
400     'DATA_PRESENT'         => $self->{data_present},
401   };
402
403   return $variables;
404 }
405
406 sub generate_html_content {
407   my $self      = shift;
408   my $variables = $self->prepare_html_content();
409
410   return $self->{form}->parse_html_template($self->{options}->{html_template}, $variables);
411 }
412
413 sub _cm2bp {
414   # 1 bp = 1/72 in
415   # 1 in = 2.54 cm
416   return $_[0] * 72 / 2.54;
417 }
418
419 sub render_pdf_with_pdf_api2 {
420   my $self       = shift;
421   my $variables  = $self->prepare_html_content();
422   my $form       = $self->{form};
423   my $myconfig   = $self->{myconfig};
424
425   my $opts       = $self->{options};
426   my $params     = $opts->{pdf_export};
427
428   my (@data, @column_props, @cell_props);
429
430   my $data_row        = [];
431   my $cell_props_row  = [];
432   my @visible_columns = $self->get_visible_columns('HTML');
433
434   foreach $name (@visible_columns) {
435     $column = $self->{columns}->{$name};
436
437     push @{ $data_row },       $column->{text};
438     push @{ $cell_props_row }, {};
439     push @column_props,        { 'justify' => $column->{align} eq 'right' ? 'right' : 'left' };
440   }
441
442   push @data,       $data_row;
443   push @cell_props, $cell_props_row;
444
445   my $num_columns = scalar @column_props;
446
447   foreach my $row_set (@{ $self->{data} }) {
448     if ('HASH' eq ref $row_set) {
449       if ($row_set->{type} eq 'colspan_data') {
450         push @data, [ $row_set->{data} ];
451
452         $cell_props_row = [];
453         push @cell_props, $cell_props_row;
454
455         foreach (0 .. $num_columns - 1) {
456           push @{ $cell_props_row }, { 'background_color' => '#000000',
457                                        'font_color'       => '#ffffff', };
458         }
459       }
460       next;
461     }
462
463     foreach my $row (@{ $row_set }) {
464       $data_row = [];
465       push @data, $data_row;
466
467       my $col_idx = 0;
468       foreach my $col_name (@visible_columns) {
469         my $col = $row->{$col_name};
470         push @{ $data_row }, join("\n", @{ $col->{data} });
471
472         $column_props[$col_idx]->{justify} = 'right' if ($col->{align} eq 'right');
473
474         $col_idx++;
475       }
476
477       $cell_props_row = [];
478       push @cell_props, $cell_props_row;
479
480       foreach (0 .. $num_columns - 1) {
481         push @{ $cell_props_row }, { };
482       }
483     }
484   }
485
486   foreach my $i (0 .. scalar(@data) - 1) {
487     my $aref             = $data[$i];
488     my $num_columns_here = scalar @{ $aref };
489
490     if ($num_columns_here < $num_columns) {
491       push @{ $aref }, ('') x ($num_columns - $num_columns_here);
492     } elsif ($num_columns_here > $num_columns) {
493       splice @{ $aref }, $num_columns;
494     }
495   }
496
497   my $papersizes = {
498     'a3'         => [ 842, 1190 ],
499     'a4'         => [ 595,  842 ],
500     'a5'         => [ 420,  595 ],
501     'letter'     => [ 612,  792 ],
502     'legal'      => [ 612, 1008 ],
503   };
504
505   my %supported_fonts = map { $_ => 1 } qw(courier georgia helvetica times verdana);
506
507   my $paper_size  = defined $params->{paper_size} && defined $papersizes->{lc $params->{paper_size}} ? lc $params->{paper_size} : 'a4';
508   my ($paper_width, $paper_height);
509
510   if (lc $params->{orientation} eq 'landscape') {
511     ($paper_width, $paper_height) = @{$papersizes->{$paper_size}}[1, 0];
512   } else {
513     ($paper_width, $paper_height) = @{$papersizes->{$paper_size}}[0, 1];
514   }
515
516   my $margin_top        = _cm2bp($params->{margin_top}    || 1.5);
517   my $margin_bottom     = _cm2bp($params->{margin_bottom} || 1.5);
518   my $margin_left       = _cm2bp($params->{margin_left}   || 1.5);
519   my $margin_right      = _cm2bp($params->{margin_right}  || 1.5);
520
521   my $table             = PDF::Table->new();
522   my $pdf               = PDF::API2->new();
523   my $page              = $pdf->page();
524
525   $pdf->mediabox($paper_width, $paper_height);
526
527   my $font              = $pdf->corefont(defined $params->{font_name} && $supported_fonts{lc $params->{font_name}} ? ucfirst $params->{font_name} : 'Verdana',
528                                          '-encoding' => $main::dbcharset || 'ISO-8859-15');
529   my $font_size         = $params->{font_size} || 7;
530   my $title_font_size   = $font_size + 1;
531   my $padding           = 1;
532   my $font_height       = $font_size + 2 * $padding;
533   my $title_font_height = $font_size + 2 * $padding;
534
535   my $header_height     = 2 * $title_font_height if ($opts->{title});
536   my $footer_height     = 2 * $font_height       if ($params->{number});
537
538   my $top_text_height   = 0;
539
540   if ($self->{options}->{top_info_text}) {
541     my $top_text     =  $self->{options}->{top_info_text};
542     $top_text        =~ s/\r//g;
543     $top_text        =~ s/\n+$//;
544
545     my @lines        =  split m/\n/, $top_text;
546     $top_text_height =  $font_height * scalar @lines;
547
548     foreach my $line_no (0 .. scalar(@lines) - 1) {
549       my $y_pos    = $paper_height - $margin_top - $header_height - $line_no * $font_height;
550       my $text_obj = $page->text();
551
552       $text_obj->font($font, $font_size);
553       $text_obj->translate($margin_left, $y_pos);
554       $text_obj->text($lines[$line_no]);
555     }
556   }
557
558   $table->table($pdf,
559                 $page,
560                 \@data,
561                 'x'                     => $margin_left,
562                 'w'                     => $paper_width - $margin_left - $margin_right,
563                 'start_y'               => $paper_height - $margin_top                  - $header_height                  - $top_text_height,
564                 'next_y'                => $paper_height - $margin_top                  - $header_height,
565                 'start_h'               => $paper_height - $margin_top - $margin_bottom - $header_height - $footer_height - $top_text_height,
566                 'next_h'                => $paper_height - $margin_top - $margin_bottom - $header_height - $footer_height,
567                 'padding'               => 1,
568                 'background_color_odd'  => '#ffffff',
569                 'background_color_even' => '#eeeeee',
570                 'font'                  => $font,
571                 'font_size'             => $font_size,
572                 'font_color'            => '#000000',
573                 'header_props'          => {
574                   'bg_color'            => '#ffffff',
575                   'repeat'              => 1,
576                   'font_color'          => '#000000',
577                 },
578                 'column_props'          => \@column_props,
579                 'cell_props'            => \@cell_props,
580     );
581
582   foreach my $page_num (1..$pdf->pages()) {
583     my $curpage  = $pdf->openpage($page_num);
584
585     if ($params->{number}) {
586       my $label    = $main::locale->text("Page #1/#2", $page_num, $pdf->pages());
587       my $text_obj = $curpage->text();
588
589       $text_obj->font($font, $font_size);
590       $text_obj->translate(($paper_width - $margin_left - $margin_right) / 2 + $margin_left - $text_obj->advancewidth($label) / 2, $margin_bottom);
591       $text_obj->text($label);
592     }
593
594     if ($opts->{title}) {
595       my $text_obj = $curpage->text();
596
597       $text_obj->font($font, $title_font_size);
598       $text_obj->translate(($paper_width - $margin_left - $margin_right) / 2 + $margin_left - $text_obj->advancewidth($opts->{title}) / 2,
599                            $paper_height - $margin_top);
600       $text_obj->text($opts->{title}, '-underline' => 1);
601     }
602   }
603
604   my $content = $pdf->stringify();
605
606   my $printer_command;
607   if ($params->{print} && $params->{printer_id}) {
608     $form->{printer_id} = $params->{printer_id};
609     $form->get_printer_code($myconfig);
610     $printer_command = $form->{printer_command};
611   }
612
613   if ($printer_command) {
614     $self->_print_content('printer_command' => $printer_command,
615                           'content'         => $content,
616                           'copies'          => $params->{copies});
617     $form->{report_generator_printed} = 1;
618
619   } else {
620     my $filename = $self->get_attachment_basename();
621
622     print qq|content-type: application/pdf\n|;
623     print qq|content-disposition: attachment; filename=${filename}.pdf\n\n|;
624
625     print $content;
626   }
627 }
628
629 sub verify_paper_size {
630   my $self                 = shift;
631   my $requested_paper_size = lc shift;
632   my $default_paper_size   = shift;
633
634   my %allowed_paper_sizes  = map { $_ => 1 } qw(a3 a4 letter legal);
635
636   return $allowed_paper_sizes{$requested_paper_size} ? $requested_paper_size : $default_paper_size;
637 }
638
639 sub render_pdf_with_html2ps {
640   my $self      = shift;
641   my $variables = $self->prepare_html_content();
642   my $form      = $self->{form};
643   my $myconfig  = $self->{myconfig};
644   my $opt       = $self->{options}->{pdf_export};
645
646   my $opt_number     = $opt->{number}                     ? 'number : 1'    : '';
647   my $opt_landscape  = $opt->{orientation} eq 'landscape' ? 'landscape : 1' : '';
648
649   my $opt_paper_size = $self->verify_paper_size($opt->{paper_size}, 'a4');
650
651   my $html2ps_config = <<"END"
652 \@html2ps {
653   option {
654     titlepage: 0;
655     hyphenate: 0;
656     colour: 1;
657     ${opt_landscape};
658     ${opt_number};
659   }
660   paper {
661     type: ${opt_paper_size};
662   }
663   break-table: 1;
664 }
665
666 \@page {
667   margin-top:    $opt->{margin_top}cm;
668   margin-left:   $opt->{margin_left}cm;
669   margin-bottom: $opt->{margin_bottom}cm;
670   margin-right:  $opt->{margin_right}cm;
671 }
672
673 BODY {
674   font-family: Helvetica;
675   font-size:   $opt->{font_size}pt;
676 }
677
678 END
679   ;
680
681   my $printer_command;
682   if ($opt->{print} && $opt->{printer_id}) {
683     $form->{printer_id} = $opt->{printer_id};
684     $form->get_printer_code($myconfig);
685     $printer_command = $form->{printer_command};
686   }
687
688   my $cfg_file_name = Common::tmpname() . '-html2ps-config';
689   my $cfg_file      = IO::File->new($cfg_file_name, 'w') || $form->error($locale->text('Could not write the html2ps config file.'));
690
691   $cfg_file->print($html2ps_config);
692   $cfg_file->close();
693
694   my $html_file_name = Common::tmpname() . '.html';
695   my $html_file      = IO::File->new($html_file_name, 'w');
696
697   if (!$html_file) {
698     unlink $cfg_file_name;
699     $form->error($locale->text('Could not write the temporary HTML file.'));
700   }
701
702   $html_file->print($form->parse_html_template($self->{options}->{pdf_template}, $variables));
703   $html_file->close();
704
705   my $cmdline =
706     "\"${main::html2ps_bin}\" -f \"${cfg_file_name}\" \"${html_file_name}\" | " .
707     "\"${main::ghostscript_bin}\" -q -dSAFER -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sPAPERSIZE=${opt_paper_size} -sOutputFile=- -c .setpdfwrite -";
708
709   my $gs = IO::File->new("${cmdline} |");
710   if ($gs) {
711     my $content;
712
713     if (!$printer_command) {
714       my $filename = $self->get_attachment_basename();
715       print qq|content-type: application/pdf\n|;
716       print qq|content-disposition: attachment; filename=${filename}.pdf\n\n|;
717
718       while (my $line = <$gs>) {
719         print $line;
720       }
721
722     } else {
723       while (my $line = <$gs>) {
724         $content .= $line;
725       }
726     }
727
728     $gs->close();
729     unlink $cfg_file_name, $html_file_name;
730
731     if ($printer_command && $content) {
732       $self->_print_content('printer_command' => $printer_command,
733                             'content'         => $content,
734                             'copies'          => $opt->{copies});
735       $form->{report_generator_printed} = 1;
736     }
737
738   } else {
739     unlink $cfg_file_name, $html_file_name;
740     $form->error($locale->text('Could not spawn html2ps or GhostScript.'));
741   }
742 }
743
744 sub _print_content {
745   my $self   = shift;
746   my %params = @_;
747
748   foreach my $i (1 .. max $params{copies}, 1) {
749     my $printer = IO::File->new("| $params{printer_command}");
750     $main::form->error($main::locale->text('Could not spawn the printer command.')) if (!$printer);
751     $printer->print($params{content});
752     $printer->close();
753   }
754 }
755
756 sub generate_pdf_content {
757   my $self = shift;
758
759   eval { require PDF::API2; require PDF::Table; };
760
761   if ($@) {
762     return $self->render_pdf_with_html2ps(@_);
763   } else {
764     return $self->render_pdf_with_pdf_api2(@_);
765   }
766 }
767
768 sub unescape_string {
769   my $self = shift;
770   my $text = shift;
771
772   foreach my $key (keys %{ $self->{escaped_strings_map} }) {
773     $text =~ s/\Q$key\E/$self->{escaped_strings_map}->{$key}/g;
774   }
775
776   $text =~ s/\Q&amp;\E/&/g;
777
778   return $text;
779 }
780
781 sub generate_csv_content {
782   my $self = shift;
783
784   my %valid_sep_chars    = (';' => ';', ',' => ',', ':' => ':', 'TAB' => "\t");
785   my %valid_escape_chars = ('"' => 1, "'" => 1);
786   my %valid_quote_chars  = ('"' => 1, "'" => 1);
787
788   my $opts        = $self->{options}->{csv_export};
789   my $eol         = $opts->{eol_style} eq 'DOS'               ? "\r\n"                              : "\n";
790   my $sep_char    = $valid_sep_chars{$opts->{sep_char}}       ? $valid_sep_chars{$opts->{sep_char}} : ';';
791   my $escape_char = $valid_escape_chars{$opts->{escape_char}} ? $opts->{escape_char}                : '"';
792   my $quote_char  = $valid_quote_chars{$opts->{quote_char}}   ? $opts->{quote_char}                 : '"';
793
794   $escape_char    = $quote_char if ($opts->{escape_char} eq 'QUOTE_CHAR');
795
796   my $csv = Text::CSV_XS->new({ 'binary'      => 1,
797                                 'sep_char'    => $sep_char,
798                                 'escape_char' => $escape_char,
799                                 'quote_char'  => $quote_char,
800                                 'eol'         => $eol, });
801
802   my $stdout          = wraphandle(\*STDOUT);
803   my @visible_columns = $self->get_visible_columns('CSV');
804
805   if ($opts->{headers}) {
806     $csv->print($stdout, [ map { $self->unescape_string($self->{columns}->{$_}->{text}) } @visible_columns ]);
807   }
808
809   foreach my $row_set (@{ $self->{data} }) {
810     next if ('ARRAY' ne ref $row_set);
811     foreach my $row (@{ $row_set }) {
812       my @data;
813       foreach my $col (@visible_columns) {
814         push @data, join($eol, map { s/\r?\n/$eol/g; $_ } @{ $row->{$col}->{data} });
815       }
816       $csv->print($stdout, \@data);
817     }
818   }
819 }
820
821 1;