diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py
index a2417e8f..0e91b15b 100644
--- a/novelwriter/core/docbuild.py
+++ b/novelwriter/core/docbuild.py
@@ -132,7 +132,7 @@ class NWBuildDocument:
yield from self._iterBuild(makeObj, filtered)
- makeObj.appendFootnotes()
+ makeObj.closeDocument()
self._error = None
self._cache = makeObj
@@ -164,7 +164,7 @@ class NWBuildDocument:
yield from self._iterBuild(makeObj, filtered)
- makeObj.appendFootnotes()
+ makeObj.closeDocument()
if not self._build.getBool("html.preserveTabs"):
makeObj.replaceTabs()
@@ -174,7 +174,7 @@ class NWBuildDocument:
yield from self._iterBuild(makeObj, filtered)
- makeObj.appendFootnotes()
+ makeObj.closeDocument()
if self._build.getBool("format.replaceTabs"):
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
@@ -184,6 +184,7 @@ class NWBuildDocument:
yield from self._iterBuild(makeObj, filtered)
+ makeObj.closeDocument()
if self._build.getBool("format.replaceTabs"):
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
@@ -203,7 +204,7 @@ class NWBuildDocument:
yield from self._iterBuild(makeObj, filtered)
- makeObj.appendFootnotes()
+ makeObj.closeDocument()
else:
logger.error("Unsupported document format")
diff --git a/novelwriter/formats/tohtml.py b/novelwriter/formats/tohtml.py
index 484a48ee..f7634a61 100644
--- a/novelwriter/formats/tohtml.py
+++ b/novelwriter/formats/tohtml.py
@@ -249,7 +249,7 @@ class ToHtml(Tokenizer):
return
- def appendFootnotes(self) -> None:
+ def closeDocument(self) -> None:
"""Append the footnotes in the buffer."""
if self._usedNotes:
footnotes = self._localLookup("Footnotes")
diff --git a/novelwriter/formats/tokenizer.py b/novelwriter/formats/tokenizer.py
index feb77ffd..64017e7e 100644
--- a/novelwriter/formats/tokenizer.py
+++ b/novelwriter/formats/tokenizer.py
@@ -428,6 +428,10 @@ class Tokenizer(ABC):
def doConvert(self) -> None:
raise NotImplementedError
+ @abstractmethod
+ def closeDocument(self) -> None:
+ raise NotImplementedError
+
@abstractmethod
def saveDocument(self, path: Path) -> None:
raise NotImplementedError
diff --git a/novelwriter/formats/tomarkdown.py b/novelwriter/formats/tomarkdown.py
index 61111091..ff1aa48e 100644
--- a/novelwriter/formats/tomarkdown.py
+++ b/novelwriter/formats/tomarkdown.py
@@ -149,7 +149,7 @@ class ToMarkdown(Tokenizer):
return
- def appendFootnotes(self) -> None:
+ def closeDocument(self) -> None:
"""Append the footnotes in the buffer."""
if self._usedNotes:
tags = EXT_MD if self._extended else STD_MD
diff --git a/novelwriter/formats/toqdoc.py b/novelwriter/formats/toqdoc.py
index 84745c58..6ef06d7d 100644
--- a/novelwriter/formats/toqdoc.py
+++ b/novelwriter/formats/toqdoc.py
@@ -40,8 +40,8 @@ from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt
from novelwriter.formats.tokenizer import HEADINGS, Tokenizer
from novelwriter.types import (
QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignLeft, QtAlignRight,
- QtPageBreakAfter, QtPageBreakBefore, QtTransparent, QtVAlignNormal,
- QtVAlignSub, QtVAlignSuper
+ QtKeepAnchor, QtMoveAnchor, QtPageBreakAfter, QtPageBreakBefore,
+ QtTransparent, QtVAlignNormal, QtVAlignSub, QtVAlignSuper
)
logger = logging.getLogger(__name__)
@@ -70,6 +70,7 @@ class ToQTextDocument(Tokenizer):
self._document.setDocumentMargin(0)
self._usedNotes: dict[str, int] = {}
+ self._usedFields: list[tuple[int, str]] = []
self._init = False
self._bold = QFont.Weight.Bold
@@ -246,11 +247,21 @@ class ToQTextDocument(Tokenizer):
return
- def appendFootnotes(self) -> None:
- """Append the footnotes in the buffer."""
- if self._usedNotes:
- self._document.blockSignals(True)
+ def closeDocument(self) -> None:
+ """Run close document tasks."""
+ self._document.blockSignals(True)
+ # Replace fields if there are stats available
+ if self._usedFields and self._counts:
+ cursor = QTextCursor(self._document)
+ for pos, field in reversed(self._usedFields):
+ if (value := self._counts.get(field)) is not None:
+ cursor.setPosition(pos, QtMoveAnchor)
+ cursor.setPosition(pos + 1, QtKeepAnchor)
+ cursor.insertText(f"{value:n}")
+
+ # Add footnotes
+ if self._usedNotes:
cursor = QTextCursor(self._document)
cursor.movePosition(QTextCursor.MoveOperation.End)
@@ -268,7 +279,7 @@ class ToQTextDocument(Tokenizer):
cursor.insertText(f"{index}. ", cFmt)
self._insertFragments(*content, cursor, self._charFmt)
- self._document.blockSignals(False)
+ self._document.blockSignals(False)
return
@@ -361,6 +372,11 @@ class ToQTextDocument(Tokenizer):
cursor.insertText(f"[{index}]", xFmt)
else:
cursor.insertText("[ERR]", cFmt)
+ elif fmt == TextFmt.FIELD:
+ if field := data.partition(":")[2]:
+ self._usedFields.append((cursor.position(), field))
+ cursor.insertText("0", cFmt)
+ pass
# Move pos for next pass
start = pos
diff --git a/novelwriter/formats/toraw.py b/novelwriter/formats/toraw.py
index 8e1ed47e..9a410f2f 100644
--- a/novelwriter/formats/toraw.py
+++ b/novelwriter/formats/toraw.py
@@ -52,6 +52,10 @@ class ToRaw(Tokenizer):
"""No conversion to perform."""
return
+ def closeDocument(self) -> None:
+ """Nothing to close."""
+ return
+
def saveDocument(self, path: Path) -> None:
"""Save the raw text to a plain text file."""
if path.suffix.lower() == ".json":
diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py
index 13d641fa..a304b12e 100644
--- a/novelwriter/gui/docviewer.py
+++ b/novelwriter/gui/docviewer.py
@@ -229,7 +229,7 @@ class GuiDocViewer(QTextBrowser):
qDoc.doPreProcessing()
qDoc.tokenizeText()
qDoc.doConvert()
- qDoc.appendFootnotes()
+ qDoc.closeDocument()
except Exception:
logger.error("Failed to generate preview for document with handle '%s'", tHandle)
logException()
diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py
index af8f5a3b..dfcb4d3d 100644
--- a/novelwriter/tools/manuscript.py
+++ b/novelwriter/tools/manuscript.py
@@ -955,7 +955,7 @@ class _StatsWidget(QWidget):
# Maximal
self.maxTotalWords.setText("{0:n}".format(data.get(nwStats.WORDS_ALL, 0)))
self.maxHeadWords.setText("{0:n}".format(data.get(nwStats.WORDS_TITLE, 0)))
- self.maxTextWords.setText("{0:n}".format(data.get(nwStats.WORDS_TITLE, 0)))
+ self.maxTextWords.setText("{0:n}".format(data.get(nwStats.WORDS_TEXT, 0)))
self.maxTitleCount.setText("{0:n}".format(data.get(nwStats.TITLES, 0)))
self.maxParCount.setText("{0:n}".format(data.get(nwStats.PARAGRAPHS, 0)))
diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx
index 1308d3b1..aaca21b8 100644
--- a/sample/nwProject.nwx
+++ b/sample/nwProject.nwx
@@ -1,6 +1,6 @@
-
Text with one1 " "or twoERR footnotes.
\n" diff --git a/tests/test_formats/test_fmt_tokenizer.py b/tests/test_formats/test_fmt_tokenizer.py index 874f3211..4b3178b8 100644 --- a/tests/test_formats/test_fmt_tokenizer.py +++ b/tests/test_formats/test_fmt_tokenizer.py @@ -44,6 +44,9 @@ class BareTokenizer(Tokenizer): def doConvert(self): super().doConvert() # type: ignore (deliberate check) + def closeDocument(self): + super().doConvert() # type: ignore (deliberate check) + def saveDocument(self, path) -> None: super().saveDocument(path) # type: ignore (deliberate check) diff --git a/tests/test_formats/test_fmt_tomarkdown.py b/tests/test_formats/test_fmt_tomarkdown.py index e94f4b72..fc53eb7e 100644 --- a/tests/test_formats/test_fmt_tomarkdown.py +++ b/tests/test_formats/test_fmt_tomarkdown.py @@ -205,7 +205,7 @@ def testFmtToMarkdown_ConvertParagraphs(mockGUI): md.doConvert() assert md._pages[-1] == "Text with one[1] or two[ERR] footnotes.\n\n" - md.appendFootnotes() + md.closeDocument() assert md._pages[-2] == ( "Text with one[1] or two[ERR] footnotes.\n\n" ) diff --git a/tests/test_formats/test_fmt_toqdoc.py b/tests/test_formats/test_fmt_toqdoc.py index 1914c82e..46ff0b85 100644 --- a/tests/test_formats/test_fmt_toqdoc.py +++ b/tests/test_formats/test_fmt_toqdoc.py @@ -594,7 +594,7 @@ def testFmtToQTextDocument_Footnotes(mockGUI): ) doc.tokenizeText() doc.doConvert() - doc.appendFootnotes() + doc.closeDocument() assert doc.document.blockCount() == 4 # 0: Scene