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:
# 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
aStyle = []
+66 -112
View File
@@ -36,7 +36,7 @@ from datetime import datetime
from novelwriter import __version__
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.tokenizer import Tokenizer, stripEscape
@@ -391,23 +391,8 @@ class ToOdt(Tokenizer):
"""Convert the list of text tokens into XML elements."""
self._result = "" # Not used, but cleared just in case
odtTags = {
self.FMT_B_B: "B", # Bold open format
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 = []
pFmt = []
pText = []
pStyle = None
for tType, _, tText, tFormat, tStyle in self._tokens:
@@ -441,19 +426,21 @@ class ToOdt(Tokenizer):
# Process Text Types
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:
pStyle.setTextAlign("left")
if len(para) > 0 and pStyle is not None:
tTemp = "\n".join(para)
fTemp = " ".join(fmt)
tTxt = tTemp.rstrip()
tFmt = fTemp[:len(tTxt)]
self._addTextPar("Text_20_body", pStyle, tTxt, tFmt=tFmt)
if len(pText) > 0 and pStyle is not None:
tTxt = ""
tFmt = []
for nText, nFmt in zip(pText, pFmt):
tLen = len(tTxt)
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 = []
para = []
pFmt = []
pText = []
pStyle = None
elif tType == self.T_TITLE:
@@ -489,18 +476,8 @@ class ToOdt(Tokenizer):
elif tType == self.T_TEXT:
if pStyle is None:
pStyle = oStyle
tFmt = " "*len(tText)
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)
pText.append(tText)
pFmt.append(tFormat)
elif tType == self.T_SYNOPSIS and self._doSynopsis:
tTemp, fTemp = self._formatSynopsis(tText)
@@ -574,46 +551,34 @@ class ToOdt(Tokenizer):
# 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."""
sSynop = self._localLookup("Synopsis")
rTxt = "**{0}:** {1}".format(sSynop, text)
rFmt = "_B{0} b_ {1}".format(" "*len(sSynop), " "*len(text))
name = self._localLookup("Synopsis")
rTxt = f"{name}: {text}"
rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)]
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."""
sComm = self._localLookup("Comment")
rTxt = "**{0}:** {1}".format(sComm, text)
rFmt = "_B{0} b_ {1}".format(" "*len(sComm), " "*len(text))
name = self._localLookup("Comment")
rTxt = f"{name}: {text}"
rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)]
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."""
valid, bits, _ = self._project.index.scanThis("@"+text)
if not valid or not bits:
return "", ""
rTxt = ""
rFmt = ""
if bits[0] in nwLabels.KEY_NAME:
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))
if not valid or not bits or bits[0] not in nwLabels.KEY_NAME:
return "", []
name = trConst(nwLabels.KEY_NAME[bits[0]])
tags = ", ".join(bits[1:])
rTxt = f"{name}: {tags}"
rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)]
return rTxt, rFmt
def _addTextPar(
self, styleName: str, oStyle: ODTParagraphStyle, tText: str, tFmt: str = "",
isHead: bool = False, oLevel: str | None = None
self, styleName: str, oStyle: ODTParagraphStyle, tText: str,
tFmt: list[tuple[int, int]] = [], isHead: bool = False, oLevel: str | None = None
) -> None:
"""Add a text paragraph to the text XML element."""
tAttr = {}
@@ -631,70 +596,59 @@ class ToOdt(Tokenizer):
if not tText:
return
##
# 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
# Loop Over Fragments
# ===================
parProc = XMLParagraph(xElem)
for i, c in enumerate(tText):
pErr = 0
xFmt = 0x00
tFrag = ""
fLast = 0
for fPos, fFmt in tFmt:
if tFmt[i] == " ":
tTemp += c
elif tFmt[i] == "_":
continue
elif tFmt[i] == "B":
# Add the text up to the current fragment
if tFrag := tText[fLast:fPos]:
if xFmt == 0x00:
parProc.appendText(tFrag)
else:
parProc.appendSpan(tFrag, self._textStyle(xFmt))
# Calculate the change of format
if fFmt == self.FMT_B_B:
xFmt |= X_BLD
elif tFmt[i] == "b":
elif fFmt == self.FMT_B_E:
xFmt &= M_BLD
elif tFmt[i] == "I":
elif fFmt == self.FMT_I_B:
xFmt |= X_ITA
elif tFmt[i] == "i":
elif fFmt == self.FMT_I_E:
xFmt &= M_ITA
elif tFmt[i] == "S":
elif fFmt == self.FMT_D_B:
xFmt |= X_DEL
elif tFmt[i] == "s":
elif fFmt == self.FMT_D_E:
xFmt &= M_DEL
elif tFmt[i] == "U":
elif fFmt == self.FMT_U_B:
xFmt |= X_UND
elif tFmt[i] == "u":
elif fFmt == self.FMT_U_E:
xFmt &= M_UND
elif tFmt[i] == "P":
elif fFmt == self.FMT_SUP_B:
xFmt |= X_SUP
elif tFmt[i] == "p":
elif fFmt == self.FMT_SUP_E:
xFmt &= M_SUP
elif tFmt[i] == "D":
elif fFmt == self.FMT_SUB_B:
xFmt |= X_SUB
elif tFmt[i] == "d":
elif fFmt == self.FMT_SUB_E:
xFmt &= M_SUB
else:
pErr += 1
if xFmt != pFmt:
if pFmt == 0x00:
parProc.appendText(tTemp)
tTemp = ""
else:
parProc.appendSpan(tTemp, self._textStyle(pFmt))
tTemp = ""
fLast = fPos
pFmt = xFmt
# Save what remains in the buffer
if pFmt == 0x00:
parProc.appendText(tTemp)
else:
parProc.appendSpan(tTemp, self._textStyle(pFmt))
if tFrag := tText[fLast:]:
if xFmt == 0x00:
parProc.appendText(tFrag)
else:
parProc.appendSpan(tFrag, self._textStyle(xFmt))
if pErr > 0:
self._errData.append("Unknown format tag encountered")