]> git.donarmstrong.com Git - bin.git/blob - txt2xls
147ace9479185c0028719b8681bad706e4a1fe47
[bin.git] / txt2xls
1 #! /usr/bin/perl
2 # txt2xls turns text files into excel workbooks, and is released
3 # under the terms of the GPL version 2, or any later version, at your
4 # option. See the file README and COPYING for more information.
5 # Copyright 2008 by Don Armstrong <don@donarmstrong.com>.
6 # $Id: perl_script 1153 2008-04-08 00:04:20Z don $
7
8
9 use warnings;
10 use strict;
11
12 use Getopt::Long;
13 use Pod::Usage;
14
15 =head1 NAME
16
17 txt2xls - Turns a (set of) text file(s) into an excel workbook
18
19 =head1 SYNOPSIS
20
21  [options]
22
23  Options:
24   --tsv, -t tab separated value mode (Default)
25   --ssv, -s space separated value mode
26   --csv, -c comma separated value mode
27   --r-mode, -r R mode (Default)
28   --debug, -d debugging level (Default 0)
29   --help, -h display this help
30   --man, -m display manual
31
32 =head1 OPTIONS
33
34 =over
35
36 =item B<--debug, -d>
37
38 Debug verbosity. (Default 0)
39
40 =item B<--help, -h>
41
42 Display brief useage information.
43
44 =item B<--man, -m>
45
46 Display this manual.
47
48 =back
49
50 =head1 EXAMPLES
51
52
53 =cut
54
55
56 use vars qw($DEBUG);
57
58 use Text::CSV;
59 use Spreadsheet::WriteExcel;
60
61 my %options = (debug           => 0,
62                help            => 0,
63                man             => 0,
64                remove_name     => [],
65                );
66
67 GetOptions(\%options,
68            'tsv|t',
69            'ssv|s',
70            'csv|c',
71            'rmode|r-mode|r!',
72            'remove_name|remove-name=s@',
73            'debug|d+','help|h|?','man|m');
74
75 pod2usage() if $options{help};
76 pod2usage({verbose=>2}) if $options{man};
77
78 $DEBUG = $options{debug};
79
80 my @USAGE_ERRORS;
81 if (0 == grep {exists $options{$_}} qw(tsv ssv csv)) {
82     $options{tsv} = 1
83 }
84 if (1 < grep {exists $options{$_}} qw(tsv ssv csv)) {
85      push @USAGE_ERRORS,"You can only pass one of --tsv, --ssv, or --csv";
86 }
87
88 pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
89
90 if (not @ARGV) {
91     # we'll use this as a special indicator to read stdin
92     push @ARGV,undef;
93 }
94
95 my $sep_char = "\t";
96 if ($options{csv}) {
97     $sep_char = ',';
98 }
99 elsif ($options{ssv}) {
100     $sep_char = ' ';
101 }
102
103 my $csv = Text::CSV->new({sep_char=>$sep_char});
104 my $wb = Spreadsheet::WriteExcel->new(\*STDOUT);
105 for my $file (@ARGV) {
106     my $fh;
107     if (not defined $file) {
108         $fh = \*STDIN;
109         $file = "STDIN";
110     }
111     else {
112         $fh = IO::File->new($file,'r') or
113             die "Unable to open $file for reading: $!";
114     }
115     my $ws_name = $file;
116     foreach my $remove (@{$options{remove_name}}) {
117         $ws_name =~ s{\Q$remove\E}{}g;
118     }
119     $ws_name =~ s{\.[^\.]+$}{}g;
120     $ws_name =~ s/_+/ /g;
121     $ws_name =~ s{[\]:*?\/\] ]+}{ }g;
122     $ws_name =~ s{(?:^\s+|\s+$)}{}g;
123     $ws_name =~ s{^(.{0,31}).*$}{$1};
124     my $ws = $wb->add_worksheet($ws_name) or
125         die "Unable to add worksheet to workbook";
126     my $row = 1;
127     my @header_row;
128     my $overflow = 0;
129     my $r_mode = $options{r_mode} // 0;
130     # set to 1 if we have attempted to autodetect R mode
131     my $r_mode_autodetected = 0;
132     while (<$fh>) {
133         chomp;
134         # parse the line
135         die "Unable to parse line $. of $file" unless $csv->parse($_);
136         my @row = $csv->fields();
137         if ($row==1 and not $r_mode_autodetected) {
138             @header_row = @row;
139             $row++;
140             next;
141         }
142         if ($row==2 and not $r_mode_autodetected) {
143             $r_mode_autodetected = 1;
144             if (@row == (@header_row+1)) {
145                 $r_mode = 1 unless exists $options{r_mode} and defined $options{r_mode};
146             }
147             if ($r_mode) {
148                 # R doesn't output headers for rownames
149                 unshift @header_row,'';
150             }
151             output_row(\@header_row,1,$ws);
152         }
153         if ($row > 65536) { # ok, we're going to overflow here
154             my $t_ws_name = $ws_name;
155             my $maxlen = 31-length('.'.$overflow);
156             $t_ws_name =~ s{^(.{0,$maxlen}).*$}{$1};
157             $ws = $wb->add_worksheet($ws_name.'.'.$overflow);
158             $row=1;
159             output_row(\@header_row,$row,$ws);
160             $row++;
161         }
162         if ($row==1) {
163             @header_row = @row;
164         }
165         output_row(\@row,$row,$ws);
166         $row++;
167     }
168 }
169
170 sub output_row{
171     my ($data,$row,$ws) = @_;
172     my @columns = ('A'..'Z','AA'..'ZZ');
173     for my $i (0..$#{$data}) {
174         $ws->write($columns[$i].$row,$data->[$i]);
175     }
176 }
177
178 __END__