Process field values in QTextDocuments
This commit is contained in:
@@ -132,7 +132,7 @@ class NWBuildDocument:
|
|||||||
|
|
||||||
yield from self._iterBuild(makeObj, filtered)
|
yield from self._iterBuild(makeObj, filtered)
|
||||||
|
|
||||||
makeObj.appendFootnotes()
|
makeObj.closeDocument()
|
||||||
|
|
||||||
self._error = None
|
self._error = None
|
||||||
self._cache = makeObj
|
self._cache = makeObj
|
||||||
@@ -164,7 +164,7 @@ class NWBuildDocument:
|
|||||||
|
|
||||||
yield from self._iterBuild(makeObj, filtered)
|
yield from self._iterBuild(makeObj, filtered)
|
||||||
|
|
||||||
makeObj.appendFootnotes()
|
makeObj.closeDocument()
|
||||||
if not self._build.getBool("html.preserveTabs"):
|
if not self._build.getBool("html.preserveTabs"):
|
||||||
makeObj.replaceTabs()
|
makeObj.replaceTabs()
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ class NWBuildDocument:
|
|||||||
|
|
||||||
yield from self._iterBuild(makeObj, filtered)
|
yield from self._iterBuild(makeObj, filtered)
|
||||||
|
|
||||||
makeObj.appendFootnotes()
|
makeObj.closeDocument()
|
||||||
if self._build.getBool("format.replaceTabs"):
|
if self._build.getBool("format.replaceTabs"):
|
||||||
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
|
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
|
||||||
|
|
||||||
@@ -184,6 +184,7 @@ class NWBuildDocument:
|
|||||||
|
|
||||||
yield from self._iterBuild(makeObj, filtered)
|
yield from self._iterBuild(makeObj, filtered)
|
||||||
|
|
||||||
|
makeObj.closeDocument()
|
||||||
if self._build.getBool("format.replaceTabs"):
|
if self._build.getBool("format.replaceTabs"):
|
||||||
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
|
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
|
||||||
|
|
||||||
@@ -203,7 +204,7 @@ class NWBuildDocument:
|
|||||||
|
|
||||||
yield from self._iterBuild(makeObj, filtered)
|
yield from self._iterBuild(makeObj, filtered)
|
||||||
|
|
||||||
makeObj.appendFootnotes()
|
makeObj.closeDocument()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.error("Unsupported document format")
|
logger.error("Unsupported document format")
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ class ToHtml(Tokenizer):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def appendFootnotes(self) -> None:
|
def closeDocument(self) -> None:
|
||||||
"""Append the footnotes in the buffer."""
|
"""Append the footnotes in the buffer."""
|
||||||
if self._usedNotes:
|
if self._usedNotes:
|
||||||
footnotes = self._localLookup("Footnotes")
|
footnotes = self._localLookup("Footnotes")
|
||||||
|
|||||||
@@ -428,6 +428,10 @@ class Tokenizer(ABC):
|
|||||||
def doConvert(self) -> None:
|
def doConvert(self) -> None:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def closeDocument(self) -> None:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def saveDocument(self, path: Path) -> None:
|
def saveDocument(self, path: Path) -> None:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ class ToMarkdown(Tokenizer):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def appendFootnotes(self) -> None:
|
def closeDocument(self) -> None:
|
||||||
"""Append the footnotes in the buffer."""
|
"""Append the footnotes in the buffer."""
|
||||||
if self._usedNotes:
|
if self._usedNotes:
|
||||||
tags = EXT_MD if self._extended else STD_MD
|
tags = EXT_MD if self._extended else STD_MD
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt
|
|||||||
from novelwriter.formats.tokenizer import HEADINGS, Tokenizer
|
from novelwriter.formats.tokenizer import HEADINGS, Tokenizer
|
||||||
from novelwriter.types import (
|
from novelwriter.types import (
|
||||||
QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignLeft, QtAlignRight,
|
QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignLeft, QtAlignRight,
|
||||||
QtPageBreakAfter, QtPageBreakBefore, QtTransparent, QtVAlignNormal,
|
QtKeepAnchor, QtMoveAnchor, QtPageBreakAfter, QtPageBreakBefore,
|
||||||
QtVAlignSub, QtVAlignSuper
|
QtTransparent, QtVAlignNormal, QtVAlignSub, QtVAlignSuper
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -70,6 +70,7 @@ class ToQTextDocument(Tokenizer):
|
|||||||
self._document.setDocumentMargin(0)
|
self._document.setDocumentMargin(0)
|
||||||
|
|
||||||
self._usedNotes: dict[str, int] = {}
|
self._usedNotes: dict[str, int] = {}
|
||||||
|
self._usedFields: list[tuple[int, str]] = []
|
||||||
|
|
||||||
self._init = False
|
self._init = False
|
||||||
self._bold = QFont.Weight.Bold
|
self._bold = QFont.Weight.Bold
|
||||||
@@ -246,11 +247,21 @@ class ToQTextDocument(Tokenizer):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def appendFootnotes(self) -> None:
|
def closeDocument(self) -> None:
|
||||||
"""Append the footnotes in the buffer."""
|
"""Run close document tasks."""
|
||||||
if self._usedNotes:
|
self._document.blockSignals(True)
|
||||||
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 = QTextCursor(self._document)
|
||||||
cursor.movePosition(QTextCursor.MoveOperation.End)
|
cursor.movePosition(QTextCursor.MoveOperation.End)
|
||||||
|
|
||||||
@@ -268,7 +279,7 @@ class ToQTextDocument(Tokenizer):
|
|||||||
cursor.insertText(f"{index}. ", cFmt)
|
cursor.insertText(f"{index}. ", cFmt)
|
||||||
self._insertFragments(*content, cursor, self._charFmt)
|
self._insertFragments(*content, cursor, self._charFmt)
|
||||||
|
|
||||||
self._document.blockSignals(False)
|
self._document.blockSignals(False)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -361,6 +372,11 @@ class ToQTextDocument(Tokenizer):
|
|||||||
cursor.insertText(f"[{index}]", xFmt)
|
cursor.insertText(f"[{index}]", xFmt)
|
||||||
else:
|
else:
|
||||||
cursor.insertText("[ERR]", cFmt)
|
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
|
# Move pos for next pass
|
||||||
start = pos
|
start = pos
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ class ToRaw(Tokenizer):
|
|||||||
"""No conversion to perform."""
|
"""No conversion to perform."""
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def closeDocument(self) -> None:
|
||||||
|
"""Nothing to close."""
|
||||||
|
return
|
||||||
|
|
||||||
def saveDocument(self, path: Path) -> None:
|
def saveDocument(self, path: Path) -> None:
|
||||||
"""Save the raw text to a plain text file."""
|
"""Save the raw text to a plain text file."""
|
||||||
if path.suffix.lower() == ".json":
|
if path.suffix.lower() == ".json":
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
qDoc.doPreProcessing()
|
qDoc.doPreProcessing()
|
||||||
qDoc.tokenizeText()
|
qDoc.tokenizeText()
|
||||||
qDoc.doConvert()
|
qDoc.doConvert()
|
||||||
qDoc.appendFootnotes()
|
qDoc.closeDocument()
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error("Failed to generate preview for document with handle '%s'", tHandle)
|
logger.error("Failed to generate preview for document with handle '%s'", tHandle)
|
||||||
logException()
|
logException()
|
||||||
|
|||||||
@@ -955,7 +955,7 @@ class _StatsWidget(QWidget):
|
|||||||
# Maximal
|
# Maximal
|
||||||
self.maxTotalWords.setText("{0:n}".format(data.get(nwStats.WORDS_ALL, 0)))
|
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.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.maxTitleCount.setText("{0:n}".format(data.get(nwStats.TITLES, 0)))
|
||||||
self.maxParCount.setText("{0:n}".format(data.get(nwStats.PARAGRAPHS, 0)))
|
self.maxParCount.setText("{0:n}".format(data.get(nwStats.PARAGRAPHS, 0)))
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.6a3" hexVersion="0x020600a3" fileVersion="1.5" fileRevision="4" timeStamp="2024-10-28 16:42:35">
|
<novelWriterXML appVersion="2.6a3" hexVersion="0x020600a3" fileVersion="1.5" fileRevision="4" timeStamp="2024-10-28 17:26:03">
|
||||||
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2134" autoCount="279" editTime="95103">
|
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2135" autoCount="279" editTime="95108">
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<author>Jane Smith</author>
|
<author>Jane Smith</author>
|
||||||
</project>
|
</project>
|
||||||
@@ -9,8 +9,8 @@
|
|||||||
<language>en_GB</language>
|
<language>en_GB</language>
|
||||||
<spellChecking auto="yes">None</spellChecking>
|
<spellChecking auto="yes">None</spellChecking>
|
||||||
<lastHandle>
|
<lastHandle>
|
||||||
<entry key="editor">53b69b83cdafc</entry>
|
<entry key="editor">636b6aa9b697b</entry>
|
||||||
<entry key="viewer">53b69b83cdafc</entry>
|
<entry key="viewer">636b6aa9b697b</entry>
|
||||||
<entry key="novelTree">7031beac91f75</entry>
|
<entry key="novelTree">7031beac91f75</entry>
|
||||||
<entry key="outline">7031beac91f75</entry>
|
<entry key="outline">7031beac91f75</entry>
|
||||||
</lastHandle>
|
</lastHandle>
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ def testFmtToHtml_ConvertParagraphs(mockGUI):
|
|||||||
"or two<sup>ERR</sup> footnotes.</p>\n"
|
"or two<sup>ERR</sup> footnotes.</p>\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
html.appendFootnotes()
|
html.closeDocument()
|
||||||
assert html._pages[-2] == (
|
assert html._pages[-2] == (
|
||||||
"<p>Text with one<sup><a href='#footnote_1'>1</a></sup> "
|
"<p>Text with one<sup><a href='#footnote_1'>1</a></sup> "
|
||||||
"or two<sup>ERR</sup> footnotes.</p>\n"
|
"or two<sup>ERR</sup> footnotes.</p>\n"
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ class BareTokenizer(Tokenizer):
|
|||||||
def doConvert(self):
|
def doConvert(self):
|
||||||
super().doConvert() # type: ignore (deliberate check)
|
super().doConvert() # type: ignore (deliberate check)
|
||||||
|
|
||||||
|
def closeDocument(self):
|
||||||
|
super().doConvert() # type: ignore (deliberate check)
|
||||||
|
|
||||||
def saveDocument(self, path) -> None:
|
def saveDocument(self, path) -> None:
|
||||||
super().saveDocument(path) # type: ignore (deliberate check)
|
super().saveDocument(path) # type: ignore (deliberate check)
|
||||||
|
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ def testFmtToMarkdown_ConvertParagraphs(mockGUI):
|
|||||||
md.doConvert()
|
md.doConvert()
|
||||||
assert md._pages[-1] == "Text with one[1] or two[ERR] footnotes.\n\n"
|
assert md._pages[-1] == "Text with one[1] or two[ERR] footnotes.\n\n"
|
||||||
|
|
||||||
md.appendFootnotes()
|
md.closeDocument()
|
||||||
assert md._pages[-2] == (
|
assert md._pages[-2] == (
|
||||||
"Text with one[1] or two[ERR] footnotes.\n\n"
|
"Text with one[1] or two[ERR] footnotes.\n\n"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -594,7 +594,7 @@ def testFmtToQTextDocument_Footnotes(mockGUI):
|
|||||||
)
|
)
|
||||||
doc.tokenizeText()
|
doc.tokenizeText()
|
||||||
doc.doConvert()
|
doc.doConvert()
|
||||||
doc.appendFootnotes()
|
doc.closeDocument()
|
||||||
assert doc.document.blockCount() == 4
|
assert doc.document.blockCount() == 4
|
||||||
|
|
||||||
# 0: Scene
|
# 0: Scene
|
||||||
|
|||||||
Reference in New Issue
Block a user