Fix typing and docstrings in Project data and XML classes

This commit is contained in:
Veronica Berglyd Olsen
2023-06-03 18:05:04 +02:00
parent d915b1b02c
commit db8140418e
3 changed files with 192 additions and 189 deletions
+4 -6
View File
@@ -1,7 +1,6 @@
""" """
novelWriter Common Functions novelWriter Common Functions
============================== ==============================
Various common functions
File History: File History:
Created: 2019-05-12 [0.1] Created: 2019-05-12 [0.1]
@@ -113,9 +112,8 @@ def checkHandle(value, default, allowNone=False):
return default return default
def checkUuid(value, default): def checkUuid(value: Any, default: str) -> str:
"""Try to process a value as an uuid, or return a default. """Try to process a value as an uuid, or return a default."""
"""
try: try:
return str(uuid.UUID(value)) return str(uuid.UUID(value))
except Exception: except Exception:
@@ -236,7 +234,7 @@ def formatInt(value):
return str(value) return str(value)
def formatTimeStamp(value, fileSafe=False): def formatTimeStamp(value: float, fileSafe: bool = False) -> str:
"""Take a number (on the format returned by time.time()) and convert """Take a number (on the format returned by time.time()) and convert
it to a timestamp string. it to a timestamp string.
""" """
@@ -246,7 +244,7 @@ def formatTimeStamp(value, fileSafe=False):
return datetime.fromtimestamp(value).strftime(nwConst.FMT_TSTAMP) return datetime.fromtimestamp(value).strftime(nwConst.FMT_TSTAMP)
def formatTime(t): def formatTime(t: int) -> str:
"""Format a time in seconds in HH:MM:SS format or d-HH:MM:SS format """Format a time in seconds in HH:MM:SS format or d-HH:MM:SS format
if a full day or longer. if a full day or longer.
""" """
+83 -72
View File
@@ -1,7 +1,6 @@
""" """
novelWriter Project Data Class novelWriter Project Data Class
================================ ================================
Data class for novelWriter projects
File History: File History:
Created: 2022-10-30 [2.0rc2] Created: 2022-10-30 [2.0rc2]
@@ -27,6 +26,8 @@ from __future__ import annotations
import uuid import uuid
import logging import logging
from typing import Any
from novelwriter.common import ( from novelwriter.common import (
checkBool, checkInt, checkStringNone, checkUuid, isHandle, simplified checkBool, checkInt, checkStringNone, checkUuid, isHandle, simplified
) )
@@ -36,6 +37,11 @@ logger = logging.getLogger(__name__)
class NWProjectData: class NWProjectData:
"""Core: Project Data Class
The class holds all project data from the main XML file, aside from
the list of project items.
"""
def __init__(self, theProject): def __init__(self, theProject):
@@ -84,75 +90,99 @@ class NWProjectData:
## ##
@property @property
def uuid(self): def uuid(self) -> str:
"""Return the project ID."""
return self._uuid return self._uuid
@property @property
def name(self): def name(self) -> str:
"""Return the project name."""
return self._name return self._name
@property @property
def title(self): def title(self) -> str:
"""Return the project title."""
return self._title return self._title
@property @property
def author(self): def author(self) -> str:
"""Return the project author."""
return self._author return self._author
@property @property
def saveCount(self): def saveCount(self) -> int:
"""Return the count of project saves."""
return self._saveCount return self._saveCount
@property @property
def autoCount(self): def autoCount(self) -> int:
"""Return the count of project auto-saves."""
return self._autoCount return self._autoCount
@property @property
def editTime(self): def editTime(self) -> int:
"""Return the number of seconds the project has been edited."""
return self._editTime return self._editTime
@property @property
def doBackup(self): def doBackup(self) -> bool:
"""Return the backup setting."""
return self._doBackup return self._doBackup
@property @property
def language(self): def language(self) -> str | None:
"""Return the project language setting."""
return self._language return self._language
@property @property
def spellCheck(self): def spellCheck(self) -> bool:
"""Return the spell check enabled setting."""
return self._spellCheck return self._spellCheck
@property @property
def spellLang(self): def spellLang(self) -> str | None:
"""Return the spell check language."""
return self._spellLang return self._spellLang
@property @property
def initCounts(self): def initCounts(self) -> tuple[int, int]:
"""Return the initial count of words for novel and note
documents.
"""
return tuple(self._initCounts) return tuple(self._initCounts)
@property @property
def currCounts(self): def currCounts(self) -> tuple[int, int]:
"""Return the current count of words for novel and note
documents.
"""
return tuple(self._currCounts) return tuple(self._currCounts)
@property @property
def lastHandle(self): def lastHandle(self) -> dict[str, str | None]:
"""Return the dictionary of last used handles for various
components of the GUI.
"""
return self._lastHandle return self._lastHandle
@property @property
def autoReplace(self): def autoReplace(self) -> dict[str, str]:
"""Return the autoreplace dictionary."""
return self._autoReplace return self._autoReplace
@property @property
def titleFormat(self): def titleFormat(self):
"""Delete"""
return self._titleFormat return self._titleFormat
@property @property
def itemStatus(self): def itemStatus(self) -> NWStatus:
"""Return the status settings object."""
return self._status return self._status
@property @property
def itemImport(self): def itemImport(self) -> NWStatus:
"""Return the importance settings object."""
return self._import return self._import
## ##
@@ -160,15 +190,13 @@ class NWProjectData:
## ##
def incSaveCount(self): def incSaveCount(self):
"""Increment the save count by one. """Increment the save count by one."""
"""
self._saveCount += 1 self._saveCount += 1
self.theProject.setProjectChanged(True) self.theProject.setProjectChanged(True)
return return
def incAutoCount(self): def incAutoCount(self):
"""Increment the auto save count by one. """Increment the auto save count by one."""
"""
self._autoCount += 1 self._autoCount += 1
self.theProject.setProjectChanged(True) self.theProject.setProjectChanged(True)
return return
@@ -177,23 +205,20 @@ class NWProjectData:
# Getters # Getters
## ##
def getLastHandle(self, component): def getLastHandle(self, component: str) -> str | None:
"""Retrieve the last used handle for a given component. """Retrieve the last used handle for a given component."""
"""
return self._lastHandle.get(component, None) return self._lastHandle.get(component, None)
def getTitleFormat(self, kind): def getTitleFormat(self, kind):
"""Retrieve the title format string for a given kind of header. """Retrieve the title format string for a given kind of header."""
"""
return self._titleFormat.get(kind, "%title%") return self._titleFormat.get(kind, "%title%")
## ##
# Setters # Setters
## ##
def setUuid(self, value): def setUuid(self, value: Any):
"""Set the project id. """Set the project id."""
"""
value = checkUuid(value, "") value = checkUuid(value, "")
if not value: if not value:
self._uuid = str(uuid.uuid4()) self._uuid = str(uuid.uuid4())
@@ -202,84 +227,74 @@ class NWProjectData:
self.theProject.setProjectChanged(True) self.theProject.setProjectChanged(True)
return return
def setName(self, value): def setName(self, value: str | None):
"""Set a new project name. """Set a new project name."""
"""
if value != self._name: if value != self._name:
self._name = simplified(str(value or "")) self._name = simplified(str(value or ""))
self.theProject.setProjectChanged(True) self.theProject.setProjectChanged(True)
return return
def setTitle(self, value): def setTitle(self, value: str | None):
"""Set a new novel title. """Set a new novel title."""
"""
if value != self._title: if value != self._title:
self._title = simplified(str(value or "")) self._title = simplified(str(value or ""))
self.theProject.setProjectChanged(True) self.theProject.setProjectChanged(True)
return return
def setAuthor(self, value): def setAuthor(self, value: str | None):
"""Set the author value. """Set the author value."""
"""
if value != self._title: if value != self._title:
self._author = simplified(str(value or "")) self._author = simplified(str(value or ""))
self.theProject.setProjectChanged(True) self.theProject.setProjectChanged(True)
return return
def setSaveCount(self, value): def setSaveCount(self, value: Any):
"""Set the save count from last session. """Set the save count from last session."""
"""
self._saveCount = checkInt(value, 0) self._saveCount = checkInt(value, 0)
self.theProject.setProjectChanged(True) self.theProject.setProjectChanged(True)
return return
def setAutoCount(self, value): def setAutoCount(self, value: Any):
"""Set the auto save count from last session. """Set the auto save count from last session."""
"""
self._autoCount = checkInt(value, 0) self._autoCount = checkInt(value, 0)
self.theProject.setProjectChanged(True) self.theProject.setProjectChanged(True)
return return
def setEditTime(self, value): def setEditTime(self, value: Any):
"""Set tyje edit time from last session. """Set tyje edit time from last session."""
"""
self._editTime = checkInt(value, 0) self._editTime = checkInt(value, 0)
self.theProject.setProjectChanged(True) self.theProject.setProjectChanged(True)
return return
def setDoBackup(self, value): def setDoBackup(self, value: Any):
"""Set the do write backup flag. """Set the do write backup flag."""
"""
if value != self._doBackup: if value != self._doBackup:
self._doBackup = checkBool(value, False) self._doBackup = checkBool(value, False)
self.theProject.setProjectChanged(True) self.theProject.setProjectChanged(True)
return return
def setLanguage(self, value): def setLanguage(self, value: str | None):
"""Set the project language. """Set the project language."""
"""
if value != self._language: if value != self._language:
self._language = checkStringNone(value, None) self._language = checkStringNone(value, None)
self.theProject.setProjectChanged(True) self.theProject.setProjectChanged(True)
return return
def setSpellCheck(self, value): def setSpellCheck(self, value: Any):
"""Set the spell check flag. """Set the spell check flag."""
"""
if value != self._spellCheck: if value != self._spellCheck:
self._spellCheck = checkBool(value, False) self._spellCheck = checkBool(value, False)
self.theProject.setProjectChanged(True) self.theProject.setProjectChanged(True)
return return
def setSpellLang(self, value): def setSpellLang(self, value: str | None):
"""Set the spell check language. """Set the spell check language."""
"""
if value != self._spellLang: if value != self._spellLang:
self._spellLang = checkStringNone(value, None) self._spellLang = checkStringNone(value, None)
self.theProject.setProjectChanged(True) self.theProject.setProjectChanged(True)
return return
def setLastHandle(self, value, component=None): def setLastHandle(self, value: dict, component: str | None = None):
"""Set a last used handle into the handle registry. If component """Set a last used handle into the handle registry. If component
is None, the value is assumed to be the whole dictionary of is None, the value is assumed to be the whole dictionary of
values. values.
@@ -294,9 +309,8 @@ class NWProjectData:
self.theProject.setProjectChanged(True) self.theProject.setProjectChanged(True)
return return
def setInitCounts(self, novel=None, notes=None): def setInitCounts(self, novel: Any = None, notes: Any = None):
"""Set the worc count totals for novel and note files. """Set the word count totals for novel and note files."""
"""
if novel is not None: if novel is not None:
self._initCounts[0] = checkInt(novel, 0) self._initCounts[0] = checkInt(novel, 0)
self._currCounts[0] = checkInt(novel, 0) self._currCounts[0] = checkInt(novel, 0)
@@ -305,18 +319,16 @@ class NWProjectData:
self._currCounts[1] = checkInt(notes, 0) self._currCounts[1] = checkInt(notes, 0)
return return
def setCurrCounts(self, novel=None, notes=None): def setCurrCounts(self, novel: Any = None, notes: Any = None):
"""Set the worc count totals for novel and note files. """Set the word count totals for novel and note files."""
"""
if novel is not None: if novel is not None:
self._currCounts[0] = checkInt(novel, 0) self._currCounts[0] = checkInt(novel, 0)
if notes is not None: if notes is not None:
self._currCounts[1] = checkInt(notes, 0) self._currCounts[1] = checkInt(notes, 0)
return return
def setAutoReplace(self, value): def setAutoReplace(self, value: dict):
"""Set the auto-replace dictionary. """Set the auto-replace dictionary."""
"""
if isinstance(value, dict): if isinstance(value, dict):
self._autoReplace = {} self._autoReplace = {}
for key, entry in value.items(): for key, entry in value.items():
@@ -326,8 +338,7 @@ class NWProjectData:
return return
def setTitleFormat(self, value): def setTitleFormat(self, value):
"""Set the title formats. """Set the title formats."""
"""
if isinstance(value, dict): if isinstance(value, dict):
for key, entry in value.items(): for key, entry in value.items():
if key in self._titleFormat and isinstance(entry, str): if key in self._titleFormat and isinstance(entry, str):
+105 -111
View File
@@ -1,7 +1,6 @@
""" """
novelWriter Project XML Read/Write novelWriter Project XML Read/Write
==================================== ====================================
Classes for reading and writing the project XML file
File History: File History:
Created: 2022-09-28 [2.0rc2] XMLReadState Created: 2022-09-28 [2.0rc2] XMLReadState
@@ -24,6 +23,7 @@ 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
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
@@ -31,6 +31,7 @@ import xml.etree.ElementTree as ET
from enum import Enum from enum import Enum
from time import time from time import time
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING
from novelwriter import __version__, __hexversion__ from novelwriter import __version__, __hexversion__
from novelwriter.common import ( from novelwriter.common import (
@@ -39,6 +40,10 @@ from novelwriter.common import (
) )
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
if TYPE_CHECKING:
from novelwriter.core.status import NWStatus
from novelwriter.core.projectdata import NWProjectData
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
FILE_VERSION = "1.5" # The current project file format version FILE_VERSION = "1.5" # The current project file format version
@@ -55,6 +60,7 @@ NUM_VERSION = {
class XMLReadState(Enum): class XMLReadState(Enum):
"""The state of an XML read process."""
NO_ACTION = 0 NO_ACTION = 0
NO_ERROR = 1 NO_ERROR = 1
@@ -69,8 +75,10 @@ class XMLReadState(Enum):
class ProjectXMLReader: class ProjectXMLReader:
"""The main project XML file reader class. All data is read into a """Core: Project XML Reader
NWProjectData instance, which must be provided.
All data is read into a NWProjectData instance, which must be
provided.
File Format Version Change History File Format Version Change History
================================== ==================================
@@ -100,16 +108,13 @@ class ProjectXMLReader:
""" """
def __init__(self, path): def __init__(self, path):
self._path = Path(path) self._path = Path(path)
self._state = XMLReadState.NO_ACTION self._state = XMLReadState.NO_ACTION
self._root = "" self._root = ""
self._version = 0x0 self._version = 0x0
self._appVersion = "" self._appVersion = ""
self._hexVersion = 0x0 self._hexVersion = 0x0
self._timeStamp = "" self._timeStamp = ""
return return
## ##
@@ -117,55 +122,46 @@ class ProjectXMLReader:
## ##
@property @property
def state(self): def state(self) -> XMLReadState:
"""The state of the parsing as an XMLReadState enum value. """Return the parsing state."""
"""
return self._state return self._state
@property @property
def xmlRoot(self): def xmlRoot(self) -> str:
"""The root tag name of the XNL file, """Return the root tag name of the XNL file."""
"""
return self._root return self._root
@property @property
def xmlVersion(self): def xmlVersion(self) -> int:
"""The project XML version number. """Return the project XML version number."""
"""
return self._version return self._version
@property @property
def appVersion(self): def appVersion(self) -> str:
"""The novelWriter version number who wrote the file. """Return the version number who wrote the file."""
"""
return self._appVersion return self._appVersion
@property @property
def hexVersion(self): def hexVersion(self) -> int:
"""The novelWriter version number who wrote the file as hex. """Return the version number who wrote the file as hex."""
"""
return self._hexVersion return self._hexVersion
@property @property
def timeStamp(self): def timeStamp(self) -> str:
"""The date and time when the file was written. """Return the date and time when the file was written."""
"""
return self._timeStamp return self._timeStamp
## ##
# Methods # Methods
## ##
def read(self, projData, projContent): def read(self, data: NWProjectData, content: list) -> bool:
"""Read and parse the project XML file. """Read and parse the project XML file."""
"""
tStart = time() tStart = time()
logger.debug("Reading project XML") logger.debug("Reading project XML")
try: try:
xml = ET.parse(str(self._path)) xml = ET.parse(str(self._path))
self._state = XMLReadState.NO_ERROR self._state = XMLReadState.NO_ERROR
except Exception as exc: except Exception as exc:
# Trying to open backup file instead # Trying to open backup file instead
logger.error("Failed to parse project XML", exc_info=exc) logger.error("Failed to parse project XML", exc_info=exc)
@@ -206,14 +202,14 @@ class ProjectXMLReader:
for xSection in xRoot: for xSection in xRoot:
if xSection.tag == "project": if xSection.tag == "project":
self._parseProjectMeta(xSection, projData) self._parseProjectMeta(xSection, data)
elif xSection.tag == "settings": elif xSection.tag == "settings":
self._parseProjectSettings(xSection, projData) self._parseProjectSettings(xSection, data)
elif xSection.tag == "content": elif xSection.tag == "content":
if self._version >= 0x0104: if self._version >= 0x0104:
self._parseProjectContent(xSection, projData, projContent) self._parseProjectContent(xSection, data, content)
else: else:
self._parseProjectContentLegacy(xSection, projData, projContent) self._parseProjectContentLegacy(xSection, data, content)
else: else:
logger.warning("Ignored <root/%s> in XML", xSection.tag) logger.warning("Ignored <root/%s> in XML", xSection.tag)
@@ -230,23 +226,22 @@ class ProjectXMLReader:
# Internal Functions # Internal Functions
## ##
def _parseProjectMeta(self, xSection, projData): def _parseProjectMeta(self, xSection: ET.Element, data: NWProjectData):
"""Parse the project section of the XML file. """Parse the project section of the XML file."""
"""
logger.debug("Parsing <project> section") logger.debug("Parsing <project> section")
projData.setUuid(xSection.attrib.get("id", None)) # Added in 1.5 data.setUuid(xSection.attrib.get("id", None)) # Added in 1.5
projData.setSaveCount(xSection.attrib.get("saveCount", 0)) # Moved in 1.5 data.setSaveCount(xSection.attrib.get("saveCount", 0)) # Moved in 1.5
projData.setAutoCount(xSection.attrib.get("autoCount", 0)) # Moved in 1.5 data.setAutoCount(xSection.attrib.get("autoCount", 0)) # Moved in 1.5
projData.setEditTime(xSection.attrib.get("editTime", 0)) # Moved in 1.5 data.setEditTime(xSection.attrib.get("editTime", 0)) # Moved in 1.5
for xItem in xSection: for xItem in xSection:
if xItem.tag == "name": if xItem.tag == "name":
projData.setName(xItem.text) data.setName(xItem.text)
elif xItem.tag == "title": elif xItem.tag == "title":
projData.setTitle(xItem.text) data.setTitle(xItem.text)
elif xItem.tag == "author": elif xItem.tag == "author":
projData.setAuthor(xItem.text) data.setAuthor(xItem.text)
else: else:
logger.warning("Ignored <root/project/%s> in XML", xItem.tag) logger.warning("Ignored <root/project/%s> in XML", xItem.tag)
@@ -254,43 +249,42 @@ class ProjectXMLReader:
if self._version < HEX_VERSION: if self._version < HEX_VERSION:
for xItem in xSection: for xItem in xSection:
if xItem.tag == "saveCount": # Moved to attribute in 1.5 if xItem.tag == "saveCount": # Moved to attribute in 1.5
projData.setSaveCount(xItem.text) data.setSaveCount(xItem.text)
elif xItem.tag == "autoCount": # Moved to attribute in 1.5 elif xItem.tag == "autoCount": # Moved to attribute in 1.5
projData.setAutoCount(xItem.text) data.setAutoCount(xItem.text)
elif xItem.tag == "editTime": # Moved to attribute in 1.5 elif xItem.tag == "editTime": # Moved to attribute in 1.5
projData.setEditTime(xItem.text) data.setEditTime(xItem.text)
return return
def _parseProjectSettings(self, xSection, projData): def _parseProjectSettings(self, xSection: ET.Element, data: NWProjectData):
"""Parse the settings section of the XML file. """Parse the settings section of the XML file."""
"""
logger.debug("Parsing <settings> section") logger.debug("Parsing <settings> section")
for xItem in xSection: for xItem in xSection:
if xItem.tag == "doBackup": if xItem.tag == "doBackup":
projData.setDoBackup(xItem.text) data.setDoBackup(xItem.text)
elif xItem.tag == "language": elif xItem.tag == "language":
projData.setLanguage(xItem.text) data.setLanguage(xItem.text)
elif xItem.tag == "spellChecking": elif xItem.tag == "spellChecking":
projData.setSpellLang(xItem.text) data.setSpellLang(xItem.text)
projData.setSpellCheck(xItem.attrib.get("auto", False)) data.setSpellCheck(xItem.attrib.get("auto", False))
elif xItem.tag == "status": elif xItem.tag == "status":
self._parseStatusImport(xItem, projData.itemStatus) self._parseStatusImport(xItem, data.itemStatus)
elif xItem.tag == "importance": elif xItem.tag == "importance":
self._parseStatusImport(xItem, projData.itemImport) self._parseStatusImport(xItem, data.itemImport)
elif xItem.tag == "lastHandle": elif xItem.tag == "lastHandle":
projData.setLastHandle(self._parseDictKeyText(xItem)) data.setLastHandle(self._parseDictKeyText(xItem))
elif xItem.tag == "autoReplace": elif xItem.tag == "autoReplace":
if self._version >= 0x0102: if self._version >= 0x0102:
projData.setAutoReplace(self._parseDictKeyText(xItem)) data.setAutoReplace(self._parseDictKeyText(xItem))
else: # Pre 1.2 format else: # Pre 1.2 format
projData.setAutoReplace(self._parseDictTagText(xItem)) data.setAutoReplace(self._parseDictTagText(xItem))
elif xItem.tag == "titleFormat": elif xItem.tag == "titleFormat":
if self._version >= 0x0105: if self._version >= 0x0105:
projData.setTitleFormat(self._parseDictKeyText(xItem)) data.setTitleFormat(self._parseDictKeyText(xItem))
else: # Pre 1.4 format else: # Pre 1.4 format
projData.setTitleFormat(self._parseDictTagText(xItem)) data.setTitleFormat(self._parseDictTagText(xItem))
else: else:
logger.warning("Ignored <root/settings/%s> in XML", xItem.tag) logger.warning("Ignored <root/settings/%s> in XML", xItem.tag)
@@ -298,23 +292,22 @@ class ProjectXMLReader:
if self._version < HEX_VERSION: if self._version < HEX_VERSION:
for xItem in xSection: for xItem in xSection:
if xItem.tag == "spellCheck": # Changed to spellChecking in 1.5 if xItem.tag == "spellCheck": # Changed to spellChecking in 1.5
projData.setSpellCheck(xItem.text) data.setSpellCheck(xItem.text)
elif xItem.tag == "spellLang": # Changed to spellChecking in 1.5 elif xItem.tag == "spellLang": # Changed to spellChecking in 1.5
projData.setSpellLang(xItem.text) data.setSpellLang(xItem.text)
elif xItem.tag == "novelWordCount": # Moved to content attribute in 1.5 elif xItem.tag == "novelWordCount": # Moved to content attribute in 1.5
projData.setInitCounts(novel=xItem.text) data.setInitCounts(novel=xItem.text)
elif xItem.tag == "notesWordCount": # Moved to content attribute in 1.5 elif xItem.tag == "notesWordCount": # Moved to content attribute in 1.5
projData.setInitCounts(notes=xItem.text) data.setInitCounts(notes=xItem.text)
return return
def _parseProjectContent(self, xSection, projData, projContent): def _parseProjectContent(self, xSection: ET.Element, data: NWProjectData, content: list):
"""Parse the content section of the XML file. """Parse the content section of the XML file."""
"""
logger.debug("Parsing <content> section") logger.debug("Parsing <content> section")
projData.setInitCounts(novel=xSection.attrib.get("novelWords", None)) # Moved in 1.5 data.setInitCounts(novel=xSection.attrib.get("novelWords", None)) # Moved in 1.5
projData.setInitCounts(notes=xSection.attrib.get("notesWords", None)) # Moved in 1.5 data.setInitCounts(notes=xSection.attrib.get("notesWords", None)) # Moved in 1.5
for xItem in xSection: for xItem in xSection:
if xItem.tag != "item": if xItem.tag != "item":
@@ -355,7 +348,7 @@ class ProjectXMLReader:
if xVal.tag == "name" and "exported" in xVal.attrib: if xVal.tag == "name" and "exported" in xVal.attrib:
name["active"] = checkBool(xVal.attrib.get("exported"), False) name["active"] = checkBool(xVal.attrib.get("exported"), False)
projContent.append({ content.append({
"name": itemName, "name": itemName,
"itemAttr": item, "itemAttr": item,
"metaAttr": meta, "metaAttr": meta,
@@ -364,14 +357,13 @@ class ProjectXMLReader:
return return
def _parseProjectContentLegacy(self, xSection, projData, projContent): def _parseProjectContentLegacy(self, xSection: ET.Element, data: NWProjectData, content: list):
"""Parse the content section of the XML file for older versions. """Parse the content section of the XML file for older versions."""
"""
logger.debug("Parsing <content> section (legacy format)") logger.debug("Parsing <content> section (legacy format)")
# Create maps to look up name -> key for status and importance # Create maps to look up name -> key for status and importance
statusMap = {entry.get("name"): key for key, entry in projData.itemStatus.items()} statusMap = {entry.get("name"): key for key, entry in data.itemStatus.items()}
importMap = {entry.get("name"): key for key, entry in projData.itemImport.items()} importMap = {entry.get("name"): key for key, entry in data.itemImport.items()}
for xItem in xSection: for xItem in xSection:
if xItem.tag != "item": if xItem.tag != "item":
@@ -432,7 +424,7 @@ class ProjectXMLReader:
if item.get("type", "") == "TRASH": if item.get("type", "") == "TRASH":
item["type"] = "ROOT" item["type"] = "ROOT"
projContent.append({ content.append({
"name": itemName, "name": itemName,
"itemAttr": item, "itemAttr": item,
"metaAttr": meta, "metaAttr": meta,
@@ -441,9 +433,8 @@ class ProjectXMLReader:
return return
def _parseStatusImport(self, xItem, sObject): def _parseStatusImport(self, xItem: ET.Element, sObject: NWStatus):
"""Parse a status or importance entry. """Parse a status or importance entry."""
"""
for xEntry in xItem: for xEntry in xItem:
if xEntry.tag == "entry": if xEntry.tag == "entry":
key = xEntry.attrib.get("key", None) key = xEntry.attrib.get("key", None)
@@ -454,7 +445,7 @@ class ProjectXMLReader:
sObject.write(key, xEntry.text, (red, green, blue), count) sObject.write(key, xEntry.text, (red, green, blue), count)
return return
def _parseDictKeyText(self, xItem): def _parseDictKeyText(self, xItem: ET.Element) -> dict:
"""Parse a dictionary stored with key as an attribute and the """Parse a dictionary stored with key as an attribute and the
value as the text porperty. value as the text porperty.
""" """
@@ -474,12 +465,15 @@ class ProjectXMLReader:
class ProjectXMLWriter: class ProjectXMLWriter:
"""Core: Project XML Writer
The project writer class will only write a file according to the
very latest spec.
"""
def __init__(self, path): def __init__(self, path):
self._path = Path(path) self._path = Path(path)
self._error = None self._error = None
return return
## ##
@@ -487,16 +481,16 @@ class ProjectXMLWriter:
## ##
@property @property
def error(self): def error(self) -> Exception | None:
"""Return the error status."""
return self._error return self._error
## ##
# Methods # Methods
## ##
def write(self, projData, projContent, saveTime, editTime): def write(self, data: NWProjectData, content: list, saveTime: float, editTime: int) -> bool:
"""Write the project data and content to the XML files. """Write the project data and content to the XML files."""
"""
tStart = time() tStart = time()
logger.debug("Writing project XML") logger.debug("Writing project XML")
@@ -509,46 +503,46 @@ class ProjectXMLWriter:
# Save Project Meta # Save Project Meta
projAttr = { projAttr = {
"id": projData.uuid, "id": data.uuid,
"saveCount": str(projData.saveCount), "saveCount": str(data.saveCount),
"autoCount": str(projData.autoCount), "autoCount": str(data.autoCount),
"editTime": str(editTime), "editTime": str(editTime),
} }
xProject = ET.SubElement(xRoot, "project", attrib=projAttr) xProject = ET.SubElement(xRoot, "project", attrib=projAttr)
self._packSingleValue(xProject, "name", projData.name) self._packSingleValue(xProject, "name", data.name)
self._packSingleValue(xProject, "title", projData.title) self._packSingleValue(xProject, "title", data.title)
self._packSingleValue(xProject, "author", projData.author) self._packSingleValue(xProject, "author", data.author)
# Save Project Settings # Save Project Settings
xSettings = ET.SubElement(xRoot, "settings") xSettings = ET.SubElement(xRoot, "settings")
self._packSingleValue(xSettings, "doBackup", yesNo(projData.doBackup)) self._packSingleValue(xSettings, "doBackup", yesNo(data.doBackup))
self._packSingleValue(xSettings, "language", projData.language) self._packSingleValue(xSettings, "language", data.language)
self._packSingleValue(xSettings, "spellChecking", projData.spellLang, attrib={ self._packSingleValue(xSettings, "spellChecking", data.spellLang, attrib={
"auto": yesNo(projData.spellCheck) "auto": yesNo(data.spellCheck)
}) })
self._packDictKeyValue(xSettings, "lastHandle", projData.lastHandle) self._packDictKeyValue(xSettings, "lastHandle", data.lastHandle)
self._packDictKeyValue(xSettings, "autoReplace", projData.autoReplace) self._packDictKeyValue(xSettings, "autoReplace", data.autoReplace)
self._packDictKeyValue(xSettings, "titleFormat", projData.titleFormat) self._packDictKeyValue(xSettings, "titleFormat", data.titleFormat)
# Save Status/Importance # Save Status/Importance
xStatus = ET.SubElement(xSettings, "status") xStatus = ET.SubElement(xSettings, "status")
for label, attrib in projData.itemStatus.pack(): for label, attrib in data.itemStatus.pack():
self._packSingleValue(xStatus, "entry", label, attrib=attrib) self._packSingleValue(xStatus, "entry", label, attrib=attrib)
xImport = ET.SubElement(xSettings, "importance") xImport = ET.SubElement(xSettings, "importance")
for label, attrib in projData.itemImport.pack(): for label, attrib in data.itemImport.pack():
self._packSingleValue(xImport, "entry", label, attrib=attrib) self._packSingleValue(xImport, "entry", label, attrib=attrib)
# Save Tree Content # Save Tree Content
contAttr = { contAttr = {
"items": str(len(projContent)), "items": str(len(content)),
"novelWords": str(projData.currCounts[0]), "novelWords": str(data.currCounts[0]),
"notesWords": str(projData.currCounts[1]), "notesWords": str(data.currCounts[1]),
} }
xContent = ET.SubElement(xRoot, "content", attrib=contAttr) xContent = ET.SubElement(xRoot, "content", attrib=contAttr)
for item in projContent: for item in content:
xItem = ET.SubElement(xContent, "item", attrib=item.get("itemAttr", {})) xItem = ET.SubElement(xContent, "item", attrib=item.get("itemAttr", {}))
ET.SubElement(xItem, "meta", attrib=item.get("metaAttr", {})) ET.SubElement(xItem, "meta", attrib=item.get("metaAttr", {}))
xName = ET.SubElement(xItem, "name", attrib=item.get("nameAttr", {})) xName = ET.SubElement(xItem, "name", attrib=item.get("nameAttr", {}))
@@ -584,16 +578,16 @@ class ProjectXMLWriter:
# Internal Functions # Internal Functions
## ##
def _packSingleValue(self, xParent, name, value, attrib=None): def _packSingleValue(
"""Pack a single value into an XML element. self, xParent: ET.Element, name: str, value: str | None, attrib: dict | None = None
""" ):
"""Pack a single value into an XML element."""
xItem = ET.SubElement(xParent, name, attrib=attrib or {}) xItem = ET.SubElement(xParent, name, attrib=attrib or {})
xItem.text = str(value) or "" xItem.text = str(value) or ""
return return
def _packDictKeyValue(self, xParent, name, data): def _packDictKeyValue(self, xParent: ET.Element, name: str, data: dict):
"""Pack the entries of a dictionary into an XML element. """Pack the entries of a dictionary into an XML element."""
"""
xItem = ET.SubElement(xParent, name) xItem = ET.SubElement(xParent, name)
for key, value in data.items(): for key, value in data.items():
if len(key) > 0: if len(key) > 0: