#!/usr/bin/env python
"""Usage: FootnoteSorter.py [-a] [--test] [< input.txt]
-a Number footnotes in order of apperance in text
--test Self-testing using the SAMPLE in the script source.
"""
SAMPLE = '''A great brown fox [13] jumped of a pile of lorem ipsum [4], [7]. He
met with a silver penguin, browsing the Linux Kernel Mailinglist [3]. They
debated other the question whether to start a C-program with "main (int argc,
char **argv)" or with "main (int argc, char *argv[])". Square brackets annoyed
them [9999]. Multiple references may exist to same targets [4].
@footnote:
[13] Al Fabetus: "On characters and animals", 1888, self published.
[4] Lorem Ipsum, <a href="
http://en.wikipedia.org/wiki/Lorem_ipsum">Web Link</a>
[9999] Annoying Link.
[7] B. Fox: "More on Blind Text".
[3] Linux Kernel Maintainers: LKML
'''
import sys, re, fileinput, tempfile, os
FOOTNOTE_PATTERN = re.compile(r'\[\d+\]')
def checkOption(args, option):
'''Check if an option is contained in the array args.
If it is in the array, it will be deleted.'''
try:
index = args.index (option)
del args[index]
return True
except ValueError:
return False
def fileFilter(input, old2New):
'''Filter every line of input.'''
def op(m, old2New=old2New):
'''This gets called for every match of the FOOTNOTE_PATTERN.'''
key = m.group(0)
# Check if we already have a mapping for this reference
newValue = old2New.get(key)
if newValue is None:
# If not, create a new one
newValue = '[%d]' % (len(old2New) + 1)
old2New[key] = newValue
return newValue
# Read every line of input and replace the footnote references in it
# using the operator defined above.
for line in input:
line = FOOTNOTE_PATTERN.sub(op, line)
sys.stdout.write(line)
if __name__ == '__main__':
isTest = checkOption (sys.argv, '--test')
byFirstOccurrence = checkOption (sys.argv, '-a')
if isTest:
# Create a text input from the sample
input = SAMPLE.split('\n')
for i in range(0,len(input)):
input[i] = '%s\n' % input[i]
else:
input = fileinput.FileInput(openhook=fileinput.hook_compressed)
if byFirstOccurrence:
# This is a simple case. Just read the file
# and build the map as we encounter references.
fileFilter (input, {})
else:
# Here, we need a temporary file because we have to do
# two passes. In the first pass, we figure out the
# correct order by reading the footnote block.
#
# In the second pass, we use the mapping to filter
# the file just as in the simple case.
#
# Since the filter will find existing mappings, it won't
# create them by itself and thus, we get the desired result.
tmpFD, tmpFile = tempfile.mkstemp(text=True)
try:
fh = os.fdopen(tmpFD, 'w+')
old2New = {}
isText = True
for line in input:
# Make a copy of the input
fh.write(line)
if isText:
if line.startswith('@footnote:'):
isText = False
continue
else:
# If we are in the footnote part, create the mapping
# by adding new references as we encounter them.
pos = line.find(']')
if pos == -1:
continue
oldValue = line[0:pos+1]
newValue = '[%d]' % (len(old2New) + 1)
old2New[oldValue] = newValue
# Read the temporary file again from the start
fh.seek(0)
# and filter it just as above.
fileFilter (fh, old2New)
fh.close()
finally:
# Always delete the temporary file
os.remove(tmpFile)