footnotes.pl 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. #!/usr/bin/env perl
  2. use strict;
  3. use v5.10;
  4. # ====================[ footnotes.pl ]====================
  5. =encoding utf8
  6. =head1 NAME
  7. footnotes - An Oddmuse module for adding footnotes to Oddmuse Wiki pages.
  8. =head1 INSTALLATION
  9. footnotes is easily installable; move this file into the B<wiki/modules/>
  10. directory for your Oddmuse Wiki.
  11. =cut
  12. AddModuleDescription('footnotes.pl', 'Footnotes Extension');
  13. our ($q, $bol, @MyRules, @MyInitVariables);
  14. # ....................{ CONFIGURATION }....................
  15. =head1 CONFIGURATION
  16. footnotes is easily configurable; set these variables in the B<wiki/config.pl>
  17. file for your Oddmuse Wiki.
  18. =cut
  19. our ($FootnotePattern,
  20. $FootnotesPattern,
  21. $FootnotesHeaderText,
  22. @FootnoteList);
  23. =head2 $FootnotePattern
  24. A regular expression matching text within an Oddmuse Wiki page, which, when
  25. matched, replaces that text with a footnote reference. In other words, text
  26. matching this regular expression becomes a "footnote."
  27. If left unset, this regular expression takes one of two defaults - depending on
  28. which other Oddmuse markup modules are installed (so as not to conflict with
  29. those other Oddmuse markup modules' markup rules).
  30. =over
  31. =item (($FootnoteText))
  32. =over
  33. =item If the Creole Markup module (B<creole.pl>) is also installed, then this
  34. is the default regular expression for marking a footnote (where
  35. C<$FootnoteText> is the displayed text for that footnote).
  36. =back
  37. =item {{$FootnoteText}}
  38. =over
  39. =item If the Creole Markup module (B<creole.pl>) is not installed, then this
  40. is the default regular expression for marking a footnote (where
  41. C<$FootnoteText> is the displayed text for that footnote). This is, also,
  42. the old default for this module.
  43. =back
  44. =back
  45. =cut
  46. $FootnotePattern = undef;
  47. =head2 $FootnotesPattern
  48. A regular expression matching text within an Oddmuse Wiki page, which, when
  49. matched, replaces that text with the set of all page footnotes.
  50. Any page with footnotes (i.e., any page with at least one string matching the
  51. C<$FootnotePattern>) should collect and show those footnotes somewhere in that
  52. page. Luckily, there are two mechanisms for effecting this - the first via
  53. explicit markup, and the second via implicit fallback; these are:
  54. =over
  55. =item <footnotes>
  56. =over
  57. =item If a page has markup explicitly matched by this regular expression, that
  58. markup is replaced by the set of footnotes for the page.
  59. =back
  60. =item N/A
  61. =over
  62. =item Otherwise, if a page has no such markup but does have at least one
  63. footnote, the set of footnotes for the page is automatically situated
  64. between the content and footer for that page. As this may, or may not, be
  65. the proper place for page footnotes, you're encouraged to explicitly
  66. provide page markup matched by this regular expression.
  67. =back
  68. =back
  69. =cut
  70. $FootnotesPattern = '\&lt;footnotes\&gt;[ \t]*(\n|$)';
  71. =head2 $FootnotesHeaderText
  72. The string displayed as the header to the set of all page footnotes.
  73. =cut
  74. $FootnotesHeaderText = 'Footnotes:';
  75. # ....................{ INITIALIZATION }....................
  76. push(@MyInitVariables, \&FootnotesInit);
  77. sub FootnotesInit {
  78. @FootnoteList = ();
  79. if (not defined $FootnotePattern) {
  80. $FootnotePattern = defined &CreoleRule ? '\(\((.+?)\)\)' : '\{\{(.+?)\}\}';
  81. }
  82. }
  83. # ....................{ MARKUP }....................
  84. push(@MyRules, \&FootnotesRule);
  85. =head2 MARKUP
  86. =head3 CREATING FOOTNOTES
  87. footnotes handles markup resembling (assuming the Creole Markup module is also
  88. installed):
  89. (($FootnoteText))
  90. C<$FootnoteText> is the text for that footnote. This extension replaces that
  91. text (and enclosing parentheses) with a numbered link to the footnote in the set
  92. of all footnotes for that page - usually, at the foot of the page. As example of
  93. a citation for Jared Diamond's "Collapse: How Societies Choose to Fail or
  94. Succeed" (2005), you might write:
  95. History suggests that societal decline does not result from a single cause,
  96. but rather the confluence of several interwoven causes.((Diamond, Jared. 2005.
  97. **Collapse: How Societies Choose to Fail or Succeed.** %%Viking, New York.%%))
  98. Note that the example above embeds Wiki Creole syntax within the footnote
  99. definition itself. This is perfectly legal and, in fact, encouraged.
  100. =head3 CREATING MULTIPLE FOOTNOTES
  101. footnotes also handles markup resembling:
  102. (($FirstFootnoteText))(($NextFootnoteText))
  103. C<$FirstFootnoteText> and C<$NextFootnoteText> are the text for two adjacent
  104. footnotes. These footnote definitions will be handled and displayed as above,
  105. except that the numbered link for the first footnote will be visually delimited
  106. from the numbered link for the footnote that follows it with a ", ". As example,
  107. you might write:
  108. History suggests that societal decline does not result from a single cause,
  109. but rather the confluence of several interwoven causes.((Diamond, Jared. 2005.
  110. **Collapse: How Societies Choose to Fail or Succeed.** %%Viking, New York.%%))
  111. ((Tainter, Joseph. 1988. **The Collapse of Complex Societies.** %%Cambridge
  112. Univ Press, Cambridge, UK.%%))
  113. =head3 REFERENCING ANOTHER FOOTNOTE
  114. footnotes also handles marking resembling:
  115. (($FootnoteNumber))
  116. C<$FootnoteNumber> is the number for another footnote. This module assigns each
  117. footnote definition a unique number, beginning at "1". Thus, this markup allows
  118. you to reference one footnote definition in multiple places throughout a page.
  119. As example, you might write:
  120. History suggests that societal decline does not result from a single cause,
  121. but rather the confluence of several interwoven causes.((Diamond, Jared. 2005.
  122. **Collapse: How Societies Choose to Fail or Succeed.** %%Viking, New York.%%))
  123. Such causes include a human-dominated ecosystem moving to a brittle, non-
  124. resilient state due to climatological changes.((Weiss H, Bradley RS. 2001.
  125. **What drives societal collapse?** %%Science 291:609–610.%%))
  126. Societal decline only occurs, however, when socio-ecological systems become
  127. brittle and incapable of adaptation.((1))
  128. The final footnote, above, is a reference to the first footnote definition
  129. rather than a new footnote definition.
  130. =head3 REFERENCING A RANGE OF OTHER FOOTNOTES
  131. footnotes also handles marking resembling:
  132. (($FirstFootnoteNumber-$LastFootnoteNumber))
  133. C<$FirstFootnoteNumber> and C<$LastFootnoteNumber> are the numbers for two
  134. other footnotes. Thus, this markup allows you to reference a range of footnote
  135. definitions in multiple places throughout a page. As example, you might write:
  136. History suggests that societal decline does not result from a single cause,
  137. but rather the confluence of several interwoven causes.((Diamond, Jared. 2005.
  138. **Collapse: How Societies Choose to Fail or Succeed.** %%Viking, New York.%%))
  139. Such causes include a human-dominated ecosystem moving to a brittle, non-
  140. resilient state due to climatological changes((Weiss H, Bradley RS. 2001.
  141. **What drives societal collapse?** %%Science 291:609–610.%%)), external
  142. forcings((Tainter, Jared. 2006. **Social complexity and sustainability.**
  143. %%Ecol Complex 3:91–103.%%)), or internal pressures((Cullen HM, et al. 2000.
  144. **Climate change and the collapse of the Akkadian empire: Evidence from the
  145. deep sea.** %%Geology 28:379–382.%%)).
  146. Societal decline only occurs, however, when socio-ecological systems become
  147. brittle and incapable of adaptation.((1-2))((4))
  148. The final footnotes, above, are a reference to the first two footnote
  149. definitions followed by a reference to the fourth footnote definition. This
  150. module visually renders this disjoint list like: "1-2, 4".
  151. =head3 CREATING THE SET OF FOOTNOTES
  152. footnotes also handles markup resembling:
  153. <footnotes>
  154. This extension replaces that markup with the set of all footnotes for that page.
  155. Note that, if that page has no such markup, this extension automatically places
  156. the set of all footnotes for that page between the content and footer for that
  157. page. (This may or not be what you want, of course.)
  158. =cut
  159. sub FootnotesRule {
  160. # A "((...))" footnote anywhere in a page.
  161. #
  162. # Footnotes and the set of all footnotes must be marked so as to ensure their
  163. # reevaluation, as each of the footnotes might contain Wiki markup requiring
  164. # reevaluation (like, say, free links).
  165. if (m/\G($FootnotePattern)(?=([ \t]*$FootnotePattern)?)/cgs) {
  166. Dirty($1); # do not cache the prefixing "\G"
  167. my $footnote_text = $2;
  168. my $is_adjacent_footnote = defined $3;
  169. # A number range (e.g., "2-5") of references to other footnotes.
  170. if ($footnote_text =~ m/^(\d+)-(\d+)$/) {
  171. my ($footnote_number_first, $footnote_number_last) = ($1, $2);
  172. # '&#x2013;', below, is the HTML entity for a Unicode en-dash.
  173. print $q->a({-href=> '#footnotes' .$footnote_number_first,
  174. -title=> 'Footnote #'.$footnote_number_first,
  175. -class=> 'footnote'
  176. }, $footnote_number_first.'&#x2013;')
  177. .$q->a({-href=> '#footnotes' .$footnote_number_last,
  178. -title=> 'Footnote #'.$footnote_number_last,
  179. -class=> 'footnote'
  180. }, $footnote_number_last.($is_adjacent_footnote ? ', ' : ''));
  181. }
  182. # A number (e.g., "5") implying reference to another footnote.
  183. elsif ($footnote_text =~ m/^(\d+)$/) {
  184. my $footnote_number = $1;
  185. print $q->a({-href=> '#footnotes' .$footnote_number,
  186. -title=> 'Footnote #'.$footnote_number,
  187. -class=> 'footnote'
  188. }, $footnote_number.($is_adjacent_footnote ? ', ' : ''));
  189. }
  190. # Otherwise, a new footnote definition.
  191. else {
  192. push(@FootnoteList, $footnote_text);
  193. my $footnote_number = @FootnoteList;
  194. print $q->a({-href=> '#footnotes'.$footnote_number,
  195. -name=> 'footnote' .$footnote_number,
  196. -title=> 'Footnote: '. # Truncate link titles to one line.
  197. ( length($footnote_text) > 48
  198. ? substr($footnote_text, 0, 44).'...'
  199. : $footnote_text),
  200. -class=> 'footnote'
  201. }, $footnote_number.($is_adjacent_footnote ? ', ' : ''));
  202. }
  203. return '';
  204. }
  205. # The "<footnotes>" list of all footnotes at the foot of a page.
  206. elsif ($bol && m/\G($FootnotesPattern)/cgis) {
  207. Clean(CloseHtmlEnvironments());
  208. Dirty($1); # do not cache the prefixing "\G"
  209. if (@FootnoteList) {
  210. my ($oldpos, $old_) = (pos, $_);
  211. PrintFootnotes();
  212. Clean(AddHtmlEnvironment('p')); # if dirty block is looked at later, this will disappear
  213. ($_, pos) = ($old_, $oldpos); # restore \G (assignment order matters!)
  214. }
  215. return '';
  216. }
  217. return;
  218. }
  219. # ....................{ HTML OUTPUT }....................
  220. *PrintFooterFootnotesOld = \&PrintFooter;
  221. *PrintFooter = \&PrintFooterFootnotes;
  222. =head2 PrintFooterFootnotes
  223. Appends the list of footnotes to the footer of the page, if and only if the
  224. user-provided content for that page had no content matching C<$FootersPattern>.
  225. Thus, this function is an eleventh-hour fallback; ideally, pages providing
  226. footnotes also provide an explicit place to list those footnotes.
  227. =cut
  228. sub PrintFooterFootnotes {
  229. my @params = @_;
  230. if (@FootnoteList) { PrintFootnotes(); }
  231. PrintFooterFootnotesOld(@params);
  232. }
  233. =head2 PrintFootnotes
  234. Prints the list of footnotes.
  235. =cut
  236. sub PrintFootnotes {
  237. print
  238. $q->start_div({-class=> 'footnotes'})
  239. .$q->h2(T($FootnotesHeaderText));
  240. # Don't use <ol>, because we want to link from the number back to
  241. # its page location.
  242. my $footnote_number = 1;
  243. foreach my $footnote (@FootnoteList) {
  244. print
  245. $q->start_div({-class=> 'footnote'})
  246. .$q->a({-class=> 'footnote_backlink',
  247. -name=> 'footnotes'.$footnote_number,
  248. -href=> '#footnote' .$footnote_number}, $footnote_number.'.')
  249. .' ';
  250. ApplyRules($footnote, 1);
  251. print $q->end_div();
  252. $footnote_number++;
  253. }
  254. print $q->end_div();
  255. # Empty the footnotes, now; this prevents our calling the fallback, later.
  256. @FootnoteList = ();
  257. }
  258. =head1 COPYRIGHT AND LICENSE
  259. The information below applies to everything in this distribution,
  260. except where noted.
  261. Copyleft 2008 by B.w.Curry <http://www.raiazome.com>.
  262. Copyright 2004 by Alex Schroeder <alex@emacswiki.org>.
  263. This program is free software; you can redistribute it and/or modify
  264. it under the terms of the GNU General Public License as published by
  265. the Free Software Foundation; either version 3 of the License, or
  266. (at your option) any later version.
  267. This program is distributed in the hope that it will be useful,
  268. but WITHOUT ANY WARRANTY; without even the implied warranty of
  269. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  270. GNU General Public License for more details.
  271. You should have received a copy of the GNU General Public License
  272. along with this program. If not, see L<http://www.gnu.org/licenses/>.
  273. =cut