line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Data::Table; |
2
|
2
|
50
|
|
2
|
|
55765
|
BEGIN { die "Your perl version is old, see README for instructions" if $] < 5.005; } |
3
|
|
|
|
|
|
|
|
4
|
2
|
|
|
2
|
|
16
|
use strict; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
52
|
|
5
|
2
|
|
|
2
|
|
9
|
use vars qw($VERSION %DEFAULTS); |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
96
|
|
6
|
2
|
|
|
2
|
|
8
|
use Carp; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
333
|
|
7
|
|
|
|
|
|
|
#use Data::Dumper; |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
$VERSION = '1.78'; |
10
|
|
|
|
|
|
|
%DEFAULTS = ( |
11
|
|
|
|
|
|
|
"CSV_DELIMITER"=>',', # controls how to read/write CSV file |
12
|
|
|
|
|
|
|
"CSV_QUALIFIER"=>'"', |
13
|
|
|
|
|
|
|
"OS"=>0, |
14
|
|
|
|
|
|
|
# operatoring system: 0 for UNIX (\n as linebreak), 1 for Windows |
15
|
|
|
|
|
|
|
# (\r\n as linebreak), 2 for MAC (\r as linebreak) |
16
|
|
|
|
|
|
|
# this controls how to read and write CSV/TSV file |
17
|
|
|
|
|
|
|
"ENCODING"=>'UTF-8' |
18
|
|
|
|
|
|
|
# default encoding for fromFile, fromCSV, fromTSV |
19
|
|
|
|
|
|
|
); |
20
|
|
|
|
|
|
|
%Data::Table::TSV_ESC = ( '0'=>"\0", 'n'=>"\n", 't'=>"\t", 'r'=>"\r", 'b'=>"\b", |
21
|
|
|
|
|
|
|
"'"=>"'", '"'=>"\"", '\\'=>"\\" ); |
22
|
|
|
|
|
|
|
%Data::Table::TSV_ENC = ( "\0"=>'0', "\n"=>'n', "\t"=>'t', "\r"=>'r', "\b"=>'b', |
23
|
|
|
|
|
|
|
"'"=>"'", "\""=>'"', "\\"=>'\\' ); |
24
|
2
|
|
|
2
|
|
12
|
use constant ROW_BASED => 0; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
178
|
|
25
|
2
|
|
|
2
|
|
10
|
use constant COL_BASED => 1; |
|
2
|
|
|
|
|
11
|
|
|
2
|
|
|
|
|
73
|
|
26
|
2
|
|
|
2
|
|
8
|
use constant NUMBER => 0; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
81
|
|
27
|
2
|
|
|
2
|
|
10
|
use constant STRING => 1; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
64
|
|
28
|
2
|
|
|
2
|
|
8
|
use constant ASC => 0; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
79
|
|
29
|
2
|
|
|
2
|
|
16
|
use constant DESC => 1; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
88
|
|
30
|
2
|
|
|
2
|
|
10
|
use constant INNER_JOIN => 0; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
67
|
|
31
|
2
|
|
|
2
|
|
8
|
use constant LEFT_JOIN => 1; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
92
|
|
32
|
2
|
|
|
2
|
|
10
|
use constant RIGHT_JOIN => 2; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
64
|
|
33
|
2
|
|
|
2
|
|
21
|
use constant FULL_JOIN => 3; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
117
|
|
34
|
2
|
|
|
2
|
|
11
|
use constant OS_UNIX => 0; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
112
|
|
35
|
2
|
|
|
2
|
|
10
|
use constant OS_PC => 1; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
73
|
|
36
|
2
|
|
|
2
|
|
8
|
use constant OS_MAC => 2; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
29908
|
|
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
sub new { |
39
|
63
|
|
|
63
|
1
|
145
|
my ($pkg, $data, $header, $type, $enforceCheck) = @_; |
40
|
63
|
|
33
|
|
|
173
|
my $class = ref($pkg) || $pkg; |
41
|
63
|
100
|
|
|
|
103
|
$type = 0 unless defined($type); |
42
|
63
|
50
|
|
|
|
104
|
$header=[] unless defined($header); |
43
|
63
|
50
|
|
|
|
92
|
$data=[] unless defined($data); |
44
|
63
|
50
|
|
|
|
92
|
$enforceCheck = 1 unless defined($enforceCheck); |
45
|
|
|
|
|
|
|
confess "new Data::Table: Size of data does not match header\n" |
46
|
2
|
|
|
|
|
5
|
if (($type && (scalar @$data) && $#{$data} != $#{$header}) || |
|
2
|
|
|
|
|
7
|
|
47
|
63
|
50
|
100
|
|
|
247
|
(!$type && (scalar @$data) && $#{$data->[0]} != $#{$header})); |
|
60
|
|
33
|
|
|
92
|
|
|
60
|
|
100
|
|
|
142
|
|
|
|
|
66
|
|
|
|
|
|
|
|
33
|
|
|
|
|
48
|
63
|
|
|
|
|
114
|
my $colHash = checkHeader($header); |
49
|
63
|
100
|
66
|
|
|
170
|
if ($enforceCheck && scalar @$data > 0) { |
|
|
50
|
|
|
|
|
|
50
|
62
|
|
|
|
|
99
|
my $size=scalar @{$data->[0]}; |
|
62
|
|
|
|
|
90
|
|
51
|
62
|
|
|
|
|
107
|
for (my $j =1; $j
|
52
|
340
|
50
|
|
|
|
326
|
confess "Inconsistent array size at data[$j]" unless (scalar @{$data->[$j]} == $size); |
|
340
|
|
|
|
|
634
|
|
53
|
|
|
|
|
|
|
} |
54
|
|
|
|
|
|
|
} elsif (scalar @$data == 0) { |
55
|
1
|
|
|
|
|
2
|
$type = 0; |
56
|
|
|
|
|
|
|
} |
57
|
63
|
|
|
|
|
205
|
my $self={ data=>$data, header=>$header, type=>$type, colHash=>$colHash, OK=>[], MATCH=>[]}; |
58
|
63
|
|
|
|
|
498
|
return bless $self, $class; |
59
|
|
|
|
|
|
|
} |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
sub checkHeader { |
62
|
64
|
|
|
64
|
0
|
74
|
my $header = shift; |
63
|
64
|
|
|
|
|
89
|
my $colHash = {}; |
64
|
64
|
|
|
|
|
112
|
for (my $i = 0; $i < scalar @$header; $i++) { |
65
|
295
|
|
|
|
|
356
|
my $elm = $header->[$i]; |
66
|
|
|
|
|
|
|
#warn "Column name: $elm at column ".($i+1)." is an integer, using an integer column name will mask the corresponding column index!" if ($elm =~ /^\d+$/); |
67
|
295
|
50
|
|
|
|
376
|
confess "Undefined column name (empty or all space) at column ".($i+1) unless $elm; |
68
|
|
|
|
|
|
|
#confess "Header name ".$colHash->{$elm}." appears more than once" if defined($colHash->{$elm}); |
69
|
295
|
50
|
|
|
|
431
|
if (defined($colHash->{$elm})) { |
70
|
0
|
|
|
|
|
0
|
confess "Header name ($elm) appears more than once: in column ".($colHash->{$elm}+1)." and column ".($i+1)."."; |
71
|
|
|
|
|
|
|
} |
72
|
295
|
|
|
|
|
594
|
$colHash->{$elm} = $i; |
73
|
|
|
|
|
|
|
} |
74
|
64
|
|
|
|
|
93
|
return $colHash; |
75
|
|
|
|
|
|
|
} |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
# translate a column name into its position in the header |
78
|
|
|
|
|
|
|
# (also in column-based table) |
79
|
|
|
|
|
|
|
sub colIndex { |
80
|
851
|
|
|
851
|
1
|
1551
|
my ($self, $colID) = @_; |
81
|
851
|
100
|
|
|
|
1471
|
return $self->{colHash}->{$colID} if exists $self->{colHash}->{$colID}; |
82
|
698
|
100
|
|
|
|
2114
|
return $colID if $colID =~ /^\d+$/; |
83
|
11
|
|
|
|
|
53
|
return -1; |
84
|
|
|
|
|
|
|
#if ($colID =~ /\D/) { |
85
|
|
|
|
|
|
|
# my $i = $self->{colHash}->{$colID}; |
86
|
|
|
|
|
|
|
# return -1 unless defined($i); |
87
|
|
|
|
|
|
|
# return $i; |
88
|
|
|
|
|
|
|
#} |
89
|
|
|
|
|
|
|
#return $colID; # assume an index already |
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
sub hasCol { |
93
|
4
|
|
|
4
|
1
|
8
|
my ($self, $col) = @_; |
94
|
4
|
|
|
|
|
8
|
return $self->colIndex($col) >= 0; |
95
|
|
|
|
|
|
|
} |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
sub nofCol { |
98
|
186
|
|
|
186
|
1
|
228
|
my $self = shift; |
99
|
186
|
|
|
|
|
186
|
return scalar @{$self->{header}}; |
|
186
|
|
|
|
|
383
|
|
100
|
|
|
|
|
|
|
} |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
sub isEmpty { |
103
|
8
|
|
|
8
|
1
|
13
|
my $self = shift; |
104
|
8
|
|
|
|
|
13
|
return $self->nofCol == 0; |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
sub nofRow { |
108
|
1654
|
|
|
1654
|
1
|
1841
|
my $self = shift; |
109
|
1654
|
100
|
|
|
|
1634
|
return 0 if (scalar @{$self->{data}} == 0); |
|
1654
|
|
|
|
|
2585
|
|
110
|
|
|
|
|
|
|
return ($self->{type})? |
111
|
1652
|
100
|
|
|
|
2261
|
scalar @{$self->{data}->[0]} : scalar @{$self->{data}}; |
|
679
|
|
|
|
|
1158
|
|
|
973
|
|
|
|
|
1512
|
|
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
sub lastRow { |
115
|
1
|
|
|
1
|
1
|
6
|
my $self = shift; |
116
|
1
|
|
|
|
|
2
|
return $self->nofRow - 1; |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
sub lastCol { |
120
|
1
|
|
|
1
|
1
|
2
|
my $self = shift; |
121
|
1
|
|
|
|
|
4
|
return $self->nofCol - 1; |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
sub colName { |
125
|
0
|
|
|
0
|
1
|
0
|
my ($self, $colNumericIndex) = @_; |
126
|
0
|
|
|
|
|
0
|
return ($self->header())[$colNumericIndex]; |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
sub iterator { |
130
|
1
|
|
|
1
|
1
|
3
|
my ($self, $arg_ref) = @_; |
131
|
1
|
50
|
|
|
|
24
|
my %arg = defined $arg_ref ? %$arg_ref : (); |
132
|
1
|
50
|
|
|
|
8
|
$arg{reverse} = 0 unless exists $arg{reverse}; |
133
|
1
|
50
|
|
|
|
3
|
my $current_row = $arg{reverse} ? $self->lastRow : 0; |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
return sub { |
136
|
155
|
|
|
155
|
|
302
|
my $rowIdx = shift; |
137
|
155
|
100
|
|
|
|
210
|
if (defined $rowIdx) { # return row index for previously returned record |
138
|
77
|
50
|
|
|
|
109
|
my $prevRow = $arg{reverse} ? $current_row+1 : $current_row-1; |
139
|
77
|
50
|
33
|
|
|
137
|
return ($prevRow<0 or $prevRow > $self->nofRow-1)? undef: $prevRow; |
140
|
|
|
|
|
|
|
} |
141
|
78
|
100
|
66
|
|
|
133
|
return undef if $current_row < 0 or $current_row > $self->nofRow - 1; |
142
|
77
|
|
|
|
|
110
|
my $oldRow = $current_row; |
143
|
77
|
50
|
|
|
|
104
|
$arg{reverse} ? $current_row-- : $current_row++; |
144
|
77
|
|
|
|
|
96
|
return $self->rowHashRef($oldRow); |
145
|
|
|
|
|
|
|
} |
146
|
1
|
|
|
|
|
7
|
} |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
# still need to consider quotes and comma in string |
149
|
|
|
|
|
|
|
# need to get csv specification |
150
|
|
|
|
|
|
|
sub csvEscape { |
151
|
86
|
|
|
86
|
1
|
132
|
my ($s, $arg_ref) = @_; |
152
|
86
|
|
|
|
|
118
|
my ($delimiter, $qualifier) = ($Data::Table::DEFAULTS{CSV_DELIMITER}, $Data::Table::DEFAULTS{CSV_QUALIFIER}); |
153
|
86
|
50
|
33
|
|
|
210
|
$delimiter = $arg_ref->{'delimiter'} if (defined($arg_ref) && defined($arg_ref->{'delimiter'})); |
154
|
86
|
50
|
33
|
|
|
185
|
$qualifier = $arg_ref->{'qualifier'} if (defined($arg_ref) && defined($arg_ref->{'qualifier'})); |
155
|
86
|
50
|
|
|
|
108
|
return '' unless defined($s); |
156
|
86
|
|
|
|
|
86
|
my $qualifier2 = $qualifier; |
157
|
86
|
50
|
|
|
|
111
|
$qualifier2 = substr($qualifier, 1, 1) if length($qualifier)>1; # in case qualifier is a special symbol for regular expression |
158
|
86
|
|
|
|
|
156
|
$s =~ s/$qualifier/$qualifier2$qualifier2/g; |
159
|
86
|
100
|
|
|
|
208
|
if ($s =~ /[$qualifier$delimiter\r\n]/) { return "$qualifier2$s$qualifier2"; } |
|
2
|
|
|
|
|
9
|
|
160
|
84
|
|
|
|
|
218
|
return $s; |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
sub tsvEscape { |
164
|
357
|
|
|
357
|
1
|
384
|
my $s = shift; |
165
|
|
|
|
|
|
|
#my %ESC = ( "\0"=>'0', "\n"=>'n', "\t"=>'t', "\r"=>'r', "\b"=>'b', |
166
|
|
|
|
|
|
|
# "'"=>"'", "\""=>'"', "\\"=>'\\' ); |
167
|
|
|
|
|
|
|
## what about \f? MySQL treats \f as f. |
168
|
357
|
50
|
|
|
|
434
|
return "\\N" unless defined($s); |
169
|
357
|
|
|
|
|
452
|
$s =~ s/([\0\\\b\r\n\t"'])/\\$Data::Table::TSV_ENC{$1}/g; |
170
|
357
|
|
|
|
|
610
|
return $s; |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
# output table in CSV format |
174
|
|
|
|
|
|
|
sub csv { |
175
|
4
|
|
|
4
|
1
|
26
|
my ($self, $header, $arg_ref)=@_; |
176
|
4
|
|
|
|
|
5
|
my ($status, @t); |
177
|
4
|
|
|
|
|
6
|
my $s = ''; |
178
|
4
|
|
|
|
|
11
|
my ($OS, $fileName_or_handler) = ($Data::Table::DEFAULTS{OS}, undef); |
179
|
4
|
50
|
33
|
|
|
12
|
$OS = $arg_ref->{'OS'} if (defined($arg_ref) && defined($arg_ref->{'OS'})); |
180
|
4
|
|
|
|
|
10
|
my ($delimiter, $qualifier) = ($Data::Table::DEFAULTS{CSV_DELIMITER}, $Data::Table::DEFAULTS{CSV_QUALIFIER}); |
181
|
4
|
50
|
|
|
|
10
|
if (defined($arg_ref)) { |
182
|
0
|
0
|
|
|
|
0
|
$delimiter = $arg_ref->{'delimiter'} if defined($arg_ref->{'delimiter'}); |
183
|
0
|
0
|
|
|
|
0
|
$qualifier = $arg_ref->{'qualifier'} if defined($arg_ref->{'qualifier'}); |
184
|
0
|
0
|
|
|
|
0
|
$fileName_or_handler = $arg_ref->{'file'} if defined($arg_ref->{'file'}); |
185
|
|
|
|
|
|
|
} |
186
|
4
|
50
|
|
|
|
5
|
my $delimiter2 = $delimiter; $delimiter2 = substr($delimiter, 1, 1) if length($delimiter)>1; |
|
4
|
|
|
|
|
10
|
|
187
|
4
|
50
|
|
|
|
13
|
my $endl = ($OS==2)?"\r":(($OS==1)?"\r\n":"\n"); |
|
|
50
|
|
|
|
|
|
188
|
4
|
50
|
|
|
|
10
|
$header=1 unless defined($header); |
189
|
4
|
50
|
|
|
|
8
|
$s=join($delimiter2, map {csvEscape($_, {delimiter=>$delimiter, qualifier=>$qualifier})} @{$self->{header}}) . $endl if $header; |
|
14
|
|
|
|
|
31
|
|
|
4
|
|
|
|
|
15
|
|
190
|
|
|
|
|
|
|
###### $self->rotate if $self->{type}; |
191
|
4
|
50
|
|
|
|
10
|
if ($self->{data}) { |
192
|
4
|
50
|
|
|
|
10
|
$self->rotate() if ($self->{type}); |
193
|
4
|
|
|
|
|
7
|
my $data=$self->{data}; |
194
|
4
|
|
|
|
|
14
|
for (my $i=0; $i<=$#{$data}; $i++) { |
|
20
|
|
|
|
|
44
|
|
195
|
16
|
|
|
|
|
18
|
$s .= join($delimiter2, map {csvEscape($_, {delimiter=>$delimiter, qualifier=>$qualifier})} @{$data->[$i]}) . $endl; |
|
72
|
|
|
|
|
147
|
|
|
16
|
|
|
|
|
19
|
|
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
} |
198
|
4
|
50
|
|
|
|
8
|
if (defined($fileName_or_handler)) { |
199
|
0
|
|
|
|
|
0
|
my $OUT; |
200
|
0
|
|
|
|
|
0
|
my $isFileHandler = ref($fileName_or_handler) ne ''; |
201
|
0
|
0
|
|
|
|
0
|
if ($isFileHandler) { |
202
|
0
|
|
|
|
|
0
|
$OUT = $fileName_or_handler; |
203
|
|
|
|
|
|
|
} else { |
204
|
0
|
0
|
|
|
|
0
|
open($OUT, "> $fileName_or_handler") or confess "Cannot open $fileName_or_handler to write.\n"; |
205
|
0
|
|
|
|
|
0
|
binmode $OUT; |
206
|
|
|
|
|
|
|
} |
207
|
0
|
|
|
|
|
0
|
print $OUT $s; |
208
|
0
|
0
|
|
|
|
0
|
close($OUT) unless $isFileHandler; |
209
|
|
|
|
|
|
|
} |
210
|
4
|
|
|
|
|
15
|
return $s; |
211
|
|
|
|
|
|
|
} |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
# output table in TSV format |
214
|
|
|
|
|
|
|
sub tsv { |
215
|
4
|
|
|
4
|
1
|
20
|
my ($self, $header, $arg_ref)=@_; |
216
|
4
|
|
|
|
|
7
|
my ($status, @t); |
217
|
4
|
|
|
|
|
6
|
my $s = ''; |
218
|
4
|
|
|
|
|
11
|
my ($OS, $fileName_or_handler, $transform_element) = ($Data::Table::DEFAULTS{OS}, undef, 1); |
219
|
4
|
50
|
|
|
|
10
|
if (defined($arg_ref)) { |
220
|
0
|
0
|
|
|
|
0
|
$OS = $arg_ref->{'OS'} if (defined($arg_ref->{'OS'})); |
221
|
0
|
0
|
|
|
|
0
|
$fileName_or_handler = $arg_ref->{'file'} if (defined($arg_ref->{'file'})); |
222
|
0
|
0
|
|
|
|
0
|
$transform_element = $arg_ref->{'transform_element'} if (defined($arg_ref->{'transform_element'})); |
223
|
|
|
|
|
|
|
} |
224
|
4
|
50
|
|
|
|
10
|
my $endl = ($OS==2)?"\r":(($OS==1)?"\r\n":"\n"); |
|
|
50
|
|
|
|
|
|
225
|
4
|
50
|
|
|
|
8
|
$header=1 unless defined($header); |
226
|
4
|
50
|
|
|
|
7
|
if ($header) { |
227
|
4
|
50
|
|
|
|
7
|
if ($transform_element) { |
228
|
4
|
|
|
|
|
6
|
$s=join("\t", map {tsvEscape($_)} @{$self->{header}}) . $endl; |
|
19
|
|
|
|
|
23
|
|
|
4
|
|
|
|
|
9
|
|
229
|
|
|
|
|
|
|
} else { |
230
|
0
|
|
|
|
|
0
|
$s=join("\t",@{$self->{header}}) . $endl; |
|
0
|
|
|
|
|
0
|
|
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
} |
233
|
|
|
|
|
|
|
###### $self->rotate if $self->{type}; |
234
|
4
|
50
|
|
|
|
13
|
if ($self->{data}) { |
235
|
4
|
50
|
|
|
|
10
|
$self->rotate() if ($self->{type}); |
236
|
4
|
|
|
|
|
6
|
my $data=$self->{data}; |
237
|
4
|
|
|
|
|
5
|
for (my $i=0; $i<=$#{$data}; $i++) { |
|
33
|
|
|
|
|
54
|
|
238
|
29
|
50
|
|
|
|
39
|
if ($transform_element) { |
239
|
29
|
|
|
|
|
27
|
$s .= join("\t", map {tsvEscape($_)} @{$data->[$i]}) . $endl; |
|
164
|
|
|
|
|
182
|
|
|
29
|
|
|
|
|
38
|
|
240
|
|
|
|
|
|
|
} else { |
241
|
0
|
|
|
|
|
0
|
$s .= join("\t", @{$data->[$i]}) . $endl; |
|
0
|
|
|
|
|
0
|
|
242
|
|
|
|
|
|
|
} |
243
|
|
|
|
|
|
|
} |
244
|
|
|
|
|
|
|
} |
245
|
4
|
50
|
|
|
|
9
|
if (defined($fileName_or_handler)) { |
246
|
0
|
|
|
|
|
0
|
my $OUT; |
247
|
0
|
|
|
|
|
0
|
my $isFileHandler = ref($fileName_or_handler) ne ''; |
248
|
0
|
0
|
|
|
|
0
|
if ($isFileHandler) { |
249
|
0
|
|
|
|
|
0
|
$OUT = $fileName_or_handler; |
250
|
|
|
|
|
|
|
} else { |
251
|
0
|
0
|
|
|
|
0
|
open($OUT, "> $fileName_or_handler") or confess "Cannot open $fileName_or_handler to write.\n"; |
252
|
0
|
|
|
|
|
0
|
binmode $OUT; |
253
|
|
|
|
|
|
|
} |
254
|
0
|
|
|
|
|
0
|
print $OUT $s; |
255
|
0
|
0
|
|
|
|
0
|
close($OUT) unless $isFileHandler;; |
256
|
|
|
|
|
|
|
} |
257
|
4
|
|
|
|
|
15
|
return $s; |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
# output table in HTML format |
261
|
|
|
|
|
|
|
sub html { |
262
|
5
|
|
|
5
|
1
|
25
|
my ($self, $colorArrayRef_or_classHashRef, $tag_tbl, $tag_tr, $tag_th, $tag_td, $portrait, $callback) = @_; |
263
|
5
|
|
|
|
|
14
|
my ($s, $s_tr, $s_td, $s_th) = ("", "tr", "", "th"); |
264
|
5
|
|
|
|
|
7
|
my $key; |
265
|
5
|
50
|
|
|
|
21
|
$tag_tbl = { class => "data_table" } unless (ref $tag_tbl eq 'HASH'); |
266
|
5
|
50
|
|
|
|
13
|
$tag_tr = {} unless (ref $tag_tr eq 'HASH'); |
267
|
5
|
50
|
|
|
|
13
|
$tag_th = {} unless (ref $tag_th eq 'HASH'); |
268
|
5
|
50
|
|
|
|
12
|
$tag_td = {} unless (ref $tag_td eq 'HASH'); |
269
|
5
|
100
|
|
|
|
10
|
$portrait = 1 unless defined($portrait); |
270
|
5
|
|
|
|
|
7
|
my $cb=0; |
271
|
5
|
100
|
|
|
|
11
|
if (defined($callback)) { |
272
|
2
|
50
|
|
|
|
7
|
confess "wiki: Expecting subroutine for callback parameter!" if ref($callback) ne 'CODE'; |
273
|
2
|
|
|
|
|
4
|
$cb=1; |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
my $tag2str = sub { |
277
|
1065
|
|
|
1065
|
|
4528
|
my $tag = shift; |
278
|
1065
|
|
|
|
|
1079
|
my $s=""; |
279
|
1065
|
|
|
|
|
1661
|
foreach my $key (keys %$tag) { |
280
|
368
|
50
|
|
|
|
526
|
next unless $tag->{$key}; |
281
|
368
|
50
|
|
|
|
482
|
if ($key eq '') { |
282
|
0
|
|
|
|
|
0
|
$s .=" ".$tag->{$key}; |
283
|
|
|
|
|
|
|
#for backward compatibility, in case the tag is a str |
284
|
|
|
|
|
|
|
# '' => 'align="right" valign="bottom"' |
285
|
|
|
|
|
|
|
} else { |
286
|
368
|
|
|
|
|
713
|
$s .= " $key=\"$tag->{$key}\""; |
287
|
|
|
|
|
|
|
} |
288
|
|
|
|
|
|
|
} |
289
|
1065
|
|
|
|
|
2302
|
return $s; |
290
|
5
|
|
|
|
|
25
|
}; |
291
|
|
|
|
|
|
|
|
292
|
5
|
|
|
|
|
14
|
$s = "($tag_tbl).">\n";
293
|
5
|
|
|
|
|
10
|
my $header=$self->{header}; |
294
|
5
|
|
|
|
|
7
|
my $l_colorByClass = 0; |
295
|
5
|
|
|
|
|
10
|
my @BG_COLOR=("#D4D4BF","#ECECE4","#CCCC99"); |
296
|
5
|
|
|
|
|
16
|
my @CELL_CLASSES=("data_table_odd","data_table_even","data_table_header"); |
297
|
5
|
100
|
66
|
|
|
34
|
if (ref($colorArrayRef_or_classHashRef) eq "HASH") { |
|
|
100
|
|
|
|
|
|
298
|
1
|
|
|
|
|
2
|
$l_colorByClass = 1; |
299
|
1
|
50
|
|
|
|
5
|
$CELL_CLASSES[1]=$colorArrayRef_or_classHashRef->{even} if defined($colorArrayRef_or_classHashRef->{even}); |
300
|
1
|
50
|
|
|
|
4
|
$CELL_CLASSES[0]=$colorArrayRef_or_classHashRef->{odd} if defined($colorArrayRef_or_classHashRef->{odd}); |
301
|
1
|
50
|
|
|
|
4
|
$CELL_CLASSES[2]=$colorArrayRef_or_classHashRef->{header} if defined($colorArrayRef_or_classHashRef->{header}); |
302
|
|
|
|
|
|
|
} elsif ((ref($colorArrayRef_or_classHashRef) eq "ARRAY") && (scalar @$colorArrayRef_or_classHashRef==3)) { |
303
|
2
|
|
|
|
|
6
|
@BG_COLOR=@$colorArrayRef_or_classHashRef; |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
|
306
|
5
|
|
|
|
|
11
|
$s_tr = $tag2str->($tag_tr); |
307
|
5
|
|
|
|
|
11
|
$s_th = $tag2str->($tag_th); |
308
|
|
|
|
|
|
|
|
309
|
5
|
100
|
|
|
|
13
|
if ($portrait) { |
310
|
3
|
|
|
|
|
4
|
$s .= "\n";
311
|
3
|
|
|
|
|
6
|
my $clr=""; |
312
|
3
|
100
|
|
|
|
7
|
if ($l_colorByClass) { |
313
|
1
|
50
|
|
|
|
4
|
$clr=" class=\"".$CELL_CLASSES[2]."\"" if ($CELL_CLASSES[2]); |
314
|
|
|
|
|
|
|
} else { |
315
|
2
|
100
|
|
|
|
7
|
$clr=" style=\"background-color:".$BG_COLOR[2].";\"" if ($BG_COLOR[2]); |
316
|
|
|
|
|
|
|
} |
317
|
3
|
|
|
|
|
7
|
$s .= " | \n";
318
|
3
|
|
|
|
|
6
|
for (my $i=0; $i<=$#{$header}; $i++) { |
|
17
|
|
|
|
|
34
|
|
319
|
14
|
100
|
|
|
|
36
|
$s .=" | ($callback->({%$tag_th}, -1, $i, $header->[$i], $self)) : $s_th) .">".$header->[$i]." | \n";
320
|
|
|
|
|
|
|
} |
321
|
3
|
|
|
|
|
5
|
$s .=" | \n";
322
|
3
|
|
|
|
|
7
|
$s .= " | \n"; |
323
|
3
|
50
|
|
|
|
7
|
$self->rotate() if $self->{type}; |
324
|
3
|
|
|
|
|
15
|
my $data=$self->{data}; |
325
|
3
|
|
|
|
|
6
|
$s .= " | \n";
326
|
3
|
|
|
|
|
6
|
for (my $i=0; $i<=$#{$data}; $i++) { |
|
91
|
|
|
|
|
131
|
|
327
|
88
|
|
|
|
|
100
|
$clr=""; |
328
|
88
|
100
|
|
|
|
111
|
if ($l_colorByClass) { |
329
|
2
|
50
|
|
|
|
6
|
$clr=" class=\"".$CELL_CLASSES[$i%2]."\"" if ($CELL_CLASSES[$i%2]); |
330
|
|
|
|
|
|
|
} else { |
331
|
86
|
100
|
|
|
|
149
|
$clr=" style=\"background-color:".$BG_COLOR[$i%2].";\"" if ($BG_COLOR[$i%2]); |
332
|
|
|
|
|
|
|
} |
333
|
88
|
|
|
|
|
133
|
$s .= " | \n";
334
|
88
|
|
|
|
|
115
|
for (my $j=0; $j<=$#{$header}; $j++) { |
|
608
|
|
|
|
|
909
|
|
335
|
520
|
|
50
|
|
|
1592
|
my $td = $tag_td->{$j} || $tag_td->{$header->[$j]} || {}; |
336
|
520
|
|
100
|
|
|
1117
|
my $s_td=$tag2str->($cb ? $callback->({%$td}, $i, $j, $header->[$j], $self) : $td) || ""; |
337
|
520
|
100
|
|
|
|
1061
|
$s .= ($s_td)? " | ":" | "; |
338
|
520
|
50
|
33
|
|
|
1381
|
$s .= (defined($data->[$i][$j]) && $data->[$i][$j] ne '')?$data->[$i][$j]:" "; |
339
|
520
|
|
|
|
|
935
|
$s .= " | \n";
340
|
|
|
|
|
|
|
} |
341
|
88
|
|
|
|
|
147
|
$s .= " | \n";
342
|
|
|
|
|
|
|
} |
343
|
3
|
|
|
|
|
7
|
$s .= " | \n";
344
|
|
|
|
|
|
|
} else { |
345
|
2
|
50
|
|
|
|
11
|
$self->rotate() unless $self->{type}; |
346
|
2
|
|
|
|
|
5
|
my $tag_th_def={}; |
347
|
2
|
50
|
|
|
|
6
|
if ($l_colorByClass) { |
348
|
0
|
0
|
|
|
|
0
|
$tag_th_def->{"class"}=$CELL_CLASSES[2] if $CELL_CLASSES[2]; |
349
|
|
|
|
|
|
|
} else { |
350
|
2
|
100
|
|
|
|
10
|
$tag_th_def->{"style"}="background-color:".$BG_COLOR[2].";" if $BG_COLOR[2]; |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
my $merge_tag = sub { |
353
|
518
|
|
|
518
|
|
650
|
my ($old, $usr)=@_; |
354
|
518
|
|
|
|
|
775
|
foreach my $k(keys %$usr) { |
355
|
0
|
0
|
|
|
|
0
|
if (exists $old->{$k}) { |
356
|
0
|
0
|
0
|
|
|
0
|
if (!defined($usr->{k}) or $usr->{k} eq '') { |
|
|
0
|
0
|
|
|
|
|
357
|
0
|
|
|
|
|
0
|
undef $old->{k}; |
358
|
|
|
|
|
|
|
} elsif ($k eq 'style' and (index($usr->{k}, 'background-color:')!=-1)) { |
359
|
0
|
|
|
|
|
0
|
$old->{$k}=$usr->{$k}; |
360
|
|
|
|
|
|
|
} else { |
361
|
0
|
|
|
|
|
0
|
$old->{$k}.= " "+$usr->{$k}; |
362
|
|
|
|
|
|
|
} |
363
|
|
|
|
|
|
|
} else { |
364
|
0
|
0
|
|
|
|
0
|
$old->{$k}=$usr->{$k} if $usr->{$k}; |
365
|
|
|
|
|
|
|
} |
366
|
|
|
|
|
|
|
} |
367
|
2
|
|
|
|
|
11
|
}; |
368
|
2
|
50
|
|
|
|
9
|
$merge_tag->($tag_th_def, $tag_th) if defined($tag_th); |
369
|
2
|
|
|
|
|
5
|
$s_th=$tag2str->($tag_th_def); |
370
|
|
|
|
|
|
|
|
371
|
2
|
|
|
|
|
4
|
my $data=$self->{data}; |
372
|
2
|
|
|
|
|
6
|
$s .=" | \n";
373
|
2
|
|
|
|
|
5
|
for (my $i = 0; $i <= $#{$header}; $i++) { |
|
14
|
|
|
|
|
31
|
|
374
|
12
|
|
|
|
|
15
|
$s .= " | \n";
375
|
12
|
100
|
|
|
|
67
|
$s .= " | ($callback->({%$tag_th_def}, -1, $i, $header->[$i], $self)) : $s_th) .">". $header->[$i] . " | \n";
376
|
12
|
|
50
|
|
|
65
|
my $td_def = $tag_td->{$i} || $tag_td->{$header->[$i]} || {}; |
377
|
12
|
50
|
|
|
|
24
|
$td_def = {'' => $td_def} unless ref $td_def; |
378
|
12
|
|
|
|
|
17
|
for (my $j=0; $j<=$#{$data->[0]}; $j++) { |
|
528
|
|
|
|
|
882
|
|
379
|
516
|
|
|
|
|
575
|
my $td = {}; |
380
|
516
|
50
|
|
|
|
621
|
if ($l_colorByClass) { |
381
|
0
|
0
|
|
|
|
0
|
$td->{"class"}=$CELL_CLASSES[$j%2] if $CELL_CLASSES[$j%2]; |
382
|
|
|
|
|
|
|
} else { |
383
|
516
|
100
|
|
|
|
762
|
$td->{"style"}="background-color:".$BG_COLOR[$j%2].";" if $BG_COLOR[$j%2]; |
384
|
|
|
|
|
|
|
} |
385
|
516
|
|
|
|
|
780
|
$merge_tag->($td, $td_def); |
386
|
516
|
|
100
|
|
|
1027
|
my $s_td=$tag2str->($cb ? $callback->({%$td}, $j, $i, $header->[$i], $self) : $td) || ""; |
387
|
516
|
100
|
|
|
|
1020
|
$s .= ($s_td)? " | ":" | "; |
388
|
516
|
50
|
33
|
|
|
1342
|
$s .= (defined($data->[$i][$j]) && $data->[$i][$j] ne '')?$data->[$i][$j]:' '; |
389
|
516
|
|
|
|
|
863
|
$s .= " | \n";
390
|
|
|
|
|
|
|
} |
391
|
12
|
|
|
|
|
26
|
$s .= " | \n";
392
|
|
|
|
|
|
|
} |
393
|
2
|
|
|
|
|
19
|
$s .=" | \n";
394
|
|
|
|
|
|
|
} |
395
|
5
|
|
|
|
|
11
|
$s .= " | \n"; |
396
|
5
|
|
|
|
|
142
|
return $s; |
397
|
|
|
|
|
|
|
} |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
# output table in wikitable |
400
|
|
|
|
|
|
|
# this method accepts the same parameters as the html() method |
401
|
|
|
|
|
|
|
sub wiki { |
402
|
4
|
|
|
4
|
1
|
15
|
my ($self, $colorArrayRef_or_classHashRef, $tag_tbl, $tag_tr, $tag_th, $tag_td, $portrait, $callback) = @_; |
403
|
4
|
|
|
|
|
24
|
my ($s, $s_tr, $s_td, $s_th) = ("", "", "", ""); |
404
|
4
|
|
|
|
|
8
|
my $key; |
405
|
4
|
50
|
|
|
|
23
|
$tag_tbl = { class => "wikitable" } unless (ref $tag_tbl eq 'HASH'); |
406
|
4
|
50
|
|
|
|
11
|
$tag_tr = {} unless (ref $tag_tr eq 'HASH'); |
407
|
4
|
50
|
|
|
|
14
|
$tag_th = {} unless (ref $tag_th eq 'HASH'); |
408
|
4
|
50
|
|
|
|
11
|
$tag_td = {} unless (ref $tag_td eq 'HASH'); |
409
|
4
|
100
|
|
|
|
14
|
$portrait = 1 unless defined($portrait); |
410
|
4
|
|
|
|
|
8
|
my $cb=0; |
411
|
4
|
100
|
|
|
|
12
|
if (defined($callback)) { |
412
|
2
|
50
|
|
|
|
8
|
confess "wiki: Expecting subroutine for callback parameter!" if ref($callback) ne 'CODE'; |
413
|
2
|
|
|
|
|
4
|
$cb=1; |
414
|
|
|
|
|
|
|
} |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
my $tag2str = sub { |
417
|
1058
|
|
|
1058
|
|
4580
|
my $tag = shift; |
418
|
1058
|
|
|
|
|
1096
|
my $s=""; |
419
|
1058
|
|
|
|
|
1643
|
foreach my $key (keys %$tag) { |
420
|
367
|
50
|
|
|
|
558
|
next unless $tag->{$key}; |
421
|
367
|
50
|
|
|
|
480
|
if ($key eq '') { |
422
|
0
|
|
|
|
|
0
|
$s .=" ".$tag->{$key}; |
423
|
|
|
|
|
|
|
#for backward compatibility, in case the tag is a str |
424
|
|
|
|
|
|
|
# '' => 'align="right" valign="bottom"' |
425
|
|
|
|
|
|
|
} else { |
426
|
367
|
|
|
|
|
686
|
$s .= " $key=\"$tag->{$key}\""; |
427
|
|
|
|
|
|
|
} |
428
|
|
|
|
|
|
|
} |
429
|
1058
|
|
|
|
|
2255
|
return $s; |
430
|
4
|
|
|
|
|
23
|
}; |
431
|
|
|
|
|
|
|
|
432
|
4
|
|
|
|
|
11
|
$s = "{|".$tag2str->($tag_tbl)."\n"; |
433
|
4
|
|
|
|
|
10
|
my $header=$self->{header}; |
434
|
4
|
|
|
|
|
6
|
my $l_colorByClass = 0; |
435
|
4
|
|
|
|
|
11
|
my @BG_COLOR=("#D4D4BF","#ECECE4","#CCCC99"); |
436
|
4
|
|
|
|
|
11
|
my @CELL_CLASSES=("wikitable_odd","wikitable_even","wikitable_header"); |
437
|
4
|
50
|
66
|
|
|
22
|
if (ref($colorArrayRef_or_classHashRef) eq "HASH") { |
|
|
100
|
|
|
|
|
|
438
|
0
|
|
|
|
|
0
|
$l_colorByClass = 1; |
439
|
0
|
0
|
|
|
|
0
|
$CELL_CLASSES[1]=$colorArrayRef_or_classHashRef->{even} if defined($colorArrayRef_or_classHashRef->{even}); |
440
|
0
|
0
|
|
|
|
0
|
$CELL_CLASSES[0]=$colorArrayRef_or_classHashRef->{odd} if defined($colorArrayRef_or_classHashRef->{odd}); |
441
|
0
|
0
|
|
|
|
0
|
$CELL_CLASSES[2]=$colorArrayRef_or_classHashRef->{header} if defined($colorArrayRef_or_classHashRef->{header}); |
442
|
|
|
|
|
|
|
} elsif ((ref($colorArrayRef_or_classHashRef) eq "ARRAY") && (scalar @$colorArrayRef_or_classHashRef==3)) { |
443
|
2
|
|
|
|
|
7
|
@BG_COLOR=@$colorArrayRef_or_classHashRef; |
444
|
|
|
|
|
|
|
} |
445
|
4
|
|
|
|
|
8
|
$s_tr = $tag2str->($tag_tr); |
446
|
4
|
|
|
|
|
8
|
$s_th = $tag2str->($tag_th); |
447
|
|
|
|
|
|
|
|
448
|
4
|
100
|
|
|
|
12
|
if ($portrait) { |
449
|
2
|
|
|
|
|
5
|
for (my $i=0; $i<=$#{$header}; $i++) { |
|
14
|
|
|
|
|
27
|
|
450
|
12
|
|
|
|
|
14
|
my $clr=""; |
451
|
12
|
50
|
|
|
|
24
|
if ($l_colorByClass) { |
452
|
0
|
0
|
|
|
|
0
|
$clr=" class=\"".$CELL_CLASSES[2]."\"" if $CELL_CLASSES[2]; |
453
|
|
|
|
|
|
|
} else { |
454
|
12
|
100
|
|
|
|
21
|
$clr=" style=\"background-color:".$BG_COLOR[2].";\"" if $BG_COLOR[2]; |
455
|
|
|
|
|
|
|
} |
456
|
12
|
|
|
|
|
20
|
$s .= "!$s_tr$clr"; |
457
|
|
|
|
|
|
|
# make a copy of $tag_th to pass as a parameter |
458
|
12
|
100
|
|
|
|
30
|
$s .= $cb ? $tag2str->($callback->({%$tag_th}, -1, $i, $header->[$i], $self)) : $s_th; |
459
|
12
|
|
|
|
|
28
|
$s .= " | ".$header->[$i]."\n"; # $join(" || ", @$header)."\n"; |
460
|
|
|
|
|
|
|
} |
461
|
2
|
50
|
|
|
|
21
|
$self->rotate() if $self->{type}; |
462
|
2
|
|
|
|
|
4
|
my $data=$self->{data}; |
463
|
2
|
|
|
|
|
6
|
for (my $i=0; $i<=$#{$data}; $i++) { |
|
88
|
|
|
|
|
162
|
|
464
|
86
|
|
|
|
|
104
|
my $clr=""; |
465
|
86
|
50
|
|
|
|
121
|
if ($l_colorByClass) { |
466
|
0
|
0
|
|
|
|
0
|
$clr=" class=\"".$CELL_CLASSES[$i%2]."\"" if $CELL_CLASSES[$i%2]; |
467
|
|
|
|
|
|
|
} else { |
468
|
86
|
100
|
|
|
|
131
|
$clr=" style=\"background-color:".$BG_COLOR[$i%2].";\"" if $BG_COLOR[$i%2]; |
469
|
|
|
|
|
|
|
} |
470
|
86
|
|
|
|
|
167
|
$s .= "|-$clr\n"; |
471
|
86
|
|
|
|
|
107
|
for (my $j=0; $j<=$#{$header}; $j++) { |
|
602
|
|
|
|
|
972
|
|
472
|
516
|
|
50
|
|
|
1549
|
my $td = $tag_td->{$j} || $tag_td->{$header->[$j]} || {}; |
473
|
|
|
|
|
|
|
# backward compatibility, when str is used instead of hash for $tag_td->{'col'} |
474
|
516
|
50
|
|
|
|
760
|
$td = {'' => $td} unless ref $td; |
475
|
516
|
|
100
|
|
|
1068
|
my $s_td=$tag2str->($cb ? $callback->({%$td}, $i, $j, $header->[$j], $self) : $td) || ""; |
476
|
516
|
100
|
|
|
|
1046
|
$s .= ($s_td)? "|$s_td | ":"| "; |
477
|
516
|
50
|
33
|
|
|
1357
|
$s .= (defined($data->[$i][$j]) && $data->[$i][$j] ne '')?$data->[$i][$j]:" "; |
478
|
516
|
|
|
|
|
891
|
$s .= "\n"; |
479
|
|
|
|
|
|
|
} |
480
|
|
|
|
|
|
|
} |
481
|
|
|
|
|
|
|
} else { |
482
|
2
|
50
|
|
|
|
13
|
$self->rotate() unless $self->{type}; |
483
|
2
|
|
|
|
|
5
|
my $tag_th_def={}; |
484
|
2
|
50
|
|
|
|
7
|
if ($l_colorByClass) { |
485
|
0
|
0
|
|
|
|
0
|
$tag_th_def->{"class"}=$CELL_CLASSES[2] if $CELL_CLASSES[2]; |
486
|
|
|
|
|
|
|
} else { |
487
|
2
|
100
|
|
|
|
5
|
$tag_th_def->{"style"}="background-color:".$BG_COLOR[2].";" if $BG_COLOR[2]; |
488
|
|
|
|
|
|
|
} |
489
|
|
|
|
|
|
|
my $merge_tag = sub { |
490
|
518
|
|
|
518
|
|
616
|
my ($old, $usr)=@_; |
491
|
518
|
|
|
|
|
732
|
foreach my $k(keys %$usr) { |
492
|
0
|
0
|
|
|
|
0
|
if (exists $old->{$k}) { |
493
|
0
|
0
|
0
|
|
|
0
|
if (!defined($usr->{k}) or $usr->{k} eq '') { |
|
|
0
|
0
|
|
|
|
|
494
|
0
|
|
|
|
|
0
|
undef $old->{k}; |
495
|
|
|
|
|
|
|
} elsif ($k eq 'style' and (index($usr->{k}, 'background-color:')!=-1)) { |
496
|
0
|
|
|
|
|
0
|
$old->{$k}=$usr->{$k}; |
497
|
|
|
|
|
|
|
} else { |
498
|
0
|
|
|
|
|
0
|
$old->{$k}.= " "+$usr->{$k}; |
499
|
|
|
|
|
|
|
} |
500
|
|
|
|
|
|
|
} else { |
501
|
0
|
|
|
|
|
0
|
$old->{$k}=$usr->{$k}; |
502
|
|
|
|
|
|
|
} |
503
|
|
|
|
|
|
|
} |
504
|
2
|
|
|
|
|
11
|
}; |
505
|
|
|
|
|
|
|
|
506
|
2
|
50
|
|
|
|
18
|
$merge_tag->($tag_th_def, $tag_th) if defined($tag_th); |
507
|
2
|
|
|
|
|
5
|
$s_th=$tag2str->($tag_th_def); |
508
|
2
|
|
|
|
|
5
|
my $data=$self->{data}; |
509
|
2
|
|
|
|
|
4
|
for (my $i = 0; $i <= $#{$header}; $i++) { |
|
14
|
|
|
|
|
39
|
|
510
|
12
|
|
|
|
|
17
|
$s .= "|-\n"; |
511
|
12
|
|
|
|
|
15
|
$s .= "!"; |
512
|
12
|
100
|
|
|
|
33
|
$s .= $cb ? $tag2str->($callback->({%$tag_th_def}, -1, $i, $header->[$i], $self)) : $s_th; |
513
|
12
|
|
|
|
|
25
|
$s .= " | ".$header->[$i]."\n"; |
514
|
12
|
|
50
|
|
|
49
|
my $td = $tag_td->{$i} || $tag_td->{$header->[$i]} || {}; |
515
|
12
|
50
|
|
|
|
21
|
$td = {'' => $td} unless ref $td; |
516
|
12
|
|
|
|
|
17
|
for (my $j=0; $j<=$#{$data->[0]}; $j++) { |
|
528
|
|
|
|
|
851
|
|
517
|
516
|
|
|
|
|
621
|
my $td_def={}; |
518
|
516
|
50
|
|
|
|
583
|
if ($l_colorByClass) { |
519
|
0
|
0
|
|
|
|
0
|
$td_def->{"class"}=$CELL_CLASSES[$j%2] if $CELL_CLASSES[$j%2]; |
520
|
|
|
|
|
|
|
} else { |
521
|
516
|
100
|
|
|
|
824
|
$td_def->{"style"}="background-color:".$BG_COLOR[$j%2].";" if $BG_COLOR[$j%2]; |
522
|
|
|
|
|
|
|
} |
523
|
516
|
|
|
|
|
777
|
$merge_tag->($td_def, $td); |
524
|
516
|
|
100
|
|
|
1032
|
my $s_td=$tag2str->($cb ? $callback->({%$td_def}, $j, $i, $header->[$i], $self) : $td_def) || ""; |
525
|
516
|
100
|
|
|
|
1037
|
$s .= ($s_td)? "|$s_td | ":"| "; |
526
|
516
|
50
|
33
|
|
|
1351
|
$s .= (defined($data->[$i][$j]) && $data->[$i][$j] ne '')?$data->[$i][$j]:' '; |
527
|
516
|
|
|
|
|
826
|
$s .= "\n"; |
528
|
|
|
|
|
|
|
} |
529
|
|
|
|
|
|
|
} |
530
|
|
|
|
|
|
|
} |
531
|
4
|
|
|
|
|
10
|
$s .= "|}\n"; |
532
|
4
|
|
|
|
|
120
|
return $s; |
533
|
|
|
|
|
|
|
} |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
# output table in wikitable format, with table orientation rotated, |
536
|
|
|
|
|
|
|
# so that each wikitable row is a column in the table |
537
|
|
|
|
|
|
|
# This is useful for a slim table (few columns but many rows) |
538
|
|
|
|
|
|
|
# The method accepts the same parameters as html2() method |
539
|
|
|
|
|
|
|
sub wiki2 { |
540
|
2
|
|
|
2
|
1
|
7
|
my ($self, $colorArrayRef_or_classHashRef, $tag_tbl, $tag_tr, $tag_th, $tag_td, $callback) = @_; |
541
|
2
|
|
|
|
|
8
|
return $self->wiki($colorArrayRef_or_classHashRef, $tag_tbl, $tag_tr, $tag_th, $tag_td, 0, $callback); |
542
|
|
|
|
|
|
|
} |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
# output table in HTML format, with table orientation rotated, |
545
|
|
|
|
|
|
|
# so that each HTML table row is a column in the table |
546
|
|
|
|
|
|
|
# This is useful for a slim table (few columns but many rows) |
547
|
|
|
|
|
|
|
sub html2 { |
548
|
2
|
|
|
2
|
1
|
7
|
my ($self, $colorArrayRef_or_classHashRef, $tag_tbl, $tag_tr, $tag_th, $tag_td, $callback) = @_; |
549
|
2
|
|
|
|
|
10
|
return $self->html($colorArrayRef_or_classHashRef, $tag_tbl, $tag_tr, $tag_th, $tag_td, 0, $callback); |
550
|
|
|
|
|
|
|
} |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
# apply a $fun to each elm in a col |
553
|
|
|
|
|
|
|
# function only has access to one element per row |
554
|
|
|
|
|
|
|
sub colMap { |
555
|
1
|
|
|
1
|
1
|
4
|
my ($self, $colID, $fun) = @_; |
556
|
1
|
|
|
|
|
5
|
my $c=$self->checkOldCol($colID); |
557
|
1
|
50
|
|
|
|
4
|
return undef unless defined $c; |
558
|
1
|
50
|
|
|
|
4
|
$self->rotate() unless $self->{type}; |
559
|
1
|
|
|
|
|
2
|
my $ref = $self->{data}->[$c]; |
560
|
1
|
|
|
|
|
3
|
my @tmp = map {scalar $fun->($_)} @$ref; |
|
9
|
|
|
|
|
25
|
|
561
|
1
|
|
|
|
|
14
|
$self->{data}->[$c] = \@tmp; |
562
|
1
|
|
|
|
|
7
|
return 1; |
563
|
|
|
|
|
|
|
} |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
# apply a $fun to each row in the table |
566
|
|
|
|
|
|
|
# function has access to all elements in that row |
567
|
|
|
|
|
|
|
sub colsMap { |
568
|
1
|
|
|
1
|
1
|
2
|
my ($self, $fun) = @_; |
569
|
1
|
50
|
|
|
|
5
|
$self->rotate() if $self->{type}; |
570
|
1
|
|
|
|
|
1
|
map {&$fun} @{$self->{data}}; |
|
9
|
|
|
|
|
26
|
|
|
1
|
|
|
|
|
2
|
|
571
|
1
|
|
|
|
|
6
|
return 1; |
572
|
|
|
|
|
|
|
} |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
sub addRow { |
575
|
8
|
|
|
8
|
1
|
17
|
my ($self, $rowRef, $rowIdx, $arg_ref) = @_; |
576
|
8
|
100
|
|
|
|
19
|
my %arg = defined $arg_ref ? %$arg_ref : (); |
577
|
8
|
100
|
|
|
|
24
|
$arg{addNewCol} = 0 unless exists $arg{addNewCol}; |
578
|
|
|
|
|
|
|
|
579
|
8
|
|
|
|
|
14
|
my $numRow=$self->nofRow(); |
580
|
8
|
|
|
|
|
9
|
my @t; |
581
|
8
|
|
|
|
|
10
|
my $myRowRef = $rowRef; |
582
|
|
|
|
|
|
|
|
583
|
8
|
100
|
|
|
|
17
|
if ($arg{addNewCol}) { |
584
|
1
|
50
|
|
|
|
4
|
if (ref $myRowRef eq 'HASH') { |
|
|
0
|
|
|
|
|
|
585
|
1
|
|
|
|
|
942
|
foreach my $key (keys %$myRowRef) { |
586
|
2
|
50
|
|
|
|
6
|
next if $self->colIndex($key) >= 0; |
587
|
2
|
|
|
|
|
6
|
my @col = (undef) x $self->nofRow; |
588
|
2
|
|
|
|
|
7
|
$self->addCol(\@col, $key); |
589
|
|
|
|
|
|
|
} |
590
|
|
|
|
|
|
|
} elsif (ref $myRowRef eq 'ARRAY') { |
591
|
0
|
|
|
|
|
0
|
for (my $i=$self->nofCol; $i< scalar @$myRowRef; $i++) { |
592
|
0
|
|
|
|
|
0
|
my @col = (undef) x $self->nofRow; |
593
|
0
|
|
|
|
|
0
|
$self->addCol(\@col, "col".($i+1)); |
594
|
|
|
|
|
|
|
} |
595
|
|
|
|
|
|
|
} |
596
|
|
|
|
|
|
|
} |
597
|
|
|
|
|
|
|
|
598
|
8
|
100
|
|
|
|
26
|
if (ref $myRowRef eq 'HASH') { |
|
|
50
|
|
|
|
|
|
599
|
2
|
50
|
|
|
|
6
|
if ($self->isEmpty) { |
600
|
0
|
|
|
|
|
0
|
my $i = 0; |
601
|
0
|
|
|
|
|
0
|
foreach my $s (keys %$myRowRef) { |
602
|
0
|
|
|
|
|
0
|
push @{$self->{header}}, $s; |
|
0
|
|
|
|
|
0
|
|
603
|
0
|
|
|
|
|
0
|
$self->{colHash}->{$s} = $i++; |
604
|
|
|
|
|
|
|
} |
605
|
|
|
|
|
|
|
} |
606
|
2
|
|
|
|
|
7
|
my @one = (); |
607
|
2
|
|
|
|
|
7
|
my @header = $self->header; |
608
|
2
|
|
|
|
|
8
|
for (my $i=0; $i< scalar @header; $i++) { |
609
|
11
|
|
|
|
|
24
|
$one[$i] = $myRowRef->{$header[$i]}; |
610
|
|
|
|
|
|
|
} |
611
|
2
|
|
|
|
|
7
|
$myRowRef = \@one; |
612
|
|
|
|
|
|
|
} elsif (ref $myRowRef eq 'ARRAY') { |
613
|
6
|
50
|
|
|
|
10
|
confess "addRow: size of added row does not match those in the table\n" |
614
|
|
|
|
|
|
|
if scalar @$myRowRef != $self->nofCol(); |
615
|
|
|
|
|
|
|
} else { |
616
|
0
|
|
|
|
|
0
|
confess "addRow: parameter rowRef has to be either an array_ref or a hash_ref\n"; |
617
|
|
|
|
|
|
|
} |
618
|
8
|
100
|
|
|
|
18
|
$rowIdx=$numRow unless defined($rowIdx); |
619
|
8
|
50
|
|
|
|
37
|
return undef unless defined $self->checkNewRow($rowIdx); |
620
|
8
|
100
|
|
|
|
16
|
$self->rotate() if $self->{type}; |
621
|
8
|
|
|
|
|
14
|
my $data=$self->{data}; |
622
|
8
|
100
|
|
|
|
21
|
if ($rowIdx == 0) { |
|
|
100
|
|
|
|
|
|
623
|
2
|
|
|
|
|
4
|
unshift @$data, $myRowRef; |
624
|
|
|
|
|
|
|
} elsif ($rowIdx == $numRow) { |
625
|
3
|
|
|
|
|
7
|
push @$data, $myRowRef; |
626
|
|
|
|
|
|
|
} else { |
627
|
3
|
|
|
|
|
5
|
@t = splice @$data, $rowIdx; |
628
|
3
|
|
|
|
|
6
|
push @$data, $myRowRef, @t; |
629
|
|
|
|
|
|
|
} |
630
|
8
|
|
|
|
|
24
|
return 1; |
631
|
|
|
|
|
|
|
} |
632
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
sub delRow { |
634
|
18
|
|
|
18
|
1
|
24
|
my ($self, $rowIdx ) = @_; |
635
|
18
|
50
|
|
|
|
30
|
return undef unless defined $self->checkOldRow($rowIdx); |
636
|
18
|
50
|
|
|
|
27
|
$self->rotate() if $self->{type}; |
637
|
18
|
|
|
|
|
22
|
my $data=$self->{data}; |
638
|
18
|
|
|
|
|
26
|
my @dels=splice(@$data, $rowIdx, 1); |
639
|
18
|
|
|
|
|
26
|
return shift @dels; |
640
|
|
|
|
|
|
|
} |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
sub delRows { |
643
|
4
|
|
|
4
|
1
|
10
|
my ($self, $rowIdcsRef) = @_; |
644
|
4
|
|
|
|
|
5
|
my $rowIdx; |
645
|
4
|
50
|
|
|
|
12
|
$self->rotate() if $self->{type}; |
646
|
4
|
|
|
|
|
7
|
my @dels = @{$self->{data}}[@$rowIdcsRef]; |
|
4
|
|
|
|
|
13
|
|
647
|
4
|
|
|
|
|
18
|
my @indices = sort { $b <=> $a } @$rowIdcsRef; |
|
17
|
|
|
|
|
29
|
|
648
|
|
|
|
|
|
|
#my @dels=(); |
649
|
4
|
|
|
|
|
8
|
foreach $rowIdx (@indices) { |
650
|
|
|
|
|
|
|
#push @dels, $self->delRow($rowIdx); |
651
|
17
|
|
|
|
|
25
|
$self->delRow($rowIdx); |
652
|
|
|
|
|
|
|
} |
653
|
4
|
|
|
|
|
14
|
return @dels; |
654
|
|
|
|
|
|
|
} |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
# append a column to the table, input is a referenceof_array |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
sub addCol { |
659
|
12
|
|
|
12
|
1
|
28
|
my ($self, $colRef, $colName, $colIdx) = @_; |
660
|
12
|
|
|
|
|
22
|
my $numCol=$self->nofCol(); |
661
|
12
|
|
|
|
|
18
|
my @t; |
662
|
12
|
100
|
66
|
|
|
51
|
if (!defined($colRef) || ref($colRef) eq '') { |
663
|
|
|
|
|
|
|
# fill the new column with $colRef as the default value |
664
|
1
|
|
|
|
|
3
|
my @col = ($colRef) x $self->nofRow; |
665
|
1
|
|
|
|
|
2
|
$colRef = \@col; |
666
|
|
|
|
|
|
|
} else { |
667
|
11
|
50
|
33
|
|
|
19
|
confess "addCol: size of added col does not match rows in the table\n" |
668
|
|
|
|
|
|
|
if @$colRef != $self->nofRow() and $numCol > 0; |
669
|
|
|
|
|
|
|
} |
670
|
12
|
100
|
|
|
|
23
|
$colIdx=$numCol unless defined($colIdx); |
671
|
12
|
50
|
|
|
|
24
|
return undef unless defined $self->checkNewCol($colIdx, $colName); |
672
|
12
|
100
|
|
|
|
25
|
$self->rotate() unless $self->{type}; |
673
|
12
|
|
|
|
|
16
|
my $data=$self->{data}; |
674
|
12
|
|
|
|
|
18
|
my $header=$self->{header}; |
675
|
12
|
100
|
|
|
|
37
|
if ($colIdx == 0) { |
|
|
100
|
|
|
|
|
|
676
|
1
|
|
|
|
|
2
|
unshift @$header, $colName; |
677
|
|
|
|
|
|
|
} elsif ($colIdx == $numCol) { |
678
|
7
|
|
|
|
|
13
|
push @$header, $colName; |
679
|
|
|
|
|
|
|
} else { |
680
|
4
|
|
|
|
|
11
|
@t = splice @$header, $colIdx; |
681
|
4
|
|
|
|
|
7
|
push @$header, $colName, @t; |
682
|
|
|
|
|
|
|
} |
683
|
|
|
|
|
|
|
|
684
|
12
|
100
|
|
|
|
29
|
if ($colIdx == 0) { |
|
|
100
|
|
|
|
|
|
685
|
1
|
|
|
|
|
2
|
unshift @$data, $colRef; |
686
|
|
|
|
|
|
|
} elsif ($colIdx == $numCol) { |
687
|
7
|
|
|
|
|
11
|
push @$data, $colRef; |
688
|
|
|
|
|
|
|
} else { |
689
|
4
|
|
|
|
|
6
|
@t = splice @$data, $colIdx; |
690
|
4
|
|
|
|
|
7
|
push @$data, $colRef, @t; |
691
|
|
|
|
|
|
|
} |
692
|
|
|
|
|
|
|
|
693
|
12
|
|
|
|
|
33
|
for (my $i = 0; $i < scalar @$header; $i++) { |
694
|
65
|
|
|
|
|
71
|
my $elm = $header->[$i]; |
695
|
65
|
|
|
|
|
122
|
$self->{colHash}->{$elm} = $i; |
696
|
|
|
|
|
|
|
} |
697
|
12
|
|
|
|
|
32
|
return 1; |
698
|
|
|
|
|
|
|
} |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
sub delCol { |
701
|
6
|
|
|
6
|
1
|
13
|
my ($self, $colID) = @_; |
702
|
6
|
|
|
|
|
12
|
my $c=$self->checkOldCol($colID); |
703
|
6
|
50
|
|
|
|
12
|
return undef unless defined $c; |
704
|
6
|
100
|
|
|
|
16
|
$self->rotate() unless $self->{type}; |
705
|
6
|
|
|
|
|
8
|
my $header=$self->{header}; |
706
|
6
|
|
|
|
|
13
|
my $name=$self->{header}->[$c]; |
707
|
6
|
|
|
|
|
9
|
splice @$header, $c, 1; |
708
|
6
|
|
|
|
|
9
|
my $data=$self->{data}; |
709
|
6
|
|
|
|
|
11
|
my @dels=splice @$data, $c, 1; |
710
|
6
|
|
|
|
|
15
|
delete $self->{colHash}->{$name}; |
711
|
6
|
|
|
|
|
16
|
for (my $i = $c; $i < scalar @$header; $i++) { |
712
|
15
|
|
|
|
|
18
|
my $elm = $header->[$i]; |
713
|
15
|
|
|
|
|
29
|
$self->{colHash}->{$elm} = $i; |
714
|
|
|
|
|
|
|
} |
715
|
6
|
|
|
|
|
14
|
return shift @dels; |
716
|
|
|
|
|
|
|
} |
717
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
sub delCols { |
719
|
1
|
|
|
1
|
1
|
3
|
my ($self, $colIDsRef) = @_; |
720
|
1
|
|
|
|
|
1
|
my $idx; |
721
|
1
|
|
|
|
|
3
|
my @indices = map { $self->colIndex($_) } @$colIDsRef; |
|
3
|
|
|
|
|
4
|
|
722
|
1
|
50
|
|
|
|
3
|
$self->rotate() unless $self->{type}; |
723
|
1
|
|
|
|
|
1
|
my @dels = @{$self->{data}}[@indices]; |
|
1
|
|
|
|
|
3
|
|
724
|
1
|
|
|
|
|
4
|
@indices = sort { $b <=> $a } @indices; |
|
3
|
|
|
|
|
5
|
|
725
|
|
|
|
|
|
|
#my @dels=(); |
726
|
1
|
|
|
|
|
2
|
foreach my $colIdx (@indices) { |
727
|
3
|
|
|
|
|
7
|
$self->delCol($colIdx); |
728
|
|
|
|
|
|
|
} |
729
|
1
|
|
|
|
|
4
|
return @dels; |
730
|
|
|
|
|
|
|
} |
731
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
sub rowRef { |
734
|
48
|
|
|
48
|
1
|
61
|
my ($self, $rowIdx) = @_; |
735
|
48
|
50
|
|
|
|
72
|
return undef unless defined $self->checkOldRow($rowIdx); |
736
|
48
|
100
|
|
|
|
78
|
$self->rotate if $self->{type}; |
737
|
48
|
|
|
|
|
63
|
return $self->{data}->[$rowIdx]; |
738
|
|
|
|
|
|
|
} |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
sub rowRefs { |
741
|
25
|
|
|
25
|
1
|
71
|
my ($self, $rowIdcsRef) = @_; |
742
|
25
|
100
|
|
|
|
73
|
$self->rotate if $self->{type}; |
743
|
25
|
50
|
|
|
|
104
|
return $self->{data} unless defined $rowIdcsRef; |
744
|
0
|
|
|
|
|
0
|
my @ones = (); |
745
|
0
|
|
|
|
|
0
|
my $rowIdx; |
746
|
0
|
|
|
|
|
0
|
foreach $rowIdx (@$rowIdcsRef) { |
747
|
0
|
|
|
|
|
0
|
push @ones, $self->rowRef($rowIdx); |
748
|
|
|
|
|
|
|
} |
749
|
0
|
|
|
|
|
0
|
return \@ones; |
750
|
|
|
|
|
|
|
} |
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
sub row { |
753
|
61
|
|
|
61
|
1
|
80
|
my ($self, $rowIdx) = @_; |
754
|
61
|
|
|
|
|
69
|
my $data = $self->{data}; |
755
|
61
|
50
|
|
|
|
120
|
return undef unless defined $self->checkOldRow($rowIdx); |
756
|
61
|
50
|
|
|
|
87
|
if ($self->{type}) { |
757
|
0
|
|
|
|
|
0
|
my @one=(); |
758
|
0
|
|
|
|
|
0
|
for (my $i = 0; $i < scalar @$data; $i++) { |
759
|
0
|
|
|
|
|
0
|
push @one, $data->[$i]->[$rowIdx]; |
760
|
|
|
|
|
|
|
} |
761
|
0
|
|
|
|
|
0
|
return @one; |
762
|
|
|
|
|
|
|
} else { |
763
|
61
|
|
|
|
|
59
|
return @{$data->[$rowIdx]}; |
|
61
|
|
|
|
|
282
|
|
764
|
|
|
|
|
|
|
} |
765
|
|
|
|
|
|
|
} |
766
|
|
|
|
|
|
|
|
767
|
|
|
|
|
|
|
sub rowHashRef { |
768
|
175
|
|
|
175
|
1
|
238
|
my ($self, $rowIdx) = @_; |
769
|
175
|
|
|
|
|
195
|
my $data = $self->{data}; |
770
|
175
|
50
|
|
|
|
248
|
return undef unless defined $self->checkOldRow($rowIdx); |
771
|
175
|
|
|
|
|
212
|
my $header=$self->{header}; |
772
|
175
|
|
|
|
|
231
|
my $one = {}; |
773
|
175
|
|
|
|
|
283
|
for (my $i = 0; $i < scalar @$header; $i++) { |
774
|
|
|
|
|
|
|
$one->{$header->[$i]} = ($self->{type})? |
775
|
1094
|
100
|
|
|
|
2424
|
$self->{data}->[$i]->[$rowIdx]:$self->{data}->[$rowIdx]->[$i]; |
776
|
|
|
|
|
|
|
} |
777
|
175
|
|
|
|
|
614
|
return $one; |
778
|
|
|
|
|
|
|
} |
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
sub colRef { |
781
|
4
|
|
|
4
|
1
|
6
|
my ($self, $colID) = @_; |
782
|
4
|
|
|
|
|
9
|
my $c=$self->checkOldCol($colID); |
783
|
4
|
50
|
|
|
|
8
|
return undef unless defined $c; |
784
|
4
|
100
|
|
|
|
10
|
$self->rotate() unless $self->{type}; |
785
|
4
|
|
|
|
|
10
|
return $self->{data}->[$c]; |
786
|
|
|
|
|
|
|
} |
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
sub colRefs { |
789
|
1
|
|
|
1
|
1
|
3
|
my ($self, $colIDsRef) = @_; |
790
|
1
|
50
|
|
|
|
11
|
$self->rotate unless $self->{type}; |
791
|
1
|
50
|
|
|
|
3
|
return $self->{data} unless defined $colIDsRef; |
792
|
1
|
|
|
|
|
3
|
my @ones = (); |
793
|
1
|
|
|
|
|
2
|
my $colID; |
794
|
1
|
|
|
|
|
3
|
foreach $colID (@$colIDsRef) { |
795
|
3
|
|
|
|
|
6
|
push @ones, $self->colRef($colID); |
796
|
|
|
|
|
|
|
} |
797
|
1
|
|
|
|
|
4
|
return \@ones; |
798
|
|
|
|
|
|
|
} |
799
|
|
|
|
|
|
|
|
800
|
|
|
|
|
|
|
sub col { |
801
|
5
|
|
|
5
|
1
|
15
|
my ($self, $colID) = @_; |
802
|
5
|
|
|
|
|
8
|
my $data = $self->{data}; |
803
|
5
|
|
|
|
|
11
|
my $c=$self->checkOldCol($colID); |
804
|
5
|
50
|
|
|
|
12
|
return undef unless defined $c; |
805
|
5
|
100
|
|
|
|
12
|
if (!$self->{type}) { |
806
|
3
|
|
|
|
|
6
|
my @one=(); |
807
|
3
|
|
|
|
|
10
|
for (my $i = 0; $i < scalar @$data; $i++) { |
808
|
16
|
|
|
|
|
26
|
push @one, $data->[$i]->[$c]; |
809
|
|
|
|
|
|
|
} |
810
|
3
|
|
|
|
|
15
|
return @one; |
811
|
|
|
|
|
|
|
} else { |
812
|
2
|
50
|
|
|
|
6
|
return () unless ref($data->[$c]) eq "ARRAY"; |
813
|
2
|
|
|
|
|
3
|
return @{$data->[$c]}; |
|
2
|
|
|
|
|
8
|
|
814
|
|
|
|
|
|
|
} |
815
|
|
|
|
|
|
|
} |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
sub rename { |
818
|
16
|
|
|
16
|
1
|
44
|
my ($self, $colID, $name) = @_; |
819
|
16
|
|
|
|
|
15
|
my $oldName; |
820
|
16
|
|
|
|
|
28
|
my $c=$self->checkOldCol($colID); |
821
|
16
|
50
|
|
|
|
31
|
return undef unless defined $c; |
822
|
16
|
|
|
|
|
20
|
$oldName=$self->{header}->[$c]; |
823
|
16
|
50
|
|
|
|
29
|
return if ($oldName eq $name); |
824
|
16
|
50
|
|
|
|
30
|
return undef unless defined $self->checkNewCol($c, $name); |
825
|
16
|
|
|
|
|
27
|
$self->{header}->[$c]=$name; |
826
|
|
|
|
|
|
|
# $self->{colHash}->{$oldName}=undef; # undef still keeps the entry, use delete instead! |
827
|
16
|
|
|
|
|
25
|
delete $self->{colHash}->{$oldName}; |
828
|
16
|
|
|
|
|
38
|
$self->{colHash}->{$name}=$c; |
829
|
16
|
|
|
|
|
29
|
return 1; |
830
|
|
|
|
|
|
|
} |
831
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
sub replace{ |
833
|
2
|
|
|
2
|
1
|
5
|
my ($self, $oldColID, $newColRef, $newName) = @_; |
834
|
2
|
|
|
|
|
3
|
my $oldName; |
835
|
2
|
|
|
|
|
4
|
my $c=$self->checkOldCol($oldColID); |
836
|
2
|
50
|
|
|
|
5
|
return undef unless defined $c; |
837
|
2
|
|
|
|
|
3
|
$oldName=$self->{header}->[$c]; |
838
|
2
|
50
|
|
|
|
3
|
$newName=$oldName unless defined($newName); |
839
|
2
|
50
|
|
|
|
6
|
unless ($oldName eq $newName) { |
840
|
2
|
50
|
|
|
|
5
|
return undef unless defined $self->checkNewCol($c, $newName); |
841
|
|
|
|
|
|
|
} |
842
|
2
|
50
|
|
|
|
5
|
confess "New column size ".(scalar @$newColRef)." must be ".$self->nofRow() unless (scalar @$newColRef==$self->nofRow()); |
843
|
2
|
|
|
|
|
6
|
$self->rename($c, $newName); |
844
|
2
|
50
|
|
|
|
3
|
$self->rotate() unless $self->{type}; |
845
|
2
|
|
|
|
|
5
|
my $old=$self->{data}->[$c]; |
846
|
2
|
|
|
|
|
10
|
$self->{data}->[$c]=$newColRef; |
847
|
2
|
|
|
|
|
8
|
return $old; |
848
|
|
|
|
|
|
|
} |
849
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
sub swap{ |
851
|
2
|
|
|
2
|
1
|
5
|
my ($self, $colID1, $colID2) = @_; |
852
|
2
|
|
|
|
|
4
|
my $c1=$self->checkOldCol($colID1); |
853
|
2
|
50
|
|
|
|
6
|
return undef unless defined $c1; |
854
|
2
|
|
|
|
|
4
|
my $c2=$self->checkOldCol($colID2); |
855
|
2
|
50
|
|
|
|
5
|
return undef unless defined $c2; |
856
|
2
|
|
|
|
|
3
|
my $name1=$self->{header}->[$c1]; |
857
|
2
|
|
|
|
|
3
|
my $name2=$self->{header}->[$c2]; |
858
|
|
|
|
|
|
|
|
859
|
2
|
|
|
|
|
4
|
$self->{header}->[$c1]=$name2; |
860
|
2
|
|
|
|
|
3
|
$self->{header}->[$c2]=$name1; |
861
|
2
|
|
|
|
|
4
|
$self->{colHash}->{$name1}=$c2; |
862
|
2
|
|
|
|
|
3
|
$self->{colHash}->{$name2}=$c1; |
863
|
2
|
50
|
|
|
|
6
|
$self->rotate() unless $self->{type}; |
864
|
2
|
|
|
|
|
4
|
my $data1=$self->{data}->[$c1]; |
865
|
2
|
|
|
|
|
3
|
my $data2=$self->{data}->[$c2]; |
866
|
2
|
|
|
|
|
3
|
$self->{data}->[$c1]=$data2; |
867
|
2
|
|
|
|
|
2
|
$self->{data}->[$c2]=$data1; |
868
|
2
|
|
|
|
|
4
|
return 1; |
869
|
|
|
|
|
|
|
} |
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
sub moveCol { |
872
|
1
|
|
|
1
|
1
|
4
|
my ($self, $colID, $colIdx, $newColName) = @_; |
873
|
1
|
|
|
|
|
3
|
my $c=$self->checkOldCol($colID); |
874
|
1
|
50
|
|
|
|
4
|
return undef unless defined $c; |
875
|
1
|
50
|
33
|
|
|
5
|
confess "New column location out of bound!" unless ($colIdx >= 0 && $colIdx < $self->nofCol); |
876
|
1
|
50
|
|
|
|
3
|
return if $c == $colIdx; |
877
|
1
|
|
|
|
|
3
|
my $colName = $self->{header}->[$c]; |
878
|
1
|
|
|
|
|
4
|
my $col = $self->delCol($colID); |
879
|
1
|
|
|
|
|
4
|
$self->addCol($col, $colName, $colIdx); |
880
|
1
|
50
|
|
|
|
3
|
$self->rename($colIdx, $newColName) if defined $newColName; |
881
|
1
|
|
|
|
|
3
|
return 1; |
882
|
|
|
|
|
|
|
} |
883
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
sub checkOldRow { |
885
|
1077
|
|
|
1077
|
0
|
1369
|
my ($self, $rowIdx) = @_; |
886
|
1077
|
|
|
|
|
1353
|
my $maxIdx=$self->nofRow()-1; |
887
|
1077
|
50
|
|
|
|
1463
|
unless (defined $rowIdx) { |
888
|
0
|
|
|
|
|
0
|
print STDERR " Invalid row index in call to checkOldRow\n"; |
889
|
0
|
|
|
|
|
0
|
return undef; |
890
|
|
|
|
|
|
|
} |
891
|
1077
|
50
|
33
|
|
|
2617
|
if ($rowIdx<0 || $rowIdx>$maxIdx) { |
892
|
0
|
|
|
|
|
0
|
print STDERR "Row index out of range [0..$maxIdx]" ; |
893
|
0
|
|
|
|
|
0
|
return undef; |
894
|
|
|
|
|
|
|
} |
895
|
1077
|
|
|
|
|
1676
|
return $rowIdx; |
896
|
|
|
|
|
|
|
} |
897
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
sub checkNewRow { |
899
|
8
|
|
|
8
|
0
|
78
|
my ($self, $rowIdx) = @_; |
900
|
8
|
|
|
|
|
15
|
my $maxIdx=$self->nofRow()-1; |
901
|
8
|
50
|
|
|
|
14
|
unless (defined $rowIdx) { |
902
|
0
|
|
|
|
|
0
|
print STDERR "Invalid row index: $rowIdx \n"; |
903
|
0
|
|
|
|
|
0
|
return undef; |
904
|
|
|
|
|
|
|
} |
905
|
8
|
|
|
|
|
12
|
$maxIdx+=1; |
906
|
8
|
50
|
33
|
|
|
28
|
if ($rowIdx<0 || $rowIdx>$maxIdx) { |
907
|
0
|
|
|
|
|
0
|
print STDERR "Row index out of range [0..$maxIdx]" ; |
908
|
0
|
|
|
|
|
0
|
return undef; |
909
|
|
|
|
|
|
|
} |
910
|
8
|
|
|
|
|
17
|
return $rowIdx; |
911
|
|
|
|
|
|
|
} |
912
|
|
|
|
|
|
|
|
913
|
|
|
|
|
|
|
sub checkOldCol { |
914
|
833
|
|
|
833
|
0
|
1038
|
my ($self, $colID) = @_; |
915
|
833
|
|
|
|
|
1162
|
my $c=$self->colIndex($colID); |
916
|
833
|
50
|
|
|
|
1275
|
if ($c < 0) { |
917
|
0
|
|
|
|
|
0
|
print STDERR "Invalid column $colID"; |
918
|
0
|
|
|
|
|
0
|
return undef; |
919
|
|
|
|
|
|
|
} |
920
|
833
|
|
|
|
|
1041
|
return $c; |
921
|
|
|
|
|
|
|
} |
922
|
|
|
|
|
|
|
|
923
|
|
|
|
|
|
|
sub checkNewCol { |
924
|
30
|
|
|
30
|
0
|
48
|
my ($self, $colIdx, $colName) = @_; |
925
|
30
|
|
|
|
|
44
|
my $numCol=$self->nofCol(); |
926
|
30
|
50
|
|
|
|
51
|
unless (defined $colIdx) { |
927
|
0
|
|
|
|
|
0
|
print STDERR "Invalid column index $colIdx"; |
928
|
0
|
|
|
|
|
0
|
return undef; |
929
|
|
|
|
|
|
|
} |
930
|
30
|
50
|
33
|
|
|
86
|
if ($colIdx<0 || $colIdx>$numCol) { |
931
|
0
|
|
|
|
|
0
|
print STDERR "Column index $colIdx out of range [0..$numCol]"; |
932
|
0
|
|
|
|
|
0
|
return undef; |
933
|
|
|
|
|
|
|
} |
934
|
30
|
50
|
|
|
|
58
|
if (defined $self->{colHash}->{$colName} ) { |
935
|
0
|
|
|
|
|
0
|
print STDERR "Column name $colName already exists" ; |
936
|
0
|
|
|
|
|
0
|
return undef; |
937
|
|
|
|
|
|
|
} |
938
|
30
|
50
|
|
|
|
84
|
unless ($colName =~ /\D/) { |
939
|
0
|
|
|
|
|
0
|
print STDERR "Invalid column name $colName" ; |
940
|
0
|
|
|
|
|
0
|
return undef; |
941
|
|
|
|
|
|
|
} |
942
|
30
|
|
|
|
|
59
|
return $colIdx; |
943
|
|
|
|
|
|
|
} |
944
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
sub elm { |
946
|
628
|
|
|
628
|
1
|
3096
|
my ($self, $rowIdx, $colID) = @_; |
947
|
628
|
|
|
|
|
840
|
my $c=$self->checkOldCol($colID); |
948
|
628
|
50
|
|
|
|
850
|
return undef unless defined $c; |
949
|
628
|
50
|
|
|
|
877
|
return undef unless defined $self->checkOldRow($rowIdx); |
950
|
|
|
|
|
|
|
return ($self->{type})? |
951
|
|
|
|
|
|
|
$self->{data}->[$c]->[$rowIdx]: |
952
|
628
|
100
|
|
|
|
1570
|
$self->{data}->[$rowIdx]->[$c]; |
953
|
|
|
|
|
|
|
} |
954
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
sub elmRef { |
956
|
1
|
|
|
1
|
1
|
4
|
my ($self, $rowIdx, $colID) = @_; |
957
|
1
|
|
|
|
|
2
|
my $c=$self->checkOldCol($colID); |
958
|
1
|
50
|
|
|
|
3
|
return undef unless defined $c; |
959
|
1
|
50
|
|
|
|
3
|
return undef unless defined $self->checkOldRow($rowIdx); |
960
|
|
|
|
|
|
|
return ($self->{type})? |
961
|
|
|
|
|
|
|
\$self->{data}->[$c]->[$rowIdx]: |
962
|
1
|
50
|
|
|
|
7
|
\$self->{data}->[$rowIdx]->[$c]; |
963
|
|
|
|
|
|
|
} |
964
|
|
|
|
|
|
|
|
965
|
|
|
|
|
|
|
sub setElm { |
966
|
80
|
|
|
80
|
1
|
125
|
my ($self, $rowIdx, $colID, $val) = @_; |
967
|
80
|
100
|
|
|
|
142
|
$rowIdx = [$rowIdx] if ref($rowIdx) eq ''; |
968
|
80
|
50
|
|
|
|
134
|
$colID = [$colID] if ref($colID) eq ''; |
969
|
80
|
|
|
|
|
108
|
foreach my $col (@$colID) { |
970
|
80
|
|
|
|
|
108
|
my $c=$self->checkOldCol($col); |
971
|
80
|
50
|
|
|
|
120
|
return undef unless defined $c; |
972
|
80
|
|
|
|
|
93
|
foreach my $row (@$rowIdx) { |
973
|
116
|
50
|
|
|
|
142
|
return undef unless defined $self->checkOldRow($row); |
974
|
116
|
50
|
|
|
|
159
|
if ($self->{type}) { |
975
|
116
|
|
|
|
|
164
|
$self->{data}->[$c]->[$row]=$val; |
976
|
|
|
|
|
|
|
} else { |
977
|
0
|
|
|
|
|
0
|
$self->{data}->[$row]->[$c]=$val; |
978
|
|
|
|
|
|
|
} |
979
|
|
|
|
|
|
|
} |
980
|
|
|
|
|
|
|
} |
981
|
80
|
|
|
|
|
176
|
return 1; |
982
|
|
|
|
|
|
|
} |
983
|
|
|
|
|
|
|
|
984
|
|
|
|
|
|
|
# convert the internal structure of a table between row-based and column-based |
985
|
|
|
|
|
|
|
sub rotate { |
986
|
26
|
|
|
26
|
1
|
41
|
my $self=shift; |
987
|
26
|
|
|
|
|
44
|
my $newdata=[]; |
988
|
26
|
|
|
|
|
39
|
my $data=$self->{data}; |
989
|
26
|
100
|
|
|
|
54
|
$self->{type} = ($self->{type})?0:1; |
990
|
26
|
50
|
66
|
|
|
77
|
if ($self->{type} && scalar @$data == 0) { |
991
|
0
|
|
|
|
|
0
|
for (my $i=0; $i < $self->nofCol; $i++) { |
992
|
0
|
|
|
|
|
0
|
$newdata->[$i] = []; |
993
|
|
|
|
|
|
|
} |
994
|
|
|
|
|
|
|
} else { |
995
|
26
|
|
|
|
|
33
|
for (my $i=$#{$data->[0]}; $i>=0; $i--) { |
|
26
|
|
|
|
|
72
|
|
996
|
366
|
|
|
|
|
354
|
for (my $j=$#{$data}; $j>=0; $j--) { |
|
366
|
|
|
|
|
563
|
|
997
|
3740
|
|
|
|
|
5796
|
$newdata->[$i][$j]=$data->[$j][$i]; |
998
|
|
|
|
|
|
|
} |
999
|
|
|
|
|
|
|
} |
1000
|
|
|
|
|
|
|
} |
1001
|
26
|
|
|
|
|
46
|
$self->{data}=$newdata; |
1002
|
26
|
|
|
|
|
184
|
return 1; |
1003
|
|
|
|
|
|
|
} |
1004
|
|
|
|
|
|
|
|
1005
|
|
|
|
|
|
|
sub header { |
1006
|
15
|
|
|
15
|
1
|
32
|
my ($self, $header) = @_; |
1007
|
15
|
100
|
|
|
|
30
|
unless (defined($header)) { |
1008
|
14
|
|
|
|
|
17
|
return @{$self->{header}}; |
|
14
|
|
|
|
|
50
|
|
1009
|
|
|
|
|
|
|
} else { |
1010
|
1
|
50
|
|
|
|
2
|
if (scalar @$header != scalar @{$self->{header}}) { |
|
1
|
|
|
|
|
5
|
|
1011
|
0
|
|
|
|
|
0
|
confess "Header array should have size ".(scalar @{$self->{header}}); |
|
0
|
|
|
|
|
0
|
|
1012
|
|
|
|
|
|
|
} else { |
1013
|
1
|
|
|
|
|
2
|
my $colHash = checkHeader($header); |
1014
|
1
|
|
|
|
|
3
|
$self->{header} = $header; |
1015
|
1
|
|
|
|
|
3
|
$self->{colHash} = $colHash; |
1016
|
|
|
|
|
|
|
} |
1017
|
|
|
|
|
|
|
} |
1018
|
|
|
|
|
|
|
} |
1019
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
sub type { |
1021
|
0
|
|
|
0
|
1
|
0
|
my $self=shift; |
1022
|
0
|
|
|
|
|
0
|
return $self->{type}; |
1023
|
|
|
|
|
|
|
} |
1024
|
|
|
|
|
|
|
|
1025
|
|
|
|
|
|
|
sub data { |
1026
|
3
|
|
|
3
|
1
|
4
|
my $self=shift; |
1027
|
3
|
|
|
|
|
9
|
return $self->{data}; |
1028
|
|
|
|
|
|
|
} |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
# $t->sort(colID1, type1, order1, colID2, type2, order2, ... ); |
1031
|
|
|
|
|
|
|
# where |
1032
|
|
|
|
|
|
|
# colID is a column index (integer) or name (string), |
1033
|
|
|
|
|
|
|
# type is 0 for numerical and 1 for others |
1034
|
|
|
|
|
|
|
# order is 0 for ascending and 1 for descending |
1035
|
|
|
|
|
|
|
# Sorting is done with priority of colname1, colname2, ... |
1036
|
|
|
|
|
|
|
|
1037
|
|
|
|
|
|
|
sub sort_v0 { |
1038
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
1039
|
0
|
|
|
|
|
0
|
my ($str, $i) = ("", 0); |
1040
|
0
|
|
|
|
|
0
|
my @cols = (); |
1041
|
0
|
|
|
|
|
0
|
while (scalar @_) { |
1042
|
0
|
|
|
|
|
0
|
my $c = shift; |
1043
|
0
|
|
|
|
|
0
|
my $col = $self->checkOldCol($c); |
1044
|
0
|
0
|
|
|
|
0
|
return undef unless defined $col; |
1045
|
0
|
|
|
|
|
0
|
push @cols, $col; |
1046
|
0
|
|
|
|
|
0
|
my $op = '<=>'; |
1047
|
0
|
0
|
|
|
|
0
|
$op = 'cmp' if shift; # string |
1048
|
0
|
0
|
|
|
|
0
|
$str .=(shift)? "(\$b->[$i] $op \$a->[$i]) || " : |
1049
|
|
|
|
|
|
|
"(\$a->[$i] $op \$b->[$i]) || " ; |
1050
|
0
|
|
|
|
|
0
|
$i++; |
1051
|
|
|
|
|
|
|
} |
1052
|
0
|
|
|
|
|
0
|
substr($str, -3) = ""; # removes || from the end of $str |
1053
|
0
|
0
|
|
|
|
0
|
$self->rotate() if $self->{type}; |
1054
|
|
|
|
|
|
|
# construct a pre-ordered array |
1055
|
0
|
|
|
0
|
|
0
|
my $fun = sub { my ($cols, $data) = @_; |
1056
|
0
|
|
|
|
|
0
|
my @ext; |
1057
|
0
|
|
|
|
|
0
|
@ext = map {$data->[$_]} @$cols; |
|
0
|
|
|
|
|
0
|
|
1058
|
0
|
|
|
|
|
0
|
push @ext, $data; |
1059
|
0
|
|
|
|
|
0
|
return \@ext; |
1060
|
0
|
|
|
|
|
0
|
}; |
1061
|
0
|
|
|
|
|
0
|
my @preordered = map {&$fun(\@cols, $_)} @{$self->{data}}; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
1062
|
0
|
|
|
|
|
0
|
$self->{data} = [ map {$_->[$i]} eval "sort {$str} \@preordered;" ]; |
|
0
|
|
|
|
|
0
|
|
1063
|
0
|
|
|
|
|
0
|
return 1; |
1064
|
|
|
|
|
|
|
} |
1065
|
|
|
|
|
|
|
|
1066
|
|
|
|
|
|
|
sub sort { |
1067
|
4
|
|
|
4
|
1
|
18
|
my $self = shift; |
1068
|
4
|
|
|
|
|
10
|
my @cols = @_; |
1069
|
4
|
50
|
|
|
|
13
|
confess "Parameters be in groups of three!\n" if ($#cols % 3 != 2); |
1070
|
4
|
|
|
|
|
15
|
foreach (0 .. ($#cols/3)) { |
1071
|
5
|
|
|
|
|
15
|
my $col = $self->checkOldCol($cols[$_*3]); |
1072
|
5
|
50
|
|
|
|
10
|
return undef unless defined $col; |
1073
|
5
|
|
|
|
|
12
|
$cols[$_*3]=$col; |
1074
|
|
|
|
|
|
|
} |
1075
|
4
|
|
|
|
|
6
|
my @subs=(); |
1076
|
4
|
|
|
|
|
10
|
for (my $i=0; $i<=$#cols; $i+=3) { |
1077
|
5
|
|
|
|
|
7
|
my $mysub; |
1078
|
5
|
50
|
|
|
|
20
|
if ($cols[$i+1] == 0) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
1079
|
0
|
0
|
|
0
|
|
0
|
$mysub = ($cols[$i+2]? sub {defined($_[1])?(defined($_[0])? $_[1] <=> $_[0]:1):(defined($_[0])?-1:0)} : sub {defined($_[1])?(defined($_[0])? $_[0] <=> $_[1]:-1):(defined($_[0])?1:0)}); |
|
0
|
0
|
|
|
|
0
|
|
|
0
|
0
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1080
|
|
|
|
|
|
|
} elsif ($cols[$i+1] == 1) { |
1081
|
4
|
50
|
|
39
|
|
22
|
$mysub = ($cols[$i+2]? sub {defined($_[1])?(defined($_[0])? $_[1] cmp $_[0]:1):(defined($_[0])?-1:0)} : sub {defined($_[1])?(defined($_[0])? $_[0] cmp $_[1]:-1):(defined($_[0])?1:0)}); |
|
21
|
0
|
|
|
|
55
|
|
|
39
|
50
|
|
|
|
111
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
1082
|
|
|
|
|
|
|
} elsif (ref $cols[$i+1] eq 'CODE') { |
1083
|
1
|
|
|
|
|
3
|
my $predicate=$cols[$i+1]; |
1084
|
0
|
0
|
|
0
|
|
0
|
$mysub = ($cols[$i+2]? sub {defined($_[1])?(defined($_[0])? $predicate->($_[1],$_[0]) : 1): (defined($_[0])?-1:0)} : |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1085
|
1
|
50
|
|
14
|
|
5
|
sub {defined($_[1])?(defined($_[0])? $predicate->($_[0],$_[1]) : -1): (defined($_[0])?1:0)} ); |
|
14
|
0
|
|
|
|
28
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
} else { |
1087
|
0
|
|
|
|
|
0
|
confess "Sort method should be 0 (numerical), 1 (other type), or a subroutine reference!\n"; |
1088
|
|
|
|
|
|
|
} |
1089
|
5
|
|
|
|
|
14
|
push @subs, $mysub; |
1090
|
|
|
|
|
|
|
} |
1091
|
|
|
|
|
|
|
my $func = sub { |
1092
|
68
|
|
|
68
|
|
71
|
my $res = 0; |
1093
|
68
|
|
|
|
|
103
|
foreach (0 .. ($#cols/3)) { |
1094
|
74
|
|
66
|
|
|
171
|
$res ||= $subs[$_]->($a->[$cols[$_*3]], $b->[$cols[$_*3]]); |
1095
|
74
|
100
|
|
|
|
188
|
return $res unless $res==0; |
1096
|
|
|
|
|
|
|
} |
1097
|
5
|
|
|
|
|
8
|
return $res; |
1098
|
4
|
|
|
|
|
21
|
}; |
1099
|
4
|
100
|
|
|
|
22
|
$self->rotate() if $self->{type}; |
1100
|
4
|
|
|
|
|
6
|
$self->{data} = [sort $func @{$self->{data}}]; |
|
4
|
|
|
|
|
15
|
|
1101
|
4
|
|
|
|
|
31
|
return 1; |
1102
|
|
|
|
|
|
|
} |
1103
|
|
|
|
|
|
|
|
1104
|
|
|
|
|
|
|
# return rows as sub table in which |
1105
|
|
|
|
|
|
|
# a pattern $pattern is matched |
1106
|
|
|
|
|
|
|
sub match_pattern { |
1107
|
1
|
|
|
1
|
1
|
3
|
my ($self, $pattern, $countOnly) = @_; |
1108
|
1
|
|
|
|
|
2
|
my @data=(); |
1109
|
1
|
50
|
|
|
|
4
|
$countOnly=0 unless defined($countOnly); |
1110
|
1
|
|
|
|
|
1
|
my $cnt=0; |
1111
|
1
|
50
|
|
|
|
4
|
$self->rotate() if $self->{type}; |
1112
|
1
|
|
|
|
|
158
|
@Data::Table::OK= eval "map { $pattern?1:0; } \@{\$self->{data}};"; |
1113
|
1
|
|
|
|
|
5
|
my @ok = @Data::Table::OK; |
1114
|
1
|
|
|
|
|
3
|
$self->{OK} = \@ok; |
1115
|
1
|
|
|
|
|
5
|
for (my $i=0; $i<$self->nofRow(); $i++) { |
1116
|
9
|
100
|
|
|
|
14
|
if ($self->{OK}->[$i]) { |
1117
|
2
|
50
|
|
|
|
6
|
push @data, $self->{data}->[$i] unless $countOnly; |
1118
|
2
|
|
|
|
|
2
|
$cnt++; |
1119
|
2
|
|
|
|
|
4
|
$self->{OK}->[$i] = 1; |
1120
|
2
|
|
|
|
|
4
|
$Data::Table::OK[$i] = 1; |
1121
|
|
|
|
|
|
|
} else { |
1122
|
|
|
|
|
|
|
# in case sometimes eval results is '' instead of 0 |
1123
|
7
|
|
|
|
|
8
|
$self->{OK}->[$i] = 0; |
1124
|
7
|
|
|
|
|
10
|
$Data::Table::OK[$i] = 0; |
1125
|
|
|
|
|
|
|
} |
1126
|
|
|
|
|
|
|
} |
1127
|
1
|
|
|
|
|
2
|
$self->{MATCH} = []; |
1128
|
1
|
100
|
|
|
|
4
|
map { push @{$self->{MATCH}}, $_ if $self->{OK}->[$_] } 0 .. $#ok; |
|
9
|
|
|
|
|
19
|
|
|
2
|
|
|
|
|
14
|
|
1129
|
1
|
50
|
|
|
|
3
|
return $cnt if $countOnly; |
1130
|
1
|
|
|
|
|
2
|
my @header=@{$self->{header}}; |
|
1
|
|
|
|
|
4
|
|
1131
|
1
|
|
|
|
|
6
|
return new Data::Table(\@data, \@header, 0); |
1132
|
|
|
|
|
|
|
} |
1133
|
|
|
|
|
|
|
|
1134
|
|
|
|
|
|
|
# return rows as sub table in which |
1135
|
|
|
|
|
|
|
# a pattern $pattern is matched |
1136
|
|
|
|
|
|
|
# each row is passed to the patern as a hash, where column names are keys |
1137
|
|
|
|
|
|
|
sub match_pattern_hash { |
1138
|
2
|
|
|
2
|
1
|
10
|
my ($self, $pattern, $countOnly) = @_; |
1139
|
2
|
|
|
|
|
5
|
my @data=(); |
1140
|
2
|
50
|
|
|
|
10
|
$countOnly=0 unless defined($countOnly); |
1141
|
2
|
|
|
|
|
4
|
my $cnt=0; |
1142
|
2
|
100
|
|
|
|
9
|
$self->rotate() if $self->{type}; |
1143
|
2
|
|
|
|
|
8
|
@Data::Table::OK = (); |
1144
|
2
|
|
|
|
|
8
|
for (my $i=0; $i<$self->nofRow(); $i++) { |
1145
|
86
|
|
|
|
|
94
|
local %_ = %{$self->rowHashRef($i)}; |
|
86
|
|
|
|
|
142
|
|
1146
|
86
|
|
|
|
|
3402
|
$Data::Table::OK[$i] = eval "$pattern?1:0"; |
1147
|
|
|
|
|
|
|
} |
1148
|
|
|
|
|
|
|
#@Data::Table::OK= eval "map { $pattern?1:0; } \@{\$self->{data}};"; |
1149
|
2
|
|
|
|
|
9
|
my @ok = @Data::Table::OK; |
1150
|
2
|
|
|
|
|
5
|
$self->{OK} = \@ok; |
1151
|
2
|
|
|
|
|
9
|
for (my $i=0; $i<$self->nofRow(); $i++) { |
1152
|
86
|
100
|
|
|
|
108
|
if ($self->{OK}->[$i]) { |
1153
|
39
|
50
|
|
|
|
59
|
push @data, $self->{data}->[$i] unless $countOnly; |
1154
|
39
|
|
|
|
|
41
|
$cnt++; |
1155
|
39
|
|
|
|
|
41
|
$self->{OK}->[$i] = 1; |
1156
|
39
|
|
|
|
|
54
|
$Data::Table::OK[$i] = 1; |
1157
|
|
|
|
|
|
|
} else { |
1158
|
|
|
|
|
|
|
# in case sometimes eval results is '' instead of 0 |
1159
|
47
|
|
|
|
|
55
|
$self->{OK}->[$i] = 0; |
1160
|
47
|
|
|
|
|
63
|
$Data::Table::OK[$i] = 0; |
1161
|
|
|
|
|
|
|
} |
1162
|
|
|
|
|
|
|
} |
1163
|
2
|
|
|
|
|
4
|
$self->{MATCH} = []; |
1164
|
2
|
100
|
|
|
|
8
|
map { push @{$self->{MATCH}}, $_ if $self->{OK}->[$_] } 0 .. $#ok; |
|
86
|
|
|
|
|
119
|
|
|
39
|
|
|
|
|
98
|
|
1165
|
2
|
50
|
|
|
|
6
|
return $cnt if $countOnly; |
1166
|
2
|
|
|
|
|
3
|
my @header=@{$self->{header}}; |
|
2
|
|
|
|
|
7
|
|
1167
|
2
|
|
|
|
|
11
|
return new Data::Table(\@data, \@header, 0); |
1168
|
|
|
|
|
|
|
} |
1169
|
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
# return rows as sub table in which |
1171
|
|
|
|
|
|
|
# a string elm in an array @$s is matched |
1172
|
|
|
|
|
|
|
sub match_string { |
1173
|
2
|
|
|
2
|
1
|
7
|
my ($self, $s, $caseIgn, $countOnly) = @_; |
1174
|
2
|
50
|
|
|
|
7
|
confess unless defined($s); |
1175
|
2
|
50
|
|
|
|
6
|
$countOnly=0 unless defined($countOnly); |
1176
|
2
|
|
|
|
|
3
|
my @data=(); |
1177
|
2
|
|
|
|
|
4
|
my $r; |
1178
|
2
|
50
|
|
|
|
7
|
$self->rotate() if $self->{type}; |
1179
|
2
|
|
|
|
|
4
|
@Data::Table::OK=(); |
1180
|
2
|
|
|
|
|
5
|
$self->{OK} = []; |
1181
|
2
|
50
|
|
|
|
7
|
$caseIgn=0 unless defined($caseIgn); |
1182
|
|
|
|
|
|
|
|
1183
|
|
|
|
|
|
|
### comment out next line if your perl version < 5.005 ### |
1184
|
2
|
50
|
|
|
|
50
|
$r = ($caseIgn)?qr/$s/i : qr/$s/; |
1185
|
2
|
|
|
|
|
5
|
my $cnt=0; |
1186
|
|
|
|
|
|
|
|
1187
|
2
|
|
|
|
|
4
|
foreach my $row_ref (@{$self->data}) { |
|
2
|
|
|
|
|
7
|
|
1188
|
18
|
|
|
|
|
21
|
push @Data::Table::OK, 0; |
1189
|
18
|
|
|
|
|
19
|
push @{$self->{OK}}, 0; |
|
18
|
|
|
|
|
24
|
|
1190
|
18
|
|
|
|
|
25
|
foreach my $elm (@$row_ref) { |
1191
|
83
|
50
|
|
|
|
111
|
next unless defined($elm); |
1192
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
### comment out the next line if your perl version < 5.005 |
1194
|
83
|
100
|
|
|
|
183
|
if ($elm =~ /$r/) { |
1195
|
|
|
|
|
|
|
### uncomment the next line if your perl version < 5.005 |
1196
|
|
|
|
|
|
|
# if ($elm =~ /$s/ || ($elm=~ /$s/i && $caseIgn)) { |
1197
|
|
|
|
|
|
|
|
1198
|
5
|
50
|
|
|
|
9
|
push @data, $row_ref unless $countOnly; |
1199
|
5
|
|
|
|
|
6
|
$Data::Table::OK[$#Data::Table::OK]=1; |
1200
|
5
|
|
|
|
|
8
|
$self->{OK}->[$#{$self->{OK}}]=1; |
|
5
|
|
|
|
|
8
|
|
1201
|
5
|
|
|
|
|
14
|
$cnt++; |
1202
|
5
|
|
|
|
|
6
|
last; |
1203
|
|
|
|
|
|
|
} |
1204
|
|
|
|
|
|
|
} |
1205
|
|
|
|
|
|
|
} |
1206
|
2
|
|
|
|
|
6
|
$self->{MATCH} = []; |
1207
|
2
|
100
|
|
|
|
4
|
map { push @{$self->{MATCH}}, $_ if $self->{OK}->[$_] } 0 .. $#{$self->{OK}}; |
|
18
|
|
|
|
|
41
|
|
|
5
|
|
|
|
|
11
|
|
|
2
|
|
|
|
|
7
|
|
1208
|
2
|
50
|
|
|
|
6
|
return $cnt if $countOnly; |
1209
|
2
|
|
|
|
|
3
|
my @header=@{$self->{header}}; |
|
2
|
|
|
|
|
7
|
|
1210
|
2
|
|
|
|
|
7
|
return new Data::Table(\@data, \@header, 0); |
1211
|
|
|
|
|
|
|
} |
1212
|
|
|
|
|
|
|
|
1213
|
|
|
|
|
|
|
sub rowMask { |
1214
|
1
|
|
|
1
|
1
|
6
|
my ($self, $OK, $c) = @_; |
1215
|
1
|
50
|
|
|
|
3
|
confess unless defined($OK); |
1216
|
1
|
50
|
|
|
|
3
|
$c = 0 unless defined ($c); |
1217
|
1
|
|
|
|
|
1
|
my @data=(); |
1218
|
1
|
50
|
|
|
|
3
|
$self->rotate() if $self->{type}; |
1219
|
1
|
|
|
|
|
4
|
my $data0=$self->data; |
1220
|
1
|
|
|
|
|
2
|
for (my $i=0; $i<$self->nofRow(); $i++) { |
1221
|
9
|
50
|
|
|
|
12
|
if ($c) { |
1222
|
9
|
100
|
|
|
|
18
|
push @data, $data0->[$i] unless $OK->[$i]; |
1223
|
|
|
|
|
|
|
} else { |
1224
|
0
|
0
|
|
|
|
0
|
push @data, $data0->[$i] if $OK->[$i]; |
1225
|
|
|
|
|
|
|
} |
1226
|
|
|
|
|
|
|
} |
1227
|
1
|
|
|
|
|
2
|
my @header=@{$self->{header}}; |
|
1
|
|
|
|
|
3
|
|
1228
|
1
|
|
|
|
|
3
|
return new Data::Table(\@data, \@header, 0); |
1229
|
|
|
|
|
|
|
} |
1230
|
|
|
|
|
|
|
|
1231
|
|
|
|
|
|
|
sub rowMerge { |
1232
|
4
|
|
|
4
|
1
|
14
|
my ($self, $tbl, $arg_ref) = @_; |
1233
|
4
|
100
|
|
|
|
42
|
my %arg = defined $arg_ref ? %$arg_ref : (); |
1234
|
4
|
100
|
|
|
|
12
|
$arg{byName} =0 unless exists $arg{byName}; |
1235
|
4
|
100
|
|
|
|
12
|
$arg{addNewCol} = 0 unless exists $arg{addNewCol}; |
1236
|
4
|
50
|
33
|
|
|
8
|
if ($self->isEmpty && !$tbl->isEmpty) { |
1237
|
0
|
|
|
|
|
0
|
my @header = $tbl->header; |
1238
|
0
|
|
|
|
|
0
|
my $i = 0; |
1239
|
0
|
|
|
|
|
0
|
foreach my $s (@header) { |
1240
|
0
|
|
|
|
|
0
|
push @{$self->{header}}, $s; |
|
0
|
|
|
|
|
0
|
|
1241
|
0
|
|
|
|
|
0
|
$self->{colHash}->{$s} = $i++; |
1242
|
|
|
|
|
|
|
} |
1243
|
|
|
|
|
|
|
} |
1244
|
4
|
100
|
100
|
|
|
19
|
if ($arg{byName} == 0 && $arg{addNewCol} == 0) { |
1245
|
1
|
50
|
|
|
|
4
|
confess "Tables must have the same number of columns" unless ($self->nofCol()==$tbl->nofCol()); |
1246
|
|
|
|
|
|
|
} else { |
1247
|
3
|
100
|
|
|
|
8
|
if ($arg{addNewCol}) { |
1248
|
2
|
100
|
|
|
|
5
|
unless ($arg{byName}) { # add extra column by index |
1249
|
1
|
50
|
|
|
|
3
|
if ($self->nofCol < $tbl->nofCol) { |
|
|
50
|
|
|
|
|
|
1250
|
0
|
|
|
|
|
0
|
my @header = $tbl->header; |
1251
|
0
|
|
|
|
|
0
|
my $nCols = $self->nofCol(); |
1252
|
0
|
|
|
|
|
0
|
my $nRows = $self->nofRow(); |
1253
|
0
|
|
|
|
|
0
|
for (my $i = $nCols; $i<@header; $i++) { |
1254
|
0
|
|
|
|
|
0
|
my @one = (undef) x $nRows; |
1255
|
0
|
|
|
|
|
0
|
$self->addCol(\@one, $header[$i]); |
1256
|
|
|
|
|
|
|
} |
1257
|
|
|
|
|
|
|
} elsif ($self->nofCol > $tbl->nofCol) { |
1258
|
1
|
|
|
|
|
2
|
my @header = $self->header; |
1259
|
1
|
|
|
|
|
2
|
my %h = (); |
1260
|
1
|
|
|
|
|
3
|
my @header2 = $tbl->header; |
1261
|
1
|
|
|
|
|
3
|
map {$h{$_} = 1} @header2; |
|
2
|
|
|
|
|
4
|
|
1262
|
1
|
|
|
|
|
2
|
my $nCols = $tbl->nofCol(); |
1263
|
1
|
|
|
|
|
3
|
my $nRows = $tbl->nofRow(); |
1264
|
1
|
|
|
|
|
3
|
for (my $i = $nCols; $i<$self->nofCol; $i++) { |
1265
|
2
|
|
|
|
|
6
|
my @one = (undef) x $nRows; |
1266
|
|
|
|
|
|
|
# make sure new col name is unique |
1267
|
2
|
|
|
|
|
3
|
my $s = $header[$i]; |
1268
|
2
|
|
|
|
|
3
|
my $cnt = 2; |
1269
|
2
|
|
|
|
|
6
|
while (exists $h{$s}) { |
1270
|
0
|
|
|
|
|
0
|
$s = $header[$i]."_".$cnt ++; |
1271
|
|
|
|
|
|
|
} |
1272
|
2
|
|
|
|
|
5
|
$tbl->addCol(\@one, $s); |
1273
|
2
|
|
|
|
|
4
|
$h{$s} = 1; |
1274
|
|
|
|
|
|
|
} |
1275
|
|
|
|
|
|
|
} |
1276
|
|
|
|
|
|
|
} else { |
1277
|
1
|
|
|
|
|
3
|
my @header = $tbl->header; |
1278
|
1
|
|
|
|
|
3
|
my $nRows = $self->nofRow(); |
1279
|
1
|
|
|
|
|
3
|
foreach my $col (@header) { |
1280
|
2
|
50
|
|
|
|
2
|
if ($self->colIndex($col) < 0) { |
1281
|
2
|
|
|
|
|
5
|
my @one = (undef) x $nRows; |
1282
|
2
|
|
|
|
|
6
|
$self->addCol(\@one, $col); |
1283
|
|
|
|
|
|
|
} |
1284
|
|
|
|
|
|
|
} |
1285
|
|
|
|
|
|
|
} |
1286
|
|
|
|
|
|
|
} |
1287
|
|
|
|
|
|
|
} |
1288
|
4
|
100
|
|
|
|
9
|
$self->rotate() if $self->{type}; |
1289
|
4
|
100
|
|
|
|
10
|
$tbl->rotate() if $tbl->{type}; |
1290
|
4
|
|
|
|
|
7
|
my $data=$self->{data}; |
1291
|
4
|
100
|
|
|
|
8
|
if ($arg{byName} == 0) { |
1292
|
2
|
|
|
|
|
9
|
push @$data, @{$tbl->{data}}; |
|
2
|
|
|
|
|
7
|
|
1293
|
|
|
|
|
|
|
} else { |
1294
|
2
|
|
|
|
|
5
|
my @header = $self->header; |
1295
|
2
|
|
|
|
|
3
|
my $nCols = scalar @header; |
1296
|
2
|
|
|
|
|
5
|
my @colIndex = map { $tbl->colIndex($_) } @header; |
|
6
|
|
|
|
|
10
|
|
1297
|
2
|
|
|
|
|
3
|
foreach my $rowRef (@{$tbl->{data}}) { |
|
2
|
|
|
|
|
5
|
|
1298
|
6
|
|
|
|
|
8
|
my @one = (); |
1299
|
6
|
|
|
|
|
10
|
for (my $j=0; $j< $nCols; $j++) { |
1300
|
18
|
100
|
|
|
|
38
|
$one[$j] = $colIndex[$j]>=0 ? $rowRef->[$colIndex[$j]]:undef; |
1301
|
|
|
|
|
|
|
} |
1302
|
6
|
|
|
|
|
11
|
push @$data, \@one; |
1303
|
|
|
|
|
|
|
} |
1304
|
|
|
|
|
|
|
} |
1305
|
4
|
|
|
|
|
10
|
return 1; |
1306
|
|
|
|
|
|
|
} |
1307
|
|
|
|
|
|
|
|
1308
|
|
|
|
|
|
|
sub colMerge { |
1309
|
2
|
|
|
2
|
1
|
14
|
my ($self, $tbl, $arg_ref) = @_; |
1310
|
2
|
100
|
|
|
|
8
|
my %arg = defined $arg_ref ? %$arg_ref : (); |
1311
|
2
|
100
|
|
|
|
8
|
$arg{renameCol} =0 unless exists $arg{renameCol}; |
1312
|
2
|
50
|
33
|
|
|
5
|
confess "Tables must have the same number of rows" unless ($self->isEmpty || $self->nofRow()==$tbl->nofRow()); |
1313
|
2
|
|
|
|
|
4
|
my $col; |
1314
|
2
|
|
|
|
|
4
|
my %h = (); |
1315
|
2
|
|
|
|
|
2
|
map {$h{$_} = 1} @{$self->{header}}; |
|
12
|
|
|
|
|
19
|
|
|
2
|
|
|
|
|
6
|
|
1316
|
2
|
|
|
|
|
5
|
my @header2 = (); |
1317
|
2
|
|
|
|
|
15
|
foreach $col ($tbl->header) { |
1318
|
7
|
|
|
|
|
9
|
my $s = $col; |
1319
|
7
|
100
|
|
|
|
16
|
if (exists $h{$s}) { |
1320
|
6
|
50
|
|
|
|
10
|
confess "Duplicate column $col in two tables" unless $arg{renameCol}; |
1321
|
6
|
|
|
|
|
7
|
my $cnt = 2; |
1322
|
6
|
|
|
|
|
9
|
while (exists $h{$s}) { |
1323
|
6
|
|
|
|
|
16
|
$s = $col ."_". $cnt++; |
1324
|
|
|
|
|
|
|
} |
1325
|
|
|
|
|
|
|
} |
1326
|
7
|
|
|
|
|
14
|
$h{$s} = 1; |
1327
|
7
|
|
|
|
|
11
|
push @header2, $s; |
1328
|
|
|
|
|
|
|
} |
1329
|
2
|
50
|
|
|
|
10
|
$self->rotate() unless $self->{type}; |
1330
|
2
|
50
|
|
|
|
9
|
$tbl->rotate() unless $tbl->{type}; |
1331
|
2
|
|
|
|
|
5
|
my $i = $self->nofCol(); |
1332
|
2
|
|
|
|
|
5
|
for my $s (@header2) { |
1333
|
7
|
|
|
|
|
9
|
push @{$self->{header}}, $s; |
|
7
|
|
|
|
|
10
|
|
1334
|
7
|
|
|
|
|
15
|
$self->{colHash}->{$s} = $i++; |
1335
|
|
|
|
|
|
|
} |
1336
|
2
|
|
|
|
|
4
|
my $data=$self->{data}; |
1337
|
2
|
|
|
|
|
6
|
for ($i=0; $i<$tbl->nofCol(); $i++) { |
1338
|
7
|
|
|
|
|
12
|
push @$data, $tbl->{data}->[$i]; |
1339
|
|
|
|
|
|
|
} |
1340
|
2
|
|
|
|
|
6
|
return 1; |
1341
|
|
|
|
|
|
|
} |
1342
|
|
|
|
|
|
|
|
1343
|
|
|
|
|
|
|
sub subTable { |
1344
|
7
|
|
|
7
|
1
|
14
|
my ($self, $rowIdcsRef, $colIDsRef, $arg_ref) = @_; |
1345
|
7
|
|
|
|
|
12
|
my @newdata=(); |
1346
|
7
|
|
|
|
|
10
|
my @newheader=(); |
1347
|
|
|
|
|
|
|
# to avoid the side effect of modifying $colIDsRef, 4/30/2012 |
1348
|
7
|
|
|
|
|
9
|
my $useRowMask = 0; |
1349
|
7
|
100
|
|
|
|
16
|
$useRowMask = $arg_ref->{useRowMask} if defined $arg_ref->{useRowMask}; |
1350
|
7
|
|
|
|
|
8
|
my @rowIdcs = (); |
1351
|
7
|
100
|
|
|
|
21
|
@rowIdcs = defined $rowIdcsRef ? @$rowIdcsRef : 0..($self->nofRow()-1) unless $useRowMask; |
|
|
100
|
|
|
|
|
|
1352
|
7
|
100
|
|
|
|
23
|
my @colIDs = defined $colIDsRef ? @$colIDsRef : 0..($self->nofCol()-1); |
1353
|
|
|
|
|
|
|
##$rowIdcsRef = [0..($self->nofRow()-1)] unless defined $rowIdcsRef; |
1354
|
|
|
|
|
|
|
#$colIDsRef = [0..($self->nofCol()-1)] unless defined $colIDsRef; |
1355
|
7
|
|
|
|
|
26
|
for (my $i = 0; $i < scalar @colIDs; $i++) { |
1356
|
33
|
|
|
|
|
55
|
$colIDs[$i]=$self->checkOldCol($colIDs[$i]); |
1357
|
|
|
|
|
|
|
#return undef unless defined $colIDsRef; |
1358
|
33
|
|
|
|
|
70
|
push @newheader, $self->{header}->[$colIDs[$i]]; |
1359
|
|
|
|
|
|
|
} |
1360
|
7
|
100
|
|
|
|
11
|
if ($useRowMask) { |
1361
|
1
|
|
|
|
|
4
|
my @OK = @$rowIdcsRef; |
1362
|
1
|
|
|
|
|
3
|
my $n = $self->nofRow; |
1363
|
1
|
|
|
|
|
4
|
for (my $i = 0; $i < $n; $i++) { |
1364
|
9
|
100
|
|
|
|
19
|
push @rowIdcs, $i if $OK[$i]; |
1365
|
|
|
|
|
|
|
} |
1366
|
|
|
|
|
|
|
} |
1367
|
7
|
50
|
|
|
|
15
|
if ($self->{type}) { |
1368
|
0
|
|
|
|
|
0
|
for (my $i = 0; $i < scalar @colIDs; $i++) { |
1369
|
0
|
|
|
|
|
0
|
my @one=(); |
1370
|
0
|
|
|
|
|
0
|
for (my $j = 0; $j < scalar @rowIdcs; $j++) { |
1371
|
0
|
0
|
|
|
|
0
|
return undef unless defined $self->checkOldRow($rowIdcs[$j]); |
1372
|
0
|
|
|
|
|
0
|
push @one, $self->{data}->[$colIDs[$i]]->[$rowIdcs[$j]]; |
1373
|
|
|
|
|
|
|
} |
1374
|
0
|
|
|
|
|
0
|
push @newdata, \@one; |
1375
|
|
|
|
|
|
|
} |
1376
|
|
|
|
|
|
|
} else { |
1377
|
7
|
|
|
|
|
16
|
for (my $i = 0; $i < scalar @rowIdcs; $i++) { |
1378
|
30
|
50
|
|
|
|
45
|
return undef unless defined $self->checkOldRow($rowIdcs[$i]); |
1379
|
30
|
|
|
|
|
33
|
my @one=(); |
1380
|
30
|
|
|
|
|
48
|
for (my $j = 0; $j < scalar @colIDs; $j++) { |
1381
|
127
|
|
|
|
|
230
|
push @one, $self->{data}->[$rowIdcs[$i]]->[$colIDs[$j]]; |
1382
|
|
|
|
|
|
|
} |
1383
|
30
|
|
|
|
|
69
|
push @newdata, \@one; |
1384
|
|
|
|
|
|
|
} |
1385
|
|
|
|
|
|
|
} |
1386
|
7
|
|
|
|
|
19
|
return new Data::Table(\@newdata, \@newheader, $self->{type}); |
1387
|
|
|
|
|
|
|
} |
1388
|
|
|
|
|
|
|
|
1389
|
|
|
|
|
|
|
sub reorder { |
1390
|
1
|
|
|
1
|
1
|
3
|
my ($self, $colIDsRef, $arg_ref) = @_; |
1391
|
1
|
50
|
|
|
|
5
|
return unless defined $colIDsRef; |
1392
|
1
|
50
|
|
|
|
4
|
$arg_ref = {keepRest => 1} unless defined $arg_ref; |
1393
|
1
|
|
|
|
|
2
|
my @newdata=(); |
1394
|
1
|
|
|
|
|
2
|
my @newheader=(); |
1395
|
1
|
|
|
|
|
2
|
my @colIDs = (); |
1396
|
1
|
|
|
|
|
2
|
my %inNew = (); |
1397
|
1
|
|
|
|
|
4
|
for (my $i = 0; $i < scalar @$colIDsRef; $i++) { |
1398
|
3
|
|
|
|
|
7
|
my $idx = $self->checkOldCol($colIDsRef->[$i]); |
1399
|
3
|
50
|
|
|
|
30
|
confess "Invalide column $colIDsRef->[$i]" unless defined $idx; |
1400
|
3
|
|
|
|
|
7
|
$colIDs[$i] = $idx; |
1401
|
3
|
|
|
|
|
6
|
$inNew{$idx} = 1; |
1402
|
|
|
|
|
|
|
#return undef unless defined $colIDsRef; |
1403
|
3
|
|
|
|
|
8
|
push @newheader, $self->{header}->[$idx]; |
1404
|
|
|
|
|
|
|
} |
1405
|
1
|
50
|
|
|
|
12
|
if ($arg_ref->{keepRest}) { |
1406
|
1
|
|
|
|
|
4
|
for (my $i = 0; $i<$self->nofCol; $i++) { |
1407
|
6
|
100
|
|
|
|
13
|
unless (exists $inNew{$i}) { |
1408
|
3
|
|
|
|
|
6
|
push @colIDs, $i; |
1409
|
3
|
|
|
|
|
6
|
push @newheader, $self->{header}->[$i]; |
1410
|
|
|
|
|
|
|
} |
1411
|
|
|
|
|
|
|
} |
1412
|
|
|
|
|
|
|
} |
1413
|
|
|
|
|
|
|
|
1414
|
1
|
50
|
|
|
|
4
|
if ($self->{type}) { |
1415
|
1
|
|
|
|
|
4
|
for (my $i = 0; $i < scalar @colIDs; $i++) { |
1416
|
6
|
|
|
|
|
18
|
push @newdata, $self->{data}->[$colIDs[$i]]; |
1417
|
|
|
|
|
|
|
} |
1418
|
|
|
|
|
|
|
} else { |
1419
|
0
|
|
|
|
|
0
|
my $n = $self->nofRow; |
1420
|
0
|
|
|
|
|
0
|
for (my $i = 0; $i < $n; $i++) { |
1421
|
0
|
|
|
|
|
0
|
my @one=(); |
1422
|
0
|
|
|
|
|
0
|
for (my $j = 0; $j < scalar @colIDs; $j++) { |
1423
|
0
|
|
|
|
|
0
|
push @one, $self->{data}->[$i]->[$colIDs[$j]]; |
1424
|
|
|
|
|
|
|
} |
1425
|
0
|
|
|
|
|
0
|
push @newdata, \@one; |
1426
|
|
|
|
|
|
|
} |
1427
|
|
|
|
|
|
|
} |
1428
|
1
|
|
|
|
|
5
|
$self->{header} = \@newheader; |
1429
|
1
|
|
|
|
|
3
|
$self->{colHash} = (); |
1430
|
1
|
|
|
|
|
4
|
for (my $i = 0; $i < scalar @colIDs; $i++) { |
1431
|
6
|
|
|
|
|
14
|
$self->{colHash}->{$newheader[$i]} = $i; |
1432
|
|
|
|
|
|
|
} |
1433
|
1
|
|
|
|
|
5
|
$self->{data} = \@newdata; |
1434
|
|
|
|
|
|
|
} |
1435
|
|
|
|
|
|
|
|
1436
|
|
|
|
|
|
|
sub clone { |
1437
|
4
|
|
|
4
|
1
|
433
|
my $self = shift; |
1438
|
4
|
|
|
|
|
8
|
my $data = $self->{data}; |
1439
|
4
|
|
|
|
|
4
|
my @newheader = @{$self->{header}}; |
|
4
|
|
|
|
|
13
|
|
1440
|
4
|
|
|
|
|
6
|
my @newdata = (); |
1441
|
4
|
|
|
|
|
7
|
for (my $i = 0; $i < scalar @{$data}; $i++) { |
|
34
|
|
|
|
|
49
|
|
1442
|
30
|
|
|
|
|
31
|
my @one=(); |
1443
|
30
|
|
|
|
|
32
|
for (my $j = 0; $j < scalar @{$data->[$i]}; $j++) { |
|
198
|
|
|
|
|
266
|
|
1444
|
168
|
|
|
|
|
230
|
push @one, $data->[$i]->[$j]; |
1445
|
|
|
|
|
|
|
} |
1446
|
30
|
|
|
|
|
42
|
push @newdata, \@one; |
1447
|
|
|
|
|
|
|
} |
1448
|
4
|
|
|
|
|
11
|
return new Data::Table(\@newdata, \@newheader, $self->{type}); |
1449
|
|
|
|
|
|
|
} |
1450
|
|
|
|
|
|
|
|
1451
|
|
|
|
|
|
|
sub fromCSVi { |
1452
|
2
|
|
|
2
|
1
|
5
|
my $self = shift; |
1453
|
2
|
|
|
|
|
7
|
return fromCSV(@_); |
1454
|
|
|
|
|
|
|
} |
1455
|
|
|
|
|
|
|
|
1456
|
|
|
|
|
|
|
sub getOneLine { |
1457
|
216
|
|
|
216
|
0
|
333
|
my ($fh, $linebreak, $qualifier) = @_; |
1458
|
216
|
|
|
|
|
232
|
my $s = ''; |
1459
|
216
|
50
|
|
|
|
328
|
$qualifier = '' unless defined $qualifier; |
1460
|
216
|
|
|
|
|
537
|
local($/) = $linebreak; |
1461
|
216
|
100
|
|
|
|
376
|
return <$fh> unless $qualifier; |
1462
|
214
|
|
|
|
|
1021
|
while (my $s2 = <$fh>) { |
1463
|
197
|
|
|
|
|
505
|
$s .= $s2; |
1464
|
197
|
|
|
|
|
437
|
my @S = ($s =~ /$qualifier/g); |
1465
|
197
|
50
|
|
|
|
738
|
return $s if (scalar @S % 2 == 0); |
1466
|
|
|
|
|
|
|
} |
1467
|
17
|
|
|
|
|
78
|
return $s; |
1468
|
|
|
|
|
|
|
} |
1469
|
|
|
|
|
|
|
|
1470
|
|
|
|
|
|
|
sub fromCSV { |
1471
|
17
|
|
|
17
|
1
|
151
|
my ($name_or_handler, $includeHeader, $header, $arg_ref) = @_; |
1472
|
17
|
100
|
|
|
|
49
|
$includeHeader = 1 unless defined($includeHeader); |
1473
|
17
|
|
|
|
|
55
|
my ($OS, $delimiter, $qualifier, $skip_lines, $skip_pattern, $encoding) = ($Data::Table::DEFAULTS{OS}, $Data::Table::DEFAULTS{CSV_DELIMITER}, $Data::Table::DEFAULTS{CSV_QUALIFIER}, 0, undef, $Data::Table::DEFAULTS{ENCODING}); |
1474
|
17
|
100
|
100
|
|
|
57
|
$OS = $arg_ref->{'OS'} if (defined($arg_ref) && defined($arg_ref->{'OS'})); |
1475
|
|
|
|
|
|
|
# OS: 0 for UNIX (\n as linebreak), 1 for Windows (\r\n as linebreak) |
1476
|
|
|
|
|
|
|
### 2 for MAC (\r as linebreak) |
1477
|
17
|
100
|
|
|
|
35
|
if (defined($arg_ref)) { |
1478
|
8
|
50
|
|
|
|
29
|
$delimiter = $arg_ref->{'delimiter'} if defined($arg_ref->{'delimiter'}); |
1479
|
8
|
100
|
|
|
|
15
|
$qualifier = $arg_ref->{'qualifier'} if defined($arg_ref->{'qualifier'}); |
1480
|
8
|
100
|
66
|
|
|
23
|
$skip_lines = $arg_ref->{'skip_lines'} if (defined($arg_ref->{'skip_lines'}) && $arg_ref->{'skip_lines'}>0); |
1481
|
8
|
100
|
|
|
|
15
|
$skip_pattern = $arg_ref->{'skip_pattern'} if defined($arg_ref->{'skip_pattern'}); |
1482
|
8
|
50
|
|
|
|
13
|
$encoding = $arg_ref->{'encoding'} if defined($arg_ref->{'encoding'}); |
1483
|
|
|
|
|
|
|
} |
1484
|
17
|
|
|
|
|
23
|
my @header; |
1485
|
17
|
|
|
|
|
26
|
my $givenHeader = 0; |
1486
|
17
|
50
|
33
|
|
|
43
|
if (defined($header) && ref($header) eq 'ARRAY') { |
1487
|
0
|
|
|
|
|
0
|
$givenHeader = 1; |
1488
|
0
|
|
|
|
|
0
|
@header= @$header; |
1489
|
|
|
|
|
|
|
} |
1490
|
17
|
|
|
|
|
35
|
my $SRC=openFileWithEncoding($name_or_handler, $encoding); |
1491
|
17
|
|
|
|
|
33
|
my @data = (); |
1492
|
17
|
|
|
|
|
36
|
my $oldRowDelimiter=$/; |
1493
|
17
|
100
|
|
|
|
48
|
my $newRowDelimiter=($OS==2)?"\r":(($OS==1)?"\r\n":"\n"); |
|
|
100
|
|
|
|
|
|
1494
|
17
|
|
|
|
|
25
|
my $n_endl = length($newRowDelimiter); |
1495
|
17
|
|
|
|
|
35
|
$/=$newRowDelimiter; |
1496
|
17
|
|
|
|
|
23
|
my $s; |
1497
|
17
|
|
|
|
|
44
|
for (my $i=0; $i<$skip_lines; $i++) { |
1498
|
|
|
|
|
|
|
#$s=<$SRC>; |
1499
|
1
|
|
|
|
|
3
|
$s = getOneLine($SRC, $newRowDelimiter, $qualifier); |
1500
|
|
|
|
|
|
|
} |
1501
|
|
|
|
|
|
|
#$s=<$SRC>; |
1502
|
17
|
|
|
|
|
40
|
$s = getOneLine($SRC, $newRowDelimiter, $qualifier); |
1503
|
17
|
100
|
66
|
|
|
42
|
if (defined($skip_pattern)) { while (defined($s) && $s =~ /$skip_pattern/) { $s = getOneLine($SRC, $newRowDelimiter, $qualifier); }} |
|
1
|
|
|
|
|
22
|
|
|
1
|
|
|
|
|
5
|
|
1504
|
|
|
|
|
|
|
#{ $s = <$SRC> }; } |
1505
|
17
|
50
|
|
|
|
63
|
if (substr($s, -$n_endl, $n_endl) eq $newRowDelimiter) { for (1..$n_endl) { chop $s }} |
|
17
|
|
|
|
|
40
|
|
|
19
|
|
|
|
|
40
|
|
1506
|
|
|
|
|
|
|
# $_=~ s/$newRowDelimiter$//; |
1507
|
17
|
50
|
|
|
|
32
|
unless ($s) { |
1508
|
|
|
|
|
|
|
#confess "Empty data file" unless $givenHeader; |
1509
|
0
|
0
|
|
|
|
0
|
return undef unless $givenHeader; |
1510
|
0
|
|
|
|
|
0
|
$/=$oldRowDelimiter; |
1511
|
0
|
|
|
|
|
0
|
return new Data::Table(\@data, \@header, 0); |
1512
|
|
|
|
|
|
|
} |
1513
|
17
|
|
|
|
|
24
|
my $one; |
1514
|
17
|
50
|
|
|
|
79
|
if ($s =~ /$delimiter$/) { # if the line ends by ',', the size of @one will be incorrect |
1515
|
|
|
|
|
|
|
# due to the tailing of split function in perl |
1516
|
0
|
|
|
|
|
0
|
$s .= ' '; # e.g., split $s="a," will only return a list of size 1. |
1517
|
0
|
|
|
|
|
0
|
$one = parseCSV($s, undef, {delimiter=>$delimiter, qualifier=>$qualifier}); |
1518
|
0
|
|
|
|
|
0
|
$one->[$#{$one}]=undef; |
|
0
|
|
|
|
|
0
|
|
1519
|
|
|
|
|
|
|
} else { |
1520
|
17
|
|
|
|
|
64
|
$one = parseCSV($s, undef, {delimiter=>$delimiter, qualifier=>$qualifier}); |
1521
|
|
|
|
|
|
|
} |
1522
|
|
|
|
|
|
|
#print join("|", @$one), scalar @$one, "\n"; |
1523
|
17
|
|
|
|
|
37
|
my $size = scalar @$one; |
1524
|
17
|
50
|
|
|
|
37
|
unless ($givenHeader) { |
1525
|
17
|
100
|
|
|
|
27
|
if ($includeHeader) { |
1526
|
16
|
|
|
|
|
48
|
@header = @$one; |
1527
|
|
|
|
|
|
|
} else { |
1528
|
1
|
|
|
|
|
4
|
@header = map {"col$_"} (1..$size); # name each column as col1, col2, .. etc |
|
3
|
|
|
|
|
9
|
|
1529
|
|
|
|
|
|
|
} |
1530
|
|
|
|
|
|
|
} |
1531
|
17
|
100
|
|
|
|
34
|
push @data, $one unless ($includeHeader); |
1532
|
|
|
|
|
|
|
|
1533
|
|
|
|
|
|
|
#while($s = <$SRC>) { |
1534
|
17
|
|
|
|
|
35
|
while($s = getOneLine($SRC, $newRowDelimiter, $qualifier)) { |
1535
|
171
|
50
|
66
|
|
|
329
|
next if (defined($skip_pattern) && $s =~ /$skip_pattern/); |
1536
|
171
|
100
|
|
|
|
343
|
if (substr($s, -$n_endl, $n_endl) eq $newRowDelimiter) { for (1..$n_endl) { chop $s }} |
|
170
|
|
|
|
|
239
|
|
|
249
|
|
|
|
|
351
|
|
1537
|
|
|
|
|
|
|
# $_=~ s/$newDelimiter$//; |
1538
|
171
|
|
|
|
|
359
|
my $one = parseCSV($s, $size, {delimiter=>$delimiter, qualifier=>$qualifier}); |
1539
|
171
|
50
|
|
|
|
359
|
confess "Inconsistent column number at data entry: ".($#data+1) unless ($size==scalar @$one); |
1540
|
171
|
|
|
|
|
316
|
push @data, $one; |
1541
|
|
|
|
|
|
|
} |
1542
|
17
|
|
|
|
|
182
|
close($SRC); |
1543
|
17
|
|
|
|
|
54
|
$/=$oldRowDelimiter; |
1544
|
17
|
|
|
|
|
94
|
return new Data::Table(\@data, \@header, 0); |
1545
|
|
|
|
|
|
|
} |
1546
|
|
|
|
|
|
|
|
1547
|
|
|
|
|
|
|
# Idea: use \ as the escape char to encode a CSV string, |
1548
|
|
|
|
|
|
|
# replace \ by \\ and comma inside a field by \c. |
1549
|
|
|
|
|
|
|
# A comma inside a field must have odd number of " in front of it, |
1550
|
|
|
|
|
|
|
# therefore it can be distinguished from comma used as the deliminator. |
1551
|
|
|
|
|
|
|
# After escape, and split by comma, we unescape each field string. |
1552
|
|
|
|
|
|
|
# |
1553
|
|
|
|
|
|
|
# This parser will never be crashed by any illegal CSV format, |
1554
|
|
|
|
|
|
|
# it always return an array! |
1555
|
|
|
|
|
|
|
sub parseCSV { |
1556
|
237
|
|
|
237
|
1
|
351
|
my ($s, $size, $arg_ref)=@_; |
1557
|
237
|
100
|
|
|
|
354
|
$size = 0 unless defined $size; |
1558
|
237
|
|
|
|
|
339
|
my ($delimiter, $qualifier) = ($Data::Table::DEFAULTS{CSV_DELIMITER}, $Data::Table::DEFAULTS{CSV_QUALIFIER}); |
1559
|
237
|
50
|
33
|
|
|
624
|
$delimiter = $arg_ref->{'delimiter'} if (defined($arg_ref) && defined($arg_ref->{'delimiter'})); |
1560
|
237
|
100
|
66
|
|
|
524
|
$qualifier = $arg_ref->{'qualifier'} if (defined($arg_ref) && defined($arg_ref->{'qualifier'})); |
1561
|
237
|
50
|
|
|
|
241
|
my $delimiter2 = $delimiter; $delimiter2 = substr($delimiter, 1, 1) if length($delimiter)>1; |
|
237
|
|
|
|
|
376
|
|
1562
|
237
|
50
|
|
|
|
245
|
my $qualifier2 = $qualifier; $qualifier2 = substr($qualifier, 1, 1) if length($qualifier)>1; |
|
237
|
|
|
|
|
360
|
|
1563
|
|
|
|
|
|
|
# $s =~ s/\n$//; # chop" # assume extra characters has been cleaned before |
1564
|
237
|
100
|
|
|
|
490
|
if (-1==index $s, $qualifier) { |
1565
|
227
|
100
|
|
|
|
308
|
if ($size == 0) { |
1566
|
57
|
|
|
|
|
66
|
my $s2 = $s; |
1567
|
57
|
|
|
|
|
283
|
$s2 =~ s/$delimiter//g; |
1568
|
57
|
|
|
|
|
126
|
$size = length($s)-length($s2)+1; |
1569
|
|
|
|
|
|
|
} |
1570
|
227
|
|
|
|
|
1130
|
return [split /$delimiter/, $s , $size]; |
1571
|
|
|
|
|
|
|
} |
1572
|
10
|
|
|
|
|
25
|
$s =~ s/\\/\\\\/g; # escape \ => \\ |
1573
|
10
|
|
|
|
|
17
|
my $n = length($s); |
1574
|
10
|
|
|
|
|
16
|
my ($q, $i)=(0, 0); |
1575
|
10
|
|
|
|
|
20
|
while ($i < $n) { |
1576
|
672
|
|
|
|
|
850
|
my $ch=substr($s, $i, 1); |
1577
|
672
|
|
|
|
|
610
|
$i++; |
1578
|
672
|
100
|
100
|
|
|
1463
|
if ($ch eq $delimiter2 && ($q%2)) { |
|
|
100
|
|
|
|
|
|
1579
|
9
|
|
|
|
|
30
|
substr($s, $i-1, 1)='\\c'; # escape , => \c if it's not a deliminator |
1580
|
9
|
|
|
|
|
15
|
$i++; |
1581
|
9
|
|
|
|
|
11
|
$n++; |
1582
|
|
|
|
|
|
|
} elsif ($ch eq $qualifier2) { |
1583
|
78
|
|
|
|
|
97
|
$q++; |
1584
|
|
|
|
|
|
|
} |
1585
|
|
|
|
|
|
|
} |
1586
|
|
|
|
|
|
|
# add look-ahead avoid the speical case where $delimiter is a tab |
1587
|
10
|
|
|
|
|
212
|
$s =~ s/(^$qualifier)|($qualifier((?!$delimiter)\s)*$)//g; # get rid of boundary ", then restore "" => " |
1588
|
10
|
|
|
|
|
149
|
$s =~ s/$qualifier((?!$delimiter)\s)*$delimiter/$delimiter2/g; |
1589
|
10
|
|
|
|
|
107
|
$s =~ s/$delimiter((?!$delimiter)\s)*$qualifier/$delimiter2/g; |
1590
|
10
|
|
|
|
|
60
|
$s =~ s/$qualifier$qualifier/$qualifier2/g; |
1591
|
10
|
100
|
|
|
|
28
|
if ($size == 0) { |
1592
|
9
|
|
|
|
|
13
|
my $s2 = $s; |
1593
|
9
|
|
|
|
|
66
|
$s2 =~ s/$delimiter//g; |
1594
|
9
|
|
|
|
|
33
|
$size = length($s)-length($s2)+1; |
1595
|
|
|
|
|
|
|
} |
1596
|
10
|
|
|
|
|
79
|
my @parts=split(/$delimiter/, $s, $size); |
1597
|
10
|
50
|
|
|
|
23
|
@parts = map {$_ =~ s/(\\c|\\\\)/$1 eq '\c'?$delimiter2:'\\'/eg; $_ } @parts; |
|
57
|
|
|
|
|
105
|
|
|
9
|
|
|
|
|
36
|
|
|
57
|
|
|
|
|
94
|
|
1598
|
|
|
|
|
|
|
# my @parts2=(); |
1599
|
|
|
|
|
|
|
# foreach $s2 (@parts) { |
1600
|
|
|
|
|
|
|
# $s2 =~ s/\\c/,/g; # restore \c => , |
1601
|
|
|
|
|
|
|
# $s2 =~ s/\\\\/\\/g; # restore \\ => \ |
1602
|
|
|
|
|
|
|
# push @parts2, $s2; |
1603
|
|
|
|
|
|
|
# } |
1604
|
10
|
|
|
|
|
33
|
return \@parts; |
1605
|
|
|
|
|
|
|
} |
1606
|
|
|
|
|
|
|
|
1607
|
|
|
|
|
|
|
sub transformElement { |
1608
|
29
|
|
|
29
|
0
|
33
|
my $one = shift; |
1609
|
29
|
|
|
|
|
48
|
for (my $i=0; $i < scalar @$one; $i++) { |
1610
|
164
|
50
|
|
|
|
239
|
next unless defined($one->[$i]); |
1611
|
164
|
50
|
|
|
|
197
|
if ($one->[$i] eq "\\N") { |
1612
|
0
|
|
|
|
|
0
|
$one->[$i]=undef; |
1613
|
|
|
|
|
|
|
} else { |
1614
|
164
|
|
|
|
|
256
|
$one->[$i] =~ s/\\([0ntrb'"\\])/$Data::Table::TSV_ESC{$1}/g; |
1615
|
|
|
|
|
|
|
} |
1616
|
|
|
|
|
|
|
} |
1617
|
29
|
|
|
|
|
37
|
return $one; |
1618
|
|
|
|
|
|
|
} |
1619
|
|
|
|
|
|
|
|
1620
|
|
|
|
|
|
|
sub fromTSVi { |
1621
|
1
|
|
|
1
|
1
|
3
|
my $self = shift; |
1622
|
1
|
|
|
|
|
3
|
return fromTSV(@_); |
1623
|
|
|
|
|
|
|
} |
1624
|
|
|
|
|
|
|
|
1625
|
|
|
|
|
|
|
sub fromTSV { |
1626
|
5
|
|
|
5
|
1
|
13
|
my ($name_or_handler, $includeHeader, $header, $arg_ref) = @_; |
1627
|
5
|
|
|
|
|
18
|
my ($OS, $skip_lines, $skip_pattern, $transform_element, $encoding) = ($Data::Table::DEFAULTS{OS}, 0, undef, 1, $Data::Table::DEFAULTS{ENCODING}); |
1628
|
5
|
100
|
66
|
|
|
18
|
$OS = $arg_ref->{'OS'} if (defined($arg_ref) && defined($arg_ref->{'OS'})); |
1629
|
|
|
|
|
|
|
# OS: 0 for UNIX (\n as linebreak), 1 for Windows (\r\n as linebreak) |
1630
|
|
|
|
|
|
|
### 2 for MAC (\r as linebreak) |
1631
|
5
|
50
|
66
|
|
|
27
|
$skip_lines = $arg_ref->{'skip_lines'} if (defined($arg_ref) && defined($arg_ref->{'skip_lines'}) && $arg_ref->{'skip_lines'}>0); |
|
|
|
33
|
|
|
|
|
1632
|
5
|
50
|
|
|
|
13
|
$skip_pattern = $arg_ref->{'skip_pattern'} if defined($arg_ref->{'skip_pattern'}); |
1633
|
5
|
100
|
|
|
|
9
|
$transform_element = $arg_ref->{'transform_element'} if (defined($arg_ref->{'transform_element'})); |
1634
|
5
|
50
|
|
|
|
13
|
$encoding = $arg_ref->{'encoding'} if (defined($arg_ref->{'encoding'})); |
1635
|
|
|
|
|
|
|
#my %ESC = ( '0'=>"\0", 'n'=>"\n", 't'=>"\t", 'r'=>"\r", 'b'=>"\b", |
1636
|
|
|
|
|
|
|
# "'"=>"'", '"'=>"\"", '\\'=>"\\" ); |
1637
|
|
|
|
|
|
|
## what about \f? MySQL treats \f as f. |
1638
|
|
|
|
|
|
|
|
1639
|
5
|
100
|
|
|
|
11
|
$includeHeader = 1 unless defined($includeHeader); |
1640
|
5
|
50
|
|
|
|
9
|
$OS=0 unless defined($OS); |
1641
|
|
|
|
|
|
|
|
1642
|
5
|
|
|
|
|
7
|
my @header; |
1643
|
5
|
|
|
|
|
5
|
my $givenHeader = 0; |
1644
|
5
|
50
|
33
|
|
|
11
|
if (defined($header) && ref($header) eq 'ARRAY') { |
1645
|
0
|
|
|
|
|
0
|
$givenHeader = 1; |
1646
|
0
|
|
|
|
|
0
|
@header= @$header; |
1647
|
|
|
|
|
|
|
} |
1648
|
5
|
|
|
|
|
13
|
my $SRC=openFileWithEncoding($name_or_handler, $encoding); |
1649
|
5
|
|
|
|
|
11
|
my @data = (); |
1650
|
5
|
|
|
|
|
11
|
my $oldRowDelimiter=$/; |
1651
|
5
|
50
|
|
|
|
15
|
my $newRowDelimiter=($OS==2)?"\r":(($OS==1)?"\r\n":"\n"); |
|
|
50
|
|
|
|
|
|
1652
|
5
|
|
|
|
|
13
|
my $n_endl = length($newRowDelimiter); |
1653
|
5
|
|
|
|
|
12
|
$/=$newRowDelimiter; |
1654
|
5
|
|
|
|
|
8
|
my $s; |
1655
|
5
|
|
|
|
|
37
|
for (my $i=0; $i<$skip_lines; $i++) { |
1656
|
0
|
|
|
|
|
0
|
$s=<$SRC>; |
1657
|
|
|
|
|
|
|
} |
1658
|
5
|
|
|
|
|
112
|
$s=<$SRC>; |
1659
|
5
|
50
|
0
|
|
|
47
|
if (defined($skip_pattern)) { while (defined($s) && $s =~ /$skip_pattern/) { $s = <$SRC> }; } |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
1660
|
5
|
50
|
|
|
|
19
|
if (substr($s, -$n_endl, $n_endl) eq $newRowDelimiter) { for (1..$n_endl) { chop $s }} |
|
5
|
|
|
|
|
17
|
|
|
5
|
|
|
|
|
16
|
|
1661
|
|
|
|
|
|
|
# $_=~ s/$newRowDelimiter$//; |
1662
|
5
|
50
|
|
|
|
10
|
unless ($s) { |
1663
|
0
|
0
|
|
|
|
0
|
confess "Empty data file" unless $givenHeader; |
1664
|
0
|
|
|
|
|
0
|
$/=$oldRowDelimiter; |
1665
|
0
|
|
|
|
|
0
|
return new Data::Table(\@data, \@header, 0); |
1666
|
|
|
|
|
|
|
} |
1667
|
|
|
|
|
|
|
#chop; |
1668
|
5
|
|
|
|
|
7
|
my $one; |
1669
|
5
|
50
|
|
|
|
16
|
if ($s =~ /\t$/) { # if the line ends by ',', the size of @$one will be incorrect |
1670
|
|
|
|
|
|
|
# due to the tailing of split function in perl |
1671
|
0
|
|
|
|
|
0
|
$s .= ' '; # e.g., split $s="a," will only return a list of size 1. |
1672
|
0
|
|
|
|
|
0
|
@$one = split(/\t/, $s); |
1673
|
0
|
|
|
|
|
0
|
$one->[$#{$one}]=''; |
|
0
|
|
|
|
|
0
|
|
1674
|
|
|
|
|
|
|
} else { |
1675
|
5
|
|
|
|
|
26
|
@$one = split(/\t/, $s); |
1676
|
|
|
|
|
|
|
} |
1677
|
|
|
|
|
|
|
# print join("|", @$one), scalar @$one, "\n"; |
1678
|
5
|
|
|
|
|
11
|
my $size = scalar @$one; |
1679
|
5
|
50
|
|
|
|
12
|
unless ($givenHeader) { |
1680
|
5
|
50
|
|
|
|
8
|
if ($includeHeader) { |
1681
|
5
|
100
|
|
|
|
11
|
if ($transform_element) { |
1682
|
4
|
|
|
|
|
6
|
@header = map { $_ =~ s/\\([0ntrb'"\\])/$Data::Table::TSV_ESC{$1}/g; $_ } @$one; |
|
19
|
|
|
|
|
49
|
|
|
19
|
|
|
|
|
35
|
|
1683
|
|
|
|
|
|
|
} else { |
1684
|
1
|
|
|
|
|
3
|
@header = @$one; |
1685
|
|
|
|
|
|
|
} |
1686
|
|
|
|
|
|
|
} else { |
1687
|
0
|
|
|
|
|
0
|
@header = map {"col$_"} (1..$size); # name each column as col1, col2, .. etc |
|
0
|
|
|
|
|
0
|
|
1688
|
|
|
|
|
|
|
} |
1689
|
|
|
|
|
|
|
} |
1690
|
5
|
50
|
|
|
|
13
|
unless ($includeHeader) { |
1691
|
0
|
0
|
|
|
|
0
|
transformElement($one) if $transform_element; |
1692
|
0
|
|
|
|
|
0
|
push @data, $one; |
1693
|
|
|
|
|
|
|
} |
1694
|
5
|
|
|
|
|
14
|
while($s = <$SRC>) { |
1695
|
|
|
|
|
|
|
#chop; |
1696
|
|
|
|
|
|
|
# $_=~ s/$newRowDelimiter$//; |
1697
|
31
|
50
|
33
|
|
|
58
|
next if (defined($skip_pattern) && $s =~ /$skip_pattern/); |
1698
|
31
|
50
|
|
|
|
62
|
if (substr($s, -$n_endl, $n_endl) eq $newRowDelimiter) { for (1..$n_endl) { chop $s }} |
|
31
|
|
|
|
|
46
|
|
|
31
|
|
|
|
|
47
|
|
1699
|
31
|
|
|
|
|
164
|
my @one = split(/\t/, $s, $size); |
1700
|
31
|
100
|
|
|
|
81
|
transformElement(\@one) if $transform_element; |
1701
|
|
|
|
|
|
|
#for (my $i=0; $i < $size; $i++) { |
1702
|
|
|
|
|
|
|
# next unless defined($one[$i]); |
1703
|
|
|
|
|
|
|
# if ($one[$i] eq "\\N") { |
1704
|
|
|
|
|
|
|
# $one[$i]=undef; |
1705
|
|
|
|
|
|
|
# } else { |
1706
|
|
|
|
|
|
|
# $one[$i] =~ s/\\([0ntrb'"\\])/$Data::Table::TSV_ESC{$1}/g; |
1707
|
|
|
|
|
|
|
# } |
1708
|
|
|
|
|
|
|
#} |
1709
|
31
|
50
|
|
|
|
47
|
confess "Inconsistent column number at data entry: ".($#data+1) unless ($size==scalar @one); |
1710
|
31
|
|
|
|
|
126
|
push @data, \@one; |
1711
|
|
|
|
|
|
|
} |
1712
|
5
|
|
|
|
|
53
|
close($SRC); |
1713
|
5
|
|
|
|
|
16
|
$/=$oldRowDelimiter; |
1714
|
5
|
|
|
|
|
27
|
return new Data::Table(\@data, \@header, 0); |
1715
|
|
|
|
|
|
|
} |
1716
|
|
|
|
|
|
|
|
1717
|
|
|
|
|
|
|
sub fromSQLi { |
1718
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1719
|
0
|
|
|
|
|
0
|
return fromSQL(@_); |
1720
|
|
|
|
|
|
|
} |
1721
|
|
|
|
|
|
|
|
1722
|
|
|
|
|
|
|
sub fromSQL { |
1723
|
0
|
|
|
0
|
1
|
0
|
my ($dbh, $sql, $vars) = @_; |
1724
|
0
|
|
|
|
|
0
|
my ($sth, $header, $t); |
1725
|
0
|
0
|
|
|
|
0
|
if (ref $sql eq 'DBI::st') { |
1726
|
0
|
|
|
|
|
0
|
$sth = $sql; |
1727
|
|
|
|
|
|
|
} else { |
1728
|
0
|
0
|
|
|
|
0
|
$sth = $dbh->prepare($sql) or confess "Preparing: , ".$dbh->errstr; |
1729
|
|
|
|
|
|
|
} |
1730
|
0
|
0
|
|
|
|
0
|
my @vars=() unless defined $vars; |
1731
|
|
|
|
|
|
|
# This enables us to execute asynchronous queries and still retrieve the results into a Data::Table object once it finishes. |
1732
|
0
|
0
|
|
|
|
0
|
unless ($sth->{Executed}) { |
1733
|
0
|
0
|
|
|
|
0
|
$sth->execute(@$vars) or confess "Executing: ".$dbh->errstr; |
1734
|
|
|
|
|
|
|
} |
1735
|
|
|
|
|
|
|
# $sth->execute(@$vars) or confess "Executing: ".$dbh->errstr; |
1736
|
|
|
|
|
|
|
# $Data::Table::ID = undef; |
1737
|
|
|
|
|
|
|
# $Data::Table::ID = $sth->{'mysql_insertid'}; |
1738
|
0
|
0
|
|
|
|
0
|
if ($sth->{NUM_OF_FIELDS}) { |
1739
|
0
|
|
|
|
|
0
|
$header=$sth->{'NAME'}; |
1740
|
0
|
|
|
|
|
0
|
$t = new Data::Table($sth->fetchall_arrayref(), $header, 0); |
1741
|
|
|
|
|
|
|
} else { |
1742
|
0
|
|
|
|
|
0
|
$t = undef; |
1743
|
|
|
|
|
|
|
} |
1744
|
0
|
|
|
|
|
0
|
$sth->finish; |
1745
|
0
|
|
|
|
|
0
|
return $t; |
1746
|
|
|
|
|
|
|
} |
1747
|
|
|
|
|
|
|
|
1748
|
|
|
|
|
|
|
sub join { |
1749
|
5
|
|
|
5
|
1
|
17
|
my ($self, $tbl, $type, $cols1, $cols2, $arg_ref) = @_; |
1750
|
5
|
|
|
|
|
8
|
my $n1 = scalar @$cols1; |
1751
|
5
|
|
|
|
|
16
|
my %arg= ( renameCol => 0, matchNULL => 0, NULLasEmpty => 0); |
1752
|
5
|
100
|
|
|
|
14
|
$arg{renameCol} = $arg_ref->{renameCol} if exists $arg_ref->{renameCol}; |
1753
|
5
|
50
|
|
|
|
12
|
$arg{matchNULL} = $arg_ref->{matchNULL} if exists $arg_ref->{matchNULL}; |
1754
|
5
|
50
|
|
|
|
19
|
$arg{NULLasEmpty} = $arg_ref->{NULLasEmpty} if exists $arg_ref->{NULLasEmpty}; |
1755
|
|
|
|
|
|
|
#%arg = %$arg_ref if defined $arg_ref; |
1756
|
|
|
|
|
|
|
# default cols2 to cols1 if not specified |
1757
|
5
|
50
|
33
|
|
|
13
|
if (!defined($cols2) && $n1>0) { |
1758
|
0
|
|
|
|
|
0
|
$cols2 = []; |
1759
|
0
|
|
|
|
|
0
|
foreach my $c (@$cols1) { |
1760
|
0
|
|
|
|
|
0
|
push @$cols2, $c; |
1761
|
|
|
|
|
|
|
} |
1762
|
|
|
|
|
|
|
} |
1763
|
5
|
|
|
|
|
9
|
my $n2 = scalar @$cols2; |
1764
|
5
|
50
|
|
|
|
10
|
confess "The number of join columns must be the same: $n1 != $n2" unless $n1==$n2; |
1765
|
5
|
50
|
|
|
|
10
|
confess "At least one join column must be specified" unless $n1; |
1766
|
5
|
|
|
|
|
7
|
my ($i, $j, $k); |
1767
|
5
|
|
|
|
|
8
|
my @cols3 = (); |
1768
|
5
|
|
|
|
|
11
|
for ($i = 0; $i < $n1; $i++) { |
1769
|
9
|
|
|
|
|
15
|
$cols1->[$i]=$self->checkOldCol($cols1->[$i]); |
1770
|
9
|
50
|
|
|
|
16
|
confess "Unknown column ". $cols1->[$i] unless defined($cols1->[$i]); |
1771
|
9
|
|
|
|
|
27
|
$cols2->[$i]=$tbl->checkOldCol($cols2->[$i]); |
1772
|
9
|
50
|
|
|
|
17
|
confess "Unknown column ". $cols2->[$i] unless defined($cols2->[$i]); |
1773
|
9
|
|
|
|
|
20
|
$cols3[$cols2->[$i]]=1; |
1774
|
|
|
|
|
|
|
} |
1775
|
5
|
|
|
|
|
7
|
my @cols4 = (); # the list of remaining columns |
1776
|
5
|
|
|
|
|
8
|
my @header2 = (); |
1777
|
5
|
|
|
|
|
132
|
for ($i = 0; $i < $tbl->nofCol; $i++) { |
1778
|
30
|
100
|
|
|
|
47
|
unless (defined($cols3[$i])) { |
1779
|
21
|
|
|
|
|
22
|
push @cols4, $i; |
1780
|
21
|
|
|
|
|
38
|
push @header2, $tbl->{header}->[$i]; |
1781
|
|
|
|
|
|
|
} |
1782
|
|
|
|
|
|
|
} |
1783
|
|
|
|
|
|
|
|
1784
|
5
|
50
|
|
|
|
9
|
$self->rotate() if $self->{type}; |
1785
|
5
|
50
|
|
|
|
10
|
$tbl->rotate() if $tbl->{type}; |
1786
|
5
|
|
|
|
|
6
|
my $data1 = $self->{data}; |
1787
|
5
|
|
|
|
|
7
|
my $data2 = $tbl->{data}; |
1788
|
5
|
|
|
|
|
6
|
my %H=(); |
1789
|
5
|
|
|
|
|
8
|
my $key; |
1790
|
|
|
|
|
|
|
my @subRow; |
1791
|
5
|
|
|
|
|
10
|
for ($i = 0; $i < $self->nofRow; $i++) { |
1792
|
37
|
|
|
|
|
38
|
@subRow = @{$data1->[$i]}[@$cols1]; |
|
37
|
|
|
|
|
66
|
|
1793
|
37
|
|
|
|
|
48
|
my @S = map {tsvEscape($_)} @subRow; |
|
65
|
|
|
|
|
75
|
|
1794
|
37
|
0
|
|
|
|
62
|
map { $_ = '' if $_ eq '\\N' } @S if $arg{NULLasEmpty}; |
|
0
|
50
|
|
|
|
0
|
|
1795
|
37
|
|
|
|
|
56
|
$key = join("\t", @S); |
1796
|
37
|
50
|
|
|
|
63
|
unless (defined($H{$key})) { |
1797
|
37
|
|
|
|
|
103
|
$H{$key} = [[$i], []]; |
1798
|
|
|
|
|
|
|
} else { |
1799
|
0
|
|
|
|
|
0
|
push @{$H{$key}->[0]}, $i; |
|
0
|
|
|
|
|
0
|
|
1800
|
|
|
|
|
|
|
} |
1801
|
|
|
|
|
|
|
} |
1802
|
5
|
|
|
|
|
9
|
for ($i = 0; $i < $tbl->nofRow; $i++) { |
1803
|
33
|
|
|
|
|
34
|
@subRow = @{$data2->[$i]}[@$cols2]; |
|
33
|
|
|
|
|
50
|
|
1804
|
|
|
|
|
|
|
# we intentionally make the second table undef keys to be '\\N\\N', |
1805
|
|
|
|
|
|
|
# so that they are different from the first table undef keys |
1806
|
|
|
|
|
|
|
# avoid NULL == NULL in the join |
1807
|
33
|
|
|
|
|
68
|
my @S = map {tsvEscape($_)} @subRow; |
|
57
|
|
|
|
|
73
|
|
1808
|
33
|
0
|
|
|
|
37
|
map { $_ = ($arg{NULLasEmpty})? '':($arg{matchNULL} ? $_ : '\\N\\N') if $_ eq '\\N' } @S; |
|
57
|
0
|
|
|
|
101
|
|
|
|
50
|
|
|
|
|
|
1809
|
|
|
|
|
|
|
#if ($j>= @S) { |
1810
|
33
|
|
|
|
|
45
|
$key = join("\t", @S); |
1811
|
|
|
|
|
|
|
#} else { |
1812
|
|
|
|
|
|
|
# $key = $arg{matchNULL} ? '\\N' : '\\N\\N'; |
1813
|
|
|
|
|
|
|
#} |
1814
|
33
|
100
|
|
|
|
53
|
unless (defined($H{$key})) { |
1815
|
8
|
|
|
|
|
21
|
$H{$key} = [[], [$i]]; |
1816
|
|
|
|
|
|
|
} else { |
1817
|
25
|
|
|
|
|
26
|
push @{$H{$key}->[1]}, $i; |
|
25
|
|
|
|
|
67
|
|
1818
|
|
|
|
|
|
|
} |
1819
|
|
|
|
|
|
|
} |
1820
|
|
|
|
|
|
|
# $type |
1821
|
|
|
|
|
|
|
# 0: inner join |
1822
|
|
|
|
|
|
|
# 1: left outer join |
1823
|
|
|
|
|
|
|
# 2: right outer join |
1824
|
|
|
|
|
|
|
# 3: full outer join |
1825
|
5
|
|
|
|
|
7
|
my @ones = (); |
1826
|
5
|
|
|
|
|
5
|
my @null1 = (); |
1827
|
5
|
|
|
|
|
8
|
my @null2 = (); |
1828
|
5
|
|
|
|
|
6
|
my @null3 = (); |
1829
|
5
|
|
|
|
|
9
|
$null1[$self->nofCol-1]=undef; |
1830
|
5
|
|
|
|
|
8
|
$null3[$self->nofCol-1]=undef; |
1831
|
5
|
50
|
|
|
|
11
|
if ($#cols4>=0) { $null2[$#cols4]=undef; } |
|
5
|
|
|
|
|
7
|
|
1832
|
5
|
|
|
|
|
22
|
foreach $key (keys %H) { |
1833
|
45
|
|
|
|
|
56
|
my ($rows1, $rows2) = @{$H{$key}}; |
|
45
|
|
|
|
|
76
|
|
1834
|
45
|
|
|
|
|
50
|
my $nr1 = scalar @$rows1; |
1835
|
45
|
|
|
|
|
43
|
my $nr2 = scalar @$rows2; |
1836
|
45
|
100
|
100
|
|
|
85
|
next if ($nr1 == 0 && ($type == 0 || $type == 1)); |
|
|
|
100
|
|
|
|
|
1837
|
41
|
100
|
100
|
|
|
82
|
next if ($nr2 == 0 && ($type == 0 || $type == 2)); |
|
|
|
100
|
|
|
|
|
1838
|
35
|
50
|
66
|
|
|
63
|
if ($nr2 == 0 && ($type == 1 || $type == 3)) { |
|
|
|
66
|
|
|
|
|
1839
|
6
|
|
|
|
|
8
|
for ($i = 0; $i < $nr1; $i++) { |
1840
|
6
|
|
|
|
|
13
|
push @ones, [$self->row($rows1->[$i]), @null2]; |
1841
|
|
|
|
|
|
|
} |
1842
|
6
|
|
|
|
|
12
|
next; |
1843
|
|
|
|
|
|
|
} |
1844
|
29
|
50
|
66
|
|
|
51
|
if ($nr1 == 0 && ($type == 2 || $type == 3)) { |
|
|
|
66
|
|
|
|
|
1845
|
4
|
|
|
|
|
7
|
for ($j = 0; $j < $nr2; $j++) { |
1846
|
4
|
|
|
|
|
8
|
my @row2 = $tbl->row($rows2->[$j]); |
1847
|
4
|
|
|
|
|
8
|
for ($k = 0; $k< scalar @$cols1; $k++) { |
1848
|
8
|
|
|
|
|
16
|
$null3[$cols1->[$k]] = $row2[$cols2->[$k]]; |
1849
|
|
|
|
|
|
|
} |
1850
|
4
|
50
|
|
|
|
7
|
if ($#cols4>=0) { |
1851
|
4
|
|
|
|
|
15
|
push @ones, [@null3, @row2[@cols4]]; |
1852
|
|
|
|
|
|
|
} else { |
1853
|
0
|
|
|
|
|
0
|
push @ones, [@null3]; |
1854
|
|
|
|
|
|
|
} |
1855
|
|
|
|
|
|
|
} |
1856
|
4
|
|
|
|
|
6
|
next; |
1857
|
|
|
|
|
|
|
} |
1858
|
25
|
|
|
|
|
43
|
for ($i = 0; $i < $nr1; $i++) { |
1859
|
25
|
|
|
|
|
34
|
for ($j = 0; $j < $nr2; $j++) { |
1860
|
25
|
|
|
|
|
38
|
my @row2 = $tbl->row($rows2->[$j]); |
1861
|
25
|
|
|
|
|
39
|
push @ones, [$self->row($rows1->[$i]), @row2[@cols4]]; |
1862
|
|
|
|
|
|
|
} |
1863
|
|
|
|
|
|
|
} |
1864
|
|
|
|
|
|
|
} |
1865
|
5
|
100
|
|
|
|
14
|
if ($arg{renameCol}) { |
1866
|
1
|
|
|
|
|
3
|
my %h = (); |
1867
|
1
|
|
|
|
|
3
|
map {$h{$_} = 1} @{$self->{header}}; |
|
6
|
|
|
|
|
11
|
|
|
1
|
|
|
|
|
3
|
|
1868
|
1
|
|
|
|
|
4
|
for (my $i=0; $i<@header2; $i++) { |
1869
|
5
|
|
|
|
|
7
|
my $s = $header2[$i]; |
1870
|
5
|
|
|
|
|
6
|
my $cnt = 2; |
1871
|
5
|
|
|
|
|
15
|
while (exists $h{$s}) { |
1872
|
5
|
|
|
|
|
17
|
$s = $header2[$i] ."_". $cnt++; |
1873
|
|
|
|
|
|
|
} |
1874
|
5
|
|
|
|
|
8
|
$header2[$i] = $s; |
1875
|
5
|
|
|
|
|
15
|
$h{$s} = 1; |
1876
|
|
|
|
|
|
|
} |
1877
|
|
|
|
|
|
|
} |
1878
|
5
|
|
|
|
|
9
|
my $header = [@{$self->{header}}, @header2]; |
|
5
|
|
|
|
|
20
|
|
1879
|
5
|
|
|
|
|
15
|
return new Data::Table(\@ones, $header, 0); |
1880
|
|
|
|
|
|
|
} |
1881
|
|
|
|
|
|
|
|
1882
|
|
|
|
|
|
|
sub melt { |
1883
|
1
|
|
|
1
|
1
|
3
|
my ($self, $keyCols, $variableCols, $arg_ref) = @_; |
1884
|
1
|
50
|
33
|
|
|
16
|
confess "key columns have to be specified!" unless defined($keyCols) && ref($keyCols) eq "ARRAY"; |
1885
|
1
|
|
|
|
|
3
|
my $variableColName = 'variable'; |
1886
|
1
|
|
|
|
|
2
|
my $valueColName = 'value'; |
1887
|
1
|
|
|
|
|
1
|
my $skip_NULL = 1; |
1888
|
1
|
|
|
|
|
2
|
my $skip_empty = 0; |
1889
|
1
|
50
|
33
|
|
|
5
|
$variableColName = $arg_ref->{'variableColName'} if (defined($arg_ref) && defined($arg_ref->{'variableColName'})); |
1890
|
1
|
50
|
33
|
|
|
3
|
$valueColName = $arg_ref->{'valueColName'} if (defined($arg_ref) && defined($arg_ref->{'valueColName'})); |
1891
|
1
|
50
|
33
|
|
|
4
|
$skip_NULL = $arg_ref->{'skip_NULL'} if (defined($arg_ref) && defined($arg_ref->{'skip_NULL'})); |
1892
|
1
|
50
|
33
|
|
|
4
|
$skip_empty= $arg_ref->{'skip_empty'} if (defined($arg_ref) && defined($arg_ref->{'skip_empty'})); |
1893
|
1
|
|
|
|
|
2
|
my @X = (); |
1894
|
1
|
|
|
|
|
1
|
my %X = (); |
1895
|
1
|
|
|
|
|
3
|
foreach my $x (@$keyCols) { |
1896
|
2
|
|
|
|
|
5
|
my $x_idx = $self->checkOldCol($x); |
1897
|
2
|
50
|
|
|
|
5
|
confess "Unknown column ". $x unless defined($x_idx); |
1898
|
2
|
|
|
|
|
4
|
push @X, $x_idx; |
1899
|
2
|
|
|
|
|
4
|
$X{$x_idx} = 1; |
1900
|
|
|
|
|
|
|
} |
1901
|
1
|
|
|
|
|
2
|
my @Y = (); |
1902
|
1
|
|
|
|
|
2
|
my %Y = (); |
1903
|
1
|
50
|
|
|
|
3
|
unless (defined($variableCols)) { |
1904
|
1
|
|
|
|
|
2
|
$variableCols = []; |
1905
|
1
|
|
|
|
|
4
|
foreach my $x (0 .. $self->nofCol-1) { |
1906
|
4
|
100
|
|
|
|
10
|
next if $X{$x}; |
1907
|
2
|
|
|
|
|
3
|
push @$variableCols, $x; |
1908
|
|
|
|
|
|
|
} |
1909
|
|
|
|
|
|
|
} |
1910
|
1
|
50
|
|
|
|
3
|
unless (scalar @$variableCols) { |
1911
|
0
|
|
|
|
|
0
|
confess "Variable columns have to be specified!"; |
1912
|
|
|
|
|
|
|
} |
1913
|
1
|
|
|
|
|
3
|
foreach my $y (@$variableCols) { |
1914
|
2
|
|
|
|
|
4
|
my $y_idx = $self->checkOldCol($y); |
1915
|
2
|
50
|
|
|
|
14
|
confess "Unknown column ". $y unless defined($y_idx); |
1916
|
2
|
|
|
|
|
4
|
push @Y, $y_idx; |
1917
|
2
|
|
|
|
|
6
|
$Y{$y_idx} = 1; |
1918
|
|
|
|
|
|
|
} |
1919
|
|
|
|
|
|
|
|
1920
|
1
|
|
|
|
|
1
|
my @newHeader = (); |
1921
|
1
|
|
|
|
|
3
|
my @header = $self->header; |
1922
|
1
|
|
|
|
|
4
|
for (my $i=0; $i<= $#X; $i++) { |
1923
|
2
|
|
|
|
|
6
|
push @newHeader, $header[$X[$i]]; |
1924
|
|
|
|
|
|
|
} |
1925
|
1
|
|
|
|
|
1
|
push @newHeader, $variableColName; |
1926
|
1
|
|
|
|
|
2
|
push @newHeader, $valueColName; |
1927
|
1
|
|
|
|
|
2
|
my @newRows = (); |
1928
|
1
|
|
|
|
|
3
|
for (my $i=0; $i<$self->nofRow; $i++) { |
1929
|
4
|
|
|
|
|
7
|
my $row = $self->rowRef($i); |
1930
|
4
|
|
|
|
|
8
|
my @key = @$row[@X]; |
1931
|
4
|
|
|
|
|
5
|
foreach my $y (@Y) { |
1932
|
8
|
50
|
33
|
|
|
15
|
next if (!defined($row->[$y]) && $skip_NULL); |
1933
|
8
|
50
|
33
|
|
|
15
|
next if ($row->[$y] eq '' && $skip_empty); |
1934
|
8
|
|
|
|
|
13
|
my @one = @key; |
1935
|
8
|
|
|
|
|
21
|
push @one, $header[$y], $row->[$y]; |
1936
|
8
|
|
|
|
|
15
|
push @newRows, \@one; |
1937
|
|
|
|
|
|
|
} |
1938
|
|
|
|
|
|
|
} |
1939
|
1
|
|
|
|
|
4
|
return new Data::Table(\@newRows, \@newHeader, 0); |
1940
|
|
|
|
|
|
|
} |
1941
|
|
|
|
|
|
|
|
1942
|
|
|
|
|
|
|
sub cast { |
1943
|
3
|
|
|
3
|
1
|
15
|
my ($self, $colsToGroupBy, $colToSplit, $colToSplitIsStringOrNumeric, $colToCalculate, $funToApply) = @_; |
1944
|
|
|
|
|
|
|
#$colToSplit = 'variable' unless defined $colToSplit; |
1945
|
|
|
|
|
|
|
#$colToCalculate = 'value' unless defined $colToCalculate; |
1946
|
3
|
100
|
|
|
|
9
|
$colsToGroupBy = [] unless defined $colsToGroupBy; |
1947
|
3
|
|
|
|
|
5
|
my $tmpColName = '_calcColumn'; |
1948
|
3
|
|
|
|
|
4
|
my $cnt = 2; |
1949
|
3
|
|
|
|
|
4
|
my $s = $tmpColName; |
1950
|
3
|
|
|
|
|
8
|
while ($self->hasCol($s)) { |
1951
|
0
|
|
|
|
|
0
|
$s = $tmpColName."_".$cnt++; |
1952
|
|
|
|
|
|
|
} |
1953
|
3
|
|
|
|
|
6
|
$tmpColName = $s; |
1954
|
3
|
|
|
|
|
5
|
my %grpBy = (); |
1955
|
3
|
|
|
|
|
5
|
map {$grpBy{$_} = 1} @$colsToGroupBy; |
|
2
|
|
|
|
|
6
|
|
1956
|
3
|
|
|
|
|
7
|
my @grpBy = @$colsToGroupBy; |
1957
|
3
|
50
|
66
|
|
|
16
|
confess "colToSplit cannot be contained in the list of colsToGroupBy!" if defined $colToSplit and $grpBy{$colToSplit}; |
1958
|
3
|
100
|
|
|
|
8
|
push @grpBy, $colToSplit if defined $colToSplit; |
1959
|
3
|
|
|
|
|
11
|
my $t = $self->group(\@grpBy, [$colToCalculate], [$funToApply], [$tmpColName], 0); |
1960
|
3
|
|
|
|
|
10
|
$t = $t->pivot($colToSplit, $colToSplitIsStringOrNumeric, $tmpColName, $colsToGroupBy); |
1961
|
3
|
|
|
|
|
16
|
return $t; |
1962
|
|
|
|
|
|
|
} |
1963
|
|
|
|
|
|
|
|
1964
|
|
|
|
|
|
|
sub each_group { |
1965
|
1
|
|
|
1
|
1
|
18
|
my ($self, $colsToGroupBy, $funToApply) = @_; |
1966
|
1
|
50
|
|
|
|
4
|
$colsToGroupBy = [] unless defined $colsToGroupBy; |
1967
|
1
|
50
|
33
|
|
|
6
|
confess "colsToGroupBy has to be specified!" unless defined($colsToGroupBy) && ref($colsToGroupBy) eq "ARRAY"; |
1968
|
1
|
50
|
|
|
|
3
|
confess "funToApply has to be a reference to CODE!" unless ref($funToApply) eq "CODE"; |
1969
|
1
|
50
|
|
|
|
3
|
unless (scalar @$colsToGroupBy) { # all rows are treated as one group |
1970
|
0
|
|
|
|
|
0
|
$funToApply->($self->clone, 0 .. $self->nofRow - 1); |
1971
|
0
|
|
|
|
|
0
|
return; |
1972
|
|
|
|
|
|
|
} |
1973
|
1
|
|
|
|
|
2
|
my @X = (); |
1974
|
1
|
|
|
|
|
2
|
my %grpBy = (); |
1975
|
1
|
|
|
|
|
2
|
foreach my $x (@$colsToGroupBy) { |
1976
|
1
|
|
|
|
|
3
|
my $x_idx = $self->checkOldCol($x); |
1977
|
1
|
50
|
|
|
|
3
|
confess "Unknown column ". $x unless defined($x_idx); |
1978
|
1
|
|
|
|
|
2
|
push @X, $x_idx; |
1979
|
1
|
|
|
|
|
3
|
$grpBy{$x_idx} = 1; |
1980
|
|
|
|
|
|
|
} |
1981
|
1
|
|
|
|
|
2
|
my %X = (); |
1982
|
1
|
|
|
|
|
3
|
for (my $i=0; $i<$self->nofRow; $i++) { |
1983
|
4
|
|
|
|
|
8
|
my $myRow = $self->rowRef($i); |
1984
|
|
|
|
|
|
|
#my @val = (); |
1985
|
|
|
|
|
|
|
#foreach my $x (@X) { |
1986
|
|
|
|
|
|
|
# push @val, defined($myRow->[$x])?$myRow->[$x]:""; |
1987
|
|
|
|
|
|
|
#} |
1988
|
4
|
|
|
|
|
6
|
my @val = map {tsvEscape($_)} @{$myRow}[@X]; |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
6
|
|
1989
|
4
|
|
|
|
|
8
|
my $myKey = CORE::join("\t", @val); |
1990
|
4
|
|
|
|
|
5
|
push @{$X{$myKey}}, $i; |
|
4
|
|
|
|
|
10
|
|
1991
|
|
|
|
|
|
|
} |
1992
|
1
|
|
|
|
|
7
|
foreach my $myKey ( sort {$a cmp $b} keys %X) { |
|
1
|
|
|
|
|
6
|
|
1993
|
2
|
|
|
|
|
5
|
$funToApply->($self->subTable($X{$myKey}, undef), $X{$myKey}); |
1994
|
|
|
|
|
|
|
} |
1995
|
|
|
|
|
|
|
} |
1996
|
|
|
|
|
|
|
|
1997
|
|
|
|
|
|
|
sub group { |
1998
|
5
|
|
|
5
|
1
|
24
|
my ($self, $colsToGroupBy, $colsToCalculate, $funsToApply, $newColNames, $keepRestCols) = @_; |
1999
|
5
|
100
|
|
|
|
10
|
$keepRestCols = 1 unless defined($keepRestCols); |
2000
|
5
|
50
|
|
|
|
12
|
$colsToGroupBy = [] unless defined $colsToGroupBy; |
2001
|
5
|
50
|
33
|
|
|
21
|
confess "colsToGroupBy has to be specified!" unless defined($colsToGroupBy) && ref($colsToGroupBy) eq "ARRAY"; |
2002
|
5
|
|
|
|
|
10
|
my @X = (); |
2003
|
5
|
|
|
|
|
6
|
my %grpBy = (); |
2004
|
5
|
|
|
|
|
10
|
foreach my $x (@$colsToGroupBy) { |
2005
|
5
|
|
|
|
|
11
|
my $x_idx = $self->checkOldCol($x); |
2006
|
5
|
50
|
|
|
|
11
|
confess "Unknown column ". $x unless defined($x_idx); |
2007
|
5
|
|
|
|
|
7
|
push @X, $x_idx; |
2008
|
5
|
|
|
|
|
11
|
$grpBy{$x_idx} = 1; |
2009
|
|
|
|
|
|
|
} |
2010
|
5
|
|
|
|
|
7
|
my @Y = (); |
2011
|
5
|
|
|
|
|
8
|
my %Y= (); |
2012
|
5
|
50
|
|
|
|
11
|
if (defined($colsToCalculate)) { |
2013
|
5
|
|
|
|
|
8
|
foreach my $y (@$colsToCalculate) { |
2014
|
7
|
|
|
|
|
13
|
my $y_idx = $self->checkOldCol($y); |
2015
|
7
|
50
|
|
|
|
14
|
confess "Unknown column ". $y unless defined($y_idx); |
2016
|
7
|
|
|
|
|
10
|
push @Y, $y_idx; |
2017
|
7
|
|
|
|
|
14
|
$Y{$y_idx} = 1; |
2018
|
|
|
|
|
|
|
} |
2019
|
|
|
|
|
|
|
} |
2020
|
5
|
50
|
|
|
|
11
|
if (scalar @Y) { |
2021
|
5
|
50
|
33
|
|
|
19
|
confess "The size of colsToCalculate, funcsToApply and newColNames should be the same!\n" |
2022
|
|
|
|
|
|
|
unless (scalar @Y == scalar @$funsToApply && scalar @Y == scalar @$newColNames); |
2023
|
|
|
|
|
|
|
} |
2024
|
|
|
|
|
|
|
|
2025
|
5
|
|
|
|
|
10
|
my @header = (); |
2026
|
5
|
|
|
|
|
7
|
my @X_name = (); |
2027
|
5
|
|
|
|
|
6
|
my $cnt = 0; |
2028
|
5
|
|
|
|
|
7
|
my $i; |
2029
|
5
|
|
|
|
|
12
|
for ($i=0; $i<$self->nofCol; $i++) { |
2030
|
20
|
100
|
66
|
|
|
69
|
if ($grpBy{$i} || ($keepRestCols && !defined($Y{$i}))) { |
|
|
|
66
|
|
|
|
|
2031
|
5
|
|
|
|
|
8
|
push @X_name, $i; |
2032
|
5
|
|
|
|
|
8
|
push @header, $self->{header}->[$i]; |
2033
|
5
|
|
|
|
|
23
|
$cnt += 1; |
2034
|
|
|
|
|
|
|
} |
2035
|
|
|
|
|
|
|
} |
2036
|
5
|
50
|
|
|
|
10
|
if (defined($newColNames)) { |
2037
|
5
|
|
|
|
|
11
|
foreach my $y (@$newColNames) { |
2038
|
7
|
|
|
|
|
9
|
push @header, $y; |
2039
|
7
|
|
|
|
|
8
|
$cnt += 1; |
2040
|
|
|
|
|
|
|
} |
2041
|
|
|
|
|
|
|
} |
2042
|
5
|
|
|
|
|
7
|
my @ones = (); |
2043
|
5
|
|
|
|
|
6
|
my %X = (); |
2044
|
5
|
|
|
|
|
6
|
my %val = (); |
2045
|
5
|
|
|
|
|
5
|
my %rowIdx = (); |
2046
|
5
|
|
|
|
|
6
|
my $idx = 0; |
2047
|
5
|
|
|
|
|
11
|
for ($i=0; $i<$self->nofRow; $i++) { |
2048
|
38
|
|
|
|
|
43
|
my @row = (); |
2049
|
38
|
|
|
|
|
52
|
my $myRow = $self->rowRef($i); |
2050
|
38
|
|
|
|
|
43
|
my $myKey = '(all)'; |
2051
|
38
|
100
|
|
|
|
73
|
if (@X) { |
2052
|
|
|
|
|
|
|
# if colsToGroupBy is not specified, all rows has myKey = '(all)', therefore treated as one group |
2053
|
23
|
|
|
|
|
25
|
my @val = map {tsvEscape($_)} @{$myRow}[@X]; |
|
38
|
|
|
|
|
45
|
|
|
23
|
|
|
|
|
41
|
|
2054
|
|
|
|
|
|
|
#foreach my $x (@X) { |
2055
|
|
|
|
|
|
|
# push @val, defined($myRow->[$x])?$myRow->[$x]:""; |
2056
|
|
|
|
|
|
|
#} |
2057
|
23
|
|
|
|
|
45
|
$myKey = CORE::join("\t", @val); |
2058
|
|
|
|
|
|
|
} |
2059
|
38
|
50
|
|
|
|
70
|
if (scalar @Y) { |
2060
|
38
|
|
|
|
|
43
|
my %Y = (); |
2061
|
38
|
|
|
|
|
50
|
foreach my $y (@Y) { |
2062
|
52
|
50
|
|
|
|
80
|
next if defined($Y{$y}); |
2063
|
52
|
|
|
|
|
61
|
$Y{$y} = 1; |
2064
|
52
|
100
|
|
|
|
77
|
if (defined($val{$y}->{$myKey})) { |
2065
|
35
|
|
|
|
|
32
|
push @{$val{$y}->{$myKey}}, $myRow->[$y]; |
|
35
|
|
|
|
|
77
|
|
2066
|
|
|
|
|
|
|
} else { |
2067
|
17
|
|
|
|
|
40
|
$val{$y}->{$myKey} = [$myRow->[$y]]; |
2068
|
|
|
|
|
|
|
} |
2069
|
|
|
|
|
|
|
} |
2070
|
|
|
|
|
|
|
} |
2071
|
38
|
100
|
|
|
|
83
|
next if defined($X{$myKey}); |
2072
|
12
|
|
|
|
|
14
|
$X{$myKey} = 1; |
2073
|
12
|
|
|
|
|
22
|
foreach my $j (@X_name) { |
2074
|
18
|
|
|
|
|
28
|
push @row, $myRow->[$j]; |
2075
|
|
|
|
|
|
|
} |
2076
|
12
|
50
|
|
|
|
23
|
$row[$cnt-1] = undef if (scalar @row < $cnt); |
2077
|
12
|
|
|
|
|
19
|
push @ones, \@row; |
2078
|
12
|
|
|
|
|
27
|
$rowIdx{$myKey} = $idx++; |
2079
|
|
|
|
|
|
|
} |
2080
|
|
|
|
|
|
|
|
2081
|
5
|
50
|
|
|
|
12
|
if (scalar @Y) { |
2082
|
5
|
|
|
|
|
6
|
$cnt -= scalar @Y; |
2083
|
5
|
|
|
|
|
12
|
for($i=0; $i
|
2084
|
7
|
|
|
|
|
20
|
foreach my $s (keys %X) { |
2085
|
17
|
50
|
|
|
|
104
|
if (ref($funsToApply->[$i]) eq "CODE") { |
2086
|
17
|
|
|
|
|
59
|
$ones[$rowIdx{$s}]->[$cnt+$i] = $funsToApply->[$i]->(@{$val{$Y[$i]}->{$s}}); |
|
17
|
|
|
|
|
36
|
|
2087
|
|
|
|
|
|
|
} else { |
2088
|
0
|
|
|
|
|
0
|
$ones[$rowIdx{$s}]->[$cnt+$i] = scalar @{$val{$Y[$i]}->{$s}}; |
|
0
|
|
|
|
|
0
|
|
2089
|
|
|
|
|
|
|
#confess "The ${i}th element in the function array is not a valid reference!\n"; |
2090
|
|
|
|
|
|
|
} |
2091
|
|
|
|
|
|
|
} |
2092
|
|
|
|
|
|
|
} |
2093
|
|
|
|
|
|
|
} |
2094
|
|
|
|
|
|
|
|
2095
|
5
|
|
|
|
|
96
|
return new Data::Table(\@ones, \@header, 0); |
2096
|
|
|
|
|
|
|
} |
2097
|
|
|
|
|
|
|
|
2098
|
|
|
|
|
|
|
sub pivot { |
2099
|
4
|
|
|
4
|
1
|
10
|
my ($self, $colToSplit, $colToSplitIsStringOrNumeric, $colToFill, $colsToGroupBy, $keepRestCols) = @_; |
2100
|
4
|
50
|
|
|
|
11
|
$keepRestCols = 0 unless defined($keepRestCols); |
2101
|
4
|
50
|
|
|
|
7
|
$colToSplitIsStringOrNumeric = 0 unless defined($colToSplitIsStringOrNumeric); |
2102
|
4
|
50
|
|
|
|
9
|
$colsToGroupBy = [] unless defined $colsToGroupBy; |
2103
|
4
|
|
|
|
|
4
|
my $y = undef; |
2104
|
4
|
100
|
|
|
|
9
|
$y = $self->checkOldCol($colToSplit) if defined $colToSplit; |
2105
|
4
|
100
|
|
|
|
9
|
my $y_name = defined($y)?$self->{header}->[$y]:undef; |
2106
|
4
|
50
|
66
|
|
|
14
|
confess "Unknown column ". $colToSplit if (!defined($y) && defined($colToSplit)); |
2107
|
4
|
|
|
|
|
6
|
my $z = undef; |
2108
|
4
|
50
|
|
|
|
9
|
$z = $self->checkOldCol($colToFill) if defined($colToFill); |
2109
|
4
|
50
|
|
|
|
8
|
my $z_name = defined($z)?$self->{header}->[$z]:undef; |
2110
|
4
|
50
|
33
|
|
|
10
|
confess "Unknown column ". $colToFill if (!defined($z) && defined($colToFill)); |
2111
|
|
|
|
|
|
|
#confess "Cannot take colToFill, if colToSplit is 'undef'" if (defined($z) && !defined($y)); |
2112
|
4
|
|
|
|
|
6
|
my @X = (); |
2113
|
4
|
50
|
|
|
|
17
|
if (defined($colsToGroupBy)) { |
2114
|
4
|
|
|
|
|
9
|
foreach my $x (@$colsToGroupBy) { |
2115
|
3
|
|
|
|
|
6
|
my $x_idx = $self->checkOldCol($x); |
2116
|
3
|
50
|
|
|
|
7
|
confess "Unknown column ". $x unless defined($x_idx); |
2117
|
3
|
|
|
|
|
6
|
push @X, $self->{header}->[$x_idx]; |
2118
|
|
|
|
|
|
|
} |
2119
|
|
|
|
|
|
|
} |
2120
|
4
|
|
|
|
|
8
|
my (@Y, %Y); |
2121
|
|
|
|
|
|
|
|
2122
|
4
|
100
|
|
|
|
7
|
if (defined($colToSplit)) { |
2123
|
2
|
|
|
|
|
8
|
@Y = $self->col($y); |
2124
|
2
|
|
|
|
|
4
|
%Y = (); |
2125
|
2
|
|
|
|
|
4
|
foreach my $val (@Y) { |
2126
|
8
|
50
|
|
|
|
11
|
$val = "NULL" unless defined($val); |
2127
|
8
|
|
|
|
|
14
|
$Y{$val} = 1; |
2128
|
|
|
|
|
|
|
} |
2129
|
|
|
|
|
|
|
} else { |
2130
|
2
|
|
|
|
|
5
|
@Y = ('(all)') x $self->nofCol; |
2131
|
2
|
|
|
|
|
5
|
%Y = ('(all)' => 1); |
2132
|
2
|
|
|
|
|
12
|
$colToSplitIsStringOrNumeric = 1; |
2133
|
|
|
|
|
|
|
} |
2134
|
4
|
50
|
|
|
|
10
|
if ($colToSplitIsStringOrNumeric == 0) { |
2135
|
0
|
|
|
|
|
0
|
foreach my $y (keys %Y) { |
2136
|
0
|
0
|
|
|
|
0
|
if ($y =~ /\D/) { |
2137
|
0
|
|
|
|
|
0
|
$colToSplitIsStringOrNumeric = 1; |
2138
|
0
|
|
|
|
|
0
|
last; |
2139
|
|
|
|
|
|
|
} |
2140
|
|
|
|
|
|
|
} |
2141
|
|
|
|
|
|
|
} |
2142
|
4
|
50
|
|
|
|
17
|
if ($colToSplitIsStringOrNumeric) { |
2143
|
4
|
|
|
|
|
16
|
@Y = sort { $a cmp $b } (keys %Y); |
|
2
|
|
|
|
|
8
|
|
2144
|
|
|
|
|
|
|
} else { |
2145
|
0
|
|
|
|
|
0
|
@Y = sort { $a <=> $b } (keys %Y); |
|
0
|
|
|
|
|
0
|
|
2146
|
|
|
|
|
|
|
} |
2147
|
|
|
|
|
|
|
|
2148
|
4
|
|
|
|
|
7
|
my @header = (); |
2149
|
4
|
|
|
|
|
5
|
my $i; |
2150
|
4
|
|
|
|
|
14
|
my @X_name = (); |
2151
|
|
|
|
|
|
|
|
2152
|
4
|
50
|
|
|
|
11
|
if (!$keepRestCols) { |
2153
|
4
|
|
|
|
|
12
|
foreach my $x (@X) { |
2154
|
3
|
|
|
|
|
9
|
push @X_name, $x; |
2155
|
|
|
|
|
|
|
} |
2156
|
|
|
|
|
|
|
} else { |
2157
|
0
|
|
|
|
|
0
|
for ($i=0; $i<$self->nofCol; $i++) { |
2158
|
0
|
0
|
0
|
|
|
0
|
next if ((defined($y) && $i==$y) || (defined($z) && $i==$z)); |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
2159
|
0
|
|
|
|
|
0
|
push @X_name, $self->{header}->[$i]; |
2160
|
|
|
|
|
|
|
} |
2161
|
|
|
|
|
|
|
} |
2162
|
4
|
|
|
|
|
6
|
my $cnt = 0; |
2163
|
4
|
|
|
|
|
9
|
for ($i=0; $i < @X_name; $i++) { |
2164
|
3
|
|
|
|
|
6
|
my $s = $X_name[$i]; |
2165
|
3
|
|
|
|
|
7
|
while (defined($Y{$s})) { |
2166
|
0
|
|
|
|
|
0
|
$s = "_".$s; |
2167
|
|
|
|
|
|
|
} |
2168
|
3
|
|
|
|
|
6
|
push @header, $s; |
2169
|
3
|
|
|
|
|
7
|
$Y{$s} = $cnt++; |
2170
|
|
|
|
|
|
|
} |
2171
|
|
|
|
|
|
|
|
2172
|
|
|
|
|
|
|
#if (defined($y)) { |
2173
|
4
|
|
|
|
|
8
|
foreach my $val (@Y) { |
2174
|
6
|
50
|
|
|
|
16
|
push @header, ($colToSplitIsStringOrNumeric?"":"$y_name=") . $val; |
2175
|
6
|
|
|
|
|
9
|
$Y{$val} = $cnt++; |
2176
|
|
|
|
|
|
|
} |
2177
|
|
|
|
|
|
|
#} |
2178
|
|
|
|
|
|
|
|
2179
|
4
|
|
|
|
|
7
|
my @ones = (); |
2180
|
4
|
|
|
|
|
6
|
my %X = (); |
2181
|
4
|
|
|
|
|
6
|
my $rowIdx = 0; |
2182
|
4
|
|
|
|
|
11
|
for ($i=0; $i<$self->nofRow; $i++) { |
2183
|
11
|
|
|
|
|
14
|
my @row = (); |
2184
|
11
|
|
|
|
|
18
|
my $myRow = $self->rowHashRef($i); |
2185
|
11
|
|
|
|
|
16
|
my $myKey = '(all)'; # set to '' to work with total agreegation (group all rows into one) |
2186
|
11
|
100
|
|
|
|
45
|
if (scalar @X) { |
2187
|
10
|
|
|
|
|
11
|
my @val = (); |
2188
|
10
|
|
|
|
|
13
|
foreach my $x (@X) { |
2189
|
10
|
|
|
|
|
17
|
push @val, tsvEscape($myRow->{$x}); |
2190
|
|
|
|
|
|
|
} |
2191
|
10
|
|
|
|
|
18
|
$myKey = CORE::join("\t", @val); |
2192
|
|
|
|
|
|
|
} |
2193
|
11
|
100
|
|
|
|
31
|
unless (defined($X{$myKey})) { |
2194
|
7
|
|
|
|
|
9
|
foreach my $s (@X_name) { |
2195
|
6
|
|
|
|
|
10
|
push @row, $myRow->{$s}; |
2196
|
|
|
|
|
|
|
} |
2197
|
7
|
|
|
|
|
14
|
for (my $j = scalar @row; $j<$cnt; $j++) { |
2198
|
11
|
|
|
|
|
20
|
$row[$j] = undef; |
2199
|
|
|
|
|
|
|
} |
2200
|
|
|
|
|
|
|
#$row[$cnt-1] = undef if (scalar @row < $cnt); |
2201
|
|
|
|
|
|
|
} |
2202
|
|
|
|
|
|
|
#if (defined($y)) { |
2203
|
11
|
100
|
|
|
|
20
|
my $val = defined($y) ? $myRow->{$y_name} : "(all)"; |
2204
|
11
|
50
|
|
|
|
17
|
$val = "NULL" unless defined($val); |
2205
|
11
|
100
|
|
|
|
21
|
if (!defined($X{$myKey})) { |
2206
|
7
|
50
|
|
|
|
15
|
$row[$Y{$val}] = defined($z)?$myRow->{$z_name}: $row[$Y{$val}]+1; |
2207
|
|
|
|
|
|
|
} else { |
2208
|
4
|
50
|
|
|
|
9
|
$ones[$X{$myKey}][$Y{$val}] = defined($z)?$myRow->{$z_name}: $ones[$X{$myKey}][$Y{$val}]+1; |
2209
|
|
|
|
|
|
|
} |
2210
|
|
|
|
|
|
|
#} |
2211
|
11
|
100
|
|
|
|
37
|
unless (defined($X{$myKey})) { |
2212
|
7
|
|
|
|
|
12
|
push @ones, \@row; |
2213
|
7
|
|
|
|
|
23
|
$X{$myKey} = $rowIdx++; |
2214
|
|
|
|
|
|
|
} |
2215
|
|
|
|
|
|
|
} |
2216
|
4
|
|
|
|
|
9
|
return new Data::Table(\@ones, \@header, 0); |
2217
|
|
|
|
|
|
|
} |
2218
|
|
|
|
|
|
|
|
2219
|
|
|
|
|
|
|
sub fromFileGuessOS { |
2220
|
9
|
|
|
9
|
0
|
87
|
my ($name, $arg_ref) = @_; |
2221
|
9
|
|
|
|
|
22
|
my @OS=("\n", "\r\n", "\r"); |
2222
|
|
|
|
|
|
|
# operatoring system: 0 for UNIX (\n as linebreak), 1 for Windows |
2223
|
|
|
|
|
|
|
# (\r\n as linebreak), 2 for MAC (\r as linebreak) |
2224
|
9
|
|
|
|
|
11
|
my $qualifier = ''; |
2225
|
9
|
|
|
|
|
15
|
my $encoding = $Data::Table::DEFAULTS{ENCODING}; |
2226
|
9
|
50
|
66
|
|
|
38
|
$qualifier = $arg_ref->{qualifier} if (defined($arg_ref) && exists $arg_ref->{qualifier}); |
2227
|
9
|
50
|
66
|
|
|
36
|
$encoding = $arg_ref->{encoding} if (defined($arg_ref) && exists $arg_ref->{encoding}); |
2228
|
9
|
|
|
|
|
16
|
my ($len, $os)=(-1, -1); |
2229
|
9
|
|
|
|
|
17
|
my $SRC=openFileWithEncoding($name, $encoding); |
2230
|
|
|
|
|
|
|
#local($/)="\n"; |
2231
|
9
|
|
|
|
|
19
|
my $s = getOneLine($SRC, "\n", $qualifier); #<$SRC>; |
2232
|
9
|
|
|
|
|
116
|
close($SRC); |
2233
|
|
|
|
|
|
|
#$s =~ s/\n$//; |
2234
|
|
|
|
|
|
|
#my $myLen=length($s); |
2235
|
|
|
|
|
|
|
#$s =~ s/\r$//; |
2236
|
9
|
100
|
|
|
|
69
|
if ($s =~ /\r\n$/) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
2237
|
2
|
|
|
|
|
11
|
return 1; |
2238
|
|
|
|
|
|
|
} elsif ($s =~ /\n$/) { |
2239
|
5
|
|
|
|
|
24
|
return 0; |
2240
|
|
|
|
|
|
|
} elsif ($s =~ /\r/) { |
2241
|
2
|
|
|
|
|
12
|
return 2; |
2242
|
|
|
|
|
|
|
} |
2243
|
0
|
|
|
|
|
0
|
return 0; |
2244
|
|
|
|
|
|
|
#if (length($s) == $myLen) { |
2245
|
|
|
|
|
|
|
# return 0; |
2246
|
|
|
|
|
|
|
#} elsif (length($s) == $myLen - 1) { |
2247
|
|
|
|
|
|
|
# return 1; |
2248
|
|
|
|
|
|
|
#} else { |
2249
|
|
|
|
|
|
|
# return 2; |
2250
|
|
|
|
|
|
|
#} |
2251
|
|
|
|
|
|
|
# for (my $i=0; $i<@OS; $i++) { |
2252
|
|
|
|
|
|
|
# open($SRC, $name) or confess "Cannot open $name to read"; |
2253
|
|
|
|
|
|
|
# binmode $SRC; |
2254
|
|
|
|
|
|
|
# local($/)=$OS[$i]; |
2255
|
|
|
|
|
|
|
# my $s = <$SRC>; |
2256
|
|
|
|
|
|
|
# #print ">> $i => ". (length($s)-length($OS[$i]))."\n"; |
2257
|
|
|
|
|
|
|
# my $myLen=length($s)-length($OS[$i]); |
2258
|
|
|
|
|
|
|
# if ($len<0 || ($myLen>0 && $myLen<$len)) { |
2259
|
|
|
|
|
|
|
# $len=length($s)-length($OS[$i]); |
2260
|
|
|
|
|
|
|
# $os=$i; |
2261
|
|
|
|
|
|
|
# } |
2262
|
|
|
|
|
|
|
# close($SRC); |
2263
|
|
|
|
|
|
|
# } |
2264
|
|
|
|
|
|
|
# # find the OS linebreak that gives the shortest first line |
2265
|
|
|
|
|
|
|
# return $os; |
2266
|
|
|
|
|
|
|
} |
2267
|
|
|
|
|
|
|
|
2268
|
|
|
|
|
|
|
sub openFileWithEncoding { |
2269
|
38
|
|
|
38
|
0
|
63
|
my ($name_or_handler, $encoding) = @_; |
2270
|
38
|
|
|
|
|
61
|
my $isFileHandler=ref($name_or_handler) ne ""; |
2271
|
38
|
|
|
|
|
45
|
my $SRC; |
2272
|
38
|
100
|
|
|
|
66
|
if ($isFileHandler) { |
2273
|
3
|
|
|
|
|
5
|
$SRC = $name_or_handler; # a file handler |
2274
|
|
|
|
|
|
|
} else { |
2275
|
35
|
50
|
|
|
|
1095
|
open($SRC, $name_or_handler) or confess "Cannot open $name_or_handler to read"; |
2276
|
|
|
|
|
|
|
} |
2277
|
|
|
|
|
|
|
# check if Perl version is recent enough to support encoding |
2278
|
38
|
50
|
33
|
|
|
608
|
$encoding ='' if (!$^V or $^V lt v5.8.1); |
2279
|
38
|
100
|
|
|
|
130
|
if ($encoding) { |
2280
|
36
|
50
|
|
|
|
198
|
$encoding='UTF-8' if ($encoding =~ /^utf-?8$/i); |
2281
|
2
|
|
|
2
|
|
12
|
binmode($SRC, ":encoding($encoding)"); |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
12
|
|
|
36
|
|
|
|
|
412
|
|
2282
|
|
|
|
|
|
|
} else { |
2283
|
2
|
|
|
|
|
5
|
binmode $SRC; |
2284
|
|
|
|
|
|
|
} |
2285
|
38
|
|
|
|
|
20469
|
return $SRC; |
2286
|
|
|
|
|
|
|
} |
2287
|
|
|
|
|
|
|
|
2288
|
|
|
|
|
|
|
sub fromFileGetTopLines { |
2289
|
7
|
|
|
7
|
0
|
15
|
my ($name, $os, $numLines, $arg_ref) = @_; |
2290
|
7
|
50
|
|
|
|
16
|
$os = fromFileGuessOS($name) unless defined($os); |
2291
|
7
|
50
|
|
|
|
13
|
$numLines = 2 unless defined($numLines); |
2292
|
7
|
|
|
|
|
18
|
my @OS=("\n", "\r\n", "\r"); |
2293
|
|
|
|
|
|
|
# operatoring system: 0 for UNIX (\n as linebreak), 1 for Windows |
2294
|
|
|
|
|
|
|
# (\r\n as linebreak), 2 for MAC (\r as linebreak) |
2295
|
7
|
|
|
|
|
10
|
my $encoding = $Data::Table::DEFAULTS{ENCODING}; |
2296
|
7
|
50
|
33
|
|
|
30
|
$encoding = $arg_ref->{encoding} if (defined($arg_ref) && exists $arg_ref->{encoding}); |
2297
|
7
|
|
|
|
|
11
|
my @lines=(); |
2298
|
7
|
|
|
|
|
14
|
my $SRC = openFileWithEncoding($name, $encoding); |
2299
|
7
|
|
|
|
|
26
|
local($/)=$OS[$os]; |
2300
|
7
|
|
|
|
|
13
|
my $n_endl = length($OS[$os]); |
2301
|
7
|
|
|
|
|
11
|
my $cnt=0; |
2302
|
7
|
|
|
|
|
95
|
while(my $line = <$SRC>) { |
2303
|
14
|
|
|
|
|
60
|
$cnt++; |
2304
|
14
|
|
|
|
|
25
|
for (1..$n_endl) { chop($line); } |
|
18
|
|
|
|
|
28
|
|
2305
|
14
|
|
|
|
|
22
|
push @lines, $line; |
2306
|
14
|
100
|
66
|
|
|
54
|
last if ($numLines>0 && $cnt>=$numLines); |
2307
|
|
|
|
|
|
|
} |
2308
|
7
|
|
|
|
|
65
|
close($SRC); |
2309
|
7
|
|
|
|
|
46
|
return @lines; |
2310
|
|
|
|
|
|
|
} |
2311
|
|
|
|
|
|
|
|
2312
|
|
|
|
|
|
|
sub fromFileIsHeader { |
2313
|
7
|
|
|
7
|
0
|
15
|
my ($s, $delimiter, $allowNumericHeader) = @_; |
2314
|
7
|
50
|
|
|
|
35
|
$delimiter=$Data::Table::DEFAULTS{'CSV_DELIMITER'} unless defined($delimiter); |
2315
|
7
|
50
|
33
|
|
|
93
|
return 0 if (!defined($s) || $s eq "" || $s=~ /$delimiter$/); |
|
|
|
33
|
|
|
|
|
2316
|
7
|
|
|
|
|
24
|
my $fields=parseCSV($s, 0, {delimiter=>$delimiter}); |
2317
|
7
|
|
|
|
|
16
|
my $allNumbers = 1; |
2318
|
7
|
|
|
|
|
16
|
foreach my $name (@$fields) { |
2319
|
20
|
50
|
|
|
|
31
|
return 0 unless $name; |
2320
|
|
|
|
|
|
|
#next if $name=~/[^0-9.eE\-+]/; |
2321
|
20
|
100
|
66
|
|
|
68
|
return 0 if $name=~/^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/ && !$allowNumericHeader; |
2322
|
|
|
|
|
|
|
# modified, so that we allow some columns to be numeric, but not all columns |
2323
|
19
|
50
|
|
|
|
47
|
$allNumbers = 0 unless $name =~ /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/; |
2324
|
|
|
|
|
|
|
} |
2325
|
|
|
|
|
|
|
#return 0 if $allNumbers; |
2326
|
6
|
|
|
|
|
14
|
return 1; |
2327
|
|
|
|
|
|
|
} |
2328
|
|
|
|
|
|
|
|
2329
|
|
|
|
|
|
|
sub fromFileGuessDelimiter { |
2330
|
7
|
|
|
7
|
0
|
12
|
my $s_line= shift; |
2331
|
7
|
|
|
|
|
14
|
my @DELIMITER=(",","\t",":"); |
2332
|
7
|
|
|
|
|
8
|
my $numCol=-1; my $i=-1; |
|
7
|
|
|
|
|
8
|
|
2333
|
7
|
50
|
|
|
|
15
|
return $Data::Table::DEFAULTS{CSV_DELIMITER} unless @$s_line; |
2334
|
7
|
|
|
|
|
18
|
for (my $d=0; $d<@DELIMITER; $d++) { |
2335
|
21
|
|
|
|
|
25
|
my $colFound=-1; |
2336
|
21
|
|
|
|
|
27
|
foreach my $line (@$s_line) { |
2337
|
42
|
50
|
|
|
|
59
|
unless (defined($line)) { |
2338
|
0
|
|
|
|
|
0
|
return $Data::Table::DEFAULTS{CSV_DELIMITER}; |
2339
|
|
|
|
|
|
|
} else { |
2340
|
42
|
|
|
|
|
90
|
my $header = parseCSV($line, 0, {delimiter=>$DELIMITER[$d]}); |
2341
|
42
|
100
|
|
|
|
113
|
if ($colFound<0) { |
|
|
50
|
|
|
|
|
|
2342
|
21
|
|
|
|
|
40
|
$colFound = scalar @$header; |
2343
|
|
|
|
|
|
|
} elsif ($colFound != scalar @$header) { |
2344
|
0
|
|
|
|
|
0
|
$colFound = -1; |
2345
|
0
|
|
|
|
|
0
|
last; |
2346
|
|
|
|
|
|
|
} |
2347
|
|
|
|
|
|
|
} |
2348
|
|
|
|
|
|
|
} |
2349
|
21
|
50
|
|
|
|
30
|
next if $colFound<0; |
2350
|
21
|
100
|
|
|
|
40
|
if ($colFound>$numCol) { |
2351
|
8
|
|
|
|
|
12
|
$numCol=$colFound; $i=$d; |
|
8
|
|
|
|
|
13
|
|
2352
|
|
|
|
|
|
|
} |
2353
|
|
|
|
|
|
|
} |
2354
|
7
|
50
|
|
|
|
22
|
return ($i<0)?$Data::Table::DEFAULTS{CSV_DELIMITER}:$DELIMITER[$i]; |
2355
|
|
|
|
|
|
|
} |
2356
|
|
|
|
|
|
|
|
2357
|
|
|
|
|
|
|
sub fromFile { |
2358
|
7
|
|
|
7
|
1
|
24
|
my ($name, $arg_ref) = @_; |
2359
|
7
|
|
|
|
|
11
|
my $linesChecked = 2; |
2360
|
7
|
|
|
|
|
8
|
my $os = undef; |
2361
|
7
|
|
|
|
|
10
|
my $hasHeader = undef; |
2362
|
7
|
|
|
|
|
9
|
my $delimiter = undef; |
2363
|
7
|
|
|
|
|
10
|
my $format = undef; |
2364
|
7
|
|
|
|
|
14
|
my $qualifier = $Data::Table::DEFAULTS{CSV_QUALIFIER}; |
2365
|
7
|
|
|
|
|
35
|
my $allowNumericHeader = 0; |
2366
|
7
|
|
|
|
|
13
|
my $encoding=$Data::Table::DEFAULTS{ENCODING}; |
2367
|
|
|
|
|
|
|
|
2368
|
7
|
100
|
|
|
|
17
|
if (defined($arg_ref)) { |
2369
|
1
|
50
|
|
|
|
3
|
$linesChecked = $arg_ref->{'linesChecked'} if defined($arg_ref->{'linesChecked'}); |
2370
|
1
|
|
|
|
|
3
|
$os = $arg_ref->{'OS'}; |
2371
|
1
|
|
|
|
|
2
|
$hasHeader = $arg_ref->{'has_header'}; |
2372
|
1
|
|
|
|
|
3
|
$delimiter = $arg_ref->{'delimiter'}; |
2373
|
1
|
|
|
|
|
2
|
$format = $arg_ref->{'format'}; |
2374
|
1
|
50
|
|
|
|
3
|
$qualifier = $arg_ref->{'qualifier'} if defined($arg_ref->{'qualifier'}); |
2375
|
1
|
|
|
|
|
2
|
$allowNumericHeader = $arg_ref->{'allowNumericHeader'}; |
2376
|
1
|
|
|
|
|
2
|
$encoding = $arg_ref->{'encoding'}; |
2377
|
|
|
|
|
|
|
} |
2378
|
|
|
|
|
|
|
|
2379
|
7
|
50
|
33
|
|
|
17
|
$qualifier = '' if ($format and uc($format) eq 'TSV'); |
2380
|
7
|
50
|
|
|
|
14
|
unless (defined($os)) { |
2381
|
7
|
|
|
|
|
26
|
$os = fromFileGuessOS($name, {qualifier=>$qualifier, encoding=>$encoding}); |
2382
|
7
|
|
|
|
|
22
|
$arg_ref->{'OS'}=$os; |
2383
|
|
|
|
|
|
|
} |
2384
|
7
|
|
|
|
|
27
|
my @S = fromFileGetTopLines($name, $os, $linesChecked, {encoding=>$encoding}); |
2385
|
7
|
50
|
|
|
|
20
|
return undef unless scalar @S; |
2386
|
7
|
50
|
|
|
|
16
|
unless (defined($delimiter)) { |
2387
|
7
|
|
|
|
|
42
|
$delimiter = fromFileGuessDelimiter(\@S); |
2388
|
7
|
|
|
|
|
14
|
$arg_ref->{'delimiter'} = $delimiter; |
2389
|
|
|
|
|
|
|
} |
2390
|
7
|
50
|
|
|
|
15
|
unless (defined($hasHeader)) { |
2391
|
7
|
|
|
|
|
16
|
$hasHeader = fromFileIsHeader($S[0], $delimiter, $allowNumericHeader); |
2392
|
|
|
|
|
|
|
} |
2393
|
7
|
|
|
|
|
11
|
my $t = undef; |
2394
|
|
|
|
|
|
|
#print ">>>". join("\n", @S)."\n"; |
2395
|
|
|
|
|
|
|
#print "OS=$os, hasHeader=$hasHeader, delimiter=$delimiter\n"; |
2396
|
7
|
100
|
|
|
|
17
|
if ($delimiter eq "\t") { |
2397
|
1
|
|
|
|
|
3
|
$t=fromTSV($name, $hasHeader, undef, $arg_ref); |
2398
|
|
|
|
|
|
|
} else { |
2399
|
6
|
|
|
|
|
17
|
$t=fromCSV($name, $hasHeader, undef, $arg_ref); |
2400
|
|
|
|
|
|
|
} |
2401
|
7
|
|
|
|
|
37
|
return $t; |
2402
|
|
|
|
|
|
|
} |
2403
|
|
|
|
|
|
|
|
2404
|
|
|
|
|
|
|
## interface to GD::Graph |
2405
|
|
|
|
|
|
|
# use GD::Graph::points; |
2406
|
|
|
|
|
|
|
# $graph = GD::Graph::points->new(400, 300); |
2407
|
|
|
|
|
|
|
# $graph->plot([$t->colRef(1), $t->colRef(2)]); |
2408
|
|
|
|
|
|
|
|
2409
|
|
|
|
|
|
|
1; |
2410
|
|
|
|
|
|
|
|
2411
|
|
|
|
|
|
|
__END__ |