File Coverage

blib/lib/Text/Table/HTML.pm
Criterion Covered Total %
statement 124 127 97.6
branch 64 74 86.4
condition 23 24 95.8
subroutine 5 5 100.0
pod 1 1 100.0
total 217 231 93.9


\n"; \n"; \n"; \n"; \n"; \n"; \n"; \n"; \n"; \n" if $needs_tbody_open; \n" if $needs_tbody_open || $needs_tbody_close;
line stmt bran cond sub pod time code
1             package Text::Table::HTML;
2              
3 10     10   2358900 use 5.010001;
  10         45  
4 10     10   71 use strict;
  10         43  
  10         341  
5 10     10   59 use warnings;
  10         19  
  10         16223  
6              
7             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
8             our $DATE = '2025-05-15'; # DATE
9             our $DIST = 'Text-Table-HTML'; # DIST
10             our $VERSION = '0.012'; # VERSION
11              
12             sub _encode {
13 169     169   252 state $load = do { require HTML::Entities };
  9         6465  
14 169         72382 my $val = shift;
15             # encode_entities change 0 (false) to empty string so we need to filter the
16             # value first
17 169 50       411 if (!defined $val) {
    100          
18 0         0 "";
19             } elsif (!$val) {
20 5         16 "$val";
21             } else {
22 164         431 HTML::Entities::encode_entities($val);
23             }
24             }
25              
26             sub table {
27 25     25 1 4122373 my %params = @_;
28 25 50       122 my $rows = delete $params{rows} or die "Must provide rows!";
29              
30             # here we go...
31 25         52 my @table;
32              
33 25   100     48 my %attr = %{ delete( $params{html_attr} ) // {} };
  25         212  
34             {
35 25         60 my @direct_attr = grep exists $params{"html_$_"}, qw( id class style );
  25         142  
36 25         124 $attr{@direct_attr} = delete @params{ map "html_$_", @direct_attr };
37             }
38              
39             my $attr =
40             keys %attr
41 25 50       184 ? join q{ }, '', map { qq{$_="$attr{$_}"} } grep defined( $attr{$_} ),
  1         5  
42             keys %attr
43             : '';
44              
45             # set all rows bottom_border if requested.
46 25         67 my $bottom_border_rows = delete($params{separate_rows});
47              
48 25         104 push @table, "\n";
49              
50 25 100       97 if ( defined( my $caption = delete $params{caption} ) ) {
51 1         4 push @table, "
" . _encode($caption) . "
52             }
53              
54 25 100       122 if ( defined( my $colgroup = delete $params{html_colgroup} ) ) {
55              
56 1 50       4 if (@$colgroup) {
57 1         2 push @table, "
58              
59 1         3 for my $col ( @{$colgroup} ) {
  1         3  
60              
61 4         9 my @element = '
62 4 100       12 if ( defined $col ) {
63 3 100       9 if ( 'HASH' eq ref $col ) {
64 2         4 push @element, qq{$_="$col->{$_}"} for keys %{$col};
  2         9  
65             }
66             else {
67 1         3 push @element, $col;
68             }
69             }
70 4         7 push @element, '/>';
71 4         18 push @table, join( q{ }, @element ), "\n";
72             }
73              
74 1         3 push @table, "\n";
75             }
76             }
77              
78             # then the header & footer
79 25   100     107 my $header_row = delete $params{header_row} // 0;
80 25   100     107 my $footer_row = delete $params{footer_row} // 0;
81              
82             # check for unrecognized options
83 25 50       108 die( "unrecognized options: ", join q{, }, sort keys %params )
84             if keys %params;
85              
86 25         87 my $footer_row_start;
87             my $footer_row_end;
88              
89             # footer is directly after the header
90 25 100       114 if ( $footer_row > 0 ) {
    100          
91 4         7 $footer_row_start = $header_row;
92 4         7 $footer_row_end = $footer_row_start + $footer_row;
93 4         9 $footer_row = !!1;
94             }
95              
96             # footer is at end
97             elsif ( $footer_row < 0 ) {
98 2         5 $footer_row_start = @{$rows} + $footer_row;
  2         5  
99 2         5 $footer_row_end = $footer_row_start - $footer_row;
100 2         5 $footer_row = !!1;
101             }
102              
103 25         59 my $needs_thead_open = !!$header_row;
104 25         54 my $needs_thead_close = !!0;
105              
106 25         48 my $needs_tbody_open = !!1;
107 25         47 my $add_tbody_open = !!1;
108 25         36 my $needs_tbody_close = !!0;
109              
110 25         52 my $needs_tfoot_close = !!0;
111 25         68 my $idx = -1;
112              
113             # then the data
114 25         49 foreach my $row ( @{$rows} ) {
  25         76  
115 86         175 ++$idx;
116              
117 86         162 my $col_tag = 'td';
118              
119 86 100       219 if ($header_row) {
120              
121 24         48 $col_tag = 'th';
122              
123 24 100       74 if ($needs_thead_open) {
    100          
124 10         21 push @table, "
125 10         29 $needs_thead_open = !!0;
126 10         27 $needs_thead_close = !!1;
127 10         20 $add_tbody_open = !!0;
128             }
129              
130             elsif ( --$header_row == 0 ) {
131 9         20 push @table, "
132 9         16 $needs_thead_close = !!0;
133 9         15 $add_tbody_open = $needs_tbody_open;
134 9         19 $col_tag = 'td';
135             }
136             }
137              
138 86 100       201 if ($footer_row) {
139              
140 28 100       80 if ( $idx == $footer_row_start ) {
    100          
141              
142 6 50       21 if ($needs_thead_close) {
    100          
143 0         0 push @table, "\n";
144 0         0 $needs_thead_close = !!0;
145             }
146              
147             elsif ($needs_tbody_close) {
148 2         4 push @table, "
149 2         5 $needs_tbody_close = !!0;
150             }
151              
152 6         10 push @table, "
153 6         11 $add_tbody_open = !!0;
154 6         11 $needs_tfoot_close = !!1;
155             }
156              
157             elsif ( $idx == $footer_row_end ) {
158 4         10 push @table, "
159 4         9 $footer_row = $needs_tfoot_close = !!0;
160 4         8 $add_tbody_open = $needs_tbody_open;
161             }
162              
163             }
164              
165 86 100       203 if ($add_tbody_open) {
166 23         48 push @table, "
167 23         82 $add_tbody_open = $needs_tbody_open = !!0;
168 23         48 $needs_tbody_close = !!1;
169             }
170              
171 86         165 my $bottom_border;
172              
173             my @row;
174              
175 86         195 for my $cell (@$row) {
176              
177 169         291 my $cell_tag = $col_tag;
178 169         235 my $text;
179 169         255 my $tag = $col_tag;
180 169         265 my $attr = '';
181              
182 169 100       381 if ( ref $cell eq 'HASH' ) {
183              
184             # add a class attribute for bottom_border if
185             # any cell in the row has it set. once the attribute is set,
186             # no need to do the check again.
187             $bottom_border //=
188             ($bottom_border_rows // $cell->{bottom_border})
189 13   100     118 && " class=has_bottom_border";
      100        
190              
191 13 100       38 if ( defined $cell->{raw_html} ) {
192 1         4 $text = $cell->{raw_html};
193             }
194             else {
195 12   50     57 $text = _encode( $cell->{text} // '' );
196             }
197              
198 13   100     454 my $rowspan = int( $cell->{rowspan} // 1 );
199 13 100       41 $attr .= " rowspan=$rowspan" if $rowspan > 1;
200              
201 13   100     67 my $colspan = int( $cell->{colspan} // 1 );
202 13 100       36 $attr .= " colspan=$colspan" if $colspan > 1;
203              
204             $attr .= ' align="' . $cell->{align} . '"'
205 13 100       39 if defined $cell->{align};
206              
207             $cell_tag = $cell->{html_element}
208 13 100       33 if defined $cell->{html_element};
209              
210 13 100       52 if ( defined $cell->{html_scope} ) {
211 1 50       30 die("'html_scope' attribute is only valid in header cells")
212             unless $col_tag eq 'th';
213 1         5 $attr .= ' scope="' . $cell->{html_scope} . '"';
214             }
215              
216             # cleaner if in a loop, but that might slow things down
217             $attr .= ' class="' . $cell->{html_class} . '"'
218 13 50       36 if defined $cell->{html_class};
219             $attr .= ' headers="' . $cell->{html_headers} . '"'
220 13 50       68 if defined $cell->{html_headers};
221             $attr .= ' id="' . $cell->{html_id} . '"'
222 13 50       49 if defined $cell->{html_id};
223             $attr .= ' style="' . $cell->{html_style} . '"'
224 13 100       50 if defined $cell->{html_style};
225             }
226             else {
227 156   100     419 $text = _encode( $cell // '' );
228             }
229              
230 169         3514 push @row,
231             '<' . $cell_tag . $attr . '>', $text, '';
232             }
233              
234 86   100     673 push @table, "", @row, "
235             }
236              
237 25 100       79 push @table, "\n" if $needs_thead_close;
238 25 100       65 push @table, "\n" if $needs_tfoot_close;
239              
240 25 100       66 push @table, "
241 25 100 100     163 push @table, "
242 25         54 push @table, "
\n"; 243               244 25         433 return join( "", @table ); 245             } 246               247             1; 248               249             # ABSTRACT: Generate HTML table 250               251             __END__