diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py
index 0e91b15b..ba3e469d 100644
--- a/novelwriter/core/docbuild.py
+++ b/novelwriter/core/docbuild.py
@@ -127,16 +127,11 @@ class NWBuildDocument:
makeObj = ToQTextDocument(self._project)
filtered = self._setupBuild(makeObj)
makeObj.initDocument()
-
self._outline = True
-
yield from self._iterBuild(makeObj, filtered)
-
makeObj.closeDocument()
-
self._error = None
self._cache = makeObj
-
return
def iterBuildDocument(self, path: Path, bFormat: nwBuildFmt) -> Iterable[tuple[int, bool]]:
@@ -152,18 +147,14 @@ class NWBuildDocument:
makeObj = ToOdt(self._project, bFormat == nwBuildFmt.FODT)
filtered = self._setupBuild(makeObj)
makeObj.initDocument()
-
yield from self._iterBuild(makeObj, filtered)
-
makeObj.closeDocument()
elif bFormat in (nwBuildFmt.HTML, nwBuildFmt.J_HTML):
makeObj = ToHtml(self._project)
filtered = self._setupBuild(makeObj)
makeObj.initDocument()
-
yield from self._iterBuild(makeObj, filtered)
-
makeObj.closeDocument()
if not self._build.getBool("html.preserveTabs"):
makeObj.replaceTabs()
@@ -171,9 +162,7 @@ class NWBuildDocument:
elif bFormat in (nwBuildFmt.STD_MD, nwBuildFmt.EXT_MD):
makeObj = ToMarkdown(self._project, bFormat == nwBuildFmt.EXT_MD)
filtered = self._setupBuild(makeObj)
-
yield from self._iterBuild(makeObj, filtered)
-
makeObj.closeDocument()
if self._build.getBool("format.replaceTabs"):
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
@@ -181,9 +170,7 @@ class NWBuildDocument:
elif bFormat in (nwBuildFmt.NWD, nwBuildFmt.J_NWD):
makeObj = ToRaw(self._project)
filtered = self._setupBuild(makeObj)
-
yield from self._iterBuild(makeObj, filtered)
-
makeObj.closeDocument()
if self._build.getBool("format.replaceTabs"):
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
@@ -192,18 +179,14 @@ class NWBuildDocument:
makeObj = ToDocX(self._project)
filtered = self._setupBuild(makeObj)
makeObj.initDocument()
-
yield from self._iterBuild(makeObj, filtered)
-
makeObj.closeDocument()
elif bFormat == nwBuildFmt.PDF:
makeObj = ToQTextDocument(self._project)
filtered = self._setupBuild(makeObj)
makeObj.initDocument()
-
yield from self._iterBuild(makeObj, filtered)
-
makeObj.closeDocument()
else:
diff --git a/novelwriter/formats/todocx.py b/novelwriter/formats/todocx.py
index 2f37d5cd..4f426453 100644
--- a/novelwriter/formats/todocx.py
+++ b/novelwriter/formats/todocx.py
@@ -192,6 +192,7 @@ class ToDocX(Tokenizer):
self._files: dict[str, DocXXmlFile] = {}
self._styles: dict[str, DocXParStyle] = {}
self._usedNotes: dict[str, int] = {}
+ self._usedFields: list[tuple[ET.Element, str]] = []
return
@@ -440,6 +441,8 @@ class ToDocX(Tokenizer):
fLink = ""
elif fFmt == TextFmt.FNOTE:
xNode = self._generateFootnote(fData)
+ elif fFmt == TextFmt.FIELD:
+ xNode = self._generateField(fData)
elif fFmt == TextFmt.STRIP:
pass
@@ -510,6 +513,15 @@ class ToDocX(Tokenizer):
return xR
return None
+ def _generateField(self, key: str) -> ET.Element | None:
+ """Generate a data field XML object."""
+ if key and (field := key.partition(":")[2]):
+ xR = ET.Element(_wTag("r"))
+ xT = xmlSubElem(xR, _wTag("t"), "0")
+ self._usedFields.append((xT, field))
+ return xR
+ return None
+
def _generateStyles(self) -> None:
"""Generate usable styles."""
styles: list[DocXParStyle] = []
@@ -920,6 +932,12 @@ class ToDocX(Tokenizer):
pars.append(par)
+ # Replace fields if there are stats available
+ if self._usedFields and self._counts:
+ for xField, field in self._usedFields:
+ if (value := self._counts.get(field)) is not None:
+ xField.text = f"{value:n}"
+
# Write Paragraphs
for par in pars:
par.toXml(xBody)
diff --git a/novelwriter/formats/tohtml.py b/novelwriter/formats/tohtml.py
index f7634a61..e27a96dc 100644
--- a/novelwriter/formats/tohtml.py
+++ b/novelwriter/formats/tohtml.py
@@ -84,6 +84,7 @@ class ToHtml(Tokenizer):
self._trMap = {}
self._cssStyles = True
self._usedNotes: dict[str, int] = {}
+ self._usedFields: list[tuple[int, str]] = []
self.setReplaceUnicode(False)
return
@@ -250,7 +251,15 @@ class ToHtml(Tokenizer):
return
def closeDocument(self) -> None:
- """Append the footnotes in the buffer."""
+ """Run close document tasks."""
+ # Replace fields if there are stats available
+ if self._usedFields and self._counts:
+ pages = len(self._pages)
+ for doc, field in self._usedFields:
+ if doc >= 0 and doc < pages and (value := self._counts.get(field)) is not None:
+ self._pages[doc] = self._pages[doc].replace(f"{{{{{field}}}}}", f"{value:n}")
+
+ # Add footnotes
if self._usedNotes:
footnotes = self._localLookup("Footnotes")
@@ -416,6 +425,10 @@ class ToHtml(Tokenizer):
tags.append((pos, f"{index}"))
else:
tags.append((pos, "ERR"))
+ elif fmt == TextFmt.FIELD:
+ if field := data.partition(":")[2]:
+ self._usedFields.append((len(self._pages), field))
+ tags.append((pos, f"{{{{{field}}}}}"))
# Check all format types and close any tag that is still open. This
# ensures that unclosed tags don't spill over to the next paragraph.
diff --git a/novelwriter/formats/tomarkdown.py b/novelwriter/formats/tomarkdown.py
index ff1aa48e..1db36152 100644
--- a/novelwriter/formats/tomarkdown.py
+++ b/novelwriter/formats/tomarkdown.py
@@ -86,6 +86,7 @@ class ToMarkdown(Tokenizer):
super().__init__(project)
self._extended = extended
self._usedNotes: dict[str, int] = {}
+ self._usedFields: list[tuple[int, str]] = []
return
##
@@ -150,7 +151,15 @@ class ToMarkdown(Tokenizer):
return
def closeDocument(self) -> None:
- """Append the footnotes in the buffer."""
+ """Run close document tasks."""
+ # Replace fields if there are stats available
+ if self._usedFields and self._counts:
+ pages = len(self._pages)
+ for doc, field in self._usedFields:
+ if doc >= 0 and doc < pages and (value := self._counts.get(field)) is not None:
+ self._pages[doc] = self._pages[doc].replace(f"{{{{{field}}}}}", f"{value:n}")
+
+ # Add footnotes
if self._usedNotes:
tags = EXT_MD if self._extended else STD_MD
footnotes = self._localLookup("Footnotes")
@@ -196,6 +205,10 @@ class ToMarkdown(Tokenizer):
md = f"[{index}]"
else:
md = "[ERR]"
+ elif fmt == TextFmt.FIELD:
+ if field := data.partition(":")[2]:
+ self._usedFields.append((len(self._pages), field))
+ md = f"{{{{{field}}}}}"
else:
md = tags.get(fmt, "")
temp = f"{temp[:pos]}{md}{temp[pos:]}"
diff --git a/novelwriter/formats/toodt.py b/novelwriter/formats/toodt.py
index afb15203..0845b14a 100644
--- a/novelwriter/formats/toodt.py
+++ b/novelwriter/formats/toodt.py
@@ -630,6 +630,8 @@ class ToOdt(Tokenizer):
fLink = ""
elif fFmt == TextFmt.FNOTE:
xNode = self._generateFootnote(fData)
+ elif fFmt == TextFmt.FIELD:
+ xNode = self._generateField(fData)
elif fFmt == TextFmt.STRIP:
pass
@@ -725,6 +727,16 @@ class ToOdt(Tokenizer):
return xNote
return None
+ def _generateField(self, key: str) -> ET.Element | None:
+ """Generate a data field XML object."""
+ if key and (field := key.partition(":")[2]):
+ xField = ET.Element(_mkTag("text", "user-field-get"), attrib={
+ _mkTag("text", "name"): f"Manuscript{field[:1].upper()}{field[1:]}",
+ })
+ xField.text = "0"
+ return xField
+ return None
+
def _emToCm(self, value: float) -> str:
"""Converts an em value to centimetres."""
return f"{value*2.54/72*self._fontSize:.3f}cm"