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