Add code for writing ODT files

This commit is contained in:
Veronica K. B. Olsen
2021-01-28 00:21:39 +01:00
parent ac0847ffe3
commit ecd858251f
3 changed files with 183 additions and 42 deletions
+170 -36
View File
@@ -30,6 +30,7 @@ import logging
from lxml import etree from lxml import etree
from hashlib import sha256 from hashlib import sha256
from datetime import datetime from datetime import datetime
from zipfile import ZipFile
from nw.core.tokenizer import Tokenizer from nw.core.tokenizer import Tokenizer
from nw.constants import nwLabels, nwKeyWords from nw.constants import nwLabels, nwKeyWords
@@ -45,6 +46,9 @@ XML_NS = {
"fo" : "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0", "fo" : "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
} }
X_MIME = "application/vnd.oasis.opendocument.text"
X_VERS = "1.2"
TAG_BR = "{%s}line-break" % XML_NS["text"] TAG_BR = "{%s}line-break" % XML_NS["text"]
TAG_TAB = "{%s}tab" % XML_NS["text"] TAG_TAB = "{%s}tab" % XML_NS["text"]
TAG_SPAN = "{%s}span" % XML_NS["text"] TAG_SPAN = "{%s}span" % XML_NS["text"]
@@ -58,16 +62,23 @@ class ToOdt(Tokenizer):
X_BRK = 0x08 X_BRK = 0x08
X_TAB = 0x10 X_TAB = 0x10
def __init__(self, theProject, theParent): def __init__(self, theProject, theParent, isFlat):
Tokenizer.__init__(self, theProject, theParent) Tokenizer.__init__(self, theProject, theParent)
self.mainConf = nw.CONFIG self.mainConf = nw.CONFIG
self._xRoot = None self._isFlat = isFlat
self._dFlat = None
self._dCont = None
self._dMeta = None
self._dStyl = None
self._xMeta = None
self._xStyl = None self._xStyl = None
self._xAuto = None
self._xBody = None self._xBody = None
self._xText = None self._xText = None
self._xAuto = None
self._mainPara = {} self._mainPara = {}
self._autoPara = {} self._autoPara = {}
@@ -149,25 +160,9 @@ class ToOdt(Tokenizer):
def initDocument(self): def initDocument(self):
"""Initialises a new open document XML tree. """Initialises a new open document XML tree.
""" """
rAttr = { # Initialise Variables
_mkTag("office", "version") : "1.3", # ====================
_mkTag("office", "mimetype") : "application/vnd.oasis.opendocument.text",
}
self._xRoot = etree.Element(_mkTag("office", "document"), attrib=rAttr, nsmap=XML_NS)
self._xMeta = etree.SubElement(self._xRoot, _mkTag("office", "meta"))
self._xFont = etree.SubElement(self._xRoot, _mkTag("office", "font-face-decls"))
self._xStyl = etree.SubElement(self._xRoot, _mkTag("office", "styles"))
self._xAuto = etree.SubElement(self._xRoot, _mkTag("office", "automatic-styles"))
self._xBody = etree.SubElement(self._xRoot, _mkTag("office", "body"))
self._xText = etree.SubElement(self._xBody, _mkTag("office", "text"))
# Meta Data
xMeta = etree.SubElement(self._xMeta, _mkTag("meta", "creation-date"))
xMeta.text = datetime.now().isoformat()
xMeta = etree.SubElement(self._xMeta, _mkTag("meta", "generator"))
xMeta.text = f"novelWriter/{nw.__version__}"
# Re-Init Variables
self._fontFamily = self.textFont self._fontFamily = self.textFont
if len(self.textFont.split()) > 1: if len(self.textFont.split()) > 1:
self._fontFamily = f"'{self.textFont}'" self._fontFamily = f"'{self.textFont}'"
@@ -208,7 +203,69 @@ class ToOdt(Tokenizer):
self._lineHeight = f"{round(100 * self.lineHeight):d}%" self._lineHeight = f"{round(100 * self.lineHeight):d}%"
self._textAlign = "justify" if self.doJustify else "left" self._textAlign = "justify" if self.doJustify else "left"
# Add Styles # Create Roots
# ============
tAttr = {}
tAttr[_mkTag("office", "version")] = X_VERS
fAttr = {}
fAttr[_mkTag("style", "name")] = self.textFont
fAttr[_mkTag("style", "font-pitch")] = self._fontPitch
if self._isFlat:
# FODT File
# =========
tAttr[_mkTag("office", "mimetype")] = X_MIME
tFlat = _mkTag("office", "document")
self._dFlat = etree.Element(tFlat, attrib=tAttr, nsmap=XML_NS)
self._xMeta = etree.SubElement(self._dFlat, _mkTag("office", "meta"))
self._xFont = etree.SubElement(self._dFlat, _mkTag("office", "font-face-decls"))
self._xStyl = etree.SubElement(self._dFlat, _mkTag("office", "styles"))
self._xAuto = etree.SubElement(self._dFlat, _mkTag("office", "automatic-styles"))
self._xBody = etree.SubElement(self._dFlat, _mkTag("office", "body"))
etree.SubElement(self._xFont, _mkTag("style", "font-face"), attrib=fAttr)
else:
# ODT File
# ========
tCont = _mkTag("office", "document-content")
tMeta = _mkTag("office", "document-meta")
tStyl = _mkTag("office", "document-styles")
self._dCont = etree.Element(tCont, attrib=tAttr, nsmap=XML_NS)
self._xFnt1 = etree.SubElement(self._dCont, _mkTag("office", "font-face-decls"))
self._xAuto = etree.SubElement(self._dCont, _mkTag("office", "automatic-styles"))
self._xBody = etree.SubElement(self._dCont, _mkTag("office", "body"))
self._dMeta = etree.Element(tMeta, attrib=tAttr, nsmap=XML_NS)
self._xMeta = etree.SubElement(self._dMeta, _mkTag("office", "meta"))
self._dStyl = etree.Element(tStyl, attrib=tAttr, nsmap=XML_NS)
self._xFnt2 = etree.SubElement(self._dCont, _mkTag("office", "font-face-decls"))
self._xStyl = etree.SubElement(self._dStyl, _mkTag("office", "styles"))
etree.SubElement(self._xFnt1, _mkTag("style", "font-face"), attrib=fAttr)
etree.SubElement(self._xFnt2, _mkTag("style", "font-face"), attrib=fAttr)
# Finalise
# ========
self._xText = etree.SubElement(self._xBody, _mkTag("office", "text"))
# Meta Data
xMeta = etree.SubElement(self._xMeta, _mkTag("meta", "creation-date"))
xMeta.text = datetime.now().isoformat()
xMeta = etree.SubElement(self._xMeta, _mkTag("meta", "generator"))
xMeta.text = f"novelWriter/{nw.__version__}"
self._defaultStyles() self._defaultStyles()
self._useableStyles() self._useableStyles()
@@ -341,12 +398,97 @@ class ToOdt(Tokenizer):
for styleName, styleObj in self._autoText.values(): for styleName, styleObj in self._autoText.values():
styleObj.packXML(self._xAuto, styleName) styleObj.packXML(self._xAuto, styleName)
self.theResult = etree.tostring( return
self._xRoot,
pretty_print = True, def saveFlatXML(self, savePath):
encoding = "utf-8", """Save the data to an .fodt file.
xml_declaration = True """
) with open(savePath, mode="wb") as outFile:
outFile.write(etree.tostring(
self._dFlat,
pretty_print = True,
encoding = "utf-8",
xml_declaration = True
))
return
def saveOpenDocText(self, savePath):
"""Save the data to an .odt file.
"""
rdfMap = {"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#"}
ns0Map = {"ns0": "http://docs.oasis-open.org/ns/office/1.2/meta/pkg#"}
rdfRDF = "{%s}RDF" % rdfMap["rdf"]
rdfDesc = "{%s}Description" % rdfMap["rdf"]
rdfAbout = "{%s}about" % rdfMap["rdf"]
rdfType = "{%s}type" % rdfMap["rdf"]
rdfRes = "{%s}resource" % rdfMap["rdf"]
ns0Part = "{%s}hasPart" % ns0Map["ns0"]
stylRes = "http://docs.oasis-open.org/ns/office/1.2/meta/odf#StylesFile"
contRes = "http://docs.oasis-open.org/ns/office/1.2/meta/odf#ContentFile"
docuRes = "http://docs.oasis-open.org/ns/office/1.2/meta/pkg#Document"
xMRdf = etree.Element(rdfRDF, nsmap=rdfMap)
xDesc = etree.SubElement(xMRdf, rdfDesc, attrib={rdfAbout: "styles.xml"})
_ = etree.SubElement(xDesc, rdfType, attrib={rdfRes: stylRes})
xDesc = etree.SubElement(xMRdf, rdfDesc, attrib={rdfAbout: ""})
_ = etree.SubElement(xDesc, ns0Part, attrib={rdfRes: "styles.xml"}, nsmap=ns0Map)
xDesc = etree.SubElement(xMRdf, rdfDesc, attrib={rdfAbout: "content.xml"})
_ = etree.SubElement(xDesc, rdfType, attrib={rdfRes: contRes})
xDesc = etree.SubElement(xMRdf, rdfDesc, attrib={rdfAbout: ""})
_ = etree.SubElement(xDesc, ns0Part, attrib={rdfRes: "content.xml"}, nsmap=ns0Map)
xDesc = etree.SubElement(xMRdf, rdfDesc, attrib={rdfAbout: ""})
_ = etree.SubElement(xDesc, rdfType, attrib={rdfRes: docuRes})
manMap = {
"manifest" : "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0",
"loext" : "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0",
}
manMani = "{%s}manifest" % manMap["manifest"]
manVers = "{%s}version" % manMap["manifest"]
manPath = "{%s}full-path" % manMap["manifest"]
manType = "{%s}media-type" % manMap["manifest"]
manFile = "{%s}file-entry" % manMap["manifest"]
typeXML = "text/xml"
typeRDF = "application/rdf+xml"
xMani = etree.Element(manMani, attrib={manVers: X_VERS}, nsmap=manMap)
etree.SubElement(xMani, manFile, attrib={manPath: "/", manVers: X_VERS, manType: X_MIME})
etree.SubElement(xMani, manFile, attrib={manPath: "manifest.rdf", manType: typeRDF})
etree.SubElement(xMani, manFile, attrib={manPath: "settings.xml", manType: typeXML})
etree.SubElement(xMani, manFile, attrib={manPath: "content.xml", manType: typeXML})
etree.SubElement(xMani, manFile, attrib={manPath: "meta.xml", manType: typeXML})
etree.SubElement(xMani, manFile, attrib={manPath: "styles.xml", manType: typeXML})
setMap = {
"office" : "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
"config" : "urn:oasis:names:tc:opendocument:xmlns:config:1.0",
}
offRoot = "{%s}document-settings" % setMap["office"]
offSett = "{%s}settings" % setMap["office"]
xSett = etree.Element(offRoot, nsmap=setMap)
etree.SubElement(xSett, offSett)
with ZipFile(savePath, mode="w") as outFile:
outFile.writestr("mimetype", X_MIME)
outFile.writestr("META-INF/manifest.xml", etree.tostring(
xMani, pretty_print=True, encoding="utf-8", xml_declaration=True
))
outFile.writestr("manifest.rdf", etree.tostring(
xMRdf, pretty_print=True, encoding="utf-8", xml_declaration=True
))
outFile.writestr("settings.xml", etree.tostring(
xSett, pretty_print=True, encoding="utf-8", xml_declaration=True
))
outFile.writestr("content.xml", etree.tostring(
self._dCont, pretty_print=True, encoding="utf-8", xml_declaration=True
))
outFile.writestr("meta.xml", etree.tostring(
self._dMeta, pretty_print=True, encoding="utf-8", xml_declaration=True
))
outFile.writestr("styles.xml", etree.tostring(
self._dStyl, pretty_print=True, encoding="utf-8", xml_declaration=True
))
return return
@@ -541,14 +683,6 @@ class ToOdt(Tokenizer):
def _defaultStyles(self): def _defaultStyles(self):
"""Set the default styles. """Set the default styles.
""" """
# Add Font
# ========
theAttr = {}
theAttr[_mkTag("style", "name")] = self.textFont
theAttr[_mkTag("style", "font-pitch")] = self._fontPitch
xStyl = etree.SubElement(self._xFont, _mkTag("style", "font-face"), attrib=theAttr)
# Add Paragraph Family Style # Add Paragraph Family Style
# ========================== # ==========================
+12 -5
View File
@@ -755,10 +755,9 @@ class GuiBuildNovel(QDialog):
# Create the settings # Create the settings
if theFormat == self.FMT_ODT: if theFormat == self.FMT_ODT:
byteFmt.append("odf")
fileExt = "odt" fileExt = "odt"
textFmt = "Open Document" textFmt = "Open Document"
outTool = "Qt" outTool = "NW_ODT"
elif theFormat == self.FMT_FODT: elif theFormat == self.FMT_FODT:
fileExt = "fodt" fileExt = "fodt"
@@ -911,12 +910,20 @@ class GuiBuildNovel(QDialog):
elif outTool == "NW_ODT": elif outTool == "NW_ODT":
if theFormat == self.FMT_FODT: if theFormat == self.FMT_FODT:
makeOdt = ToOdt(self.theProject, self.theParent) makeOdt = ToOdt(self.theProject, self.theParent, isFlat=True)
self._doBuild(makeOdt) self._doBuild(makeOdt)
try: try:
with open(savePath, mode="wb") as outFile: makeOdt.saveFlatXML(savePath)
outFile.write(makeOdt.theResult) wSuccess = True
except Exception as e:
errMsg = str(e)
elif theFormat == self.FMT_ODT:
makeOdt = ToOdt(self.theProject, self.theParent, isFlat=False)
self._doBuild(makeOdt)
try:
makeOdt.saveOpenDocText(savePath)
wSuccess = True wSuccess = True
except Exception as e: except Exception as e:
+1 -1
View File
@@ -52,7 +52,7 @@ def testCoreToOdt_Convert(tmpConf, dummyGUI):
theProject = NWProject(dummyGUI) theProject = NWProject(dummyGUI)
dummyGUI.theIndex = NWIndex(theProject, dummyGUI) dummyGUI.theIndex = NWIndex(theProject, dummyGUI)
theDoc = ToOdt(theProject, dummyGUI) theDoc = ToOdt(theProject, dummyGUI, isFlat=True)
# Export Mode # Export Mode
# =========== # ===========