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:
|
for tType, tLine, tText, tFormat, tStyle in self._tokens:
|
||||||
|
|
||||||
# Replace < and > with HTML entities
|
# 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
|
# Styles
|
||||||
aStyle = []
|
aStyle = []
|
||||||
|
|||||||
+66
-112
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user