web commit by PatrickWinnertz: since revert isn't possible via web, I 'reverted'...
[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 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                         $p->handler(text => sub {
91                                 $page.=join("", @_);
92                                 $index.=&$indent."</li>\n" unless $liststarted;
93                                 $liststarted=0;
94                                 $index.=&$indent."<li class=\"L$curlevel\">".
95                                         "<a href=\"#$anchor\">".
96                                         join("", @_).
97                                         "</a>\n";
98                                 $p->handler(text => undef);
99                         }, "dtext");
100                 }
101                 else {
102                         $page.=$text;
103                 }
104         }, "tagname, text");
105         $p->handler(default => sub { $page.=join("", @_) }, "text");
106         $p->parse($content);
107         $p->eof;
108
109         while ($startlevel && $curlevel >= $startlevel) {
110                 $index.=&$indent."</li>\n" if $curlevel;
111                 $curlevel--;
112                 $index.=&$indent."</ol>\n";
113         }
114
115         $page=~s/(<div class=\"toc\">)/$1\n$index/;
116         return $page;
117 }
118
119 1