Merge branch 'dev' into feature/build_gui

This commit is contained in:
Veronica Berglyd Olsen
2023-05-29 22:20:08 +02:00
43 changed files with 805 additions and 814 deletions
-6
View File
@@ -185,12 +185,6 @@ def main(sysArgs=None):
)
errorCode |= 0x10
try:
import lxml # noqa: F401
except ImportError:
errorData.append("Python module 'lxml' is missing")
errorCode |= 0x20
if errorData:
errApp = QApplication([])
errDlg = QErrorMessage()
-1
View File
@@ -47,7 +47,6 @@ more contributions are listed on the project's <a href="https://crowdin.com/proj
<p>
<a href="https://www.qt.io">Qt5</a> by Qt Company<br>
<a href="https://www.riverbankcomputing.com/software/pyqt">PyQt5</a> by Riverbank Computing<br>
<a href="https://lxml.de">lxml</a> by Martijn Faassen<br>
<a href="https://abiword.github.io/enchant">Enchant</a> by Dom Lachowicz<br>
<a href="https://pyenchant.github.io/pyenchant">PyEnchant</a> by Dimitri Merejkowsky
</p>
+42
View File
@@ -23,10 +23,13 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
import json
import uuid
import hashlib
import logging
import xml.etree.ElementTree as ET
from pathlib import Path
from datetime import datetime
@@ -435,6 +438,45 @@ def jsonEncode(data, n=0, nmax=0):
return "".join(buffer)
def xmlIndent(tree: ET.Element | ET.ElementTree):
"""A modified version of the XML indent function in the standard
library. It behaves more closely to how the one from lxml does.
"""
if isinstance(tree, ET.ElementTree):
tree = tree.getroot()
indentations = ["\n"]
def indentChildren(elem, level):
chLevel = level + 1
try:
chIndent = indentations[chLevel]
except IndexError:
chIndent = indentations[level] + " "
indentations.append(chIndent)
if elem.text is None:
elem.text = chIndent
last = None
for child in elem:
if len(child):
indentChildren(child, chLevel)
if child.tail is None:
child.tail = chIndent
last = child
# Dedent the last child
if last is not None:
last.tail = indentations[level]
if len(tree):
indentChildren(tree, 0)
tree.tail = "\n"
return
# =============================================================================================== #
# File and File System Functions
# =============================================================================================== #
-1
View File
@@ -196,7 +196,6 @@ class Config:
# Check Python Version
self.verPyString = sys.version.split()[0]
self.verPyHexVal = sys.hexversion
# Check OS Type
self.osType = sys.platform
+19 -19
View File
@@ -26,16 +26,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import logging
import xml.etree.ElementTree as ET
from enum import Enum
from lxml import etree
from time import time
from pathlib import Path
from novelwriter import __version__, __hexversion__
from novelwriter.common import (
checkBool, checkInt, checkString, checkStringNone, formatTimeStamp,
hexToInt, simplified, yesNo
hexToInt, simplified, xmlIndent, yesNo
)
from novelwriter.constants import nwFiles
@@ -163,7 +163,7 @@ class ProjectXMLReader:
logger.debug("Reading project XML")
try:
xml = etree.parse(str(self._path))
xml = ET.parse(str(self._path))
self._state = XMLReadState.NO_ERROR
except Exception as exc:
@@ -174,7 +174,7 @@ class ProjectXMLReader:
backFile = self._path.with_suffix(".bak")
if backFile.is_file():
try:
xml = etree.parse(str(backFile))
xml = ET.parse(str(backFile))
self._state = XMLReadState.PARSED_BACKUP
logger.info("Backup project file parsed")
except Exception as exc:
@@ -500,7 +500,7 @@ class ProjectXMLWriter:
tStart = time()
logger.debug("Writing project XML")
xRoot = etree.Element("novelWriterXML", attrib={
xRoot = ET.Element("novelWriterXML", attrib={
"appVersion": str(__version__),
"hexVersion": str(__hexversion__),
"fileVersion": FILE_VERSION,
@@ -515,13 +515,13 @@ class ProjectXMLWriter:
"editTime": str(editTime),
}
xProject = etree.SubElement(xRoot, "project", attrib=projAttr)
xProject = ET.SubElement(xRoot, "project", attrib=projAttr)
self._packSingleValue(xProject, "name", projData.name)
self._packSingleValue(xProject, "title", projData.title)
self._packSingleValue(xProject, "author", projData.author)
# Save Project Settings
xSettings = etree.SubElement(xRoot, "settings")
xSettings = ET.SubElement(xRoot, "settings")
self._packSingleValue(xSettings, "doBackup", yesNo(projData.doBackup))
self._packSingleValue(xSettings, "language", projData.language)
self._packSingleValue(xSettings, "spellChecking", projData.spellLang, attrib={
@@ -532,11 +532,11 @@ class ProjectXMLWriter:
self._packDictKeyValue(xSettings, "titleFormat", projData.titleFormat)
# Save Status/Importance
xStatus = etree.SubElement(xSettings, "status")
xStatus = ET.SubElement(xSettings, "status")
for label, attrib in projData.itemStatus.pack():
self._packSingleValue(xStatus, "entry", label, attrib=attrib)
xImport = etree.SubElement(xSettings, "importance")
xImport = ET.SubElement(xSettings, "importance")
for label, attrib in projData.itemImport.pack():
self._packSingleValue(xImport, "entry", label, attrib=attrib)
@@ -547,11 +547,11 @@ class ProjectXMLWriter:
"notesWords": str(projData.currCounts[1]),
}
xContent = etree.SubElement(xRoot, "content", attrib=contAttr)
xContent = ET.SubElement(xRoot, "content", attrib=contAttr)
for item in projContent:
xItem = etree.SubElement(xContent, "item", attrib=item.get("itemAttr", {}))
etree.SubElement(xItem, "meta", attrib=item.get("metaAttr", {}))
xName = etree.SubElement(xItem, "name", attrib=item.get("nameAttr", {}))
xItem = ET.SubElement(xContent, "item", attrib=item.get("itemAttr", {}))
ET.SubElement(xItem, "meta", attrib=item.get("metaAttr", {}))
xName = ET.SubElement(xItem, "name", attrib=item.get("nameAttr", {}))
xName.text = item["name"]
# Write the XML tree to file
@@ -559,9 +559,9 @@ class ProjectXMLWriter:
tempFile = saveFile.with_suffix(".tmp")
backFile = saveFile.with_suffix(".bak")
try:
tempFile.write_bytes(etree.tostring(
xRoot, pretty_print=True, encoding="utf-8", xml_declaration=True
))
xml = ET.ElementTree(xRoot)
xmlIndent(xml)
xml.write(tempFile, encoding="utf-8", xml_declaration=True)
except Exception as exc:
self._error = exc
return False
@@ -587,17 +587,17 @@ class ProjectXMLWriter:
def _packSingleValue(self, xParent, name, value, attrib=None):
"""Pack a single value into an XML element.
"""
xItem = etree.SubElement(xParent, name, attrib=attrib)
xItem = ET.SubElement(xParent, name, attrib=attrib or {})
xItem.text = str(value) or ""
return
def _packDictKeyValue(self, xParent, name, data):
"""Pack the entries of a dictionary into an XML element.
"""
xItem = etree.SubElement(xParent, name)
xItem = ET.SubElement(xParent, name)
for key, value in data.items():
if len(key) > 0:
xEntry = etree.SubElement(xItem, "entry", attrib={"key": key})
xEntry = ET.SubElement(xItem, "entry", attrib={"key": key})
xEntry.text = str(value) or ""
return
+129 -140
View File
@@ -27,13 +27,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import logging
import xml.etree.ElementTree as ET
from lxml import etree
from hashlib import sha256
from zipfile import ZipFile
from datetime import datetime
from novelwriter import __version__
from novelwriter.common import xmlIndent
from novelwriter.constants import nwKeyWords, nwLabels
from novelwriter.core.tokenizer import Tokenizer, stripEscape
@@ -41,30 +42,27 @@ logger = logging.getLogger(__name__)
# Main XML NameSpaces
XML_NS = {
"office": "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
"style": "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
"loext": "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0",
"text": "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
"meta": "urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
"fo": "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
"dc": "http://purl.org/dc/elements/1.1/",
}
MANI_NS = {
"manifest": "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"
}
OFFICE_NS = {
"office": "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
"manifest": "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0",
"office": "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
"style": "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
"loext": "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0",
"text": "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
"meta": "urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
"fo": "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
"dc": "http://purl.org/dc/elements/1.1/",
}
for ns, uri in XML_NS.items():
ET.register_namespace(ns, uri)
def _mkTag(nsName, tagName, nsMap=XML_NS):
def _mkTag(ns, tag):
"""Assemble namespace and tag name.
"""
theNS = nsMap.get(nsName, "")
if theNS:
return f"{{{theNS}}}{tagName}"
logger.warning("Missing xml namespace '%s'", nsName)
return tagName
uri = XML_NS.get(ns, "")
if uri:
return f"{{{uri}}}{tag}"
logger.warning("Missing xml namespace '%s'", ns)
return tag
# Mimetype and Version
@@ -97,20 +95,20 @@ class ToOdt(Tokenizer):
self._isFlat = isFlat # Flat: .fodt, otherwise .odt
self._dFlat = None # FODT file XML root
self._dCont = None # ODT content.xml root
self._dMeta = None # ODT meta.xml root
self._dStyl = None # ODT styles.xml root
self._dFlat = ET.Element("") # FODT file XML root
self._dCont = ET.Element("") # ODT content.xml root
self._dMeta = ET.Element("") # ODT meta.xml root
self._dStyl = ET.Element("") # ODT styles.xml root
self._xMeta = None # Office meta root
self._xFont = None # Office font face declaration
self._xFnt2 = None # Office font face declaration, secondary
self._xStyl = None # Office styles root
self._xAuto = None # Office auto-styles root
self._xAut2 = None # Office auto-styles root, secondary
self._xMast = None # Office master-styles root
self._xBody = None # Office body root
self._xText = None # Office text root
self._xMeta = ET.Element("") # Office meta root
self._xFont = ET.Element("") # Office font face declaration
self._xFnt2 = ET.Element("") # Office font face declaration, secondary
self._xStyl = ET.Element("") # Office styles root
self._xAuto = ET.Element("") # Office auto-styles root
self._xAut2 = ET.Element("") # Office auto-styles root, secondary
self._xMast = ET.Element("") # Office master-styles root
self._xBody = ET.Element("") # Office body root
self._xText = ET.Element("") # Office text root
self._mainPara = {} # User-accessible paragraph styles
self._autoPara = {} # Auto-generated paragraph styles
@@ -287,16 +285,16 @@ class ToOdt(Tokenizer):
tAttr[_mkTag("office", "mimetype")] = X_MIME
tFlat = _mkTag("office", "document")
self._dFlat = etree.Element(tFlat, attrib=tAttr, nsmap=XML_NS)
self._dFlat = ET.Element(tFlat, attrib=tAttr)
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._xMast = etree.SubElement(self._dFlat, _mkTag("office", "master-styles"))
self._xBody = etree.SubElement(self._dFlat, _mkTag("office", "body"))
self._xMeta = ET.SubElement(self._dFlat, _mkTag("office", "meta"))
self._xFont = ET.SubElement(self._dFlat, _mkTag("office", "font-face-decls"))
self._xStyl = ET.SubElement(self._dFlat, _mkTag("office", "styles"))
self._xAuto = ET.SubElement(self._dFlat, _mkTag("office", "automatic-styles"))
self._xMast = ET.SubElement(self._dFlat, _mkTag("office", "master-styles"))
self._xBody = ET.SubElement(self._dFlat, _mkTag("office", "body"))
etree.SubElement(self._xFont, _mkTag("style", "font-face"), attrib=fAttr)
ET.SubElement(self._xFont, _mkTag("style", "font-face"), attrib=fAttr)
else:
@@ -308,59 +306,59 @@ class ToOdt(Tokenizer):
tStyl = _mkTag("office", "document-styles")
# content.xml
self._dCont = etree.Element(tCont, attrib=tAttr, nsmap=XML_NS)
self._xFont = 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._dCont = ET.Element(tCont, attrib=tAttr)
self._xFont = ET.SubElement(self._dCont, _mkTag("office", "font-face-decls"))
self._xAuto = ET.SubElement(self._dCont, _mkTag("office", "automatic-styles"))
self._xBody = ET.SubElement(self._dCont, _mkTag("office", "body"))
# meta.xml
self._dMeta = etree.Element(tMeta, attrib=tAttr, nsmap=XML_NS)
self._xMeta = etree.SubElement(self._dMeta, _mkTag("office", "meta"))
self._dMeta = ET.Element(tMeta, attrib=tAttr)
self._xMeta = ET.SubElement(self._dMeta, _mkTag("office", "meta"))
# styles.xml
self._dStyl = etree.Element(tStyl, attrib=tAttr, nsmap=XML_NS)
self._xFnt2 = etree.SubElement(self._dStyl, _mkTag("office", "font-face-decls"))
self._xStyl = etree.SubElement(self._dStyl, _mkTag("office", "styles"))
self._xAut2 = etree.SubElement(self._dStyl, _mkTag("office", "automatic-styles"))
self._xMast = etree.SubElement(self._dStyl, _mkTag("office", "master-styles"))
self._dStyl = ET.Element(tStyl, attrib=tAttr)
self._xFnt2 = ET.SubElement(self._dStyl, _mkTag("office", "font-face-decls"))
self._xStyl = ET.SubElement(self._dStyl, _mkTag("office", "styles"))
self._xAut2 = ET.SubElement(self._dStyl, _mkTag("office", "automatic-styles"))
self._xMast = ET.SubElement(self._dStyl, _mkTag("office", "master-styles"))
etree.SubElement(self._xFont, _mkTag("style", "font-face"), attrib=fAttr)
etree.SubElement(self._xFnt2, _mkTag("style", "font-face"), attrib=fAttr)
ET.SubElement(self._xFont, _mkTag("style", "font-face"), attrib=fAttr)
ET.SubElement(self._xFnt2, _mkTag("style", "font-face"), attrib=fAttr)
# Finalise
# ========
self._xText = etree.SubElement(self._xBody, _mkTag("office", "text"))
self._xText = ET.SubElement(self._xBody, _mkTag("office", "text"))
timeStamp = datetime.now().isoformat(sep="T", timespec="seconds")
# Office Meta Data
xMeta = etree.SubElement(self._xMeta, _mkTag("meta", "creation-date"))
xMeta = ET.SubElement(self._xMeta, _mkTag("meta", "creation-date"))
xMeta.text = timeStamp
xMeta = etree.SubElement(self._xMeta, _mkTag("meta", "generator"))
xMeta = ET.SubElement(self._xMeta, _mkTag("meta", "generator"))
xMeta.text = f"novelWriter/{__version__}"
xMeta = etree.SubElement(self._xMeta, _mkTag("meta", "initial-creator"))
xMeta = ET.SubElement(self._xMeta, _mkTag("meta", "initial-creator"))
xMeta.text = self.theProject.data.author
xMeta = etree.SubElement(self._xMeta, _mkTag("meta", "editing-cycles"))
xMeta = ET.SubElement(self._xMeta, _mkTag("meta", "editing-cycles"))
xMeta.text = str(self.theProject.data.saveCount)
# Format is: PnYnMnDTnHnMnS
# https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#duration
eT = self.theProject.data.editTime
xMeta = etree.SubElement(self._xMeta, _mkTag("meta", "editing-duration"))
xMeta = ET.SubElement(self._xMeta, _mkTag("meta", "editing-duration"))
xMeta.text = f"P{eT//86400:d}DT{eT%86400//3600:d}H{eT%3600//60:d}M{eT%60:d}S"
# Dublin Core Meta Data
xMeta = etree.SubElement(self._xMeta, _mkTag("dc", "title"))
xMeta = ET.SubElement(self._xMeta, _mkTag("dc", "title"))
xMeta.text = self.theProject.data.title or self.theProject.data.name
xMeta = etree.SubElement(self._xMeta, _mkTag("dc", "date"))
xMeta = ET.SubElement(self._xMeta, _mkTag("dc", "date"))
xMeta.text = timeStamp
xMeta = etree.SubElement(self._xMeta, _mkTag("dc", "creator"))
xMeta = ET.SubElement(self._xMeta, _mkTag("dc", "creator"))
xMeta.text = self.theProject.data.author
self._pageStyles()
@@ -506,51 +504,43 @@ class ToOdt(Tokenizer):
"""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
))
xml = ET.ElementTree(self._dFlat)
xmlIndent(xml)
xml.write(outFile, encoding="utf-8", xml_declaration=True)
return
def saveOpenDocText(self, savePath):
"""Save the data to an .odt file.
"""
mMani = _mkTag("manifest", "manifest", nsMap=MANI_NS)
mVers = _mkTag("manifest", "version", nsMap=MANI_NS)
mPath = _mkTag("manifest", "full-path", nsMap=MANI_NS)
mType = _mkTag("manifest", "media-type", nsMap=MANI_NS)
mFile = _mkTag("manifest", "file-entry", nsMap=MANI_NS)
mMani = _mkTag("manifest", "manifest")
mVers = _mkTag("manifest", "version")
mPath = _mkTag("manifest", "full-path")
mType = _mkTag("manifest", "media-type")
mFile = _mkTag("manifest", "file-entry")
xMani = etree.Element(mMani, attrib={mVers: X_VERS}, nsmap=MANI_NS)
etree.SubElement(xMani, mFile, attrib={mPath: "/", mVers: X_VERS, mType: X_MIME})
etree.SubElement(xMani, mFile, attrib={mPath: "settings.xml", mType: "text/xml"})
etree.SubElement(xMani, mFile, attrib={mPath: "content.xml", mType: "text/xml"})
etree.SubElement(xMani, mFile, attrib={mPath: "meta.xml", mType: "text/xml"})
etree.SubElement(xMani, mFile, attrib={mPath: "styles.xml", mType: "text/xml"})
xMani = ET.Element(mMani, attrib={mVers: X_VERS})
ET.SubElement(xMani, mFile, attrib={mPath: "/", mVers: X_VERS, mType: X_MIME})
ET.SubElement(xMani, mFile, attrib={mPath: "settings.xml", mType: "text/xml"})
ET.SubElement(xMani, mFile, attrib={mPath: "content.xml", mType: "text/xml"})
ET.SubElement(xMani, mFile, attrib={mPath: "meta.xml", mType: "text/xml"})
ET.SubElement(xMani, mFile, attrib={mPath: "styles.xml", mType: "text/xml"})
oRoot = _mkTag("office", "document-settings", nsMap=OFFICE_NS)
oVers = _mkTag("office", "version", nsMap=OFFICE_NS)
xSett = etree.Element(oRoot, nsmap=OFFICE_NS, attrib={oVers: X_VERS})
oRoot = _mkTag("office", "document-settings")
oVers = _mkTag("office", "version")
xSett = ET.Element(oRoot, attrib={oVers: X_VERS})
with ZipFile(savePath, mode="w") as outFile:
outFile.writestr("mimetype", X_MIME)
outFile.writestr("META-INF/manifest.xml", etree.tostring(
xMani, pretty_print=False, encoding="utf-8", xml_declaration=True
))
outFile.writestr("settings.xml", etree.tostring(
xSett, pretty_print=False, encoding="utf-8", xml_declaration=True
))
outFile.writestr("content.xml", etree.tostring(
self._dCont, pretty_print=False, encoding="utf-8", xml_declaration=True
))
outFile.writestr("meta.xml", etree.tostring(
self._dMeta, pretty_print=False, encoding="utf-8", xml_declaration=True
))
outFile.writestr("styles.xml", etree.tostring(
self._dStyl, pretty_print=False, encoding="utf-8", xml_declaration=True
))
def putInZip(name, xObj, zipObj):
with zipObj.open(name, mode="w") as fObj:
xml = ET.ElementTree(xObj)
xml.write(fObj, encoding="utf-8", xml_declaration=True)
with ZipFile(savePath, mode="w") as outZip:
outZip.writestr("mimetype", X_MIME)
putInZip("META-INF/manifest.xml", xMani, outZip)
putInZip("settings.xml", xSett, outZip)
putInZip("content.xml", self._dCont, outZip)
putInZip("meta.xml", self._dMeta, outZip)
putInZip("styles.xml", self._dStyl, outZip)
return
@@ -607,10 +597,10 @@ class ToOdt(Tokenizer):
tAttr[_mkTag("text", "outline-level")] = oLevel
pTag = "h" if isHead else "p"
xElem = etree.SubElement(self._xText, _mkTag("text", pTag), attrib=tAttr)
xElem = ET.SubElement(self._xText, _mkTag("text", pTag), attrib=tAttr)
# It's important to set the initial text field to empty, otherwise
# lxml 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 = ""
if not theText:
@@ -735,25 +725,25 @@ class ToOdt(Tokenizer):
theAttr = {}
theAttr[_mkTag("style", "name")] = "PM1"
if self._isFlat:
xPage = etree.SubElement(self._xAuto, _mkTag("style", "page-layout"), attrib=theAttr)
xPage = ET.SubElement(self._xAuto, _mkTag("style", "page-layout"), attrib=theAttr)
else:
xPage = etree.SubElement(self._xAut2, _mkTag("style", "page-layout"), attrib=theAttr)
xPage = ET.SubElement(self._xAut2, _mkTag("style", "page-layout"), attrib=theAttr)
theAttr = {}
theAttr[_mkTag("fo", "margin-top")] = self._mDocTop
theAttr[_mkTag("fo", "margin-bottom")] = self._mDocBtm
theAttr[_mkTag("fo", "margin-left")] = self._mDocLeft
theAttr[_mkTag("fo", "margin-right")] = self._mDocRight
etree.SubElement(xPage, _mkTag("style", "page-layout-properties"), attrib=theAttr)
ET.SubElement(xPage, _mkTag("style", "page-layout-properties"), attrib=theAttr)
xHead = etree.SubElement(xPage, _mkTag("style", "header-style"))
xHead = ET.SubElement(xPage, _mkTag("style", "header-style"))
theAttr = {}
theAttr[_mkTag("fo", "min-height")] = "0.600cm"
theAttr[_mkTag("fo", "margin-left")] = "0.000cm"
theAttr[_mkTag("fo", "margin-right")] = "0.000cm"
theAttr[_mkTag("fo", "margin-bottom")] = "0.500cm"
etree.SubElement(xHead, _mkTag("style", "header-footer-properties"), attrib=theAttr)
ET.SubElement(xHead, _mkTag("style", "header-footer-properties"), attrib=theAttr)
return
@@ -765,13 +755,13 @@ class ToOdt(Tokenizer):
theAttr = {}
theAttr[_mkTag("style", "family")] = "paragraph"
xStyl = etree.SubElement(self._xStyl, _mkTag("style", "default-style"), attrib=theAttr)
xStyl = ET.SubElement(self._xStyl, _mkTag("style", "default-style"), attrib=theAttr)
theAttr = {}
theAttr[_mkTag("style", "line-break")] = "strict"
theAttr[_mkTag("style", "tab-stop-distance")] = "1.251cm"
theAttr[_mkTag("style", "writing-mode")] = "page"
etree.SubElement(xStyl, _mkTag("style", "paragraph-properties"), attrib=theAttr)
ET.SubElement(xStyl, _mkTag("style", "paragraph-properties"), attrib=theAttr)
theAttr = {}
theAttr[_mkTag("style", "font-name")] = self._textFont
@@ -779,7 +769,7 @@ class ToOdt(Tokenizer):
theAttr[_mkTag("fo", "font-size")] = self._fSizeText
theAttr[_mkTag("fo", "language")] = self._dLanguage
theAttr[_mkTag("fo", "country")] = self._dCountry
etree.SubElement(xStyl, _mkTag("style", "text-properties"), attrib=theAttr)
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib=theAttr)
# Add Standard Paragraph Style
# ============================
@@ -788,13 +778,13 @@ class ToOdt(Tokenizer):
theAttr[_mkTag("style", "name")] = "Standard"
theAttr[_mkTag("style", "family")] = "paragraph"
theAttr[_mkTag("style", "class")] = "text"
xStyl = etree.SubElement(self._xStyl, _mkTag("style", "style"), attrib=theAttr)
xStyl = ET.SubElement(self._xStyl, _mkTag("style", "style"), attrib=theAttr)
theAttr = {}
theAttr[_mkTag("style", "font-name")] = self._textFont
theAttr[_mkTag("fo", "font-family")] = self._fontFamily
theAttr[_mkTag("fo", "font-size")] = self._fSizeText
etree.SubElement(xStyl, _mkTag("style", "text-properties"), attrib=theAttr)
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib=theAttr)
# Add Default Heading Style
# =========================
@@ -805,19 +795,19 @@ class ToOdt(Tokenizer):
theAttr[_mkTag("style", "parent-style-name")] = "Standard"
theAttr[_mkTag("style", "next-style-name")] = "Text_20_body"
theAttr[_mkTag("style", "class")] = "text"
xStyl = etree.SubElement(self._xStyl, _mkTag("style", "style"), attrib=theAttr)
xStyl = ET.SubElement(self._xStyl, _mkTag("style", "style"), attrib=theAttr)
theAttr = {}
theAttr[_mkTag("fo", "margin-top")] = self._mTopHead
theAttr[_mkTag("fo", "margin-bottom")] = self._mBotHead
theAttr[_mkTag("fo", "keep-with-next")] = "always"
etree.SubElement(xStyl, _mkTag("style", "paragraph-properties"), attrib=theAttr)
ET.SubElement(xStyl, _mkTag("style", "paragraph-properties"), attrib=theAttr)
theAttr = {}
theAttr[_mkTag("style", "font-name")] = self._textFont
theAttr[_mkTag("fo", "font-family")] = self._fontFamily
theAttr[_mkTag("fo", "font-size")] = self._fSizeHead
etree.SubElement(xStyl, _mkTag("style", "text-properties"), attrib=theAttr)
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib=theAttr)
# Add Header and Footer Styles
# ============================
@@ -827,7 +817,7 @@ class ToOdt(Tokenizer):
theAttr[_mkTag("style", "family")] = "paragraph"
theAttr[_mkTag("style", "parent-style-name")] = "Standard"
theAttr[_mkTag("style", "class")] = "extra"
etree.SubElement(self._xStyl, _mkTag("style", "style"), attrib=theAttr)
ET.SubElement(self._xStyl, _mkTag("style", "style"), attrib=theAttr)
return
@@ -992,23 +982,24 @@ class ToOdt(Tokenizer):
theAttr = {}
theAttr[_mkTag("style", "name")] = "Standard"
theAttr[_mkTag("style", "page-layout-name")] = "PM1"
xPage = etree.SubElement(self._xMast, _mkTag("style", "master-page"), attrib=theAttr)
xPage = ET.SubElement(self._xMast, _mkTag("style", "master-page"), attrib=theAttr)
# Standard Page Header
xHead = etree.SubElement(xPage, _mkTag("style", "header"))
xPar = etree.SubElement(xHead, _mkTag("text", "p"), attrib={
xHead = ET.SubElement(xPage, _mkTag("style", "header"))
xPar = ET.SubElement(xHead, _mkTag("text", "p"), attrib={
_mkTag("text", "style-name"): "Header"
})
xPar.text = self._headerText.strip() + " "
xTail = etree.SubElement(xPar, _mkTag("text", "page-number"), attrib={
xTail = ET.SubElement(xPar, _mkTag("text", "page-number"), attrib={
_mkTag("text", "select-page"): "current"
})
xTail.text = "2"
xTail.tail = "" # Prevent line break in indented XML
# First Page Header
xHead = etree.SubElement(xPage, _mkTag("style", "header-first"))
xPar = etree.SubElement(xHead, _mkTag("text", "p"), attrib={
xHead = ET.SubElement(xPage, _mkTag("style", "header-first"))
xPar = ET.SubElement(xHead, _mkTag("text", "p"), attrib={
_mkTag("text", "style-name"): "Header"
})
@@ -1212,7 +1203,7 @@ class ODTParagraphStyle:
if aVal is not None:
theAttr[_mkTag(aNm, aName)] = aVal
xEntry = etree.SubElement(xParent, _mkTag("style", "style"), attrib=theAttr)
xEntry = ET.SubElement(xParent, _mkTag("style", "style"), attrib=theAttr)
theAttr = {}
for aName, (aNm, aVal) in self._pAttr.items():
@@ -1220,7 +1211,7 @@ class ODTParagraphStyle:
theAttr[_mkTag(aNm, aName)] = aVal
if theAttr:
etree.SubElement(xEntry, _mkTag("style", "paragraph-properties"), attrib=theAttr)
ET.SubElement(xEntry, _mkTag("style", "paragraph-properties"), attrib=theAttr)
theAttr = {}
for aName, (aNm, aVal) in self._tAttr.items():
@@ -1228,7 +1219,7 @@ class ODTParagraphStyle:
theAttr[_mkTag(aNm, aName)] = aVal
if theAttr:
etree.SubElement(xEntry, _mkTag("style", "text-properties"), attrib=theAttr)
ET.SubElement(xEntry, _mkTag("style", "text-properties"), attrib=theAttr)
return
@@ -1299,7 +1290,7 @@ class ODTTextStyle:
theAttr = {}
theAttr[_mkTag("style", "name")] = xName
theAttr[_mkTag("style", "family")] = "text"
xEntry = etree.SubElement(xParent, _mkTag("style", "style"), attrib=theAttr)
xEntry = ET.SubElement(xParent, _mkTag("style", "style"), attrib=theAttr)
theAttr = {}
for aName, (aNm, aVal) in self._tAttr.items():
@@ -1307,7 +1298,7 @@ class ODTTextStyle:
theAttr[_mkTag(aNm, aName)] = aVal
if theAttr:
etree.SubElement(xEntry, _mkTag("style", "text-properties"), attrib=theAttr)
ET.SubElement(xEntry, _mkTag("style", "text-properties"), attrib=theAttr)
return
@@ -1328,8 +1319,6 @@ class XMLParagraph:
"""This is a helper class to manage the text content of a single
XML element using mixed content tags.
See: https://lxml.de/tutorial.html#the-element-class
Rules:
* The root tag can only have text set, never tail.
* Any span must be under root, and the text in the span is set in
@@ -1349,8 +1338,8 @@ class XMLParagraph:
def __init__(self, xRoot):
self._xRoot = xRoot
self._xTail = None
self._xSing = None
self._xTail = ET.Element("")
self._xSing = ET.Element("")
self._nState = X_ROOT_TEXT
self._chrPos = 0
@@ -1380,26 +1369,26 @@ class XMLParagraph:
if c == "\n":
if self._nState in (X_ROOT_TEXT, X_ROOT_TAIL):
self._xTail = etree.SubElement(self._xRoot, TAG_BR)
self._xTail = ET.SubElement(self._xRoot, TAG_BR)
self._xTail.tail = ""
self._nState = X_ROOT_TAIL
self._chrPos += 1
elif self._nState in (X_SPAN_TEXT, X_SPAN_SING):
self._xSing = etree.SubElement(self._xTail, TAG_BR)
self._xSing = ET.SubElement(self._xTail, TAG_BR)
self._xSing.tail = ""
self._nState = X_SPAN_SING
self._chrPos += 1
elif c == "\t":
if self._nState in (X_ROOT_TEXT, X_ROOT_TAIL):
self._xTail = etree.SubElement(self._xRoot, TAG_TAB)
self._xTail = ET.SubElement(self._xRoot, TAG_TAB)
self._xTail.tail = ""
self._nState = X_ROOT_TAIL
self._chrPos += 1
elif self._nState in (X_SPAN_TEXT, X_SPAN_SING):
self._xSing = etree.SubElement(self._xTail, TAG_TAB)
self._xSing = ET.SubElement(self._xTail, TAG_TAB)
self._xSing.tail = ""
self._chrPos += 1
self._nState = X_SPAN_SING
@@ -1429,7 +1418,7 @@ class XMLParagraph:
Therefore we return to the root element level when we're done
processing the text of the span.
"""
self._xTail = etree.SubElement(self._xRoot, TAG_SPAN, attrib={
self._xTail = ET.SubElement(self._xRoot, TAG_SPAN, attrib={
TAG_STNM: tFmt
})
self._xTail.text = "" # Defaults to None
@@ -1482,20 +1471,20 @@ class XMLParagraph:
if nSpaces == 2:
if self._nState in (X_ROOT_TEXT, X_ROOT_TAIL):
self._xTail = etree.SubElement(self._xRoot, TAG_SPC)
self._xTail = ET.SubElement(self._xRoot, TAG_SPC)
self._xTail.tail = ""
self._nState = X_ROOT_TAIL
self._chrPos += nSpaces - 1
elif self._nState in (X_SPAN_TEXT, X_SPAN_SING):
self._xSing = etree.SubElement(self._xTail, TAG_SPC)
self._xSing = ET.SubElement(self._xTail, TAG_SPC)
self._xSing.tail = ""
self._nState = X_SPAN_SING
self._chrPos += nSpaces - 1
elif nSpaces > 2:
if self._nState in (X_ROOT_TEXT, X_ROOT_TAIL):
self._xTail = etree.SubElement(self._xRoot, TAG_SPC, attrib={
self._xTail = ET.SubElement(self._xRoot, TAG_SPC, attrib={
TAG_NSPC: str(nSpaces - 1)
})
self._xTail.tail = ""
@@ -1503,7 +1492,7 @@ class XMLParagraph:
self._chrPos += nSpaces - 1
elif self._nState in (X_SPAN_TEXT, X_SPAN_SING):
self._xSing = etree.SubElement(self._xTail, TAG_SPC, attrib={
self._xSing = ET.SubElement(self._xTail, TAG_SPC, attrib={
TAG_NSPC: str(nSpaces - 1)
})
self._xSing.tail = ""
-7
View File
@@ -128,12 +128,6 @@ class NWErrorMessage(QDialog):
except Exception:
kernelVersion = "Unknown"
try:
import lxml
lxmlVersion = lxml.__version__ # type: ignore
except Exception:
lxmlVersion = "Unknown"
try:
import enchant
enchantVersion = enchant.__version__
@@ -148,7 +142,6 @@ class NWErrorMessage(QDialog):
f"Host OS: {sys.platform} ({kernelVersion})\n"
f"Python: {sys.version.split()[0]} ({sys.hexversion:#x})\n"
f"Qt: {QT_VERSION_STR}, PyQt: {PYQT_VERSION_STR}\n"
f"lxml: {lxmlVersion}\n"
f"enchant: {enchantVersion}\n\n"
f"{exType.__name__}:\n{str(exValue)}\n\n"
f"Traceback:\n{exTrace}\n"
+5 -4
View File
@@ -23,6 +23,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import sys
import logging
from enum import Enum
@@ -31,10 +32,10 @@ from pathlib import Path
from datetime import datetime
from PyQt5.QtCore import Qt, QTimer, QThreadPool, pyqtSlot
from PyQt5.QtGui import QIcon, QKeySequence, QCursor
from PyQt5.QtGui import QCursor, QIcon, QKeySequence
from PyQt5.QtWidgets import (
qApp, QMainWindow, QVBoxLayout, QWidget, QSplitter, QFileDialog, QShortcut,
QMessageBox, QDialog, QStackedWidget
qApp, QDialog, QFileDialog, QMainWindow, QMessageBox, QShortcut, QSplitter,
QStackedWidget, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, __hexversion__
@@ -89,7 +90,7 @@ class GuiMain(QMainWindow):
logger.info("Host: %s", CONFIG.hostName)
logger.info("Qt5: %s (0x%06x)", CONFIG.verQtString, CONFIG.verQtValue)
logger.info("PyQt5: %s (0x%06x)", CONFIG.verPyQtString, CONFIG.verPyQtValue)
logger.info("Python: %s (0x%08x)", CONFIG.verPyString, CONFIG.verPyHexVal)
logger.info("Python: %s (0x%08x)", CONFIG.verPyString, sys.hexversion)
logger.info("GUI Language: %s", CONFIG.guiLocale)
# Core Classes