Fix typing and docstrings in Project data and XML classes
This commit is contained in:
@@ -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.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user