* Use fieldsets in the preferences form to group related options together.
[ikiwiki] / IkiWiki / Plugin / table.pm
1 package IkiWiki::Plugin::table;
2 # by Victor Moral <victor@taquiones.net>
3
4 use warnings;
5 use strict;
6 use IkiWiki 2.00;
7
8 sub import { #{{{
9         hook(type => "preprocess", id => "table", call => \&preprocess);
10 } # }}}
11
12 sub preprocess (@) { #{{{
13         my %params =(
14                 format  => 'auto',
15                 header  => 'yes',
16                 @_
17         );
18
19         if (exists $params{file}) {
20                 if (! $pagesources{$params{file}}) {
21                         return "[[table ".gettext("cannot find file")."]]";
22                 }
23                 $params{data} = readfile(srcfile($params{file}));
24         }
25
26         if (lc $params{format} eq 'auto') {
27                 # first try the more simple format
28                 if (is_dsv_data($params{data})) {
29                         $params{format} = 'dsv';
30                 }
31                 else {
32                         $params{format} = 'csv';
33                 }
34         }
35
36         my @data;
37         if (lc $params{format} eq 'csv') {
38                 @data=split_csv($params{data}, $params{delimiter});
39         }
40         elsif (lc $params{format} eq 'dsv') {
41                 @data=split_dsv($params{data}, $params{delimiter});
42         }
43         else {
44                 return "[[table ".gettext("unknown data format")."]]";
45         }
46
47         my $header;
48         if (lc($params{header}) eq "yes") {
49                 $header=shift @data;
50         }
51         if (! @data) {
52                 return "[[table ".gettext("empty data")."]]";
53         }
54
55         my @lines;
56         push @lines, defined $params{class}
57                         ? "<table class=\"".$params{class}.'">'
58                         : '<table>';
59         push @lines, "\t<thead>",
60                 genrow($params{page}, $params{destpage}, "th", @$header),
61                 "\t</thead>" if defined $header;
62         push @lines, "\t<tbody>";
63         push @lines, genrow($params{page}, $params{destpage}, "td", @$_)
64                 foreach @data;
65         push @lines, "\t</tbody>" if defined $header;
66         push @lines, '</table>';
67         my $html = join("\n", @lines);
68
69         if (exists $params{file}) {
70                 return $html."\n\n".
71                         htmllink($params{page}, $params{destpage}, $params{file},
72                                 linktext => gettext('Direct data download'));
73         }
74         else {  
75                 return $html;
76         }            
77 } #}}}
78
79 sub is_dsv_data ($) { #{{{
80         my $text = shift;
81
82         my ($line) = split(/\n/, $text);
83         return $line =~ m{.+\|};
84 }
85
86 sub split_csv ($$) { #{{{
87         my @text_lines = split(/\n/, shift);
88         my $delimiter = shift;
89
90         eval q{use Text::CSV};
91         error($@) if $@;
92         my $csv = Text::CSV->new({ 
93                 sep_char        => defined $delimiter ? $delimiter : ",",
94                 binary          => 1,
95         }) || error("could not create a Text::CSV object");
96         
97         my $l=0;
98         my @data;
99         foreach my $line (@text_lines) {
100                 $l++;
101                 if ($csv->parse($line)) {
102                         push(@data, [ $csv->fields() ]);
103                 }
104                 else {
105                         debug(sprintf(gettext('parse fail at line %d: %s'), 
106                                 $l, $csv->error_input()));
107                 }
108         }
109
110         return @data;
111 } #}}}
112
113 sub split_dsv ($$) { #{{{
114         my @text_lines = split(/\n/, shift);
115         my $delimiter = shift;
116         $delimiter="|" unless defined $delimiter;
117
118         my @data;
119         foreach my $line (@text_lines) {
120                 push @data, [ split(/\Q$delimiter\E/, $line, -1) ];
121         }
122     
123         return @data;
124 } #}}}
125
126 sub genrow ($$$@) { #{{{
127         my $page = shift;
128         my $destpage = shift;
129         my $elt = shift;
130         my @data = @_;
131
132         my @ret;
133         push @ret, "\t\t<tr>";
134         for (my $x=0; $x < @data; $x++) {
135                 my $cell=htmlize($page, $destpage, $data[$x]);
136                 my $colspan=1;
137                 while ($x+1 < @data && $data[$x+1] eq '') {
138                         $x++;
139                         $colspan++;
140                 }
141                 if ($colspan > 1) {
142                         push @ret, "\t\t\t<$elt colspan=\"$colspan\">$cell</$elt>"
143                 }
144                 else {
145                         push @ret, "\t\t\t<$elt>$cell</$elt>"
146                 }
147         }
148         push @ret, "\t\t</tr>";
149
150         return @ret;
151 } #}}}
152
153 sub htmlize ($$$) { #{{{
154         my $page = shift;
155         my $destpage = shift;
156         my $text = shift;
157
158         $text=IkiWiki::htmlize($page, pagetype($pagesources{$page}),
159                 IkiWiki::preprocess($page, $destpage, $text));
160
161         # hack to get rid of enclosing junk added by markdown
162         $text=~s!^<p>!!;
163         $text=~s!</p>$!!;
164         chomp $text;
165
166         return $text;
167 }
168
169 1