Skip to content

Commit

Permalink
Refactor document builder (#2047)
Browse files Browse the repository at this point in the history
  • Loading branch information
vkbo authored Oct 15, 2024
2 parents 93c105f + 7bb2924 commit c8d7cca
Show file tree
Hide file tree
Showing 13 changed files with 213 additions and 211 deletions.
140 changes: 38 additions & 102 deletions novelwriter/core/docbuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from novelwriter.formats.tomarkdown import ToMarkdown
from novelwriter.formats.toodt import ToOdt
from novelwriter.formats.toqdoc import TextDocumentTheme, ToQTextDocument
from novelwriter.formats.toraw import ToRaw

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -146,95 +147,49 @@ def iterBuildPreview(self, theme: TextDocumentTheme) -> Iterable[tuple[int, bool

return

def iterBuild(self, path: Path, bFormat: nwBuildFmt) -> Iterable[tuple[int, bool]]:
def iterBuildDocument(self, path: Path, bFormat: nwBuildFmt) -> Iterable[tuple[int, bool]]:
"""Wrapper for builders based on format."""
if bFormat in (nwBuildFmt.ODT, nwBuildFmt.FODT):
yield from self.iterBuildOpenDocument(path, bFormat == nwBuildFmt.FODT)
elif bFormat in (nwBuildFmt.HTML, nwBuildFmt.J_HTML):
yield from self.iterBuildHTML(path, asJson=bFormat == nwBuildFmt.J_HTML)
elif bFormat in (nwBuildFmt.STD_MD, nwBuildFmt.EXT_MD):
yield from self.iterBuildMarkdown(path, bFormat == nwBuildFmt.EXT_MD)
elif bFormat in (nwBuildFmt.NWD, nwBuildFmt.J_NWD):
yield from self.iterBuildNWD(path, asJson=bFormat == nwBuildFmt.J_NWD)
return

def iterBuildOpenDocument(self, path: Path, isFlat: bool) -> Iterable[tuple[int, bool]]:
"""Build an Open Document file."""
makeObj = ToOdt(self._project, isFlat=isFlat)
filtered = self._setupBuild(makeObj)
makeObj.initDocument()

self._count = True
for i, tHandle in enumerate(self._queue):
self._error = None
if filtered.get(tHandle, (False, 0))[0]:
yield i, self._doBuild(makeObj, tHandle)
else:
yield i, False

makeObj.closeDocument()

self._error = None
self._cache = makeObj
if bFormat in (nwBuildFmt.J_HTML, nwBuildFmt.J_NWD):
# Ensure that JSON output has the correct extension
path = path.with_suffix(".json")

try:
makeObj.saveDocument(path)
except Exception as exc:
logException()
self._error = formatException(exc)

return
if bFormat in (nwBuildFmt.ODT, nwBuildFmt.FODT):
makeObj = ToOdt(self._project, bFormat == nwBuildFmt.FODT)
filtered = self._setupBuild(makeObj)
makeObj.initDocument()

def iterBuildHTML(self, path: Path | None, asJson: bool = False) -> Iterable[tuple[int, bool]]:
"""Build an HTML file. If path is None, no file is saved. This
is used for generating build previews.
"""
makeObj = ToHtml(self._project)
filtered = self._setupBuild(makeObj)
yield from self._iterBuild(makeObj, filtered)

self._count = False
for i, tHandle in enumerate(self._queue):
self._error = None
if filtered.get(tHandle, (False, 0))[0]:
yield i, self._doBuild(makeObj, tHandle)
else:
yield i, False
makeObj.closeDocument()

makeObj.appendFootnotes()
elif bFormat in (nwBuildFmt.HTML, nwBuildFmt.J_HTML):
makeObj = ToHtml(self._project)
filtered = self._setupBuild(makeObj)

if not self._build.getBool("html.preserveTabs"):
makeObj.replaceTabs()
yield from self._iterBuild(makeObj, filtered)

self._error = None
self._cache = makeObj
makeObj.appendFootnotes()
if not self._build.getBool("html.preserveTabs"):
makeObj.replaceTabs()

if isinstance(path, Path):
try:
makeObj.saveDocument(path, asJson=asJson)
except Exception as exc:
logException()
self._error = formatException(exc)
elif bFormat in (nwBuildFmt.STD_MD, nwBuildFmt.EXT_MD):
makeObj = ToMarkdown(self._project, bFormat == nwBuildFmt.EXT_MD)
filtered = self._setupBuild(makeObj)

return
yield from self._iterBuild(makeObj, filtered)

def iterBuildMarkdown(self, path: Path, extendedMd: bool) -> Iterable[tuple[int, bool]]:
"""Build a Markdown file."""
makeObj = ToMarkdown(self._project)
filtered = self._setupBuild(makeObj)
makeObj.appendFootnotes()
if self._build.getBool("format.replaceTabs"):
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")

makeObj.setExtendedMarkdown(extendedMd)
if self._build.getBool("format.replaceTabs"):
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
elif bFormat in (nwBuildFmt.NWD, nwBuildFmt.J_NWD):
makeObj = ToRaw(self._project)
filtered = self._setupBuild(makeObj)

self._count = False
for i, tHandle in enumerate(self._queue):
self._error = None
if filtered.get(tHandle, (False, 0))[0]:
yield i, self._doBuild(makeObj, tHandle)
else:
yield i, False
yield from self._iterBuild(makeObj, filtered)

makeObj.appendFootnotes()
if self._build.getBool("format.replaceTabs"):
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")

self._error = None
self._cache = makeObj
Expand All @@ -247,40 +202,21 @@ def iterBuildMarkdown(self, path: Path, extendedMd: bool) -> Iterable[tuple[int,

return

def iterBuildNWD(self, path: Path | None, asJson: bool = False) -> Iterable[tuple[int, bool]]:
"""Build a novelWriter Markdown file."""
makeObj = ToMarkdown(self._project)
filtered = self._setupBuild(makeObj)

makeObj.setKeepMarkdown(True)
##
# Internal Functions
##

self._count = False
def _iterBuild(self, makeObj: Tokenizer, filtered: dict) -> Iterable[tuple[int, bool]]:
"""Iterate over buildable documents."""
self._count = True
for i, tHandle in enumerate(self._queue):
self._error = None
if filtered.get(tHandle, (False, 0))[0]:
yield i, self._doBuild(makeObj, tHandle, convert=False)
yield i, self._doBuild(makeObj, tHandle)
else:
yield i, False

if self._build.getBool("format.replaceTabs"):
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")

self._error = None
self._cache = makeObj

if isinstance(path, Path):
try:
makeObj.saveRawDocument(path, asJson=asJson)
except Exception as exc:
logException()
self._error = formatException(exc)

return

##
# Internal Functions
##

def _setupBuild(self, bldObj: Tokenizer) -> dict:
"""Configure the build object."""
# Get Settings
Expand Down
4 changes: 2 additions & 2 deletions novelwriter/formats/tohtml.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,9 @@ def appendFootnotes(self) -> None:

return

def saveDocument(self, path: str | Path, asJson: bool = False) -> None:
def saveDocument(self, path: Path) -> None:
"""Save the data to an HTML file."""
if asJson:
if path.suffix.lower() == ".json":
ts = time()
data = {
"meta": {
Expand Down
41 changes: 18 additions & 23 deletions novelwriter/formats/tokenizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,10 @@ def __init__(self, project: NWProject) -> None:
self._project = project

# Data Variables
self._text = "" # The raw text to be tokenized
self._handle = None # The item handle currently being processed
self._result = "" # The result of the last document
self._keepMD = False # Whether to keep the markdown text
self._text = "" # The raw text to be tokenized
self._handle = None # The item handle currently being processed
self._result = "" # The result of the last document
self._keepRaw = False # Whether to keep the raw text, used by ToRaw

# Tokens and Meta Data (Per Document)
self._tokens: list[T_Token] = []
Expand Down Expand Up @@ -473,11 +473,6 @@ def setKeepLineBreaks(self, state: bool) -> None:
self._keepBreaks = state
return

def setKeepMarkdown(self, state: bool) -> None:
"""Keep original markdown during build."""
self._keepMD = state
return

##
# Class Methods
##
Expand All @@ -487,7 +482,7 @@ def doConvert(self) -> None:
raise NotImplementedError

@abstractmethod
def saveDocument(self, path: str | Path) -> None:
def saveDocument(self, path: Path) -> None:
raise NotImplementedError

def addRootHeading(self, tHandle: str) -> None:
Expand All @@ -509,7 +504,7 @@ def addRootHeading(self, tHandle: str) -> None:
self._tokens.append((
self.T_TITLE, 1, title, [], textAlign
))
if self._keepMD:
if self._keepRaw:
self._markdown.append(f"#! {title}\n\n")

return
Expand Down Expand Up @@ -574,7 +569,7 @@ def tokenizeText(self) -> None:
tokens.append((
self.T_EMPTY, nHead, "", [], self.A_NONE
))
if self._keepMD:
if self._keepRaw:
tmpMarkdown.append("\n")

continue
Expand Down Expand Up @@ -632,26 +627,26 @@ def tokenizeText(self) -> None:
tokens.append((
self.T_SYNOPSIS, nHead, tLine, tFmt, sAlign
))
if self._doSynopsis and self._keepMD:
if self._doSynopsis and self._keepRaw:
tmpMarkdown.append(f"{aLine}\n")
elif cStyle == nwComment.SHORT:
tLine, tFmt = self._extractFormats(cText)
tokens.append((
self.T_SHORT, nHead, tLine, tFmt, sAlign
))
if self._doSynopsis and self._keepMD:
if self._doSynopsis and self._keepRaw:
tmpMarkdown.append(f"{aLine}\n")
elif cStyle == nwComment.FOOTNOTE:
tLine, tFmt = self._extractFormats(cText, skip=self.FMT_FNOTE)
self._footnotes[f"{tHandle}:{cKey}"] = (tLine, tFmt)
if self._keepMD:
if self._keepRaw:
tmpMarkdown.append(f"{aLine}\n")
else:
tLine, tFmt = self._extractFormats(cText)
tokens.append((
self.T_COMMENT, nHead, tLine, tFmt, sAlign
))
if self._doComments and self._keepMD:
if self._doComments and self._keepRaw:
tmpMarkdown.append(f"{aLine}\n")

elif aLine.startswith("@"):
Expand All @@ -668,7 +663,7 @@ def tokenizeText(self) -> None:
tokens.append((
self.T_KEYWORD, nHead, aLine[1:].strip(), [], sAlign
))
if self._doKeywords and self._keepMD:
if self._doKeywords and self._keepRaw:
tmpMarkdown.append(f"{aLine}\n")

elif aLine.startswith(("# ", "#! ")):
Expand Down Expand Up @@ -704,7 +699,7 @@ def tokenizeText(self) -> None:
tokens.append((
tType, nHead, tText, [], tStyle
))
if self._keepMD:
if self._keepRaw:
tmpMarkdown.append(f"{aLine}\n")

elif aLine.startswith(("## ", "##! ")):
Expand Down Expand Up @@ -739,7 +734,7 @@ def tokenizeText(self) -> None:
tokens.append((
tType, nHead, tText, [], tStyle
))
if self._keepMD:
if self._keepRaw:
tmpMarkdown.append(f"{aLine}\n")

elif aLine.startswith(("### ", "###! ")):
Expand Down Expand Up @@ -780,7 +775,7 @@ def tokenizeText(self) -> None:
tokens.append((
tType, nHead, tText, [], tStyle
))
if self._keepMD:
if self._keepRaw:
tmpMarkdown.append(f"{aLine}\n")

elif aLine.startswith("#### "):
Expand Down Expand Up @@ -810,7 +805,7 @@ def tokenizeText(self) -> None:
tokens.append((
tType, nHead, tText, [], tStyle
))
if self._keepMD:
if self._keepRaw:
tmpMarkdown.append(f"{aLine}\n")

else:
Expand Down Expand Up @@ -858,7 +853,7 @@ def tokenizeText(self) -> None:
tokens.append((
self.T_TEXT, nHead, tLine, tFmt, sAlign
))
if self._keepMD:
if self._keepRaw:
tmpMarkdown.append(f"{aLine}\n")

# If we have content, turn off the first page flag
Expand All @@ -877,7 +872,7 @@ def tokenizeText(self) -> None:
tokens.append((
self.T_EMPTY, nHead, "", [], self.A_NONE
))
if self._keepMD:
if self._keepRaw:
tmpMarkdown.append("\n")
self._markdown.append("".join(tmpMarkdown))

Expand Down
17 changes: 3 additions & 14 deletions novelwriter/formats/tomarkdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,11 @@ class ToMarkdown(Tokenizer):
supports concatenating novelWriter markup files.
"""

def __init__(self, project: NWProject) -> None:
def __init__(self, project: NWProject, extended: bool) -> None:
super().__init__(project)
self._fullMD: list[str] = []
self._usedNotes: dict[str, int] = {}
self._extended = True
self._extended = extended
return

##
Expand All @@ -97,15 +97,6 @@ def fullMD(self) -> list[str]:
"""Return the markdown as a list."""
return self._fullMD

##
# Setters
##

def setExtendedMarkdown(self, state: bool) -> None:
"""Set the converter to use Extended Markdown formatting."""
self._extended = state
return

##
# Class Methods
##
Expand Down Expand Up @@ -199,7 +190,7 @@ def appendFootnotes(self) -> None:

return

def saveDocument(self, path: str | Path) -> None:
def saveDocument(self, path: Path) -> None:
"""Save the data to a plain text file."""
with open(path, mode="w", encoding="utf-8") as outFile:
outFile.write("".join(self._fullMD))
Expand All @@ -210,8 +201,6 @@ def replaceTabs(self, nSpaces: int = 8, spaceChar: str = " ") -> None:
"""Replace tabs with spaces."""
spaces = spaceChar*nSpaces
self._fullMD = [p.replace("\t", spaces) for p in self._fullMD]
if self._keepMD:
self._markdown = [p.replace("\t", spaces) for p in self._markdown]
return

##
Expand Down
2 changes: 1 addition & 1 deletion novelwriter/formats/toodt.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ def closeDocument(self) -> None:
self._xText.insert(0, xFields)
return

def saveDocument(self, path: str | Path) -> None:
def saveDocument(self, path: Path) -> None:
"""Save the data to an .fodt or .odt file."""
if self._isFlat:
with open(path, mode="wb") as fObj:
Expand Down
Loading

0 comments on commit c8d7cca

Please sign in to comment.