summaryrefslogtreecommitdiff
path: root/IkiWiki/Plugin/table.pm
blob: c08087c713a612e2086657aea3ffbb62fb216c25 (plain)
  1. package IkiWiki::Plugin::table;
  2. # by Victor Moral <victor@taquiones.net>
  3. use warnings;
  4. use strict;
  5. use IkiWiki;
  6. use IkiWiki::Plugin::mdwn;
  7. my %defaults = (
  8. data => undef,
  9. file => undef,
  10. format => 'auto',
  11. sep_char => {
  12. 'csv' => ',',
  13. 'dsv' => '\|',
  14. },
  15. class => undef,
  16. header => 1,
  17. );
  18. sub import { #{{{
  19. hook(type => "preprocess", id => "table", call => \&preprocess);
  20. } # }}}
  21. sub preprocess (@) { #{{{
  22. my %params = (%defaults, @_);
  23. if (defined $params{delimiter}) {
  24. $params{sep_char}->{$params{format}} = $params{delimiter};
  25. }
  26. if (defined $params{file}) {
  27. if (! $pagesources{$params{file}}) {
  28. return "[[table ".gettext("cannot find file")."]]";
  29. }
  30. $params{data} = readfile(srcfile($params{file}));
  31. }
  32. if (lc $params{format} eq 'auto') {
  33. # first try the more simple format
  34. if (is_dsv_data($params{data})) {
  35. $params{format} = 'dsv';
  36. $params{sep_char}->{dsv} = '\|';
  37. }
  38. else {
  39. $params{format} = 'csv';
  40. $params{sep_char}->{csv} = ',';
  41. }
  42. }
  43. my @data;
  44. if (lc $params{format} eq 'csv') {
  45. @data=read_csv(\%params);
  46. }
  47. elsif (lc $params{format} eq 'dsv') {
  48. @data=read_dsv(\%params);
  49. }
  50. else {
  51. return "[[table ".gettext("unknown data format")."]]";
  52. }
  53. my $header;
  54. if ($params{header} != 1) {
  55. $header=shift @data;
  56. }
  57. if (! @data) {
  58. return "[[table ".gettext("empty data")."]]";
  59. }
  60. my $html = tidy_up(open_table(\%params, $header),
  61. build_rows(\%params, @data),
  62. close_table(\%params, $header));
  63. if (defined $params{file}) {
  64. return $html."\n\n".
  65. htmllink($params{page}, $params{destpage}, $params{file},
  66. linktext => gettext('Direct data download'));
  67. }
  68. else {
  69. return $html;
  70. }
  71. } #}}}
  72. sub tidy_up (@) { #{{{
  73. my $html="";
  74. foreach my $text (@_) {
  75. my $indentation = $text =~ m{thead>|tbody>} ? 0 :
  76. $text =~ m{tr>} ? 4 :
  77. $text =~ m{td>|th>} ? 8 :
  78. 0;
  79. $html .= (' ' x $indentation)."$text\n";
  80. }
  81. return $html;
  82. } #}}}
  83. sub is_dsv_data ($) { #{{{
  84. my $text = shift;
  85. my ($line) = split(/\n/, $text);
  86. return $line =~ m{.+\|};
  87. }
  88. sub read_csv ($) { #{{{
  89. my $params=shift;
  90. my @text_lines = split(/\n/, $params->{data});
  91. eval q{use Text::CSV};
  92. error($@) if $@;
  93. my $csv = Text::CSV->new({
  94. sep_char => $params->{sep_char}->{csv},
  95. binary => 1,
  96. }) || error("could not create a Text::CSV object");
  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. return @data;
  110. } #}}}
  111. sub read_dsv ($) { #{{{
  112. my $params = shift;
  113. my @text_lines = split(/\n/, $params->{data});
  114. my @data;
  115. my $splitter = qr{$params->{sep_char}->{dsv}};
  116. foreach my $line (@text_lines) {
  117. push @data, [ split($splitter, $line) ];
  118. }
  119. return @data;
  120. } #}}}
  121. sub open_table ($$) { #{{{
  122. my $params = shift;
  123. my $header = shift;
  124. my @items;
  125. push @items, defined $params->{class}
  126. ? "<table class=\"".$params->{class}.'">'
  127. : '<table>';
  128. push @items, '<thead>','<tr>',
  129. (map { "<th>".htmlize($params, $_)."</th>" } @$header),
  130. '</tr>','</thead>' if defined $header;
  131. push @items, '<tbody>';
  132. return @items;
  133. }
  134. sub build_rows ($@) { #{{{
  135. my $params = shift;
  136. my @items;
  137. foreach my $record (@_) {
  138. push @items, '<tr>',
  139. (map { "<td>".htmlize($params, $_)."</td>" } @$record),
  140. '</tr>';
  141. }
  142. return @items;
  143. } #}}}
  144. sub close_table ($$) { #{{{
  145. my $params = shift;
  146. my $header = shift;
  147. my @items;
  148. push @items, '</tbody>' if defined $header;
  149. push @items, '</table>';
  150. return @items;
  151. } #}}}
  152. sub htmlize { #{{{
  153. my $params = shift;
  154. my $text = shift;
  155. $text=IkiWiki::preprocess($params->{page},
  156. $params->{destpage}, $text);
  157. $text=IkiWiki::htmlize($params->{page},
  158. pagetype($pagesources{$params->{page}}), $text);
  159. # hack to get rid of enclosing junk added by markdown
  160. $text=~s!^<p>!!;
  161. $text=~s!</p>$!!;
  162. chomp $text;
  163. return $text;
  164. }
  165. 1