Fix DocX tabs in text, and page break before issues
This commit is contained in:
@@ -193,6 +193,8 @@ class NWBuildDocument:
|
|||||||
|
|
||||||
yield from self._iterBuild(makeObj, filtered)
|
yield from self._iterBuild(makeObj, filtered)
|
||||||
|
|
||||||
|
makeObj.closeDocument()
|
||||||
|
|
||||||
elif bFormat == nwBuildFmt.PDF:
|
elif bFormat == nwBuildFmt.PDF:
|
||||||
makeObj = ToQTextDocument(self._project)
|
makeObj = ToQTextDocument(self._project)
|
||||||
filtered = self._setupBuild(makeObj)
|
filtered = self._setupBuild(makeObj)
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -40,6 +41,9 @@ from novelwriter.formats.tokenizer import T_Formats, Tokenizer
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# RegEx
|
||||||
|
RX_TEXT = re.compile(r"([\n\t])", re.UNICODE)
|
||||||
|
|
||||||
# Types and Relationships
|
# Types and Relationships
|
||||||
WORD_BASE = "application/vnd.openxmlformats-officedocument"
|
WORD_BASE = "application/vnd.openxmlformats-officedocument"
|
||||||
RELS_TYPE = "application/vnd.openxmlformats-package.relationships+xml"
|
RELS_TYPE = "application/vnd.openxmlformats-package.relationships+xml"
|
||||||
@@ -158,8 +162,9 @@ class ToDocX(Tokenizer):
|
|||||||
self._fontSize = 12.0
|
self._fontSize = 12.0
|
||||||
self._dLanguage = "en-GB"
|
self._dLanguage = "en-GB"
|
||||||
|
|
||||||
# Maps
|
# Data Variables
|
||||||
self._styles: dict[str, DocXParStyle] = {}
|
self._styles: dict[str, DocXParStyle] = {}
|
||||||
|
self._pars: list[DocXParagraph] = []
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -215,8 +220,11 @@ class ToDocX(Tokenizer):
|
|||||||
# xText = self._xText
|
# xText = self._xText
|
||||||
for tType, _, tText, tFormat, tStyle in self._tokens:
|
for tType, _, tText, tFormat, tStyle in self._tokens:
|
||||||
|
|
||||||
# Styles
|
# Create Paragraph
|
||||||
par = DocXParagraph()
|
par = DocXParagraph()
|
||||||
|
self._pars.append(par)
|
||||||
|
|
||||||
|
# Styles
|
||||||
if tStyle is not None:
|
if tStyle is not None:
|
||||||
if tStyle & self.A_LEFT:
|
if tStyle & self.A_LEFT:
|
||||||
par.setAlignment("left")
|
par.setAlignment("left")
|
||||||
@@ -292,7 +300,29 @@ class ToDocX(Tokenizer):
|
|||||||
tTemp, tFmt = self._formatKeywords(tText)
|
tTemp, tFmt = self._formatKeywords(tText)
|
||||||
self._processFragments(par, S_META, tTemp, tFmt)
|
self._processFragments(par, S_META, tTemp, tFmt)
|
||||||
|
|
||||||
par.finalise(self._xBody)
|
return
|
||||||
|
|
||||||
|
def closeDocument(self) -> None:
|
||||||
|
"""Finalise document."""
|
||||||
|
# Map all Page Break Before to After where possible
|
||||||
|
pars: list[DocXParagraph] = []
|
||||||
|
for i, par in enumerate(self._pars):
|
||||||
|
if i > 0 and par.pageBreakBefore:
|
||||||
|
prev = self._pars[i-1]
|
||||||
|
if prev.pageBreakAfter:
|
||||||
|
# We already have a break, so we inject a new paragraph instead
|
||||||
|
empty = DocXParagraph()
|
||||||
|
empty.setStyle(self._styles.get(S_NORM))
|
||||||
|
empty.setPageBreakAfter(True)
|
||||||
|
pars.append(empty)
|
||||||
|
else:
|
||||||
|
par.setPageBreakBefore(False)
|
||||||
|
prev.setPageBreakAfter(True)
|
||||||
|
|
||||||
|
pars.append(par)
|
||||||
|
|
||||||
|
for par in pars:
|
||||||
|
par.toXml(self._xBody)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -486,8 +516,8 @@ class ToDocX(Tokenizer):
|
|||||||
# Move pos for next pass
|
# Move pos for next pass
|
||||||
fStart = fPos
|
fStart = fPos
|
||||||
|
|
||||||
if rest := text[fStart:]:
|
if temp := text[fStart:]:
|
||||||
par.addContent(self._textRunToXml(rest, xFmt))
|
par.addContent(self._textRunToXml(temp, xFmt))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -516,16 +546,15 @@ class ToDocX(Tokenizer):
|
|||||||
if fmt & X_DLA == X_DLA:
|
if fmt & X_DLA == X_DLA:
|
||||||
xmlSubElem(rPr, _wTag("color"), attrib={_wTag("val"): COL_DIALOG_A})
|
xmlSubElem(rPr, _wTag("color"), attrib={_wTag("val"): COL_DIALOG_A})
|
||||||
|
|
||||||
remaining = text
|
for segment in RX_TEXT.split(text):
|
||||||
while (parts := remaining.partition("\n"))[0]:
|
if segment == "\n":
|
||||||
segment = parts[0]
|
|
||||||
attr = {}
|
|
||||||
if len(segment) != len(segment.strip()):
|
|
||||||
attr[_mkTag("xml", "space")] = "preserve"
|
|
||||||
xmlSubElem(run, _wTag("t"), segment, attrib=attr)
|
|
||||||
if parts[1]:
|
|
||||||
xmlSubElem(run, _wTag("br"))
|
xmlSubElem(run, _wTag("br"))
|
||||||
remaining = parts[2]
|
elif segment == "\t":
|
||||||
|
xmlSubElem(run, _wTag("tab"))
|
||||||
|
elif len(segment) != len(segment.strip()):
|
||||||
|
xmlSubElem(run, _wTag("t"), segment, attrib={_mkTag("xml", "space"): "preserve"})
|
||||||
|
elif segment:
|
||||||
|
xmlSubElem(run, _wTag("t"), segment)
|
||||||
|
|
||||||
return run
|
return run
|
||||||
|
|
||||||
@@ -716,6 +745,11 @@ class ToDocX(Tokenizer):
|
|||||||
|
|
||||||
class DocXParagraph:
|
class DocXParagraph:
|
||||||
|
|
||||||
|
__slots__ = (
|
||||||
|
"_content", "_style", "_textAlign", "_topMargin", "_bottomMargin",
|
||||||
|
"_breakBefore", "_breakAfter",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._content: list[ET.Element] = []
|
self._content: list[ET.Element] = []
|
||||||
self._style: DocXParStyle | None = None
|
self._style: DocXParStyle | None = None
|
||||||
@@ -726,6 +760,20 @@ class DocXParagraph:
|
|||||||
self._breakAfter = False
|
self._breakAfter = False
|
||||||
return
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Properties
|
||||||
|
##
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pageBreakBefore(self) -> bool:
|
||||||
|
"""Has page break before."""
|
||||||
|
return self._breakBefore
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pageBreakAfter(self) -> bool:
|
||||||
|
"""Has page break after."""
|
||||||
|
return self._breakAfter
|
||||||
|
|
||||||
##
|
##
|
||||||
# Setters
|
# Setters
|
||||||
##
|
##
|
||||||
@@ -770,18 +818,11 @@ class DocXParagraph:
|
|||||||
self._content.append(run)
|
self._content.append(run)
|
||||||
return
|
return
|
||||||
|
|
||||||
def finalise(self, body: ET.Element) -> None:
|
def toXml(self, body: ET.Element) -> None:
|
||||||
"""Called after all content is set."""
|
"""Called after all content is set."""
|
||||||
if style := self._style:
|
if style := self._style:
|
||||||
par = xmlSubElem(body, _wTag("p"))
|
par = xmlSubElem(body, _wTag("p"))
|
||||||
|
|
||||||
# Values
|
|
||||||
spacing = {}
|
|
||||||
if self._topMargin is not None:
|
|
||||||
spacing["before"] = str(self._topMargin)
|
|
||||||
if self._bottomMargin is not None:
|
|
||||||
spacing["after"] = str(self._bottomMargin)
|
|
||||||
|
|
||||||
# Paragraph
|
# Paragraph
|
||||||
pPr = xmlSubElem(par, _wTag("pPr"))
|
pPr = xmlSubElem(par, _wTag("pPr"))
|
||||||
xmlSubElem(pPr, _wTag("pStyle"), attrib={_wTag("val"): style.styleId})
|
xmlSubElem(pPr, _wTag("pStyle"), attrib={_wTag("val"): style.styleId})
|
||||||
|
|||||||
Reference in New Issue
Block a user