line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package HTML::Tabulate; |
2
|
|
|
|
|
|
|
|
3
|
26
|
|
|
26
|
|
959260
|
use 5.005; |
|
26
|
|
|
|
|
110
|
|
|
26
|
|
|
|
|
1173
|
|
4
|
26
|
|
|
26
|
|
236
|
use Carp; |
|
26
|
|
|
|
|
61
|
|
|
26
|
|
|
|
|
2656
|
|
5
|
26
|
|
|
26
|
|
28621
|
use URI::Escape; |
|
26
|
|
|
|
|
58772
|
|
|
26
|
|
|
|
|
2268
|
|
6
|
26
|
|
|
26
|
|
208
|
use Scalar::Util qw(blessed); |
|
26
|
|
|
|
|
54
|
|
|
26
|
|
|
|
|
3431
|
|
7
|
26
|
|
|
26
|
|
154
|
use strict; |
|
26
|
|
|
|
|
52
|
|
|
26
|
|
|
|
|
1234
|
|
8
|
26
|
|
|
26
|
|
157
|
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $TITLE_HEADING_LEVEL); |
|
26
|
|
|
|
|
72
|
|
|
26
|
|
|
|
|
426594
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
require Exporter; |
11
|
|
|
|
|
|
|
@ISA = qw(Exporter); |
12
|
|
|
|
|
|
|
@EXPORT = qw(); |
13
|
|
|
|
|
|
|
@EXPORT_OK = qw(&render); |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
$VERSION = '0.44'; |
16
|
|
|
|
|
|
|
my $DEFAULT_TEXT_FORMAT = " %s \n"; |
17
|
|
|
|
|
|
|
my %DEFAULT_DEFN = ( |
18
|
|
|
|
|
|
|
style => 'down', |
19
|
|
|
|
|
|
|
table => {}, |
20
|
|
|
|
|
|
|
title => { format => "%s\n" }, |
21
|
|
|
|
|
|
|
text => { format => $DEFAULT_TEXT_FORMAT }, |
22
|
|
|
|
|
|
|
caption => { type => 'caption', format => $DEFAULT_TEXT_FORMAT }, |
23
|
|
|
|
|
|
|
field_attr => { -defaults => {}, }, |
24
|
|
|
|
|
|
|
); |
25
|
|
|
|
|
|
|
my %VALID_ARG = ( |
26
|
|
|
|
|
|
|
table => 'HASH/SCALAR', |
27
|
|
|
|
|
|
|
thead => 'HASH/SCALAR', |
28
|
|
|
|
|
|
|
tbody => 'HASH/SCALAR', |
29
|
|
|
|
|
|
|
tfoot => 'HASH/SCALAR', |
30
|
|
|
|
|
|
|
tr => 'HASH/CODE', |
31
|
|
|
|
|
|
|
thtr => 'HASH', |
32
|
|
|
|
|
|
|
th => 'HASH', |
33
|
|
|
|
|
|
|
td => 'HASH', |
34
|
|
|
|
|
|
|
fields => 'ARRAY', |
35
|
|
|
|
|
|
|
fields_add => 'HASH', |
36
|
|
|
|
|
|
|
fields_omit => 'ARRAY', |
37
|
|
|
|
|
|
|
in_fields => 'ARRAY', |
38
|
|
|
|
|
|
|
labels => 'SCALAR/HASH', |
39
|
|
|
|
|
|
|
label_links => 'HASH', |
40
|
|
|
|
|
|
|
stripe => 'ARRAY/SCALAR/HASH', |
41
|
|
|
|
|
|
|
null => 'SCALAR', |
42
|
|
|
|
|
|
|
trim => 'SCALAR', |
43
|
|
|
|
|
|
|
style => 'SCALAR', |
44
|
|
|
|
|
|
|
# limit => 'SCALAR', |
45
|
|
|
|
|
|
|
# output => 'SCALAR', |
46
|
|
|
|
|
|
|
# first => 'SCALAR', |
47
|
|
|
|
|
|
|
# last => 'SCALAR', |
48
|
|
|
|
|
|
|
field_attr => 'HASH', |
49
|
|
|
|
|
|
|
# xhtml: boolean indicating whether to use xhtml-style tagging |
50
|
|
|
|
|
|
|
xhtml => 'SCALAR', |
51
|
|
|
|
|
|
|
# title: title/heading to be rendered above table |
52
|
|
|
|
|
|
|
title => 'SCALAR/HASH/CODE', |
53
|
|
|
|
|
|
|
# text: text to be rendered above table, after title |
54
|
|
|
|
|
|
|
text => 'SCALAR/HASH/CODE', |
55
|
|
|
|
|
|
|
# caption: text to be rendered below table |
56
|
|
|
|
|
|
|
caption => 'SCALAR/HASH/CODE', |
57
|
|
|
|
|
|
|
# data_prepend: data rows to be inserted before main dataset |
58
|
|
|
|
|
|
|
data_prepend => 'ARRAY', |
59
|
|
|
|
|
|
|
# data_append: data rows to be appended to main dataset |
60
|
|
|
|
|
|
|
data_append => 'ARRAY', |
61
|
|
|
|
|
|
|
# colgroups: array of hashrefs to be inserted as individual colgroups |
62
|
|
|
|
|
|
|
colgroups => 'ARRAY', |
63
|
|
|
|
|
|
|
# labelgroups: named groupings of labels used to create two-tier headers |
64
|
|
|
|
|
|
|
labelgroups => 'HASH', |
65
|
|
|
|
|
|
|
# derived: fields not present in the underlying data, to skip unnecessary lookups |
66
|
|
|
|
|
|
|
derived => 'ARRAY', |
67
|
|
|
|
|
|
|
); |
68
|
|
|
|
|
|
|
my %VALID_FIELDS = ( |
69
|
|
|
|
|
|
|
-defaults => 'HASH', |
70
|
|
|
|
|
|
|
); |
71
|
|
|
|
|
|
|
my %FIELD_ATTR = ( |
72
|
|
|
|
|
|
|
escape => 'SCALAR', |
73
|
|
|
|
|
|
|
value => 'SCALAR/CODE', |
74
|
|
|
|
|
|
|
format => 'SCALAR/CODE', |
75
|
|
|
|
|
|
|
link => 'SCALAR/CODE', |
76
|
|
|
|
|
|
|
label => 'SCALAR/CODE', |
77
|
|
|
|
|
|
|
label_format => 'SCALAR/CODE', |
78
|
|
|
|
|
|
|
label_link => 'SCALAR/CODE', |
79
|
|
|
|
|
|
|
label_escape => 'SCALAR', |
80
|
|
|
|
|
|
|
default => 'SCALAR', |
81
|
|
|
|
|
|
|
composite => 'ARRAY', |
82
|
|
|
|
|
|
|
composite_join => 'SCALAR/CODE', |
83
|
|
|
|
|
|
|
derived => 'SCALAR', |
84
|
|
|
|
|
|
|
); |
85
|
|
|
|
|
|
|
my %MINIMISED_ATTR = map { $_ => 1 } qw( |
86
|
|
|
|
|
|
|
checked compact declare defer disabled ismap multiple |
87
|
|
|
|
|
|
|
nohref noresize noshade nowrap readonly selected |
88
|
|
|
|
|
|
|
); |
89
|
|
|
|
|
|
|
my $URI_ESCAPE_CHARS = "^A-Za-z0-9\-_.!~*'()?&;:/="; |
90
|
|
|
|
|
|
|
$TITLE_HEADING_LEVEL = 'h2'; # TODO: deprecated |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
# ------------------------------------------------------------------------- |
93
|
|
|
|
|
|
|
# Provided for subclassing |
94
|
|
|
|
|
|
|
sub get_valid_arg |
95
|
|
|
|
|
|
|
{ |
96
|
123
|
50
|
|
123
|
0
|
3098
|
return wantarray ? %VALID_ARG : \%VALID_ARG; |
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
# Provided for subclassing |
100
|
|
|
|
|
|
|
sub get_valid_fields |
101
|
|
|
|
|
|
|
{ |
102
|
123
|
50
|
|
123
|
0
|
935
|
return wantarray ? %VALID_FIELDS : \%VALID_FIELDS; |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
# Provided for subclassing |
106
|
|
|
|
|
|
|
sub get_field_attributes |
107
|
|
|
|
|
|
|
{ |
108
|
31
|
50
|
|
31
|
0
|
198
|
return wantarray ? %FIELD_ATTR : \%FIELD_ATTR; |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
# |
112
|
|
|
|
|
|
|
# Check $self->{defn} for invalid arguments or types |
113
|
|
|
|
|
|
|
# |
114
|
|
|
|
|
|
|
sub check_valid |
115
|
|
|
|
|
|
|
{ |
116
|
123
|
|
|
123
|
0
|
241
|
my ($self, $defn) = @_; |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
# Check top-level args |
119
|
123
|
|
|
|
|
378
|
my %valid = $self->get_valid_arg(); |
120
|
123
|
|
|
|
|
489
|
my (@invalid, @badtype); |
121
|
123
|
|
|
|
|
632
|
for (sort keys %$defn) { |
122
|
241
|
50
|
|
|
|
579
|
if (! exists $valid{$_}) { |
123
|
0
|
|
|
|
|
0
|
push @invalid, $_; |
124
|
0
|
|
|
|
|
0
|
next; |
125
|
|
|
|
|
|
|
} |
126
|
241
|
|
|
|
|
959
|
my $type = ref $defn->{$_}; |
127
|
241
|
50
|
66
|
|
|
2726
|
push @badtype, $_ |
|
|
|
66
|
|
|
|
|
128
|
|
|
|
|
|
|
if $type && $type ne 'SCALAR' && $valid{$_} !~ m/$type/; |
129
|
241
|
50
|
66
|
|
|
1051
|
push @badtype, $_ |
130
|
|
|
|
|
|
|
if ! $type && $valid{$_} !~ m/SCALAR/; |
131
|
|
|
|
|
|
|
} |
132
|
123
|
50
|
|
|
|
371
|
croak "[check_valid] invalid argument found: " . join(',',@invalid) |
133
|
|
|
|
|
|
|
if @invalid; |
134
|
123
|
50
|
|
|
|
358
|
croak "[check_valid] invalid types for argument: " . join(',',@badtype) |
135
|
|
|
|
|
|
|
if @badtype; |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
# Check special fields |
138
|
123
|
|
|
|
|
393
|
%valid = $self->get_valid_fields(); |
139
|
123
|
|
|
|
|
247
|
@invalid = (); |
140
|
123
|
|
|
|
|
446
|
@badtype = (); |
141
|
123
|
|
|
|
|
205
|
for (sort grep(/^-/, keys(%{$defn->{field_attr}})) ) { |
|
123
|
|
|
|
|
694
|
|
142
|
6
|
50
|
|
|
|
25
|
if (! exists $valid{$_}) { |
143
|
0
|
|
|
|
|
0
|
push @invalid, $_; |
144
|
0
|
|
|
|
|
0
|
next; |
145
|
|
|
|
|
|
|
} |
146
|
6
|
|
|
|
|
19
|
my $type = ref $defn->{field_attr}->{$_}; |
147
|
6
|
50
|
33
|
|
|
130
|
push @badtype, $_ |
|
|
|
33
|
|
|
|
|
148
|
|
|
|
|
|
|
if $type && $type ne 'SCALAR' && $valid{$_} !~ m/$type/; |
149
|
6
|
0
|
33
|
|
|
31
|
push @badtype, $_ |
150
|
|
|
|
|
|
|
if ! $type && $valid{$_} !~ m/SCALAR/; |
151
|
|
|
|
|
|
|
} |
152
|
123
|
50
|
|
|
|
461
|
croak "[check_valid] invalid field argument found: " . join(',',@invalid) |
153
|
|
|
|
|
|
|
if @invalid; |
154
|
123
|
50
|
|
|
|
296
|
croak "[check_valid] invalid types for field argument: " . join(',',@badtype) |
155
|
|
|
|
|
|
|
if @badtype; |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
# Check field attributes |
158
|
123
|
|
66
|
|
|
655
|
$self->{field_attr} ||= $self->get_field_attributes(); |
159
|
123
|
|
|
|
|
156
|
%valid = %{$self->{field_attr}}; |
|
123
|
|
|
|
|
1485
|
|
160
|
123
|
|
|
|
|
430
|
@badtype = (); |
161
|
123
|
|
|
|
|
186
|
for my $field (keys %{$defn->{field_attr}}) { |
|
123
|
|
|
|
|
845
|
|
162
|
55
|
50
|
|
|
|
204
|
croak "[check_valid] invalid field argument entry '$field': " . |
163
|
|
|
|
|
|
|
$defn->{field_attr}->{$field} |
164
|
|
|
|
|
|
|
if ref $defn->{field_attr}->{$field} ne 'HASH'; |
165
|
55
|
|
|
|
|
78
|
for (sort keys %{$defn->{field_attr}->{$field}}) { |
|
55
|
|
|
|
|
212
|
|
166
|
91
|
100
|
|
|
|
251
|
next if ! exists $valid{$_}; |
167
|
46
|
50
|
|
|
|
98
|
next if ! $valid{$_}; |
168
|
46
|
|
|
|
|
96
|
my $type = ref $defn->{field_attr}->{$field}->{$_}; |
169
|
46
|
50
|
|
|
|
99
|
if (! ref $valid{$_}) { |
|
|
0
|
|
|
|
|
|
170
|
46
|
50
|
66
|
|
|
336
|
push @badtype, $_ |
|
|
|
66
|
|
|
|
|
171
|
|
|
|
|
|
|
if $type && $type ne 'SCALAR' && $valid{$_} !~ m/$type/; |
172
|
46
|
50
|
66
|
|
|
216
|
push @badtype, $_ |
173
|
|
|
|
|
|
|
if ! $type && $valid{$_} !~ m/SCALAR/; |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
elsif (ref $valid{$_} eq 'ARRAY') { |
176
|
0
|
0
|
|
|
|
0
|
if ($type) { |
177
|
0
|
|
|
|
|
0
|
push @badtype, $_; |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
else { |
180
|
0
|
|
|
|
|
0
|
my $val = $defn->{field_attr}->{$field}->{$_}; |
181
|
0
|
0
|
|
|
|
0
|
push @badtype, "$_ ($val)" if ! grep /^$val$/, @{$valid{$_}}; |
|
0
|
|
|
|
|
0
|
|
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
} |
184
|
|
|
|
|
|
|
else { |
185
|
0
|
|
|
|
|
0
|
croak "[check_valid] invalid field attribute entry for '$_': " . |
186
|
|
|
|
|
|
|
ref $valid{$_}; |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
} |
189
|
55
|
50
|
|
|
|
231
|
croak "[check_valid] invalid type for '$field' field attribute: " . |
190
|
|
|
|
|
|
|
join(',',@badtype) if @badtype; |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
# |
195
|
|
|
|
|
|
|
# Merge $hash1 and $hash2 together, returning the result (or, in void |
196
|
|
|
|
|
|
|
# context, merging into $self->{defn}). Performs a shallow (one-level deep) |
197
|
|
|
|
|
|
|
# hash merge unless the field is defined in the @recurse_keys array, in |
198
|
|
|
|
|
|
|
# which case we do a full recursive merge. |
199
|
|
|
|
|
|
|
# |
200
|
|
|
|
|
|
|
sub merge |
201
|
|
|
|
|
|
|
{ |
202
|
246
|
|
|
246
|
1
|
3672
|
my $self = shift; |
203
|
246
|
|
50
|
|
|
797
|
my $hash1 = shift || {}; |
204
|
246
|
|
|
|
|
299
|
my $hash2 = shift; |
205
|
246
|
|
|
|
|
315
|
my $arg = shift; |
206
|
|
|
|
|
|
|
|
207
|
246
|
50
|
|
|
|
720
|
croak "[merge] invalid hash1 '$hash1'" if ref $hash1 ne 'HASH'; |
208
|
246
|
50
|
66
|
|
|
1153
|
croak "[merge] invalid hash2 '$hash2'" if $hash2 && ref $hash2 ne 'HASH'; |
209
|
|
|
|
|
|
|
|
210
|
246
|
|
|
|
|
422
|
my $single_arg = ! $hash2; |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
# Use $self->{defn} as $hash1 if only one argument |
213
|
246
|
100
|
|
|
|
702
|
if ($single_arg) { |
214
|
2
|
|
|
|
|
4
|
$hash2 = $hash1; |
215
|
2
|
|
|
|
|
5
|
$hash1 = $self->{defn}; |
216
|
|
|
|
|
|
|
} |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
# Check hash2 for valid args (except when recursive) |
219
|
246
|
|
100
|
|
|
1373
|
my $sub = (caller(1))[3] || ''; |
220
|
246
|
100
|
|
|
|
1118
|
$self->check_valid($hash2) unless substr($sub, -7) eq '::merge'; |
221
|
|
|
|
|
|
|
|
222
|
246
|
|
|
|
|
673
|
my $merge = $self->deepcopy($hash1); |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
# Add hash2 to $merge |
225
|
246
|
|
|
|
|
719
|
my @recurse_keys = qw(field_attr); |
226
|
246
|
|
|
|
|
660
|
for my $key (keys %$hash2) { |
227
|
|
|
|
|
|
|
# If this value is a hashref on both sides, do a shallow hash merge |
228
|
|
|
|
|
|
|
# unless we need to do a proper recursive merge |
229
|
395
|
100
|
100
|
|
|
1876
|
if (ref $hash2->{$key} eq 'HASH' && ref $merge->{$key} eq 'HASH') { |
230
|
|
|
|
|
|
|
# Recursive merge |
231
|
153
|
100
|
|
|
|
1942
|
if (grep /^$key$/, @recurse_keys) { |
232
|
123
|
|
|
|
|
539
|
$merge->{$key} = $self->merge($hash1->{$key}, $hash2->{$key}); |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
# Shallow hash merge |
235
|
|
|
|
|
|
|
else { |
236
|
30
|
|
|
|
|
50
|
@{$merge->{$key}}{ keys %{$hash1->{$key}}, keys %{$hash2->{$key}} } = (values %{$hash1->{$key}}, values %{$hash2->{$key}}); |
|
30
|
|
|
|
|
133
|
|
|
30
|
|
|
|
|
67
|
|
|
30
|
|
|
|
|
59
|
|
|
30
|
|
|
|
|
73
|
|
|
30
|
|
|
|
|
79
|
|
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
} |
239
|
|
|
|
|
|
|
# Otherwise (scalars, arrayrefs etc) just copy the value |
240
|
|
|
|
|
|
|
else { |
241
|
242
|
|
|
|
|
643
|
$merge->{$key} = $hash2->{$key}; |
242
|
|
|
|
|
|
|
} |
243
|
|
|
|
|
|
|
} |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
# In void context update $self->{defn} |
246
|
246
|
100
|
|
|
|
566
|
if (! defined wantarray) { |
247
|
2
|
|
|
|
|
5
|
$self->{defn} = $merge; |
248
|
|
|
|
|
|
|
# Must invalidate transient $self->{defn_t} when $self->{defn} changes |
249
|
2
|
50
|
|
|
|
14
|
delete $self->{defn_t} if exists $self->{defn_t}; |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
else { |
252
|
244
|
|
|
|
|
925
|
return $merge; |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
sub defn |
257
|
|
|
|
|
|
|
{ |
258
|
8
|
|
|
8
|
0
|
1245
|
my $self = shift; |
259
|
8
|
|
|
|
|
21
|
return $self->{defn}; |
260
|
|
|
|
|
|
|
} |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
# Initialisation |
263
|
|
|
|
|
|
|
sub init |
264
|
|
|
|
|
|
|
{ |
265
|
31
|
|
|
31
|
0
|
62
|
my $self = shift; |
266
|
31
|
|
100
|
|
|
145
|
my $defn = shift || {}; |
267
|
31
|
50
|
33
|
|
|
323
|
croak "[init] invalid defn '$defn'" if $defn && ref $defn ne 'HASH'; |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
# Map $defn table => 1 to table => {} for cleaner merging |
270
|
31
|
50
|
66
|
|
|
173
|
$defn->{table} = {} if $defn->{table} && ! ref $defn->{table}; |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
# Initialise $self->{defn} by merging defaults and $defn |
273
|
31
|
|
|
|
|
163
|
$self->{defn} = $self->merge(\%DEFAULT_DEFN, $defn); |
274
|
|
|
|
|
|
|
|
275
|
31
|
|
|
|
|
110
|
return $self; |
276
|
|
|
|
|
|
|
} |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
sub new |
279
|
|
|
|
|
|
|
{ |
280
|
31
|
|
|
31
|
1
|
13276
|
my $class = shift; |
281
|
31
|
|
|
|
|
68
|
my $self = {}; |
282
|
31
|
|
|
|
|
91
|
bless $self, $class; |
283
|
31
|
|
|
|
|
152
|
$self->init(@_); |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
# ------------------------------------------------------------------------- |
287
|
|
|
|
|
|
|
# |
288
|
|
|
|
|
|
|
# If deriving field names, also derive labels (if not already defined) |
289
|
|
|
|
|
|
|
# |
290
|
|
|
|
|
|
|
sub derive_label |
291
|
|
|
|
|
|
|
{ |
292
|
356
|
|
|
356
|
0
|
540
|
my ($self, $field) = @_; |
293
|
356
|
|
|
|
|
1383
|
$field =~ s/_+/ /g; |
294
|
356
|
|
|
|
|
1089
|
$field = join ' ', map { ucfirst($_) } split(/\s+/, $field); |
|
668
|
|
|
|
|
8940
|
|
295
|
356
|
|
|
|
|
1288
|
$field =~ s/(Id)$/\U$1/; |
296
|
356
|
|
|
|
|
831
|
return $field; |
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
# |
300
|
|
|
|
|
|
|
# Try and derive a reasonable field list from $self->{defn_t} using the set data. |
301
|
|
|
|
|
|
|
# Croaks on failure. |
302
|
|
|
|
|
|
|
# |
303
|
|
|
|
|
|
|
sub derive_fields |
304
|
|
|
|
|
|
|
{ |
305
|
7
|
|
|
7
|
0
|
11
|
my ($self, $set) = @_; |
306
|
7
|
|
|
|
|
13
|
my $defn = $self->{defn_t}; |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
# For iterators, prefetch the first row and use its keys |
309
|
7
|
50
|
|
|
|
17
|
croak "invalid Tabulate data type '$set'" unless ref $set; |
310
|
7
|
50
|
33
|
|
|
69
|
if (ref $set eq 'CODE') { |
|
|
50
|
33
|
|
|
|
|
|
|
50
|
33
|
|
|
|
|
|
|
100
|
33
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
311
|
0
|
|
|
|
|
0
|
my $row = $set->(); |
312
|
0
|
|
|
|
|
0
|
$self->{prefetch} = $row; |
313
|
0
|
0
|
|
|
|
0
|
$defn->{fields} = [ sort keys %$row ] if eval { keys %$row }; |
|
0
|
|
|
|
|
0
|
|
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
elsif (blessed $set and $set->can('Next')) { |
316
|
0
|
0
|
|
|
|
0
|
my $row = $set->can('First') ? $set->First : $set->Next; |
317
|
0
|
|
|
|
|
0
|
$self->{prefetch} = $row; |
318
|
0
|
0
|
|
|
|
0
|
$defn->{fields} = [ sort keys %$row ] if eval { keys %$row }; |
|
0
|
|
|
|
|
0
|
|
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
elsif (blessed $set and $set->can('next')) { |
321
|
0
|
0
|
|
|
|
0
|
my $row = $set->can('first') ? $set->first : $set->next; |
322
|
0
|
|
|
|
|
0
|
$self->{prefetch} = $row; |
323
|
0
|
0
|
|
|
|
0
|
$defn->{fields} = [ sort keys %$row ] if eval { keys %$row }; |
|
0
|
|
|
|
|
0
|
|
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
# For arrays |
326
|
|
|
|
|
|
|
elsif (ref $set eq 'ARRAY') { |
327
|
6
|
50
|
|
|
|
15
|
if (! @$set) { |
328
|
0
|
|
|
|
|
0
|
$defn->{fields} = []; |
329
|
0
|
|
|
|
|
0
|
return; |
330
|
|
|
|
|
|
|
} |
331
|
6
|
|
|
|
|
9
|
my $obj = $set->[0]; |
332
|
|
|
|
|
|
|
# Arrayref of hashrefs |
333
|
6
|
100
|
|
|
|
16
|
if (ref $obj eq 'HASH') { |
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
334
|
3
|
|
|
|
|
21
|
$defn->{fields} = [ sort keys %$obj ]; |
335
|
|
|
|
|
|
|
} |
336
|
|
|
|
|
|
|
# Arrayref of arrayrefs - access via subscripts unless labels are defined |
337
|
|
|
|
|
|
|
elsif (ref $obj eq 'ARRAY') { |
338
|
3
|
50
|
|
|
|
9
|
if ($defn->{labels}) { |
339
|
0
|
|
|
|
|
0
|
croak "[derive_fields] no fields found and cannot derive fields from data arrayrefs"; |
340
|
|
|
|
|
|
|
} |
341
|
|
|
|
|
|
|
# Arrayref of arrayrefs, labels off |
342
|
|
|
|
|
|
|
else { |
343
|
3
|
|
|
|
|
11
|
$defn->{fields} = [ 0 .. $#$obj ]; |
344
|
|
|
|
|
|
|
} |
345
|
|
|
|
|
|
|
} |
346
|
|
|
|
|
|
|
# For Class::DBI objects, derive via columns groups |
347
|
|
|
|
|
|
|
elsif ($obj->isa('Class::DBI')) { |
348
|
0
|
|
|
|
|
0
|
my @col = $obj->columns('Tabulate'); |
349
|
0
|
0
|
0
|
|
|
0
|
@col = ( $obj->columns('Essential'), $obj->columns('Others') ) |
350
|
|
|
|
|
|
|
if ! @col && $obj->columns('Essential'); |
351
|
0
|
0
|
|
|
|
0
|
@col = $obj->columns('All') if ! @col; |
352
|
0
|
0
|
|
|
|
0
|
$defn->{fields} = [ @col ] if @col; |
353
|
|
|
|
|
|
|
} |
354
|
|
|
|
|
|
|
# If all else fails, try treating as a hash |
355
|
6
|
50
|
33
|
|
|
25
|
unless (ref $defn->{fields} && @{$defn->{fields}}) { |
|
6
|
|
|
|
|
26
|
|
356
|
0
|
0
|
|
|
|
0
|
if (! defined eval { $defn->{fields} = [ sort keys %$obj ] }) { |
|
0
|
|
|
|
|
0
|
|
357
|
0
|
|
|
|
|
0
|
croak "[derive_fields] no fields found and initial object '$obj' is strange type"; |
358
|
|
|
|
|
|
|
} |
359
|
|
|
|
|
|
|
} |
360
|
|
|
|
|
|
|
} |
361
|
|
|
|
|
|
|
# Else looks like a single object - check for Class::DBI |
362
|
|
|
|
|
|
|
elsif (ref $set && ref $set ne 'HASH' && $set->isa('Class::DBI')) { |
363
|
0
|
|
|
|
|
0
|
my @col = $set->columns('Tabulate'); |
364
|
0
|
0
|
0
|
|
|
0
|
@col = ( $set->columns('Essential'), $set->columns('Others') ) |
365
|
|
|
|
|
|
|
if ! @col && $set->columns('Essential'); |
366
|
0
|
0
|
|
|
|
0
|
@col = $set->columns('All') if ! @col; |
367
|
0
|
0
|
|
|
|
0
|
$defn->{fields} = [ @col ] if @col; |
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
# Otherwise try treating as a hash |
370
|
1
|
|
|
|
|
3
|
elsif (defined eval { keys %$set }) { |
371
|
1
|
|
|
|
|
6
|
my $first = (sort keys %$set)[0]; |
372
|
1
|
50
|
|
|
|
4
|
my $ref = ref $set->{$first} if defined $first; |
373
|
|
|
|
|
|
|
# Check whether first value is reference |
374
|
1
|
50
|
|
|
|
3
|
if ($ref) { |
375
|
|
|
|
|
|
|
# Hashref of hashrefs |
376
|
0
|
0
|
|
|
|
0
|
if ($ref eq 'HASH') { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
377
|
0
|
|
|
|
|
0
|
$defn->{fields} = [ sort keys %{$set->{$first}} ]; |
|
0
|
|
|
|
|
0
|
|
378
|
|
|
|
|
|
|
} |
379
|
|
|
|
|
|
|
elsif (ref $set->[0] ne 'ARRAY') { |
380
|
0
|
|
|
|
|
0
|
croak "[derive_fields] no fields found and first row '" . $set->[0] . "' is strange type"; |
381
|
|
|
|
|
|
|
} |
382
|
|
|
|
|
|
|
# Hashref of arrayrefs - fatal only if labels => 1 |
383
|
|
|
|
|
|
|
elsif ($defn->{labels}) { |
384
|
0
|
|
|
|
|
0
|
croak "[derive_fields] no fields found and cannot derive fields from data arrayrefs"; |
385
|
|
|
|
|
|
|
} |
386
|
|
|
|
|
|
|
# Hashref of arrayrefs, labels off |
387
|
|
|
|
|
|
|
else { |
388
|
0
|
|
|
|
|
0
|
$defn->{fields} = [ 0 .. $#{$set->[$first]} ]; |
|
0
|
|
|
|
|
0
|
|
389
|
|
|
|
|
|
|
} |
390
|
|
|
|
|
|
|
} |
391
|
|
|
|
|
|
|
else { |
392
|
1
|
|
|
|
|
6
|
$defn->{fields} = [ sort keys %$set ]; |
393
|
|
|
|
|
|
|
} |
394
|
|
|
|
|
|
|
} |
395
|
|
|
|
|
|
|
else { |
396
|
0
|
|
|
|
|
0
|
croak "[derive_fields] no fields found and set '$set' is strange type: $@"; |
397
|
|
|
|
|
|
|
} |
398
|
|
|
|
|
|
|
|
399
|
7
|
50
|
|
|
|
31
|
croak sprintf "[derive_fields] field derivation failed (fields: %s)", |
400
|
|
|
|
|
|
|
$defn->{fields} |
401
|
|
|
|
|
|
|
unless ref $defn->{fields} eq 'ARRAY'; |
402
|
|
|
|
|
|
|
} |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
# Derive a fields list if none is defined |
405
|
|
|
|
|
|
|
sub check_fields |
406
|
|
|
|
|
|
|
{ |
407
|
107
|
|
|
107
|
0
|
193
|
my $self = shift; |
408
|
107
|
|
|
|
|
166
|
my ($set) = @_; |
409
|
100
|
|
|
|
|
593
|
$self->derive_fields($set) |
410
|
|
|
|
|
|
|
if ! $self->{defn_t}->{fields} || |
411
|
|
|
|
|
|
|
ref $self->{defn_t}->{fields} ne 'ARRAY' || |
412
|
107
|
100
|
66
|
|
|
1056
|
! @{$self->{defn_t}->{fields}}; |
|
|
|
66
|
|
|
|
|
413
|
|
|
|
|
|
|
} |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
# Splice additional fields into the fields array |
416
|
|
|
|
|
|
|
sub splice_fields |
417
|
|
|
|
|
|
|
{ |
418
|
1
|
|
|
1
|
0
|
2
|
my $self = shift; |
419
|
1
|
|
|
|
|
2
|
my $defn = $self->{defn_t}; |
420
|
1
|
|
|
|
|
2
|
my $add = $defn->{fields_add}; |
421
|
1
|
50
|
33
|
|
|
14
|
return unless ref $defn->{fields} eq 'ARRAY' && ref $add eq 'HASH'; |
422
|
|
|
|
|
|
|
|
423
|
1
|
|
|
|
|
2
|
for (my $i = $#{$defn->{fields}}; $i >= 0; $i--) { |
|
1
|
|
|
|
|
5
|
|
424
|
6
|
|
|
|
|
10
|
my $f = $defn->{fields}->[$i]; |
425
|
6
|
100
|
|
|
|
20
|
next unless $add->{$f}; |
426
|
2
|
100
|
|
|
|
5
|
if (ref $add->{$f} eq 'ARRAY') { |
427
|
1
|
|
|
|
|
2
|
splice @{$defn->{fields}}, $i+1, 0, @{$add->{$f}}; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
5
|
|
428
|
|
|
|
|
|
|
} |
429
|
|
|
|
|
|
|
else { |
430
|
1
|
|
|
|
|
2
|
splice @{$defn->{fields}}, $i+1, 0, $add->{$f}; |
|
1
|
|
|
|
|
7
|
|
431
|
|
|
|
|
|
|
} |
432
|
|
|
|
|
|
|
} |
433
|
|
|
|
|
|
|
} |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
# Omit/remove fields from the fields array |
436
|
|
|
|
|
|
|
sub omit_fields |
437
|
|
|
|
|
|
|
{ |
438
|
26
|
|
|
26
|
0
|
40
|
my $self = shift; |
439
|
26
|
|
|
|
|
40
|
my $defn = $self->{defn_t}; |
440
|
26
|
|
|
|
|
36
|
my %omit = map { $_ => 1 } @{$defn->{fields_omit}}; |
|
52
|
|
|
|
|
126
|
|
|
26
|
|
|
|
|
52
|
|
441
|
26
|
|
|
|
|
174
|
$defn->{fields} = [ grep { ! exists $omit{$_} } @{$defn->{fields}} ]; |
|
134
|
|
|
|
|
308
|
|
|
26
|
|
|
|
|
53
|
|
442
|
|
|
|
|
|
|
} |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
# |
445
|
|
|
|
|
|
|
# Deep copy routine, originally swiped from a Randal Schwartz column |
446
|
|
|
|
|
|
|
# |
447
|
|
|
|
|
|
|
sub deepcopy |
448
|
|
|
|
|
|
|
{ |
449
|
5304
|
|
|
5304
|
0
|
15549
|
my ($self, $this) = @_; |
450
|
5304
|
100
|
|
|
|
12842
|
if (! ref $this) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
451
|
2488
|
|
|
|
|
9582
|
return $this; |
452
|
|
|
|
|
|
|
} elsif (ref $this eq "ARRAY") { |
453
|
249
|
|
|
|
|
647
|
return [map $self->deepcopy($_), @$this]; |
454
|
|
|
|
|
|
|
} elsif (ref $this eq "HASH") { |
455
|
2458
|
|
|
|
|
8027
|
return {map { $_ => $self->deepcopy($this->{$_}) } keys %$this}; |
|
3583
|
|
|
|
|
12572
|
|
456
|
|
|
|
|
|
|
} elsif (ref $this eq "CODE") { |
457
|
109
|
|
|
|
|
458
|
return $this; |
458
|
|
|
|
|
|
|
} elsif (sprintf $this) { |
459
|
|
|
|
|
|
|
# Object! As a last resort, try copying the stringification value |
460
|
0
|
|
|
|
|
0
|
return sprintf $this; |
461
|
|
|
|
|
|
|
} else { |
462
|
0
|
|
|
|
|
0
|
die "what type is $_? (" . ref($this) . ")"; |
463
|
|
|
|
|
|
|
} |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
# |
467
|
|
|
|
|
|
|
# Create a transient presentation definition (defn_t) by doing a set of one-off |
468
|
|
|
|
|
|
|
# or dataset-specific mappings on the current table definition e.g. deriving |
469
|
|
|
|
|
|
|
# a field list if none is set, setting up a field map for arrayref-of- |
470
|
|
|
|
|
|
|
# arrayref sets, and mapping top-level shortcuts into their field |
471
|
|
|
|
|
|
|
# attribute equivalents. |
472
|
|
|
|
|
|
|
# |
473
|
|
|
|
|
|
|
sub prerender_munge |
474
|
|
|
|
|
|
|
{ |
475
|
107
|
|
|
107
|
0
|
186
|
my $self = shift; |
476
|
107
|
|
|
|
|
205
|
my ($set, $defn) = @_; |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
# Use $self->{defn} if $defn not passed |
479
|
107
|
|
66
|
|
|
348
|
$defn ||= $self->{defn}; |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
# If already done, return unless we require any dataset-specific mappings |
482
|
|
|
|
|
|
|
# if ($self->{defn_t}) { |
483
|
|
|
|
|
|
|
# return unless |
484
|
|
|
|
|
|
|
# ref $defn->{fields} ne 'ARRAY' || |
485
|
|
|
|
|
|
|
# ! @{$defn->{fields}} || |
486
|
|
|
|
|
|
|
# (ref $set eq 'ARRAY' && @$set && ref $set->[0] eq 'ARRAY'); |
487
|
|
|
|
|
|
|
# } |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
# Copy $defn to $self->{defn_t} |
490
|
107
|
|
|
|
|
259
|
$self->{defn_t} = $self->deepcopy($defn); |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
# Try to derive field list if not set |
493
|
107
|
|
|
|
|
1922
|
$self->check_fields($set); |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
# Set up a field map in case we have arrayref based data |
496
|
107
|
|
|
|
|
230
|
my $defn_t = $self->{defn_t}; |
497
|
107
|
|
|
|
|
168
|
my $pos = 0; |
498
|
107
|
100
|
|
|
|
355
|
my $fields = ref $defn_t->{in_fields} eq 'ARRAY' ? $defn_t->{in_fields} : $defn_t->{fields}; |
499
|
107
|
|
|
|
|
278
|
$defn_t->{field_map} = { map { $_ => $pos++; } @$fields }; |
|
437
|
|
|
|
|
1225
|
|
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
# Splice any additional fields into the fields array |
502
|
107
|
100
|
|
|
|
366
|
$self->splice_fields if $defn_t->{fields_add}; |
503
|
107
|
100
|
|
|
|
344
|
$self->omit_fields if $defn_t->{fields_omit}; |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
# Map top-level 'labels' and 'label_links' hashrefs into fields |
506
|
107
|
100
|
|
|
|
323
|
if (ref $defn_t->{labels} eq 'HASH') { |
507
|
10
|
|
|
|
|
20
|
for (keys %{$defn_t->{labels}}) { |
|
10
|
|
|
|
|
37
|
|
508
|
23
|
|
100
|
|
|
94
|
$defn_t->{field_attr}->{$_} ||= {}; |
509
|
23
|
|
|
|
|
87
|
$defn_t->{field_attr}->{$_}->{label} = $defn_t->{labels}->{$_}; |
510
|
|
|
|
|
|
|
} |
511
|
|
|
|
|
|
|
} |
512
|
107
|
100
|
|
|
|
341
|
if (ref $defn_t->{label_links} eq 'HASH') { |
513
|
1
|
|
|
|
|
2
|
for (keys %{$defn_t->{label_links}}) { |
|
1
|
|
|
|
|
5
|
|
514
|
1
|
|
50
|
|
|
8
|
$defn_t->{field_attr}->{$_} ||= {}; |
515
|
1
|
|
|
|
|
5
|
$defn_t->{field_attr}->{$_}->{label_link} = $defn_t->{label_links}->{$_}; |
516
|
|
|
|
|
|
|
} |
517
|
|
|
|
|
|
|
} |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
# Map top-level 'derived' field list into fields |
520
|
107
|
50
|
|
|
|
306
|
if ($defn_t->{derived}) { |
521
|
0
|
|
|
|
|
0
|
for (@{ $defn_t->{derived} }) { |
|
0
|
|
|
|
|
0
|
|
522
|
0
|
|
|
|
|
0
|
$defn_t->{field_attr}->{$_}->{derived} = 1; |
523
|
|
|
|
|
|
|
} |
524
|
|
|
|
|
|
|
} |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
# If style across, map top-level 'thtr' hashref into -defaults label_ attributes |
527
|
107
|
100
|
100
|
|
|
458
|
if ($self->{defn_t}->{style} eq 'across' && ref $defn_t->{thtr} eq 'HASH') { |
528
|
1
|
|
|
|
|
2
|
for (keys %{$defn_t->{thtr}}) { |
|
1
|
|
|
|
|
5
|
|
529
|
1
|
50
|
|
|
|
12
|
$defn_t->{field_attr}->{-defaults}->{"label_$_"} = $defn_t->{thtr}->{$_} |
530
|
|
|
|
|
|
|
if ! exists $defn_t->{field_attr}->{-defaults}->{"label_$_"}; |
531
|
|
|
|
|
|
|
} |
532
|
|
|
|
|
|
|
} |
533
|
|
|
|
|
|
|
# Map top-level 'th' hashref into -defaults label_ attributes |
534
|
107
|
100
|
|
|
|
343
|
if (ref $defn_t->{th} eq 'HASH') { |
535
|
8
|
|
|
|
|
18
|
for (keys %{$defn_t->{th}}) { |
|
8
|
|
|
|
|
30
|
|
536
|
8
|
50
|
|
|
|
80
|
$defn_t->{field_attr}->{-defaults}->{"label_$_"} = $defn_t->{th}->{$_} |
537
|
|
|
|
|
|
|
if ! exists $defn_t->{field_attr}->{-defaults}->{"label_$_"}; |
538
|
|
|
|
|
|
|
} |
539
|
|
|
|
|
|
|
} |
540
|
|
|
|
|
|
|
# Map top-level 'td' hashref into -defaults |
541
|
107
|
100
|
|
|
|
411
|
if (ref $defn_t->{td} eq 'HASH') { |
542
|
4
|
|
|
|
|
8
|
$defn_t->{field_attr}->{-defaults} = { %{$defn_t->{td}}, %{$defn_t->{field_attr}->{-defaults}} }; |
|
4
|
|
|
|
|
13
|
|
|
4
|
|
|
|
|
24
|
|
543
|
|
|
|
|
|
|
} |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
# Move regex field_attr definitions into a -regex hash |
546
|
107
|
|
|
|
|
374
|
$defn_t->{field_attr}->{-regex} = {}; |
547
|
107
|
|
|
|
|
180
|
for (keys %{$defn_t->{field_attr}}) { |
|
107
|
|
|
|
|
403
|
|
548
|
|
|
|
|
|
|
# The following test is an ugly hack, but the regex is stringified now |
549
|
289
|
100
|
|
|
|
851
|
next unless m/^\(\?.*\)$/; |
550
|
4
|
|
|
|
|
13
|
$defn_t->{field_attr}->{-regex}->{$_} = $defn_t->{field_attr}->{$_}; |
551
|
4
|
|
|
|
|
11
|
delete $defn_t->{field_attr}->{$_}; |
552
|
|
|
|
|
|
|
} |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
# Force a non-array stripe to be a binary array |
555
|
107
|
100
|
100
|
|
|
651
|
if ($defn_t->{stripe} && ref $defn_t->{stripe} ne 'ARRAY') { |
556
|
6
|
|
|
|
|
38
|
$defn_t->{stripe} = [ undef, $defn_t->{stripe} ]; |
557
|
|
|
|
|
|
|
} |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
# thead and tfoot imply tbody |
560
|
107
|
100
|
|
|
|
312
|
if ($defn_t->{thead}) { |
561
|
7
|
|
100
|
|
|
37
|
$defn_t->{tbody} ||= 1; |
562
|
7
|
100
|
|
|
|
28
|
$defn_t->{thead} = {} if ! ref $defn_t->{thead}; |
563
|
|
|
|
|
|
|
} |
564
|
107
|
100
|
|
|
|
339
|
if ($defn_t->{tfoot}) { |
565
|
3
|
|
100
|
|
|
9
|
$defn_t->{tbody} ||= 1; |
566
|
3
|
100
|
|
|
|
8
|
$defn_t->{tfoot} = {} if ! ref $defn_t->{tfoot}; |
567
|
|
|
|
|
|
|
} |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
# Setup tbody attributes hash for hashref tbodies |
570
|
107
|
100
|
|
|
|
503
|
if ($defn_t->{tbody}) { |
571
|
23
|
100
|
|
|
|
53
|
if (ref $defn_t->{tbody}) { |
572
|
13
|
|
|
|
|
34
|
$defn_t->{tbody_attr} = $self->deepcopy($defn_t->{tbody}); |
573
|
13
|
|
|
|
|
23
|
for (keys %{$defn_t->{tbody_attr}}) { |
|
13
|
|
|
|
|
43
|
|
574
|
17
|
100
|
|
|
|
99
|
delete $defn_t->{tbody_attr}->{$_} if m/^-/; |
575
|
|
|
|
|
|
|
} |
576
|
|
|
|
|
|
|
} |
577
|
|
|
|
|
|
|
else { |
578
|
10
|
|
|
|
|
35
|
$defn_t->{tbody_attr} = {}; |
579
|
|
|
|
|
|
|
} |
580
|
|
|
|
|
|
|
} |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
} |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
# Split fields up according to labelgroups into two field lists |
585
|
|
|
|
|
|
|
# labelgroup entries look like LabelGroup => [ qw(field1 field2 field3) ] |
586
|
|
|
|
|
|
|
sub labelgroup_fields |
587
|
|
|
|
|
|
|
{ |
588
|
1
|
|
|
1
|
0
|
10
|
my $self = shift; |
589
|
|
|
|
|
|
|
|
590
|
1
|
|
|
|
|
2
|
my @fields = @{$self->{defn_t}->{fields}}; |
|
1
|
|
|
|
|
5
|
|
591
|
1
|
|
|
|
|
2
|
my $labelgroups = $self->{defn_t}->{labelgroups}; |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
# Map first field of each labelgroup into a hash |
594
|
1
|
|
|
|
|
2
|
my %grouped_fields; |
595
|
1
|
|
|
|
|
11
|
for my $label (keys %$labelgroups) { |
596
|
1
|
|
|
|
|
3
|
my $field1 = $labelgroups->{$label}->[0]; |
597
|
1
|
|
|
|
|
5
|
$grouped_fields{ $field1 } = $label; |
598
|
|
|
|
|
|
|
} |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
# Process all fields looking for label groups, and splitting out if found |
601
|
1
|
|
|
|
|
2
|
my (@fields1, @fields2); |
602
|
1
|
|
|
|
|
6
|
while (my $f = shift @fields) { |
603
|
3
|
100
|
|
|
|
10
|
if (my $label = $grouped_fields{ $f }) { |
604
|
|
|
|
|
|
|
# Found a grouped label - splice labelled fields into fields2 |
605
|
1
|
|
|
|
|
2
|
my @gfields = @{ $labelgroups->{$label} }; |
|
1
|
|
|
|
|
3
|
|
606
|
1
|
|
|
|
|
2
|
shift @gfields; # discard $f |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
# Check all fields match |
609
|
1
|
|
|
|
|
2
|
my @next_group; |
610
|
1
|
|
|
|
|
5
|
while (my $g = shift @gfields) { |
611
|
1
|
|
|
|
|
2
|
my $fn = shift @fields; |
612
|
1
|
50
|
|
|
|
8
|
push @next_group, $fn if $fn eq $g; |
613
|
|
|
|
|
|
|
} |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
# If we have as many as we're expecting, we're good |
616
|
1
|
50
|
|
|
|
2
|
if (@next_group == @{ $labelgroups->{$label} } - 1) { |
|
1
|
|
|
|
|
39
|
|
617
|
1
|
|
|
|
|
4
|
push @fields2, $f, @next_group; |
618
|
1
|
|
|
|
|
5
|
push @fields1, $label; |
619
|
|
|
|
|
|
|
} |
620
|
|
|
|
|
|
|
# Otherwise our field list doesn't exactly match the label group - omit |
621
|
|
|
|
|
|
|
else { |
622
|
0
|
|
|
|
|
0
|
push @fields1, $f; |
623
|
|
|
|
|
|
|
# Push @next_group back into @fields for reprocessing |
624
|
0
|
|
|
|
|
0
|
unshift @fields, @next_group; |
625
|
|
|
|
|
|
|
} |
626
|
|
|
|
|
|
|
} |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
# Not a labelgroup |
629
|
|
|
|
|
|
|
else { |
630
|
2
|
|
|
|
|
8
|
push @fields1, $f; |
631
|
|
|
|
|
|
|
} |
632
|
|
|
|
|
|
|
} |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
# Setup $field1_tx_attr map if we have any @fields2 fields |
635
|
1
|
|
|
|
|
2
|
my $field1_tx_attr = {}; |
636
|
1
|
50
|
|
|
|
5
|
if (@fields2) { |
637
|
1
|
|
|
|
|
2
|
for my $f (@fields1) { |
638
|
3
|
100
|
|
|
|
11
|
if (my $grouped_fields = $labelgroups->{$f}) { |
639
|
1
|
|
|
|
|
4
|
$field1_tx_attr->{$f} = { colspan => scalar(@$grouped_fields) }; |
640
|
|
|
|
|
|
|
} |
641
|
|
|
|
|
|
|
else { |
642
|
2
|
|
|
|
|
9
|
$field1_tx_attr->{$f} = { rowspan => 2 }; |
643
|
|
|
|
|
|
|
} |
644
|
|
|
|
|
|
|
} |
645
|
|
|
|
|
|
|
} |
646
|
|
|
|
|
|
|
|
647
|
1
|
|
|
|
|
5
|
return (\@fields1, \@fields2, $field1_tx_attr); |
648
|
|
|
|
|
|
|
} |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
# ------------------------------------------------------------------------- |
651
|
|
|
|
|
|
|
# |
652
|
|
|
|
|
|
|
# Return the given HTML $tag with attributes from the $attr hashref. |
653
|
|
|
|
|
|
|
# An attribute with a non-empty value (i.e. not '' or undef) is rendered |
654
|
|
|
|
|
|
|
# attr="value"; one with a value of '' is rendered as a 'bare' attribute |
655
|
|
|
|
|
|
|
# (i.e. no '=') in non-xhtml mode; one with undef is simply ignored |
656
|
|
|
|
|
|
|
# (e.g. allowing unset CGI parameters to be ignored). |
657
|
|
|
|
|
|
|
# |
658
|
|
|
|
|
|
|
sub start_tag |
659
|
|
|
|
|
|
|
{ |
660
|
2279
|
|
|
2279
|
0
|
3788
|
my ($self, $tag, $attr, $close) = @_; |
661
|
2279
|
|
|
|
|
3837
|
my $xhtml = $self->{defn_t}->{xhtml}; |
662
|
2279
|
|
|
|
|
3386
|
my $str = "<$tag"; |
663
|
2279
|
100
|
|
|
|
5361
|
if (ref $attr eq 'HASH') { |
664
|
2245
|
|
|
|
|
8183
|
for my $a (sort keys %$attr) { |
665
|
364
|
100
|
|
|
|
840
|
next if ! defined $attr->{$a}; |
666
|
340
|
100
|
|
|
|
759
|
if ($attr->{$a} ne '') { |
667
|
316
|
|
|
|
|
978
|
$str .= qq( $a="$attr->{$a}"); |
668
|
|
|
|
|
|
|
} |
669
|
|
|
|
|
|
|
else { |
670
|
24
|
100
|
|
|
|
44
|
if ($MINIMISED_ATTR{$a}) { |
671
|
12
|
100
|
|
|
|
39
|
$str .= $xhtml ? qq( $a="$a") : qq( $a); |
672
|
|
|
|
|
|
|
} |
673
|
|
|
|
|
|
|
else { |
674
|
12
|
|
|
|
|
29
|
$str .= qq( $a=""); |
675
|
|
|
|
|
|
|
} |
676
|
|
|
|
|
|
|
} |
677
|
|
|
|
|
|
|
} |
678
|
|
|
|
|
|
|
} |
679
|
2279
|
100
|
100
|
|
|
5779
|
$str .= ' /' if $close && $xhtml; |
680
|
2279
|
|
|
|
|
3142
|
$str .= ">"; |
681
|
2279
|
|
|
|
|
7415
|
return $str; |
682
|
|
|
|
|
|
|
} |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
sub end_tag |
685
|
|
|
|
|
|
|
{ |
686
|
2270
|
|
|
2270
|
0
|
3312
|
my ($self, $tag) = @_; |
687
|
2270
|
|
|
|
|
6907
|
return "$tag>"; |
688
|
|
|
|
|
|
|
} |
689
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
691
|
|
|
|
|
|
|
# Pre- and post-table content |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
# Title, text, and caption elements may be: |
694
|
|
|
|
|
|
|
# - hashref, containing 'value' (scalar) and 'format' (scalar or subref) |
695
|
|
|
|
|
|
|
# elements that are rendered like table cells |
696
|
|
|
|
|
|
|
# - scalar, that is treated as a scalar 'value' as above with a default |
697
|
|
|
|
|
|
|
# 'format' |
698
|
|
|
|
|
|
|
# - subref, that is executed and the results used verbatim (i.e. no default |
699
|
|
|
|
|
|
|
# 'format' applies |
700
|
|
|
|
|
|
|
sub text_element |
701
|
|
|
|
|
|
|
{ |
702
|
321
|
|
|
321
|
0
|
405
|
my $self = shift; |
703
|
321
|
|
|
|
|
522
|
my ($type, $dataset) = @_; |
704
|
321
|
50
|
|
|
|
6497
|
return '' unless grep /^$type$/, qw(title text caption); |
705
|
|
|
|
|
|
|
|
706
|
321
|
|
|
|
|
689
|
my $elt = $self->{defn_t}->{$type}; |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
# Subref - execute and return results |
709
|
321
|
100
|
|
|
|
1172
|
if (ref $elt eq 'CODE') { |
|
|
100
|
|
|
|
|
|
710
|
2
|
|
|
|
|
12
|
return $elt->($dataset, $type); |
711
|
|
|
|
|
|
|
} |
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
# Scalar - convert to hashref |
714
|
|
|
|
|
|
|
elsif (! ref $elt) { |
715
|
16
|
|
|
|
|
22
|
my $value = $elt; |
716
|
16
|
|
|
|
|
73
|
$elt = {}; |
717
|
|
|
|
|
|
|
# If there's a DEFAULT_DEFN $elt entry, use that as defaults |
718
|
16
|
50
|
33
|
|
|
97
|
if ($DEFAULT_DEFN{$type} && ref $DEFAULT_DEFN{$type} eq 'HASH') { |
719
|
16
|
|
|
|
|
16
|
$elt = { %{$DEFAULT_DEFN{$type}} }; |
|
16
|
|
|
|
|
67
|
|
720
|
|
|
|
|
|
|
} |
721
|
16
|
|
|
|
|
43
|
$elt->{value} = $value; |
722
|
|
|
|
|
|
|
} |
723
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
# Hashref - render and return |
725
|
319
|
50
|
|
|
|
704
|
if (ref $elt eq 'HASH') { |
726
|
319
|
100
|
100
|
|
|
2362
|
return '' unless defined $elt->{value} or defined $elt->{title}; |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
# Omit formatting if tag-wrapped |
729
|
25
|
100
|
100
|
|
|
165
|
return $elt->{value} |
730
|
|
|
|
|
|
|
if defined $elt->{value} && $elt->{value} =~ m/^\s*\<.*\>\s*$/s; |
731
|
19
|
50
|
66
|
|
|
70
|
return $elt->{title} |
732
|
|
|
|
|
|
|
if defined $elt->{title} && $elt->{title} =~ m/^\s*\<.*\>\s*$/s; |
733
|
|
|
|
|
|
|
|
734
|
|
|
|
|
|
|
# sprintf format pattern |
735
|
19
|
100
|
100
|
|
|
235
|
return sprintf $elt->{format}, $elt->{value} |
|
|
|
100
|
|
|
|
|
736
|
|
|
|
|
|
|
if defined $elt->{value} && defined $elt->{format} && |
737
|
|
|
|
|
|
|
! ref $elt->{format}; |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
# subref format pattern |
740
|
6
|
100
|
100
|
|
|
53
|
return $elt->{format}->($elt->{value}, $dataset, $type) |
|
|
|
66
|
|
|
|
|
741
|
|
|
|
|
|
|
if defined $elt->{value} && defined $elt->{format} && |
742
|
|
|
|
|
|
|
ref $elt->{format} eq 'CODE'; |
743
|
|
|
|
|
|
|
|
744
|
|
|
|
|
|
|
# Deprecated formatting style |
745
|
3
|
100
|
|
|
|
12
|
if ($elt->{title}) { |
746
|
2
|
|
|
|
|
5
|
my $title = $elt->{title}; |
747
|
2
|
|
100
|
|
|
10
|
my $tag = $elt->{tag} || 'h2'; |
748
|
2
|
|
|
|
|
4
|
delete $elt->{title}; |
749
|
2
|
|
|
|
|
3
|
delete $elt->{tag}; |
750
|
2
|
|
|
|
|
5
|
delete $elt->{format}; |
751
|
2
|
|
|
|
|
5
|
return $self->start_tag($tag, $elt) . $title . |
752
|
|
|
|
|
|
|
$self->end_tag($tag, $elt) . "\n"; |
753
|
|
|
|
|
|
|
} |
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
# fallthru: return 'value' |
756
|
1
|
|
|
|
|
7
|
return $elt->{value}; |
757
|
|
|
|
|
|
|
} |
758
|
|
|
|
|
|
|
|
759
|
0
|
|
|
|
|
0
|
return ''; |
760
|
|
|
|
|
|
|
} |
761
|
|
|
|
|
|
|
|
762
|
|
|
|
|
|
|
# unchomp: ensure (non-empty) elements end with a newline |
763
|
|
|
|
|
|
|
sub unchomp |
764
|
|
|
|
|
|
|
{ |
765
|
321
|
|
|
321
|
0
|
748
|
my $self = shift; |
766
|
321
|
|
|
|
|
381
|
my $data = shift; |
767
|
321
|
100
|
66
|
|
|
1782
|
$data .= "\n" if defined $data && $data ne '' && substr($data,-1) ne "\n"; |
|
|
|
100
|
|
|
|
|
768
|
321
|
|
|
|
|
764
|
$data |
769
|
|
|
|
|
|
|
} |
770
|
|
|
|
|
|
|
|
771
|
|
|
|
|
|
|
# title: title/heading preceding the table |
772
|
107
|
|
|
107
|
1
|
165
|
sub title { my $self = shift; $self->unchomp($self->text_element('title', @_)) } |
|
107
|
|
|
|
|
365
|
|
773
|
|
|
|
|
|
|
# text: text preceding begin table tag (after title, if any) |
774
|
107
|
|
|
107
|
1
|
166
|
sub text { my $self = shift; $self->unchomp($self->text_element('text', @_)) } |
|
107
|
|
|
|
|
366
|
|
775
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
# caption: either new-style text, or legacy text after end table tag |
777
|
|
|
|
|
|
|
sub caption { |
778
|
214
|
|
|
214
|
1
|
287
|
my $self = shift; |
779
|
214
|
|
|
|
|
307
|
my ($set, $post_table) = @_; |
780
|
214
|
|
|
|
|
347
|
my $defn_t = $self->{defn_t}; |
781
|
|
|
|
|
|
|
|
782
|
|
|
|
|
|
|
# Legacy text must have a 'format' element |
783
|
214
|
100
|
100
|
|
|
2674
|
if ($post_table && |
|
|
100
|
66
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
66
|
|
|
|
|
784
|
|
|
|
|
|
|
(ref $defn_t->{caption} ne 'HASH' || |
785
|
|
|
|
|
|
|
! $defn_t->{caption}->{type} || |
786
|
|
|
|
|
|
|
$defn_t->{caption}->{type} ne 'caption_caption')) { |
787
|
105
|
|
|
|
|
294
|
$self->unchomp($self->text_element('caption', $set)); |
788
|
|
|
|
|
|
|
} |
789
|
|
|
|
|
|
|
elsif (! $post_table && |
790
|
|
|
|
|
|
|
(ref $defn_t->{caption} eq 'HASH' && |
791
|
|
|
|
|
|
|
$defn_t->{caption}->{type} && |
792
|
|
|
|
|
|
|
$defn_t->{caption}->{type} eq 'caption_caption')) { |
793
|
2
|
100
|
50
|
|
|
12
|
delete $defn_t->{caption}->{format} |
794
|
|
|
|
|
|
|
if ($defn_t->{caption}->{format} || '') eq $DEFAULT_TEXT_FORMAT; |
795
|
2
|
|
|
|
|
5
|
$self->unchomp( |
796
|
|
|
|
|
|
|
$self->start_tag('caption') . |
797
|
|
|
|
|
|
|
$self->text_element('caption', $set) . |
798
|
|
|
|
|
|
|
$self->end_tag('caption') |
799
|
|
|
|
|
|
|
) |
800
|
|
|
|
|
|
|
} |
801
|
|
|
|
|
|
|
} |
802
|
|
|
|
|
|
|
|
803
|
|
|
|
|
|
|
sub colgroups { |
804
|
107
|
|
|
107
|
1
|
169
|
my $self = shift; |
805
|
107
|
|
|
|
|
155
|
my ($set) = @_; |
806
|
107
|
|
|
|
|
191
|
my $defn_t = $self->{defn_t}; |
807
|
|
|
|
|
|
|
|
808
|
107
|
100
|
|
|
|
418
|
return '' unless $self->{defn_t}->{colgroups}; |
809
|
|
|
|
|
|
|
|
810
|
3
|
|
|
|
|
4
|
my $content = ''; |
811
|
3
|
|
|
|
|
4
|
for my $cg (@{$self->{defn_t}->{colgroups}}) { |
|
3
|
|
|
|
|
6
|
|
812
|
8
|
100
|
66
|
|
|
49
|
if ($cg->{cols} && ref $cg->{cols} && ref $cg->{cols} eq 'ARRAY') { |
|
|
|
66
|
|
|
|
|
813
|
1
|
|
|
|
|
3
|
my $cols = delete $cg->{cols}; |
814
|
1
|
|
|
|
|
4
|
$content .= $self->start_tag('colgroup', $cg, 0) . "\n"; |
815
|
1
|
|
|
|
|
3
|
for my $col (@$cols) { |
816
|
2
|
|
|
|
|
7
|
$content .= $self->start_tag('col', $col, 1) . "\n"; |
817
|
|
|
|
|
|
|
} |
818
|
1
|
|
|
|
|
5
|
$content .= $self->end_tag('colgroup') . "\n"; |
819
|
|
|
|
|
|
|
} |
820
|
|
|
|
|
|
|
else { |
821
|
7
|
|
|
|
|
12
|
$content .= $self->start_tag('colgroup', $cg, 1) . "\n"; |
822
|
|
|
|
|
|
|
} |
823
|
|
|
|
|
|
|
} |
824
|
3
|
|
|
|
|
7
|
return $content; |
825
|
|
|
|
|
|
|
} |
826
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
828
|
|
|
|
|
|
|
# Content before begin table tag |
829
|
|
|
|
|
|
|
sub pre_table |
830
|
|
|
|
|
|
|
{ |
831
|
107
|
|
|
107
|
0
|
167
|
my $self = shift; |
832
|
107
|
|
|
|
|
176
|
my ($set) = @_; |
833
|
107
|
|
|
|
|
169
|
my $content = ''; |
834
|
107
|
50
|
|
|
|
642
|
$content .= $self->title($set) if $self->{defn_t}->{title}; |
835
|
107
|
50
|
|
|
|
665
|
$content .= $self->text($set) if $self->{defn_t}->{text}; |
836
|
107
|
|
|
|
|
286
|
return $content; |
837
|
|
|
|
|
|
|
} |
838
|
|
|
|
|
|
|
|
839
|
|
|
|
|
|
|
# Provided for subclassing |
840
|
|
|
|
|
|
|
sub start_table |
841
|
|
|
|
|
|
|
{ |
842
|
107
|
|
|
107
|
0
|
182
|
my $self = shift; |
843
|
107
|
100
|
66
|
|
|
721
|
return '' if exists $self->{defn_t}->{table} && ! $self->{defn_t}->{table}; |
844
|
106
|
|
|
|
|
340
|
return $self->start_tag('table',$self->{defn_t}->{table}) . "\n"; |
845
|
|
|
|
|
|
|
} |
846
|
|
|
|
|
|
|
|
847
|
|
|
|
|
|
|
# Provided for subclassing |
848
|
|
|
|
|
|
|
sub end_table |
849
|
|
|
|
|
|
|
{ |
850
|
107
|
|
|
107
|
0
|
168
|
my $self = shift; |
851
|
107
|
100
|
66
|
|
|
668
|
return '' if exists $self->{defn_t}->{table} && ! $self->{defn_t}->{table}; |
852
|
106
|
|
|
|
|
274
|
return $self->end_tag('table') . "\n"; |
853
|
|
|
|
|
|
|
} |
854
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
# Content after end table tag |
856
|
|
|
|
|
|
|
sub post_table |
857
|
|
|
|
|
|
|
{ |
858
|
107
|
|
|
107
|
0
|
155
|
my $self = shift; |
859
|
107
|
|
|
|
|
159
|
my ($set) = @_; |
860
|
107
|
|
|
|
|
202
|
my $content = ''; |
861
|
107
|
|
|
|
|
311
|
$content .= $self->caption($set, 'post_table'); |
862
|
107
|
|
|
|
|
242
|
return $content; |
863
|
|
|
|
|
|
|
} |
864
|
|
|
|
|
|
|
|
865
|
|
|
|
|
|
|
# ------------------------------------------------------------------------ |
866
|
|
|
|
|
|
|
# Apply 'format' formatting |
867
|
|
|
|
|
|
|
sub cell_format_format |
868
|
|
|
|
|
|
|
{ |
869
|
28
|
|
|
28
|
0
|
43
|
my ($self, $data, $fattr, $row, $field) = @_; |
870
|
28
|
|
|
|
|
44
|
my $ref = ref $fattr->{format}; |
871
|
28
|
50
|
66
|
|
|
104
|
croak "[cell_format] invalid '$field' format: $ref" if $ref && $ref ne 'CODE'; |
872
|
28
|
100
|
50
|
|
|
118
|
$data = $fattr->{format}->($data, $row || {}, $field) if $ref eq 'CODE'; |
873
|
28
|
100
|
|
|
|
183
|
$data = sprintf $fattr->{format}, $data if ! $ref; |
874
|
28
|
|
|
|
|
50
|
return $data; |
875
|
|
|
|
|
|
|
} |
876
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
# Simple tag escaping |
878
|
|
|
|
|
|
|
sub cell_format_escape |
879
|
|
|
|
|
|
|
{ |
880
|
1515
|
|
|
1515
|
0
|
2164
|
my ($self, $data) = @_; |
881
|
1515
|
|
|
|
|
2336
|
$data =~ s/</g; |
882
|
1515
|
|
|
|
|
1741
|
$data =~ s/>/>/g; |
883
|
1515
|
|
|
|
|
2845
|
return $data; |
884
|
|
|
|
|
|
|
} |
885
|
|
|
|
|
|
|
|
886
|
|
|
|
|
|
|
# Link formatting |
887
|
|
|
|
|
|
|
sub cell_format_link |
888
|
|
|
|
|
|
|
{ |
889
|
30
|
|
|
30
|
0
|
77
|
my ($self, $data, $fattr, $row, $field, $data_unformatted) = @_; |
890
|
30
|
|
|
|
|
31
|
my $ldata; |
891
|
30
|
|
|
|
|
52
|
my $ref = ref $fattr->{link}; |
892
|
30
|
50
|
66
|
|
|
154
|
croak "[cell_format] invalid '$field' link: $ref" |
893
|
|
|
|
|
|
|
if $ref && $ref ne 'CODE'; |
894
|
30
|
100
|
100
|
|
|
125
|
$ldata = $fattr->{link}->($data_unformatted, $row || {}, $field) |
895
|
|
|
|
|
|
|
if $ref eq 'CODE'; |
896
|
30
|
100
|
|
|
|
185
|
$ldata = sprintf $fattr->{link}, $data_unformatted |
897
|
|
|
|
|
|
|
if ! $ref; |
898
|
30
|
50
|
|
|
|
60
|
if ($ldata) { |
899
|
|
|
|
|
|
|
# $data = sprintf qq(%s), |
900
|
|
|
|
|
|
|
# uri_escape($ldata, $URI_ESCAPE_CHARS), $data; |
901
|
30
|
|
|
|
|
122
|
my $link_attr = { href => uri_escape($ldata, $URI_ESCAPE_CHARS)}; |
902
|
30
|
|
|
|
|
2376
|
for my $attr (keys %$fattr) { |
903
|
151
|
100
|
|
|
|
411
|
if ($attr =~ m/^link_/) { |
904
|
34
|
|
|
|
|
54
|
my $val = $fattr->{$attr}; |
905
|
34
|
|
|
|
|
85
|
$attr =~ s/^link_//; |
906
|
34
|
100
|
100
|
|
|
125
|
$link_attr->{$attr} = ref $val eq 'CODE' ? |
907
|
|
|
|
|
|
|
$val->($data_unformatted, $row || {}, $field) : |
908
|
|
|
|
|
|
|
$val; |
909
|
|
|
|
|
|
|
} |
910
|
|
|
|
|
|
|
} |
911
|
30
|
|
|
|
|
96
|
$data = $self->start_tag('a', $link_attr) . $data . $self->end_tag('a'); |
912
|
|
|
|
|
|
|
} |
913
|
30
|
|
|
|
|
79
|
return $data; |
914
|
|
|
|
|
|
|
} |
915
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
# |
917
|
|
|
|
|
|
|
# Format the given data item using formatting field attributes (e.g. format, |
918
|
|
|
|
|
|
|
# link, escape etc.) |
919
|
|
|
|
|
|
|
# |
920
|
|
|
|
|
|
|
sub cell_format |
921
|
|
|
|
|
|
|
{ |
922
|
1630
|
|
|
1630
|
0
|
1953
|
my $self = shift; |
923
|
1630
|
|
|
|
|
2415
|
my ($data, $fattr, $row, $field) = @_; |
924
|
1630
|
|
|
|
|
2139
|
my $defn = $self->{defn_t}; |
925
|
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
# Trim |
927
|
1630
|
100
|
100
|
|
|
7492
|
$data =~ s/^\s*(.*?)\s*$/$1/ if $data ne '' && $defn->{trim}; |
928
|
|
|
|
|
|
|
|
929
|
1630
|
|
|
|
|
2001
|
my $data_unformatted = $data; |
930
|
|
|
|
|
|
|
|
931
|
|
|
|
|
|
|
# 'escape' boolean for simple tag escaping (defaults to on) |
932
|
1630
|
50
|
33
|
|
|
10363
|
$data = $self->cell_format_escape($data) |
|
|
|
66
|
|
|
|
|
933
|
|
|
|
|
|
|
if $data ne '' && ($fattr->{escape} || ! exists $fattr->{escape}); |
934
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
# 'format' subroutine or sprintf pattern |
936
|
1630
|
100
|
|
|
|
3573
|
$data = $self->cell_format_format(@_) |
937
|
|
|
|
|
|
|
if $fattr->{format}; |
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
# 'link' subroutine or sprintf pattern |
940
|
1630
|
100
|
100
|
|
|
6348
|
$data = $self->cell_format_link($data, $fattr, $row, $field, $data_unformatted) |
941
|
|
|
|
|
|
|
if $data ne '' && $fattr->{link}; |
942
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
# 'null' defaults |
944
|
1630
|
100
|
100
|
|
|
4429
|
$data = $defn->{null} |
945
|
|
|
|
|
|
|
if defined $defn->{null} && $data eq ''; |
946
|
|
|
|
|
|
|
|
947
|
1630
|
|
|
|
|
3287
|
return $data; |
948
|
|
|
|
|
|
|
} |
949
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
sub label |
951
|
|
|
|
|
|
|
{ |
952
|
391
|
|
|
391
|
1
|
1283
|
my ($self, $label, $field) = @_; |
953
|
|
|
|
|
|
|
|
954
|
|
|
|
|
|
|
# Use first label if arrayref |
955
|
391
|
|
|
|
|
421
|
my $l; |
956
|
391
|
100
|
|
|
|
800
|
if (ref $label eq 'CODE') { |
957
|
2
|
|
|
|
|
7
|
$l = $label->($field); |
958
|
|
|
|
|
|
|
} |
959
|
|
|
|
|
|
|
else { |
960
|
389
|
|
|
|
|
526
|
$l = $label; |
961
|
|
|
|
|
|
|
} |
962
|
391
|
100
|
|
|
|
1273
|
$l = $self->derive_label($field) unless defined $l; |
963
|
391
|
100
|
66
|
|
|
1110
|
$l = $self->{defn_t}->{null} if $l eq '' && defined $self->{defn_t}->{null}; |
964
|
391
|
|
|
|
|
1706
|
return $l; |
965
|
|
|
|
|
|
|
} |
966
|
|
|
|
|
|
|
|
967
|
|
|
|
|
|
|
# |
968
|
|
|
|
|
|
|
# Add in any extra (conditional) defaults for this field. |
969
|
|
|
|
|
|
|
# Provided for subclassing. |
970
|
|
|
|
|
|
|
# |
971
|
|
|
|
|
|
|
sub cell_merge_extras |
972
|
|
|
|
|
|
|
{ |
973
|
391
|
|
|
391
|
0
|
769
|
return (); |
974
|
|
|
|
|
|
|
} |
975
|
|
|
|
|
|
|
|
976
|
|
|
|
|
|
|
# |
977
|
|
|
|
|
|
|
# Split field attr data into label, tfoot, and data buckets |
978
|
|
|
|
|
|
|
sub cell_split_label_tfoot_data { |
979
|
391
|
|
|
391
|
0
|
559
|
my ($self, $fattr, $field) = @_; |
980
|
|
|
|
|
|
|
|
981
|
391
|
|
50
|
|
|
2107
|
$self->{defn_t}->{label_attr}->{$field} ||= {}; |
982
|
391
|
|
50
|
|
|
1782
|
$self->{defn_t}->{tfoot_attr}->{$field} ||= {}; |
983
|
391
|
|
50
|
|
|
1699
|
$self->{defn_t}->{data_attr}->{$field} ||= {}; |
984
|
|
|
|
|
|
|
|
985
|
391
|
|
|
|
|
1144
|
for (keys %$fattr) { |
986
|
202
|
100
|
|
|
|
608
|
if (substr($_,0,6) eq 'label_') { |
|
|
100
|
|
|
|
|
|
987
|
52
|
|
|
|
|
209
|
$self->{defn_t}->{label_attr}->{$field}->{substr($_,6)} = $fattr->{$_}; |
988
|
|
|
|
|
|
|
} |
989
|
|
|
|
|
|
|
elsif (substr($_,0,6) eq 'tfoot_') { |
990
|
18
|
|
|
|
|
51
|
$self->{defn_t}->{tfoot_attr}->{$field}->{substr($_,6)} = $fattr->{$_}; |
991
|
|
|
|
|
|
|
} |
992
|
|
|
|
|
|
|
else { |
993
|
132
|
|
|
|
|
472
|
$self->{defn_t}->{data_attr}->{$field}->{$_} = $fattr->{$_}; |
994
|
|
|
|
|
|
|
} |
995
|
|
|
|
|
|
|
} |
996
|
391
|
|
|
|
|
1264
|
$self->{defn_t}->{label_attr}->{$field}->{value} = $self->label(delete $fattr->{label}, $field); |
997
|
|
|
|
|
|
|
} |
998
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
# |
1000
|
|
|
|
|
|
|
# Create tx_attr for each attr bucket by removing attributes in $field_attr |
1001
|
|
|
|
|
|
|
# |
1002
|
|
|
|
|
|
|
sub cell_split_out_tx_attr { |
1003
|
391
|
|
|
391
|
0
|
571
|
my ($self, $field) = @_; |
1004
|
|
|
|
|
|
|
|
1005
|
391
|
|
|
|
|
637
|
for my $attr (qw(label_attr tfoot_attr data_attr)) { |
1006
|
1173
|
|
|
|
|
1373
|
my %tx_attr = %{ $self->{defn_t}->{$attr}->{$field} }; |
|
1173
|
|
|
|
|
5535
|
|
1007
|
1173
|
|
|
|
|
1655
|
my $tx_code = 0; |
1008
|
1173
|
|
|
|
|
2712
|
for (keys %tx_attr) { |
1009
|
593
|
100
|
|
|
|
1745
|
delete $tx_attr{$_} if exists $self->{field_attr}->{$_}; |
1010
|
593
|
100
|
|
|
|
1334
|
delete $tx_attr{$_} if m/^link_/; |
1011
|
593
|
100
|
|
|
|
1827
|
$tx_code = 1 if ref $tx_attr{$_} eq 'CODE'; |
1012
|
|
|
|
|
|
|
} |
1013
|
1173
|
|
|
|
|
3236
|
$self->{defn_t}->{$attr}->{$field}->{tx_attr} = \%tx_attr; |
1014
|
1173
|
|
|
|
|
3899
|
$self->{defn_t}->{$attr}->{$field}->{tx_code} = $tx_code; |
1015
|
|
|
|
|
|
|
} |
1016
|
|
|
|
|
|
|
} |
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
# |
1019
|
|
|
|
|
|
|
# Merge default and field attributes once each per-field for labels and data |
1020
|
|
|
|
|
|
|
# |
1021
|
|
|
|
|
|
|
sub cell_merge_defaults |
1022
|
|
|
|
|
|
|
{ |
1023
|
391
|
|
|
391
|
0
|
550
|
my ($self, $row, $field) = @_; |
1024
|
|
|
|
|
|
|
|
1025
|
391
|
50
|
|
|
|
1047
|
return if $self->{defn_t}->{data_attr}->{$field}; |
1026
|
|
|
|
|
|
|
|
1027
|
|
|
|
|
|
|
# Create a temp $fattr hash merging defaults, regexes, and field attrs |
1028
|
391
|
|
|
|
|
483
|
my $fattr = { %{$self->{defn_t}->{field_attr}->{-defaults}}, |
|
391
|
|
|
|
|
2267
|
|
1029
|
|
|
|
|
|
|
$self->cell_merge_extras($row, $field) }; |
1030
|
391
|
|
|
|
|
560
|
for my $regex (sort keys %{$self->{defn_t}->{field_attr}->{-regex}}) { |
|
391
|
|
|
|
|
1569
|
|
1031
|
15
|
100
|
|
|
|
163
|
next unless $field =~ $regex; |
1032
|
6
|
|
|
|
|
31
|
@$fattr{ keys %{$self->{defn_t}->{field_attr}->{-regex}->{$regex}} } = |
|
6
|
|
|
|
|
21
|
|
1033
|
6
|
|
|
|
|
8
|
values %{$self->{defn_t}->{field_attr}->{-regex}->{$regex}}; |
1034
|
|
|
|
|
|
|
} |
1035
|
391
|
|
|
|
|
1032
|
@$fattr{ keys %{$self->{defn_t}->{field_attr}->{$field}} } = |
|
391
|
|
|
|
|
1242
|
|
1036
|
391
|
|
|
|
|
892
|
values %{$self->{defn_t}->{field_attr}->{$field}}; |
1037
|
|
|
|
|
|
|
|
1038
|
|
|
|
|
|
|
# Split out label, data, and tfoot attributes |
1039
|
391
|
|
|
|
|
1116
|
$self->cell_split_label_tfoot_data($fattr, $field); |
1040
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
# Remove tx_attr for label, data, and tfoot attr buckets |
1042
|
391
|
|
|
|
|
977
|
$self->cell_split_out_tx_attr($field); |
1043
|
|
|
|
|
|
|
} |
1044
|
|
|
|
|
|
|
|
1045
|
|
|
|
|
|
|
# |
1046
|
|
|
|
|
|
|
# Set and format the data for a single (data) cell or item |
1047
|
|
|
|
|
|
|
# |
1048
|
|
|
|
|
|
|
sub cell_value |
1049
|
|
|
|
|
|
|
{ |
1050
|
1666
|
|
|
1666
|
0
|
1929
|
my $self = shift; |
1051
|
1666
|
|
|
|
|
2449
|
my ($row, $field, $fattr) = @_; |
1052
|
1666
|
|
|
|
|
2337
|
my $defn = $self->{defn_t}; |
1053
|
1666
|
|
|
|
|
1568
|
my $value; |
1054
|
|
|
|
|
|
|
|
1055
|
|
|
|
|
|
|
# 'value' literal takes precedence over row |
1056
|
1666
|
100
|
100
|
|
|
11016
|
if (exists $fattr->{value} && ! ref $fattr->{value}) { |
|
|
100
|
66
|
|
|
|
|
1057
|
175
|
50
|
|
|
|
433
|
$value = defined $fattr->{value} ? $fattr->{value} : ''; |
1058
|
|
|
|
|
|
|
} |
1059
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
# Get value from $row (but skip 'derived' fields) |
1061
|
|
|
|
|
|
|
elsif (ref $row && ! $fattr->{derived}) { |
1062
|
1482
|
50
|
66
|
|
|
6414
|
if (blessed $row) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
1063
|
|
|
|
|
|
|
# Field-methods e.g. Class::DBI, DBIx::Class |
1064
|
0
|
0
|
0
|
|
|
0
|
if (eval { $row->can($field) } |
|
0
|
0
|
|
|
|
0
|
|
1065
|
|
|
|
|
|
|
&& $field ne 'delete') { # special DBIx::Class protection :-) |
1066
|
0
|
|
|
|
|
0
|
$value = eval { $row->$field() }; |
|
0
|
|
|
|
|
0
|
|
1067
|
|
|
|
|
|
|
} |
1068
|
|
|
|
|
|
|
# For DBIx::Class we need to check both methods and get_column() values, |
1069
|
|
|
|
|
|
|
# since joined fields (+columns/+select) are only available via the latter |
1070
|
0
|
|
|
|
|
0
|
elsif (eval { $row->can('get_column') }) { |
1071
|
0
|
|
|
|
|
0
|
$value = eval { $row->get_column($field) }; |
|
0
|
|
|
|
|
0
|
|
1072
|
|
|
|
|
|
|
} |
1073
|
|
|
|
|
|
|
} |
1074
|
|
|
|
|
|
|
elsif (ref $row eq 'ARRAY') { |
1075
|
1149
|
50
|
|
|
|
1111
|
my $i = keys %{$defn->{field_map}} ? $defn->{field_map}->{$field} : $field; |
|
1149
|
|
|
|
|
3490
|
|
1076
|
1149
|
100
|
|
|
|
3611
|
$value = $row->[ $i ] if defined $i; |
1077
|
|
|
|
|
|
|
} |
1078
|
|
|
|
|
|
|
elsif (ref $row eq 'HASH' && exists $row->{$field}) { |
1079
|
295
|
|
|
|
|
517
|
$value = $row->{$field}; |
1080
|
|
|
|
|
|
|
} |
1081
|
|
|
|
|
|
|
} |
1082
|
|
|
|
|
|
|
|
1083
|
|
|
|
|
|
|
# Handle 'value' subref |
1084
|
1666
|
100
|
100
|
|
|
4629
|
if (exists $fattr->{value} && ref $fattr->{value}) { |
1085
|
37
|
|
|
|
|
66
|
my $ref = ref $fattr->{value}; |
1086
|
37
|
50
|
|
|
|
72
|
if ($ref eq 'CODE') { |
1087
|
37
|
|
|
|
|
114
|
$value = $fattr->{value}->($value, $row, $field); |
1088
|
|
|
|
|
|
|
} |
1089
|
|
|
|
|
|
|
else { |
1090
|
0
|
|
|
|
|
0
|
croak "[cell_value] invalid '$field' value (not scalar or code ref): $ref"; |
1091
|
|
|
|
|
|
|
}; |
1092
|
|
|
|
|
|
|
} |
1093
|
|
|
|
|
|
|
|
1094
|
1666
|
50
|
66
|
|
|
4028
|
$value = $fattr->{default} if ! defined $value && exists $fattr->{default}; |
1095
|
|
|
|
|
|
|
|
1096
|
1666
|
100
|
|
|
|
5382
|
return defined $value ? $value : ''; |
1097
|
|
|
|
|
|
|
} |
1098
|
|
|
|
|
|
|
|
1099
|
|
|
|
|
|
|
# |
1100
|
|
|
|
|
|
|
# Return a cell value created from one or more other cells |
1101
|
|
|
|
|
|
|
# |
1102
|
|
|
|
|
|
|
sub cell_composite |
1103
|
|
|
|
|
|
|
{ |
1104
|
6
|
|
|
6
|
0
|
10
|
my $self = shift; |
1105
|
6
|
|
|
|
|
10
|
my ($row, $field, $fattr) = @_; |
1106
|
|
|
|
|
|
|
|
1107
|
6
|
50
|
|
|
|
18
|
my $composite = $fattr->{composite} |
1108
|
|
|
|
|
|
|
or die "Missing composite field attribute"; |
1109
|
6
|
|
|
|
|
8
|
my @composite = (); |
1110
|
6
|
|
|
|
|
12
|
for my $f (@$composite) { |
1111
|
12
|
|
|
|
|
34
|
push @composite, $self->cell_single(row => $row, field => $f, tags => 0); |
1112
|
|
|
|
|
|
|
} |
1113
|
|
|
|
|
|
|
|
1114
|
6
|
|
50
|
|
|
30
|
my $composite_join = $fattr->{composite_join} || ' '; |
1115
|
6
|
50
|
|
|
|
14
|
if (ref $composite_join eq 'CODE') { |
1116
|
0
|
|
|
|
|
0
|
return $composite_join->(\@composite, $row, $field); |
1117
|
|
|
|
|
|
|
} |
1118
|
|
|
|
|
|
|
else { |
1119
|
6
|
|
|
|
|
26
|
return join ' ', @composite; |
1120
|
|
|
|
|
|
|
} |
1121
|
|
|
|
|
|
|
} |
1122
|
|
|
|
|
|
|
|
1123
|
|
|
|
|
|
|
# |
1124
|
|
|
|
|
|
|
# Set and format the data for a single (data) cell or item |
1125
|
|
|
|
|
|
|
# |
1126
|
|
|
|
|
|
|
sub cell_content |
1127
|
|
|
|
|
|
|
{ |
1128
|
1630
|
|
|
1630
|
0
|
1940
|
my $self = shift; |
1129
|
1630
|
|
|
|
|
2438
|
my ($row, $field, $fattr) = @_; |
1130
|
1630
|
|
|
|
|
1672
|
my $value; |
1131
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
# Composite fields - concatenate members together |
1133
|
1630
|
100
|
|
|
|
3083
|
if ($fattr->{composite}) { |
1134
|
6
|
|
|
|
|
13
|
$value = $self->cell_composite(@_); |
1135
|
|
|
|
|
|
|
} |
1136
|
|
|
|
|
|
|
|
1137
|
|
|
|
|
|
|
# Standard field - get value from $row |
1138
|
|
|
|
|
|
|
else { |
1139
|
1624
|
|
|
|
|
3395
|
$value = $self->cell_value(@_); |
1140
|
|
|
|
|
|
|
} |
1141
|
|
|
|
|
|
|
|
1142
|
|
|
|
|
|
|
# Format |
1143
|
1630
|
|
|
|
|
3694
|
my $fvalue = $self->cell_format($value, $fattr, $row, $field); |
1144
|
|
|
|
|
|
|
|
1145
|
1630
|
50
|
|
|
|
5361
|
return wantarray ? ($fvalue, $value) : $fvalue; |
1146
|
|
|
|
|
|
|
} |
1147
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
# |
1149
|
|
|
|
|
|
|
# Wrap cell in | or | table tags |
1150
|
|
|
|
|
|
|
# |
1151
|
|
|
|
|
|
|
sub cell_tags |
1152
|
|
|
|
|
|
|
{ |
1153
|
1610
|
|
|
1610
|
0
|
14843
|
my ($self, $data, $row, $field, $tx_attr) = @_; |
1154
|
|
|
|
|
|
|
|
1155
|
1610
|
100
|
|
|
|
2836
|
my $tag = ! defined $row ? 'th' : 'td'; |
1156
|
1610
|
50
|
|
|
|
3086
|
$data = '' unless defined $data; |
1157
|
1610
|
|
|
|
|
3035
|
return $self->start_tag($tag, $tx_attr) . $data . $self->end_tag($tag); |
1158
|
|
|
|
|
|
|
} |
1159
|
|
|
|
|
|
|
|
1160
|
|
|
|
|
|
|
# |
1161
|
|
|
|
|
|
|
# Execute any th or td attribute subrefs |
1162
|
|
|
|
|
|
|
# |
1163
|
|
|
|
|
|
|
sub cell_tx_execute |
1164
|
|
|
|
|
|
|
{ |
1165
|
63
|
|
|
63
|
0
|
83
|
my $self = shift; |
1166
|
63
|
|
|
|
|
103
|
my ($tx_attr, $value, $row, $field) = @_; |
1167
|
63
|
|
|
|
|
92
|
my %tx2 = (); |
1168
|
63
|
|
|
|
|
208
|
while (my ($k,$v) = each %$tx_attr) { |
1169
|
82
|
100
|
|
|
|
313
|
if (ref $v eq 'CODE') { |
1170
|
79
|
|
|
|
|
199
|
$tx2{$k} = $v->($value, $row, $field); |
1171
|
|
|
|
|
|
|
} |
1172
|
|
|
|
|
|
|
else { |
1173
|
3
|
|
|
|
|
11
|
$tx2{$k} = $v; |
1174
|
|
|
|
|
|
|
} |
1175
|
|
|
|
|
|
|
} |
1176
|
63
|
|
|
|
|
669
|
return \%tx2; |
1177
|
|
|
|
|
|
|
} |
1178
|
|
|
|
|
|
|
|
1179
|
|
|
|
|
|
|
# |
1180
|
|
|
|
|
|
|
# Render a single table cell or item |
1181
|
|
|
|
|
|
|
# |
1182
|
|
|
|
|
|
|
sub cell_single |
1183
|
|
|
|
|
|
|
{ |
1184
|
1630
|
|
|
1630
|
0
|
5988
|
my ($self, %args) = @_; |
1185
|
1630
|
|
|
|
|
3003
|
my $row = delete $args{row}; |
1186
|
1630
|
|
|
|
|
2610
|
my $field = delete $args{field}; |
1187
|
1630
|
|
|
|
|
2311
|
my $fattr = delete $args{field_attr}; |
1188
|
1630
|
|
|
|
|
2106
|
my $tx_attr = delete $args{tx_attr}; |
1189
|
1630
|
|
|
|
|
1906
|
my $tx_attr_extra = delete $args{tx_attr_extra}; |
1190
|
1630
|
|
|
|
|
2459
|
my $skip_count = delete $args{skip_count}; |
1191
|
1630
|
|
|
|
|
2812
|
my $tags = delete $args{tags}; |
1192
|
1630
|
100
|
|
|
|
3300
|
$tags = 1 unless defined $tags; |
1193
|
1630
|
50
|
|
|
|
3136
|
die "Unknown arguments to cell_single: " . join(',', keys %args) if %args; |
1194
|
|
|
|
|
|
|
|
1195
|
|
|
|
|
|
|
# Merge default and field attributes once for each field |
1196
|
1630
|
100
|
|
|
|
5459
|
$self->cell_merge_defaults($row, $field) |
1197
|
|
|
|
|
|
|
if ! $self->{defn_t}->{data_attr}->{$field}; |
1198
|
|
|
|
|
|
|
|
1199
|
1630
|
|
|
|
|
1990
|
my $tx_code = 0; |
1200
|
1630
|
50
|
33
|
|
|
3713
|
unless ($fattr && $tx_attr) { |
1201
|
1630
|
100
|
66
|
|
|
8765
|
if (! defined $row || $row eq 'thead') { |
|
|
100
|
|
|
|
|
|
1202
|
161
|
|
|
|
|
372
|
$fattr = $self->{defn_t}->{label_attr}->{$field}; |
1203
|
|
|
|
|
|
|
} |
1204
|
|
|
|
|
|
|
elsif ($row eq 'tfoot') { |
1205
|
12
|
|
|
|
|
19
|
$fattr = $self->{defn_t}->{tfoot_attr}->{$field}; |
1206
|
|
|
|
|
|
|
} |
1207
|
|
|
|
|
|
|
else { |
1208
|
1457
|
|
|
|
|
3315
|
$fattr = $self->{defn_t}->{data_attr}->{$field}; |
1209
|
|
|
|
|
|
|
} |
1210
|
1630
|
|
|
|
|
3198
|
$tx_attr = $fattr->{tx_attr}; |
1211
|
1630
|
|
|
|
|
3114
|
$tx_code = $fattr->{tx_code}; |
1212
|
|
|
|
|
|
|
} |
1213
|
|
|
|
|
|
|
|
1214
|
|
|
|
|
|
|
# Standard (non-composite) fields |
1215
|
1630
|
|
|
|
|
3269
|
my ($fvalue, $value) = $self->cell_content($row, $field, $fattr); |
1216
|
|
|
|
|
|
|
|
1217
|
|
|
|
|
|
|
# If $tx_attr includes coderefs, execute them |
1218
|
1630
|
100
|
|
|
|
3538
|
$tx_attr = $self->cell_tx_execute($tx_attr, $value, $row, $field) |
1219
|
|
|
|
|
|
|
if $tx_code; |
1220
|
|
|
|
|
|
|
|
1221
|
1630
|
|
|
|
|
1909
|
my $tx_attr_merged = $tx_attr; |
1222
|
1630
|
100
|
66
|
|
|
3407
|
$tx_attr_merged = { %$tx_attr, %{$tx_attr_extra->{$field}} } |
|
3
|
|
|
|
|
12
|
|
1223
|
|
|
|
|
|
|
if $tx_attr_extra && $tx_attr_extra->{$field}; |
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
# Generate tags |
1226
|
1630
|
100
|
|
|
|
4088
|
my $cell = $tags ? $self->cell_tags($fvalue, $row, $field, $tx_attr_merged) : $fvalue; |
1227
|
|
|
|
|
|
|
|
1228
|
1630
|
100
|
66
|
|
|
13196
|
$$skip_count = $tx_attr->{colspan} ? ($tx_attr->{colspan}-1) : 0 |
|
|
50
|
66
|
|
|
|
|
1229
|
|
|
|
|
|
|
if $skip_count && ref $skip_count && ref $skip_count eq 'SCALAR'; |
1230
|
|
|
|
|
|
|
|
1231
|
1630
|
|
|
|
|
6650
|
return $cell; |
1232
|
|
|
|
|
|
|
} |
1233
|
|
|
|
|
|
|
|
1234
|
|
|
|
|
|
|
# |
1235
|
|
|
|
|
|
|
# Legacy interface (deprecated) |
1236
|
|
|
|
|
|
|
# |
1237
|
|
|
|
|
|
|
sub cell_wantarray |
1238
|
|
|
|
|
|
|
{ |
1239
|
0
|
|
|
0
|
0
|
0
|
my ($self, $row, $field, $fattr, $tx_attr, %opts) = @_; |
1240
|
|
|
|
|
|
|
|
1241
|
0
|
|
|
|
|
0
|
my $skip_count; |
1242
|
0
|
|
|
|
|
0
|
my $cell = $self->cell_single( |
1243
|
|
|
|
|
|
|
%opts, |
1244
|
|
|
|
|
|
|
row => $row, |
1245
|
|
|
|
|
|
|
field => $field, |
1246
|
|
|
|
|
|
|
field_attr => $fattr, |
1247
|
|
|
|
|
|
|
tx_attr => $tx_attr, |
1248
|
|
|
|
|
|
|
skip_count => \$skip_count, |
1249
|
|
|
|
|
|
|
); |
1250
|
|
|
|
|
|
|
|
1251
|
0
|
|
|
|
|
0
|
return ($cell, $skip_count); |
1252
|
|
|
|
|
|
|
} |
1253
|
|
|
|
|
|
|
|
1254
|
|
|
|
|
|
|
# |
1255
|
|
|
|
|
|
|
# Render a single table cell (legacy interface) |
1256
|
|
|
|
|
|
|
# |
1257
|
|
|
|
|
|
|
sub cell |
1258
|
|
|
|
|
|
|
{ |
1259
|
0
|
|
|
0
|
0
|
0
|
my ($self, $row, $field, $fattr, $tx_attr, %opts) = @_; |
1260
|
|
|
|
|
|
|
|
1261
|
0
|
|
|
|
|
0
|
$self->cell_single( |
1262
|
|
|
|
|
|
|
%opts, |
1263
|
|
|
|
|
|
|
row => $row, |
1264
|
|
|
|
|
|
|
field => $field, |
1265
|
|
|
|
|
|
|
field_attr => $fattr, |
1266
|
|
|
|
|
|
|
tx_attr => $tx_attr, |
1267
|
|
|
|
|
|
|
); |
1268
|
|
|
|
|
|
|
} |
1269
|
|
|
|
|
|
|
|
1270
|
|
|
|
|
|
|
# |
1271
|
|
|
|
|
|
|
# Modify the $tr hashref for striping. If $type is 'SCALAR', the stripe is |
1272
|
|
|
|
|
|
|
# a HTML colour string for a bgcolor attribute for the relevant row; if |
1273
|
|
|
|
|
|
|
# $type is 'HASH' the stripe is a set of attributes to be merged. |
1274
|
|
|
|
|
|
|
# $stripe has already been coerced to an arrayref if something else. |
1275
|
|
|
|
|
|
|
# |
1276
|
|
|
|
|
|
|
sub stripe |
1277
|
|
|
|
|
|
|
{ |
1278
|
459
|
|
|
459
|
1
|
665
|
my ($self, $tr, $rownum) = @_; |
1279
|
459
|
|
|
|
|
790
|
my $stripe = $self->{defn_t}->{stripe}; |
1280
|
459
|
100
|
|
|
|
2066
|
return $tr unless $stripe; |
1281
|
|
|
|
|
|
|
|
1282
|
31
|
|
|
|
|
72
|
my $r = int($rownum % scalar(@$stripe)) - 1; |
1283
|
31
|
100
|
|
|
|
77
|
if (defined $stripe->[$r]) { |
1284
|
22
|
100
|
|
|
|
135
|
if (! ref $stripe->[$r]) { |
|
|
50
|
|
|
|
|
|
1285
|
|
|
|
|
|
|
# Set bgcolor to stripe (exception: header where bgcolor already set) |
1286
|
13
|
100
|
100
|
|
|
74
|
$tr->{bgcolor} = $stripe->[$r] |
1287
|
|
|
|
|
|
|
unless $rownum == 0 && exists $tr->{bgcolor}; |
1288
|
|
|
|
|
|
|
} |
1289
|
|
|
|
|
|
|
elsif (ref $stripe->[$r] eq 'HASH') { |
1290
|
|
|
|
|
|
|
# Class attributes are special in that they're additive, |
1291
|
|
|
|
|
|
|
# so we can merge instead of overwriting |
1292
|
9
|
100
|
66
|
|
|
65
|
if ($stripe->[$r]->{class} && $tr->{class}) { |
|
|
100
|
|
|
|
|
|
1293
|
5
|
|
|
|
|
18
|
$tr->{class} = "$stripe->[$r]->{class} $tr->{class}"; |
1294
|
|
|
|
|
|
|
} |
1295
|
|
|
|
|
|
|
|
1296
|
|
|
|
|
|
|
# Existing attributes take precedence over stripe ones for header |
1297
|
|
|
|
|
|
|
elsif ($rownum == 0) { |
1298
|
1
|
|
|
|
|
2
|
for (keys %{$stripe->[$r]}) { |
|
1
|
|
|
|
|
5
|
|
1299
|
1
|
50
|
|
|
|
7
|
$tr->{$_} = $stripe->[$r]->{$_} unless exists $tr->{$_}; |
1300
|
|
|
|
|
|
|
} |
1301
|
|
|
|
|
|
|
} |
1302
|
|
|
|
|
|
|
|
1303
|
|
|
|
|
|
|
# For non-header rows, merge attributes straight into $tr |
1304
|
|
|
|
|
|
|
else { |
1305
|
3
|
|
|
|
|
4
|
@$tr{keys %{$stripe->[$r]}} = values %{$stripe->[$r]}; |
|
3
|
|
|
|
|
10
|
|
|
3
|
|
|
|
|
8
|
|
1306
|
|
|
|
|
|
|
} |
1307
|
|
|
|
|
|
|
} |
1308
|
|
|
|
|
|
|
# Else silently ignore |
1309
|
|
|
|
|
|
|
} |
1310
|
31
|
|
|
|
|
118
|
return $tr; |
1311
|
|
|
|
|
|
|
} |
1312
|
|
|
|
|
|
|
|
1313
|
|
|
|
|
|
|
# |
1314
|
|
|
|
|
|
|
# Return tbody close and/or open tags if appropriate, '' otherwise |
1315
|
|
|
|
|
|
|
# |
1316
|
|
|
|
|
|
|
sub tbody |
1317
|
|
|
|
|
|
|
{ |
1318
|
407
|
|
|
407
|
1
|
479
|
my $self = shift; |
1319
|
407
|
|
|
|
|
525
|
my ($row, $rownum) = @_; |
1320
|
407
|
|
|
|
|
469
|
my $generate = 0; |
1321
|
|
|
|
|
|
|
|
1322
|
407
|
100
|
|
|
|
1457
|
return '' unless $self->{defn_t}->{tbody}; |
1323
|
|
|
|
|
|
|
|
1324
|
|
|
|
|
|
|
# Scalar tbody - generate once only |
1325
|
121
|
100
|
|
|
|
477
|
if (! ref $self->{defn_t}->{tbody}) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
1326
|
36
|
100
|
|
|
|
90
|
$generate++ if ! $self->{defn_t}->{tbody_open}; |
1327
|
|
|
|
|
|
|
} |
1328
|
|
|
|
|
|
|
|
1329
|
|
|
|
|
|
|
# tbody with -field - generate when field value changes |
1330
|
|
|
|
|
|
|
elsif ($self->{defn_t}->{tbody}->{'-field'}) { |
1331
|
18
|
|
|
|
|
47
|
my $value = $self->cell_value($row, $self->{defn_t}->{tbody}->{'-field'}); |
1332
|
18
|
100
|
|
|
|
49
|
if (exists $self->{defn_t}->{tbody_field_value}) { |
1333
|
15
|
100
|
33
|
|
|
68
|
if ($value eq $self->{defn_t}->{tbody_field_value} || |
|
|
|
66
|
|
|
|
|
1334
|
|
|
|
|
|
|
(! defined $value && |
1335
|
|
|
|
|
|
|
! defined $self->{defn_t}->{tbody_field_value})) { |
1336
|
8
|
|
|
|
|
19
|
return ''; |
1337
|
|
|
|
|
|
|
} |
1338
|
|
|
|
|
|
|
else { |
1339
|
7
|
|
|
|
|
11
|
$generate++; |
1340
|
|
|
|
|
|
|
} |
1341
|
|
|
|
|
|
|
} |
1342
|
|
|
|
|
|
|
else { |
1343
|
3
|
|
|
|
|
7
|
$generate++; |
1344
|
|
|
|
|
|
|
} |
1345
|
10
|
|
|
|
|
20
|
$self->{defn_t}->{tbody_field_value} = $value; |
1346
|
|
|
|
|
|
|
} |
1347
|
|
|
|
|
|
|
|
1348
|
|
|
|
|
|
|
# tbody with -rows - generate when $rownum == $r ** n + 1 |
1349
|
|
|
|
|
|
|
elsif (my $r = $self->{defn_t}->{tbody}->{'-rows'}) { |
1350
|
54
|
100
|
|
|
|
199
|
$generate++ if int(($rownum-1) % $r) == 0; |
1351
|
|
|
|
|
|
|
} |
1352
|
|
|
|
|
|
|
|
1353
|
|
|
|
|
|
|
# else a hashref - treat like a scalar |
1354
|
|
|
|
|
|
|
else { |
1355
|
13
|
100
|
|
|
|
35
|
$generate++ if ! $self->{defn_t}->{tbody_open}; |
1356
|
|
|
|
|
|
|
} |
1357
|
|
|
|
|
|
|
|
1358
|
113
|
|
|
|
|
148
|
my $tbody = ''; |
1359
|
113
|
100
|
|
|
|
223
|
if ($generate) { |
1360
|
50
|
100
|
|
|
|
158
|
if ($self->{defn_t}->{tbody_open}) { |
1361
|
27
|
|
|
|
|
56
|
$tbody .= $self->end_tag('tbody') . "\n"; |
1362
|
|
|
|
|
|
|
} |
1363
|
50
|
|
|
|
|
137
|
$tbody .= $self->start_tag('tbody', $self->{defn_t}->{tbody_attr}) . "\n"; |
1364
|
50
|
|
|
|
|
125
|
$self->{defn_t}->{tbody_open} = 1; |
1365
|
|
|
|
|
|
|
} |
1366
|
113
|
|
|
|
|
228
|
return $tbody; |
1367
|
|
|
|
|
|
|
} |
1368
|
|
|
|
|
|
|
|
1369
|
|
|
|
|
|
|
# |
1370
|
|
|
|
|
|
|
# Return an attribute hash for table rows |
1371
|
|
|
|
|
|
|
# |
1372
|
|
|
|
|
|
|
sub tr_attr |
1373
|
|
|
|
|
|
|
{ |
1374
|
459
|
|
|
459
|
0
|
843
|
my ($self, $rownum, $row, $dataset) = @_; |
1375
|
459
|
|
|
|
|
751
|
my $defn_t = $self->{defn_t}; |
1376
|
459
|
|
|
|
|
714
|
my $tr = undef; |
1377
|
459
|
100
|
|
|
|
846
|
if ($rownum == 0) { |
1378
|
38
|
100
|
|
|
|
141
|
$tr = $defn_t->{thtr} if $defn_t->{thtr}; |
1379
|
38
|
|
66
|
|
|
275
|
$tr ||= $self->deepcopy($defn_t->{tr_base}); |
1380
|
|
|
|
|
|
|
} |
1381
|
|
|
|
|
|
|
else { |
1382
|
421
|
100
|
66
|
|
|
1396
|
if (ref $defn_t->{tr} eq 'CODE' && $row) { |
1383
|
3
|
|
|
|
|
15
|
$tr = $defn_t->{tr}->($row, $dataset); |
1384
|
|
|
|
|
|
|
} |
1385
|
|
|
|
|
|
|
else { |
1386
|
418
|
100
|
|
|
|
1144
|
$defn_t->{tr} = {} unless ref $defn_t->{tr} eq 'HASH'; |
1387
|
418
|
|
|
|
|
1107
|
$tr = $self->deepcopy($defn_t->{tr}); |
1388
|
|
|
|
|
|
|
# Evaluate any code attributes |
1389
|
418
|
|
50
|
|
|
910
|
$tr ||= {}; |
1390
|
418
|
|
|
|
|
2351
|
while (my ($k,$v) = each %$tr) { |
1391
|
29
|
100
|
|
|
|
126
|
$tr->{$k} = $v->($row, $dataset) if ref $v eq 'CODE'; |
1392
|
|
|
|
|
|
|
} |
1393
|
|
|
|
|
|
|
} |
1394
|
|
|
|
|
|
|
} |
1395
|
|
|
|
|
|
|
# Stripe and return |
1396
|
459
|
|
|
|
|
1268
|
return $self->stripe($tr, $rownum); |
1397
|
|
|
|
|
|
|
} |
1398
|
|
|
|
|
|
|
|
1399
|
|
|
|
|
|
|
# |
1400
|
|
|
|
|
|
|
# Render a single table row (style 'down') |
1401
|
|
|
|
|
|
|
# |
1402
|
|
|
|
|
|
|
sub row_down |
1403
|
|
|
|
|
|
|
{ |
1404
|
451
|
|
|
451
|
0
|
815
|
my ($self, $row, $rownum, %args) = @_; |
1405
|
451
|
|
|
|
|
655
|
my $fields = delete $args{fields}; |
1406
|
451
|
|
66
|
|
|
2026
|
$fields ||= $self->{defn_t}->{fields}; |
1407
|
451
|
|
|
|
|
751
|
my $tx_attr_extra = delete $args{tx_attr_extra}; |
1408
|
451
|
100
|
|
|
|
951
|
my %tx_attr_extra = $tx_attr_extra ? ( tx_attr_extra => $tx_attr_extra ) : (); |
1409
|
|
|
|
|
|
|
|
1410
|
|
|
|
|
|
|
# Open tr |
1411
|
451
|
|
|
|
|
541
|
my $out = ''; |
1412
|
451
|
|
|
|
|
1048
|
$out .= $self->start_tag('tr', $self->tr_attr($rownum, $row)); |
1413
|
|
|
|
|
|
|
|
1414
|
|
|
|
|
|
|
# Render cells |
1415
|
451
|
|
|
|
|
953
|
my @cells = (); |
1416
|
451
|
|
|
|
|
555
|
my $skip_count = 0; |
1417
|
451
|
|
|
|
|
1045
|
for my $f (@$fields) { |
1418
|
1588
|
100
|
|
|
|
3152
|
if ($skip_count > 0) { |
1419
|
10
|
|
|
|
|
12
|
$skip_count--; |
1420
|
10
|
|
|
|
|
15
|
next; |
1421
|
|
|
|
|
|
|
} |
1422
|
|
|
|
|
|
|
|
1423
|
1578
|
100
|
|
|
|
2579
|
if (! $row) { |
1424
|
145
|
|
|
|
|
480
|
$out .= $self->cell_single(field => $f, skip_count => \$skip_count, %tx_attr_extra); |
1425
|
|
|
|
|
|
|
} |
1426
|
|
|
|
|
|
|
else { |
1427
|
1433
|
|
|
|
|
3760
|
$out .= $self->cell_single(row => $row, field => $f, skip_count => \$skip_count, , %tx_attr_extra); |
1428
|
|
|
|
|
|
|
} |
1429
|
|
|
|
|
|
|
} |
1430
|
|
|
|
|
|
|
|
1431
|
451
|
|
|
|
|
1070
|
$out .= $self->end_tag('tr') . "\n"; |
1432
|
451
|
|
|
|
|
1845
|
return $out; |
1433
|
|
|
|
|
|
|
} |
1434
|
|
|
|
|
|
|
|
1435
|
|
|
|
|
|
|
# |
1436
|
|
|
|
|
|
|
# Return a generalised iterator function to walk the set, returning undef at eod |
1437
|
|
|
|
|
|
|
# |
1438
|
|
|
|
|
|
|
sub data_iterator |
1439
|
|
|
|
|
|
|
{ |
1440
|
105
|
|
|
105
|
0
|
188
|
my ($self, $set, $fields) = @_; |
1441
|
105
|
|
|
|
|
238
|
my $row = 0; |
1442
|
|
|
|
|
|
|
|
1443
|
105
|
50
|
|
|
|
287
|
croak "invalid Tabulate data type '$set'" unless ref $set; |
1444
|
105
|
50
|
33
|
|
|
1361
|
if (ref $set eq 'CODE') { |
|
|
50
|
33
|
|
|
|
|
|
|
50
|
33
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
1445
|
|
|
|
|
|
|
return sub { |
1446
|
0
|
0
|
0
|
0
|
|
0
|
$row = $row ? $set->() : ($self->{prefetch} || $set->()); |
1447
|
0
|
|
|
|
|
0
|
}; |
1448
|
|
|
|
|
|
|
} |
1449
|
|
|
|
|
|
|
elsif (blessed $set and $set->can('Next')) { |
1450
|
|
|
|
|
|
|
return sub { |
1451
|
0
|
0
|
0
|
0
|
|
0
|
$row = $row ? $set->Next : ($self->{prefetch} || eval { $set->First } || $set->Next); |
1452
|
0
|
|
|
|
|
0
|
}; |
1453
|
|
|
|
|
|
|
} |
1454
|
|
|
|
|
|
|
elsif (blessed $set and $set->can('next')) { |
1455
|
|
|
|
|
|
|
return sub { |
1456
|
0
|
0
|
0
|
0
|
|
0
|
$row = $row ? $set->next : ($self->{prefetch} || eval { $set->first } || $set->next); |
1457
|
0
|
|
|
|
|
0
|
}; |
1458
|
|
|
|
|
|
|
} |
1459
|
|
|
|
|
|
|
elsif (ref $set eq 'ARRAY') { |
1460
|
|
|
|
|
|
|
return sub { |
1461
|
489
|
100
|
|
489
|
|
1926
|
return undef if $row > $#$set; |
1462
|
390
|
|
|
|
|
1394
|
$set->[$row++]; |
1463
|
99
|
|
|
|
|
739
|
}; |
1464
|
|
|
|
|
|
|
} |
1465
|
0
|
|
|
|
|
0
|
elsif (ref $set eq 'HASH' || eval { keys %$set }) { |
1466
|
|
|
|
|
|
|
# Check first value - drill down further unless non-reference |
1467
|
6
|
|
33
|
|
|
34
|
my $k = $fields->[0] || (sort keys %$set)[0]; |
1468
|
|
|
|
|
|
|
# For hashes of scalars, just return the hash once-only |
1469
|
6
|
50
|
|
|
|
24
|
if (! ref $set->{$k}) { |
1470
|
|
|
|
|
|
|
return sub { |
1471
|
12
|
100
|
|
12
|
|
46
|
return undef if $row++; |
1472
|
6
|
|
|
|
|
22
|
$set; |
1473
|
6
|
|
|
|
|
47
|
}; |
1474
|
|
|
|
|
|
|
} |
1475
|
|
|
|
|
|
|
# For hashes of refs, return the refs in key order |
1476
|
|
|
|
|
|
|
else { |
1477
|
|
|
|
|
|
|
return sub { |
1478
|
0
|
|
|
0
|
|
0
|
my @k = sort keys %$set; |
1479
|
0
|
0
|
|
|
|
0
|
return undef if $row > $#k; |
1480
|
0
|
|
|
|
|
0
|
return $k[$row++]; |
1481
|
0
|
|
|
|
|
0
|
}; |
1482
|
|
|
|
|
|
|
} |
1483
|
|
|
|
|
|
|
} |
1484
|
|
|
|
|
|
|
else { |
1485
|
0
|
|
|
|
|
0
|
croak "invalid Tabulate data type '$set'"; |
1486
|
|
|
|
|
|
|
} |
1487
|
|
|
|
|
|
|
} |
1488
|
|
|
|
|
|
|
|
1489
|
|
|
|
|
|
|
# |
1490
|
|
|
|
|
|
|
# Render the table body with successive records down the page |
1491
|
|
|
|
|
|
|
# |
1492
|
|
|
|
|
|
|
sub body_down |
1493
|
|
|
|
|
|
|
{ |
1494
|
105
|
|
|
105
|
0
|
167
|
my ($self, $set) = @_; |
1495
|
|
|
|
|
|
|
|
1496
|
|
|
|
|
|
|
# Get data_iterator |
1497
|
105
|
50
|
|
|
|
521
|
my @fields = @{$self->{defn_t}->{fields}} |
|
105
|
|
|
|
|
332
|
|
1498
|
|
|
|
|
|
|
if ref $self->{defn_t}->{fields} eq 'ARRAY'; |
1499
|
105
|
|
|
|
|
472
|
my $data_next = $self->data_iterator($set, \@fields); |
1500
|
105
|
|
|
|
|
251
|
my $data_prepend = $self->{defn_t}->{data_prepend}; |
1501
|
|
|
|
|
|
|
|
1502
|
|
|
|
|
|
|
# Labels/headings |
1503
|
105
|
|
|
|
|
163
|
my $thead = ''; |
1504
|
105
|
100
|
66
|
|
|
1707
|
if ($self->{defn_t}->{labels} && @fields) { |
|
|
100
|
|
|
|
|
|
1505
|
37
|
100
|
|
|
|
153
|
$thead .= $self->start_tag('thead', $self->{defn_t}->{thead}) . "\n" |
1506
|
|
|
|
|
|
|
if $self->{defn_t}->{thead}; |
1507
|
|
|
|
|
|
|
|
1508
|
37
|
100
|
|
|
|
140
|
if ($self->{defn_t}->{labelgroups}) { |
1509
|
1
|
|
|
|
|
5
|
my ($fields1, $fields2, $field1_tx_attr) = $self->labelgroup_fields; |
1510
|
1
|
|
|
|
|
7
|
$thead .= $self->row_down(undef, 0, fields => $fields1, tx_attr_extra => $field1_tx_attr); |
1511
|
1
|
50
|
|
|
|
17
|
$thead .= $self->row_down(undef, 0, fields => $fields2) if @$fields2; |
1512
|
|
|
|
|
|
|
} |
1513
|
|
|
|
|
|
|
else { |
1514
|
36
|
|
|
|
|
146
|
$thead .= $self->row_down(undef, 0); |
1515
|
|
|
|
|
|
|
} |
1516
|
|
|
|
|
|
|
|
1517
|
37
|
100
|
|
|
|
176
|
if ($self->{defn_t}->{thead}) { |
1518
|
4
|
|
|
|
|
10
|
$thead .= $self->end_tag('thead') . "\n"; |
1519
|
4
|
|
|
|
|
12
|
$self->{defn_t}->{thead} = 0; |
1520
|
|
|
|
|
|
|
} |
1521
|
|
|
|
|
|
|
} |
1522
|
|
|
|
|
|
|
elsif ($self->{defn_t}->{thead}) { |
1523
|
|
|
|
|
|
|
# If thead set and labels isn't, use the first data row |
1524
|
3
|
50
|
33
|
|
|
19
|
my $row = $data_prepend && @$data_prepend ? shift @$data_prepend : $data_next->(); |
1525
|
3
|
50
|
|
|
|
10
|
if ($row) { |
1526
|
3
|
|
|
|
|
13
|
$thead .= $self->start_tag('thead', $self->{defn_t}->{thead}) . "\n"; |
1527
|
3
|
|
|
|
|
13
|
$thead .= $self->row_down($row, 1); |
1528
|
3
|
|
|
|
|
9
|
$thead .= $self->end_tag('thead') . "\n"; |
1529
|
|
|
|
|
|
|
} |
1530
|
|
|
|
|
|
|
} |
1531
|
|
|
|
|
|
|
|
1532
|
|
|
|
|
|
|
# Table body |
1533
|
105
|
|
|
|
|
199
|
my $tbody = ''; |
1534
|
105
|
|
|
|
|
158
|
my $rownum = 1; |
1535
|
105
|
100
|
100
|
|
|
336
|
if ($data_prepend && @$data_prepend) { |
1536
|
3
|
|
|
|
|
8
|
for my $row (@$data_prepend) { |
1537
|
7
|
|
|
|
|
19
|
$tbody .= $self->tbody($row, $rownum); |
1538
|
7
|
|
|
|
|
20
|
$tbody .= $self->row_down($row, $rownum); |
1539
|
7
|
|
|
|
|
16
|
$rownum++; |
1540
|
|
|
|
|
|
|
} |
1541
|
|
|
|
|
|
|
} |
1542
|
105
|
|
|
|
|
258
|
while (my $row = $data_next->()) { |
1543
|
393
|
|
|
|
|
976
|
$tbody .= $self->tbody($row, $rownum); |
1544
|
393
|
|
|
|
|
917
|
$tbody .= $self->row_down($row, $rownum); |
1545
|
393
|
|
|
|
|
1081
|
$rownum++; |
1546
|
|
|
|
|
|
|
} |
1547
|
105
|
100
|
|
|
|
535
|
if (my $data_append = $self->{defn_t}->{data_append}) { |
1548
|
4
|
|
|
|
|
7
|
for my $row (@$data_append) { |
1549
|
7
|
|
|
|
|
16
|
$tbody .= $self->tbody($row, $rownum); |
1550
|
7
|
|
|
|
|
15
|
$tbody .= $self->row_down($row, $rownum); |
1551
|
7
|
|
|
|
|
13
|
$rownum++; |
1552
|
|
|
|
|
|
|
} |
1553
|
|
|
|
|
|
|
} |
1554
|
|
|
|
|
|
|
|
1555
|
105
|
100
|
|
|
|
382
|
$tbody .= $self->end_tag('tbody') . "\n" if $self->{defn_t}->{tbody_open}; |
1556
|
|
|
|
|
|
|
|
1557
|
105
|
|
|
|
|
182
|
my $tfoot = ''; |
1558
|
105
|
100
|
|
|
|
342
|
if ($self->{defn_t}->{tfoot}) { |
1559
|
3
|
|
|
|
|
7
|
$tfoot .= $self->start_tag('tfoot', $self->{defn_t}->{tfoot}) . "\n"; |
1560
|
3
|
|
|
|
|
7
|
$tfoot .= $self->row_down('tfoot', $rownum); |
1561
|
3
|
|
|
|
|
14
|
$tfoot .= $self->end_tag('tfoot') . "\n"; |
1562
|
|
|
|
|
|
|
} |
1563
|
|
|
|
|
|
|
|
1564
|
105
|
|
|
|
|
1070
|
return $thead . $tfoot . $tbody; |
1565
|
|
|
|
|
|
|
} |
1566
|
|
|
|
|
|
|
|
1567
|
|
|
|
|
|
|
# |
1568
|
|
|
|
|
|
|
# Render a single table row (style 'across') |
1569
|
|
|
|
|
|
|
# |
1570
|
|
|
|
|
|
|
sub row_across |
1571
|
|
|
|
|
|
|
{ |
1572
|
8
|
|
|
8
|
0
|
14
|
my ($self, $data, $rownum, $field) = @_; |
1573
|
8
|
|
|
|
|
18
|
my @cells = (); |
1574
|
8
|
|
|
|
|
12
|
my @across_row = (); |
1575
|
8
|
|
|
|
|
12
|
my $skip_count = 0; |
1576
|
|
|
|
|
|
|
|
1577
|
|
|
|
|
|
|
# Label/heading |
1578
|
8
|
50
|
|
|
|
29
|
if ($self->{defn_t}->{labels}) { |
1579
|
8
|
|
|
|
|
24
|
push @cells, $self->cell_single(field => $field, skip_count => \$skip_count); |
1580
|
8
|
|
|
|
|
25
|
push @across_row, $self->cell_single(field => $field, tags => 0); |
1581
|
|
|
|
|
|
|
} |
1582
|
|
|
|
|
|
|
|
1583
|
|
|
|
|
|
|
# Data |
1584
|
8
|
|
|
|
|
18
|
for my $row (@$data) { |
1585
|
24
|
50
|
|
|
|
51
|
if ($skip_count > 0) { |
1586
|
0
|
|
|
|
|
0
|
$skip_count--; |
1587
|
0
|
|
|
|
|
0
|
next; |
1588
|
|
|
|
|
|
|
} |
1589
|
|
|
|
|
|
|
|
1590
|
24
|
|
|
|
|
59
|
push @cells, $self->cell_single(row => $row, field => $field, skip_count => \$skip_count); |
1591
|
24
|
|
|
|
|
61
|
push @across_row, $self->cell_value($row, $field); |
1592
|
|
|
|
|
|
|
} |
1593
|
|
|
|
|
|
|
|
1594
|
|
|
|
|
|
|
# Build row |
1595
|
8
|
|
|
|
|
28
|
my $out = $self->start_tag('tr', $self->tr_attr($rownum, $data, \@across_row)); |
1596
|
8
|
|
|
|
|
42
|
$out .= join('', @cells); |
1597
|
8
|
|
|
|
|
19
|
$out .= $self->end_tag('tr') . "\n"; |
1598
|
|
|
|
|
|
|
} |
1599
|
|
|
|
|
|
|
|
1600
|
|
|
|
|
|
|
sub get_dataset |
1601
|
|
|
|
|
|
|
{ |
1602
|
2
|
|
|
2
|
0
|
6
|
my ($self, $set) = @_; |
1603
|
|
|
|
|
|
|
|
1604
|
|
|
|
|
|
|
# Fetch the full data set |
1605
|
2
|
|
|
|
|
5
|
my @data = (); |
1606
|
2
|
50
|
|
|
|
10
|
croak "invalid Tabulate data type '$set'" unless ref $set; |
1607
|
2
|
50
|
33
|
|
|
37
|
if (ref $set eq 'CODE') { |
|
|
50
|
33
|
|
|
|
|
|
|
50
|
0
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1608
|
0
|
|
|
|
|
0
|
while (my $row = $set->()) { |
1609
|
0
|
|
|
|
|
0
|
push @data, $row; |
1610
|
|
|
|
|
|
|
} |
1611
|
|
|
|
|
|
|
} |
1612
|
|
|
|
|
|
|
elsif (blessed $set and $set->can('Next')) { |
1613
|
0
|
|
0
|
|
|
0
|
my $row = eval { $set->First } || $set->Next; |
1614
|
0
|
0
|
|
|
|
0
|
if (ref $row) { |
1615
|
0
|
|
|
|
|
0
|
do { |
1616
|
0
|
|
|
|
|
0
|
push @data, $row; |
1617
|
|
|
|
|
|
|
} |
1618
|
|
|
|
|
|
|
while ($row = $set->Next); |
1619
|
|
|
|
|
|
|
} |
1620
|
|
|
|
|
|
|
} |
1621
|
|
|
|
|
|
|
elsif (blessed $set and $set->can('next')) { |
1622
|
0
|
|
0
|
|
|
0
|
my $row = eval { $set->first } || $set->next; |
1623
|
0
|
0
|
|
|
|
0
|
if (ref $row) { |
1624
|
0
|
|
|
|
|
0
|
do { |
1625
|
0
|
|
|
|
|
0
|
push @data, $row; |
1626
|
|
|
|
|
|
|
} |
1627
|
|
|
|
|
|
|
while ($row = $set->next); |
1628
|
|
|
|
|
|
|
} |
1629
|
|
|
|
|
|
|
} |
1630
|
|
|
|
|
|
|
elsif (ref $set eq 'ARRAY') { |
1631
|
2
|
|
|
|
|
6
|
@data = @$set; |
1632
|
|
|
|
|
|
|
} |
1633
|
0
|
|
|
|
|
0
|
elsif (ref $set eq 'HASH' || eval { keys %$set }) { |
1634
|
0
|
|
|
|
|
0
|
@data = ( $set ); |
1635
|
|
|
|
|
|
|
} |
1636
|
|
|
|
|
|
|
else { |
1637
|
0
|
|
|
|
|
0
|
croak "[body_across] invalid Tabulate data type '$set'"; |
1638
|
|
|
|
|
|
|
} |
1639
|
|
|
|
|
|
|
|
1640
|
2
|
|
|
|
|
44
|
return @data; |
1641
|
|
|
|
|
|
|
} |
1642
|
|
|
|
|
|
|
|
1643
|
|
|
|
|
|
|
# |
1644
|
|
|
|
|
|
|
# Render the table body with successive records across the page |
1645
|
|
|
|
|
|
|
# (i.e. fields down the page) |
1646
|
|
|
|
|
|
|
# |
1647
|
|
|
|
|
|
|
sub body_across |
1648
|
|
|
|
|
|
|
{ |
1649
|
2
|
|
|
2
|
0
|
20
|
my ($self, $set) = @_; |
1650
|
|
|
|
|
|
|
|
1651
|
|
|
|
|
|
|
# Iterate over fields (instead of data rows) |
1652
|
2
|
|
|
|
|
11
|
my @data = $self->get_dataset($set); |
1653
|
2
|
|
|
|
|
5
|
my $rownum = 1; |
1654
|
2
|
|
|
|
|
5
|
my $body = ''; |
1655
|
2
|
|
|
|
|
4
|
for my $field (@{$self->{defn_t}->{fields}}) { |
|
2
|
|
|
|
|
7
|
|
1656
|
8
|
|
|
|
|
28
|
$body .= $self->row_across(\@data, $rownum, $field); |
1657
|
8
|
|
|
|
|
23
|
$rownum++; |
1658
|
|
|
|
|
|
|
} |
1659
|
|
|
|
|
|
|
|
1660
|
2
|
|
|
|
|
9
|
return $body; |
1661
|
|
|
|
|
|
|
} |
1662
|
|
|
|
|
|
|
|
1663
|
|
|
|
|
|
|
# ------------------------------------------------------------------------- |
1664
|
|
|
|
|
|
|
sub render_table |
1665
|
|
|
|
|
|
|
{ |
1666
|
107
|
|
|
107
|
0
|
181
|
my ($self, $set) = @_; |
1667
|
107
|
|
|
|
|
201
|
my $defn_t = $self->{defn_t}; |
1668
|
|
|
|
|
|
|
|
1669
|
|
|
|
|
|
|
# Style-specific bodies (default is 'down') |
1670
|
107
|
|
|
|
|
148
|
my $body; |
1671
|
107
|
100
|
|
|
|
347
|
if ($defn_t->{style} eq 'down') { |
|
|
50
|
|
|
|
|
|
1672
|
105
|
|
|
|
|
440
|
$body .= $self->body_down($set); |
1673
|
|
|
|
|
|
|
} |
1674
|
|
|
|
|
|
|
elsif ($defn_t->{style} eq 'across') { |
1675
|
2
|
|
|
|
|
11
|
$body .= $self->body_across($set); |
1676
|
|
|
|
|
|
|
} |
1677
|
|
|
|
|
|
|
else { |
1678
|
0
|
|
|
|
|
0
|
croak sprintf "[render] invalid style '%s'", $defn_t->{style}; |
1679
|
|
|
|
|
|
|
} |
1680
|
|
|
|
|
|
|
|
1681
|
|
|
|
|
|
|
# Build table |
1682
|
107
|
|
|
|
|
269
|
my $table = ''; |
1683
|
107
|
|
|
|
|
460
|
$table .= $self->pre_table($set); |
1684
|
107
|
|
|
|
|
390
|
$table .= $self->start_table(); |
1685
|
107
|
|
|
|
|
364
|
$table .= $self->caption($set); |
1686
|
107
|
|
|
|
|
498
|
$table .= $self->colgroups($set); |
1687
|
107
|
|
|
|
|
223
|
$table .= $body; |
1688
|
107
|
|
|
|
|
322
|
$table .= $self->end_table(); |
1689
|
107
|
|
|
|
|
345
|
$table .= $self->post_table($set); |
1690
|
|
|
|
|
|
|
|
1691
|
107
|
|
|
|
|
1244
|
return $table; |
1692
|
|
|
|
|
|
|
} |
1693
|
|
|
|
|
|
|
|
1694
|
|
|
|
|
|
|
# |
1695
|
|
|
|
|
|
|
# Render the data set $set using the settings in $self->{defn} + $defn, |
1696
|
|
|
|
|
|
|
# returning the resulting string. |
1697
|
|
|
|
|
|
|
# |
1698
|
|
|
|
|
|
|
sub render |
1699
|
|
|
|
|
|
|
{ |
1700
|
107
|
|
|
107
|
1
|
69162
|
my ($self, $set, $defn) = @_; |
1701
|
107
|
50
|
|
|
|
395
|
$set = {} unless ref $set; |
1702
|
|
|
|
|
|
|
|
1703
|
|
|
|
|
|
|
# If $self is not a subclass of HTML::Tabulate, this is a procedural call, $self is $set |
1704
|
107
|
100
|
33
|
|
|
1678
|
if (! ref $self || ! blessed $self || ! $self->isa('HTML::Tabulate')) { |
|
|
|
66
|
|
|
|
|
1705
|
6
|
|
|
|
|
11
|
$defn = $set; |
1706
|
6
|
|
|
|
|
9
|
$set = $self; |
1707
|
6
|
|
|
|
|
33
|
$self = __PACKAGE__->new($defn); |
1708
|
6
|
|
|
|
|
11
|
undef $defn; |
1709
|
|
|
|
|
|
|
} |
1710
|
|
|
|
|
|
|
|
1711
|
|
|
|
|
|
|
# If $defn defined, merge with $self->{defn} for this render only |
1712
|
107
|
100
|
66
|
|
|
818
|
if (ref $defn eq 'HASH' && keys %$defn) { |
1713
|
90
|
|
|
|
|
373
|
$defn = $self->merge($self->{defn}, $defn); |
1714
|
90
|
|
|
|
|
309
|
$self->prerender_munge($set, $defn); |
1715
|
|
|
|
|
|
|
} |
1716
|
|
|
|
|
|
|
else { |
1717
|
17
|
|
|
|
|
56
|
$self->prerender_munge($set); |
1718
|
|
|
|
|
|
|
} |
1719
|
|
|
|
|
|
|
|
1720
|
107
|
|
|
|
|
787
|
$self->render_table($set); |
1721
|
|
|
|
|
|
|
} |
1722
|
|
|
|
|
|
|
|
1723
|
|
|
|
|
|
|
# ------------------------------------------------------------------------- |
1724
|
|
|
|
|
|
|
|
1725
|
|
|
|
|
|
|
1; |
1726
|
|
|
|
|
|
|
|
1727
|
|
|
|
|
|
|
__END__ |