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 hashlib import sha256
from datetime import datetime
from zipfile import ZipFile
from nw.core.tokenizer import Tokenizer
from nw.constants import nwLabels, nwKeyWords
@@ -45,6 +46,9 @@ XML_NS = {
"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_TAB = "{%s}tab" % XML_NS["text"]
TAG_SPAN = "{%s}span" % XML_NS["text"]
@@ -58,16 +62,23 @@ class ToOdt(Tokenizer):
X_BRK = 0x08
X_TAB = 0x10
def __init__(self, theProject, theParent):
def __init__(self, theProject, theParent, isFlat):
Tokenizer.__init__(self, theProject, theParent)
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._xAuto = None
self._xBody = None
self._xText = None
self._xAuto = None
self._mainPara = {}
self._autoPara = {}
@@ -149,25 +160,9 @@ class ToOdt(Tokenizer):
def initDocument(self):
"""Initialises a new open document XML tree.
"""
rAttr = {
_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"))
# Initialise Variables
# ====================
# 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
if len(self.textFont.split()) > 1:
self._fontFamily = f"'{self.textFont}'"
@@ -208,7 +203,69 @@ class ToOdt(Tokenizer):
self._lineHeight = f"{round(100 * self.lineHeight):d}%"
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._useableStyles()
@@ -341,12 +398,97 @@ class ToOdt(Tokenizer):
for styleName, styleObj in self._autoText.values():
styleObj.packXML(self._xAuto, styleName)
self.theResult = etree.tostring(
self._xRoot,
pretty_print = True,
encoding = "utf-8",
xml_declaration = True
)
return
def saveFlatXML(self, savePath):
"""Save the data to an .fodt file.
"""
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
@@ -541,14 +683,6 @@ class ToOdt(Tokenizer):
def _defaultStyles(self):
"""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
# ==========================
+12 -5
View File
@@ -755,10 +755,9 @@ class GuiBuildNovel(QDialog):
# Create the settings
if theFormat == self.FMT_ODT:
byteFmt.append("odf")
fileExt = "odt"
textFmt = "Open Document"
outTool = "Qt"
outTool = "NW_ODT"
elif theFormat == self.FMT_FODT:
fileExt = "fodt"
@@ -911,12 +910,20 @@ class GuiBuildNovel(QDialog):
elif outTool == "NW_ODT":
if theFormat == self.FMT_FODT:
makeOdt = ToOdt(self.theProject, self.theParent)
makeOdt = ToOdt(self.theProject, self.theParent, isFlat=True)
self._doBuild(makeOdt)
try:
with open(savePath, mode="wb") as outFile:
outFile.write(makeOdt.theResult)
makeOdt.saveFlatXML(savePath)
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
except Exception as e:
+1 -1
View File
@@ -52,7 +52,7 @@ def testCoreToOdt_Convert(tmpConf, dummyGUI):
theProject = NWProject(dummyGUI)
dummyGUI.theIndex = NWIndex(theProject, dummyGUI)
theDoc = ToOdt(theProject, dummyGUI)
theDoc = ToOdt(theProject, dummyGUI, isFlat=True)
# Export Mode
# ===========