Merge commit 'baaa848f6c06b0b3a59677d3551e130c65e5fde7' into sipb
[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                 $params{startlevel}=2 unless exists $params{startlevel};
33
34                 # It's too early to generate the toc here, so just record the
35                 # info.
36                 $tocpages{$params{destpage}}=\%params;
37
38                 return "\n<div class=\"toc\"></div>\n";
39         }
40         else {
41                 # Don't generate toc in an inlined page, doesn't work
42                 # right.
43                 return "";
44         }
45 }
46
47 sub format (@) {
48         my %params=@_;
49         my $content=$params{content};
50         
51         return $content unless exists $tocpages{$params{page}};
52         %params=%{$tocpages{$params{page}}};
53
54         my $p=HTML::Parser->new(api_version => 3);
55         my $page="";
56         my $index="";
57         my %anchors;
58         my $startlevel=($params{startlevel} ? $params{startlevel} : 0);
59         my $curlevel=$startlevel-1;
60         my $liststarted=0;
61         my $indent=sub { "\t" x $curlevel };
62         $p->handler(start => sub {
63                 my $tagname=shift;
64                 my $text=shift;
65                 if ($tagname =~ /^h(\d+)$/i) {
66                         my $level=$1;
67                         my $anchor="index".++$anchors{$level}."h$level";
68                         $page.="$text<a name=\"$anchor\"></a>";
69         
70                         # Unless we're given startlevel as a parameter,
71                         # take the first header level seen as the topmost level,
72                         # even if there are higher levels seen later on.
73                         if (! $startlevel) {
74                                 $startlevel=$level;
75                                 $curlevel=$startlevel-1;
76                         }
77                         elsif (defined $params{startlevel} &&
78                                $level < $params{startlevel}) {
79                             return;
80                         }
81                         elsif ($level < $startlevel) {
82                                 $level=$startlevel;
83                         }
84                         
85                         return if $level - $startlevel >= $params{levels};
86         
87                         if ($level > $curlevel) {
88                                 while ($level > $curlevel + 1) {
89                                         $index.=&$indent."<ol>\n";
90                                         $curlevel++;
91                                         $index.=&$indent."<li class=\"L$curlevel\">\n";
92                                 }
93                                 $index.=&$indent."<ol>\n";
94                                 $curlevel=$level;
95                                 $liststarted=1;
96                         }
97                         elsif ($level < $curlevel) {
98                                 while ($level < $curlevel) {
99                                         $index.=&$indent."</li>\n" if $curlevel;
100                                         $curlevel--;
101                                         $index.=&$indent."</ol>\n";
102                                 }
103                                 $liststarted=0;
104                         }
105                                 
106                         $index.=&$indent."</li>\n" unless $liststarted;
107                         $liststarted=0;
108                         $index.=&$indent."<li class=\"L$curlevel\">".
109                                 "<a href=\"#$anchor\">";
110         
111                         $p->handler(text => sub {
112                                 $page.=join("", @_);
113                                 $index.=join("", @_);
114                         }, "dtext");
115                         $p->handler(end => sub {
116                                 my $tagname=shift;
117                                 if ($tagname =~ /^h(\d+)$/i) {
118                                         $p->handler(text => undef);
119                                         $p->handler(end => undef);
120                                         $index.="</a>\n";
121                                 }
122                                 $page.=join("", @_);
123                         }, "tagname, text");
124                 }
125                 else {
126                         $page.=$text;
127                 }
128         }, "tagname, text");
129         $p->handler(default => sub { $page.=join("", @_) }, "text");
130         $p->parse($content);
131         $p->eof;
132
133         while ($startlevel && $curlevel >= $startlevel) {
134                 $index.=&$indent."</li>\n" if $curlevel;
135                 $curlevel--;
136                 $index.=&$indent."</ol>\n";
137         }
138
139         $page=~s/(<div class=\"toc\">)/$1\n$index/;
140         return $page;
141 }
142
143 1