Refactor project XML handling (#1221)

This commit is contained in:
Veronica Berglyd Olsen
2022-11-01 18:44:59 +01:00
committed by GitHub
67 changed files with 6967 additions and 2070 deletions
+1 -1
View File
@@ -75,7 +75,7 @@ class NWIndex:
return
def __repr__(self):
return f"<NWIndex project='{self.theProject.projName}'>"
return f"<NWIndex project='{self.theProject.data.name}'>"
##
# Properties
+82 -122
View File
@@ -25,8 +25,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging
from lxml import etree
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
from novelwriter.common import (
checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified
@@ -148,107 +146,70 @@ class NWItem:
return self._cursorPos
##
# XML Pack/Unpack
# Pack/Unpack Data
##
def packXML(self, xParent):
"""Pack all the data in the class instance into an XML object.
def pack(self):
"""Pack all the data in the class instance into a dictionary.
"""
itemAttrib = {}
itemAttrib["handle"] = str(self._handle)
itemAttrib["parent"] = str(self._parent)
itemAttrib["root"] = str(self._root)
itemAttrib["order"] = str(self._order)
itemAttrib["type"] = str(self._type.name)
itemAttrib["class"] = str(self._class.name)
item = {}
meta = {}
name = {}
item["handle"] = str(self._handle)
item["parent"] = str(self._parent)
item["root"] = str(self._root)
item["order"] = str(self._order)
item["type"] = str(self._type.name)
item["class"] = str(self._class.name)
meta["expanded"] = str(self._expanded)
name["status"] = str(self._status)
name["import"] = str(self._import)
if self._type == nwItemType.FILE:
itemAttrib["layout"] = str(self._layout.name)
item["layout"] = str(self._layout.name)
meta["heading"] = str(self._heading)
meta["charCount"] = str(self._charCount)
meta["wordCount"] = str(self._wordCount)
meta["paraCount"] = str(self._paraCount)
meta["cursorPos"] = str(self._cursorPos)
name["active"] = str(self._active)
metaAttrib = {}
metaAttrib["expanded"] = str(self._expanded)
if self._type == nwItemType.FILE:
metaAttrib["mainHeading"] = str(self._heading)
metaAttrib["charCount"] = str(self._charCount)
metaAttrib["wordCount"] = str(self._wordCount)
metaAttrib["paraCount"] = str(self._paraCount)
metaAttrib["cursorPos"] = str(self._cursorPos)
data = {
"name": str(self._name),
"itemAttr": item,
"metaAttr": meta,
"nameAttr": name,
}
nameAttrib = {}
nameAttrib["status"] = str(self._status)
nameAttrib["import"] = str(self._import)
if self._type == nwItemType.FILE:
nameAttrib["active"] = str(self._active)
return data
xPack = etree.SubElement(xParent, "item", attrib=itemAttrib)
self._subPack(xPack, "meta", attrib=metaAttrib)
self._subPack(xPack, "name", text=str(self._name), attrib=nameAttrib)
return
def unpackXML(self, xItem):
"""Set the values from an XML entry of type 'item'.
def unpack(self, data):
"""Set the values from a data dictionary.
"""
if xItem.tag != "item":
logger.error("XML entry is not an NWItem")
return False
if "handle" in xItem.attrib:
self.setHandle(xItem.attrib["handle"])
if "handle" in data:
self.setHandle(data["handle"])
else:
logger.error("XML item entry does not have a handle")
logger.error("Item does not have a handle")
return False
self.setParent(xItem.attrib.get("parent", None))
self.setRoot(xItem.attrib.get("root", None))
self.setOrder(xItem.attrib.get("order", 0))
self.setType(xItem.attrib.get("type", nwItemType.NO_TYPE))
self.setClass(xItem.attrib.get("class", nwItemClass.NO_CLASS))
self.setLayout(xItem.attrib.get("layout", nwItemLayout.NO_LAYOUT))
self.setName(data.get("label", ""))
self.setParent(data.get("parent", None))
self.setRoot(data.get("root", None))
self.setOrder(data.get("order", 0))
self.setType(data.get("type", nwItemType.NO_TYPE))
self.setClass(data.get("class", nwItemClass.NO_CLASS))
self.setLayout(data.get("layout", nwItemLayout.NO_LAYOUT))
for xValue in xItem:
if xValue.tag == "meta":
self.setExpanded(xValue.attrib.get("expanded", False))
self.setMainHeading(xValue.attrib.get("mainHeading", "H0"))
self.setCharCount(xValue.attrib.get("charCount", 0))
self.setWordCount(xValue.attrib.get("wordCount", 0))
self.setParaCount(xValue.attrib.get("paraCount", 0))
self.setCursorPos(xValue.attrib.get("cursorPos", 0))
elif xValue.tag == "name":
self.setName(xValue.text)
self.setStatus(xValue.attrib.get("status", None))
self.setImport(xValue.attrib.get("import", None))
self.setActive(xValue.attrib.get("active", True))
# ToDo: Remove before 2.0 release. Only needed for 2.0 pre-releases.
if "exported" in xValue.attrib:
self.setActive(xValue.attrib.get("exported", True))
# Legacy Format (1.3 and earlier)
elif xValue.tag == "status":
self.setImportStatus(xValue.text)
elif xValue.tag == "type":
self.setType(xValue.text)
elif xValue.tag == "class":
self.setClass(xValue.text)
elif xValue.tag == "layout":
self.setLayout(xValue.text)
elif xValue.tag == "expanded":
self.setExpanded(xValue.text)
elif xValue.tag == "exported":
self.setActive(xValue.text)
elif xValue.tag == "charCount":
self.setCharCount(xValue.text)
elif xValue.tag == "wordCount":
self.setWordCount(xValue.text)
elif xValue.tag == "paraCount":
self.setParaCount(xValue.text)
elif xValue.tag == "cursorPos":
self.setCursorPos(xValue.text)
else:
# Sliently skip as we may otherwise cause orphaned
# items if an otherwise valid file is opened by a
# version of novelWriter that doesn't know the tag
logger.error("Unknown tag '%s'", xValue.tag)
self.setExpanded(data.get("expanded", False))
self.setStatus(data.get("status", None))
self.setImport(data.get("import", None))
self.setMainHeading(data.get("heading", "H0"))
self.setCharCount(data.get("charCount", 0))
self.setWordCount(data.get("wordCount", 0))
self.setParaCount(data.get("paraCount", 0))
self.setCursorPos(data.get("cursorPos", 0))
self.setActive(data.get("active", True))
# Make some checks to ensure consistency
if self._type == nwItemType.ROOT:
@@ -256,26 +217,17 @@ class NWItem:
self._parent = None # Root items cannot have a parent
if self._type != nwItemType.FILE:
self._charCount = 0 # Only set for files
self._wordCount = 0 # Only set for files
self._paraCount = 0 # Only set for files
self._cursorPos = 0 # Only set for files
# Reset values that should only be set for files
self._layout = nwItemLayout.NO_LAYOUT
self._heading = "H0"
self._active = False
self._charCount = 0
self._wordCount = 0
self._paraCount = 0
self._cursorPos = 0
return True
@staticmethod
def _subPack(xParent, name, attrib=None, text=None, none=True):
"""Pack the values into an XML element.
"""
if not none and (text is None or text == "None"):
return None
xAttr = {} if attrib is None else attrib
xSub = etree.SubElement(xParent, name, attrib=xAttr)
if text is not None:
xSub.text = text
return
##
# Lookup Methods
##
@@ -310,11 +262,11 @@ class NWItem:
the current item based on its class.
"""
if self.isNovelLike():
stName = self.theProject.statusItems.name(self._status)
stIcon = self.theProject.statusItems.icon(self._status) if incIcon else None
stName = self.theProject.data.itemStatus.name(self._status)
stIcon = self.theProject.data.itemStatus.icon(self._status) if incIcon else None
else:
stName = self.theProject.importItems.name(self._import)
stIcon = self.theProject.importItems.icon(self._import) if incIcon else None
stName = self.theProject.data.itemImport.name(self._import)
stIcon = self.theProject.data.itemImport.icon(self._import) if incIcon else None
return stName, stIcon
##
@@ -451,8 +403,6 @@ class NWItem:
self._type = value
elif isItemType(value):
self._type = nwItemType[value]
elif value == "TRASH":
self._type = nwItemType.ROOT
else:
logger.error("Unrecognised item type '%s'", value)
self._type = nwItemType.NO_TYPE
@@ -479,8 +429,6 @@ class NWItem:
self._layout = value
elif isItemLayout(value):
self._layout = nwItemLayout[value]
elif value in ("TITLE", "PAGE", "BOOK", "PARTITION", "UNNUMBERED", "CHAPTER", "SCENE"):
self._layout = nwItemLayout.DOCUMENT
else:
logger.error("Unrecognised item layout '%s'", value)
self._layout = nwItemLayout.NO_LAYOUT
@@ -490,14 +438,14 @@ class NWItem:
"""Set the item status by looking it up in the valid status
items of the current project.
"""
self._status = self.theProject.statusItems.check(value)
self._status = self.theProject.data.itemStatus.check(value)
return
def setImport(self, value):
"""Set the item importance by looking it up in the valid import
items of the current project.
"""
self._import = self.theProject.importItems.check(value)
self._import = self.theProject.data.itemImport.check(value)
return
def setActive(self, state):
@@ -532,25 +480,37 @@ class NWItem:
def setCharCount(self, count):
"""Set the character count, and ensure that it is an integer.
"""
self._charCount = max(0, checkInt(count, 0))
if isinstance(count, int):
self._charCount = max(0, count)
else:
self._charCount = 0
return
def setWordCount(self, count):
"""Set the word count, and ensure that it is an integer.
"""
self._wordCount = max(0, checkInt(count, 0))
if isinstance(count, int):
self._wordCount = max(0, count)
else:
self._wordCount = 0
return
def setParaCount(self, count):
"""Set the paragraph count, and ensure that it is an integer.
"""
self._paraCount = max(0, checkInt(count, 0))
if isinstance(count, int):
self._paraCount = max(0, count)
else:
self._paraCount = 0
return
def setCursorPos(self, position):
"""Set the cursor position, and ensure that it is an integer.
"""
self._cursorPos = max(0, checkInt(position, 0))
if isinstance(position, int):
self._cursorPos = max(0, position)
else:
self._cursorPos = 0
return
def saveInitialCount(self):
File diff suppressed because it is too large Load Diff
+557
View File
@@ -0,0 +1,557 @@
"""
novelWriter Project XML Read/Write
====================================
Classes for reading and writing the project XML file
File History:
Created: 2022-09-28 [2.0rc1] ProjectXMLReader
Created: 2022-09-28 [2.0rc1] XMLReadState
This file is a part of novelWriter
Copyright 20182022, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
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 os
import logging
import novelwriter
from enum import Enum
from lxml import etree
from time import time
from novelwriter.common import (
checkBool, checkInt, checkStringNone, formatTimeStamp, simplified, checkString
)
from novelwriter.constants import nwFiles
logger = logging.getLogger(__name__)
FILE_VERSION = "1.4" # The current project file format version
NUM_VERSION = {
"1.0": 0x0100,
"1.1": 0x0101,
"1.2": 0x0102,
"1.3": 0x0103,
"1.4": 0x0104,
}
class XMLReadState(Enum):
NO_ACTION = 0
NO_ERROR = 1
PARSED_BACKUP = 2
CANNOT_PARSE = 3
NOT_NWX_FILE = 4
UNKNOWN_VERSION = 5
PARSED_OK = 6
WAS_LEGACY = 7
# END Class XMLReadState
class ProjectXMLReader:
"""The main project XML file reader class. All data is read into a
NWProjectData instance, which must be provided.
Version Change History
======================
1.0 Original file format.
1.1 Changes the way documents are structured in the project folder
from data_X, where X is the first hex value of the handle, to a
single content folder. Introduced in version 0.7.
1.2 Changes the way autoReplace entries are stored. The 1.1 parser
will lose the autoReplace settings if allowed to read the file.
Introduced in version 0.10.
1.3 Reduces the number of layouts to only two. One for novel
documents and one for project notes. Introduced in version 1.5.
1.4 Introduces a more compact format for storing items. All settings
aside from name are now attributes. This format also changes the
way satus and importance labels are stored and handled.
Introduced in version 2.0.
"""
def __init__(self, path):
self._path = path
self._state = XMLReadState.NO_ACTION
self._root = ""
self._version = 0x0000
self._appVersion = ""
self._hexVersion = ""
self._timeStamp = ""
return
##
# Properties
##
@property
def state(self):
"""The state of the parsing as an XMLReadState enum value.
"""
return self._state
@property
def xmlRoot(self):
"""The root tag name of the XNL file,
"""
return self._root
@property
def xmlVersion(self):
"""The project XML version number.
"""
return self._version
@property
def appVersion(self):
"""The novelWriter version number who wrote the file.
"""
return self._appVersion
@property
def hexVersion(self):
"""The novelWriter version number who wrote the file as hex.
"""
return self._hexVersion
@property
def timeStamp(self):
"""The date and time when the file was written.
"""
return self._timeStamp
##
# Methods
##
def read(self, projData, projContent):
"""Read and parse the project XML file.
"""
tStart = time()
logger.debug("Reading project XML")
try:
xml = etree.parse(self._path)
self._state = XMLReadState.NO_ERROR
except Exception as exc:
# Trying to open backup file instead
logger.error("Failed to parse project xml", exc_info=exc)
self._state = XMLReadState.CANNOT_PARSE
backFile = self._path[:-3]+"bak"
if os.path.isfile(backFile):
try:
xml = etree.parse(backFile)
self._state = XMLReadState.PARSED_BACKUP
logger.info("Backup project file parsed")
except Exception as exc:
logger.error("Failed to parse backup project xml", exc_info=exc)
self._state = XMLReadState.CANNOT_PARSE
return False
else:
self._state = XMLReadState.CANNOT_PARSE
return False
xRoot = xml.getroot()
self._root = str(xRoot.tag)
if self._root != "novelWriterXML":
self._state = XMLReadState.NOT_NWX_FILE
return False
fileVersion = str(xRoot.attrib.get("fileVersion", ""))
if fileVersion in NUM_VERSION:
self._version = NUM_VERSION[fileVersion]
else:
self._state = XMLReadState.UNKNOWN_VERSION
return False
logger.debug("XML is '%s' version '%s'", self._root, fileVersion)
self._appVersion = str(xRoot.attrib.get("appVersion", ""))
self._hexVersion = str(xRoot.attrib.get("hexVersion", ""))
self._timeStamp = str(xRoot.attrib.get("timeStamp", ""))
for xSection in xRoot:
if xSection.tag == "project":
self._parseProjectMeta(xSection, projData)
elif xSection.tag == "settings":
self._parseProjectSettings(xSection, projData)
elif xSection.tag == "content":
if self._version >= 0x0104:
self._parseProjectContent(xSection, projContent)
else:
self._parseProjectContentLegacy(xSection, projContent, projData)
else:
logger.warning("Ignored <root/%s> in xml", xSection.tag)
if self._version == 0x0104:
self._state = XMLReadState.PARSED_OK
else:
self._state = XMLReadState.WAS_LEGACY
logger.debug("Project XML loaded in %.3f ms", (time() - tStart)*1000)
return True
##
# Internal Functions
##
def _parseProjectMeta(self, xSection, projData):
"""Parse the project section of the XML file.
"""
logger.debug("Parsing <project> section")
for xItem in xSection:
if xItem.tag == "name":
projData.setName(xItem.text)
elif xItem.tag == "title":
projData.setTitle(xItem.text)
elif xItem.tag == "author":
projData.addAuthor(xItem.text)
elif xItem.tag == "saveCount":
projData.setSaveCount(xItem.text)
elif xItem.tag == "autoCount":
projData.setAutoCount(xItem.text)
elif xItem.tag == "editTime":
projData.setEditTime(xItem.text)
else:
logger.warning("Ignored <root/project/%s> in xml", xItem.tag)
return
def _parseProjectSettings(self, xSection, projData):
"""Parse the settings section of the XML file.
"""
logger.debug("Parsing <settings> section")
for xItem in xSection:
if xItem.tag == "doBackup":
projData.setDoBackup(xItem.text)
elif xItem.tag == "language":
projData.setLanguage(xItem.text)
elif xItem.tag == "spellCheck":
projData.setSpellCheck(xItem.text)
elif xItem.tag == "spellLang":
projData.setSpellLang(xItem.text)
elif xItem.tag == "novelWordCount":
projData.setInitCounts(novel=xItem.text)
elif xItem.tag == "notesWordCount":
projData.setInitCounts(notes=xItem.text)
elif xItem.tag == "status":
self._parseStatusImport(xItem, projData.itemStatus)
elif xItem.tag in ("import", "importance"):
self._parseStatusImport(xItem, projData.itemImport)
elif xItem.tag == "lastHandle":
projData.setLastHandle(self._parseDictKeyText(xItem))
elif xItem.tag == "autoReplace":
if self._version >= 0x0102:
projData.setAutoReplace(self._parseDictKeyText(xItem))
else: # Pre 1.2 format
projData.setAutoReplace(self._parseDictTagText(xItem))
elif xItem.tag == "titleFormat":
if self._version >= 0x0104:
projData.setTitleFormat(self._parseDictKeyText(xItem))
else: # Pre 1.4 format
projData.setTitleFormat(self._parseDictTagText(xItem))
else:
logger.warning("Ignored <root/settings/%s> in xml", xItem.tag)
return
def _parseProjectContent(self, xSection, projContent):
"""Parse the content section of the XML file.
"""
logger.debug("Parsing <content> section")
for xItem in xSection:
if xItem.tag == "item":
item = {}
item["handle"] = checkStringNone(xItem.attrib.get("handle"), None)
item["parent"] = checkStringNone(xItem.attrib.get("parent"), None)
item["root"] = checkStringNone(xItem.attrib.get("root"), None)
item["order"] = checkInt(xItem.attrib.get("order"), 0)
item["type"] = checkString(xItem.attrib.get("type"), "NO_TYPE")
item["class"] = checkString(xItem.attrib.get("class"), "NO_CLASS")
item["layout"] = checkString(xItem.attrib.get("layout"), "NO_LAYOUT")
for xVal in xItem:
if xVal.tag == "meta":
item["expanded"] = checkBool(xVal.attrib.get("expanded"), False)
item["heading"] = checkString(xVal.attrib.get("heading"), "H0")
item["charCount"] = checkInt(xVal.attrib.get("charCount"), 0)
item["wordCount"] = checkInt(xVal.attrib.get("wordCount"), 0)
item["paraCount"] = checkInt(xVal.attrib.get("paraCount"), 0)
item["cursorPos"] = checkInt(xVal.attrib.get("cursorPos"), 0)
elif xVal.tag == "name":
item["label"] = simplified(checkString(xVal.text, ""))
item["status"] = checkStringNone(xVal.attrib.get("status"), None)
item["import"] = checkStringNone(xVal.attrib.get("import"), None)
item["active"] = checkBool(xVal.attrib.get("active"), False)
# ToDo: Remove before 2.0 release. Only needed for 2.0 pre-releases.
if "exported" in xVal.attrib:
item["active"] = checkBool(xVal.attrib.get("exported"), False)
else:
logger.warning("Ignored <root/content/item/%s> in xml", xVal.tag)
projContent.append(item)
else:
logger.warning("Ignored item <root/content/%s> in xml", xItem.tag)
return
def _parseProjectContentLegacy(self, xSection, projContent, projData):
"""Parse the content section of the XML file for older versions.
"""
logger.debug("Parsing <content> section (legacy format)")
# Create maps to look up name -> key for status and importance
statusMap = {entry["name"]: key for key, entry in projData.itemStatus.items()}
importMap = {entry["name"]: key for key, entry in projData.itemImport.items()}
for xItem in xSection:
item = {}
if xItem.tag == "item":
item["handle"] = checkStringNone(xItem.attrib.get("handle", None), None)
item["parent"] = checkStringNone(xItem.attrib.get("parent", None), None)
item["root"] = None # Value was added in 1.4
item["order"] = checkInt(xItem.attrib.get("order", 0), 0)
item["heading"] = "H0" # Value was added in 1.4
tmpStatus = ""
for xVal in xItem:
if xVal.tag == "name":
item["label"] = simplified(checkString(xVal.text, ""))
elif xVal.tag == "status":
tmpStatus = checkStringNone(xVal.text, None)
elif xVal.tag == "type":
item["type"] = checkString(xVal.text, "")
elif xVal.tag == "class":
item["class"] = checkString(xVal.text, "")
elif xVal.tag == "layout":
item["layout"] = checkString(xVal.text, "")
elif xVal.tag == "expanded":
item["expanded"] = checkBool(xVal.text, False)
elif xVal.tag == "exported": # Renamed to active in 1.4
item["active"] = checkBool(xVal.text, False)
elif xVal.tag == "charCount":
item["charCount"] = checkInt(xVal.text, 0)
elif xVal.tag == "wordCount":
item["wordCount"] = checkInt(xVal.text, 0)
elif xVal.tag == "paraCount":
item["paraCount"] = checkInt(xVal.text, 0)
elif xVal.tag == "cursorPos":
item["cursorPos"] = checkInt(xVal.text, 0)
else:
logger.warning("Ignored <root/content/item/%s> in xml", xVal.tag)
# Status was split into separate status/import with a key in 1.4
if item.get("class", "") in ("NOVEL", "ARCHIVE"):
item["status"] = statusMap.get(tmpStatus, None)
else:
item["import"] = importMap.get(tmpStatus, None)
# A number of layouts were removed in 1.3
if item.get("layout", "") in (
"TITLE", "PAGE", "BOOK", "PARTITION", "UNNUMBERED", "CHAPTER", "SCENE"
):
item["layout"] = "DOCUMENT"
# The trast type was removed in 1.4
if item.get("type", "") == "TRASH":
item["type"] = "ROOT"
projContent.append(item)
else:
logger.warning("Ignored <root/content/%s> in xml", xItem.tag)
return
def _parseStatusImport(self, xItem, sObject):
"""Parse a status or importance entry.
"""
for xEntry in xItem:
if xEntry.tag == "entry":
key = xEntry.attrib.get("key", None)
red = checkInt(xEntry.attrib.get("red", 0), 0)
green = checkInt(xEntry.attrib.get("green", 0), 0)
blue = checkInt(xEntry.attrib.get("blue", 0), 0)
count = checkInt(xEntry.attrib.get("count", 0), 0)
sObject.write(key, xEntry.text, (red, green, blue), count)
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
class ProjectXMLWriter:
def __init__(self, path):
self._path = path
self._error = None
return
##
# Properties
##
@property
def error(self):
return self._error
##
# Methods
##
def write(self, projData, projContent, saveTime, editTime):
"""Write the project data and content to the XML files.
"""
tStart = time()
logger.debug("Writing project XML")
xRoot = etree.Element("novelWriterXML", attrib={
"appVersion": str(novelwriter.__version__),
"hexVersion": str(novelwriter.__hexversion__),
"fileVersion": FILE_VERSION,
"timeStamp": formatTimeStamp(saveTime),
})
# Save Project Meta
xProject = etree.SubElement(xRoot, "project")
self._packSingleValue(xProject, "name", projData.name)
self._packSingleValue(xProject, "title", projData.title)
self._packListValue(xProject, "author", projData.authors)
self._packSingleValue(xProject, "saveCount", projData.saveCount)
self._packSingleValue(xProject, "autoCount", projData.autoCount)
self._packSingleValue(xProject, "editTime", editTime)
# Save Project Settings
xSettings = etree.SubElement(xRoot, "settings")
self._packSingleValue(xSettings, "doBackup", projData.doBackup)
self._packSingleValue(xSettings, "language", projData.language)
self._packSingleValue(xSettings, "spellCheck", projData.spellCheck)
self._packSingleValue(xSettings, "spellLang", projData.spellLang)
self._packSingleValue(xSettings, "novelWordCount", projData.currCounts[0])
self._packSingleValue(xSettings, "notesWordCount", projData.currCounts[1])
self._packDictKeyValue(xSettings, "lastHandle", projData.lastHandle)
self._packDictKeyValue(xSettings, "autoReplace", projData.autoReplace)
self._packDictKeyValue(xSettings, "titleFormat", projData.titleFormat)
# Save Status/Importance
xStatus = etree.SubElement(xSettings, "status")
for label, attrib in projData.itemStatus.pack():
self._packSingleValue(xStatus, "entry", label, attrib=attrib)
xImport = etree.SubElement(xSettings, "importance")
for label, attrib in projData.itemImport.pack():
self._packSingleValue(xImport, "entry", label, attrib=attrib)
# Save Tree Content
xContent = etree.SubElement(xRoot, "content", attrib={"count": str(len(projContent))})
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", {}))
xName.text = item["name"]
# Write the xml tree to file
saveFile = os.path.join(self._path, nwFiles.PROJ_FILE)
tempFile = os.path.join(self._path, nwFiles.PROJ_FILE+"~")
backFile = os.path.join(self._path, nwFiles.PROJ_FILE[:-3]+"bak")
try:
with open(tempFile, mode="wb") as outFile:
outFile.write(etree.tostring(
xRoot,
pretty_print=True,
encoding="utf-8",
xml_declaration=True
))
except Exception as exc:
self._error = exc
return False
# If we're here, the file was successfully saved,
# so let's sort out the temps and backups
try:
if os.path.isfile(saveFile):
os.replace(saveFile, backFile)
os.replace(tempFile, saveFile)
except OSError as exc:
self._error = exc
return False
logger.debug("Project XML saved in %.3f ms", (time() - tStart)*1000)
return True
##
# Internal Functions
##
def _packSingleValue(self, xParent, name, value, attrib=None):
"""Pack a single value into an xml element.
"""
xItem = etree.SubElement(xParent, name, attrib=attrib)
xItem.text = str(value) or ""
return
def _packListValue(self, xParent, name, data):
"""Pack a list of values into an xml element.
"""
for value in data:
xItem = etree.SubElement(xParent, name)
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)
for key, value in data.items():
if len(key) > 0:
xEntry = etree.SubElement(xItem, "entry", attrib={"key": key})
xEntry.text = str(value) or ""
return
# END Class ProjectXMLWriter
+21 -32
View File
@@ -28,12 +28,10 @@ import random
import logging
import novelwriter
from lxml import etree
from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor
from PyQt5.QtCore import QRectF, Qt
from novelwriter.common import checkInt, minmax, simplified
from novelwriter.common import minmax, simplified
logger = logging.getLogger(__name__)
@@ -47,7 +45,6 @@ class NWStatus:
self._type = type
self._store = {}
self._reverse = {}
self._default = None
self._iPX = novelwriter.CONFIG.pxInt(24)
@@ -58,7 +55,7 @@ class NWStatus:
self._iconPath = QPainterPath()
self._iconPath.addRoundedRect(QRectF(pA, pA, pB, pB), pR, pR)
self._defaultIcon = self._createIcon([100, 100, 100])
self._defaultIcon = self._createIcon(100, 100, 100)
if self._type == self.STATUS:
self._prefix = "s"
@@ -80,17 +77,19 @@ class NWStatus:
if len(col) != 3:
col = (100, 100, 100)
cR = minmax(col[0], 0, 255)
cG = minmax(col[1], 0, 255)
cB = minmax(col[2], 0, 255)
name = simplified(name)
if count is None:
count = self._store[key]["count"] if key in self._store else 0
count = self._store.get(key, {}).get("count", 0)
self._store[key] = {
"name": name,
"icon": self._createIcon(col),
"cols": col,
"icon": self._createIcon(cR, cG, cB),
"cols": (cR, cG, cB),
"count": count,
}
self._reverse[name] = key
if self._default is None:
self._default = key
@@ -106,7 +105,6 @@ class NWStatus:
if self._store[key]["count"] > 0:
return False
del self._reverse[self._store[key]["name"]]
del self._store[key]
keys = list(self._store.keys())
@@ -123,8 +121,6 @@ class NWStatus:
"""
if self._isKey(value) and value in self._store:
return value
elif value in self._reverse:
return self._reverse[value]
elif self._default is not None:
return self._default
else:
@@ -206,37 +202,30 @@ class NWStatus:
self._store[key]["count"] += 1
return
def packXML(self, xParent):
"""Pack the status entries into an XML object for saving to the
main project file.
def pack(self):
"""Pack the status entries into a dictionary.
"""
for key, data in self._store.items():
xSub = etree.SubElement(xParent, "entry", attrib={
yield (data["name"], {
"key": key,
"count": str(data["count"]),
"red": str(data["cols"][0]),
"green": str(data["cols"][1]),
"blue": str(data["cols"][2]),
})
xSub.text = data["name"]
return
return True
def unpackXML(self, xParent):
"""Unpack an XML tree and set the class values.
def unpack(self, data):
"""Unpack a data dictionary and set the class values.
"""
self._store = {}
self._reverse = {}
self._default = None
for xChild in xParent:
key = xChild.attrib.get("key", None)
name = xChild.text.strip()
count = max(checkInt(xChild.attrib.get("count", 0), 0), 0)
red = minmax(checkInt(xChild.attrib.get("red", 100), 100), 0, 255)
green = minmax(checkInt(xChild.attrib.get("green", 100), 100), 0, 255)
blue = minmax(checkInt(xChild.attrib.get("blue", 100), 100), 0, 255)
self.write(key, name, (red, green, blue), count)
for key, entry in data.items():
label = entry.get("label", "")
colour = entry.get("colour", (100, 100, 100))
count = entry.get("count", 0)
self.write(key, label, colour, count)
return True
@@ -270,7 +259,7 @@ class NWStatus:
return False
return True
def _createIcon(self, col):
def _createIcon(self, red, green, blue):
"""Generate an icon for a status label.
"""
pixmap = QPixmap(self._iPX, self._iPX)
@@ -278,7 +267,7 @@ class NWStatus:
painter = QPainter(pixmap)
painter.setRenderHint(QPainter.Antialiasing)
painter.fillPath(self._iconPath, QColor(*col))
painter.fillPath(self._iconPath, QColor(red, green, blue))
painter.end()
return QIcon(pixmap)
+1 -1
View File
@@ -315,7 +315,7 @@ class ToHtml(Tokenizer):
"</body>\n"
"</html>\n"
).format(
projTitle=self.theProject.projName,
projTitle=self.theProject.data.name,
htmlStyle="\n".join(theStyle),
bodyText=bodyText,
)
+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)
+2 -2
View File
@@ -261,8 +261,8 @@ class ToOdt(Tokenizer):
# ===============
if self._headerText == "":
theTitle = self.theProject.bookTitle
theAuth = self.theProject.getAuthors()
theTitle = self.theProject.data.title
theAuth = self.theProject.getFormattedAuthors()
self._headerText = f"{theTitle} / {theAuth} /"
# Create Roots
+10 -17
View File
@@ -27,8 +27,6 @@ import os
import random
import logging
from lxml import etree
from novelwriter.enum import nwItemClass, nwItemLayout
from novelwriter.error import logException
from novelwriter.common import checkHandle
@@ -114,30 +112,25 @@ class NWTree:
return True
def packXML(self, xParent):
def pack(self):
"""Pack the content of the tree into the provided XML object. In
the order defined by the _treeOrder list.
"""
xContent = etree.SubElement(xParent, "content", attrib={
"count": str(len(self._treeOrder))}
)
tree = []
for tHandle in self._treeOrder:
tItem = self.__getitem__(tHandle)
tItem.packXML(xContent)
return
if tItem:
tree.append(tItem.pack())
return tree
def unpackXML(self, xContent):
"""Iterate through all items of a content XML object and add
them to the project tree.
def unpack(self, data):
"""Iterate through all items of a list and add them to the
project tree.
"""
if xContent.tag != "content":
logger.error("XML entry is not a NWTree")
return False
self.clear()
for xItem in xContent:
for item in data:
nwItem = NWItem(self.theProject)
if nwItem.unpackXML(xItem):
if nwItem.unpack(item):
self.append(nwItem.itemHandle, nwItem.itemParent, nwItem)
nwItem.saveInitialCount()
+6 -4
View File
@@ -157,7 +157,7 @@ class GuiProjectDetailsMain(QWidget):
# Header
# ======
self.bookTitle = QLabel(self.theProject.bookTitle)
self.bookTitle = QLabel(self.theProject.data.title)
bookFont = self.bookTitle.font()
bookFont.setPointSizeF(2.2*fPt)
bookFont.setWeight(QFont.Bold)
@@ -166,7 +166,7 @@ class GuiProjectDetailsMain(QWidget):
self.bookTitle.setWordWrap(True)
self.projName = QLabel(
self.tr("Working Title: {0}").format(self.theProject.projName)
self.tr("Working Title: {0}").format(self.theProject.data.name)
)
workFont = self.projName.font()
workFont.setPointSizeF(0.8*fPt)
@@ -175,7 +175,9 @@ class GuiProjectDetailsMain(QWidget):
self.projName.setAlignment(Qt.AlignHCenter)
self.projName.setWordWrap(True)
self.bookAuthors = QLabel(self.tr("By {0}").format(self.theProject.getAuthors()))
self.bookAuthors = QLabel(self.tr("By {0}").format(
self.theProject.getFormattedAuthors()
))
authFont = self.bookAuthors.font()
authFont.setPointSizeF(1.2*fPt)
self.bookAuthors.setFont(authFont)
@@ -255,7 +257,7 @@ class GuiProjectDetailsMain(QWidget):
self.wordCountVal.setText(f"{nwCount:n}")
self.chapCountVal.setText(f"{hCounts[2]:n}")
self.sceneCountVal.setText(f"{hCounts[3]:n}")
self.revCountVal.setText(f"{self.theProject.saveCount:n}")
self.revCountVal.setText(f"{self.theProject.data.saveCount:n}")
self.editTimeVal.setText(f"{edTime//3600:02d}:{edTime%3600//60:02d}")
self.projPathVal.setText(self.theProject.projPath)
+15 -15
View File
@@ -114,13 +114,13 @@ class GuiProjectSettings(PagedDialog):
spellLang = self.tabMain.spellLang.currentData()
doBackup = not self.tabMain.doBackup.isChecked()
self.theProject.setProjectName(projName)
self.theProject.setBookTitle(bookTitle)
self.theProject.setBookAuthors(bookAuthors)
self.theProject.setProjBackup(doBackup)
self.theProject.data.setName(projName)
self.theProject.data.setTitle(bookTitle)
self.theProject.data.setAuthors(bookAuthors)
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()
@@ -209,7 +209,7 @@ class GuiProjectEditMain(QWidget):
self.editName = QLineEdit()
self.editName.setMaxLength(200)
self.editName.setMaximumWidth(xW)
self.editName.setText(self.theProject.projName)
self.editName.setText(self.theProject.data.name)
self.mainForm.addRow(
self.tr("Project name"),
self.editName,
@@ -219,7 +219,7 @@ class GuiProjectEditMain(QWidget):
self.editTitle = QLineEdit()
self.editTitle.setMaxLength(200)
self.editTitle.setMaximumWidth(xW)
self.editTitle.setText(self.theProject.bookTitle)
self.editTitle.setText(self.theProject.data.title)
self.mainForm.addRow(
self.tr("Novel title"),
self.editTitle,
@@ -229,7 +229,7 @@ class GuiProjectEditMain(QWidget):
self.editAuthors = QPlainTextEdit()
self.editAuthors.setMaximumHeight(xH)
self.editAuthors.setMaximumWidth(xW)
self.editAuthors.setPlainText("\n".join(self.theProject.bookAuthors))
self.editAuthors.setPlainText("\n".join(self.theProject.data.authors))
self.mainForm.addRow(
self.tr("Author(s)"),
self.editAuthors,
@@ -252,13 +252,13 @@ 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)
self.doBackup = QSwitch(self)
self.doBackup.setChecked(not self.theProject.doBackup)
self.doBackup.setChecked(not self.theProject.data.doBackup)
self.mainForm.addRow(
self.tr("No backup on close"),
self.doBackup,
@@ -288,11 +288,11 @@ class GuiProjectEditStatus(QWidget):
self.mainTheme = projGui.mainGui.mainTheme
if isStatus:
self.theStatus = self.theProject.statusItems
self.theStatus = self.theProject.data.itemStatus
pageLabel = self.tr("Novel File Status Levels")
colSetting = "statusColW"
else:
self.theStatus = self.theProject.importItems
self.theStatus = self.theProject.data.itemImport
pageLabel = self.tr("Note File Importance Levels")
colSetting = "importColW"
@@ -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
@@ -217,7 +217,7 @@ class GuiDocViewer(QTextBrowser):
self.verticalScrollBar().setValue(sPos)
self._docHandle = tHandle
self.theProject.setLastViewed(tHandle)
self.theProject._data.setLastHandle(tHandle, "viewer")
self.docHeader.setTitleFromHandle(self._docHandle)
self.updateDocMargins()
+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)
+6 -5
View File
@@ -109,7 +109,7 @@ class GuiNovelView(QWidget):
def refreshTree(self):
"""Refresh the current tree.
"""
self.novelTree.refreshTree(rootHandle=self.theProject.lastNovel)
self.novelTree.refreshTree(rootHandle=self.theProject.data.getLastHandle("novelTree"))
return
def clearProject(self):
@@ -122,7 +122,7 @@ class GuiNovelView(QWidget):
def openProjectTasks(self):
"""Run open project tasks.
"""
lastNovel = self.theProject.lastNovel
lastNovel = self.theProject.data.getLastHandle("novelTree")
if lastNovel not in self.theProject.tree:
lastNovel = self.theProject.tree.findRoot(nwItemClass.NOVEL)
@@ -319,7 +319,7 @@ class GuiNovelToolBar(QWidget):
def _refreshNovelTree(self):
"""Rebuild the current tree.
"""
rootHandle = self.theProject.lastNovel
rootHandle = self.theProject.data.getLastHandle("novelTree")
self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True)
return
@@ -485,7 +485,7 @@ class GuiNovelTree(QTreeWidget):
titleKey = selItem[0].data(self.C_TITLE, self.D_KEY)
self._populateTree(rootHandle)
self.theProject.setLastNovelViewed(rootHandle)
self.theProject.data.setLastHandle(rootHandle, "novelTree")
if titleKey is not None and titleKey in self._treeMap:
self._treeMap[titleKey].setSelected(True)
@@ -523,7 +523,8 @@ class GuiNovelTree(QTreeWidget):
self._lastCol = colType
self.setColumnHidden(self.C_EXTRA, colType == NovelTreeColumn.HIDDEN)
if doRefresh:
self.refreshTree(rootHandle=self.theProject.lastNovel, overRide=True)
lastNovel = self.theProject.data.getLastHandle("novelTree")
self.refreshTree(rootHandle=lastNovel, overRide=True)
return
def setActiveHandle(self, tHandle):
+3 -3
View File
@@ -114,7 +114,7 @@ class GuiOutlineView(QWidget):
def refreshTree(self):
"""Refresh the current tree.
"""
self.outlineTree.refreshTree(rootHandle=self.theProject.lastOutline)
self.outlineTree.refreshTree(rootHandle=self.theProject.data.getLastHandle("outline"))
return
def clearProject(self):
@@ -126,7 +126,7 @@ class GuiOutlineView(QWidget):
def openProjectTasks(self):
"""Run open project tasks.
"""
lastOutline = self.theProject.lastOutline
lastOutline = self.theProject.data.getLastHandle("outline")
if not (lastOutline in self.theProject.tree or lastOutline is None):
lastOutline = self.theProject.tree.findRoot(nwItemClass.NOVEL)
@@ -504,7 +504,7 @@ class GuiOutlineTree(QTreeWidget):
return
self._populateTree(rootHandle)
self.theProject.setLastOutlineViewed(rootHandle or None)
self.theProject.data.setLastHandle(rootHandle or None, "outline")
return
+2 -2
View File
@@ -1208,7 +1208,7 @@ class GuiProjectTree(QTreeWidget):
checkMark = f" ({nwUnicode.U_CHECK})"
if tItem.isNovelLike():
mStatus = ctxMenu.addMenu(self.tr("Set Status to ..."))
for n, (key, entry) in enumerate(self.theProject.statusItems.items()):
for n, (key, entry) in enumerate(self.theProject.data.itemStatus.items()):
entryName = entry["name"] + (checkMark if tItem.itemStatus == key else "")
aStatus = mStatus.addAction(entry["icon"], entryName)
aStatus.triggered.connect(
@@ -1221,7 +1221,7 @@ class GuiProjectTree(QTreeWidget):
)
else:
mImport = ctxMenu.addMenu(self.tr("Set Importance to ..."))
for n, (key, entry) in enumerate(self.theProject.importItems.items()):
for n, (key, entry) in enumerate(self.theProject.data.itemImport.items()):
entryName = entry["name"] + (checkMark if tItem.itemImport == key else "")
aImport = mImport.addAction(entry["icon"], entryName)
aImport.triggered.connect(
+23 -19
View File
@@ -197,6 +197,8 @@ class GuiMain(QMainWindow):
# Connect Signals
# ===============
self.theProject.projectStatusChanged.connect(self.mainStatus.doUpdateProjectStatus)
self.viewsBar.viewChangeRequested.connect(self._changeView)
self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
@@ -383,7 +385,7 @@ class GuiMain(QMainWindow):
self.mainStatus.setDocumentStatus(nwState.NONE)
self.mainStatus.setStatus(self.tr("New project created ..."))
self._updateWindowTitle(self.theProject.projName)
self._updateWindowTitle(self.theProject.data.name)
else:
self.theProject.clearProject()
@@ -417,7 +419,7 @@ class GuiMain(QMainWindow):
if self.theProject.projAltered:
saveOK = self.saveProject()
doBackup = False
if self.theProject.doBackup and self.mainConf.backupOnClose:
if self.theProject.data.doBackup and self.mainConf.backupOnClose:
doBackup = True
if self.mainConf.askBeforeBackup:
msgYes = self.askQuestion(
@@ -521,10 +523,10 @@ class GuiMain(QMainWindow):
self.theProject.index.loadIndex()
# Update GUI
self._updateWindowTitle(self.theProject.projName)
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()
@@ -532,11 +534,13 @@ class GuiMain(QMainWindow):
self._updateStatusWordCount()
# Restore previously open documents, if any
if self.theProject.lastEdited is not None:
self.openDocument(self.theProject.lastEdited, doScroll=True)
lastEdited = self.theProject.data.getLastHandle("editor")
if lastEdited is not None:
self.openDocument(lastEdited, doScroll=True)
if self.theProject.lastViewed is not None:
self.viewDocument(self.theProject.lastViewed)
lastViewed = self.theProject.data.getLastHandle("viewer")
if lastViewed is not None:
self.viewDocument(lastViewed)
# Check if we need to rebuild the index
if self.theProject.index.indexBroken:
@@ -607,7 +611,7 @@ class GuiMain(QMainWindow):
if self.docEditor.loadText(tHandle, tLine):
if changeFocus:
self.docEditor.setFocus()
self.theProject.setLastEdited(tHandle)
self.theProject.data.setLastHandle(tHandle, "editor")
self.projView.setSelectedHandle(tHandle, doScroll=doScroll)
self.novelView.setActiveHandle(tHandle)
else:
@@ -676,7 +680,7 @@ class GuiMain(QMainWindow):
tHandle = self.projView.getSelectedHandle()
if tHandle is None:
tHandle = self.theProject.lastViewed
tHandle = self.theProject.data.getLastHandle("viewer")
if tHandle is None:
logger.debug("No document to view, giving up")
@@ -960,7 +964,7 @@ class GuiMain(QMainWindow):
if dlgProj.spellChanged:
self.docEditor.setDictionaries()
self.itemDetails.refreshDetails()
self._updateWindowTitle(self.theProject.projName)
self._updateWindowTitle(self.theProject.data.name)
return True
@@ -1223,14 +1227,14 @@ class GuiMain(QMainWindow):
"""Close the document edit panel. This does not hide the editor.
"""
self.closeDocument()
self.theProject.setLastEdited(None)
self.theProject.data.setLastHandle(None, "editor")
return
def closeDocViewer(self):
"""Close the document view panel.
"""
self.docViewer.clearViewer()
self.theProject.setLastViewed(None)
self.theProject.data.setLastHandle(None, "viewer")
bPos = self.splitMain.sizes()
self.splitView.setVisible(False)
self.splitDocs.setSizes([bPos[1], 0])
@@ -1555,13 +1559,13 @@ class GuiMain(QMainWindow):
self.theProject.updateWordCounts()
if self.mainConf.incNotesWCount:
currWords = self.theProject.currWCount
diffWords = currWords - self.theProject.lastWCount
iTotal = sum(self.theProject.data.initCounts)
cTotal = sum(self.theProject.data.currCounts)
self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
else:
currWords = self.theProject.currNovelWC
diffWords = currWords - self.theProject.lastNovelWC
self.mainStatus.setProjectStats(currWords, diffWords)
iNovel, _ = self.theProject.data.initCounts
cNovel, _ = self.theProject.data.currCounts
self.mainStatus.setProjectStats(cNovel, cNovel - iNovel)
return
+11 -11
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()
@@ -168,7 +168,7 @@ class GuiBuildNovel(QDialog):
for langID, langName in theLangs:
self.buildLang.addItem(langName, langID)
langIdx = self.buildLang.findData(self.theProject.projLang)
langIdx = self.buildLang.findData(self.theProject.data.language)
if langIdx != -1:
self.buildLang.setCurrentIndex(langIdx)
@@ -888,7 +888,7 @@ class GuiBuildNovel(QDialog):
# Generate File Name
# ==================
cleanName = makeFileNameSafe(self.theProject.projName)
cleanName = makeFileNameSafe(self.theProject.data.name)
fileName = "%s.%s" % (cleanName, fileExt)
saveDir = self.mainConf.lastPath
if not os.path.isdir(saveDir):
@@ -972,9 +972,9 @@ class GuiBuildNovel(QDialog):
elif theFmt == self.FMT_JSON_H or theFmt == self.FMT_JSON_M:
jsonData = {
"meta": {
"workingTitle": self.theProject.projName,
"novelTitle": self.theProject.bookTitle,
"authors": self.theProject.bookAuthors,
"workingTitle": self.theProject.data.name,
"novelTitle": self.theProject.data.title,
"authors": self.theProject.data.authors,
"buildTime": self.buildTime,
}
}
@@ -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(),
+33 -32
View File
@@ -1,37 +1,38 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-25 18:45:45">
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 12:20:53">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>1386</saveCount>
<saveCount>1409</saveCount>
<autoCount>236</autoCount>
<editTime>69352</editTime>
<editTime>69427</editTime>
</project>
<settings>
<doBackup>False</doBackup>
<language>en_GB</language>
<spellCheck>True</spellCheck>
<spellLang>None</spellLang>
<lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>636b6aa9b697b</lastViewed>
<lastNovel>7031beac91f75</lastNovel>
<lastOutline>7031beac91f75</lastOutline>
<lastWordCount>1363</lastWordCount>
<novelWordCount>954</novelWordCount>
<notesWordCount>409</notesWordCount>
<lastHandle>
<entry key="editor">636b6aa9b697b</entry>
<entry key="viewer">636b6aa9b697b</entry>
<entry key="novelTree">7031beac91f75</entry>
<entry key="outline">7031beac91f75</entry>
</lastHandle>
<autoReplace>
<entry key="A">B</entry>
<entry key="B">E</entry>
<entry key="C">D</entry>
</autoReplace>
<titleFormat>
<title>%title%</title>
<chapter>Chapter %chw%: %title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>Scene %ch%.%sc%: %title%</scene>
<section></section>
<entry key="title">%title%</entry>
<entry key="chapter">Chapter %chw%: %title%</entry>
<entry key="unnumbered">%title%</entry>
<entry key="scene">Scene %ch%.%sc%: %title%</entry>
<entry key="section"></entry>
</titleFormat>
<status>
<entry key="sf12341" count="4" red="100" green="100" blue="100">New</entry>
@@ -55,43 +56,43 @@
<name status="sc24b8f" import="ia857f0">Novel</name>
</item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H1" charCount="93" wordCount="19" paraCount="2" cursorPos="119"/>
<meta expanded="False" heading="H1" charCount="93" wordCount="19" paraCount="2" cursorPos="119"/>
<name status="sc24b8f" import="ia857f0" active="True">Title Page</name>
</item>
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="251" wordCount="50" paraCount="2" cursorPos="277"/>
<meta expanded="False" heading="H0" charCount="251" wordCount="50" paraCount="2" cursorPos="277"/>
<name status="sf12341" import="ia857f0" active="True">Page</name>
</item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H1" charCount="26" wordCount="6" paraCount="1" cursorPos="36"/>
<meta expanded="False" heading="H1" charCount="26" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s90e6c9" import="ia857f0" active="True">Part One</name>
</item>
<item handle="6a2d6d5f4f401" parent="7031beac91f75" root="7031beac91f75" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="True" mainHeading="H2" charCount="95" wordCount="18" paraCount="1" cursorPos="291"/>
<meta expanded="True" heading="H2" charCount="95" wordCount="18" paraCount="1" cursorPos="291"/>
<name status="sf24ce6" import="ia857f0" active="True">Chapter One</name>
</item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H3" charCount="2687" wordCount="479" paraCount="14" cursorPos="67"/>
<meta expanded="False" heading="H3" charCount="2687" wordCount="479" paraCount="14" cursorPos="67"/>
<name status="s90e6c9" import="ia857f0" active="True">Making a Scene</name>
</item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H3" charCount="548" wordCount="108" paraCount="3" cursorPos="465"/>
<meta expanded="False" heading="H3" charCount="548" wordCount="108" paraCount="3" cursorPos="465"/>
<name status="s90e6c9" import="ia857f0" active="True">Another Scene</name>
</item>
<item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="310"/>
<meta expanded="False" heading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="310"/>
<name status="s78ea90" import="ia857f0" active="True">Interlude</name>
</item>
<item handle="96b68994dfa3d" parent="7031beac91f75" root="7031beac91f75" order="5" type="FILE" class="NOVEL" layout="NOTE">
<meta expanded="False" mainHeading="H1" charCount="1909" wordCount="346" paraCount="7" cursorPos="0"/>
<meta expanded="False" heading="H1" charCount="1909" wordCount="346" paraCount="7" cursorPos="0"/>
<name status="sf24ce6" import="ia857f0" active="False">A Note on Structure</name>
</item>
<item handle="88706ddc78b1b" parent="7031beac91f75" root="7031beac91f75" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="True" mainHeading="H2" charCount="139" wordCount="28" paraCount="1" cursorPos="188"/>
<meta expanded="True" heading="H2" charCount="139" wordCount="28" paraCount="1" cursorPos="188"/>
<name status="s90e6c9" import="ia857f0" active="True">Chapter Two</name>
</item>
<item handle="ae7339df26ded" parent="88706ddc78b1b" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H3" charCount="189" wordCount="37" paraCount="1" cursorPos="0"/>
<meta expanded="False" heading="H3" charCount="189" wordCount="37" paraCount="1" cursorPos="0"/>
<name status="s90e6c9" import="ia857f0" active="True">We Found John!</name>
</item>
<item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL">
@@ -99,11 +100,11 @@
<name status="sf12341" import="ia857f0">Sequel</name>
</item>
<item handle="bacb7059e3083" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H1" charCount="27" wordCount="5" paraCount="1" cursorPos="100"/>
<meta expanded="False" heading="H1" charCount="27" wordCount="5" paraCount="1" cursorPos="100"/>
<name status="sc24b8f" import="ia857f0" active="True">Title Page</name>
</item>
<item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H2" charCount="299" wordCount="55" paraCount="2" cursorPos="104"/>
<meta expanded="False" heading="H2" charCount="299" wordCount="55" paraCount="2" cursorPos="104"/>
<name status="s90e6c9" import="ia857f0" active="True">Chapter One</name>
</item>
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="2" type="ROOT" class="CHARACTER">
@@ -115,11 +116,11 @@
<name status="sf12341" import="ia857f0">Main Characters</name>
</item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" mainHeading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<meta expanded="False" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="sf12341" import="icfb3a5" active="True">John Smith</name>
</item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" mainHeading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<meta expanded="False" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="sf12341" import="i2d7a54" active="True">Jane Smith</name>
</item>
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD">
@@ -127,15 +128,15 @@
<name status="sf12341" import="ia857f0">Locations</name>
</item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" mainHeading="H1" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<meta expanded="False" heading="H1" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="sf12341" import="i56be10" active="True">Earth</name>
</item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" mainHeading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<meta expanded="False" heading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="sf12341" import="icfb3a5" active="True">Space</name>
</item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" mainHeading="H1" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<meta expanded="False" heading="H1" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="sf12341" import="i2d7a54" active="True">Mars</name>
</item>
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="4" type="ROOT" class="ARCHIVE">
@@ -147,7 +148,7 @@
<name status="sf12341" import="ia857f0">Scenes</name>
</item>
<item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" root="6827118336ac1" order="0" type="FILE" class="ARCHIVE" layout="DOCUMENT">
<meta expanded="False" mainHeading="H3" charCount="232" wordCount="42" paraCount="1" cursorPos="239"/>
<meta expanded="False" heading="H3" charCount="232" wordCount="42" paraCount="1" cursorPos="239"/>
<name status="s90e6c9" import="ia857f0" active="True">Old File</name>
</item>
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="5" type="ROOT" class="TRASH">
@@ -155,7 +156,7 @@
<name status="sf12341" import="ia857f0">Trash</name>
</item>
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="98acd8c76c93a" order="0" type="FILE" class="TRASH" layout="DOCUMENT">
<meta expanded="False" mainHeading="H3" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<meta expanded="False" heading="H3" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="sf12341" import="ia857f0" active="True">Delete Me!</name>
</item>
</content>
-21
View File
@@ -214,27 +214,6 @@ def mockRnd(monkeypatch):
# Temp Project Folders
##
@pytest.fixture(scope="function")
def nwMinimal(tmpDir):
"""A minimal novelWriter example project.
"""
tstDir = os.path.dirname(__file__)
srcDir = os.path.join(tstDir, "minimal")
dstDir = os.path.join(tmpDir, "minimal")
if os.path.isdir(dstDir):
shutil.rmtree(dstDir)
shutil.copytree(srcDir, dstDir)
cleanProject(dstDir)
yield dstDir
if os.path.isdir(dstDir):
shutil.rmtree(dstDir)
return
@pytest.fixture(scope="function")
def nwLipsum(tmpDir):
"""A medium sized novelWriter example project with a lot of Lorem
+299
View File
@@ -0,0 +1,299 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.6.1" hexVersion="0x000601f0" fileVersion="1.0" saveCount="186" autoCount="28" timeStamp="2020-05-28 09:59:15">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<backup>False</backup>
</project>
<settings>
<spellCheck>True</spellCheck>
<autoOutline>True</autoOutline>
<lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>636b6aa9b697b</lastViewed>
<lastWordCount>914</lastWordCount>
<autoReplace>
<A>B</A>
<B>E</B>
<C>D</C>
</autoReplace>
<titleFormat>
<title>%title%</title>
<chapter>Chapter %ch%: %title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>Scene %ch%.%sc%: %title%</scene>
<section></section>
<withSynopsis>True</withSynopsis>
<withComments>True</withComments>
<withKeywords>False</withKeywords>
</titleFormat>
<status>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Notes</entry>
<entry blue="0" green="60" red="182">Started</entry>
<entry blue="0" green="129" red="193">1st Draft</entry>
<entry blue="0" green="129" red="193">2nd Draft</entry>
<entry blue="0" green="129" red="193">3rd Draft</entry>
<entry blue="58" green="180" red="58">Finished</entry>
</status>
<importance>
<entry blue="100" green="100" red="100">None</entry>
<entry blue="188" green="122" red="0">Minor</entry>
<entry blue="180" green="0" red="21">Major</entry>
<entry blue="175" green="0" red="117">Main</entry>
</importance>
</settings>
<content count="22">
<item handle="7031beac91f75" order="0" parent="None">
<name>Novel</name>
<type>ROOT</type>
<class>NOVEL</class>
<status>Started</status>
<expanded>True</expanded>
</item>
<item handle="53b69b83cdafc" order="0" parent="7031beac91f75">
<name>Title Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>Started</status>
<expanded>False</expanded>
<exported>True</exported>
<layout>TITLE</layout>
<charCount>72</charCount>
<wordCount>15</wordCount>
<paraCount>2</paraCount>
<cursorPos>78</cursorPos>
</item>
<item handle="974e400180a99" order="1" parent="7031beac91f75">
<name>Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
<exported>True</exported>
<layout>PAGE</layout>
<charCount>208</charCount>
<wordCount>40</wordCount>
<paraCount>2</paraCount>
<cursorPos>213</cursorPos>
</item>
<item handle="edca4be2fcaf8" order="2" parent="7031beac91f75">
<name>Part One</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
<exported>True</exported>
<layout>PARTITION</layout>
<charCount>23</charCount>
<wordCount>5</wordCount>
<paraCount>1</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="e7ded148d6e4a" order="3" parent="7031beac91f75">
<name>A Folder</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>1st Draft</status>
<expanded>True</expanded>
</item>
<item handle="6a2d6d5f4f401" order="0" parent="e7ded148d6e4a">
<name>Chapter One</name>
<type>FILE</type>
<class>NOVEL</class>
<status>Notes</status>
<expanded>False</expanded>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>12</charCount>
<wordCount>3</wordCount>
<paraCount>0</paraCount>
<cursorPos>215</cursorPos>
</item>
<item handle="636b6aa9b697b" order="1" parent="e7ded148d6e4a">
<name>Making a Scene</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<expanded>False</expanded>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>1199</charCount>
<wordCount>216</wordCount>
<paraCount>7</paraCount>
<cursorPos>527</cursorPos>
</item>
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
<name>Another Scene</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<expanded>False</expanded>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>476</charCount>
<wordCount>93</wordCount>
<paraCount>3</paraCount>
<cursorPos>551</cursorPos>
</item>
<item handle="ba8a28a246524" order="3" parent="e7ded148d6e4a">
<name>Interlude</name>
<type>FILE</type>
<class>NOVEL</class>
<status>Finished</status>
<expanded>False</expanded>
<exported>True</exported>
<layout>UNNUMBERED</layout>
<charCount>633</charCount>
<wordCount>101</wordCount>
<paraCount>3</paraCount>
<cursorPos>1238</cursorPos>
</item>
<item handle="96b68994dfa3d" order="4" parent="e7ded148d6e4a">
<name>A Note on Structure</name>
<type>FILE</type>
<class>NOVEL</class>
<status>2nd Draft</status>
<expanded>False</expanded>
<exported>False</exported>
<layout>NOTE</layout>
<charCount>1692</charCount>
<wordCount>313</wordCount>
<paraCount>6</paraCount>
<cursorPos>1721</cursorPos>
</item>
<item handle="88706ddc78b1b" order="5" parent="e7ded148d6e4a">
<name>Chapter Two</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<expanded>False</expanded>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>139</charCount>
<wordCount>28</wordCount>
<paraCount>1</paraCount>
<cursorPos>343</cursorPos>
</item>
<item handle="ae7339df26ded" order="6" parent="e7ded148d6e4a">
<name>We Found John!</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<expanded>False</expanded>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>189</charCount>
<wordCount>37</wordCount>
<paraCount>1</paraCount>
<cursorPos>224</cursorPos>
</item>
<item handle="f6622b4617424" order="1" parent="None">
<name>Characters</name>
<type>ROOT</type>
<class>CHARACTER</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="f7e2d9f330615" order="0" parent="f6622b4617424">
<name>Main Characters</name>
<type>FOLDER</type>
<class>CHARACTER</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="14298de4d9524" order="0" parent="f7e2d9f330615">
<name>John Smith</name>
<type>FILE</type>
<class>CHARACTER</class>
<status>Minor</status>
<expanded>False</expanded>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>49</charCount>
<wordCount>9</wordCount>
<paraCount>1</paraCount>
<cursorPos>24</cursorPos>
</item>
<item handle="bb2c23b3c42cc" order="1" parent="f7e2d9f330615">
<name>Jane Smith</name>
<type>FILE</type>
<class>CHARACTER</class>
<status>Major</status>
<expanded>False</expanded>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>55</charCount>
<wordCount>9</wordCount>
<paraCount>1</paraCount>
<cursorPos>25</cursorPos>
</item>
<item handle="15c4492bd5107" order="2" parent="None">
<name>Locations</name>
<type>ROOT</type>
<class>WORLD</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="b3e74dbc1f584" order="0" parent="15c4492bd5107">
<name>Earth</name>
<type>FILE</type>
<class>WORLD</class>
<status>Main</status>
<expanded>False</expanded>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>76</charCount>
<wordCount>15</wordCount>
<paraCount>1</paraCount>
<cursorPos>20</cursorPos>
</item>
<item handle="f1471bef9f2ae" order="1" parent="15c4492bd5107">
<name>Space</name>
<type>FILE</type>
<class>WORLD</class>
<status>Minor</status>
<expanded>False</expanded>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>115</charCount>
<wordCount>24</wordCount>
<paraCount>1</paraCount>
<cursorPos>133</cursorPos>
</item>
<item handle="5eaea4e8cdee8" order="2" parent="15c4492bd5107">
<name>Mars</name>
<type>FILE</type>
<class>WORLD</class>
<status>Major</status>
<expanded>False</expanded>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>28</charCount>
<wordCount>6</wordCount>
<paraCount>1</paraCount>
<cursorPos>45</cursorPos>
</item>
<item handle="98acd8c76c93a" order="3" parent="None">
<name>Trash</name>
<type>TRASH</type>
<class>TRASH</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="b8136a5a774a0" order="0" parent="98acd8c76c93a">
<name>Delete Me!</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>36</cursorPos>
</item>
</content>
</novelWriterXML>
+283
View File
@@ -0,0 +1,283 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.9.2" hexVersion="0x000902f0" fileVersion="1.1" timeStamp="2020-06-26 21:20:24">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>5</saveCount>
<autoCount>10</autoCount>
<editTime>1000</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<spellCheck>True</spellCheck>
<autoOutline>True</autoOutline>
<lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>bb2c23b3c42cc</lastViewed>
<lastWordCount>967</lastWordCount>
<autoReplace>
<A>B</A>
<B>E</B>
<C>D</C>
</autoReplace>
<titleFormat>
<title>%title%</title>
<chapter>Chapter %ch%: %title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>Scene %ch%.%sc%: %title%</scene>
<section></section>
</titleFormat>
<status>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Notes</entry>
<entry blue="0" green="60" red="182">Started</entry>
<entry blue="0" green="129" red="193">1st Draft</entry>
<entry blue="0" green="129" red="193">2nd Draft</entry>
<entry blue="0" green="129" red="193">3rd Draft</entry>
<entry blue="58" green="180" red="58">Finished</entry>
</status>
<importance>
<entry blue="100" green="100" red="100">None</entry>
<entry blue="188" green="122" red="0">Minor</entry>
<entry blue="180" green="0" red="21">Major</entry>
<entry blue="175" green="0" red="117">Main</entry>
</importance>
</settings>
<content count="22">
<item handle="7031beac91f75" order="0" parent="None">
<name>Novel</name>
<type>ROOT</type>
<class>NOVEL</class>
<status>Started</status>
<expanded>True</expanded>
</item>
<item handle="53b69b83cdafc" order="0" parent="7031beac91f75">
<name>Title Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>Started</status>
<exported>True</exported>
<layout>TITLE</layout>
<charCount>72</charCount>
<wordCount>15</wordCount>
<paraCount>2</paraCount>
<cursorPos>78</cursorPos>
</item>
<item handle="974e400180a99" order="1" parent="7031beac91f75">
<name>Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>PAGE</layout>
<charCount>210</charCount>
<wordCount>40</wordCount>
<paraCount>2</paraCount>
<cursorPos>213</cursorPos>
</item>
<item handle="edca4be2fcaf8" order="2" parent="7031beac91f75">
<name>Part One</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>PARTITION</layout>
<charCount>23</charCount>
<wordCount>5</wordCount>
<paraCount>1</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="e7ded148d6e4a" order="3" parent="7031beac91f75">
<name>A Folder</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>1st Draft</status>
<expanded>True</expanded>
</item>
<item handle="6a2d6d5f4f401" order="0" parent="e7ded148d6e4a">
<name>Chapter One</name>
<type>FILE</type>
<class>NOVEL</class>
<status>Notes</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>12</charCount>
<wordCount>3</wordCount>
<paraCount>0</paraCount>
<cursorPos>215</cursorPos>
</item>
<item handle="636b6aa9b697b" order="1" parent="e7ded148d6e4a">
<name>Making a Scene</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>1483</charCount>
<wordCount>263</wordCount>
<paraCount>8</paraCount>
<cursorPos>1086</cursorPos>
</item>
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
<name>Another Scene</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>476</charCount>
<wordCount>93</wordCount>
<paraCount>3</paraCount>
<cursorPos>428</cursorPos>
</item>
<item handle="ba8a28a246524" order="3" parent="e7ded148d6e4a">
<name>Interlude</name>
<type>FILE</type>
<class>NOVEL</class>
<status>Finished</status>
<exported>True</exported>
<layout>UNNUMBERED</layout>
<charCount>633</charCount>
<wordCount>101</wordCount>
<paraCount>3</paraCount>
<cursorPos>1238</cursorPos>
</item>
<item handle="96b68994dfa3d" order="4" parent="e7ded148d6e4a">
<name>A Note on Structure</name>
<type>FILE</type>
<class>NOVEL</class>
<status>2nd Draft</status>
<exported>False</exported>
<layout>NOTE</layout>
<charCount>1692</charCount>
<wordCount>313</wordCount>
<paraCount>6</paraCount>
<cursorPos>1721</cursorPos>
</item>
<item handle="88706ddc78b1b" order="5" parent="e7ded148d6e4a">
<name>Chapter Two</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>139</charCount>
<wordCount>28</wordCount>
<paraCount>1</paraCount>
<cursorPos>343</cursorPos>
</item>
<item handle="ae7339df26ded" order="6" parent="e7ded148d6e4a">
<name>We Found John!</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>189</charCount>
<wordCount>37</wordCount>
<paraCount>1</paraCount>
<cursorPos>224</cursorPos>
</item>
<item handle="f6622b4617424" order="1" parent="None">
<name>Characters</name>
<type>ROOT</type>
<class>CHARACTER</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="f7e2d9f330615" order="0" parent="f6622b4617424">
<name>Main Characters</name>
<type>FOLDER</type>
<class>CHARACTER</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="14298de4d9524" order="0" parent="f7e2d9f330615">
<name>John Smith</name>
<type>FILE</type>
<class>CHARACTER</class>
<status>Minor</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>49</charCount>
<wordCount>9</wordCount>
<paraCount>1</paraCount>
<cursorPos>24</cursorPos>
</item>
<item handle="bb2c23b3c42cc" order="1" parent="f7e2d9f330615">
<name>Jane Smith</name>
<type>FILE</type>
<class>CHARACTER</class>
<status>Major</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>55</charCount>
<wordCount>9</wordCount>
<paraCount>1</paraCount>
<cursorPos>25</cursorPos>
</item>
<item handle="15c4492bd5107" order="2" parent="None">
<name>Locations</name>
<type>ROOT</type>
<class>WORLD</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="b3e74dbc1f584" order="0" parent="15c4492bd5107">
<name>Earth</name>
<type>FILE</type>
<class>WORLD</class>
<status>Main</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>76</charCount>
<wordCount>15</wordCount>
<paraCount>1</paraCount>
<cursorPos>20</cursorPos>
</item>
<item handle="f1471bef9f2ae" order="1" parent="15c4492bd5107">
<name>Space</name>
<type>FILE</type>
<class>WORLD</class>
<status>Minor</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>115</charCount>
<wordCount>24</wordCount>
<paraCount>1</paraCount>
<cursorPos>133</cursorPos>
</item>
<item handle="5eaea4e8cdee8" order="2" parent="15c4492bd5107">
<name>Mars</name>
<type>FILE</type>
<class>WORLD</class>
<status>Major</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>28</charCount>
<wordCount>6</wordCount>
<paraCount>1</paraCount>
<cursorPos>45</cursorPos>
</item>
<item handle="98acd8c76c93a" order="3" parent="None">
<name>Trash</name>
<type>TRASH</type>
<class>TRASH</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="b8136a5a774a0" order="0" parent="98acd8c76c93a">
<name>Delete Me!</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>30</charCount>
<wordCount>6</wordCount>
<paraCount>1</paraCount>
<cursorPos>36</cursorPos>
</item>
</content>
</novelWriterXML>
+313
View File
@@ -0,0 +1,313 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.4.2" hexVersion="0x010402f0" fileVersion="1.2" timeStamp="2021-08-30 23:33:44">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>5</saveCount>
<autoCount>10</autoCount>
<editTime>1000</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<language>en_GB</language>
<spellCheck>True</spellCheck>
<spellLang>en_GB</spellLang>
<autoOutline>True</autoOutline>
<lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>636b6aa9b697b</lastViewed>
<lastWordCount>1216</lastWordCount>
<novelWordCount>840</novelWordCount>
<notesWordCount>376</notesWordCount>
<autoReplace>
<entry key="A">B</entry>
<entry key="B">E</entry>
<entry key="C">D</entry>
</autoReplace>
<titleFormat>
<title>%title%</title>
<chapter>Chapter %chw%: %title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>Scene %ch%.%sc%: %title%</scene>
<section></section>
</titleFormat>
<status>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Notes</entry>
<entry blue="0" green="60" red="182">Started</entry>
<entry blue="0" green="129" red="193">1st Draft</entry>
<entry blue="0" green="129" red="193">2nd Draft</entry>
<entry blue="0" green="129" red="193">3rd Draft</entry>
<entry blue="58" green="180" red="58">Finished</entry>
</status>
<importance>
<entry blue="100" green="100" red="100">None</entry>
<entry blue="188" green="122" red="0">Minor</entry>
<entry blue="180" green="0" red="21">Major</entry>
<entry blue="175" green="0" red="117">Main</entry>
</importance>
</settings>
<content count="25">
<item handle="7031beac91f75" order="0" parent="None">
<name>Novel</name>
<type>ROOT</type>
<class>NOVEL</class>
<status>Started</status>
<expanded>True</expanded>
</item>
<item handle="53b69b83cdafc" order="0" parent="7031beac91f75">
<name>Title Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>Started</status>
<exported>True</exported>
<layout>TITLE</layout>
<charCount>241</charCount>
<wordCount>42</wordCount>
<paraCount>3</paraCount>
<cursorPos>252</cursorPos>
</item>
<item handle="974e400180a99" order="1" parent="7031beac91f75">
<name>Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>PAGE</layout>
<charCount>125</charCount>
<wordCount>26</wordCount>
<paraCount>2</paraCount>
<cursorPos>127</cursorPos>
</item>
<item handle="edca4be2fcaf8" order="2" parent="7031beac91f75">
<name>Part One</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>PARTITION</layout>
<charCount>26</charCount>
<wordCount>6</wordCount>
<paraCount>1</paraCount>
<cursorPos>30</cursorPos>
</item>
<item handle="e7ded148d6e4a" order="3" parent="7031beac91f75">
<name>A Folder</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>1st Draft</status>
<expanded>True</expanded>
</item>
<item handle="6a2d6d5f4f401" order="0" parent="e7ded148d6e4a">
<name>Chapter One</name>
<type>FILE</type>
<class>NOVEL</class>
<status>Notes</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>75</charCount>
<wordCount>14</wordCount>
<paraCount>1</paraCount>
<cursorPos>279</cursorPos>
</item>
<item handle="636b6aa9b697b" order="1" parent="e7ded148d6e4a">
<name>Making a Scene</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>2429</charCount>
<wordCount>432</wordCount>
<paraCount>14</paraCount>
<cursorPos>61</cursorPos>
</item>
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
<name>Another Scene</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>476</charCount>
<wordCount>93</wordCount>
<paraCount>3</paraCount>
<cursorPos>577</cursorPos>
</item>
<item handle="ba8a28a246524" order="3" parent="e7ded148d6e4a">
<name>Interlude</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>UNNUMBERED</layout>
<charCount>617</charCount>
<wordCount>101</wordCount>
<paraCount>3</paraCount>
<cursorPos>1137</cursorPos>
</item>
<item handle="96b68994dfa3d" order="4" parent="e7ded148d6e4a">
<name>A Note on Structure</name>
<type>FILE</type>
<class>NOVEL</class>
<status>2nd Draft</status>
<exported>False</exported>
<layout>NOTE</layout>
<charCount>1692</charCount>
<wordCount>313</wordCount>
<paraCount>6</paraCount>
<cursorPos>1110</cursorPos>
</item>
<item handle="88706ddc78b1b" order="5" parent="e7ded148d6e4a">
<name>Chapter Two</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>139</charCount>
<wordCount>28</wordCount>
<paraCount>1</paraCount>
<cursorPos>343</cursorPos>
</item>
<item handle="ae7339df26ded" order="6" parent="e7ded148d6e4a">
<name>We Found John!</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>189</charCount>
<wordCount>37</wordCount>
<paraCount>1</paraCount>
<cursorPos>224</cursorPos>
</item>
<item handle="f6622b4617424" order="1" parent="None">
<name>Characters</name>
<type>ROOT</type>
<class>CHARACTER</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="f7e2d9f330615" order="0" parent="f6622b4617424">
<name>Main Characters</name>
<type>FOLDER</type>
<class>CHARACTER</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="14298de4d9524" order="0" parent="f7e2d9f330615">
<name>John Smith</name>
<type>FILE</type>
<class>CHARACTER</class>
<status>Minor</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>49</charCount>
<wordCount>9</wordCount>
<paraCount>1</paraCount>
<cursorPos>24</cursorPos>
</item>
<item handle="bb2c23b3c42cc" order="1" parent="f7e2d9f330615">
<name>Jane Smith</name>
<type>FILE</type>
<class>CHARACTER</class>
<status>Major</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>55</charCount>
<wordCount>9</wordCount>
<paraCount>1</paraCount>
<cursorPos>25</cursorPos>
</item>
<item handle="15c4492bd5107" order="2" parent="None">
<name>Locations</name>
<type>ROOT</type>
<class>WORLD</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="b3e74dbc1f584" order="0" parent="15c4492bd5107">
<name>Earth</name>
<type>FILE</type>
<class>WORLD</class>
<status>Main</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>76</charCount>
<wordCount>15</wordCount>
<paraCount>1</paraCount>
<cursorPos>20</cursorPos>
</item>
<item handle="f1471bef9f2ae" order="1" parent="15c4492bd5107">
<name>Space</name>
<type>FILE</type>
<class>WORLD</class>
<status>Minor</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>115</charCount>
<wordCount>24</wordCount>
<paraCount>1</paraCount>
<cursorPos>133</cursorPos>
</item>
<item handle="5eaea4e8cdee8" order="2" parent="15c4492bd5107">
<name>Mars</name>
<type>FILE</type>
<class>WORLD</class>
<status>Major</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>28</charCount>
<wordCount>6</wordCount>
<paraCount>1</paraCount>
<cursorPos>45</cursorPos>
</item>
<item handle="6827118336ac1" order="3" parent="None">
<name>Outtakes</name>
<type>ROOT</type>
<class>ARCHIVE</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="ae9bf3c3ea159" order="0" parent="6827118336ac1">
<name>Scenes</name>
<type>FOLDER</type>
<class>ARCHIVE</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="8a5deb88c0e97" order="0" parent="ae9bf3c3ea159">
<name>Old File</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>315</charCount>
<wordCount>55</wordCount>
<paraCount>1</paraCount>
<cursorPos>322</cursorPos>
</item>
<item handle="98acd8c76c93a" order="4" parent="None">
<name>Trash</name>
<type>TRASH</type>
<class>TRASH</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="b8136a5a774a0" order="0" parent="98acd8c76c93a">
<name>Delete Me!</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>30</charCount>
<wordCount>6</wordCount>
<paraCount>1</paraCount>
<cursorPos>36</cursorPos>
</item>
</content>
</novelWriterXML>
+313
View File
@@ -0,0 +1,313 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.6.6" hexVersion="0x010606f0" fileVersion="1.3" timeStamp="2022-10-25 18:26:15">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>5</saveCount>
<autoCount>10</autoCount>
<editTime>1000</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<language>en_GB</language>
<spellCheck>True</spellCheck>
<spellLang>en_GB</spellLang>
<autoOutline>True</autoOutline>
<lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>636b6aa9b697b</lastViewed>
<lastWordCount>1206</lastWordCount>
<novelWordCount>830</novelWordCount>
<notesWordCount>376</notesWordCount>
<autoReplace>
<entry key="A">B</entry>
<entry key="B">E</entry>
<entry key="C">D</entry>
</autoReplace>
<titleFormat>
<title>%title%</title>
<chapter>Chapter %chw%: %title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>Scene %ch%.%sc%: %title%</scene>
<section></section>
</titleFormat>
<status>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Notes</entry>
<entry blue="0" green="60" red="182">Started</entry>
<entry blue="0" green="129" red="193">1st Draft</entry>
<entry blue="0" green="129" red="193">2nd Draft</entry>
<entry blue="0" green="129" red="193">3rd Draft</entry>
<entry blue="58" green="180" red="58">Finished</entry>
</status>
<importance>
<entry blue="100" green="100" red="100">None</entry>
<entry blue="188" green="122" red="0">Minor</entry>
<entry blue="180" green="0" red="21">Major</entry>
<entry blue="175" green="0" red="117">Main</entry>
</importance>
</settings>
<content count="25">
<item handle="7031beac91f75" order="0" parent="None">
<name>Novel</name>
<type>ROOT</type>
<class>NOVEL</class>
<status>Started</status>
<expanded>True</expanded>
</item>
<item handle="53b69b83cdafc" order="0" parent="7031beac91f75">
<name>Title Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>Started</status>
<exported>True</exported>
<layout>DOCUMENT</layout>
<charCount>93</charCount>
<wordCount>19</wordCount>
<paraCount>2</paraCount>
<cursorPos>2</cursorPos>
</item>
<item handle="974e400180a99" order="1" parent="7031beac91f75">
<name>Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>DOCUMENT</layout>
<charCount>186</charCount>
<wordCount>39</wordCount>
<paraCount>2</paraCount>
<cursorPos>212</cursorPos>
</item>
<item handle="edca4be2fcaf8" order="2" parent="7031beac91f75">
<name>Part One</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>DOCUMENT</layout>
<charCount>26</charCount>
<wordCount>6</wordCount>
<paraCount>1</paraCount>
<cursorPos>33</cursorPos>
</item>
<item handle="e7ded148d6e4a" order="3" parent="7031beac91f75">
<name>A Folder</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>1st Draft</status>
<expanded>True</expanded>
</item>
<item handle="6a2d6d5f4f401" order="0" parent="e7ded148d6e4a">
<name>Chapter One</name>
<type>FILE</type>
<class>NOVEL</class>
<status>Notes</status>
<exported>True</exported>
<layout>DOCUMENT</layout>
<charCount>75</charCount>
<wordCount>14</wordCount>
<paraCount>1</paraCount>
<cursorPos>279</cursorPos>
</item>
<item handle="636b6aa9b697b" order="1" parent="e7ded148d6e4a">
<name>Making a Scene</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<exported>True</exported>
<layout>DOCUMENT</layout>
<charCount>2429</charCount>
<wordCount>432</wordCount>
<paraCount>14</paraCount>
<cursorPos>62</cursorPos>
</item>
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
<name>Another Scene</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<exported>True</exported>
<layout>DOCUMENT</layout>
<charCount>476</charCount>
<wordCount>93</wordCount>
<paraCount>3</paraCount>
<cursorPos>577</cursorPos>
</item>
<item handle="ba8a28a246524" order="3" parent="e7ded148d6e4a">
<name>Interlude</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>DOCUMENT</layout>
<charCount>617</charCount>
<wordCount>101</wordCount>
<paraCount>3</paraCount>
<cursorPos>4</cursorPos>
</item>
<item handle="96b68994dfa3d" order="4" parent="e7ded148d6e4a">
<name>A Note on Structure</name>
<type>FILE</type>
<class>NOVEL</class>
<status>2nd Draft</status>
<exported>False</exported>
<layout>NOTE</layout>
<charCount>1692</charCount>
<wordCount>313</wordCount>
<paraCount>6</paraCount>
<cursorPos>1110</cursorPos>
</item>
<item handle="88706ddc78b1b" order="5" parent="e7ded148d6e4a">
<name>Chapter Two</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<exported>True</exported>
<layout>DOCUMENT</layout>
<charCount>139</charCount>
<wordCount>28</wordCount>
<paraCount>1</paraCount>
<cursorPos>343</cursorPos>
</item>
<item handle="ae7339df26ded" order="6" parent="e7ded148d6e4a">
<name>We Found John!</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<exported>True</exported>
<layout>DOCUMENT</layout>
<charCount>189</charCount>
<wordCount>37</wordCount>
<paraCount>1</paraCount>
<cursorPos>224</cursorPos>
</item>
<item handle="f6622b4617424" order="1" parent="None">
<name>Characters</name>
<type>ROOT</type>
<class>CHARACTER</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="f7e2d9f330615" order="0" parent="f6622b4617424">
<name>Main Characters</name>
<type>FOLDER</type>
<class>CHARACTER</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="14298de4d9524" order="0" parent="f7e2d9f330615">
<name>John Smith</name>
<type>FILE</type>
<class>CHARACTER</class>
<status>Minor</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>49</charCount>
<wordCount>9</wordCount>
<paraCount>1</paraCount>
<cursorPos>24</cursorPos>
</item>
<item handle="bb2c23b3c42cc" order="1" parent="f7e2d9f330615">
<name>Jane Smith</name>
<type>FILE</type>
<class>CHARACTER</class>
<status>Major</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>55</charCount>
<wordCount>9</wordCount>
<paraCount>1</paraCount>
<cursorPos>25</cursorPos>
</item>
<item handle="15c4492bd5107" order="2" parent="None">
<name>Locations</name>
<type>ROOT</type>
<class>WORLD</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="b3e74dbc1f584" order="0" parent="15c4492bd5107">
<name>Earth</name>
<type>FILE</type>
<class>WORLD</class>
<status>Main</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>76</charCount>
<wordCount>15</wordCount>
<paraCount>1</paraCount>
<cursorPos>20</cursorPos>
</item>
<item handle="f1471bef9f2ae" order="1" parent="15c4492bd5107">
<name>Space</name>
<type>FILE</type>
<class>WORLD</class>
<status>Minor</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>115</charCount>
<wordCount>24</wordCount>
<paraCount>1</paraCount>
<cursorPos>133</cursorPos>
</item>
<item handle="5eaea4e8cdee8" order="2" parent="15c4492bd5107">
<name>Mars</name>
<type>FILE</type>
<class>WORLD</class>
<status>Major</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>28</charCount>
<wordCount>6</wordCount>
<paraCount>1</paraCount>
<cursorPos>45</cursorPos>
</item>
<item handle="6827118336ac1" order="3" parent="None">
<name>Archive</name>
<type>ROOT</type>
<class>ARCHIVE</class>
<status>New</status>
<expanded>True</expanded>
</item>
<item handle="ae9bf3c3ea159" order="0" parent="6827118336ac1">
<name>Scenes</name>
<type>FOLDER</type>
<class>ARCHIVE</class>
<status>New</status>
<expanded>True</expanded>
</item>
<item handle="8a5deb88c0e97" order="0" parent="ae9bf3c3ea159">
<name>Old File</name>
<type>FILE</type>
<class>NOVEL</class>
<status>1st Draft</status>
<exported>True</exported>
<layout>DOCUMENT</layout>
<charCount>314</charCount>
<wordCount>55</wordCount>
<paraCount>1</paraCount>
<cursorPos>322</cursorPos>
</item>
<item handle="98acd8c76c93a" order="4" parent="None">
<name>Trash</name>
<type>TRASH</type>
<class>TRASH</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="b8136a5a774a0" order="0" parent="98acd8c76c93a">
<name>Delete Me!</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>DOCUMENT</layout>
<charCount>30</charCount>
<wordCount>6</wordCount>
<paraCount>1</paraCount>
<cursorPos>36</cursorPos>
</item>
</content>
</novelWriterXML>
+163
View File
@@ -0,0 +1,163 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 12:20:53">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>5</saveCount>
<autoCount>10</autoCount>
<editTime>1000</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<language>en_GB</language>
<spellCheck>True</spellCheck>
<spellLang>en_GB</spellLang>
<novelWordCount>954</novelWordCount>
<notesWordCount>409</notesWordCount>
<lastHandle>
<entry key="editor">636b6aa9b697b</entry>
<entry key="viewer">636b6aa9b697b</entry>
<entry key="novelTree">7031beac91f75</entry>
<entry key="outline">7031beac91f75</entry>
</lastHandle>
<autoReplace>
<entry key="A">B</entry>
<entry key="B">E</entry>
<entry key="C">D</entry>
</autoReplace>
<titleFormat>
<entry key="title">%title%</entry>
<entry key="chapter">Chapter %chw%: %title%</entry>
<entry key="unnumbered">%title%</entry>
<entry key="scene">Scene %ch%.%sc%: %title%</entry>
<entry key="section"></entry>
</titleFormat>
<status>
<entry key="sf12341" count="4" red="100" green="100" blue="100">New</entry>
<entry key="sf24ce6" count="2" red="200" green="50" blue="0">Notes</entry>
<entry key="sc24b8f" count="3" red="182" green="60" blue="0">Started</entry>
<entry key="s90e6c9" count="7" red="193" green="129" blue="0">1st Draft</entry>
<entry key="sd51c5b" count="0" red="193" green="129" blue="0">2nd Draft</entry>
<entry key="s8ae72a" count="0" red="193" green="129" blue="0">3rd Draft</entry>
<entry key="s78ea90" count="1" red="58" green="180" blue="58">Finished</entry>
</status>
<importance>
<entry key="ia857f0" count="5" red="100" green="100" blue="100">None</entry>
<entry key="icfb3a5" count="2" red="0" green="122" blue="188">Minor</entry>
<entry key="i2d7a54" count="2" red="21" green="0" blue="180">Major</entry>
<entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry>
</importance>
</settings>
<content count="27">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/>
<name status="sc24b8f" import="ia857f0">Novel</name>
</item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H1" charCount="93" wordCount="19" paraCount="2" cursorPos="119"/>
<name status="sc24b8f" import="ia857f0" active="True">Title Page</name>
</item>
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="251" wordCount="50" paraCount="2" cursorPos="277"/>
<name status="sf12341" import="ia857f0" active="True">Page</name>
</item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H1" charCount="26" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s90e6c9" import="ia857f0" active="True">Part One</name>
</item>
<item handle="6a2d6d5f4f401" parent="7031beac91f75" root="7031beac91f75" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="True" heading="H2" charCount="95" wordCount="18" paraCount="1" cursorPos="291"/>
<name status="sf24ce6" import="ia857f0" active="True">Chapter One</name>
</item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H3" charCount="2687" wordCount="479" paraCount="14" cursorPos="67"/>
<name status="s90e6c9" import="ia857f0" active="True">Making a Scene</name>
</item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H3" charCount="548" wordCount="108" paraCount="3" cursorPos="465"/>
<name status="s90e6c9" import="ia857f0" active="True">Another Scene</name>
</item>
<item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="310"/>
<name status="s78ea90" import="ia857f0" active="True">Interlude</name>
</item>
<item handle="96b68994dfa3d" parent="7031beac91f75" root="7031beac91f75" order="5" type="FILE" class="NOVEL" layout="NOTE">
<meta expanded="False" heading="H1" charCount="1909" wordCount="346" paraCount="7" cursorPos="0"/>
<name status="sf24ce6" import="ia857f0" active="False">A Note on Structure</name>
</item>
<item handle="88706ddc78b1b" parent="7031beac91f75" root="7031beac91f75" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="True" heading="H2" charCount="139" wordCount="28" paraCount="1" cursorPos="188"/>
<name status="s90e6c9" import="ia857f0" active="True">Chapter Two</name>
</item>
<item handle="ae7339df26ded" parent="88706ddc78b1b" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H3" charCount="189" wordCount="37" paraCount="1" cursorPos="0"/>
<name status="s90e6c9" import="ia857f0" active="True">We Found John!</name>
</item>
<item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL">
<meta expanded="True"/>
<name status="sf12341" import="ia857f0">Sequel</name>
</item>
<item handle="bacb7059e3083" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H1" charCount="27" wordCount="5" paraCount="1" cursorPos="100"/>
<name status="sc24b8f" import="ia857f0" active="True">Title Page</name>
</item>
<item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H2" charCount="299" wordCount="55" paraCount="2" cursorPos="104"/>
<name status="s90e6c9" import="ia857f0" active="True">Chapter One</name>
</item>
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="2" type="ROOT" class="CHARACTER">
<meta expanded="True"/>
<name status="sf12341" import="ia857f0">Characters</name>
</item>
<item handle="f7e2d9f330615" parent="f6622b4617424" root="f6622b4617424" order="0" type="FOLDER" class="CHARACTER">
<meta expanded="True"/>
<name status="sf12341" import="ia857f0">Main Characters</name>
</item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="sf12341" import="icfb3a5" active="True">John Smith</name>
</item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="sf12341" import="i2d7a54" active="True">Jane Smith</name>
</item>
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD">
<meta expanded="True"/>
<name status="sf12341" import="ia857f0">Locations</name>
</item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H1" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="sf12341" import="i56be10" active="True">Earth</name>
</item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="sf12341" import="icfb3a5" active="True">Space</name>
</item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H1" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="sf12341" import="i2d7a54" active="True">Mars</name>
</item>
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="4" type="ROOT" class="ARCHIVE">
<meta expanded="True"/>
<name status="sf12341" import="ia857f0">Archive</name>
</item>
<item handle="ae9bf3c3ea159" parent="6827118336ac1" root="6827118336ac1" order="0" type="FOLDER" class="ARCHIVE">
<meta expanded="True"/>
<name status="sf12341" import="ia857f0">Scenes</name>
</item>
<item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" root="6827118336ac1" order="0" type="FILE" class="ARCHIVE" layout="DOCUMENT">
<meta expanded="False" heading="H3" charCount="232" wordCount="42" paraCount="1" cursorPos="239"/>
<name status="s90e6c9" import="ia857f0" active="True">Old File</name>
</item>
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="5" type="ROOT" class="TRASH">
<meta expanded="True"/>
<name status="sf12341" import="ia857f0">Trash</name>
</item>
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="98acd8c76c93a" order="0" type="FILE" class="TRASH" layout="DOCUMENT">
<meta expanded="False" heading="H3" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="sf12341" import="ia857f0" active="True">Delete Me!</name>
</item>
</content>
</novelWriterXML>
+30 -28
View File
@@ -1,35 +1,37 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-17 21:17:31">
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-31 22:17:38">
<project>
<name>Lorem Ipsum</name>
<title>Lorem Ipsum</title>
<author>lipsum.com</author>
<saveCount>28</saveCount>
<saveCount>32</saveCount>
<autoCount>24</autoCount>
<editTime>1874</editTime>
<editTime>1889</editTime>
</project>
<settings>
<doBackup>False</doBackup>
<language>en_GB</language>
<spellCheck>False</spellCheck>
<spellLang>None</spellLang>
<lastEdited>7a992350f3eb6</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>3847</lastWordCount>
<totalWordCount>3847</totalWordCount>
<novelWordCount>3109</novelWordCount>
<notesWordCount>738</notesWordCount>
<lastHandle>
<entry key="editor">7a992350f3eb6</entry>
<entry key="viewer">None</entry>
<entry key="novelTree">b3643d0f92e32</entry>
<entry key="outline">None</entry>
</lastHandle>
<autoReplace>
<entry key="Rep1">Replace Text 1</entry>
<entry key="Rep2">Replace Text 2</entry>
</autoReplace>
<titleFormat>
<title>%title%</title>
<chapter>Chapter %ch%: %title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>* * *</scene>
<section></section>
<entry key="title">%title%</entry>
<entry key="chapter">Chapter %ch%: %title%</entry>
<entry key="unnumbered">%title%</entry>
<entry key="scene">* * *</entry>
<entry key="section"></entry>
</titleFormat>
<status>
<entry key="sbaa94f" count="3" red="100" green="100" blue="100">New</entry>
@@ -50,19 +52,19 @@
<name status="sbaa94f" import="i613591">Novel</name>
</item>
<item handle="7a992350f3eb6" parent="b3643d0f92e32" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H1" charCount="230" wordCount="40" paraCount="3" cursorPos="148"/>
<meta expanded="False" heading="H1" charCount="230" wordCount="40" paraCount="3" cursorPos="148"/>
<name status="sedd043" import="i613591" active="True">Lorem Ipsum</name>
</item>
<item handle="8c58a65414c23" parent="b3643d0f92e32" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="1058" wordCount="176" paraCount="2" cursorPos="43"/>
<meta expanded="False" heading="H0" charCount="1058" wordCount="176" paraCount="2" cursorPos="43"/>
<name status="sedd043" import="i613591" active="True">Front Matter</name>
</item>
<item handle="88d59a277361b" parent="b3643d0f92e32" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H2" charCount="584" wordCount="92" paraCount="1" cursorPos="4"/>
<meta expanded="False" heading="H2" charCount="584" wordCount="92" paraCount="1" cursorPos="4"/>
<name status="s92a87b" import="i613591" active="True">Prologue</name>
</item>
<item handle="db7e733775d4d" parent="b3643d0f92e32" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H1" charCount="35" wordCount="6" paraCount="1" cursorPos="42"/>
<meta expanded="False" heading="H1" charCount="35" wordCount="6" paraCount="1" cursorPos="42"/>
<name status="sbaa94f" import="i613591" active="True">Act One</name>
</item>
<item handle="45e6b01ca35c1" parent="b3643d0f92e32" root="b3643d0f92e32" order="4" type="FOLDER" class="NOVEL">
@@ -70,19 +72,19 @@
<name status="s92a87b" import="i613591">Chapter One</name>
</item>
<item handle="fb609cd8319dc" parent="45e6b01ca35c1" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H2" charCount="419" wordCount="67" paraCount="1" cursorPos="56"/>
<meta expanded="False" heading="H2" charCount="419" wordCount="67" paraCount="1" cursorPos="56"/>
<name status="s92a87b" import="i613591" active="True">Chapter One</name>
</item>
<item handle="88243afbe5ed8" parent="45e6b01ca35c1" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H3" charCount="2758" wordCount="404" paraCount="4" cursorPos="1528"/>
<meta expanded="False" heading="H3" charCount="2758" wordCount="404" paraCount="4" cursorPos="1528"/>
<name status="sedd043" import="i613591" active="True">Scene One</name>
</item>
<item handle="f96ec11c6a3da" parent="45e6b01ca35c1" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H3" charCount="4043" wordCount="600" paraCount="6" cursorPos="2335"/>
<meta expanded="False" heading="H3" charCount="4043" wordCount="600" paraCount="6" cursorPos="2335"/>
<name status="sedd043" import="i613591" active="True">Scene Two</name>
</item>
<item handle="846352075de7d" parent="b3643d0f92e32" root="b3643d0f92e32" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H2" charCount="631" wordCount="109" paraCount="3" cursorPos="376"/>
<meta expanded="False" heading="H2" charCount="631" wordCount="109" paraCount="3" cursorPos="376"/>
<name status="sbaa94f" import="i613591" active="False">Interlude</name>
</item>
<item handle="6bd935d2490cd" parent="b3643d0f92e32" root="b3643d0f92e32" order="6" type="FOLDER" class="NOVEL">
@@ -90,19 +92,19 @@
<name status="s92a87b" import="i613591">Chapter Two</name>
</item>
<item handle="441420a886d82" parent="6bd935d2490cd" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H2" charCount="477" wordCount="70" paraCount="1" cursorPos="56"/>
<meta expanded="False" heading="H2" charCount="477" wordCount="70" paraCount="1" cursorPos="56"/>
<name status="s92a87b" import="i613591" active="True">Chapter Two</name>
</item>
<item handle="eb103bc70c90c" parent="6bd935d2490cd" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H3" charCount="3006" wordCount="439" paraCount="4" cursorPos="57"/>
<meta expanded="False" heading="H3" charCount="3006" wordCount="439" paraCount="4" cursorPos="57"/>
<name status="sedd043" import="i613591" active="True">Scene Three</name>
</item>
<item handle="f8c0562e50f1b" parent="6bd935d2490cd" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H3" charCount="3839" wordCount="563" paraCount="6" cursorPos="56"/>
<meta expanded="False" heading="H3" charCount="3839" wordCount="563" paraCount="6" cursorPos="56"/>
<name status="sedd043" import="i613591" active="True">Scene Four</name>
</item>
<item handle="47666c91c7ccf" parent="6bd935d2490cd" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H3" charCount="3644" wordCount="543" paraCount="5" cursorPos="351"/>
<meta expanded="False" heading="H3" charCount="3644" wordCount="543" paraCount="5" cursorPos="351"/>
<name status="sedd043" import="i613591" active="True">Scene Five</name>
</item>
<item handle="67a8707f2f249" parent="None" root="67a8707f2f249" order="1" type="ROOT" class="CHARACTER">
@@ -110,7 +112,7 @@
<name status="sbaa94f" import="i613591">Characters</name>
</item>
<item handle="4c4f28287af27" parent="67a8707f2f249" root="67a8707f2f249" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" mainHeading="H1" charCount="1864" wordCount="284" paraCount="3" cursorPos="1883"/>
<meta expanded="False" heading="H1" charCount="1864" wordCount="284" paraCount="3" cursorPos="1883"/>
<name status="sbaa94f" import="i613591" active="True">Mr. Nobody</name>
</item>
<item handle="6c6afb1247750" parent="None" root="6c6afb1247750" order="2" type="ROOT" class="PLOT">
@@ -118,7 +120,7 @@
<name status="sbaa94f" import="i613591">Plot</name>
</item>
<item handle="2426c6f0ca922" parent="6c6afb1247750" root="6c6afb1247750" order="0" type="FILE" class="PLOT" layout="NOTE">
<meta expanded="False" mainHeading="H1" charCount="1369" wordCount="195" paraCount="2" cursorPos="1387"/>
<meta expanded="False" heading="H1" charCount="1369" wordCount="195" paraCount="2" cursorPos="1387"/>
<name status="sbaa94f" import="i613591" active="True">Main</name>
</item>
<item handle="60bdf227455cc" parent="None" root="60bdf227455cc" order="3" type="ROOT" class="WORLD">
@@ -126,7 +128,7 @@
<name status="sbaa94f" import="i613591">World</name>
</item>
<item handle="04468803b92e1" parent="60bdf227455cc" root="60bdf227455cc" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" mainHeading="H1" charCount="1770" wordCount="259" paraCount="3" cursorPos="1792"/>
<meta expanded="False" heading="H1" charCount="1770" wordCount="259" paraCount="3" cursorPos="1792"/>
<name status="sbaa94f" import="i613591" active="True">Ancient Europe</name>
</item>
</content>
-5
View File
@@ -1,5 +0,0 @@
%%~name: New Scene
%%~path: a6d311a93600a/8c659a11cd429
%%~kind: NOVEL/DOCUMENT
### New Scene
-6
View File
@@ -1,6 +0,0 @@
%%~name: Title Page
%%~path: a508bb932959c/a35baf2e93843
%%~kind: NOVEL/DOCUMENT
#! Minimal
>> By Jane Doe, John Doh <<
-5
View File
@@ -1,5 +0,0 @@
%%~name: New Chapter
%%~path: a6d311a93600a/f5ab3e30151e1
%%~kind: NOVEL/DOCUMENT
## New Chapter
-79
View File
@@ -1,79 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-17 20:32:06">
<project>
<name>Test Minimal</name>
<title>Minimal</title>
<author>Jane Doe</author>
<author>John Doh</author>
<saveCount>19</saveCount>
<autoCount>2</autoCount>
<editTime>167</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<language>en_GB</language>
<spellCheck>False</spellCheck>
<spellLang>None</spellLang>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>a508bb932959c</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>10</lastWordCount>
<novelWordCount>10</novelWordCount>
<notesWordCount>0</notesWordCount>
<autoReplace/>
<titleFormat>
<title>%title%</title>
<chapter>Chapter %ch%: %title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>* * *</scene>
<section></section>
</titleFormat>
<status>
<entry key="s72322d" count="5" red="100" green="100" blue="100">New</entry>
<entry key="sb6adc7" count="0" red="200" green="50" blue="0">Note</entry>
<entry key="s2ae76d" count="0" red="200" green="150" blue="0">Draft</entry>
<entry key="s541953" count="0" red="50" green="200" blue="0">Finished</entry>
</status>
<importance>
<entry key="iffcacb" count="3" red="100" green="100" blue="100">New</entry>
<entry key="i6b130b" count="0" red="200" green="50" blue="0">Minor</entry>
<entry key="iece803" count="0" red="200" green="150" blue="0">Major</entry>
<entry key="i5ba06e" count="0" red="50" green="200" blue="0">Main</entry>
</importance>
</settings>
<content count="8">
<item handle="a508bb932959c" parent="None" root="a508bb932959c" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/>
<name status="s72322d" import="iffcacb">Novel</name>
</item>
<item handle="a35baf2e93843" parent="a508bb932959c" root="a508bb932959c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H1" charCount="28" wordCount="6" paraCount="1" cursorPos="33"/>
<name status="s72322d" import="iffcacb" active="True">Title Page</name>
</item>
<item handle="a6d311a93600a" parent="a508bb932959c" root="a508bb932959c" order="1" type="FOLDER" class="NOVEL">
<meta expanded="True"/>
<name status="s72322d" import="iffcacb">New Chapter</name>
</item>
<item handle="f5ab3e30151e1" parent="a6d311a93600a" root="a508bb932959c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="16"/>
<name status="s72322d" import="iffcacb" active="True">New Chapter</name>
</item>
<item handle="8c659a11cd429" parent="a6d311a93600a" root="a508bb932959c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="15"/>
<name status="s72322d" import="iffcacb" active="True">New Scene</name>
</item>
<item handle="7695ce551d265" parent="None" root="7695ce551d265" order="1" type="ROOT" class="PLOT">
<meta expanded="False"/>
<name status="s72322d" import="iffcacb">Plot</name>
</item>
<item handle="afb3043c7b2b3" parent="None" root="afb3043c7b2b3" order="2" type="ROOT" class="CHARACTER">
<meta expanded="False"/>
<name status="s72322d" import="iffcacb">Characters</name>
</item>
<item handle="9d5247ab588e0" parent="None" root="9d5247ab588e0" order="3" type="ROOT" class="WORLD">
<meta expanded="False"/>
<name status="s72322d" import="iffcacb">World</name>
</item>
</content>
</novelWriterXML>
+5 -1
View File
@@ -19,14 +19,18 @@ 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 PyQt5.QtCore import QObject
# =========================================================================== #
# Mock GUI
# =========================================================================== #
class MockGuiMain:
class MockGuiMain(QObject):
def __init__(self):
super().__init__()
self.mainConf = None
self.hasProject = True
self.theProject = None
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-20 18:21:56">
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 11:53:22">
<project>
<name>Test Custom</name>
<title>Test Novel</title>
@@ -14,122 +14,123 @@
<language>None</language>
<spellCheck>False</spellCheck>
<spellLang>None</spellLang>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
<entry key="novelTree">None</entry>
<entry key="outline">None</entry>
</lastHandle>
<autoReplace/>
<titleFormat>
<title>%title%</title>
<chapter>%title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>* * *</scene>
<section></section>
<entry key="title">%title%</entry>
<entry key="chapter">%title%</entry>
<entry key="unnumbered">%title%</entry>
<entry key="scene">* * *</entry>
<entry key="section"></entry>
</titleFormat>
<status>
<entry key="s000008" count="15" red="100" green="100" blue="100">New</entry>
<entry key="s000009" count="0" red="200" green="50" blue="0">Note</entry>
<entry key="s00000a" count="0" red="200" green="150" blue="0">Draft</entry>
<entry key="s00000b" count="0" red="50" green="200" blue="0">Finished</entry>
<entry key="s000000" count="15" red="100" green="100" blue="100">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Note</entry>
<entry key="s000002" count="0" red="200" green="150" blue="0">Draft</entry>
<entry key="s000003" count="0" red="50" green="200" blue="0">Finished</entry>
</status>
<importance>
<entry key="i00000c" count="7" red="100" green="100" blue="100">New</entry>
<entry key="i00000d" count="0" red="200" green="50" blue="0">Minor</entry>
<entry key="i00000e" count="0" red="200" green="150" blue="0">Major</entry>
<entry key="i00000f" count="0" red="50" green="200" blue="0">Main</entry>
<entry key="i000004" count="7" red="100" green="100" blue="100">New</entry>
<entry key="i000005" count="0" red="200" green="50" blue="0">Minor</entry>
<entry key="i000006" count="0" red="200" green="150" blue="0">Major</entry>
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry>
</importance>
</settings>
<content count="22">
<item handle="0000000000010" parent="None" root="0000000000010" order="0" type="ROOT" class="NOVEL">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Novel</name>
<name status="s000000" import="i000004">Novel</name>
</item>
<item handle="0000000000011" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Title Page</name>
<item handle="0000000000009" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Title Page</name>
</item>
<item handle="0000000000012" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Chapter 1</name>
<item handle="000000000000a" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Chapter 1</name>
</item>
<item handle="0000000000013" parent="0000000000012" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 1.1</name>
<item handle="000000000000b" parent="000000000000a" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 1.1</name>
</item>
<item handle="0000000000014" parent="0000000000012" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 1.2</name>
<item handle="000000000000c" parent="000000000000a" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 1.2</name>
</item>
<item handle="0000000000015" parent="0000000000012" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 1.3</name>
<item handle="000000000000d" parent="000000000000a" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 1.3</name>
</item>
<item handle="0000000000016" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Chapter 2</name>
<item handle="000000000000e" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Chapter 2</name>
</item>
<item handle="0000000000017" parent="0000000000016" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 2.1</name>
<item handle="000000000000f" parent="000000000000e" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 2.1</name>
</item>
<item handle="0000000000018" parent="0000000000016" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 2.2</name>
<item handle="0000000000010" parent="000000000000e" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 2.2</name>
</item>
<item handle="0000000000019" parent="0000000000016" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 2.3</name>
<item handle="0000000000011" parent="000000000000e" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 2.3</name>
</item>
<item handle="000000000001a" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Chapter 3</name>
<item handle="0000000000012" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Chapter 3</name>
</item>
<item handle="000000000001b" parent="000000000001a" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 3.1</name>
<item handle="0000000000013" parent="0000000000012" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 3.1</name>
</item>
<item handle="000000000001c" parent="000000000001a" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 3.2</name>
<item handle="0000000000014" parent="0000000000012" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 3.2</name>
</item>
<item handle="000000000001d" parent="000000000001a" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 3.3</name>
<item handle="0000000000015" parent="0000000000012" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 3.3</name>
</item>
<item handle="000000000001e" parent="None" root="000000000001e" order="0" type="ROOT" class="PLOT">
<item handle="0000000000016" parent="None" root="0000000000016" order="0" type="ROOT" class="PLOT">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Plot</name>
<name status="s000000" import="i000004">Plot</name>
</item>
<item handle="000000000001f" parent="000000000001e" root="000000000001e" order="0" type="FILE" class="PLOT" layout="NOTE">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Main Plot</name>
<item handle="0000000000017" parent="0000000000016" root="0000000000016" order="0" type="FILE" class="PLOT" layout="NOTE">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Main Plot</name>
</item>
<item handle="0000000000020" parent="None" root="0000000000020" order="0" type="ROOT" class="CHARACTER">
<item handle="0000000000018" parent="None" root="0000000000018" order="0" type="ROOT" class="CHARACTER">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Characters</name>
<name status="s000000" import="i000004">Characters</name>
</item>
<item handle="0000000000021" parent="0000000000020" root="0000000000020" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Protagonist</name>
<item handle="0000000000019" parent="0000000000018" root="0000000000018" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Protagonist</name>
</item>
<item handle="0000000000022" parent="None" root="0000000000022" order="0" type="ROOT" class="WORLD">
<item handle="000000000001a" parent="None" root="000000000001a" order="0" type="ROOT" class="WORLD">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Locations</name>
<name status="s000000" import="i000004">Locations</name>
</item>
<item handle="0000000000023" parent="0000000000022" root="0000000000022" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Main Location</name>
<item handle="000000000001b" parent="000000000001a" root="000000000001a" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Main Location</name>
</item>
<item handle="0000000000024" parent="None" root="0000000000024" order="0" type="ROOT" class="ARCHIVE">
<item handle="000000000001c" parent="None" root="000000000001c" order="0" type="ROOT" class="ARCHIVE">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Archive</name>
<name status="s000000" import="i000004">Archive</name>
</item>
<item handle="0000000000025" parent="None" root="0000000000025" order="0" type="ROOT" class="TRASH">
<item handle="000000000001d" parent="None" root="000000000001d" order="0" type="ROOT" class="TRASH">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Trash</name>
<name status="s000000" import="i000004">Trash</name>
</item>
</content>
</novelWriterXML>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-20 18:21:56">
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 11:53:22">
<project>
<name>Test Custom</name>
<title>Test Novel</title>
@@ -14,98 +14,99 @@
<language>None</language>
<spellCheck>False</spellCheck>
<spellLang>None</spellLang>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
<entry key="novelTree">None</entry>
<entry key="outline">None</entry>
</lastHandle>
<autoReplace/>
<titleFormat>
<title>%title%</title>
<chapter>%title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>* * *</scene>
<section></section>
<entry key="title">%title%</entry>
<entry key="chapter">%title%</entry>
<entry key="unnumbered">%title%</entry>
<entry key="scene">* * *</entry>
<entry key="section"></entry>
</titleFormat>
<status>
<entry key="s000008" count="9" red="100" green="100" blue="100">New</entry>
<entry key="s000009" count="0" red="200" green="50" blue="0">Note</entry>
<entry key="s00000a" count="0" red="200" green="150" blue="0">Draft</entry>
<entry key="s00000b" count="0" red="50" green="200" blue="0">Finished</entry>
<entry key="s000000" count="9" red="100" green="100" blue="100">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Note</entry>
<entry key="s000002" count="0" red="200" green="150" blue="0">Draft</entry>
<entry key="s000003" count="0" red="50" green="200" blue="0">Finished</entry>
</status>
<importance>
<entry key="i00000c" count="7" red="100" green="100" blue="100">New</entry>
<entry key="i00000d" count="0" red="200" green="50" blue="0">Minor</entry>
<entry key="i00000e" count="0" red="200" green="150" blue="0">Major</entry>
<entry key="i00000f" count="0" red="50" green="200" blue="0">Main</entry>
<entry key="i000004" count="7" red="100" green="100" blue="100">New</entry>
<entry key="i000005" count="0" red="200" green="50" blue="0">Minor</entry>
<entry key="i000006" count="0" red="200" green="150" blue="0">Major</entry>
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry>
</importance>
</settings>
<content count="16">
<item handle="0000000000010" parent="None" root="0000000000010" order="0" type="ROOT" class="NOVEL">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Novel</name>
<name status="s000000" import="i000004">Novel</name>
</item>
<item handle="0000000000011" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Title Page</name>
<item handle="0000000000009" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Title Page</name>
</item>
<item handle="0000000000012" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 1</name>
<item handle="000000000000a" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 1</name>
</item>
<item handle="0000000000013" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 2</name>
<item handle="000000000000b" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 2</name>
</item>
<item handle="0000000000014" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 3</name>
<item handle="000000000000c" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 3</name>
</item>
<item handle="0000000000015" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 4</name>
<item handle="000000000000d" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 4</name>
</item>
<item handle="0000000000016" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 5</name>
<item handle="000000000000e" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 5</name>
</item>
<item handle="0000000000017" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 6</name>
<item handle="000000000000f" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 6</name>
</item>
<item handle="0000000000018" parent="None" root="0000000000018" order="0" type="ROOT" class="PLOT">
<item handle="0000000000010" parent="None" root="0000000000010" order="0" type="ROOT" class="PLOT">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Plot</name>
<name status="s000000" import="i000004">Plot</name>
</item>
<item handle="0000000000019" parent="0000000000018" root="0000000000018" order="0" type="FILE" class="PLOT" layout="NOTE">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Main Plot</name>
<item handle="0000000000011" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="PLOT" layout="NOTE">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Main Plot</name>
</item>
<item handle="000000000001a" parent="None" root="000000000001a" order="0" type="ROOT" class="CHARACTER">
<item handle="0000000000012" parent="None" root="0000000000012" order="0" type="ROOT" class="CHARACTER">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Characters</name>
<name status="s000000" import="i000004">Characters</name>
</item>
<item handle="000000000001b" parent="000000000001a" root="000000000001a" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Protagonist</name>
<item handle="0000000000013" parent="0000000000012" root="0000000000012" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Protagonist</name>
</item>
<item handle="000000000001c" parent="None" root="000000000001c" order="0" type="ROOT" class="WORLD">
<item handle="0000000000014" parent="None" root="0000000000014" order="0" type="ROOT" class="WORLD">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Locations</name>
<name status="s000000" import="i000004">Locations</name>
</item>
<item handle="000000000001d" parent="000000000001c" root="000000000001c" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Main Location</name>
<item handle="0000000000015" parent="0000000000014" root="0000000000014" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Main Location</name>
</item>
<item handle="000000000001e" parent="None" root="000000000001e" order="0" type="ROOT" class="ARCHIVE">
<item handle="0000000000016" parent="None" root="0000000000016" order="0" type="ROOT" class="ARCHIVE">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Archive</name>
<name status="s000000" import="i000004">Archive</name>
</item>
<item handle="000000000001f" parent="None" root="000000000001f" order="0" type="ROOT" class="TRASH">
<item handle="0000000000017" parent="None" root="0000000000017" order="0" type="ROOT" class="TRASH">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Trash</name>
<name status="s000000" import="i000004">Trash</name>
</item>
</content>
</novelWriterXML>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-20 18:21:56">
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 11:53:22">
<project>
<name>New Project</name>
<title>New Novel</title>
@@ -13,20 +13,21 @@
<language>None</language>
<spellCheck>False</spellCheck>
<spellLang>None</spellLang>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>13</lastWordCount>
<novelWordCount>10</novelWordCount>
<notesWordCount>3</notesWordCount>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
<entry key="novelTree">None</entry>
<entry key="outline">None</entry>
</lastHandle>
<autoReplace/>
<titleFormat>
<title>%title%</title>
<chapter>%title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>* * *</scene>
<section></section>
<entry key="title">%title%</entry>
<entry key="chapter">%title%</entry>
<entry key="unnumbered">%title%</entry>
<entry key="scene">* * *</entry>
<entry key="section"></entry>
</titleFormat>
<status>
<entry key="s000000" count="7" red="100" green="100" blue="100">New</entry>
@@ -59,7 +60,7 @@
<name status="s000000" import="i000004">World</name>
</item>
<item handle="000000000000c" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/>
<meta expanded="False" heading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Title Page</name>
</item>
<item handle="000000000000d" parent="0000000000008" root="0000000000008" order="0" type="FOLDER" class="NOVEL">
@@ -67,23 +68,23 @@
<name status="s000000" import="i000004">New Chapter</name>
</item>
<item handle="000000000000e" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<meta expanded="False" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">New Chapter</name>
</item>
<item handle="000000000000f" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0"/>
<meta expanded="False" heading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">New Scene</name>
</item>
<item handle="0000000000020" parent="0000000000008" root="0000000000008" order="0" type="FOLDER" class="NOVEL">
<item handle="0000000000010" parent="0000000000008" root="0000000000008" order="0" type="FOLDER" class="NOVEL">
<meta expanded="False"/>
<name status="s000000" import="i000004">Stuff</name>
</item>
<item handle="0000000000021" parent="0000000000020" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H2" charCount="5" wordCount="1" paraCount="0" cursorPos="0"/>
<item handle="0000000000011" parent="0000000000010" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H2" charCount="5" wordCount="1" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Hello</name>
</item>
<item handle="0000000000022" parent="000000000000a" root="000000000000a" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" mainHeading="H1" charCount="11" wordCount="3" paraCount="1" cursorPos="0"/>
<item handle="0000000000012" parent="000000000000a" root="000000000000a" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H1" charCount="11" wordCount="3" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Jane</name>
</item>
</content>
@@ -1,8 +1,8 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-20 18:21:56">
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 11:53:22">
<project>
<name>New Project</name>
<title></title>
<title>None</title>
<saveCount>2</saveCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
@@ -12,66 +12,67 @@
<language>None</language>
<spellCheck>False</spellCheck>
<spellLang>None</spellLang>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
<entry key="novelTree">None</entry>
<entry key="outline">None</entry>
</lastHandle>
<autoReplace/>
<titleFormat>
<title>%title%</title>
<chapter>%title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>* * *</scene>
<section></section>
<entry key="title">%title%</entry>
<entry key="chapter">%title%</entry>
<entry key="unnumbered">%title%</entry>
<entry key="scene">* * *</entry>
<entry key="section"></entry>
</titleFormat>
<status>
<entry key="s000008" count="5" red="100" green="100" blue="100">New</entry>
<entry key="s000009" count="0" red="200" green="50" blue="0">Note</entry>
<entry key="s00000a" count="0" red="200" green="150" blue="0">Draft</entry>
<entry key="s00000b" count="0" red="50" green="200" blue="0">Finished</entry>
<entry key="s000000" count="5" red="100" green="100" blue="100">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Note</entry>
<entry key="s000002" count="0" red="200" green="150" blue="0">Draft</entry>
<entry key="s000003" count="0" red="50" green="200" blue="0">Finished</entry>
</status>
<importance>
<entry key="i00000c" count="3" red="100" green="100" blue="100">New</entry>
<entry key="i00000d" count="0" red="200" green="50" blue="0">Minor</entry>
<entry key="i00000e" count="0" red="200" green="150" blue="0">Major</entry>
<entry key="i00000f" count="0" red="50" green="200" blue="0">Main</entry>
<entry key="i000004" count="3" red="100" green="100" blue="100">New</entry>
<entry key="i000005" count="0" red="200" green="50" blue="0">Minor</entry>
<entry key="i000006" count="0" red="200" green="150" blue="0">Major</entry>
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry>
</importance>
</settings>
<content count="8">
<item handle="0000000000010" parent="None" root="0000000000010" order="0" type="ROOT" class="NOVEL">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Novel</name>
<name status="s000000" import="i000004">Novel</name>
</item>
<item handle="0000000000011" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Title Page</name>
<item handle="0000000000009" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Title Page</name>
</item>
<item handle="0000000000012" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">New Chapter</name>
<item handle="000000000000a" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">New Chapter</name>
</item>
<item handle="0000000000013" parent="0000000000012" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">New Scene</name>
<item handle="000000000000b" parent="000000000000a" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">New Scene</name>
</item>
<item handle="0000000000014" parent="None" root="0000000000014" order="0" type="ROOT" class="PLOT">
<item handle="000000000000c" parent="None" root="000000000000c" order="0" type="ROOT" class="PLOT">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Plot</name>
<name status="s000000" import="i000004">Plot</name>
</item>
<item handle="0000000000015" parent="None" root="0000000000015" order="0" type="ROOT" class="CHARACTER">
<item handle="000000000000d" parent="None" root="000000000000d" order="0" type="ROOT" class="CHARACTER">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Characters</name>
<name status="s000000" import="i000004">Characters</name>
</item>
<item handle="0000000000016" parent="None" root="0000000000016" order="0" type="ROOT" class="WORLD">
<item handle="000000000000e" parent="None" root="000000000000e" order="0" type="ROOT" class="WORLD">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Locations</name>
<name status="s000000" import="i000004">Locations</name>
</item>
<item handle="0000000000017" parent="None" root="0000000000017" order="0" type="ROOT" class="ARCHIVE">
<item handle="000000000000f" parent="None" root="000000000000f" order="0" type="ROOT" class="ARCHIVE">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Archive</name>
<name status="s000000" import="i000004">Archive</name>
</item>
</content>
</novelWriterXML>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-20 18:21:56">
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 11:53:22">
<project>
<name>New Project</name>
<title>New Novel</title>
@@ -13,20 +13,21 @@
<language>None</language>
<spellCheck>False</spellCheck>
<spellLang>None</spellLang>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>9</lastWordCount>
<novelWordCount>9</novelWordCount>
<notesWordCount>0</notesWordCount>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
<entry key="novelTree">None</entry>
<entry key="outline">None</entry>
</lastHandle>
<autoReplace/>
<titleFormat>
<title>%title%</title>
<chapter>%title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>* * *</scene>
<section></section>
<entry key="title">%title%</entry>
<entry key="chapter">%title%</entry>
<entry key="unnumbered">%title%</entry>
<entry key="scene">* * *</entry>
<entry key="section"></entry>
</titleFormat>
<status>
<entry key="s000000" count="6" red="100" green="100" blue="100">New</entry>
@@ -59,7 +60,7 @@
<name status="s000000" import="i000004">World</name>
</item>
<item handle="000000000000c" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/>
<meta expanded="False" heading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Title Page</name>
</item>
<item handle="000000000000d" parent="0000000000008" root="0000000000008" order="0" type="FOLDER" class="NOVEL">
@@ -67,42 +68,42 @@
<name status="s000000" import="i000004">New Chapter</name>
</item>
<item handle="000000000000e" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<meta expanded="False" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">New Chapter</name>
</item>
<item handle="000000000000f" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0"/>
<meta expanded="False" heading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">New Scene</name>
</item>
<item handle="0000000000020" parent="None" root="0000000000020" order="0" type="ROOT" class="NOVEL">
<item handle="0000000000010" parent="None" root="0000000000010" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/>
<name status="s000000" import="i000004">Novel</name>
</item>
<item handle="0000000000021" parent="None" root="0000000000021" order="0" type="ROOT" class="PLOT">
<item handle="0000000000011" parent="None" root="0000000000011" order="0" type="ROOT" class="PLOT">
<meta expanded="False"/>
<name status="s000000" import="i000004">Plot</name>
</item>
<item handle="0000000000022" parent="None" root="0000000000022" order="0" type="ROOT" class="CHARACTER">
<item handle="0000000000012" parent="None" root="0000000000012" order="0" type="ROOT" class="CHARACTER">
<meta expanded="False"/>
<name status="s000000" import="i000004">Characters</name>
</item>
<item handle="0000000000023" parent="None" root="0000000000023" order="0" type="ROOT" class="WORLD">
<item handle="0000000000013" parent="None" root="0000000000013" order="0" type="ROOT" class="WORLD">
<meta expanded="False"/>
<name status="s000000" import="i000004">Locations</name>
</item>
<item handle="0000000000024" parent="None" root="0000000000024" order="0" type="ROOT" class="TIMELINE">
<item handle="0000000000014" parent="None" root="0000000000014" order="0" type="ROOT" class="TIMELINE">
<meta expanded="False"/>
<name status="s000000" import="i000004">Timeline</name>
</item>
<item handle="0000000000025" parent="None" root="0000000000025" order="0" type="ROOT" class="OBJECT">
<item handle="0000000000015" parent="None" root="0000000000015" order="0" type="ROOT" class="OBJECT">
<meta expanded="False"/>
<name status="s000000" import="i000004">Objects</name>
</item>
<item handle="0000000000026" parent="None" root="0000000000026" order="0" type="ROOT" class="CUSTOM">
<item handle="0000000000016" parent="None" root="0000000000016" order="0" type="ROOT" class="CUSTOM">
<meta expanded="False"/>
<name status="s000000" import="i000004">Custom</name>
</item>
<item handle="0000000000027" parent="None" root="0000000000027" order="0" type="ROOT" class="CUSTOM">
<item handle="0000000000017" parent="None" root="0000000000017" order="0" type="ROOT" class="CUSTOM">
<meta expanded="False"/>
<name status="s000000" import="i000004">Custom</name>
</item>
@@ -1,5 +1,5 @@
%%~name: New Note
%%~path: 000000000000a/0000000000020
%%~path: 000000000000a/0000000000010
%%~kind: CHARACTER/NOTE
# Jane Doe
@@ -1,5 +1,5 @@
%%~name: New Note
%%~path: 0000000000009/0000000000021
%%~path: 0000000000009/0000000000011
%%~kind: PLOT/NOTE
# Main Plot
@@ -1,5 +1,5 @@
%%~name: New Note
%%~path: 000000000000b/0000000000022
%%~path: 000000000000b/0000000000012
%%~kind: WORLD/NOTE
# Main Location
@@ -1,32 +1,33 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-20 18:25:48">
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 12:01:37">
<project>
<name>New Project</name>
<title>New Novel</title>
<author>Jane Doe</author>
<saveCount>4</saveCount>
<autoCount>2</autoCount>
<editTime>4</editTime>
<editTime>3</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<language>None</language>
<spellCheck>True</spellCheck>
<spellLang>None</spellLang>
<lastEdited>000000000000f</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>0000000000008</lastNovel>
<lastOutline>0000000000008</lastOutline>
<lastWordCount>163</lastWordCount>
<novelWordCount>136</novelWordCount>
<notesWordCount>27</notesWordCount>
<lastHandle>
<entry key="editor">000000000000f</entry>
<entry key="viewer">None</entry>
<entry key="novelTree">0000000000008</entry>
<entry key="outline">0000000000008</entry>
</lastHandle>
<autoReplace/>
<titleFormat>
<title>%title%</title>
<chapter>%title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>* * *</scene>
<section></section>
<entry key="title">%title%</entry>
<entry key="chapter">%title%</entry>
<entry key="unnumbered">%title%</entry>
<entry key="scene">* * *</entry>
<entry key="section"></entry>
</titleFormat>
<status>
<entry key="s000000" count="5" red="100" green="100" blue="100">New</entry>
@@ -47,7 +48,7 @@
<name status="s000000" import="i000004">Novel</name>
</item>
<item handle="000000000000c" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/>
<meta expanded="False" heading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Title Page</name>
</item>
<item handle="000000000000d" parent="0000000000008" root="0000000000008" order="1" type="FOLDER" class="NOVEL">
@@ -55,38 +56,38 @@
<name status="s000000" import="i000004">New Chapter</name>
</item>
<item handle="000000000000e" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<meta expanded="False" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">New Chapter</name>
</item>
<item handle="000000000000f" parent="000000000000d" root="0000000000008" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H1" charCount="781" wordCount="129" paraCount="14" cursorPos="1010"/>
<meta expanded="False" heading="H1" charCount="781" wordCount="129" paraCount="14" cursorPos="1010"/>
<name status="s000000" import="i000004" active="True">New Scene</name>
</item>
<item handle="0000000000009" parent="None" root="0000000000009" order="1" type="ROOT" class="PLOT">
<meta expanded="True"/>
<name status="s000000" import="i000004">Plot</name>
</item>
<item handle="0000000000021" parent="0000000000009" root="0000000000009" order="0" type="FILE" class="PLOT" layout="NOTE">
<meta expanded="False" mainHeading="H1" charCount="48" wordCount="10" paraCount="1" cursorPos="69"/>
<item handle="0000000000011" parent="0000000000009" root="0000000000009" order="0" type="FILE" class="PLOT" layout="NOTE">
<meta expanded="False" heading="H1" charCount="48" wordCount="10" paraCount="1" cursorPos="69"/>
<name status="s000000" import="i000004" active="True">New Note</name>
</item>
<item handle="000000000000a" parent="None" root="000000000000a" order="2" type="ROOT" class="CHARACTER">
<meta expanded="True"/>
<name status="s000000" import="i000004">Characters</name>
</item>
<item handle="0000000000020" parent="000000000000a" root="000000000000a" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" mainHeading="H1" charCount="34" wordCount="8" paraCount="1" cursorPos="51"/>
<item handle="0000000000010" parent="000000000000a" root="000000000000a" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H1" charCount="34" wordCount="8" paraCount="1" cursorPos="51"/>
<name status="s000000" import="i000004" active="True">New Note</name>
</item>
<item handle="000000000000b" parent="None" root="000000000000b" order="3" type="ROOT" class="WORLD">
<meta expanded="True"/>
<name status="s000000" import="i000004">World</name>
</item>
<item handle="0000000000022" parent="000000000000b" root="000000000000b" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" mainHeading="H1" charCount="51" wordCount="9" paraCount="1" cursorPos="68"/>
<item handle="0000000000012" parent="000000000000b" root="000000000000b" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H1" charCount="51" wordCount="9" paraCount="1" cursorPos="68"/>
<name status="s000000" import="i000004" active="True">New Note</name>
</item>
<item handle="0000000000024" parent="None" root="0000000000024" order="4" type="ROOT" class="TRASH">
<item handle="0000000000014" parent="None" root="0000000000014" order="4" type="ROOT" class="TRASH">
<meta expanded="False"/>
<name status="s000000" import="i000004">Trash</name>
</item>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-20 18:22:03">
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 11:53:30">
<project>
<name>New Project</name>
<title>New Novel</title>
@@ -13,20 +13,21 @@
<language>None</language>
<spellCheck>False</spellCheck>
<spellLang>None</spellLang>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>9</lastWordCount>
<novelWordCount>9</novelWordCount>
<notesWordCount>0</notesWordCount>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
<entry key="novelTree">None</entry>
<entry key="outline">None</entry>
</lastHandle>
<autoReplace/>
<titleFormat>
<title>%title%</title>
<chapter>%title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>* * *</scene>
<section></section>
<entry key="title">%title%</entry>
<entry key="chapter">%title%</entry>
<entry key="unnumbered">%title%</entry>
<entry key="scene">* * *</entry>
<entry key="section"></entry>
</titleFormat>
<status>
<entry key="s000000" count="5" red="100" green="100" blue="100">New</entry>
@@ -47,7 +48,7 @@
<name status="s000000" import="i000004">Novel</name>
</item>
<item handle="000000000000c" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/>
<meta expanded="False" heading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Title Page</name>
</item>
<item handle="000000000000d" parent="0000000000008" root="0000000000008" order="1" type="FOLDER" class="NOVEL">
@@ -55,11 +56,11 @@
<name status="s000000" import="i000004">New Chapter</name>
</item>
<item handle="000000000000e" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<meta expanded="False" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">New Chapter</name>
</item>
<item handle="000000000000f" parent="000000000000d" root="0000000000008" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0"/>
<meta expanded="False" heading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">New Scene</name>
</item>
<item handle="0000000000009" parent="None" root="0000000000009" order="1" type="ROOT" class="PLOT">
+515
View File
@@ -0,0 +1,515 @@
[
{
"handle": "7031beac91f75",
"parent": null,
"root": "7031beac91f75",
"order": 0,
"type": "ROOT",
"class": "NOVEL",
"layout": "NO_LAYOUT",
"expanded": true,
"heading": "H0",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 0,
"label": "Novel",
"status": "sc24b8f",
"import": "ia857f0",
"active": false
},
{
"handle": "53b69b83cdafc",
"parent": "7031beac91f75",
"root": "7031beac91f75",
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": false,
"heading": "H1",
"charCount": 93,
"wordCount": 19,
"paraCount": 2,
"cursorPos": 119,
"label": "Title Page",
"status": "sc24b8f",
"import": "ia857f0",
"active": true
},
{
"handle": "974e400180a99",
"parent": "7031beac91f75",
"root": "7031beac91f75",
"order": 1,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": false,
"heading": "H0",
"charCount": 251,
"wordCount": 50,
"paraCount": 2,
"cursorPos": 277,
"label": "Page",
"status": "sf12341",
"import": "ia857f0",
"active": true
},
{
"handle": "edca4be2fcaf8",
"parent": "7031beac91f75",
"root": "7031beac91f75",
"order": 2,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": false,
"heading": "H1",
"charCount": 26,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 36,
"label": "Part One",
"status": "s90e6c9",
"import": "ia857f0",
"active": true
},
{
"handle": "6a2d6d5f4f401",
"parent": "7031beac91f75",
"root": "7031beac91f75",
"order": 3,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": true,
"heading": "H2",
"charCount": 95,
"wordCount": 18,
"paraCount": 1,
"cursorPos": 291,
"label": "Chapter One",
"status": "sf24ce6",
"import": "ia857f0",
"active": true
},
{
"handle": "636b6aa9b697b",
"parent": "6a2d6d5f4f401",
"root": "7031beac91f75",
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": false,
"heading": "H3",
"charCount": 2687,
"wordCount": 479,
"paraCount": 14,
"cursorPos": 67,
"label": "Making a Scene",
"status": "s90e6c9",
"import": "ia857f0",
"active": true
},
{
"handle": "bc0cbd2a407f3",
"parent": "6a2d6d5f4f401",
"root": "7031beac91f75",
"order": 1,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": false,
"heading": "H3",
"charCount": 548,
"wordCount": 108,
"paraCount": 3,
"cursorPos": 465,
"label": "Another Scene",
"status": "s90e6c9",
"import": "ia857f0",
"active": true
},
{
"handle": "ba8a28a246524",
"parent": "7031beac91f75",
"root": "7031beac91f75",
"order": 4,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": false,
"heading": "H2",
"charCount": 617,
"wordCount": 101,
"paraCount": 3,
"cursorPos": 310,
"label": "Interlude",
"status": "s78ea90",
"import": "ia857f0",
"active": true
},
{
"handle": "96b68994dfa3d",
"parent": "7031beac91f75",
"root": "7031beac91f75",
"order": 5,
"type": "FILE",
"class": "NOVEL",
"layout": "NOTE",
"expanded": false,
"heading": "H1",
"charCount": 1909,
"wordCount": 346,
"paraCount": 7,
"cursorPos": 0,
"label": "A Note on Structure",
"status": "sf24ce6",
"import": "ia857f0",
"active": false
},
{
"handle": "88706ddc78b1b",
"parent": "7031beac91f75",
"root": "7031beac91f75",
"order": 6,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": true,
"heading": "H2",
"charCount": 139,
"wordCount": 28,
"paraCount": 1,
"cursorPos": 188,
"label": "Chapter Two",
"status": "s90e6c9",
"import": "ia857f0",
"active": true
},
{
"handle": "ae7339df26ded",
"parent": "88706ddc78b1b",
"root": "7031beac91f75",
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": false,
"heading": "H3",
"charCount": 189,
"wordCount": 37,
"paraCount": 1,
"cursorPos": 0,
"label": "We Found John!",
"status": "s90e6c9",
"import": "ia857f0",
"active": true
},
{
"handle": "e5e47ebf63b1c",
"parent": null,
"root": "e5e47ebf63b1c",
"order": 1,
"type": "ROOT",
"class": "NOVEL",
"layout": "NO_LAYOUT",
"expanded": true,
"heading": "H0",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 0,
"label": "Sequel",
"status": "sf12341",
"import": "ia857f0",
"active": false
},
{
"handle": "bacb7059e3083",
"parent": "e5e47ebf63b1c",
"root": "e5e47ebf63b1c",
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": false,
"heading": "H1",
"charCount": 27,
"wordCount": 5,
"paraCount": 1,
"cursorPos": 100,
"label": "Title Page",
"status": "sc24b8f",
"import": "ia857f0",
"active": true
},
{
"handle": "a520879ca0b45",
"parent": "e5e47ebf63b1c",
"root": "e5e47ebf63b1c",
"order": 1,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": false,
"heading": "H2",
"charCount": 299,
"wordCount": 55,
"paraCount": 2,
"cursorPos": 104,
"label": "Chapter One",
"status": "s90e6c9",
"import": "ia857f0",
"active": true
},
{
"handle": "f6622b4617424",
"parent": null,
"root": "f6622b4617424",
"order": 2,
"type": "ROOT",
"class": "CHARACTER",
"layout": "NO_LAYOUT",
"expanded": true,
"heading": "H0",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 0,
"label": "Characters",
"status": "sf12341",
"import": "ia857f0",
"active": false
},
{
"handle": "f7e2d9f330615",
"parent": "f6622b4617424",
"root": "f6622b4617424",
"order": 0,
"type": "FOLDER",
"class": "CHARACTER",
"layout": "NO_LAYOUT",
"expanded": true,
"heading": "H0",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 0,
"label": "Main Characters",
"status": "sf12341",
"import": "ia857f0",
"active": false
},
{
"handle": "14298de4d9524",
"parent": "f7e2d9f330615",
"root": "f6622b4617424",
"order": 0,
"type": "FILE",
"class": "CHARACTER",
"layout": "NOTE",
"expanded": false,
"heading": "H1",
"charCount": 49,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 24,
"label": "John Smith",
"status": "sf12341",
"import": "icfb3a5",
"active": true
},
{
"handle": "bb2c23b3c42cc",
"parent": "f7e2d9f330615",
"root": "f6622b4617424",
"order": 1,
"type": "FILE",
"class": "CHARACTER",
"layout": "NOTE",
"expanded": false,
"heading": "H1",
"charCount": 55,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 25,
"label": "Jane Smith",
"status": "sf12341",
"import": "i2d7a54",
"active": true
},
{
"handle": "15c4492bd5107",
"parent": null,
"root": "15c4492bd5107",
"order": 3,
"type": "ROOT",
"class": "WORLD",
"layout": "NO_LAYOUT",
"expanded": true,
"heading": "H0",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 0,
"label": "Locations",
"status": "sf12341",
"import": "ia857f0",
"active": false
},
{
"handle": "b3e74dbc1f584",
"parent": "15c4492bd5107",
"root": "15c4492bd5107",
"order": 0,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE",
"expanded": false,
"heading": "H1",
"charCount": 76,
"wordCount": 15,
"paraCount": 1,
"cursorPos": 20,
"label": "Earth",
"status": "sf12341",
"import": "i56be10",
"active": true
},
{
"handle": "f1471bef9f2ae",
"parent": "15c4492bd5107",
"root": "15c4492bd5107",
"order": 1,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE",
"expanded": false,
"heading": "H1",
"charCount": 115,
"wordCount": 24,
"paraCount": 1,
"cursorPos": 133,
"label": "Space",
"status": "sf12341",
"import": "icfb3a5",
"active": true
},
{
"handle": "5eaea4e8cdee8",
"parent": "15c4492bd5107",
"root": "15c4492bd5107",
"order": 2,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE",
"expanded": false,
"heading": "H1",
"charCount": 28,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 45,
"label": "Mars",
"status": "sf12341",
"import": "i2d7a54",
"active": true
},
{
"handle": "6827118336ac1",
"parent": null,
"root": "6827118336ac1",
"order": 4,
"type": "ROOT",
"class": "ARCHIVE",
"layout": "NO_LAYOUT",
"expanded": true,
"heading": "H0",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 0,
"label": "Archive",
"status": "sf12341",
"import": "ia857f0",
"active": false
},
{
"handle": "ae9bf3c3ea159",
"parent": "6827118336ac1",
"root": "6827118336ac1",
"order": 0,
"type": "FOLDER",
"class": "ARCHIVE",
"layout": "NO_LAYOUT",
"expanded": true,
"heading": "H0",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 0,
"label": "Scenes",
"status": "sf12341",
"import": "ia857f0",
"active": false
},
{
"handle": "8a5deb88c0e97",
"parent": "ae9bf3c3ea159",
"root": "6827118336ac1",
"order": 0,
"type": "FILE",
"class": "ARCHIVE",
"layout": "DOCUMENT",
"expanded": false,
"heading": "H3",
"charCount": 232,
"wordCount": 42,
"paraCount": 1,
"cursorPos": 239,
"label": "Old File",
"status": "s90e6c9",
"import": "ia857f0",
"active": true
},
{
"handle": "98acd8c76c93a",
"parent": null,
"root": "98acd8c76c93a",
"order": 5,
"type": "ROOT",
"class": "TRASH",
"layout": "NO_LAYOUT",
"expanded": true,
"heading": "H0",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 0,
"label": "Trash",
"status": "sf12341",
"import": "ia857f0",
"active": false
},
{
"handle": "b8136a5a774a0",
"parent": "98acd8c76c93a",
"root": "98acd8c76c93a",
"order": 0,
"type": "FILE",
"class": "TRASH",
"layout": "DOCUMENT",
"expanded": false,
"heading": "H3",
"charCount": 30,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 36,
"label": "Delete Me!",
"status": "sf12341",
"import": "ia857f0",
"active": true
}
]
@@ -0,0 +1,362 @@
[
{
"handle": "7031beac91f75",
"parent": null,
"root": null,
"order": 0,
"heading": "H0",
"label": "Novel",
"type": "ROOT",
"class": "NOVEL",
"expanded": true,
"status": "s000002"
},
{
"handle": "53b69b83cdafc",
"parent": "7031beac91f75",
"root": null,
"order": 0,
"heading": "H0",
"label": "Title Page",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 72,
"wordCount": 15,
"paraCount": 2,
"cursorPos": 78,
"status": "s000002"
},
{
"handle": "974e400180a99",
"parent": "7031beac91f75",
"root": null,
"order": 1,
"heading": "H0",
"label": "Page",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 208,
"wordCount": 40,
"paraCount": 2,
"cursorPos": 213,
"status": "s000000"
},
{
"handle": "edca4be2fcaf8",
"parent": "7031beac91f75",
"root": null,
"order": 2,
"heading": "H0",
"label": "Part One",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 23,
"wordCount": 5,
"paraCount": 1,
"cursorPos": 0,
"status": "s000000"
},
{
"handle": "e7ded148d6e4a",
"parent": "7031beac91f75",
"root": null,
"order": 3,
"heading": "H0",
"label": "A Folder",
"type": "FOLDER",
"class": "NOVEL",
"expanded": true,
"status": "s000003"
},
{
"handle": "6a2d6d5f4f401",
"parent": "e7ded148d6e4a",
"root": null,
"order": 0,
"heading": "H0",
"label": "Chapter One",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 12,
"wordCount": 3,
"paraCount": 0,
"cursorPos": 215,
"status": "s000001"
},
{
"handle": "636b6aa9b697b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 1,
"heading": "H0",
"label": "Making a Scene",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 1199,
"wordCount": 216,
"paraCount": 7,
"cursorPos": 527,
"status": "s000003"
},
{
"handle": "bc0cbd2a407f3",
"parent": "e7ded148d6e4a",
"root": null,
"order": 2,
"heading": "H0",
"label": "Another Scene",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 476,
"wordCount": 93,
"paraCount": 3,
"cursorPos": 551,
"status": "s000003"
},
{
"handle": "ba8a28a246524",
"parent": "e7ded148d6e4a",
"root": null,
"order": 3,
"heading": "H0",
"label": "Interlude",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 633,
"wordCount": 101,
"paraCount": 3,
"cursorPos": 1238,
"status": "s000006"
},
{
"handle": "96b68994dfa3d",
"parent": "e7ded148d6e4a",
"root": null,
"order": 4,
"heading": "H0",
"label": "A Note on Structure",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": false,
"layout": "NOTE",
"charCount": 1692,
"wordCount": 313,
"paraCount": 6,
"cursorPos": 1721,
"status": "s000004"
},
{
"handle": "88706ddc78b1b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 5,
"heading": "H0",
"label": "Chapter Two",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 139,
"wordCount": 28,
"paraCount": 1,
"cursorPos": 343,
"status": "s000003"
},
{
"handle": "ae7339df26ded",
"parent": "e7ded148d6e4a",
"root": null,
"order": 6,
"heading": "H0",
"label": "We Found John!",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 189,
"wordCount": 37,
"paraCount": 1,
"cursorPos": 224,
"status": "s000003"
},
{
"handle": "f6622b4617424",
"parent": null,
"root": null,
"order": 1,
"heading": "H0",
"label": "Characters",
"type": "ROOT",
"class": "CHARACTER",
"expanded": true,
"import": null
},
{
"handle": "f7e2d9f330615",
"parent": "f6622b4617424",
"root": null,
"order": 0,
"heading": "H0",
"label": "Main Characters",
"type": "FOLDER",
"class": "CHARACTER",
"expanded": true,
"import": null
},
{
"handle": "14298de4d9524",
"parent": "f7e2d9f330615",
"root": null,
"order": 0,
"heading": "H0",
"label": "John Smith",
"type": "FILE",
"class": "CHARACTER",
"expanded": false,
"active": true,
"layout": "NOTE",
"charCount": 49,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 24,
"import": "i000008"
},
{
"handle": "bb2c23b3c42cc",
"parent": "f7e2d9f330615",
"root": null,
"order": 1,
"heading": "H0",
"label": "Jane Smith",
"type": "FILE",
"class": "CHARACTER",
"expanded": false,
"active": true,
"layout": "NOTE",
"charCount": 55,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 25,
"import": "i000009"
},
{
"handle": "15c4492bd5107",
"parent": null,
"root": null,
"order": 2,
"heading": "H0",
"label": "Locations",
"type": "ROOT",
"class": "WORLD",
"expanded": true,
"import": null
},
{
"handle": "b3e74dbc1f584",
"parent": "15c4492bd5107",
"root": null,
"order": 0,
"heading": "H0",
"label": "Earth",
"type": "FILE",
"class": "WORLD",
"expanded": false,
"active": true,
"layout": "NOTE",
"charCount": 76,
"wordCount": 15,
"paraCount": 1,
"cursorPos": 20,
"import": "i00000a"
},
{
"handle": "f1471bef9f2ae",
"parent": "15c4492bd5107",
"root": null,
"order": 1,
"heading": "H0",
"label": "Space",
"type": "FILE",
"class": "WORLD",
"expanded": false,
"active": true,
"layout": "NOTE",
"charCount": 115,
"wordCount": 24,
"paraCount": 1,
"cursorPos": 133,
"import": "i000008"
},
{
"handle": "5eaea4e8cdee8",
"parent": "15c4492bd5107",
"root": null,
"order": 2,
"heading": "H0",
"label": "Mars",
"type": "FILE",
"class": "WORLD",
"expanded": false,
"active": true,
"layout": "NOTE",
"charCount": 28,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 45,
"import": "i000009"
},
{
"handle": "98acd8c76c93a",
"parent": null,
"root": null,
"order": 3,
"heading": "H0",
"label": "Trash",
"type": "ROOT",
"class": "TRASH",
"expanded": true,
"import": null
},
{
"handle": "b8136a5a774a0",
"parent": "98acd8c76c93a",
"root": null,
"order": 0,
"heading": "H0",
"label": "Delete Me!",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 36,
"status": "s000000"
}
]
+143
View File
@@ -0,0 +1,143 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2020-05-28 09:59:15">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>0</saveCount>
<autoCount>0</autoCount>
<editTime>1000</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<language>None</language>
<spellCheck>True</spellCheck>
<spellLang>None</spellLang>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
<entry key="novelTree">None</entry>
<entry key="outline">None</entry>
</lastHandle>
<autoReplace>
<entry key="A">B</entry>
<entry key="B">E</entry>
<entry key="C">D</entry>
</autoReplace>
<titleFormat>
<entry key="title">%title%</entry>
<entry key="chapter">Chapter %ch%: %title%</entry>
<entry key="unnumbered">%title%</entry>
<entry key="scene">Scene %ch%.%sc%: %title%</entry>
<entry key="section"></entry>
</titleFormat>
<status>
<entry key="s000000" count="0" red="100" green="100" blue="100">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Notes</entry>
<entry key="s000002" count="0" red="182" green="60" blue="0">Started</entry>
<entry key="s000003" count="0" red="193" green="129" blue="0">1st Draft</entry>
<entry key="s000004" count="0" red="193" green="129" blue="0">2nd Draft</entry>
<entry key="s000005" count="0" red="193" green="129" blue="0">3rd Draft</entry>
<entry key="s000006" count="0" red="58" green="180" blue="58">Finished</entry>
</status>
<importance>
<entry key="i000007" count="0" red="100" green="100" blue="100">None</entry>
<entry key="i000008" count="0" red="0" green="122" blue="188">Minor</entry>
<entry key="i000009" count="0" red="21" green="0" blue="180">Major</entry>
<entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry>
</importance>
</settings>
<content count="22">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/>
<name status="s000002" import="i000007">Novel</name>
</item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="72" wordCount="15" paraCount="2" cursorPos="78"/>
<name status="s000002" import="i000007" active="True">Title Page</name>
</item>
<item handle="974e400180a99" parent="7031beac91f75" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="208" wordCount="40" paraCount="2" cursorPos="213"/>
<name status="s000000" import="i000007" active="True">Page</name>
</item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="23" wordCount="5" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000007" active="True">Part One</name>
</item>
<item handle="e7ded148d6e4a" parent="7031beac91f75" root="None" order="3" type="FOLDER" class="NOVEL">
<meta expanded="True"/>
<name status="s000003" import="i000007">A Folder</name>
</item>
<item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="12" wordCount="3" paraCount="0" cursorPos="215"/>
<name status="s000001" import="i000007" active="True">Chapter One</name>
</item>
<item handle="636b6aa9b697b" parent="e7ded148d6e4a" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="1199" wordCount="216" paraCount="7" cursorPos="527"/>
<name status="s000003" import="i000007" active="True">Making a Scene</name>
</item>
<item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="476" wordCount="93" paraCount="3" cursorPos="551"/>
<name status="s000003" import="i000007" active="True">Another Scene</name>
</item>
<item handle="ba8a28a246524" parent="e7ded148d6e4a" root="None" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="633" wordCount="101" paraCount="3" cursorPos="1238"/>
<name status="s000006" import="i000007" active="True">Interlude</name>
</item>
<item handle="96b68994dfa3d" parent="e7ded148d6e4a" root="None" order="4" type="FILE" class="NOVEL" layout="NOTE">
<meta expanded="False" heading="H0" charCount="1692" wordCount="313" paraCount="6" cursorPos="1721"/>
<name status="s000004" import="i000007" active="False">A Note on Structure</name>
</item>
<item handle="88706ddc78b1b" parent="e7ded148d6e4a" root="None" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<name status="s000003" import="i000007" active="True">Chapter Two</name>
</item>
<item handle="ae7339df26ded" parent="e7ded148d6e4a" root="None" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="s000003" import="i000007" active="True">We Found John!</name>
</item>
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="1" type="ROOT" class="CHARACTER">
<meta expanded="True"/>
<name status="s000000" import="i000007">Characters</name>
</item>
<item handle="f7e2d9f330615" parent="f6622b4617424" root="None" order="0" type="FOLDER" class="CHARACTER">
<meta expanded="True"/>
<name status="s000000" import="i000007">Main Characters</name>
</item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="None" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="s000000" import="i000008" active="True">John Smith</name>
</item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="None" order="1" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="s000000" import="i000009" active="True">Jane Smith</name>
</item>
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="2" type="ROOT" class="WORLD">
<meta expanded="True"/>
<name status="s000000" import="i000007">Locations</name>
</item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="None" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="s000000" import="i00000a" active="True">Earth</name>
</item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="None" order="1" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="s000000" import="i000008" active="True">Space</name>
</item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="None" order="2" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="s000000" import="i000009" active="True">Mars</name>
</item>
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="3" type="ROOT" class="TRASH">
<meta expanded="True"/>
<name status="s000000" import="i000007">Trash</name>
</item>
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="36"/>
<name status="s000000" import="i000007" active="True">Delete Me!</name>
</item>
</content>
</novelWriterXML>
@@ -0,0 +1,346 @@
[
{
"handle": "7031beac91f75",
"parent": null,
"root": null,
"order": 0,
"heading": "H0",
"label": "Novel",
"type": "ROOT",
"class": "NOVEL",
"expanded": true,
"status": "s000002"
},
{
"handle": "53b69b83cdafc",
"parent": "7031beac91f75",
"root": null,
"order": 0,
"heading": "H0",
"label": "Title Page",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 72,
"wordCount": 15,
"paraCount": 2,
"cursorPos": 78,
"status": "s000002"
},
{
"handle": "974e400180a99",
"parent": "7031beac91f75",
"root": null,
"order": 1,
"heading": "H0",
"label": "Page",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 210,
"wordCount": 40,
"paraCount": 2,
"cursorPos": 213,
"status": "s000000"
},
{
"handle": "edca4be2fcaf8",
"parent": "7031beac91f75",
"root": null,
"order": 2,
"heading": "H0",
"label": "Part One",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 23,
"wordCount": 5,
"paraCount": 1,
"cursorPos": 0,
"status": "s000000"
},
{
"handle": "e7ded148d6e4a",
"parent": "7031beac91f75",
"root": null,
"order": 3,
"heading": "H0",
"label": "A Folder",
"type": "FOLDER",
"class": "NOVEL",
"expanded": true,
"status": "s000003"
},
{
"handle": "6a2d6d5f4f401",
"parent": "e7ded148d6e4a",
"root": null,
"order": 0,
"heading": "H0",
"label": "Chapter One",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 12,
"wordCount": 3,
"paraCount": 0,
"cursorPos": 215,
"status": "s000001"
},
{
"handle": "636b6aa9b697b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 1,
"heading": "H0",
"label": "Making a Scene",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 1483,
"wordCount": 263,
"paraCount": 8,
"cursorPos": 1086,
"status": "s000003"
},
{
"handle": "bc0cbd2a407f3",
"parent": "e7ded148d6e4a",
"root": null,
"order": 2,
"heading": "H0",
"label": "Another Scene",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 476,
"wordCount": 93,
"paraCount": 3,
"cursorPos": 428,
"status": "s000003"
},
{
"handle": "ba8a28a246524",
"parent": "e7ded148d6e4a",
"root": null,
"order": 3,
"heading": "H0",
"label": "Interlude",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 633,
"wordCount": 101,
"paraCount": 3,
"cursorPos": 1238,
"status": "s000006"
},
{
"handle": "96b68994dfa3d",
"parent": "e7ded148d6e4a",
"root": null,
"order": 4,
"heading": "H0",
"label": "A Note on Structure",
"type": "FILE",
"class": "NOVEL",
"active": false,
"layout": "NOTE",
"charCount": 1692,
"wordCount": 313,
"paraCount": 6,
"cursorPos": 1721,
"status": "s000004"
},
{
"handle": "88706ddc78b1b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 5,
"heading": "H0",
"label": "Chapter Two",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 139,
"wordCount": 28,
"paraCount": 1,
"cursorPos": 343,
"status": "s000003"
},
{
"handle": "ae7339df26ded",
"parent": "e7ded148d6e4a",
"root": null,
"order": 6,
"heading": "H0",
"label": "We Found John!",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 189,
"wordCount": 37,
"paraCount": 1,
"cursorPos": 224,
"status": "s000003"
},
{
"handle": "f6622b4617424",
"parent": null,
"root": null,
"order": 1,
"heading": "H0",
"label": "Characters",
"type": "ROOT",
"class": "CHARACTER",
"expanded": true,
"import": null
},
{
"handle": "f7e2d9f330615",
"parent": "f6622b4617424",
"root": null,
"order": 0,
"heading": "H0",
"label": "Main Characters",
"type": "FOLDER",
"class": "CHARACTER",
"expanded": true,
"import": null
},
{
"handle": "14298de4d9524",
"parent": "f7e2d9f330615",
"root": null,
"order": 0,
"heading": "H0",
"label": "John Smith",
"type": "FILE",
"class": "CHARACTER",
"active": true,
"layout": "NOTE",
"charCount": 49,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 24,
"import": "i000008"
},
{
"handle": "bb2c23b3c42cc",
"parent": "f7e2d9f330615",
"root": null,
"order": 1,
"heading": "H0",
"label": "Jane Smith",
"type": "FILE",
"class": "CHARACTER",
"active": true,
"layout": "NOTE",
"charCount": 55,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 25,
"import": "i000009"
},
{
"handle": "15c4492bd5107",
"parent": null,
"root": null,
"order": 2,
"heading": "H0",
"label": "Locations",
"type": "ROOT",
"class": "WORLD",
"expanded": true,
"import": null
},
{
"handle": "b3e74dbc1f584",
"parent": "15c4492bd5107",
"root": null,
"order": 0,
"heading": "H0",
"label": "Earth",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 76,
"wordCount": 15,
"paraCount": 1,
"cursorPos": 20,
"import": "i00000a"
},
{
"handle": "f1471bef9f2ae",
"parent": "15c4492bd5107",
"root": null,
"order": 1,
"heading": "H0",
"label": "Space",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 115,
"wordCount": 24,
"paraCount": 1,
"cursorPos": 133,
"import": "i000008"
},
{
"handle": "5eaea4e8cdee8",
"parent": "15c4492bd5107",
"root": null,
"order": 2,
"heading": "H0",
"label": "Mars",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 28,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 45,
"import": "i000009"
},
{
"handle": "98acd8c76c93a",
"parent": null,
"root": null,
"order": 3,
"heading": "H0",
"label": "Trash",
"type": "ROOT",
"class": "TRASH",
"expanded": true,
"import": null
},
{
"handle": "b8136a5a774a0",
"parent": "98acd8c76c93a",
"root": null,
"order": 0,
"heading": "H0",
"label": "Delete Me!",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 30,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 36,
"status": "s000000"
}
]
+143
View File
@@ -0,0 +1,143 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2020-06-26 21:20:24">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>5</saveCount>
<autoCount>10</autoCount>
<editTime>1000</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<language>None</language>
<spellCheck>True</spellCheck>
<spellLang>None</spellLang>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
<entry key="novelTree">None</entry>
<entry key="outline">None</entry>
</lastHandle>
<autoReplace>
<entry key="A">B</entry>
<entry key="B">E</entry>
<entry key="C">D</entry>
</autoReplace>
<titleFormat>
<entry key="title">%title%</entry>
<entry key="chapter">Chapter %ch%: %title%</entry>
<entry key="unnumbered">%title%</entry>
<entry key="scene">Scene %ch%.%sc%: %title%</entry>
<entry key="section"></entry>
</titleFormat>
<status>
<entry key="s000000" count="0" red="100" green="100" blue="100">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Notes</entry>
<entry key="s000002" count="0" red="182" green="60" blue="0">Started</entry>
<entry key="s000003" count="0" red="193" green="129" blue="0">1st Draft</entry>
<entry key="s000004" count="0" red="193" green="129" blue="0">2nd Draft</entry>
<entry key="s000005" count="0" red="193" green="129" blue="0">3rd Draft</entry>
<entry key="s000006" count="0" red="58" green="180" blue="58">Finished</entry>
</status>
<importance>
<entry key="i000007" count="0" red="100" green="100" blue="100">None</entry>
<entry key="i000008" count="0" red="0" green="122" blue="188">Minor</entry>
<entry key="i000009" count="0" red="21" green="0" blue="180">Major</entry>
<entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry>
</importance>
</settings>
<content count="22">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/>
<name status="s000002" import="i000007">Novel</name>
</item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="72" wordCount="15" paraCount="2" cursorPos="78"/>
<name status="s000002" import="i000007" active="True">Title Page</name>
</item>
<item handle="974e400180a99" parent="7031beac91f75" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="210" wordCount="40" paraCount="2" cursorPos="213"/>
<name status="s000000" import="i000007" active="True">Page</name>
</item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="23" wordCount="5" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000007" active="True">Part One</name>
</item>
<item handle="e7ded148d6e4a" parent="7031beac91f75" root="None" order="3" type="FOLDER" class="NOVEL">
<meta expanded="True"/>
<name status="s000003" import="i000007">A Folder</name>
</item>
<item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="12" wordCount="3" paraCount="0" cursorPos="215"/>
<name status="s000001" import="i000007" active="True">Chapter One</name>
</item>
<item handle="636b6aa9b697b" parent="e7ded148d6e4a" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="1483" wordCount="263" paraCount="8" cursorPos="1086"/>
<name status="s000003" import="i000007" active="True">Making a Scene</name>
</item>
<item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="476" wordCount="93" paraCount="3" cursorPos="428"/>
<name status="s000003" import="i000007" active="True">Another Scene</name>
</item>
<item handle="ba8a28a246524" parent="e7ded148d6e4a" root="None" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="633" wordCount="101" paraCount="3" cursorPos="1238"/>
<name status="s000006" import="i000007" active="True">Interlude</name>
</item>
<item handle="96b68994dfa3d" parent="e7ded148d6e4a" root="None" order="4" type="FILE" class="NOVEL" layout="NOTE">
<meta expanded="False" heading="H0" charCount="1692" wordCount="313" paraCount="6" cursorPos="1721"/>
<name status="s000004" import="i000007" active="False">A Note on Structure</name>
</item>
<item handle="88706ddc78b1b" parent="e7ded148d6e4a" root="None" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<name status="s000003" import="i000007" active="True">Chapter Two</name>
</item>
<item handle="ae7339df26ded" parent="e7ded148d6e4a" root="None" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="s000003" import="i000007" active="True">We Found John!</name>
</item>
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="1" type="ROOT" class="CHARACTER">
<meta expanded="True"/>
<name status="s000000" import="i000007">Characters</name>
</item>
<item handle="f7e2d9f330615" parent="f6622b4617424" root="None" order="0" type="FOLDER" class="CHARACTER">
<meta expanded="True"/>
<name status="s000000" import="i000007">Main Characters</name>
</item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="None" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="s000000" import="i000008" active="True">John Smith</name>
</item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="None" order="1" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="s000000" import="i000009" active="True">Jane Smith</name>
</item>
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="2" type="ROOT" class="WORLD">
<meta expanded="True"/>
<name status="s000000" import="i000007">Locations</name>
</item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="None" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="s000000" import="i00000a" active="True">Earth</name>
</item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="None" order="1" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="s000000" import="i000008" active="True">Space</name>
</item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="None" order="2" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="s000000" import="i000009" active="True">Mars</name>
</item>
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="3" type="ROOT" class="TRASH">
<meta expanded="True"/>
<name status="s000000" import="i000007">Trash</name>
</item>
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s000000" import="i000007" active="True">Delete Me!</name>
</item>
</content>
</novelWriterXML>
@@ -0,0 +1,387 @@
[
{
"handle": "7031beac91f75",
"parent": null,
"root": null,
"order": 0,
"heading": "H0",
"label": "Novel",
"type": "ROOT",
"class": "NOVEL",
"expanded": true,
"status": "s000002"
},
{
"handle": "53b69b83cdafc",
"parent": "7031beac91f75",
"root": null,
"order": 0,
"heading": "H0",
"label": "Title Page",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 241,
"wordCount": 42,
"paraCount": 3,
"cursorPos": 252,
"status": "s000002"
},
{
"handle": "974e400180a99",
"parent": "7031beac91f75",
"root": null,
"order": 1,
"heading": "H0",
"label": "Page",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 125,
"wordCount": 26,
"paraCount": 2,
"cursorPos": 127,
"status": "s000000"
},
{
"handle": "edca4be2fcaf8",
"parent": "7031beac91f75",
"root": null,
"order": 2,
"heading": "H0",
"label": "Part One",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 26,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 30,
"status": "s000000"
},
{
"handle": "e7ded148d6e4a",
"parent": "7031beac91f75",
"root": null,
"order": 3,
"heading": "H0",
"label": "A Folder",
"type": "FOLDER",
"class": "NOVEL",
"expanded": true,
"status": "s000003"
},
{
"handle": "6a2d6d5f4f401",
"parent": "e7ded148d6e4a",
"root": null,
"order": 0,
"heading": "H0",
"label": "Chapter One",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 75,
"wordCount": 14,
"paraCount": 1,
"cursorPos": 279,
"status": "s000001"
},
{
"handle": "636b6aa9b697b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 1,
"heading": "H0",
"label": "Making a Scene",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 2429,
"wordCount": 432,
"paraCount": 14,
"cursorPos": 61,
"status": "s000003"
},
{
"handle": "bc0cbd2a407f3",
"parent": "e7ded148d6e4a",
"root": null,
"order": 2,
"heading": "H0",
"label": "Another Scene",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 476,
"wordCount": 93,
"paraCount": 3,
"cursorPos": 577,
"status": "s000003"
},
{
"handle": "ba8a28a246524",
"parent": "e7ded148d6e4a",
"root": null,
"order": 3,
"heading": "H0",
"label": "Interlude",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 617,
"wordCount": 101,
"paraCount": 3,
"cursorPos": 1137,
"status": "s000000"
},
{
"handle": "96b68994dfa3d",
"parent": "e7ded148d6e4a",
"root": null,
"order": 4,
"heading": "H0",
"label": "A Note on Structure",
"type": "FILE",
"class": "NOVEL",
"active": false,
"layout": "NOTE",
"charCount": 1692,
"wordCount": 313,
"paraCount": 6,
"cursorPos": 1110,
"status": "s000004"
},
{
"handle": "88706ddc78b1b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 5,
"heading": "H0",
"label": "Chapter Two",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 139,
"wordCount": 28,
"paraCount": 1,
"cursorPos": 343,
"status": "s000003"
},
{
"handle": "ae7339df26ded",
"parent": "e7ded148d6e4a",
"root": null,
"order": 6,
"heading": "H0",
"label": "We Found John!",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 189,
"wordCount": 37,
"paraCount": 1,
"cursorPos": 224,
"status": "s000003"
},
{
"handle": "f6622b4617424",
"parent": null,
"root": null,
"order": 1,
"heading": "H0",
"label": "Characters",
"type": "ROOT",
"class": "CHARACTER",
"expanded": true,
"import": null
},
{
"handle": "f7e2d9f330615",
"parent": "f6622b4617424",
"root": null,
"order": 0,
"heading": "H0",
"label": "Main Characters",
"type": "FOLDER",
"class": "CHARACTER",
"expanded": true,
"import": null
},
{
"handle": "14298de4d9524",
"parent": "f7e2d9f330615",
"root": null,
"order": 0,
"heading": "H0",
"label": "John Smith",
"type": "FILE",
"class": "CHARACTER",
"active": true,
"layout": "NOTE",
"charCount": 49,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 24,
"import": "i000008"
},
{
"handle": "bb2c23b3c42cc",
"parent": "f7e2d9f330615",
"root": null,
"order": 1,
"heading": "H0",
"label": "Jane Smith",
"type": "FILE",
"class": "CHARACTER",
"active": true,
"layout": "NOTE",
"charCount": 55,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 25,
"import": "i000009"
},
{
"handle": "15c4492bd5107",
"parent": null,
"root": null,
"order": 2,
"heading": "H0",
"label": "Locations",
"type": "ROOT",
"class": "WORLD",
"expanded": true,
"import": null
},
{
"handle": "b3e74dbc1f584",
"parent": "15c4492bd5107",
"root": null,
"order": 0,
"heading": "H0",
"label": "Earth",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 76,
"wordCount": 15,
"paraCount": 1,
"cursorPos": 20,
"import": "i00000a"
},
{
"handle": "f1471bef9f2ae",
"parent": "15c4492bd5107",
"root": null,
"order": 1,
"heading": "H0",
"label": "Space",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 115,
"wordCount": 24,
"paraCount": 1,
"cursorPos": 133,
"import": "i000008"
},
{
"handle": "5eaea4e8cdee8",
"parent": "15c4492bd5107",
"root": null,
"order": 2,
"heading": "H0",
"label": "Mars",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 28,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 45,
"import": "i000009"
},
{
"handle": "6827118336ac1",
"parent": null,
"root": null,
"order": 3,
"heading": "H0",
"label": "Outtakes",
"type": "ROOT",
"class": "ARCHIVE",
"expanded": true,
"status": null
},
{
"handle": "ae9bf3c3ea159",
"parent": "6827118336ac1",
"root": null,
"order": 0,
"heading": "H0",
"label": "Scenes",
"type": "FOLDER",
"class": "ARCHIVE",
"expanded": true,
"status": null
},
{
"handle": "8a5deb88c0e97",
"parent": "ae9bf3c3ea159",
"root": null,
"order": 0,
"heading": "H0",
"label": "Old File",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 315,
"wordCount": 55,
"paraCount": 1,
"cursorPos": 322,
"status": "s000003"
},
{
"handle": "98acd8c76c93a",
"parent": null,
"root": null,
"order": 4,
"heading": "H0",
"label": "Trash",
"type": "ROOT",
"class": "TRASH",
"expanded": true,
"import": null
},
{
"handle": "b8136a5a774a0",
"parent": "98acd8c76c93a",
"root": null,
"order": 0,
"heading": "H0",
"label": "Delete Me!",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 30,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 36,
"status": "s000000"
}
]
+155
View File
@@ -0,0 +1,155 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2021-08-30 23:33:44">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>5</saveCount>
<autoCount>10</autoCount>
<editTime>1000</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<language>en_GB</language>
<spellCheck>True</spellCheck>
<spellLang>en_GB</spellLang>
<novelWordCount>840</novelWordCount>
<notesWordCount>376</notesWordCount>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
<entry key="novelTree">None</entry>
<entry key="outline">None</entry>
</lastHandle>
<autoReplace>
<entry key="A">B</entry>
<entry key="B">E</entry>
<entry key="C">D</entry>
</autoReplace>
<titleFormat>
<entry key="title">%title%</entry>
<entry key="chapter">Chapter %chw%: %title%</entry>
<entry key="unnumbered">%title%</entry>
<entry key="scene">Scene %ch%.%sc%: %title%</entry>
<entry key="section"></entry>
</titleFormat>
<status>
<entry key="s000000" count="0" red="100" green="100" blue="100">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Notes</entry>
<entry key="s000002" count="0" red="182" green="60" blue="0">Started</entry>
<entry key="s000003" count="0" red="193" green="129" blue="0">1st Draft</entry>
<entry key="s000004" count="0" red="193" green="129" blue="0">2nd Draft</entry>
<entry key="s000005" count="0" red="193" green="129" blue="0">3rd Draft</entry>
<entry key="s000006" count="0" red="58" green="180" blue="58">Finished</entry>
</status>
<importance>
<entry key="i000007" count="0" red="100" green="100" blue="100">None</entry>
<entry key="i000008" count="0" red="0" green="122" blue="188">Minor</entry>
<entry key="i000009" count="0" red="21" green="0" blue="180">Major</entry>
<entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry>
</importance>
</settings>
<content count="25">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/>
<name status="s000002" import="i000007">Novel</name>
</item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="241" wordCount="42" paraCount="3" cursorPos="252"/>
<name status="s000002" import="i000007" active="True">Title Page</name>
</item>
<item handle="974e400180a99" parent="7031beac91f75" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="125" wordCount="26" paraCount="2" cursorPos="127"/>
<name status="s000000" import="i000007" active="True">Page</name>
</item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="26" wordCount="6" paraCount="1" cursorPos="30"/>
<name status="s000000" import="i000007" active="True">Part One</name>
</item>
<item handle="e7ded148d6e4a" parent="7031beac91f75" root="None" order="3" type="FOLDER" class="NOVEL">
<meta expanded="True"/>
<name status="s000003" import="i000007">A Folder</name>
</item>
<item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="75" wordCount="14" paraCount="1" cursorPos="279"/>
<name status="s000001" import="i000007" active="True">Chapter One</name>
</item>
<item handle="636b6aa9b697b" parent="e7ded148d6e4a" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="2429" wordCount="432" paraCount="14" cursorPos="61"/>
<name status="s000003" import="i000007" active="True">Making a Scene</name>
</item>
<item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="476" wordCount="93" paraCount="3" cursorPos="577"/>
<name status="s000003" import="i000007" active="True">Another Scene</name>
</item>
<item handle="ba8a28a246524" parent="e7ded148d6e4a" root="None" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="617" wordCount="101" paraCount="3" cursorPos="1137"/>
<name status="s000000" import="i000007" active="True">Interlude</name>
</item>
<item handle="96b68994dfa3d" parent="e7ded148d6e4a" root="None" order="4" type="FILE" class="NOVEL" layout="NOTE">
<meta expanded="False" heading="H0" charCount="1692" wordCount="313" paraCount="6" cursorPos="1110"/>
<name status="s000004" import="i000007" active="False">A Note on Structure</name>
</item>
<item handle="88706ddc78b1b" parent="e7ded148d6e4a" root="None" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<name status="s000003" import="i000007" active="True">Chapter Two</name>
</item>
<item handle="ae7339df26ded" parent="e7ded148d6e4a" root="None" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="s000003" import="i000007" active="True">We Found John!</name>
</item>
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="1" type="ROOT" class="CHARACTER">
<meta expanded="True"/>
<name status="s000000" import="i000007">Characters</name>
</item>
<item handle="f7e2d9f330615" parent="f6622b4617424" root="None" order="0" type="FOLDER" class="CHARACTER">
<meta expanded="True"/>
<name status="s000000" import="i000007">Main Characters</name>
</item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="None" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="s000000" import="i000008" active="True">John Smith</name>
</item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="None" order="1" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="s000000" import="i000009" active="True">Jane Smith</name>
</item>
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="2" type="ROOT" class="WORLD">
<meta expanded="True"/>
<name status="s000000" import="i000007">Locations</name>
</item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="None" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="s000000" import="i00000a" active="True">Earth</name>
</item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="None" order="1" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="s000000" import="i000008" active="True">Space</name>
</item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="None" order="2" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="s000000" import="i000009" active="True">Mars</name>
</item>
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="3" type="ROOT" class="ARCHIVE">
<meta expanded="True"/>
<name status="s000000" import="i000007">Outtakes</name>
</item>
<item handle="ae9bf3c3ea159" parent="6827118336ac1" root="None" order="0" type="FOLDER" class="ARCHIVE">
<meta expanded="True"/>
<name status="s000000" import="i000007">Scenes</name>
</item>
<item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="315" wordCount="55" paraCount="1" cursorPos="322"/>
<name status="s000003" import="i000007" active="True">Old File</name>
</item>
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="4" type="ROOT" class="TRASH">
<meta expanded="True"/>
<name status="s000000" import="i000007">Trash</name>
</item>
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s000000" import="i000007" active="True">Delete Me!</name>
</item>
</content>
</novelWriterXML>
@@ -0,0 +1,387 @@
[
{
"handle": "7031beac91f75",
"parent": null,
"root": null,
"order": 0,
"heading": "H0",
"label": "Novel",
"type": "ROOT",
"class": "NOVEL",
"expanded": true,
"status": "s000002"
},
{
"handle": "53b69b83cdafc",
"parent": "7031beac91f75",
"root": null,
"order": 0,
"heading": "H0",
"label": "Title Page",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 93,
"wordCount": 19,
"paraCount": 2,
"cursorPos": 2,
"status": "s000002"
},
{
"handle": "974e400180a99",
"parent": "7031beac91f75",
"root": null,
"order": 1,
"heading": "H0",
"label": "Page",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 186,
"wordCount": 39,
"paraCount": 2,
"cursorPos": 212,
"status": "s000000"
},
{
"handle": "edca4be2fcaf8",
"parent": "7031beac91f75",
"root": null,
"order": 2,
"heading": "H0",
"label": "Part One",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 26,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 33,
"status": "s000000"
},
{
"handle": "e7ded148d6e4a",
"parent": "7031beac91f75",
"root": null,
"order": 3,
"heading": "H0",
"label": "A Folder",
"type": "FOLDER",
"class": "NOVEL",
"expanded": true,
"status": "s000003"
},
{
"handle": "6a2d6d5f4f401",
"parent": "e7ded148d6e4a",
"root": null,
"order": 0,
"heading": "H0",
"label": "Chapter One",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 75,
"wordCount": 14,
"paraCount": 1,
"cursorPos": 279,
"status": "s000001"
},
{
"handle": "636b6aa9b697b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 1,
"heading": "H0",
"label": "Making a Scene",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 2429,
"wordCount": 432,
"paraCount": 14,
"cursorPos": 62,
"status": "s000003"
},
{
"handle": "bc0cbd2a407f3",
"parent": "e7ded148d6e4a",
"root": null,
"order": 2,
"heading": "H0",
"label": "Another Scene",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 476,
"wordCount": 93,
"paraCount": 3,
"cursorPos": 577,
"status": "s000003"
},
{
"handle": "ba8a28a246524",
"parent": "e7ded148d6e4a",
"root": null,
"order": 3,
"heading": "H0",
"label": "Interlude",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 617,
"wordCount": 101,
"paraCount": 3,
"cursorPos": 4,
"status": "s000000"
},
{
"handle": "96b68994dfa3d",
"parent": "e7ded148d6e4a",
"root": null,
"order": 4,
"heading": "H0",
"label": "A Note on Structure",
"type": "FILE",
"class": "NOVEL",
"active": false,
"layout": "NOTE",
"charCount": 1692,
"wordCount": 313,
"paraCount": 6,
"cursorPos": 1110,
"status": "s000004"
},
{
"handle": "88706ddc78b1b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 5,
"heading": "H0",
"label": "Chapter Two",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 139,
"wordCount": 28,
"paraCount": 1,
"cursorPos": 343,
"status": "s000003"
},
{
"handle": "ae7339df26ded",
"parent": "e7ded148d6e4a",
"root": null,
"order": 6,
"heading": "H0",
"label": "We Found John!",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 189,
"wordCount": 37,
"paraCount": 1,
"cursorPos": 224,
"status": "s000003"
},
{
"handle": "f6622b4617424",
"parent": null,
"root": null,
"order": 1,
"heading": "H0",
"label": "Characters",
"type": "ROOT",
"class": "CHARACTER",
"expanded": true,
"import": null
},
{
"handle": "f7e2d9f330615",
"parent": "f6622b4617424",
"root": null,
"order": 0,
"heading": "H0",
"label": "Main Characters",
"type": "FOLDER",
"class": "CHARACTER",
"expanded": true,
"import": null
},
{
"handle": "14298de4d9524",
"parent": "f7e2d9f330615",
"root": null,
"order": 0,
"heading": "H0",
"label": "John Smith",
"type": "FILE",
"class": "CHARACTER",
"active": true,
"layout": "NOTE",
"charCount": 49,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 24,
"import": "i000008"
},
{
"handle": "bb2c23b3c42cc",
"parent": "f7e2d9f330615",
"root": null,
"order": 1,
"heading": "H0",
"label": "Jane Smith",
"type": "FILE",
"class": "CHARACTER",
"active": true,
"layout": "NOTE",
"charCount": 55,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 25,
"import": "i000009"
},
{
"handle": "15c4492bd5107",
"parent": null,
"root": null,
"order": 2,
"heading": "H0",
"label": "Locations",
"type": "ROOT",
"class": "WORLD",
"expanded": true,
"import": null
},
{
"handle": "b3e74dbc1f584",
"parent": "15c4492bd5107",
"root": null,
"order": 0,
"heading": "H0",
"label": "Earth",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 76,
"wordCount": 15,
"paraCount": 1,
"cursorPos": 20,
"import": "i00000a"
},
{
"handle": "f1471bef9f2ae",
"parent": "15c4492bd5107",
"root": null,
"order": 1,
"heading": "H0",
"label": "Space",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 115,
"wordCount": 24,
"paraCount": 1,
"cursorPos": 133,
"import": "i000008"
},
{
"handle": "5eaea4e8cdee8",
"parent": "15c4492bd5107",
"root": null,
"order": 2,
"heading": "H0",
"label": "Mars",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 28,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 45,
"import": "i000009"
},
{
"handle": "6827118336ac1",
"parent": null,
"root": null,
"order": 3,
"heading": "H0",
"label": "Archive",
"type": "ROOT",
"class": "ARCHIVE",
"expanded": true,
"status": "s000000"
},
{
"handle": "ae9bf3c3ea159",
"parent": "6827118336ac1",
"root": null,
"order": 0,
"heading": "H0",
"label": "Scenes",
"type": "FOLDER",
"class": "ARCHIVE",
"expanded": true,
"status": "s000000"
},
{
"handle": "8a5deb88c0e97",
"parent": "ae9bf3c3ea159",
"root": null,
"order": 0,
"heading": "H0",
"label": "Old File",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 314,
"wordCount": 55,
"paraCount": 1,
"cursorPos": 322,
"status": "s000003"
},
{
"handle": "98acd8c76c93a",
"parent": null,
"root": null,
"order": 4,
"heading": "H0",
"label": "Trash",
"type": "ROOT",
"class": "TRASH",
"expanded": true,
"import": null
},
{
"handle": "b8136a5a774a0",
"parent": "98acd8c76c93a",
"root": null,
"order": 0,
"heading": "H0",
"label": "Delete Me!",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 30,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 36,
"status": "s000000"
}
]
+155
View File
@@ -0,0 +1,155 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-25 18:26:15">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>5</saveCount>
<autoCount>10</autoCount>
<editTime>1000</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<language>en_GB</language>
<spellCheck>True</spellCheck>
<spellLang>en_GB</spellLang>
<novelWordCount>830</novelWordCount>
<notesWordCount>376</notesWordCount>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
<entry key="novelTree">None</entry>
<entry key="outline">None</entry>
</lastHandle>
<autoReplace>
<entry key="A">B</entry>
<entry key="B">E</entry>
<entry key="C">D</entry>
</autoReplace>
<titleFormat>
<entry key="title">%title%</entry>
<entry key="chapter">Chapter %chw%: %title%</entry>
<entry key="unnumbered">%title%</entry>
<entry key="scene">Scene %ch%.%sc%: %title%</entry>
<entry key="section"></entry>
</titleFormat>
<status>
<entry key="s000000" count="0" red="100" green="100" blue="100">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Notes</entry>
<entry key="s000002" count="0" red="182" green="60" blue="0">Started</entry>
<entry key="s000003" count="0" red="193" green="129" blue="0">1st Draft</entry>
<entry key="s000004" count="0" red="193" green="129" blue="0">2nd Draft</entry>
<entry key="s000005" count="0" red="193" green="129" blue="0">3rd Draft</entry>
<entry key="s000006" count="0" red="58" green="180" blue="58">Finished</entry>
</status>
<importance>
<entry key="i000007" count="0" red="100" green="100" blue="100">None</entry>
<entry key="i000008" count="0" red="0" green="122" blue="188">Minor</entry>
<entry key="i000009" count="0" red="21" green="0" blue="180">Major</entry>
<entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry>
</importance>
</settings>
<content count="25">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/>
<name status="s000002" import="i000007">Novel</name>
</item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="93" wordCount="19" paraCount="2" cursorPos="2"/>
<name status="s000002" import="i000007" active="True">Title Page</name>
</item>
<item handle="974e400180a99" parent="7031beac91f75" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="186" wordCount="39" paraCount="2" cursorPos="212"/>
<name status="s000000" import="i000007" active="True">Page</name>
</item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="26" wordCount="6" paraCount="1" cursorPos="33"/>
<name status="s000000" import="i000007" active="True">Part One</name>
</item>
<item handle="e7ded148d6e4a" parent="7031beac91f75" root="None" order="3" type="FOLDER" class="NOVEL">
<meta expanded="True"/>
<name status="s000003" import="i000007">A Folder</name>
</item>
<item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="75" wordCount="14" paraCount="1" cursorPos="279"/>
<name status="s000001" import="i000007" active="True">Chapter One</name>
</item>
<item handle="636b6aa9b697b" parent="e7ded148d6e4a" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="2429" wordCount="432" paraCount="14" cursorPos="62"/>
<name status="s000003" import="i000007" active="True">Making a Scene</name>
</item>
<item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="476" wordCount="93" paraCount="3" cursorPos="577"/>
<name status="s000003" import="i000007" active="True">Another Scene</name>
</item>
<item handle="ba8a28a246524" parent="e7ded148d6e4a" root="None" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="617" wordCount="101" paraCount="3" cursorPos="4"/>
<name status="s000000" import="i000007" active="True">Interlude</name>
</item>
<item handle="96b68994dfa3d" parent="e7ded148d6e4a" root="None" order="4" type="FILE" class="NOVEL" layout="NOTE">
<meta expanded="False" heading="H0" charCount="1692" wordCount="313" paraCount="6" cursorPos="1110"/>
<name status="s000004" import="i000007" active="False">A Note on Structure</name>
</item>
<item handle="88706ddc78b1b" parent="e7ded148d6e4a" root="None" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<name status="s000003" import="i000007" active="True">Chapter Two</name>
</item>
<item handle="ae7339df26ded" parent="e7ded148d6e4a" root="None" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="s000003" import="i000007" active="True">We Found John!</name>
</item>
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="1" type="ROOT" class="CHARACTER">
<meta expanded="True"/>
<name status="s000000" import="i000007">Characters</name>
</item>
<item handle="f7e2d9f330615" parent="f6622b4617424" root="None" order="0" type="FOLDER" class="CHARACTER">
<meta expanded="True"/>
<name status="s000000" import="i000007">Main Characters</name>
</item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="None" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="s000000" import="i000008" active="True">John Smith</name>
</item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="None" order="1" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="s000000" import="i000009" active="True">Jane Smith</name>
</item>
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="2" type="ROOT" class="WORLD">
<meta expanded="True"/>
<name status="s000000" import="i000007">Locations</name>
</item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="None" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="s000000" import="i00000a" active="True">Earth</name>
</item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="None" order="1" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="s000000" import="i000008" active="True">Space</name>
</item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="None" order="2" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="s000000" import="i000009" active="True">Mars</name>
</item>
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="3" type="ROOT" class="ARCHIVE">
<meta expanded="True"/>
<name status="s000000" import="i000007">Archive</name>
</item>
<item handle="ae9bf3c3ea159" parent="6827118336ac1" root="None" order="0" type="FOLDER" class="ARCHIVE">
<meta expanded="True"/>
<name status="s000000" import="i000007">Scenes</name>
</item>
<item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="314" wordCount="55" paraCount="1" cursorPos="322"/>
<name status="s000003" import="i000007" active="True">Old File</name>
</item>
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="4" type="ROOT" class="TRASH">
<meta expanded="True"/>
<name status="s000000" import="i000007">Trash</name>
</item>
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s000000" import="i000007" active="True">Delete Me!</name>
</item>
</content>
</novelWriterXML>
+25 -15
View File
@@ -54,53 +54,63 @@ def testBaseCommon_CheckStringNone():
@pytest.mark.base
def testBaseCommon_CheckString():
"""Test the checkString function.
"""Test the checkString function. Anything that is a string should
be returned, otherwise it returns the default.
"""
assert checkString("None", "NotNone") == "None"
assert checkString(None, "NotNone") == "NotNone"
assert checkString(1, "NotNone") == "NotNone"
assert checkString(1.0, "NotNone") == "NotNone"
assert checkString(True, "NotNone") == "NotNone"
assert checkString("None", "default") == "None"
assert checkString("Text", "default") == "Text"
assert checkString(None, "default") == "default"
assert checkString(1, "default") == "default"
assert checkString(1.0, "default") == "default"
assert checkString(True, "default") == "default"
# END Test testBaseCommon_CheckString
@pytest.mark.base
def testBaseCommon_CheckInt():
"""Test the checkInt function.
"""Test the checkInt function. Anything that can be converted to an
integer should be returned, otherwise it returns the default.
"""
assert checkInt(None, 3) == 3
assert checkInt("1", 3) == 1
assert checkInt("1.0", 3) == 3
assert checkInt(1, 3) == 1
assert checkInt(1.0, 3) == 1
assert checkInt(True, 3) == 1
assert checkInt(False, 3) == 0
assert checkInt(None, 3) == 3
assert checkInt("1", 3) == 1
assert checkInt("1.0", 3) == 3
# END Test testBaseCommon_CheckInt
@pytest.mark.base
def testBaseCommon_CheckFloat():
"""Test the checkFloat function.
"""Test the checkFloat function. Anything that can be converted to an
integer should be returned, otherwise it returns the default.
"""
assert checkFloat(None, 3.0) == 3.0
assert checkFloat("1", 3.0) == 1.0
assert checkFloat("1.0", 3.0) == 1.0
assert checkFloat(1, 3.0) == 1.0
assert checkFloat(1.0, 3.0) == 1.0
assert checkFloat(True, 3.0) == 1.0
assert checkFloat(False, 3.0) == 0.0
assert checkFloat(None, 3.0) == 3.0
assert checkFloat("1", 3.0) == 1.0
assert checkFloat("1.0", 3.0) == 1.0
# END Test testBaseCommon_CheckInt
@pytest.mark.base
def testBaseCommon_CheckBool():
"""Test the checkBool function.
"""Test the checkBool function. Any bool, string version of Python
bool, or integer 1 or 0, are returned as bool. Otherwise, the
default is returned.
"""
assert checkBool("True", False) is True
assert checkBool("False", True) is False
assert checkBool("Boo", False) is False
assert checkBool("Boo", True) is True
assert checkBool(None, True) is True
assert checkBool(None, False) is False
assert checkBool(0, True) is False
assert checkBool(1, False) is True
assert checkBool(2, True) is True
+18 -23
View File
@@ -23,7 +23,7 @@ import os
import pytest
from mock import causeOSError
from tools import readFile, writeFile
from tools import C, buildTestProject, readFile, writeFile
from novelwriter.enum import nwItemClass, nwItemLayout
from novelwriter.core.project import NWProject
@@ -31,14 +31,12 @@ from novelwriter.core.document import NWDoc
@pytest.mark.core
def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal):
def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncDir, mockRnd):
"""Test loading and saving a document with the NWDoc class.
"""
theProject = NWProject(mockGUI)
assert theProject.openProject(nwMinimal) is True
assert theProject.projPath == nwMinimal
sHandle = "8c659a11cd429"
mockRnd.reset()
buildTestProject(theProject, fncDir)
# Read Document
# =============
@@ -49,25 +47,23 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal):
assert theDoc.readDocument() is None
# Non-existent handle
theDoc = NWDoc(theProject, "0000000000000")
theDoc = NWDoc(theProject, C.hInvalid)
assert theDoc.readDocument() is None
assert theDoc._currHash is None
# Cause open() to fail while loading
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
theDoc = NWDoc(theProject, sHandle)
theDoc = NWDoc(theProject, C.hSceneDoc)
assert theDoc.readDocument() is None
assert theDoc.getError() == "OSError: Mock OSError"
# Load the text
theDoc = NWDoc(theProject, sHandle)
theDoc = NWDoc(theProject, C.hSceneDoc)
assert theDoc.readDocument() == "### New Scene\n\n"
# Try to open a new (non-existent) file
nHandle = theProject.tree.findRoot(nwItemClass.NOVEL)
assert nHandle is not None
xHandle = theProject.newFile("New File", nHandle)
xHandle = theProject.newFile("New File", C.hNovelRoot)
theDoc = NWDoc(theProject, xHandle)
assert bool(theDoc) is True
assert repr(theDoc) == f"<NWDoc handle={xHandle}>"
@@ -86,10 +82,10 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal):
assert theDoc.writeDocument(theText)
# Check file content
docPath = os.path.join(nwMinimal, "content", xHandle+".nwd")
docPath = os.path.join(fncDir, "content", xHandle+".nwd")
assert readFile(docPath) == (
"%%~name: New File\n"
f"%%~path: a508bb932959c/{xHandle}\n"
f"%%~path: {C.hNovelRoot}/{xHandle}\n"
"%%~kind: NOVEL/DOCUMENT\n"
"### Test File\n\n"
"Text ...\n\n"
@@ -153,16 +149,15 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal):
@pytest.mark.core
def testCoreDocument_Methods(mockGUI, nwMinimal):
def testCoreDocument_Methods(mockGUI, fncDir, mockRnd):
"""Test other methods of the NWDoc class.
"""
theProject = NWProject(mockGUI)
assert theProject.openProject(nwMinimal)
assert theProject.projPath == nwMinimal
mockRnd.reset()
buildTestProject(theProject, fncDir)
sHandle = "8c659a11cd429"
theDoc = NWDoc(theProject, sHandle)
docPath = os.path.join(nwMinimal, "content", sHandle+".nwd")
theDoc = NWDoc(theProject, C.hSceneDoc)
docPath = os.path.join(fncDir, "content", C.hSceneDoc+".nwd")
assert theDoc.readDocument() == "### New Scene\n\n"
@@ -171,12 +166,12 @@ def testCoreDocument_Methods(mockGUI, nwMinimal):
# Check the item
assert theDoc.getCurrentItem() is not None
assert theDoc.getCurrentItem().itemHandle == sHandle
assert theDoc.getCurrentItem().itemHandle == C.hSceneDoc
# Check the meta
theName, theParent, theClass, theLayout = theDoc.getMeta()
assert theName == "New Scene"
assert theParent == "a6d311a93600a"
assert theParent == C.hChapterDir
assert theClass == nwItemClass.NOVEL
assert theLayout == nwItemLayout.DOCUMENT
@@ -184,7 +179,7 @@ def testCoreDocument_Methods(mockGUI, nwMinimal):
assert theDoc.writeDocument("%%~ stuff\n### Test File\n\nText ...\n\n")
assert readFile(docPath) == (
"%%~name: New Scene\n"
f"%%~path: a6d311a93600a/{sHandle}\n"
f"%%~path: {C.hChapterDir}/{C.hSceneDoc}\n"
"%%~kind: NOVEL/DOCUMENT\n"
"%%~ stuff\n"
"### Test File\n\n"
+197 -248
View File
@@ -21,20 +21,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import pytest
from lxml import etree
from PyQt5.QtGui import QIcon
from tools import C, buildTestProject
from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
@pytest.mark.core
def testCoreItem_Setters(mockGUI, mockRnd):
def testCoreItem_Setters(mockGUI, mockRnd, fncDir):
"""Test all the simple setters for the NWItem class.
"""
theProject = NWProject(mockGUI)
mockRnd.reset()
buildTestProject(theProject, fncDir)
theItem = NWItem(theProject)
statusKeys = ["s000000", "s000001", "s000002", "s000003"]
@@ -153,7 +155,7 @@ def testCoreItem_Setters(mockGUI, mockRnd):
theItem.setCharCount(None)
assert theItem.charCount == 0
theItem.setCharCount("1")
assert theItem.charCount == 1
assert theItem.charCount == 0
theItem.setCharCount(1)
assert theItem.charCount == 1
@@ -161,7 +163,7 @@ def testCoreItem_Setters(mockGUI, mockRnd):
theItem.setWordCount(None)
assert theItem.wordCount == 0
theItem.setWordCount("1")
assert theItem.wordCount == 1
assert theItem.wordCount == 0
theItem.setWordCount(1)
assert theItem.wordCount == 1
@@ -169,7 +171,7 @@ def testCoreItem_Setters(mockGUI, mockRnd):
theItem.setParaCount(None)
assert theItem.paraCount == 0
theItem.setParaCount("1")
assert theItem.paraCount == 1
assert theItem.paraCount == 0
theItem.setParaCount(1)
assert theItem.paraCount == 1
@@ -177,7 +179,7 @@ def testCoreItem_Setters(mockGUI, mockRnd):
theItem.setCursorPos(None)
assert theItem.cursorPos == 0
theItem.setCursorPos("1")
assert theItem.cursorPos == 1
assert theItem.cursorPos == 0
theItem.setCursorPos(1)
assert theItem.cursorPos == 1
@@ -190,10 +192,12 @@ def testCoreItem_Setters(mockGUI, mockRnd):
@pytest.mark.core
def testCoreItem_Methods(mockGUI):
def testCoreItem_Methods(mockGUI, mockRnd, fncDir):
"""Test the simple methods of the NWItem class.
"""
theProject = NWProject(mockGUI)
mockRnd.reset()
buildTestProject(theProject, fncDir)
theItem = NWItem(theProject)
# Describe Me
@@ -250,15 +254,15 @@ def testCoreItem_Methods(mockGUI):
# =============
theItem.setType("FILE")
theItem.setStatus("Note")
theItem.setImport("Minor")
theItem.setStatus(C.sNote)
theItem.setImport(C.iMinor)
theItem.setClass("NOVEL")
stT, stI = theItem.getImportStatus()
assert stT == "Note"
assert isinstance(stI, QIcon)
theItem.setImportStatus("Draft")
theItem.setImportStatus(C.sDraft)
stT, stI = theItem.getImportStatus()
assert stT == "Draft"
@@ -267,7 +271,7 @@ def testCoreItem_Methods(mockGUI):
assert stT == "Minor"
assert isinstance(stI, QIcon)
theItem.setImportStatus("Major")
theItem.setImportStatus(C.iMajor)
stT, stI = theItem.getImportStatus()
assert stT == "Major"
@@ -491,254 +495,199 @@ def testCoreItem_ClassDefaults(mockGUI):
@pytest.mark.core
def testCoreItem_XMLPackUnpack(mockGUI, caplog, mockRnd):
"""Test packing and unpacking XML objects for the NWItem class.
def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
"""Test packing and unpacking entries for the NWItem class.
"""
theProject = NWProject(mockGUI)
nwXML = etree.Element("novelWriterXML")
theProject.data.itemStatus.write(None, "New", (100, 100, 100))
theProject.data.itemImport.write(None, "New", (100, 100, 100))
statusKeys = ["s000000", "s000001", "s000002", "s000003"]
importKeys = ["i000004", "i000005", "i000006", "i000007"]
# Invalid
theItem = NWItem(theProject)
assert theItem.unpack({}) is False
# File
# ====
theItem = NWItem(theProject)
theItem.setHandle("0123456789abc")
theItem.setParent("0123456789abc")
theItem.setRoot("0123456789abc")
theItem.setOrder(1)
theItem.setName("A Name")
theItem.setClass("NOVEL")
theItem.setType("FILE")
theItem.setImport(importKeys[3])
theItem.setLayout("NOTE")
theItem.setActive(False)
theItem.setParaCount(3)
theItem.setWordCount(5)
theItem.setCharCount(7)
theItem.setCursorPos(11)
assert theItem.unpack({
"label": "A File",
"handle": "0000000000003",
"parent": "0000000000002",
"root": "0000000000001",
"order": 1,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": True,
"status": None,
"import": None,
"heading": "H1",
"charCount": 100,
"wordCount": 20,
"paraCount": 2,
"cursorPos": 50,
"active": False,
}) is True
# Pack
xContent = etree.SubElement(nwXML, "content")
theItem.packXML(xContent)
assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == (
b'<content>'
b'<item handle="0123456789abc" parent="0123456789abc" root="0123456789abc" order="1" '
b'type="FILE" class="NOVEL" layout="NOTE"><meta expanded="False" mainHeading="H0" '
b'charCount="7" wordCount="5" paraCount="3" cursorPos="11"/><name status="None" '
b'import="%s" active="False">A Name</name></item>'
b'</content>'
) % bytes(importKeys[3], encoding="utf8")
# Unpack
theItem = NWItem(theProject)
assert theItem.unpackXML(xContent[0])
assert theItem.itemHandle == "0123456789abc"
assert theItem.itemParent == "0123456789abc"
assert theItem.itemRoot == "0123456789abc"
assert theItem.itemName == "A File"
assert theItem.itemHandle == "0000000000003"
assert theItem.itemParent == "0000000000002"
assert theItem.itemRoot == "0000000000001"
assert theItem.itemOrder == 1
assert theItem.itemType == nwItemType.FILE
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemLayout == nwItemLayout.DOCUMENT
assert theItem.itemStatus == "s000000"
assert theItem.itemImport == "i000001"
assert theItem.isActive is False
assert theItem.paraCount == 3
assert theItem.wordCount == 5
assert theItem.charCount == 7
assert theItem.cursorPos == 11
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FILE
assert theItem.itemLayout == nwItemLayout.NOTE
assert theItem.itemStatus == statusKeys[0] # Was None, should now be default
assert theItem.itemImport == importKeys[3]
# Folder
# ======
theItem = NWItem(theProject)
theItem.setHandle("0123456789abc")
theItem.setParent("0123456789abc")
theItem.setRoot("0123456789abc")
theItem.setOrder(1)
theItem.setName("A Name")
theItem.setClass("NOVEL")
theItem.setType("FOLDER")
theItem.setStatus(statusKeys[1])
theItem.setLayout("NOTE")
theItem.setExpanded(True)
theItem.setActive(False)
theItem.setParaCount(3)
theItem.setWordCount(5)
theItem.setCharCount(7)
theItem.setCursorPos(11)
# Pack
xContent = etree.SubElement(nwXML, "content")
theItem.packXML(xContent)
assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == (
b'<content>'
b'<item handle="0123456789abc" parent="0123456789abc" root="0123456789abc" order="1" '
b'type="FOLDER" class="NOVEL"><meta expanded="True"/><name status="%s" '
b'import="None">A Name</name></item>'
b'</content>'
) % bytes(statusKeys[1], encoding="utf8")
# Unpack
theItem = NWItem(theProject)
assert theItem.unpackXML(xContent[0])
assert theItem.itemHandle == "0123456789abc"
assert theItem.itemParent == "0123456789abc"
assert theItem.itemRoot == "0123456789abc"
assert theItem.itemOrder == 1
assert theItem.isExpanded is True
assert theItem.isActive is True
assert theItem.paraCount == 0
assert theItem.wordCount == 0
assert theItem.charCount == 0
assert theItem.cursorPos == 0
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FOLDER
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
assert theItem.itemStatus == statusKeys[1]
assert theItem.itemImport == importKeys[0] # Was None, should now be default
# Errors
# ======
# Not an Item
mockXml = etree.SubElement(nwXML, "stuff")
assert theItem.unpackXML(mockXml) is False
# Item without Handle
mockXml = etree.SubElement(nwXML, "item", attrib={"stuff": "nah"})
assert theItem.unpackXML(mockXml) is False
# Item with Invalid SubElement is Accepted w/Error
mockXml = etree.SubElement(nwXML, "item", attrib={"handle": "0123456789abc"})
xParam = etree.SubElement(mockXml, "invalid")
xParam.text = "stuff"
caplog.clear()
assert theItem.unpackXML(mockXml) is True
assert "Unknown tag 'invalid'" in caplog.text
# Pack Valid Item
mockXml = etree.SubElement(nwXML, "group")
theItem._subPack(mockXml, "subGroup", {"one": "two"}, "value", False)
assert etree.tostring(mockXml, pretty_print=False, encoding="utf-8") == (
b"<group><subGroup one=\"two\">value</subGroup></group>"
)
# Pack Not Allowed None
mockXml = etree.SubElement(nwXML, "group")
assert theItem._subPack(mockXml, "subGroup", {}, None, False) is None
assert theItem._subPack(mockXml, "subGroup", {}, "None", False) is None
assert etree.tostring(mockXml, pretty_print=False, encoding="utf-8") == (
b"<group/>"
)
# END Test testCoreItem_XMLPackUnpack
@pytest.mark.core
def testCoreItem_ConvertFromFmt12(mockGUI):
"""Test the setter for all the nwItemLayout values for the NWItem
class using the class names that were present in file format 1.2.
"""
theProject = NWProject(mockGUI)
theItem = NWItem(theProject)
# Deprecated Layouts
theItem.setLayout("TITLE")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("PAGE")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("BOOK")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("PARTITION")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("UNNUMBERED")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("CHAPTER")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("SCENE")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("MUMBOJUMBO")
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
# END Test testCoreItem_ConvertFromFmt12
@pytest.mark.core
def testCoreItem_ConvertFromFmt13(mockGUI):
"""Test packing and unpacking XML objects for the NWItem class from
format version 1.3
"""
theProject = NWProject(mockGUI)
# Make Version 1.3 XML
nwXML = etree.Element("novelWriterXML")
xContent = etree.SubElement(nwXML, "content")
# Folder
xPack = etree.SubElement(xContent, "item", attrib={
"handle": "a000000000001",
"order": "1",
"parent": "b000000000001",
})
NWItem._subPack(xPack, "name", text="Folder")
NWItem._subPack(xPack, "type", text="FOLDER")
NWItem._subPack(xPack, "class", text="NOVEL")
NWItem._subPack(xPack, "status", text="New")
NWItem._subPack(xPack, "expanded", text="True")
# Unpack Folder
theItem = NWItem(theProject)
theItem.unpackXML(xContent[0])
assert theItem.itemHandle == "a000000000001"
assert theItem.itemParent == "b000000000001"
assert theItem.itemOrder == 1
assert theItem.isExpanded is True
assert theItem.isActive is True
assert theItem.charCount == 0
assert theItem.wordCount == 0
assert theItem.paraCount == 0
assert theItem.cursorPos == 0
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FOLDER
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
# File
xPack = etree.SubElement(xContent, "item", attrib={
"handle": "c000000000001",
"order": "2",
"parent": "a000000000001",
})
NWItem._subPack(xPack, "name", text="Scene")
NWItem._subPack(xPack, "type", text="FILE")
NWItem._subPack(xPack, "class", text="NOVEL")
NWItem._subPack(xPack, "status", text="New")
NWItem._subPack(xPack, "exported", text="True")
NWItem._subPack(xPack, "layout", text="DOCUMENT")
NWItem._subPack(xPack, "charCount", text="600")
NWItem._subPack(xPack, "wordCount", text="100")
NWItem._subPack(xPack, "paraCount", text="6")
NWItem._subPack(xPack, "cursorPos", text="50")
# Unpack File
theItem = NWItem(theProject)
theItem.unpackXML(xContent[1])
assert theItem.itemHandle == "c000000000001"
assert theItem.itemParent == "a000000000001"
assert theItem.itemOrder == 2
assert theItem.isExpanded is False
assert theItem.isActive is True
assert theItem.charCount == 600
assert theItem.wordCount == 100
assert theItem.paraCount == 6
assert theItem.mainHeading == "H1"
assert theItem.charCount == 100
assert theItem.wordCount == 20
assert theItem.paraCount == 2
assert theItem.cursorPos == 50
assert theItem.pack() == {
"name": "A File",
"itemAttr": {
"handle": "0000000000003",
"parent": "0000000000002",
"root": "0000000000001",
"order": "1",
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT",
},
"metaAttr": {
"expanded": "True",
"heading": "H1",
"charCount": "100",
"wordCount": "20",
"paraCount": "2",
"cursorPos": "50",
},
"nameAttr": {
"status": "s000000",
"import": "i000001",
"active": "False",
}
}
# Folder
theItem = NWItem(theProject)
assert theItem.unpack({
"label": "A Folder",
"handle": "0000000000003",
"parent": "0000000000002",
"root": "0000000000001",
"order": 1,
"type": "FOLDER",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": True,
"status": "",
"import": "",
"heading": "H1",
"charCount": 100,
"wordCount": 20,
"paraCount": 2,
"cursorPos": 50,
"active": True,
}) is True
assert theItem.itemName == "A Folder"
assert theItem.itemHandle == "0000000000003"
assert theItem.itemParent == "0000000000002"
assert theItem.itemRoot == "0000000000001"
assert theItem.itemOrder == 1
assert theItem.itemType == nwItemType.FOLDER
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FILE
assert theItem.itemLayout == nwItemLayout.DOCUMENT
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
assert theItem.itemStatus == "s000000"
assert theItem.itemImport == "i000001"
assert theItem.isActive is False
assert theItem.isExpanded is True
assert theItem.mainHeading == "H0"
assert theItem.charCount == 0
assert theItem.wordCount == 0
assert theItem.paraCount == 0
assert theItem.cursorPos == 0
# Deprecated Type
theItem.setType("TRASH")
assert theItem.pack() == {
"name": "A Folder",
"itemAttr": {
"handle": "0000000000003",
"parent": "0000000000002",
"root": "0000000000001",
"order": "1",
"type": "FOLDER",
"class": "NOVEL",
},
"metaAttr": {
"expanded": "True",
},
"nameAttr": {
"status": "s000000",
"import": "i000001",
}
}
# Root
theItem = NWItem(theProject)
assert theItem.unpack({
"label": "A Novel",
"handle": "0000000000003",
"parent": "0000000000002",
"root": "0000000000001",
"order": 1,
"type": "ROOT",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": True,
"status": None,
"import": None,
"heading": "H1",
"charCount": 100,
"wordCount": 20,
"paraCount": 2,
"cursorPos": 50,
"active": True,
}) is True
assert theItem.itemName == "A Novel"
assert theItem.itemHandle == "0000000000003"
assert theItem.itemParent is None
assert theItem.itemRoot == "0000000000003"
assert theItem.itemOrder == 1
assert theItem.itemType == nwItemType.ROOT
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
assert theItem.itemStatus == "s000000"
assert theItem.itemImport == "i000001"
assert theItem.isActive is False
assert theItem.isExpanded is True
assert theItem.mainHeading == "H0"
assert theItem.charCount == 0
assert theItem.wordCount == 0
assert theItem.paraCount == 0
assert theItem.cursorPos == 0
# END Test testCoreItem_ConvertFromFmt13
assert theItem.pack() == {
"name": "A Novel",
"itemAttr": {
"handle": "0000000000003",
"parent": "None",
"root": "0000000000003",
"order": "1",
"type": "ROOT",
"class": "NOVEL",
},
"metaAttr": {
"expanded": "True",
},
"nameAttr": {
"status": "s000000",
"import": "i000001",
}
}
# END Test testCoreItem_PackUnpack
+248 -371
View File
@@ -20,14 +20,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import shutil
import pytest
from lxml import etree
from shutil import copyfile
from zipfile import ZipFile
from tools import cmpFiles, writeFile, readFile, buildTestProject, XML_IGNORE, C
from mock import causeOSError
from tools import cmpFiles, writeFile, readFile, buildTestProject, XML_IGNORE, C
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
from novelwriter.common import formatTimeStamp
@@ -37,6 +37,7 @@ from novelwriter.core.index import NWIndex
from novelwriter.core.project import NWProject
from novelwriter.core.options import OptionState
from novelwriter.core.document import NWDoc
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
@pytest.mark.core
@@ -202,7 +203,7 @@ def testCoreProject_NewSampleA(fncDir, tmpConf, mockGUI, tmpDir):
assert theProject.newProject(projData) is True
assert theProject.openProject(fncDir) is True
assert theProject.projName == "Sample Project"
assert theProject.data.name == "Sample Project"
assert theProject.saveProject() is True
assert theProject.closeProject() is True
os.unlink(dstSample)
@@ -236,7 +237,7 @@ def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, mockGUI, tmpDir):
monkeypatch.setattr(nwFiles, "PROJ_FILE", "nwProject.nwx")
assert theProject.newProject(projData) is True
assert theProject.openProject(fncDir) is True
assert theProject.projName == "Sample Project"
assert theProject.data.name == "Sample Project"
assert theProject.saveProject() is True
assert theProject.closeProject() is True
@@ -264,14 +265,14 @@ def testCoreProject_NewRoot(fncDir, outDir, refDir, mockGUI, mockRnd):
assert theProject.closeProject() is True
assert theProject.openProject(projFile) is True
assert theProject.newRoot(nwItemClass.NOVEL) == "0000000000020"
assert theProject.newRoot(nwItemClass.PLOT) == "0000000000021"
assert theProject.newRoot(nwItemClass.CHARACTER) == "0000000000022"
assert theProject.newRoot(nwItemClass.WORLD) == "0000000000023"
assert theProject.newRoot(nwItemClass.TIMELINE) == "0000000000024"
assert theProject.newRoot(nwItemClass.OBJECT) == "0000000000025"
assert theProject.newRoot(nwItemClass.CUSTOM) == "0000000000026"
assert theProject.newRoot(nwItemClass.CUSTOM) == "0000000000027"
assert theProject.newRoot(nwItemClass.NOVEL) == "0000000000010"
assert theProject.newRoot(nwItemClass.PLOT) == "0000000000011"
assert theProject.newRoot(nwItemClass.CHARACTER) == "0000000000012"
assert theProject.newRoot(nwItemClass.WORLD) == "0000000000013"
assert theProject.newRoot(nwItemClass.TIMELINE) == "0000000000014"
assert theProject.newRoot(nwItemClass.OBJECT) == "0000000000015"
assert theProject.newRoot(nwItemClass.CUSTOM) == "0000000000016"
assert theProject.newRoot(nwItemClass.CUSTOM) == "0000000000017"
assert theProject.projChanged is True
assert theProject.saveProject() is True
@@ -282,23 +283,23 @@ def testCoreProject_NewRoot(fncDir, outDir, refDir, mockGUI, mockRnd):
assert theProject.projChanged is False
# Delete the new items
assert theProject.removeItem("0000000000020") is True
assert theProject.removeItem("0000000000021") is True
assert theProject.removeItem("0000000000022") is True
assert theProject.removeItem("0000000000023") is True
assert theProject.removeItem("0000000000024") is True
assert theProject.removeItem("0000000000025") is True
assert theProject.removeItem("0000000000026") is True
assert theProject.removeItem("0000000000027") is True
assert theProject.removeItem("0000000000010") is True
assert theProject.removeItem("0000000000011") is True
assert theProject.removeItem("0000000000012") is True
assert theProject.removeItem("0000000000013") is True
assert theProject.removeItem("0000000000014") is True
assert theProject.removeItem("0000000000015") is True
assert theProject.removeItem("0000000000016") is True
assert theProject.removeItem("0000000000017") is True
assert "0000000000020" not in theProject.tree
assert "0000000000021" not in theProject.tree
assert "0000000000022" not in theProject.tree
assert "0000000000023" not in theProject.tree
assert "0000000000024" not in theProject.tree
assert "0000000000025" not in theProject.tree
assert "0000000000026" not in theProject.tree
assert "0000000000027" not in theProject.tree
assert "0000000000010" not in theProject.tree
assert "0000000000011" not in theProject.tree
assert "0000000000012" not in theProject.tree
assert "0000000000013" not in theProject.tree
assert "0000000000014" not in theProject.tree
assert "0000000000015" not in theProject.tree
assert "0000000000016" not in theProject.tree
assert "0000000000017" not in theProject.tree
# END Test testCoreProject_NewRoot
@@ -325,26 +326,26 @@ def testCoreProject_NewFileFolder(monkeypatch, fncDir, outDir, refDir, mockGUI,
assert theProject.newFile("New File", "1234567890abc") is None
# Add files properly
assert theProject.newFolder("Stuff", C.hNovelRoot) == "0000000000020"
assert theProject.newFile("Hello", "0000000000020") == "0000000000021"
assert theProject.newFile("Jane", C.hCharRoot) == "0000000000022"
assert theProject.newFolder("Stuff", C.hNovelRoot) == "0000000000010"
assert theProject.newFile("Hello", "0000000000010") == "0000000000011"
assert theProject.newFile("Jane", C.hCharRoot) == "0000000000012"
assert "0000000000020" in theProject.tree
assert "0000000000021" in theProject.tree
assert "0000000000022" in theProject.tree
assert "0000000000010" in theProject.tree
assert "0000000000011" in theProject.tree
assert "0000000000012" in theProject.tree
# Write to file, failed
assert theProject.writeNewFile("blabla", 1, True) is False # Not a handle
assert theProject.writeNewFile("0000000000020", 1, True) is False # Not a file
assert theProject.writeNewFile("0000000000010", 1, True) is False # Not a file
assert theProject.writeNewFile(C.hTitlePage, 1, True) is False # Already has content
# Write to file, success
assert theProject.writeNewFile("0000000000021", 2, True) is True
assert NWDoc(theProject, "0000000000021").readDocument() == "## Hello\n\n"
assert theProject.writeNewFile("0000000000011", 2, True) is True
assert NWDoc(theProject, "0000000000011").readDocument() == "## Hello\n\n"
# Write to file with additional text, success
assert theProject.writeNewFile("0000000000022", 1, False, "Hi Jane\n\n") is True
assert NWDoc(theProject, "0000000000022").readDocument() == "# Jane\n\nHi Jane\n\n"
assert theProject.writeNewFile("0000000000012", 1, False, "Hi Jane\n\n") is True
assert NWDoc(theProject, "0000000000012").readDocument() == "# Jane\n\nHi Jane\n\n"
# Save, close and check
assert theProject.projChanged is True
@@ -357,23 +358,23 @@ def testCoreProject_NewFileFolder(monkeypatch, fncDir, outDir, refDir, mockGUI,
# Delete new file, but block access
with monkeypatch.context() as mp:
mp.setattr("os.unlink", causeOSError)
assert theProject.removeItem("0000000000021") is False
assert "0000000000021" in theProject.tree
assert theProject.removeItem("0000000000011") is False
assert "0000000000011" in theProject.tree
# Delete new files and folders
assert os.path.isfile(os.path.join(fncDir, "content", "0000000000022.nwd"))
assert os.path.isfile(os.path.join(fncDir, "content", "0000000000021.nwd"))
assert os.path.isfile(os.path.join(fncDir, "content", "0000000000012.nwd"))
assert os.path.isfile(os.path.join(fncDir, "content", "0000000000011.nwd"))
assert theProject.removeItem("0000000000022") is True
assert theProject.removeItem("0000000000021") is True
assert theProject.removeItem("0000000000020") is True
assert theProject.removeItem("0000000000012") is True
assert theProject.removeItem("0000000000011") is True
assert theProject.removeItem("0000000000010") is True
assert not os.path.isfile(os.path.join(fncDir, "content", "0000000000022.nwd"))
assert not os.path.isfile(os.path.join(fncDir, "content", "0000000000021.nwd"))
assert not os.path.isfile(os.path.join(fncDir, "content", "0000000000012.nwd"))
assert not os.path.isfile(os.path.join(fncDir, "content", "0000000000011.nwd"))
assert "0000000000020" not in theProject.tree
assert "0000000000021" not in theProject.tree
assert "0000000000022" not in theProject.tree
assert "0000000000010" not in theProject.tree
assert "0000000000011" not in theProject.tree
assert "0000000000012" not in theProject.tree
assert theProject.closeProject() is True
@@ -381,139 +382,90 @@ def testCoreProject_NewFileFolder(monkeypatch, fncDir, outDir, refDir, mockGUI,
@pytest.mark.core
def testCoreProject_Open(monkeypatch, nwMinimal, mockGUI):
def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncDir, mockRnd):
"""Test opening a project.
"""
theProject = NWProject(mockGUI)
mockRnd.reset()
buildTestProject(theProject, fncDir)
# Rename the project file to check handling
rName = os.path.join(nwMinimal, nwFiles.PROJ_FILE)
wName = os.path.join(nwMinimal, nwFiles.PROJ_FILE+"_sdfghj")
rName = os.path.join(fncDir, nwFiles.PROJ_FILE)
wName = os.path.join(fncDir, nwFiles.PROJ_FILE+"_sdfghj")
os.rename(rName, wName)
assert theProject.openProject(nwMinimal) is False
assert theProject.openProject(fncDir) is False
os.rename(wName, rName)
# Fail on folder structure check
with monkeypatch.context() as mp:
mp.setattr("os.mkdir", causeOSError)
assert theProject.openProject(nwMinimal) is False
shutil.rmtree(os.path.join(fncDir, "meta"))
assert theProject.openProject(fncDir) is False
# Fail on lock file
theProject.setProjectPath(nwMinimal)
theProject.setProjectPath(fncDir)
assert theProject._writeLockFile()
assert theProject.openProject(nwMinimal) is False
assert theProject.openProject(fncDir) is False
# Fail to read lockfile (which still opens the project)
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert theProject.openProject(nwMinimal) is True
caplog.clear()
assert theProject.openProject(fncDir) is True
assert "Failed to check lock file" in caplog.text
assert theProject.closeProject()
# Force open with lockfile
theProject.setProjectPath(nwMinimal)
theProject.setProjectPath(fncDir)
assert theProject._writeLockFile()
assert theProject.openProject(nwMinimal, overrideLock=True) is True
assert theProject.openProject(fncDir, overrideLock=True) is True
assert theProject.closeProject()
# Make a junk XML file
oName = os.path.join(nwMinimal, nwFiles.PROJ_FILE[:-3]+"orig")
bName = os.path.join(nwMinimal, nwFiles.PROJ_FILE[:-3]+"bak")
os.rename(rName, oName)
writeFile(rName, "stuff")
assert theProject.openProject(nwMinimal) is False
# Not a novelwriter XML file
with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "read", lambda *a: False)
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.NOT_NWX_FILE))
assert theProject.openProject(fncDir) is False
assert "Project file does not appear" in mockGUI.lastAlert
# Also write a jun XML backup file
writeFile(bName, "stuff")
assert theProject.openProject(nwMinimal) is False
# Unknown project file version
with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "read", lambda *a: False)
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.UNKNOWN_VERSION))
assert theProject.openProject(fncDir) is False
assert "Unknown or unsupported novelWriter project file" in mockGUI.lastAlert
# Wrong root item
writeFile(rName, "<not_novelWriterXML></not_novelWriterXML>\n")
assert theProject.openProject(nwMinimal) is False
# Other parse error
with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "read", lambda *a: False)
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.CANNOT_PARSE))
assert theProject.openProject(fncDir) is False
assert "Failed to parse project xml" in mockGUI.lastAlert
# Wrong file version
writeFile(rName, (
"<?xml version='0.0' encoding='utf-8'?>\n"
"<novelWriterXML "
"appVersion=\"1.0\" "
"hexVersion=\"0x01000000\" "
"fileVersion=\"1.0\" "
"timeStamp=\"2020-01-01 00:00:00\">\n"
"</novelWriterXML>\n"
))
mockGUI.askResponse = False
assert theProject.openProject(nwMinimal) is False
mockGUI.undo()
# Won't convert legacy file
with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY))
mockGUI.askResponse = False
assert theProject.openProject(fncDir) is False
assert "The file format of your project is about to be" in mockGUI.lastQuestion[1]
mockGUI.askResponse = True
# Future file version
writeFile(rName, (
"<?xml version='1.0' encoding='utf-8'?>\n"
"<novelWriterXML "
"appVersion=\"1.0\" "
"hexVersion=\"0x01000000\" "
"fileVersion=\"99.99\" "
"timeStamp=\"2020-01-01 00:00:00\">\n"
"</novelWriterXML>\n"
))
assert theProject.openProject(nwMinimal) is False
# Update file version
writeFile(rName, (
"<?xml version='1.0' encoding='utf-8'?>\n"
"<novelWriterXML "
"appVersion=\"1.0\" "
"hexVersion=\"0xffffffff\" "
"fileVersion=\"1.2\" "
"timeStamp=\"2020-01-01 00:00:00\">\n"
"</novelWriterXML>\n"
))
mockGUI.askResponse = False
assert theProject.openProject(nwMinimal) is False
assert mockGUI.lastQuestion[0] == "File Version"
mockGUI.undo()
# Larger hex version
writeFile(rName, (
"<?xml version='1.0' encoding='utf-8'?>\n"
"<novelWriterXML "
"appVersion=\"1.0\" "
"hexVersion=\"0xffffffff\" "
"fileVersion=\"%s\" "
"timeStamp=\"2020-01-01 00:00:00\">\n"
"</novelWriterXML>\n"
) % theProject.FILE_VERSION)
mockGUI.askResponse = False
assert theProject.openProject(nwMinimal) is False
assert mockGUI.lastQuestion[0] == "Version Conflict"
mockGUI.undo()
# Test skipping XML entries
writeFile(rName, (
"<?xml version='1.0' encoding='utf-8'?>\n"
"<novelWriterXML "
"appVersion=\"1.0\" "
"hexVersion=\"0x01000000\" "
"fileVersion=\"1.2\" "
"timeStamp=\"2020-01-01 00:00:00\">\n"
"<project><stuff/></project>\n"
"<settings><stuff/></settings>\n"
"</novelWriterXML>\n"
))
assert theProject.openProject(nwMinimal) is True
assert theProject.closeProject()
# Clean up XML files
os.unlink(rName)
os.unlink(bName)
os.rename(oName, rName)
# Won't convert legacy file
with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "hexVersion", property(lambda *a: "0x99999999"))
mockGUI.askResponse = False
assert theProject.openProject(fncDir) is False
assert "This project was saved by a newer version" in mockGUI.lastQuestion[1]
mockGUI.askResponse = True
# Add some legacy stuff that cannot be removed
with monkeypatch.context() as mp:
mp.setattr(theProject, "_legacyDataFolder", causeOSError)
os.mkdir(os.path.join(nwMinimal, "data_0"))
writeFile(os.path.join(nwMinimal, "data_0", "123456789abc_main.nwd"), "stuff")
writeFile(os.path.join(nwMinimal, "data_0", "123456789abc_main.bak"), "stuff")
os.mkdir(os.path.join(fncDir, "data_0"))
writeFile(os.path.join(fncDir, "data_0", "123456789abc_main.nwd"), "stuff")
writeFile(os.path.join(fncDir, "data_0", "123456789abc_main.bak"), "stuff")
mockGUI.clear()
assert theProject.openProject(nwMinimal) is True
assert theProject.openProject(fncDir) is True
assert "There was an error updating the project." in mockGUI.lastAlert
assert theProject.closeProject()
@@ -522,56 +474,31 @@ def testCoreProject_Open(monkeypatch, nwMinimal, mockGUI):
@pytest.mark.core
def testCoreProject_Save(monkeypatch, nwMinimal, mockGUI, refDir):
def testCoreProject_Save(monkeypatch, mockGUI, mockRnd, fncDir, refDir):
"""Test saving a project.
"""
theProject = NWProject(mockGUI)
testFile = os.path.join(nwMinimal, "nwProject.nwx")
backFile = os.path.join(nwMinimal, "nwProject.bak")
compFile = os.path.join(refDir, os.path.pardir, "minimal", "nwProject.nwx")
# Nothing to save
assert theProject.saveProject() is False
# Open test project
assert theProject.openProject(nwMinimal)
mockRnd.reset()
buildTestProject(theProject, fncDir)
# Fail on folder structure check
with monkeypatch.context() as mp:
mp.setattr("os.path.isdir", lambda *a: False)
mp.setattr("os.mkdir", causeOSError)
shutil.rmtree(os.path.join(fncDir, "meta"))
assert theProject.saveProject() is False
# Fail on open file
# Fail writing
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
mp.setattr(ProjectXMLWriter, "write", lambda *a: False)
assert theProject.saveProject() is False
# Fail on creating .bak file
with monkeypatch.context() as mp:
mp.setattr("os.replace", causeOSError)
assert theProject.saveProject() is False
assert os.path.isfile(backFile) is False
# Successful save
saveCount = theProject.saveCount
autoCount = theProject.autoCount
assert theProject.saveProject() is True
assert theProject.saveCount == saveCount + 1
assert theProject.autoCount == autoCount
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
# Check that a second save creates a .bak file
assert os.path.isfile(backFile) is True
# Successful autosave
saveCount = theProject.saveCount
autoCount = theProject.autoCount
# Save with and without autosave
assert theProject.saveProject(autoSave=False) is True
assert theProject.saveProject(autoSave=True) is True
assert theProject.saveCount == saveCount
assert theProject.autoCount == autoCount + 1
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
# Close test project
assert theProject.closeProject()
# END Test testCoreProject_Save
@@ -682,11 +609,11 @@ def testCoreProject_Helpers(monkeypatch, fncDir, mockGUI):
@pytest.mark.core
def testCoreProject_AccessItems(nwMinimal, mockGUI):
def testCoreProject_AccessItems(mockGUI, fncDir, mockRnd):
"""Test helper functions for the project folder.
"""
theProject = NWProject(mockGUI)
theProject.openProject(nwMinimal)
buildTestProject(theProject, fncDir)
# Storage Objects
assert isinstance(theProject.index, NWIndex)
@@ -695,34 +622,34 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI):
# Move Novel ROOT to after its files
oldOrder = [
"a508bb932959c", # ROOT: Novel
"a35baf2e93843", # FILE: Title Page
"a6d311a93600a", # FOLDER: New Chapter
"f5ab3e30151e1", # FILE: New Chapter
"8c659a11cd429", # FILE: New Scene
"7695ce551d265", # ROOT: Plot
"afb3043c7b2b3", # ROOT: Characters
"9d5247ab588e0", # ROOT: World
C.hNovelRoot,
C.hPlotRoot,
C.hCharRoot,
C.hWorldRoot,
C.hTitlePage,
C.hChapterDir,
C.hChapterDoc,
C.hSceneDoc,
]
newOrder = [
"a35baf2e93843", # FILE: Title Page
"f5ab3e30151e1", # FILE: New Chapter
"8c659a11cd429", # FILE: New Scene
"a6d311a93600a", # FOLDER: New Chapter
"a508bb932959c", # ROOT: Novel
"7695ce551d265", # ROOT: Plot
"afb3043c7b2b3", # ROOT: Characters
"9d5247ab588e0", # ROOT: World
C.hTitlePage,
C.hChapterDoc,
C.hSceneDoc,
C.hChapterDir,
C.hNovelRoot,
C.hPlotRoot,
C.hCharRoot,
C.hWorldRoot,
]
assert theProject.tree.handles() == oldOrder
assert theProject.setTreeOrder(newOrder)
assert theProject.tree.handles() == newOrder
# Add a non-existing item
theProject.tree._treeOrder.append("01234567789abc")
theProject.tree._treeOrder.append(C.hInvalid)
# Add an item with a non-existent parent
nHandle = theProject.newFile("Test File", "a6d311a93600a")
nHandle = theProject.newFile("Test File", C.hChapterDir)
theProject.tree[nHandle].setParent("cba9876543210")
assert theProject.tree[nHandle].itemParent == "cba9876543210"
@@ -731,15 +658,15 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI):
retOrder.append(tItem.itemHandle)
assert retOrder == [
"a508bb932959c", # ROOT: Novel
"7695ce551d265", # ROOT: Plot
"afb3043c7b2b3", # ROOT: Characters
"9d5247ab588e0", # ROOT: World
nHandle, # FILE: Test File
"a35baf2e93843", # FILE: Title Page
"a6d311a93600a", # FOLDER: New Chapter
"f5ab3e30151e1", # FILE: New Chapter
"8c659a11cd429", # FILE: New Scene
C.hNovelRoot,
C.hPlotRoot,
C.hCharRoot,
C.hWorldRoot,
nHandle,
C.hTitlePage,
C.hChapterDir,
C.hChapterDoc,
C.hSceneDoc,
]
assert theProject.tree[nHandle].itemParent is None
@@ -751,23 +678,24 @@ def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd):
"""Test the status and importance flag handling.
"""
theProject = NWProject(mockGUI)
mockRnd.reset()
buildTestProject(theProject, fncDir)
statusKeys = ["s000008", "s000009", "s00000a", "s00000b"]
importKeys = ["i00000c", "i00000d", "i00000e", "i00000f"]
statusKeys = [C.sNew, C.sNote, C.sDraft, C.sFinished]
importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain]
# Change Status
# =============
theProject.tree["0000000000014"].setStatus("Finished")
theProject.tree["0000000000015"].setStatus("Draft")
theProject.tree["0000000000016"].setStatus("Note")
theProject.tree["0000000000017"].setStatus("Finished")
theProject.tree[C.hNovelRoot].setStatus(statusKeys[3])
theProject.tree[C.hPlotRoot].setStatus(statusKeys[2])
theProject.tree[C.hCharRoot].setStatus(statusKeys[1])
theProject.tree[C.hWorldRoot].setStatus(statusKeys[3])
assert theProject.tree["0000000000014"].itemStatus == statusKeys[3]
assert theProject.tree["0000000000015"].itemStatus == statusKeys[2]
assert theProject.tree["0000000000016"].itemStatus == statusKeys[1]
assert theProject.tree["0000000000017"].itemStatus == statusKeys[3]
assert theProject.tree[C.hNovelRoot].itemStatus == statusKeys[3]
assert theProject.tree[C.hPlotRoot].itemStatus == statusKeys[2]
assert theProject.tree[C.hCharRoot].itemStatus == statusKeys[1]
assert theProject.tree[C.hWorldRoot].itemStatus == statusKeys[3]
newList = [
{"key": statusKeys[0], "name": "New", "cols": (1, 1, 1)},
@@ -780,30 +708,30 @@ def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd):
assert theProject.setStatusColours([], []) is False
assert theProject.setStatusColours(newList, []) is True
assert theProject.statusItems.name(statusKeys[0]) == "New"
assert theProject.statusItems.name(statusKeys[1]) == "Draft"
assert theProject.statusItems.name(statusKeys[2]) == "Note"
assert theProject.statusItems.name(statusKeys[3]) == "Edited"
assert theProject.statusItems.cols(statusKeys[0]) == (1, 1, 1)
assert theProject.statusItems.cols(statusKeys[1]) == (2, 2, 2)
assert theProject.statusItems.cols(statusKeys[2]) == (3, 3, 3)
assert theProject.statusItems.cols(statusKeys[3]) == (4, 4, 4)
assert theProject.data.itemStatus.name(statusKeys[0]) == "New"
assert theProject.data.itemStatus.name(statusKeys[1]) == "Draft"
assert theProject.data.itemStatus.name(statusKeys[2]) == "Note"
assert theProject.data.itemStatus.name(statusKeys[3]) == "Edited"
assert theProject.data.itemStatus.cols(statusKeys[0]) == (1, 1, 1)
assert theProject.data.itemStatus.cols(statusKeys[1]) == (2, 2, 2)
assert theProject.data.itemStatus.cols(statusKeys[2]) == (3, 3, 3)
assert theProject.data.itemStatus.cols(statusKeys[3]) == (4, 4, 4)
# Check the new entry
lastKey = theProject.statusItems.check("Finished")
assert lastKey == "s000018"
assert theProject.statusItems.name(lastKey) == "Finished"
assert theProject.statusItems.cols(lastKey) == (5, 5, 5)
lastKey = theProject.data.itemStatus.check("s000010")
assert lastKey == "s000010"
assert theProject.data.itemStatus.name(lastKey) == "Finished"
assert theProject.data.itemStatus.cols(lastKey) == (5, 5, 5)
# Delete last entry
assert theProject.setStatusColours([], [lastKey]) is True
assert theProject.statusItems.name(lastKey) == "New"
assert theProject.data.itemStatus.name(lastKey) == "New"
# Change Importance
# =================
fHandle = theProject.newFile("Jane Doe", "0000000000012")
theProject.tree[fHandle].setImport("Main")
fHandle = theProject.newFile("Jane Doe", C.hCharRoot)
theProject.tree[fHandle].setImport(importKeys[3])
assert theProject.tree[fHandle].itemImport == importKeys[3]
newList = [
@@ -817,53 +745,41 @@ def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd):
assert theProject.setImportColours([], []) is False
assert theProject.setImportColours(newList, []) is True
assert theProject.importItems.name(importKeys[0]) == "New"
assert theProject.importItems.name(importKeys[1]) == "Minor"
assert theProject.importItems.name(importKeys[2]) == "Major"
assert theProject.importItems.name(importKeys[3]) == "Min"
assert theProject.importItems.cols(importKeys[0]) == (1, 1, 1)
assert theProject.importItems.cols(importKeys[1]) == (2, 2, 2)
assert theProject.importItems.cols(importKeys[2]) == (3, 3, 3)
assert theProject.importItems.cols(importKeys[3]) == (4, 4, 4)
assert theProject.data.itemImport.name(importKeys[0]) == "New"
assert theProject.data.itemImport.name(importKeys[1]) == "Minor"
assert theProject.data.itemImport.name(importKeys[2]) == "Major"
assert theProject.data.itemImport.name(importKeys[3]) == "Min"
assert theProject.data.itemImport.cols(importKeys[0]) == (1, 1, 1)
assert theProject.data.itemImport.cols(importKeys[1]) == (2, 2, 2)
assert theProject.data.itemImport.cols(importKeys[2]) == (3, 3, 3)
assert theProject.data.itemImport.cols(importKeys[3]) == (4, 4, 4)
# Check the new entry
lastKey = theProject.importItems.check("Max")
assert lastKey == "i00001a"
assert theProject.importItems.name(lastKey) == "Max"
assert theProject.importItems.cols(lastKey) == (5, 5, 5)
lastKey = theProject.data.itemImport.check("i000012")
assert lastKey == "i000012"
assert theProject.data.itemImport.name(lastKey) == "Max"
assert theProject.data.itemImport.cols(lastKey) == (5, 5, 5)
# Delete last entry
assert theProject.setImportColours([], [lastKey]) is True
assert theProject.importItems.name(lastKey) == "New"
assert theProject.data.itemImport.name(lastKey) == "New"
# Delete Status/Import
# ====================
theProject.statusItems.resetCounts()
for key in list(theProject.statusItems.keys()):
assert theProject.statusItems.remove(key) is True
theProject.data.itemStatus.resetCounts()
for key in list(theProject.data.itemStatus.keys()):
assert theProject.data.itemStatus.remove(key) is True
theProject.importItems.resetCounts()
for key in list(theProject.importItems.keys()):
assert theProject.importItems.remove(key) is True
theProject.data.itemImport.resetCounts()
for key in list(theProject.data.itemImport.keys()):
assert theProject.data.itemImport.remove(key) is True
assert len(theProject.statusItems) == 0
assert len(theProject.importItems) == 0
assert len(theProject.data.itemStatus) == 0
assert len(theProject.data.itemImport) == 0
assert theProject.saveProject() is True
assert theProject.closeProject() is True
# This should restore the default status/import labels
assert theProject.openProject(fncDir) is True
assert theProject.saveProject() is True
assert theProject.statusItems.name("s000023") == "New"
assert theProject.statusItems.name("s000024") == "Note"
assert theProject.statusItems.name("s000025") == "Draft"
assert theProject.statusItems.name("s000026") == "Finished"
assert theProject.importItems.name("i000027") == "New"
assert theProject.importItems.name("i000028") == "Minor"
assert theProject.importItems.name("i000029") == "Major"
assert theProject.importItems.name("i00002a") == "Main"
# END Test testCoreProject_StatusImport
@@ -895,117 +811,104 @@ def testCoreProject_Methods(monkeypatch, mockGUI, tmpDir, fncDir, mockRnd):
assert theProject.setProjectPath(fncDir)
# Project Name
assert theProject.setProjectName(" A Name ")
assert theProject.projName == "A Name"
theProject.data.setName(" A Name ")
assert theProject.data.name == "A Name"
# Project Title
assert theProject.setBookTitle(" A Title ")
assert theProject.bookTitle == "A Title"
theProject.data.setTitle(" A Title ")
assert theProject.data.title == "A Title"
# Project Authors
# Check that the list is cleaned up and that it can be extracted as
# a properly formatted string, depending on number of names
assert not theProject.setBookAuthors([])
assert theProject.setBookAuthors(" Jane Doe \n John Doh \n ")
assert theProject.bookAuthors == ["Jane Doe", "John Doh"]
theProject.data.setAuthors([])
assert theProject.data.authors == []
theProject.data.setAuthors(" Jane Doe \n John Doh \n ")
assert theProject.data.authors == ["Jane Doe", "John Doh"]
assert theProject.setBookAuthors("")
assert theProject.getAuthors() == ""
theProject.data.setAuthors("")
assert theProject.getFormattedAuthors() == ""
assert theProject.setBookAuthors("Jane Doe")
assert theProject.getAuthors() == "Jane Doe"
theProject.data.setAuthors("Jane Doe")
assert theProject.getFormattedAuthors() == "Jane Doe"
assert theProject.setBookAuthors("Jane Doe\nJohn Doh")
assert theProject.getAuthors() == "Jane Doe and John Doh"
theProject.data.setAuthors("Jane Doe\nJohn Doh")
assert theProject.getFormattedAuthors() == "Jane Doe and John Doh"
assert theProject.setBookAuthors("Jane Doe\nJohn Doh\nBod Owens")
assert theProject.getAuthors() == "Jane Doe, John Doh and Bod Owens"
theProject.data.setAuthors("Jane Doe\nJohn Doh\nBod Owens")
assert theProject.getFormattedAuthors() == "Jane Doe, John Doh and Bod Owens"
# Edit Time
theProject.editTime = 1234
theProject.projOpened = 1600000000
theProject.data.setEditTime(1234)
theProject._projOpened = 1600000000
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.project.time", lambda: 1600005600)
assert theProject.getCurrentEditTime() == 6834
# Trash folder
# Should create on first call, and just returned on later calls
hTrash = "0000000000018"
hTrash = "0000000000010"
assert theProject.tree[hTrash] is None
assert theProject.trashFolder() == hTrash
assert theProject.trashFolder() == hTrash
# Project backup
assert theProject.doBackup is True
assert theProject.setProjBackup(False)
assert theProject.doBackup is False
assert not theProject.setProjBackup(True)
theProject.mainConf.backupPath = tmpDir
assert theProject.setProjBackup(True)
assert theProject.setProjectName("")
assert not theProject.setProjBackup(True)
assert theProject.setProjectName("A Name")
assert theProject.setProjBackup(True)
# Spell check
theProject.projChanged = False
assert theProject.setSpellCheck(True)
assert not theProject.setSpellCheck(False)
theProject.setProjectChanged(False)
theProject.data.setSpellCheck(True)
theProject.data.setSpellCheck(False)
assert theProject.projChanged
# Spell language
theProject.projChanged = False
assert theProject.projSpell is None
assert theProject.setSpellLang(None) is False
assert theProject.projSpell is None
assert theProject.setSpellLang("None") is False # Should be interpreded as None
assert theProject.projSpell is None
assert theProject.setSpellLang("en_GB")
assert theProject.projSpell == "en_GB"
theProject.setProjectChanged(False)
assert theProject.data.spellLang is None
theProject.data.setSpellLang(None)
assert theProject.data.spellLang is None
theProject.data.setSpellLang("None") # Should be interpreded as None
assert theProject.data.spellLang is None
theProject.data.setSpellLang("en_GB")
assert theProject.data.spellLang == "en_GB"
assert theProject.projChanged
# Project Language
theProject.projChanged = False
theProject.projLang = "en"
theProject.setProjectChanged(False)
theProject.data.setLanguage("en")
assert theProject.setProjectLang(None) is True
assert theProject.projLang is None
assert theProject.data.language is None
assert theProject.setProjectLang("en_GB") is True
assert theProject.projLang == "en_GB"
assert theProject.data.language == "en_GB"
# Language Lookup
assert theProject.localLookup(1) == "One"
assert theProject.localLookup(10) == "Ten"
# Last edited
theProject.projChanged = False
assert theProject.setLastEdited("0123456789abc")
assert theProject.lastEdited == "0123456789abc"
theProject.setProjectChanged(False)
theProject._data.setLastHandle("0123456789abc", "editor")
assert theProject._data.getLastHandle("editor") == "0123456789abc"
assert theProject.projChanged
# Last viewed
theProject.projChanged = False
assert theProject.setLastViewed("0123456789abc")
assert theProject.lastViewed == "0123456789abc"
theProject.setProjectChanged(False)
theProject._data.setLastHandle("0123456789abc", "viewer")
assert theProject._data.getLastHandle("viewer") == "0123456789abc"
assert theProject.projChanged
# Autoreplace
theProject.projChanged = False
assert theProject.setAutoReplace({"A": "B", "C": "D"})
assert theProject.autoReplace == {"A": "B", "C": "D"}
theProject.setProjectChanged(False)
theProject.data.setAutoReplace({"A": "B", "C": "D"})
assert theProject.data.autoReplace == {"A": "B", "C": "D"}
assert theProject.projChanged
# Change project tree order
oldOrder = [
"0000000000010", "0000000000011", "0000000000012",
"0000000000013", "0000000000014", "0000000000015",
"0000000000016", "0000000000017", "0000000000018",
"0000000000008", "0000000000009", "000000000000a",
"000000000000b", "000000000000c", "000000000000d",
"000000000000e", "000000000000f", "0000000000010",
]
newOrder = [
"0000000000013", "0000000000014", "0000000000015",
"0000000000010", "0000000000011", "0000000000012",
"0000000000016", "0000000000017",
"000000000000b", "000000000000c", "000000000000d",
"0000000000008", "0000000000009", "000000000000a",
"000000000000e", "000000000000f",
]
assert theProject.tree.handles() == oldOrder
assert theProject.setTreeOrder(newOrder)
@@ -1014,8 +917,8 @@ def testCoreProject_Methods(monkeypatch, mockGUI, tmpDir, fncDir, mockRnd):
assert theProject.tree.handles() == oldOrder
# Session stats
theProject.currWCount = 200
theProject.lastWCount = 100
theProject._data._initCounts = [50, 50]
theProject._data._currCounts = [100, 100]
with monkeypatch.context() as mp:
mp.setattr("os.path.isdir", lambda *a, **k: False)
assert not theProject._appendSessionStats(idleTime=0)
@@ -1029,9 +932,8 @@ def testCoreProject_Methods(monkeypatch, mockGUI, tmpDir, fncDir, mockRnd):
assert theProject.projMeta == os.path.join(fncDir, "meta")
statsFile = os.path.join(theProject.projMeta, nwFiles.SESS_STATS)
theProject.projOpened = 1600002000
theProject.currNovelWC = 200
theProject.currNotesWC = 100
theProject._projOpened = 1600002000
theProject._data._currCounts = [200, 100]
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.project.time", lambda: 1600005600)
@@ -1043,31 +945,6 @@ def testCoreProject_Methods(monkeypatch, mockGUI, tmpDir, fncDir, mockRnd):
"%s %s 200 100 99\n"
) % (formatTimeStamp(1600002000), formatTimeStamp(1600005600))
# Pack XML Value
xElem = etree.Element("element")
theProject._packProjectValue(xElem, "A", "B", allowNone=False)
assert etree.tostring(xElem, pretty_print=False, encoding="utf-8") == (
b"<element><A>B</A></element>"
)
xElem = etree.Element("element")
theProject._packProjectValue(xElem, "A", "", allowNone=False)
assert etree.tostring(xElem, pretty_print=False, encoding="utf-8") == (
b"<element/>"
)
# Pack XML Key/Value
xElem = etree.Element("element")
theProject._packProjectKeyValue(xElem, "item", {"A": "B", "C": "D"})
assert etree.tostring(xElem, pretty_print=False, encoding="utf-8") == (
b"<element>"
b"<item>"
b"<entry key=\"A\">B</entry>"
b"<entry key=\"C\">D</entry>"
b"</item>"
b"</element>"
)
# END Test testCoreProject_Methods
@@ -1305,14 +1182,14 @@ def testCoreProject_LegacyData(monkeypatch, mockGUI, fncDir):
@pytest.mark.core
def testCoreProject_Backup(monkeypatch, mockGUI, nwMinimal, tmpDir):
def testCoreProject_Backup(monkeypatch, mockGUI, fncDir, tmpDir):
"""Test the automated backup feature of the project class. The test
creates a backup of the Minimal test project, and then unzips the
backupd file and checks that the project XML file is identical to
the original file.
"""
theProject = NWProject(mockGUI)
assert theProject.openProject(nwMinimal)
buildTestProject(theProject, fncDir)
# Test faulty settings
@@ -1327,20 +1204,20 @@ def testCoreProject_Backup(monkeypatch, mockGUI, nwMinimal, tmpDir):
# Missing project name
theProject.mainConf.backupPath = tmpDir
theProject.projName = ""
theProject.data.setName("")
assert theProject.zipIt(doNotify=False) is False
# Non-existent folder
theProject.mainConf.backupPath = os.path.join(tmpDir, "nonexistent")
theProject.projName = "Test Minimal"
theProject.data.setName("Test Minimal")
assert theProject.zipIt(doNotify=False) is False
# Same folder as project (causes infinite loop in zipping)
theProject.mainConf.backupPath = nwMinimal
theProject.mainConf.backupPath = fncDir
assert theProject.zipIt(doNotify=False) is False
# Subfolder of project (causes infinite loop in zipping)
theProject.mainConf.backupPath = os.path.join(nwMinimal, "subdir")
theProject.mainConf.backupPath = os.path.join(fncDir, "subdir")
assert theProject.zipIt(doNotify=False) is False
# Set a valid folder
@@ -1372,7 +1249,7 @@ def testCoreProject_Backup(monkeypatch, mockGUI, nwMinimal, tmpDir):
# Check that the main project file was restored
assert cmpFiles(
os.path.join(nwMinimal, "nwProject.nwx"),
os.path.join(fncDir, "nwProject.nwx"),
os.path.join(tmpDir, "extract", "nwProject.nwx")
)
+798
View File
@@ -0,0 +1,798 @@
"""
novelWriter ProjectXMLReader/Writer Class Tester
==================================================
This file is a part of novelWriter
Copyright 20182022, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
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 json
import os
import pytest
import shutil
from datetime import datetime
from mock import causeOSError
from tools import cmpFiles, writeFile
from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProjectData
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
class MockProject:
def setProjectChanged(self, *a):
pass
@pytest.mark.core
def testCoreProjectXML_ReadCurrent(monkeypatch, filesDir, fncDir, outDir, refDir):
"""Test reading the current XML file format.
"""
refFile = os.path.join(filesDir, "nwProject-1.4.nwx")
xmlFile = os.path.join(fncDir, "nwProject-1.4.nwx")
bakFile = os.path.join(fncDir, "nwProject-1.4.bak")
outFile = os.path.join(fncDir, "nwProject.nwx")
xmlReader = ProjectXMLReader(xmlFile)
assert xmlReader.state == XMLReadState.NO_ACTION
data = NWProjectData(MockProject())
content = []
# With no valid files, the read should fail
writeFile(xmlFile, "")
assert xmlReader.read(data, content) is False
assert xmlReader.state == XMLReadState.CANNOT_PARSE
# Also add an invalid backup file
writeFile(bakFile, "")
assert xmlReader.read(data, content) is False
assert xmlReader.state == XMLReadState.CANNOT_PARSE
# Add a valid backup file, that is not novelWriter
writeFile(bakFile, "<xml/>")
assert xmlReader.read(data, content) is False
assert xmlReader.state == XMLReadState.NOT_NWX_FILE
# Add a valid project file, that is not novelWriter
writeFile(xmlFile, "<xml/>")
assert xmlReader.read(data, content) is False
assert xmlReader.state == XMLReadState.NOT_NWX_FILE
# Add a valid novelwriter file without a file version
writeFile(xmlFile, "<novelWriterXML/>")
assert xmlReader.read(data, content) is False
assert xmlReader.state == XMLReadState.UNKNOWN_VERSION
# Check parsing of unkown sections
writeFile(xmlFile, (
"<novelWriterXML fileVersion='1.4'>"
" <project>"
" <stuff></stuff>"
" </project>"
" <settings>"
" <stuff></stuff>"
" </settings>"
" <content>"
" <item>"
" <stuff></stuff>"
" </item>"
" <stuff></stuff>"
" </content>"
" <stuff></stuff>"
"</novelWriterXML>"
))
assert xmlReader.read(data, content) is True
assert xmlReader.state == XMLReadState.PARSED_OK
writeFile(xmlFile, (
"<novelWriterXML fileVersion='1.0'>"
" <project>"
" <stuff></stuff>"
" </project>"
" <settings>"
" <stuff></stuff>"
" </settings>"
" <content>"
" <item>"
" <stuff></stuff>"
" </item>"
" <stuff></stuff>"
" </content>"
" <stuff></stuff>"
"</novelWriterXML>"
))
assert xmlReader.read(data, content) is True
assert xmlReader.state == XMLReadState.WAS_LEGACY
# Reset data objects
data = NWProjectData(MockProject())
content = []
# Parse a valid, complete file
shutil.copy(refFile, xmlFile)
assert xmlReader.read(data, content) is True
assert xmlReader.state == XMLReadState.PARSED_OK
assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0104
assert xmlReader.appVersion == "2.0-rc1"
assert xmlReader.hexVersion == "0x020000c1"
# Check loaded data
assert data.name == "Sample Project"
assert data.title == "Sample Project"
assert data.authors == ["Jane Smith", "Jay Doh"]
assert data.saveCount == 5
assert data.autoCount == 10
assert data.editTime == 1000
assert data.doBackup is True
assert data.language == "en_GB"
assert data.spellCheck is True
assert data.spellLang == "en_GB"
assert data.initCounts == (954, 409)
assert data.currCounts == (954, 409)
assert data.getLastHandle("editor") == "636b6aa9b697b"
assert data.getLastHandle("viewer") == "636b6aa9b697b"
assert data.getLastHandle("novelTree") == "7031beac91f75"
assert data.getLastHandle("outline") == "7031beac91f75"
assert data.getTitleFormat("title") == "%title%"
assert data.getTitleFormat("chapter") == "Chapter %chw%: %title%"
assert data.getTitleFormat("unnumbered") == "%title%"
assert data.getTitleFormat("scene") == "Scene %ch%.%sc%: %title%"
assert data.getTitleFormat("section") == ""
assert data.itemStatus.name("sf12341") == "New"
assert data.itemStatus.name("sf24ce6") == "Notes"
assert data.itemStatus.name("sc24b8f") == "Started"
assert data.itemStatus.name("s90e6c9") == "1st Draft"
assert data.itemStatus.name("sd51c5b") == "2nd Draft"
assert data.itemStatus.name("s8ae72a") == "3rd Draft"
assert data.itemStatus.name("s78ea90") == "Finished"
assert data.itemImport.name("ia857f0") == "None"
assert data.itemImport.name("icfb3a5") == "Minor"
assert data.itemImport.name("i2d7a54") == "Major"
assert data.itemImport.name("i56be10") == "Main"
assert data.itemStatus.cols("sf12341") == (100, 100, 100)
assert data.itemStatus.cols("sf24ce6") == (200, 50, 0)
assert data.itemStatus.cols("sc24b8f") == (182, 60, 0)
assert data.itemStatus.cols("s90e6c9") == (193, 129, 0)
assert data.itemStatus.cols("sd51c5b") == (193, 129, 0)
assert data.itemStatus.cols("s8ae72a") == (193, 129, 0)
assert data.itemStatus.cols("s78ea90") == (58, 180, 58)
assert data.itemImport.cols("ia857f0") == (100, 100, 100)
assert data.itemImport.cols("icfb3a5") == (0, 122, 188)
assert data.itemImport.cols("i2d7a54") == (21, 0, 180)
assert data.itemImport.cols("i56be10") == (117, 0, 175)
assert data.itemStatus.count("sf12341") == 4
assert data.itemStatus.count("sf24ce6") == 2
assert data.itemStatus.count("sc24b8f") == 3
assert data.itemStatus.count("s90e6c9") == 7
assert data.itemStatus.count("sd51c5b") == 0
assert data.itemStatus.count("s8ae72a") == 0
assert data.itemStatus.count("s78ea90") == 1
assert data.itemImport.count("ia857f0") == 5
assert data.itemImport.count("icfb3a5") == 2
assert data.itemImport.count("i2d7a54") == 2
assert data.itemImport.count("i56be10") == 1
# Compare content
dumpFile = os.path.join(outDir, "projectXML_ReadCurrent.json")
compFile = os.path.join(refDir, "projectXML_ReadCurrent.json")
with open(dumpFile, mode="w", encoding="utf-8") as dump:
json.dump(content, dump, indent=2)
assert cmpFiles(dumpFile, compFile)
packedContent = []
mockProject = MockProject()
mockProject.__setattr__("data", data)
for entry in content:
item = NWItem(mockProject)
item.unpack(entry)
packedContent.append(item.pack())
# Save the project again, which should produce an identical project xml
timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp())
xmlWriter = ProjectXMLWriter(fncDir)
# Fail saving
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is False
assert str(xmlWriter.error) == "Mock OSError"
with monkeypatch.context() as mp:
mp.setattr("os.replace", causeOSError)
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is False
assert str(xmlWriter.error) == "Mock OSError"
# Successful save (should be twice)
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
assert cmpFiles(outFile, xmlFile)
# END Test testCoreProjectXML_ReadCurrent
@pytest.mark.core
def testCoreProjectXML_ReadLegacy10(filesDir, fncDir, outDir, refDir, mockRnd):
"""Test reading the version 1.0 XML file format.
"""
refFile = os.path.join(filesDir, "nwProject-1.0.nwx")
xmlFile = os.path.join(fncDir, "nwProject-1.0.nwx")
outFile = os.path.join(fncDir, "nwProject.nwx")
shutil.copy(refFile, xmlFile)
xmlReader = ProjectXMLReader(xmlFile)
assert xmlReader.state == XMLReadState.NO_ACTION
data = NWProjectData(MockProject())
content = []
assert xmlReader.read(data, content) is True
assert xmlReader.state == XMLReadState.WAS_LEGACY
assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0100
assert xmlReader.appVersion == "0.6.1"
assert xmlReader.hexVersion == "0x000601f0"
# Check loaded data
assert data.name == "Sample Project"
assert data.title == "Sample Project"
assert data.authors == ["Jane Smith", "Jay Doh"]
assert data.saveCount == 0 # Doesn't exist in 1.0
assert data.autoCount == 0 # Doesn't exist in 1.0
assert data.editTime == 0 # Doesn't exist in 1.0
assert data.doBackup is True
assert data.language is None # Doesn't exist in 1.0
assert data.spellCheck is True
assert data.spellLang is None # Doesn't exist in 1.0
assert data.initCounts == (0, 0)
assert data.currCounts == (0, 0)
assert data.getLastHandle("editor") is None # Dropped by conversion
assert data.getLastHandle("viewer") is None # Dropped by conversion
assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.0
assert data.getLastHandle("outline") is None # Doesn't exist in 1.0
assert data.getTitleFormat("title") == "%title%"
assert data.getTitleFormat("chapter") == "Chapter %ch%: %title%"
assert data.getTitleFormat("unnumbered") == "%title%"
assert data.getTitleFormat("scene") == "Scene %ch%.%sc%: %title%"
assert data.getTitleFormat("section") == ""
assert data.itemStatus.name("s000000") == "New"
assert data.itemStatus.name("s000001") == "Notes"
assert data.itemStatus.name("s000002") == "Started"
assert data.itemStatus.name("s000003") == "1st Draft"
assert data.itemStatus.name("s000004") == "2nd Draft"
assert data.itemStatus.name("s000005") == "3rd Draft"
assert data.itemStatus.name("s000006") == "Finished"
assert data.itemImport.name("i000007") == "None"
assert data.itemImport.name("i000008") == "Minor"
assert data.itemImport.name("i000009") == "Major"
assert data.itemImport.name("i00000a") == "Main"
assert data.itemStatus.cols("s000000") == (100, 100, 100)
assert data.itemStatus.cols("s000001") == (200, 50, 0)
assert data.itemStatus.cols("s000002") == (182, 60, 0)
assert data.itemStatus.cols("s000003") == (193, 129, 0)
assert data.itemStatus.cols("s000004") == (193, 129, 0)
assert data.itemStatus.cols("s000005") == (193, 129, 0)
assert data.itemStatus.cols("s000006") == (58, 180, 58)
assert data.itemImport.cols("i000007") == (100, 100, 100)
assert data.itemImport.cols("i000008") == (0, 122, 188)
assert data.itemImport.cols("i000009") == (21, 0, 180)
assert data.itemImport.cols("i00000a") == (117, 0, 175)
assert data.itemStatus.count("s000000") == 0
assert data.itemStatus.count("s000001") == 0
assert data.itemStatus.count("s000002") == 0
assert data.itemStatus.count("s000003") == 0
assert data.itemStatus.count("s000004") == 0
assert data.itemStatus.count("s000005") == 0
assert data.itemStatus.count("s000006") == 0
assert data.itemImport.count("i000007") == 0
assert data.itemImport.count("i000008") == 0
assert data.itemImport.count("i000009") == 0
assert data.itemImport.count("i00000a") == 0
# Compare content
dumpFile = os.path.join(outDir, "projectXML_ReadLegacy10.json")
compFile = os.path.join(refDir, "projectXML_ReadLegacy10.json")
with open(dumpFile, mode="w", encoding="utf-8") as dump:
json.dump(content, dump, indent=2)
assert cmpFiles(dumpFile, compFile)
packedContent = []
mockProject = MockProject()
mockProject.__setattr__("data", data)
status = {}
for entry in content:
item = NWItem(mockProject)
item.unpack(entry)
status[item.itemHandle] = item.getImportStatus(incIcon=False)[0]
packedContent.append(item.pack())
assert status == {
"7031beac91f75": "Started",
"53b69b83cdafc": "Started",
"974e400180a99": "New",
"edca4be2fcaf8": "New",
"e7ded148d6e4a": "1st Draft",
"6a2d6d5f4f401": "Notes",
"636b6aa9b697b": "1st Draft",
"bc0cbd2a407f3": "1st Draft",
"ba8a28a246524": "Finished",
"96b68994dfa3d": "2nd Draft",
"88706ddc78b1b": "1st Draft",
"ae7339df26ded": "1st Draft",
"f6622b4617424": "None",
"f7e2d9f330615": "None",
"14298de4d9524": "Minor",
"bb2c23b3c42cc": "Major",
"15c4492bd5107": "None",
"b3e74dbc1f584": "Main",
"f1471bef9f2ae": "Minor",
"5eaea4e8cdee8": "Major",
"98acd8c76c93a": "None",
"b8136a5a774a0": "New",
}
# Save the project again, which should produce an identical project xml
timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp())
xmlWriter = ProjectXMLWriter(fncDir)
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
compFile = os.path.join(refDir, "projectXML_ReadLegacy10.nwx")
assert cmpFiles(outFile, compFile)
# END Test testCoreProjectXML_ReadLegacy10
@pytest.mark.core
def testCoreProjectXML_ReadLegacy11(filesDir, fncDir, outDir, refDir, mockRnd):
"""Test reading the version 1.1 XML file format.
"""
refFile = os.path.join(filesDir, "nwProject-1.1.nwx")
xmlFile = os.path.join(fncDir, "nwProject-1.1.nwx")
outFile = os.path.join(fncDir, "nwProject.nwx")
shutil.copy(refFile, xmlFile)
xmlReader = ProjectXMLReader(xmlFile)
assert xmlReader.state == XMLReadState.NO_ACTION
data = NWProjectData(MockProject())
content = []
assert xmlReader.read(data, content) is True
assert xmlReader.state == XMLReadState.WAS_LEGACY
assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0101
assert xmlReader.appVersion == "0.9.2"
assert xmlReader.hexVersion == "0x000902f0"
# Check loaded data
assert data.name == "Sample Project"
assert data.title == "Sample Project"
assert data.authors == ["Jane Smith", "Jay Doh"]
assert data.saveCount == 5
assert data.autoCount == 10
assert data.editTime == 1000
assert data.doBackup is True
assert data.language is None # Doesn't exist in 1.1
assert data.spellCheck is True
assert data.spellLang is None # Doesn't exist in 1.1
assert data.initCounts == (0, 0)
assert data.currCounts == (0, 0)
assert data.getLastHandle("editor") is None # Dropped by conversion
assert data.getLastHandle("viewer") is None # Dropped by conversion
assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.1
assert data.getLastHandle("outline") is None # Doesn't exist in 1.1
assert data.getTitleFormat("title") == "%title%"
assert data.getTitleFormat("chapter") == "Chapter %ch%: %title%"
assert data.getTitleFormat("unnumbered") == "%title%"
assert data.getTitleFormat("scene") == "Scene %ch%.%sc%: %title%"
assert data.getTitleFormat("section") == ""
assert data.itemStatus.name("s000000") == "New"
assert data.itemStatus.name("s000001") == "Notes"
assert data.itemStatus.name("s000002") == "Started"
assert data.itemStatus.name("s000003") == "1st Draft"
assert data.itemStatus.name("s000004") == "2nd Draft"
assert data.itemStatus.name("s000005") == "3rd Draft"
assert data.itemStatus.name("s000006") == "Finished"
assert data.itemImport.name("i000007") == "None"
assert data.itemImport.name("i000008") == "Minor"
assert data.itemImport.name("i000009") == "Major"
assert data.itemImport.name("i00000a") == "Main"
assert data.itemStatus.cols("s000000") == (100, 100, 100)
assert data.itemStatus.cols("s000001") == (200, 50, 0)
assert data.itemStatus.cols("s000002") == (182, 60, 0)
assert data.itemStatus.cols("s000003") == (193, 129, 0)
assert data.itemStatus.cols("s000004") == (193, 129, 0)
assert data.itemStatus.cols("s000005") == (193, 129, 0)
assert data.itemStatus.cols("s000006") == (58, 180, 58)
assert data.itemImport.cols("i000007") == (100, 100, 100)
assert data.itemImport.cols("i000008") == (0, 122, 188)
assert data.itemImport.cols("i000009") == (21, 0, 180)
assert data.itemImport.cols("i00000a") == (117, 0, 175)
assert data.itemStatus.count("s000000") == 0
assert data.itemStatus.count("s000001") == 0
assert data.itemStatus.count("s000002") == 0
assert data.itemStatus.count("s000003") == 0
assert data.itemStatus.count("s000004") == 0
assert data.itemStatus.count("s000005") == 0
assert data.itemStatus.count("s000006") == 0
assert data.itemImport.count("i000007") == 0
assert data.itemImport.count("i000008") == 0
assert data.itemImport.count("i000009") == 0
assert data.itemImport.count("i00000a") == 0
# Compare content
dumpFile = os.path.join(outDir, "projectXML_ReadLegacy11.json")
compFile = os.path.join(refDir, "projectXML_ReadLegacy11.json")
with open(dumpFile, mode="w", encoding="utf-8") as dump:
json.dump(content, dump, indent=2)
assert cmpFiles(dumpFile, compFile)
packedContent = []
mockProject = MockProject()
mockProject.__setattr__("data", data)
status = {}
for entry in content:
item = NWItem(mockProject)
item.unpack(entry)
status[item.itemHandle] = item.getImportStatus(incIcon=False)[0]
packedContent.append(item.pack())
assert status == {
"7031beac91f75": "Started",
"53b69b83cdafc": "Started",
"974e400180a99": "New",
"edca4be2fcaf8": "New",
"e7ded148d6e4a": "1st Draft",
"6a2d6d5f4f401": "Notes",
"636b6aa9b697b": "1st Draft",
"bc0cbd2a407f3": "1st Draft",
"ba8a28a246524": "Finished",
"96b68994dfa3d": "2nd Draft",
"88706ddc78b1b": "1st Draft",
"ae7339df26ded": "1st Draft",
"f6622b4617424": "None",
"f7e2d9f330615": "None",
"14298de4d9524": "Minor",
"bb2c23b3c42cc": "Major",
"15c4492bd5107": "None",
"b3e74dbc1f584": "Main",
"f1471bef9f2ae": "Minor",
"5eaea4e8cdee8": "Major",
"98acd8c76c93a": "None",
"b8136a5a774a0": "New",
}
# Save the project again, which should produce an identical project xml
timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp())
xmlWriter = ProjectXMLWriter(fncDir)
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
compFile = os.path.join(refDir, "projectXML_ReadLegacy11.nwx")
assert cmpFiles(outFile, compFile)
# END Test testCoreProjectXML_ReadLegacy11
@pytest.mark.core
def testCoreProjectXML_ReadLegacy12(filesDir, fncDir, outDir, refDir, mockRnd):
"""Test reading the version 1.2 XML file format.
"""
refFile = os.path.join(filesDir, "nwProject-1.2.nwx")
xmlFile = os.path.join(fncDir, "nwProject-1.2.nwx")
outFile = os.path.join(fncDir, "nwProject.nwx")
shutil.copy(refFile, xmlFile)
xmlReader = ProjectXMLReader(xmlFile)
assert xmlReader.state == XMLReadState.NO_ACTION
data = NWProjectData(MockProject())
content = []
assert xmlReader.read(data, content) is True
assert xmlReader.state == XMLReadState.WAS_LEGACY
assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0102
assert xmlReader.appVersion == "1.4.2"
assert xmlReader.hexVersion == "0x010402f0"
# Check loaded data
assert data.name == "Sample Project"
assert data.title == "Sample Project"
assert data.authors == ["Jane Smith", "Jay Doh"]
assert data.saveCount == 5
assert data.autoCount == 10
assert data.editTime == 1000
assert data.doBackup is True
assert data.language == "en_GB"
assert data.spellCheck is True
assert data.spellLang == "en_GB"
assert data.initCounts == (840, 376)
assert data.currCounts == (840, 376)
assert data.getLastHandle("editor") is None # Dropped by conversion
assert data.getLastHandle("viewer") is None # Dropped by conversion
assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.2
assert data.getLastHandle("outline") is None # Doesn't exist in 1.2
assert data.getTitleFormat("title") == "%title%"
assert data.getTitleFormat("chapter") == "Chapter %chw%: %title%"
assert data.getTitleFormat("unnumbered") == "%title%"
assert data.getTitleFormat("scene") == "Scene %ch%.%sc%: %title%"
assert data.getTitleFormat("section") == ""
assert data.itemStatus.name("s000000") == "New"
assert data.itemStatus.name("s000001") == "Notes"
assert data.itemStatus.name("s000002") == "Started"
assert data.itemStatus.name("s000003") == "1st Draft"
assert data.itemStatus.name("s000004") == "2nd Draft"
assert data.itemStatus.name("s000005") == "3rd Draft"
assert data.itemStatus.name("s000006") == "Finished"
assert data.itemImport.name("i000007") == "None"
assert data.itemImport.name("i000008") == "Minor"
assert data.itemImport.name("i000009") == "Major"
assert data.itemImport.name("i00000a") == "Main"
assert data.itemStatus.cols("s000000") == (100, 100, 100)
assert data.itemStatus.cols("s000001") == (200, 50, 0)
assert data.itemStatus.cols("s000002") == (182, 60, 0)
assert data.itemStatus.cols("s000003") == (193, 129, 0)
assert data.itemStatus.cols("s000004") == (193, 129, 0)
assert data.itemStatus.cols("s000005") == (193, 129, 0)
assert data.itemStatus.cols("s000006") == (58, 180, 58)
assert data.itemImport.cols("i000007") == (100, 100, 100)
assert data.itemImport.cols("i000008") == (0, 122, 188)
assert data.itemImport.cols("i000009") == (21, 0, 180)
assert data.itemImport.cols("i00000a") == (117, 0, 175)
assert data.itemStatus.count("s000000") == 0
assert data.itemStatus.count("s000001") == 0
assert data.itemStatus.count("s000002") == 0
assert data.itemStatus.count("s000003") == 0
assert data.itemStatus.count("s000004") == 0
assert data.itemStatus.count("s000005") == 0
assert data.itemStatus.count("s000006") == 0
assert data.itemImport.count("i000007") == 0
assert data.itemImport.count("i000008") == 0
assert data.itemImport.count("i000009") == 0
assert data.itemImport.count("i00000a") == 0
# Compare content
dumpFile = os.path.join(outDir, "projectXML_ReadLegacy12.json")
compFile = os.path.join(refDir, "projectXML_ReadLegacy12.json")
with open(dumpFile, mode="w", encoding="utf-8") as dump:
json.dump(content, dump, indent=2)
assert cmpFiles(dumpFile, compFile)
packedContent = []
mockProject = MockProject()
mockProject.__setattr__("data", data)
status = {}
for entry in content:
item = NWItem(mockProject)
item.unpack(entry)
status[item.itemHandle] = item.getImportStatus(incIcon=False)[0]
packedContent.append(item.pack())
assert status == {
"7031beac91f75": "Started",
"53b69b83cdafc": "Started",
"974e400180a99": "New",
"edca4be2fcaf8": "New",
"e7ded148d6e4a": "1st Draft",
"6a2d6d5f4f401": "Notes",
"636b6aa9b697b": "1st Draft",
"bc0cbd2a407f3": "1st Draft",
"ba8a28a246524": "New",
"96b68994dfa3d": "2nd Draft",
"88706ddc78b1b": "1st Draft",
"ae7339df26ded": "1st Draft",
"f6622b4617424": "None",
"f7e2d9f330615": "None",
"14298de4d9524": "Minor",
"bb2c23b3c42cc": "Major",
"15c4492bd5107": "None",
"b3e74dbc1f584": "Main",
"f1471bef9f2ae": "Minor",
"5eaea4e8cdee8": "Major",
"6827118336ac1": "New", # Is now treated as novel-like
"ae9bf3c3ea159": "New", # Is now treated as novel-like
"8a5deb88c0e97": "1st Draft",
"98acd8c76c93a": "None",
"b8136a5a774a0": "New",
}
# Save the project again, which should produce an identical project xml
timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp())
xmlWriter = ProjectXMLWriter(fncDir)
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
compFile = os.path.join(refDir, "projectXML_ReadLegacy12.nwx")
assert cmpFiles(outFile, compFile)
# END Test testCoreProjectXML_ReadLegacy12
@pytest.mark.core
def testCoreProjectXML_ReadLegacy13(filesDir, fncDir, outDir, refDir, mockRnd):
"""Test reading the version 1.3 XML file format.
"""
refFile = os.path.join(filesDir, "nwProject-1.3.nwx")
xmlFile = os.path.join(fncDir, "nwProject-1.3.nwx")
outFile = os.path.join(fncDir, "nwProject.nwx")
shutil.copy(refFile, xmlFile)
xmlReader = ProjectXMLReader(xmlFile)
assert xmlReader.state == XMLReadState.NO_ACTION
data = NWProjectData(MockProject())
content = []
assert xmlReader.read(data, content) is True
assert xmlReader.state == XMLReadState.WAS_LEGACY
assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0103
assert xmlReader.appVersion == "1.6.6"
assert xmlReader.hexVersion == "0x010606f0"
# Check loaded data
assert data.name == "Sample Project"
assert data.title == "Sample Project"
assert data.authors == ["Jane Smith", "Jay Doh"]
assert data.saveCount == 5
assert data.autoCount == 10
assert data.editTime == 1000
assert data.doBackup is True
assert data.language == "en_GB"
assert data.spellCheck is True
assert data.spellLang == "en_GB"
assert data.initCounts == (830, 376)
assert data.currCounts == (830, 376)
assert data.getLastHandle("editor") is None # Dropped by conversion
assert data.getLastHandle("viewer") is None # Dropped by conversion
assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.3
assert data.getLastHandle("outline") is None # Doesn't exist in 1.3
assert data.getTitleFormat("title") == "%title%"
assert data.getTitleFormat("chapter") == "Chapter %chw%: %title%"
assert data.getTitleFormat("unnumbered") == "%title%"
assert data.getTitleFormat("scene") == "Scene %ch%.%sc%: %title%"
assert data.getTitleFormat("section") == ""
assert data.itemStatus.name("s000000") == "New"
assert data.itemStatus.name("s000001") == "Notes"
assert data.itemStatus.name("s000002") == "Started"
assert data.itemStatus.name("s000003") == "1st Draft"
assert data.itemStatus.name("s000004") == "2nd Draft"
assert data.itemStatus.name("s000005") == "3rd Draft"
assert data.itemStatus.name("s000006") == "Finished"
assert data.itemImport.name("i000007") == "None"
assert data.itemImport.name("i000008") == "Minor"
assert data.itemImport.name("i000009") == "Major"
assert data.itemImport.name("i00000a") == "Main"
assert data.itemStatus.cols("s000000") == (100, 100, 100)
assert data.itemStatus.cols("s000001") == (200, 50, 0)
assert data.itemStatus.cols("s000002") == (182, 60, 0)
assert data.itemStatus.cols("s000003") == (193, 129, 0)
assert data.itemStatus.cols("s000004") == (193, 129, 0)
assert data.itemStatus.cols("s000005") == (193, 129, 0)
assert data.itemStatus.cols("s000006") == (58, 180, 58)
assert data.itemImport.cols("i000007") == (100, 100, 100)
assert data.itemImport.cols("i000008") == (0, 122, 188)
assert data.itemImport.cols("i000009") == (21, 0, 180)
assert data.itemImport.cols("i00000a") == (117, 0, 175)
assert data.itemStatus.count("s000000") == 0
assert data.itemStatus.count("s000001") == 0
assert data.itemStatus.count("s000002") == 0
assert data.itemStatus.count("s000003") == 0
assert data.itemStatus.count("s000004") == 0
assert data.itemStatus.count("s000005") == 0
assert data.itemStatus.count("s000006") == 0
assert data.itemImport.count("i000007") == 0
assert data.itemImport.count("i000008") == 0
assert data.itemImport.count("i000009") == 0
assert data.itemImport.count("i00000a") == 0
# Compare content
dumpFile = os.path.join(outDir, "projectXML_ReadLegacy13.json")
compFile = os.path.join(refDir, "projectXML_ReadLegacy13.json")
with open(dumpFile, mode="w", encoding="utf-8") as dump:
json.dump(content, dump, indent=2)
assert cmpFiles(dumpFile, compFile)
packedContent = []
mockProject = MockProject()
mockProject.__setattr__("data", data)
status = {}
for entry in content:
item = NWItem(mockProject)
item.unpack(entry)
status[item.itemHandle] = item.getImportStatus(incIcon=False)[0]
packedContent.append(item.pack())
assert status == {
"7031beac91f75": "Started",
"53b69b83cdafc": "Started",
"974e400180a99": "New",
"edca4be2fcaf8": "New",
"e7ded148d6e4a": "1st Draft",
"6a2d6d5f4f401": "Notes",
"636b6aa9b697b": "1st Draft",
"bc0cbd2a407f3": "1st Draft",
"ba8a28a246524": "New",
"96b68994dfa3d": "2nd Draft",
"88706ddc78b1b": "1st Draft",
"ae7339df26ded": "1st Draft",
"f6622b4617424": "None",
"f7e2d9f330615": "None",
"14298de4d9524": "Minor",
"bb2c23b3c42cc": "Major",
"15c4492bd5107": "None",
"b3e74dbc1f584": "Main",
"f1471bef9f2ae": "Minor",
"5eaea4e8cdee8": "Major",
"6827118336ac1": "New", # Is now treated as novel-like
"ae9bf3c3ea159": "New", # Is now treated as novel-like
"8a5deb88c0e97": "1st Draft",
"98acd8c76c93a": "None",
"b8136a5a774a0": "New",
}
# Save the project again, which should produce an identical project xml
timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp())
xmlWriter = ProjectXMLWriter(fncDir)
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
compFile = os.path.join(refDir, "projectXML_ReadLegacy13.nwx")
assert cmpFiles(outFile, compFile)
# END Test testCoreProjectXML_ReadLegacy13
+53 -49
View File
@@ -20,23 +20,21 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import pytest
import random
from lxml import etree
from tools import C
from PyQt5.QtGui import QIcon
from novelwriter.core.status import NWStatus
statusKeys = ["sa3b179", "s1c8031", "s06671a", "sbdd640"]
importKeys = ["i466852", "i3eb13b", "i392456", "i23b8c1"]
statusKeys = [C.sNew, C.sNote, C.sDraft, C.sFinished]
importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain]
@pytest.mark.core
def testCoreStatus_Internal():
def testCoreStatus_Internal(mockRnd):
"""Test all the internal functions of the NWStatus class.
"""
random.seed(42)
theStatus = NWStatus(NWStatus.STATUS)
theImport = NWStatus(NWStatus.IMPORT)
@@ -87,10 +85,9 @@ def testCoreStatus_Internal():
@pytest.mark.core
def testCoreStatus_Iterator():
def testCoreStatus_Iterator(mockRnd):
"""Test the iterator functions of the NWStatus class.
"""
random.seed(42)
theStatus = NWStatus(NWStatus.STATUS)
theStatus.write(None, "New", (100, 100, 100))
@@ -132,10 +129,9 @@ def testCoreStatus_Iterator():
@pytest.mark.core
def testCoreStatus_Entries():
def testCoreStatus_Entries(mockRnd):
"""Test all the simple setters for the NWStatus class.
"""
random.seed(42)
theStatus = NWStatus(NWStatus.STATUS)
# Write
@@ -161,14 +157,6 @@ def testCoreStatus_Entries():
assert theStatus[statusKeys[3]]["name"] == "Entry 4"
assert theStatus[statusKeys[3]]["cols"] == (100, 100, 100)
# Check reverse map
assert theStatus._reverse == {
"Entry 1": statusKeys[0],
"Entry 2": statusKeys[1],
"Entry 3": statusKeys[2],
"Entry 4": statusKeys[3],
}
# Check
# =====
@@ -176,14 +164,8 @@ def testCoreStatus_Entries():
for key in statusKeys:
assert theStatus.check(key) == key
# Reverse map lookup
assert theStatus.check("Entry 1") == statusKeys[0]
assert theStatus.check("Entry 2") == statusKeys[1]
assert theStatus.check("Entry 3") == statusKeys[2]
assert theStatus.check("Entry 4") == statusKeys[3]
# Non-existing name
assert theStatus.check("Entry 5") == statusKeys[0]
assert theStatus.check("s987654") == statusKeys[0]
# Name Access
# ===========
@@ -314,10 +296,9 @@ def testCoreStatus_Entries():
@pytest.mark.core
def testCoreStatus_XMLPackUnpack():
"""Test all the XML pack/unpack of the NWStatus class.
def testCoreStatus_PackUnpack(mockRnd):
"""Test all the pack/unpack of the NWStatus class.
"""
random.seed(42)
theStatus = NWStatus(NWStatus.STATUS)
theStatus.write(None, "New", (100, 100, 100))
theStatus.write(None, "Note", (200, 50, 0))
@@ -329,36 +310,59 @@ def testCoreStatus_XMLPackUnpack():
for _ in range(n):
theStatus.increment(statusKeys[i])
nwXML = etree.Element("novelWriterXML")
# Pack
xStatus = etree.SubElement(nwXML, "status")
theStatus.packXML(xStatus)
assert etree.tostring(xStatus, pretty_print=False, encoding="utf-8") == (
b'<status>'
b'<entry key="sa3b179" count="3" red="100" green="100" blue="100">New</entry>'
b'<entry key="s1c8031" count="5" red="200" green="50" blue="0">Note</entry>'
b'<entry key="s06671a" count="7" red="200" green="150" blue="0">Draft</entry>'
b'<entry key="sbdd640" count="9" red="50" green="200" blue="0">Finished</entry>'
b'</status>'
)
assert list(theStatus.pack()) == [
("New", {
"key": statusKeys[0],
"count": "3",
"red": "100",
"green": "100",
"blue": "100"
}),
("Note", {
"key": statusKeys[1],
"count": "5",
"red": "200",
"green": "50",
"blue": "0"
}),
("Draft", {
"key": statusKeys[2],
"count": "7",
"red": "200",
"green": "150",
"blue": "0"
}),
("Finished", {
"key": statusKeys[3],
"count": "9",
"red": "50",
"green": "200",
"blue": "0"
}),
]
# Unpack
theStatus = NWStatus(NWStatus.STATUS)
assert theStatus.unpackXML(xStatus)
assert theStatus.unpack({
statusKeys[0]: {"label": "New0", "colour": (100, 100, 100), "count": countTo[0]},
statusKeys[1]: {"label": "New1", "colour": (150, 150, 150), "count": countTo[1]},
statusKeys[2]: {"label": "New2", "colour": (200, 200, 200), "count": countTo[2]},
statusKeys[3]: {"label": "New3", "colour": (250, 250, 250), "count": countTo[3]},
})
assert len(theStatus._store) == 4
assert list(theStatus._store.keys()) == statusKeys
assert theStatus._store[statusKeys[0]]["name"] == "New"
assert theStatus._store[statusKeys[1]]["name"] == "Note"
assert theStatus._store[statusKeys[2]]["name"] == "Draft"
assert theStatus._store[statusKeys[3]]["name"] == "Finished"
assert theStatus._store[statusKeys[0]]["name"] == "New0"
assert theStatus._store[statusKeys[1]]["name"] == "New1"
assert theStatus._store[statusKeys[2]]["name"] == "New2"
assert theStatus._store[statusKeys[3]]["name"] == "New3"
assert theStatus._store[statusKeys[0]]["cols"] == (100, 100, 100)
assert theStatus._store[statusKeys[1]]["cols"] == (200, 50, 0)
assert theStatus._store[statusKeys[2]]["cols"] == (200, 150, 0)
assert theStatus._store[statusKeys[3]]["cols"] == (50, 200, 0)
assert theStatus._store[statusKeys[1]]["cols"] == (150, 150, 150)
assert theStatus._store[statusKeys[2]]["cols"] == (200, 200, 200)
assert theStatus._store[statusKeys[3]]["cols"] == (250, 250, 250)
assert theStatus._store[statusKeys[0]]["count"] == countTo[0]
assert theStatus._store[statusKeys[1]]["count"] == countTo[1]
assert theStatus._store[statusKeys[2]]["count"] == countTo[2]
assert theStatus._store[statusKeys[3]]["count"] == countTo[3]
# END Test testCoreStatus_XMLPackUnpack
# END Test testCoreStatus_PackUnpack
+16 -17
View File
@@ -22,7 +22,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import os
import pytest
from tools import readFile
from tools import C, buildTestProject, readFile
from novelwriter.core.project import NWProject
from novelwriter.core.document import NWDoc
@@ -133,21 +133,20 @@ def testCoreToken_Setters(mockGUI):
@pytest.mark.core
def testCoreToken_TextOps(monkeypatch, nwMinimal, mockGUI):
def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncDir):
"""Test handling files and text in the Tokenizer class.
"""
theProject = NWProject(mockGUI)
theProject.projLang = "en"
mockRnd.reset()
buildTestProject(theProject, fncDir)
theProject.data.setLanguage("en")
theProject._loadProjectLocalisation()
theToken = BareTokenizer(theProject)
theToken.setKeepMarkdown(True)
assert theProject.openProject(nwMinimal)
sHandle = "8c659a11cd429"
# Set some content to work with
docText = (
"### Scene Six\n\n"
"This is text with _italic text_, some **bold text**, some ~~deleted text~~, "
@@ -157,26 +156,26 @@ def testCoreToken_TextOps(monkeypatch, nwMinimal, mockGUI):
)
docTextR = docText.replace("<A>", "this").replace("<B>", "that")
nDoc = NWDoc(theProject, sHandle)
nDoc = NWDoc(theProject, C.hSceneDoc)
assert nDoc.writeDocument(docText)
theProject.setAutoReplace({"A": "this", "B": "that"})
theProject.data.setAutoReplace({"A": "this", "B": "that"})
assert theProject.saveProject()
# Root Heading
assert theToken.addRootHeading("stuff") is False
assert theToken.addRootHeading(sHandle) is False
assert theToken.addRootHeading(C.hSceneDoc) is False
# First Page
assert theToken.addRootHeading("7695ce551d265") is True
assert theToken.addRootHeading(C.hPlotRoot) is True
assert theToken.theMarkdown[-1] == "# Notes: Plot\n\n"
assert theToken._theTokens[-1] == (
Tokenizer.T_TITLE, 0, "Notes: Plot", None, Tokenizer.A_CENTRE
)
# Not First Page
assert theToken.addRootHeading("7695ce551d265") is True
assert theToken.addRootHeading(C.hPlotRoot) is True
assert theToken.theMarkdown[-1] == "# Notes: Plot\n\n"
assert theToken._theTokens[-1] == (
Tokenizer.T_TITLE, 0, "Notes: Plot", None, Tokenizer.A_CENTRE | Tokenizer.A_PBB
@@ -184,18 +183,18 @@ def testCoreToken_TextOps(monkeypatch, nwMinimal, mockGUI):
# Set Text
assert theToken.setText("stuff") is False
assert theToken.setText(sHandle) is True
assert theToken.setText(C.hSceneDoc) is True
assert theToken._theText == docText
with monkeypatch.context() as mp:
mp.setattr("novelwriter.constants.nwConst.MAX_DOCSIZE", 100)
assert theToken.setText(sHandle, docText) is True
assert theToken.setText(C.hSceneDoc, docText) is True
assert theToken._theText == (
"# ERROR\n\n"
"Document 'New Scene' is too big (0.00 MB). Skipping.\n\n"
)
assert theToken.setText(sHandle, docText) is True
assert theToken.setText(C.hSceneDoc, docText) is True
assert theToken._theText == docText
assert theToken._isNone is False
@@ -212,7 +211,7 @@ def testCoreToken_TextOps(monkeypatch, nwMinimal, mockGUI):
assert theToken.theResult == "This is text with escapes: ** ~~ __"
# Save File
savePath = os.path.join(nwMinimal, "dump.nwd")
savePath = os.path.join(fncDir, "dump.nwd")
theToken.saveRawMarkdown(savePath)
assert readFile(savePath) == (
"# Notes: Plot\n\n"
@@ -884,7 +883,7 @@ def testCoreToken_ProcessHeaders(mockGUI):
"""Test the header and page parser of the Tokenizer class.
"""
theProject = NWProject(mockGUI)
theProject.projLang = "en"
theProject.data.setLanguage("en")
theProject._loadProjectLocalisation()
theToken = BareTokenizer(theProject)
+1 -61
View File
@@ -23,8 +23,6 @@ import os
import pytest
import random
from lxml import etree
from tools import readFile
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
@@ -198,7 +196,7 @@ def testCoreTree_BuildTree(mockGUI, mockItems):
assert len(theTree) == len(mockItems) + 1
theList = theTree.handles()
nHandle = "0000000000010"
nHandle = "0000000000000"
assert theList[-1] == nHandle
# Try to add existing handle
@@ -395,64 +393,6 @@ def testCoreTree_Reorder(mockGUI, mockItems):
# END Test testCoreTree_Reorder
@pytest.mark.core
def testCoreTree_XMLPackUnpack(mockGUI, mockItems):
"""Test packing and unpacking the tree to and from XML.
"""
theProject = NWProject(mockGUI)
theTree = NWTree(theProject)
for tHandle, pHandle, nwItem in mockItems:
theTree.append(tHandle, pHandle, nwItem)
theTree.updateItemData(tHandle)
assert len(theTree) == len(mockItems)
nwXML = etree.Element("novelWriterXML")
theTree.packXML(nwXML)
assert etree.tostring(nwXML, pretty_print=False, encoding="utf-8") == (
b'<novelWriterXML>'
b'<content count="8">'
b'<item handle="a000000000001" parent="None" root="a000000000001" order="0" type="ROOT" '
b'class="NOVEL"><meta expanded="True"/><name status="s000000" '
b'import="i000004">Novel</name></item>'
b'<item handle="b000000000001" parent="a000000000001" root="a000000000001" order="0" '
b'type="FOLDER" class="NOVEL"><meta expanded="True"/><name status="s000000" '
b'import="i000004">Act One</name></item>'
b'<item handle="c000000000001" parent="b000000000001" root="a000000000001" order="0" '
b'type="FILE" class="NOVEL" layout="DOCUMENT"><meta expanded="False" mainHeading="H0" '
b'charCount="300" wordCount="50" paraCount="2" cursorPos="0"/><name status="s000000" '
b'import="i000004" active="True">Chapter One</name></item>'
b'<item handle="c000000000002" parent="b000000000001" root="a000000000001" order="0" '
b'type="FILE" class="NOVEL" layout="DOCUMENT"><meta expanded="False" mainHeading="H0" '
b'charCount="3000" wordCount="500" paraCount="20" cursorPos="0"/><name status="s000000" '
b'import="i000004" active="True">Scene One</name></item>'
b'<item handle="a000000000002" parent="None" root="a000000000002" order="0" type="ROOT" '
b'class="ARCHIVE"><meta expanded="False"/><name status="s000000" '
b'import="i000004">Outtakes</name></item>'
b'<item handle="a000000000003" parent="None" root="a000000000003" order="0" type="ROOT" '
b'class="TRASH"><meta expanded="False"/><name status="s000000" '
b'import="i000004">Trash</name></item>'
b'<item handle="a000000000004" parent="None" root="a000000000004" order="0" type="ROOT" '
b'class="CHARACTER"><meta expanded="True"/><name status="s000000" '
b'import="i000004">Characters</name></item>'
b'<item handle="b000000000002" parent="a000000000004" root="a000000000004" order="0" '
b'type="FILE" class="CHARACTER" layout="NOTE"><meta expanded="False" mainHeading="H0" '
b'charCount="2000" wordCount="400" paraCount="16" cursorPos="0"/><name status="s000000" '
b'import="i000004" active="True">Jane Doe</name></item>'
b'</content>'
b'</novelWriterXML>'
)
theTree.clear()
assert len(theTree) == 0
assert not theTree.unpackXML(nwXML)
assert theTree.unpackXML(nwXML[0])
assert len(theTree) == len(mockItems)
# END Test testCoreTree_XMLPackUnpack
@pytest.mark.core
def testCoreTree_ToCFile(monkeypatch, mockGUI, mockItems, tmpDir):
"""Test writing the ToC.txt file.
+1 -1
View File
@@ -55,7 +55,7 @@ def testDlgProjDetails_Dialog(qtbot, nwGUI, nwLipsum):
assert projDet.tabMain.wordCountVal.text() == f"{3000:n}"
assert projDet.tabMain.chapCountVal.text() == f"{3:n}"
assert projDet.tabMain.sceneCountVal.text() == f"{5:n}"
assert projDet.tabMain.revCountVal.text() == f"{nwGUI.theProject.saveCount:n}"
assert projDet.tabMain.revCountVal.text() == f"{nwGUI.theProject.data.saveCount:n}"
assert projDet.tabMain.projPathVal.text() == nwLipsum
+5 -5
View File
@@ -22,7 +22,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import pytest
import os
from tools import getGuiItem
from tools import buildTestProject, getGuiItem
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
@@ -33,10 +33,10 @@ from novelwriter.dialogs.projload import GuiProjectLoad
@pytest.mark.gui
def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, fncProj):
"""Test the load project wizard.
"""
assert nwGUI.openProject(nwMinimal)
buildTestProject(nwGUI, fncProj)
assert nwGUI.closeProject()
monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *a: None)
@@ -87,10 +87,10 @@ def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
nwLoad._doDeleteRecent()
assert nwLoad.listBox.topLevelItemCount() == recentCount - 1
getFile = os.path.join(nwMinimal, "nwProject.nwx")
getFile = os.path.join(fncProj, "nwProject.nwx")
monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *a, **k: (getFile, None))
qtbot.mouseClick(nwLoad.browseButton, Qt.LeftButton)
assert nwLoad.openPath == nwMinimal
assert nwLoad.openPath == fncProj
assert nwLoad.openState == nwLoad.OPEN_STATE
nwLoad.close()
+12 -12
View File
@@ -50,7 +50,7 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI):
# Pretend we have a project
nwGUI.hasProject = True
nwGUI.theProject.setSpellLang("en")
nwGUI.theProject.data.setSpellLang("en")
# Get the dialog object
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
@@ -95,9 +95,9 @@ def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd
# Set some values
theProject = nwGUI.theProject
theProject.setSpellLang("en")
theProject.setBookAuthors("Jane Smith\nJohn Smith")
theProject.setAutoReplace({"A": "B", "C": "D"})
theProject.data.setSpellLang("en")
theProject.data.setAuthors("Jane Smith\nJohn Smith")
theProject.data.setAutoReplace({"A": "B", "C": "D"})
# Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_MAIN)
@@ -136,9 +136,9 @@ def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd
assert projSettings.spellChanged is False
projSettings._doSave()
assert theProject.projName == "Project Name"
assert theProject.bookTitle == "Project Title"
assert theProject.bookAuthors == ["Jane Doe", "John Doh"]
assert theProject.data.name == "Project Name"
assert theProject.data.title == "Project Title"
assert theProject.data.authors == ["Jane Doe", "John Doh"]
# Clean up
projSettings._doClose()
@@ -330,13 +330,13 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncDir, fncProj,
# Check Project
projSettings._doSave()
statusItems = dict(theProject.statusItems.items())
statusItems = dict(theProject.data.itemStatus.items())
assert statusItems[C.sNew]["name"] == "New"
assert statusItems[C.sDraft]["name"] == "Draft"
assert statusItems[C.sFinished]["name"] == "Finished"
assert statusItems["s000013"]["name"] == "Final"
importItems = dict(theProject.importItems.items())
importItems = dict(theProject.data.itemImport.items())
assert importItems[C.iNew]["name"] == "New"
assert importItems[C.iMajor]["name"] == "Major"
assert importItems[C.iMain]["name"] == "Main"
@@ -365,9 +365,9 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mock
# Set some values
theProject = nwGUI.theProject
theProject.autoReplace = {
theProject.data.setAutoReplace({
"A": "B", "C": "D"
}
})
# Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_REPLACE)
@@ -429,7 +429,7 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mock
# Check Project
projSettings._doSave()
assert theProject.autoReplace == {
assert theProject.data.autoReplace == {
"A": "B", "C": "D", "This": "With This Stuff"
}
+6 -4
View File
@@ -25,7 +25,7 @@ import pytest
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QDialog, QAction
from tools import writeFile, readFile, getGuiItem
from tools import buildTestProject, writeFile, readFile, getGuiItem
from mock import causeOSError
from novelwriter.constants import nwFiles
@@ -33,16 +33,18 @@ from novelwriter.dialogs.wordlist import GuiWordList
@pytest.mark.gui
def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal):
def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, fncProj):
"""test the word list editor.
"""
buildTestProject(nwGUI, fncProj)
monkeypatch.setattr(GuiWordList, "exec_", lambda *a: None)
monkeypatch.setattr(GuiWordList, "result", lambda *a: QDialog.Accepted)
monkeypatch.setattr(GuiWordList, "accept", lambda *a: None)
# Open project
nwGUI.openProject(nwMinimal)
dictFile = os.path.join(nwMinimal, "meta", nwFiles.PROJ_DICT)
nwGUI.openProject(fncProj)
dictFile = os.path.join(fncProj, "meta", nwFiles.PROJ_DICT)
# Load the dialog
nwGUI.mainMenu.aEditWordList.activate(QAction.Trigger)
+52 -67
View File
@@ -22,6 +22,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import pytest
from mock import causeOSError
from tools import C, buildTestProject
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QTextBlock, QTextCursor, QTextOption
@@ -36,12 +37,12 @@ KEY_DELAY = 1
@pytest.mark.gui
def testGuiEditor_Init(qtbot, nwGUI, nwMinimal, ipsumText):
def testGuiEditor_Init(qtbot, nwGUI, fncProj, ipsumText, mockRnd):
"""Test initialising the editor.
"""
# Open project
assert nwGUI.openProject(nwMinimal)
assert nwGUI.openDocument("8c659a11cd429")
buildTestProject(nwGUI, fncProj)
assert nwGUI.openDocument(C.hSceneDoc)
nwGUI.docEditor.setText("### Lorem Ipsum\n\n%s" % ipsumText[0])
assert nwGUI.saveDocument()
@@ -79,13 +80,11 @@ def testGuiEditor_Init(qtbot, nwGUI, nwMinimal, ipsumText):
@pytest.mark.gui
def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumText):
def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, fncProj, ipsumText, mockRnd):
"""Test loading text into the editor.
"""
# Open project
sHandle = "8c659a11cd429"
assert nwGUI.openProject(nwMinimal) is True
assert nwGUI.openDocument(sHandle) is True
buildTestProject(nwGUI, fncProj)
assert nwGUI.openDocument(C.hSceneDoc) is True
longText = "### Lorem Ipsum\n\n%s" % "\n\n".join(ipsumText*20)
nwGUI.docEditor.replaceText(longText)
@@ -101,11 +100,11 @@ def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe
# Document too big
with monkeypatch.context() as mp:
mp.setattr("novelwriter.constants.nwConst.MAX_DOCSIZE", 100)
assert nwGUI.docEditor.loadText(sHandle) is False
assert nwGUI.docEditor.loadText(C.hSceneDoc) is False
assert "The document you are trying to open is too big." in caplog.text
# Regular open
assert nwGUI.docEditor.loadText(sHandle) is True
assert nwGUI.docEditor.loadText(C.hSceneDoc) is True
assert nwGUI.docEditor._bigDoc is False
# Reload too big text
@@ -116,18 +115,18 @@ def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe
# Big doc handling
nwGUI.mainConf.bigDocLimit = 50
assert nwGUI.docEditor.loadText(sHandle) is True
assert nwGUI.docEditor.loadText(C.hSceneDoc) is True
assert nwGUI.docEditor._bigDoc is True
# Regular open, with line number
assert nwGUI.docEditor.loadText(sHandle, tLine=4) is True
assert nwGUI.docEditor.loadText(C.hSceneDoc, tLine=4) is True
cursPos = nwGUI.docEditor.getCursorPosition()
assert nwGUI.docEditor.document().findBlock(cursPos).blockNumber() == 4
# Load empty document
nwGUI.docEditor.replaceText("")
assert nwGUI.saveDocument() is True
assert nwGUI.docEditor.loadText(sHandle) is True
assert nwGUI.docEditor.loadText(C.hSceneDoc) is True
assert nwGUI.docEditor.toPlainText() == ""
# qtbot.stop()
@@ -136,13 +135,11 @@ def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe
@pytest.mark.gui
def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumText):
def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, fncProj, ipsumText, mockRnd):
"""Test saving text from the editor.
"""
# Open project
sHandle = "8c659a11cd429"
assert nwGUI.openProject(nwMinimal) is True
assert nwGUI.openDocument(sHandle) is True
buildTestProject(nwGUI, fncProj)
assert nwGUI.openDocument(C.hSceneDoc) is True
# Save Text
# =========
@@ -159,7 +156,7 @@ def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe
# Unkown handle
nwGUI.docEditor._docHandle = "0123456789abcdef"
assert nwGUI.docEditor.saveText() is False
nwGUI.docEditor._docHandle = sHandle
nwGUI.docEditor._docHandle = C.hSceneDoc
# Cause error when saving
with monkeypatch.context() as mp:
@@ -168,10 +165,10 @@ def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe
assert "Could not save document." in caplog.text
# Change header level
assert nwGUI.theProject.tree[sHandle].itemLayout == nwItemLayout.DOCUMENT
assert nwGUI.theProject.tree[C.hSceneDoc].itemLayout == nwItemLayout.DOCUMENT
nwGUI.docEditor.replaceText(longText[1:])
assert nwGUI.docEditor.saveText() is True
assert nwGUI.theProject.tree[sHandle].itemLayout == nwItemLayout.DOCUMENT
assert nwGUI.theProject.tree[C.hSceneDoc].itemLayout == nwItemLayout.DOCUMENT
# Regular save
assert nwGUI.docEditor.saveText() is True
@@ -182,13 +179,11 @@ def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe
@pytest.mark.gui
def testGuiEditor_MetaData(qtbot, nwGUI, nwMinimal):
def testGuiEditor_MetaData(qtbot, nwGUI, fncProj, mockRnd):
"""Test extracting various meta data and other values.
"""
# Open project
sHandle = "8c659a11cd429"
assert nwGUI.openProject(nwMinimal) is True
assert nwGUI.openDocument(sHandle) is True
buildTestProject(nwGUI, fncProj)
assert nwGUI.openDocument(C.hSceneDoc) is True
# Get Text
# This should replace line and paragraph separators, but preserve
@@ -203,7 +198,7 @@ def testGuiEditor_MetaData(qtbot, nwGUI, nwMinimal):
# Check Propertoes
assert nwGUI.docEditor.docChanged() is True
assert nwGUI.docEditor.docHandle() == sHandle
assert nwGUI.docEditor.docHandle() == C.hSceneDoc
assert nwGUI.docEditor.lastActive() > 0.0
assert nwGUI.docEditor.isEmpty() is False
@@ -211,9 +206,9 @@ def testGuiEditor_MetaData(qtbot, nwGUI, nwMinimal):
assert nwGUI.docEditor.setCursorPosition(None) is False
assert nwGUI.docEditor.setCursorPosition(10) is True
assert nwGUI.docEditor.getCursorPosition() == 10
assert nwGUI.theProject.tree[sHandle].cursorPos != 10
assert nwGUI.theProject.tree[C.hSceneDoc].cursorPos != 10
nwGUI.docEditor.saveCursorPosition()
assert nwGUI.theProject.tree[sHandle].cursorPos == 10
assert nwGUI.theProject.tree[C.hSceneDoc].cursorPos == 10
assert nwGUI.docEditor.setCursorLine(None) is False
assert nwGUI.docEditor.setCursorLine(2) is True
@@ -231,16 +226,14 @@ def testGuiEditor_MetaData(qtbot, nwGUI, nwMinimal):
@pytest.mark.gui
def testGuiEditor_Actions(qtbot, nwGUI, nwMinimal, ipsumText):
def testGuiEditor_Actions(qtbot, nwGUI, fncProj, ipsumText, mockRnd):
"""Test the document actions. This is not an extensive test of the
action features, just that the actions are actually called. The
various action features are tested when their respective functions
are tested.
"""
# Open project
sHandle = "8c659a11cd429"
assert nwGUI.openProject(nwMinimal) is True
assert nwGUI.openDocument(sHandle) is True
buildTestProject(nwGUI, fncProj)
assert nwGUI.openDocument(C.hSceneDoc) is True
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
assert nwGUI.docEditor.replaceText(theText) is True
@@ -452,7 +445,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, nwMinimal, ipsumText):
# No Document Handle
nwGUI.docEditor._docHandle = None
assert nwGUI.docEditor.docAction(nwDocAction.BLOCK_TXT) is False
nwGUI.docEditor._docHandle = sHandle
nwGUI.docEditor._docHandle = C.hSceneDoc
# Wrong Action Type
assert nwGUI.docEditor.docAction(None) is False
@@ -466,13 +459,11 @@ def testGuiEditor_Actions(qtbot, nwGUI, nwMinimal, ipsumText):
@pytest.mark.gui
def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, fncProj, ipsumText, mockRnd):
"""Test the document insert functions.
"""
# Open project
sHandle = "8c659a11cd429"
assert nwGUI.openProject(nwMinimal) is True
assert nwGUI.openDocument(sHandle) is True
buildTestProject(nwGUI, fncProj)
assert nwGUI.openDocument(C.hSceneDoc) is True
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
assert nwGUI.docEditor.replaceText(theText) is True
@@ -487,7 +478,7 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
nwGUI.docEditor._docHandle = None
assert nwGUI.docEditor.setCursorPosition(24) is True
assert nwGUI.docEditor.insertText("Stuff") is False
nwGUI.docEditor._docHandle = sHandle
nwGUI.docEditor._docHandle = C.hSceneDoc
# Insert String
assert nwGUI.docEditor.setCursorPosition(24) is True
@@ -551,13 +542,11 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
@pytest.mark.gui
def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, fncProj, ipsumText, mockRnd):
"""Test the text manipulation functions.
"""
# Open project
sHandle = "8c659a11cd429"
assert nwGUI.openProject(nwMinimal) is True
assert nwGUI.openDocument(sHandle) is True
buildTestProject(nwGUI, fncProj)
assert nwGUI.openDocument(C.hSceneDoc) is True
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
assert nwGUI.docEditor.replaceText(theText) is True
@@ -760,13 +749,11 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumTe
@pytest.mark.gui
def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, fncProj, ipsumText, mockRnd):
"""Test the block formatting function.
"""
# Open project
sHandle = "8c659a11cd429"
assert nwGUI.openProject(nwMinimal) is True
assert nwGUI.openDocument(sHandle) is True
buildTestProject(nwGUI, fncProj)
assert nwGUI.openDocument(C.hSceneDoc) is True
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
assert nwGUI.docEditor.replaceText(theText) is True
@@ -1075,13 +1062,11 @@ def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumTex
@pytest.mark.gui
def testGuiEditor_Tags(qtbot, nwGUI, nwMinimal, ipsumText):
def testGuiEditor_Tags(qtbot, nwGUI, fncProj, ipsumText, mockRnd):
"""Test the document editor tags functionality.
"""
# Open project
sHandle = "8c659a11cd429"
assert nwGUI.openProject(nwMinimal) is True
assert nwGUI.openDocument(sHandle) is True
buildTestProject(nwGUI, fncProj)
assert nwGUI.openDocument(C.hSceneDoc) is True
# Create Scene
theText = "### A Scene\n\n@char: Jane, John\n\n" + ipsumText[0] + "\n\n"
@@ -1089,7 +1074,7 @@ def testGuiEditor_Tags(qtbot, nwGUI, nwMinimal, ipsumText):
# Create Character
theText = "### Jane Doe\n\n@tag: Jane\n\n" + ipsumText[1] + "\n\n"
cHandle = nwGUI.theProject.newFile("Jane Doe", "afb3043c7b2b3")
cHandle = nwGUI.theProject.newFile("Jane Doe", C.hCharRoot)
assert nwGUI.openDocument(cHandle) is True
assert nwGUI.docEditor.replaceText(theText) is True
assert nwGUI.saveDocument() is True
@@ -1098,7 +1083,7 @@ def testGuiEditor_Tags(qtbot, nwGUI, nwMinimal, ipsumText):
# Follow Tag
# ==========
assert nwGUI.openDocument(sHandle) is True
assert nwGUI.openDocument(C.hSceneDoc) is True
# Empty Block
assert nwGUI.docEditor.setCursorLine(1) is True
@@ -1136,7 +1121,7 @@ def testGuiEditor_Tags(qtbot, nwGUI, nwMinimal, ipsumText):
@pytest.mark.gui
def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, fncProj, ipsumText, mockRnd):
"""Test saving text from the editor.
"""
class MockThreadPool:
@@ -1153,7 +1138,8 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
nwGUI.threadPool = MockThreadPool()
nwGUI.docEditor.wcTimerDoc.blockSignals(True)
nwGUI.docEditor.wcTimerSel.blockSignals(True)
assert nwGUI.openProject(nwMinimal) is True
buildTestProject(nwGUI, fncProj)
# Run on an empty document
nwGUI.docEditor._runDocCounter()
@@ -1167,10 +1153,9 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
assert nwGUI.docEditor.docFooter.wordsText.text() == "Words: 0 (+0)"
# Open a document and populate it
sHandle = "8c659a11cd429"
nwGUI.theProject.tree[sHandle]._initCount = 0 # Clear item's count
nwGUI.theProject.tree[sHandle]._wordCount = 0 # Clear item's count
assert nwGUI.openDocument(sHandle) is True
nwGUI.theProject.tree[C.hSceneDoc]._initCount = 0 # Clear item's count
nwGUI.theProject.tree[C.hSceneDoc]._wordCount = 0 # Clear item's count
assert nwGUI.openDocument(C.hSceneDoc) is True
theText = "\n\n".join(ipsumText)
cC, wC, pC = countWords(theText)
@@ -1193,9 +1178,9 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
nwGUI.docEditor.wCounterDoc.run()
# nwGUI.docEditor._updateDocCounts(cC, wC, pC)
assert nwGUI.theProject.tree[sHandle]._charCount == cC
assert nwGUI.theProject.tree[sHandle]._wordCount == wC
assert nwGUI.theProject.tree[sHandle]._paraCount == pC
assert nwGUI.theProject.tree[C.hSceneDoc]._charCount == cC
assert nwGUI.theProject.tree[C.hSceneDoc]._wordCount == wC
assert nwGUI.theProject.tree[C.hSceneDoc]._paraCount == pC
assert nwGUI.docEditor.docFooter.wordsText.text() == f"Words: {wC} (+{wC})"
# Select all text
+20 -19
View File
@@ -172,10 +172,10 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
assert nwGUI.theProject.tree.trashRoot() is None
assert nwGUI.theProject.projPath is None
assert nwGUI.theProject.projMeta is None
assert nwGUI.theProject.projName == ""
assert nwGUI.theProject.bookTitle == ""
assert len(nwGUI.theProject.bookAuthors) == 0
assert not nwGUI.theProject.spellCheck
assert nwGUI.theProject.data.name == ""
assert nwGUI.theProject.data.title == ""
assert nwGUI.theProject.data.authors == []
assert nwGUI.theProject.data.spellCheck is False
# Check the files
projFile = os.path.join(fncProj, "nwProject.nwx")
@@ -194,10 +194,10 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
assert nwGUI.theProject.tree.trashRoot() is None
assert nwGUI.theProject.projPath == fncProj
assert nwGUI.theProject.projMeta == os.path.join(fncProj, "meta")
assert nwGUI.theProject.projName == "New Project"
assert nwGUI.theProject.bookTitle == "New Novel"
assert len(nwGUI.theProject.bookAuthors) == 1
assert nwGUI.theProject.spellCheck is False
assert nwGUI.theProject.data.name == "New Project"
assert nwGUI.theProject.data.title == "New Novel"
assert nwGUI.theProject.data.authors == ["Jane Doe"]
assert nwGUI.theProject.data.spellCheck is False
# Check that tree items have been created
assert nwGUI.projView.projTree._getTreeItem(C.hNovelRoot) is not None
@@ -489,11 +489,12 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
# Check a Quick Create and Delete
assert nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None)
newHandle = nwGUI.projView.getSelectedHandle()
assert nwGUI.theProject.tree["0000000000020"] is not None
assert newHandle == "0000000000013"
assert nwGUI.theProject.tree[newHandle] is not None
assert nwGUI.projView.requestDeleteItem()
assert nwGUI.projView.setSelectedHandle(newHandle)
assert nwGUI.projView.requestDeleteItem()
assert nwGUI.theProject.tree["0000000000024"] is not None # Trash
assert nwGUI.theProject.tree["0000000000014"] is not None # Trash
assert nwGUI.saveProject()
# Check the files
@@ -509,21 +510,21 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile)
projFile = os.path.join(fncProj, "content", "0000000000020.nwd")
testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000020.nwd")
compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000020.nwd")
projFile = os.path.join(fncProj, "content", "0000000000010.nwd")
testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000010.nwd")
compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000010.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile)
projFile = os.path.join(fncProj, "content", "0000000000021.nwd")
testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000021.nwd")
compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000021.nwd")
projFile = os.path.join(fncProj, "content", "0000000000011.nwd")
testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000011.nwd")
compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000011.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile)
projFile = os.path.join(fncProj, "content", "0000000000022.nwd")
testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000022.nwd")
compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000022.nwd")
projFile = os.path.join(fncProj, "content", "0000000000012.nwd")
testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000012.nwd")
compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000012.nwd")
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile)
+4 -4
View File
@@ -23,7 +23,7 @@ import os
import sys
import pytest
from tools import getGuiItem
from tools import buildTestProject, getGuiItem
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QFileDialog, QWizard, QDialog
@@ -37,7 +37,7 @@ from novelwriter.tools.projwizard import (
@pytest.mark.gui
@pytest.mark.skipif(sys.platform.startswith("darwin"), reason="Not running on Darwin")
def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, nwMinimal):
def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, fncProj):
"""Test the launch of the project wizard.
Disabled for macOS because the test segfaults on QWizard.show()
"""
@@ -45,7 +45,7 @@ def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, nwMinimal):
# ========================
# New with a project open should cause an error
assert nwGUI.openProject(nwMinimal)
buildTestProject(nwGUI, fncProj)
with monkeypatch.context() as mp:
mp.setattr(nwGUI, "closeProject", lambda *a: False)
assert nwGUI.newProject() is False
@@ -61,7 +61,7 @@ def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, nwMinimal):
assert nwGUI.newProject() is False
# Now, with a non-empty folder
mp.setattr(nwGUI, "showNewProjectDialog", lambda *a: {"projPath": nwMinimal})
mp.setattr(nwGUI, "showNewProjectDialog", lambda *a: {"projPath": fncProj})
assert nwGUI.newProject() is False
# Test the Wizard Launching
+14 -4
View File
@@ -167,9 +167,19 @@ def buildTestProject(theObject, projPath):
theProject.clearProject()
theProject.setProjectPath(projPath, newProject=True)
theProject.setProjectName("New Project")
theProject.setBookTitle("New Novel")
theProject.setBookAuthors("Jane Doe")
theProject.data.itemStatus.write(None, "New", (100, 100, 100))
theProject.data.itemStatus.write(None, "Note", (200, 50, 0))
theProject.data.itemStatus.write(None, "Draft", (200, 150, 0))
theProject.data.itemStatus.write(None, "Finished", (50, 200, 0))
theProject.data.itemImport.write(None, "New", (100, 100, 100))
theProject.data.itemImport.write(None, "Minor", (200, 50, 0))
theProject.data.itemImport.write(None, "Major", (200, 150, 0))
theProject.data.itemImport.write(None, "Main", (50, 200, 0))
theProject.data.setName("New Project")
theProject.data.setTitle("New Novel")
theProject.data.setAuthors("Jane Doe")
# Creating a minimal project with a few root folders and a
# single chapter folder with a single file.
@@ -195,7 +205,7 @@ def buildTestProject(theObject, projPath):
aDoc.writeDocument("### %s\n\n" % theProject.tr("New Scene"))
theProject.index.reIndexHandle(xHandle[8])
theProject.projOpened = time.time()
theProject._projOpened = time.time()
theProject.setProjectChanged(True)
theProject.saveProject(autoSave=True)