#!/usr/bin/env perl use strict; use v5.10; # ====================[ footnotes.pl ]==================== =encoding utf8 =head1 NAME footnotes - An Oddmuse module for adding footnotes to Oddmuse Wiki pages. =head1 INSTALLATION footnotes is easily installable; move this file into the B directory for your Oddmuse Wiki. =cut AddModuleDescription('footnotes.pl', 'Footnotes Extension'); our ($q, $bol, @MyRules, @MyInitVariables); # ....................{ CONFIGURATION }.................... =head1 CONFIGURATION footnotes is easily configurable; set these variables in the B file for your Oddmuse Wiki. =cut our ($FootnotePattern, $FootnotesPattern, $FootnotesHeaderText, @FootnoteList); =head2 $FootnotePattern A regular expression matching text within an Oddmuse Wiki page, which, when matched, replaces that text with a footnote reference. In other words, text matching this regular expression becomes a "footnote." If left unset, this regular expression takes one of two defaults - depending on which other Oddmuse markup modules are installed (so as not to conflict with those other Oddmuse markup modules' markup rules). =over =item (($FootnoteText)) =over =item If the Creole Markup module (B) is also installed, then this is the default regular expression for marking a footnote (where C<$FootnoteText> is the displayed text for that footnote). =back =item {{$FootnoteText}} =over =item If the Creole Markup module (B) is not installed, then this is the default regular expression for marking a footnote (where C<$FootnoteText> is the displayed text for that footnote). This is, also, the old default for this module. =back =back =cut $FootnotePattern = undef; =head2 $FootnotesPattern A regular expression matching text within an Oddmuse Wiki page, which, when matched, replaces that text with the set of all page footnotes. Any page with footnotes (i.e., any page with at least one string matching the C<$FootnotePattern>) should collect and show those footnotes somewhere in that page. Luckily, there are two mechanisms for effecting this - the first via explicit markup, and the second via implicit fallback; these are: =over =item =over =item If a page has markup explicitly matched by this regular expression, that markup is replaced by the set of footnotes for the page. =back =item N/A =over =item Otherwise, if a page has no such markup but does have at least one footnote, the set of footnotes for the page is automatically situated between the content and footer for that page. As this may, or may not, be the proper place for page footnotes, you're encouraged to explicitly provide page markup matched by this regular expression. =back =back =cut $FootnotesPattern = '\<footnotes\>[ \t]*(\n|$)'; =head2 $FootnotesHeaderText The string displayed as the header to the set of all page footnotes. =cut $FootnotesHeaderText = 'Footnotes:'; # ....................{ INITIALIZATION }.................... push(@MyInitVariables, \&FootnotesInit); sub FootnotesInit { @FootnoteList = (); if (not defined $FootnotePattern) { $FootnotePattern = defined &CreoleRule ? '\(\((.+?)\)\)' : '\{\{(.+?)\}\}'; } } # ....................{ MARKUP }.................... push(@MyRules, \&FootnotesRule); =head2 MARKUP =head3 CREATING FOOTNOTES footnotes handles markup resembling (assuming the Creole Markup module is also installed): (($FootnoteText)) C<$FootnoteText> is the text for that footnote. This extension replaces that text (and enclosing parentheses) with a numbered link to the footnote in the set of all footnotes for that page - usually, at the foot of the page. As example of a citation for Jared Diamond's "Collapse: How Societies Choose to Fail or Succeed" (2005), you might write: History suggests that societal decline does not result from a single cause, but rather the confluence of several interwoven causes.((Diamond, Jared. 2005. **Collapse: How Societies Choose to Fail or Succeed.** %%Viking, New York.%%)) Note that the example above embeds Wiki Creole syntax within the footnote definition itself. This is perfectly legal and, in fact, encouraged. =head3 CREATING MULTIPLE FOOTNOTES footnotes also handles markup resembling: (($FirstFootnoteText))(($NextFootnoteText)) C<$FirstFootnoteText> and C<$NextFootnoteText> are the text for two adjacent footnotes. These footnote definitions will be handled and displayed as above, except that the numbered link for the first footnote will be visually delimited from the numbered link for the footnote that follows it with a ", ". As example, you might write: History suggests that societal decline does not result from a single cause, but rather the confluence of several interwoven causes.((Diamond, Jared. 2005. **Collapse: How Societies Choose to Fail or Succeed.** %%Viking, New York.%%)) ((Tainter, Joseph. 1988. **The Collapse of Complex Societies.** %%Cambridge Univ Press, Cambridge, UK.%%)) =head3 REFERENCING ANOTHER FOOTNOTE footnotes also handles marking resembling: (($FootnoteNumber)) C<$FootnoteNumber> is the number for another footnote. This module assigns each footnote definition a unique number, beginning at "1". Thus, this markup allows you to reference one footnote definition in multiple places throughout a page. As example, you might write: History suggests that societal decline does not result from a single cause, but rather the confluence of several interwoven causes.((Diamond, Jared. 2005. **Collapse: How Societies Choose to Fail or Succeed.** %%Viking, New York.%%)) Such causes include a human-dominated ecosystem moving to a brittle, non- resilient state due to climatological changes.((Weiss H, Bradley RS. 2001. **What drives societal collapse?** %%Science 291:609–610.%%)) Societal decline only occurs, however, when socio-ecological systems become brittle and incapable of adaptation.((1)) The final footnote, above, is a reference to the first footnote definition rather than a new footnote definition. =head3 REFERENCING A RANGE OF OTHER FOOTNOTES footnotes also handles marking resembling: (($FirstFootnoteNumber-$LastFootnoteNumber)) C<$FirstFootnoteNumber> and C<$LastFootnoteNumber> are the numbers for two other footnotes. Thus, this markup allows you to reference a range of footnote definitions in multiple places throughout a page. As example, you might write: History suggests that societal decline does not result from a single cause, but rather the confluence of several interwoven causes.((Diamond, Jared. 2005. **Collapse: How Societies Choose to Fail or Succeed.** %%Viking, New York.%%)) Such causes include a human-dominated ecosystem moving to a brittle, non- resilient state due to climatological changes((Weiss H, Bradley RS. 2001. **What drives societal collapse?** %%Science 291:609–610.%%)), external forcings((Tainter, Jared. 2006. **Social complexity and sustainability.** %%Ecol Complex 3:91–103.%%)), or internal pressures((Cullen HM, et al. 2000. **Climate change and the collapse of the Akkadian empire: Evidence from the deep sea.** %%Geology 28:379–382.%%)). Societal decline only occurs, however, when socio-ecological systems become brittle and incapable of adaptation.((1-2))((4)) The final footnotes, above, are a reference to the first two footnote definitions followed by a reference to the fourth footnote definition. This module visually renders this disjoint list like: "1-2, 4". =head3 CREATING THE SET OF FOOTNOTES footnotes also handles markup resembling: This extension replaces that markup with the set of all footnotes for that page. Note that, if that page has no such markup, this extension automatically places the set of all footnotes for that page between the content and footer for that page. (This may or not be what you want, of course.) =cut sub FootnotesRule { # A "((...))" footnote anywhere in a page. # # Footnotes and the set of all footnotes must be marked so as to ensure their # reevaluation, as each of the footnotes might contain Wiki markup requiring # reevaluation (like, say, free links). if (m/\G($FootnotePattern)(?=([ \t]*$FootnotePattern)?)/cgs) { Dirty($1); # do not cache the prefixing "\G" my $footnote_text = $2; my $is_adjacent_footnote = defined $3; # A number range (e.g., "2-5") of references to other footnotes. if ($footnote_text =~ m/^(\d+)-(\d+)$/) { my ($footnote_number_first, $footnote_number_last) = ($1, $2); # '–', below, is the HTML entity for a Unicode en-dash. print $q->a({-href=> '#footnotes' .$footnote_number_first, -title=> 'Footnote #'.$footnote_number_first, -class=> 'footnote' }, $footnote_number_first.'–') .$q->a({-href=> '#footnotes' .$footnote_number_last, -title=> 'Footnote #'.$footnote_number_last, -class=> 'footnote' }, $footnote_number_last.($is_adjacent_footnote ? ', ' : '')); } # A number (e.g., "5") implying reference to another footnote. elsif ($footnote_text =~ m/^(\d+)$/) { my $footnote_number = $1; print $q->a({-href=> '#footnotes' .$footnote_number, -title=> 'Footnote #'.$footnote_number, -class=> 'footnote' }, $footnote_number.($is_adjacent_footnote ? ', ' : '')); } # Otherwise, a new footnote definition. else { push(@FootnoteList, $footnote_text); my $footnote_number = @FootnoteList; print $q->a({-href=> '#footnotes'.$footnote_number, -name=> 'footnote' .$footnote_number, -title=> 'Footnote: '. # Truncate link titles to one line. ( length($footnote_text) > 48 ? substr($footnote_text, 0, 44).'...' : $footnote_text), -class=> 'footnote' }, $footnote_number.($is_adjacent_footnote ? ', ' : '')); } return ''; } # The "" list of all footnotes at the foot of a page. elsif ($bol && m/\G($FootnotesPattern)/cgis) { Clean(CloseHtmlEnvironments()); Dirty($1); # do not cache the prefixing "\G" if (@FootnoteList) { my ($oldpos, $old_) = (pos, $_); PrintFootnotes(); Clean(AddHtmlEnvironment('p')); # if dirty block is looked at later, this will disappear ($_, pos) = ($old_, $oldpos); # restore \G (assignment order matters!) } return ''; } return; } # ....................{ HTML OUTPUT }.................... *PrintFooterFootnotesOld = \&PrintFooter; *PrintFooter = \&PrintFooterFootnotes; =head2 PrintFooterFootnotes Appends the list of footnotes to the footer of the page, if and only if the user-provided content for that page had no content matching C<$FootersPattern>. Thus, this function is an eleventh-hour fallback; ideally, pages providing footnotes also provide an explicit place to list those footnotes. =cut sub PrintFooterFootnotes { my @params = @_; if (@FootnoteList) { PrintFootnotes(); } PrintFooterFootnotesOld(@params); } =head2 PrintFootnotes Prints the list of footnotes. =cut sub PrintFootnotes { print $q->start_div({-class=> 'footnotes'}) .$q->h2(T($FootnotesHeaderText)); # Don't use
    , because we want to link from the number back to # its page location. my $footnote_number = 1; foreach my $footnote (@FootnoteList) { print $q->start_div({-class=> 'footnote'}) .$q->a({-class=> 'footnote_backlink', -name=> 'footnotes'.$footnote_number, -href=> '#footnote' .$footnote_number}, $footnote_number.'.') .' '; ApplyRules($footnote, 1); print $q->end_div(); $footnote_number++; } print $q->end_div(); # Empty the footnotes, now; this prevents our calling the fallback, later. @FootnoteList = (); } =head1 COPYRIGHT AND LICENSE The information below applies to everything in this distribution, except where noted. Copyleft 2008 by B.w.Curry . Copyright 2004 by Alex Schroeder . This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see L. =cut