Add code for writing ODT files
This commit is contained in:
+170
-36
@@ -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
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
# ===========
|
# ===========
|
||||||
|
|||||||
Reference in New Issue
Block a user