mirror of
https://github.com/kensanata/oddmuse.git
synced 2026-04-04 18:30:30 +09:00
377 lines
13 KiB
Perl
377 lines
13 KiB
Perl
#!/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<wiki/modules/>
|
||
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<wiki/config.pl>
|
||
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<creole.pl>) 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<creole.pl>) 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 <footnotes>
|
||
|
||
=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:
|
||
|
||
<footnotes>
|
||
|
||
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 "<footnotes>" 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 <ol>, 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 <http://www.raiazome.com>.
|
||
Copyright 2004 by Alex Schroeder <alex@emacswiki.org>.
|
||
|
||
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<http://www.gnu.org/licenses/>.
|
||
|
||
=cut
|