Merge branch 'master' of git.freeside.biz:/home/git/freeside
[freeside.git] / httemplate / search / elements / search-xls.html
index 0b5636c..c4265e8 100644 (file)
@@ -1,20 +1,24 @@
-<% $data %>
 <%init>
 
 my %args = @_;
-my $type   = $args{'type'};
 my $header = $args{'header'};
 my $rows   = $args{'rows'};
 my %opt    = %{ $args{'opt'} };    
 
+my $override = scalar(@$rows) >= 65536 ? 'XLSX' : '';
+
+my $format = $FS::CurrentUser::CurrentUser->spreadsheet_format($override);
+
+my $filename = $opt{'name'} || PL($opt{'name_singular'});
+$filename .= $format->{extension};
+
 #http_header('Content-Type' => 'application/excel' ); #eww
 #http_header('Content-Type' => 'application/msexcel' ); #alas
 #http_header('Content-Type' => 'application/x-msexcel' ); #?
 
 #http://support.microsoft.com/kb/199841
-http_header('Content-Type' => 'application/vnd.ms-excel' );
-http_header('Content-Disposition' => 
-  'attachment;filename="'.($opt{'name'} || PL($opt{'name_singular'}) ).'.xls"');
+http_header('Content-Type' => $format->{mime_type} );
+http_header('Content-Disposition' => qq!attachment;filename="$filename"! );
  
 #http://support.microsoft.com/kb/812935
 #http://support.microsoft.com/kb/323308
@@ -22,12 +26,44 @@ $HTML::Mason::Commands::r->headers_out->{'Cache-control'} = 'max-age=0';
 
 my $data = '';
 my $XLS = new IO::Scalar \$data;
-my $workbook = Spreadsheet::WriteExcel->new($XLS)
-  or die "Error opening .xls file: $!";
+my $workbook = $format->{class}->new($XLS)
+  or die "Error opening Excel file: $!";
+
+my $title = $opt{'title'};
+$title =~ s/[\[\]\:\*\?\/\/]//g;
+$title = substr($title, 0, 31);
+
+# append a single worksheet
+$m->comp( 'SELF:worksheet',
+  workbook  => $workbook,
+  title     => $title,
+  opt       => \%opt,
+  header    => $header,
+  rows      => $rows
+);
+
+$workbook->close();# or die "Error creating .xls file: $!";
+
+http_header('Content-Length' => length($data) );
+$m->clear_buffer();
+$m->print($data);
+
+</%init>
+<%method worksheet>
+<%args>
+$workbook
+$title
+%opt
+$header
+$rows
+</%args>
+<%perl>
+
+my $worksheet = $workbook->add_worksheet($title);
 
-my $worksheet = $workbook->add_worksheet(substr($opt{'title'},0,31));
+#$worksheet->protect();
 
-$worksheet->protect();
+my $style = $opt{style};
 
 my($r,$c) = (0,0);
 
@@ -37,19 +73,65 @@ my $header_format = $workbook->add_format(
   bg_color => 55, #22,
   bottom   => 3,
 );
+my $footer_format = $workbook->add_format(
+  italic   => 1,
+  locked   => 1,
+  bg_color => 55,
+  top      => 3,
+);
 my $default_format = $workbook->add_format(locked => 0);
 
 my %money_format;
 my $money_char = FS::Conf->new->config('money_char') || '$';
 
-my $writer = sub {
+my %date_format;
+xl_parse_date_init();
+
+my %bold_format;
+
+my @widths;
+
+my $writer;
+$writer = sub {
   # Wrapper for $worksheet->write.
   # Do any massaging of the value/format here.
   my ($r, $c, $value, $format) = @_;
-  if ( $value =~ /^\Q$money_char\E(\d+\.?\d*)$/ ) {
+  #warn "writer called with format $format\n";
+
+  if ( ref $value eq 'ARRAY' ) {
+    # imitate the write_row() method: write the array into a column starting
+    # with $r.
+    # (currently only used in the footer; to use it anywhere else we'd need
+    # some way to return the number of rows written)
+    foreach my $v (@$value) {
+      $writer->($r, $c, $v, $format);
+      $r++;
+    }
+    return;
+  }
+
+  my $bold = 0;
+  my $date = 0;
+  if ( $style->[$c] eq 'b' or $value =~ /<b>/i ) { # the only one in common use
+    $value =~ s[</?b>][]ig;
+    if ( !exists($bold_format{$format}) ) {
+      $bold_format{$format} = $workbook->add_format();
+      $bold_format{$format}->copy($format);
+      $bold_format{$format}->set_bold();
+    }
+    $format = $bold_format{$format};
+    $bold = 1;
+  }
+
+  # convert HTML entities
+  # both Spreadsheet::WriteExcel and Excel::Writer::XLSX accept UTF-8 strings
+  $value = decode_entities($value);
+
+  if ( $value =~ /^\Q$money_char\E(-?\d+\.?\d*)$/ ) {
     # Currency: strip the symbol, clone the requested format,
     # and format it for currency
     $value = $1;
+#    warn "formatting $value as money\n";
     if ( !exists($money_format{$format}) ) {
       $money_format{$format} = $workbook->add_format();
       $money_format{$format}->copy($format);
@@ -57,7 +139,33 @@ my $writer = sub {
     }
     $format = $money_format{$format};
   }
+  elsif ( $value =~ /^([A-Z][a-z]{2}) (\d{2}) (\d{4})$/ ) {
+    # Date: convert the value to an Excel date number and set 
+    # the format
+    $value = xl_parse_date($value);
+#    warn "formatting $value as date\n";
+    if ( !exists($date_format{$format}) ) {
+      $date_format{$format} = $workbook->add_format();
+      $date_format{$format}->copy($format);
+      $date_format{$format}->set_num_format('mmm dd yyyy');
+    }
+    $format = $date_format{$format};
+    $date = 1;
+  }
+  else {
+    # String: replace line breaks with newlines
+    $value =~ s/<BR>/\n/gi;
+  }
+  #warn "writing with format $format\n";
   $worksheet->write($r, $c, $value, $format);
+
+  # estimate width
+  # use Font::TTFMetrics; # would work, but we can't redistribute the font...
+  my $width = length($value);
+  $width = 11 if $date;
+  $width *= 1.1 if $bold;
+  $width += 1; # pad it a little
+  $widths[$c] = $width if $width > ($widths[$c] || 0);
 };
 
 $writer->( $r, $c++, $_, $header_format ) foreach @$header;
@@ -104,8 +212,20 @@ foreach my $row ( @$rows ) {
 
 }
 
-$workbook->close();# or die "Error creating .xls file: $!";
+if ( $opt{'footer'} ) {
+  $r++;
+  $c = 0;
+  foreach my $item (@{ $opt{'footer'} }) {
+    if ( ref($item) eq 'CODE' ) {
+      $item = &{$item}();
+    }
+    $writer->( $r, $c++, $item, $footer_format );
+  }
+}
 
-http_header('Content-Length' => length($data) );
+for ( my $x = 0; $x < scalar @widths; $x++ ) {
+  $worksheet->set_column($x, $x, $widths[$x]);
+}
 
-</%init>
+</%perl>
+</%method>