Add ODT support for new formats

This commit is contained in:
Veronica Berglyd Olsen
2023-10-20 21:42:00 +02:00
parent e669560462
commit b9df625914
2 changed files with 86 additions and 113 deletions
+20 -1
View File
@@ -174,7 +174,26 @@ class ToHtml(Tokenizer):
for tType, tLine, tText, tFormat, tStyle in self._tokens: for tType, tLine, tText, tFormat, tStyle in self._tokens:
# Replace < and > with HTML entities # Replace < and > with HTML entities
tText = tText.replace("<", "&lt;").replace(">", "&gt;") if tFormat:
# If we have formatting, we must recompute the locations
cText = []
i = 0
for c in tText:
if c == "<":
cText.append("&lt;")
tFormat = [[p + 3 if p > i else p, f] for p, f in tFormat]
i += 4
elif c == ">":
cText.append("&gt;")
tFormat = [[p + 3 if p > i else p, f] for p, f in tFormat]
i += 4
else:
cText.append(c)
i += 1
tText = "".join(cText)
else:
# If we don't have formatting, we can do a plain replace
tText = tText.replace("<", "&lt;").replace(">", "&gt;")
# Styles # Styles
aStyle = [] aStyle = []
+66 -112
View File
@@ -36,7 +36,7 @@ from datetime import datetime
from novelwriter import __version__ from novelwriter import __version__
from novelwriter.common import xmlIndent from novelwriter.common import xmlIndent
from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels from novelwriter.constants import nwHeadFmt, nwLabels, trConst
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import Tokenizer, stripEscape from novelwriter.core.tokenizer import Tokenizer, stripEscape
@@ -391,23 +391,8 @@ class ToOdt(Tokenizer):
"""Convert the list of text tokens into XML elements.""" """Convert the list of text tokens into XML elements."""
self._result = "" # Not used, but cleared just in case self._result = "" # Not used, but cleared just in case
odtTags = { pFmt = []
self.FMT_B_B: "B", # Bold open format pText = []
self.FMT_B_E: "b", # Bold close format
self.FMT_I_B: "I", # Italic open format
self.FMT_I_E: "i", # Italic close format
self.FMT_D_B: "S", # Strikethrough open format
self.FMT_D_E: "s", # Strikethrough close format
self.FMT_U_B: "U", # Underline open format
self.FMT_U_E: "u", # Underline close format
self.FMT_SUP_B: "P", # Superscript open format
self.FMT_SUP_E: "p", # Superscript close format
self.FMT_SUB_B: "D", # Subscript open format
self.FMT_SUB_E: "d", # Subscript close format
}
fmt = []
para = []
pStyle = None pStyle = None
for tType, _, tText, tFormat, tStyle in self._tokens: for tType, _, tText, tFormat, tStyle in self._tokens:
@@ -441,19 +426,21 @@ class ToOdt(Tokenizer):
# Process Text Types # Process Text Types
if tType == self.T_EMPTY: if tType == self.T_EMPTY:
if len(para) > 1 and pStyle is not None: if len(pText) > 1 and pStyle is not None:
if self._doJustify: if self._doJustify:
pStyle.setTextAlign("left") pStyle.setTextAlign("left")
if len(para) > 0 and pStyle is not None: if len(pText) > 0 and pStyle is not None:
tTemp = "\n".join(para) tTxt = ""
fTemp = " ".join(fmt) tFmt = []
tTxt = tTemp.rstrip() for nText, nFmt in zip(pText, pFmt):
tFmt = fTemp[:len(tTxt)] tLen = len(tTxt)
self._addTextPar("Text_20_body", pStyle, tTxt, tFmt=tFmt) tTxt += f"{nText}\n"
tFmt.extend((p+tLen, fmt) for p, fmt in nFmt)
self._addTextPar("Text_20_body", pStyle, tTxt.rstrip(), tFmt=tFmt)
fmt = [] pFmt = []
para = [] pText = []
pStyle = None pStyle = None
elif tType == self.T_TITLE: elif tType == self.T_TITLE:
@@ -489,18 +476,8 @@ class ToOdt(Tokenizer):
elif tType == self.T_TEXT: elif tType == self.T_TEXT:
if pStyle is None: if pStyle is None:
pStyle = oStyle pStyle = oStyle
pText.append(tText)
tFmt = " "*len(tText) pFmt.append(tFormat)
for xPos, xLen, xFmt in tFormat:
if xFmt%2 == 0: # Even number: End
tFmt = tFmt[:xPos] + odtTags[xFmt].ljust(xLen, "_") + tFmt[xPos+xLen:]
else: # Odd number: Begin
tFmt = tFmt[:xPos] + odtTags[xFmt].rjust(xLen, "_") + tFmt[xPos+xLen:]
tTxt = tText.rstrip()
tFmt = tFmt[:len(tTxt)]
para.append(tTxt)
fmt.append(tFmt)
elif tType == self.T_SYNOPSIS and self._doSynopsis: elif tType == self.T_SYNOPSIS and self._doSynopsis:
tTemp, fTemp = self._formatSynopsis(tText) tTemp, fTemp = self._formatSynopsis(tText)
@@ -574,46 +551,34 @@ class ToOdt(Tokenizer):
# Internal Functions # Internal Functions
## ##
def _formatSynopsis(self, text: str) -> tuple[str, str]: def _formatSynopsis(self, text: str) -> tuple[str, list[tuple[int, int]]]:
"""Apply formatting to synopsis lines.""" """Apply formatting to synopsis lines."""
sSynop = self._localLookup("Synopsis") name = self._localLookup("Synopsis")
rTxt = "**{0}:** {1}".format(sSynop, text) rTxt = f"{name}: {text}"
rFmt = "_B{0} b_ {1}".format(" "*len(sSynop), " "*len(text)) rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)]
return rTxt, rFmt return rTxt, rFmt
def _formatComments(self, text: str) -> tuple[str, str]: def _formatComments(self, text: str) -> tuple[str, list[tuple[int, int]]]:
"""Apply formatting to comments.""" """Apply formatting to comments."""
sComm = self._localLookup("Comment") name = self._localLookup("Comment")
rTxt = "**{0}:** {1}".format(sComm, text) rTxt = f"{name}: {text}"
rFmt = "_B{0} b_ {1}".format(" "*len(sComm), " "*len(text)) rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)]
return rTxt, rFmt return rTxt, rFmt
def _formatKeywords(self, text: str) -> tuple[str, str]: def _formatKeywords(self, text: str) -> tuple[str, list[tuple[int, int]]]:
"""Apply formatting to keywords.""" """Apply formatting to keywords."""
valid, bits, _ = self._project.index.scanThis("@"+text) valid, bits, _ = self._project.index.scanThis("@"+text)
if not valid or not bits: if not valid or not bits or bits[0] not in nwLabels.KEY_NAME:
return "", "" return "", []
name = trConst(nwLabels.KEY_NAME[bits[0]])
rTxt = "" tags = ", ".join(bits[1:])
rFmt = "" rTxt = f"{name}: {tags}"
if bits[0] in nwLabels.KEY_NAME: rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)]
text = nwLabels.KEY_NAME[bits[0]]
rTxt += "**{0}:** ".format(text)
rFmt += "_B{0} b_ ".format(" "*len(text))
if len(bits) > 1:
if bits[0] == nwKeyWords.TAG_KEY:
rTxt += bits[1]
rFmt += " "*len(bits[1])
else:
tTags = ", ".join(bits[1:])
rTxt += tTags
rFmt += (" "*len(tTags))
return rTxt, rFmt return rTxt, rFmt
def _addTextPar( def _addTextPar(
self, styleName: str, oStyle: ODTParagraphStyle, tText: str, tFmt: str = "", self, styleName: str, oStyle: ODTParagraphStyle, tText: str,
isHead: bool = False, oLevel: str | None = None tFmt: list[tuple[int, int]] = [], isHead: bool = False, oLevel: str | None = None
) -> None: ) -> None:
"""Add a text paragraph to the text XML element.""" """Add a text paragraph to the text XML element."""
tAttr = {} tAttr = {}
@@ -631,70 +596,59 @@ class ToOdt(Tokenizer):
if not tText: if not tText:
return return
## # Loop Over Fragments
# Process Formatting # ===================
##
if len(tText) != len(tFmt):
# Generate an empty format if there isn't any or it doesn't match
tFmt = " "*len(tText)
# The formatting loop
tTemp = ""
xFmt = 0x00
pFmt = 0x00
pErr = 0
parProc = XMLParagraph(xElem) parProc = XMLParagraph(xElem)
for i, c in enumerate(tText): pErr = 0
xFmt = 0x00
tFrag = ""
fLast = 0
for fPos, fFmt in tFmt:
if tFmt[i] == " ": # Add the text up to the current fragment
tTemp += c if tFrag := tText[fLast:fPos]:
elif tFmt[i] == "_": if xFmt == 0x00:
continue parProc.appendText(tFrag)
elif tFmt[i] == "B": else:
parProc.appendSpan(tFrag, self._textStyle(xFmt))
# Calculate the change of format
if fFmt == self.FMT_B_B:
xFmt |= X_BLD xFmt |= X_BLD
elif tFmt[i] == "b": elif fFmt == self.FMT_B_E:
xFmt &= M_BLD xFmt &= M_BLD
elif tFmt[i] == "I": elif fFmt == self.FMT_I_B:
xFmt |= X_ITA xFmt |= X_ITA
elif tFmt[i] == "i": elif fFmt == self.FMT_I_E:
xFmt &= M_ITA xFmt &= M_ITA
elif tFmt[i] == "S": elif fFmt == self.FMT_D_B:
xFmt |= X_DEL xFmt |= X_DEL
elif tFmt[i] == "s": elif fFmt == self.FMT_D_E:
xFmt &= M_DEL xFmt &= M_DEL
elif tFmt[i] == "U": elif fFmt == self.FMT_U_B:
xFmt |= X_UND xFmt |= X_UND
elif tFmt[i] == "u": elif fFmt == self.FMT_U_E:
xFmt &= M_UND xFmt &= M_UND
elif tFmt[i] == "P": elif fFmt == self.FMT_SUP_B:
xFmt |= X_SUP xFmt |= X_SUP
elif tFmt[i] == "p": elif fFmt == self.FMT_SUP_E:
xFmt &= M_SUP xFmt &= M_SUP
elif tFmt[i] == "D": elif fFmt == self.FMT_SUB_B:
xFmt |= X_SUB xFmt |= X_SUB
elif tFmt[i] == "d": elif fFmt == self.FMT_SUB_E:
xFmt &= M_SUB xFmt &= M_SUB
else: else:
pErr += 1 pErr += 1
if xFmt != pFmt: fLast = fPos
if pFmt == 0x00:
parProc.appendText(tTemp)
tTemp = ""
else:
parProc.appendSpan(tTemp, self._textStyle(pFmt))
tTemp = ""
pFmt = xFmt if tFrag := tText[fLast:]:
if xFmt == 0x00:
# Save what remains in the buffer parProc.appendText(tFrag)
if pFmt == 0x00: else:
parProc.appendText(tTemp) parProc.appendSpan(tFrag, self._textStyle(xFmt))
else:
parProc.appendSpan(tTemp, self._textStyle(pFmt))
if pErr > 0: if pErr > 0:
self._errData.append("Unknown format tag encountered") self._errData.append("Unknown format tag encountered")