Merge branch 'master' of git.freeside.biz:/home/git/freeside
[freeside.git] / httemplate / search / elements / search-xls.html
index 8a6ad5c..c4265e8 100644 (file)
-<% $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-Type' => $format->{mime_type} );
+http_header('Content-Disposition' => qq!attachment;filename="$filename"! );
 #http://support.microsoft.com/kb/812935
 #http://support.microsoft.com/kb/323308
 $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(substr($opt{'title'},0,31));
+my $worksheet = $workbook->add_worksheet($title);
+
+#$worksheet->protect();
+
+my $style = $opt{style};
 
 my($r,$c) = (0,0);
 
-$worksheet->write($r, $c++, $_) foreach @$header;
+my $header_format = $workbook->add_format(
+  bold     => 1,
+  locked   => 1,
+  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 %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) = @_;
+  #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);
+      $money_format{$format}->set_num_format($money_char.'#0.00#');
+    }
+    $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;
 
 foreach my $row ( @$rows ) {
   $r++;
@@ -37,45 +178,54 @@ foreach my $row ( @$rows ) {
 
     #my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : '';
     #my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : '';
+    #could also translate color, size, style into xls equivalents?
+    my $formats = $opt{'xls_format'} ? [ @{$opt{'xls_format'}} ] : [];
 
     foreach my $field ( @{$opt{'fields'}} ) {
-      #my $align = $aligns ? shift @$aligns : '';
-      #$align = " ALIGN=$align" if $align;
-      #my $a = '';
-      #if ( $links ) {
-      #  my $link = shift @$links;
-      #  $link = &{$link}($row) if ref($link) eq 'CODE';
-      #  if ( $link ) {
-      #    my( $url, $method ) = @{$link};
-      #    if ( ref($method) eq 'CODE' ) {
-      #      $a = $url. &{$method}($row);
-      #    } else {
-      #      $a = $url. $row->$method();
-      #    }
-      #    $a = qq(<A HREF="$a">);
-      #  }
-      #}
+
+      my $xls_format = $default_format;
+
+      if ( my $format = shift @$formats ) {
+        $format = &{$format}($row) if ref($format) eq 'CODE';
+        $format ||= {};
+        $xls_format = $workbook->add_format(locked=>0, %$format);
+      }
+
       if ( ref($field) eq 'CODE' ) {
         foreach my $value ( &{$field}($row) ) {
           if ( ref($value) eq 'ARRAY' ) { 
-            $worksheet->write($r, $c++, '(N/A)' ); #unimplemented
+            $writer->($r, $c++, '(N/A)' ); #unimplemented
           } else {
-            $worksheet->write($r, $c++, $value );
+            $writer->($r, $c++, $value, $xls_format );
           }
         }
       } else {
-        $worksheet->write($r, $c++, $row->$field() );
+        $writer->( $r, $c++, $row->$field(), $xls_format );
       }
     }
 
   } else {
-    $worksheet->write($r, $c++, $_) foreach @$row;
+    # no need for each row to need a new format
+    #my $xls_format = $workbook->add_format(locked=>0);
+    $writer->( $r, $c++, $_, $default_format ) foreach @$row;
   }
 
 }
 
-$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) );
-</%init>
+for ( my $x = 0; $x < scalar @widths; $x++ ) {
+  $worksheet->set_column($x, $x, $widths[$x]);
+}
+
+</%perl>
+</%method>