diff --git a/Skripten/EOAconvert.py b/Skripten/EOAconvert.py index 70d8027..2b29051 100755 --- a/Skripten/EOAconvert.py +++ b/Skripten/EOAconvert.py @@ -803,15 +803,22 @@ def cleanup(): intNoteNumber += 1 +# the new-style footnotes that use LaTeX bigfoot show up in the following order: footnote_groups = ["decimal", "lower-latin"] def get_bigfoot_data(chapter): - # footnotes are per-chapter, with numbers resetting + """ + footnotes are per-chapter + footnote numbers reset for each chapter + this helper takes a chapter and returns a collection containing its new-style footnotes that use LaTeX bigfoot + the result is an association list: a list of key-value pairs + the values are, for each type of footnote, a list of the footnotes of that type, in the order in which they appear in the chapter + """ xmlBigfootNotes = list(chapter.findall(".//EOAbigfoot")) - return [ - ( - grouping, - [ + return [ # a list + ( # of tuples + grouping, # the key + [ # the value: a filter of the above list note for note in xmlBigfootNotes @@ -819,7 +826,7 @@ def get_bigfoot_data(chapter): ], ) for grouping - in footnote_groups + in footnote_groups # the types we support ] @@ -1508,15 +1515,38 @@ def get_bigfoot_data(chapter): print ("-----------------------------------------------------") print ("Preparing Footnotes") -def alph_fndex(fndex): +def alph_footnote_index(fndex): + """ + lowercase Latin footnotes need to support more than 26 values + These are zero-indexed. + + >>> alph_footnote_index(0) + 'a' + >>> alph_footnote_index(1) + 'b' + >>> alph_footnote_index(24) + 'y' + >>> alph_footnote_index(25) + 'z' + >>> alph_footnote_index(26) + 'aa' + >>> alph_footnote_index(27) + 'ab' + """ alphabet = "abcdefghijklmnopqrstuvwxyz" quotient, remainder = divmod(fndex, len(alphabet)) if not quotient: return alphabet[fndex] - return alph_fndex(quotient - 1) + alph_fndex(remainder) + return alph_footnote_index(quotient - 1) + alph_footnote_index(remainder) def replace_footnote_equations(footnote): - # usage: contentopf = replace_footnote_equations(my_footnote) + """ + captures reusable behavior from the existing code + potentially, some of the old code could be replaced by calls to this helper + + usage: contentopf = replace_footnote_equations(my_footnote) + unfortunately, returning the result seemed like a better idea than mutating the global variable + """ result = contentopf for equation in footnote.findall(".//EOAequationnonumber"): filename = equation.get("filename") @@ -1531,6 +1561,13 @@ def replace_footnote_equations(footnote): def replace_footnote_with_sup(note): + """ + captures reusable behavior from the existing code + potentially, some of the old code could be replaced by calls to this helper + + this behavior showed up in a few places + I thought I would be able to extract a little more, but this was all that was actually common + """ tail = note.tail note.clear() note.tail = tail @@ -1538,35 +1575,85 @@ def replace_footnote_with_sup(note): def bring_footnote_down_epub(footnote, footnote_name, destination): - contentopf = replace_footnote_equations(footnote) + """ + captures reusable behavior from the existing code + potentially, some of the old code could be replaced by calls to this helper + """ + + contentopf = replace_footnote_equations(footnote) # see usage note kids = list(footnote.getchildren()) - if len(kids): - first_child = kids[0] - first_child.text = "[%s] %s" % (footnote_name, (first_child.text or "")) + prefix = "[%s]" % footnote_name + + # we would like to prepend this footnote identifier to the footnote element + if footnote.text is not None: + # if the element starts with some text anyway, prepend it there + footnote.text = "%s %s" (prefix, footnote.text) + else: + # if, however, the element begins with a child, prepend the text at the beginning of the first child instead + if len(kids): + first_child = kids[0] + child_text = prefix + # separate them with a space, unless the child had no text to begin with + child_suffix = first_child.text + if child_suffix is None: + child_suffix = "" + else: + child_prefix += " " + first_child.text = child_prefix + child_suffix + else: + # a totally empty footnote is weird, but who am I to judge? + footnote.text = prefix + footnote_text = footnote.text or "" replace_footnote_with_sup(footnote) footnote.text = "[%s] " % footnote_name + # append any text the footnote used to have to the destination + destkids = list(destination.getchildren()) + if len(destkids): + # if the destination has children, append after the last one's tail + last_kid = destkids[-1] + prefix = last_kid.tail + if prefix is None: + prefix = "" + else: + prefix += " " + last_kid.tail = prefix + footnote_text + else: + # if the destination has no children, append to its text + prefix = destination.text + if prefix is None: + prefix = "" + else: + prefix += " " + destination.text = prefix + footnote_text for kid in kids: destination.append(kid) return contentopf class FootnoteError(Exception): + """ + we only support one type of footnote per chapter + don't try to mix-and-match + """ pass + for xmlChapter in xmlChapters: groupings = get_bigfoot_data(xmlChapter) xmlFootnotes = list(xmlChapter.findall(".//note")) has_old = 0 != len(xmlFootnotes) has_new = 0 != len( - [ + [ # flatten the association list whose values are lists, so we can take the length note for grouping, notes in groupings for note in notes ] ) + + # the XOR case falls through, the AND is an error, and the NOR skips to the next chapter if has_old: if has_new: - raise FootnoteError("This chapter contains both old-style footnotes and new-style footnotes") + raise FootnoteError("Chapter %s contains both \\EOAfn and footnotes in the style of \\EOAfnalph" % xmlChapter.get("id-text")) else: if not has_new: continue @@ -1575,10 +1662,11 @@ class FootnoteError(Exception): xmlNewFootnotesHeader.text = dictLangFootnotes[xmlChapter.get("language")] xmlNewFootnotes.append(xmlNewFootnotesHeader) for grouping, notes in groupings: + # do for the new-style footnotes what was being done for the old for index, note in enumerate(notes): footnote_name = str(index + 1) if "lower-latin" == grouping: - footnote_name = alph_fndex(index) + footnote_name = alph_footnote_index(index) para = etree.Element("p") para.text = "[%s] %s" % (footnote_name, note.text) contentopf = bring_footnote_down_epub(note, footnote_name, para) @@ -2792,6 +2880,11 @@ def djangoParseHeadline(xmlElement): print ("Processing and linking Footnotes for django") def bring_footnote_down_django(footnote, fragment, footnote_number, object_number, unique_id, destination): + """ + captures reusable behavior from the existing code + potentially, some of the old code could be replaced by calls to this helper + """ + kids = list(footnote.getchildren()) footnote_text = footnote.text or "" replace_footnote_with_sup(footnote) @@ -2834,12 +2927,13 @@ def bring_footnote_down_django(footnote, fragment, footnote_number, object_numbe groupings = get_bigfoot_data(xmlEOAchapter) has_old = 0 != len(xmlEOAchapter.findall(".//note")) has_new = 0 != len( - [ + [ # flatten note for grouping, notes in groupings for note in notes ] ) + # XOR falls through, AND is an error (that should have already been thrown during the epub phase), and NOR skips to the next chapter if has_old: if has_new: raise FootnoteError("This chapter contains both old-style footnotes and new-style footnotes") @@ -2865,9 +2959,10 @@ def bring_footnote_down_django(footnote, fragment, footnote_number, object_numbe for grouping, notes in groupings: for index, note in enumerate(notes): + # do for the new-style notes what the old code did for the other footnotes fntext = str(index+1) if "lower-latin" == grouping: - fntext = alph_fndex(index) + fntext = alph_footnote_index(index) unique_id = "fn%s" % fntext intObjectNumber = bring_footnote_down_django(note, unique_id, fntext, intObjectNumber, unique_id, xmlResult)