Add ODT support for new formats
This commit is contained in:
@@ -174,7 +174,26 @@ class ToHtml(Tokenizer):
|
||||
for tType, tLine, tText, tFormat, tStyle in self._tokens:
|
||||
|
||||
# Replace < and > with HTML entities
|
||||
tText = tText.replace("<", "<").replace(">", ">")
|
||||
if tFormat:
|
||||
# If we have formatting, we must recompute the locations
|
||||
cText = []
|
||||
i = 0
|
||||
for c in tText:
|
||||
if c == "<":
|
||||
cText.append("<")
|
||||
tFormat = [[p + 3 if p > i else p, f] for p, f in tFormat]
|
||||
i += 4
|
||||
elif c == ">":
|
||||
cText.append(">")
|
||||
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("<", "<").replace(">", ">")
|
||||
|
||||
# Styles
|
||||
aStyle = []
|
||||
|
||||
+66
-112
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user