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