summaryrefslogtreecommitdiff
path: root/IkiWiki/Plugin/toc.pm
blob: 063e66f3babafaa674bcd9a3d5ef472dfd2e4f20 (plain)
  1. #!/usr/bin/perl
  2. # Table Of Contents generator
  3. package IkiWiki::Plugin::toc;
  4. use warnings;
  5. use strict;
  6. use IkiWiki;
  7. use HTML::Parser;
  8. sub import { #{{{
  9. IkiWiki::hook(type => "preprocess", id => "toc",
  10. call => \&preprocess);
  11. IkiWiki::hook(type => "format", id => "toc",
  12. call => \&format);
  13. } # }}}
  14. my %tocpages;
  15. sub preprocess (@) { #{{{
  16. my %params=@_;
  17. $params{levels}=1 unless exists $params{levels};
  18. # It's too early to generate the toc here, so just record the
  19. # info.
  20. $tocpages{$params{destpage}}=\%params;
  21. return "\n<div class=\"toc\"></div>\n";
  22. } # }}}
  23. sub format (@) { #{{{
  24. my %params=@_;
  25. my $content=$params{content};
  26. return $content unless exists $tocpages{$params{page}};
  27. %params=%{$tocpages{$params{page}}};
  28. my $p=HTML::Parser->new(api_version => 3);
  29. my $page="";
  30. my $index="";
  31. my %anchors;
  32. my $curlevel;
  33. my $startlevel=0;
  34. my $liststarted=0;
  35. my $indent=sub { "\t" x $curlevel };
  36. $p->handler(start => sub {
  37. my $tagname=shift;
  38. my $text=shift;
  39. if ($tagname =~ /^h(\d+)$/i) {
  40. my $level=$1;
  41. my $anchor="index".++$anchors{$level}."h$level";
  42. $page.="$text<a name=\"$anchor\" />";
  43. # Take the first header level seen as the topmost level,
  44. # even if there are higher levels seen later on.
  45. if (! $startlevel) {
  46. $startlevel=$level;
  47. $curlevel=$startlevel-1;
  48. }
  49. elsif ($level < $startlevel) {
  50. $level=$startlevel;
  51. }
  52. return if $level - $startlevel >= $params{levels};
  53. if ($level > $curlevel) {
  54. while ($level > $curlevel + 1) {
  55. $index.=&$indent."<ol>\n";
  56. $curlevel++;
  57. $index.=&$indent."<li class=\"L$curlevel\">\n";
  58. }
  59. $index.=&$indent."<ol>\n";
  60. $curlevel=$level;
  61. $liststarted=1;
  62. }
  63. elsif ($level < $curlevel) {
  64. while ($level < $curlevel) {
  65. $index.=&$indent."</li>\n" if $curlevel;
  66. $curlevel--;
  67. $index.=&$indent."</ol>\n";
  68. }
  69. $liststarted=0;
  70. }
  71. $p->handler(text => sub {
  72. $page.=join("", @_);
  73. $index.=&$indent."</li>\n" unless $liststarted;
  74. $liststarted=0;
  75. $index.=&$indent."<li class=\"L$curlevel\">".
  76. "<a href=\"#$anchor\">".
  77. join("", @_).
  78. "</a>\n";
  79. $p->handler(text => undef);
  80. }, "dtext");
  81. }
  82. else {
  83. $page.=$text;
  84. }
  85. }, "tagname, text");
  86. $p->handler(default => sub { $page.=join("", @_) }, "text");
  87. $p->parse($content);
  88. $p->eof;
  89. while ($startlevel && $curlevel >= $startlevel) {
  90. $index.=&$indent."</li>\n" if $curlevel;
  91. $curlevel--;
  92. $index.=&$indent."</ol>\n";
  93. }
  94. $page=~s/(<div class=\"toc\">)/$1\n$index/;
  95. return $page;
  96. }
  97. 1