ikiwiki (3.20130711) unstable; urgency=low
[ikiwiki.git] / IkiWiki / Plugin / toc.pm
1 #!/usr/bin/perl
2 # Table Of Contents generator
3 package IkiWiki::Plugin::toc;
4
5 use warnings;
6 use strict;
7 use IkiWiki 3.00;
8 use HTML::Parser;
9
10 sub import {
11         hook(type => "getsetup", id => "toc", call => \&getsetup);
12         hook(type => "preprocess", id => "toc", call => \&preprocess);
13         hook(type => "format", id => "toc", call => \&format);
14 }
15
16 sub getsetup () {
17         return
18                 plugin => {
19                         safe => 1,
20                         rebuild => undef,
21                         section => "widget",
22                 },
23 }
24
25 my %tocpages;
26
27 sub preprocess (@) {
28         my %params=@_;
29
30         if ($params{page} eq $params{destpage}) {
31                 $params{levels}=1 unless exists $params{levels};
32
33                 # It's too early to generate the toc here, so just record the
34                 # info.
35                 $tocpages{$params{destpage}}=\%params;
36
37                 return "\n<div class=\"toc\"></div>\n";
38         }
39         else {
40                 # Don't generate toc in an inlined page, doesn't work
41                 # right.
42                 return "";
43         }
44 }
45
46 sub format (@) {
47         my %params=@_;
48         my $content=$params{content};
49         
50         return $content unless exists $tocpages{$params{page}};
51         %params=%{$tocpages{$params{page}}};
52
53         my $p=HTML::Parser->new(api_version => 3);
54         my $page="";
55         my $index="";
56         my %anchors;
57         my $startlevel=($params{startlevel} ? $params{startlevel} : 0);
58         my $curlevel=$startlevel-1;
59         my $liststarted=0;
60         my $indent=sub { "\t" x $curlevel };
61         $p->handler(start => sub {
62                 my $tagname=shift;
63                 my $text=shift;
64                 if ($tagname =~ /^h(\d+)$/i) {
65                         my $level=$1;
66                         my $anchor="index".++$anchors{$level}."h$level";
67                         $page.="$text<a name=\"$anchor\"></a>";
68         
69                         # Unless we're given startlevel as a parameter,
70                         # take the first header level seen as the topmost level,
71                         # even if there are higher levels seen later on.
72                         if (! $startlevel) {
73                                 $startlevel=$level;
74                                 $curlevel=$startlevel-1;
75                         }
76                         elsif (defined $params{startlevel} &&
77                                $level < $params{startlevel}) {
78                             return;
79                         }
80                         elsif ($level < $startlevel) {
81                                 $level=$startlevel;
82                         }
83                         
84                         return if $level - $startlevel >= $params{levels};
85         
86                         if ($level > $curlevel) {
87                                 while ($level > $curlevel + 1) {
88                                         $index.=&$indent."<ol>\n";
89                                         $curlevel++;
90                                         $index.=&$indent."<li class=\"L$curlevel\">\n";
91                                 }
92                                 $index.=&$indent."<ol>\n";
93                                 $curlevel=$level;
94                                 $liststarted=1;
95                         }
96                         elsif ($level < $curlevel) {
97                                 while ($level < $curlevel) {
98                                         $index.=&$indent."</li>\n" if $curlevel;
99                                         $curlevel--;
100                                         $index.=&$indent."</ol>\n";
101                                 }
102                                 $liststarted=0;
103                         }
104                                 
105                         $index.=&$indent."</li>\n" unless $liststarted;
106                         $liststarted=0;
107                         $index.=&$indent."<li class=\"L$curlevel\">".
108                                 "<a href=\"#$anchor\">";
109         
110                         $p->handler(text => sub {
111                                 $page.=join("", @_);
112                                 $index.=join("", @_);
113                         }, "dtext");
114                         $p->handler(end => sub {
115                                 my $tagname=shift;
116                                 if ($tagname =~ /^h(\d+)$/i) {
117                                         $p->handler(text => undef);
118                                         $p->handler(end => undef);
119                                         $index.="</a>\n";
120                                 }
121                                 $page.=join("", @_);
122                         }, "tagname, text");
123                 }
124                 else {
125                         $page.=$text;
126                 }
127         }, "tagname, text");
128         $p->handler(default => sub { $page.=join("", @_) }, "text");
129         $p->parse($content);
130         $p->eof;
131
132         while ($startlevel && $curlevel >= $startlevel) {
133                 $index.=&$indent."</li>\n" if $curlevel;
134                 $curlevel--;
135                 $index.=&$indent."</ol>\n";
136         }
137
138         $page=~s/(<div class=\"toc\">)/$1\n$index/;
139         return $page;
140 }
141
142 1