From bdce0e12c7b0d415dd125197e2ac3ab63a9257e3 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Tue, 16 Apr 2024 21:54:31 +0200
Subject: [PATCH] Simplify footnote handling in writer classes
---
novelwriter/core/index.py | 2 +-
novelwriter/core/tohtml.py | 17 +++++++++-------
novelwriter/core/tokenizer.py | 6 ++----
novelwriter/core/tomd.py | 18 +++++++++--------
novelwriter/core/toodt.py | 37 +++++++++++++----------------------
novelwriter/enum.py | 13 ++++++------
6 files changed, 44 insertions(+), 49 deletions(-)
diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py
index c46bff04..13f75407 100644
--- a/novelwriter/core/index.py
+++ b/novelwriter/core/index.py
@@ -1494,4 +1494,4 @@ def processComment(text: str) -> tuple[nwComment, str, str, int, int]:
if content and (clean := classifier.strip().lower()) in CLASSIFIERS:
term = "ERR" if term and clean not in TERMS else term.strip()
return CLASSIFIERS[clean], term, content.strip(), text.find(".") + 1, text.find(":") + 1
- return nwComment.PLAIN, "", check, 0, 0
+ return nwComment.IGNORE if text.startswith("%~") else nwComment.PLAIN, "", check, 0, 0
diff --git a/novelwriter/core/tohtml.py b/novelwriter/core/tohtml.py
index 604799be..1384c96e 100644
--- a/novelwriter/core/tohtml.py
+++ b/novelwriter/core/tohtml.py
@@ -94,7 +94,7 @@ class ToHtml(Tokenizer):
# Internals
self._trMap = {}
- self._usedNotes = set()
+ self._usedNotes: dict[str, int] = {}
self.setReplaceUnicode(False)
return
@@ -313,9 +313,9 @@ class ToHtml(Tokenizer):
lines = []
lines.append(f"
{footnotes}
\n")
lines.append("\n")
- for key, (index, content) in self._footnotes.items():
- if key in self._usedNotes:
- text = "".join(self._formatText(t, f, tags) for t, f in content)
+ for key, index in self._usedNotes.items():
+ if content := self._footnotes.get(key):
+ text = self._formatText(*content, tags)
lines.append(f"
\n")
lines.append("
\n")
@@ -482,9 +482,12 @@ class ToHtml(Tokenizer):
for pos, fmt, data in reversed(tFmt):
html = ""
if fmt == self.FMT_FNOTE:
- self._usedNotes.add(data)
- index = self._footnotes.get(data, (0, ""))[0] or "ERR"
- html = f"[{index}]"
+ if data in self._footnotes:
+ index = len(self._usedNotes) + 1
+ self._usedNotes[data] = index
+ html = f"{index}"
+ else:
+ html = "ERR"
else:
html = tags.get(fmt, "ERR")
temp = f"{temp[:pos]}{html}{temp[pos:]}"
diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py
index db1a5f3f..ad9ea768 100644
--- a/novelwriter/core/tokenizer.py
+++ b/novelwriter/core/tokenizer.py
@@ -49,7 +49,7 @@ ESCAPES = {r"\*": "*", r"\~": "~", r"\_": "_", r"\[": "[", r"\]": "]", r"\ ": ""
RX_ESC = re.compile("|".join([re.escape(k) for k in ESCAPES.keys()]), flags=re.DOTALL)
T_Formats = list[tuple[int, int, str]]
-T_Comment = tuple[int, list[tuple[str, T_Formats]]]
+T_Comment = tuple[str, T_Formats]
def stripEscape(text: str) -> str:
@@ -561,9 +561,7 @@ class Tokenizer(ABC):
tmpMarkdown.append(f"{aLine}\n")
elif cStyle == nwComment.FOOTNOTE:
tLine, tFmt = self._extractFormats(cText, skip=self.FMT_FNOTE)
- if cKey not in self._footnotes:
- self._footnotes[cKey] = (len(self._footnotes) + 1, [])
- self._footnotes[cKey][1].append((tLine, tFmt))
+ self._footnotes[cKey] = (tLine, tFmt)
else:
tLine, tFmt = self._extractFormats(cText)
self._tokens.append((
diff --git a/novelwriter/core/tomd.py b/novelwriter/core/tomd.py
index 1d5fa318..bcf8a639 100644
--- a/novelwriter/core/tomd.py
+++ b/novelwriter/core/tomd.py
@@ -89,7 +89,7 @@ class ToMarkdown(Tokenizer):
self._genMode = self.M_STD
self._fullMD: list[str] = []
self._preserveBreaks = True
- self._usedNotes = set()
+ self._usedNotes: dict[str, int] = {}
return
##
@@ -208,11 +208,10 @@ class ToMarkdown(Tokenizer):
lines = []
lines.append(f"### {footnotes}\n\n")
- for key, (index, content) in self._footnotes.items():
- if key in self._usedNotes:
+ for key, index in self._usedNotes.items():
+ if content := self._footnotes.get(key):
marker = f"{index}. "
- indent = "\n\n"+" "*len(marker)
- text = indent.join(self._formatText(t, f, tags) for t, f in content)
+ text = self._formatText(*content, tags)
lines.append(f"{marker}{text}\n")
lines.append("\n")
@@ -247,9 +246,12 @@ class ToMarkdown(Tokenizer):
for pos, fmt, data in reversed(tFmt):
md = ""
if fmt == self.FMT_FNOTE:
- self._usedNotes.add(data)
- index = self._footnotes.get(data, (0, ""))[0] or "ERR"
- md = f"[{index}]"
+ if data in self._footnotes:
+ index = len(self._usedNotes) + 1
+ self._usedNotes[data] = index
+ md = f"[{index}]"
+ else:
+ md = "[ERR]"
else:
md = tags.get(fmt, "")
temp = f"{temp[:pos]}{md}{temp[pos:]}"
diff --git a/novelwriter/core/toodt.py b/novelwriter/core/toodt.py
index e82835e9..67867133 100644
--- a/novelwriter/core/toodt.py
+++ b/novelwriter/core/toodt.py
@@ -30,7 +30,6 @@ import logging
import xml.etree.ElementTree as ET
from collections.abc import Sequence
-from copy import deepcopy
from datetime import datetime
from hashlib import sha256
from pathlib import Path
@@ -412,7 +411,6 @@ class ToOdt(Tokenizer):
def doConvert(self) -> None:
"""Convert the list of text tokens into XML elements."""
self._result = "" # Not used, but cleared just in case
- self._preProcessNotes()
pFmt: list[T_Formats] = []
pText = []
@@ -687,7 +685,9 @@ class ToOdt(Tokenizer):
elif fFmt == self.FMT_SUB_E:
xFmt &= M_SUB
elif fFmt == self.FMT_FNOTE:
- parProc.appendNode(self._etNotes.get(fData))
+ parProc.appendNode(self._generateFootnote(fData))
+ elif fFmt == self.FMT_STRIP:
+ pass
else:
pErr += 1
@@ -758,16 +758,11 @@ class ToOdt(Tokenizer):
return style.name
- def _preProcessNotes(self) -> None:
- """Generate XML elements for footnotes."""
- fStyle = ODTParagraphStyle("New")
- sStyle = ODTParagraphStyle("New")
- sStyle.setTextIndent("0.000cm")
- sStyle.setMarginLeft(self._mLeftFoot)
- update = [key for key in self._footnotes.keys() if key not in self._etNotes]
- for key in update:
- cStyle = fStyle
+ def _generateFootnote(self, key: str) -> ET.Element | None:
+ """Generate a footnote XML object."""
+ if content := self._footnotes.get(key):
self._nNote += 1
+ nStyle = ODTParagraphStyle("New")
xNote = ET.Element(_mkTag("text", "note"), attrib={
_mkTag("text", "id"): f"ftn{self._nNote}",
_mkTag("text", "note-class"): "footnote",
@@ -775,11 +770,9 @@ class ToOdt(Tokenizer):
xCite = ET.SubElement(xNote, _mkTag("text", "note-citation"))
xCite.text = str(self._nNote)
xBody = ET.SubElement(xNote, _mkTag("text", "note-body"))
- for text, fmt in self._footnotes[key][1]:
- self._addTextPar(xBody, "Footnote", cStyle, text, tFmt=fmt)
- cStyle = sStyle
- self._etNotes[key] = xNote
- return
+ self._addTextPar(xBody, "Footnote", nStyle, content[0], tFmt=content[1])
+ return xNote
+ return None
def _emToCm(self, value: float) -> str:
"""Converts an em value to centimetres."""
@@ -1584,16 +1577,14 @@ class XMLParagraph:
def appendNode(self, xNode: ET.Element | None) -> None:
"""Append an XML node to the paragraph."""
if xNode:
- # We must make a copy in case the node is reused
- xCopy = deepcopy(xNode)
if self._nState in (X_ROOT_TEXT, X_ROOT_TAIL):
- self._xRoot.append(xCopy)
- self._xTail = xCopy
+ self._xRoot.append(xNode)
+ self._xTail = xNode
self._xTail.tail = ""
self._nState = X_ROOT_TAIL
elif self._nState in (X_SPAN_TEXT, X_SPAN_SING):
- self._xTail.append(xCopy)
- self._xSing = xCopy
+ self._xTail.append(xNode)
+ self._xSing = xNode
self._xSing.tail = ""
self._nState = X_SPAN_SING
return
diff --git a/novelwriter/enum.py b/novelwriter/enum.py
index 1781eb2f..eae462a0 100644
--- a/novelwriter/enum.py
+++ b/novelwriter/enum.py
@@ -65,12 +65,13 @@ class nwItemLayout(Enum):
class nwComment(Enum):
PLAIN = 0
- SYNOPSIS = 1
- SHORT = 2
- NOTE = 3
- FOOTNOTE = 4
- COMMENT = 5
- STORY = 6
+ IGNORE = 1
+ SYNOPSIS = 2
+ SHORT = 3
+ NOTE = 4
+ FOOTNOTE = 5
+ COMMENT = 6
+ STORY = 7
# END Enum nwComment