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