def footnotesToHtml(wb): matchFN = re.compile("<fn\s+(.*?)/?>") footnotes = {} # footnote marks in text i = 0 while i < len(wb): off = 0 line = wb[i] while True: m = matchFN.search(line, off) if not m: break opts = m.group(1) args = parseTagAttributes("fn", opts, [ "id", "target" ]) fmid = args["id"] if not "target" in args: fatal("Missing internal target in fn: " + line) target = args["target"] dprint(1, "id: " + fmid + ", target: " + target) if fmid in footnotes and footnotes[fmid] == target: cprint("warning: footnote id <fn id='" + fmid + "'> occurs multiple times. <footnote> link will be to the first.") repl = "<a href='#f{0}' style='text-decoration:none'><sup><span style='font-size:0.9em'>{1}</span></sup></a>".format(target, fmid) else: footnotes[fmid] = target repl = "<a id='r{0}'/><a href='#f{0}' style='text-decoration:none'><sup><span style='font-size:0.9em'>{1}</span></sup></a>".format(target, fmid) l = line[0:m.start(0)] + repl off = len(l) # Next loop line = l + line[m.end(0):] wb[i] = line i += 1 # footnote targets and text i = 0 while i < len(wb): m = re.match("<footnote\s+(.*?)>", wb[i]) if m: opts = m.group(1) args = parseTagAttributes("footnote", opts, [ "id", "target" ]) fnid = args["id"] target = args["target"] wb[i] = "<div id='f{0}'><a href='#r{0}'>{1}</a></div>".format(target, fnid) while not re.match("<\/footnote>", wb[i]): i += 1 wb[i] = "</div> <!-- footnote end -->" i += 1
def defineTemplate(self, opts, block): attributes = parseTagAttributes("template", opts, [ "name", "type" ]) if not "name" in attributes or not "type" in attributes: fatal("Template definition requires both name and type attributes: " + opts) name = attributes["name"] type = attributes["type"] dprint(1, "defining template name " + name + " of type " + type + ": " + str(block)) self.add(type, name, block) return []
def expandMacro(self, opts): attributes = parseTagAttributes("expand-macro", opts, [ "name", "vars" ]) if not "name" in attributes: fatal("expand-macro: No macro name given: " + line) template = self.get([ "macro" ], attributes["name"]) if "vars" in attributes: keys = parseOption("expand-macro", attributes["vars"]) else: keys = {} return template.expand(keys)
def footnoteRelocate(opts, block): opts = opts.strip() # Parse and recreate the footnote tag, to handle autonumbering footnotes args = parseTagAttributes("footnote", opts, [ "id" ]) if not "id" in args: fatal("<footnote> does not have id attribute: " + opts) id = args["id"] target = None if id == '#': nonlocal footnotec id = str(footnotec) if mode == asterisk and footnotec <= len(footnoteMarkers): displayid = footnoteMarkers[footnotec-1] else: displayid = "[" + id + "]" footnotec += 1 else: if id in footnoteMarkers: i = footnoteMarkers.index(id) displayid = id # Don't add the square brackets target = footnoteMarkersText[i] if not reset: fatal("Use of explicit footnote symbols requires footnote-location to be set to either asterisk or heading-reset: " + str(opts)) else: displayid = "[" + id + "]" if target == None: target = id if reset: target += "_" + str(footnoteChapter) opts = "id='" + displayid + "' target='" + target + "'" # Handle fn tags inside footnotes! relocateFootnotes(block) # Recreate the block block.insert(0, "<footnote " + opts + ">") block.append("</footnote>") if emitAtReference: noteMap[target] = block return [] # If we aren't supposed to move footnotes, do nothing if mode == none: return block # Otherwise accumulate them for emitting elsewhere nonlocal notes notes.append(block) # Clear the current location of the footnote return []
def FNtoHtml(wb): matchFN = re.compile("<fn\s+(.*?)/?>") footnotes = {} # footnote marks in text i = 0 while i < len(wb): off = 0 line = wb[i] block = [ ] while True: m = matchFN.search(line, off) if not m: break opts = m.group(1) args = parseTagAttributes("fn", opts, [ "id", "target" ]) fmid = args["id"] if not "target" in args: fatal("Missing internal target in fn: " + line) target = args["target"] dprint(1, "id: " + fmid + ", target: " + target) repl = "<sup><span style='font-size:0.9em'>" + fmid + "</span></sup>" if target in noteMap: # Note no link when we are co-locating the reference with the footnote block.extend(noteMap[target]) del noteMap[target] elif fmid in footnotes and footnotes[fmid] == target: wprint('multifootnote', "warning: footnote id <fn id='" + fmid + "'> occurs multiple times. <footnote> link will be to the first. Line: >>>" + line + "<<<") repl = "<a href='#f{0}' style='text-decoration:none'>{1}</a>".format(target, repl) else: footnotes[fmid] = target repl = "<a id='r{0}'/><a href='#f{0}' style='text-decoration:none'>{1}</a>".format(target, repl) l = line[0:m.start(0)] + repl off = len(l) # Next loop line = l + line[m.end(0):] wb[i] = line # Emit the footnote, right before the line with the footnote reference # If the line with the footnote reference is a paragraph start, need # to emit the footnote between the paragraph tag, and the rest of the # text on the first line of the paragraph if len(block) > 0: m = re.match("(<p.*?>)", wb[i]) if m: block.insert(0, m.group(1)) wb[i] = wb[i][len(m.group(1)):] wb[i:i] = block i += len(block) i += 1
def footnotesToSidenoteTags(wb): FNtoHtml(wb) # footnote targets and text i = 0 while i < len(wb): m = re.match("<footnote\s+(.*?)>", wb[i]) if m: opts = m.group(1) args = parseTagAttributes("footnote", opts, [ "id", "target" ]) fnid = args["id"] target = args["target"] wb[i] = "<sidenote>" + fnid while not re.match("<\/footnote>", wb[i]): i += 1 wb[i] = "</sidenote>" i += 1
def footnotesToHtmlTags(wb): FNtoHtml(wb) # footnote targets and text i = 0 while i < len(wb): m = re.match("<footnote\s+(.*?)>", wb[i]) if m: opts = m.group(1) args = parseTagAttributes("footnote", opts, [ "id", "target" ]) fnid = args["id"] target = args["target"] wb[i] = "<div class='footnote-id' id='f{0}'><a href='#r{0}'>{1}</a></div>".format(target, fnid) while not re.match("<\/footnote>", wb[i]): i += 1 wb[i] = "</div> <!-- footnote end -->" i += 1
def chapterTemplates(self, lines, properties, meta): # Figure out what template name to use. if "template-chapter" in properties: chapterTemplateName = properties["template-chapter"] else: chapterTemplateName = "default" if "template-chapter-first" in properties: chapterTemplateNameFirst = properties["template-chapter-first"] else: chapterTemplateNameFirst = "default-first" dprint(1, "Chapter Template: Using first: " + chapterTemplateNameFirst + \ ", subsequent: " + chapterTemplateName) # Now we can set the globals, since we have now extracted all the metadata self.setGlobals(meta) # Figure out which templates we are going to use. tFirst = self.get([ "chapter" ], chapterTemplateNameFirst) t = self.get([ "chapter" ], chapterTemplateName) regexMacro = re.compile("<expand-macro\s+(.*?)/?>") i = 0 first = True while i < len(lines): line = lines[i] if line.startswith("<chap-head"): keys = {} opts, keys["chap-head"] = parseLineEntry("chap-head", line) j = i+1 while j < len(lines) and re.match(lines[j], "^\s*$"): j += 1 if j == len(lines): fatal("End of file after <chap-head>") if opts != "": attributes = parseTagAttributes("chap-head", opts, [ "vars" ]) dprint(1, "<chap-head> attributes: " + str(attributes)) if "vars" in attributes: vars = parseOption("chap-head", attributes["vars"]) dprint(1, "<chap-head> vars: " + str(vars)) keys.update(vars) line = lines[j] if line.startswith("<sub-head"): opts, keys["sub-head"] = parseLineEntry("sub-head", line) else: # Do not eat this line! j -= 1 # If the first we've seen, it starts the book if first: templ = tFirst first = False else: templ = t dprint(1, "expand keys: " + str(keys)) replacement = templ.expand(keys) dprint(2, "replace " + str(lines[i:j+1]) + " with " + str(replacement)) lines[i:j+1] = replacement i += len(replacement) continue if line.startswith("<sub-head>"): fatal("Found <sub-head> not after a <chap-head>: " + line) # What about multiple macro expansions on a line? Or recursion? # Make it simpler for now by just punting: if you expand, then we move on # to the next line. m = regexMacro.search(line) if m: opts = m.group(1) attributes = parseTagAttributes("expand-macro", opts, [ "name", "vars" ]) if not "name" in attributes: fatal("expand-macro: No macro name given: " + line) template = self.get([ "macro" ], attributes["name"]) if "vars" in attributes: keys = parseOption("expand-macro", attributes["vars"]) else: keys = {} replacement = template.expand(keys) prefix = line[:m.start(0)] suffix = line[m.end(0):] if len(replacement) == 0: # If the template returns nothing, then you end up with a single line of # the prefix and suffix around the <expand-macro> replacement = [ prefix + suffix ] else: # Otherwise the prefix goes on the first line; and the suffix at the end of # the last; which might be the same single line. replacement[0] = prefix + replacement[0] replacement[-1] = replacement[-1] + suffix lines[i:i+1] = replacement i += len(replacement) continue i += 1
def processLine(i, line): nonlocal fnc, footnotec # Process <fn> tags, fixing id='#' with an appropriate number # Loop, can be multiple on a line. off = 0 while True: m = matchFN.search(line, off) if not m: break opts = m.group(1) args = parseTagAttributes("fn", opts, [ "id" ]) if not "id" in args: fatal("<fn> does not have id attribute: " + line) id = args["id"] target = None if id == '#': id = str(fnc) if mode == asterisk and fnc <= len(footnoteMarkers): displayid = footnoteMarkers[fnc-1] else: displayid = "[" + id + "]" fnc += 1 else: if id in footnoteMarkers: i = footnoteMarkers.index(id) displayid = id # Don't add the square brackets target = footnoteMarkersText[i] else: displayid = "[" + id + "]" if target == None: target = id if reset: nonlocal footnoteChapter target += "_" + str(footnoteChapter) opts = "id='" + displayid + "' target='" + target + "'" l = line[:m.start(0)] + "<fn " + opts + ">" off = len(l) # Start next loop after end of this line = l + line[m.end(0):] # Are we going to emit it here? # Always emit if we hit a genfootnotes, # emit when we hit a heading, but only in heading mode. emit = False if line.startswith("<genfootnotes>"): emit = True line = None # Remove the line, we don't want it! elif emitAtHeading: if line.startswith("<heading"): emit = True # If there weren't any, forget it, nothing to do if len(notes) == 0: emit = False if not emit: if line == None: return [] else: return [ line ] all = formatNotes(line) # If our mode is reset, then whenever we emit, we reset our counters if reset: fnc = 1 footnotec = 1 footnoteChapter += 1 return all