Add a storage class (#1225)

This commit is contained in:
Veronica Berglyd Olsen
2022-11-08 22:46:50 +01:00
committed by GitHub
74 changed files with 6881 additions and 4904 deletions
+4 -4
View File
@@ -3707,8 +3707,8 @@ helpful feedback and issue reports for the new features added in this, and previ
**User Interface** **User Interface**
* Added a preferences dialog for the program settings. No longer necessary to edit the config file. * Added a preferences dialog for the program settings. It is no longer necessary to edit the config
PR #30. file. PR #30.
* The document viewer remembers scroll bar position when pressing `Ctrl+R` on a document already * The document viewer remembers scroll bar position when pressing `Ctrl+R` on a document already
being viewed. PR #28. being viewed. PR #28.
* Removed version number from windows title. PR #28. * Removed version number from windows title. PR #28.
@@ -3749,8 +3749,8 @@ helpful feedback and issue reports for the new features added in this, and previ
**Status Bar** **Status Bar**
* Redesign of the status bar adding project and session stats as well as a session timer. PR #21. * Redesign of the status bar adding project and session stats as well as a session timer. PR #21.
* Project word count is written to the project file, which is needed for the session word count. PR * Project word count is written to the project file, which is needed for the session word count.
#21. PR #21.
* Closing a project now clears the status bar. PR #21. * Closing a project now clears the status bar. PR #21.
**Editor** **Editor**
+19 -2
View File
@@ -25,6 +25,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import os import os
import json import json
import uuid
import hashlib import hashlib
import logging import logging
@@ -87,9 +88,10 @@ def checkBool(value, default):
if isinstance(value, bool): if isinstance(value, bool):
return value return value
elif isinstance(value, str): elif isinstance(value, str):
if value == "True": check = value.lower()
if check in ("true", "yes", "on"):
return True return True
elif value == "False": elif check in ("false", "no", "off"):
return False return False
else: else:
return default return default
@@ -113,6 +115,15 @@ def checkHandle(value, default, allowNone=False):
return default return default
def checkUuid(value, default):
"""Try to process a value as an uuid, or return a default.
"""
try:
return str(uuid.UUID(value))
except Exception:
return default
# =============================================================================================== # # =============================================================================================== #
# Validator Functions # Validator Functions
# =============================================================================================== # # =============================================================================================== #
@@ -249,6 +260,12 @@ def simplified(string):
return " ".join(str(string).strip().split()) return " ".join(str(string).strip().split())
def yesNo(value):
"""Convert a boolean evaluated variable to a yes or no.
"""
return "yes" if value else "no"
def splitVersionNumber(value): def splitVersionNumber(value):
"""Split a version string on the form aa.bb.cc into major, minor """Split a version string on the form aa.bb.cc into major, minor
and patch, and computes an integer value aabbcc. and patch, and computes an integer value aabbcc.
+2 -3
View File
@@ -19,8 +19,7 @@ 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 novelwriter.core.doctools import DocMerger, DocSplitter from novelwriter.core.coretools import DocMerger, DocSplitter, ProjectBuilder
from novelwriter.core.document import NWDoc
from novelwriter.core.index import countWords from novelwriter.core.index import countWords
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.core.spellcheck import NWSpellEnchant from novelwriter.core.spellcheck import NWSpellEnchant
@@ -31,8 +30,8 @@ from novelwriter.core.tomd import ToMarkdown
__all__ = [ __all__ = [
"DocMerger", "DocMerger",
"DocSplitter", "DocSplitter",
"ProjectBuilder",
"countWords", "countWords",
"NWDoc",
"NWProject", "NWProject",
"NWSpellEnchant", "NWSpellEnchant",
"ToHtml", "ToHtml",
+454
View File
@@ -0,0 +1,454 @@
"""
novelWriter Project Document Tools
====================================
A collection of tools to create and manipulate documents
File History:
Created: 2022-10-02 [2.0b1] DocMerger
Created: 2022-10-11 [2.0b1] DocSplitter
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 shutil
import logging
import novelwriter
from time import time
from functools import partial
from PyQt5.QtCore import QCoreApplication
from novelwriter.enum import nwAlert
from novelwriter.common import minmax, simplified
from novelwriter.constants import nwItemClass
from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__)
class DocMerger:
"""Document tool for merging a set of documents into a single new
document. The parameters are defined by the user using the
GuiDocMerge dialog.
"""
def __init__(self, theProject):
self.theProject = theProject
self._error = ""
self._targetDoc = None
self._targetText = []
return
##
# Methods
##
def getError(self):
"""Return any collected errors.
"""
return self._error
def setTargetDoc(self, tHandle):
"""Set the target document for the merging. Calling this
function resets the class.
"""
self._targetDoc = tHandle
self._targetText = []
return
def newTargetDoc(self, srcHandle, docLabel):
"""Create a barnd new target document based on a source handle
and a new doc label. Calling this function resets the class.
"""
srcItem = self.theProject.tree[srcHandle]
if srcItem is None:
return None
newHandle = self.theProject.newFile(docLabel, srcItem.itemParent)
newItem = self.theProject.tree[newHandle]
newItem.setLayout(srcItem.itemLayout)
newItem.setStatus(srcItem.itemStatus)
newItem.setImport(srcItem.itemImport)
self._targetDoc = newHandle
self._targetText = []
return newHandle
def appendText(self, srcHandle, addComment, cmtPrefix):
"""Append text from an existing document to the text buffer.
"""
srcItem = self.theProject.tree[srcHandle]
if srcItem is None:
return False
inDoc = self.theProject.storage.getDocument(srcHandle)
docText = (inDoc.readDocument() or "").rstrip("\n")
if addComment:
docInfo = srcItem.describeMe()
docSt, _ = srcItem.getImportStatus(incIcon=False)
cmtLine = f"% {cmtPrefix} {docInfo}: {srcItem.itemName} [{docSt}]\n\n"
docText = cmtLine + docText
self._targetText.append(docText)
return True
def writeTargetDoc(self):
"""Write the accumulated text into the designated target
document, appending any existing text.
"""
if self._targetDoc is None:
return False
outDoc = self.theProject.storage.getDocument(self._targetDoc)
docText = (outDoc.readDocument() or "").rstrip("\n")
if docText:
self._targetText.insert(0, docText)
status = outDoc.writeDocument("\n\n".join(self._targetText) + "\n\n")
if not status:
self._error = outDoc.getError()
return status
# END Class DocMerger
class DocSplitter:
"""Document tool for splitting a document into a set of new
documents. The parameters are defined by the user using the
GuiDocSplit dialog.
"""
def __init__(self, theProject, sHandle):
self.theProject = theProject
self._error = ""
self._parHandle = None
self._srcHandle = None
self._srcItem = None
self._inFolder = False
self._rawData = []
srcItem = self.theProject.tree[sHandle]
if srcItem is not None and srcItem.isFileType():
self._srcHandle = sHandle
self._srcItem = srcItem
return
##
# Methods
##
def getError(self):
"""Return any collected errors.
"""
return self._error
def setParentItem(self, pHandle):
"""Set the item that will be the top level parent item for the
new documents.
"""
self._parHandle = pHandle
self._inFolder = False
return
def newParentFolder(self, pHandle, folderLabel):
"""Create a new folder that will be the top level parent item
for the new documents.
"""
if self._srcItem is None:
return None
newHandle = self.theProject.newFolder(folderLabel, pHandle)
newItem = self.theProject.tree[newHandle]
newItem.setStatus(self._srcItem.itemStatus)
newItem.setImport(self._srcItem.itemImport)
self._parHandle = newHandle
self._inFolder = True
return newHandle
def splitDocument(self, splitData, splitText):
"""Loop through the split data record and perform the split job.
"""
self._rawData = []
buffer = splitText.copy()
for lineNo, hLevel, hLabel in reversed(splitData):
chunk = buffer[lineNo:]
buffer = buffer[:lineNo]
self._rawData.insert(0, (chunk, hLevel, hLabel))
return True
def writeDocuments(self, docHierarchy):
"""An iterator that will write each document in the buffer, and
return its new handle, parent handle, and sibling handle.
"""
if self._srcHandle is None or self._srcItem is None:
return
pHandle = self._parHandle
nHandle = self._parHandle if self._inFolder else self._srcHandle
hHandle = [self._parHandle, None, None, None, None]
pLevel = 0
for docText, hLevel, docLabel in self._rawData:
hLevel = minmax(hLevel, 1, 4)
if pLevel == 0:
pLevel = hLevel
if docHierarchy:
if hLevel == 1:
pHandle = self._parHandle
elif hLevel == 2:
pHandle = hHandle[1] or hHandle[0]
elif hLevel == 3:
pHandle = hHandle[2] or hHandle[1] or hHandle[0]
elif hLevel == 4:
pHandle = hHandle[3] or hHandle[2] or hHandle[1] or hHandle[0]
if hLevel < pLevel:
nHandle = hHandle[hLevel] or hHandle[0]
elif hLevel > pLevel:
nHandle = pHandle
dHandle = self.theProject.newFile(docLabel, pHandle)
hHandle[hLevel] = dHandle
newItem = self.theProject.tree[dHandle]
newItem.setStatus(self._srcItem.itemStatus)
newItem.setImport(self._srcItem.itemImport)
outDoc = self.theProject.storage.getDocument(dHandle)
status = outDoc.writeDocument("\n".join(docText))
if not status:
self._error = outDoc.getError()
yield status, dHandle, nHandle
hHandle[hLevel] = dHandle
nHandle = dHandle
pLevel = hLevel
return
# END Class DocSplitter
class ProjectBuilder:
"""A class to build a new project from a set of user-defined
parameter provided by the New Projecty Wizard.
"""
def __init__(self, mainGui):
self.mainGui = mainGui
self.mainConf = novelwriter.CONFIG
self.tr = partial(QCoreApplication.translate, "NWProject")
return
##
# Methods
##
def buildProject(self, data):
"""Build a project from a data dictionary of specifications
provided by the wizard.
"""
if not isinstance(data, dict):
logger.error("Invalid call to newProject function")
return False
popMinimal = data.get("popMinimal", True)
popCustom = data.get("popCustom", False)
popSample = data.get("popSample", False)
# Check if we're extracting the sample project. This is handled
# differently as it isn't actually a new project, so we forward
# this to another function and return here.
if popSample:
return self._extractSampleProject(data)
projPath = data.get("projPath", None)
if projPath is None:
logger.error("No project path set for the new project")
return False
project = NWProject(self.mainGui)
if not project.storage.openProjectInPlace(projPath, newProject=True):
return False
lblNewProject = self.tr("New Project")
lblNewChapter = self.tr("New Chapter")
lblNewScene = self.tr("New Scene")
lblTitlePage = self.tr("Title Page")
lblByAuthors = self.tr("By")
# Settings
projName = data.get("projName", lblNewProject)
projTitle = data.get("projTitle", lblNewProject)
projAuthors = data.get("projAuthors", "")
project.data.setUuid(None)
project.data.setName(projName)
project.data.setTitle(projTitle)
project.data.setAuthors(projAuthors)
project.setDefaultStatusImport()
project._projOpened = int(time())
# Add Root Folders
hNovelRoot = project.newRoot(nwItemClass.NOVEL)
hTitlePage = project.newFile(lblTitlePage, hNovelRoot)
novelTitle = project.data.title if project.data.title else project.data.name
titlePage = f"#! {novelTitle}\n\n"
if project.data.authors:
titlePage += f">> {lblByAuthors} {project.getFormattedAuthors()} <<\n\n"
aDoc = project.storage.getDocument(hTitlePage)
aDoc.writeDocument(titlePage)
if popMinimal:
# Creating a minimal project with a few root folders and a
# single chapter with a single scene.
hChapter = project.newFile(lblNewChapter, hNovelRoot)
aDoc = project.storage.getDocument(hChapter)
aDoc.writeDocument(f"## {lblNewChapter}\n\n")
hScene = project.newFile(lblNewScene, hChapter)
aDoc = project.storage.getDocument(hScene)
aDoc.writeDocument(f"### {lblNewScene}\n\n")
project.newRoot(nwItemClass.PLOT)
project.newRoot(nwItemClass.CHARACTER)
project.newRoot(nwItemClass.WORLD)
project.newRoot(nwItemClass.ARCHIVE)
project.saveProject()
project.closeProject()
elif popCustom:
# Create a project structure based on selected root folders
# and a number of chapters and scenes selected in the
# wizard's custom page.
# Create chapters and scenes
numChapters = data.get("numChapters", 0)
numScenes = data.get("numScenes", 0)
chSynop = self.tr("Summary of the chapter.")
scSynop = self.tr("Summary of the scene.")
# Create chapters
if numChapters > 0:
for ch in range(numChapters):
chTitle = self.tr("Chapter {0}").format(f"{ch+1:d}")
cHandle = project.newFile(chTitle, hNovelRoot)
aDoc = project.storage.getDocument(cHandle)
aDoc.writeDocument(f"## {chTitle}\n\n% Synopsis: {chSynop}\n\n")
# Create chapter scenes
if numScenes > 0:
for sc in range(numScenes):
scTitle = self.tr("Scene {0}").format(f"{ch+1:d}.{sc+1:d}")
sHandle = project.newFile(scTitle, cHandle)
aDoc = project.storage.getDocument(sHandle)
aDoc.writeDocument(f"### {scTitle}\n\n% Synopsis: {scSynop}\n\n")
# Create scenes (no chapters)
elif numScenes > 0:
for sc in range(numScenes):
scTitle = self.tr("Scene {0}").format(f"{sc+1:d}")
sHandle = project.newFile(scTitle, hNovelRoot)
aDoc = project.storage.getDocument(sHandle)
aDoc.writeDocument(f"### {scTitle}\n\n% Synopsis: {scSynop}\n\n")
# Create notes folders
noteTitles = {
nwItemClass.PLOT: self.tr("Main Plot"),
nwItemClass.CHARACTER: self.tr("Protagonist"),
nwItemClass.WORLD: self.tr("Main Location"),
}
addNotes = data.get("addNotes", False)
for newRoot in data.get("addRoots", []):
if newRoot in nwItemClass:
rHandle = project.newRoot(newRoot)
if addNotes:
aHandle = project.newFile(noteTitles[newRoot], rHandle)
ntTag = simplified(noteTitles[newRoot]).replace(" ", "")
aDoc = project.storage.getDocument(aHandle)
aDoc.writeDocument(f"# {noteTitles[newRoot]}\n\n@tag: {ntTag}\n\n")
# Also add the archive and trash folders
project.newRoot(nwItemClass.ARCHIVE)
project.trashFolder()
project.saveProject()
project.closeProject()
return True
##
# Internal Functions
##
def _extractSampleProject(self, data):
"""Make a copy of the sample project by extracting the
sample.zip file to the new path.
"""
projPath = data.get("projPath", None)
if projPath is None:
logger.error("No project path set for the example project")
return False
pkgSample = os.path.join(self.mainConf.assetPath, "sample.zip")
if os.path.isfile(pkgSample):
try:
shutil.unpack_archive(pkgSample, projPath)
except Exception as exc:
self.mainGui.makeAlert(self.tr(
"Failed to create a new example project."
), nwAlert.ERROR, exception=exc)
return False
else:
self.mainGui.makeAlert(self.tr(
"Failed to create a new example project. "
"Could not find the necessary files. "
"They seem to be missing from this installation."
), nwAlert.ERROR)
return False
return True
# END Class ProjectBuilder
-244
View File
@@ -1,244 +0,0 @@
"""
novelWriter Project Document Tools
====================================
A collection of tools to create and manipulate documents
File History:
Created: 2022-10-02 [2.0b1] DocMerger
Created: 2022-10-11 [2.0b1] DocSplitter
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 logging
from novelwriter.common import minmax
from novelwriter.core.document import NWDoc
logger = logging.getLogger(__name__)
class DocMerger:
def __init__(self, theProject):
self.theProject = theProject
self._error = ""
self._targetDoc = None
self._targetText = []
return
##
# Methods
##
def getError(self):
"""Return any collected errors.
"""
return self._error
def setTargetDoc(self, tHandle):
"""Set the target document for the merging. Calling this
function resets the class.
"""
self._targetDoc = tHandle
self._targetText = []
return
def newTargetDoc(self, srcHandle, docLabel):
"""Create a barnd new target document based on a source handle
and a new doc label. Calling this function resets the class.
"""
srcItem = self.theProject.tree[srcHandle]
if srcItem is None:
return None
newHandle = self.theProject.newFile(docLabel, srcItem.itemParent)
newItem = self.theProject.tree[newHandle]
newItem.setLayout(srcItem.itemLayout)
newItem.setStatus(srcItem.itemStatus)
newItem.setImport(srcItem.itemImport)
self._targetDoc = newHandle
self._targetText = []
return newHandle
def appendText(self, srcHandle, addComment, cmtPrefix):
"""Append text from an existing document to the text buffer.
"""
srcItem = self.theProject.tree[srcHandle]
if srcItem is None:
return False
inDoc = NWDoc(self.theProject, srcHandle)
docText = (inDoc.readDocument() or "").rstrip("\n")
if addComment:
docInfo = srcItem.describeMe()
docSt, _ = srcItem.getImportStatus(incIcon=False)
cmtLine = f"% {cmtPrefix} {docInfo}: {srcItem.itemName} [{docSt}]\n\n"
docText = cmtLine + docText
self._targetText.append(docText)
return True
def writeTargetDoc(self):
"""Write the accumulated text into the designated target
document, appending any existing text.
"""
if self._targetDoc is None:
return False
outDoc = NWDoc(self.theProject, self._targetDoc)
docText = (outDoc.readDocument() or "").rstrip("\n")
if docText:
self._targetText.insert(0, docText)
status = outDoc.writeDocument("\n\n".join(self._targetText) + "\n\n")
if not status:
self._error = outDoc.getError()
return status
# END Class DocMerger
class DocSplitter:
def __init__(self, theProject, sHandle):
self.theProject = theProject
self._error = ""
self._parHandle = None
self._srcHandle = None
self._srcItem = None
self._inFolder = False
self._rawData = []
srcItem = self.theProject.tree[sHandle]
if srcItem is not None and srcItem.isFileType():
self._srcHandle = sHandle
self._srcItem = srcItem
return
##
# Methods
##
def getError(self):
"""Return any collected errors.
"""
return self._error
def setParentItem(self, pHandle):
"""Set the item that will be the top level parent item for the
new documents.
"""
self._parHandle = pHandle
self._inFolder = False
return
def newParentFolder(self, pHandle, folderLabel):
"""Create a new folder that will be the top level parent item
for the new documents.
"""
if self._srcItem is None:
return None
newHandle = self.theProject.newFolder(folderLabel, pHandle)
newItem = self.theProject.tree[newHandle]
newItem.setStatus(self._srcItem.itemStatus)
newItem.setImport(self._srcItem.itemImport)
self._parHandle = newHandle
self._inFolder = True
return newHandle
def splitDocument(self, splitData, splitText):
"""Loop through the split data record and perform the split job.
"""
self._rawData = []
buffer = splitText.copy()
for lineNo, hLevel, hLabel in reversed(splitData):
chunk = buffer[lineNo:]
buffer = buffer[:lineNo]
self._rawData.insert(0, (chunk, hLevel, hLabel))
return True
def writeDocuments(self, docHierarchy):
"""An iterator that will write each document in the buffer, and
return its new handle, parent handle, and sibling handle.
"""
if self._srcHandle is None or self._srcItem is None:
return
pHandle = self._parHandle
nHandle = self._parHandle if self._inFolder else self._srcHandle
hHandle = [self._parHandle, None, None, None, None]
pLevel = 0
for docText, hLevel, docLabel in self._rawData:
hLevel = minmax(hLevel, 1, 4)
if pLevel == 0:
pLevel = hLevel
if docHierarchy:
if hLevel == 1:
pHandle = self._parHandle
elif hLevel == 2:
pHandle = hHandle[1] or hHandle[0]
elif hLevel == 3:
pHandle = hHandle[2] or hHandle[1] or hHandle[0]
elif hLevel == 4:
pHandle = hHandle[3] or hHandle[2] or hHandle[1] or hHandle[0]
if hLevel < pLevel:
nHandle = hHandle[hLevel] or hHandle[0]
elif hLevel > pLevel:
nHandle = pHandle
dHandle = self.theProject.newFile(docLabel, pHandle)
hHandle[hLevel] = dHandle
newItem = self.theProject.tree[dHandle]
newItem.setStatus(self._srcItem.itemStatus)
newItem.setImport(self._srcItem.itemImport)
outDoc = NWDoc(self.theProject, dHandle)
status = outDoc.writeDocument("\n".join(docText))
if not status:
self._error = outDoc.getError()
yield status, dHandle, nHandle
hHandle[hLevel] = dHandle
nHandle = dHandle
pLevel = hLevel
return
# END Class DocSplitter
+40 -26
View File
@@ -23,9 +23,10 @@ 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/>.
""" """
import os
import logging import logging
from pathlib import Path
from novelwriter.enum import nwItemLayout, nwItemClass from novelwriter.enum import nwItemLayout, nwItemClass
from novelwriter.error import formatException from novelwriter.error import formatException
from novelwriter.common import isHandle, sha256sum from novelwriter.common import isHandle, sha256sum
@@ -33,7 +34,7 @@ from novelwriter.common import isHandle, sha256sum
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class NWDoc: class NWDocument:
def __init__(self, theProject, theHandle): def __init__(self, theProject, theHandle):
@@ -57,7 +58,7 @@ class NWDoc:
return return
def __repr__(self): def __repr__(self):
return f"<NWDoc handle={self._docHandle}>" return f"<NWDocument handle={self._docHandle}>"
def __bool__(self): def __bool__(self):
return self._docHandle is not None and bool(self._theItem) return self._docHandle is not None and bool(self._theItem)
@@ -73,7 +74,7 @@ class NWDoc:
empty string. If something went wrong, return None. empty string. If something went wrong, return None.
""" """
self._docError = "" self._docError = ""
if self._docHandle is None: if not isinstance(self._docHandle, str):
logger.error("No document handle set") logger.error("No document handle set")
return None return None
@@ -81,17 +82,22 @@ class NWDoc:
logger.error("Unknown novelWriter document") logger.error("Unknown novelWriter document")
return None return None
contentPath = self.theProject.storage.contentPath
if not isinstance(contentPath, Path):
logger.error("No content path set")
return None
docFile = self._docHandle+".nwd" docFile = self._docHandle+".nwd"
logger.debug("Opening document: %s", docFile) logger.debug("Opening document: %s", docFile)
docPath = os.path.join(self.theProject.projContent, docFile) docPath = contentPath / docFile
self._fileLoc = docPath self._fileLoc = docPath
theText = "" theText = ""
self._docMeta = {} self._docMeta = {}
self._prevHash = None self._prevHash = None
if os.path.isfile(docPath): if docPath.exists():
self._prevHash = sha256sum(docPath) self._prevHash = sha256sum(docPath)
try: try:
with open(docPath, mode="r", encoding="utf-8") as inFile: with open(docPath, mode="r", encoding="utf-8") as inFile:
@@ -125,17 +131,20 @@ class NWDoc:
if not. if not.
""" """
self._docError = "" self._docError = ""
if self._docHandle is None: if not isinstance(self._docHandle, str):
logger.error("No document handle set") logger.error("No document handle set")
return False return False
self.theProject.ensureFolderStructure() contentPath = self.theProject.storage.contentPath
if not isinstance(contentPath, Path):
logger.error("No content path set")
return False
docFile = self._docHandle+".nwd" docFile = self._docHandle+".nwd"
logger.debug("Saving document: %s", docFile) logger.debug("Saving document: %s", docFile)
docPath = os.path.join(self.theProject.projContent, docFile) docPath = contentPath / docFile
docTemp = os.path.join(self.theProject.projContent, docFile+"~") docTemp = docPath.with_suffix(".tmp")
if self._prevHash is not None and not forceWrite: if self._prevHash is not None and not forceWrite:
self._currHash = sha256sum(docPath) self._currHash = sha256sum(docPath)
@@ -164,7 +173,7 @@ class NWDoc:
# If we're here, the file was successfully saved, so we can # If we're here, the file was successfully saved, so we can
# replace the temp file with the actual file # replace the temp file with the actual file
try: try:
os.replace(docTemp, docPath) docTemp.replace(docPath)
except OSError as exc: except OSError as exc:
self._docError = formatException(exc) self._docError = formatException(exc)
return False return False
@@ -179,23 +188,28 @@ class NWDoc:
from the project data folder. from the project data folder.
""" """
self._docError = "" self._docError = ""
if self._docHandle is None: if not isinstance(self._docHandle, str):
logger.error("No document handle set") logger.error("No document handle set")
return False return False
chkList = [ contentPath = self.theProject.storage.contentPath
os.path.join(self.theProject.projContent, f"{self._docHandle}.nwd"), if not isinstance(contentPath, Path):
os.path.join(self.theProject.projContent, f"{self._docHandle}.nwd~"), logger.error("No content path set")
] return False
for chkFile in chkList: docPath = contentPath / f"{self._docHandle}.nwd"
if os.path.isfile(chkFile): docTemp = docPath.with_suffix(".tmp")
try:
os.unlink(chkFile) try:
logger.debug("Deleted: %s", chkFile) # ToDo: When Python 3.7 is dropped, these can be changed to
except Exception as exc: # path.unlink(missing_ok=True)
self._docError = formatException(exc) if docPath.exists():
return False docPath.unlink()
if docTemp.exists():
docTemp.unlink()
except Exception as exc:
self._docError = formatException(exc)
return False
return True return True
@@ -206,7 +220,7 @@ class NWDoc:
def getFileLocation(self): def getFileLocation(self):
"""Return the file location of the current document. """Return the file location of the current document.
""" """
return self._fileLoc return str(self._fileLoc)
def getCurrentItem(self): def getCurrentItem(self):
"""Return a pointer to the currently open NWItem. """Return a pointer to the currently open NWItem.
@@ -263,4 +277,4 @@ class NWDoc:
return return
# END Class NWDoc # END Class NWDocument
+11 -8
View File
@@ -26,16 +26,15 @@ 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/>.
""" """
import os
import json import json
import logging import logging
from time import time from time import time
from pathlib import Path
from novelwriter.enum import nwItemType, nwItemLayout from novelwriter.enum import nwItemType, nwItemLayout
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.constants import nwFiles, nwKeyWords, nwUnicode, nwHeaders from novelwriter.constants import nwFiles, nwKeyWords, nwUnicode, nwHeaders
from novelwriter.core.document import NWDoc
from novelwriter.common import ( from novelwriter.common import (
checkInt, isHandle, isItemClass, isTitleTag, jsonEncode checkInt, isHandle, isItemClass, isTitleTag, jsonEncode
) )
@@ -118,7 +117,7 @@ class NWIndex:
return False return False
logger.debug("Re-indexing item '%s'", tHandle) logger.debug("Re-indexing item '%s'", tHandle)
theDoc = NWDoc(self.theProject, tHandle) theDoc = self.theProject.storage.getDocument(tHandle)
self.scanText(tHandle, theDoc.readDocument() or "") self.scanText(tHandle, theDoc.readDocument() or "")
return True return True
@@ -141,12 +140,15 @@ class NWIndex:
def loadIndex(self): def loadIndex(self):
"""Load index from last session from the project meta folder. """Load index from last session from the project meta folder.
""" """
indexFile = self.theProject.storage.getMetaFile(nwFiles.INDEX_FILE)
if not isinstance(indexFile, Path):
return False
theData = {} theData = {}
indexFile = os.path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
tStart = time() tStart = time()
self._indexBroken = False self._indexBroken = False
if os.path.isfile(indexFile): if indexFile.exists():
logger.debug("Loading index file") logger.debug("Loading index file")
try: try:
with open(indexFile, mode="r", encoding="utf-8") as inFile: with open(indexFile, mode="r", encoding="utf-8") as inFile:
@@ -184,8 +186,11 @@ class NWIndex:
"""Save the current index as a json file in the project meta """Save the current index as a json file in the project meta
data folder. data folder.
""" """
indexFile = self.theProject.storage.getMetaFile(nwFiles.INDEX_FILE)
if not isinstance(indexFile, Path):
return False
logger.debug("Saving index file") logger.debug("Saving index file")
indexFile = os.path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
tStart = time() tStart = time()
try: try:
@@ -815,8 +820,6 @@ class ItemIndex:
elif tItem.itemRoot == rootHandle: elif tItem.itemRoot == rootHandle:
for sTitle in self._items[tHandle].headings(): for sTitle in self._items[tHandle].headings():
yield tHandle, sTitle, self._items[tHandle][sTitle] yield tHandle, sTitle, self._items[tHandle][sTitle]
else:
continue
return return
+26 -21
View File
@@ -27,7 +27,7 @@ import logging
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, yesNo
) )
from novelwriter.constants import nwHeaders, nwLabels, trConst from novelwriter.constants import nwHeaders, nwLabels, trConst
@@ -162,7 +162,7 @@ class NWItem:
item["order"] = str(self._order) item["order"] = str(self._order)
item["type"] = str(self._type.name) item["type"] = str(self._type.name)
item["class"] = str(self._class.name) item["class"] = str(self._class.name)
meta["expanded"] = str(self._expanded) meta["expanded"] = yesNo(self._expanded)
name["status"] = str(self._status) name["status"] = str(self._status)
name["import"] = str(self._import) name["import"] = str(self._import)
@@ -173,7 +173,7 @@ class NWItem:
meta["wordCount"] = str(self._wordCount) meta["wordCount"] = str(self._wordCount)
meta["paraCount"] = str(self._paraCount) meta["paraCount"] = str(self._paraCount)
meta["cursorPos"] = str(self._cursorPos) meta["cursorPos"] = str(self._cursorPos)
name["active"] = str(self._active) name["active"] = yesNo(self._active)
data = { data = {
"name": str(self._name), "name": str(self._name),
@@ -187,29 +187,34 @@ class NWItem:
def unpack(self, data): def unpack(self, data):
"""Set the values from a data dictionary. """Set the values from a data dictionary.
""" """
if "handle" in data: item = data.get("itemAttr", {})
self.setHandle(data["handle"]) meta = data.get("metaAttr", {})
name = data.get("nameAttr", {})
if "handle" in item:
self.setHandle(item["handle"])
else: else:
logger.error("Item does not have a handle") logger.error("Item does not have a handle")
return False return False
self.setName(data.get("label", "")) self.setName(data.get("name", ""))
self.setParent(data.get("parent", None)) self.setParent(item.get("parent", None))
self.setRoot(data.get("root", None)) self.setRoot(item.get("root", None))
self.setOrder(data.get("order", 0)) self.setOrder(item.get("order", 0))
self.setType(data.get("type", nwItemType.NO_TYPE)) self.setType(item.get("type", nwItemType.NO_TYPE))
self.setClass(data.get("class", nwItemClass.NO_CLASS)) self.setClass(item.get("class", nwItemClass.NO_CLASS))
self.setLayout(data.get("layout", nwItemLayout.NO_LAYOUT)) self.setExpanded(meta.get("expanded", False))
self.setStatus(name.get("status", None))
self.setImport(name.get("import", None))
self.setExpanded(data.get("expanded", False)) if self._type == nwItemType.FILE:
self.setStatus(data.get("status", None)) self.setLayout(item.get("layout", nwItemLayout.NO_LAYOUT))
self.setImport(data.get("import", None)) self.setMainHeading(meta.get("heading", "H0"))
self.setMainHeading(data.get("heading", "H0")) self.setCharCount(meta.get("charCount", 0))
self.setCharCount(data.get("charCount", 0)) self.setWordCount(meta.get("wordCount", 0))
self.setWordCount(data.get("wordCount", 0)) self.setParaCount(meta.get("paraCount", 0))
self.setParaCount(data.get("paraCount", 0)) self.setCursorPos(meta.get("cursorPos", 0))
self.setCursorPos(data.get("cursorPos", 0)) self.setActive(name.get("active", True))
self.setActive(data.get("active", True))
# Make some checks to ensure consistency # Make some checks to ensure consistency
if self._type == nwItemType.ROOT: if self._type == nwItemType.ROOT:
+6 -8
View File
@@ -24,11 +24,11 @@ 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/>.
""" """
import os
import json import json
import logging import logging
from enum import Enum from enum import Enum
from pathlib import Path
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.common import checkBool, checkFloat, checkInt, checkString from novelwriter.common import checkBool, checkFloat, checkInt, checkString
@@ -77,13 +77,12 @@ class OptionState:
def loadSettings(self): def loadSettings(self):
"""Load the options dictionary from the project settings file. """Load the options dictionary from the project settings file.
""" """
if self.theProject.projMeta is None: stateFile = self.theProject.storage.getMetaFile(nwFiles.OPTS_FILE)
if not isinstance(stateFile, Path):
return False return False
stateFile = os.path.join(self.theProject.projMeta, nwFiles.OPTS_FILE)
theState = {} theState = {}
if stateFile.exists():
if os.path.isfile(stateFile):
logger.debug("Loading GUI options file") logger.debug("Loading GUI options file")
try: try:
with open(stateFile, mode="r", encoding="utf-8") as inFile: with open(stateFile, mode="r", encoding="utf-8") as inFile:
@@ -106,12 +105,11 @@ class OptionState:
def saveSettings(self): def saveSettings(self):
"""Save the options dictionary to the project settings file. """Save the options dictionary to the project settings file.
""" """
if self.theProject.projMeta is None: stateFile = self.theProject.storage.getMetaFile(nwFiles.OPTS_FILE)
if not isinstance(stateFile, Path):
return False return False
stateFile = os.path.join(self.theProject.projMeta, nwFiles.OPTS_FILE)
logger.debug("Saving GUI options file") logger.debug("Saving GUI options file")
try: try:
with open(stateFile, mode="w+", encoding="utf-8") as outFile: with open(stateFile, mode="w+", encoding="utf-8") as outFile:
json.dump(self._theState, outFile, indent=2) json.dump(self._theState, outFile, indent=2)
File diff suppressed because it is too large Load Diff
+354
View File
@@ -0,0 +1,354 @@
"""
novelWriter Project Data Class
================================
Data class for novelWriter projects
File History:
Created: 2022-10-30 [2.0rc1]
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/>.
"""
from __future__ import annotations
import uuid
import logging
from novelwriter.common import (
checkBool, checkInt, checkStringNone, checkUuid, isHandle, simplified
)
from novelwriter.core.status import NWStatus
logger = logging.getLogger(__name__)
class NWProjectData:
def __init__(self, theProject):
self.theProject = theProject
# Project Meta
self._uuid = ""
self._name = ""
self._title = ""
self._authors = []
self._saveCount = 0
self._autoCount = 0
self._editTime = 0
# Project Settings
self._doBackup = True
self._language = None
self._spellCheck = False
self._spellLang = None
# Project Dictionaries
self._initCounts = [0, 0]
self._currCounts = [0, 0]
self._lastHandle: dict[str, str | None] = {
"editor": None,
"viewer": None,
"novelTree": None,
"outline": None,
}
self._autoReplace: dict[str, str] = {}
self._titleFormat: dict[str, str] = {
"title": "%title%",
"chapter": "%title%",
"unnumbered": "%title%",
"scene": "* * *",
"section": "",
}
self._status = NWStatus(NWStatus.STATUS)
self._import = NWStatus(NWStatus.IMPORT)
return
##
# Properties
##
@property
def uuid(self):
return self._uuid
@property
def name(self):
return self._name
@property
def title(self):
return self._title
@property
def authors(self):
return self._authors
@property
def saveCount(self):
return self._saveCount
@property
def autoCount(self):
return self._autoCount
@property
def editTime(self):
return self._editTime
@property
def doBackup(self):
return self._doBackup
@property
def language(self):
return self._language
@property
def spellCheck(self):
return self._spellCheck
@property
def spellLang(self):
return self._spellLang
@property
def initCounts(self):
return tuple(self._initCounts)
@property
def currCounts(self):
return tuple(self._currCounts)
@property
def lastHandle(self):
return self._lastHandle
@property
def autoReplace(self):
return self._autoReplace
@property
def titleFormat(self):
return self._titleFormat
@property
def itemStatus(self):
return self._status
@property
def itemImport(self):
return self._import
##
# Methods
##
def addAuthor(self, value):
"""Add an author to the authors list.
"""
self._authors.append(simplified(str(value)))
self.theProject.setProjectChanged(True)
return
def incSaveCount(self):
"""Increment the save count by one.
"""
self._saveCount += 1
self.theProject.setProjectChanged(True)
return
def incAutoCount(self):
"""Increment the auto save count by one.
"""
self._autoCount += 1
self.theProject.setProjectChanged(True)
return
##
# Getters
##
def getLastHandle(self, component):
"""Retrieve the last used handle for a given component.
"""
return self._lastHandle.get(component, None)
def getTitleFormat(self, kind):
"""Retrieve the title format string for a given kind of header.
"""
return self._titleFormat.get(kind, "%title%")
##
# Setters
##
def setUuid(self, value):
"""Set the project id.
"""
value = checkUuid(value, "")
if not value:
self._uuid = str(uuid.uuid4())
elif value != self._uuid:
self._uuid = value
self.theProject.setProjectChanged(True)
return
def setName(self, value):
"""Set a new project name.
"""
if value != self._name:
self._name = simplified(str(value))
self.theProject.setProjectChanged(True)
return
def setTitle(self, value):
"""Set a new novel title.
"""
if value != self._title:
self._title = simplified(str(value))
self.theProject.setProjectChanged(True)
return
def setAuthors(self, value):
"""Set the list of authors from either a string with one author
per line, or a list of authors.
"""
self._authors = []
self.theProject.setProjectChanged(True)
if isinstance(value, str):
for author in value.splitlines():
author = simplified(author)
if author:
self._authors.append(author)
self.theProject.setProjectChanged(True)
elif isinstance(value, list):
self._authors = value
return
def setSaveCount(self, value):
"""Set the save count from last session.
"""
self._saveCount = checkInt(value, 0)
self.theProject.setProjectChanged(True)
return
def setAutoCount(self, value):
"""Set the auto save count from last session.
"""
self._autoCount = checkInt(value, 0)
self.theProject.setProjectChanged(True)
return
def setEditTime(self, value):
"""Set tyje edit time from last session.
"""
self._editTime = checkInt(value, 0)
self.theProject.setProjectChanged(True)
return
def setDoBackup(self, value):
"""Set the do write backup flag.
"""
if value != self._doBackup:
self._doBackup = checkBool(value, False)
self.theProject.setProjectChanged(True)
return
def setLanguage(self, value):
"""Set the project language.
"""
if value != self._language:
self._language = checkStringNone(value, None)
self.theProject.setProjectChanged(True)
return
def setSpellCheck(self, value):
"""Set the spell check flag.
"""
if value != self._spellCheck:
self._spellCheck = checkBool(value, False)
self.theProject.setProjectChanged(True)
return
def setSpellLang(self, value):
"""Set the spell check language.
"""
if value != self._spellLang:
self._spellLang = checkStringNone(value, None)
self.theProject.setProjectChanged(True)
return
def setLastHandle(self, value, component=None):
"""Set a last used handle into the handle registry. If component
is None, the value is assumed to be the whole dictionary of
values.
"""
if isinstance(component, str):
self._lastHandle[component] = checkStringNone(value, None)
self.theProject.setProjectChanged(True)
elif isinstance(value, dict):
for key, entry in value.items():
if key in self._lastHandle:
self._lastHandle[key] = str(entry) if isHandle(entry) else None
self.theProject.setProjectChanged(True)
return
def setInitCounts(self, novel=None, notes=None):
"""Set the worc count totals for novel and note files.
"""
if novel is not None:
self._initCounts[0] = checkInt(novel, 0)
self._currCounts[0] = checkInt(novel, 0)
if notes is not None:
self._initCounts[1] = checkInt(notes, 0)
self._currCounts[1] = checkInt(notes, 0)
return
def setCurrCounts(self, novel=None, notes=None):
"""Set the worc count totals for novel and note files.
"""
if novel is not None:
self._currCounts[0] = checkInt(novel, 0)
if notes is not None:
self._currCounts[1] = checkInt(notes, 0)
return
def setAutoReplace(self, value):
"""Set the auto-replace dictionary.
"""
if isinstance(value, dict):
self._autoReplace = {}
for key, entry in value.items():
if isinstance(entry, str):
self._autoReplace[key] = simplified(entry)
self.theProject.setProjectChanged(True)
return
def setTitleFormat(self, value):
"""Set the title formats.
"""
if isinstance(value, dict):
for key, entry in value.items():
if key in self._titleFormat and isinstance(entry, str):
self._titleFormat[key] = simplified(entry)
self.theProject.setProjectChanged(True)
return
# END Class NWProjectData
+214 -160
View File
@@ -24,29 +24,32 @@ 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/>.
""" """
import os
import logging import logging
import novelwriter import novelwriter
from enum import Enum from enum import Enum
from lxml import etree from lxml import etree
from time import time from time import time
from pathlib import Path
from novelwriter.common import ( from novelwriter.common import (
checkBool, checkInt, checkStringNone, formatTimeStamp, simplified, checkString checkBool, checkInt, checkString, checkStringNone, formatTimeStamp,
hexToInt, simplified, yesNo
) )
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
FILE_VERSION = "1.4" # The current project file format version FILE_VERSION = "1.5" # The current project file format version
HEX_VERSION = 0x0105
NUM_VERSION = { NUM_VERSION = {
"1.0": 0x0100, "1.0": 0x0100, # Up to 0.7
"1.1": 0x0101, "1.1": 0x0101, # Up to 0.10
"1.2": 0x0102, "1.2": 0x0102, # Up to 1.5
"1.3": 0x0103, "1.3": 0x0103, # Up to 2.0 Beta 1
"1.4": 0x0104, "1.4": 0x0104, # Up to 2.0 RC 2
"1.5": 0x0105, # Current
} }
@@ -68,36 +71,42 @@ class ProjectXMLReader:
"""The main project XML file reader class. All data is read into a """The main project XML file reader class. All data is read into a
NWProjectData instance, which must be provided. NWProjectData instance, which must be provided.
Version Change History File Format Version Change History
====================== ==================================
1.0 Original file format. 1.0 Original file format.
1.1 Changes the way documents are structured in the project folder 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 from data_X, where X is the first hex value of the handle, to a
single content folder. Introduced in version 0.7. single content folder. Introduced in version 0.7.
1.2 Changes the way autoReplace entries are stored. The 1.1 parser 1.2 Changes the way autoReplace entries are stored. Introduced in
will lose the autoReplace settings if allowed to read the file. version 0.10.
Introduced in version 0.10.
1.3 Reduces the number of layouts to only two. One for novel 1.3 Reduces the number of layouts to only two. One for novel
documents and one for project notes. Introduced in version 1.5. documents and one for project notes. Introduced in version 1.5.
1.4 Introduces a more compact format for storing items. All settings 1.4 Introduces a more compact format for storing items. All settings
aside from name are now attributes. This format also changes the aside from name are now attributes. This format also changes the
way satus and importance labels are stored and handled. way satus and importance labels are stored. This format was only
Introduced in version 2.0. a part of version 2.0 RC 1
1.5 The actual format released for 2.0. It moves last used handles
and title formats into a key/value format similar to auto-
replace, status and imporetance. It adds the heading value to
the content item meta entry. It also moves meta data related to
the project or the content into their respective section nodes
as attributes. The id attribute was also added to the project.
""" """
def __init__(self, path): def __init__(self, path):
self._path = path self._path = Path(path)
self._state = XMLReadState.NO_ACTION self._state = XMLReadState.NO_ACTION
self._root = "" self._root = ""
self._version = 0x0000 self._version = 0x0
self._appVersion = "" self._appVersion = ""
self._hexVersion = "" self._hexVersion = 0x0
self._timeStamp = "" self._timeStamp = ""
return return
@@ -153,22 +162,22 @@ class ProjectXMLReader:
logger.debug("Reading project XML") logger.debug("Reading project XML")
try: try:
xml = etree.parse(self._path) xml = etree.parse(str(self._path))
self._state = XMLReadState.NO_ERROR self._state = XMLReadState.NO_ERROR
except Exception as exc: except Exception as exc:
# Trying to open backup file instead # Trying to open backup file instead
logger.error("Failed to parse project xml", exc_info=exc) logger.error("Failed to parse project XML", exc_info=exc)
self._state = XMLReadState.CANNOT_PARSE self._state = XMLReadState.CANNOT_PARSE
backFile = self._path[:-3]+"bak" backFile = self._path.with_suffix(".bak")
if os.path.isfile(backFile): if backFile.is_file():
try: try:
xml = etree.parse(backFile) xml = etree.parse(str(backFile))
self._state = XMLReadState.PARSED_BACKUP self._state = XMLReadState.PARSED_BACKUP
logger.info("Backup project file parsed") logger.info("Backup project file parsed")
except Exception as exc: except Exception as exc:
logger.error("Failed to parse backup project xml", exc_info=exc) logger.error("Failed to parse backup project XML", exc_info=exc)
self._state = XMLReadState.CANNOT_PARSE self._state = XMLReadState.CANNOT_PARSE
return False return False
else: else:
@@ -191,7 +200,7 @@ class ProjectXMLReader:
logger.debug("XML is '%s' version '%s'", self._root, fileVersion) logger.debug("XML is '%s' version '%s'", self._root, fileVersion)
self._appVersion = str(xRoot.attrib.get("appVersion", "")) self._appVersion = str(xRoot.attrib.get("appVersion", ""))
self._hexVersion = str(xRoot.attrib.get("hexVersion", "")) self._hexVersion = hexToInt(xRoot.attrib.get("hexVersion", ""))
self._timeStamp = str(xRoot.attrib.get("timeStamp", "")) self._timeStamp = str(xRoot.attrib.get("timeStamp", ""))
for xSection in xRoot: for xSection in xRoot:
@@ -201,13 +210,13 @@ class ProjectXMLReader:
self._parseProjectSettings(xSection, projData) self._parseProjectSettings(xSection, projData)
elif xSection.tag == "content": elif xSection.tag == "content":
if self._version >= 0x0104: if self._version >= 0x0104:
self._parseProjectContent(xSection, projContent) self._parseProjectContent(xSection, projData, projContent)
else: else:
self._parseProjectContentLegacy(xSection, projContent, projData) self._parseProjectContentLegacy(xSection, projData, projContent)
else: else:
logger.warning("Ignored <root/%s> in xml", xSection.tag) logger.warning("Ignored <root/%s> in XML", xSection.tag)
if self._version == 0x0104: if self._version == HEX_VERSION:
self._state = XMLReadState.PARSED_OK self._state = XMLReadState.PARSED_OK
else: else:
self._state = XMLReadState.WAS_LEGACY self._state = XMLReadState.WAS_LEGACY
@@ -224,6 +233,12 @@ class ProjectXMLReader:
"""Parse the project section of the XML file. """Parse the project section of the XML file.
""" """
logger.debug("Parsing <project> section") logger.debug("Parsing <project> section")
projData.setUuid(xSection.attrib.get("id", None)) # Added in 1.5
projData.setSaveCount(xSection.attrib.get("saveCount", 0)) # Moved in 1.5
projData.setAutoCount(xSection.attrib.get("autoCount", 0)) # Moved in 1.5
projData.setEditTime(xSection.attrib.get("editTime", 0)) # Moved in 1.5
for xItem in xSection: for xItem in xSection:
if xItem.tag == "name": if xItem.tag == "name":
projData.setName(xItem.text) projData.setName(xItem.text)
@@ -231,14 +246,18 @@ class ProjectXMLReader:
projData.setTitle(xItem.text) projData.setTitle(xItem.text)
elif xItem.tag == "author": elif xItem.tag == "author":
projData.addAuthor(xItem.text) 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: else:
logger.warning("Ignored <root/project/%s> in xml", xItem.tag) logger.warning("Ignored <root/project/%s> in XML", xItem.tag)
# Deprecated Nodes
if self._version < HEX_VERSION:
for xItem in xSection:
if xItem.tag == "saveCount": # Moved to attribute in 1.5
projData.setSaveCount(xItem.text)
elif xItem.tag == "autoCount": # Moved to attribute in 1.5
projData.setAutoCount(xItem.text)
elif xItem.tag == "editTime": # Moved to attribute in 1.5
projData.setEditTime(xItem.text)
return return
@@ -252,17 +271,12 @@ class ProjectXMLReader:
projData.setDoBackup(xItem.text) projData.setDoBackup(xItem.text)
elif xItem.tag == "language": elif xItem.tag == "language":
projData.setLanguage(xItem.text) projData.setLanguage(xItem.text)
elif xItem.tag == "spellCheck": elif xItem.tag == "spellChecking":
projData.setSpellCheck(xItem.text)
elif xItem.tag == "spellLang":
projData.setSpellLang(xItem.text) projData.setSpellLang(xItem.text)
elif xItem.tag == "novelWordCount": projData.setSpellCheck(xItem.attrib.get("auto", False))
projData.setInitCounts(novel=xItem.text)
elif xItem.tag == "notesWordCount":
projData.setInitCounts(notes=xItem.text)
elif xItem.tag == "status": elif xItem.tag == "status":
self._parseStatusImport(xItem, projData.itemStatus) self._parseStatusImport(xItem, projData.itemStatus)
elif xItem.tag in ("import", "importance"): elif xItem.tag == "importance":
self._parseStatusImport(xItem, projData.itemImport) self._parseStatusImport(xItem, projData.itemImport)
elif xItem.tag == "lastHandle": elif xItem.tag == "lastHandle":
projData.setLastHandle(self._parseDictKeyText(xItem)) projData.setLastHandle(self._parseDictKeyText(xItem))
@@ -272,122 +286,157 @@ class ProjectXMLReader:
else: # Pre 1.2 format else: # Pre 1.2 format
projData.setAutoReplace(self._parseDictTagText(xItem)) projData.setAutoReplace(self._parseDictTagText(xItem))
elif xItem.tag == "titleFormat": elif xItem.tag == "titleFormat":
if self._version >= 0x0104: if self._version >= 0x0105:
projData.setTitleFormat(self._parseDictKeyText(xItem)) projData.setTitleFormat(self._parseDictKeyText(xItem))
else: # Pre 1.4 format else: # Pre 1.4 format
projData.setTitleFormat(self._parseDictTagText(xItem)) projData.setTitleFormat(self._parseDictTagText(xItem))
else: else:
logger.warning("Ignored <root/settings/%s> in xml", xItem.tag) logger.warning("Ignored <root/settings/%s> in XML", xItem.tag)
# Deprecated Nodes
if self._version < HEX_VERSION:
for xItem in xSection:
if xItem.tag == "spellCheck": # Changed to spellChecking in 1.5
projData.setSpellCheck(xItem.text)
elif xItem.tag == "spellLang": # Changed to spellChecking in 1.5
projData.setSpellLang(xItem.text)
elif xItem.tag == "novelWordCount": # Moved to content attribute in 1.5
projData.setInitCounts(novel=xItem.text)
elif xItem.tag == "notesWordCount": # Moved to content attribute in 1.5
projData.setInitCounts(notes=xItem.text)
return return
def _parseProjectContent(self, xSection, projContent): def _parseProjectContent(self, xSection, projData, projContent):
"""Parse the content section of the XML file. """Parse the content section of the XML file.
""" """
logger.debug("Parsing <content> section") logger.debug("Parsing <content> section")
projData.setInitCounts(novel=xSection.attrib.get("novelWords", None)) # Moved in 1.5
projData.setInitCounts(notes=xSection.attrib.get("notesWords", None)) # Moved in 1.5
for xItem in xSection: for xItem in xSection:
if xItem.tag == "item": if xItem.tag != "item":
item = {} logger.warning("Ignored item <root/content/%s> in XML", xItem.tag)
item["handle"] = checkStringNone(xItem.attrib.get("handle"), None) continue
item["parent"] = checkStringNone(xItem.attrib.get("parent"), None)
item["root"] = checkStringNone(xItem.attrib.get("root"), None) item = {}
item["order"] = checkInt(xItem.attrib.get("order"), 0) meta = {}
item["type"] = checkString(xItem.attrib.get("type"), "NO_TYPE") name = {}
item["class"] = checkString(xItem.attrib.get("class"), "NO_CLASS") itemName = ""
item["layout"] = checkString(xItem.attrib.get("layout"), "NO_LAYOUT")
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":
meta["expanded"] = checkBool(xVal.attrib.get("expanded"), False)
meta["heading"] = checkString(xVal.attrib.get("heading"), "H0")
meta["charCount"] = checkInt(xVal.attrib.get("charCount"), 0)
meta["wordCount"] = checkInt(xVal.attrib.get("wordCount"), 0)
meta["paraCount"] = checkInt(xVal.attrib.get("paraCount"), 0)
meta["cursorPos"] = checkInt(xVal.attrib.get("cursorPos"), 0)
elif xVal.tag == "name":
itemName = simplified(checkString(xVal.text, ""))
name["status"] = checkStringNone(xVal.attrib.get("status"), None)
name["import"] = checkStringNone(xVal.attrib.get("import"), None)
name["active"] = checkBool(xVal.attrib.get("active"), False)
else:
logger.warning("Ignored <root/content/item/%s> in XML", xVal.tag)
# Deprecated Nodes
if self._version < HEX_VERSION:
for xVal in xItem: for xVal in xItem:
if xVal.tag == "meta": if xVal.tag == "name" and "exported" in xVal.attrib:
item["expanded"] = checkBool(xVal.attrib.get("expanded"), False) name["active"] = checkBool(xVal.attrib.get("exported"), 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. projContent.append({
if "exported" in xVal.attrib: "name": itemName,
item["active"] = checkBool(xVal.attrib.get("exported"), False) "itemAttr": item,
else: "metaAttr": meta,
logger.warning("Ignored <root/content/item/%s> in xml", xVal.tag) "nameAttr": name,
})
projContent.append(item)
else:
logger.warning("Ignored item <root/content/%s> in xml", xItem.tag)
return return
def _parseProjectContentLegacy(self, xSection, projContent, projData): def _parseProjectContentLegacy(self, xSection, projData, projContent):
"""Parse the content section of the XML file for older versions. """Parse the content section of the XML file for older versions.
""" """
logger.debug("Parsing <content> section (legacy format)") logger.debug("Parsing <content> section (legacy format)")
# Create maps to look up name -> key for status and importance # Create maps to look up name -> key for status and importance
statusMap = {entry["name"]: key for key, entry in projData.itemStatus.items()} statusMap = {entry.get("name"): key for key, entry in projData.itemStatus.items()}
importMap = {entry["name"]: key for key, entry in projData.itemImport.items()} importMap = {entry.get("name"): key for key, entry in projData.itemImport.items()}
for xItem in xSection: for xItem in xSection:
if xItem.tag != "item":
logger.warning("Ignored item <root/content/%s> in XML", xItem.tag)
continue
item = {} item = {}
if xItem.tag == "item": meta = {}
item["handle"] = checkStringNone(xItem.attrib.get("handle", None), None) name = {}
item["parent"] = checkStringNone(xItem.attrib.get("parent", None), None) itemName = ""
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 = "" item["handle"] = checkStringNone(xItem.attrib.get("handle", None), None)
for xVal in xItem: item["parent"] = checkStringNone(xItem.attrib.get("parent", None), None)
if xVal.tag == "name": item["root"] = None # Value was added in 1.4
item["label"] = simplified(checkString(xVal.text, "")) item["order"] = checkInt(xItem.attrib.get("order", 0), 0)
elif xVal.tag == "status": meta["heading"] = "H0" # Value was added in 1.4
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 tmpStatus = ""
if item.get("class", "") in ("NOVEL", "ARCHIVE"): for xVal in xItem:
item["status"] = statusMap.get(tmpStatus, None) if xVal.tag == "name":
itemName = 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":
meta["expanded"] = checkBool(xVal.text, False)
elif xVal.tag == "exported": # Renamed to active in 1.5
name["active"] = checkBool(xVal.text, False)
elif xVal.tag == "charCount":
meta["charCount"] = checkInt(xVal.text, 0)
elif xVal.tag == "wordCount":
meta["wordCount"] = checkInt(xVal.text, 0)
elif xVal.tag == "paraCount":
meta["paraCount"] = checkInt(xVal.text, 0)
elif xVal.tag == "cursorPos":
meta["cursorPos"] = checkInt(xVal.text, 0)
else: else:
item["import"] = importMap.get(tmpStatus, None) logger.warning("Ignored <root/content/item/%s> in XML", xVal.tag)
# 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)
# Status was split into separate status/import with a key in 1.4
if item.get("class", "") in ("NOVEL", "ARCHIVE"):
name["status"] = statusMap.get(tmpStatus, None)
else: else:
logger.warning("Ignored <root/content/%s> in xml", xItem.tag) name["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 trash type was removed in 1.4
if item.get("type", "") == "TRASH":
item["type"] = "ROOT"
projContent.append({
"name": itemName,
"itemAttr": item,
"metaAttr": meta,
"nameAttr": name,
})
return return
@@ -418,7 +467,7 @@ class ProjectXMLReader:
"""Parse a dictionary stored with key as the tag and the value """Parse a dictionary stored with key as the tag and the value
as the text porperty. as the text porperty.
""" """
return {n.tag: checkString(n.text, "") for n in xItem} return {xNode.tag: checkString(xNode.text, "") for xNode in xItem}
# END Class ProjectXMLReader # END Class ProjectXMLReader
@@ -427,7 +476,7 @@ class ProjectXMLWriter:
def __init__(self, path): def __init__(self, path):
self._path = path self._path = Path(path)
self._error = None self._error = None
return return
@@ -458,22 +507,25 @@ class ProjectXMLWriter:
}) })
# Save Project Meta # Save Project Meta
xProject = etree.SubElement(xRoot, "project") projAttr = {
"id": projData.uuid,
"saveCount": str(projData.saveCount),
"autoCount": str(projData.autoCount),
"editTime": str(editTime),
}
xProject = etree.SubElement(xRoot, "project", attrib=projAttr)
self._packSingleValue(xProject, "name", projData.name) self._packSingleValue(xProject, "name", projData.name)
self._packSingleValue(xProject, "title", projData.title) self._packSingleValue(xProject, "title", projData.title)
self._packListValue(xProject, "author", projData.authors) 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 # Save Project Settings
xSettings = etree.SubElement(xRoot, "settings") xSettings = etree.SubElement(xRoot, "settings")
self._packSingleValue(xSettings, "doBackup", projData.doBackup) self._packSingleValue(xSettings, "doBackup", yesNo(projData.doBackup))
self._packSingleValue(xSettings, "language", projData.language) self._packSingleValue(xSettings, "language", projData.language)
self._packSingleValue(xSettings, "spellCheck", projData.spellCheck) self._packSingleValue(xSettings, "spellChecking", projData.spellLang, attrib={
self._packSingleValue(xSettings, "spellLang", projData.spellLang) "auto": yesNo(projData.spellCheck)
self._packSingleValue(xSettings, "novelWordCount", projData.currCounts[0]) })
self._packSingleValue(xSettings, "notesWordCount", projData.currCounts[1])
self._packDictKeyValue(xSettings, "lastHandle", projData.lastHandle) self._packDictKeyValue(xSettings, "lastHandle", projData.lastHandle)
self._packDictKeyValue(xSettings, "autoReplace", projData.autoReplace) self._packDictKeyValue(xSettings, "autoReplace", projData.autoReplace)
self._packDictKeyValue(xSettings, "titleFormat", projData.titleFormat) self._packDictKeyValue(xSettings, "titleFormat", projData.titleFormat)
@@ -488,25 +540,27 @@ class ProjectXMLWriter:
self._packSingleValue(xImport, "entry", label, attrib=attrib) self._packSingleValue(xImport, "entry", label, attrib=attrib)
# Save Tree Content # Save Tree Content
xContent = etree.SubElement(xRoot, "content", attrib={"count": str(len(projContent))}) contAttr = {
"items": str(len(projContent)),
"novelWords": str(projData.currCounts[0]),
"notesWords": str(projData.currCounts[1]),
}
xContent = etree.SubElement(xRoot, "content", attrib=contAttr)
for item in projContent: for item in projContent:
xItem = etree.SubElement(xContent, "item", attrib=item.get("itemAttr", {})) xItem = etree.SubElement(xContent, "item", attrib=item.get("itemAttr", {}))
etree.SubElement(xItem, "meta", attrib=item.get("metaAttr", {})) etree.SubElement(xItem, "meta", attrib=item.get("metaAttr", {}))
xName = etree.SubElement(xItem, "name", attrib=item.get("nameAttr", {})) xName = etree.SubElement(xItem, "name", attrib=item.get("nameAttr", {}))
xName.text = item["name"] xName.text = item["name"]
# Write the xml tree to file # Write the XML tree to file
saveFile = os.path.join(self._path, nwFiles.PROJ_FILE) saveFile = self._path / nwFiles.PROJ_FILE
tempFile = os.path.join(self._path, nwFiles.PROJ_FILE+"~") tempFile = saveFile.with_suffix(".tmp")
backFile = os.path.join(self._path, nwFiles.PROJ_FILE[:-3]+"bak") backFile = saveFile.with_suffix(".bak")
try: try:
with open(tempFile, mode="wb") as outFile: tempFile.write_bytes(etree.tostring(
outFile.write(etree.tostring( xRoot, pretty_print=True, encoding="utf-8", xml_declaration=True
xRoot, ))
pretty_print=True,
encoding="utf-8",
xml_declaration=True
))
except Exception as exc: except Exception as exc:
self._error = exc self._error = exc
return False return False
@@ -514,10 +568,10 @@ class ProjectXMLWriter:
# If we're here, the file was successfully saved, # If we're here, the file was successfully saved,
# so let's sort out the temps and backups # so let's sort out the temps and backups
try: try:
if os.path.isfile(saveFile): if saveFile.exists():
os.replace(saveFile, backFile) saveFile.replace(backFile)
os.replace(tempFile, saveFile) tempFile.replace(saveFile)
except OSError as exc: except Exception as exc:
self._error = exc self._error = exc
return False return False
@@ -530,14 +584,14 @@ class ProjectXMLWriter:
## ##
def _packSingleValue(self, xParent, name, value, attrib=None): def _packSingleValue(self, xParent, name, value, attrib=None):
"""Pack a single value into an xml element. """Pack a single value into an XML element.
""" """
xItem = etree.SubElement(xParent, name, attrib=attrib) xItem = etree.SubElement(xParent, name, attrib=attrib)
xItem.text = str(value) or "" xItem.text = str(value) or ""
return return
def _packListValue(self, xParent, name, data): def _packListValue(self, xParent, name, data):
"""Pack a list of values into an xml element. """Pack a list of values into an XML element.
""" """
for value in data: for value in data:
xItem = etree.SubElement(xParent, name) xItem = etree.SubElement(xParent, name)
@@ -545,7 +599,7 @@ class ProjectXMLWriter:
return return
def _packDictKeyValue(self, xParent, name, data): def _packDictKeyValue(self, xParent, name, data):
"""Pack the entries of a dictionary into an xml element. """Pack the entries of a dictionary into an XML element.
""" """
xItem = etree.SubElement(xParent, name) xItem = etree.SubElement(xParent, name)
for key, value in data.items(): for key, value in data.items():
+3 -3
View File
@@ -23,10 +23,10 @@ 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/>.
""" """
import os
import logging import logging
from collections import namedtuple from collections import namedtuple
from pathlib import Path
from novelwriter.error import logException from novelwriter.error import logException
@@ -173,10 +173,10 @@ class NWSpellEnchant:
self._projDict = set() self._projDict = set()
self._projectDict = projectDict self._projectDict = projectDict
if projectDict is None: if not isinstance(projectDict, Path):
return False return False
if not os.path.isfile(projectDict): if not projectDict.exists():
return False return False
try: try:
+396
View File
@@ -0,0 +1,396 @@
"""
novelWriter Project Storage Class
===================================
The main class handling the project storage
File History:
Created: 2022-11-01 [2.0rc1] NWStorage
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 logging
import novelwriter
from time import time
from pathlib import Path
from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile
from novelwriter.common import minmax
from novelwriter.constants import nwFiles
from novelwriter.core.document import NWDocument
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter
from novelwriter.error import logException
logger = logging.getLogger(__name__)
class NWStorage:
MODE_INACTIVE = 0
MODE_INPLACE = 1
MODE_ARCHIVE = 2
def __init__(self, theProject):
self.mainConf = novelwriter.CONFIG
self.theProject = theProject
self._storagePath = None
self._runtimePath = None
self._lockFilePath = None
self._openMode = self.MODE_INACTIVE
return
def clear(self):
"""Reset internal variables.
"""
self._storagePath = None
self._runtimePath = None
self._openMode = self.MODE_INACTIVE
return
##
# Properties
##
@property
def storagePath(self):
return self._storagePath
@property
def runtimePath(self):
return self._runtimePath
@property
def contentPath(self):
if self._runtimePath is not None:
return self._runtimePath / "content"
return None
##
# Core Methods
##
def isOpen(self):
"""Check if the storage location is open.
"""
return self._runtimePath is not None
def openProjectInPlace(self, path, newProject=False):
"""Open a novelWriter project in-place. That is, it is opened
directly from a project folder.
"""
inPath = Path(path).resolve()
if inPath.is_file():
# The path should not point to an exisitng file,
# but it can point to a folder containing files
inPath = inPath.parent
self._storagePath = inPath
self._runtimePath = inPath
self._lockFilePath = inPath / nwFiles.PROJ_LOCK
self._openMode = self.MODE_INPLACE
if not self._prepareStorage(checkLegacy=True, newProject=newProject):
self.clear()
return False
return True
def openProjectArchive(self, path): # pragma: no cover
pass
def runPostSaveTasks(self, autoSave=False): # pragma: no cover
"""Run tasks after the project has been saved.
"""
if self._openMode == self.MODE_INPLACE:
# Nothing to do, so we just return
return True
return True
def closeSession(self):
"""Run tasks related to closing the session.
"""
# Clear lockfile
self.clear()
return
##
# Content Access Methods
##
def getXmlReader(self):
"""Return a properly configured ProjectXMLReader instance.
"""
if self._runtimePath is None:
return None
projFile = self._runtimePath / nwFiles.PROJ_FILE
xmlReader = ProjectXMLReader(projFile)
return xmlReader
def getXmlWriter(self):
"""Return a properly configured ProjectXMLWriter instance.
"""
if self._runtimePath is None:
return None
xmlWriter = ProjectXMLWriter(self._runtimePath)
return xmlWriter
def getDocument(self, tHandle):
"""Return a document wrapper object.
"""
if self._runtimePath is not None:
return NWDocument(self.theProject, tHandle)
return NWDocument(self.theProject, None)
def getMetaFile(self, fileName):
"""Return the path to a file in the project meta folder.
"""
if self._runtimePath is not None:
return self._runtimePath / "meta" / fileName
return None
def getCacheFile(self, fileName):
"""Return the path to a file in the project cache folder.
"""
if self._runtimePath is not None:
return self._runtimePath / "cache" / fileName
return None
def readLockFile(self):
"""Read the project lock file.
"""
if self._lockFilePath is None:
return ["ERROR"]
if not self._lockFilePath.exists():
return []
try:
lines = self._lockFilePath.read_text(encoding="utf-8").split(";")
except Exception:
logger.error("Failed to read project lockfile")
logException()
return ["ERROR"]
if len(lines) != 4:
return ["ERROR"]
return lines
def writeLockFile(self):
"""Write the project lock file.
"""
if self._lockFilePath is None:
return False
data = [
self.mainConf.hostName, self.mainConf.osType,
self.mainConf.kernelVer, str(int(time()))
]
try:
self._lockFilePath.write_text(";".join(data), encoding="utf-8")
except Exception:
logger.error("Failed to write project lockfile")
logException()
return False
return True
def clearLockFile(self):
"""Remove the lock file, if it exists.
"""
if self._lockFilePath is None:
return False
if self._lockFilePath.exists():
try:
self._lockFilePath.unlink()
except Exception:
logger.error("Failed to remove project lockfile")
logException()
return False
return True
def zipIt(self, target, compression=None):
"""Zip the content of the project at its runtime location into a
zip file. This process will only grab files that are supposed to
be in the project. All non-project files will be left out.
"""
basePath = self._runtimePath
if not isinstance(basePath, Path):
logger.error("No path set")
return False
baseMeta = basePath / "meta"
baseCont = basePath / "content"
files = [
(basePath / nwFiles.PROJ_FILE, nwFiles.PROJ_FILE),
(baseMeta / nwFiles.OPTS_FILE, f"meta/{nwFiles.OPTS_FILE}"),
(baseMeta / nwFiles.SESS_STATS, f"meta/{nwFiles.SESS_STATS}"),
(baseMeta / nwFiles.INDEX_FILE, f"meta/{nwFiles.INDEX_FILE}"),
(baseMeta / nwFiles.PROJ_DICT, f"meta/{nwFiles.PROJ_DICT}"),
]
for contItem in baseCont.iterdir():
name = contItem.name
if contItem.is_file() and len(name) == 17 and name.endswith(".nwd"):
files.append((contItem, f"content/{name}"))
comp = ZIP_STORED if compression is None else ZIP_DEFLATED
level = minmax(compression, 0, 9) if isinstance(compression, int) else None
try:
with ZipFile(target, mode="w", compression=comp, compresslevel=level) as zipObj:
logger.info("Creating archive: %s", target)
for srcPath, zipPath in files:
if srcPath.is_file():
zipObj.write(srcPath, zipPath)
logger.debug("Added: %s", zipPath)
except Exception:
logger.error("Failed to create acrhive")
logException()
return False
return True
##
# Internal Functions
##
def _prepareStorage(self, checkLegacy=True, newProject=False):
"""Prepare the storage area for the project.
"""
path = self._runtimePath
if not isinstance(path, Path):
logger.error("No path set")
self.clear()
return False
if path == Path.home().absolute():
logger.error("Cannot use the user's home path as the root of a project")
self.clear()
return False
if newProject:
# If it's a new project, we check that there is no existing
# project in the selected path.
if path.exists() and len(list(path.iterdir())) > 0:
logger.error("The new project folder is not empty")
self.clear()
return False
# The folder is not required to exist, as it could be a new
# project, so we make sure it does. Then we add subfolders.
try:
path.mkdir(exist_ok=True)
(path / "content").mkdir(exist_ok=True)
(path / "cache").mkdir(exist_ok=True)
(path / "meta").mkdir(exist_ok=True)
except Exception as exc:
logger.error("Failed to create required project folders", exc_info=exc)
self.clear()
return False
if not checkLegacy:
# The legacy content check is only needed for project folder
# storage, so if it is not expected to be that, there's no
# need for the remaning checks.
return True
# Check for legacy data folders
for child in path.iterdir():
if child.is_dir() and child.name.startswith("data_"):
self._legacyDataFolder(path, child)
# Check for no longer used files, and delete them
self._deleteDeprecatedFiles(path)
return True
##
# Legacy Project Data Handlers
##
def _legacyDataFolder(self, path: Path, child: Path):
"""Handle the content of a legacy data folder from a version 1.0
project.
"""
logger.info("Processing legacy data folder: %s", path)
# Move Documents to Content
first = child.name[-1]
if first not in "0123456789abcdef":
return
for item in child.iterdir():
if not item.is_file():
continue
name = item.name
if len(name) == 21 and name.endswith("_main.nwd"):
newPath = path / "content" / f"{first}{name[:12]}.nwd"
try:
item.rename(newPath)
logger.info("Moved file: %s", newPath)
except Exception as exc:
logger.warning("Failed to move: %s", item, exc_info=exc)
elif len(name) == 21 and name.endswith("_main.bak"):
try:
item.unlink()
logger.info("Deleted file: %s", item)
except Exception as exc:
logger.warning("Failed to delete: %s", item, exc_info=exc)
# Remove Data Folder
try:
child.rmdir()
logger.info("Deleted folder: %s", child)
except Exception as exc:
logger.warning("Failed to delete: %s", child, exc_info=exc)
return
def _deleteDeprecatedFiles(self, path: Path):
"""Delete files that are no longer used by novelWriter.
"""
remove = [
path / "meta" / "mainOptions.json", # Replaced in 0.5
path / "meta" / "exportOptions.json", # Replaced in 0.5
path / "meta" / "outlineOptions.json", # Replaced in 0.5
path / "meta" / "timelineOptions.json", # Replaced in 0.5
path / "meta" / "docMergeOptions.json", # Replaced in 0.5
path / "meta" / "sessionLogOptions.json", # Replaced in 0.5
path / "ToC.json", # Dropped in 1.0 RC 1
]
for item in remove:
if item.is_file():
try:
item.unlink()
logger.info("Deleted: %s", item)
except Exception as exc:
logger.warning("Failed to delete: %s", item, exc_info=exc)
return
# END Class NWStorage
+1 -2
View File
@@ -36,7 +36,6 @@ from PyQt5.QtCore import QCoreApplication, QRegularExpression
from novelwriter.enum import nwItemLayout, nwItemType from novelwriter.enum import nwItemLayout, nwItemType
from novelwriter.common import numberToRoman, checkInt from novelwriter.common import numberToRoman, checkInt
from novelwriter.constants import nwConst, nwRegEx, nwUnicode from novelwriter.constants import nwConst, nwRegEx, nwUnicode
from novelwriter.core.document import NWDoc
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -305,7 +304,7 @@ class Tokenizer(ABC):
return False return False
if theText is None: if theText is None:
theText = NWDoc(self.theProject, theHandle).readDocument() or "" theText = self.theProject.storage.getDocument(theHandle).readDocument() or ""
self._theText = theText self._theText = theText
+11 -4
View File
@@ -23,10 +23,11 @@ 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/>.
""" """
import os
import random import random
import logging import logging
from pathlib import Path
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
@@ -140,16 +141,22 @@ class NWTree:
"""Write the convenience table of contents file in the root of """Write the convenience table of contents file in the root of
the project directory. the project directory.
""" """
runtimePath = self.theProject.storage.runtimePath
contentPath = self.theProject.storage.contentPath
if not (isinstance(contentPath, Path) and isinstance(runtimePath, Path)):
return False
tocList = [] tocList = []
tocLen = 0 tocLen = 0
for tHandle in self._treeOrder: for tHandle in self._treeOrder:
tItem = self.__getitem__(tHandle) tItem = self.__getitem__(tHandle)
if tItem is None: if tItem is None:
continue continue
tFile = tHandle+".nwd" tFile = tHandle+".nwd"
if os.path.isfile(os.path.join(self.theProject.projContent, tFile)): if (contentPath / tFile).is_file():
tocLine = "{0:<25s} {1:<9s} {2:<8s} {3:s}".format( tocLine = "{0:<25s} {1:<9s} {2:<8s} {3:s}".format(
os.path.join("content", tFile), str(Path("content") / tFile),
tItem.itemClass.name, tItem.itemClass.name,
tItem.itemLayout.name, tItem.itemLayout.name,
tItem.itemName, tItem.itemName,
@@ -159,7 +166,7 @@ class NWTree:
try: try:
# Dump the text # Dump the text
tocText = os.path.join(self.theProject.projPath, nwFiles.TOC_TXT) tocText = runtimePath / nwFiles.TOC_TXT
with open(tocText, mode="w", encoding="utf-8") as outFile: with open(tocText, mode="w", encoding="utf-8") as outFile:
outFile.write("\n") outFile.write("\n")
outFile.write("Table of Contents\n") outFile.write("Table of Contents\n")
+1 -2
View File
@@ -33,7 +33,6 @@ from PyQt5.QtWidgets import (
QListWidgetItem, QDialogButtonBox, QLabel, QGridLayout QListWidgetItem, QDialogButtonBox, QLabel, QGridLayout
) )
from novelwriter.core import NWDoc
from novelwriter.custom import QHelpLabel, QSwitch from novelwriter.custom import QHelpLabel, QSwitch
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -204,7 +203,7 @@ class GuiDocSplit(QDialog):
spLevel = self.splitLevel.currentData() spLevel = self.splitLevel.currentData()
if not self._text: if not self._text:
inDoc = NWDoc(self.theProject, sHandle) inDoc = self.theProject.storage.getDocument(sHandle)
self._text = (inDoc.readDocument() or "").splitlines() self._text = (inDoc.readDocument() or "").splitlines()
for lineNo, aLine in enumerate(self._text): for lineNo, aLine in enumerate(self._text):
+1 -1
View File
@@ -260,7 +260,7 @@ class GuiProjectDetailsMain(QWidget):
self.revCountVal.setText(f"{self.theProject.data.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(str(self.theProject.storage.storagePath))
return return
+13 -9
View File
@@ -23,10 +23,11 @@ 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/>.
""" """
import os
import logging import logging
import novelwriter import novelwriter
from pathlib import Path
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QDialogButtonBox, QVBoxLayout, QHBoxLayout, QListWidget, QDialog, QDialogButtonBox, QVBoxLayout, QHBoxLayout, QListWidget,
@@ -150,9 +151,11 @@ class GuiWordList(QDialog):
""" """
self._saveGuiSettings() self._saveGuiSettings()
dctFile = os.path.join(self.theProject.projMeta, nwFiles.PROJ_DICT) dctFile = self.theProject.storage.getMetaFile(nwFiles.PROJ_DICT)
tmpFile = dctFile + "~" if not isinstance(dctFile, Path):
return False
tmpFile = dctFile.with_suffix(".tmp")
try: try:
with open(tmpFile, mode="w", encoding="utf-8") as outFile: with open(tmpFile, mode="w", encoding="utf-8") as outFile:
for i in range(self.listBox.count()): for i in range(self.listBox.count()):
@@ -160,15 +163,14 @@ class GuiWordList(QDialog):
if item is not None: if item is not None:
outFile.write(item.text() + "\n") outFile.write(item.text() + "\n")
tmpFile.replace(dctFile)
except Exception: except Exception:
logger.error("Could not save new word list") logger.error("Could not save new word list")
logException() logException()
self.reject() self.reject()
return False return False
if os.path.isfile(dctFile):
os.unlink(dctFile)
os.rename(tmpFile, dctFile)
self.accept() self.accept()
return True return True
@@ -187,10 +189,12 @@ class GuiWordList(QDialog):
def _loadWordList(self): def _loadWordList(self):
"""Load the project's word list, if it exists. """Load the project's word list, if it exists.
""" """
self.listBox.clear() wordList = self.theProject.storage.getMetaFile(nwFiles.PROJ_DICT)
if not isinstance(wordList, Path):
return False
wordList = os.path.join(self.theProject.projMeta, nwFiles.PROJ_DICT) self.listBox.clear()
if not os.path.isfile(wordList): if not wordList.exists():
logger.debug("No project dictionary file found") logger.debug("No project dictionary file found")
return False return False
+7 -6
View File
@@ -50,10 +50,10 @@ from PyQt5.QtWidgets import (
QFrame QFrame
) )
from novelwriter.core import NWDoc, NWSpellEnchant, countWords from novelwriter.core import NWSpellEnchant, countWords
from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert, nwDocMode from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert, nwDocMode
from novelwriter.common import transferCase from novelwriter.common import transferCase
from novelwriter.constants import nwConst, nwKeyWords, nwUnicode from novelwriter.constants import nwConst, nwFiles, nwKeyWords, nwUnicode
from novelwriter.gui.dochighlight import GuiDocHighlighter from novelwriter.gui.dochighlight import GuiDocHighlighter
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -339,7 +339,7 @@ class GuiDocEditor(QTextEdit):
document is new (empty string), we set up the editor for editing document is new (empty string), we set up the editor for editing
the file. the file.
""" """
self._nwDocument = NWDoc(self.theProject, tHandle) self._nwDocument = self.theProject.storage.getDocument(tHandle)
self._nwItem = self._nwDocument.getCurrentItem() self._nwItem = self._nwDocument.getCurrentItem()
theDoc = self._nwDocument.readDocument() theDoc = self._nwDocument.readDocument()
@@ -470,8 +470,8 @@ class GuiDocEditor(QTextEdit):
return True return True
def saveText(self): def saveText(self):
"""Save the text currently in the editor to the NWDoc object, """Save the text currently in the editor to the NWDocument
and update the NWItem meta data. object, and update the NWItem meta data.
""" """
if self._nwItem is None or self._nwDocument is None: if self._nwItem is None or self._nwDocument is None:
logger.error("Cannot save text as no document is open") logger.error("Cannot save text as no document is open")
@@ -689,7 +689,8 @@ class GuiDocEditor(QTextEdit):
else: else:
theLang = self.theProject.data.spellLang theLang = self.theProject.data.spellLang
self.spEnchant.setLanguage(theLang, self.theProject.projDict) projDict = self.theProject.storage.getMetaFile(nwFiles.PROJ_DICT)
self.spEnchant.setLanguage(theLang, projDict)
_, theProvider = self.spEnchant.describeDict() _, theProvider = self.spEnchant.describeDict()
self.spellDictionaryChanged.emit(str(theLang), str(theProvider)) self.spellDictionaryChanged.emit(str(theLang), str(theProvider))
+1 -1
View File
@@ -824,7 +824,7 @@ class GuiMainMenu(QMenuBar):
# Tools > Backup # Tools > Backup
self.aBackupProject = QAction(self.tr("Backup Project"), self) self.aBackupProject = QAction(self.tr("Backup Project"), self)
self.aBackupProject.triggered.connect(lambda: self.theProject.zipIt(True)) self.aBackupProject.triggered.connect(lambda: self.theProject.backupProject(doNoify=True))
self.toolsMenu.addAction(self.aBackupProject) self.toolsMenu.addAction(self.aBackupProject)
# Tools > Export Project # Tools > Export Project
+8 -33
View File
@@ -50,9 +50,9 @@ from novelwriter.dialogs import (
from novelwriter.tools import ( from novelwriter.tools import (
GuiBuildNovel, GuiLipsum, GuiProjectWizard, GuiWritingStats GuiBuildNovel, GuiLipsum, GuiProjectWizard, GuiWritingStats
) )
from novelwriter.core import NWProject from novelwriter.core import NWProject, ProjectBuilder
from novelwriter.enum import ( from novelwriter.enum import (
nwDocMode, nwItemType, nwItemClass, nwAlert, nwWidget, nwState, nwView nwDocMode, nwItemType, nwItemClass, nwAlert, nwWidget, nwView
) )
from novelwriter.common import getGuiItem, hexToInt from novelwriter.common import getGuiItem, hexToInt
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
@@ -365,30 +365,10 @@ class GuiMain(QMainWindow):
return False return False
logger.info("Creating new project") logger.info("Creating new project")
if self.theProject.newProject(projData): nwProject = ProjectBuilder(self)
if nwProject.buildProject(projData):
self.hasProject = True self.openProject(projPath)
self.idleRefTime = time()
self.idleTime = 0.0
self.rebuildTrees()
self.saveProject()
self.docEditor.setDictionaries()
self.projView.openProjectTasks()
self.novelView.openProjectTasks()
self.outlineView.openProjectTasks()
self.rebuildIndex(beQuiet=True)
self.mainStatus.setRefTime(self.theProject.projOpened)
self.mainStatus.setProjectStatus(nwState.GOOD)
self.mainStatus.setDocumentStatus(nwState.NONE)
self.mainStatus.setStatus(self.tr("New project created ..."))
self._updateWindowTitle(self.theProject.data.name)
else: else:
self.theProject.clearProject()
return False return False
return True return True
@@ -429,7 +409,7 @@ class GuiMain(QMainWindow):
if not msgYes: if not msgYes:
doBackup = False doBackup = False
if doBackup: if doBackup:
self.theProject.zipIt(False) self.theProject.backupProject(doNotify=False)
else: else:
saveOK = True saveOK = True
@@ -443,7 +423,6 @@ class GuiMain(QMainWindow):
self.idleRefTime = time() self.idleRefTime = time()
self.idleTime = 0.0 self.idleTime = 0.0
self.theProject.index.clearIndex()
self.clearGUI() self.clearGUI()
self.hasProject = False self.hasProject = False
self._changeView(nwView.PROJECT) self._changeView(nwView.PROJECT)
@@ -519,9 +498,6 @@ class GuiMain(QMainWindow):
self.idleRefTime = time() self.idleRefTime = time()
self.idleTime = 0.0 self.idleTime = 0.0
# Load the tag index
self.theProject.index.loadIndex()
# Update GUI # Update GUI
self._updateWindowTitle(self.theProject.data.name) self._updateWindowTitle(self.theProject.data.name)
self.rebuildTrees() self.rebuildTrees()
@@ -573,8 +549,7 @@ class GuiMain(QMainWindow):
return False return False
self.projView.saveProjectTasks() self.projView.saveProjectTasks()
if self.theProject.saveProject(autoSave=autoSave): self.theProject.saveProject(autoSave=autoSave)
self.theProject.index.saveIndex()
return True return True
@@ -1407,7 +1382,7 @@ class GuiMain(QMainWindow):
""" """
doSave = self.hasProject doSave = self.hasProject
doSave &= self.theProject.projChanged doSave &= self.theProject.projChanged
doSave &= self.theProject.projPath is not None doSave &= self.theProject.storage.isOpen()
if doSave: if doSave:
logger.debug("Autosaving project") logger.debug("Autosaving project")
+10 -3
View File
@@ -29,6 +29,7 @@ import logging
import novelwriter import novelwriter
from time import time from time import time
from pathlib import Path
from datetime import datetime from datetime import datetime
from PyQt5.QtGui import ( from PyQt5.QtGui import (
@@ -1088,9 +1089,12 @@ class GuiBuildNovel(QDialog):
def _loadCache(self): def _loadCache(self):
"""Save the current data to cache. """Save the current data to cache.
""" """
buildCache = os.path.join(self.theProject.projCache, nwFiles.BUILD_CACHE) buildCache = self.theProject.storage.getCacheFile(nwFiles.BUILD_CACHE)
if not isinstance(buildCache, Path):
return False
dataCount = 0 dataCount = 0
if os.path.isfile(buildCache): if buildCache.exists():
logger.debug("Loading build cache") logger.debug("Loading build cache")
try: try:
with open(buildCache, mode="r", encoding="utf-8") as inFile: with open(buildCache, mode="r", encoding="utf-8") as inFile:
@@ -1115,7 +1119,10 @@ class GuiBuildNovel(QDialog):
def _saveCache(self): def _saveCache(self):
"""Save the current data to cache. """Save the current data to cache.
""" """
buildCache = os.path.join(self.theProject.projCache, nwFiles.BUILD_CACHE) buildCache = self.theProject.storage.getCacheFile(nwFiles.BUILD_CACHE)
if not isinstance(buildCache, Path):
return False
logger.debug("Saving build cache") logger.debug("Saving build cache")
try: try:
with open(buildCache, mode="w+", encoding="utf-8") as outFile: with open(buildCache, mode="w+", encoding="utf-8") as outFile:
+3 -2
View File
@@ -28,6 +28,7 @@ import json
import logging import logging
import novelwriter import novelwriter
from pathlib import Path
from datetime import datetime from datetime import datetime
from PyQt5.QtGui import QPixmap, QCursor from PyQt5.QtGui import QPixmap, QCursor
@@ -439,8 +440,8 @@ class GuiWritingStats(QDialog):
ttTime = 0 ttTime = 0
ttIdle = 0 ttIdle = 0
logFile = os.path.join(self.theProject.projMeta, nwFiles.SESS_STATS) logFile = self.theProject.storage.getMetaFile(nwFiles.SESS_STATS)
if not os.path.isfile(logFile): if not isinstance(logFile, Path) or not logFile.exists():
logger.info("This project has no writing stats logfile") logger.info("This project has no writing stats logfile")
return False return False
+51 -57
View File
@@ -1,21 +1,15 @@
<?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-11-01 12:20:53"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 13:00:48">
<project> <project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1448" autoCount="237" editTime="69737">
<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>1409</saveCount>
<autoCount>236</autoCount>
<editTime>69427</editTime>
</project> </project>
<settings> <settings>
<doBackup>False</doBackup> <doBackup>no</doBackup>
<language>en_GB</language> <language>en_GB</language>
<spellCheck>True</spellCheck> <spellChecking auto="yes">None</spellChecking>
<spellLang>None</spellLang>
<novelWordCount>954</novelWordCount>
<notesWordCount>409</notesWordCount>
<lastHandle> <lastHandle>
<entry key="editor">636b6aa9b697b</entry> <entry key="editor">636b6aa9b697b</entry>
<entry key="viewer">636b6aa9b697b</entry> <entry key="viewer">636b6aa9b697b</entry>
@@ -50,114 +44,114 @@
<entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry> <entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="27"> <content items="27" novelWords="954" notesWords="409">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL"> <item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/> <meta expanded="yes"/>
<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" heading="H1" charCount="93" wordCount="19" paraCount="2" cursorPos="119"/> <meta expanded="no" 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="yes">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" heading="H0" charCount="251" wordCount="50" paraCount="2" cursorPos="277"/> <meta expanded="no" 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="yes">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" heading="H1" charCount="26" wordCount="6" paraCount="1" cursorPos="36"/> <meta expanded="no" 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="yes">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" heading="H2" charCount="95" wordCount="18" paraCount="1" cursorPos="291"/> <meta expanded="yes" 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="yes">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" heading="H3" charCount="2687" wordCount="479" paraCount="14" cursorPos="67"/> <meta expanded="no" 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="yes">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" heading="H3" charCount="548" wordCount="108" paraCount="3" cursorPos="465"/> <meta expanded="no" 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="yes">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" heading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="310"/> <meta expanded="no" 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="yes">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" heading="H1" charCount="1909" wordCount="346" paraCount="7" cursorPos="0"/> <meta expanded="no" 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="no">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" heading="H2" charCount="139" wordCount="28" paraCount="1" cursorPos="188"/> <meta expanded="yes" 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="yes">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" heading="H3" charCount="189" wordCount="37" paraCount="1" cursorPos="0"/> <meta expanded="no" 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="yes">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">
<meta expanded="True"/> <meta expanded="yes"/>
<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" heading="H1" charCount="27" wordCount="5" paraCount="1" cursorPos="100"/> <meta expanded="no" 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="yes">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" heading="H2" charCount="299" wordCount="55" paraCount="2" cursorPos="104"/> <meta expanded="no" 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="yes">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">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="sf12341" import="ia857f0">Characters</name> <name status="sf12341" import="ia857f0">Characters</name>
</item> </item>
<item handle="f7e2d9f330615" parent="f6622b4617424" root="f6622b4617424" order="0" type="FOLDER" class="CHARACTER"> <item handle="f7e2d9f330615" parent="f6622b4617424" root="f6622b4617424" order="0" type="FOLDER" class="CHARACTER">
<meta expanded="True"/> <meta expanded="yes"/>
<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" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/> <meta expanded="no" 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="yes">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" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/> <meta expanded="no" 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="yes">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">
<meta expanded="True"/> <meta expanded="yes"/>
<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" heading="H1" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/> <meta expanded="no" 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="yes">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" heading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/> <meta expanded="no" 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="yes">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" heading="H1" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/> <meta expanded="no" 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="yes">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">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="sf12341" import="ia857f0">Archive</name> <name status="sf12341" import="ia857f0">Archive</name>
</item> </item>
<item handle="ae9bf3c3ea159" parent="6827118336ac1" root="6827118336ac1" order="0" type="FOLDER" class="ARCHIVE"> <item handle="ae9bf3c3ea159" parent="6827118336ac1" root="6827118336ac1" order="0" type="FOLDER" class="ARCHIVE">
<meta expanded="True"/> <meta expanded="yes"/>
<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" heading="H3" charCount="232" wordCount="42" paraCount="1" cursorPos="239"/> <meta expanded="no" 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="yes">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">
<meta expanded="True"/> <meta expanded="yes"/>
<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" heading="H3" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/> <meta expanded="no" 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="yes">Delete Me!</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
+39 -23
View File
@@ -24,10 +24,12 @@ import sys
import pytest import pytest
import shutil import shutil
from pathlib import Path
from mock import MockGuiMain from mock import MockGuiMain
from tools import cleanProject from tools import cleanProject
sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) sys.path.insert(1, str(Path(__file__).parent.parent.absolute()))
import novelwriter # noqa: E402 import novelwriter # noqa: E402
@@ -62,6 +64,41 @@ def tmpDir():
return theDir return theDir
@pytest.fixture(scope="session")
def tmpPath(tmpDir):
"""A temporary folder for the test session. Path version.
"""
return Path(tmpDir)
@pytest.fixture(scope="session")
def tstPaths(tmpPath):
"""Returns an object that can provide the various paths needed for
running tests.
"""
class _Store:
testDir = Path(__file__).parent
filesDir = testDir / "files"
refDir = testDir / "reference"
outDir = tmpPath / "results"
store = _Store()
store.outDir.mkdir(exist_ok=True)
return store
@pytest.fixture(scope="function")
def fncPath(tmpPath):
"""A temporary folder for a single test function. Path version.
"""
fncPath = tmpPath / "function"
if fncPath.is_dir():
shutil.rmtree(fncPath)
fncPath.mkdir(exist_ok=True)
return fncPath
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def refDir(): def refDir():
"""The folder where all the reference files are stored for verifying """The folder where all the reference files are stored for verifying
@@ -95,7 +132,7 @@ def outDir(tmpDir):
def fncDir(tmpDir): def fncDir(tmpDir):
"""A temporary folder for a single test function. """A temporary folder for a single test function.
""" """
fncDir = os.path.join(tmpDir, "f_temp") fncDir = os.path.join(tmpDir, "function")
if os.path.isdir(fncDir): if os.path.isdir(fncDir):
shutil.rmtree(fncDir) shutil.rmtree(fncDir)
if not os.path.isdir(fncDir): if not os.path.isdir(fncDir):
@@ -236,27 +273,6 @@ def nwLipsum(tmpDir):
return return
@pytest.fixture(scope="function")
def nwOldProj(tmpDir):
"""A minimal movelWriter project using the old folder structure used
for storage versions < 1.2.
"""
tstDir = os.path.dirname(__file__)
srcDir = os.path.join(tstDir, "oldproj")
dstDir = os.path.join(tmpDir, "oldproj")
if os.path.isdir(dstDir):
shutil.rmtree(dstDir)
shutil.copytree(srcDir, dstDir)
yield dstDir
if os.path.isdir(dstDir):
shutil.rmtree(dstDir)
return
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def ipsumText(): def ipsumText():
"""Return five paragraphs of Lorem Ipsum text. """Return five paragraphs of Lorem Ipsum text.
+49 -50
View File
@@ -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-11-01 12:20:53"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-15 12:12:59">
<project> <project>
<name>Sample Project</name> <name>Sample Project</name>
<title>Sample Project</title> <title>Sample Project</title>
@@ -14,25 +14,24 @@
<language>en_GB</language> <language>en_GB</language>
<spellCheck>True</spellCheck> <spellCheck>True</spellCheck>
<spellLang>en_GB</spellLang> <spellLang>en_GB</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>
<entry key="title">%title%</entry> <title>%title%</title>
<entry key="chapter">Chapter %chw%: %title%</entry> <chapter>Chapter %chw%: %title%</chapter>
<entry key="unnumbered">%title%</entry> <unnumbered>%title%</unnumbered>
<entry key="scene">Scene %ch%.%sc%: %title%</entry> <scene>Scene %ch%.%sc%: %title%</scene>
<entry key="section"></entry> <section></section>
</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>
@@ -56,56 +55,56 @@
<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" heading="H1" charCount="93" wordCount="19" paraCount="2" cursorPos="119"/> <meta expanded="False" charCount="93" wordCount="19" paraCount="2" cursorPos="119"/>
<name status="sc24b8f" import="ia857f0" active="True">Title Page</name> <name status="sc24b8f" import="ia857f0" exported="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" heading="H0" charCount="251" wordCount="50" paraCount="2" cursorPos="277"/> <meta expanded="False" charCount="251" wordCount="50" paraCount="2" cursorPos="277"/>
<name status="sf12341" import="ia857f0" active="True">Page</name> <name status="sf12341" import="ia857f0" exported="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" heading="H1" charCount="26" wordCount="6" paraCount="1" cursorPos="36"/> <meta expanded="False" charCount="26" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s90e6c9" import="ia857f0" active="True">Part One</name> <name status="s90e6c9" import="ia857f0" exported="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" heading="H2" charCount="95" wordCount="18" paraCount="1" cursorPos="291"/> <meta expanded="True" charCount="95" wordCount="18" paraCount="1" cursorPos="291"/>
<name status="sf24ce6" import="ia857f0" active="True">Chapter One</name> <name status="sf24ce6" import="ia857f0" exported="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" heading="H3" charCount="2687" wordCount="479" paraCount="14" cursorPos="67"/> <meta expanded="False" charCount="2687" wordCount="479" paraCount="14" cursorPos="67"/>
<name status="s90e6c9" import="ia857f0" active="True">Making a Scene</name> <name status="s90e6c9" import="ia857f0" exported="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" heading="H3" charCount="548" wordCount="108" paraCount="3" cursorPos="465"/> <meta expanded="False" charCount="548" wordCount="108" paraCount="3" cursorPos="465"/>
<name status="s90e6c9" import="ia857f0" active="True">Another Scene</name> <name status="s90e6c9" import="ia857f0" exported="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" heading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="310"/> <meta expanded="False" charCount="617" wordCount="101" paraCount="3" cursorPos="310"/>
<name status="s78ea90" import="ia857f0" active="True">Interlude</name> <name status="s78ea90" import="ia857f0" exported="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" heading="H1" charCount="1909" wordCount="346" paraCount="7" cursorPos="0"/> <meta expanded="False" 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" exported="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" heading="H2" charCount="139" wordCount="28" paraCount="1" cursorPos="188"/> <meta expanded="True" charCount="139" wordCount="28" paraCount="1" cursorPos="188"/>
<name status="s90e6c9" import="ia857f0" active="True">Chapter Two</name> <name status="s90e6c9" import="ia857f0" exported="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" heading="H3" charCount="189" wordCount="37" paraCount="1" cursorPos="0"/> <meta expanded="False" charCount="189" wordCount="37" paraCount="1" cursorPos="0"/>
<name status="s90e6c9" import="ia857f0" active="True">We Found John!</name> <name status="s90e6c9" import="ia857f0" exported="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">
<meta expanded="True"/> <meta expanded="True"/>
<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" heading="H1" charCount="27" wordCount="5" paraCount="1" cursorPos="100"/> <meta expanded="False" charCount="27" wordCount="5" paraCount="1" cursorPos="100"/>
<name status="sc24b8f" import="ia857f0" active="True">Title Page</name> <name status="sc24b8f" import="ia857f0" exported="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" heading="H2" charCount="299" wordCount="55" paraCount="2" cursorPos="104"/> <meta expanded="False" charCount="299" wordCount="55" paraCount="2" cursorPos="104"/>
<name status="s90e6c9" import="ia857f0" active="True">Chapter One</name> <name status="s90e6c9" import="ia857f0" exported="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">
<meta expanded="True"/> <meta expanded="True"/>
@@ -116,28 +115,28 @@
<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" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/> <meta expanded="False" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="sf12341" import="icfb3a5" active="True">John Smith</name> <name status="sf12341" import="icfb3a5" exported="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" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/> <meta expanded="False" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="sf12341" import="i2d7a54" active="True">Jane Smith</name> <name status="sf12341" import="i2d7a54" exported="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">
<meta expanded="True"/> <meta expanded="True"/>
<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" heading="H1" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/> <meta expanded="False" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="sf12341" import="i56be10" active="True">Earth</name> <name status="sf12341" import="i56be10" exported="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" heading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/> <meta expanded="False" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="sf12341" import="icfb3a5" active="True">Space</name> <name status="sf12341" import="icfb3a5" exported="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" heading="H1" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/> <meta expanded="False" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="sf12341" import="i2d7a54" active="True">Mars</name> <name status="sf12341" import="i2d7a54" exported="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">
<meta expanded="True"/> <meta expanded="True"/>
@@ -148,16 +147,16 @@
<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" heading="H3" charCount="232" wordCount="42" paraCount="1" cursorPos="239"/> <meta expanded="False" charCount="232" wordCount="42" paraCount="1" cursorPos="239"/>
<name status="s90e6c9" import="ia857f0" active="True">Old File</name> <name status="s90e6c9" import="ia857f0" exported="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">
<meta expanded="True"/> <meta expanded="True"/>
<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" heading="H3" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/> <meta expanded="False" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="sf12341" import="ia857f0" active="True">Delete Me!</name> <name status="sf12341" import="ia857f0" exported="True">Delete Me!</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
+157
View File
@@ -0,0 +1,157 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 13:00:48">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="5" autoCount="10" editTime="1000">
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
</project>
<settings>
<doBackup>yes</doBackup>
<language>en_GB</language>
<spellChecking auto="yes">en_GB</spellChecking>
<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 items="27" novelWords="954" notesWords="409">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="yes"/>
<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="no" heading="H1" charCount="93" wordCount="19" paraCount="2" cursorPos="119"/>
<name status="sc24b8f" import="ia857f0" active="yes">Title Page</name>
</item>
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H0" charCount="251" wordCount="50" paraCount="2" cursorPos="277"/>
<name status="sf12341" import="ia857f0" active="yes">Page</name>
</item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H1" charCount="26" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s90e6c9" import="ia857f0" active="yes">Part One</name>
</item>
<item handle="6a2d6d5f4f401" parent="7031beac91f75" root="7031beac91f75" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="yes" heading="H2" charCount="95" wordCount="18" paraCount="1" cursorPos="291"/>
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
</item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="2687" wordCount="479" paraCount="14" cursorPos="67"/>
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
</item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="548" wordCount="108" paraCount="3" cursorPos="465"/>
<name status="s90e6c9" import="ia857f0" active="yes">Another Scene</name>
</item>
<item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="310"/>
<name status="s78ea90" import="ia857f0" active="yes">Interlude</name>
</item>
<item handle="96b68994dfa3d" parent="7031beac91f75" root="7031beac91f75" order="5" type="FILE" class="NOVEL" layout="NOTE">
<meta expanded="no" heading="H1" charCount="1909" wordCount="346" paraCount="7" cursorPos="0"/>
<name status="sf24ce6" import="ia857f0" active="no">A Note on Structure</name>
</item>
<item handle="88706ddc78b1b" parent="7031beac91f75" root="7031beac91f75" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="yes" heading="H2" charCount="139" wordCount="28" paraCount="1" cursorPos="188"/>
<name status="s90e6c9" import="ia857f0" active="yes">Chapter Two</name>
</item>
<item handle="ae7339df26ded" parent="88706ddc78b1b" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="189" wordCount="37" paraCount="1" cursorPos="0"/>
<name status="s90e6c9" import="ia857f0" active="yes">We Found John!</name>
</item>
<item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL">
<meta expanded="yes"/>
<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="no" heading="H1" charCount="27" wordCount="5" paraCount="1" cursorPos="100"/>
<name status="sc24b8f" import="ia857f0" active="yes">Title Page</name>
</item>
<item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H2" charCount="299" wordCount="55" paraCount="2" cursorPos="104"/>
<name status="s90e6c9" import="ia857f0" active="yes">Chapter One</name>
</item>
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="2" type="ROOT" class="CHARACTER">
<meta expanded="yes"/>
<name status="sf12341" import="ia857f0">Characters</name>
</item>
<item handle="f7e2d9f330615" parent="f6622b4617424" root="f6622b4617424" order="0" type="FOLDER" class="CHARACTER">
<meta expanded="yes"/>
<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="no" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="sf12341" import="icfb3a5" active="yes">John Smith</name>
</item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="no" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="sf12341" import="i2d7a54" active="yes">Jane Smith</name>
</item>
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD">
<meta expanded="yes"/>
<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="no" heading="H1" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="sf12341" import="i56be10" active="yes">Earth</name>
</item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="no" heading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="sf12341" import="icfb3a5" active="yes">Space</name>
</item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="no" heading="H1" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="sf12341" import="i2d7a54" active="yes">Mars</name>
</item>
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="4" type="ROOT" class="ARCHIVE">
<meta expanded="yes"/>
<name status="sf12341" import="ia857f0">Archive</name>
</item>
<item handle="ae9bf3c3ea159" parent="6827118336ac1" root="6827118336ac1" order="0" type="FOLDER" class="ARCHIVE">
<meta expanded="yes"/>
<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="no" heading="H3" charCount="232" wordCount="42" paraCount="1" cursorPos="239"/>
<name status="s90e6c9" import="ia857f0" active="yes">Old File</name>
</item>
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="5" type="ROOT" class="TRASH">
<meta expanded="yes"/>
<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="no" heading="H3" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="sf12341" import="ia857f0" active="yes">Delete Me!</name>
</item>
</content>
</novelWriterXML>
+41 -48
View File
@@ -1,21 +1,14 @@
<?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-31 22:17:38"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 13:00:43">
<project> <project id="5ac0df12-8b8b-476b-9905-21d33685687c" saveCount="38" autoCount="24" editTime="1900">
<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>32</saveCount>
<autoCount>24</autoCount>
<editTime>1889</editTime>
</project> </project>
<settings> <settings>
<doBackup>False</doBackup> <doBackup>no</doBackup>
<language>en_GB</language> <language>en_GB</language>
<spellCheck>False</spellCheck> <spellChecking auto="no">None</spellChecking>
<spellLang>None</spellLang>
<totalWordCount>3847</totalWordCount>
<novelWordCount>3109</novelWordCount>
<notesWordCount>738</notesWordCount>
<lastHandle> <lastHandle>
<entry key="editor">7a992350f3eb6</entry> <entry key="editor">7a992350f3eb6</entry>
<entry key="viewer">None</entry> <entry key="viewer">None</entry>
@@ -46,90 +39,90 @@
<entry key="id6b1d0" count="0" red="50" green="200" blue="0">Main</entry> <entry key="id6b1d0" count="0" red="50" green="200" blue="0">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="21"> <content items="21" novelWords="3109" notesWords="738">
<item handle="b3643d0f92e32" parent="None" root="b3643d0f92e32" order="0" type="ROOT" class="NOVEL"> <item handle="b3643d0f92e32" parent="None" root="b3643d0f92e32" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/> <meta expanded="yes"/>
<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" heading="H1" charCount="230" wordCount="40" paraCount="3" cursorPos="148"/> <meta expanded="no" 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="yes">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" heading="H0" charCount="1058" wordCount="176" paraCount="2" cursorPos="43"/> <meta expanded="no" 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="yes">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" heading="H2" charCount="584" wordCount="92" paraCount="1" cursorPos="4"/> <meta expanded="no" 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="yes">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" heading="H1" charCount="35" wordCount="6" paraCount="1" cursorPos="42"/> <meta expanded="no" 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="yes">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">
<meta expanded="True"/> <meta expanded="yes"/>
<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" heading="H2" charCount="419" wordCount="67" paraCount="1" cursorPos="56"/> <meta expanded="no" 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="yes">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" heading="H3" charCount="2758" wordCount="404" paraCount="4" cursorPos="1528"/> <meta expanded="no" 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="yes">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" heading="H3" charCount="4043" wordCount="600" paraCount="6" cursorPos="2335"/> <meta expanded="no" 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="yes">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" heading="H2" charCount="631" wordCount="109" paraCount="3" cursorPos="376"/> <meta expanded="no" 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="no">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">
<meta expanded="True"/> <meta expanded="yes"/>
<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" heading="H2" charCount="477" wordCount="70" paraCount="1" cursorPos="56"/> <meta expanded="no" 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="yes">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" heading="H3" charCount="3006" wordCount="439" paraCount="4" cursorPos="57"/> <meta expanded="no" 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="yes">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" heading="H3" charCount="3839" wordCount="563" paraCount="6" cursorPos="56"/> <meta expanded="no" 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="yes">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" heading="H3" charCount="3644" wordCount="543" paraCount="5" cursorPos="351"/> <meta expanded="no" 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="yes">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">
<meta expanded="True"/> <meta expanded="yes"/>
<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" heading="H1" charCount="1864" wordCount="284" paraCount="3" cursorPos="1883"/> <meta expanded="no" 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="yes">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">
<meta expanded="True"/> <meta expanded="yes"/>
<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" heading="H1" charCount="1369" wordCount="195" paraCount="2" cursorPos="1387"/> <meta expanded="no" 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="yes">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">
<meta expanded="True"/> <meta expanded="yes"/>
<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" heading="H1" charCount="1770" wordCount="259" paraCount="3" cursorPos="1792"/> <meta expanded="no" 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="yes">Ancient Europe</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
@@ -1,4 +0,0 @@
### Scene Four
Scene Four
@@ -1,4 +0,0 @@
# Antagonist
Antagonist
@@ -1,4 +0,0 @@
### Scene Two
Scene Two
@@ -1,4 +0,0 @@
# Protagonist
Protagonist
@@ -1,4 +0,0 @@
### Scene Three
Scene Three
@@ -1,4 +0,0 @@
### Scene Five
Scene Five
@@ -1,4 +0,0 @@
### Scene One
Scene One
-2
View File
@@ -1,2 +0,0 @@
Start: 2020-09-26 16:13:00 End: 2020-09-26 16:15:54 Words: 24
Start: 2020-09-26 16:16:28 End: 2020-09-26 16:16:40 Words: -1
-72
View File
@@ -1,72 +0,0 @@
{
"tagIndex": {},
"refIndex": {
"f528d831f5b24": [],
"88124a4292d8b": [],
"91239bf2f8b69": [],
"19752e7f9d8af": [],
"a764d5acf5a21": [],
"9058ae29f0dfd": [],
"7ff63b8afc4cd": []
},
"novelIndex": {
"f528d831f5b24": [
[
1,
3,
"Scene One",
"SCENE"
]
],
"88124a4292d8b": [
[
1,
3,
"Scene Two",
"SCENE"
]
],
"91239bf2f8b69": [
[
1,
3,
"Scene Three",
"SCENE"
]
],
"19752e7f9d8af": [
[
1,
3,
"Scene Four",
"SCENE"
]
],
"a764d5acf5a21": [
[
1,
3,
"Scene Five",
"SCENE"
]
]
},
"noteIndex": {
"9058ae29f0dfd": [
[
1,
1,
"Protagonist",
"NOTE"
]
],
"7ff63b8afc4cd": [
[
1,
1,
"Antagonist",
"NOTE"
]
]
}
}
-148
View File
@@ -1,148 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.4.5" fileVersion="1.0" timeStamp="2020-09-26 16:16:39">
<project>
<name></name>
<title></title>
<backup>True</backup>
</project>
<settings>
<spellCheck>False</spellCheck>
<lastEdited>a764d5acf5a21</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>23</lastWordCount>
<autoReplace/>
<status>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Note</entry>
<entry blue="0" green="150" red="200">Draft</entry>
<entry blue="0" green="200" red="50">Finished</entry>
</status>
<importance>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Minor</entry>
<entry blue="0" green="150" red="200">Major</entry>
<entry blue="0" green="200" red="50">Main</entry>
</importance>
</settings>
<content count="12">
<item handle="095c337ad50c8" order="0" parent="None">
<name>Novel</name>
<type>ROOT</type>
<class>NOVEL</class>
<status>New</status>
<expanded>True</expanded>
</item>
<item handle="04d47c4b31af7" order="0" parent="095c337ad50c8">
<name>Chapter One</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status>
<expanded>True</expanded>
</item>
<item handle="f528d831f5b24" order="0" parent="04d47c4b31af7">
<name>Scene One</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
<layout>SCENE</layout>
<charCount>18</charCount>
<wordCount>4</wordCount>
<paraCount>1</paraCount>
<cursorPos>3</cursorPos>
</item>
<item handle="88124a4292d8b" order="1" parent="04d47c4b31af7">
<name>Scene Two</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
<layout>SCENE</layout>
<charCount>18</charCount>
<wordCount>4</wordCount>
<paraCount>1</paraCount>
<cursorPos>2</cursorPos>
</item>
<item handle="91239bf2f8b69" order="2" parent="04d47c4b31af7">
<name>Scene Three</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
<layout>SCENE</layout>
<charCount>22</charCount>
<wordCount>4</wordCount>
<paraCount>1</paraCount>
<cursorPos>2</cursorPos>
</item>
<item handle="19752e7f9d8af" order="3" parent="04d47c4b31af7">
<name>Scene Four</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
<layout>SCENE</layout>
<charCount>20</charCount>
<wordCount>4</wordCount>
<paraCount>1</paraCount>
<cursorPos>2</cursorPos>
</item>
<item handle="a764d5acf5a21" order="4" parent="04d47c4b31af7">
<name>Scene Five</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
<layout>SCENE</layout>
<charCount>20</charCount>
<wordCount>4</wordCount>
<paraCount>1</paraCount>
<cursorPos>2</cursorPos>
</item>
<item handle="a11282c943444" order="1" parent="None">
<name>Characters</name>
<type>ROOT</type>
<class>CHARACTER</class>
<status>New</status>
<expanded>True</expanded>
</item>
<item handle="9058ae29f0dfd" order="0" parent="a11282c943444">
<name>Protagonist</name>
<type>FILE</type>
<class>CHARACTER</class>
<status>New</status>
<expanded>False</expanded>
<layout>NOTE</layout>
<charCount>11</charCount>
<wordCount>1</wordCount>
<paraCount>0</paraCount>
<cursorPos>28</cursorPos>
</item>
<item handle="7ff63b8afc4cd" order="1" parent="a11282c943444">
<name>Antagonist</name>
<type>FILE</type>
<class>CHARACTER</class>
<status>New</status>
<expanded>False</expanded>
<layout>NOTE</layout>
<charCount>13</charCount>
<wordCount>2</wordCount>
<paraCount>1</paraCount>
<cursorPos>26</cursorPos>
</item>
<item handle="dabb9d158b2b2" order="2" parent="None">
<name>Plot</name>
<type>ROOT</type>
<class>PLOT</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="4a817a3a84b42" order="3" parent="None">
<name>World</name>
<type>ROOT</type>
<class>WORLD</class>
<status>New</status>
<expanded>False</expanded>
</item>
</content>
</novelWriterXML>
@@ -1,20 +1,14 @@
<?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-11-01 11:53:22"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 13:05:22">
<project> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
<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>2</saveCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>yes</doBackup>
<language>None</language> <language>None</language>
<spellCheck>False</spellCheck> <spellChecking auto="no">None</spellChecking>
<spellLang>None</spellLang>
<novelWordCount>10</novelWordCount>
<notesWordCount>3</notesWordCount>
<lastHandle> <lastHandle>
<entry key="editor">None</entry> <entry key="editor">None</entry>
<entry key="viewer">None</entry> <entry key="viewer">None</entry>
@@ -42,50 +36,50 @@
<entry key="i000007" 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="11"> <content items="11" novelWords="10" notesWords="3">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL"> <item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Novel</name> <name status="s000000" import="i000004">Novel</name>
</item> </item>
<item handle="0000000000009" parent="None" root="0000000000009" order="0" type="ROOT" class="PLOT"> <item handle="0000000000009" parent="None" root="0000000000009" order="0" type="ROOT" class="PLOT">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Plot</name> <name status="s000000" import="i000004">Plot</name>
</item> </item>
<item handle="000000000000a" parent="None" root="000000000000a" order="0" type="ROOT" class="CHARACTER"> <item handle="000000000000a" parent="None" root="000000000000a" order="0" type="ROOT" class="CHARACTER">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Characters</name> <name status="s000000" import="i000004">Characters</name>
</item> </item>
<item handle="000000000000b" parent="None" root="000000000000b" order="0" type="ROOT" class="WORLD"> <item handle="000000000000b" parent="None" root="000000000000b" order="0" type="ROOT" class="WORLD">
<meta expanded="False"/> <meta expanded="no"/>
<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" heading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/> <meta expanded="no" 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="yes">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">
<meta expanded="False"/> <meta expanded="no"/>
<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" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/> <meta expanded="no" 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="yes">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" heading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0"/> <meta expanded="no" 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="yes">New Scene</name>
</item> </item>
<item handle="0000000000010" 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="no"/>
<name status="s000000" import="i000004">Stuff</name> <name status="s000000" import="i000004">Stuff</name>
</item> </item>
<item handle="0000000000011" parent="0000000000010" 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" heading="H2" charCount="5" wordCount="1" paraCount="0" cursorPos="0"/> <meta expanded="no" 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="yes">Hello</name>
</item> </item>
<item handle="0000000000012" 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" heading="H1" charCount="11" wordCount="3" paraCount="1" cursorPos="0"/> <meta expanded="no" 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="yes">Jane</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
@@ -1,20 +1,14 @@
<?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-11-01 11:53:22"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 13:05:22">
<project> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
<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>2</saveCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>yes</doBackup>
<language>None</language> <language>None</language>
<spellCheck>False</spellCheck> <spellChecking auto="no">None</spellChecking>
<spellLang>None</spellLang>
<novelWordCount>9</novelWordCount>
<notesWordCount>0</notesWordCount>
<lastHandle> <lastHandle>
<entry key="editor">None</entry> <entry key="editor">None</entry>
<entry key="viewer">None</entry> <entry key="viewer">None</entry>
@@ -42,69 +36,69 @@
<entry key="i000007" 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 items="16" novelWords="9" notesWords="0">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL"> <item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Novel</name> <name status="s000000" import="i000004">Novel</name>
</item> </item>
<item handle="0000000000009" parent="None" root="0000000000009" order="0" type="ROOT" class="PLOT"> <item handle="0000000000009" parent="None" root="0000000000009" order="0" type="ROOT" class="PLOT">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Plot</name> <name status="s000000" import="i000004">Plot</name>
</item> </item>
<item handle="000000000000a" parent="None" root="000000000000a" order="0" type="ROOT" class="CHARACTER"> <item handle="000000000000a" parent="None" root="000000000000a" order="0" type="ROOT" class="CHARACTER">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Characters</name> <name status="s000000" import="i000004">Characters</name>
</item> </item>
<item handle="000000000000b" parent="None" root="000000000000b" order="0" type="ROOT" class="WORLD"> <item handle="000000000000b" parent="None" root="000000000000b" order="0" type="ROOT" class="WORLD">
<meta expanded="False"/> <meta expanded="no"/>
<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" heading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/> <meta expanded="no" 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="yes">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">
<meta expanded="False"/> <meta expanded="no"/>
<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" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/> <meta expanded="no" 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="yes">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" heading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0"/> <meta expanded="no" 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="yes">New Scene</name>
</item> </item>
<item handle="0000000000010" parent="None" root="0000000000010" order="0" type="ROOT" class="NOVEL"> <item handle="0000000000010" parent="None" root="0000000000010" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Novel</name> <name status="s000000" import="i000004">Novel</name>
</item> </item>
<item handle="0000000000011" parent="None" root="0000000000011" order="0" type="ROOT" class="PLOT"> <item handle="0000000000011" parent="None" root="0000000000011" order="0" type="ROOT" class="PLOT">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Plot</name> <name status="s000000" import="i000004">Plot</name>
</item> </item>
<item handle="0000000000012" parent="None" root="0000000000012" order="0" type="ROOT" class="CHARACTER"> <item handle="0000000000012" parent="None" root="0000000000012" order="0" type="ROOT" class="CHARACTER">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Characters</name> <name status="s000000" import="i000004">Characters</name>
</item> </item>
<item handle="0000000000013" parent="None" root="0000000000013" order="0" type="ROOT" class="WORLD"> <item handle="0000000000013" parent="None" root="0000000000013" order="0" type="ROOT" class="WORLD">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Locations</name> <name status="s000000" import="i000004">Locations</name>
</item> </item>
<item handle="0000000000014" parent="None" root="0000000000014" order="0" type="ROOT" class="TIMELINE"> <item handle="0000000000014" parent="None" root="0000000000014" order="0" type="ROOT" class="TIMELINE">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Timeline</name> <name status="s000000" import="i000004">Timeline</name>
</item> </item>
<item handle="0000000000015" parent="None" root="0000000000015" order="0" type="ROOT" class="OBJECT"> <item handle="0000000000015" parent="None" root="0000000000015" order="0" type="ROOT" class="OBJECT">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Objects</name> <name status="s000000" import="i000004">Objects</name>
</item> </item>
<item handle="0000000000016" parent="None" root="0000000000016" order="0" type="ROOT" class="CUSTOM"> <item handle="0000000000016" parent="None" root="0000000000016" order="0" type="ROOT" class="CUSTOM">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Custom</name> <name status="s000000" import="i000004">Custom</name>
</item> </item>
<item handle="0000000000017" parent="None" root="0000000000017" order="0" type="ROOT" class="CUSTOM"> <item handle="0000000000017" parent="None" root="0000000000017" order="0" type="ROOT" class="CUSTOM">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Custom</name> <name status="s000000" import="i000004">Custom</name>
</item> </item>
</content> </content>
@@ -1,21 +1,15 @@
<?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-11-01 11:53:22"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 13:05:22">
<project> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
<name>Test Custom</name> <name>Test Custom</name>
<title>Test Novel</title> <title>Test Novel</title>
<author>Jane Doe</author> <author>Jane Doe</author>
<author>John Doh</author> <author>John Doh</author>
<saveCount>1</saveCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>yes</doBackup>
<language>None</language> <language>None</language>
<spellCheck>False</spellCheck> <spellChecking auto="no">None</spellChecking>
<spellLang>None</spellLang>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<lastHandle> <lastHandle>
<entry key="editor">None</entry> <entry key="editor">None</entry>
<entry key="viewer">None</entry> <entry key="viewer">None</entry>
@@ -43,93 +37,93 @@
<entry key="i000007" 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 items="22" novelWords="0" notesWords="0">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL"> <item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Novel</name> <name status="s000000" import="i000004">Novel</name>
</item> </item>
<item handle="0000000000009" parent="0000000000008" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Title Page</name> <name status="s000000" import="i000004" active="yes">Title Page</name>
</item> </item>
<item handle="000000000000a" parent="0000000000008" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Chapter 1</name> <name status="s000000" import="i000004" active="yes">Chapter 1</name>
</item> </item>
<item handle="000000000000b" parent="000000000000a" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 1.1</name> <name status="s000000" import="i000004" active="yes">Scene 1.1</name>
</item> </item>
<item handle="000000000000c" parent="000000000000a" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 1.2</name> <name status="s000000" import="i000004" active="yes">Scene 1.2</name>
</item> </item>
<item handle="000000000000d" parent="000000000000a" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 1.3</name> <name status="s000000" import="i000004" active="yes">Scene 1.3</name>
</item> </item>
<item handle="000000000000e" parent="0000000000008" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Chapter 2</name> <name status="s000000" import="i000004" active="yes">Chapter 2</name>
</item> </item>
<item handle="000000000000f" parent="000000000000e" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 2.1</name> <name status="s000000" import="i000004" active="yes">Scene 2.1</name>
</item> </item>
<item handle="0000000000010" parent="000000000000e" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 2.2</name> <name status="s000000" import="i000004" active="yes">Scene 2.2</name>
</item> </item>
<item handle="0000000000011" parent="000000000000e" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 2.3</name> <name status="s000000" import="i000004" active="yes">Scene 2.3</name>
</item> </item>
<item handle="0000000000012" parent="0000000000008" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Chapter 3</name> <name status="s000000" import="i000004" active="yes">Chapter 3</name>
</item> </item>
<item handle="0000000000013" parent="0000000000012" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 3.1</name> <name status="s000000" import="i000004" active="yes">Scene 3.1</name>
</item> </item>
<item handle="0000000000014" parent="0000000000012" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 3.2</name> <name status="s000000" import="i000004" active="yes">Scene 3.2</name>
</item> </item>
<item handle="0000000000015" parent="0000000000012" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 3.3</name> <name status="s000000" import="i000004" active="yes">Scene 3.3</name>
</item> </item>
<item handle="0000000000016" parent="None" root="0000000000016" order="0" type="ROOT" class="PLOT"> <item handle="0000000000016" parent="None" root="0000000000016" order="0" type="ROOT" class="PLOT">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Plot</name> <name status="s000000" import="i000004">Plot</name>
</item> </item>
<item handle="0000000000017" parent="0000000000016" root="0000000000016" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Main Plot</name> <name status="s000000" import="i000004" active="yes">Main Plot</name>
</item> </item>
<item handle="0000000000018" parent="None" root="0000000000018" order="0" type="ROOT" class="CHARACTER"> <item handle="0000000000018" parent="None" root="0000000000018" order="0" type="ROOT" class="CHARACTER">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Characters</name> <name status="s000000" import="i000004">Characters</name>
</item> </item>
<item handle="0000000000019" parent="0000000000018" root="0000000000018" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Protagonist</name> <name status="s000000" import="i000004" active="yes">Protagonist</name>
</item> </item>
<item handle="000000000001a" parent="None" root="000000000001a" order="0" type="ROOT" class="WORLD"> <item handle="000000000001a" parent="None" root="000000000001a" order="0" type="ROOT" class="WORLD">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Locations</name> <name status="s000000" import="i000004">Locations</name>
</item> </item>
<item handle="000000000001b" parent="000000000001a" root="000000000001a" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Main Location</name> <name status="s000000" import="i000004" active="yes">Main Location</name>
</item> </item>
<item handle="000000000001c" parent="None" root="000000000001c" order="0" type="ROOT" class="ARCHIVE"> <item handle="000000000001c" parent="None" root="000000000001c" order="0" type="ROOT" class="ARCHIVE">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Archive</name> <name status="s000000" import="i000004">Archive</name>
</item> </item>
<item handle="000000000001d" parent="None" root="000000000001d" order="0" type="ROOT" class="TRASH"> <item handle="000000000001d" parent="None" root="000000000001d" order="0" type="ROOT" class="TRASH">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Trash</name> <name status="s000000" import="i000004">Trash</name>
</item> </item>
</content> </content>
@@ -1,21 +1,15 @@
<?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-11-01 11:53:22"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 13:05:22">
<project> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
<name>Test Custom</name> <name>Test Custom</name>
<title>Test Novel</title> <title>Test Novel</title>
<author>Jane Doe</author> <author>Jane Doe</author>
<author>John Doh</author> <author>John Doh</author>
<saveCount>1</saveCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>yes</doBackup>
<language>None</language> <language>None</language>
<spellCheck>False</spellCheck> <spellChecking auto="no">None</spellChecking>
<spellLang>None</spellLang>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<lastHandle> <lastHandle>
<entry key="editor">None</entry> <entry key="editor">None</entry>
<entry key="viewer">None</entry> <entry key="viewer">None</entry>
@@ -43,69 +37,69 @@
<entry key="i000007" 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 items="16" novelWords="0" notesWords="0">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL"> <item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Novel</name> <name status="s000000" import="i000004">Novel</name>
</item> </item>
<item handle="0000000000009" parent="0000000000008" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Title Page</name> <name status="s000000" import="i000004" active="yes">Title Page</name>
</item> </item>
<item handle="000000000000a" parent="0000000000008" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 1</name> <name status="s000000" import="i000004" active="yes">Scene 1</name>
</item> </item>
<item handle="000000000000b" parent="0000000000008" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 2</name> <name status="s000000" import="i000004" active="yes">Scene 2</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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 3</name> <name status="s000000" import="i000004" active="yes">Scene 3</name>
</item> </item>
<item handle="000000000000d" parent="0000000000008" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 4</name> <name status="s000000" import="i000004" active="yes">Scene 4</name>
</item> </item>
<item handle="000000000000e" parent="0000000000008" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 5</name> <name status="s000000" import="i000004" active="yes">Scene 5</name>
</item> </item>
<item handle="000000000000f" parent="0000000000008" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 6</name> <name status="s000000" import="i000004" active="yes">Scene 6</name>
</item> </item>
<item handle="0000000000010" parent="None" root="0000000000010" order="0" type="ROOT" class="PLOT"> <item handle="0000000000010" parent="None" root="0000000000010" order="0" type="ROOT" class="PLOT">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Plot</name> <name status="s000000" import="i000004">Plot</name>
</item> </item>
<item handle="0000000000011" parent="0000000000010" root="0000000000010" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Main Plot</name> <name status="s000000" import="i000004" active="yes">Main Plot</name>
</item> </item>
<item handle="0000000000012" parent="None" root="0000000000012" order="0" type="ROOT" class="CHARACTER"> <item handle="0000000000012" parent="None" root="0000000000012" order="0" type="ROOT" class="CHARACTER">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Characters</name> <name status="s000000" import="i000004">Characters</name>
</item> </item>
<item handle="0000000000013" parent="0000000000012" root="0000000000012" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Protagonist</name> <name status="s000000" import="i000004" active="yes">Protagonist</name>
</item> </item>
<item handle="0000000000014" parent="None" root="0000000000014" order="0" type="ROOT" class="WORLD"> <item handle="0000000000014" parent="None" root="0000000000014" order="0" type="ROOT" class="WORLD">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Locations</name> <name status="s000000" import="i000004">Locations</name>
</item> </item>
<item handle="0000000000015" parent="0000000000014" root="0000000000014" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Main Location</name> <name status="s000000" import="i000004" active="yes">Main Location</name>
</item> </item>
<item handle="0000000000016" parent="None" root="0000000000016" order="0" type="ROOT" class="ARCHIVE"> <item handle="0000000000016" parent="None" root="0000000000016" order="0" type="ROOT" class="ARCHIVE">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Archive</name> <name status="s000000" import="i000004">Archive</name>
</item> </item>
<item handle="0000000000017" parent="None" root="0000000000017" order="0" type="ROOT" class="TRASH"> <item handle="0000000000017" parent="None" root="0000000000017" order="0" type="ROOT" class="TRASH">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Trash</name> <name status="s000000" import="i000004">Trash</name>
</item> </item>
</content> </content>
@@ -1,19 +1,13 @@
<?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-11-01 11:53:22"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 13:05:22">
<project> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
<name>New Project</name> <name>New Project</name>
<title>None</title> <title>New Project</title>
<saveCount>2</saveCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>yes</doBackup>
<language>None</language> <language>None</language>
<spellCheck>False</spellCheck> <spellChecking auto="no">None</spellChecking>
<spellLang>None</spellLang>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<lastHandle> <lastHandle>
<entry key="editor">None</entry> <entry key="editor">None</entry>
<entry key="viewer">None</entry> <entry key="viewer">None</entry>
@@ -41,37 +35,37 @@
<entry key="i000007" 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 items="8" novelWords="0" notesWords="0">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL"> <item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Novel</name> <name status="s000000" import="i000004">Novel</name>
</item> </item>
<item handle="0000000000009" parent="0000000000008" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Title Page</name> <name status="s000000" import="i000004" active="yes">Title Page</name>
</item> </item>
<item handle="000000000000a" parent="0000000000008" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">New Chapter</name> <name status="s000000" import="i000004" active="yes">New Chapter</name>
</item> </item>
<item handle="000000000000b" parent="000000000000a" root="0000000000008" 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" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">New Scene</name> <name status="s000000" import="i000004" active="yes">New Scene</name>
</item> </item>
<item handle="000000000000c" parent="None" root="000000000000c" order="0" type="ROOT" class="PLOT"> <item handle="000000000000c" parent="None" root="000000000000c" order="0" type="ROOT" class="PLOT">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Plot</name> <name status="s000000" import="i000004">Plot</name>
</item> </item>
<item handle="000000000000d" parent="None" root="000000000000d" order="0" type="ROOT" class="CHARACTER"> <item handle="000000000000d" parent="None" root="000000000000d" order="0" type="ROOT" class="CHARACTER">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Characters</name> <name status="s000000" import="i000004">Characters</name>
</item> </item>
<item handle="000000000000e" parent="None" root="000000000000e" order="0" type="ROOT" class="WORLD"> <item handle="000000000000e" parent="None" root="000000000000e" order="0" type="ROOT" class="WORLD">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Locations</name> <name status="s000000" import="i000004">Locations</name>
</item> </item>
<item handle="000000000000f" parent="None" root="000000000000f" order="0" type="ROOT" class="ARCHIVE"> <item handle="000000000000f" parent="None" root="000000000000f" order="0" type="ROOT" class="ARCHIVE">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Archive</name> <name status="s000000" import="i000004">Archive</name>
</item> </item>
</content> </content>
@@ -1,20 +1,14 @@
<?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-11-01 12:01:37"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 12:58:26">
<project> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="4" autoCount="2" editTime="3">
<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>
<autoCount>2</autoCount>
<editTime>3</editTime>
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>yes</doBackup>
<language>None</language> <language>None</language>
<spellCheck>True</spellCheck> <spellChecking auto="yes">None</spellChecking>
<spellLang>None</spellLang>
<novelWordCount>136</novelWordCount>
<notesWordCount>27</notesWordCount>
<lastHandle> <lastHandle>
<entry key="editor">000000000000f</entry> <entry key="editor">000000000000f</entry>
<entry key="viewer">None</entry> <entry key="viewer">None</entry>
@@ -42,53 +36,53 @@
<entry key="i000007" 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="12"> <content items="12" novelWords="136" notesWords="27">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL"> <item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/> <meta expanded="yes"/>
<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" heading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/> <meta expanded="no" 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="yes">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">
<meta expanded="True"/> <meta expanded="yes"/>
<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" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/> <meta expanded="no" 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="yes">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" heading="H1" charCount="781" wordCount="129" paraCount="14" cursorPos="1010"/> <meta expanded="no" 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="yes">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="yes"/>
<name status="s000000" import="i000004">Plot</name> <name status="s000000" import="i000004">Plot</name>
</item> </item>
<item handle="0000000000011" 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" heading="H1" charCount="48" wordCount="10" paraCount="1" cursorPos="69"/> <meta expanded="no" 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="yes">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="yes"/>
<name status="s000000" import="i000004">Characters</name> <name status="s000000" import="i000004">Characters</name>
</item> </item>
<item handle="0000000000010" 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" heading="H1" charCount="34" wordCount="8" paraCount="1" cursorPos="51"/> <meta expanded="no" 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="yes">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="yes"/>
<name status="s000000" import="i000004">World</name> <name status="s000000" import="i000004">World</name>
</item> </item>
<item handle="0000000000012" 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" heading="H1" charCount="51" wordCount="9" paraCount="1" cursorPos="68"/> <meta expanded="no" 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="yes">New Note</name>
</item> </item>
<item handle="0000000000014" parent="None" root="0000000000014" order="4" type="ROOT" class="TRASH"> <item handle="0000000000014" parent="None" root="0000000000014" order="4" type="ROOT" class="TRASH">
<meta expanded="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Trash</name> <name status="s000000" import="i000004">Trash</name>
</item> </item>
</content> </content>
@@ -1,20 +1,14 @@
<?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-11-01 11:53:30"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 12:57:21">
<project> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="2" autoCount="1" editTime="0">
<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>2</saveCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>yes</doBackup>
<language>None</language> <language>None</language>
<spellCheck>False</spellCheck> <spellChecking auto="no">None</spellChecking>
<spellLang>None</spellLang>
<novelWordCount>9</novelWordCount>
<notesWordCount>0</notesWordCount>
<lastHandle> <lastHandle>
<entry key="editor">None</entry> <entry key="editor">None</entry>
<entry key="viewer">None</entry> <entry key="viewer">None</entry>
@@ -42,37 +36,37 @@
<entry key="i000007" 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 items="8" novelWords="9" notesWords="0">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL"> <item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/> <meta expanded="no"/>
<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" heading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/> <meta expanded="no" 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="yes">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">
<meta expanded="False"/> <meta expanded="no"/>
<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" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/> <meta expanded="no" 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="yes">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" heading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0"/> <meta expanded="no" 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="yes">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="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Plot</name> <name status="s000000" import="i000004">Plot</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="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">Characters</name> <name status="s000000" import="i000004">Characters</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="False"/> <meta expanded="no"/>
<name status="s000000" import="i000004">World</name> <name status="s000000" import="i000004">World</name>
</item> </item>
</content> </content>
File diff suppressed because it is too large Load Diff
+449 -317
View File
@@ -1,362 +1,494 @@
[ [
{ {
"handle": "7031beac91f75", "name": "Novel",
"parent": null, "itemAttr": {
"root": null, "handle": "7031beac91f75",
"order": 0, "parent": null,
"heading": "H0", "root": null,
"label": "Novel", "order": 0,
"type": "ROOT", "type": "ROOT",
"class": "NOVEL", "class": "NOVEL"
"expanded": true, },
"status": "s000002" "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000002"
}
}, },
{ {
"handle": "53b69b83cdafc", "name": "Title Page",
"parent": "7031beac91f75", "itemAttr": {
"root": null, "handle": "53b69b83cdafc",
"order": 0, "parent": "7031beac91f75",
"heading": "H0", "root": null,
"label": "Title Page", "order": 0,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"expanded": false, "layout": "DOCUMENT"
"active": true, },
"layout": "DOCUMENT", "metaAttr": {
"charCount": 72, "heading": "H0",
"wordCount": 15, "expanded": false,
"paraCount": 2, "charCount": 72,
"cursorPos": 78, "wordCount": 15,
"status": "s000002" "paraCount": 2,
"cursorPos": 78
},
"nameAttr": {
"active": true,
"status": "s000002"
}
}, },
{ {
"handle": "974e400180a99", "name": "Page",
"parent": "7031beac91f75", "itemAttr": {
"root": null, "handle": "974e400180a99",
"order": 1, "parent": "7031beac91f75",
"heading": "H0", "root": null,
"label": "Page", "order": 1,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"expanded": false, "layout": "DOCUMENT"
"active": true, },
"layout": "DOCUMENT", "metaAttr": {
"charCount": 208, "heading": "H0",
"wordCount": 40, "expanded": false,
"paraCount": 2, "charCount": 208,
"cursorPos": 213, "wordCount": 40,
"status": "s000000" "paraCount": 2,
"cursorPos": 213
},
"nameAttr": {
"active": true,
"status": "s000000"
}
}, },
{ {
"handle": "edca4be2fcaf8", "name": "Part One",
"parent": "7031beac91f75", "itemAttr": {
"root": null, "handle": "edca4be2fcaf8",
"order": 2, "parent": "7031beac91f75",
"heading": "H0", "root": null,
"label": "Part One", "order": 2,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"expanded": false, "layout": "DOCUMENT"
"active": true, },
"layout": "DOCUMENT", "metaAttr": {
"charCount": 23, "heading": "H0",
"wordCount": 5, "expanded": false,
"paraCount": 1, "charCount": 23,
"cursorPos": 0, "wordCount": 5,
"status": "s000000" "paraCount": 1,
"cursorPos": 0
},
"nameAttr": {
"active": true,
"status": "s000000"
}
}, },
{ {
"handle": "e7ded148d6e4a", "name": "A Folder",
"parent": "7031beac91f75", "itemAttr": {
"root": null, "handle": "e7ded148d6e4a",
"order": 3, "parent": "7031beac91f75",
"heading": "H0", "root": null,
"label": "A Folder", "order": 3,
"type": "FOLDER", "type": "FOLDER",
"class": "NOVEL", "class": "NOVEL"
"expanded": true, },
"status": "s000003" "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000003"
}
}, },
{ {
"handle": "6a2d6d5f4f401", "name": "Chapter One",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "6a2d6d5f4f401",
"order": 0, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Chapter One", "order": 0,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"expanded": false, "layout": "DOCUMENT"
"active": true, },
"layout": "DOCUMENT", "metaAttr": {
"charCount": 12, "heading": "H0",
"wordCount": 3, "expanded": false,
"paraCount": 0, "charCount": 12,
"cursorPos": 215, "wordCount": 3,
"status": "s000001" "paraCount": 0,
"cursorPos": 215
},
"nameAttr": {
"active": true,
"status": "s000001"
}
}, },
{ {
"handle": "636b6aa9b697b", "name": "Making a Scene",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "636b6aa9b697b",
"order": 1, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Making a Scene", "order": 1,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"expanded": false, "layout": "DOCUMENT"
"active": true, },
"layout": "DOCUMENT", "metaAttr": {
"charCount": 1199, "heading": "H0",
"wordCount": 216, "expanded": false,
"paraCount": 7, "charCount": 1199,
"cursorPos": 527, "wordCount": 216,
"status": "s000003" "paraCount": 7,
"cursorPos": 527
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "bc0cbd2a407f3", "name": "Another Scene",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "bc0cbd2a407f3",
"order": 2, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Another Scene", "order": 2,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"expanded": false, "layout": "DOCUMENT"
"active": true, },
"layout": "DOCUMENT", "metaAttr": {
"charCount": 476, "heading": "H0",
"wordCount": 93, "expanded": false,
"paraCount": 3, "charCount": 476,
"cursorPos": 551, "wordCount": 93,
"status": "s000003" "paraCount": 3,
"cursorPos": 551
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "ba8a28a246524", "name": "Interlude",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "ba8a28a246524",
"order": 3, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Interlude", "order": 3,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"expanded": false, "layout": "DOCUMENT"
"active": true, },
"layout": "DOCUMENT", "metaAttr": {
"charCount": 633, "heading": "H0",
"wordCount": 101, "expanded": false,
"paraCount": 3, "charCount": 633,
"cursorPos": 1238, "wordCount": 101,
"status": "s000006" "paraCount": 3,
"cursorPos": 1238
},
"nameAttr": {
"active": true,
"status": "s000006"
}
}, },
{ {
"handle": "96b68994dfa3d", "name": "A Note on Structure",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "96b68994dfa3d",
"order": 4, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "A Note on Structure", "order": 4,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"expanded": false, "layout": "NOTE"
"active": false, },
"layout": "NOTE", "metaAttr": {
"charCount": 1692, "heading": "H0",
"wordCount": 313, "expanded": false,
"paraCount": 6, "charCount": 1692,
"cursorPos": 1721, "wordCount": 313,
"status": "s000004" "paraCount": 6,
"cursorPos": 1721
},
"nameAttr": {
"active": false,
"status": "s000004"
}
}, },
{ {
"handle": "88706ddc78b1b", "name": "Chapter Two",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "88706ddc78b1b",
"order": 5, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Chapter Two", "order": 5,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"expanded": false, "layout": "DOCUMENT"
"active": true, },
"layout": "DOCUMENT", "metaAttr": {
"charCount": 139, "heading": "H0",
"wordCount": 28, "expanded": false,
"paraCount": 1, "charCount": 139,
"cursorPos": 343, "wordCount": 28,
"status": "s000003" "paraCount": 1,
"cursorPos": 343
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "ae7339df26ded", "name": "We Found John!",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "ae7339df26ded",
"order": 6, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "We Found John!", "order": 6,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"expanded": false, "layout": "DOCUMENT"
"active": true, },
"layout": "DOCUMENT", "metaAttr": {
"charCount": 189, "heading": "H0",
"wordCount": 37, "expanded": false,
"paraCount": 1, "charCount": 189,
"cursorPos": 224, "wordCount": 37,
"status": "s000003" "paraCount": 1,
"cursorPos": 224
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "f6622b4617424", "name": "Characters",
"parent": null, "itemAttr": {
"root": null, "handle": "f6622b4617424",
"order": 1, "parent": null,
"heading": "H0", "root": null,
"label": "Characters", "order": 1,
"type": "ROOT", "type": "ROOT",
"class": "CHARACTER", "class": "CHARACTER"
"expanded": true, },
"import": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
}, },
{ {
"handle": "f7e2d9f330615", "name": "Main Characters",
"parent": "f6622b4617424", "itemAttr": {
"root": null, "handle": "f7e2d9f330615",
"order": 0, "parent": "f6622b4617424",
"heading": "H0", "root": null,
"label": "Main Characters", "order": 0,
"type": "FOLDER", "type": "FOLDER",
"class": "CHARACTER", "class": "CHARACTER"
"expanded": true, },
"import": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
}, },
{ {
"handle": "14298de4d9524", "name": "John Smith",
"parent": "f7e2d9f330615", "itemAttr": {
"root": null, "handle": "14298de4d9524",
"order": 0, "parent": "f7e2d9f330615",
"heading": "H0", "root": null,
"label": "John Smith", "order": 0,
"type": "FILE", "type": "FILE",
"class": "CHARACTER", "class": "CHARACTER",
"expanded": false, "layout": "NOTE"
"active": true, },
"layout": "NOTE", "metaAttr": {
"charCount": 49, "heading": "H0",
"wordCount": 9, "expanded": false,
"paraCount": 1, "charCount": 49,
"cursorPos": 24, "wordCount": 9,
"import": "i000008" "paraCount": 1,
"cursorPos": 24
},
"nameAttr": {
"active": true,
"import": "i000008"
}
}, },
{ {
"handle": "bb2c23b3c42cc", "name": "Jane Smith",
"parent": "f7e2d9f330615", "itemAttr": {
"root": null, "handle": "bb2c23b3c42cc",
"order": 1, "parent": "f7e2d9f330615",
"heading": "H0", "root": null,
"label": "Jane Smith", "order": 1,
"type": "FILE", "type": "FILE",
"class": "CHARACTER", "class": "CHARACTER",
"expanded": false, "layout": "NOTE"
"active": true, },
"layout": "NOTE", "metaAttr": {
"charCount": 55, "heading": "H0",
"wordCount": 9, "expanded": false,
"paraCount": 1, "charCount": 55,
"cursorPos": 25, "wordCount": 9,
"import": "i000009" "paraCount": 1,
"cursorPos": 25
},
"nameAttr": {
"active": true,
"import": "i000009"
}
}, },
{ {
"handle": "15c4492bd5107", "name": "Locations",
"parent": null, "itemAttr": {
"root": null, "handle": "15c4492bd5107",
"order": 2, "parent": null,
"heading": "H0", "root": null,
"label": "Locations", "order": 2,
"type": "ROOT", "type": "ROOT",
"class": "WORLD", "class": "WORLD"
"expanded": true, },
"import": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
}, },
{ {
"handle": "b3e74dbc1f584", "name": "Earth",
"parent": "15c4492bd5107", "itemAttr": {
"root": null, "handle": "b3e74dbc1f584",
"order": 0, "parent": "15c4492bd5107",
"heading": "H0", "root": null,
"label": "Earth", "order": 0,
"type": "FILE", "type": "FILE",
"class": "WORLD", "class": "WORLD",
"expanded": false, "layout": "NOTE"
"active": true, },
"layout": "NOTE", "metaAttr": {
"charCount": 76, "heading": "H0",
"wordCount": 15, "expanded": false,
"paraCount": 1, "charCount": 76,
"cursorPos": 20, "wordCount": 15,
"import": "i00000a" "paraCount": 1,
"cursorPos": 20
},
"nameAttr": {
"active": true,
"import": "i00000a"
}
}, },
{ {
"handle": "f1471bef9f2ae", "name": "Space",
"parent": "15c4492bd5107", "itemAttr": {
"root": null, "handle": "f1471bef9f2ae",
"order": 1, "parent": "15c4492bd5107",
"heading": "H0", "root": null,
"label": "Space", "order": 1,
"type": "FILE", "type": "FILE",
"class": "WORLD", "class": "WORLD",
"expanded": false, "layout": "NOTE"
"active": true, },
"layout": "NOTE", "metaAttr": {
"charCount": 115, "heading": "H0",
"wordCount": 24, "expanded": false,
"paraCount": 1, "charCount": 115,
"cursorPos": 133, "wordCount": 24,
"import": "i000008" "paraCount": 1,
"cursorPos": 133
},
"nameAttr": {
"active": true,
"import": "i000008"
}
}, },
{ {
"handle": "5eaea4e8cdee8", "name": "Mars",
"parent": "15c4492bd5107", "itemAttr": {
"root": null, "handle": "5eaea4e8cdee8",
"order": 2, "parent": "15c4492bd5107",
"heading": "H0", "root": null,
"label": "Mars", "order": 2,
"type": "FILE", "type": "FILE",
"class": "WORLD", "class": "WORLD",
"expanded": false, "layout": "NOTE"
"active": true, },
"layout": "NOTE", "metaAttr": {
"charCount": 28, "heading": "H0",
"wordCount": 6, "expanded": false,
"paraCount": 1, "charCount": 28,
"cursorPos": 45, "wordCount": 6,
"import": "i000009" "paraCount": 1,
"cursorPos": 45
},
"nameAttr": {
"active": true,
"import": "i000009"
}
}, },
{ {
"handle": "98acd8c76c93a", "name": "Trash",
"parent": null, "itemAttr": {
"root": null, "handle": "98acd8c76c93a",
"order": 3, "parent": null,
"heading": "H0", "root": null,
"label": "Trash", "order": 3,
"type": "ROOT", "type": "ROOT",
"class": "TRASH", "class": "TRASH"
"expanded": true, },
"import": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
}, },
{ {
"handle": "b8136a5a774a0", "name": "Delete Me!",
"parent": "98acd8c76c93a", "itemAttr": {
"root": null, "handle": "b8136a5a774a0",
"order": 0, "parent": "98acd8c76c93a",
"heading": "H0", "root": null,
"label": "Delete Me!", "order": 0,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"expanded": false, "layout": "DOCUMENT"
"active": true, },
"layout": "DOCUMENT", "metaAttr": {
"charCount": 0, "heading": "H0",
"wordCount": 0, "expanded": false,
"paraCount": 0, "charCount": 0,
"cursorPos": 36, "wordCount": 0,
"status": "s000000" "paraCount": 0,
"cursorPos": 36
},
"nameAttr": {
"active": true,
"status": "s000000"
}
} }
] ]
+43 -49
View File
@@ -1,21 +1,15 @@
<?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="2020-05-28 09:59:15"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2020-05-28 09:59:15">
<project> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="0" autoCount="0" editTime="1000">
<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>0</saveCount>
<autoCount>0</autoCount>
<editTime>1000</editTime>
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>yes</doBackup>
<language>None</language> <language>None</language>
<spellCheck>True</spellCheck> <spellChecking auto="yes">None</spellChecking>
<spellLang>None</spellLang>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<lastHandle> <lastHandle>
<entry key="editor">None</entry> <entry key="editor">None</entry>
<entry key="viewer">None</entry> <entry key="viewer">None</entry>
@@ -50,94 +44,94 @@
<entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry> <entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="22"> <content items="22" novelWords="0" notesWords="0">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL"> <item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000002" import="i000007">Novel</name> <name status="s000002" import="i000007">Novel</name>
</item> </item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="72" wordCount="15" paraCount="2" cursorPos="78"/>
<name status="s000002" import="i000007" active="True">Title Page</name> <name status="s000002" import="i000007" active="yes">Title Page</name>
</item> </item>
<item handle="974e400180a99" parent="7031beac91f75" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="208" wordCount="40" paraCount="2" cursorPos="213"/>
<name status="s000000" import="i000007" active="True">Page</name> <name status="s000000" import="i000007" active="yes">Page</name>
</item> </item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="23" wordCount="5" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000007" active="True">Part One</name> <name status="s000000" import="i000007" active="yes">Part One</name>
</item> </item>
<item handle="e7ded148d6e4a" parent="7031beac91f75" root="None" order="3" type="FOLDER" class="NOVEL"> <item handle="e7ded148d6e4a" parent="7031beac91f75" root="None" order="3" type="FOLDER" class="NOVEL">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000003" import="i000007">A Folder</name> <name status="s000003" import="i000007">A Folder</name>
</item> </item>
<item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="12" wordCount="3" paraCount="0" cursorPos="215"/>
<name status="s000001" import="i000007" active="True">Chapter One</name> <name status="s000001" import="i000007" active="yes">Chapter One</name>
</item> </item>
<item handle="636b6aa9b697b" parent="e7ded148d6e4a" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="1199" wordCount="216" paraCount="7" cursorPos="527"/>
<name status="s000003" import="i000007" active="True">Making a Scene</name> <name status="s000003" import="i000007" active="yes">Making a Scene</name>
</item> </item>
<item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="476" wordCount="93" paraCount="3" cursorPos="551"/>
<name status="s000003" import="i000007" active="True">Another Scene</name> <name status="s000003" import="i000007" active="yes">Another Scene</name>
</item> </item>
<item handle="ba8a28a246524" parent="e7ded148d6e4a" root="None" order="3" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="633" wordCount="101" paraCount="3" cursorPos="1238"/>
<name status="s000006" import="i000007" active="True">Interlude</name> <name status="s000006" import="i000007" active="yes">Interlude</name>
</item> </item>
<item handle="96b68994dfa3d" parent="e7ded148d6e4a" root="None" order="4" type="FILE" class="NOVEL" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="1692" wordCount="313" paraCount="6" cursorPos="1721"/>
<name status="s000004" import="i000007" active="False">A Note on Structure</name> <name status="s000004" import="i000007" active="no">A Note on Structure</name>
</item> </item>
<item handle="88706ddc78b1b" parent="e7ded148d6e4a" root="None" order="5" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<name status="s000003" import="i000007" active="True">Chapter Two</name> <name status="s000003" import="i000007" active="yes">Chapter Two</name>
</item> </item>
<item handle="ae7339df26ded" parent="e7ded148d6e4a" root="None" order="6" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="s000003" import="i000007" active="True">We Found John!</name> <name status="s000003" import="i000007" active="yes">We Found John!</name>
</item> </item>
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="1" type="ROOT" class="CHARACTER"> <item handle="f6622b4617424" parent="None" root="f6622b4617424" order="1" type="ROOT" class="CHARACTER">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Characters</name> <name status="s000000" import="i000007">Characters</name>
</item> </item>
<item handle="f7e2d9f330615" parent="f6622b4617424" root="None" order="0" type="FOLDER" class="CHARACTER"> <item handle="f7e2d9f330615" parent="f6622b4617424" root="None" order="0" type="FOLDER" class="CHARACTER">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Main Characters</name> <name status="s000000" import="i000007">Main Characters</name>
</item> </item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="None" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="s000000" import="i000008" active="True">John Smith</name> <name status="s000000" import="i000008" active="yes">John Smith</name>
</item> </item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="None" order="1" type="FILE" class="CHARACTER" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="s000000" import="i000009" active="True">Jane Smith</name> <name status="s000000" import="i000009" active="yes">Jane Smith</name>
</item> </item>
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="2" type="ROOT" class="WORLD"> <item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="2" type="ROOT" class="WORLD">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Locations</name> <name status="s000000" import="i000007">Locations</name>
</item> </item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="None" order="0" type="FILE" class="WORLD" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="s000000" import="i00000a" active="True">Earth</name> <name status="s000000" import="i00000a" active="yes">Earth</name>
</item> </item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="None" order="1" type="FILE" class="WORLD" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="s000000" import="i000008" active="True">Space</name> <name status="s000000" import="i000008" active="yes">Space</name>
</item> </item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="None" order="2" type="FILE" class="WORLD" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="s000000" import="i000009" active="True">Mars</name> <name status="s000000" import="i000009" active="yes">Mars</name>
</item> </item>
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="3" type="ROOT" class="TRASH"> <item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="3" type="ROOT" class="TRASH">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Trash</name> <name status="s000000" import="i000007">Trash</name>
</item> </item>
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="36"/>
<name status="s000000" import="i000007" active="True">Delete Me!</name> <name status="s000000" import="i000007" active="yes">Delete Me!</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
+433 -301
View File
@@ -1,346 +1,478 @@
[ [
{ {
"handle": "7031beac91f75", "name": "Novel",
"parent": null, "itemAttr": {
"root": null, "handle": "7031beac91f75",
"order": 0, "parent": null,
"heading": "H0", "root": null,
"label": "Novel", "order": 0,
"type": "ROOT", "type": "ROOT",
"class": "NOVEL", "class": "NOVEL"
"expanded": true, },
"status": "s000002" "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000002"
}
}, },
{ {
"handle": "53b69b83cdafc", "name": "Title Page",
"parent": "7031beac91f75", "itemAttr": {
"root": null, "handle": "53b69b83cdafc",
"order": 0, "parent": "7031beac91f75",
"heading": "H0", "root": null,
"label": "Title Page", "order": 0,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 72, "metaAttr": {
"wordCount": 15, "heading": "H0",
"paraCount": 2, "charCount": 72,
"cursorPos": 78, "wordCount": 15,
"status": "s000002" "paraCount": 2,
"cursorPos": 78
},
"nameAttr": {
"active": true,
"status": "s000002"
}
}, },
{ {
"handle": "974e400180a99", "name": "Page",
"parent": "7031beac91f75", "itemAttr": {
"root": null, "handle": "974e400180a99",
"order": 1, "parent": "7031beac91f75",
"heading": "H0", "root": null,
"label": "Page", "order": 1,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 210, "metaAttr": {
"wordCount": 40, "heading": "H0",
"paraCount": 2, "charCount": 210,
"cursorPos": 213, "wordCount": 40,
"status": "s000000" "paraCount": 2,
"cursorPos": 213
},
"nameAttr": {
"active": true,
"status": "s000000"
}
}, },
{ {
"handle": "edca4be2fcaf8", "name": "Part One",
"parent": "7031beac91f75", "itemAttr": {
"root": null, "handle": "edca4be2fcaf8",
"order": 2, "parent": "7031beac91f75",
"heading": "H0", "root": null,
"label": "Part One", "order": 2,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 23, "metaAttr": {
"wordCount": 5, "heading": "H0",
"paraCount": 1, "charCount": 23,
"cursorPos": 0, "wordCount": 5,
"status": "s000000" "paraCount": 1,
"cursorPos": 0
},
"nameAttr": {
"active": true,
"status": "s000000"
}
}, },
{ {
"handle": "e7ded148d6e4a", "name": "A Folder",
"parent": "7031beac91f75", "itemAttr": {
"root": null, "handle": "e7ded148d6e4a",
"order": 3, "parent": "7031beac91f75",
"heading": "H0", "root": null,
"label": "A Folder", "order": 3,
"type": "FOLDER", "type": "FOLDER",
"class": "NOVEL", "class": "NOVEL"
"expanded": true, },
"status": "s000003" "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000003"
}
}, },
{ {
"handle": "6a2d6d5f4f401", "name": "Chapter One",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "6a2d6d5f4f401",
"order": 0, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Chapter One", "order": 0,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 12, "metaAttr": {
"wordCount": 3, "heading": "H0",
"paraCount": 0, "charCount": 12,
"cursorPos": 215, "wordCount": 3,
"status": "s000001" "paraCount": 0,
"cursorPos": 215
},
"nameAttr": {
"active": true,
"status": "s000001"
}
}, },
{ {
"handle": "636b6aa9b697b", "name": "Making a Scene",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "636b6aa9b697b",
"order": 1, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Making a Scene", "order": 1,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 1483, "metaAttr": {
"wordCount": 263, "heading": "H0",
"paraCount": 8, "charCount": 1483,
"cursorPos": 1086, "wordCount": 263,
"status": "s000003" "paraCount": 8,
"cursorPos": 1086
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "bc0cbd2a407f3", "name": "Another Scene",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "bc0cbd2a407f3",
"order": 2, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Another Scene", "order": 2,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 476, "metaAttr": {
"wordCount": 93, "heading": "H0",
"paraCount": 3, "charCount": 476,
"cursorPos": 428, "wordCount": 93,
"status": "s000003" "paraCount": 3,
"cursorPos": 428
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "ba8a28a246524", "name": "Interlude",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "ba8a28a246524",
"order": 3, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Interlude", "order": 3,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 633, "metaAttr": {
"wordCount": 101, "heading": "H0",
"paraCount": 3, "charCount": 633,
"cursorPos": 1238, "wordCount": 101,
"status": "s000006" "paraCount": 3,
"cursorPos": 1238
},
"nameAttr": {
"active": true,
"status": "s000006"
}
}, },
{ {
"handle": "96b68994dfa3d", "name": "A Note on Structure",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "96b68994dfa3d",
"order": 4, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "A Note on Structure", "order": 4,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": false, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 1692, "metaAttr": {
"wordCount": 313, "heading": "H0",
"paraCount": 6, "charCount": 1692,
"cursorPos": 1721, "wordCount": 313,
"status": "s000004" "paraCount": 6,
"cursorPos": 1721
},
"nameAttr": {
"active": false,
"status": "s000004"
}
}, },
{ {
"handle": "88706ddc78b1b", "name": "Chapter Two",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "88706ddc78b1b",
"order": 5, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Chapter Two", "order": 5,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 139, "metaAttr": {
"wordCount": 28, "heading": "H0",
"paraCount": 1, "charCount": 139,
"cursorPos": 343, "wordCount": 28,
"status": "s000003" "paraCount": 1,
"cursorPos": 343
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "ae7339df26ded", "name": "We Found John!",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "ae7339df26ded",
"order": 6, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "We Found John!", "order": 6,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 189, "metaAttr": {
"wordCount": 37, "heading": "H0",
"paraCount": 1, "charCount": 189,
"cursorPos": 224, "wordCount": 37,
"status": "s000003" "paraCount": 1,
"cursorPos": 224
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "f6622b4617424", "name": "Characters",
"parent": null, "itemAttr": {
"root": null, "handle": "f6622b4617424",
"order": 1, "parent": null,
"heading": "H0", "root": null,
"label": "Characters", "order": 1,
"type": "ROOT", "type": "ROOT",
"class": "CHARACTER", "class": "CHARACTER"
"expanded": true, },
"import": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
}, },
{ {
"handle": "f7e2d9f330615", "name": "Main Characters",
"parent": "f6622b4617424", "itemAttr": {
"root": null, "handle": "f7e2d9f330615",
"order": 0, "parent": "f6622b4617424",
"heading": "H0", "root": null,
"label": "Main Characters", "order": 0,
"type": "FOLDER", "type": "FOLDER",
"class": "CHARACTER", "class": "CHARACTER"
"expanded": true, },
"import": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
}, },
{ {
"handle": "14298de4d9524", "name": "John Smith",
"parent": "f7e2d9f330615", "itemAttr": {
"root": null, "handle": "14298de4d9524",
"order": 0, "parent": "f7e2d9f330615",
"heading": "H0", "root": null,
"label": "John Smith", "order": 0,
"type": "FILE", "type": "FILE",
"class": "CHARACTER", "class": "CHARACTER",
"active": true, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 49, "metaAttr": {
"wordCount": 9, "heading": "H0",
"paraCount": 1, "charCount": 49,
"cursorPos": 24, "wordCount": 9,
"import": "i000008" "paraCount": 1,
"cursorPos": 24
},
"nameAttr": {
"active": true,
"import": "i000008"
}
}, },
{ {
"handle": "bb2c23b3c42cc", "name": "Jane Smith",
"parent": "f7e2d9f330615", "itemAttr": {
"root": null, "handle": "bb2c23b3c42cc",
"order": 1, "parent": "f7e2d9f330615",
"heading": "H0", "root": null,
"label": "Jane Smith", "order": 1,
"type": "FILE", "type": "FILE",
"class": "CHARACTER", "class": "CHARACTER",
"active": true, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 55, "metaAttr": {
"wordCount": 9, "heading": "H0",
"paraCount": 1, "charCount": 55,
"cursorPos": 25, "wordCount": 9,
"import": "i000009" "paraCount": 1,
"cursorPos": 25
},
"nameAttr": {
"active": true,
"import": "i000009"
}
}, },
{ {
"handle": "15c4492bd5107", "name": "Locations",
"parent": null, "itemAttr": {
"root": null, "handle": "15c4492bd5107",
"order": 2, "parent": null,
"heading": "H0", "root": null,
"label": "Locations", "order": 2,
"type": "ROOT", "type": "ROOT",
"class": "WORLD", "class": "WORLD"
"expanded": true, },
"import": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
}, },
{ {
"handle": "b3e74dbc1f584", "name": "Earth",
"parent": "15c4492bd5107", "itemAttr": {
"root": null, "handle": "b3e74dbc1f584",
"order": 0, "parent": "15c4492bd5107",
"heading": "H0", "root": null,
"label": "Earth", "order": 0,
"type": "FILE", "type": "FILE",
"class": "WORLD", "class": "WORLD",
"active": true, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 76, "metaAttr": {
"wordCount": 15, "heading": "H0",
"paraCount": 1, "charCount": 76,
"cursorPos": 20, "wordCount": 15,
"import": "i00000a" "paraCount": 1,
"cursorPos": 20
},
"nameAttr": {
"active": true,
"import": "i00000a"
}
}, },
{ {
"handle": "f1471bef9f2ae", "name": "Space",
"parent": "15c4492bd5107", "itemAttr": {
"root": null, "handle": "f1471bef9f2ae",
"order": 1, "parent": "15c4492bd5107",
"heading": "H0", "root": null,
"label": "Space", "order": 1,
"type": "FILE", "type": "FILE",
"class": "WORLD", "class": "WORLD",
"active": true, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 115, "metaAttr": {
"wordCount": 24, "heading": "H0",
"paraCount": 1, "charCount": 115,
"cursorPos": 133, "wordCount": 24,
"import": "i000008" "paraCount": 1,
"cursorPos": 133
},
"nameAttr": {
"active": true,
"import": "i000008"
}
}, },
{ {
"handle": "5eaea4e8cdee8", "name": "Mars",
"parent": "15c4492bd5107", "itemAttr": {
"root": null, "handle": "5eaea4e8cdee8",
"order": 2, "parent": "15c4492bd5107",
"heading": "H0", "root": null,
"label": "Mars", "order": 2,
"type": "FILE", "type": "FILE",
"class": "WORLD", "class": "WORLD",
"active": true, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 28, "metaAttr": {
"wordCount": 6, "heading": "H0",
"paraCount": 1, "charCount": 28,
"cursorPos": 45, "wordCount": 6,
"import": "i000009" "paraCount": 1,
"cursorPos": 45
},
"nameAttr": {
"active": true,
"import": "i000009"
}
}, },
{ {
"handle": "98acd8c76c93a", "name": "Trash",
"parent": null, "itemAttr": {
"root": null, "handle": "98acd8c76c93a",
"order": 3, "parent": null,
"heading": "H0", "root": null,
"label": "Trash", "order": 3,
"type": "ROOT", "type": "ROOT",
"class": "TRASH", "class": "TRASH"
"expanded": true, },
"import": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
}, },
{ {
"handle": "b8136a5a774a0", "name": "Delete Me!",
"parent": "98acd8c76c93a", "itemAttr": {
"root": null, "handle": "b8136a5a774a0",
"order": 0, "parent": "98acd8c76c93a",
"heading": "H0", "root": null,
"label": "Delete Me!", "order": 0,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 30, "metaAttr": {
"wordCount": 6, "heading": "H0",
"paraCount": 1, "charCount": 30,
"cursorPos": 36, "wordCount": 6,
"status": "s000000" "paraCount": 1,
"cursorPos": 36
},
"nameAttr": {
"active": true,
"status": "s000000"
}
} }
] ]
+43 -49
View File
@@ -1,21 +1,15 @@
<?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="2020-06-26 21:20:24"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2020-06-26 21:20:24">
<project> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
<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>5</saveCount>
<autoCount>10</autoCount>
<editTime>1000</editTime>
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>yes</doBackup>
<language>None</language> <language>None</language>
<spellCheck>True</spellCheck> <spellChecking auto="yes">None</spellChecking>
<spellLang>None</spellLang>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<lastHandle> <lastHandle>
<entry key="editor">None</entry> <entry key="editor">None</entry>
<entry key="viewer">None</entry> <entry key="viewer">None</entry>
@@ -50,94 +44,94 @@
<entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry> <entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="22"> <content items="22" novelWords="0" notesWords="0">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL"> <item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000002" import="i000007">Novel</name> <name status="s000002" import="i000007">Novel</name>
</item> </item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="72" wordCount="15" paraCount="2" cursorPos="78"/>
<name status="s000002" import="i000007" active="True">Title Page</name> <name status="s000002" import="i000007" active="yes">Title Page</name>
</item> </item>
<item handle="974e400180a99" parent="7031beac91f75" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="210" wordCount="40" paraCount="2" cursorPos="213"/>
<name status="s000000" import="i000007" active="True">Page</name> <name status="s000000" import="i000007" active="yes">Page</name>
</item> </item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="23" wordCount="5" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000007" active="True">Part One</name> <name status="s000000" import="i000007" active="yes">Part One</name>
</item> </item>
<item handle="e7ded148d6e4a" parent="7031beac91f75" root="None" order="3" type="FOLDER" class="NOVEL"> <item handle="e7ded148d6e4a" parent="7031beac91f75" root="None" order="3" type="FOLDER" class="NOVEL">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000003" import="i000007">A Folder</name> <name status="s000003" import="i000007">A Folder</name>
</item> </item>
<item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="12" wordCount="3" paraCount="0" cursorPos="215"/>
<name status="s000001" import="i000007" active="True">Chapter One</name> <name status="s000001" import="i000007" active="yes">Chapter One</name>
</item> </item>
<item handle="636b6aa9b697b" parent="e7ded148d6e4a" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="1483" wordCount="263" paraCount="8" cursorPos="1086"/>
<name status="s000003" import="i000007" active="True">Making a Scene</name> <name status="s000003" import="i000007" active="yes">Making a Scene</name>
</item> </item>
<item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="476" wordCount="93" paraCount="3" cursorPos="428"/>
<name status="s000003" import="i000007" active="True">Another Scene</name> <name status="s000003" import="i000007" active="yes">Another Scene</name>
</item> </item>
<item handle="ba8a28a246524" parent="e7ded148d6e4a" root="None" order="3" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="633" wordCount="101" paraCount="3" cursorPos="1238"/>
<name status="s000006" import="i000007" active="True">Interlude</name> <name status="s000006" import="i000007" active="yes">Interlude</name>
</item> </item>
<item handle="96b68994dfa3d" parent="e7ded148d6e4a" root="None" order="4" type="FILE" class="NOVEL" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="1692" wordCount="313" paraCount="6" cursorPos="1721"/>
<name status="s000004" import="i000007" active="False">A Note on Structure</name> <name status="s000004" import="i000007" active="no">A Note on Structure</name>
</item> </item>
<item handle="88706ddc78b1b" parent="e7ded148d6e4a" root="None" order="5" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<name status="s000003" import="i000007" active="True">Chapter Two</name> <name status="s000003" import="i000007" active="yes">Chapter Two</name>
</item> </item>
<item handle="ae7339df26ded" parent="e7ded148d6e4a" root="None" order="6" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="s000003" import="i000007" active="True">We Found John!</name> <name status="s000003" import="i000007" active="yes">We Found John!</name>
</item> </item>
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="1" type="ROOT" class="CHARACTER"> <item handle="f6622b4617424" parent="None" root="f6622b4617424" order="1" type="ROOT" class="CHARACTER">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Characters</name> <name status="s000000" import="i000007">Characters</name>
</item> </item>
<item handle="f7e2d9f330615" parent="f6622b4617424" root="None" order="0" type="FOLDER" class="CHARACTER"> <item handle="f7e2d9f330615" parent="f6622b4617424" root="None" order="0" type="FOLDER" class="CHARACTER">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Main Characters</name> <name status="s000000" import="i000007">Main Characters</name>
</item> </item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="None" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="s000000" import="i000008" active="True">John Smith</name> <name status="s000000" import="i000008" active="yes">John Smith</name>
</item> </item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="None" order="1" type="FILE" class="CHARACTER" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="s000000" import="i000009" active="True">Jane Smith</name> <name status="s000000" import="i000009" active="yes">Jane Smith</name>
</item> </item>
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="2" type="ROOT" class="WORLD"> <item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="2" type="ROOT" class="WORLD">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Locations</name> <name status="s000000" import="i000007">Locations</name>
</item> </item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="None" order="0" type="FILE" class="WORLD" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="s000000" import="i00000a" active="True">Earth</name> <name status="s000000" import="i00000a" active="yes">Earth</name>
</item> </item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="None" order="1" type="FILE" class="WORLD" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="s000000" import="i000008" active="True">Space</name> <name status="s000000" import="i000008" active="yes">Space</name>
</item> </item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="None" order="2" type="FILE" class="WORLD" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="s000000" import="i000009" active="True">Mars</name> <name status="s000000" import="i000009" active="yes">Mars</name>
</item> </item>
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="3" type="ROOT" class="TRASH"> <item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="3" type="ROOT" class="TRASH">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Trash</name> <name status="s000000" import="i000007">Trash</name>
</item> </item>
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s000000" import="i000007" active="True">Delete Me!</name> <name status="s000000" import="i000007" active="yes">Delete Me!</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
+486 -336
View File
@@ -1,387 +1,537 @@
[ [
{ {
"handle": "7031beac91f75", "name": "Novel",
"parent": null, "itemAttr": {
"root": null, "handle": "7031beac91f75",
"order": 0, "parent": null,
"heading": "H0", "root": null,
"label": "Novel", "order": 0,
"type": "ROOT", "type": "ROOT",
"class": "NOVEL", "class": "NOVEL"
"expanded": true, },
"status": "s000002" "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000002"
}
}, },
{ {
"handle": "53b69b83cdafc", "name": "Title Page",
"parent": "7031beac91f75", "itemAttr": {
"root": null, "handle": "53b69b83cdafc",
"order": 0, "parent": "7031beac91f75",
"heading": "H0", "root": null,
"label": "Title Page", "order": 0,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 241, "metaAttr": {
"wordCount": 42, "heading": "H0",
"paraCount": 3, "charCount": 241,
"cursorPos": 252, "wordCount": 42,
"status": "s000002" "paraCount": 3,
"cursorPos": 252
},
"nameAttr": {
"active": true,
"status": "s000002"
}
}, },
{ {
"handle": "974e400180a99", "name": "Page",
"parent": "7031beac91f75", "itemAttr": {
"root": null, "handle": "974e400180a99",
"order": 1, "parent": "7031beac91f75",
"heading": "H0", "root": null,
"label": "Page", "order": 1,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 125, "metaAttr": {
"wordCount": 26, "heading": "H0",
"paraCount": 2, "charCount": 125,
"cursorPos": 127, "wordCount": 26,
"status": "s000000" "paraCount": 2,
"cursorPos": 127
},
"nameAttr": {
"active": true,
"status": "s000000"
}
}, },
{ {
"handle": "edca4be2fcaf8", "name": "Part One",
"parent": "7031beac91f75", "itemAttr": {
"root": null, "handle": "edca4be2fcaf8",
"order": 2, "parent": "7031beac91f75",
"heading": "H0", "root": null,
"label": "Part One", "order": 2,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 26, "metaAttr": {
"wordCount": 6, "heading": "H0",
"paraCount": 1, "charCount": 26,
"cursorPos": 30, "wordCount": 6,
"status": "s000000" "paraCount": 1,
"cursorPos": 30
},
"nameAttr": {
"active": true,
"status": "s000000"
}
}, },
{ {
"handle": "e7ded148d6e4a", "name": "A Folder",
"parent": "7031beac91f75", "itemAttr": {
"root": null, "handle": "e7ded148d6e4a",
"order": 3, "parent": "7031beac91f75",
"heading": "H0", "root": null,
"label": "A Folder", "order": 3,
"type": "FOLDER", "type": "FOLDER",
"class": "NOVEL", "class": "NOVEL"
"expanded": true, },
"status": "s000003" "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000003"
}
}, },
{ {
"handle": "6a2d6d5f4f401", "name": "Chapter One",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "6a2d6d5f4f401",
"order": 0, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Chapter One", "order": 0,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 75, "metaAttr": {
"wordCount": 14, "heading": "H0",
"paraCount": 1, "charCount": 75,
"cursorPos": 279, "wordCount": 14,
"status": "s000001" "paraCount": 1,
"cursorPos": 279
},
"nameAttr": {
"active": true,
"status": "s000001"
}
}, },
{ {
"handle": "636b6aa9b697b", "name": "Making a Scene",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "636b6aa9b697b",
"order": 1, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Making a Scene", "order": 1,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 2429, "metaAttr": {
"wordCount": 432, "heading": "H0",
"paraCount": 14, "charCount": 2429,
"cursorPos": 61, "wordCount": 432,
"status": "s000003" "paraCount": 14,
"cursorPos": 61
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "bc0cbd2a407f3", "name": "Another Scene",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "bc0cbd2a407f3",
"order": 2, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Another Scene", "order": 2,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 476, "metaAttr": {
"wordCount": 93, "heading": "H0",
"paraCount": 3, "charCount": 476,
"cursorPos": 577, "wordCount": 93,
"status": "s000003" "paraCount": 3,
"cursorPos": 577
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "ba8a28a246524", "name": "Interlude",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "ba8a28a246524",
"order": 3, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Interlude", "order": 3,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 617, "metaAttr": {
"wordCount": 101, "heading": "H0",
"paraCount": 3, "charCount": 617,
"cursorPos": 1137, "wordCount": 101,
"status": "s000000" "paraCount": 3,
"cursorPos": 1137
},
"nameAttr": {
"active": true,
"status": "s000000"
}
}, },
{ {
"handle": "96b68994dfa3d", "name": "A Note on Structure",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "96b68994dfa3d",
"order": 4, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "A Note on Structure", "order": 4,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": false, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 1692, "metaAttr": {
"wordCount": 313, "heading": "H0",
"paraCount": 6, "charCount": 1692,
"cursorPos": 1110, "wordCount": 313,
"status": "s000004" "paraCount": 6,
"cursorPos": 1110
},
"nameAttr": {
"active": false,
"status": "s000004"
}
}, },
{ {
"handle": "88706ddc78b1b", "name": "Chapter Two",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "88706ddc78b1b",
"order": 5, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Chapter Two", "order": 5,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 139, "metaAttr": {
"wordCount": 28, "heading": "H0",
"paraCount": 1, "charCount": 139,
"cursorPos": 343, "wordCount": 28,
"status": "s000003" "paraCount": 1,
"cursorPos": 343
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "ae7339df26ded", "name": "We Found John!",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "ae7339df26ded",
"order": 6, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "We Found John!", "order": 6,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 189, "metaAttr": {
"wordCount": 37, "heading": "H0",
"paraCount": 1, "charCount": 189,
"cursorPos": 224, "wordCount": 37,
"status": "s000003" "paraCount": 1,
"cursorPos": 224
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "f6622b4617424", "name": "Characters",
"parent": null, "itemAttr": {
"root": null, "handle": "f6622b4617424",
"order": 1, "parent": null,
"heading": "H0", "root": null,
"label": "Characters", "order": 1,
"type": "ROOT", "type": "ROOT",
"class": "CHARACTER", "class": "CHARACTER"
"expanded": true, },
"import": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
}, },
{ {
"handle": "f7e2d9f330615", "name": "Main Characters",
"parent": "f6622b4617424", "itemAttr": {
"root": null, "handle": "f7e2d9f330615",
"order": 0, "parent": "f6622b4617424",
"heading": "H0", "root": null,
"label": "Main Characters", "order": 0,
"type": "FOLDER", "type": "FOLDER",
"class": "CHARACTER", "class": "CHARACTER"
"expanded": true, },
"import": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
}, },
{ {
"handle": "14298de4d9524", "name": "John Smith",
"parent": "f7e2d9f330615", "itemAttr": {
"root": null, "handle": "14298de4d9524",
"order": 0, "parent": "f7e2d9f330615",
"heading": "H0", "root": null,
"label": "John Smith", "order": 0,
"type": "FILE", "type": "FILE",
"class": "CHARACTER", "class": "CHARACTER",
"active": true, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 49, "metaAttr": {
"wordCount": 9, "heading": "H0",
"paraCount": 1, "charCount": 49,
"cursorPos": 24, "wordCount": 9,
"import": "i000008" "paraCount": 1,
"cursorPos": 24
},
"nameAttr": {
"active": true,
"import": "i000008"
}
}, },
{ {
"handle": "bb2c23b3c42cc", "name": "Jane Smith",
"parent": "f7e2d9f330615", "itemAttr": {
"root": null, "handle": "bb2c23b3c42cc",
"order": 1, "parent": "f7e2d9f330615",
"heading": "H0", "root": null,
"label": "Jane Smith", "order": 1,
"type": "FILE", "type": "FILE",
"class": "CHARACTER", "class": "CHARACTER",
"active": true, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 55, "metaAttr": {
"wordCount": 9, "heading": "H0",
"paraCount": 1, "charCount": 55,
"cursorPos": 25, "wordCount": 9,
"import": "i000009" "paraCount": 1,
"cursorPos": 25
},
"nameAttr": {
"active": true,
"import": "i000009"
}
}, },
{ {
"handle": "15c4492bd5107", "name": "Locations",
"parent": null, "itemAttr": {
"root": null, "handle": "15c4492bd5107",
"order": 2, "parent": null,
"heading": "H0", "root": null,
"label": "Locations", "order": 2,
"type": "ROOT", "type": "ROOT",
"class": "WORLD", "class": "WORLD"
"expanded": true, },
"import": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
}, },
{ {
"handle": "b3e74dbc1f584", "name": "Earth",
"parent": "15c4492bd5107", "itemAttr": {
"root": null, "handle": "b3e74dbc1f584",
"order": 0, "parent": "15c4492bd5107",
"heading": "H0", "root": null,
"label": "Earth", "order": 0,
"type": "FILE", "type": "FILE",
"class": "WORLD", "class": "WORLD",
"active": true, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 76, "metaAttr": {
"wordCount": 15, "heading": "H0",
"paraCount": 1, "charCount": 76,
"cursorPos": 20, "wordCount": 15,
"import": "i00000a" "paraCount": 1,
"cursorPos": 20
},
"nameAttr": {
"active": true,
"import": "i00000a"
}
}, },
{ {
"handle": "f1471bef9f2ae", "name": "Space",
"parent": "15c4492bd5107", "itemAttr": {
"root": null, "handle": "f1471bef9f2ae",
"order": 1, "parent": "15c4492bd5107",
"heading": "H0", "root": null,
"label": "Space", "order": 1,
"type": "FILE", "type": "FILE",
"class": "WORLD", "class": "WORLD",
"active": true, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 115, "metaAttr": {
"wordCount": 24, "heading": "H0",
"paraCount": 1, "charCount": 115,
"cursorPos": 133, "wordCount": 24,
"import": "i000008" "paraCount": 1,
"cursorPos": 133
},
"nameAttr": {
"active": true,
"import": "i000008"
}
}, },
{ {
"handle": "5eaea4e8cdee8", "name": "Mars",
"parent": "15c4492bd5107", "itemAttr": {
"root": null, "handle": "5eaea4e8cdee8",
"order": 2, "parent": "15c4492bd5107",
"heading": "H0", "root": null,
"label": "Mars", "order": 2,
"type": "FILE", "type": "FILE",
"class": "WORLD", "class": "WORLD",
"active": true, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 28, "metaAttr": {
"wordCount": 6, "heading": "H0",
"paraCount": 1, "charCount": 28,
"cursorPos": 45, "wordCount": 6,
"import": "i000009" "paraCount": 1,
"cursorPos": 45
},
"nameAttr": {
"active": true,
"import": "i000009"
}
}, },
{ {
"handle": "6827118336ac1", "name": "Outtakes",
"parent": null, "itemAttr": {
"root": null, "handle": "6827118336ac1",
"order": 3, "parent": null,
"heading": "H0", "root": null,
"label": "Outtakes", "order": 3,
"type": "ROOT", "type": "ROOT",
"class": "ARCHIVE", "class": "ARCHIVE"
"expanded": true, },
"status": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": null
}
}, },
{ {
"handle": "ae9bf3c3ea159", "name": "Scenes",
"parent": "6827118336ac1", "itemAttr": {
"root": null, "handle": "ae9bf3c3ea159",
"order": 0, "parent": "6827118336ac1",
"heading": "H0", "root": null,
"label": "Scenes", "order": 0,
"type": "FOLDER", "type": "FOLDER",
"class": "ARCHIVE", "class": "ARCHIVE"
"expanded": true, },
"status": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": null
}
}, },
{ {
"handle": "8a5deb88c0e97", "name": "Old File",
"parent": "ae9bf3c3ea159", "itemAttr": {
"root": null, "handle": "8a5deb88c0e97",
"order": 0, "parent": "ae9bf3c3ea159",
"heading": "H0", "root": null,
"label": "Old File", "order": 0,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 315, "metaAttr": {
"wordCount": 55, "heading": "H0",
"paraCount": 1, "charCount": 315,
"cursorPos": 322, "wordCount": 55,
"status": "s000003" "paraCount": 1,
"cursorPos": 322
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "98acd8c76c93a", "name": "Trash",
"parent": null, "itemAttr": {
"root": null, "handle": "98acd8c76c93a",
"order": 4, "parent": null,
"heading": "H0", "root": null,
"label": "Trash", "order": 4,
"type": "ROOT", "type": "ROOT",
"class": "TRASH", "class": "TRASH"
"expanded": true, },
"import": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
}, },
{ {
"handle": "b8136a5a774a0", "name": "Delete Me!",
"parent": "98acd8c76c93a", "itemAttr": {
"root": null, "handle": "b8136a5a774a0",
"order": 0, "parent": "98acd8c76c93a",
"heading": "H0", "root": null,
"label": "Delete Me!", "order": 0,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 30, "metaAttr": {
"wordCount": 6, "heading": "H0",
"paraCount": 1, "charCount": 30,
"cursorPos": 36, "wordCount": 6,
"status": "s000000" "paraCount": 1,
"cursorPos": 36
},
"nameAttr": {
"active": true,
"status": "s000000"
}
} }
] ]
+47 -53
View File
@@ -1,21 +1,15 @@
<?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="2021-08-30 23:33:44"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2021-08-30 23:33:44">
<project> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
<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>5</saveCount>
<autoCount>10</autoCount>
<editTime>1000</editTime>
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>yes</doBackup>
<language>en_GB</language> <language>en_GB</language>
<spellCheck>True</spellCheck> <spellChecking auto="yes">en_GB</spellChecking>
<spellLang>en_GB</spellLang>
<novelWordCount>840</novelWordCount>
<notesWordCount>376</notesWordCount>
<lastHandle> <lastHandle>
<entry key="editor">None</entry> <entry key="editor">None</entry>
<entry key="viewer">None</entry> <entry key="viewer">None</entry>
@@ -50,106 +44,106 @@
<entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry> <entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="25"> <content items="25" novelWords="840" notesWords="376">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL"> <item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000002" import="i000007">Novel</name> <name status="s000002" import="i000007">Novel</name>
</item> </item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="241" wordCount="42" paraCount="3" cursorPos="252"/>
<name status="s000002" import="i000007" active="True">Title Page</name> <name status="s000002" import="i000007" active="yes">Title Page</name>
</item> </item>
<item handle="974e400180a99" parent="7031beac91f75" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="125" wordCount="26" paraCount="2" cursorPos="127"/>
<name status="s000000" import="i000007" active="True">Page</name> <name status="s000000" import="i000007" active="yes">Page</name>
</item> </item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="26" wordCount="6" paraCount="1" cursorPos="30"/>
<name status="s000000" import="i000007" active="True">Part One</name> <name status="s000000" import="i000007" active="yes">Part One</name>
</item> </item>
<item handle="e7ded148d6e4a" parent="7031beac91f75" root="None" order="3" type="FOLDER" class="NOVEL"> <item handle="e7ded148d6e4a" parent="7031beac91f75" root="None" order="3" type="FOLDER" class="NOVEL">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000003" import="i000007">A Folder</name> <name status="s000003" import="i000007">A Folder</name>
</item> </item>
<item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="75" wordCount="14" paraCount="1" cursorPos="279"/>
<name status="s000001" import="i000007" active="True">Chapter One</name> <name status="s000001" import="i000007" active="yes">Chapter One</name>
</item> </item>
<item handle="636b6aa9b697b" parent="e7ded148d6e4a" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="2429" wordCount="432" paraCount="14" cursorPos="61"/>
<name status="s000003" import="i000007" active="True">Making a Scene</name> <name status="s000003" import="i000007" active="yes">Making a Scene</name>
</item> </item>
<item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="476" wordCount="93" paraCount="3" cursorPos="577"/>
<name status="s000003" import="i000007" active="True">Another Scene</name> <name status="s000003" import="i000007" active="yes">Another Scene</name>
</item> </item>
<item handle="ba8a28a246524" parent="e7ded148d6e4a" root="None" order="3" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="617" wordCount="101" paraCount="3" cursorPos="1137"/>
<name status="s000000" import="i000007" active="True">Interlude</name> <name status="s000000" import="i000007" active="yes">Interlude</name>
</item> </item>
<item handle="96b68994dfa3d" parent="e7ded148d6e4a" root="None" order="4" type="FILE" class="NOVEL" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="1692" wordCount="313" paraCount="6" cursorPos="1110"/>
<name status="s000004" import="i000007" active="False">A Note on Structure</name> <name status="s000004" import="i000007" active="no">A Note on Structure</name>
</item> </item>
<item handle="88706ddc78b1b" parent="e7ded148d6e4a" root="None" order="5" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<name status="s000003" import="i000007" active="True">Chapter Two</name> <name status="s000003" import="i000007" active="yes">Chapter Two</name>
</item> </item>
<item handle="ae7339df26ded" parent="e7ded148d6e4a" root="None" order="6" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="s000003" import="i000007" active="True">We Found John!</name> <name status="s000003" import="i000007" active="yes">We Found John!</name>
</item> </item>
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="1" type="ROOT" class="CHARACTER"> <item handle="f6622b4617424" parent="None" root="f6622b4617424" order="1" type="ROOT" class="CHARACTER">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Characters</name> <name status="s000000" import="i000007">Characters</name>
</item> </item>
<item handle="f7e2d9f330615" parent="f6622b4617424" root="None" order="0" type="FOLDER" class="CHARACTER"> <item handle="f7e2d9f330615" parent="f6622b4617424" root="None" order="0" type="FOLDER" class="CHARACTER">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Main Characters</name> <name status="s000000" import="i000007">Main Characters</name>
</item> </item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="None" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="s000000" import="i000008" active="True">John Smith</name> <name status="s000000" import="i000008" active="yes">John Smith</name>
</item> </item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="None" order="1" type="FILE" class="CHARACTER" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="s000000" import="i000009" active="True">Jane Smith</name> <name status="s000000" import="i000009" active="yes">Jane Smith</name>
</item> </item>
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="2" type="ROOT" class="WORLD"> <item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="2" type="ROOT" class="WORLD">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Locations</name> <name status="s000000" import="i000007">Locations</name>
</item> </item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="None" order="0" type="FILE" class="WORLD" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="s000000" import="i00000a" active="True">Earth</name> <name status="s000000" import="i00000a" active="yes">Earth</name>
</item> </item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="None" order="1" type="FILE" class="WORLD" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="s000000" import="i000008" active="True">Space</name> <name status="s000000" import="i000008" active="yes">Space</name>
</item> </item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="None" order="2" type="FILE" class="WORLD" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="s000000" import="i000009" active="True">Mars</name> <name status="s000000" import="i000009" active="yes">Mars</name>
</item> </item>
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="3" type="ROOT" class="ARCHIVE"> <item handle="6827118336ac1" parent="None" root="6827118336ac1" order="3" type="ROOT" class="ARCHIVE">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Outtakes</name> <name status="s000000" import="i000007">Outtakes</name>
</item> </item>
<item handle="ae9bf3c3ea159" parent="6827118336ac1" root="None" order="0" type="FOLDER" class="ARCHIVE"> <item handle="ae9bf3c3ea159" parent="6827118336ac1" root="None" order="0" type="FOLDER" class="ARCHIVE">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Scenes</name> <name status="s000000" import="i000007">Scenes</name>
</item> </item>
<item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="315" wordCount="55" paraCount="1" cursorPos="322"/>
<name status="s000003" import="i000007" active="True">Old File</name> <name status="s000003" import="i000007" active="yes">Old File</name>
</item> </item>
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="4" type="ROOT" class="TRASH"> <item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="4" type="ROOT" class="TRASH">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Trash</name> <name status="s000000" import="i000007">Trash</name>
</item> </item>
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s000000" import="i000007" active="True">Delete Me!</name> <name status="s000000" import="i000007" active="yes">Delete Me!</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
+486 -336
View File
@@ -1,387 +1,537 @@
[ [
{ {
"handle": "7031beac91f75", "name": "Novel",
"parent": null, "itemAttr": {
"root": null, "handle": "7031beac91f75",
"order": 0, "parent": null,
"heading": "H0", "root": null,
"label": "Novel", "order": 0,
"type": "ROOT", "type": "ROOT",
"class": "NOVEL", "class": "NOVEL"
"expanded": true, },
"status": "s000002" "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000002"
}
}, },
{ {
"handle": "53b69b83cdafc", "name": "Title Page",
"parent": "7031beac91f75", "itemAttr": {
"root": null, "handle": "53b69b83cdafc",
"order": 0, "parent": "7031beac91f75",
"heading": "H0", "root": null,
"label": "Title Page", "order": 0,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 93, "metaAttr": {
"wordCount": 19, "heading": "H0",
"paraCount": 2, "charCount": 93,
"cursorPos": 2, "wordCount": 19,
"status": "s000002" "paraCount": 2,
"cursorPos": 2
},
"nameAttr": {
"active": true,
"status": "s000002"
}
}, },
{ {
"handle": "974e400180a99", "name": "Page",
"parent": "7031beac91f75", "itemAttr": {
"root": null, "handle": "974e400180a99",
"order": 1, "parent": "7031beac91f75",
"heading": "H0", "root": null,
"label": "Page", "order": 1,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 186, "metaAttr": {
"wordCount": 39, "heading": "H0",
"paraCount": 2, "charCount": 186,
"cursorPos": 212, "wordCount": 39,
"status": "s000000" "paraCount": 2,
"cursorPos": 212
},
"nameAttr": {
"active": true,
"status": "s000000"
}
}, },
{ {
"handle": "edca4be2fcaf8", "name": "Part One",
"parent": "7031beac91f75", "itemAttr": {
"root": null, "handle": "edca4be2fcaf8",
"order": 2, "parent": "7031beac91f75",
"heading": "H0", "root": null,
"label": "Part One", "order": 2,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 26, "metaAttr": {
"wordCount": 6, "heading": "H0",
"paraCount": 1, "charCount": 26,
"cursorPos": 33, "wordCount": 6,
"status": "s000000" "paraCount": 1,
"cursorPos": 33
},
"nameAttr": {
"active": true,
"status": "s000000"
}
}, },
{ {
"handle": "e7ded148d6e4a", "name": "A Folder",
"parent": "7031beac91f75", "itemAttr": {
"root": null, "handle": "e7ded148d6e4a",
"order": 3, "parent": "7031beac91f75",
"heading": "H0", "root": null,
"label": "A Folder", "order": 3,
"type": "FOLDER", "type": "FOLDER",
"class": "NOVEL", "class": "NOVEL"
"expanded": true, },
"status": "s000003" "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000003"
}
}, },
{ {
"handle": "6a2d6d5f4f401", "name": "Chapter One",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "6a2d6d5f4f401",
"order": 0, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Chapter One", "order": 0,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 75, "metaAttr": {
"wordCount": 14, "heading": "H0",
"paraCount": 1, "charCount": 75,
"cursorPos": 279, "wordCount": 14,
"status": "s000001" "paraCount": 1,
"cursorPos": 279
},
"nameAttr": {
"active": true,
"status": "s000001"
}
}, },
{ {
"handle": "636b6aa9b697b", "name": "Making a Scene",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "636b6aa9b697b",
"order": 1, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Making a Scene", "order": 1,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 2429, "metaAttr": {
"wordCount": 432, "heading": "H0",
"paraCount": 14, "charCount": 2429,
"cursorPos": 62, "wordCount": 432,
"status": "s000003" "paraCount": 14,
"cursorPos": 62
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "bc0cbd2a407f3", "name": "Another Scene",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "bc0cbd2a407f3",
"order": 2, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Another Scene", "order": 2,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 476, "metaAttr": {
"wordCount": 93, "heading": "H0",
"paraCount": 3, "charCount": 476,
"cursorPos": 577, "wordCount": 93,
"status": "s000003" "paraCount": 3,
"cursorPos": 577
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "ba8a28a246524", "name": "Interlude",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "ba8a28a246524",
"order": 3, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Interlude", "order": 3,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 617, "metaAttr": {
"wordCount": 101, "heading": "H0",
"paraCount": 3, "charCount": 617,
"cursorPos": 4, "wordCount": 101,
"status": "s000000" "paraCount": 3,
"cursorPos": 4
},
"nameAttr": {
"active": true,
"status": "s000000"
}
}, },
{ {
"handle": "96b68994dfa3d", "name": "A Note on Structure",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "96b68994dfa3d",
"order": 4, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "A Note on Structure", "order": 4,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": false, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 1692, "metaAttr": {
"wordCount": 313, "heading": "H0",
"paraCount": 6, "charCount": 1692,
"cursorPos": 1110, "wordCount": 313,
"status": "s000004" "paraCount": 6,
"cursorPos": 1110
},
"nameAttr": {
"active": false,
"status": "s000004"
}
}, },
{ {
"handle": "88706ddc78b1b", "name": "Chapter Two",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "88706ddc78b1b",
"order": 5, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "Chapter Two", "order": 5,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 139, "metaAttr": {
"wordCount": 28, "heading": "H0",
"paraCount": 1, "charCount": 139,
"cursorPos": 343, "wordCount": 28,
"status": "s000003" "paraCount": 1,
"cursorPos": 343
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "ae7339df26ded", "name": "We Found John!",
"parent": "e7ded148d6e4a", "itemAttr": {
"root": null, "handle": "ae7339df26ded",
"order": 6, "parent": "e7ded148d6e4a",
"heading": "H0", "root": null,
"label": "We Found John!", "order": 6,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 189, "metaAttr": {
"wordCount": 37, "heading": "H0",
"paraCount": 1, "charCount": 189,
"cursorPos": 224, "wordCount": 37,
"status": "s000003" "paraCount": 1,
"cursorPos": 224
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "f6622b4617424", "name": "Characters",
"parent": null, "itemAttr": {
"root": null, "handle": "f6622b4617424",
"order": 1, "parent": null,
"heading": "H0", "root": null,
"label": "Characters", "order": 1,
"type": "ROOT", "type": "ROOT",
"class": "CHARACTER", "class": "CHARACTER"
"expanded": true, },
"import": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
}, },
{ {
"handle": "f7e2d9f330615", "name": "Main Characters",
"parent": "f6622b4617424", "itemAttr": {
"root": null, "handle": "f7e2d9f330615",
"order": 0, "parent": "f6622b4617424",
"heading": "H0", "root": null,
"label": "Main Characters", "order": 0,
"type": "FOLDER", "type": "FOLDER",
"class": "CHARACTER", "class": "CHARACTER"
"expanded": true, },
"import": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
}, },
{ {
"handle": "14298de4d9524", "name": "John Smith",
"parent": "f7e2d9f330615", "itemAttr": {
"root": null, "handle": "14298de4d9524",
"order": 0, "parent": "f7e2d9f330615",
"heading": "H0", "root": null,
"label": "John Smith", "order": 0,
"type": "FILE", "type": "FILE",
"class": "CHARACTER", "class": "CHARACTER",
"active": true, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 49, "metaAttr": {
"wordCount": 9, "heading": "H0",
"paraCount": 1, "charCount": 49,
"cursorPos": 24, "wordCount": 9,
"import": "i000008" "paraCount": 1,
"cursorPos": 24
},
"nameAttr": {
"active": true,
"import": "i000008"
}
}, },
{ {
"handle": "bb2c23b3c42cc", "name": "Jane Smith",
"parent": "f7e2d9f330615", "itemAttr": {
"root": null, "handle": "bb2c23b3c42cc",
"order": 1, "parent": "f7e2d9f330615",
"heading": "H0", "root": null,
"label": "Jane Smith", "order": 1,
"type": "FILE", "type": "FILE",
"class": "CHARACTER", "class": "CHARACTER",
"active": true, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 55, "metaAttr": {
"wordCount": 9, "heading": "H0",
"paraCount": 1, "charCount": 55,
"cursorPos": 25, "wordCount": 9,
"import": "i000009" "paraCount": 1,
"cursorPos": 25
},
"nameAttr": {
"active": true,
"import": "i000009"
}
}, },
{ {
"handle": "15c4492bd5107", "name": "Locations",
"parent": null, "itemAttr": {
"root": null, "handle": "15c4492bd5107",
"order": 2, "parent": null,
"heading": "H0", "root": null,
"label": "Locations", "order": 2,
"type": "ROOT", "type": "ROOT",
"class": "WORLD", "class": "WORLD"
"expanded": true, },
"import": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
}, },
{ {
"handle": "b3e74dbc1f584", "name": "Earth",
"parent": "15c4492bd5107", "itemAttr": {
"root": null, "handle": "b3e74dbc1f584",
"order": 0, "parent": "15c4492bd5107",
"heading": "H0", "root": null,
"label": "Earth", "order": 0,
"type": "FILE", "type": "FILE",
"class": "WORLD", "class": "WORLD",
"active": true, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 76, "metaAttr": {
"wordCount": 15, "heading": "H0",
"paraCount": 1, "charCount": 76,
"cursorPos": 20, "wordCount": 15,
"import": "i00000a" "paraCount": 1,
"cursorPos": 20
},
"nameAttr": {
"active": true,
"import": "i00000a"
}
}, },
{ {
"handle": "f1471bef9f2ae", "name": "Space",
"parent": "15c4492bd5107", "itemAttr": {
"root": null, "handle": "f1471bef9f2ae",
"order": 1, "parent": "15c4492bd5107",
"heading": "H0", "root": null,
"label": "Space", "order": 1,
"type": "FILE", "type": "FILE",
"class": "WORLD", "class": "WORLD",
"active": true, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 115, "metaAttr": {
"wordCount": 24, "heading": "H0",
"paraCount": 1, "charCount": 115,
"cursorPos": 133, "wordCount": 24,
"import": "i000008" "paraCount": 1,
"cursorPos": 133
},
"nameAttr": {
"active": true,
"import": "i000008"
}
}, },
{ {
"handle": "5eaea4e8cdee8", "name": "Mars",
"parent": "15c4492bd5107", "itemAttr": {
"root": null, "handle": "5eaea4e8cdee8",
"order": 2, "parent": "15c4492bd5107",
"heading": "H0", "root": null,
"label": "Mars", "order": 2,
"type": "FILE", "type": "FILE",
"class": "WORLD", "class": "WORLD",
"active": true, "layout": "NOTE"
"layout": "NOTE", },
"charCount": 28, "metaAttr": {
"wordCount": 6, "heading": "H0",
"paraCount": 1, "charCount": 28,
"cursorPos": 45, "wordCount": 6,
"import": "i000009" "paraCount": 1,
"cursorPos": 45
},
"nameAttr": {
"active": true,
"import": "i000009"
}
}, },
{ {
"handle": "6827118336ac1", "name": "Archive",
"parent": null, "itemAttr": {
"root": null, "handle": "6827118336ac1",
"order": 3, "parent": null,
"heading": "H0", "root": null,
"label": "Archive", "order": 3,
"type": "ROOT", "type": "ROOT",
"class": "ARCHIVE", "class": "ARCHIVE"
"expanded": true, },
"status": "s000000" "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000000"
}
}, },
{ {
"handle": "ae9bf3c3ea159", "name": "Scenes",
"parent": "6827118336ac1", "itemAttr": {
"root": null, "handle": "ae9bf3c3ea159",
"order": 0, "parent": "6827118336ac1",
"heading": "H0", "root": null,
"label": "Scenes", "order": 0,
"type": "FOLDER", "type": "FOLDER",
"class": "ARCHIVE", "class": "ARCHIVE"
"expanded": true, },
"status": "s000000" "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000000"
}
}, },
{ {
"handle": "8a5deb88c0e97", "name": "Old File",
"parent": "ae9bf3c3ea159", "itemAttr": {
"root": null, "handle": "8a5deb88c0e97",
"order": 0, "parent": "ae9bf3c3ea159",
"heading": "H0", "root": null,
"label": "Old File", "order": 0,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 314, "metaAttr": {
"wordCount": 55, "heading": "H0",
"paraCount": 1, "charCount": 314,
"cursorPos": 322, "wordCount": 55,
"status": "s000003" "paraCount": 1,
"cursorPos": 322
},
"nameAttr": {
"active": true,
"status": "s000003"
}
}, },
{ {
"handle": "98acd8c76c93a", "name": "Trash",
"parent": null, "itemAttr": {
"root": null, "handle": "98acd8c76c93a",
"order": 4, "parent": null,
"heading": "H0", "root": null,
"label": "Trash", "order": 4,
"type": "ROOT", "type": "ROOT",
"class": "TRASH", "class": "TRASH"
"expanded": true, },
"import": null "metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
}, },
{ {
"handle": "b8136a5a774a0", "name": "Delete Me!",
"parent": "98acd8c76c93a", "itemAttr": {
"root": null, "handle": "b8136a5a774a0",
"order": 0, "parent": "98acd8c76c93a",
"heading": "H0", "root": null,
"label": "Delete Me!", "order": 0,
"type": "FILE", "type": "FILE",
"class": "NOVEL", "class": "NOVEL",
"active": true, "layout": "DOCUMENT"
"layout": "DOCUMENT", },
"charCount": 30, "metaAttr": {
"wordCount": 6, "heading": "H0",
"paraCount": 1, "charCount": 30,
"cursorPos": 36, "wordCount": 6,
"status": "s000000" "paraCount": 1,
"cursorPos": 36
},
"nameAttr": {
"active": true,
"status": "s000000"
}
} }
] ]
+47 -53
View File
@@ -1,21 +1,15 @@
<?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:26:15"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-10-25 18:26:15">
<project> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
<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>5</saveCount>
<autoCount>10</autoCount>
<editTime>1000</editTime>
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>yes</doBackup>
<language>en_GB</language> <language>en_GB</language>
<spellCheck>True</spellCheck> <spellChecking auto="yes">en_GB</spellChecking>
<spellLang>en_GB</spellLang>
<novelWordCount>830</novelWordCount>
<notesWordCount>376</notesWordCount>
<lastHandle> <lastHandle>
<entry key="editor">None</entry> <entry key="editor">None</entry>
<entry key="viewer">None</entry> <entry key="viewer">None</entry>
@@ -50,106 +44,106 @@
<entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry> <entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="25"> <content items="25" novelWords="830" notesWords="376">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL"> <item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000002" import="i000007">Novel</name> <name status="s000002" import="i000007">Novel</name>
</item> </item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="93" wordCount="19" paraCount="2" cursorPos="2"/>
<name status="s000002" import="i000007" active="True">Title Page</name> <name status="s000002" import="i000007" active="yes">Title Page</name>
</item> </item>
<item handle="974e400180a99" parent="7031beac91f75" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="186" wordCount="39" paraCount="2" cursorPos="212"/>
<name status="s000000" import="i000007" active="True">Page</name> <name status="s000000" import="i000007" active="yes">Page</name>
</item> </item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="26" wordCount="6" paraCount="1" cursorPos="33"/>
<name status="s000000" import="i000007" active="True">Part One</name> <name status="s000000" import="i000007" active="yes">Part One</name>
</item> </item>
<item handle="e7ded148d6e4a" parent="7031beac91f75" root="None" order="3" type="FOLDER" class="NOVEL"> <item handle="e7ded148d6e4a" parent="7031beac91f75" root="None" order="3" type="FOLDER" class="NOVEL">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000003" import="i000007">A Folder</name> <name status="s000003" import="i000007">A Folder</name>
</item> </item>
<item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="75" wordCount="14" paraCount="1" cursorPos="279"/>
<name status="s000001" import="i000007" active="True">Chapter One</name> <name status="s000001" import="i000007" active="yes">Chapter One</name>
</item> </item>
<item handle="636b6aa9b697b" parent="e7ded148d6e4a" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="2429" wordCount="432" paraCount="14" cursorPos="62"/>
<name status="s000003" import="i000007" active="True">Making a Scene</name> <name status="s000003" import="i000007" active="yes">Making a Scene</name>
</item> </item>
<item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="476" wordCount="93" paraCount="3" cursorPos="577"/>
<name status="s000003" import="i000007" active="True">Another Scene</name> <name status="s000003" import="i000007" active="yes">Another Scene</name>
</item> </item>
<item handle="ba8a28a246524" parent="e7ded148d6e4a" root="None" order="3" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="617" wordCount="101" paraCount="3" cursorPos="4"/>
<name status="s000000" import="i000007" active="True">Interlude</name> <name status="s000000" import="i000007" active="yes">Interlude</name>
</item> </item>
<item handle="96b68994dfa3d" parent="e7ded148d6e4a" root="None" order="4" type="FILE" class="NOVEL" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="1692" wordCount="313" paraCount="6" cursorPos="1110"/>
<name status="s000004" import="i000007" active="False">A Note on Structure</name> <name status="s000004" import="i000007" active="no">A Note on Structure</name>
</item> </item>
<item handle="88706ddc78b1b" parent="e7ded148d6e4a" root="None" order="5" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<name status="s000003" import="i000007" active="True">Chapter Two</name> <name status="s000003" import="i000007" active="yes">Chapter Two</name>
</item> </item>
<item handle="ae7339df26ded" parent="e7ded148d6e4a" root="None" order="6" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="s000003" import="i000007" active="True">We Found John!</name> <name status="s000003" import="i000007" active="yes">We Found John!</name>
</item> </item>
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="1" type="ROOT" class="CHARACTER"> <item handle="f6622b4617424" parent="None" root="f6622b4617424" order="1" type="ROOT" class="CHARACTER">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Characters</name> <name status="s000000" import="i000007">Characters</name>
</item> </item>
<item handle="f7e2d9f330615" parent="f6622b4617424" root="None" order="0" type="FOLDER" class="CHARACTER"> <item handle="f7e2d9f330615" parent="f6622b4617424" root="None" order="0" type="FOLDER" class="CHARACTER">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Main Characters</name> <name status="s000000" import="i000007">Main Characters</name>
</item> </item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="None" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="s000000" import="i000008" active="True">John Smith</name> <name status="s000000" import="i000008" active="yes">John Smith</name>
</item> </item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="None" order="1" type="FILE" class="CHARACTER" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="s000000" import="i000009" active="True">Jane Smith</name> <name status="s000000" import="i000009" active="yes">Jane Smith</name>
</item> </item>
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="2" type="ROOT" class="WORLD"> <item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="2" type="ROOT" class="WORLD">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Locations</name> <name status="s000000" import="i000007">Locations</name>
</item> </item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="None" order="0" type="FILE" class="WORLD" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="s000000" import="i00000a" active="True">Earth</name> <name status="s000000" import="i00000a" active="yes">Earth</name>
</item> </item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="None" order="1" type="FILE" class="WORLD" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="s000000" import="i000008" active="True">Space</name> <name status="s000000" import="i000008" active="yes">Space</name>
</item> </item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="None" order="2" type="FILE" class="WORLD" layout="NOTE"> <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"/> <meta expanded="no" heading="H0" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="s000000" import="i000009" active="True">Mars</name> <name status="s000000" import="i000009" active="yes">Mars</name>
</item> </item>
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="3" type="ROOT" class="ARCHIVE"> <item handle="6827118336ac1" parent="None" root="6827118336ac1" order="3" type="ROOT" class="ARCHIVE">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Archive</name> <name status="s000000" import="i000007">Archive</name>
</item> </item>
<item handle="ae9bf3c3ea159" parent="6827118336ac1" root="None" order="0" type="FOLDER" class="ARCHIVE"> <item handle="ae9bf3c3ea159" parent="6827118336ac1" root="None" order="0" type="FOLDER" class="ARCHIVE">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Scenes</name> <name status="s000000" import="i000007">Scenes</name>
</item> </item>
<item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="314" wordCount="55" paraCount="1" cursorPos="322"/>
<name status="s000003" import="i000007" active="True">Old File</name> <name status="s000003" import="i000007" active="yes">Old File</name>
</item> </item>
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="4" type="ROOT" class="TRASH"> <item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="4" type="ROOT" class="TRASH">
<meta expanded="True"/> <meta expanded="yes"/>
<name status="s000000" import="i000007">Trash</name> <name status="s000000" import="i000007">Trash</name>
</item> </item>
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="no" heading="H0" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s000000" import="i000007" active="True">Delete Me!</name> <name status="s000000" import="i000007" active="yes">Delete Me!</name>
</item> </item>
</content> </content>
</novelWriterXML> </novelWriterXML>
@@ -0,0 +1,677 @@
[
{
"name": "Novel",
"itemAttr": {
"handle": "7031beac91f75",
"parent": null,
"root": "7031beac91f75",
"order": 0,
"type": "ROOT",
"class": "NOVEL",
"layout": "NO_LAYOUT"
},
"metaAttr": {
"expanded": true,
"heading": "H0",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 0
},
"nameAttr": {
"status": "sc24b8f",
"import": "ia857f0",
"active": false
}
},
{
"name": "Title Page",
"itemAttr": {
"handle": "53b69b83cdafc",
"parent": "7031beac91f75",
"root": "7031beac91f75",
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"expanded": false,
"heading": "H0",
"charCount": 93,
"wordCount": 19,
"paraCount": 2,
"cursorPos": 119
},
"nameAttr": {
"status": "sc24b8f",
"import": "ia857f0",
"active": true
}
},
{
"name": "Page",
"itemAttr": {
"handle": "974e400180a99",
"parent": "7031beac91f75",
"root": "7031beac91f75",
"order": 1,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"expanded": false,
"heading": "H0",
"charCount": 251,
"wordCount": 50,
"paraCount": 2,
"cursorPos": 277
},
"nameAttr": {
"status": "sf12341",
"import": "ia857f0",
"active": true
}
},
{
"name": "Part One",
"itemAttr": {
"handle": "edca4be2fcaf8",
"parent": "7031beac91f75",
"root": "7031beac91f75",
"order": 2,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"expanded": false,
"heading": "H0",
"charCount": 26,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 36
},
"nameAttr": {
"status": "s90e6c9",
"import": "ia857f0",
"active": true
}
},
{
"name": "Chapter One",
"itemAttr": {
"handle": "6a2d6d5f4f401",
"parent": "7031beac91f75",
"root": "7031beac91f75",
"order": 3,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"expanded": true,
"heading": "H0",
"charCount": 95,
"wordCount": 18,
"paraCount": 1,
"cursorPos": 291
},
"nameAttr": {
"status": "sf24ce6",
"import": "ia857f0",
"active": true
}
},
{
"name": "Making a Scene",
"itemAttr": {
"handle": "636b6aa9b697b",
"parent": "6a2d6d5f4f401",
"root": "7031beac91f75",
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"expanded": false,
"heading": "H0",
"charCount": 2687,
"wordCount": 479,
"paraCount": 14,
"cursorPos": 67
},
"nameAttr": {
"status": "s90e6c9",
"import": "ia857f0",
"active": true
}
},
{
"name": "Another Scene",
"itemAttr": {
"handle": "bc0cbd2a407f3",
"parent": "6a2d6d5f4f401",
"root": "7031beac91f75",
"order": 1,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"expanded": false,
"heading": "H0",
"charCount": 548,
"wordCount": 108,
"paraCount": 3,
"cursorPos": 465
},
"nameAttr": {
"status": "s90e6c9",
"import": "ia857f0",
"active": true
}
},
{
"name": "Interlude",
"itemAttr": {
"handle": "ba8a28a246524",
"parent": "7031beac91f75",
"root": "7031beac91f75",
"order": 4,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"expanded": false,
"heading": "H0",
"charCount": 617,
"wordCount": 101,
"paraCount": 3,
"cursorPos": 310
},
"nameAttr": {
"status": "s78ea90",
"import": "ia857f0",
"active": true
}
},
{
"name": "A Note on Structure",
"itemAttr": {
"handle": "96b68994dfa3d",
"parent": "7031beac91f75",
"root": "7031beac91f75",
"order": 5,
"type": "FILE",
"class": "NOVEL",
"layout": "NOTE"
},
"metaAttr": {
"expanded": false,
"heading": "H0",
"charCount": 1909,
"wordCount": 346,
"paraCount": 7,
"cursorPos": 0
},
"nameAttr": {
"status": "sf24ce6",
"import": "ia857f0",
"active": false
}
},
{
"name": "Chapter Two",
"itemAttr": {
"handle": "88706ddc78b1b",
"parent": "7031beac91f75",
"root": "7031beac91f75",
"order": 6,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"expanded": true,
"heading": "H0",
"charCount": 139,
"wordCount": 28,
"paraCount": 1,
"cursorPos": 188
},
"nameAttr": {
"status": "s90e6c9",
"import": "ia857f0",
"active": true
}
},
{
"name": "We Found John!",
"itemAttr": {
"handle": "ae7339df26ded",
"parent": "88706ddc78b1b",
"root": "7031beac91f75",
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"expanded": false,
"heading": "H0",
"charCount": 189,
"wordCount": 37,
"paraCount": 1,
"cursorPos": 0
},
"nameAttr": {
"status": "s90e6c9",
"import": "ia857f0",
"active": true
}
},
{
"name": "Sequel",
"itemAttr": {
"handle": "e5e47ebf63b1c",
"parent": null,
"root": "e5e47ebf63b1c",
"order": 1,
"type": "ROOT",
"class": "NOVEL",
"layout": "NO_LAYOUT"
},
"metaAttr": {
"expanded": true,
"heading": "H0",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 0
},
"nameAttr": {
"status": "sf12341",
"import": "ia857f0",
"active": false
}
},
{
"name": "Title Page",
"itemAttr": {
"handle": "bacb7059e3083",
"parent": "e5e47ebf63b1c",
"root": "e5e47ebf63b1c",
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"expanded": false,
"heading": "H0",
"charCount": 27,
"wordCount": 5,
"paraCount": 1,
"cursorPos": 100
},
"nameAttr": {
"status": "sc24b8f",
"import": "ia857f0",
"active": true
}
},
{
"name": "Chapter One",
"itemAttr": {
"handle": "a520879ca0b45",
"parent": "e5e47ebf63b1c",
"root": "e5e47ebf63b1c",
"order": 1,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"expanded": false,
"heading": "H0",
"charCount": 299,
"wordCount": 55,
"paraCount": 2,
"cursorPos": 104
},
"nameAttr": {
"status": "s90e6c9",
"import": "ia857f0",
"active": true
}
},
{
"name": "Characters",
"itemAttr": {
"handle": "f6622b4617424",
"parent": null,
"root": "f6622b4617424",
"order": 2,
"type": "ROOT",
"class": "CHARACTER",
"layout": "NO_LAYOUT"
},
"metaAttr": {
"expanded": true,
"heading": "H0",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 0
},
"nameAttr": {
"status": "sf12341",
"import": "ia857f0",
"active": false
}
},
{
"name": "Main Characters",
"itemAttr": {
"handle": "f7e2d9f330615",
"parent": "f6622b4617424",
"root": "f6622b4617424",
"order": 0,
"type": "FOLDER",
"class": "CHARACTER",
"layout": "NO_LAYOUT"
},
"metaAttr": {
"expanded": true,
"heading": "H0",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 0
},
"nameAttr": {
"status": "sf12341",
"import": "ia857f0",
"active": false
}
},
{
"name": "John Smith",
"itemAttr": {
"handle": "14298de4d9524",
"parent": "f7e2d9f330615",
"root": "f6622b4617424",
"order": 0,
"type": "FILE",
"class": "CHARACTER",
"layout": "NOTE"
},
"metaAttr": {
"expanded": false,
"heading": "H0",
"charCount": 49,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 24
},
"nameAttr": {
"status": "sf12341",
"import": "icfb3a5",
"active": true
}
},
{
"name": "Jane Smith",
"itemAttr": {
"handle": "bb2c23b3c42cc",
"parent": "f7e2d9f330615",
"root": "f6622b4617424",
"order": 1,
"type": "FILE",
"class": "CHARACTER",
"layout": "NOTE"
},
"metaAttr": {
"expanded": false,
"heading": "H0",
"charCount": 55,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 25
},
"nameAttr": {
"status": "sf12341",
"import": "i2d7a54",
"active": true
}
},
{
"name": "Locations",
"itemAttr": {
"handle": "15c4492bd5107",
"parent": null,
"root": "15c4492bd5107",
"order": 3,
"type": "ROOT",
"class": "WORLD",
"layout": "NO_LAYOUT"
},
"metaAttr": {
"expanded": true,
"heading": "H0",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 0
},
"nameAttr": {
"status": "sf12341",
"import": "ia857f0",
"active": false
}
},
{
"name": "Earth",
"itemAttr": {
"handle": "b3e74dbc1f584",
"parent": "15c4492bd5107",
"root": "15c4492bd5107",
"order": 0,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE"
},
"metaAttr": {
"expanded": false,
"heading": "H0",
"charCount": 76,
"wordCount": 15,
"paraCount": 1,
"cursorPos": 20
},
"nameAttr": {
"status": "sf12341",
"import": "i56be10",
"active": true
}
},
{
"name": "Space",
"itemAttr": {
"handle": "f1471bef9f2ae",
"parent": "15c4492bd5107",
"root": "15c4492bd5107",
"order": 1,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE"
},
"metaAttr": {
"expanded": false,
"heading": "H0",
"charCount": 115,
"wordCount": 24,
"paraCount": 1,
"cursorPos": 133
},
"nameAttr": {
"status": "sf12341",
"import": "icfb3a5",
"active": true
}
},
{
"name": "Mars",
"itemAttr": {
"handle": "5eaea4e8cdee8",
"parent": "15c4492bd5107",
"root": "15c4492bd5107",
"order": 2,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE"
},
"metaAttr": {
"expanded": false,
"heading": "H0",
"charCount": 28,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 45
},
"nameAttr": {
"status": "sf12341",
"import": "i2d7a54",
"active": true
}
},
{
"name": "Archive",
"itemAttr": {
"handle": "6827118336ac1",
"parent": null,
"root": "6827118336ac1",
"order": 4,
"type": "ROOT",
"class": "ARCHIVE",
"layout": "NO_LAYOUT"
},
"metaAttr": {
"expanded": true,
"heading": "H0",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 0
},
"nameAttr": {
"status": "sf12341",
"import": "ia857f0",
"active": false
}
},
{
"name": "Scenes",
"itemAttr": {
"handle": "ae9bf3c3ea159",
"parent": "6827118336ac1",
"root": "6827118336ac1",
"order": 0,
"type": "FOLDER",
"class": "ARCHIVE",
"layout": "NO_LAYOUT"
},
"metaAttr": {
"expanded": true,
"heading": "H0",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 0
},
"nameAttr": {
"status": "sf12341",
"import": "ia857f0",
"active": false
}
},
{
"name": "Old File",
"itemAttr": {
"handle": "8a5deb88c0e97",
"parent": "ae9bf3c3ea159",
"root": "6827118336ac1",
"order": 0,
"type": "FILE",
"class": "ARCHIVE",
"layout": "DOCUMENT"
},
"metaAttr": {
"expanded": false,
"heading": "H0",
"charCount": 232,
"wordCount": 42,
"paraCount": 1,
"cursorPos": 239
},
"nameAttr": {
"status": "s90e6c9",
"import": "ia857f0",
"active": true
}
},
{
"name": "Trash",
"itemAttr": {
"handle": "98acd8c76c93a",
"parent": null,
"root": "98acd8c76c93a",
"order": 5,
"type": "ROOT",
"class": "TRASH",
"layout": "NO_LAYOUT"
},
"metaAttr": {
"expanded": true,
"heading": "H0",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 0
},
"nameAttr": {
"status": "sf12341",
"import": "ia857f0",
"active": false
}
},
{
"name": "Delete Me!",
"itemAttr": {
"handle": "b8136a5a774a0",
"parent": "98acd8c76c93a",
"root": "98acd8c76c93a",
"order": 0,
"type": "FILE",
"class": "TRASH",
"layout": "DOCUMENT"
},
"metaAttr": {
"expanded": false,
"heading": "H0",
"charCount": 30,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 36
},
"nameAttr": {
"status": "sf12341",
"import": "ia857f0",
"active": true
}
}
]
+157
View File
@@ -0,0 +1,157 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-10-15 12:12:59">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
</project>
<settings>
<doBackup>yes</doBackup>
<language>en_GB</language>
<spellChecking auto="yes">en_GB</spellChecking>
<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="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 items="27" novelWords="954" notesWords="409">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="yes"/>
<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="no" heading="H0" charCount="93" wordCount="19" paraCount="2" cursorPos="119"/>
<name status="sc24b8f" import="ia857f0" active="yes">Title Page</name>
</item>
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H0" charCount="251" wordCount="50" paraCount="2" cursorPos="277"/>
<name status="sf12341" import="ia857f0" active="yes">Page</name>
</item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H0" charCount="26" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s90e6c9" import="ia857f0" active="yes">Part One</name>
</item>
<item handle="6a2d6d5f4f401" parent="7031beac91f75" root="7031beac91f75" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="yes" heading="H0" charCount="95" wordCount="18" paraCount="1" cursorPos="291"/>
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
</item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H0" charCount="2687" wordCount="479" paraCount="14" cursorPos="67"/>
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
</item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H0" charCount="548" wordCount="108" paraCount="3" cursorPos="465"/>
<name status="s90e6c9" import="ia857f0" active="yes">Another Scene</name>
</item>
<item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H0" charCount="617" wordCount="101" paraCount="3" cursorPos="310"/>
<name status="s78ea90" import="ia857f0" active="yes">Interlude</name>
</item>
<item handle="96b68994dfa3d" parent="7031beac91f75" root="7031beac91f75" order="5" type="FILE" class="NOVEL" layout="NOTE">
<meta expanded="no" heading="H0" charCount="1909" wordCount="346" paraCount="7" cursorPos="0"/>
<name status="sf24ce6" import="ia857f0" active="no">A Note on Structure</name>
</item>
<item handle="88706ddc78b1b" parent="7031beac91f75" root="7031beac91f75" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="yes" heading="H0" charCount="139" wordCount="28" paraCount="1" cursorPos="188"/>
<name status="s90e6c9" import="ia857f0" active="yes">Chapter Two</name>
</item>
<item handle="ae7339df26ded" parent="88706ddc78b1b" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H0" charCount="189" wordCount="37" paraCount="1" cursorPos="0"/>
<name status="s90e6c9" import="ia857f0" active="yes">We Found John!</name>
</item>
<item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL">
<meta expanded="yes"/>
<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="no" heading="H0" charCount="27" wordCount="5" paraCount="1" cursorPos="100"/>
<name status="sc24b8f" import="ia857f0" active="yes">Title Page</name>
</item>
<item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H0" charCount="299" wordCount="55" paraCount="2" cursorPos="104"/>
<name status="s90e6c9" import="ia857f0" active="yes">Chapter One</name>
</item>
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="2" type="ROOT" class="CHARACTER">
<meta expanded="yes"/>
<name status="sf12341" import="ia857f0">Characters</name>
</item>
<item handle="f7e2d9f330615" parent="f6622b4617424" root="f6622b4617424" order="0" type="FOLDER" class="CHARACTER">
<meta expanded="yes"/>
<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="no" heading="H0" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="sf12341" import="icfb3a5" active="yes">John Smith</name>
</item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="no" heading="H0" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="sf12341" import="i2d7a54" active="yes">Jane Smith</name>
</item>
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD">
<meta expanded="yes"/>
<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="no" heading="H0" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="sf12341" import="i56be10" active="yes">Earth</name>
</item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="no" heading="H0" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="sf12341" import="icfb3a5" active="yes">Space</name>
</item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="no" heading="H0" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="sf12341" import="i2d7a54" active="yes">Mars</name>
</item>
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="4" type="ROOT" class="ARCHIVE">
<meta expanded="yes"/>
<name status="sf12341" import="ia857f0">Archive</name>
</item>
<item handle="ae9bf3c3ea159" parent="6827118336ac1" root="6827118336ac1" order="0" type="FOLDER" class="ARCHIVE">
<meta expanded="yes"/>
<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="no" heading="H0" charCount="232" wordCount="42" paraCount="1" cursorPos="239"/>
<name status="s90e6c9" import="ia857f0" active="yes">Old File</name>
</item>
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="5" type="ROOT" class="TRASH">
<meta expanded="yes"/>
<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="no" heading="H0" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="sf12341" import="ia857f0" active="yes">Delete Me!</name>
</item>
</content>
</novelWriterXML>
+76 -9
View File
@@ -30,11 +30,11 @@ from tools import writeFile
from novelwriter.guimain import GuiMain from novelwriter.guimain import GuiMain
from novelwriter.common import ( from novelwriter.common import (
checkStringNone, checkString, checkInt, checkFloat, checkBool, checkHandle, checkStringNone, checkString, checkInt, checkFloat, checkBool, checkHandle,
isHandle, isTitleTag, isItemClass, isItemType, isItemLayout, hexToInt, checkUuid, isHandle, isTitleTag, isItemClass, isItemType, isItemLayout,
minmax, checkIntTuple, formatInt, formatTimeStamp, formatTime, simplified, hexToInt, minmax, checkIntTuple, formatInt, formatTimeStamp, formatTime,
splitVersionNumber, transferCase, fuzzyTime, numberToRoman, jsonEncode, simplified, yesNo, splitVersionNumber, transferCase, fuzzyTime,
readTextFile, makeFileNameSafe, ensureFolder, sha256sum, getGuiItem, numberToRoman, jsonEncode, readTextFile, makeFileNameSafe, ensureFolder,
NWConfigParser sha256sum, getGuiItem, NWConfigParser
) )
@@ -105,16 +105,41 @@ def testBaseCommon_CheckBool():
bool, or integer 1 or 0, are returned as bool. Otherwise, the bool, or integer 1 or 0, are returned as bool. Otherwise, the
default is returned. default is returned.
""" """
# Bools
assert checkBool(True, False) is True
assert checkBool(False, True) is False
# Valid Strings
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("true", False) is True
assert checkBool("Boo", True) is True assert checkBool("false", True) is False
assert checkBool(None, True) is True assert checkBool("Yes", False) is True
assert checkBool(None, False) is False assert checkBool("No", True) is False
assert checkBool("yes", False) is True
assert checkBool("no", True) is False
assert checkBool("On", False) is True
assert checkBool("Off", True) is False
assert checkBool("on", False) is True
assert checkBool("off", True) is False
# Invalid Strings
assert checkBool("Foo", False) is False
assert checkBool("Foo", True) is True
assert checkBool("bar", False) is False
assert checkBool("bar", True) is True
# Valid Integers
assert checkBool(0, True) is False assert checkBool(0, True) is False
assert checkBool(1, False) is True assert checkBool(1, False) is True
# Inalid Integers
assert checkBool(2, True) is True assert checkBool(2, True) is True
assert checkBool(2, False) is False assert checkBool(2, False) is False
# Other Types
assert checkBool(None, True) is True
assert checkBool(None, False) is False
assert checkBool(0.0, True) is True assert checkBool(0.0, True) is True
assert checkBool(1.0, False) is False assert checkBool(1.0, False) is False
assert checkBool(2.0, True) is True assert checkBool(2.0, True) is True
@@ -137,6 +162,20 @@ def testBaseCommon_CheckHandle():
# END Test testBaseCommon_CheckHandle # END Test testBaseCommon_CheckHandle
@pytest.mark.base
def testBaseCommon_CheckUuid():
"""Test the checkUuid function.
"""
testUuid = "e2be99af-f9bf-4403-857a-c3d1ac25abea"
assert checkUuid("", None) is None
assert checkUuid("e2be99af-f9bf-4403-857a-c3d1ac25abe", None) is None
assert checkUuid("e2be99af-f9bf-qq03-857a-c3d1ac25abea", None) is None
assert checkUuid("e2be99af-f9bf-4403-857a-c3d1ac25abeaa", None) is None
assert checkUuid(testUuid, None) == testUuid
# END Test testBaseCommon_CheckUuid
@pytest.mark.base @pytest.mark.base
def testBaseCommon_IsHandle(): def testBaseCommon_IsHandle():
"""Test the isHandle function. """Test the isHandle function.
@@ -317,6 +356,34 @@ def testBaseCommon_Simplified():
# END Test testBaseCommon_Simplified # END Test testBaseCommon_Simplified
@pytest.mark.base
def testBaseCommon_YesNo():
"""Test the yesNo function.
"""
# Bool
assert yesNo(True) == "yes"
assert yesNo(False) == "no"
# None
assert yesNo(None) == "no"
# String
assert yesNo("foo") == "yes"
assert yesNo("") == "no"
# Integer
assert yesNo(0) == "no"
assert yesNo(1) == "yes"
assert yesNo(2) == "yes"
# Float
assert yesNo(0.0) == "no"
assert yesNo(1.0) == "yes"
assert yesNo(2.0) == "yes"
# END Test testBaseCommon_YesNo
@pytest.mark.base @pytest.mark.base
def testBaseCommon_SplitVersionNumber(): def testBaseCommon_SplitVersionNumber():
"""Test the splitVersionNumber function. """Test the splitVersionNumber function.
@@ -20,20 +20,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import os import os
import uuid
import pytest import pytest
from shutil import copyfile from shutil import copyfile
from zipfile import ZipFile
from mock import causeOSError from mock import causeOSError
from tools import C, buildTestProject, cmpFiles from tools import C, buildTestProject, cmpFiles, XML_IGNORE
from novelwriter.constants import nwItemClass
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.core.doctools import DocMerger, DocSplitter from novelwriter.core.coretools import DocMerger, DocSplitter, ProjectBuilder
from novelwriter.core.document import NWDoc
@pytest.mark.core @pytest.mark.core
def testCoreDocTools_DocMerger(monkeypatch, mockGUI, fncDir, outDir, refDir, mockRnd, ipsumText): def testCoreTools_DocMerger(monkeypatch, mockGUI, fncDir, outDir, refDir, mockRnd, ipsumText):
"""Test the DocMerger utility. """Test the DocMerger utility.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
@@ -118,11 +120,11 @@ def testCoreDocTools_DocMerger(monkeypatch, mockGUI, fncDir, outDir, refDir, moc
# Just for debugging # Just for debugging
docMerger.writeTargetDoc() docMerger.writeTargetDoc()
# END Test testCoreDocTools_DocMerger # END Test testCoreTools_DocMerger
@pytest.mark.core @pytest.mark.core
def testCoreDocTools_DocSplitter(monkeypatch, mockGUI, fncDir, outDir, refDir, mockRnd, ipsumText): def testCoreTools_DocSplitter(monkeypatch, mockGUI, fncDir, outDir, refDir, mockRnd, ipsumText):
"""Test the DocSplitter utility. """Test the DocSplitter utility.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
@@ -161,7 +163,7 @@ def testCoreDocTools_DocSplitter(monkeypatch, mockGUI, fncDir, outDir, refDir, m
docText = "\n\n".join(docData) docText = "\n\n".join(docData)
docRaw = docText.splitlines() docRaw = docText.splitlines()
assert NWDoc(theProject, hSplitDoc).writeDocument(docText) is True assert theProject.storage.getDocument(hSplitDoc).writeDocument(docText) is True
theProject.tree[hSplitDoc].setStatus(C.sFinished) theProject.tree[hSplitDoc].setStatus(C.sFinished)
theProject.tree[hSplitDoc].setImport(C.iMain) theProject.tree[hSplitDoc].setImport(C.iMain)
@@ -258,4 +260,160 @@ def testCoreDocTools_DocSplitter(monkeypatch, mockGUI, fncDir, outDir, refDir, m
theProject.saveProject() theProject.saveProject()
# END Test testCoreDocTools_DocSplitter # END Test testCoreTools_DocSplitter
@pytest.mark.core
def testCoreTools_NewMinimal(monkeypatch, fncDir, outDir, refDir, mockGUI, mockRnd):
"""Create a new project from a project wizard dictionary. With
default setting, creating a Minimal project.
"""
monkeypatch.setattr("uuid.uuid4", lambda *a: uuid.UUID("d0f3fe10-c6e6-4310-8bfd-181eb4224eed"))
projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreTools_NewMinimal_nwProject.nwx")
compFile = os.path.join(refDir, "coreTools_NewMinimal_nwProject.nwx")
projBuild = ProjectBuilder(mockGUI)
# Setting no data should fail
assert projBuild.buildProject({}) is False
# Wrong type should also fail
assert projBuild.buildProject("stuff") is False
# Try again with a proper path
assert projBuild.buildProject({"projPath": fncDir}) is True
# Creating the project once more should fail
assert projBuild.buildProject({"projPath": fncDir}) is False
# Save and close
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
# END Test testCoreTools_NewMinimal
@pytest.mark.core
def testCoreTools_NewCustomA(monkeypatch, fncDir, outDir, refDir, mockGUI, mockRnd):
"""Create a new project from a project wizard dictionary.
Custom type with chapters and scenes.
"""
monkeypatch.setattr("uuid.uuid4", lambda *a: uuid.UUID("d0f3fe10-c6e6-4310-8bfd-181eb4224eed"))
projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreTools_NewCustomA_nwProject.nwx")
compFile = os.path.join(refDir, "coreTools_NewCustomA_nwProject.nwx")
projData = {
"projName": "Test Custom",
"projTitle": "Test Novel",
"projAuthors": "Jane Doe\nJohn Doh\n",
"projPath": fncDir,
"popSample": False,
"popMinimal": False,
"popCustom": True,
"addRoots": [
nwItemClass.PLOT,
nwItemClass.CHARACTER,
nwItemClass.WORLD,
],
"addNotes": True,
"numChapters": 3,
"numScenes": 3,
}
projBuild = ProjectBuilder(mockGUI)
assert projBuild.buildProject(projData) is True
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
# END Test testCoreTools_NewCustomA
@pytest.mark.core
def testCoreTools_NewCustomB(monkeypatch, fncDir, outDir, refDir, mockGUI, mockRnd):
"""Create a new project from a project wizard dictionary.
Custom type without chapters, but with scenes.
"""
monkeypatch.setattr("uuid.uuid4", lambda *a: uuid.UUID("d0f3fe10-c6e6-4310-8bfd-181eb4224eed"))
projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreTools_NewCustomB_nwProject.nwx")
compFile = os.path.join(refDir, "coreTools_NewCustomB_nwProject.nwx")
projData = {
"projName": "Test Custom",
"projTitle": "Test Novel",
"projAuthors": "Jane Doe\nJohn Doh\n",
"projPath": fncDir,
"popSample": False,
"popMinimal": False,
"popCustom": True,
"addRoots": [
nwItemClass.PLOT,
nwItemClass.CHARACTER,
nwItemClass.WORLD,
],
"addNotes": True,
"numChapters": 0,
"numScenes": 6,
}
projBuild = ProjectBuilder(mockGUI)
assert projBuild.buildProject(projData) is True
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
# END Test testCoreTools_NewCustomB
@pytest.mark.core
def testCoreTools_NewSample(fncDir, tmpConf, mockGUI, tmpDir):
"""Check that we can create a new project can be created from the
provided sample project via a zip file.
"""
projData = {
"projName": "Test Sample",
"projTitle": "Test Novel",
"projAuthors": "Jane Doe\nJohn Doh\n",
"projPath": fncDir,
"popSample": True,
"popMinimal": False,
"popCustom": False,
}
projBuild = ProjectBuilder(mockGUI)
# No path set
assert projBuild.buildProject({"popSample": True}) is False
# Force the lookup path for assets to our temp folder
srcSample = os.path.abspath(os.path.join(tmpConf.appRoot, "sample"))
dstSample = os.path.join(tmpDir, "sample.zip")
tmpConf.assetPath = tmpDir
# Cannot extract when the zip does not exist
assert projBuild.buildProject(projData) is False
# Create and open a defective zip file
with open(dstSample, mode="w+") as outFile:
outFile.write("foo")
assert projBuild.buildProject(projData) is False
os.unlink(dstSample)
# Create a real zip file, and unpack it
with ZipFile(dstSample, "w") as zipObj:
zipObj.write(os.path.join(srcSample, "nwProject.nwx"), "nwProject.nwx")
for docFile in os.listdir(os.path.join(srcSample, "content")):
srcDoc = os.path.join(srcSample, "content", docFile)
zipObj.write(srcDoc, "content/"+docFile)
assert projBuild.buildProject(projData) is True
os.unlink(dstSample)
# END Test testCoreTools_NewSample
+48 -31
View File
@@ -1,6 +1,6 @@
""" """
novelWriter NWDoc Class Tester novelWriter NWDocument Class Tester
================================ =====================================
This file is a part of novelWriter This file is a part of novelWriter
Copyright 20182022, Veronica Berglyd Olsen Copyright 20182022, Veronica Berglyd Olsen
@@ -19,7 +19,6 @@ 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/>.
""" """
import os
import pytest import pytest
from mock import causeOSError from mock import causeOSError
@@ -27,62 +26,74 @@ 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
from novelwriter.core.document import NWDoc from novelwriter.core.document import NWDocument
@pytest.mark.core @pytest.mark.core
def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncDir, mockRnd): def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd):
"""Test loading and saving a document with the NWDoc class. """Test loading and saving a document with the NWDocument class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncDir) buildTestProject(theProject, fncPath)
# Read Document # Read Document
# ============= # =============
# Not a valid handle # Not a valid handle
theDoc = NWDoc(theProject, "stuff") theDoc = NWDocument(theProject, "stuff")
assert bool(theDoc) is False assert bool(theDoc) is False
assert theDoc.readDocument() is None assert theDoc.readDocument() is None
# Non-existent handle # Non-existent handle
theDoc = NWDoc(theProject, C.hInvalid) theDoc = NWDocument(theProject, C.hInvalid)
assert theDoc.readDocument() is None assert theDoc.readDocument() is None
assert theDoc._currHash is None assert theDoc._currHash is None
# No content path
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.storage.NWStorage.contentPath", property(lambda *a: None))
theDoc = NWDocument(theProject, C.hSceneDoc)
assert theDoc.readDocument() 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, C.hSceneDoc) theDoc = NWDocument(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, C.hSceneDoc) theDoc = NWDocument(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
xHandle = theProject.newFile("New File", C.hNovelRoot) xHandle = theProject.newFile("New File", C.hNovelRoot)
theDoc = NWDoc(theProject, xHandle) theDoc = NWDocument(theProject, xHandle)
assert bool(theDoc) is True assert bool(theDoc) is True
assert repr(theDoc) == f"<NWDoc handle={xHandle}>" assert repr(theDoc) == f"<NWDocument handle={xHandle}>"
assert theDoc.readDocument() == "" assert theDoc.readDocument() == ""
# Write Document # Write Document
# ============== # ==============
# Set handle and save again # No content path
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.storage.NWStorage.contentPath", property(lambda *a: None))
theDoc = NWDocument(theProject, xHandle)
assert theDoc.writeDocument("") is False
# Set handle and save
theText = "### Test File\n\nText ...\n\n" theText = "### Test File\n\nText ...\n\n"
theDoc = NWDoc(theProject, xHandle) theDoc = NWDocument(theProject, xHandle)
assert theDoc.readDocument(xHandle) == "" assert theDoc.readDocument(xHandle) == ""
assert theDoc.writeDocument(theText) is True assert theDoc.writeDocument(theText) is True
# Save again to ensure temp file and previous file is handled # Save again to ensure temp file and previous file is handled
assert theDoc.writeDocument(theText) assert theDoc.writeDocument(theText) is True
# Check file content # Check file content
docPath = os.path.join(fncDir, "content", xHandle+".nwd") docPath = fncPath / "content" / f"{xHandle}.nwd"
assert readFile(docPath) == ( assert readFile(docPath) == (
"%%~name: New File\n" "%%~name: New File\n"
f"%%~path: {C.hNovelRoot}/{xHandle}\n" f"%%~path: {C.hNovelRoot}/{xHandle}\n"
@@ -114,7 +125,7 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncDir, mockRnd):
# Cause os.replace() to fail while saving # Cause os.replace() to fail while saving
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("os.replace", causeOSError) mp.setattr("pathlib.Path.replace", causeOSError)
assert theDoc.writeDocument(theText) is False assert theDoc.writeDocument(theText) is False
assert theDoc.getError() == "OSError: Mock OSError" assert theDoc.getError() == "OSError: Mock OSError"
@@ -128,41 +139,47 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncDir, mockRnd):
# Delete Document # Delete Document
# =============== # ===============
# Delete the last document # Delete a non-existing document
theDoc = NWDoc(theProject, "stuff") theDoc = NWDocument(theProject, "stuff")
assert theDoc.deleteDocument() is False assert theDoc.deleteDocument() is False
assert os.path.isfile(docPath) assert docPath.exists()
# No content path
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.storage.NWStorage.contentPath", property(lambda *a: None))
theDoc = NWDocument(theProject, xHandle)
assert theDoc.deleteDocument() is False
# Cause the delete to fail # Cause the delete to fail
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("os.unlink", causeOSError) mp.setattr("pathlib.Path.unlink", causeOSError)
theDoc = NWDoc(theProject, xHandle) theDoc = NWDocument(theProject, xHandle)
assert theDoc.deleteDocument() is False assert theDoc.deleteDocument() is False
assert theDoc.getError() == "OSError: Mock OSError" assert theDoc.getError() == "OSError: Mock OSError"
# Make the delete pass # Make the delete pass
theDoc = NWDoc(theProject, xHandle) theDoc = NWDocument(theProject, xHandle)
assert theDoc.deleteDocument() is True assert theDoc.deleteDocument() is True
assert not os.path.isfile(docPath) assert not docPath.exists()
# END Test testCoreDocument_Load # END Test testCoreDocument_Load
@pytest.mark.core @pytest.mark.core
def testCoreDocument_Methods(mockGUI, fncDir, mockRnd): def testCoreDocument_Methods(mockGUI, fncPath, mockRnd):
"""Test other methods of the NWDoc class. """Test other methods of the NWDocument class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncDir) buildTestProject(theProject, fncPath)
theDoc = NWDoc(theProject, C.hSceneDoc) theDoc = NWDocument(theProject, C.hSceneDoc)
docPath = os.path.join(fncDir, "content", C.hSceneDoc+".nwd") docPath = fncPath / "content" / f"{C.hSceneDoc}.nwd"
assert theDoc.readDocument() == "### New Scene\n\n" assert theDoc.readDocument() == "### New Scene\n\n"
# Check location # Check location
assert theDoc.getFileLocation() == docPath assert theDoc.getFileLocation() == str(docPath)
# Check the item # Check the item
assert theDoc.getCurrentItem() is not None assert theDoc.getCurrentItem() is not None
+18 -6
View File
@@ -19,28 +19,29 @@ 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/>.
""" """
import os
import json import json
import pytest import pytest
from shutil import copyfile from shutil import copyfile
from pathlib import Path
from mock import causeException from mock import causeException
from tools import C, buildTestProject, cmpFiles, writeFile from tools import C, buildTestProject, cmpFiles, writeFile
from novelwriter.enum import nwItemClass, nwItemLayout from novelwriter.enum import nwItemClass, nwItemLayout
from novelwriter.constants import nwFiles
from novelwriter.core.index import NWIndex, countWords, TagsIndex from novelwriter.core.index import NWIndex, countWords, TagsIndex
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
@pytest.mark.core @pytest.mark.core
def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, outDir, refDir): def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, tstPaths):
"""Test core functionality of scaning, saving, loading and checking """Test core functionality of scaning, saving, loading and checking
the index cache file. the index cache file.
""" """
projFile = os.path.join(nwLipsum, "meta", "tagsIndex.json") projFile = Path(nwLipsum) / "meta" / nwFiles.INDEX_FILE
testFile = os.path.join(outDir, "coreIndex_LoadSave_tagsIndex.json") testFile = tstPaths.outDir / "coreIndex_LoadSave_tagsIndex.json"
compFile = os.path.join(refDir, "coreIndex_LoadSave_tagsIndex.json") compFile = tstPaths.refDir / "coreIndex_LoadSave_tagsIndex.json"
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
assert theProject.openProject(nwLipsum) assert theProject.openProject(nwLipsum)
@@ -61,6 +62,11 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, outDir, refDir):
assert theIndex.reIndexHandle(None) is False assert theIndex.reIndexHandle(None) is False
# No folder for saving
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.storage.NWStorage.getMetaFile", lambda *a: None)
assert theIndex.saveIndex() is False
# Make the save fail # Make the save fail
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeException) mp.setattr("builtins.open", causeException)
@@ -85,6 +91,11 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, outDir, refDir):
assert theIndex._tagsIndex._tags == {} assert theIndex._tagsIndex._tags == {}
assert theIndex._itemIndex._items == {} assert theIndex._itemIndex._items == {}
# No folder for sloading
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.storage.NWStorage.getMetaFile", lambda *a: None)
assert theIndex.loadIndex() is False
# Make the load fail # Make the load fail
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(json, "load", causeException) mp.setattr(json, "load", causeException)
@@ -752,7 +763,6 @@ def testCoreIndex_ExtractData(mockGUI, fncDir, mockRnd):
assert theIndex.saveIndex() is True assert theIndex.saveIndex() is True
assert theProject.saveProject() is True assert theProject.saveProject() is True
assert theProject.closeProject() is True
# Header Record # Header Record
bHandle = "0000000000000" bHandle = "0000000000000"
@@ -764,6 +774,8 @@ def testCoreIndex_ExtractData(mockGUI, fncDir, mockRnd):
("T000001", "H1", "Hello World!"), ("T000011", "H1", "Hello World!") ("T000001", "H1", "Hello World!"), ("T000011", "H1", "Hello World!")
] ]
assert theProject.closeProject() is True
# END Test testCoreIndex_ExtractData # END Test testCoreIndex_ExtractData
+73 -55
View File
@@ -509,23 +509,29 @@ def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
# File # File
theItem = NWItem(theProject) theItem = NWItem(theProject)
assert theItem.unpack({ assert theItem.unpack({
"label": "A File", "name": "A File",
"handle": "0000000000003", "itemAttr": {
"parent": "0000000000002", "handle": "0000000000003",
"root": "0000000000001", "parent": "0000000000002",
"order": 1, "root": "0000000000001",
"type": "FILE", "order": 1,
"class": "NOVEL", "type": "FILE",
"layout": "DOCUMENT", "class": "NOVEL",
"expanded": True, "layout": "DOCUMENT",
"status": None, },
"import": None, "metaAttr": {
"heading": "H1", "expanded": True,
"charCount": 100, "heading": "H1",
"wordCount": 20, "charCount": 100,
"paraCount": 2, "wordCount": 20,
"cursorPos": 50, "paraCount": 2,
"active": False, "cursorPos": 50,
},
"nameAttr": {
"status": None,
"import": None,
"active": False,
},
}) is True }) is True
assert theItem.itemName == "A File" assert theItem.itemName == "A File"
@@ -558,7 +564,7 @@ def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
"layout": "DOCUMENT", "layout": "DOCUMENT",
}, },
"metaAttr": { "metaAttr": {
"expanded": "True", "expanded": "yes",
"heading": "H1", "heading": "H1",
"charCount": "100", "charCount": "100",
"wordCount": "20", "wordCount": "20",
@@ -568,30 +574,36 @@ def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
"nameAttr": { "nameAttr": {
"status": "s000000", "status": "s000000",
"import": "i000001", "import": "i000001",
"active": "False", "active": "no",
} }
} }
# Folder # Folder
theItem = NWItem(theProject) theItem = NWItem(theProject)
assert theItem.unpack({ assert theItem.unpack({
"label": "A Folder", "name": "A Folder",
"handle": "0000000000003", "itemAttr": {
"parent": "0000000000002", "handle": "0000000000003",
"root": "0000000000001", "parent": "0000000000002",
"order": 1, "root": "0000000000001",
"type": "FOLDER", "order": 1,
"class": "NOVEL", "type": "FOLDER",
"layout": "DOCUMENT", "class": "NOVEL",
"expanded": True, "layout": "DOCUMENT",
"status": "", },
"import": "", "metaAttr": {
"heading": "H1", "expanded": True,
"charCount": 100, "heading": "H1",
"wordCount": 20, "charCount": 100,
"paraCount": 2, "wordCount": 20,
"cursorPos": 50, "paraCount": 2,
"active": True, "cursorPos": 50,
},
"nameAttr": {
"status": "",
"import": "",
"active": True,
}
}) is True }) is True
assert theItem.itemName == "A Folder" assert theItem.itemName == "A Folder"
@@ -623,7 +635,7 @@ def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
"class": "NOVEL", "class": "NOVEL",
}, },
"metaAttr": { "metaAttr": {
"expanded": "True", "expanded": "yes",
}, },
"nameAttr": { "nameAttr": {
"status": "s000000", "status": "s000000",
@@ -634,23 +646,29 @@ def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
# Root # Root
theItem = NWItem(theProject) theItem = NWItem(theProject)
assert theItem.unpack({ assert theItem.unpack({
"label": "A Novel", "name": "A Novel",
"handle": "0000000000003", "itemAttr": {
"parent": "0000000000002", "handle": "0000000000003",
"root": "0000000000001", "parent": "0000000000002",
"order": 1, "root": "0000000000001",
"type": "ROOT", "order": 1,
"class": "NOVEL", "type": "ROOT",
"layout": "DOCUMENT", "class": "NOVEL",
"expanded": True, "layout": "DOCUMENT",
"status": None, },
"import": None, "metaAttr": {
"heading": "H1", "expanded": True,
"charCount": 100, "heading": "H1",
"wordCount": 20, "charCount": 100,
"paraCount": 2, "wordCount": 20,
"cursorPos": 50, "paraCount": 2,
"active": True, "cursorPos": 50,
},
"nameAttr": {
"status": None,
"import": None,
"active": True,
},
}) is True }) is True
assert theItem.itemName == "A Novel" assert theItem.itemName == "A Novel"
@@ -682,7 +700,7 @@ def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
"class": "NOVEL", "class": "NOVEL",
}, },
"metaAttr": { "metaAttr": {
"expanded": "True", "expanded": "yes",
}, },
"nameAttr": { "nameAttr": {
"status": "s000000", "status": "s000000",
+30 -16
View File
@@ -19,28 +19,30 @@ 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/>.
""" """
import os
import json import json
import pytest import pytest
from mock import causeOSError from mock import causeOSError
from tools import writeFile
from novelwriter.constants import nwFiles
from novelwriter.core.options import OptionState from novelwriter.core.options import OptionState
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.constants import nwFiles from novelwriter.gui.noveltree import NovelTreeColumn
@pytest.mark.core @pytest.mark.core
def testCoreOptions_LoadSave(monkeypatch, mockGUI, tmpDir): def testCoreOptions_LoadSave(monkeypatch, mockGUI, fncPath):
"""Test loading and saving from the OptionState class. """Test loading and saving from the OptionState class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theOpts = OptionState(theProject) theOpts = OptionState(theProject)
metaDir = fncPath / "meta"
metaDir.mkdir()
# Write a test file # Write a test file
optFile = os.path.join(tmpDir, nwFiles.OPTS_FILE) optFile = metaDir / nwFiles.OPTS_FILE
writeFile(optFile, json.dumps({ optFile.write_text(json.dumps({
"GuiBuildNovel": { "GuiBuildNovel": {
"winWidth": 1000, "winWidth": 1000,
"winHeight": 700, "winHeight": 700,
@@ -52,22 +54,22 @@ def testCoreOptions_LoadSave(monkeypatch, mockGUI, tmpDir):
"MockGroup": { "MockGroup": {
"mockItem": None, "mockItem": None,
}, },
})) }), encoding="utf-8")
# Load and save with no path set # Load and save with no path set
theProject.projMeta = None theProject.storage._runtimePath = None
assert not theOpts.loadSettings() assert theOpts.loadSettings() is False
assert not theOpts.saveSettings() assert theOpts.saveSettings() is False
# Set path # Set path
theProject.projMeta = tmpDir theProject.storage._runtimePath = fncPath
assert theProject.projMeta == tmpDir assert theProject.storage.getMetaFile(nwFiles.OPTS_FILE) == optFile
# Cause open() to fail # Cause open() to fail
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError) mp.setattr("builtins.open", causeOSError)
assert not theOpts.loadSettings() assert theOpts.loadSettings() is False
assert not theOpts.saveSettings() assert theOpts.saveSettings() is False
# Load proper # Load proper
assert theOpts.loadSettings() assert theOpts.loadSettings()
@@ -108,9 +110,11 @@ def testCoreOptions_SetGet(mockGUI):
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theOpts = OptionState(theProject) theOpts = OptionState(theProject)
nwColHidden = NovelTreeColumn.HIDDEN
# Set invalid values # Set invalid values
assert not theOpts.setValue("MockGroup", "mockItem", None) assert theOpts.setValue("MockGroup", "mockItem", None) is False
assert not theOpts.setValue("GuiBuildNovel", "mockItem", None) assert theOpts.setValue("GuiBuildNovel", "mockItem", None) is False
# Set valid value # Set valid value
assert theOpts.setValue("GuiBuildNovel", "winWidth", 100) assert theOpts.setValue("GuiBuildNovel", "winWidth", 100)
@@ -120,6 +124,7 @@ def testCoreOptions_SetGet(mockGUI):
assert theOpts.setValue("GuiBuildNovel", "winHeight", 12.34) assert theOpts.setValue("GuiBuildNovel", "winHeight", 12.34)
assert theOpts.setValue("GuiBuildNovel", "addNovel", True) assert theOpts.setValue("GuiBuildNovel", "addNovel", True)
assert theOpts.setValue("GuiBuildNovel", "textFont", "Cantarell") assert theOpts.setValue("GuiBuildNovel", "textFont", "Cantarell")
assert theOpts.setValue("GuiNovelView", "lastCol", nwColHidden)
# Generic get, doesn't check type # Generic get, doesn't check type
assert theOpts.getValue("GuiBuildNovel", "winWidth", None) == 100 assert theOpts.getValue("GuiBuildNovel", "winWidth", None) == 100
@@ -139,5 +144,14 @@ def testCoreOptions_SetGet(mockGUI):
assert theOpts.getFloat("GuiBuildNovel", "mockItem", None) is None assert theOpts.getFloat("GuiBuildNovel", "mockItem", None) is None
assert theOpts.getBool("GuiBuildNovel", "addNovel", None) is True assert theOpts.getBool("GuiBuildNovel", "addNovel", None) is True
assert theOpts.getBool("GuiBuildNovel", "mockItem", None) is None assert theOpts.getBool("GuiBuildNovel", "mockItem", None) is None
assert theOpts.getEnum("GuiNovelView", "lastCol", NovelTreeColumn, None) == nwColHidden
# Get from non-existent groups
assert theOpts.getValue("SomeGroup", "mockItem", None) is None
assert theOpts.getString("SomeGroup", "mockItem", None) is None
assert theOpts.getInt("SomeGroup", "mockItem", None) is None
assert theOpts.getFloat("SomeGroup", "mockItem", None) is None
assert theOpts.getBool("SomeGroup", "mockItem", None) is None
assert theOpts.getEnum("SomeGroup", "mockItem", NovelTreeColumn, None) is None
# END Test testCoreOptions_SetGet # END Test testCoreOptions_SetGet
File diff suppressed because it is too large Load Diff
+222 -62
View File
@@ -20,18 +20,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import json import json
import os
import pytest import pytest
import shutil
from shutil import copyfile
from datetime import datetime from datetime import datetime
from mock import causeOSError from mock import causeOSError
from tools import cmpFiles, writeFile from tools import cmpFiles, writeFile
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProjectData
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
from novelwriter.core.projectdata import NWProjectData
class MockProject: class MockProject:
@@ -40,13 +39,14 @@ class MockProject:
@pytest.mark.core @pytest.mark.core
def testCoreProjectXML_ReadCurrent(monkeypatch, filesDir, fncDir, outDir, refDir): def testCoreProjectXML_ReadCurrent(monkeypatch, tstPaths, fncPath):
"""Test reading the current XML file format. """Test reading the current XML file format.
""" """
refFile = os.path.join(filesDir, "nwProject-1.4.nwx") refFile = tstPaths.filesDir / "nwProject-1.5.nwx"
xmlFile = os.path.join(fncDir, "nwProject-1.4.nwx") tstFile = tstPaths.outDir / "ProjectXML_ReadCurrent.nwx"
bakFile = os.path.join(fncDir, "nwProject-1.4.bak") xmlFile = fncPath / "nwProject-1.5.nwx"
outFile = os.path.join(fncDir, "nwProject.nwx") bakFile = fncPath / "nwProject-1.5.bak"
outFile = fncPath / "nwProject.nwx"
xmlReader = ProjectXMLReader(xmlFile) xmlReader = ProjectXMLReader(xmlFile)
assert xmlReader.state == XMLReadState.NO_ACTION assert xmlReader.state == XMLReadState.NO_ACTION
@@ -81,7 +81,7 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, filesDir, fncDir, outDir, refDir
# Check parsing of unkown sections # Check parsing of unkown sections
writeFile(xmlFile, ( writeFile(xmlFile, (
"<novelWriterXML fileVersion='1.4'>" "<novelWriterXML fileVersion='1.5'>"
" <project>" " <project>"
" <stuff></stuff>" " <stuff></stuff>"
" </project>" " </project>"
@@ -125,13 +125,13 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, filesDir, fncDir, outDir, refDir
content = [] content = []
# Parse a valid, complete file # Parse a valid, complete file
shutil.copy(refFile, xmlFile) copyfile(refFile, xmlFile)
assert xmlReader.read(data, content) is True assert xmlReader.read(data, content) is True
assert xmlReader.state == XMLReadState.PARSED_OK assert xmlReader.state == XMLReadState.PARSED_OK
assert xmlReader.xmlRoot == "novelWriterXML" assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0104 assert xmlReader.xmlVersion == 0x0105
assert xmlReader.appVersion == "2.0-rc1" assert xmlReader.appVersion == "2.0-rc1"
assert xmlReader.hexVersion == "0x020000c1" assert xmlReader.hexVersion == 0x020000c1
# Check loaded data # Check loaded data
assert data.name == "Sample Project" assert data.name == "Sample Project"
@@ -199,8 +199,8 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, filesDir, fncDir, outDir, refDir
assert data.itemImport.count("i56be10") == 1 assert data.itemImport.count("i56be10") == 1
# Compare content # Compare content
dumpFile = os.path.join(outDir, "projectXML_ReadCurrent.json") dumpFile = tstPaths.outDir / "projectXML_ReadCurrent.json"
compFile = os.path.join(refDir, "projectXML_ReadCurrent.json") compFile = tstPaths.refDir / "projectXML_ReadCurrent.json"
with open(dumpFile, mode="w", encoding="utf-8") as dump: with open(dumpFile, mode="w", encoding="utf-8") as dump:
json.dump(content, dump, indent=2) json.dump(content, dump, indent=2)
assert cmpFiles(dumpFile, compFile) assert cmpFiles(dumpFile, compFile)
@@ -215,35 +215,36 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, filesDir, fncDir, outDir, refDir
# Save the project again, which should produce an identical project xml # Save the project again, which should produce an identical project xml
timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp()) timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp())
xmlWriter = ProjectXMLWriter(fncDir) xmlWriter = ProjectXMLWriter(fncPath)
# Fail saving # Fail saving
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError) mp.setattr("pathlib.Path.write_bytes", causeOSError)
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is False assert xmlWriter.write(data, packedContent, timeStamp, 1000) is False
assert str(xmlWriter.error) == "Mock OSError" assert str(xmlWriter.error) == "Mock OSError"
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("os.replace", causeOSError) mp.setattr("pathlib.Path.replace", causeOSError)
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is False assert xmlWriter.write(data, packedContent, timeStamp, 1000) is False
assert str(xmlWriter.error) == "Mock OSError" assert str(xmlWriter.error) == "Mock OSError"
# Successful save (should be twice) # Successful save (should be twice)
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
assert cmpFiles(outFile, xmlFile) copyfile(outFile, tstFile)
assert cmpFiles(tstFile, refFile)
# END Test testCoreProjectXML_ReadCurrent # END Test testCoreProjectXML_ReadCurrent
@pytest.mark.core @pytest.mark.core
def testCoreProjectXML_ReadLegacy10(filesDir, fncDir, outDir, refDir, mockRnd): def testCoreProjectXML_ReadLegacy10(tstPaths, fncPath, mockRnd):
"""Test reading the version 1.0 XML file format. """Test reading the version 1.0 XML file format.
""" """
refFile = os.path.join(filesDir, "nwProject-1.0.nwx") refFile = tstPaths.filesDir / "nwProject-1.0.nwx"
xmlFile = os.path.join(fncDir, "nwProject-1.0.nwx") xmlFile = fncPath / "nwProject-1.0.nwx"
outFile = os.path.join(fncDir, "nwProject.nwx") outFile = fncPath / "nwProject.nwx"
shutil.copy(refFile, xmlFile) copyfile(refFile, xmlFile)
xmlReader = ProjectXMLReader(xmlFile) xmlReader = ProjectXMLReader(xmlFile)
assert xmlReader.state == XMLReadState.NO_ACTION assert xmlReader.state == XMLReadState.NO_ACTION
@@ -256,7 +257,7 @@ def testCoreProjectXML_ReadLegacy10(filesDir, fncDir, outDir, refDir, mockRnd):
assert xmlReader.xmlRoot == "novelWriterXML" assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0100 assert xmlReader.xmlVersion == 0x0100
assert xmlReader.appVersion == "0.6.1" assert xmlReader.appVersion == "0.6.1"
assert xmlReader.hexVersion == "0x000601f0" assert xmlReader.hexVersion == 0x000601f0
# Check loaded data # Check loaded data
assert data.name == "Sample Project" assert data.name == "Sample Project"
@@ -324,8 +325,8 @@ def testCoreProjectXML_ReadLegacy10(filesDir, fncDir, outDir, refDir, mockRnd):
assert data.itemImport.count("i00000a") == 0 assert data.itemImport.count("i00000a") == 0
# Compare content # Compare content
dumpFile = os.path.join(outDir, "projectXML_ReadLegacy10.json") dumpFile = tstPaths.outDir / "projectXML_ReadLegacy10.json"
compFile = os.path.join(refDir, "projectXML_ReadLegacy10.json") compFile = tstPaths.refDir / "projectXML_ReadLegacy10.json"
with open(dumpFile, mode="w", encoding="utf-8") as dump: with open(dumpFile, mode="w", encoding="utf-8") as dump:
json.dump(content, dump, indent=2) json.dump(content, dump, indent=2)
assert cmpFiles(dumpFile, compFile) assert cmpFiles(dumpFile, compFile)
@@ -367,22 +368,25 @@ def testCoreProjectXML_ReadLegacy10(filesDir, fncDir, outDir, refDir, mockRnd):
# Save the project again, which should produce an identical project xml # Save the project again, which should produce an identical project xml
timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp()) timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp())
xmlWriter = ProjectXMLWriter(fncDir) xmlWriter = ProjectXMLWriter(fncPath)
data.setUuid("d0f3fe10-c6e6-4310-8bfd-181eb4224eed")
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
compFile = os.path.join(refDir, "projectXML_ReadLegacy10.nwx") testFile = tstPaths.outDir / "projectXML_ReadLegacy10.nwx"
assert cmpFiles(outFile, compFile) compFile = tstPaths.refDir / "projectXML_ReadLegacy10.nwx"
copyfile(outFile, testFile)
assert cmpFiles(testFile, compFile)
# END Test testCoreProjectXML_ReadLegacy10 # END Test testCoreProjectXML_ReadLegacy10
@pytest.mark.core @pytest.mark.core
def testCoreProjectXML_ReadLegacy11(filesDir, fncDir, outDir, refDir, mockRnd): def testCoreProjectXML_ReadLegacy11(tstPaths, fncPath, mockRnd):
"""Test reading the version 1.1 XML file format. """Test reading the version 1.1 XML file format.
""" """
refFile = os.path.join(filesDir, "nwProject-1.1.nwx") refFile = tstPaths.filesDir / "nwProject-1.1.nwx"
xmlFile = os.path.join(fncDir, "nwProject-1.1.nwx") xmlFile = fncPath / "nwProject-1.1.nwx"
outFile = os.path.join(fncDir, "nwProject.nwx") outFile = fncPath / "nwProject.nwx"
shutil.copy(refFile, xmlFile) copyfile(refFile, xmlFile)
xmlReader = ProjectXMLReader(xmlFile) xmlReader = ProjectXMLReader(xmlFile)
assert xmlReader.state == XMLReadState.NO_ACTION assert xmlReader.state == XMLReadState.NO_ACTION
@@ -395,7 +399,7 @@ def testCoreProjectXML_ReadLegacy11(filesDir, fncDir, outDir, refDir, mockRnd):
assert xmlReader.xmlRoot == "novelWriterXML" assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0101 assert xmlReader.xmlVersion == 0x0101
assert xmlReader.appVersion == "0.9.2" assert xmlReader.appVersion == "0.9.2"
assert xmlReader.hexVersion == "0x000902f0" assert xmlReader.hexVersion == 0x000902f0
# Check loaded data # Check loaded data
assert data.name == "Sample Project" assert data.name == "Sample Project"
@@ -463,8 +467,8 @@ def testCoreProjectXML_ReadLegacy11(filesDir, fncDir, outDir, refDir, mockRnd):
assert data.itemImport.count("i00000a") == 0 assert data.itemImport.count("i00000a") == 0
# Compare content # Compare content
dumpFile = os.path.join(outDir, "projectXML_ReadLegacy11.json") dumpFile = tstPaths.outDir / "projectXML_ReadLegacy11.json"
compFile = os.path.join(refDir, "projectXML_ReadLegacy11.json") compFile = tstPaths.refDir / "projectXML_ReadLegacy11.json"
with open(dumpFile, mode="w", encoding="utf-8") as dump: with open(dumpFile, mode="w", encoding="utf-8") as dump:
json.dump(content, dump, indent=2) json.dump(content, dump, indent=2)
assert cmpFiles(dumpFile, compFile) assert cmpFiles(dumpFile, compFile)
@@ -506,22 +510,25 @@ def testCoreProjectXML_ReadLegacy11(filesDir, fncDir, outDir, refDir, mockRnd):
# Save the project again, which should produce an identical project xml # Save the project again, which should produce an identical project xml
timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp()) timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp())
xmlWriter = ProjectXMLWriter(fncDir) xmlWriter = ProjectXMLWriter(fncPath)
data.setUuid("d0f3fe10-c6e6-4310-8bfd-181eb4224eed")
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
compFile = os.path.join(refDir, "projectXML_ReadLegacy11.nwx") testFile = tstPaths.outDir / "projectXML_ReadLegacy11.nwx"
assert cmpFiles(outFile, compFile) compFile = tstPaths.refDir / "projectXML_ReadLegacy11.nwx"
copyfile(outFile, testFile)
assert cmpFiles(testFile, compFile)
# END Test testCoreProjectXML_ReadLegacy11 # END Test testCoreProjectXML_ReadLegacy11
@pytest.mark.core @pytest.mark.core
def testCoreProjectXML_ReadLegacy12(filesDir, fncDir, outDir, refDir, mockRnd): def testCoreProjectXML_ReadLegacy12(tstPaths, fncPath, mockRnd):
"""Test reading the version 1.2 XML file format. """Test reading the version 1.2 XML file format.
""" """
refFile = os.path.join(filesDir, "nwProject-1.2.nwx") refFile = tstPaths.filesDir / "nwProject-1.2.nwx"
xmlFile = os.path.join(fncDir, "nwProject-1.2.nwx") xmlFile = fncPath / "nwProject-1.2.nwx"
outFile = os.path.join(fncDir, "nwProject.nwx") outFile = fncPath / "nwProject.nwx"
shutil.copy(refFile, xmlFile) copyfile(refFile, xmlFile)
xmlReader = ProjectXMLReader(xmlFile) xmlReader = ProjectXMLReader(xmlFile)
assert xmlReader.state == XMLReadState.NO_ACTION assert xmlReader.state == XMLReadState.NO_ACTION
@@ -534,7 +541,7 @@ def testCoreProjectXML_ReadLegacy12(filesDir, fncDir, outDir, refDir, mockRnd):
assert xmlReader.xmlRoot == "novelWriterXML" assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0102 assert xmlReader.xmlVersion == 0x0102
assert xmlReader.appVersion == "1.4.2" assert xmlReader.appVersion == "1.4.2"
assert xmlReader.hexVersion == "0x010402f0" assert xmlReader.hexVersion == 0x010402f0
# Check loaded data # Check loaded data
assert data.name == "Sample Project" assert data.name == "Sample Project"
@@ -602,8 +609,8 @@ def testCoreProjectXML_ReadLegacy12(filesDir, fncDir, outDir, refDir, mockRnd):
assert data.itemImport.count("i00000a") == 0 assert data.itemImport.count("i00000a") == 0
# Compare content # Compare content
dumpFile = os.path.join(outDir, "projectXML_ReadLegacy12.json") dumpFile = tstPaths.outDir / "projectXML_ReadLegacy12.json"
compFile = os.path.join(refDir, "projectXML_ReadLegacy12.json") compFile = tstPaths.refDir / "projectXML_ReadLegacy12.json"
with open(dumpFile, mode="w", encoding="utf-8") as dump: with open(dumpFile, mode="w", encoding="utf-8") as dump:
json.dump(content, dump, indent=2) json.dump(content, dump, indent=2)
assert cmpFiles(dumpFile, compFile) assert cmpFiles(dumpFile, compFile)
@@ -648,22 +655,25 @@ def testCoreProjectXML_ReadLegacy12(filesDir, fncDir, outDir, refDir, mockRnd):
# Save the project again, which should produce an identical project xml # Save the project again, which should produce an identical project xml
timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp()) timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp())
xmlWriter = ProjectXMLWriter(fncDir) xmlWriter = ProjectXMLWriter(fncPath)
data.setUuid("d0f3fe10-c6e6-4310-8bfd-181eb4224eed")
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
compFile = os.path.join(refDir, "projectXML_ReadLegacy12.nwx") testFile = tstPaths.outDir / "projectXML_ReadLegacy12.nwx"
assert cmpFiles(outFile, compFile) compFile = tstPaths.refDir / "projectXML_ReadLegacy12.nwx"
copyfile(outFile, testFile)
assert cmpFiles(testFile, compFile)
# END Test testCoreProjectXML_ReadLegacy12 # END Test testCoreProjectXML_ReadLegacy12
@pytest.mark.core @pytest.mark.core
def testCoreProjectXML_ReadLegacy13(filesDir, fncDir, outDir, refDir, mockRnd): def testCoreProjectXML_ReadLegacy13(tstPaths, fncPath, mockRnd):
"""Test reading the version 1.3 XML file format. """Test reading the version 1.3 XML file format.
""" """
refFile = os.path.join(filesDir, "nwProject-1.3.nwx") refFile = tstPaths.filesDir / "nwProject-1.3.nwx"
xmlFile = os.path.join(fncDir, "nwProject-1.3.nwx") xmlFile = fncPath / "nwProject-1.3.nwx"
outFile = os.path.join(fncDir, "nwProject.nwx") outFile = fncPath / "nwProject.nwx"
shutil.copy(refFile, xmlFile) copyfile(refFile, xmlFile)
xmlReader = ProjectXMLReader(xmlFile) xmlReader = ProjectXMLReader(xmlFile)
assert xmlReader.state == XMLReadState.NO_ACTION assert xmlReader.state == XMLReadState.NO_ACTION
@@ -676,7 +686,7 @@ def testCoreProjectXML_ReadLegacy13(filesDir, fncDir, outDir, refDir, mockRnd):
assert xmlReader.xmlRoot == "novelWriterXML" assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0103 assert xmlReader.xmlVersion == 0x0103
assert xmlReader.appVersion == "1.6.6" assert xmlReader.appVersion == "1.6.6"
assert xmlReader.hexVersion == "0x010606f0" assert xmlReader.hexVersion == 0x010606f0
# Check loaded data # Check loaded data
assert data.name == "Sample Project" assert data.name == "Sample Project"
@@ -744,8 +754,8 @@ def testCoreProjectXML_ReadLegacy13(filesDir, fncDir, outDir, refDir, mockRnd):
assert data.itemImport.count("i00000a") == 0 assert data.itemImport.count("i00000a") == 0
# Compare content # Compare content
dumpFile = os.path.join(outDir, "projectXML_ReadLegacy13.json") dumpFile = tstPaths.outDir / "projectXML_ReadLegacy13.json"
compFile = os.path.join(refDir, "projectXML_ReadLegacy13.json") compFile = tstPaths.refDir / "projectXML_ReadLegacy13.json"
with open(dumpFile, mode="w", encoding="utf-8") as dump: with open(dumpFile, mode="w", encoding="utf-8") as dump:
json.dump(content, dump, indent=2) json.dump(content, dump, indent=2)
assert cmpFiles(dumpFile, compFile) assert cmpFiles(dumpFile, compFile)
@@ -790,9 +800,159 @@ def testCoreProjectXML_ReadLegacy13(filesDir, fncDir, outDir, refDir, mockRnd):
# Save the project again, which should produce an identical project xml # Save the project again, which should produce an identical project xml
timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp()) timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp())
xmlWriter = ProjectXMLWriter(fncDir) xmlWriter = ProjectXMLWriter(fncPath)
data.setUuid("d0f3fe10-c6e6-4310-8bfd-181eb4224eed")
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
compFile = os.path.join(refDir, "projectXML_ReadLegacy13.nwx") testFile = tstPaths.outDir / "projectXML_ReadLegacy13.nwx"
assert cmpFiles(outFile, compFile) compFile = tstPaths.refDir / "projectXML_ReadLegacy13.nwx"
copyfile(outFile, testFile)
assert cmpFiles(testFile, compFile)
# END Test testCoreProjectXML_ReadLegacy13 # END Test testCoreProjectXML_ReadLegacy13
@pytest.mark.core
def testCoreProjectXML_ReadLegacy14(tstPaths, fncPath, mockRnd):
"""Test reading the version 1.4 XML file format.
"""
refFile = tstPaths.filesDir / "nwProject-1.4.nwx"
xmlFile = fncPath / "nwProject-1.4.nwx"
outFile = fncPath / "nwProject.nwx"
copyfile(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 == 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") 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("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 = tstPaths.outDir / "projectXML_ReadLegacy14.json"
compFile = tstPaths.refDir / "projectXML_ReadLegacy14.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": "1st Draft",
"6a2d6d5f4f401": "Notes",
"636b6aa9b697b": "1st Draft",
"bc0cbd2a407f3": "1st Draft",
"ba8a28a246524": "Finished",
"96b68994dfa3d": "Notes",
"88706ddc78b1b": "1st Draft",
"ae7339df26ded": "1st Draft",
"e5e47ebf63b1c": "New",
"bacb7059e3083": "Started",
"a520879ca0b45": "1st Draft",
"f6622b4617424": "None",
"f7e2d9f330615": "None",
"14298de4d9524": "Minor",
"bb2c23b3c42cc": "Major",
"15c4492bd5107": "None",
"b3e74dbc1f584": "Main",
"f1471bef9f2ae": "Minor",
"5eaea4e8cdee8": "Major",
"6827118336ac1": "New",
"ae9bf3c3ea159": "New",
"8a5deb88c0e97": "1st Draft",
"98acd8c76c93a": "None",
"b8136a5a774a0": "None",
}
# Save the project again, which should produce an identical project xml
timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp())
xmlWriter = ProjectXMLWriter(fncPath)
data.setUuid("d0f3fe10-c6e6-4310-8bfd-181eb4224eed")
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
testFile = tstPaths.outDir / "projectXML_ReadLegacy14.nwx"
compFile = tstPaths.refDir / "projectXML_ReadLegacy14.nwx"
copyfile(outFile, testFile)
assert cmpFiles(testFile, compFile)
# END Test testCoreProjectXML_ReadLegacy14
+5 -6
View File
@@ -19,7 +19,6 @@ 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/>.
""" """
import os
import sys import sys
import pytest import pytest
@@ -63,10 +62,10 @@ def testCoreSpell_FakeEnchant(monkeypatch):
@pytest.mark.core @pytest.mark.core
def testCoreSpell_Enchant(monkeypatch, fncDir): def testCoreSpell_Enchant(monkeypatch, fncPath):
"""Test the pyenchant spell checker. """Test the pyenchant spell checker.
""" """
wList = os.path.join(fncDir, "wordlist.txt") wList = fncPath / "wordlist.txt"
writeFile(wList, "a_word\nb_word\nc_word\n") writeFile(wList, "a_word\nb_word\nc_word\n")
# Break the enchant package, and check error handling # Break the enchant package, and check error handling
@@ -134,13 +133,13 @@ def testCoreSpell_Enchant(monkeypatch, fncDir):
@pytest.mark.core @pytest.mark.core
def testCoreSpell_SessionWords(fncDir): def testCoreSpell_SessionWords(fncPath):
"""Test the handling of the custom word list in the spell checker. """Test the handling of the custom word list in the spell checker.
New project sessions should not inherit the project word list from New project sessions should not inherit the project word list from
other sessions, so this test checks that they don't bleed through. other sessions, so this test checks that they don't bleed through.
""" """
wList1 = os.path.join(fncDir, "wordlist1.txt") wList1 = fncPath / "wordlist1.txt"
wList2 = os.path.join(fncDir, "wordlist2.txt") wList2 = fncPath / "wordlist2.txt"
writeFile(wList1, "a_word\nb_word\nc_word\n") writeFile(wList1, "a_word\nb_word\nc_word\n")
writeFile(wList2, "d_word\ne_word\nf_word\n") writeFile(wList2, "d_word\ne_word\nf_word\n")
+332
View File
@@ -0,0 +1,332 @@
"""
novelWriter NWStorage 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/>.
"""
from zipfile import ZipFile
import pytest
from mock import causeOSError
from tools import C, buildTestProject, writeFile
from novelwriter.constants import nwFiles
from novelwriter.core.project import NWProject
from novelwriter.core.storage import NWStorage
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter
class MockProject:
pass
@pytest.mark.core
def testCoreStorage_OpenProjectInPlace(mockGUI, fncPath, mockRnd):
"""Test opening a project in a folder.
"""
theProject = NWProject(mockGUI)
mockRnd.reset()
buildTestProject(theProject, fncPath)
theProject.closeProject()
# Create instance
storage = NWStorage(theProject)
# Check defaults
assert storage.storagePath is None
assert storage.runtimePath is None
assert storage.contentPath is None
assert storage._openMode == NWStorage.MODE_INACTIVE
# Check closed project return values
assert storage.isOpen() is False
assert storage.getXmlReader() is None
assert storage.getXmlWriter() is None
assert bool(storage.getDocument(C.hSceneDoc)) is False
assert storage.getMetaFile("file") is None
assert storage.getCacheFile("file") is None
# Open project as a new project should fail
assert storage.openProjectInPlace(fncPath, newProject=True) is False
# Opening as a no-new project is fine
assert storage.openProjectInPlace(fncPath, newProject=False) is True
# Opening the project file is also fine
assert storage.openProjectInPlace(fncPath / nwFiles.PROJ_FILE, newProject=False) is True
# Check settings
assert storage.storagePath == fncPath
assert storage.runtimePath == fncPath
assert storage.contentPath == fncPath / "content"
assert storage._openMode == NWStorage.MODE_INPLACE
# Open the project itself
theProject.openProject(fncPath)
storage = theProject.storage
# Get XML components
assert isinstance(storage.getXmlReader(), ProjectXMLReader)
assert isinstance(storage.getXmlWriter(), ProjectXMLWriter)
# Get document
assert storage.getDocument(C.hSceneDoc).readDocument() == "### New Scene\n\n"
# Get paths
assert storage.getMetaFile("stuff") == fncPath / "meta" / "stuff"
assert storage.getCacheFile("stuff") == fncPath / "cache" / "stuff"
# Clean up
assert theProject.closeProject() is True
# Check closed project return values (again)
assert storage.isOpen() is False
assert storage.getXmlReader() is None
assert storage.getXmlWriter() is None
assert bool(storage.getDocument(C.hSceneDoc)) is False
assert storage.getMetaFile("file") is None
assert storage.getCacheFile("file") is None
# END Test testCoreStorage_ProjectInPlace
@pytest.mark.core
def testCoreStorage_LockFile(monkeypatch, fncPath):
"""Test the project lock file.
"""
monkeypatch.setattr("novelwriter.core.storage.time", lambda: 1000.0)
storage = NWStorage(MockProject())
assert storage.isOpen() is False
# Project not open, so cannot read/write lock file
assert storage.readLockFile() == ["ERROR"]
assert storage.writeLockFile() is False
assert storage.clearLockFile() is False
# Set a path to work with
lockFilePath = fncPath / nwFiles.PROJ_LOCK
storage._lockFilePath = lockFilePath
# Path is set, but there is no lockfile
assert storage.readLockFile() == []
# Write lockfile fails
with monkeypatch.context() as mp:
mp.setattr("pathlib.Path.write_text", causeOSError)
assert storage.writeLockFile() is False
assert not lockFilePath.exists()
# Successful write
assert storage.writeLockFile() is True
assert lockFilePath.exists()
assert lockFilePath.read_text().split(";")[3] == "1000"
# Read lockfile fails
with monkeypatch.context() as mp:
mp.setattr("pathlib.Path.read_text", causeOSError)
assert storage.readLockFile() == ["ERROR"]
assert lockFilePath.exists()
# Successful read
assert storage.readLockFile() == [
storage.mainConf.hostName,
storage.mainConf.osType,
storage.mainConf.kernelVer,
"1000",
]
# Write an invalid lockfile
writeFile(lockFilePath, "a;b;c")
assert storage.readLockFile() == ["ERROR"]
# Fail to remove lockfile
with monkeypatch.context() as mp:
mp.setattr("pathlib.Path.unlink", causeOSError)
assert storage.clearLockFile() is False
assert lockFilePath.exists()
# Successful remove
assert storage.clearLockFile() is True
assert not lockFilePath.exists()
# END Test testCoreStorage_LockFile
@pytest.mark.core
def testCoreStorage_PrepareStorage(monkeypatch, fncPath):
"""Test the project path preparation functions.
"""
storage = NWStorage(MockProject())
assert storage.isOpen() is False
# No path set
assert storage._prepareStorage() is False
# Set path to home
storage._runtimePath = fncPath
with monkeypatch.context() as mp:
mp.setattr("pathlib.Path.home", lambda: fncPath)
assert storage._prepareStorage() is False
# Fail on mkdir
storage._runtimePath = fncPath
with monkeypatch.context() as mp:
mp.setattr("pathlib.Path.mkdir", causeOSError)
assert storage._prepareStorage() is False
# Set up the folder
storage._runtimePath = fncPath
assert storage._prepareStorage(checkLegacy=False) is True
assert (fncPath / "content").exists()
assert (fncPath / "cache").exists()
assert (fncPath / "meta").exists()
# Add a legacy folder
storage._runtimePath = fncPath
dataDir = fncPath / "data_0"
dataDir.mkdir()
assert storage._prepareStorage(checkLegacy=True) is True
assert not dataDir.exists()
# We cannot add a new project here
storage._runtimePath = fncPath
assert storage._prepareStorage(checkLegacy=False, newProject=True) is False
# Legacy Data Folder
# ==================
storage._runtimePath = fncPath
data = []
files = []
for c in "0123456789abcdefX":
dataDir = fncPath / f"data_{c}"
dataDir.mkdir()
data.append(dataDir)
nwdFile = dataDir / f"00000000000{c}_main.nwd"
bakFile = dataDir / f"00000000000{c}_main.bak"
nwdFile.write_text("#")
bakFile.write_text("#")
files.append(nwdFile)
files.append(bakFile)
for item in files:
assert item.exists()
# Pollute folder 7 and 8
(data[7] / "stuff.txt").write_text("foo")
(data[8] / "bar").mkdir()
# Process folders
for i in range(9):
storage._legacyDataFolder(fncPath, data[i])
# Files form 0 to 8 should now be in content
for c in "012345678":
assert (fncPath / "content" / f"{c}00000000000{c}.nwd").exists()
# Folders 0 to 6 should be deleted
for i in range(7):
assert not data[i].exists()
# While 7 and 8 remain
assert data[7].exists()
assert data[8].exists()
# So does folder X, which is invalid
storage._legacyDataFolder(fncPath, data[16])
assert data[16].exists()
# Fail cleanup of folder 9
with monkeypatch.context() as mp:
mp.setattr("pathlib.Path.rename", causeOSError)
mp.setattr("pathlib.Path.unlink", causeOSError)
storage._legacyDataFolder(fncPath, data[9])
assert data[9].exists()
assert not (fncPath / "content" / "9000000000009.nwd").exists()
# Run the remaining through the prepare storage call
assert storage._prepareStorage(checkLegacy=True) is True
for c in "0123456789abcdef":
assert (fncPath / "content" / f"{c}00000000000{c}.nwd").exists()
# Deprecated Files
# ================
remove = [
fncPath / "meta" / "mainOptions.json",
fncPath / "meta" / "exportOptions.json",
fncPath / "meta" / "outlineOptions.json",
fncPath / "meta" / "timelineOptions.json",
fncPath / "meta" / "docMergeOptions.json",
fncPath / "meta" / "sessionLogOptions.json",
fncPath / "ToC.json",
]
for depFile in remove:
depFile.write_text("foo")
assert depFile.exists()
with monkeypatch.context() as mp:
mp.setattr("pathlib.Path.unlink", causeOSError)
storage._deleteDeprecatedFiles(fncPath)
for depFile in remove:
assert depFile.exists()
storage._deleteDeprecatedFiles(fncPath)
for depFile in remove:
assert not depFile.exists()
# END Test testCoreStorage_PrepareStorage
@pytest.mark.core
def testCoreStorage_ZipIt(monkeypatch, mockGUI, fncPath, tmpPath, mockRnd):
"""Test making a zip archive of a project.
"""
zipFile = tmpPath / "project.zip"
theProject = NWProject(mockGUI)
storage = theProject.storage
assert storage.zipIt(zipFile) is False
# Make a project
mockRnd.reset()
buildTestProject(theProject, fncPath)
# Fail to create archive
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.storage.ZipFile.write", causeOSError)
assert storage.zipIt(zipFile) is False
# Create archive
assert storage.zipIt(zipFile) is True
# Check content
with ZipFile(zipFile, mode="r") as archive:
names = archive.namelist()
assert nwFiles.PROJ_FILE in names
assert f"meta/{nwFiles.OPTS_FILE}" in names
assert f"meta/{nwFiles.INDEX_FILE}" in names
assert f"content/{C.hTitlePage}.nwd" in names
assert f"content/{C.hChapterDoc}.nwd" in names
assert f"content/{C.hSceneDoc}.nwd" in names
theProject.closeProject()
# END Test testCoreStorage_ZipIt
+1 -2
View File
@@ -25,7 +25,6 @@ import pytest
from tools import C, buildTestProject, 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.tokenizer import Tokenizer from novelwriter.core.tokenizer import Tokenizer
@@ -156,7 +155,7 @@ def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncDir):
) )
docTextR = docText.replace("<A>", "this").replace("<B>", "that") docTextR = docText.replace("<A>", "this").replace("<B>", "that")
nDoc = NWDoc(theProject, C.hSceneDoc) nDoc = theProject.storage.getDocument(C.hSceneDoc)
assert nDoc.writeDocument(docText) assert nDoc.writeDocument(docText)
theProject.data.setAutoReplace({"A": "this", "B": "that"}) theProject.data.setAutoReplace({"A": "this", "B": "that"})
+13 -13
View File
@@ -19,10 +19,11 @@ 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/>.
""" """
import os
import pytest import pytest
import random import random
from pathlib import Path
from tools import readFile from tools import readFile
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
@@ -394,7 +395,7 @@ def testCoreTree_Reorder(mockGUI, mockItems):
@pytest.mark.core @pytest.mark.core
def testCoreTree_ToCFile(monkeypatch, mockGUI, mockItems, tmpDir): def testCoreTree_ToCFile(monkeypatch, mockGUI, mockItems, tmpPath):
"""Test writing the ToC.txt file. """Test writing the ToC.txt file.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
@@ -411,24 +412,23 @@ def testCoreTree_ToCFile(monkeypatch, mockGUI, mockItems, tmpDir):
"""Return True for items that are files in novelWriter and """Return True for items that are files in novelWriter and
should thus also be files in the project folder structure. should thus also be files in the project folder structure.
""" """
dItem = theTree[fileName[8:21]] dItem = theTree[fileName.name[:13]]
assert dItem is not None assert dItem is not None
return dItem.itemType == nwItemType.FILE return dItem.itemType == nwItemType.FILE
monkeypatch.setattr("os.path.isfile", mockIsFile) monkeypatch.setattr("pathlib.Path.is_file", mockIsFile)
theProject.projContent = "content" theProject._storage._runtimePath = None
theProject.projPath = None assert theTree.writeToCFile() is False
assert not theTree.writeToCFile()
theProject.projPath = tmpDir theProject._storage._runtimePath = tmpPath
assert theTree.writeToCFile() assert theTree.writeToCFile() is True
pathA = os.path.join("content", "c000000000001.nwd") pathA = str(Path("content") / "c000000000001.nwd")
pathB = os.path.join("content", "c000000000002.nwd") pathB = str(Path("content") / "c000000000002.nwd")
pathC = os.path.join("content", "b000000000002.nwd") pathC = str(Path("content") / "b000000000002.nwd")
assert readFile(os.path.join(tmpDir, nwFiles.TOC_TXT)) == ( assert readFile(tmpPath / nwFiles.TOC_TXT) == (
"\n" "\n"
"Table of Contents\n" "Table of Contents\n"
"=================\n" "=================\n"
-4
View File
@@ -170,8 +170,6 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
assert len(nwGUI.theProject.tree._treeOrder) == 0 assert len(nwGUI.theProject.tree._treeOrder) == 0
assert len(nwGUI.theProject.tree._treeRoots) == 0 assert len(nwGUI.theProject.tree._treeRoots) == 0
assert nwGUI.theProject.tree.trashRoot() is None assert nwGUI.theProject.tree.trashRoot() is None
assert nwGUI.theProject.projPath is None
assert nwGUI.theProject.projMeta is None
assert nwGUI.theProject.data.name == "" assert nwGUI.theProject.data.name == ""
assert nwGUI.theProject.data.title == "" assert nwGUI.theProject.data.title == ""
assert nwGUI.theProject.data.authors == [] assert nwGUI.theProject.data.authors == []
@@ -192,8 +190,6 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
assert len(nwGUI.theProject.tree._treeOrder) == 8 assert len(nwGUI.theProject.tree._treeOrder) == 8
assert len(nwGUI.theProject.tree._treeRoots) == 4 assert len(nwGUI.theProject.tree._treeRoots) == 4
assert nwGUI.theProject.tree.trashRoot() is None assert nwGUI.theProject.tree.trashRoot() is None
assert nwGUI.theProject.projPath == fncProj
assert nwGUI.theProject.projMeta == os.path.join(fncProj, "meta")
assert nwGUI.theProject.data.name == "New Project" assert nwGUI.theProject.data.name == "New Project"
assert nwGUI.theProject.data.title == "New Novel" assert nwGUI.theProject.data.title == "New Novel"
assert nwGUI.theProject.data.authors == ["Jane Doe"] assert nwGUI.theProject.data.authors == ["Jane Doe"]
+14 -13
View File
@@ -19,10 +19,11 @@ 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/>.
""" """
import os
import pytest import pytest
from tools import C, buildTestProject, writeFile from pathlib import Path
from tools import C, buildTestProject
from PyQt5.QtGui import QFocusEvent from PyQt5.QtGui import QFocusEvent
from PyQt5.QtCore import Qt, QEvent from PyQt5.QtCore import Qt, QEvent
@@ -46,18 +47,18 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
nwGUI.projView.projTree._getTreeItem(C.hCharRoot).setSelected(True) nwGUI.projView.projTree._getTreeItem(C.hCharRoot).setSelected(True)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE) nwGUI.projView.projTree.newTreeItem(nwItemType.FILE)
writeFile( contentPath = nwGUI.theProject.storage.contentPath
os.path.join(nwGUI.theProject.projContent, "0000000000010.nwd"), assert isinstance(contentPath, Path)
"# Jane Doe\n\n@tag: Jane\n\n"
) (contentPath / "0000000000010.nwd").write_text(
writeFile( "# Jane Doe\n\n@tag: Jane\n\n", encoding="utf-8"
os.path.join(nwGUI.theProject.projContent, "000000000000f.nwd"), (
"### Scene One\n\n"
"@pov: Jane\n"
"@focus: Jane\n\n"
"% Synopsis: This is a scene."
)
) )
(contentPath / "000000000000f.nwd").write_text((
"### Scene One\n\n"
"@pov: Jane\n"
"@focus: Jane\n\n"
"% Synopsis: This is a scene."
), encoding="utf-8")
novelView = nwGUI.novelView novelView = nwGUI.novelView
novelTree = novelView.novelTree novelTree = novelView.novelTree
+3 -4
View File
@@ -29,7 +29,6 @@ from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMessageBox, QMenu, QTreeWidgetItem, QDialog from PyQt5.QtWidgets import QMessageBox, QMenu, QTreeWidgetItem, QDialog
from novelwriter.enum import nwItemLayout, nwItemType, nwItemClass from novelwriter.enum import nwItemLayout, nwItemType, nwItemClass
from novelwriter.core import NWDoc
from novelwriter.gui.projtree import GuiProjectTree from novelwriter.gui.projtree import GuiProjectTree
from novelwriter.dialogs.docmerge import GuiDocMerge from novelwriter.dialogs.docmerge import GuiDocMerge
from novelwriter.dialogs.docsplit import GuiDocSplit from novelwriter.dialogs.docsplit import GuiDocSplit
@@ -714,7 +713,7 @@ def testGuiProjTree_MergeDocuments(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, i
# The merge goes through # The merge goes through
assert projTree._mergeDocuments(hChapter1, True) is True assert projTree._mergeDocuments(hChapter1, True) is True
assert len(NWDoc(theProject, mergedDoc1).readDocument()) > lenAll assert len(theProject.storage.getDocument(mergedDoc1).readDocument()) > lenAll
# Merge to Existing Doc # Merge to Existing Doc
# ===================== # =====================
@@ -733,9 +732,9 @@ def testGuiProjTree_MergeDocuments(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, i
# Successful merge, and move to trash # Successful merge, and move to trash
mergeData["moveToTrash"] = True mergeData["moveToTrash"] = True
assert len(NWDoc(theProject, hChapter1).readDocument()) < lenAll assert len(theProject.storage.getDocument(hChapter1).readDocument()) < lenAll
assert projTree._mergeDocuments(hChapter1, False) is True assert projTree._mergeDocuments(hChapter1, False) is True
assert len(NWDoc(theProject, hChapter1).readDocument()) > lenAll assert len(theProject.storage.getDocument(hChapter1).readDocument()) > lenAll
assert theProject.tree.isTrash(hSceneOne11) assert theProject.tree.isTrash(hSceneOne11)
assert theProject.tree.isTrash(hSceneOne12) assert theProject.tree.isTrash(hSceneOne12)
+1 -2
View File
@@ -25,7 +25,6 @@ import pytest
from tools import C, buildTestProject from tools import C, buildTestProject
from novelwriter.enum import nwState from novelwriter.enum import nwState
from novelwriter.core.document import NWDoc
@pytest.mark.gui @pytest.mark.gui
@@ -34,7 +33,7 @@ def testGuiStatusBar_Main(qtbot, nwGUI, fncProj, mockRnd):
""" """
buildTestProject(nwGUI, fncProj) buildTestProject(nwGUI, fncProj)
cHandle = nwGUI.theProject.newFile("A Note", C.hCharRoot) cHandle = nwGUI.theProject.newFile("A Note", C.hCharRoot)
newDoc = NWDoc(nwGUI.theProject, cHandle) newDoc = nwGUI.theProject.storage.getDocument(cHandle)
newDoc.writeDocument("# A Note\n\n") newDoc.writeDocument("# A Note\n\n")
nwGUI.projView.projTree.revealNewTreeItem(cHandle) nwGUI.projView.projTree.revealNewTreeItem(cHandle)
nwGUI.rebuildIndex(beQuiet=True) nwGUI.rebuildIndex(beQuiet=True)
+8 -15
View File
@@ -25,7 +25,7 @@ import shutil
from PyQt5.QtWidgets import qApp from PyQt5.QtWidgets import qApp
XML_IGNORE = ("<novelWriterXML", "<saveCount", "<autoCount", "<editTime") XML_IGNORE = ("<novelWriterXML", "<project")
class C: class C:
@@ -156,7 +156,7 @@ def buildTestProject(theObject, projPath):
object as the parent. object as the parent.
""" """
from novelwriter.enum import nwItemClass from novelwriter.enum import nwItemClass
from novelwriter.core import NWProject, NWDoc from novelwriter.core import NWProject
if isinstance(theObject, NWProject): if isinstance(theObject, NWProject):
theGUI = None theGUI = None
@@ -166,17 +166,10 @@ def buildTestProject(theObject, projPath):
theProject = theObject.theProject theProject = theObject.theProject
theProject.clearProject() theProject.clearProject()
theProject.setProjectPath(projPath, newProject=True) theProject.storage.openProjectInPlace(projPath)
theProject.setDefaultStatusImport()
theProject.data.itemStatus.write(None, "New", (100, 100, 100))
theProject.data.itemStatus.write(None, "Note", (200, 50, 0))
theProject.data.itemStatus.write(None, "Draft", (200, 150, 0))
theProject.data.itemStatus.write(None, "Finished", (50, 200, 0))
theProject.data.itemImport.write(None, "New", (100, 100, 100))
theProject.data.itemImport.write(None, "Minor", (200, 50, 0))
theProject.data.itemImport.write(None, "Major", (200, 150, 0))
theProject.data.itemImport.write(None, "Main", (50, 200, 0))
theProject.data.setUuid("d0f3fe10-c6e6-4310-8bfd-181eb4224eed")
theProject.data.setName("New Project") theProject.data.setName("New Project")
theProject.data.setTitle("New Novel") theProject.data.setTitle("New Novel")
theProject.data.setAuthors("Jane Doe") theProject.data.setAuthors("Jane Doe")
@@ -193,15 +186,15 @@ def buildTestProject(theObject, projPath):
xHandle[7] = theProject.newFile("New Chapter", xHandle[6]) xHandle[7] = theProject.newFile("New Chapter", xHandle[6])
xHandle[8] = theProject.newFile("New Scene", xHandle[6]) xHandle[8] = theProject.newFile("New Scene", xHandle[6])
aDoc = NWDoc(theProject, xHandle[5]) aDoc = theProject.storage.getDocument(xHandle[5])
aDoc.writeDocument("#! New Novel\n\n>> By Jane Doe <<\n") aDoc.writeDocument("#! New Novel\n\n>> By Jane Doe <<\n")
theProject.index.reIndexHandle(xHandle[5]) theProject.index.reIndexHandle(xHandle[5])
aDoc = NWDoc(theProject, xHandle[7]) aDoc = theProject.storage.getDocument(xHandle[7])
aDoc.writeDocument("## %s\n\n" % theProject.tr("New Chapter")) aDoc.writeDocument("## %s\n\n" % theProject.tr("New Chapter"))
theProject.index.reIndexHandle(xHandle[7]) theProject.index.reIndexHandle(xHandle[7])
aDoc = NWDoc(theProject, xHandle[8]) aDoc = theProject.storage.getDocument(xHandle[8])
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])