Add annotations to odt class

This commit is contained in:
Veronica Berglyd Olsen
2023-06-06 22:12:56 +02:00
parent 6b6c517b1f
commit b09068ab99
6 changed files with 322 additions and 333 deletions
+5 -1
View File
@@ -1,7 +1,6 @@
""" """
novelWriter HTML Text Converter novelWriter HTML Text Converter
================================= =================================
Extends the Tokenizer class to generate HTML output
File History: File History:
Created: 2019-05-07 [0.0.1] Created: 2019-05-07 [0.0.1]
@@ -37,6 +36,11 @@ logger = logging.getLogger(__name__)
class ToHtml(Tokenizer): class ToHtml(Tokenizer):
"""Core: HTML Document Writer
Extend the Tokenizer class to writer HTML output. This class is
also used by the Document Viewer, and Manuscript Build Preview.
"""
M_PREVIEW = 0 # Tweak output for the DocViewer M_PREVIEW = 0 # Tweak output for the DocViewer
M_EXPORT = 1 # Tweak output for saving to HTML or printing M_EXPORT = 1 # Tweak output for saving to HTML or printing
+12 -4
View File
@@ -1,7 +1,6 @@
""" """
novelWriter Text Tokenizer novelWriter Text Tokenizer
============================ ============================
Split novelWriter plain text into its elements
File History: File History:
Created: 2019-05-05 [0.0.1] Tokenizer Created: 2019-05-05 [0.0.1] Tokenizer
@@ -55,6 +54,13 @@ def stripEscape(text):
class Tokenizer(ABC): class Tokenizer(ABC):
"""Core: Text Tokenizer Abstract Base Class
This is the base class for all document build classes. It parses the
novelWriter markup format and generates a registry of tokens and
text that can be further processed into other output formats by
subclasses.
"""
# In-Text Format # In-Text Format
FMT_B_B = 1 # Begin bold FMT_B_B = 1 # Begin bold
@@ -309,6 +315,9 @@ class Tokenizer(ABC):
"""Add a heading at the start of a new root folder.""" """Add a heading at the start of a new root folder."""
if not self._project.tree.checkType(tHandle, nwItemType.ROOT): if not self._project.tree.checkType(tHandle, nwItemType.ROOT):
return False return False
theItem = self._project.tree[tHandle]
if not theItem:
return False
if self._isFirst: if self._isFirst:
textAlign = self.A_CENTRE textAlign = self.A_CENTRE
@@ -316,7 +325,6 @@ class Tokenizer(ABC):
else: else:
textAlign = self.A_PBB | self.A_CENTRE textAlign = self.A_PBB | self.A_CENTRE
theItem = self._project.tree[tHandle]
locNotes = self._localLookup("Notes") locNotes = self._localLookup("Notes")
theTitle = f"{locNotes}: {theItem.itemName}" theTitle = f"{locNotes}: {theItem.itemName}"
self._tokens = [] self._tokens = []
@@ -781,10 +789,10 @@ class HeadingFormatter:
chWord = self._project.localLookup(self._chCount) chWord = self._project.localLookup(self._chCount)
hFormat = hFormat.replace(nwHeadFmt.CH_WORD, chWord) hFormat = hFormat.replace(nwHeadFmt.CH_WORD, chWord)
if nwHeadFmt.CH_ROML in hFormat: if nwHeadFmt.CH_ROML in hFormat:
chRom = numberToRoman(self._chCount, True) chRom = numberToRoman(self._chCount, toLower=True)
hFormat = hFormat.replace(nwHeadFmt.CH_ROML, chRom) hFormat = hFormat.replace(nwHeadFmt.CH_ROML, chRom)
if nwHeadFmt.CH_ROMU in hFormat: if nwHeadFmt.CH_ROMU in hFormat:
chRom = numberToRoman(self._chCount, False) chRom = numberToRoman(self._chCount, toLower=False)
hFormat = hFormat.replace(nwHeadFmt.CH_ROMU, chRom) hFormat = hFormat.replace(nwHeadFmt.CH_ROMU, chRom)
return hFormat return hFormat
+6 -1
View File
@@ -1,7 +1,6 @@
""" """
novelWriter Markdown Text Converter novelWriter Markdown Text Converter
===================================== =====================================
Extends the Tokenizer class to generate Makrdown output
File History: File History:
Created: 2021-02-06 [1.2b1] Created: 2021-02-06 [1.2b1]
@@ -36,6 +35,12 @@ logger = logging.getLogger(__name__)
class ToMarkdown(Tokenizer): class ToMarkdown(Tokenizer):
"""Core: Markdown Document Writer
Extend the Tokenizer class to writer Markdown output. It supports
both Standard Markdown and GitHub Flavour Markdown (Extended). The
class also supports concatenating novelWriter markup files.
"""
M_STD = 0 # Standard Markdown M_STD = 0 # Standard Markdown
M_GH = 1 # GitHub Markdown M_GH = 1 # GitHub Markdown
+248 -268
View File
@@ -1,7 +1,6 @@
""" """
novelWriter ODT Text Converter novelWriter ODT Text Converter
================================ ================================
Extends the Tokenizer class to generate ODT and FODT files
File History: File History:
Created: 2021-01-26 [1.2b1] ToOdt Created: 2021-01-26 [1.2b1] ToOdt
@@ -25,8 +24,10 @@ General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations
import logging import logging
from pathlib import Path
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from hashlib import sha256 from hashlib import sha256
@@ -36,6 +37,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 nwKeyWords, nwLabels from novelwriter.constants import nwKeyWords, nwLabels
from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import Tokenizer, stripEscape from novelwriter.core.tokenizer import Tokenizer, stripEscape
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -55,9 +57,8 @@ for ns, uri in XML_NS.items():
ET.register_namespace(ns, uri) ET.register_namespace(ns, uri)
def _mkTag(ns, tag): def _mkTag(ns: str, tag: str) -> str:
"""Assemble namespace and tag name. """Assemble namespace and tag name."""
"""
uri = XML_NS.get(ns, "") uri = XML_NS.get(ns, "")
if uri: if uri:
return f"{{{uri}}}{tag}" return f"{{{uri}}}{tag}"
@@ -89,9 +90,16 @@ M_DEL = ~X_DEL
class ToOdt(Tokenizer): class ToOdt(Tokenizer):
"""Core: Open Document Writer
def __init__(self, theProject, isFlat): Extend the Tokenizer class to writer Open Document files. The output
super().__init__(theProject) should conform to the 1.3 Extended standard.
Test with: https://odfvalidator.org/
"""
def __init__(self, project: NWProject, isFlat: bool):
super().__init__(project)
self._isFlat = isFlat # Flat: .fodt, otherwise .odt self._isFlat = isFlat # Flat: .fodt, otherwise .odt
@@ -178,36 +186,26 @@ class ToOdt(Tokenizer):
# Setters # Setters
## ##
def setLanguage(self, theLang): def setLanguage(self, language: str):
"""Set language for the document. """Set language for the document."""
""" if language:
if theLang is None: langBits = language.split("_")
return False self._dLanguage = langBits[0]
if len(langBits) > 1:
self._dCountry = langBits[1]
return
langBits = theLang.split("_") def setColourHeaders(self, state: bool):
self._dLanguage = langBits[0] """Enable/disable coloured headings and comments."""
if len(langBits) > 1: self._colourHead = state
self._dCountry = langBits[1]
return True
def setColourHeaders(self, doColour):
"""Enable/disable coloured headings and comments.
"""
self._colourHead = doColour
return return
## ##
# Class Methods # Class Methods
## ##
def getErrors(self):
"""Return the list of errors."""
return self._errData
def initDocument(self): def initDocument(self):
"""Initialises a new open document XML tree. """Initialises a new open document XML tree."""
"""
# Initialise Variables # Initialise Variables
# ==================== # ====================
@@ -369,8 +367,7 @@ class ToOdt(Tokenizer):
return return
def doConvert(self): def doConvert(self):
"""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 = { odtTags = {
@@ -421,12 +418,12 @@ class ToOdt(Tokenizer):
if self._doJustify: if self._doJustify:
parStyle.setTextAlign("left") parStyle.setTextAlign("left")
if len(thisPar) > 0: if len(thisPar) > 0 and parStyle is not None:
tTemp = "\n".join(thisPar) tTemp = "\n".join(thisPar)
fTemp = " ".join(thisFmt) fTemp = " ".join(thisFmt)
tTxt = tTemp.rstrip() tTxt = tTemp.rstrip()
tFmt = fTemp[:len(tTxt)] tFmt = fTemp[:len(tTxt)]
self._addTextPar("Text_20_body", parStyle, tTxt, theFmt=tFmt) self._addTextPar("Text_20_body", parStyle, tTxt, tFmt=tFmt)
thisPar = [] thisPar = []
thisFmt = [] thisFmt = []
@@ -477,41 +474,37 @@ class ToOdt(Tokenizer):
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)
self._addTextPar("Text_20_Meta", oStyle, tTemp, theFmt=fTemp) self._addTextPar("Text_20_Meta", oStyle, tTemp, tFmt=fTemp)
elif tType == self.T_COMMENT and self._doComments: elif tType == self.T_COMMENT and self._doComments:
tTemp, fTemp = self._formatComments(tText) tTemp, fTemp = self._formatComments(tText)
self._addTextPar("Text_20_Meta", oStyle, tTemp, theFmt=fTemp) self._addTextPar("Text_20_Meta", oStyle, tTemp, tFmt=fTemp)
elif tType == self.T_KEYWORD and self._doKeywords: elif tType == self.T_KEYWORD and self._doKeywords:
tTemp, fTemp = self._formatKeywords(tText) tTemp, fTemp = self._formatKeywords(tText)
self._addTextPar("Text_20_Meta", oStyle, tTemp, theFmt=fTemp) self._addTextPar("Text_20_Meta", oStyle, tTemp, tFmt=fTemp)
return return
def closeDocument(self): def closeDocument(self):
"""Return the serialised XML document """Return the serialised XML document"""
"""
# Build the auto-generated styles # Build the auto-generated styles
for styleName, styleObj in self._autoPara.values(): for styleName, styleObj in self._autoPara.values():
styleObj.packXML(self._xAuto, styleName) styleObj.packXML(self._xAuto, styleName)
for styleName, styleObj in self._autoText.values(): for styleName, styleObj in self._autoText.values():
styleObj.packXML(self._xAuto, styleName) styleObj.packXML(self._xAuto, styleName)
return return
def saveFlatXML(self, savePath): def saveFlatXML(self, path: str | Path):
"""Save the data to an .fodt file. """Save the data to an .fodt file."""
""" with open(path, mode="wb") as fObj:
with open(savePath, mode="wb") as outFile:
xml = ET.ElementTree(self._dFlat) xml = ET.ElementTree(self._dFlat)
xmlIndent(xml) xmlIndent(xml)
xml.write(outFile, encoding="utf-8", xml_declaration=True) xml.write(fObj, encoding="utf-8", xml_declaration=True)
return return
def saveOpenDocText(self, savePath): def saveOpenDocText(self, path: str | Path):
"""Save the data to an .odt file. """Save the data to an .odt file."""
"""
mMani = _mkTag("manifest", "manifest") mMani = _mkTag("manifest", "manifest")
mVers = _mkTag("manifest", "version") mVers = _mkTag("manifest", "version")
mPath = _mkTag("manifest", "full-path") mPath = _mkTag("manifest", "full-path")
@@ -534,7 +527,7 @@ class ToOdt(Tokenizer):
xml = ET.ElementTree(xObj) xml = ET.ElementTree(xObj)
xml.write(fObj, encoding="utf-8", xml_declaration=True) xml.write(fObj, encoding="utf-8", xml_declaration=True)
with ZipFile(savePath, mode="w") as outZip: with ZipFile(path, mode="w") as outZip:
outZip.writestr("mimetype", X_MIME) outZip.writestr("mimetype", X_MIME)
putInZip("META-INF/manifest.xml", xMani, outZip) putInZip("META-INF/manifest.xml", xMani, outZip)
putInZip("settings.xml", xSett, outZip) putInZip("settings.xml", xSett, outZip)
@@ -548,49 +541,48 @@ class ToOdt(Tokenizer):
# Internal Functions # Internal Functions
## ##
def _formatSynopsis(self, tText): def _formatSynopsis(self, text: str) -> tuple[str, str]:
"""Apply formatting to synopsis lines. """Apply formatting to synopsis lines."""
"""
sSynop = self._localLookup("Synopsis") sSynop = self._localLookup("Synopsis")
rTxt = "**{0}:** {1}".format(sSynop, tText) rTxt = "**{0}:** {1}".format(sSynop, text)
rFmt = "_B{0} b_ {1}".format(" "*len(sSynop), " "*len(tText)) rFmt = "_B{0} b_ {1}".format(" "*len(sSynop), " "*len(text))
return rTxt, rFmt return rTxt, rFmt
def _formatComments(self, tText): def _formatComments(self, text: str) -> tuple[str, str]:
"""Apply formatting to comments. """Apply formatting to comments."""
"""
sComm = self._localLookup("Comment") sComm = self._localLookup("Comment")
rTxt = "**{0}:** {1}".format(sComm, tText) rTxt = "**{0}:** {1}".format(sComm, text)
rFmt = "_B{0} b_ {1}".format(" "*len(sComm), " "*len(tText)) rFmt = "_B{0} b_ {1}".format(" "*len(sComm), " "*len(text))
return rTxt, rFmt return rTxt, rFmt
def _formatKeywords(self, tText): def _formatKeywords(self, text: str) -> tuple[str, str]:
"""Apply formatting to keywords. """Apply formatting to keywords."""
""" valid, bits, _ = self._project.index.scanThis("@"+text)
isValid, theBits, _ = self._project.index.scanThis("@"+tText) if not valid or not bits:
if not isValid or not theBits: return "", ""
return ""
rTxt = "" rTxt = ""
rFmt = "" rFmt = ""
if theBits[0] in nwLabels.KEY_NAME: if bits[0] in nwLabels.KEY_NAME:
tText = nwLabels.KEY_NAME[theBits[0]] text = nwLabels.KEY_NAME[bits[0]]
rTxt += "**{0}:** ".format(tText) rTxt += "**{0}:** ".format(text)
rFmt += "_B{0} b_ ".format(" "*len(tText)) rFmt += "_B{0} b_ ".format(" "*len(text))
if len(theBits) > 1: if len(bits) > 1:
if theBits[0] == nwKeyWords.TAG_KEY: if bits[0] == nwKeyWords.TAG_KEY:
rTxt += theBits[1] rTxt += bits[1]
rFmt += " "*len(theBits[1]) rFmt += " "*len(bits[1])
else: else:
tTags = ", ".join(theBits[1:]) tTags = ", ".join(bits[1:])
rTxt += tTags rTxt += tTags
rFmt += (" "*len(tTags)) rFmt += (" "*len(tTags))
return rTxt, rFmt return rTxt, rFmt
def _addTextPar(self, styleName, oStyle, theText, theFmt="", isHead=False, oLevel=None): def _addTextPar(
"""Add a text paragraph to the text XML element. self, styleName: str, oStyle: ODTParagraphStyle, tText: str, tFmt: str = "",
""" isHead: bool = False, oLevel: str | None = None
):
"""Add a text paragraph to the text XML element."""
tAttr = {} tAttr = {}
tAttr[_mkTag("text", "style-name")] = self._paraStyle(styleName, oStyle) tAttr[_mkTag("text", "style-name")] = self._paraStyle(styleName, oStyle)
if oLevel is not None: if oLevel is not None:
@@ -603,16 +595,16 @@ class ToOdt(Tokenizer):
# xmlIndent will add a line break if the first subelement is a span. # xmlIndent will add a line break if the first subelement is a span.
xElem.text = "" xElem.text = ""
if not theText: if not tText:
return return
## ##
# Process Formatting # Process Formatting
## ##
if len(theText) != len(theFmt): if len(tText) != len(tFmt):
# Generate an empty format if there isn't any or it doesn't match # Generate an empty format if there isn't any or it doesn't match
theFmt = " "*len(theText) tFmt = " "*len(tText)
# The formatting loop # The formatting loop
tTemp = "" tTemp = ""
@@ -622,23 +614,23 @@ class ToOdt(Tokenizer):
parProc = XMLParagraph(xElem) parProc = XMLParagraph(xElem)
for i, c in enumerate(theText): for i, c in enumerate(tText):
if theFmt[i] == " ": if tFmt[i] == " ":
tTemp += c tTemp += c
elif theFmt[i] == "_": elif tFmt[i] == "_":
continue continue
elif theFmt[i] == "B": elif tFmt[i] == "B":
xFmt |= X_BLD xFmt |= X_BLD
elif theFmt[i] == "b": elif tFmt[i] == "b":
xFmt &= M_BLD xFmt &= M_BLD
elif theFmt[i] == "I": elif tFmt[i] == "I":
xFmt |= X_ITA xFmt |= X_ITA
elif theFmt[i] == "i": elif tFmt[i] == "i":
xFmt &= M_ITA xFmt &= M_ITA
elif theFmt[i] == "S": elif tFmt[i] == "S":
xFmt |= X_DEL xFmt |= X_DEL
elif theFmt[i] == "s": elif tFmt[i] == "s":
xFmt &= M_DEL xFmt &= M_DEL
else: else:
pErr += 1 pErr += 1
@@ -669,9 +661,8 @@ class ToOdt(Tokenizer):
return return
def _paraStyle(self, parName, oStyle): def _paraStyle(self, parName: str, oStyle: ODTParagraphStyle) -> str:
"""Return a name for a style object. """Return a name for a style object."""
"""
refStyle = self._mainPara.get(parName, None) refStyle = self._mainPara.get(parName, None)
if refStyle is None: if refStyle is None:
logger.error("Unknown paragraph style '%s'", parName) logger.error("Unknown paragraph style '%s'", parName)
@@ -690,29 +681,27 @@ class ToOdt(Tokenizer):
return newName return newName
def _textStyle(self, tFmt): def _textStyle(self, hFmt: int) -> str:
"""Return a text style for a given style code. """Return a text style for a given style code."""
""" if hFmt in self._autoText:
if tFmt in self._autoText: return self._autoText[hFmt][0]
return self._autoText[tFmt][0]
newName = "T%d" % (len(self._autoText) + 1) newName = "T%d" % (len(self._autoText) + 1)
newStyle = ODTTextStyle() newStyle = ODTTextStyle()
if tFmt & X_BLD: if hFmt & X_BLD:
newStyle.setFontWeight("bold") newStyle.setFontWeight("bold")
if tFmt & X_ITA: if hFmt & X_ITA:
newStyle.setFontStyle("italic") newStyle.setFontStyle("italic")
if tFmt & X_DEL: if hFmt & X_DEL:
newStyle.setStrikeStyle("solid") newStyle.setStrikeStyle("solid")
newStyle.setStrikeType("single") newStyle.setStrikeType("single")
self._autoText[tFmt] = (newName, newStyle) self._autoText[hFmt] = (newName, newStyle)
return newName return newName
def _emToCm(self, emVal): def _emToCm(self, emVal: float) -> str:
"""Converts an em value to centimetres. """Converts an em value to centimetres."""
"""
return f"{emVal*2.54/72*self._textSize:.3f}cm" return f"{emVal*2.54/72*self._textSize:.3f}cm"
## ##
@@ -720,110 +709,107 @@ class ToOdt(Tokenizer):
## ##
def _pageStyles(self): def _pageStyles(self):
"""Set the default page style. """Set the default page style."""
""" tAttr = {}
theAttr = {} tAttr[_mkTag("style", "name")] = "PM1"
theAttr[_mkTag("style", "name")] = "PM1"
if self._isFlat: if self._isFlat:
xPage = ET.SubElement(self._xAuto, _mkTag("style", "page-layout"), attrib=theAttr) xPage = ET.SubElement(self._xAuto, _mkTag("style", "page-layout"), attrib=tAttr)
else: else:
xPage = ET.SubElement(self._xAut2, _mkTag("style", "page-layout"), attrib=theAttr) xPage = ET.SubElement(self._xAut2, _mkTag("style", "page-layout"), attrib=tAttr)
theAttr = {} tAttr = {}
theAttr[_mkTag("fo", "margin-top")] = self._mDocTop tAttr[_mkTag("fo", "margin-top")] = self._mDocTop
theAttr[_mkTag("fo", "margin-bottom")] = self._mDocBtm tAttr[_mkTag("fo", "margin-bottom")] = self._mDocBtm
theAttr[_mkTag("fo", "margin-left")] = self._mDocLeft tAttr[_mkTag("fo", "margin-left")] = self._mDocLeft
theAttr[_mkTag("fo", "margin-right")] = self._mDocRight tAttr[_mkTag("fo", "margin-right")] = self._mDocRight
ET.SubElement(xPage, _mkTag("style", "page-layout-properties"), attrib=theAttr) ET.SubElement(xPage, _mkTag("style", "page-layout-properties"), attrib=tAttr)
xHead = ET.SubElement(xPage, _mkTag("style", "header-style")) xHead = ET.SubElement(xPage, _mkTag("style", "header-style"))
theAttr = {} tAttr = {}
theAttr[_mkTag("fo", "min-height")] = "0.600cm" tAttr[_mkTag("fo", "min-height")] = "0.600cm"
theAttr[_mkTag("fo", "margin-left")] = "0.000cm" tAttr[_mkTag("fo", "margin-left")] = "0.000cm"
theAttr[_mkTag("fo", "margin-right")] = "0.000cm" tAttr[_mkTag("fo", "margin-right")] = "0.000cm"
theAttr[_mkTag("fo", "margin-bottom")] = "0.500cm" tAttr[_mkTag("fo", "margin-bottom")] = "0.500cm"
ET.SubElement(xHead, _mkTag("style", "header-footer-properties"), attrib=theAttr) ET.SubElement(xHead, _mkTag("style", "header-footer-properties"), attrib=tAttr)
return return
def _defaultStyles(self): def _defaultStyles(self):
"""Set the default styles. """Set the default styles."""
"""
# Add Paragraph Family Style # Add Paragraph Family Style
# ========================== # ==========================
theAttr = {} tAttr = {}
theAttr[_mkTag("style", "family")] = "paragraph" tAttr[_mkTag("style", "family")] = "paragraph"
xStyl = ET.SubElement(self._xStyl, _mkTag("style", "default-style"), attrib=theAttr) xStyl = ET.SubElement(self._xStyl, _mkTag("style", "default-style"), attrib=tAttr)
theAttr = {} tAttr = {}
theAttr[_mkTag("style", "line-break")] = "strict" tAttr[_mkTag("style", "line-break")] = "strict"
theAttr[_mkTag("style", "tab-stop-distance")] = "1.251cm" tAttr[_mkTag("style", "tab-stop-distance")] = "1.251cm"
theAttr[_mkTag("style", "writing-mode")] = "page" tAttr[_mkTag("style", "writing-mode")] = "page"
ET.SubElement(xStyl, _mkTag("style", "paragraph-properties"), attrib=theAttr) ET.SubElement(xStyl, _mkTag("style", "paragraph-properties"), attrib=tAttr)
theAttr = {} tAttr = {}
theAttr[_mkTag("style", "font-name")] = self._textFont tAttr[_mkTag("style", "font-name")] = self._textFont
theAttr[_mkTag("fo", "font-family")] = self._fontFamily tAttr[_mkTag("fo", "font-family")] = self._fontFamily
theAttr[_mkTag("fo", "font-size")] = self._fSizeText tAttr[_mkTag("fo", "font-size")] = self._fSizeText
theAttr[_mkTag("fo", "language")] = self._dLanguage tAttr[_mkTag("fo", "language")] = self._dLanguage
theAttr[_mkTag("fo", "country")] = self._dCountry tAttr[_mkTag("fo", "country")] = self._dCountry
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib=theAttr) ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib=tAttr)
# Add Standard Paragraph Style # Add Standard Paragraph Style
# ============================ # ============================
theAttr = {} tAttr = {}
theAttr[_mkTag("style", "name")] = "Standard" tAttr[_mkTag("style", "name")] = "Standard"
theAttr[_mkTag("style", "family")] = "paragraph" tAttr[_mkTag("style", "family")] = "paragraph"
theAttr[_mkTag("style", "class")] = "text" tAttr[_mkTag("style", "class")] = "text"
xStyl = ET.SubElement(self._xStyl, _mkTag("style", "style"), attrib=theAttr) xStyl = ET.SubElement(self._xStyl, _mkTag("style", "style"), attrib=tAttr)
theAttr = {} tAttr = {}
theAttr[_mkTag("style", "font-name")] = self._textFont tAttr[_mkTag("style", "font-name")] = self._textFont
theAttr[_mkTag("fo", "font-family")] = self._fontFamily tAttr[_mkTag("fo", "font-family")] = self._fontFamily
theAttr[_mkTag("fo", "font-size")] = self._fSizeText tAttr[_mkTag("fo", "font-size")] = self._fSizeText
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib=theAttr) ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib=tAttr)
# Add Default Heading Style # Add Default Heading Style
# ========================= # =========================
theAttr = {} tAttr = {}
theAttr[_mkTag("style", "name")] = "Heading" tAttr[_mkTag("style", "name")] = "Heading"
theAttr[_mkTag("style", "family")] = "paragraph" tAttr[_mkTag("style", "family")] = "paragraph"
theAttr[_mkTag("style", "parent-style-name")] = "Standard" tAttr[_mkTag("style", "parent-style-name")] = "Standard"
theAttr[_mkTag("style", "next-style-name")] = "Text_20_body" tAttr[_mkTag("style", "next-style-name")] = "Text_20_body"
theAttr[_mkTag("style", "class")] = "text" tAttr[_mkTag("style", "class")] = "text"
xStyl = ET.SubElement(self._xStyl, _mkTag("style", "style"), attrib=theAttr) xStyl = ET.SubElement(self._xStyl, _mkTag("style", "style"), attrib=tAttr)
theAttr = {} tAttr = {}
theAttr[_mkTag("fo", "margin-top")] = self._mTopHead tAttr[_mkTag("fo", "margin-top")] = self._mTopHead
theAttr[_mkTag("fo", "margin-bottom")] = self._mBotHead tAttr[_mkTag("fo", "margin-bottom")] = self._mBotHead
theAttr[_mkTag("fo", "keep-with-next")] = "always" tAttr[_mkTag("fo", "keep-with-next")] = "always"
ET.SubElement(xStyl, _mkTag("style", "paragraph-properties"), attrib=theAttr) ET.SubElement(xStyl, _mkTag("style", "paragraph-properties"), attrib=tAttr)
theAttr = {} tAttr = {}
theAttr[_mkTag("style", "font-name")] = self._textFont tAttr[_mkTag("style", "font-name")] = self._textFont
theAttr[_mkTag("fo", "font-family")] = self._fontFamily tAttr[_mkTag("fo", "font-family")] = self._fontFamily
theAttr[_mkTag("fo", "font-size")] = self._fSizeHead tAttr[_mkTag("fo", "font-size")] = self._fSizeHead
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib=theAttr) ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib=tAttr)
# Add Header and Footer Styles # Add Header and Footer Styles
# ============================ # ============================
theAttr = {} tAttr = {}
theAttr[_mkTag("style", "name")] = "Header_20_and_20_Footer" tAttr[_mkTag("style", "name")] = "Header_20_and_20_Footer"
theAttr[_mkTag("style", "display-name")] = "Header and Footer" tAttr[_mkTag("style", "display-name")] = "Header and Footer"
theAttr[_mkTag("style", "family")] = "paragraph" tAttr[_mkTag("style", "family")] = "paragraph"
theAttr[_mkTag("style", "parent-style-name")] = "Standard" tAttr[_mkTag("style", "parent-style-name")] = "Standard"
theAttr[_mkTag("style", "class")] = "extra" tAttr[_mkTag("style", "class")] = "extra"
ET.SubElement(self._xStyl, _mkTag("style", "style"), attrib=theAttr) ET.SubElement(self._xStyl, _mkTag("style", "style"), attrib=tAttr)
return return
def _useableStyles(self): def _useableStyles(self):
"""Set the usable styles. """Set the usable styles."""
"""
# Add Text Body Style # Add Text Body Style
# =================== # ===================
@@ -977,12 +963,11 @@ class ToOdt(Tokenizer):
return return
def _writeHeader(self): def _writeHeader(self):
"""Write the header elements. """Write the header elements."""
""" tAttr = {}
theAttr = {} tAttr[_mkTag("style", "name")] = "Standard"
theAttr[_mkTag("style", "name")] = "Standard" tAttr[_mkTag("style", "page-layout-name")] = "PM1"
theAttr[_mkTag("style", "page-layout-name")] = "PM1" xPage = ET.SubElement(self._xMast, _mkTag("style", "master-page"), attrib=tAttr)
xPage = ET.SubElement(self._xMast, _mkTag("style", "master-page"), attrib=theAttr)
# Standard Page Header # Standard Page Header
xHead = ET.SubElement(xPage, _mkTag("style", "header")) xHead = ET.SubElement(xPage, _mkTag("style", "header"))
@@ -1062,28 +1047,28 @@ class ODTParagraphStyle:
# Attribute Setters # Attribute Setters
## ##
def setDisplayName(self, theValue): def setDisplayName(self, value: str | None):
self._mAttr["display-name"][1] = str(theValue) self._mAttr["display-name"][1] = value
return return
def setParentStyleName(self, theValue): def setParentStyleName(self, value: str | None):
self._mAttr["parent-style-name"][1] = str(theValue) self._mAttr["parent-style-name"][1] = value
return return
def setNextStyleName(self, theValue): def setNextStyleName(self, value: str | None):
self._mAttr["next-style-name"][1] = str(theValue) self._mAttr["next-style-name"][1] = value
return return
def setOutlineLevel(self, theValue): def setOutlineLevel(self, value: str | None):
if theValue in self.VALID_LEVEL: if value in self.VALID_LEVEL:
self._mAttr["default-outline-level"][1] = str(theValue) self._mAttr["default-outline-level"][1] = value
else: else:
self._mAttr["default-outline-level"][1] = None self._mAttr["default-outline-level"][1] = None
return return
def setClass(self, theValue): def setClass(self, value: str | None):
if theValue in self.VALID_CLASS: if value in self.VALID_CLASS:
self._mAttr["class"][1] = str(theValue) self._mAttr["class"][1] = value
else: else:
self._mAttr["class"][1] = None self._mAttr["class"][1] = None
return return
@@ -1092,43 +1077,43 @@ class ODTParagraphStyle:
# Paragraph Setters # Paragraph Setters
## ##
def setMarginTop(self, theValue): def setMarginTop(self, value: str | None):
self._pAttr["margin-top"][1] = str(theValue) self._pAttr["margin-top"][1] = value
return return
def setMarginBottom(self, theValue): def setMarginBottom(self, value: str | None):
self._pAttr["margin-bottom"][1] = str(theValue) self._pAttr["margin-bottom"][1] = value
return return
def setMarginLeft(self, theValue): def setMarginLeft(self, value: str | None):
self._pAttr["margin-left"][1] = str(theValue) self._pAttr["margin-left"][1] = value
return return
def setMarginRight(self, theValue): def setMarginRight(self, value: str | None):
self._pAttr["margin-right"][1] = str(theValue) self._pAttr["margin-right"][1] = value
return return
def setLineHeight(self, theValue): def setLineHeight(self, value: str | None):
self._pAttr["line-height"][1] = str(theValue) self._pAttr["line-height"][1] = value
return return
def setTextAlign(self, theValue): def setTextAlign(self, value: str | None):
if theValue in self.VALID_ALIGN: if value in self.VALID_ALIGN:
self._pAttr["text-align"][1] = str(theValue) self._pAttr["text-align"][1] = value
else: else:
self._pAttr["text-align"][1] = None self._pAttr["text-align"][1] = None
return return
def setBreakBefore(self, theValue): def setBreakBefore(self, value: str | None):
if theValue in self.VALID_BREAK: if value in self.VALID_BREAK:
self._pAttr["break-before"][1] = str(theValue) self._pAttr["break-before"][1] = value
else: else:
self._pAttr["break-before"][1] = None self._pAttr["break-before"][1] = None
return return
def setBreakAfter(self, theValue): def setBreakAfter(self, value: str | None):
if theValue in self.VALID_BREAK: if value in self.VALID_BREAK:
self._pAttr["break-after"][1] = str(theValue) self._pAttr["break-after"][1] = value
else: else:
self._pAttr["break-after"][1] = None self._pAttr["break-after"][1] = None
return return
@@ -1137,38 +1122,38 @@ class ODTParagraphStyle:
# Text Setters # Text Setters
## ##
def setFontName(self, theValue): def setFontName(self, value: str | None):
self._tAttr["font-name"][1] = str(theValue) self._tAttr["font-name"][1] = value
return return
def setFontFamily(self, theValue): def setFontFamily(self, value: str | None):
self._tAttr["font-family"][1] = str(theValue) self._tAttr["font-family"][1] = value
return return
def setFontSize(self, theValue): def setFontSize(self, value: str | None):
self._tAttr["font-size"][1] = str(theValue) self._tAttr["font-size"][1] = value
return return
def setFontWeight(self, theValue): def setFontWeight(self, value: str | None):
if theValue in self.VALID_WEIGHT: if value in self.VALID_WEIGHT:
self._tAttr["font-weight"][1] = str(theValue) self._tAttr["font-weight"][1] = value
else: else:
self._tAttr["font-weight"][1] = None self._tAttr["font-weight"][1] = None
return return
def setColor(self, theValue): def setColor(self, value: str | None):
self._tAttr["color"][1] = str(theValue) self._tAttr["color"][1] = value
return return
def setOpacity(self, theValue): def setOpacity(self, value: str | None):
self._tAttr["opacity"][1] = str(theValue) self._tAttr["opacity"][1] = value
return return
## ##
# Methods # Methods
## ##
def checkNew(self, refStyle): def checkNew(self, refStyle: ODTParagraphStyle):
"""Check if there are new settings in refStyle that differ from """Check if there are new settings in refStyle that differ from
those in the current object. those in the current object.
""" """
@@ -1183,9 +1168,8 @@ class ODTParagraphStyle:
return True return True
return False return False
def getID(self): def getID(self) -> str:
"""Generate a unique ID from the settings. """Generate a unique ID from the settings."""
"""
theString = ( theString = (
f"Paragraph:Main:{str(self._mAttr)}:" f"Paragraph:Main:{str(self._mAttr)}:"
f"Paragraph:Para:{str(self._pAttr)}:" f"Paragraph:Para:{str(self._pAttr)}:"
@@ -1193,11 +1177,10 @@ class ODTParagraphStyle:
) )
return sha256(theString.encode()).hexdigest() return sha256(theString.encode()).hexdigest()
def packXML(self, xParent, xName): def packXML(self, xParent: ET.Element, name: str):
"""Pack the content into an xml element. """Pack the content into an xml element."""
"""
theAttr = {} theAttr = {}
theAttr[_mkTag("style", "name")] = xName theAttr[_mkTag("style", "name")] = name
theAttr[_mkTag("style", "family")] = "paragraph" theAttr[_mkTag("style", "family")] = "paragraph"
for aName, (aNm, aVal) in self._mAttr.items(): for aName, (aNm, aVal) in self._mAttr.items():
if aVal is not None: if aVal is not None:
@@ -1252,30 +1235,30 @@ class ODTTextStyle:
# Setters # Setters
## ##
def setFontWeight(self, theValue): def setFontWeight(self, value: str | None):
if theValue in self.VALID_WEIGHT: if value in self.VALID_WEIGHT:
self._tAttr["font-weight"][1] = str(theValue) self._tAttr["font-weight"][1] = value
else: else:
self._tAttr["font-weight"][1] = None self._tAttr["font-weight"][1] = None
return return
def setFontStyle(self, theValue): def setFontStyle(self, value: str | None):
if theValue in self.VALID_STYLE: if value in self.VALID_STYLE:
self._tAttr["font-style"][1] = str(theValue) self._tAttr["font-style"][1] = value
else: else:
self._tAttr["font-style"][1] = None self._tAttr["font-style"][1] = None
return return
def setStrikeStyle(self, theValue): def setStrikeStyle(self, value: str | None):
if theValue in self.VALID_LSTYLE: if value in self.VALID_LSTYLE:
self._tAttr["text-line-through-style"][1] = str(theValue) self._tAttr["text-line-through-style"][1] = value
else: else:
self._tAttr["text-line-through-style"][1] = None self._tAttr["text-line-through-style"][1] = None
return return
def setStrikeType(self, theValue): def setStrikeType(self, value: str | None):
if theValue in self.VALID_LTYPE: if value in self.VALID_LTYPE:
self._tAttr["text-line-through-type"][1] = str(theValue) self._tAttr["text-line-through-type"][1] = value
else: else:
self._tAttr["text-line-through-type"][1] = None self._tAttr["text-line-through-type"][1] = None
return return
@@ -1284,11 +1267,10 @@ class ODTTextStyle:
# Methods # Methods
## ##
def packXML(self, xParent, xName): def packXML(self, xParent: ET.Element, name: str):
"""Pack the content into an xml element. """Pack the content into an xml element."""
"""
theAttr = {} theAttr = {}
theAttr[_mkTag("style", "name")] = xName theAttr[_mkTag("style", "name")] = name
theAttr[_mkTag("style", "family")] = "text" theAttr[_mkTag("style", "family")] = "text"
xEntry = ET.SubElement(xParent, _mkTag("style", "style"), attrib=theAttr) xEntry = ET.SubElement(xParent, _mkTag("style", "style"), attrib=theAttr)
@@ -1335,7 +1317,7 @@ class XMLParagraph:
object and attribute is written to, object and attribute is written to,
""" """
def __init__(self, xRoot): def __init__(self, xRoot: ET.Element):
self._xRoot = xRoot self._xRoot = xRoot
self._xTail = ET.Element("") self._xTail = ET.Element("")
@@ -1348,17 +1330,17 @@ class XMLParagraph:
return return
def appendText(self, text): def appendText(self, tText: str):
"""Append text to the XML element. We do this one character at """Append text to the XML element. We do this one character at
the time in order to be able to process line breaks, tabs and the time in order to be able to process line breaks, tabs and
spaces separately. Multiple spaces above one are concatenated spaces separately. Multiple spaces are concatenated into a
into a single tag, and must therefore be processed separately. single tag, and must therefore be processed separately.
""" """
text = stripEscape(text) tText = stripEscape(tText)
nSpaces = 0 nSpaces = 0
self._rawTxt += text self._rawTxt += tText
for c in text: for c in tText:
if c == " ": if c == " ":
nSpaces += 1 nSpaces += 1
continue continue
@@ -1395,38 +1377,36 @@ class XMLParagraph:
else: else:
if self._nState == X_ROOT_TEXT: if self._nState == X_ROOT_TEXT:
self._xRoot.text += c self._xRoot.text = (self._xRoot.text or "") + c
self._chrPos += 1 self._chrPos += 1
elif self._nState == X_ROOT_TAIL: elif self._nState == X_ROOT_TAIL:
self._xTail.tail += c self._xTail.tail = (self._xTail.tail or "") + c
self._chrPos += 1 self._chrPos += 1
elif self._nState == X_SPAN_TEXT: elif self._nState == X_SPAN_TEXT:
self._xTail.text += c self._xTail.text = (self._xTail.text or "") + c
self._chrPos += 1 self._chrPos += 1
elif self._nState == X_SPAN_SING: elif self._nState == X_SPAN_SING:
self._xSing.tail += c self._xSing.tail = (self._xSing.tail or "") + c
self._chrPos += 1 self._chrPos += 1
if nSpaces > 0: if nSpaces > 0:
# Handle trailing spaces
self._processSpaces(nSpaces) self._processSpaces(nSpaces)
return return
def appendSpan(self, tText, tFmt): def appendSpan(self, tText: str, tFmt: str):
"""Append a text span to the XML element. The span is always """Append a text span to the XML element. The span is always
closed since we do not allow nested spans (like Libre Office). closed since we do not allow nested spans (like Libre Office).
Therefore we return to the root element level when we're done Therefore we return to the root element level when we're done
processing the text of the span. processing the text of the span.
""" """
self._xTail = ET.SubElement(self._xRoot, TAG_SPAN, attrib={ self._xTail = ET.SubElement(self._xRoot, TAG_SPAN, attrib={TAG_STNM: tFmt})
TAG_STNM: tFmt
})
self._xTail.text = "" # Defaults to None self._xTail.text = "" # Defaults to None
self._xTail.tail = "" # Defaults to None self._xTail.tail = "" # Defaults to None
self._nState = X_SPAN_TEXT self._nState = X_SPAN_TEXT
self.appendText(tText) self.appendText(tText)
self._nState = X_ROOT_TAIL self._nState = X_ROOT_TAIL
return return
def checkError(self): def checkError(self):
@@ -1443,7 +1423,7 @@ class XMLParagraph:
# Internal Functions # Internal Functions
## ##
def _processSpaces(self, nSpaces): def _processSpaces(self, nSpaces: int):
"""Add spaces to paragraph. The first space is always written """Add spaces to paragraph. The first space is always written
as-is (unless it's the first character of the paragraph). The as-is (unless it's the first character of the paragraph). The
second space uses the dedicated tag for spaces, and from the second space uses the dedicated tag for spaces, and from the
@@ -1455,16 +1435,16 @@ class XMLParagraph:
if nSpaces > 0: if nSpaces > 0:
if self._chrPos > 0: if self._chrPos > 0:
if self._nState == X_ROOT_TEXT: if self._nState == X_ROOT_TEXT:
self._xRoot.text += " " self._xRoot.text = (self._xRoot.text or "") + " "
self._chrPos += 1 self._chrPos += 1
elif self._nState == X_ROOT_TAIL: elif self._nState == X_ROOT_TAIL:
self._xTail.tail += " " self._xTail.tail = (self._xTail.tail or "") + " "
self._chrPos += 1 self._chrPos += 1
elif self._nState == X_SPAN_TEXT: elif self._nState == X_SPAN_TEXT:
self._xTail.text += " " self._xTail.text = (self._xTail.text or "") + " "
self._chrPos += 1 self._chrPos += 1
elif self._nState == X_SPAN_SING: elif self._nState == X_SPAN_SING:
self._xSing.tail += " " self._xSing.tail = (self._xSing.tail or "") + " "
self._chrPos += 1 self._chrPos += 1
else: else:
nSpaces += 1 nSpaces += 1
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<office:document-styles xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" office:version="1.3"> <office:document-styles xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" office:version="1.3">
<office:font-face-decls> <office:font-face-decls>
<style:font-face style:name="Liberation Serif" style:font-pitch="variable" /> <style:font-face style:name="Liberation Serif" style:font-pitch="variable" />
</office:font-face-decls> </office:font-face-decls>
@@ -22,7 +22,7 @@
</style:style> </style:style>
<style:style style:name="Text_20_Meta" style:family="paragraph" style:display-name="Text Meta" style:parent-style-name="Standard" style:class="text"> <style:style style:name="Text_20_Meta" style:family="paragraph" style:display-name="Text Meta" style:parent-style-name="Standard" style:class="text">
<style:paragraph-properties fo:margin-top="0.000cm" fo:margin-bottom="0.247cm" fo:line-height="115%" /> <style:paragraph-properties fo:margin-top="0.000cm" fo:margin-bottom="0.247cm" fo:line-height="115%" />
<style:text-properties style:font-name="Liberation Serif" fo:font-family="'Liberation Serif'" fo:font-size="12pt" fo:color="None" loext:opacity="None" /> <style:text-properties style:font-name="Liberation Serif" fo:font-family="'Liberation Serif'" fo:font-size="12pt" />
</style:style> </style:style>
<style:style style:name="Title" style:family="paragraph" style:display-name="Title" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="chapter"> <style:style style:name="Title" style:family="paragraph" style:display-name="Title" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="chapter">
<style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" fo:text-align="center" /> <style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" fo:text-align="center" />
@@ -30,19 +30,19 @@
</style:style> </style:style>
<style:style style:name="Heading_20_1" style:family="paragraph" style:display-name="Heading 1" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="1" style:class="text"> <style:style style:name="Heading_20_1" style:family="paragraph" style:display-name="Heading 1" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="1" style:class="text">
<style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" /> <style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" />
<style:text-properties style:font-name="Liberation Serif" fo:font-family="'Liberation Serif'" fo:font-size="24pt" fo:font-weight="bold" fo:color="None" loext:opacity="None" /> <style:text-properties style:font-name="Liberation Serif" fo:font-family="'Liberation Serif'" fo:font-size="24pt" fo:font-weight="bold" />
</style:style> </style:style>
<style:style style:name="Heading_20_2" style:family="paragraph" style:display-name="Heading 2" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="2" style:class="text"> <style:style style:name="Heading_20_2" style:family="paragraph" style:display-name="Heading 2" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="2" style:class="text">
<style:paragraph-properties fo:margin-top="0.353cm" fo:margin-bottom="0.212cm" /> <style:paragraph-properties fo:margin-top="0.353cm" fo:margin-bottom="0.212cm" />
<style:text-properties style:font-name="Liberation Serif" fo:font-family="'Liberation Serif'" fo:font-size="19pt" fo:font-weight="bold" fo:color="None" loext:opacity="None" /> <style:text-properties style:font-name="Liberation Serif" fo:font-family="'Liberation Serif'" fo:font-size="19pt" fo:font-weight="bold" />
</style:style> </style:style>
<style:style style:name="Heading_20_3" style:family="paragraph" style:display-name="Heading 3" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="3" style:class="text"> <style:style style:name="Heading_20_3" style:family="paragraph" style:display-name="Heading 3" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="3" style:class="text">
<style:paragraph-properties fo:margin-top="0.247cm" fo:margin-bottom="0.212cm" /> <style:paragraph-properties fo:margin-top="0.247cm" fo:margin-bottom="0.212cm" />
<style:text-properties style:font-name="Liberation Serif" fo:font-family="'Liberation Serif'" fo:font-size="16pt" fo:font-weight="bold" fo:color="None" loext:opacity="None" /> <style:text-properties style:font-name="Liberation Serif" fo:font-family="'Liberation Serif'" fo:font-size="16pt" fo:font-weight="bold" />
</style:style> </style:style>
<style:style style:name="Heading_20_4" style:family="paragraph" style:display-name="Heading 4" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="4" style:class="text"> <style:style style:name="Heading_20_4" style:family="paragraph" style:display-name="Heading 4" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="4" style:class="text">
<style:paragraph-properties fo:margin-top="0.247cm" fo:margin-bottom="0.212cm" /> <style:paragraph-properties fo:margin-top="0.247cm" fo:margin-bottom="0.212cm" />
<style:text-properties style:font-name="Liberation Serif" fo:font-family="'Liberation Serif'" fo:font-size="14pt" fo:font-weight="bold" fo:color="None" loext:opacity="None" /> <style:text-properties style:font-name="Liberation Serif" fo:font-family="'Liberation Serif'" fo:font-size="14pt" fo:font-weight="bold" />
</style:style> </style:style>
<style:style style:name="Header" style:family="paragraph" style:display-name="Header" style:parent-style-name="Header_20_and_20_Footer"> <style:style style:name="Header" style:family="paragraph" style:display-name="Header" style:parent-style-name="Header_20_and_20_Footer">
<style:paragraph-properties fo:text-align="right" /> <style:paragraph-properties fo:text-align="right" />
+45 -53
View File
@@ -43,8 +43,7 @@ XML_NS = [
def xmlToText(xElem): def xmlToText(xElem):
"""Get the text content of an XML element. """Get the text content of an XML element."""
"""
rTxt = ET.tostring(xElem, encoding="utf-8", xml_declaration=False).decode() rTxt = ET.tostring(xElem, encoding="utf-8", xml_declaration=False).decode()
for nSpace in XML_NS: for nSpace in XML_NS:
rTxt = rTxt.replace(nSpace, "") rTxt = rTxt.replace(nSpace, "")
@@ -53,8 +52,7 @@ def xmlToText(xElem):
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_Init(mockGUI): def testCoreToOdt_Init(mockGUI):
"""Test initialisation of the ODT document. """Test initialisation of the ODT document."""
"""
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
# Flat Doc # Flat Doc
@@ -109,8 +107,7 @@ def testCoreToOdt_Init(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_TextFormatting(mockGUI): def testCoreToOdt_TextFormatting(mockGUI):
"""Test formatting of paragraphs. """Test formatting of paragraphs."""
"""
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theDoc = ToOdt(theProject, isFlat=True) theDoc = ToOdt(theProject, isFlat=True)
@@ -156,7 +153,7 @@ def testCoreToOdt_TextFormatting(mockGUI):
# No Format # No Format
theDoc.initDocument() theDoc.initDocument()
theDoc._addTextPar("Standard", oStyle, "Hello World") theDoc._addTextPar("Standard", oStyle, "Hello World")
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
"<office:text>" "<office:text>"
"<text:p text:style-name=\"Standard\">Hello World</text:p>" "<text:p text:style-name=\"Standard\">Hello World</text:p>"
@@ -166,7 +163,7 @@ def testCoreToOdt_TextFormatting(mockGUI):
# Heading Level None # Heading Level None
theDoc.initDocument() theDoc.initDocument()
theDoc._addTextPar("Standard", oStyle, "Hello World", isHead=True) theDoc._addTextPar("Standard", oStyle, "Hello World", isHead=True)
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
"<office:text>" "<office:text>"
"<text:h text:style-name=\"Standard\">Hello World</text:h>" "<text:h text:style-name=\"Standard\">Hello World</text:h>"
@@ -176,7 +173,7 @@ def testCoreToOdt_TextFormatting(mockGUI):
# Heading Level 1 # Heading Level 1
theDoc.initDocument() theDoc.initDocument()
theDoc._addTextPar("Standard", oStyle, "Hello World", isHead=True, oLevel="1") theDoc._addTextPar("Standard", oStyle, "Hello World", isHead=True, oLevel="1")
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
"<office:text>" "<office:text>"
"<text:h text:style-name=\"Standard\" text:outline-level=\"1\">Hello World</text:h>" "<text:h text:style-name=\"Standard\" text:outline-level=\"1\">Hello World</text:h>"
@@ -187,8 +184,8 @@ def testCoreToOdt_TextFormatting(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theTxt = "A **few** _words_ from ~~our~~ sponsor" theTxt = "A **few** _words_ from ~~our~~ sponsor"
theFmt = " _B b_ I i _S s_ " theFmt = " _B b_ I i _S s_ "
theDoc._addTextPar("Standard", oStyle, theTxt, theFmt=theFmt) theDoc._addTextPar("Standard", oStyle, theTxt, tFmt=theFmt)
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
"<office:text>" "<office:text>"
"<text:p text:style-name=\"Standard\">A <text:span text:style-name=\"T1\">few</text:span> " "<text:p text:style-name=\"Standard\">A <text:span text:style-name=\"T1\">few</text:span> "
@@ -201,8 +198,8 @@ def testCoreToOdt_TextFormatting(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theTxt = "A **few** _wordsXXX" theTxt = "A **few** _wordsXXX"
theFmt = " _b b_ I XXX" theFmt = " _b b_ I XXX"
theDoc._addTextPar("Standard", oStyle, theTxt, theFmt=theFmt) theDoc._addTextPar("Standard", oStyle, theTxt, tFmt=theFmt)
assert theDoc.getErrors() == ["Unknown format tag encountered"] assert theDoc.errData == ["Unknown format tag encountered"]
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
"<office:text>" "<office:text>"
"<text:p text:style-name=\"Standard\">" "<text:p text:style-name=\"Standard\">"
@@ -215,8 +212,8 @@ def testCoreToOdt_TextFormatting(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theTxt = "Hello\n\tWorld" theTxt = "Hello\n\tWorld"
theFmt = " " theFmt = " "
theDoc._addTextPar("Standard", oStyle, theTxt, theFmt=theFmt) theDoc._addTextPar("Standard", oStyle, theTxt, tFmt=theFmt)
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
"<office:text>" "<office:text>"
"<text:p text:style-name=\"Standard\">Hello<text:line-break /><text:tab />World</text:p>" "<text:p text:style-name=\"Standard\">Hello<text:line-break /><text:tab />World</text:p>"
@@ -230,8 +227,8 @@ def testCoreToOdt_TextFormatting(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theTxt = "Test text \\**_bold_** and more." theTxt = "Test text \\**_bold_** and more."
theFmt = " I i " theFmt = " I i "
theDoc._addTextPar("Standard", oStyle, theTxt, theFmt=theFmt) theDoc._addTextPar("Standard", oStyle, theTxt, tFmt=theFmt)
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
"<office:text>" "<office:text>"
"<text:p text:style-name=\"Standard\">Test text **<text:span text:style-name=\"T2\">" "<text:p text:style-name=\"Standard\">Test text **<text:span text:style-name=\"T2\">"
@@ -244,8 +241,7 @@ def testCoreToOdt_TextFormatting(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_Convert(mockGUI): def testCoreToOdt_Convert(mockGUI):
"""Test the converter of the ToOdt class. """Test the converter of the ToOdt class."""
"""
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theDoc = ToOdt(theProject, isFlat=True) theDoc = ToOdt(theProject, isFlat=True)
@@ -266,7 +262,7 @@ def testCoreToOdt_Convert(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theDoc.doConvert() theDoc.doConvert()
theDoc.closeDocument() theDoc.closeDocument()
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
'<office:text>' '<office:text>'
'<text:h text:style-name="P1" text:outline-level="1">Title</text:h>' '<text:h text:style-name="P1" text:outline-level="1">Title</text:h>'
@@ -279,7 +275,7 @@ def testCoreToOdt_Convert(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theDoc.doConvert() theDoc.doConvert()
theDoc.closeDocument() theDoc.closeDocument()
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
'<office:text>' '<office:text>'
'<text:h text:style-name="P2" text:outline-level="2">Chapter</text:h>' '<text:h text:style-name="P2" text:outline-level="2">Chapter</text:h>'
@@ -292,7 +288,7 @@ def testCoreToOdt_Convert(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theDoc.doConvert() theDoc.doConvert()
theDoc.closeDocument() theDoc.closeDocument()
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
'<office:text>' '<office:text>'
'<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>' '<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>'
@@ -305,7 +301,7 @@ def testCoreToOdt_Convert(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theDoc.doConvert() theDoc.doConvert()
theDoc.closeDocument() theDoc.closeDocument()
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
'<office:text>' '<office:text>'
'<text:h text:style-name="Heading_20_4" text:outline-level="4">Section</text:h>' '<text:h text:style-name="Heading_20_4" text:outline-level="4">Section</text:h>'
@@ -318,7 +314,7 @@ def testCoreToOdt_Convert(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theDoc.doConvert() theDoc.doConvert()
theDoc.closeDocument() theDoc.closeDocument()
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
'<office:text>' '<office:text>'
'<text:p text:style-name="Title">Title</text:p>' '<text:p text:style-name="Title">Title</text:p>'
@@ -331,7 +327,7 @@ def testCoreToOdt_Convert(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theDoc.doConvert() theDoc.doConvert()
theDoc.closeDocument() theDoc.closeDocument()
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
'<office:text>' '<office:text>'
'<text:h text:style-name="P2" text:outline-level="2">Prologue</text:h>' '<text:h text:style-name="P2" text:outline-level="2">Prologue</text:h>'
@@ -347,7 +343,7 @@ def testCoreToOdt_Convert(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theDoc.doConvert() theDoc.doConvert()
theDoc.closeDocument() theDoc.closeDocument()
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
'<office:text>' '<office:text>'
'<text:p text:style-name="Text_20_body">Some ' '<text:p text:style-name="Text_20_body">Some '
@@ -365,7 +361,7 @@ def testCoreToOdt_Convert(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theDoc.doConvert() theDoc.doConvert()
theDoc.closeDocument() theDoc.closeDocument()
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
'<office:text>' '<office:text>'
'<text:p text:style-name="Text_20_body">Some text.<text:line-break />Next line</text:p>' '<text:p text:style-name="Text_20_body">Some text.<text:line-break />Next line</text:p>'
@@ -378,7 +374,7 @@ def testCoreToOdt_Convert(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theDoc.doConvert() theDoc.doConvert()
theDoc.closeDocument() theDoc.closeDocument()
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
'<office:text>' '<office:text>'
'<text:p text:style-name="Text_20_body"><text:tab />Item 1<text:tab />Item 2</text:p>' '<text:p text:style-name="Text_20_body"><text:tab />Item 1<text:tab />Item 2</text:p>'
@@ -391,7 +387,7 @@ def testCoreToOdt_Convert(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theDoc.doConvert() theDoc.doConvert()
theDoc.closeDocument() theDoc.closeDocument()
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
'<office:text>' '<office:text>'
'<text:p text:style-name="Text_20_body">Some <text:span text:style-name="T4">' '<text:p text:style-name="Text_20_body">Some <text:span text:style-name="T4">'
@@ -410,7 +406,7 @@ def testCoreToOdt_Convert(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theDoc.doConvert() theDoc.doConvert()
theDoc.closeDocument() theDoc.closeDocument()
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
'<office:text>' '<office:text>'
'<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>' '<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>'
@@ -434,7 +430,7 @@ def testCoreToOdt_Convert(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theDoc.doConvert() theDoc.doConvert()
theDoc.closeDocument() theDoc.closeDocument()
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
'<office:text>' '<office:text>'
'<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>' '<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>'
@@ -455,7 +451,7 @@ def testCoreToOdt_Convert(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theDoc.doConvert() theDoc.doConvert()
theDoc.closeDocument() theDoc.closeDocument()
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
'<office:text>' '<office:text>'
'<text:p text:style-name="P3">* * *</text:p>' '<text:p text:style-name="P3">* * *</text:p>'
@@ -473,7 +469,7 @@ def testCoreToOdt_Convert(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theDoc.doConvert() theDoc.doConvert()
theDoc.closeDocument() theDoc.closeDocument()
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
'<office:text>' '<office:text>'
'<text:p text:style-name="Text_20_body" />' '<text:p text:style-name="Text_20_body" />'
@@ -500,7 +496,7 @@ def testCoreToOdt_Convert(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theDoc.doConvert() theDoc.doConvert()
theDoc.closeDocument() theDoc.closeDocument()
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
'<office:text>' '<office:text>'
'<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>' '<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>'
@@ -537,7 +533,7 @@ def testCoreToOdt_Convert(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theDoc.doConvert() theDoc.doConvert()
theDoc.closeDocument() theDoc.closeDocument()
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
'<office:text>' '<office:text>'
'<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>' '<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>'
@@ -559,7 +555,7 @@ def testCoreToOdt_Convert(mockGUI):
theDoc.initDocument() theDoc.initDocument()
theDoc.doConvert() theDoc.doConvert()
theDoc.closeDocument() theDoc.closeDocument()
assert theDoc.getErrors() == [] assert theDoc.errData == []
assert xmlToText(theDoc._xText) == ( assert xmlToText(theDoc._xText) == (
'<office:text>' '<office:text>'
'<text:h text:style-name="P2" text:outline-level="2">Chapter One</text:h>' '<text:h text:style-name="P2" text:outline-level="2">Chapter One</text:h>'
@@ -629,8 +625,7 @@ def testCoreToOdt_ConvertDirect(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths): def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths):
"""Test the document save functions. """Test the document save functions."""
"""
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theProject.data.setAuthor("Jane Smith") theProject.data.setAuthor("Jane Smith")
theProject.data.setName("Test Project") theProject.data.setName("Test Project")
@@ -639,8 +634,11 @@ def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths):
theDoc = ToOdt(theProject, isFlat=True) theDoc = ToOdt(theProject, isFlat=True)
theDoc._isNovel = True theDoc._isNovel = True
assert theDoc.setLanguage(None) is False theDoc._dLanguage = ""
assert theDoc.setLanguage("nb_NO") is True theDoc.setLanguage(None)
assert theDoc._dLanguage == ""
theDoc.setLanguage("nb_NO")
assert theDoc._dLanguage == "nb"
theDoc.setColourHeaders(True) theDoc.setColourHeaders(True)
theDoc._text = ( theDoc._text = (
@@ -669,8 +667,7 @@ def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths):
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths): def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths):
"""Test the document save functions. """Test the document save functions."""
"""
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theProject.data.setAuthor("Jane Smith") theProject.data.setAuthor("Jane Smith")
theProject.data.setName("Test Project") theProject.data.setName("Test Project")
@@ -747,8 +744,7 @@ def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths):
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_Format(mockGUI): def testCoreToOdt_Format(mockGUI):
"""Test the formatters for the ToOdt class. """Test the formatters for the ToOdt class."""
"""
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theDoc = ToOdt(theProject, isFlat=True) theDoc = ToOdt(theProject, isFlat=True)
@@ -761,7 +757,7 @@ def testCoreToOdt_Format(mockGUI):
"_B b_ " "_B b_ "
) )
assert theDoc._formatKeywords("") == "" assert theDoc._formatKeywords("") == ("", "")
assert theDoc._formatKeywords("tag: Jane") == ( assert theDoc._formatKeywords("tag: Jane") == (
"**Tag:** Jane", "**Tag:** Jane",
"_B b_ " "_B b_ "
@@ -776,8 +772,7 @@ def testCoreToOdt_Format(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_ODTParagraphStyle(): def testCoreToOdt_ODTParagraphStyle():
"""Test the ODTParagraphStyle class. """Test the ODTParagraphStyle class."""
"""
parStyle = ODTParagraphStyle() parStyle = ODTParagraphStyle()
# Set Attributes # Set Attributes
@@ -992,8 +987,7 @@ def testCoreToOdt_ODTParagraphStyle():
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_ODTTextStyle(): def testCoreToOdt_ODTTextStyle():
"""Test the ODTTextStyle class. """Test the ODTTextStyle class."""
"""
txtStyle = ODTTextStyle() txtStyle = ODTTextStyle()
# Font Weight # Font Weight
@@ -1065,8 +1059,7 @@ def testCoreToOdt_ODTTextStyle():
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_XMLParagraph(): def testCoreToOdt_XMLParagraph():
"""Test XML encoding of paragraph. """Test XML encoding of paragraph."""
"""
# Stage 1 : Text # Stage 1 : Text
# ============== # ==============
@@ -1257,8 +1250,7 @@ def testCoreToOdt_XMLParagraph():
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_MkTag(): def testCoreToOdt_MkTag():
"""Test the tag maker function. """Test the tag maker function."""
"""
assert _mkTag("office", "text") == "{urn:oasis:names:tc:opendocument:xmlns:office:1.0}text" assert _mkTag("office", "text") == "{urn:oasis:names:tc:opendocument:xmlns:office:1.0}text"
assert _mkTag("style", "text") == "{urn:oasis:names:tc:opendocument:xmlns:style:1.0}text" assert _mkTag("style", "text") == "{urn:oasis:names:tc:opendocument:xmlns:style:1.0}text"
assert _mkTag("blabla", "text") == "text" assert _mkTag("blabla", "text") == "text"