Merge branch 'master' of ssh://git.kitenet.net/srv/git/ikiwiki.info
[ikiwiki] / 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 2.00;
8 use HTML::Parser;
9
10 sub import { #{{{
11         hook(type => "preprocess", id => "toc", call => \&preprocess);
12         hook(type => "format", id => "toc", call => \&format);
13 } # }}}
14
15 my %tocpages;
16
17 sub preprocess (@) { #{{{
18         my %params=@_;
19
20         if ($params{page} eq $params{destpage}) {
21                 $params{levels}=1 unless exists $params{levels};
22
23                 # It's too early to generate the toc here, so just record the
24                 # info.
25                 $tocpages{$params{destpage}}=\%params;
26
27                 return "\n<div class=\"toc\"></div>\n";
28         }
29         else {
30                 # Don't generate toc in an inlined page, doesn't work
31                 # right.
32                 return "";
33         }
34 } # }}}
35
36 sub format (@) { #{{{
37         my %params=@_;
38         my $content=$params{content};
39         
40         return $content unless exists $tocpages{$params{page}};
41         %params=%{$tocpages{$params{page}}};
42
43         my $p=HTML::Parser->new(api_version => 3);
44         my $page="";
45         my $index="";
46         my %anchors;
47         my $curlevel;
48         my $startlevel=0;
49         my $liststarted=0;
50         my $indent=sub { "\t" x $curlevel };
51         $p->handler(start => sub {
52                 my $tagname=shift;
53                 my $text=shift;
54                 if ($tagname =~ /^h(\d+)$/i) {
55                         my $level=$1;
56                         my $anchor="index".++$anchors{$level}."h$level";
57                         $page.="$text<a name=\"$anchor\"></a>";
58         
59                         # Take the first header level seen as the topmost level,
60                         # even if there are higher levels seen later on.
61                         if (! $startlevel) {
62                                 $startlevel=$level;
63                                 $curlevel=$startlevel-1;
64                         }
65                         elsif ($level < $startlevel) {
66                                 $level=$startlevel;
67                         }
68                         
69                         return if $level - $startlevel >= $params{levels};
70         
71                         if ($level > $curlevel) {
72                                 while ($level > $curlevel + 1) {
73                                         $index.=&$indent."<ol>\n";
74                                         $curlevel++;
75                                         $index.=&$indent."<li class=\"L$curlevel\">\n";
76                                 }
77                                 $index.=&$indent."<ol>\n";
78                                 $curlevel=$level;
79                                 $liststarted=1;
80                         }
81                         elsif ($level < $curlevel) {
82                                 while ($level < $curlevel) {
83                                         $index.=&$indent."</li>\n" if $curlevel;
84                                         $curlevel--;
85                                         $index.=&$indent."</ol>\n";
86                                 }
87                                 $liststarted=0;
88                         }
89                                 
90                         $index.=&$indent."</li>\n" unless $liststarted;
91                         $liststarted=0;
92                         $index.=&$indent."<li class=\"L$curlevel\">".
93                                 "<a href=\"#$anchor\">";
94         
95                         $p->handler(text => sub {
96                                 $page.=join("", @_);
97                                 $index.=join("", @_);
98                         }, "dtext");
99                         $p->handler(end => sub {
100                                 my $tagname=shift;
101                                 if ($tagname =~ /^h(\d+)$/i) {
102                                         $p->handler(text => undef);
103                                         $p->handler(end => undef);
104                                         $index.="</a>\n";
105                                 }
106                                 $page.=join("", @_);
107                         }, "tagname, text");
108                 }
109                 else {
110                         $page.=$text;
111                 }
112         }, "tagname, text");
113         $p->handler(default => sub { $page.=join("", @_) }, "text");
114         $p->parse($content);
115         $p->eof;
116
117         while ($startlevel && $curlevel >= $startlevel) {
118                 $index.=&$indent."</li>\n" if $curlevel;
119                 $curlevel--;
120                 $index.=&$indent."</ol>\n";
121         }
122
123         $page=~s/(<div class=\"toc\">)/$1\n$index/;
124         return $page;
125 }
126
127 1