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 return
def __repr__(self): def __repr__(self):
return f"<NWIndex project='{self.theProject.projName}'>" return f"<NWIndex project='{self.theProject.data.name}'>"
## ##
# Properties # Properties
+82 -122
View File
@@ -25,8 +25,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging import logging
from lxml import etree
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
from novelwriter.common import ( from novelwriter.common import (
checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified
@@ -148,107 +146,70 @@ class NWItem:
return self._cursorPos return self._cursorPos
## ##
# XML Pack/Unpack # Pack/Unpack Data
## ##
def packXML(self, xParent): def pack(self):
"""Pack all the data in the class instance into an XML object. """Pack all the data in the class instance into a dictionary.
""" """
itemAttrib = {} item = {}
itemAttrib["handle"] = str(self._handle) meta = {}
itemAttrib["parent"] = str(self._parent) name = {}
itemAttrib["root"] = str(self._root)
itemAttrib["order"] = str(self._order) item["handle"] = str(self._handle)
itemAttrib["type"] = str(self._type.name) item["parent"] = str(self._parent)
itemAttrib["class"] = str(self._class.name) 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: 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 = {} data = {
metaAttrib["expanded"] = str(self._expanded) "name": str(self._name),
if self._type == nwItemType.FILE: "itemAttr": item,
metaAttrib["mainHeading"] = str(self._heading) "metaAttr": meta,
metaAttrib["charCount"] = str(self._charCount) "nameAttr": name,
metaAttrib["wordCount"] = str(self._wordCount) }
metaAttrib["paraCount"] = str(self._paraCount)
metaAttrib["cursorPos"] = str(self._cursorPos)
nameAttrib = {} return data
nameAttrib["status"] = str(self._status)
nameAttrib["import"] = str(self._import)
if self._type == nwItemType.FILE:
nameAttrib["active"] = str(self._active)
xPack = etree.SubElement(xParent, "item", attrib=itemAttrib) def unpack(self, data):
self._subPack(xPack, "meta", attrib=metaAttrib) """Set the values from a data dictionary.
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'.
""" """
if xItem.tag != "item": if "handle" in data:
logger.error("XML entry is not an NWItem") self.setHandle(data["handle"])
return False
if "handle" in xItem.attrib:
self.setHandle(xItem.attrib["handle"])
else: else:
logger.error("XML item entry does not have a handle") logger.error("Item does not have a handle")
return False return False
self.setParent(xItem.attrib.get("parent", None)) self.setName(data.get("label", ""))
self.setRoot(xItem.attrib.get("root", None)) self.setParent(data.get("parent", None))
self.setOrder(xItem.attrib.get("order", 0)) self.setRoot(data.get("root", None))
self.setType(xItem.attrib.get("type", nwItemType.NO_TYPE)) self.setOrder(data.get("order", 0))
self.setClass(xItem.attrib.get("class", nwItemClass.NO_CLASS)) self.setType(data.get("type", nwItemType.NO_TYPE))
self.setLayout(xItem.attrib.get("layout", nwItemLayout.NO_LAYOUT)) self.setClass(data.get("class", nwItemClass.NO_CLASS))
self.setLayout(data.get("layout", nwItemLayout.NO_LAYOUT))
for xValue in xItem: self.setExpanded(data.get("expanded", False))
if xValue.tag == "meta": self.setStatus(data.get("status", None))
self.setExpanded(xValue.attrib.get("expanded", False)) self.setImport(data.get("import", None))
self.setMainHeading(xValue.attrib.get("mainHeading", "H0")) self.setMainHeading(data.get("heading", "H0"))
self.setCharCount(xValue.attrib.get("charCount", 0)) self.setCharCount(data.get("charCount", 0))
self.setWordCount(xValue.attrib.get("wordCount", 0)) self.setWordCount(data.get("wordCount", 0))
self.setParaCount(xValue.attrib.get("paraCount", 0)) self.setParaCount(data.get("paraCount", 0))
self.setCursorPos(xValue.attrib.get("cursorPos", 0)) self.setCursorPos(data.get("cursorPos", 0))
elif xValue.tag == "name": self.setActive(data.get("active", True))
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)
# Make some checks to ensure consistency # Make some checks to ensure consistency
if self._type == nwItemType.ROOT: if self._type == nwItemType.ROOT:
@@ -256,26 +217,17 @@ class NWItem:
self._parent = None # Root items cannot have a parent self._parent = None # Root items cannot have a parent
if self._type != nwItemType.FILE: if self._type != nwItemType.FILE:
self._charCount = 0 # Only set for files # Reset values that should only be set for files
self._wordCount = 0 # Only set for files self._layout = nwItemLayout.NO_LAYOUT
self._paraCount = 0 # Only set for files self._heading = "H0"
self._cursorPos = 0 # Only set for files self._active = False
self._charCount = 0
self._wordCount = 0
self._paraCount = 0
self._cursorPos = 0
return True 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 # Lookup Methods
## ##
@@ -310,11 +262,11 @@ class NWItem:
the current item based on its class. the current item based on its class.
""" """
if self.isNovelLike(): if self.isNovelLike():
stName = self.theProject.statusItems.name(self._status) stName = self.theProject.data.itemStatus.name(self._status)
stIcon = self.theProject.statusItems.icon(self._status) if incIcon else None stIcon = self.theProject.data.itemStatus.icon(self._status) if incIcon else None
else: else:
stName = self.theProject.importItems.name(self._import) stName = self.theProject.data.itemImport.name(self._import)
stIcon = self.theProject.importItems.icon(self._import) if incIcon else None stIcon = self.theProject.data.itemImport.icon(self._import) if incIcon else None
return stName, stIcon return stName, stIcon
## ##
@@ -451,8 +403,6 @@ class NWItem:
self._type = value self._type = value
elif isItemType(value): elif isItemType(value):
self._type = nwItemType[value] self._type = nwItemType[value]
elif value == "TRASH":
self._type = nwItemType.ROOT
else: else:
logger.error("Unrecognised item type '%s'", value) logger.error("Unrecognised item type '%s'", value)
self._type = nwItemType.NO_TYPE self._type = nwItemType.NO_TYPE
@@ -479,8 +429,6 @@ class NWItem:
self._layout = value self._layout = value
elif isItemLayout(value): elif isItemLayout(value):
self._layout = nwItemLayout[value] self._layout = nwItemLayout[value]
elif value in ("TITLE", "PAGE", "BOOK", "PARTITION", "UNNUMBERED", "CHAPTER", "SCENE"):
self._layout = nwItemLayout.DOCUMENT
else: else:
logger.error("Unrecognised item layout '%s'", value) logger.error("Unrecognised item layout '%s'", value)
self._layout = nwItemLayout.NO_LAYOUT self._layout = nwItemLayout.NO_LAYOUT
@@ -490,14 +438,14 @@ class NWItem:
"""Set the item status by looking it up in the valid status """Set the item status by looking it up in the valid status
items of the current project. items of the current project.
""" """
self._status = self.theProject.statusItems.check(value) self._status = self.theProject.data.itemStatus.check(value)
return return
def setImport(self, value): def setImport(self, value):
"""Set the item importance by looking it up in the valid import """Set the item importance by looking it up in the valid import
items of the current project. items of the current project.
""" """
self._import = self.theProject.importItems.check(value) self._import = self.theProject.data.itemImport.check(value)
return return
def setActive(self, state): def setActive(self, state):
@@ -532,25 +480,37 @@ class NWItem:
def setCharCount(self, count): def setCharCount(self, count):
"""Set the character count, and ensure that it is an integer. """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 return
def setWordCount(self, count): def setWordCount(self, count):
"""Set the word count, and ensure that it is an integer. """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 return
def setParaCount(self, count): def setParaCount(self, count):
"""Set the paragraph count, and ensure that it is an integer. """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 return
def setCursorPos(self, position): def setCursorPos(self, position):
"""Set the cursor position, and ensure that it is an integer. """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 return
def saveInitialCount(self): 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 logging
import novelwriter import novelwriter
from lxml import etree
from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor
from PyQt5.QtCore import QRectF, Qt from PyQt5.QtCore import QRectF, Qt
from novelwriter.common import checkInt, minmax, simplified from novelwriter.common import minmax, simplified
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -47,7 +45,6 @@ class NWStatus:
self._type = type self._type = type
self._store = {} self._store = {}
self._reverse = {}
self._default = None self._default = None
self._iPX = novelwriter.CONFIG.pxInt(24) self._iPX = novelwriter.CONFIG.pxInt(24)
@@ -58,7 +55,7 @@ class NWStatus:
self._iconPath = QPainterPath() self._iconPath = QPainterPath()
self._iconPath.addRoundedRect(QRectF(pA, pA, pB, pB), pR, pR) 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: if self._type == self.STATUS:
self._prefix = "s" self._prefix = "s"
@@ -80,17 +77,19 @@ class NWStatus:
if len(col) != 3: if len(col) != 3:
col = (100, 100, 100) 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) name = simplified(name)
if count is None: 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] = { self._store[key] = {
"name": name, "name": name,
"icon": self._createIcon(col), "icon": self._createIcon(cR, cG, cB),
"cols": col, "cols": (cR, cG, cB),
"count": count, "count": count,
} }
self._reverse[name] = key
if self._default is None: if self._default is None:
self._default = key self._default = key
@@ -106,7 +105,6 @@ class NWStatus:
if self._store[key]["count"] > 0: if self._store[key]["count"] > 0:
return False return False
del self._reverse[self._store[key]["name"]]
del self._store[key] del self._store[key]
keys = list(self._store.keys()) keys = list(self._store.keys())
@@ -123,8 +121,6 @@ class NWStatus:
""" """
if self._isKey(value) and value in self._store: if self._isKey(value) and value in self._store:
return value return value
elif value in self._reverse:
return self._reverse[value]
elif self._default is not None: elif self._default is not None:
return self._default return self._default
else: else:
@@ -206,37 +202,30 @@ class NWStatus:
self._store[key]["count"] += 1 self._store[key]["count"] += 1
return return
def packXML(self, xParent): def pack(self):
"""Pack the status entries into an XML object for saving to the """Pack the status entries into a dictionary.
main project file.
""" """
for key, data in self._store.items(): for key, data in self._store.items():
xSub = etree.SubElement(xParent, "entry", attrib={ yield (data["name"], {
"key": key, "key": key,
"count": str(data["count"]), "count": str(data["count"]),
"red": str(data["cols"][0]), "red": str(data["cols"][0]),
"green": str(data["cols"][1]), "green": str(data["cols"][1]),
"blue": str(data["cols"][2]), "blue": str(data["cols"][2]),
}) })
xSub.text = data["name"] return
return True def unpack(self, data):
"""Unpack a data dictionary and set the class values.
def unpackXML(self, xParent):
"""Unpack an XML tree and set the class values.
""" """
self._store = {} self._store = {}
self._reverse = {}
self._default = None self._default = None
for xChild in xParent: for key, entry in data.items():
key = xChild.attrib.get("key", None) label = entry.get("label", "")
name = xChild.text.strip() colour = entry.get("colour", (100, 100, 100))
count = max(checkInt(xChild.attrib.get("count", 0), 0), 0) count = entry.get("count", 0)
red = minmax(checkInt(xChild.attrib.get("red", 100), 100), 0, 255) self.write(key, label, colour, count)
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)
return True return True
@@ -270,7 +259,7 @@ class NWStatus:
return False return False
return True return True
def _createIcon(self, col): def _createIcon(self, red, green, blue):
"""Generate an icon for a status label. """Generate an icon for a status label.
""" """
pixmap = QPixmap(self._iPX, self._iPX) pixmap = QPixmap(self._iPX, self._iPX)
@@ -278,7 +267,7 @@ class NWStatus:
painter = QPainter(pixmap) painter = QPainter(pixmap)
painter.setRenderHint(QPainter.Antialiasing) painter.setRenderHint(QPainter.Antialiasing)
painter.fillPath(self._iconPath, QColor(*col)) painter.fillPath(self._iconPath, QColor(red, green, blue))
painter.end() painter.end()
return QIcon(pixmap) return QIcon(pixmap)
+1 -1
View File
@@ -315,7 +315,7 @@ class ToHtml(Tokenizer):
"</body>\n" "</body>\n"
"</html>\n" "</html>\n"
).format( ).format(
projTitle=self.theProject.projName, projTitle=self.theProject.data.name,
htmlStyle="\n".join(theStyle), htmlStyle="\n".join(theStyle),
bodyText=bodyText, bodyText=bodyText,
) )
+3 -2
View File
@@ -327,9 +327,10 @@ class Tokenizer(ABC):
"""Run trough the various replace doctionaries. """Run trough the various replace doctionaries.
""" """
# Process the user's auto-replace dictionary # Process the user's auto-replace dictionary
if len(self.theProject.autoReplace) > 0: autoReplace = self.theProject.data.autoReplace
if len(autoReplace) > 0:
repDict = {} repDict = {}
for aKey, aVal in self.theProject.autoReplace.items(): for aKey, aVal in autoReplace.items():
repDict[f"<{aKey}>"] = aVal repDict[f"<{aKey}>"] = aVal
xRep = re.compile("|".join([re.escape(k) for k in repDict.keys()]), flags=re.DOTALL) 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) 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 == "": if self._headerText == "":
theTitle = self.theProject.bookTitle theTitle = self.theProject.data.title
theAuth = self.theProject.getAuthors() theAuth = self.theProject.getFormattedAuthors()
self._headerText = f"{theTitle} / {theAuth} /" self._headerText = f"{theTitle} / {theAuth} /"
# Create Roots # Create Roots
+10 -17
View File
@@ -27,8 +27,6 @@ import os
import random import random
import logging import logging
from lxml import etree
from novelwriter.enum import nwItemClass, nwItemLayout from novelwriter.enum import nwItemClass, nwItemLayout
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.common import checkHandle from novelwriter.common import checkHandle
@@ -114,30 +112,25 @@ class NWTree:
return True return True
def packXML(self, xParent): def pack(self):
"""Pack the content of the tree into the provided XML object. In """Pack the content of the tree into the provided XML object. In
the order defined by the _treeOrder list. the order defined by the _treeOrder list.
""" """
xContent = etree.SubElement(xParent, "content", attrib={ tree = []
"count": str(len(self._treeOrder))}
)
for tHandle in self._treeOrder: for tHandle in self._treeOrder:
tItem = self.__getitem__(tHandle) tItem = self.__getitem__(tHandle)
tItem.packXML(xContent) if tItem:
return tree.append(tItem.pack())
return tree
def unpackXML(self, xContent): def unpack(self, data):
"""Iterate through all items of a content XML object and add """Iterate through all items of a list and add them to the
them to the project tree. project tree.
""" """
if xContent.tag != "content":
logger.error("XML entry is not a NWTree")
return False
self.clear() self.clear()
for xItem in xContent: for item in data:
nwItem = NWItem(self.theProject) nwItem = NWItem(self.theProject)
if nwItem.unpackXML(xItem): if nwItem.unpack(item):
self.append(nwItem.itemHandle, nwItem.itemParent, nwItem) self.append(nwItem.itemHandle, nwItem.itemParent, nwItem)
nwItem.saveInitialCount() nwItem.saveInitialCount()
+6 -4
View File
@@ -157,7 +157,7 @@ class GuiProjectDetailsMain(QWidget):
# Header # Header
# ====== # ======
self.bookTitle = QLabel(self.theProject.bookTitle) self.bookTitle = QLabel(self.theProject.data.title)
bookFont = self.bookTitle.font() bookFont = self.bookTitle.font()
bookFont.setPointSizeF(2.2*fPt) bookFont.setPointSizeF(2.2*fPt)
bookFont.setWeight(QFont.Bold) bookFont.setWeight(QFont.Bold)
@@ -166,7 +166,7 @@ class GuiProjectDetailsMain(QWidget):
self.bookTitle.setWordWrap(True) self.bookTitle.setWordWrap(True)
self.projName = QLabel( 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 = self.projName.font()
workFont.setPointSizeF(0.8*fPt) workFont.setPointSizeF(0.8*fPt)
@@ -175,7 +175,9 @@ class GuiProjectDetailsMain(QWidget):
self.projName.setAlignment(Qt.AlignHCenter) self.projName.setAlignment(Qt.AlignHCenter)
self.projName.setWordWrap(True) 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 = self.bookAuthors.font()
authFont.setPointSizeF(1.2*fPt) authFont.setPointSizeF(1.2*fPt)
self.bookAuthors.setFont(authFont) self.bookAuthors.setFont(authFont)
@@ -255,7 +257,7 @@ class GuiProjectDetailsMain(QWidget):
self.wordCountVal.setText(f"{nwCount:n}") self.wordCountVal.setText(f"{nwCount:n}")
self.chapCountVal.setText(f"{hCounts[2]:n}") self.chapCountVal.setText(f"{hCounts[2]:n}")
self.sceneCountVal.setText(f"{hCounts[3]: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.editTimeVal.setText(f"{edTime//3600:02d}:{edTime%3600//60:02d}")
self.projPathVal.setText(self.theProject.projPath) self.projPathVal.setText(self.theProject.projPath)
+15 -15
View File
@@ -114,13 +114,13 @@ class GuiProjectSettings(PagedDialog):
spellLang = self.tabMain.spellLang.currentData() spellLang = self.tabMain.spellLang.currentData()
doBackup = not self.tabMain.doBackup.isChecked() doBackup = not self.tabMain.doBackup.isChecked()
self.theProject.setProjectName(projName) self.theProject.data.setName(projName)
self.theProject.setBookTitle(bookTitle) self.theProject.data.setTitle(bookTitle)
self.theProject.setBookAuthors(bookAuthors) self.theProject.data.setAuthors(bookAuthors)
self.theProject.setProjBackup(doBackup) self.theProject.data.setDoBackup(doBackup)
# Remember this as updating spell dictionary can be expensive # 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: if self.tabStatus.colChanged:
newList, delList = self.tabStatus.getNewList() newList, delList = self.tabStatus.getNewList()
@@ -135,7 +135,7 @@ class GuiProjectSettings(PagedDialog):
if self.tabReplace.arChanged: if self.tabReplace.arChanged:
newList = self.tabReplace.getNewList() newList = self.tabReplace.getNewList()
self.theProject.setAutoReplace(newList) self.theProject.data.setAutoReplace(newList)
self._saveGuiSettings() self._saveGuiSettings()
self.accept() self.accept()
@@ -209,7 +209,7 @@ class GuiProjectEditMain(QWidget):
self.editName = QLineEdit() self.editName = QLineEdit()
self.editName.setMaxLength(200) self.editName.setMaxLength(200)
self.editName.setMaximumWidth(xW) self.editName.setMaximumWidth(xW)
self.editName.setText(self.theProject.projName) self.editName.setText(self.theProject.data.name)
self.mainForm.addRow( self.mainForm.addRow(
self.tr("Project name"), self.tr("Project name"),
self.editName, self.editName,
@@ -219,7 +219,7 @@ class GuiProjectEditMain(QWidget):
self.editTitle = QLineEdit() self.editTitle = QLineEdit()
self.editTitle.setMaxLength(200) self.editTitle.setMaxLength(200)
self.editTitle.setMaximumWidth(xW) self.editTitle.setMaximumWidth(xW)
self.editTitle.setText(self.theProject.bookTitle) self.editTitle.setText(self.theProject.data.title)
self.mainForm.addRow( self.mainForm.addRow(
self.tr("Novel title"), self.tr("Novel title"),
self.editTitle, self.editTitle,
@@ -229,7 +229,7 @@ class GuiProjectEditMain(QWidget):
self.editAuthors = QPlainTextEdit() self.editAuthors = QPlainTextEdit()
self.editAuthors.setMaximumHeight(xH) self.editAuthors.setMaximumHeight(xH)
self.editAuthors.setMaximumWidth(xW) 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.mainForm.addRow(
self.tr("Author(s)"), self.tr("Author(s)"),
self.editAuthors, self.editAuthors,
@@ -252,13 +252,13 @@ class GuiProjectEditMain(QWidget):
) )
spellIdx = 0 spellIdx = 0
if self.theProject.projSpell is not None: if self.theProject.data.spellLang is not None:
spellIdx = self.spellLang.findData(self.theProject.projSpell) spellIdx = self.spellLang.findData(self.theProject.data.spellLang)
if spellIdx != -1: if spellIdx != -1:
self.spellLang.setCurrentIndex(spellIdx) self.spellLang.setCurrentIndex(spellIdx)
self.doBackup = QSwitch(self) self.doBackup = QSwitch(self)
self.doBackup.setChecked(not self.theProject.doBackup) self.doBackup.setChecked(not self.theProject.data.doBackup)
self.mainForm.addRow( self.mainForm.addRow(
self.tr("No backup on close"), self.tr("No backup on close"),
self.doBackup, self.doBackup,
@@ -288,11 +288,11 @@ class GuiProjectEditStatus(QWidget):
self.mainTheme = projGui.mainGui.mainTheme self.mainTheme = projGui.mainGui.mainTheme
if isStatus: if isStatus:
self.theStatus = self.theProject.statusItems self.theStatus = self.theProject.data.itemStatus
pageLabel = self.tr("Novel File Status Levels") pageLabel = self.tr("Novel File Status Levels")
colSetting = "statusColW" colSetting = "statusColW"
else: else:
self.theStatus = self.theProject.importItems self.theStatus = self.theProject.data.itemImport
pageLabel = self.tr("Note File Importance Levels") pageLabel = self.tr("Note File Importance Levels")
colSetting = "importColW" colSetting = "importColW"
@@ -578,7 +578,7 @@ class GuiProjectEditReplace(QWidget):
self.listBox.setColumnWidth(self.COL_KEY, wCol0) self.listBox.setColumnWidth(self.COL_KEY, wCol0)
self.listBox.setIndentation(0) 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]) newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
self.listBox.addTopLevelItem(newItem) self.listBox.addTopLevelItem(newItem)
+3 -3
View File
@@ -684,10 +684,10 @@ class GuiDocEditor(QTextEdit):
"""Set the spell checker dictionary language, and emit the """Set the spell checker dictionary language, and emit the
dictionary changed signal. dictionary changed signal.
""" """
if self.theProject.projSpell is None: if self.theProject.data.spellLang is None:
theLang = self.mainConf.spellLanguage theLang = self.mainConf.spellLanguage
else: else:
theLang = self.theProject.projSpell theLang = self.theProject.data.spellLang
self.spEnchant.setLanguage(theLang, self.theProject.projDict) self.spEnchant.setLanguage(theLang, self.theProject.projDict)
_, theProvider = self.spEnchant.describeDict() _, theProvider = self.spEnchant.describeDict()
@@ -721,7 +721,7 @@ class GuiDocEditor(QTextEdit):
self._spellCheck = theMode self._spellCheck = theMode
self.mainGui.mainMenu.setSpellCheck(theMode) self.mainGui.mainMenu.setSpellCheck(theMode)
self.theProject.setSpellCheck(theMode) self.theProject.data.setSpellCheck(theMode)
self.highLight.setSpellCheck(theMode) self.highLight.setSpellCheck(theMode)
if not self._bigDoc: if not self._bigDoc:
self.spellCheckDocument() self.spellCheckDocument()
+1 -1
View File
@@ -217,7 +217,7 @@ class GuiDocViewer(QTextBrowser):
self.verticalScrollBar().setValue(sPos) self.verticalScrollBar().setValue(sPos)
self._docHandle = tHandle self._docHandle = tHandle
self.theProject.setLastViewed(tHandle) self.theProject._data.setLastHandle(tHandle, "viewer")
self.docHeader.setTitleFromHandle(self._docHandle) self.docHeader.setTitleFromHandle(self._docHandle)
self.updateDocMargins() self.updateDocMargins()
+1 -1
View File
@@ -794,7 +794,7 @@ class GuiMainMenu(QMenuBar):
# Tools > Check Spelling # Tools > Check Spelling
self.aSpellCheck = QAction(self.tr("Check Spelling"), self) self.aSpellCheck = QAction(self.tr("Check Spelling"), self)
self.aSpellCheck.setCheckable(True) 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.triggered.connect(self._toggleSpellCheck) # triggered, not toggled!
self.aSpellCheck.setShortcut("Ctrl+F7") self.aSpellCheck.setShortcut("Ctrl+F7")
self.toolsMenu.addAction(self.aSpellCheck) self.toolsMenu.addAction(self.aSpellCheck)
+6 -5
View File
@@ -109,7 +109,7 @@ class GuiNovelView(QWidget):
def refreshTree(self): def refreshTree(self):
"""Refresh the current tree. """Refresh the current tree.
""" """
self.novelTree.refreshTree(rootHandle=self.theProject.lastNovel) self.novelTree.refreshTree(rootHandle=self.theProject.data.getLastHandle("novelTree"))
return return
def clearProject(self): def clearProject(self):
@@ -122,7 +122,7 @@ class GuiNovelView(QWidget):
def openProjectTasks(self): def openProjectTasks(self):
"""Run open project tasks. """Run open project tasks.
""" """
lastNovel = self.theProject.lastNovel lastNovel = self.theProject.data.getLastHandle("novelTree")
if lastNovel not in self.theProject.tree: if lastNovel not in self.theProject.tree:
lastNovel = self.theProject.tree.findRoot(nwItemClass.NOVEL) lastNovel = self.theProject.tree.findRoot(nwItemClass.NOVEL)
@@ -319,7 +319,7 @@ class GuiNovelToolBar(QWidget):
def _refreshNovelTree(self): def _refreshNovelTree(self):
"""Rebuild the current tree. """Rebuild the current tree.
""" """
rootHandle = self.theProject.lastNovel rootHandle = self.theProject.data.getLastHandle("novelTree")
self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True) self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True)
return return
@@ -485,7 +485,7 @@ class GuiNovelTree(QTreeWidget):
titleKey = selItem[0].data(self.C_TITLE, self.D_KEY) titleKey = selItem[0].data(self.C_TITLE, self.D_KEY)
self._populateTree(rootHandle) self._populateTree(rootHandle)
self.theProject.setLastNovelViewed(rootHandle) self.theProject.data.setLastHandle(rootHandle, "novelTree")
if titleKey is not None and titleKey in self._treeMap: if titleKey is not None and titleKey in self._treeMap:
self._treeMap[titleKey].setSelected(True) self._treeMap[titleKey].setSelected(True)
@@ -523,7 +523,8 @@ class GuiNovelTree(QTreeWidget):
self._lastCol = colType self._lastCol = colType
self.setColumnHidden(self.C_EXTRA, colType == NovelTreeColumn.HIDDEN) self.setColumnHidden(self.C_EXTRA, colType == NovelTreeColumn.HIDDEN)
if doRefresh: if doRefresh:
self.refreshTree(rootHandle=self.theProject.lastNovel, overRide=True) lastNovel = self.theProject.data.getLastHandle("novelTree")
self.refreshTree(rootHandle=lastNovel, overRide=True)
return return
def setActiveHandle(self, tHandle): def setActiveHandle(self, tHandle):
+3 -3
View File
@@ -114,7 +114,7 @@ class GuiOutlineView(QWidget):
def refreshTree(self): def refreshTree(self):
"""Refresh the current tree. """Refresh the current tree.
""" """
self.outlineTree.refreshTree(rootHandle=self.theProject.lastOutline) self.outlineTree.refreshTree(rootHandle=self.theProject.data.getLastHandle("outline"))
return return
def clearProject(self): def clearProject(self):
@@ -126,7 +126,7 @@ class GuiOutlineView(QWidget):
def openProjectTasks(self): def openProjectTasks(self):
"""Run open project tasks. """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): if not (lastOutline in self.theProject.tree or lastOutline is None):
lastOutline = self.theProject.tree.findRoot(nwItemClass.NOVEL) lastOutline = self.theProject.tree.findRoot(nwItemClass.NOVEL)
@@ -504,7 +504,7 @@ class GuiOutlineTree(QTreeWidget):
return return
self._populateTree(rootHandle) self._populateTree(rootHandle)
self.theProject.setLastOutlineViewed(rootHandle or None) self.theProject.data.setLastHandle(rootHandle or None, "outline")
return return
+2 -2
View File
@@ -1208,7 +1208,7 @@ class GuiProjectTree(QTreeWidget):
checkMark = f" ({nwUnicode.U_CHECK})" checkMark = f" ({nwUnicode.U_CHECK})"
if tItem.isNovelLike(): if tItem.isNovelLike():
mStatus = ctxMenu.addMenu(self.tr("Set Status to ...")) 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 "") entryName = entry["name"] + (checkMark if tItem.itemStatus == key else "")
aStatus = mStatus.addAction(entry["icon"], entryName) aStatus = mStatus.addAction(entry["icon"], entryName)
aStatus.triggered.connect( aStatus.triggered.connect(
@@ -1221,7 +1221,7 @@ class GuiProjectTree(QTreeWidget):
) )
else: else:
mImport = ctxMenu.addMenu(self.tr("Set Importance to ...")) 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 "") entryName = entry["name"] + (checkMark if tItem.itemImport == key else "")
aImport = mImport.addAction(entry["icon"], entryName) aImport = mImport.addAction(entry["icon"], entryName)
aImport.triggered.connect( aImport.triggered.connect(
+23 -19
View File
@@ -197,6 +197,8 @@ class GuiMain(QMainWindow):
# Connect Signals # Connect Signals
# =============== # ===============
self.theProject.projectStatusChanged.connect(self.mainStatus.doUpdateProjectStatus)
self.viewsBar.viewChangeRequested.connect(self._changeView) self.viewsBar.viewChangeRequested.connect(self._changeView)
self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox) self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
@@ -383,7 +385,7 @@ class GuiMain(QMainWindow):
self.mainStatus.setDocumentStatus(nwState.NONE) self.mainStatus.setDocumentStatus(nwState.NONE)
self.mainStatus.setStatus(self.tr("New project created ...")) self.mainStatus.setStatus(self.tr("New project created ..."))
self._updateWindowTitle(self.theProject.projName) self._updateWindowTitle(self.theProject.data.name)
else: else:
self.theProject.clearProject() self.theProject.clearProject()
@@ -417,7 +419,7 @@ class GuiMain(QMainWindow):
if self.theProject.projAltered: if self.theProject.projAltered:
saveOK = self.saveProject() saveOK = self.saveProject()
doBackup = False doBackup = False
if self.theProject.doBackup and self.mainConf.backupOnClose: if self.theProject.data.doBackup and self.mainConf.backupOnClose:
doBackup = True doBackup = True
if self.mainConf.askBeforeBackup: if self.mainConf.askBeforeBackup:
msgYes = self.askQuestion( msgYes = self.askQuestion(
@@ -521,10 +523,10 @@ class GuiMain(QMainWindow):
self.theProject.index.loadIndex() self.theProject.index.loadIndex()
# Update GUI # Update GUI
self._updateWindowTitle(self.theProject.projName) self._updateWindowTitle(self.theProject.data.name)
self.rebuildTrees() self.rebuildTrees()
self.docEditor.setDictionaries() self.docEditor.setDictionaries()
self.docEditor.toggleSpellCheck(self.theProject.spellCheck) self.docEditor.toggleSpellCheck(self.theProject.data.spellCheck)
self.mainStatus.setRefTime(self.theProject.projOpened) self.mainStatus.setRefTime(self.theProject.projOpened)
self.projView.openProjectTasks() self.projView.openProjectTasks()
self.novelView.openProjectTasks() self.novelView.openProjectTasks()
@@ -532,11 +534,13 @@ class GuiMain(QMainWindow):
self._updateStatusWordCount() self._updateStatusWordCount()
# Restore previously open documents, if any # Restore previously open documents, if any
if self.theProject.lastEdited is not None: lastEdited = self.theProject.data.getLastHandle("editor")
self.openDocument(self.theProject.lastEdited, doScroll=True) if lastEdited is not None:
self.openDocument(lastEdited, doScroll=True)
if self.theProject.lastViewed is not None: lastViewed = self.theProject.data.getLastHandle("viewer")
self.viewDocument(self.theProject.lastViewed) if lastViewed is not None:
self.viewDocument(lastViewed)
# Check if we need to rebuild the index # Check if we need to rebuild the index
if self.theProject.index.indexBroken: if self.theProject.index.indexBroken:
@@ -607,7 +611,7 @@ class GuiMain(QMainWindow):
if self.docEditor.loadText(tHandle, tLine): if self.docEditor.loadText(tHandle, tLine):
if changeFocus: if changeFocus:
self.docEditor.setFocus() self.docEditor.setFocus()
self.theProject.setLastEdited(tHandle) self.theProject.data.setLastHandle(tHandle, "editor")
self.projView.setSelectedHandle(tHandle, doScroll=doScroll) self.projView.setSelectedHandle(tHandle, doScroll=doScroll)
self.novelView.setActiveHandle(tHandle) self.novelView.setActiveHandle(tHandle)
else: else:
@@ -676,7 +680,7 @@ class GuiMain(QMainWindow):
tHandle = self.projView.getSelectedHandle() tHandle = self.projView.getSelectedHandle()
if tHandle is None: if tHandle is None:
tHandle = self.theProject.lastViewed tHandle = self.theProject.data.getLastHandle("viewer")
if tHandle is None: if tHandle is None:
logger.debug("No document to view, giving up") logger.debug("No document to view, giving up")
@@ -960,7 +964,7 @@ class GuiMain(QMainWindow):
if dlgProj.spellChanged: if dlgProj.spellChanged:
self.docEditor.setDictionaries() self.docEditor.setDictionaries()
self.itemDetails.refreshDetails() self.itemDetails.refreshDetails()
self._updateWindowTitle(self.theProject.projName) self._updateWindowTitle(self.theProject.data.name)
return True return True
@@ -1223,14 +1227,14 @@ class GuiMain(QMainWindow):
"""Close the document edit panel. This does not hide the editor. """Close the document edit panel. This does not hide the editor.
""" """
self.closeDocument() self.closeDocument()
self.theProject.setLastEdited(None) self.theProject.data.setLastHandle(None, "editor")
return return
def closeDocViewer(self): def closeDocViewer(self):
"""Close the document view panel. """Close the document view panel.
""" """
self.docViewer.clearViewer() self.docViewer.clearViewer()
self.theProject.setLastViewed(None) self.theProject.data.setLastHandle(None, "viewer")
bPos = self.splitMain.sizes() bPos = self.splitMain.sizes()
self.splitView.setVisible(False) self.splitView.setVisible(False)
self.splitDocs.setSizes([bPos[1], 0]) self.splitDocs.setSizes([bPos[1], 0])
@@ -1555,13 +1559,13 @@ class GuiMain(QMainWindow):
self.theProject.updateWordCounts() self.theProject.updateWordCounts()
if self.mainConf.incNotesWCount: if self.mainConf.incNotesWCount:
currWords = self.theProject.currWCount iTotal = sum(self.theProject.data.initCounts)
diffWords = currWords - self.theProject.lastWCount cTotal = sum(self.theProject.data.currCounts)
self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
else: else:
currWords = self.theProject.currNovelWC iNovel, _ = self.theProject.data.initCounts
diffWords = currWords - self.theProject.lastNovelWC cNovel, _ = self.theProject.data.currCounts
self.mainStatus.setProjectStats(cNovel, cNovel - iNovel)
self.mainStatus.setProjectStats(currWords, diffWords)
return return
+11 -11
View File
@@ -126,7 +126,7 @@ class GuiBuildNovel(QDialog):
self.fmtTitle.setMinimumWidth(xFmt) self.fmtTitle.setMinimumWidth(xFmt)
self.fmtTitle.setToolTip(fmtHelp) self.fmtTitle.setToolTip(fmtHelp)
self.fmtTitle.setText( self.fmtTitle.setText(
self._reFmtCodes(self.theProject.titleFormat["title"]) self._reFmtCodes(self.theProject.data.getTitleFormat("title"))
) )
self.fmtChapter = QLineEdit() self.fmtChapter = QLineEdit()
@@ -134,7 +134,7 @@ class GuiBuildNovel(QDialog):
self.fmtChapter.setMinimumWidth(xFmt) self.fmtChapter.setMinimumWidth(xFmt)
self.fmtChapter.setToolTip(fmtHelp) self.fmtChapter.setToolTip(fmtHelp)
self.fmtChapter.setText( self.fmtChapter.setText(
self._reFmtCodes(self.theProject.titleFormat["chapter"]) self._reFmtCodes(self.theProject.data.getTitleFormat("chapter"))
) )
self.fmtUnnumbered = QLineEdit() self.fmtUnnumbered = QLineEdit()
@@ -142,7 +142,7 @@ class GuiBuildNovel(QDialog):
self.fmtUnnumbered.setMinimumWidth(xFmt) self.fmtUnnumbered.setMinimumWidth(xFmt)
self.fmtUnnumbered.setToolTip(fmtHelp) self.fmtUnnumbered.setToolTip(fmtHelp)
self.fmtUnnumbered.setText( self.fmtUnnumbered.setText(
self._reFmtCodes(self.theProject.titleFormat["unnumbered"]) self._reFmtCodes(self.theProject.data.getTitleFormat("unnumbered"))
) )
self.fmtScene = QLineEdit() self.fmtScene = QLineEdit()
@@ -150,7 +150,7 @@ class GuiBuildNovel(QDialog):
self.fmtScene.setMinimumWidth(xFmt) self.fmtScene.setMinimumWidth(xFmt)
self.fmtScene.setToolTip(fmtHelp + fmtScHelp) self.fmtScene.setToolTip(fmtHelp + fmtScHelp)
self.fmtScene.setText( self.fmtScene.setText(
self._reFmtCodes(self.theProject.titleFormat["scene"]) self._reFmtCodes(self.theProject.data.getTitleFormat("scene"))
) )
self.fmtSection = QLineEdit() self.fmtSection = QLineEdit()
@@ -158,7 +158,7 @@ class GuiBuildNovel(QDialog):
self.fmtSection.setMinimumWidth(xFmt) self.fmtSection.setMinimumWidth(xFmt)
self.fmtSection.setToolTip(fmtHelp + fmtScHelp) self.fmtSection.setToolTip(fmtHelp + fmtScHelp)
self.fmtSection.setText( self.fmtSection.setText(
self._reFmtCodes(self.theProject.titleFormat["section"]) self._reFmtCodes(self.theProject.data.getTitleFormat("section"))
) )
self.buildLang = QComboBox() self.buildLang = QComboBox()
@@ -168,7 +168,7 @@ class GuiBuildNovel(QDialog):
for langID, langName in theLangs: for langID, langName in theLangs:
self.buildLang.addItem(langName, langID) self.buildLang.addItem(langName, langID)
langIdx = self.buildLang.findData(self.theProject.projLang) langIdx = self.buildLang.findData(self.theProject.data.language)
if langIdx != -1: if langIdx != -1:
self.buildLang.setCurrentIndex(langIdx) self.buildLang.setCurrentIndex(langIdx)
@@ -888,7 +888,7 @@ class GuiBuildNovel(QDialog):
# Generate File Name # Generate File Name
# ================== # ==================
cleanName = makeFileNameSafe(self.theProject.projName) cleanName = makeFileNameSafe(self.theProject.data.name)
fileName = "%s.%s" % (cleanName, fileExt) fileName = "%s.%s" % (cleanName, fileExt)
saveDir = self.mainConf.lastPath saveDir = self.mainConf.lastPath
if not os.path.isdir(saveDir): 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: elif theFmt == self.FMT_JSON_H or theFmt == self.FMT_JSON_M:
jsonData = { jsonData = {
"meta": { "meta": {
"workingTitle": self.theProject.projName, "workingTitle": self.theProject.data.name,
"novelTitle": self.theProject.bookTitle, "novelTitle": self.theProject.data.title,
"authors": self.theProject.bookAuthors, "authors": self.theProject.data.authors,
"buildTime": self.buildTime, "buildTime": self.buildTime,
} }
} }
@@ -1159,7 +1159,7 @@ class GuiBuildNovel(QDialog):
logger.debug("Saving GuiBuildNovel settings") logger.debug("Saving GuiBuildNovel settings")
# Formatting # Formatting
self.theProject.setTitleFormat({ self.theProject.data.setTitleFormat({
"title": self.fmtTitle.text().strip(), "title": self.fmtTitle.text().strip(),
"chapter": self.fmtChapter.text().strip(), "chapter": self.fmtChapter.text().strip(),
"unnumbered": self.fmtUnnumbered.text().strip(), "unnumbered": self.fmtUnnumbered.text().strip(),
+33 -32
View File
@@ -1,37 +1,38 @@
<?xml version='1.0' encoding='utf-8'?> <?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> <project>
<name>Sample Project</name> <name>Sample Project</name>
<title>Sample Project</title> <title>Sample Project</title>
<author>Jane Smith</author> <author>Jane Smith</author>
<author>Jay Doh</author> <author>Jay Doh</author>
<saveCount>1386</saveCount> <saveCount>1409</saveCount>
<autoCount>236</autoCount> <autoCount>236</autoCount>
<editTime>69352</editTime> <editTime>69427</editTime>
</project> </project>
<settings> <settings>
<doBackup>False</doBackup> <doBackup>False</doBackup>
<language>en_GB</language> <language>en_GB</language>
<spellCheck>True</spellCheck> <spellCheck>True</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>636b6aa9b697b</lastViewed>
<lastNovel>7031beac91f75</lastNovel>
<lastOutline>7031beac91f75</lastOutline>
<lastWordCount>1363</lastWordCount>
<novelWordCount>954</novelWordCount> <novelWordCount>954</novelWordCount>
<notesWordCount>409</notesWordCount> <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> <autoReplace>
<entry key="A">B</entry> <entry key="A">B</entry>
<entry key="B">E</entry> <entry key="B">E</entry>
<entry key="C">D</entry> <entry key="C">D</entry>
</autoReplace> </autoReplace>
<titleFormat> <titleFormat>
<title>%title%</title> <entry key="title">%title%</entry>
<chapter>Chapter %chw%: %title%</chapter> <entry key="chapter">Chapter %chw%: %title%</entry>
<unnumbered>%title%</unnumbered> <entry key="unnumbered">%title%</entry>
<scene>Scene %ch%.%sc%: %title%</scene> <entry key="scene">Scene %ch%.%sc%: %title%</entry>
<section></section> <entry key="section"></entry>
</titleFormat> </titleFormat>
<status> <status>
<entry key="sf12341" count="4" red="100" green="100" blue="100">New</entry> <entry key="sf12341" count="4" red="100" green="100" blue="100">New</entry>
@@ -55,43 +56,43 @@
<name status="sc24b8f" import="ia857f0">Novel</name> <name status="sc24b8f" import="ia857f0">Novel</name>
</item> </item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="sc24b8f" import="ia857f0" active="True">Title Page</name>
</item> </item>
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="sf12341" import="ia857f0" active="True">Page</name>
</item> </item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s90e6c9" import="ia857f0" active="True">Part One</name>
</item> </item>
<item handle="6a2d6d5f4f401" parent="7031beac91f75" root="7031beac91f75" order="3" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="sf24ce6" import="ia857f0" active="True">Chapter One</name>
</item> </item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s90e6c9" import="ia857f0" active="True">Making a Scene</name>
</item> </item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s90e6c9" import="ia857f0" active="True">Another Scene</name>
</item> </item>
<item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s78ea90" import="ia857f0" active="True">Interlude</name>
</item> </item>
<item handle="96b68994dfa3d" parent="7031beac91f75" root="7031beac91f75" order="5" type="FILE" class="NOVEL" layout="NOTE"> <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> <name status="sf24ce6" import="ia857f0" active="False">A Note on Structure</name>
</item> </item>
<item handle="88706ddc78b1b" parent="7031beac91f75" root="7031beac91f75" order="6" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s90e6c9" import="ia857f0" active="True">Chapter Two</name>
</item> </item>
<item handle="ae7339df26ded" parent="88706ddc78b1b" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s90e6c9" import="ia857f0" active="True">We Found John!</name>
</item> </item>
<item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL"> <item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL">
@@ -99,11 +100,11 @@
<name status="sf12341" import="ia857f0">Sequel</name> <name status="sf12341" import="ia857f0">Sequel</name>
</item> </item>
<item handle="bacb7059e3083" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="sc24b8f" import="ia857f0" active="True">Title Page</name>
</item> </item>
<item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s90e6c9" import="ia857f0" active="True">Chapter One</name>
</item> </item>
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="2" type="ROOT" class="CHARACTER"> <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> <name status="sf12341" import="ia857f0">Main Characters</name>
</item> </item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <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> <name status="sf12341" import="icfb3a5" active="True">John Smith</name>
</item> </item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE"> <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> <name status="sf12341" import="i2d7a54" active="True">Jane Smith</name>
</item> </item>
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD"> <item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD">
@@ -127,15 +128,15 @@
<name status="sf12341" import="ia857f0">Locations</name> <name status="sf12341" import="ia857f0">Locations</name>
</item> </item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE"> <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> <name status="sf12341" import="i56be10" active="True">Earth</name>
</item> </item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE"> <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> <name status="sf12341" import="icfb3a5" active="True">Space</name>
</item> </item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE"> <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> <name status="sf12341" import="i2d7a54" active="True">Mars</name>
</item> </item>
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="4" type="ROOT" class="ARCHIVE"> <item handle="6827118336ac1" parent="None" root="6827118336ac1" order="4" type="ROOT" class="ARCHIVE">
@@ -147,7 +148,7 @@
<name status="sf12341" import="ia857f0">Scenes</name> <name status="sf12341" import="ia857f0">Scenes</name>
</item> </item>
<item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" root="6827118336ac1" order="0" type="FILE" class="ARCHIVE" layout="DOCUMENT"> <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> <name status="s90e6c9" import="ia857f0" active="True">Old File</name>
</item> </item>
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="5" type="ROOT" class="TRASH"> <item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="5" type="ROOT" class="TRASH">
@@ -155,7 +156,7 @@
<name status="sf12341" import="ia857f0">Trash</name> <name status="sf12341" import="ia857f0">Trash</name>
</item> </item>
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="98acd8c76c93a" order="0" type="FILE" class="TRASH" layout="DOCUMENT"> <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> <name status="sf12341" import="ia857f0" active="True">Delete Me!</name>
</item> </item>
</content> </content>
-21
View File
@@ -214,27 +214,6 @@ def mockRnd(monkeypatch):
# Temp Project Folders # 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") @pytest.fixture(scope="function")
def nwLipsum(tmpDir): def nwLipsum(tmpDir):
"""A medium sized novelWriter example project with a lot of Lorem """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'?> <?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> <project>
<name>Lorem Ipsum</name> <name>Lorem Ipsum</name>
<title>Lorem Ipsum</title> <title>Lorem Ipsum</title>
<author>lipsum.com</author> <author>lipsum.com</author>
<saveCount>28</saveCount> <saveCount>32</saveCount>
<autoCount>24</autoCount> <autoCount>24</autoCount>
<editTime>1874</editTime> <editTime>1889</editTime>
</project> </project>
<settings> <settings>
<doBackup>False</doBackup> <doBackup>False</doBackup>
<language>en_GB</language> <language>en_GB</language>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<lastEdited>7a992350f3eb6</lastEdited> <totalWordCount>3847</totalWordCount>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>3847</lastWordCount>
<novelWordCount>3109</novelWordCount> <novelWordCount>3109</novelWordCount>
<notesWordCount>738</notesWordCount> <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> <autoReplace>
<entry key="Rep1">Replace Text 1</entry> <entry key="Rep1">Replace Text 1</entry>
<entry key="Rep2">Replace Text 2</entry> <entry key="Rep2">Replace Text 2</entry>
</autoReplace> </autoReplace>
<titleFormat> <titleFormat>
<title>%title%</title> <entry key="title">%title%</entry>
<chapter>Chapter %ch%: %title%</chapter> <entry key="chapter">Chapter %ch%: %title%</entry>
<unnumbered>%title%</unnumbered> <entry key="unnumbered">%title%</entry>
<scene>* * *</scene> <entry key="scene">* * *</entry>
<section></section> <entry key="section"></entry>
</titleFormat> </titleFormat>
<status> <status>
<entry key="sbaa94f" count="3" red="100" green="100" blue="100">New</entry> <entry key="sbaa94f" count="3" red="100" green="100" blue="100">New</entry>
@@ -50,19 +52,19 @@
<name status="sbaa94f" import="i613591">Novel</name> <name status="sbaa94f" import="i613591">Novel</name>
</item> </item>
<item handle="7a992350f3eb6" parent="b3643d0f92e32" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="sedd043" import="i613591" active="True">Lorem Ipsum</name>
</item> </item>
<item handle="8c58a65414c23" parent="b3643d0f92e32" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="sedd043" import="i613591" active="True">Front Matter</name>
</item> </item>
<item handle="88d59a277361b" parent="b3643d0f92e32" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s92a87b" import="i613591" active="True">Prologue</name>
</item> </item>
<item handle="db7e733775d4d" parent="b3643d0f92e32" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="sbaa94f" import="i613591" active="True">Act One</name>
</item> </item>
<item handle="45e6b01ca35c1" parent="b3643d0f92e32" root="b3643d0f92e32" order="4" type="FOLDER" class="NOVEL"> <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> <name status="s92a87b" import="i613591">Chapter One</name>
</item> </item>
<item handle="fb609cd8319dc" parent="45e6b01ca35c1" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s92a87b" import="i613591" active="True">Chapter One</name>
</item> </item>
<item handle="88243afbe5ed8" parent="45e6b01ca35c1" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="sedd043" import="i613591" active="True">Scene One</name>
</item> </item>
<item handle="f96ec11c6a3da" parent="45e6b01ca35c1" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="sedd043" import="i613591" active="True">Scene Two</name>
</item> </item>
<item handle="846352075de7d" parent="b3643d0f92e32" root="b3643d0f92e32" order="5" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="sbaa94f" import="i613591" active="False">Interlude</name>
</item> </item>
<item handle="6bd935d2490cd" parent="b3643d0f92e32" root="b3643d0f92e32" order="6" type="FOLDER" class="NOVEL"> <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> <name status="s92a87b" import="i613591">Chapter Two</name>
</item> </item>
<item handle="441420a886d82" parent="6bd935d2490cd" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s92a87b" import="i613591" active="True">Chapter Two</name>
</item> </item>
<item handle="eb103bc70c90c" parent="6bd935d2490cd" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="sedd043" import="i613591" active="True">Scene Three</name>
</item> </item>
<item handle="f8c0562e50f1b" parent="6bd935d2490cd" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="sedd043" import="i613591" active="True">Scene Four</name>
</item> </item>
<item handle="47666c91c7ccf" parent="6bd935d2490cd" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="sedd043" import="i613591" active="True">Scene Five</name>
</item> </item>
<item handle="67a8707f2f249" parent="None" root="67a8707f2f249" order="1" type="ROOT" class="CHARACTER"> <item handle="67a8707f2f249" parent="None" root="67a8707f2f249" order="1" type="ROOT" class="CHARACTER">
@@ -110,7 +112,7 @@
<name status="sbaa94f" import="i613591">Characters</name> <name status="sbaa94f" import="i613591">Characters</name>
</item> </item>
<item handle="4c4f28287af27" parent="67a8707f2f249" root="67a8707f2f249" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <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> <name status="sbaa94f" import="i613591" active="True">Mr. Nobody</name>
</item> </item>
<item handle="6c6afb1247750" parent="None" root="6c6afb1247750" order="2" type="ROOT" class="PLOT"> <item handle="6c6afb1247750" parent="None" root="6c6afb1247750" order="2" type="ROOT" class="PLOT">
@@ -118,7 +120,7 @@
<name status="sbaa94f" import="i613591">Plot</name> <name status="sbaa94f" import="i613591">Plot</name>
</item> </item>
<item handle="2426c6f0ca922" parent="6c6afb1247750" root="6c6afb1247750" order="0" type="FILE" class="PLOT" layout="NOTE"> <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> <name status="sbaa94f" import="i613591" active="True">Main</name>
</item> </item>
<item handle="60bdf227455cc" parent="None" root="60bdf227455cc" order="3" type="ROOT" class="WORLD"> <item handle="60bdf227455cc" parent="None" root="60bdf227455cc" order="3" type="ROOT" class="WORLD">
@@ -126,7 +128,7 @@
<name status="sbaa94f" import="i613591">World</name> <name status="sbaa94f" import="i613591">World</name>
</item> </item>
<item handle="04468803b92e1" parent="60bdf227455cc" root="60bdf227455cc" order="0" type="FILE" class="WORLD" layout="NOTE"> <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> <name status="sbaa94f" import="i613591" active="True">Ancient Europe</name>
</item> </item>
</content> </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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from PyQt5.QtCore import QObject
# =========================================================================== # # =========================================================================== #
# Mock GUI # Mock GUI
# =========================================================================== # # =========================================================================== #
class MockGuiMain: class MockGuiMain(QObject):
def __init__(self): def __init__(self):
super().__init__()
self.mainConf = None self.mainConf = None
self.hasProject = True self.hasProject = True
self.theProject = None self.theProject = None
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-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> <project>
<name>Test Custom</name> <name>Test Custom</name>
<title>Test Novel</title> <title>Test Novel</title>
@@ -14,122 +14,123 @@
<language>None</language> <language>None</language>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount> <novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount> <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/> <autoReplace/>
<titleFormat> <titleFormat>
<title>%title%</title> <entry key="title">%title%</entry>
<chapter>%title%</chapter> <entry key="chapter">%title%</entry>
<unnumbered>%title%</unnumbered> <entry key="unnumbered">%title%</entry>
<scene>* * *</scene> <entry key="scene">* * *</entry>
<section></section> <entry key="section"></entry>
</titleFormat> </titleFormat>
<status> <status>
<entry key="s000008" count="15" red="100" green="100" blue="100">New</entry> <entry key="s000000" 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="s000001" 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="s000002" 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="s000003" count="0" red="50" green="200" blue="0">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="i00000c" count="7" red="100" green="100" blue="100">New</entry> <entry key="i000004" 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="i000005" 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="i000006" 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="i000007" count="0" red="50" green="200" blue="0">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="22"> <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"/> <meta expanded="False"/>
<name status="s000008" import="i00000c">Novel</name> <name status="s000000" import="i000004">Novel</name>
</item> </item>
<item handle="0000000000011" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0000000000009" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Title Page</name> <name status="s000000" import="i000004" active="True">Title Page</name>
</item> </item>
<item handle="0000000000012" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="000000000000a" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Chapter 1</name> <name status="s000000" import="i000004" active="True">Chapter 1</name>
</item> </item>
<item handle="0000000000013" parent="0000000000012" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="000000000000b" parent="000000000000a" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 1.1</name> <name status="s000000" import="i000004" active="True">Scene 1.1</name>
</item> </item>
<item handle="0000000000014" parent="0000000000012" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="000000000000c" parent="000000000000a" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 1.2</name> <name status="s000000" import="i000004" active="True">Scene 1.2</name>
</item> </item>
<item handle="0000000000015" parent="0000000000012" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="000000000000d" parent="000000000000a" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 1.3</name> <name status="s000000" import="i000004" active="True">Scene 1.3</name>
</item> </item>
<item handle="0000000000016" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="000000000000e" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Chapter 2</name> <name status="s000000" import="i000004" active="True">Chapter 2</name>
</item> </item>
<item handle="0000000000017" parent="0000000000016" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="000000000000f" parent="000000000000e" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 2.1</name> <name status="s000000" import="i000004" active="True">Scene 2.1</name>
</item> </item>
<item handle="0000000000018" parent="0000000000016" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0000000000010" parent="000000000000e" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 2.2</name> <name status="s000000" import="i000004" active="True">Scene 2.2</name>
</item> </item>
<item handle="0000000000019" parent="0000000000016" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0000000000011" parent="000000000000e" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 2.3</name> <name status="s000000" import="i000004" active="True">Scene 2.3</name>
</item> </item>
<item handle="000000000001a" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0000000000012" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Chapter 3</name> <name status="s000000" import="i000004" active="True">Chapter 3</name>
</item> </item>
<item handle="000000000001b" parent="000000000001a" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0000000000013" parent="0000000000012" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 3.1</name> <name status="s000000" import="i000004" active="True">Scene 3.1</name>
</item> </item>
<item handle="000000000001c" parent="000000000001a" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0000000000014" parent="0000000000012" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 3.2</name> <name status="s000000" import="i000004" active="True">Scene 3.2</name>
</item> </item>
<item handle="000000000001d" parent="000000000001a" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0000000000015" parent="0000000000012" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 3.3</name> <name status="s000000" import="i000004" active="True">Scene 3.3</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000008" import="i00000c">Plot</name> <name status="s000000" import="i000004">Plot</name>
</item> </item>
<item handle="000000000001f" parent="000000000001e" root="000000000001e" order="0" type="FILE" class="PLOT" layout="NOTE"> <item handle="0000000000017" parent="0000000000016" root="0000000000016" order="0" type="FILE" class="PLOT" layout="NOTE">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Main Plot</name> <name status="s000000" import="i000004" active="True">Main Plot</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000008" import="i00000c">Characters</name> <name status="s000000" import="i000004">Characters</name>
</item> </item>
<item handle="0000000000021" parent="0000000000020" root="0000000000020" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="0000000000019" parent="0000000000018" root="0000000000018" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Protagonist</name> <name status="s000000" import="i000004" active="True">Protagonist</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000008" import="i00000c">Locations</name> <name status="s000000" import="i000004">Locations</name>
</item> </item>
<item handle="0000000000023" parent="0000000000022" root="0000000000022" order="0" type="FILE" class="WORLD" layout="NOTE"> <item handle="000000000001b" parent="000000000001a" root="000000000001a" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Main Location</name> <name status="s000000" import="i000004" active="True">Main Location</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000008" import="i00000c">Archive</name> <name status="s000000" import="i000004">Archive</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000008" import="i00000c">Trash</name> <name status="s000000" import="i000004">Trash</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-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> <project>
<name>Test Custom</name> <name>Test Custom</name>
<title>Test Novel</title> <title>Test Novel</title>
@@ -14,98 +14,99 @@
<language>None</language> <language>None</language>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount> <novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount> <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/> <autoReplace/>
<titleFormat> <titleFormat>
<title>%title%</title> <entry key="title">%title%</entry>
<chapter>%title%</chapter> <entry key="chapter">%title%</entry>
<unnumbered>%title%</unnumbered> <entry key="unnumbered">%title%</entry>
<scene>* * *</scene> <entry key="scene">* * *</entry>
<section></section> <entry key="section"></entry>
</titleFormat> </titleFormat>
<status> <status>
<entry key="s000008" count="9" red="100" green="100" blue="100">New</entry> <entry key="s000000" 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="s000001" 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="s000002" 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="s000003" count="0" red="50" green="200" blue="0">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="i00000c" count="7" red="100" green="100" blue="100">New</entry> <entry key="i000004" 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="i000005" 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="i000006" 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="i000007" count="0" red="50" green="200" blue="0">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="16"> <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"/> <meta expanded="False"/>
<name status="s000008" import="i00000c">Novel</name> <name status="s000000" import="i000004">Novel</name>
</item> </item>
<item handle="0000000000011" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0000000000009" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Title Page</name> <name status="s000000" import="i000004" active="True">Title Page</name>
</item> </item>
<item handle="0000000000012" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="000000000000a" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 1</name> <name status="s000000" import="i000004" active="True">Scene 1</name>
</item> </item>
<item handle="0000000000013" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="000000000000b" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 2</name> <name status="s000000" import="i000004" active="True">Scene 2</name>
</item> </item>
<item handle="0000000000014" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="000000000000c" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 3</name> <name status="s000000" import="i000004" active="True">Scene 3</name>
</item> </item>
<item handle="0000000000015" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="000000000000d" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 4</name> <name status="s000000" import="i000004" active="True">Scene 4</name>
</item> </item>
<item handle="0000000000016" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="000000000000e" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 5</name> <name status="s000000" import="i000004" active="True">Scene 5</name>
</item> </item>
<item handle="0000000000017" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="000000000000f" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Scene 6</name> <name status="s000000" import="i000004" active="True">Scene 6</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000008" import="i00000c">Plot</name> <name status="s000000" import="i000004">Plot</name>
</item> </item>
<item handle="0000000000019" parent="0000000000018" root="0000000000018" order="0" type="FILE" class="PLOT" layout="NOTE"> <item handle="0000000000011" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="PLOT" layout="NOTE">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Main Plot</name> <name status="s000000" import="i000004" active="True">Main Plot</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000008" import="i00000c">Characters</name> <name status="s000000" import="i000004">Characters</name>
</item> </item>
<item handle="000000000001b" parent="000000000001a" root="000000000001a" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="0000000000013" parent="0000000000012" root="0000000000012" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Protagonist</name> <name status="s000000" import="i000004" active="True">Protagonist</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000008" import="i00000c">Locations</name> <name status="s000000" import="i000004">Locations</name>
</item> </item>
<item handle="000000000001d" parent="000000000001c" root="000000000001c" order="0" type="FILE" class="WORLD" layout="NOTE"> <item handle="0000000000015" parent="0000000000014" root="0000000000014" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Main Location</name> <name status="s000000" import="i000004" active="True">Main Location</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000008" import="i00000c">Archive</name> <name status="s000000" import="i000004">Archive</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000008" import="i00000c">Trash</name> <name status="s000000" import="i000004">Trash</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-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> <project>
<name>New Project</name> <name>New Project</name>
<title>New Novel</title> <title>New Novel</title>
@@ -13,20 +13,21 @@
<language>None</language> <language>None</language>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>13</lastWordCount>
<novelWordCount>10</novelWordCount> <novelWordCount>10</novelWordCount>
<notesWordCount>3</notesWordCount> <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/> <autoReplace/>
<titleFormat> <titleFormat>
<title>%title%</title> <entry key="title">%title%</entry>
<chapter>%title%</chapter> <entry key="chapter">%title%</entry>
<unnumbered>%title%</unnumbered> <entry key="unnumbered">%title%</entry>
<scene>* * *</scene> <entry key="scene">* * *</entry>
<section></section> <entry key="section"></entry>
</titleFormat> </titleFormat>
<status> <status>
<entry key="s000000" count="7" red="100" green="100" blue="100">New</entry> <entry key="s000000" count="7" red="100" green="100" blue="100">New</entry>
@@ -59,7 +60,7 @@
<name status="s000000" import="i000004">World</name> <name status="s000000" import="i000004">World</name>
</item> </item>
<item handle="000000000000c" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s000000" import="i000004" active="True">Title Page</name>
</item> </item>
<item handle="000000000000d" parent="0000000000008" root="0000000000008" order="0" type="FOLDER" class="NOVEL"> <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> <name status="s000000" import="i000004">New Chapter</name>
</item> </item>
<item handle="000000000000e" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s000000" import="i000004" active="True">New Chapter</name>
</item> </item>
<item handle="000000000000f" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s000000" import="i000004" active="True">New Scene</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000000" import="i000004">Stuff</name> <name status="s000000" import="i000004">Stuff</name>
</item> </item>
<item handle="0000000000021" parent="0000000000020" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0000000000011" parent="0000000000010" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H2" charCount="5" wordCount="1" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H2" charCount="5" wordCount="1" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Hello</name> <name status="s000000" import="i000004" active="True">Hello</name>
</item> </item>
<item handle="0000000000022" parent="000000000000a" root="000000000000a" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="0000000000012" 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"/> <meta expanded="False" heading="H1" charCount="11" wordCount="3" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Jane</name> <name status="s000000" import="i000004" active="True">Jane</name>
</item> </item>
</content> </content>
@@ -1,8 +1,8 @@
<?xml version='1.0' encoding='utf-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> <project>
<name>New Project</name> <name>New Project</name>
<title></title> <title>None</title>
<saveCount>2</saveCount> <saveCount>2</saveCount>
<autoCount>1</autoCount> <autoCount>1</autoCount>
<editTime>0</editTime> <editTime>0</editTime>
@@ -12,66 +12,67 @@
<language>None</language> <language>None</language>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount> <novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount> <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/> <autoReplace/>
<titleFormat> <titleFormat>
<title>%title%</title> <entry key="title">%title%</entry>
<chapter>%title%</chapter> <entry key="chapter">%title%</entry>
<unnumbered>%title%</unnumbered> <entry key="unnumbered">%title%</entry>
<scene>* * *</scene> <entry key="scene">* * *</entry>
<section></section> <entry key="section"></entry>
</titleFormat> </titleFormat>
<status> <status>
<entry key="s000008" count="5" red="100" green="100" blue="100">New</entry> <entry key="s000000" 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="s000001" 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="s000002" 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="s000003" count="0" red="50" green="200" blue="0">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="i00000c" count="3" red="100" green="100" blue="100">New</entry> <entry key="i000004" 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="i000005" 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="i000006" 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="i000007" count="0" red="50" green="200" blue="0">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="8"> <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"/> <meta expanded="False"/>
<name status="s000008" import="i00000c">Novel</name> <name status="s000000" import="i000004">Novel</name>
</item> </item>
<item handle="0000000000011" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="0000000000009" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">Title Page</name> <name status="s000000" import="i000004" active="True">Title Page</name>
</item> </item>
<item handle="0000000000012" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="000000000000a" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">New Chapter</name> <name status="s000000" import="i000004" active="True">New Chapter</name>
</item> </item>
<item handle="0000000000013" parent="0000000000012" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="000000000000b" parent="000000000000a" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" active="True">New Scene</name> <name status="s000000" import="i000004" active="True">New Scene</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000008" import="i00000c">Plot</name> <name status="s000000" import="i000004">Plot</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000008" import="i00000c">Characters</name> <name status="s000000" import="i000004">Characters</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000008" import="i00000c">Locations</name> <name status="s000000" import="i000004">Locations</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000008" import="i00000c">Archive</name> <name status="s000000" import="i000004">Archive</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-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> <project>
<name>New Project</name> <name>New Project</name>
<title>New Novel</title> <title>New Novel</title>
@@ -13,20 +13,21 @@
<language>None</language> <language>None</language>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>9</lastWordCount>
<novelWordCount>9</novelWordCount> <novelWordCount>9</novelWordCount>
<notesWordCount>0</notesWordCount> <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/> <autoReplace/>
<titleFormat> <titleFormat>
<title>%title%</title> <entry key="title">%title%</entry>
<chapter>%title%</chapter> <entry key="chapter">%title%</entry>
<unnumbered>%title%</unnumbered> <entry key="unnumbered">%title%</entry>
<scene>* * *</scene> <entry key="scene">* * *</entry>
<section></section> <entry key="section"></entry>
</titleFormat> </titleFormat>
<status> <status>
<entry key="s000000" count="6" red="100" green="100" blue="100">New</entry> <entry key="s000000" count="6" red="100" green="100" blue="100">New</entry>
@@ -59,7 +60,7 @@
<name status="s000000" import="i000004">World</name> <name status="s000000" import="i000004">World</name>
</item> </item>
<item handle="000000000000c" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s000000" import="i000004" active="True">Title Page</name>
</item> </item>
<item handle="000000000000d" parent="0000000000008" root="0000000000008" order="0" type="FOLDER" class="NOVEL"> <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> <name status="s000000" import="i000004">New Chapter</name>
</item> </item>
<item handle="000000000000e" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s000000" import="i000004" active="True">New Chapter</name>
</item> </item>
<item handle="000000000000f" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s000000" import="i000004" active="True">New Scene</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000000" import="i000004">Novel</name> <name status="s000000" import="i000004">Novel</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000000" import="i000004">Plot</name> <name status="s000000" import="i000004">Plot</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000000" import="i000004">Characters</name> <name status="s000000" import="i000004">Characters</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000000" import="i000004">Locations</name> <name status="s000000" import="i000004">Locations</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000000" import="i000004">Timeline</name> <name status="s000000" import="i000004">Timeline</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000000" import="i000004">Objects</name> <name status="s000000" import="i000004">Objects</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000000" import="i000004">Custom</name> <name status="s000000" import="i000004">Custom</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000000" import="i000004">Custom</name> <name status="s000000" import="i000004">Custom</name>
</item> </item>
@@ -1,5 +1,5 @@
%%~name: New Note %%~name: New Note
%%~path: 000000000000a/0000000000020 %%~path: 000000000000a/0000000000010
%%~kind: CHARACTER/NOTE %%~kind: CHARACTER/NOTE
# Jane Doe # Jane Doe
@@ -1,5 +1,5 @@
%%~name: New Note %%~name: New Note
%%~path: 0000000000009/0000000000021 %%~path: 0000000000009/0000000000011
%%~kind: PLOT/NOTE %%~kind: PLOT/NOTE
# Main Plot # Main Plot
@@ -1,5 +1,5 @@
%%~name: New Note %%~name: New Note
%%~path: 000000000000b/0000000000022 %%~path: 000000000000b/0000000000012
%%~kind: WORLD/NOTE %%~kind: WORLD/NOTE
# Main Location # Main Location
@@ -1,32 +1,33 @@
<?xml version='1.0' encoding='utf-8'?> <?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> <project>
<name>New Project</name> <name>New Project</name>
<title>New Novel</title> <title>New Novel</title>
<author>Jane Doe</author> <author>Jane Doe</author>
<saveCount>4</saveCount> <saveCount>4</saveCount>
<autoCount>2</autoCount> <autoCount>2</autoCount>
<editTime>4</editTime> <editTime>3</editTime>
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>True</doBackup>
<language>None</language> <language>None</language>
<spellCheck>True</spellCheck> <spellCheck>True</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<lastEdited>000000000000f</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>0000000000008</lastNovel>
<lastOutline>0000000000008</lastOutline>
<lastWordCount>163</lastWordCount>
<novelWordCount>136</novelWordCount> <novelWordCount>136</novelWordCount>
<notesWordCount>27</notesWordCount> <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/> <autoReplace/>
<titleFormat> <titleFormat>
<title>%title%</title> <entry key="title">%title%</entry>
<chapter>%title%</chapter> <entry key="chapter">%title%</entry>
<unnumbered>%title%</unnumbered> <entry key="unnumbered">%title%</entry>
<scene>* * *</scene> <entry key="scene">* * *</entry>
<section></section> <entry key="section"></entry>
</titleFormat> </titleFormat>
<status> <status>
<entry key="s000000" count="5" red="100" green="100" blue="100">New</entry> <entry key="s000000" count="5" red="100" green="100" blue="100">New</entry>
@@ -47,7 +48,7 @@
<name status="s000000" import="i000004">Novel</name> <name status="s000000" import="i000004">Novel</name>
</item> </item>
<item handle="000000000000c" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s000000" import="i000004" active="True">Title Page</name>
</item> </item>
<item handle="000000000000d" parent="0000000000008" root="0000000000008" order="1" type="FOLDER" class="NOVEL"> <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> <name status="s000000" import="i000004">New Chapter</name>
</item> </item>
<item handle="000000000000e" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s000000" import="i000004" active="True">New Chapter</name>
</item> </item>
<item handle="000000000000f" parent="000000000000d" root="0000000000008" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s000000" import="i000004" active="True">New Scene</name>
</item> </item>
<item handle="0000000000009" parent="None" root="0000000000009" order="1" type="ROOT" class="PLOT"> <item handle="0000000000009" parent="None" root="0000000000009" order="1" type="ROOT" class="PLOT">
<meta expanded="True"/> <meta expanded="True"/>
<name status="s000000" import="i000004">Plot</name> <name status="s000000" import="i000004">Plot</name>
</item> </item>
<item handle="0000000000021" parent="0000000000009" root="0000000000009" order="0" type="FILE" class="PLOT" layout="NOTE"> <item handle="0000000000011" 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"/> <meta expanded="False" heading="H1" charCount="48" wordCount="10" paraCount="1" cursorPos="69"/>
<name status="s000000" import="i000004" active="True">New Note</name> <name status="s000000" import="i000004" active="True">New Note</name>
</item> </item>
<item handle="000000000000a" parent="None" root="000000000000a" order="2" type="ROOT" class="CHARACTER"> <item handle="000000000000a" parent="None" root="000000000000a" order="2" type="ROOT" class="CHARACTER">
<meta expanded="True"/> <meta expanded="True"/>
<name status="s000000" import="i000004">Characters</name> <name status="s000000" import="i000004">Characters</name>
</item> </item>
<item handle="0000000000020" parent="000000000000a" root="000000000000a" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="0000000000010" 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"/> <meta expanded="False" heading="H1" charCount="34" wordCount="8" paraCount="1" cursorPos="51"/>
<name status="s000000" import="i000004" active="True">New Note</name> <name status="s000000" import="i000004" active="True">New Note</name>
</item> </item>
<item handle="000000000000b" parent="None" root="000000000000b" order="3" type="ROOT" class="WORLD"> <item handle="000000000000b" parent="None" root="000000000000b" order="3" type="ROOT" class="WORLD">
<meta expanded="True"/> <meta expanded="True"/>
<name status="s000000" import="i000004">World</name> <name status="s000000" import="i000004">World</name>
</item> </item>
<item handle="0000000000022" parent="000000000000b" root="000000000000b" order="0" type="FILE" class="WORLD" layout="NOTE"> <item handle="0000000000012" 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"/> <meta expanded="False" heading="H1" charCount="51" wordCount="9" paraCount="1" cursorPos="68"/>
<name status="s000000" import="i000004" active="True">New Note</name> <name status="s000000" import="i000004" active="True">New Note</name>
</item> </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"/> <meta expanded="False"/>
<name status="s000000" import="i000004">Trash</name> <name status="s000000" import="i000004">Trash</name>
</item> </item>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?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> <project>
<name>New Project</name> <name>New Project</name>
<title>New Novel</title> <title>New Novel</title>
@@ -13,20 +13,21 @@
<language>None</language> <language>None</language>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>9</lastWordCount>
<novelWordCount>9</novelWordCount> <novelWordCount>9</novelWordCount>
<notesWordCount>0</notesWordCount> <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/> <autoReplace/>
<titleFormat> <titleFormat>
<title>%title%</title> <entry key="title">%title%</entry>
<chapter>%title%</chapter> <entry key="chapter">%title%</entry>
<unnumbered>%title%</unnumbered> <entry key="unnumbered">%title%</entry>
<scene>* * *</scene> <entry key="scene">* * *</entry>
<section></section> <entry key="section"></entry>
</titleFormat> </titleFormat>
<status> <status>
<entry key="s000000" count="5" red="100" green="100" blue="100">New</entry> <entry key="s000000" count="5" red="100" green="100" blue="100">New</entry>
@@ -47,7 +48,7 @@
<name status="s000000" import="i000004">Novel</name> <name status="s000000" import="i000004">Novel</name>
</item> </item>
<item handle="000000000000c" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s000000" import="i000004" active="True">Title Page</name>
</item> </item>
<item handle="000000000000d" parent="0000000000008" root="0000000000008" order="1" type="FOLDER" class="NOVEL"> <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> <name status="s000000" import="i000004">New Chapter</name>
</item> </item>
<item handle="000000000000e" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s000000" import="i000004" active="True">New Chapter</name>
</item> </item>
<item handle="000000000000f" parent="000000000000d" root="0000000000008" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="s000000" import="i000004" active="True">New Scene</name>
</item> </item>
<item handle="0000000000009" parent="None" root="0000000000009" order="1" type="ROOT" class="PLOT"> <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 @pytest.mark.base
def testBaseCommon_CheckString(): 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", "default") == "None"
assert checkString(None, "NotNone") == "NotNone" assert checkString("Text", "default") == "Text"
assert checkString(1, "NotNone") == "NotNone" assert checkString(None, "default") == "default"
assert checkString(1.0, "NotNone") == "NotNone" assert checkString(1, "default") == "default"
assert checkString(True, "NotNone") == "NotNone" assert checkString(1.0, "default") == "default"
assert checkString(True, "default") == "default"
# END Test testBaseCommon_CheckString # END Test testBaseCommon_CheckString
@pytest.mark.base @pytest.mark.base
def testBaseCommon_CheckInt(): 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, 3) == 1
assert checkInt(1.0, 3) == 1 assert checkInt(1.0, 3) == 1
assert checkInt(True, 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 # END Test testBaseCommon_CheckInt
@pytest.mark.base @pytest.mark.base
def testBaseCommon_CheckFloat(): 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, 3.0) == 1.0
assert checkFloat(1.0, 3.0) == 1.0 assert checkFloat(1.0, 3.0) == 1.0
assert checkFloat(True, 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 # END Test testBaseCommon_CheckInt
@pytest.mark.base @pytest.mark.base
def testBaseCommon_CheckBool(): 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("True", False) is True
assert checkBool("False", True) is False assert checkBool("False", True) is False
assert checkBool("Boo", False) is False assert checkBool("Boo", False) is False
assert checkBool("Boo", True) is True 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(0, True) is False
assert checkBool(1, False) is True assert checkBool(1, False) is True
assert checkBool(2, True) is True assert checkBool(2, True) is True
+18 -23
View File
@@ -23,7 +23,7 @@ import os
import pytest import pytest
from mock import causeOSError from mock import causeOSError
from tools import readFile, writeFile from tools import C, buildTestProject, readFile, writeFile
from novelwriter.enum import nwItemClass, nwItemLayout from novelwriter.enum import nwItemClass, nwItemLayout
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
@@ -31,14 +31,12 @@ from novelwriter.core.document import NWDoc
@pytest.mark.core @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. """Test loading and saving a document with the NWDoc class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
assert theProject.openProject(nwMinimal) is True mockRnd.reset()
assert theProject.projPath == nwMinimal buildTestProject(theProject, fncDir)
sHandle = "8c659a11cd429"
# Read Document # Read Document
# ============= # =============
@@ -49,25 +47,23 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal):
assert theDoc.readDocument() is None assert theDoc.readDocument() is None
# Non-existent handle # Non-existent handle
theDoc = NWDoc(theProject, "0000000000000") theDoc = NWDoc(theProject, C.hInvalid)
assert theDoc.readDocument() is None assert theDoc.readDocument() is None
assert theDoc._currHash is None assert theDoc._currHash is None
# Cause open() to fail while loading # Cause open() to fail while loading
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError) mp.setattr("builtins.open", causeOSError)
theDoc = NWDoc(theProject, sHandle) theDoc = NWDoc(theProject, C.hSceneDoc)
assert theDoc.readDocument() is None assert theDoc.readDocument() is None
assert theDoc.getError() == "OSError: Mock OSError" assert theDoc.getError() == "OSError: Mock OSError"
# Load the text # Load the text
theDoc = NWDoc(theProject, sHandle) theDoc = NWDoc(theProject, C.hSceneDoc)
assert theDoc.readDocument() == "### New Scene\n\n" assert theDoc.readDocument() == "### New Scene\n\n"
# Try to open a new (non-existent) file # Try to open a new (non-existent) file
nHandle = theProject.tree.findRoot(nwItemClass.NOVEL) xHandle = theProject.newFile("New File", C.hNovelRoot)
assert nHandle is not None
xHandle = theProject.newFile("New File", nHandle)
theDoc = NWDoc(theProject, xHandle) theDoc = NWDoc(theProject, xHandle)
assert bool(theDoc) is True assert bool(theDoc) is True
assert repr(theDoc) == f"<NWDoc handle={xHandle}>" assert repr(theDoc) == f"<NWDoc handle={xHandle}>"
@@ -86,10 +82,10 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal):
assert theDoc.writeDocument(theText) assert theDoc.writeDocument(theText)
# Check file content # Check file content
docPath = os.path.join(nwMinimal, "content", xHandle+".nwd") docPath = os.path.join(fncDir, "content", xHandle+".nwd")
assert readFile(docPath) == ( assert readFile(docPath) == (
"%%~name: New File\n" "%%~name: New File\n"
f"%%~path: a508bb932959c/{xHandle}\n" f"%%~path: {C.hNovelRoot}/{xHandle}\n"
"%%~kind: NOVEL/DOCUMENT\n" "%%~kind: NOVEL/DOCUMENT\n"
"### Test File\n\n" "### Test File\n\n"
"Text ...\n\n" "Text ...\n\n"
@@ -153,16 +149,15 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal):
@pytest.mark.core @pytest.mark.core
def testCoreDocument_Methods(mockGUI, nwMinimal): def testCoreDocument_Methods(mockGUI, fncDir, mockRnd):
"""Test other methods of the NWDoc class. """Test other methods of the NWDoc class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
assert theProject.openProject(nwMinimal) mockRnd.reset()
assert theProject.projPath == nwMinimal buildTestProject(theProject, fncDir)
sHandle = "8c659a11cd429" theDoc = NWDoc(theProject, C.hSceneDoc)
theDoc = NWDoc(theProject, sHandle) docPath = os.path.join(fncDir, "content", C.hSceneDoc+".nwd")
docPath = os.path.join(nwMinimal, "content", sHandle+".nwd")
assert theDoc.readDocument() == "### New Scene\n\n" assert theDoc.readDocument() == "### New Scene\n\n"
@@ -171,12 +166,12 @@ def testCoreDocument_Methods(mockGUI, nwMinimal):
# Check the item # Check the item
assert theDoc.getCurrentItem() is not None assert theDoc.getCurrentItem() is not None
assert theDoc.getCurrentItem().itemHandle == sHandle assert theDoc.getCurrentItem().itemHandle == C.hSceneDoc
# Check the meta # Check the meta
theName, theParent, theClass, theLayout = theDoc.getMeta() theName, theParent, theClass, theLayout = theDoc.getMeta()
assert theName == "New Scene" assert theName == "New Scene"
assert theParent == "a6d311a93600a" assert theParent == C.hChapterDir
assert theClass == nwItemClass.NOVEL assert theClass == nwItemClass.NOVEL
assert theLayout == nwItemLayout.DOCUMENT 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 theDoc.writeDocument("%%~ stuff\n### Test File\n\nText ...\n\n")
assert readFile(docPath) == ( assert readFile(docPath) == (
"%%~name: New Scene\n" "%%~name: New Scene\n"
f"%%~path: a6d311a93600a/{sHandle}\n" f"%%~path: {C.hChapterDir}/{C.hSceneDoc}\n"
"%%~kind: NOVEL/DOCUMENT\n" "%%~kind: NOVEL/DOCUMENT\n"
"%%~ stuff\n" "%%~ stuff\n"
"### Test File\n\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 import pytest
from lxml import etree
from PyQt5.QtGui import QIcon from PyQt5.QtGui import QIcon
from tools import C, buildTestProject
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
@pytest.mark.core @pytest.mark.core
def testCoreItem_Setters(mockGUI, mockRnd): def testCoreItem_Setters(mockGUI, mockRnd, fncDir):
"""Test all the simple setters for the NWItem class. """Test all the simple setters for the NWItem class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
mockRnd.reset()
buildTestProject(theProject, fncDir)
theItem = NWItem(theProject) theItem = NWItem(theProject)
statusKeys = ["s000000", "s000001", "s000002", "s000003"] statusKeys = ["s000000", "s000001", "s000002", "s000003"]
@@ -153,7 +155,7 @@ def testCoreItem_Setters(mockGUI, mockRnd):
theItem.setCharCount(None) theItem.setCharCount(None)
assert theItem.charCount == 0 assert theItem.charCount == 0
theItem.setCharCount("1") theItem.setCharCount("1")
assert theItem.charCount == 1 assert theItem.charCount == 0
theItem.setCharCount(1) theItem.setCharCount(1)
assert theItem.charCount == 1 assert theItem.charCount == 1
@@ -161,7 +163,7 @@ def testCoreItem_Setters(mockGUI, mockRnd):
theItem.setWordCount(None) theItem.setWordCount(None)
assert theItem.wordCount == 0 assert theItem.wordCount == 0
theItem.setWordCount("1") theItem.setWordCount("1")
assert theItem.wordCount == 1 assert theItem.wordCount == 0
theItem.setWordCount(1) theItem.setWordCount(1)
assert theItem.wordCount == 1 assert theItem.wordCount == 1
@@ -169,7 +171,7 @@ def testCoreItem_Setters(mockGUI, mockRnd):
theItem.setParaCount(None) theItem.setParaCount(None)
assert theItem.paraCount == 0 assert theItem.paraCount == 0
theItem.setParaCount("1") theItem.setParaCount("1")
assert theItem.paraCount == 1 assert theItem.paraCount == 0
theItem.setParaCount(1) theItem.setParaCount(1)
assert theItem.paraCount == 1 assert theItem.paraCount == 1
@@ -177,7 +179,7 @@ def testCoreItem_Setters(mockGUI, mockRnd):
theItem.setCursorPos(None) theItem.setCursorPos(None)
assert theItem.cursorPos == 0 assert theItem.cursorPos == 0
theItem.setCursorPos("1") theItem.setCursorPos("1")
assert theItem.cursorPos == 1 assert theItem.cursorPos == 0
theItem.setCursorPos(1) theItem.setCursorPos(1)
assert theItem.cursorPos == 1 assert theItem.cursorPos == 1
@@ -190,10 +192,12 @@ def testCoreItem_Setters(mockGUI, mockRnd):
@pytest.mark.core @pytest.mark.core
def testCoreItem_Methods(mockGUI): def testCoreItem_Methods(mockGUI, mockRnd, fncDir):
"""Test the simple methods of the NWItem class. """Test the simple methods of the NWItem class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
mockRnd.reset()
buildTestProject(theProject, fncDir)
theItem = NWItem(theProject) theItem = NWItem(theProject)
# Describe Me # Describe Me
@@ -250,15 +254,15 @@ def testCoreItem_Methods(mockGUI):
# ============= # =============
theItem.setType("FILE") theItem.setType("FILE")
theItem.setStatus("Note") theItem.setStatus(C.sNote)
theItem.setImport("Minor") theItem.setImport(C.iMinor)
theItem.setClass("NOVEL") theItem.setClass("NOVEL")
stT, stI = theItem.getImportStatus() stT, stI = theItem.getImportStatus()
assert stT == "Note" assert stT == "Note"
assert isinstance(stI, QIcon) assert isinstance(stI, QIcon)
theItem.setImportStatus("Draft") theItem.setImportStatus(C.sDraft)
stT, stI = theItem.getImportStatus() stT, stI = theItem.getImportStatus()
assert stT == "Draft" assert stT == "Draft"
@@ -267,7 +271,7 @@ def testCoreItem_Methods(mockGUI):
assert stT == "Minor" assert stT == "Minor"
assert isinstance(stI, QIcon) assert isinstance(stI, QIcon)
theItem.setImportStatus("Major") theItem.setImportStatus(C.iMajor)
stT, stI = theItem.getImportStatus() stT, stI = theItem.getImportStatus()
assert stT == "Major" assert stT == "Major"
@@ -491,254 +495,199 @@ def testCoreItem_ClassDefaults(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreItem_XMLPackUnpack(mockGUI, caplog, mockRnd): def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
"""Test packing and unpacking XML objects for the NWItem class. """Test packing and unpacking entries for the NWItem class.
""" """
theProject = NWProject(mockGUI) 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"] # Invalid
importKeys = ["i000004", "i000005", "i000006", "i000007"] theItem = NWItem(theProject)
assert theItem.unpack({}) is False
# File # File
# ====
theItem = NWItem(theProject) theItem = NWItem(theProject)
theItem.setHandle("0123456789abc") assert theItem.unpack({
theItem.setParent("0123456789abc") "label": "A File",
theItem.setRoot("0123456789abc") "handle": "0000000000003",
theItem.setOrder(1) "parent": "0000000000002",
theItem.setName("A Name") "root": "0000000000001",
theItem.setClass("NOVEL") "order": 1,
theItem.setType("FILE") "type": "FILE",
theItem.setImport(importKeys[3]) "class": "NOVEL",
theItem.setLayout("NOTE") "layout": "DOCUMENT",
theItem.setActive(False) "expanded": True,
theItem.setParaCount(3) "status": None,
theItem.setWordCount(5) "import": None,
theItem.setCharCount(7) "heading": "H1",
theItem.setCursorPos(11) "charCount": 100,
"wordCount": 20,
"paraCount": 2,
"cursorPos": 50,
"active": False,
}) is True
# Pack assert theItem.itemName == "A File"
xContent = etree.SubElement(nwXML, "content") assert theItem.itemHandle == "0000000000003"
theItem.packXML(xContent) assert theItem.itemParent == "0000000000002"
assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == ( assert theItem.itemRoot == "0000000000001"
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.itemOrder == 1 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.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.isExpanded is True
assert theItem.isActive is True assert theItem.mainHeading == "H1"
assert theItem.paraCount == 0 assert theItem.charCount == 100
assert theItem.wordCount == 0 assert theItem.wordCount == 20
assert theItem.charCount == 0 assert theItem.paraCount == 2
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.cursorPos == 50 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.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FILE assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
assert theItem.itemLayout == nwItemLayout.DOCUMENT 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 assert theItem.pack() == {
theItem.setType("TRASH") "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.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 os
import shutil
import pytest import pytest
from lxml import etree
from shutil import copyfile from shutil import copyfile
from zipfile import ZipFile from zipfile import ZipFile
from tools import cmpFiles, writeFile, readFile, buildTestProject, XML_IGNORE, C
from mock import causeOSError from mock import causeOSError
from tools import cmpFiles, writeFile, readFile, buildTestProject, XML_IGNORE, C
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
from novelwriter.common import formatTimeStamp from novelwriter.common import formatTimeStamp
@@ -37,6 +37,7 @@ from novelwriter.core.index import NWIndex
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.core.options import OptionState from novelwriter.core.options import OptionState
from novelwriter.core.document import NWDoc from novelwriter.core.document import NWDoc
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
@pytest.mark.core @pytest.mark.core
@@ -202,7 +203,7 @@ def testCoreProject_NewSampleA(fncDir, tmpConf, mockGUI, tmpDir):
assert theProject.newProject(projData) is True assert theProject.newProject(projData) is True
assert theProject.openProject(fncDir) 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.saveProject() is True
assert theProject.closeProject() is True assert theProject.closeProject() is True
os.unlink(dstSample) os.unlink(dstSample)
@@ -236,7 +237,7 @@ def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, mockGUI, tmpDir):
monkeypatch.setattr(nwFiles, "PROJ_FILE", "nwProject.nwx") monkeypatch.setattr(nwFiles, "PROJ_FILE", "nwProject.nwx")
assert theProject.newProject(projData) is True assert theProject.newProject(projData) is True
assert theProject.openProject(fncDir) 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.saveProject() is True
assert theProject.closeProject() 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.closeProject() is True
assert theProject.openProject(projFile) is True assert theProject.openProject(projFile) is True
assert theProject.newRoot(nwItemClass.NOVEL) == "0000000000020" assert theProject.newRoot(nwItemClass.NOVEL) == "0000000000010"
assert theProject.newRoot(nwItemClass.PLOT) == "0000000000021" assert theProject.newRoot(nwItemClass.PLOT) == "0000000000011"
assert theProject.newRoot(nwItemClass.CHARACTER) == "0000000000022" assert theProject.newRoot(nwItemClass.CHARACTER) == "0000000000012"
assert theProject.newRoot(nwItemClass.WORLD) == "0000000000023" assert theProject.newRoot(nwItemClass.WORLD) == "0000000000013"
assert theProject.newRoot(nwItemClass.TIMELINE) == "0000000000024" assert theProject.newRoot(nwItemClass.TIMELINE) == "0000000000014"
assert theProject.newRoot(nwItemClass.OBJECT) == "0000000000025" assert theProject.newRoot(nwItemClass.OBJECT) == "0000000000015"
assert theProject.newRoot(nwItemClass.CUSTOM) == "0000000000026" assert theProject.newRoot(nwItemClass.CUSTOM) == "0000000000016"
assert theProject.newRoot(nwItemClass.CUSTOM) == "0000000000027" assert theProject.newRoot(nwItemClass.CUSTOM) == "0000000000017"
assert theProject.projChanged is True assert theProject.projChanged is True
assert theProject.saveProject() is True assert theProject.saveProject() is True
@@ -282,23 +283,23 @@ def testCoreProject_NewRoot(fncDir, outDir, refDir, mockGUI, mockRnd):
assert theProject.projChanged is False assert theProject.projChanged is False
# Delete the new items # Delete the new items
assert theProject.removeItem("0000000000020") is True assert theProject.removeItem("0000000000010") is True
assert theProject.removeItem("0000000000021") is True assert theProject.removeItem("0000000000011") is True
assert theProject.removeItem("0000000000022") is True assert theProject.removeItem("0000000000012") is True
assert theProject.removeItem("0000000000023") is True assert theProject.removeItem("0000000000013") is True
assert theProject.removeItem("0000000000024") is True assert theProject.removeItem("0000000000014") is True
assert theProject.removeItem("0000000000025") is True assert theProject.removeItem("0000000000015") is True
assert theProject.removeItem("0000000000026") is True assert theProject.removeItem("0000000000016") is True
assert theProject.removeItem("0000000000027") is True assert theProject.removeItem("0000000000017") is True
assert "0000000000020" not in theProject.tree assert "0000000000010" not in theProject.tree
assert "0000000000021" not in theProject.tree assert "0000000000011" not in theProject.tree
assert "0000000000022" not in theProject.tree assert "0000000000012" not in theProject.tree
assert "0000000000023" not in theProject.tree assert "0000000000013" not in theProject.tree
assert "0000000000024" not in theProject.tree assert "0000000000014" not in theProject.tree
assert "0000000000025" not in theProject.tree assert "0000000000015" not in theProject.tree
assert "0000000000026" not in theProject.tree assert "0000000000016" not in theProject.tree
assert "0000000000027" not in theProject.tree assert "0000000000017" not in theProject.tree
# END Test testCoreProject_NewRoot # END Test testCoreProject_NewRoot
@@ -325,26 +326,26 @@ def testCoreProject_NewFileFolder(monkeypatch, fncDir, outDir, refDir, mockGUI,
assert theProject.newFile("New File", "1234567890abc") is None assert theProject.newFile("New File", "1234567890abc") is None
# Add files properly # Add files properly
assert theProject.newFolder("Stuff", C.hNovelRoot) == "0000000000020" assert theProject.newFolder("Stuff", C.hNovelRoot) == "0000000000010"
assert theProject.newFile("Hello", "0000000000020") == "0000000000021" assert theProject.newFile("Hello", "0000000000010") == "0000000000011"
assert theProject.newFile("Jane", C.hCharRoot) == "0000000000022" assert theProject.newFile("Jane", C.hCharRoot) == "0000000000012"
assert "0000000000020" in theProject.tree assert "0000000000010" in theProject.tree
assert "0000000000021" in theProject.tree assert "0000000000011" in theProject.tree
assert "0000000000022" in theProject.tree assert "0000000000012" in theProject.tree
# Write to file, failed # Write to file, failed
assert theProject.writeNewFile("blabla", 1, True) is False # Not a handle 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 assert theProject.writeNewFile(C.hTitlePage, 1, True) is False # Already has content
# Write to file, success # Write to file, success
assert theProject.writeNewFile("0000000000021", 2, True) is True assert theProject.writeNewFile("0000000000011", 2, True) is True
assert NWDoc(theProject, "0000000000021").readDocument() == "## Hello\n\n" assert NWDoc(theProject, "0000000000011").readDocument() == "## Hello\n\n"
# Write to file with additional text, success # Write to file with additional text, success
assert theProject.writeNewFile("0000000000022", 1, False, "Hi Jane\n\n") is True assert theProject.writeNewFile("0000000000012", 1, False, "Hi Jane\n\n") is True
assert NWDoc(theProject, "0000000000022").readDocument() == "# Jane\n\nHi Jane\n\n" assert NWDoc(theProject, "0000000000012").readDocument() == "# Jane\n\nHi Jane\n\n"
# Save, close and check # Save, close and check
assert theProject.projChanged is True assert theProject.projChanged is True
@@ -357,23 +358,23 @@ def testCoreProject_NewFileFolder(monkeypatch, fncDir, outDir, refDir, mockGUI,
# Delete new file, but block access # Delete new file, but block access
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("os.unlink", causeOSError) mp.setattr("os.unlink", causeOSError)
assert theProject.removeItem("0000000000021") is False assert theProject.removeItem("0000000000011") is False
assert "0000000000021" in theProject.tree assert "0000000000011" in theProject.tree
# Delete new files and folders # 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", "0000000000012.nwd"))
assert os.path.isfile(os.path.join(fncDir, "content", "0000000000021.nwd")) assert os.path.isfile(os.path.join(fncDir, "content", "0000000000011.nwd"))
assert theProject.removeItem("0000000000022") is True assert theProject.removeItem("0000000000012") is True
assert theProject.removeItem("0000000000021") is True assert theProject.removeItem("0000000000011") is True
assert theProject.removeItem("0000000000020") 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", "0000000000012.nwd"))
assert not os.path.isfile(os.path.join(fncDir, "content", "0000000000021.nwd")) assert not os.path.isfile(os.path.join(fncDir, "content", "0000000000011.nwd"))
assert "0000000000020" not in theProject.tree assert "0000000000010" not in theProject.tree
assert "0000000000021" not in theProject.tree assert "0000000000011" not in theProject.tree
assert "0000000000022" not in theProject.tree assert "0000000000012" not in theProject.tree
assert theProject.closeProject() is True assert theProject.closeProject() is True
@@ -381,139 +382,90 @@ def testCoreProject_NewFileFolder(monkeypatch, fncDir, outDir, refDir, mockGUI,
@pytest.mark.core @pytest.mark.core
def testCoreProject_Open(monkeypatch, nwMinimal, mockGUI): def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncDir, mockRnd):
"""Test opening a project. """Test opening a project.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
mockRnd.reset()
buildTestProject(theProject, fncDir)
# Rename the project file to check handling # Rename the project file to check handling
rName = os.path.join(nwMinimal, nwFiles.PROJ_FILE) rName = os.path.join(fncDir, nwFiles.PROJ_FILE)
wName = os.path.join(nwMinimal, nwFiles.PROJ_FILE+"_sdfghj") wName = os.path.join(fncDir, nwFiles.PROJ_FILE+"_sdfghj")
os.rename(rName, wName) os.rename(rName, wName)
assert theProject.openProject(nwMinimal) is False assert theProject.openProject(fncDir) is False
os.rename(wName, rName) os.rename(wName, rName)
# Fail on folder structure check # Fail on folder structure check
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("os.mkdir", causeOSError) 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 # Fail on lock file
theProject.setProjectPath(nwMinimal) theProject.setProjectPath(fncDir)
assert theProject._writeLockFile() assert theProject._writeLockFile()
assert theProject.openProject(nwMinimal) is False assert theProject.openProject(fncDir) is False
# Fail to read lockfile (which still opens the project) # Fail to read lockfile (which still opens the project)
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError) 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() assert theProject.closeProject()
# Force open with lockfile # Force open with lockfile
theProject.setProjectPath(nwMinimal) theProject.setProjectPath(fncDir)
assert theProject._writeLockFile() assert theProject._writeLockFile()
assert theProject.openProject(nwMinimal, overrideLock=True) is True assert theProject.openProject(fncDir, overrideLock=True) is True
assert theProject.closeProject() assert theProject.closeProject()
# Make a junk XML file # Not a novelwriter XML file
oName = os.path.join(nwMinimal, nwFiles.PROJ_FILE[:-3]+"orig") with monkeypatch.context() as mp:
bName = os.path.join(nwMinimal, nwFiles.PROJ_FILE[:-3]+"bak") mp.setattr(ProjectXMLReader, "read", lambda *a: False)
os.rename(rName, oName) mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.NOT_NWX_FILE))
writeFile(rName, "stuff") assert theProject.openProject(fncDir) is False
assert theProject.openProject(nwMinimal) is False assert "Project file does not appear" in mockGUI.lastAlert
# Also write a jun XML backup file # Unknown project file version
writeFile(bName, "stuff") with monkeypatch.context() as mp:
assert theProject.openProject(nwMinimal) is False 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 # Other parse error
writeFile(rName, "<not_novelWriterXML></not_novelWriterXML>\n") with monkeypatch.context() as mp:
assert theProject.openProject(nwMinimal) is False 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 # Won't convert legacy file
writeFile(rName, ( with monkeypatch.context() as mp:
"<?xml version='0.0' encoding='utf-8'?>\n" mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY))
"<novelWriterXML " mockGUI.askResponse = False
"appVersion=\"1.0\" " assert theProject.openProject(fncDir) is False
"hexVersion=\"0x01000000\" " assert "The file format of your project is about to be" in mockGUI.lastQuestion[1]
"fileVersion=\"1.0\" " mockGUI.askResponse = True
"timeStamp=\"2020-01-01 00:00:00\">\n"
"</novelWriterXML>\n"
))
mockGUI.askResponse = False
assert theProject.openProject(nwMinimal) is False
mockGUI.undo()
# Future file version # Won't convert legacy file
writeFile(rName, ( with monkeypatch.context() as mp:
"<?xml version='1.0' encoding='utf-8'?>\n" mp.setattr(ProjectXMLReader, "hexVersion", property(lambda *a: "0x99999999"))
"<novelWriterXML " mockGUI.askResponse = False
"appVersion=\"1.0\" " assert theProject.openProject(fncDir) is False
"hexVersion=\"0x01000000\" " assert "This project was saved by a newer version" in mockGUI.lastQuestion[1]
"fileVersion=\"99.99\" " mockGUI.askResponse = True
"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)
# Add some legacy stuff that cannot be removed # Add some legacy stuff that cannot be removed
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(theProject, "_legacyDataFolder", causeOSError) mp.setattr(theProject, "_legacyDataFolder", causeOSError)
os.mkdir(os.path.join(nwMinimal, "data_0")) os.mkdir(os.path.join(fncDir, "data_0"))
writeFile(os.path.join(nwMinimal, "data_0", "123456789abc_main.nwd"), "stuff") writeFile(os.path.join(fncDir, "data_0", "123456789abc_main.nwd"), "stuff")
writeFile(os.path.join(nwMinimal, "data_0", "123456789abc_main.bak"), "stuff") writeFile(os.path.join(fncDir, "data_0", "123456789abc_main.bak"), "stuff")
mockGUI.clear() 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 "There was an error updating the project." in mockGUI.lastAlert
assert theProject.closeProject() assert theProject.closeProject()
@@ -522,56 +474,31 @@ def testCoreProject_Open(monkeypatch, nwMinimal, mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreProject_Save(monkeypatch, nwMinimal, mockGUI, refDir): def testCoreProject_Save(monkeypatch, mockGUI, mockRnd, fncDir, refDir):
"""Test saving a project. """Test saving a project.
""" """
theProject = NWProject(mockGUI) 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 # Nothing to save
assert theProject.saveProject() is False assert theProject.saveProject() is False
# Open test project mockRnd.reset()
assert theProject.openProject(nwMinimal) buildTestProject(theProject, fncDir)
# Fail on folder structure check # Fail on folder structure check
with monkeypatch.context() as mp: 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 assert theProject.saveProject() is False
# Fail on open file # Fail writing
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError) mp.setattr(ProjectXMLWriter, "write", lambda *a: False)
assert theProject.saveProject() is False assert theProject.saveProject() is False
# Fail on creating .bak file # Save with and without autosave
with monkeypatch.context() as mp: assert theProject.saveProject(autoSave=False) is True
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
assert theProject.saveProject(autoSave=True) 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() assert theProject.closeProject()
# END Test testCoreProject_Save # END Test testCoreProject_Save
@@ -682,11 +609,11 @@ def testCoreProject_Helpers(monkeypatch, fncDir, mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreProject_AccessItems(nwMinimal, mockGUI): def testCoreProject_AccessItems(mockGUI, fncDir, mockRnd):
"""Test helper functions for the project folder. """Test helper functions for the project folder.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theProject.openProject(nwMinimal) buildTestProject(theProject, fncDir)
# Storage Objects # Storage Objects
assert isinstance(theProject.index, NWIndex) assert isinstance(theProject.index, NWIndex)
@@ -695,34 +622,34 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI):
# Move Novel ROOT to after its files # Move Novel ROOT to after its files
oldOrder = [ oldOrder = [
"a508bb932959c", # ROOT: Novel C.hNovelRoot,
"a35baf2e93843", # FILE: Title Page C.hPlotRoot,
"a6d311a93600a", # FOLDER: New Chapter C.hCharRoot,
"f5ab3e30151e1", # FILE: New Chapter C.hWorldRoot,
"8c659a11cd429", # FILE: New Scene C.hTitlePage,
"7695ce551d265", # ROOT: Plot C.hChapterDir,
"afb3043c7b2b3", # ROOT: Characters C.hChapterDoc,
"9d5247ab588e0", # ROOT: World C.hSceneDoc,
] ]
newOrder = [ newOrder = [
"a35baf2e93843", # FILE: Title Page C.hTitlePage,
"f5ab3e30151e1", # FILE: New Chapter C.hChapterDoc,
"8c659a11cd429", # FILE: New Scene C.hSceneDoc,
"a6d311a93600a", # FOLDER: New Chapter C.hChapterDir,
"a508bb932959c", # ROOT: Novel C.hNovelRoot,
"7695ce551d265", # ROOT: Plot C.hPlotRoot,
"afb3043c7b2b3", # ROOT: Characters C.hCharRoot,
"9d5247ab588e0", # ROOT: World C.hWorldRoot,
] ]
assert theProject.tree.handles() == oldOrder assert theProject.tree.handles() == oldOrder
assert theProject.setTreeOrder(newOrder) assert theProject.setTreeOrder(newOrder)
assert theProject.tree.handles() == newOrder assert theProject.tree.handles() == newOrder
# Add a non-existing item # Add a non-existing item
theProject.tree._treeOrder.append("01234567789abc") theProject.tree._treeOrder.append(C.hInvalid)
# Add an item with a non-existent parent # 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") theProject.tree[nHandle].setParent("cba9876543210")
assert theProject.tree[nHandle].itemParent == "cba9876543210" assert theProject.tree[nHandle].itemParent == "cba9876543210"
@@ -731,15 +658,15 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI):
retOrder.append(tItem.itemHandle) retOrder.append(tItem.itemHandle)
assert retOrder == [ assert retOrder == [
"a508bb932959c", # ROOT: Novel C.hNovelRoot,
"7695ce551d265", # ROOT: Plot C.hPlotRoot,
"afb3043c7b2b3", # ROOT: Characters C.hCharRoot,
"9d5247ab588e0", # ROOT: World C.hWorldRoot,
nHandle, # FILE: Test File nHandle,
"a35baf2e93843", # FILE: Title Page C.hTitlePage,
"a6d311a93600a", # FOLDER: New Chapter C.hChapterDir,
"f5ab3e30151e1", # FILE: New Chapter C.hChapterDoc,
"8c659a11cd429", # FILE: New Scene C.hSceneDoc,
] ]
assert theProject.tree[nHandle].itemParent is None assert theProject.tree[nHandle].itemParent is None
@@ -751,23 +678,24 @@ def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd):
"""Test the status and importance flag handling. """Test the status and importance flag handling.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
mockRnd.reset()
buildTestProject(theProject, fncDir) buildTestProject(theProject, fncDir)
statusKeys = ["s000008", "s000009", "s00000a", "s00000b"] statusKeys = [C.sNew, C.sNote, C.sDraft, C.sFinished]
importKeys = ["i00000c", "i00000d", "i00000e", "i00000f"] importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain]
# Change Status # Change Status
# ============= # =============
theProject.tree["0000000000014"].setStatus("Finished") theProject.tree[C.hNovelRoot].setStatus(statusKeys[3])
theProject.tree["0000000000015"].setStatus("Draft") theProject.tree[C.hPlotRoot].setStatus(statusKeys[2])
theProject.tree["0000000000016"].setStatus("Note") theProject.tree[C.hCharRoot].setStatus(statusKeys[1])
theProject.tree["0000000000017"].setStatus("Finished") theProject.tree[C.hWorldRoot].setStatus(statusKeys[3])
assert theProject.tree["0000000000014"].itemStatus == statusKeys[3] assert theProject.tree[C.hNovelRoot].itemStatus == statusKeys[3]
assert theProject.tree["0000000000015"].itemStatus == statusKeys[2] assert theProject.tree[C.hPlotRoot].itemStatus == statusKeys[2]
assert theProject.tree["0000000000016"].itemStatus == statusKeys[1] assert theProject.tree[C.hCharRoot].itemStatus == statusKeys[1]
assert theProject.tree["0000000000017"].itemStatus == statusKeys[3] assert theProject.tree[C.hWorldRoot].itemStatus == statusKeys[3]
newList = [ newList = [
{"key": statusKeys[0], "name": "New", "cols": (1, 1, 1)}, {"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([], []) is False
assert theProject.setStatusColours(newList, []) is True assert theProject.setStatusColours(newList, []) is True
assert theProject.statusItems.name(statusKeys[0]) == "New" assert theProject.data.itemStatus.name(statusKeys[0]) == "New"
assert theProject.statusItems.name(statusKeys[1]) == "Draft" assert theProject.data.itemStatus.name(statusKeys[1]) == "Draft"
assert theProject.statusItems.name(statusKeys[2]) == "Note" assert theProject.data.itemStatus.name(statusKeys[2]) == "Note"
assert theProject.statusItems.name(statusKeys[3]) == "Edited" assert theProject.data.itemStatus.name(statusKeys[3]) == "Edited"
assert theProject.statusItems.cols(statusKeys[0]) == (1, 1, 1) assert theProject.data.itemStatus.cols(statusKeys[0]) == (1, 1, 1)
assert theProject.statusItems.cols(statusKeys[1]) == (2, 2, 2) assert theProject.data.itemStatus.cols(statusKeys[1]) == (2, 2, 2)
assert theProject.statusItems.cols(statusKeys[2]) == (3, 3, 3) assert theProject.data.itemStatus.cols(statusKeys[2]) == (3, 3, 3)
assert theProject.statusItems.cols(statusKeys[3]) == (4, 4, 4) assert theProject.data.itemStatus.cols(statusKeys[3]) == (4, 4, 4)
# Check the new entry # Check the new entry
lastKey = theProject.statusItems.check("Finished") lastKey = theProject.data.itemStatus.check("s000010")
assert lastKey == "s000018" assert lastKey == "s000010"
assert theProject.statusItems.name(lastKey) == "Finished" assert theProject.data.itemStatus.name(lastKey) == "Finished"
assert theProject.statusItems.cols(lastKey) == (5, 5, 5) assert theProject.data.itemStatus.cols(lastKey) == (5, 5, 5)
# Delete last entry # Delete last entry
assert theProject.setStatusColours([], [lastKey]) is True assert theProject.setStatusColours([], [lastKey]) is True
assert theProject.statusItems.name(lastKey) == "New" assert theProject.data.itemStatus.name(lastKey) == "New"
# Change Importance # Change Importance
# ================= # =================
fHandle = theProject.newFile("Jane Doe", "0000000000012") fHandle = theProject.newFile("Jane Doe", C.hCharRoot)
theProject.tree[fHandle].setImport("Main") theProject.tree[fHandle].setImport(importKeys[3])
assert theProject.tree[fHandle].itemImport == importKeys[3] assert theProject.tree[fHandle].itemImport == importKeys[3]
newList = [ newList = [
@@ -817,53 +745,41 @@ def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd):
assert theProject.setImportColours([], []) is False assert theProject.setImportColours([], []) is False
assert theProject.setImportColours(newList, []) is True assert theProject.setImportColours(newList, []) is True
assert theProject.importItems.name(importKeys[0]) == "New" assert theProject.data.itemImport.name(importKeys[0]) == "New"
assert theProject.importItems.name(importKeys[1]) == "Minor" assert theProject.data.itemImport.name(importKeys[1]) == "Minor"
assert theProject.importItems.name(importKeys[2]) == "Major" assert theProject.data.itemImport.name(importKeys[2]) == "Major"
assert theProject.importItems.name(importKeys[3]) == "Min" assert theProject.data.itemImport.name(importKeys[3]) == "Min"
assert theProject.importItems.cols(importKeys[0]) == (1, 1, 1) assert theProject.data.itemImport.cols(importKeys[0]) == (1, 1, 1)
assert theProject.importItems.cols(importKeys[1]) == (2, 2, 2) assert theProject.data.itemImport.cols(importKeys[1]) == (2, 2, 2)
assert theProject.importItems.cols(importKeys[2]) == (3, 3, 3) assert theProject.data.itemImport.cols(importKeys[2]) == (3, 3, 3)
assert theProject.importItems.cols(importKeys[3]) == (4, 4, 4) assert theProject.data.itemImport.cols(importKeys[3]) == (4, 4, 4)
# Check the new entry # Check the new entry
lastKey = theProject.importItems.check("Max") lastKey = theProject.data.itemImport.check("i000012")
assert lastKey == "i00001a" assert lastKey == "i000012"
assert theProject.importItems.name(lastKey) == "Max" assert theProject.data.itemImport.name(lastKey) == "Max"
assert theProject.importItems.cols(lastKey) == (5, 5, 5) assert theProject.data.itemImport.cols(lastKey) == (5, 5, 5)
# Delete last entry # Delete last entry
assert theProject.setImportColours([], [lastKey]) is True assert theProject.setImportColours([], [lastKey]) is True
assert theProject.importItems.name(lastKey) == "New" assert theProject.data.itemImport.name(lastKey) == "New"
# Delete Status/Import # Delete Status/Import
# ==================== # ====================
theProject.statusItems.resetCounts() theProject.data.itemStatus.resetCounts()
for key in list(theProject.statusItems.keys()): for key in list(theProject.data.itemStatus.keys()):
assert theProject.statusItems.remove(key) is True assert theProject.data.itemStatus.remove(key) is True
theProject.importItems.resetCounts() theProject.data.itemImport.resetCounts()
for key in list(theProject.importItems.keys()): for key in list(theProject.data.itemImport.keys()):
assert theProject.importItems.remove(key) is True assert theProject.data.itemImport.remove(key) is True
assert len(theProject.statusItems) == 0 assert len(theProject.data.itemStatus) == 0
assert len(theProject.importItems) == 0 assert len(theProject.data.itemImport) == 0
assert theProject.saveProject() is True assert theProject.saveProject() is True
assert theProject.closeProject() 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 # END Test testCoreProject_StatusImport
@@ -895,117 +811,104 @@ def testCoreProject_Methods(monkeypatch, mockGUI, tmpDir, fncDir, mockRnd):
assert theProject.setProjectPath(fncDir) assert theProject.setProjectPath(fncDir)
# Project Name # Project Name
assert theProject.setProjectName(" A Name ") theProject.data.setName(" A Name ")
assert theProject.projName == "A Name" assert theProject.data.name == "A Name"
# Project Title # Project Title
assert theProject.setBookTitle(" A Title ") theProject.data.setTitle(" A Title ")
assert theProject.bookTitle == "A Title" assert theProject.data.title == "A Title"
# Project Authors # Project Authors
# Check that the list is cleaned up and that it can be extracted as # Check that the list is cleaned up and that it can be extracted as
# a properly formatted string, depending on number of names # a properly formatted string, depending on number of names
assert not theProject.setBookAuthors([]) theProject.data.setAuthors([])
assert theProject.setBookAuthors(" Jane Doe \n John Doh \n ") assert theProject.data.authors == []
assert theProject.bookAuthors == ["Jane Doe", "John Doh"] theProject.data.setAuthors(" Jane Doe \n John Doh \n ")
assert theProject.data.authors == ["Jane Doe", "John Doh"]
assert theProject.setBookAuthors("") theProject.data.setAuthors("")
assert theProject.getAuthors() == "" assert theProject.getFormattedAuthors() == ""
assert theProject.setBookAuthors("Jane Doe") theProject.data.setAuthors("Jane Doe")
assert theProject.getAuthors() == "Jane Doe" assert theProject.getFormattedAuthors() == "Jane Doe"
assert theProject.setBookAuthors("Jane Doe\nJohn Doh") theProject.data.setAuthors("Jane Doe\nJohn Doh")
assert theProject.getAuthors() == "Jane Doe and John Doh" assert theProject.getFormattedAuthors() == "Jane Doe and John Doh"
assert theProject.setBookAuthors("Jane Doe\nJohn Doh\nBod Owens") theProject.data.setAuthors("Jane Doe\nJohn Doh\nBod Owens")
assert theProject.getAuthors() == "Jane Doe, John Doh and Bod Owens" assert theProject.getFormattedAuthors() == "Jane Doe, John Doh and Bod Owens"
# Edit Time # Edit Time
theProject.editTime = 1234 theProject.data.setEditTime(1234)
theProject.projOpened = 1600000000 theProject._projOpened = 1600000000
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.project.time", lambda: 1600005600) mp.setattr("novelwriter.core.project.time", lambda: 1600005600)
assert theProject.getCurrentEditTime() == 6834 assert theProject.getCurrentEditTime() == 6834
# Trash folder # Trash folder
# Should create on first call, and just returned on later calls # Should create on first call, and just returned on later calls
hTrash = "0000000000018" hTrash = "0000000000010"
assert theProject.tree[hTrash] is None assert theProject.tree[hTrash] is None
assert theProject.trashFolder() == hTrash assert theProject.trashFolder() == hTrash
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 # Spell check
theProject.projChanged = False theProject.setProjectChanged(False)
assert theProject.setSpellCheck(True) theProject.data.setSpellCheck(True)
assert not theProject.setSpellCheck(False) theProject.data.setSpellCheck(False)
assert theProject.projChanged assert theProject.projChanged
# Spell language # Spell language
theProject.projChanged = False theProject.setProjectChanged(False)
assert theProject.projSpell is None assert theProject.data.spellLang is None
assert theProject.setSpellLang(None) is False theProject.data.setSpellLang(None)
assert theProject.projSpell is None assert theProject.data.spellLang is None
assert theProject.setSpellLang("None") is False # Should be interpreded as None theProject.data.setSpellLang("None") # Should be interpreded as None
assert theProject.projSpell is None assert theProject.data.spellLang is None
assert theProject.setSpellLang("en_GB") theProject.data.setSpellLang("en_GB")
assert theProject.projSpell == "en_GB" assert theProject.data.spellLang == "en_GB"
assert theProject.projChanged assert theProject.projChanged
# Project Language # Project Language
theProject.projChanged = False theProject.setProjectChanged(False)
theProject.projLang = "en" theProject.data.setLanguage("en")
assert theProject.setProjectLang(None) is True 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.setProjectLang("en_GB") is True
assert theProject.projLang == "en_GB" assert theProject.data.language == "en_GB"
# Language Lookup # Language Lookup
assert theProject.localLookup(1) == "One" assert theProject.localLookup(1) == "One"
assert theProject.localLookup(10) == "Ten" assert theProject.localLookup(10) == "Ten"
# Last edited # Last edited
theProject.projChanged = False theProject.setProjectChanged(False)
assert theProject.setLastEdited("0123456789abc") theProject._data.setLastHandle("0123456789abc", "editor")
assert theProject.lastEdited == "0123456789abc" assert theProject._data.getLastHandle("editor") == "0123456789abc"
assert theProject.projChanged assert theProject.projChanged
# Last viewed # Last viewed
theProject.projChanged = False theProject.setProjectChanged(False)
assert theProject.setLastViewed("0123456789abc") theProject._data.setLastHandle("0123456789abc", "viewer")
assert theProject.lastViewed == "0123456789abc" assert theProject._data.getLastHandle("viewer") == "0123456789abc"
assert theProject.projChanged assert theProject.projChanged
# Autoreplace # Autoreplace
theProject.projChanged = False theProject.setProjectChanged(False)
assert theProject.setAutoReplace({"A": "B", "C": "D"}) theProject.data.setAutoReplace({"A": "B", "C": "D"})
assert theProject.autoReplace == {"A": "B", "C": "D"} assert theProject.data.autoReplace == {"A": "B", "C": "D"}
assert theProject.projChanged assert theProject.projChanged
# Change project tree order # Change project tree order
oldOrder = [ oldOrder = [
"0000000000010", "0000000000011", "0000000000012", "0000000000008", "0000000000009", "000000000000a",
"0000000000013", "0000000000014", "0000000000015", "000000000000b", "000000000000c", "000000000000d",
"0000000000016", "0000000000017", "0000000000018", "000000000000e", "000000000000f", "0000000000010",
] ]
newOrder = [ newOrder = [
"0000000000013", "0000000000014", "0000000000015", "000000000000b", "000000000000c", "000000000000d",
"0000000000010", "0000000000011", "0000000000012", "0000000000008", "0000000000009", "000000000000a",
"0000000000016", "0000000000017", "000000000000e", "000000000000f",
] ]
assert theProject.tree.handles() == oldOrder assert theProject.tree.handles() == oldOrder
assert theProject.setTreeOrder(newOrder) assert theProject.setTreeOrder(newOrder)
@@ -1014,8 +917,8 @@ def testCoreProject_Methods(monkeypatch, mockGUI, tmpDir, fncDir, mockRnd):
assert theProject.tree.handles() == oldOrder assert theProject.tree.handles() == oldOrder
# Session stats # Session stats
theProject.currWCount = 200 theProject._data._initCounts = [50, 50]
theProject.lastWCount = 100 theProject._data._currCounts = [100, 100]
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("os.path.isdir", lambda *a, **k: False) mp.setattr("os.path.isdir", lambda *a, **k: False)
assert not theProject._appendSessionStats(idleTime=0) 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") assert theProject.projMeta == os.path.join(fncDir, "meta")
statsFile = os.path.join(theProject.projMeta, nwFiles.SESS_STATS) statsFile = os.path.join(theProject.projMeta, nwFiles.SESS_STATS)
theProject.projOpened = 1600002000 theProject._projOpened = 1600002000
theProject.currNovelWC = 200 theProject._data._currCounts = [200, 100]
theProject.currNotesWC = 100
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.project.time", lambda: 1600005600) 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" "%s %s 200 100 99\n"
) % (formatTimeStamp(1600002000), formatTimeStamp(1600005600)) ) % (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 # END Test testCoreProject_Methods
@@ -1305,14 +1182,14 @@ def testCoreProject_LegacyData(monkeypatch, mockGUI, fncDir):
@pytest.mark.core @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 """Test the automated backup feature of the project class. The test
creates a backup of the Minimal test project, and then unzips the creates a backup of the Minimal test project, and then unzips the
backupd file and checks that the project XML file is identical to backupd file and checks that the project XML file is identical to
the original file. the original file.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
assert theProject.openProject(nwMinimal) buildTestProject(theProject, fncDir)
# Test faulty settings # Test faulty settings
@@ -1327,20 +1204,20 @@ def testCoreProject_Backup(monkeypatch, mockGUI, nwMinimal, tmpDir):
# Missing project name # Missing project name
theProject.mainConf.backupPath = tmpDir theProject.mainConf.backupPath = tmpDir
theProject.projName = "" theProject.data.setName("")
assert theProject.zipIt(doNotify=False) is False assert theProject.zipIt(doNotify=False) is False
# Non-existent folder # Non-existent folder
theProject.mainConf.backupPath = os.path.join(tmpDir, "nonexistent") theProject.mainConf.backupPath = os.path.join(tmpDir, "nonexistent")
theProject.projName = "Test Minimal" theProject.data.setName("Test Minimal")
assert theProject.zipIt(doNotify=False) is False assert theProject.zipIt(doNotify=False) is False
# Same folder as project (causes infinite loop in zipping) # Same folder as project (causes infinite loop in zipping)
theProject.mainConf.backupPath = nwMinimal theProject.mainConf.backupPath = fncDir
assert theProject.zipIt(doNotify=False) is False assert theProject.zipIt(doNotify=False) is False
# Subfolder of project (causes infinite loop in zipping) # 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 assert theProject.zipIt(doNotify=False) is False
# Set a valid folder # Set a valid folder
@@ -1372,7 +1249,7 @@ def testCoreProject_Backup(monkeypatch, mockGUI, nwMinimal, tmpDir):
# Check that the main project file was restored # Check that the main project file was restored
assert cmpFiles( assert cmpFiles(
os.path.join(nwMinimal, "nwProject.nwx"), os.path.join(fncDir, "nwProject.nwx"),
os.path.join(tmpDir, "extract", "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 pytest
import random
from lxml import etree from tools import C
from PyQt5.QtGui import QIcon from PyQt5.QtGui import QIcon
from novelwriter.core.status import NWStatus from novelwriter.core.status import NWStatus
statusKeys = ["sa3b179", "s1c8031", "s06671a", "sbdd640"] statusKeys = [C.sNew, C.sNote, C.sDraft, C.sFinished]
importKeys = ["i466852", "i3eb13b", "i392456", "i23b8c1"] importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain]
@pytest.mark.core @pytest.mark.core
def testCoreStatus_Internal(): def testCoreStatus_Internal(mockRnd):
"""Test all the internal functions of the NWStatus class. """Test all the internal functions of the NWStatus class.
""" """
random.seed(42)
theStatus = NWStatus(NWStatus.STATUS) theStatus = NWStatus(NWStatus.STATUS)
theImport = NWStatus(NWStatus.IMPORT) theImport = NWStatus(NWStatus.IMPORT)
@@ -87,10 +85,9 @@ def testCoreStatus_Internal():
@pytest.mark.core @pytest.mark.core
def testCoreStatus_Iterator(): def testCoreStatus_Iterator(mockRnd):
"""Test the iterator functions of the NWStatus class. """Test the iterator functions of the NWStatus class.
""" """
random.seed(42)
theStatus = NWStatus(NWStatus.STATUS) theStatus = NWStatus(NWStatus.STATUS)
theStatus.write(None, "New", (100, 100, 100)) theStatus.write(None, "New", (100, 100, 100))
@@ -132,10 +129,9 @@ def testCoreStatus_Iterator():
@pytest.mark.core @pytest.mark.core
def testCoreStatus_Entries(): def testCoreStatus_Entries(mockRnd):
"""Test all the simple setters for the NWStatus class. """Test all the simple setters for the NWStatus class.
""" """
random.seed(42)
theStatus = NWStatus(NWStatus.STATUS) theStatus = NWStatus(NWStatus.STATUS)
# Write # Write
@@ -161,14 +157,6 @@ def testCoreStatus_Entries():
assert theStatus[statusKeys[3]]["name"] == "Entry 4" assert theStatus[statusKeys[3]]["name"] == "Entry 4"
assert theStatus[statusKeys[3]]["cols"] == (100, 100, 100) 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 # Check
# ===== # =====
@@ -176,14 +164,8 @@ def testCoreStatus_Entries():
for key in statusKeys: for key in statusKeys:
assert theStatus.check(key) == key 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 # Non-existing name
assert theStatus.check("Entry 5") == statusKeys[0] assert theStatus.check("s987654") == statusKeys[0]
# Name Access # Name Access
# =========== # ===========
@@ -314,10 +296,9 @@ def testCoreStatus_Entries():
@pytest.mark.core @pytest.mark.core
def testCoreStatus_XMLPackUnpack(): def testCoreStatus_PackUnpack(mockRnd):
"""Test all the XML pack/unpack of the NWStatus class. """Test all the pack/unpack of the NWStatus class.
""" """
random.seed(42)
theStatus = NWStatus(NWStatus.STATUS) theStatus = NWStatus(NWStatus.STATUS)
theStatus.write(None, "New", (100, 100, 100)) theStatus.write(None, "New", (100, 100, 100))
theStatus.write(None, "Note", (200, 50, 0)) theStatus.write(None, "Note", (200, 50, 0))
@@ -329,36 +310,59 @@ def testCoreStatus_XMLPackUnpack():
for _ in range(n): for _ in range(n):
theStatus.increment(statusKeys[i]) theStatus.increment(statusKeys[i])
nwXML = etree.Element("novelWriterXML")
# Pack # Pack
xStatus = etree.SubElement(nwXML, "status") assert list(theStatus.pack()) == [
theStatus.packXML(xStatus) ("New", {
assert etree.tostring(xStatus, pretty_print=False, encoding="utf-8") == ( "key": statusKeys[0],
b'<status>' "count": "3",
b'<entry key="sa3b179" count="3" red="100" green="100" blue="100">New</entry>' "red": "100",
b'<entry key="s1c8031" count="5" red="200" green="50" blue="0">Note</entry>' "green": "100",
b'<entry key="s06671a" count="7" red="200" green="150" blue="0">Draft</entry>' "blue": "100"
b'<entry key="sbdd640" count="9" red="50" green="200" blue="0">Finished</entry>' }),
b'</status>' ("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 # Unpack
theStatus = NWStatus(NWStatus.STATUS) 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 len(theStatus._store) == 4
assert list(theStatus._store.keys()) == statusKeys assert list(theStatus._store.keys()) == statusKeys
assert theStatus._store[statusKeys[0]]["name"] == "New" assert theStatus._store[statusKeys[0]]["name"] == "New0"
assert theStatus._store[statusKeys[1]]["name"] == "Note" assert theStatus._store[statusKeys[1]]["name"] == "New1"
assert theStatus._store[statusKeys[2]]["name"] == "Draft" assert theStatus._store[statusKeys[2]]["name"] == "New2"
assert theStatus._store[statusKeys[3]]["name"] == "Finished" assert theStatus._store[statusKeys[3]]["name"] == "New3"
assert theStatus._store[statusKeys[0]]["cols"] == (100, 100, 100) assert theStatus._store[statusKeys[0]]["cols"] == (100, 100, 100)
assert theStatus._store[statusKeys[1]]["cols"] == (200, 50, 0) assert theStatus._store[statusKeys[1]]["cols"] == (150, 150, 150)
assert theStatus._store[statusKeys[2]]["cols"] == (200, 150, 0) assert theStatus._store[statusKeys[2]]["cols"] == (200, 200, 200)
assert theStatus._store[statusKeys[3]]["cols"] == (50, 200, 0) assert theStatus._store[statusKeys[3]]["cols"] == (250, 250, 250)
assert theStatus._store[statusKeys[0]]["count"] == countTo[0] assert theStatus._store[statusKeys[0]]["count"] == countTo[0]
assert theStatus._store[statusKeys[1]]["count"] == countTo[1] assert theStatus._store[statusKeys[1]]["count"] == countTo[1]
assert theStatus._store[statusKeys[2]]["count"] == countTo[2] assert theStatus._store[statusKeys[2]]["count"] == countTo[2]
assert theStatus._store[statusKeys[3]]["count"] == countTo[3] 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 os
import pytest import pytest
from tools import readFile from tools import C, buildTestProject, readFile
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.core.document import NWDoc from novelwriter.core.document import NWDoc
@@ -133,21 +133,20 @@ def testCoreToken_Setters(mockGUI):
@pytest.mark.core @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. """Test handling files and text in the Tokenizer class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theProject.projLang = "en" mockRnd.reset()
buildTestProject(theProject, fncDir)
theProject.data.setLanguage("en")
theProject._loadProjectLocalisation() theProject._loadProjectLocalisation()
theToken = BareTokenizer(theProject) theToken = BareTokenizer(theProject)
theToken.setKeepMarkdown(True) theToken.setKeepMarkdown(True)
assert theProject.openProject(nwMinimal)
sHandle = "8c659a11cd429"
# Set some content to work with # Set some content to work with
docText = ( docText = (
"### Scene Six\n\n" "### Scene Six\n\n"
"This is text with _italic text_, some **bold text**, some ~~deleted text~~, " "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") docTextR = docText.replace("<A>", "this").replace("<B>", "that")
nDoc = NWDoc(theProject, sHandle) nDoc = NWDoc(theProject, C.hSceneDoc)
assert nDoc.writeDocument(docText) assert nDoc.writeDocument(docText)
theProject.setAutoReplace({"A": "this", "B": "that"}) theProject.data.setAutoReplace({"A": "this", "B": "that"})
assert theProject.saveProject() assert theProject.saveProject()
# Root Heading # Root Heading
assert theToken.addRootHeading("stuff") is False assert theToken.addRootHeading("stuff") is False
assert theToken.addRootHeading(sHandle) is False assert theToken.addRootHeading(C.hSceneDoc) is False
# First Page # 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.theMarkdown[-1] == "# Notes: Plot\n\n"
assert theToken._theTokens[-1] == ( assert theToken._theTokens[-1] == (
Tokenizer.T_TITLE, 0, "Notes: Plot", None, Tokenizer.A_CENTRE Tokenizer.T_TITLE, 0, "Notes: Plot", None, Tokenizer.A_CENTRE
) )
# Not First Page # 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.theMarkdown[-1] == "# Notes: Plot\n\n"
assert theToken._theTokens[-1] == ( assert theToken._theTokens[-1] == (
Tokenizer.T_TITLE, 0, "Notes: Plot", None, Tokenizer.A_CENTRE | Tokenizer.A_PBB 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 # Set Text
assert theToken.setText("stuff") is False assert theToken.setText("stuff") is False
assert theToken.setText(sHandle) is True assert theToken.setText(C.hSceneDoc) is True
assert theToken._theText == docText assert theToken._theText == docText
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("novelwriter.constants.nwConst.MAX_DOCSIZE", 100) 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 == ( assert theToken._theText == (
"# ERROR\n\n" "# ERROR\n\n"
"Document 'New Scene' is too big (0.00 MB). Skipping.\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._theText == docText
assert theToken._isNone is False assert theToken._isNone is False
@@ -212,7 +211,7 @@ def testCoreToken_TextOps(monkeypatch, nwMinimal, mockGUI):
assert theToken.theResult == "This is text with escapes: ** ~~ __" assert theToken.theResult == "This is text with escapes: ** ~~ __"
# Save File # Save File
savePath = os.path.join(nwMinimal, "dump.nwd") savePath = os.path.join(fncDir, "dump.nwd")
theToken.saveRawMarkdown(savePath) theToken.saveRawMarkdown(savePath)
assert readFile(savePath) == ( assert readFile(savePath) == (
"# Notes: Plot\n\n" "# Notes: Plot\n\n"
@@ -884,7 +883,7 @@ def testCoreToken_ProcessHeaders(mockGUI):
"""Test the header and page parser of the Tokenizer class. """Test the header and page parser of the Tokenizer class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theProject.projLang = "en" theProject.data.setLanguage("en")
theProject._loadProjectLocalisation() theProject._loadProjectLocalisation()
theToken = BareTokenizer(theProject) theToken = BareTokenizer(theProject)
+1 -61
View File
@@ -23,8 +23,6 @@ import os
import pytest import pytest
import random import random
from lxml import etree
from tools import readFile from tools import readFile
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
@@ -198,7 +196,7 @@ def testCoreTree_BuildTree(mockGUI, mockItems):
assert len(theTree) == len(mockItems) + 1 assert len(theTree) == len(mockItems) + 1
theList = theTree.handles() theList = theTree.handles()
nHandle = "0000000000010" nHandle = "0000000000000"
assert theList[-1] == nHandle assert theList[-1] == nHandle
# Try to add existing handle # Try to add existing handle
@@ -395,64 +393,6 @@ def testCoreTree_Reorder(mockGUI, mockItems):
# END Test testCoreTree_Reorder # 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 @pytest.mark.core
def testCoreTree_ToCFile(monkeypatch, mockGUI, mockItems, tmpDir): def testCoreTree_ToCFile(monkeypatch, mockGUI, mockItems, tmpDir):
"""Test writing the ToC.txt file. """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.wordCountVal.text() == f"{3000:n}"
assert projDet.tabMain.chapCountVal.text() == f"{3:n}" assert projDet.tabMain.chapCountVal.text() == f"{3:n}"
assert projDet.tabMain.sceneCountVal.text() == f"{5: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 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 pytest
import os import os
from tools import getGuiItem from tools import buildTestProject, getGuiItem
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
@@ -33,10 +33,10 @@ from novelwriter.dialogs.projload import GuiProjectLoad
@pytest.mark.gui @pytest.mark.gui
def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal): def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, fncProj):
"""Test the load project wizard. """Test the load project wizard.
""" """
assert nwGUI.openProject(nwMinimal) buildTestProject(nwGUI, fncProj)
assert nwGUI.closeProject() assert nwGUI.closeProject()
monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *a: None) monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *a: None)
@@ -87,10 +87,10 @@ def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
nwLoad._doDeleteRecent() nwLoad._doDeleteRecent()
assert nwLoad.listBox.topLevelItemCount() == recentCount - 1 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)) monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *a, **k: (getFile, None))
qtbot.mouseClick(nwLoad.browseButton, Qt.LeftButton) qtbot.mouseClick(nwLoad.browseButton, Qt.LeftButton)
assert nwLoad.openPath == nwMinimal assert nwLoad.openPath == fncProj
assert nwLoad.openState == nwLoad.OPEN_STATE assert nwLoad.openState == nwLoad.OPEN_STATE
nwLoad.close() nwLoad.close()
+12 -12
View File
@@ -50,7 +50,7 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI):
# Pretend we have a project # Pretend we have a project
nwGUI.hasProject = True nwGUI.hasProject = True
nwGUI.theProject.setSpellLang("en") nwGUI.theProject.data.setSpellLang("en")
# Get the dialog object # Get the dialog object
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger) nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
@@ -95,9 +95,9 @@ def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd
# Set some values # Set some values
theProject = nwGUI.theProject theProject = nwGUI.theProject
theProject.setSpellLang("en") theProject.data.setSpellLang("en")
theProject.setBookAuthors("Jane Smith\nJohn Smith") theProject.data.setAuthors("Jane Smith\nJohn Smith")
theProject.setAutoReplace({"A": "B", "C": "D"}) theProject.data.setAutoReplace({"A": "B", "C": "D"})
# Create Dialog # Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_MAIN) projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_MAIN)
@@ -136,9 +136,9 @@ def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd
assert projSettings.spellChanged is False assert projSettings.spellChanged is False
projSettings._doSave() projSettings._doSave()
assert theProject.projName == "Project Name" assert theProject.data.name == "Project Name"
assert theProject.bookTitle == "Project Title" assert theProject.data.title == "Project Title"
assert theProject.bookAuthors == ["Jane Doe", "John Doh"] assert theProject.data.authors == ["Jane Doe", "John Doh"]
# Clean up # Clean up
projSettings._doClose() projSettings._doClose()
@@ -330,13 +330,13 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncDir, fncProj,
# Check Project # Check Project
projSettings._doSave() projSettings._doSave()
statusItems = dict(theProject.statusItems.items()) statusItems = dict(theProject.data.itemStatus.items())
assert statusItems[C.sNew]["name"] == "New" assert statusItems[C.sNew]["name"] == "New"
assert statusItems[C.sDraft]["name"] == "Draft" assert statusItems[C.sDraft]["name"] == "Draft"
assert statusItems[C.sFinished]["name"] == "Finished" assert statusItems[C.sFinished]["name"] == "Finished"
assert statusItems["s000013"]["name"] == "Final" 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.iNew]["name"] == "New"
assert importItems[C.iMajor]["name"] == "Major" assert importItems[C.iMajor]["name"] == "Major"
assert importItems[C.iMain]["name"] == "Main" assert importItems[C.iMain]["name"] == "Main"
@@ -365,9 +365,9 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mock
# Set some values # Set some values
theProject = nwGUI.theProject theProject = nwGUI.theProject
theProject.autoReplace = { theProject.data.setAutoReplace({
"A": "B", "C": "D" "A": "B", "C": "D"
} })
# Create Dialog # Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_REPLACE) projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_REPLACE)
@@ -429,7 +429,7 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mock
# Check Project # Check Project
projSettings._doSave() projSettings._doSave()
assert theProject.autoReplace == { assert theProject.data.autoReplace == {
"A": "B", "C": "D", "This": "With This Stuff" "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.QtCore import Qt
from PyQt5.QtWidgets import QDialog, QAction from PyQt5.QtWidgets import QDialog, QAction
from tools import writeFile, readFile, getGuiItem from tools import buildTestProject, writeFile, readFile, getGuiItem
from mock import causeOSError from mock import causeOSError
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
@@ -33,16 +33,18 @@ from novelwriter.dialogs.wordlist import GuiWordList
@pytest.mark.gui @pytest.mark.gui
def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal): def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, fncProj):
"""test the word list editor. """test the word list editor.
""" """
buildTestProject(nwGUI, fncProj)
monkeypatch.setattr(GuiWordList, "exec_", lambda *a: None) monkeypatch.setattr(GuiWordList, "exec_", lambda *a: None)
monkeypatch.setattr(GuiWordList, "result", lambda *a: QDialog.Accepted) monkeypatch.setattr(GuiWordList, "result", lambda *a: QDialog.Accepted)
monkeypatch.setattr(GuiWordList, "accept", lambda *a: None) monkeypatch.setattr(GuiWordList, "accept", lambda *a: None)
# Open project # Open project
nwGUI.openProject(nwMinimal) nwGUI.openProject(fncProj)
dictFile = os.path.join(nwMinimal, "meta", nwFiles.PROJ_DICT) dictFile = os.path.join(fncProj, "meta", nwFiles.PROJ_DICT)
# Load the dialog # Load the dialog
nwGUI.mainMenu.aEditWordList.activate(QAction.Trigger) 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 import pytest
from mock import causeOSError from mock import causeOSError
from tools import C, buildTestProject
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QTextBlock, QTextCursor, QTextOption from PyQt5.QtGui import QTextBlock, QTextCursor, QTextOption
@@ -36,12 +37,12 @@ KEY_DELAY = 1
@pytest.mark.gui @pytest.mark.gui
def testGuiEditor_Init(qtbot, nwGUI, nwMinimal, ipsumText): def testGuiEditor_Init(qtbot, nwGUI, fncProj, ipsumText, mockRnd):
"""Test initialising the editor. """Test initialising the editor.
""" """
# Open project # Open project
assert nwGUI.openProject(nwMinimal) buildTestProject(nwGUI, fncProj)
assert nwGUI.openDocument("8c659a11cd429") assert nwGUI.openDocument(C.hSceneDoc)
nwGUI.docEditor.setText("### Lorem Ipsum\n\n%s" % ipsumText[0]) nwGUI.docEditor.setText("### Lorem Ipsum\n\n%s" % ipsumText[0])
assert nwGUI.saveDocument() assert nwGUI.saveDocument()
@@ -79,13 +80,11 @@ def testGuiEditor_Init(qtbot, nwGUI, nwMinimal, ipsumText):
@pytest.mark.gui @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. """Test loading text into the editor.
""" """
# Open project buildTestProject(nwGUI, fncProj)
sHandle = "8c659a11cd429" assert nwGUI.openDocument(C.hSceneDoc) is True
assert nwGUI.openProject(nwMinimal) is True
assert nwGUI.openDocument(sHandle) is True
longText = "### Lorem Ipsum\n\n%s" % "\n\n".join(ipsumText*20) longText = "### Lorem Ipsum\n\n%s" % "\n\n".join(ipsumText*20)
nwGUI.docEditor.replaceText(longText) nwGUI.docEditor.replaceText(longText)
@@ -101,11 +100,11 @@ def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe
# Document too big # Document too big
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("novelwriter.constants.nwConst.MAX_DOCSIZE", 100) 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 assert "The document you are trying to open is too big." in caplog.text
# Regular open # Regular open
assert nwGUI.docEditor.loadText(sHandle) is True assert nwGUI.docEditor.loadText(C.hSceneDoc) is True
assert nwGUI.docEditor._bigDoc is False assert nwGUI.docEditor._bigDoc is False
# Reload too big text # Reload too big text
@@ -116,18 +115,18 @@ def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe
# Big doc handling # Big doc handling
nwGUI.mainConf.bigDocLimit = 50 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 assert nwGUI.docEditor._bigDoc is True
# Regular open, with line number # 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() cursPos = nwGUI.docEditor.getCursorPosition()
assert nwGUI.docEditor.document().findBlock(cursPos).blockNumber() == 4 assert nwGUI.docEditor.document().findBlock(cursPos).blockNumber() == 4
# Load empty document # Load empty document
nwGUI.docEditor.replaceText("") nwGUI.docEditor.replaceText("")
assert nwGUI.saveDocument() is True assert nwGUI.saveDocument() is True
assert nwGUI.docEditor.loadText(sHandle) is True assert nwGUI.docEditor.loadText(C.hSceneDoc) is True
assert nwGUI.docEditor.toPlainText() == "" assert nwGUI.docEditor.toPlainText() == ""
# qtbot.stop() # qtbot.stop()
@@ -136,13 +135,11 @@ def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe
@pytest.mark.gui @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. """Test saving text from the editor.
""" """
# Open project buildTestProject(nwGUI, fncProj)
sHandle = "8c659a11cd429" assert nwGUI.openDocument(C.hSceneDoc) is True
assert nwGUI.openProject(nwMinimal) is True
assert nwGUI.openDocument(sHandle) is True
# Save Text # Save Text
# ========= # =========
@@ -159,7 +156,7 @@ def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe
# Unkown handle # Unkown handle
nwGUI.docEditor._docHandle = "0123456789abcdef" nwGUI.docEditor._docHandle = "0123456789abcdef"
assert nwGUI.docEditor.saveText() is False assert nwGUI.docEditor.saveText() is False
nwGUI.docEditor._docHandle = sHandle nwGUI.docEditor._docHandle = C.hSceneDoc
# Cause error when saving # Cause error when saving
with monkeypatch.context() as mp: 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 assert "Could not save document." in caplog.text
# Change header level # 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:]) nwGUI.docEditor.replaceText(longText[1:])
assert nwGUI.docEditor.saveText() is True 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 # Regular save
assert nwGUI.docEditor.saveText() is True assert nwGUI.docEditor.saveText() is True
@@ -182,13 +179,11 @@ def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe
@pytest.mark.gui @pytest.mark.gui
def testGuiEditor_MetaData(qtbot, nwGUI, nwMinimal): def testGuiEditor_MetaData(qtbot, nwGUI, fncProj, mockRnd):
"""Test extracting various meta data and other values. """Test extracting various meta data and other values.
""" """
# Open project buildTestProject(nwGUI, fncProj)
sHandle = "8c659a11cd429" assert nwGUI.openDocument(C.hSceneDoc) is True
assert nwGUI.openProject(nwMinimal) is True
assert nwGUI.openDocument(sHandle) is True
# Get Text # Get Text
# This should replace line and paragraph separators, but preserve # This should replace line and paragraph separators, but preserve
@@ -203,7 +198,7 @@ def testGuiEditor_MetaData(qtbot, nwGUI, nwMinimal):
# Check Propertoes # Check Propertoes
assert nwGUI.docEditor.docChanged() is True 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.lastActive() > 0.0
assert nwGUI.docEditor.isEmpty() is False 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(None) is False
assert nwGUI.docEditor.setCursorPosition(10) is True assert nwGUI.docEditor.setCursorPosition(10) is True
assert nwGUI.docEditor.getCursorPosition() == 10 assert nwGUI.docEditor.getCursorPosition() == 10
assert nwGUI.theProject.tree[sHandle].cursorPos != 10 assert nwGUI.theProject.tree[C.hSceneDoc].cursorPos != 10
nwGUI.docEditor.saveCursorPosition() 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(None) is False
assert nwGUI.docEditor.setCursorLine(2) is True assert nwGUI.docEditor.setCursorLine(2) is True
@@ -231,16 +226,14 @@ def testGuiEditor_MetaData(qtbot, nwGUI, nwMinimal):
@pytest.mark.gui @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 """Test the document actions. This is not an extensive test of the
action features, just that the actions are actually called. The action features, just that the actions are actually called. The
various action features are tested when their respective functions various action features are tested when their respective functions
are tested. are tested.
""" """
# Open project buildTestProject(nwGUI, fncProj)
sHandle = "8c659a11cd429" assert nwGUI.openDocument(C.hSceneDoc) is True
assert nwGUI.openProject(nwMinimal) is True
assert nwGUI.openDocument(sHandle) is True
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText) theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
assert nwGUI.docEditor.replaceText(theText) is True assert nwGUI.docEditor.replaceText(theText) is True
@@ -452,7 +445,7 @@ def testGuiEditor_Actions(qtbot, nwGUI, nwMinimal, ipsumText):
# No Document Handle # No Document Handle
nwGUI.docEditor._docHandle = None nwGUI.docEditor._docHandle = None
assert nwGUI.docEditor.docAction(nwDocAction.BLOCK_TXT) is False assert nwGUI.docEditor.docAction(nwDocAction.BLOCK_TXT) is False
nwGUI.docEditor._docHandle = sHandle nwGUI.docEditor._docHandle = C.hSceneDoc
# Wrong Action Type # Wrong Action Type
assert nwGUI.docEditor.docAction(None) is False assert nwGUI.docEditor.docAction(None) is False
@@ -466,13 +459,11 @@ def testGuiEditor_Actions(qtbot, nwGUI, nwMinimal, ipsumText):
@pytest.mark.gui @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. """Test the document insert functions.
""" """
# Open project buildTestProject(nwGUI, fncProj)
sHandle = "8c659a11cd429" assert nwGUI.openDocument(C.hSceneDoc) is True
assert nwGUI.openProject(nwMinimal) is True
assert nwGUI.openDocument(sHandle) is True
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText) theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
assert nwGUI.docEditor.replaceText(theText) is True assert nwGUI.docEditor.replaceText(theText) is True
@@ -487,7 +478,7 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
nwGUI.docEditor._docHandle = None nwGUI.docEditor._docHandle = None
assert nwGUI.docEditor.setCursorPosition(24) is True assert nwGUI.docEditor.setCursorPosition(24) is True
assert nwGUI.docEditor.insertText("Stuff") is False assert nwGUI.docEditor.insertText("Stuff") is False
nwGUI.docEditor._docHandle = sHandle nwGUI.docEditor._docHandle = C.hSceneDoc
# Insert String # Insert String
assert nwGUI.docEditor.setCursorPosition(24) is True assert nwGUI.docEditor.setCursorPosition(24) is True
@@ -551,13 +542,11 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
@pytest.mark.gui @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. """Test the text manipulation functions.
""" """
# Open project buildTestProject(nwGUI, fncProj)
sHandle = "8c659a11cd429" assert nwGUI.openDocument(C.hSceneDoc) is True
assert nwGUI.openProject(nwMinimal) is True
assert nwGUI.openDocument(sHandle) is True
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText) theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
assert nwGUI.docEditor.replaceText(theText) is True assert nwGUI.docEditor.replaceText(theText) is True
@@ -760,13 +749,11 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumTe
@pytest.mark.gui @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. """Test the block formatting function.
""" """
# Open project buildTestProject(nwGUI, fncProj)
sHandle = "8c659a11cd429" assert nwGUI.openDocument(C.hSceneDoc) is True
assert nwGUI.openProject(nwMinimal) is True
assert nwGUI.openDocument(sHandle) is True
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText) theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
assert nwGUI.docEditor.replaceText(theText) is True assert nwGUI.docEditor.replaceText(theText) is True
@@ -1075,13 +1062,11 @@ def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumTex
@pytest.mark.gui @pytest.mark.gui
def testGuiEditor_Tags(qtbot, nwGUI, nwMinimal, ipsumText): def testGuiEditor_Tags(qtbot, nwGUI, fncProj, ipsumText, mockRnd):
"""Test the document editor tags functionality. """Test the document editor tags functionality.
""" """
# Open project buildTestProject(nwGUI, fncProj)
sHandle = "8c659a11cd429" assert nwGUI.openDocument(C.hSceneDoc) is True
assert nwGUI.openProject(nwMinimal) is True
assert nwGUI.openDocument(sHandle) is True
# Create Scene # Create Scene
theText = "### A Scene\n\n@char: Jane, John\n\n" + ipsumText[0] + "\n\n" 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 # Create Character
theText = "### Jane Doe\n\n@tag: Jane\n\n" + ipsumText[1] + "\n\n" 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.openDocument(cHandle) is True
assert nwGUI.docEditor.replaceText(theText) is True assert nwGUI.docEditor.replaceText(theText) is True
assert nwGUI.saveDocument() is True assert nwGUI.saveDocument() is True
@@ -1098,7 +1083,7 @@ def testGuiEditor_Tags(qtbot, nwGUI, nwMinimal, ipsumText):
# Follow Tag # Follow Tag
# ========== # ==========
assert nwGUI.openDocument(sHandle) is True assert nwGUI.openDocument(C.hSceneDoc) is True
# Empty Block # Empty Block
assert nwGUI.docEditor.setCursorLine(1) is True assert nwGUI.docEditor.setCursorLine(1) is True
@@ -1136,7 +1121,7 @@ def testGuiEditor_Tags(qtbot, nwGUI, nwMinimal, ipsumText):
@pytest.mark.gui @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. """Test saving text from the editor.
""" """
class MockThreadPool: class MockThreadPool:
@@ -1153,7 +1138,8 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
nwGUI.threadPool = MockThreadPool() nwGUI.threadPool = MockThreadPool()
nwGUI.docEditor.wcTimerDoc.blockSignals(True) nwGUI.docEditor.wcTimerDoc.blockSignals(True)
nwGUI.docEditor.wcTimerSel.blockSignals(True) nwGUI.docEditor.wcTimerSel.blockSignals(True)
assert nwGUI.openProject(nwMinimal) is True
buildTestProject(nwGUI, fncProj)
# Run on an empty document # Run on an empty document
nwGUI.docEditor._runDocCounter() nwGUI.docEditor._runDocCounter()
@@ -1167,10 +1153,9 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
assert nwGUI.docEditor.docFooter.wordsText.text() == "Words: 0 (+0)" assert nwGUI.docEditor.docFooter.wordsText.text() == "Words: 0 (+0)"
# Open a document and populate it # Open a document and populate it
sHandle = "8c659a11cd429" nwGUI.theProject.tree[C.hSceneDoc]._initCount = 0 # Clear item's count
nwGUI.theProject.tree[sHandle]._initCount = 0 # Clear item's count nwGUI.theProject.tree[C.hSceneDoc]._wordCount = 0 # Clear item's count
nwGUI.theProject.tree[sHandle]._wordCount = 0 # Clear item's count assert nwGUI.openDocument(C.hSceneDoc) is True
assert nwGUI.openDocument(sHandle) is True
theText = "\n\n".join(ipsumText) theText = "\n\n".join(ipsumText)
cC, wC, pC = countWords(theText) cC, wC, pC = countWords(theText)
@@ -1193,9 +1178,9 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
nwGUI.docEditor.wCounterDoc.run() nwGUI.docEditor.wCounterDoc.run()
# nwGUI.docEditor._updateDocCounts(cC, wC, pC) # nwGUI.docEditor._updateDocCounts(cC, wC, pC)
assert nwGUI.theProject.tree[sHandle]._charCount == cC assert nwGUI.theProject.tree[C.hSceneDoc]._charCount == cC
assert nwGUI.theProject.tree[sHandle]._wordCount == wC assert nwGUI.theProject.tree[C.hSceneDoc]._wordCount == wC
assert nwGUI.theProject.tree[sHandle]._paraCount == pC assert nwGUI.theProject.tree[C.hSceneDoc]._paraCount == pC
assert nwGUI.docEditor.docFooter.wordsText.text() == f"Words: {wC} (+{wC})" assert nwGUI.docEditor.docFooter.wordsText.text() == f"Words: {wC} (+{wC})"
# Select all text # 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.tree.trashRoot() is None
assert nwGUI.theProject.projPath is None assert nwGUI.theProject.projPath is None
assert nwGUI.theProject.projMeta is None assert nwGUI.theProject.projMeta is None
assert nwGUI.theProject.projName == "" assert nwGUI.theProject.data.name == ""
assert nwGUI.theProject.bookTitle == "" assert nwGUI.theProject.data.title == ""
assert len(nwGUI.theProject.bookAuthors) == 0 assert nwGUI.theProject.data.authors == []
assert not nwGUI.theProject.spellCheck assert nwGUI.theProject.data.spellCheck is False
# Check the files # Check the files
projFile = os.path.join(fncProj, "nwProject.nwx") 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.tree.trashRoot() is None
assert nwGUI.theProject.projPath == fncProj assert nwGUI.theProject.projPath == fncProj
assert nwGUI.theProject.projMeta == os.path.join(fncProj, "meta") assert nwGUI.theProject.projMeta == os.path.join(fncProj, "meta")
assert nwGUI.theProject.projName == "New Project" assert nwGUI.theProject.data.name == "New Project"
assert nwGUI.theProject.bookTitle == "New Novel" assert nwGUI.theProject.data.title == "New Novel"
assert len(nwGUI.theProject.bookAuthors) == 1 assert nwGUI.theProject.data.authors == ["Jane Doe"]
assert nwGUI.theProject.spellCheck is False assert nwGUI.theProject.data.spellCheck is False
# Check that tree items have been created # Check that tree items have been created
assert nwGUI.projView.projTree._getTreeItem(C.hNovelRoot) is not None 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 # Check a Quick Create and Delete
assert nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None) assert nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None)
newHandle = nwGUI.projView.getSelectedHandle() 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.requestDeleteItem()
assert nwGUI.projView.setSelectedHandle(newHandle) assert nwGUI.projView.setSelectedHandle(newHandle)
assert nwGUI.projView.requestDeleteItem() 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() assert nwGUI.saveProject()
# Check the files # Check the files
@@ -509,21 +510,21 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
copyfile(projFile, testFile) copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile) assert cmpFiles(testFile, compFile)
projFile = os.path.join(fncProj, "content", "0000000000020.nwd") projFile = os.path.join(fncProj, "content", "0000000000010.nwd")
testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000020.nwd") testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000010.nwd")
compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000020.nwd") compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000010.nwd")
copyfile(projFile, testFile) copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile) assert cmpFiles(testFile, compFile)
projFile = os.path.join(fncProj, "content", "0000000000021.nwd") projFile = os.path.join(fncProj, "content", "0000000000011.nwd")
testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000021.nwd") testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000011.nwd")
compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000021.nwd") compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000011.nwd")
copyfile(projFile, testFile) copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile) assert cmpFiles(testFile, compFile)
projFile = os.path.join(fncProj, "content", "0000000000022.nwd") projFile = os.path.join(fncProj, "content", "0000000000012.nwd")
testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000022.nwd") testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000012.nwd")
compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000022.nwd") compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000012.nwd")
copyfile(projFile, testFile) copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile) assert cmpFiles(testFile, compFile)
+4 -4
View File
@@ -23,7 +23,7 @@ import os
import sys import sys
import pytest import pytest
from tools import getGuiItem from tools import buildTestProject, getGuiItem
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QFileDialog, QWizard, QDialog from PyQt5.QtWidgets import QFileDialog, QWizard, QDialog
@@ -37,7 +37,7 @@ from novelwriter.tools.projwizard import (
@pytest.mark.gui @pytest.mark.gui
@pytest.mark.skipif(sys.platform.startswith("darwin"), reason="Not running on Darwin") @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. """Test the launch of the project wizard.
Disabled for macOS because the test segfaults on QWizard.show() 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 # New with a project open should cause an error
assert nwGUI.openProject(nwMinimal) buildTestProject(nwGUI, fncProj)
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(nwGUI, "closeProject", lambda *a: False) mp.setattr(nwGUI, "closeProject", lambda *a: False)
assert nwGUI.newProject() is False assert nwGUI.newProject() is False
@@ -61,7 +61,7 @@ def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, nwMinimal):
assert nwGUI.newProject() is False assert nwGUI.newProject() is False
# Now, with a non-empty folder # 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 assert nwGUI.newProject() is False
# Test the Wizard Launching # Test the Wizard Launching
+14 -4
View File
@@ -167,9 +167,19 @@ def buildTestProject(theObject, projPath):
theProject.clearProject() theProject.clearProject()
theProject.setProjectPath(projPath, newProject=True) theProject.setProjectPath(projPath, newProject=True)
theProject.setProjectName("New Project")
theProject.setBookTitle("New Novel") theProject.data.itemStatus.write(None, "New", (100, 100, 100))
theProject.setBookAuthors("Jane Doe") 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 # Creating a minimal project with a few root folders and a
# single chapter folder with a single file. # single chapter folder with a single file.
@@ -195,7 +205,7 @@ def buildTestProject(theObject, projPath):
aDoc.writeDocument("### %s\n\n" % theProject.tr("New Scene")) aDoc.writeDocument("### %s\n\n" % theProject.tr("New Scene"))
theProject.index.reIndexHandle(xHandle[8]) theProject.index.reIndexHandle(xHandle[8])
theProject.projOpened = time.time() theProject._projOpened = time.time()
theProject.setProjectChanged(True) theProject.setProjectChanged(True)
theProject.saveProject(autoSave=True) theProject.saveProject(autoSave=True)