Get rid of the DocXRun class
This commit is contained in:
@@ -4,6 +4,7 @@ novelWriter – DocX Text Converter
|
||||
|
||||
File History:
|
||||
Created: 2024-10-18 [2.6b1] ToDocX
|
||||
Created: 2024-10-18 [2.6b1] DocXParagraph
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2024, Veronica Berglyd Olsen
|
||||
@@ -109,6 +110,16 @@ M_SUB = ~X_SUB
|
||||
M_DLG = ~X_DLG
|
||||
M_DLA = ~X_DLA
|
||||
|
||||
# DocX Styles
|
||||
S_NORM = "Normal"
|
||||
S_TITLE = "Title"
|
||||
S_HEAD1 = "Heading1"
|
||||
S_HEAD2 = "Heading2"
|
||||
S_HEAD3 = "Heading3"
|
||||
S_HEAD4 = "Heading4"
|
||||
S_SEP = "Separator"
|
||||
S_FIND = "FirstLineIndent"
|
||||
S_META = "TextMeta"
|
||||
|
||||
# Colours
|
||||
COL_HEAD_L12 = "2a6099"
|
||||
@@ -196,9 +207,9 @@ class ToDocX(Tokenizer):
|
||||
|
||||
# xText = self._xText
|
||||
for tType, _, tText, tFormat, tStyle in self._tokens:
|
||||
par = DocXParagraph()
|
||||
|
||||
# Styles
|
||||
par = DocXParagraph()
|
||||
if tStyle is not None:
|
||||
if tStyle & self.A_LEFT:
|
||||
par.setAlignment("left")
|
||||
@@ -230,49 +241,49 @@ class ToDocX(Tokenizer):
|
||||
# dedicated pre-defined style for it
|
||||
# if tStyle & self.A_IND_T:
|
||||
# else:
|
||||
self._addFragments(par, "Normal", tText, tFormat)
|
||||
self._processFragments(par, S_NORM, tText, tFormat)
|
||||
|
||||
elif tType == self.T_TITLE:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
self._addFragments(par, "Title", tHead, tFormat)
|
||||
self._processFragments(par, S_TITLE, tHead, tFormat)
|
||||
|
||||
elif tType == self.T_HEAD1:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
self._addFragments(par, "Heading1", tHead, tFormat)
|
||||
self._processFragments(par, S_HEAD1, tHead, tFormat)
|
||||
|
||||
elif tType == self.T_HEAD2:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
self._addFragments(par, "Heading2", tHead, tFormat)
|
||||
self._processFragments(par, S_HEAD2, tHead, tFormat)
|
||||
|
||||
elif tType == self.T_HEAD3:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
self._addFragments(par, "Heading3", tHead, tFormat)
|
||||
self._processFragments(par, S_HEAD3, tHead, tFormat)
|
||||
|
||||
elif tType == self.T_HEAD4:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
||||
self._addFragments(par, "Heading4", tHead, tFormat)
|
||||
self._processFragments(par, S_HEAD4, tHead, tFormat)
|
||||
|
||||
elif tType == self.T_SEP:
|
||||
self._addFragments(par, "Separator", tText)
|
||||
self._processFragments(par, S_SEP, tText)
|
||||
|
||||
elif tType == self.T_SKIP:
|
||||
self._addFragments(par, "Normal", "")
|
||||
self._processFragments(par, S_NORM, "")
|
||||
|
||||
elif tType == self.T_SYNOPSIS and self._doSynopsis:
|
||||
tTemp, tFmt = self._formatSynopsis(tText, tFormat, True)
|
||||
self._addFragments(par, "TextMeta", tTemp, tFmt)
|
||||
self._processFragments(par, S_META, tTemp, tFmt)
|
||||
|
||||
elif tType == self.T_SHORT and self._doSynopsis:
|
||||
tTemp, tFmt = self._formatSynopsis(tText, tFormat, False)
|
||||
self._addFragments(par, "TextMeta", tTemp, tFmt)
|
||||
self._processFragments(par, S_META, tTemp, tFmt)
|
||||
|
||||
elif tType == self.T_COMMENT and self._doComments:
|
||||
tTemp, tFmt = self._formatComments(tText, tFormat)
|
||||
self._addFragments(par, "TextMeta", tTemp, tFmt)
|
||||
self._processFragments(par, S_META, tTemp, tFmt)
|
||||
|
||||
elif tType == self.T_KEYWORD and self._doKeywords:
|
||||
tTemp, tFmt = self._formatKeywords(tText)
|
||||
self._addFragments(par, "TextMeta", tTemp, tFmt)
|
||||
self._processFragments(par, S_META, tTemp, tFmt)
|
||||
|
||||
par.finalise(self._xBody)
|
||||
|
||||
@@ -413,7 +424,7 @@ class ToDocX(Tokenizer):
|
||||
|
||||
return rTxt, rFmt
|
||||
|
||||
def _addFragments(
|
||||
def _processFragments(
|
||||
self, par: DocXParagraph, pStyle: str, text: str, tFmt: T_Formats | None = None
|
||||
) -> None:
|
||||
"""Apply formatting tags to text."""
|
||||
@@ -422,8 +433,7 @@ class ToDocX(Tokenizer):
|
||||
fStart = 0
|
||||
for fPos, fFmt, fData in tFmt or []:
|
||||
|
||||
run = DocXRun(text[fStart:fPos], xFmt)
|
||||
par.addRun(run)
|
||||
par.addContent(self._textRunToXml(text[fStart:fPos], xFmt))
|
||||
|
||||
if fFmt == self.FMT_B_B:
|
||||
xFmt |= X_BLD
|
||||
@@ -470,11 +480,52 @@ class ToDocX(Tokenizer):
|
||||
fStart = fPos
|
||||
|
||||
if rest := text[fStart:]:
|
||||
run = DocXRun(rest, xFmt)
|
||||
par.addRun(run)
|
||||
par.addContent(self._textRunToXml(rest, xFmt))
|
||||
|
||||
return
|
||||
|
||||
def _textRunToXml(self, text: str, fmt: int) -> ET.Element:
|
||||
"""Encode the text run into XML."""
|
||||
run = ET.Element(_wTag("r"))
|
||||
rPr = ET.SubElement(run, _wTag("rPr"))
|
||||
if fmt & X_BLD == X_BLD:
|
||||
ET.SubElement(rPr, _wTag("b"))
|
||||
if fmt & X_ITA == X_ITA:
|
||||
ET.SubElement(rPr, _wTag("i"))
|
||||
if fmt & X_UND == X_UND:
|
||||
ET.SubElement(rPr, _wTag("u"), attrib={_wTag("val"): "single"})
|
||||
if fmt & X_MRK == X_MRK:
|
||||
ET.SubElement(rPr, _wTag("shd"), attrib={
|
||||
_wTag("fill"): COL_MARK_TXT, _wTag("val"): "clear",
|
||||
})
|
||||
if fmt & X_DEL == X_DEL:
|
||||
ET.SubElement(rPr, _wTag("strike"))
|
||||
if fmt & X_SUP == X_SUP:
|
||||
ET.SubElement(rPr, _wTag("vertAlign"), attrib={_wTag("val"): "superscript"})
|
||||
if fmt & X_SUB == X_SUB:
|
||||
ET.SubElement(rPr, _wTag("vertAlign"), attrib={_wTag("val"): "subscript"})
|
||||
if fmt & X_DLG == X_DLG:
|
||||
ET.SubElement(rPr, _wTag("color"), attrib={_wTag("val"): COL_DIALOG_M})
|
||||
if fmt & X_DLA == X_DLA:
|
||||
ET.SubElement(rPr, _wTag("color"), attrib={_wTag("val"): COL_DIALOG_A})
|
||||
|
||||
remaining = text
|
||||
while (parts := remaining.partition("\n"))[0]:
|
||||
segment = parts[0]
|
||||
attr = {}
|
||||
if len(segment) != len(segment.strip()):
|
||||
attr[_mkTag("xml", "space")] = "preserve"
|
||||
_addSingle(run, _wTag("t"), segment, attrib=attr)
|
||||
if parts[1]:
|
||||
_addSingle(run, _wTag("br"))
|
||||
remaining = parts[2]
|
||||
|
||||
return run
|
||||
|
||||
##
|
||||
# Style Elements
|
||||
##
|
||||
|
||||
def _defaultStyles(self) -> None:
|
||||
"""Set the default styles."""
|
||||
xStyl = ET.SubElement(self._dStyl, _wTag("docDefaults"))
|
||||
@@ -506,7 +557,7 @@ class ToDocX(Tokenizer):
|
||||
# Add Normal Style
|
||||
self._addParStyle(
|
||||
name="Normal",
|
||||
styleId="Normal",
|
||||
styleId=S_NORM,
|
||||
default=True,
|
||||
margins=self._marginText,
|
||||
)
|
||||
@@ -514,10 +565,10 @@ class ToDocX(Tokenizer):
|
||||
# Add Title
|
||||
self._addParStyle(
|
||||
name="Title",
|
||||
styleId="Title",
|
||||
styleId=S_TITLE,
|
||||
size=nwStyles.H_SIZES[0] if hScale else 1.0,
|
||||
basedOn="Normal",
|
||||
nextStyle="Normal",
|
||||
basedOn=S_NORM,
|
||||
nextStyle=S_NORM,
|
||||
margins=self._marginTitle,
|
||||
level=0,
|
||||
)
|
||||
@@ -525,10 +576,10 @@ class ToDocX(Tokenizer):
|
||||
# Add Heading 1
|
||||
self._addParStyle(
|
||||
name="Heading 1",
|
||||
styleId="Heading1",
|
||||
styleId=S_HEAD1,
|
||||
size=nwStyles.H_SIZES[1] if hScale else 1.0,
|
||||
basedOn="Normal",
|
||||
nextStyle="Normal",
|
||||
basedOn=S_NORM,
|
||||
nextStyle=S_NORM,
|
||||
margins=self._marginHead1,
|
||||
level=0,
|
||||
color=COL_HEAD_L12 if hColor else None,
|
||||
@@ -537,10 +588,10 @@ class ToDocX(Tokenizer):
|
||||
# Add Heading 2
|
||||
self._addParStyle(
|
||||
name="Heading 2",
|
||||
styleId="Heading2",
|
||||
styleId=S_HEAD2,
|
||||
size=nwStyles.H_SIZES[2] if hScale else 1.0,
|
||||
basedOn="Normal",
|
||||
nextStyle="Normal",
|
||||
basedOn=S_NORM,
|
||||
nextStyle=S_NORM,
|
||||
margins=self._marginHead2,
|
||||
level=1,
|
||||
color=COL_HEAD_L12 if hColor else None,
|
||||
@@ -549,10 +600,10 @@ class ToDocX(Tokenizer):
|
||||
# Add Heading 3
|
||||
self._addParStyle(
|
||||
name="Heading 3",
|
||||
styleId="Heading3",
|
||||
styleId=S_HEAD3,
|
||||
size=nwStyles.H_SIZES[3] if hScale else 1.0,
|
||||
basedOn="Normal",
|
||||
nextStyle="Normal",
|
||||
basedOn=S_NORM,
|
||||
nextStyle=S_NORM,
|
||||
margins=self._marginHead3,
|
||||
level=1,
|
||||
color=COL_HEAD_L34 if hColor else None,
|
||||
@@ -561,10 +612,10 @@ class ToDocX(Tokenizer):
|
||||
# Add Heading 4
|
||||
self._addParStyle(
|
||||
name="Heading 4",
|
||||
styleId="Heading4",
|
||||
styleId=S_HEAD4,
|
||||
size=nwStyles.H_SIZES[4] if hScale else 1.0,
|
||||
basedOn="Normal",
|
||||
nextStyle="Normal",
|
||||
basedOn=S_NORM,
|
||||
nextStyle=S_NORM,
|
||||
margins=self._marginHead4,
|
||||
level=1,
|
||||
color=COL_HEAD_L34 if hColor else None,
|
||||
@@ -573,9 +624,9 @@ class ToDocX(Tokenizer):
|
||||
# Add Separator
|
||||
self._addParStyle(
|
||||
name="Separator",
|
||||
styleId="Separator",
|
||||
basedOn="Normal",
|
||||
nextStyle="Normal",
|
||||
styleId=S_SEP,
|
||||
basedOn=S_NORM,
|
||||
nextStyle=S_NORM,
|
||||
margins=self._marginSep,
|
||||
align="center",
|
||||
)
|
||||
@@ -583,9 +634,9 @@ class ToDocX(Tokenizer):
|
||||
# Add Text Meta Style
|
||||
self._addParStyle(
|
||||
name="Text Meta",
|
||||
styleId="TextMeta",
|
||||
basedOn="Normal",
|
||||
nextStyle="Normal",
|
||||
styleId=S_META,
|
||||
basedOn=S_NORM,
|
||||
nextStyle=S_NORM,
|
||||
margins=self._marginMeta,
|
||||
color=COL_META_TXT,
|
||||
)
|
||||
@@ -646,8 +697,8 @@ class ToDocX(Tokenizer):
|
||||
class DocXParagraph:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._text: list[DocXRun] = []
|
||||
self._style: str = "Normal"
|
||||
self._content: list[ET.Element] = []
|
||||
self._style: str = S_NORM
|
||||
self._textAlign: str | None = None
|
||||
self._topMargin: int | None = None
|
||||
self._bottomMargin: int | None = None
|
||||
@@ -694,9 +745,9 @@ class DocXParagraph:
|
||||
# Methods
|
||||
##
|
||||
|
||||
def addRun(self, run: DocXRun) -> None:
|
||||
def addContent(self, run: ET.Element) -> None:
|
||||
"""Add a run segment to the paragraph."""
|
||||
self._text.append(run)
|
||||
self._content.append(run)
|
||||
return
|
||||
|
||||
def finalise(self, body: ET.Element) -> None:
|
||||
@@ -721,55 +772,9 @@ class DocXParagraph:
|
||||
# Text
|
||||
if self._breakBefore:
|
||||
_addSingle(ET.SubElement(par, _wTag("r")), _wTag("br"), attrib={_wTag("type"): "page"})
|
||||
for run in self._text:
|
||||
run.append(ET.SubElement(par, _wTag("r")))
|
||||
for run in self._content:
|
||||
par.append(run)
|
||||
if self._breakAfter:
|
||||
_addSingle(ET.SubElement(par, _wTag("r")), _wTag("br"), attrib={_wTag("type"): "page"})
|
||||
|
||||
return
|
||||
|
||||
|
||||
class DocXRun:
|
||||
|
||||
def __init__(self, text: str, fmt: int) -> None:
|
||||
self._text = text
|
||||
self._fmt = fmt
|
||||
return
|
||||
|
||||
def append(self, parent: ET.Element) -> None:
|
||||
"""Append the text run to a paragraph."""
|
||||
if text := self._text:
|
||||
fmt = self._fmt
|
||||
rPr = ET.SubElement(parent, _wTag("rPr"))
|
||||
if fmt & X_BLD == X_BLD:
|
||||
ET.SubElement(rPr, _wTag("b"))
|
||||
if fmt & X_ITA == X_ITA:
|
||||
ET.SubElement(rPr, _wTag("i"))
|
||||
if fmt & X_UND == X_UND:
|
||||
ET.SubElement(rPr, _wTag("u"), attrib={_wTag("val"): "single"})
|
||||
if fmt & X_MRK == X_MRK:
|
||||
ET.SubElement(rPr, _wTag("shd"), attrib={
|
||||
_wTag("fill"): COL_MARK_TXT, _wTag("val"): "clear",
|
||||
})
|
||||
if fmt & X_DEL == X_DEL:
|
||||
ET.SubElement(rPr, _wTag("strike"))
|
||||
if fmt & X_SUP == X_SUP:
|
||||
ET.SubElement(rPr, _wTag("vertAlign"), attrib={_wTag("val"): "superscript"})
|
||||
if fmt & X_SUB == X_SUB:
|
||||
ET.SubElement(rPr, _wTag("vertAlign"), attrib={_wTag("val"): "subscript"})
|
||||
if fmt & X_DLG == X_DLG:
|
||||
ET.SubElement(rPr, _wTag("color"), attrib={_wTag("val"): COL_DIALOG_M})
|
||||
if fmt & X_DLA == X_DLA:
|
||||
ET.SubElement(rPr, _wTag("color"), attrib={_wTag("val"): COL_DIALOG_A})
|
||||
|
||||
temp = text
|
||||
while (parts := temp.partition("\n"))[0]:
|
||||
part = parts[0]
|
||||
attr = {}
|
||||
if len(part) != len(part.strip()):
|
||||
attr[_mkTag("xml", "space")] = "preserve"
|
||||
_addSingle(parent, _wTag("t"), part, attrib=attr)
|
||||
if parts[1]:
|
||||
_addSingle(parent, _wTag("br"))
|
||||
temp = parts[2]
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user