Complete loading project xml via data class

This commit is contained in:
Veronica Berglyd Olsen
2022-10-31 18:11:46 +01:00
parent 522acc479b
commit 3a161e90f9
14 changed files with 129 additions and 161 deletions
+6 -72
View File
@@ -38,8 +38,8 @@ from PyQt5.QtCore import QCoreApplication
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
from novelwriter.error import logException
from novelwriter.common import (
checkString, checkStringNone, isHandle, formatTimeStamp, makeFileNameSafe,
hexToInt, minmax, simplified
checkStringNone, isHandle, formatTimeStamp, makeFileNameSafe, hexToInt,
minmax, simplified
)
from novelwriter.constants import trConst, nwFiles, nwLabels
from novelwriter.core.tree import NWTree
@@ -63,8 +63,8 @@ class NWProject:
self.mainConf = novelwriter.CONFIG
self.mainGui = mainGui
# Project Data
self._data = NWProjectData()
self._raw = {}
# Core Elements
self._optState = OptionState(self) # Project-specific GUI options
@@ -84,14 +84,8 @@ class NWProject:
self.projCache = None # The full path to the project's cache folder
self.projContent = None # The full path to the project's content folder
self.projDict = None # The spell check dictionary
self.projSpell = None # The spell check language, if different than default
self.projFiles = [] # A list of all files in the content folder on load
# Project Settings
self.autoReplace = {} # Text to auto-replace on exports
self.titleFormat = {} # The formatting of titles for exports
self.spellCheck = False # Controls the spellcheck-as-you-type feature
# Internal Mapping
self.tr = partial(QCoreApplication.translate, "NWProject")
@@ -255,17 +249,7 @@ class NWProject:
self.projCache = None
self.projContent = None
self.projDict = None
self.projSpell = None
self.projFiles = []
self.autoReplace = {}
self.titleFormat = {
"title": "%title%",
"chapter": "%title%",
"unnumbered": "%title%",
"scene": "* * *",
"section": "",
}
self.spellCheck = False
self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100))
self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0))
self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0))
@@ -466,9 +450,6 @@ class NWProject:
self._data = NWProjectData()
xmlReader = ProjectXMLReader(fileName)
xmlParsed = xmlReader.read(self._data)
xmlData = xmlReader.data
# print(json.dumps(xmlData, indent=2))
nwxRoot = xmlReader.xmlRoot
appVersion = xmlReader.appVersion or self.tr("Unknown")
@@ -494,8 +475,6 @@ class NWProject:
self.clearProject()
return False
self._raw = xmlData
logger.debug("XML root is '%s'", nwxRoot)
logger.debug("File version is '%s'", xmlVersion)
@@ -539,15 +518,7 @@ class NWProject:
logger.info("Project Name: '%s'", self._data.name)
logger.info("Project Title: '%s'", self._data.title)
xmlSettings = xmlData.get("settings", {})
self.spellCheck = self._data.spellCheck
self.projSpell = self._data.spellLang
self.autoReplace = xmlSettings.get("autoReplace", {})
self.titleFormat.update(xmlSettings.get("titleFormat", {}))
self._projTree.unpack(xmlData.get("content", []))
self._projTree.unpack(xmlReader.content)
self._optState.loadSettings()
# Sort out old file locations
@@ -648,10 +619,10 @@ class NWProject:
self._packProjectValue(xSettings, "lastWordCount", self._data.getCurrCount("total"))
self._packProjectValue(xSettings, "novelWordCount", self._data.getCurrCount("novel"))
self._packProjectValue(xSettings, "notesWordCount", self._data.getCurrCount("notes"))
self._packProjectKeyValue(xSettings, "autoReplace", self.autoReplace)
self._packProjectKeyValue(xSettings, "autoReplace", self._data.autoReplace)
xTitleFmt = etree.SubElement(xSettings, "titleFormat")
for aKey, aValue in self.titleFormat.items():
for aKey, aValue in self._data.titleFormat.items():
if len(aKey) > 0:
self._packProjectValue(xTitleFmt, aKey, aValue)
@@ -921,24 +892,6 @@ class NWProject:
return True
def setSpellCheck(self, theMode):
"""Enable/disable spell checking.
"""
if self.spellCheck != theMode:
self.spellCheck = theMode
self.setProjectChanged(True)
return self.spellCheck
def setSpellLang(self, theLang):
"""Set the project-specific spell check language.
"""
theLang = checkStringNone(theLang, None)
if self.projSpell != theLang:
self.projSpell = theLang
self.setProjectChanged(True)
return True
return False
def setProjectLang(self, theLang):
"""Set the project-specific language.
"""
@@ -970,25 +923,6 @@ class NWProject:
"""
return self._setStatusImport(newCols, delCols, self._data.itemImport)
def setAutoReplace(self, autoReplace):
"""Update the auto-replace dictionary.
"""
self.autoReplace = {}
for key, entry in autoReplace.items():
self.autoReplace[key] = simplified(entry)
self.setProjectChanged(True)
return True
def setTitleFormat(self, titleFormat):
"""Set the formatting of titles in the project.
"""
for valKey, valEntry in titleFormat.items():
if valKey in self.titleFormat:
self.titleFormat[valKey] = checkString(
simplified(valEntry), self.titleFormat[valKey]
)
return True
def setProjectChanged(self, bValue):
"""Toggle the project changed flag, and propagate the
information to the GUI statusbar.
+37
View File
@@ -54,6 +54,15 @@ class NWProjectData:
self._lastCount = {}
self._currCount = {}
self._autoReplace = {}
self._titleFormat = {
"title": "%title%",
"chapter": "%title%",
"unnumbered": "%title%",
"scene": "* * *",
"section": "",
}
self._status = NWStatus(NWStatus.STATUS)
self._import = NWStatus(NWStatus.IMPORT)
@@ -106,6 +115,14 @@ class NWProjectData:
def spellLang(self):
return self._spellLang
@property
def autoReplace(self):
return self._autoReplace
@property
def titleFormat(self):
return self._titleFormat
@property
def itemStatus(self):
return self._status
@@ -153,6 +170,9 @@ class NWProjectData:
def getCurrCount(self, type):
return self._currCount.get(type, 0)
def getTitleFormat(self, kind):
return self._titleFormat.get(kind, "%title%")
##
# Setters
##
@@ -231,4 +251,21 @@ class NWProjectData:
self._changed = True
return
def setAutoReplace(self, value):
if isinstance(value, dict):
self._autoReplace = {}
for key, entry in value.items():
if isinstance(entry, str):
self._autoReplace[key] = simplified(entry)
self._changed = True
return
def setTitleFormat(self, value):
if isinstance(value, dict):
for key, entry in value.items():
if key in self._titleFormat and isinstance(entry, str):
self._titleFormat[key] = simplified(entry)
self._changed = True
return
# END Class NWProjectData
+25 -29
View File
@@ -68,7 +68,6 @@ class ProjectXMLReader:
self._path = path
self._state = XMLReadState.NO_ACTION
self._data = {}
self._content = []
self._statusData = {}
self._statusMap = {}
@@ -85,10 +84,6 @@ class ProjectXMLReader:
# Properties
##
@property
def data(self):
return self._data
@property
def content(self):
return self._content
@@ -124,7 +119,6 @@ class ProjectXMLReader:
def read(self, projData):
"""Read and parse the project XML file.
"""
self._data = {}
self._content = []
try:
@@ -231,6 +225,7 @@ class ProjectXMLReader:
projData.setEditTime(xItem.text)
else:
logger.warning("Ignored <root/project/%s> in xml", xItem.tag)
return True
def _parseProjectSettings(self, xSection, projData):
@@ -238,9 +233,6 @@ class ProjectXMLReader:
"""
logger.debug("Parsing xml <root/settings>")
data = {}
autoReplace = {}
titleFormat = {}
for xItem in xSection:
if xItem.tag == "doBackup":
projData.setDoBackup(xItem.text)
@@ -270,22 +262,14 @@ class ProjectXMLReader:
self._parseStatusImport(xItem, projData.itemImport)
elif xItem.tag == "autoReplace":
if self._version >= 0x0102:
for xEntry in xItem:
if xEntry.tag == "entry" and "key" in xEntry.attrib:
autoReplace[xEntry.attrib["key"]] = checkString(xEntry.text, "ERROR")
projData.setAutoReplace(self._parseDictKeyText(xItem))
else: # Pre 1.2 format
for xEntry in xItem:
autoReplace[xEntry.tag] = checkString(xEntry.text, "ERROR")
projData.setAutoReplace(self._parseDictTagText(xItem))
elif xItem.tag == "titleFormat":
for xEntry in xItem:
titleFormat[xEntry.tag] = checkString(xEntry.text, "")
projData.setTitleFormat(self._parseDictTagText(xItem))
else:
logger.warning("Ignored <root/settings/%s> in xml", xItem.tag)
data["autoReplace"] = autoReplace
data["titleFormat"] = titleFormat
self._data["settings"] = data
return True
def _parseProjectContent(self, xSection):
@@ -293,7 +277,6 @@ class ProjectXMLReader:
"""
logger.debug("Parsing xml <root/content>")
data = []
for xItem in xSection:
if xItem.tag == "item":
item = {}
@@ -323,21 +306,20 @@ class ProjectXMLReader:
item["active"] = checkBool(xVal.attrib.get("exported", False), False)
else:
logger.warning("Ignored <root/content/item/%s> in xml", xVal.tag)
data.append(item)
self._content.append(item)
else:
logger.warning("Ignored item <root/content/%s> in xml", xItem.tag)
self._data["content"] = data
return True
def _parseProjectContentLegacy(self, xSection):
"""Parse the content section of the XML file for version before 1.4.
"""Parse the content section of the XML file for older version.
"""
logger.debug("Parsing xml <root/content> (legacy format)")
depLayout = ("TITLE", "PAGE", "BOOK", "PARTITION", "UNNUMBERED", "CHAPTER", "SCENE")
data = []
for xItem in xSection:
item = {}
if xItem.tag == "item":
@@ -388,13 +370,11 @@ class ProjectXMLReader:
if item.get("type", "") == "TRASH":
item["type"] = "ROOT"
data.append(item)
self._content.append(item)
else:
logger.warning("Ignored <root/content/%s> in xml", xItem.tag)
self._data["content"] = data
return True
def _parseStatusImport(self, xItem, sObject):
@@ -417,6 +397,22 @@ class ProjectXMLReader:
self._importMap = {entry["name"]: key for key, entry in projData.itemImport.items()}
return
def _parseDictKeyText(self, xItem):
"""Parse a dictionary stored with key as an attribute and the
value as the text porperty.
"""
result = {}
for xEntry in xItem:
if xEntry.tag == "entry" and "key" in xEntry.attrib:
result[xEntry.attrib["key"]] = checkString(xEntry.text, "")
return result
def _parseDictTagText(self, xItem):
"""Parse a dictionary stored with key as the tag and the value
as the text porperty.
"""
return {n.tag: checkString(n.text, "") for n in xItem}
# END Class ProjectXMLReader
+3 -2
View File
@@ -327,9 +327,10 @@ class Tokenizer(ABC):
"""Run trough the various replace doctionaries.
"""
# Process the user's auto-replace dictionary
if len(self.theProject.autoReplace) > 0:
autoReplace = self.theProject.data.autoReplace
if len(autoReplace) > 0:
repDict = {}
for aKey, aVal in self.theProject.autoReplace.items():
for aKey, aVal in autoReplace.items():
repDict[f"<{aKey}>"] = aVal
xRep = re.compile("|".join([re.escape(k) for k in repDict.keys()]), flags=re.DOTALL)
self._theText = xRep.sub(lambda x: repDict[x.group(0)], self._theText)
+5 -5
View File
@@ -120,7 +120,7 @@ class GuiProjectSettings(PagedDialog):
self.theProject.data.setDoBackup(doBackup)
# Remember this as updating spell dictionary can be expensive
self._spellChanged = self.theProject.setSpellLang(spellLang)
self._spellChanged = self.theProject.data.setSpellLang(spellLang)
if self.tabStatus.colChanged:
newList, delList = self.tabStatus.getNewList()
@@ -135,7 +135,7 @@ class GuiProjectSettings(PagedDialog):
if self.tabReplace.arChanged:
newList = self.tabReplace.getNewList()
self.theProject.setAutoReplace(newList)
self.theProject.data.setAutoReplace(newList)
self._saveGuiSettings()
self.accept()
@@ -252,8 +252,8 @@ class GuiProjectEditMain(QWidget):
)
spellIdx = 0
if self.theProject.projSpell is not None:
spellIdx = self.spellLang.findData(self.theProject.projSpell)
if self.theProject.data.spellLang is not None:
spellIdx = self.spellLang.findData(self.theProject.data.spellLang)
if spellIdx != -1:
self.spellLang.setCurrentIndex(spellIdx)
@@ -578,7 +578,7 @@ class GuiProjectEditReplace(QWidget):
self.listBox.setColumnWidth(self.COL_KEY, wCol0)
self.listBox.setIndentation(0)
for aKey, aVal in self.theProject.autoReplace.items():
for aKey, aVal in self.theProject.data.autoReplace.items():
newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
self.listBox.addTopLevelItem(newItem)
+3 -3
View File
@@ -684,10 +684,10 @@ class GuiDocEditor(QTextEdit):
"""Set the spell checker dictionary language, and emit the
dictionary changed signal.
"""
if self.theProject.projSpell is None:
if self.theProject.data.spellLang is None:
theLang = self.mainConf.spellLanguage
else:
theLang = self.theProject.projSpell
theLang = self.theProject.data.spellLang
self.spEnchant.setLanguage(theLang, self.theProject.projDict)
_, theProvider = self.spEnchant.describeDict()
@@ -721,7 +721,7 @@ class GuiDocEditor(QTextEdit):
self._spellCheck = theMode
self.mainGui.mainMenu.setSpellCheck(theMode)
self.theProject.setSpellCheck(theMode)
self.theProject.data.setSpellCheck(theMode)
self.highLight.setSpellCheck(theMode)
if not self._bigDoc:
self.spellCheckDocument()
+1 -1
View File
@@ -794,7 +794,7 @@ class GuiMainMenu(QMenuBar):
# Tools > Check Spelling
self.aSpellCheck = QAction(self.tr("Check Spelling"), self)
self.aSpellCheck.setCheckable(True)
self.aSpellCheck.setChecked(self.theProject.spellCheck)
self.aSpellCheck.setChecked(self.theProject.data.spellCheck)
self.aSpellCheck.triggered.connect(self._toggleSpellCheck) # triggered, not toggled!
self.aSpellCheck.setShortcut("Ctrl+F7")
self.toolsMenu.addAction(self.aSpellCheck)
+1 -1
View File
@@ -524,7 +524,7 @@ class GuiMain(QMainWindow):
self._updateWindowTitle(self.theProject.data.name)
self.rebuildTrees()
self.docEditor.setDictionaries()
self.docEditor.toggleSpellCheck(self.theProject.spellCheck)
self.docEditor.toggleSpellCheck(self.theProject.data.spellCheck)
self.mainStatus.setRefTime(self.theProject.projOpened)
self.projView.openProjectTasks()
self.novelView.openProjectTasks()
+6 -6
View File
@@ -126,7 +126,7 @@ class GuiBuildNovel(QDialog):
self.fmtTitle.setMinimumWidth(xFmt)
self.fmtTitle.setToolTip(fmtHelp)
self.fmtTitle.setText(
self._reFmtCodes(self.theProject.titleFormat["title"])
self._reFmtCodes(self.theProject.data.getTitleFormat("title"))
)
self.fmtChapter = QLineEdit()
@@ -134,7 +134,7 @@ class GuiBuildNovel(QDialog):
self.fmtChapter.setMinimumWidth(xFmt)
self.fmtChapter.setToolTip(fmtHelp)
self.fmtChapter.setText(
self._reFmtCodes(self.theProject.titleFormat["chapter"])
self._reFmtCodes(self.theProject.data.getTitleFormat("chapter"))
)
self.fmtUnnumbered = QLineEdit()
@@ -142,7 +142,7 @@ class GuiBuildNovel(QDialog):
self.fmtUnnumbered.setMinimumWidth(xFmt)
self.fmtUnnumbered.setToolTip(fmtHelp)
self.fmtUnnumbered.setText(
self._reFmtCodes(self.theProject.titleFormat["unnumbered"])
self._reFmtCodes(self.theProject.data.getTitleFormat("unnumbered"))
)
self.fmtScene = QLineEdit()
@@ -150,7 +150,7 @@ class GuiBuildNovel(QDialog):
self.fmtScene.setMinimumWidth(xFmt)
self.fmtScene.setToolTip(fmtHelp + fmtScHelp)
self.fmtScene.setText(
self._reFmtCodes(self.theProject.titleFormat["scene"])
self._reFmtCodes(self.theProject.data.getTitleFormat("scene"))
)
self.fmtSection = QLineEdit()
@@ -158,7 +158,7 @@ class GuiBuildNovel(QDialog):
self.fmtSection.setMinimumWidth(xFmt)
self.fmtSection.setToolTip(fmtHelp + fmtScHelp)
self.fmtSection.setText(
self._reFmtCodes(self.theProject.titleFormat["section"])
self._reFmtCodes(self.theProject.data.getTitleFormat("section"))
)
self.buildLang = QComboBox()
@@ -1159,7 +1159,7 @@ class GuiBuildNovel(QDialog):
logger.debug("Saving GuiBuildNovel settings")
# Formatting
self.theProject.setTitleFormat({
self.theProject.data.setTitleFormat({
"title": self.fmtTitle.text().strip(),
"chapter": self.fmtChapter.text().strip(),
"unnumbered": self.fmtUnnumbered.text().strip(),