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