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**
* Added a preferences dialog for the program settings. No longer necessary to edit the config file.
PR #30.
* Added a preferences dialog for the program settings. It is no longer necessary to edit the config
file. PR #30.
* The document viewer remembers scroll bar position when pressing `Ctrl+R` on a document already
being viewed. 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**
* 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
#21.
* Project word count is written to the project file, which is needed for the session word count.
PR #21.
* Closing a project now clears the status bar. PR #21.
**Editor**
+19 -2
View File
@@ -25,6 +25,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import os
import json
import uuid
import hashlib
import logging
@@ -87,9 +88,10 @@ def checkBool(value, default):
if isinstance(value, bool):
return value
elif isinstance(value, str):
if value == "True":
check = value.lower()
if check in ("true", "yes", "on"):
return True
elif value == "False":
elif check in ("false", "no", "off"):
return False
else:
return default
@@ -113,6 +115,15 @@ def checkHandle(value, default, allowNone=False):
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
# =============================================================================================== #
@@ -249,6 +260,12 @@ def simplified(string):
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):
"""Split a version string on the form aa.bb.cc into major, minor
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/>.
"""
from novelwriter.core.doctools import DocMerger, DocSplitter
from novelwriter.core.document import NWDoc
from novelwriter.core.coretools import DocMerger, DocSplitter, ProjectBuilder
from novelwriter.core.index import countWords
from novelwriter.core.project import NWProject
from novelwriter.core.spellcheck import NWSpellEnchant
@@ -31,8 +30,8 @@ from novelwriter.core.tomd import ToMarkdown
__all__ = [
"DocMerger",
"DocSplitter",
"ProjectBuilder",
"countWords",
"NWDoc",
"NWProject",
"NWSpellEnchant",
"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/>.
"""
import os
import logging
from pathlib import Path
from novelwriter.enum import nwItemLayout, nwItemClass
from novelwriter.error import formatException
from novelwriter.common import isHandle, sha256sum
@@ -33,7 +34,7 @@ from novelwriter.common import isHandle, sha256sum
logger = logging.getLogger(__name__)
class NWDoc:
class NWDocument:
def __init__(self, theProject, theHandle):
@@ -57,7 +58,7 @@ class NWDoc:
return
def __repr__(self):
return f"<NWDoc handle={self._docHandle}>"
return f"<NWDocument handle={self._docHandle}>"
def __bool__(self):
return self._docHandle is not None and bool(self._theItem)
@@ -73,7 +74,7 @@ class NWDoc:
empty string. If something went wrong, return None.
"""
self._docError = ""
if self._docHandle is None:
if not isinstance(self._docHandle, str):
logger.error("No document handle set")
return None
@@ -81,17 +82,22 @@ class NWDoc:
logger.error("Unknown novelWriter document")
return None
contentPath = self.theProject.storage.contentPath
if not isinstance(contentPath, Path):
logger.error("No content path set")
return None
docFile = self._docHandle+".nwd"
logger.debug("Opening document: %s", docFile)
docPath = os.path.join(self.theProject.projContent, docFile)
docPath = contentPath / docFile
self._fileLoc = docPath
theText = ""
self._docMeta = {}
self._prevHash = None
if os.path.isfile(docPath):
if docPath.exists():
self._prevHash = sha256sum(docPath)
try:
with open(docPath, mode="r", encoding="utf-8") as inFile:
@@ -125,17 +131,20 @@ class NWDoc:
if not.
"""
self._docError = ""
if self._docHandle is None:
if not isinstance(self._docHandle, str):
logger.error("No document handle set")
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"
logger.debug("Saving document: %s", docFile)
docPath = os.path.join(self.theProject.projContent, docFile)
docTemp = os.path.join(self.theProject.projContent, docFile+"~")
docPath = contentPath / docFile
docTemp = docPath.with_suffix(".tmp")
if self._prevHash is not None and not forceWrite:
self._currHash = sha256sum(docPath)
@@ -164,7 +173,7 @@ class NWDoc:
# If we're here, the file was successfully saved, so we can
# replace the temp file with the actual file
try:
os.replace(docTemp, docPath)
docTemp.replace(docPath)
except OSError as exc:
self._docError = formatException(exc)
return False
@@ -179,23 +188,28 @@ class NWDoc:
from the project data folder.
"""
self._docError = ""
if self._docHandle is None:
if not isinstance(self._docHandle, str):
logger.error("No document handle set")
return False
chkList = [
os.path.join(self.theProject.projContent, f"{self._docHandle}.nwd"),
os.path.join(self.theProject.projContent, f"{self._docHandle}.nwd~"),
]
contentPath = self.theProject.storage.contentPath
if not isinstance(contentPath, Path):
logger.error("No content path set")
return False
for chkFile in chkList:
if os.path.isfile(chkFile):
try:
os.unlink(chkFile)
logger.debug("Deleted: %s", chkFile)
except Exception as exc:
self._docError = formatException(exc)
return False
docPath = contentPath / f"{self._docHandle}.nwd"
docTemp = docPath.with_suffix(".tmp")
try:
# ToDo: When Python 3.7 is dropped, these can be changed to
# path.unlink(missing_ok=True)
if docPath.exists():
docPath.unlink()
if docTemp.exists():
docTemp.unlink()
except Exception as exc:
self._docError = formatException(exc)
return False
return True
@@ -206,7 +220,7 @@ class NWDoc:
def getFileLocation(self):
"""Return the file location of the current document.
"""
return self._fileLoc
return str(self._fileLoc)
def getCurrentItem(self):
"""Return a pointer to the currently open NWItem.
@@ -263,4 +277,4 @@ class NWDoc:
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/>.
"""
import os
import json
import logging
from time import time
from pathlib import Path
from novelwriter.enum import nwItemType, nwItemLayout
from novelwriter.error import logException
from novelwriter.constants import nwFiles, nwKeyWords, nwUnicode, nwHeaders
from novelwriter.core.document import NWDoc
from novelwriter.common import (
checkInt, isHandle, isItemClass, isTitleTag, jsonEncode
)
@@ -118,7 +117,7 @@ class NWIndex:
return False
logger.debug("Re-indexing item '%s'", tHandle)
theDoc = NWDoc(self.theProject, tHandle)
theDoc = self.theProject.storage.getDocument(tHandle)
self.scanText(tHandle, theDoc.readDocument() or "")
return True
@@ -141,12 +140,15 @@ class NWIndex:
def loadIndex(self):
"""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 = {}
indexFile = os.path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
tStart = time()
self._indexBroken = False
if os.path.isfile(indexFile):
if indexFile.exists():
logger.debug("Loading index file")
try:
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
data folder.
"""
indexFile = self.theProject.storage.getMetaFile(nwFiles.INDEX_FILE)
if not isinstance(indexFile, Path):
return False
logger.debug("Saving index file")
indexFile = os.path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
tStart = time()
try:
@@ -815,8 +820,6 @@ class ItemIndex:
elif tItem.itemRoot == rootHandle:
for sTitle in self._items[tHandle].headings():
yield tHandle, sTitle, self._items[tHandle][sTitle]
else:
continue
return
+26 -21
View File
@@ -27,7 +27,7 @@ import logging
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
from novelwriter.common import (
checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified
checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified, yesNo
)
from novelwriter.constants import nwHeaders, nwLabels, trConst
@@ -162,7 +162,7 @@ class NWItem:
item["order"] = str(self._order)
item["type"] = str(self._type.name)
item["class"] = str(self._class.name)
meta["expanded"] = str(self._expanded)
meta["expanded"] = yesNo(self._expanded)
name["status"] = str(self._status)
name["import"] = str(self._import)
@@ -173,7 +173,7 @@ class NWItem:
meta["wordCount"] = str(self._wordCount)
meta["paraCount"] = str(self._paraCount)
meta["cursorPos"] = str(self._cursorPos)
name["active"] = str(self._active)
name["active"] = yesNo(self._active)
data = {
"name": str(self._name),
@@ -187,29 +187,34 @@ class NWItem:
def unpack(self, data):
"""Set the values from a data dictionary.
"""
if "handle" in data:
self.setHandle(data["handle"])
item = data.get("itemAttr", {})
meta = data.get("metaAttr", {})
name = data.get("nameAttr", {})
if "handle" in item:
self.setHandle(item["handle"])
else:
logger.error("Item does not have a handle")
return False
self.setName(data.get("label", ""))
self.setParent(data.get("parent", None))
self.setRoot(data.get("root", None))
self.setOrder(data.get("order", 0))
self.setType(data.get("type", nwItemType.NO_TYPE))
self.setClass(data.get("class", nwItemClass.NO_CLASS))
self.setLayout(data.get("layout", nwItemLayout.NO_LAYOUT))
self.setName(data.get("name", ""))
self.setParent(item.get("parent", None))
self.setRoot(item.get("root", None))
self.setOrder(item.get("order", 0))
self.setType(item.get("type", nwItemType.NO_TYPE))
self.setClass(item.get("class", nwItemClass.NO_CLASS))
self.setExpanded(meta.get("expanded", False))
self.setStatus(name.get("status", None))
self.setImport(name.get("import", None))
self.setExpanded(data.get("expanded", False))
self.setStatus(data.get("status", None))
self.setImport(data.get("import", None))
self.setMainHeading(data.get("heading", "H0"))
self.setCharCount(data.get("charCount", 0))
self.setWordCount(data.get("wordCount", 0))
self.setParaCount(data.get("paraCount", 0))
self.setCursorPos(data.get("cursorPos", 0))
self.setActive(data.get("active", True))
if self._type == nwItemType.FILE:
self.setLayout(item.get("layout", nwItemLayout.NO_LAYOUT))
self.setMainHeading(meta.get("heading", "H0"))
self.setCharCount(meta.get("charCount", 0))
self.setWordCount(meta.get("wordCount", 0))
self.setParaCount(meta.get("paraCount", 0))
self.setCursorPos(meta.get("cursorPos", 0))
self.setActive(name.get("active", True))
# Make some checks to ensure consistency
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/>.
"""
import os
import json
import logging
from enum import Enum
from pathlib import Path
from novelwriter.error import logException
from novelwriter.common import checkBool, checkFloat, checkInt, checkString
@@ -77,13 +77,12 @@ class OptionState:
def loadSettings(self):
"""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
stateFile = os.path.join(self.theProject.projMeta, nwFiles.OPTS_FILE)
theState = {}
if os.path.isfile(stateFile):
if stateFile.exists():
logger.debug("Loading GUI options file")
try:
with open(stateFile, mode="r", encoding="utf-8") as inFile:
@@ -106,12 +105,11 @@ class OptionState:
def saveSettings(self):
"""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
stateFile = os.path.join(self.theProject.projMeta, nwFiles.OPTS_FILE)
logger.debug("Saving GUI options file")
try:
with open(stateFile, mode="w+", encoding="utf-8") as outFile:
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/>.
"""
import os
import logging
import novelwriter
from enum import Enum
from lxml import etree
from time import time
from pathlib import Path
from novelwriter.common import (
checkBool, checkInt, checkStringNone, formatTimeStamp, simplified, checkString
checkBool, checkInt, checkString, checkStringNone, formatTimeStamp,
hexToInt, simplified, yesNo
)
from novelwriter.constants import nwFiles
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 = {
"1.0": 0x0100,
"1.1": 0x0101,
"1.2": 0x0102,
"1.3": 0x0103,
"1.4": 0x0104,
"1.0": 0x0100, # Up to 0.7
"1.1": 0x0101, # Up to 0.10
"1.2": 0x0102, # Up to 1.5
"1.3": 0x0103, # Up to 2.0 Beta 1
"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
NWProjectData instance, which must be provided.
Version Change History
======================
File Format Version Change History
==================================
1.0 Original file format.
1.1 Changes the way documents are structured in the project folder
from data_X, where X is the first hex value of the handle, to a
single content folder. Introduced in version 0.7.
1.2 Changes the way autoReplace entries are stored. The 1.1 parser
will lose the autoReplace settings if allowed to read the file.
Introduced in version 0.10.
1.2 Changes the way autoReplace entries are stored. Introduced in
version 0.10.
1.3 Reduces the number of layouts to only two. One for novel
documents and one for project notes. Introduced in version 1.5.
1.4 Introduces a more compact format for storing items. All settings
aside from name are now attributes. This format also changes the
way satus and importance labels are stored and handled.
Introduced in version 2.0.
way satus and importance labels are stored. This format was only
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):
self._path = path
self._path = Path(path)
self._state = XMLReadState.NO_ACTION
self._root = ""
self._version = 0x0000
self._version = 0x0
self._appVersion = ""
self._hexVersion = ""
self._hexVersion = 0x0
self._timeStamp = ""
return
@@ -153,22 +162,22 @@ class ProjectXMLReader:
logger.debug("Reading project XML")
try:
xml = etree.parse(self._path)
xml = etree.parse(str(self._path))
self._state = XMLReadState.NO_ERROR
except Exception as exc:
# Trying to open backup file instead
logger.error("Failed to parse project xml", exc_info=exc)
logger.error("Failed to parse project XML", exc_info=exc)
self._state = XMLReadState.CANNOT_PARSE
backFile = self._path[:-3]+"bak"
if os.path.isfile(backFile):
backFile = self._path.with_suffix(".bak")
if backFile.is_file():
try:
xml = etree.parse(backFile)
xml = etree.parse(str(backFile))
self._state = XMLReadState.PARSED_BACKUP
logger.info("Backup project file parsed")
except Exception as exc:
logger.error("Failed to parse backup project xml", exc_info=exc)
logger.error("Failed to parse backup project XML", exc_info=exc)
self._state = XMLReadState.CANNOT_PARSE
return False
else:
@@ -191,7 +200,7 @@ class ProjectXMLReader:
logger.debug("XML is '%s' version '%s'", self._root, fileVersion)
self._appVersion = str(xRoot.attrib.get("appVersion", ""))
self._hexVersion = str(xRoot.attrib.get("hexVersion", ""))
self._hexVersion = hexToInt(xRoot.attrib.get("hexVersion", ""))
self._timeStamp = str(xRoot.attrib.get("timeStamp", ""))
for xSection in xRoot:
@@ -201,13 +210,13 @@ class ProjectXMLReader:
self._parseProjectSettings(xSection, projData)
elif xSection.tag == "content":
if self._version >= 0x0104:
self._parseProjectContent(xSection, projContent)
self._parseProjectContent(xSection, projData, projContent)
else:
self._parseProjectContentLegacy(xSection, projContent, projData)
self._parseProjectContentLegacy(xSection, projData, projContent)
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
else:
self._state = XMLReadState.WAS_LEGACY
@@ -224,6 +233,12 @@ class ProjectXMLReader:
"""Parse the project section of the XML file.
"""
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:
if xItem.tag == "name":
projData.setName(xItem.text)
@@ -231,14 +246,18 @@ class ProjectXMLReader:
projData.setTitle(xItem.text)
elif xItem.tag == "author":
projData.addAuthor(xItem.text)
elif xItem.tag == "saveCount":
projData.setSaveCount(xItem.text)
elif xItem.tag == "autoCount":
projData.setAutoCount(xItem.text)
elif xItem.tag == "editTime":
projData.setEditTime(xItem.text)
else:
logger.warning("Ignored <root/project/%s> in xml", xItem.tag)
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
@@ -252,17 +271,12 @@ class ProjectXMLReader:
projData.setDoBackup(xItem.text)
elif xItem.tag == "language":
projData.setLanguage(xItem.text)
elif xItem.tag == "spellCheck":
projData.setSpellCheck(xItem.text)
elif xItem.tag == "spellLang":
elif xItem.tag == "spellChecking":
projData.setSpellLang(xItem.text)
elif xItem.tag == "novelWordCount":
projData.setInitCounts(novel=xItem.text)
elif xItem.tag == "notesWordCount":
projData.setInitCounts(notes=xItem.text)
projData.setSpellCheck(xItem.attrib.get("auto", False))
elif xItem.tag == "status":
self._parseStatusImport(xItem, projData.itemStatus)
elif xItem.tag in ("import", "importance"):
elif xItem.tag == "importance":
self._parseStatusImport(xItem, projData.itemImport)
elif xItem.tag == "lastHandle":
projData.setLastHandle(self._parseDictKeyText(xItem))
@@ -272,122 +286,157 @@ class ProjectXMLReader:
else: # Pre 1.2 format
projData.setAutoReplace(self._parseDictTagText(xItem))
elif xItem.tag == "titleFormat":
if self._version >= 0x0104:
if self._version >= 0x0105:
projData.setTitleFormat(self._parseDictKeyText(xItem))
else: # Pre 1.4 format
projData.setTitleFormat(self._parseDictTagText(xItem))
else:
logger.warning("Ignored <root/settings/%s> in xml", xItem.tag)
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
def _parseProjectContent(self, xSection, projContent):
def _parseProjectContent(self, xSection, projData, projContent):
"""Parse the content section of the XML file.
"""
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:
if xItem.tag == "item":
item = {}
item["handle"] = checkStringNone(xItem.attrib.get("handle"), None)
item["parent"] = checkStringNone(xItem.attrib.get("parent"), None)
item["root"] = checkStringNone(xItem.attrib.get("root"), None)
item["order"] = checkInt(xItem.attrib.get("order"), 0)
item["type"] = checkString(xItem.attrib.get("type"), "NO_TYPE")
item["class"] = checkString(xItem.attrib.get("class"), "NO_CLASS")
item["layout"] = checkString(xItem.attrib.get("layout"), "NO_LAYOUT")
if xItem.tag != "item":
logger.warning("Ignored item <root/content/%s> in XML", xItem.tag)
continue
item = {}
meta = {}
name = {}
itemName = ""
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:
if xVal.tag == "meta":
item["expanded"] = checkBool(xVal.attrib.get("expanded"), False)
item["heading"] = checkString(xVal.attrib.get("heading"), "H0")
item["charCount"] = checkInt(xVal.attrib.get("charCount"), 0)
item["wordCount"] = checkInt(xVal.attrib.get("wordCount"), 0)
item["paraCount"] = checkInt(xVal.attrib.get("paraCount"), 0)
item["cursorPos"] = checkInt(xVal.attrib.get("cursorPos"), 0)
elif xVal.tag == "name":
item["label"] = simplified(checkString(xVal.text, ""))
item["status"] = checkStringNone(xVal.attrib.get("status"), None)
item["import"] = checkStringNone(xVal.attrib.get("import"), None)
item["active"] = checkBool(xVal.attrib.get("active"), False)
if xVal.tag == "name" and "exported" in xVal.attrib:
name["active"] = checkBool(xVal.attrib.get("exported"), False)
# ToDo: Remove before 2.0 release. Only needed for 2.0 pre-releases.
if "exported" in xVal.attrib:
item["active"] = checkBool(xVal.attrib.get("exported"), False)
else:
logger.warning("Ignored <root/content/item/%s> in xml", xVal.tag)
projContent.append(item)
else:
logger.warning("Ignored item <root/content/%s> in xml", xItem.tag)
projContent.append({
"name": itemName,
"itemAttr": item,
"metaAttr": meta,
"nameAttr": name,
})
return
def _parseProjectContentLegacy(self, xSection, projContent, projData):
def _parseProjectContentLegacy(self, xSection, projData, projContent):
"""Parse the content section of the XML file for older versions.
"""
logger.debug("Parsing <content> section (legacy format)")
# Create maps to look up name -> key for status and importance
statusMap = {entry["name"]: key for key, entry in projData.itemStatus.items()}
importMap = {entry["name"]: key for key, entry in projData.itemImport.items()}
statusMap = {entry.get("name"): key for key, entry in projData.itemStatus.items()}
importMap = {entry.get("name"): key for key, entry in projData.itemImport.items()}
for xItem in xSection:
if xItem.tag != "item":
logger.warning("Ignored item <root/content/%s> in XML", xItem.tag)
continue
item = {}
if xItem.tag == "item":
item["handle"] = checkStringNone(xItem.attrib.get("handle", None), None)
item["parent"] = checkStringNone(xItem.attrib.get("parent", None), None)
item["root"] = None # Value was added in 1.4
item["order"] = checkInt(xItem.attrib.get("order", 0), 0)
item["heading"] = "H0" # Value was added in 1.4
meta = {}
name = {}
itemName = ""
tmpStatus = ""
for xVal in xItem:
if xVal.tag == "name":
item["label"] = simplified(checkString(xVal.text, ""))
elif xVal.tag == "status":
tmpStatus = checkStringNone(xVal.text, None)
elif xVal.tag == "type":
item["type"] = checkString(xVal.text, "")
elif xVal.tag == "class":
item["class"] = checkString(xVal.text, "")
elif xVal.tag == "layout":
item["layout"] = checkString(xVal.text, "")
elif xVal.tag == "expanded":
item["expanded"] = checkBool(xVal.text, False)
elif xVal.tag == "exported": # Renamed to active in 1.4
item["active"] = checkBool(xVal.text, False)
elif xVal.tag == "charCount":
item["charCount"] = checkInt(xVal.text, 0)
elif xVal.tag == "wordCount":
item["wordCount"] = checkInt(xVal.text, 0)
elif xVal.tag == "paraCount":
item["paraCount"] = checkInt(xVal.text, 0)
elif xVal.tag == "cursorPos":
item["cursorPos"] = checkInt(xVal.text, 0)
else:
logger.warning("Ignored <root/content/item/%s> in xml", xVal.tag)
item["handle"] = checkStringNone(xItem.attrib.get("handle", None), None)
item["parent"] = checkStringNone(xItem.attrib.get("parent", None), None)
item["root"] = None # Value was added in 1.4
item["order"] = checkInt(xItem.attrib.get("order", 0), 0)
meta["heading"] = "H0" # Value was added in 1.4
# Status was split into separate status/import with a key in 1.4
if item.get("class", "") in ("NOVEL", "ARCHIVE"):
item["status"] = statusMap.get(tmpStatus, None)
tmpStatus = ""
for xVal in xItem:
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:
item["import"] = importMap.get(tmpStatus, None)
# A number of layouts were removed in 1.3
if item.get("layout", "") in (
"TITLE", "PAGE", "BOOK", "PARTITION", "UNNUMBERED", "CHAPTER", "SCENE"
):
item["layout"] = "DOCUMENT"
# The trast type was removed in 1.4
if item.get("type", "") == "TRASH":
item["type"] = "ROOT"
projContent.append(item)
logger.warning("Ignored <root/content/item/%s> in XML", xVal.tag)
# Status was split into separate status/import with a key in 1.4
if item.get("class", "") in ("NOVEL", "ARCHIVE"):
name["status"] = statusMap.get(tmpStatus, None)
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
@@ -418,7 +467,7 @@ class ProjectXMLReader:
"""Parse a dictionary stored with key as the tag and the value
as the text porperty.
"""
return {n.tag: checkString(n.text, "") for n in xItem}
return {xNode.tag: checkString(xNode.text, "") for xNode in xItem}
# END Class ProjectXMLReader
@@ -427,7 +476,7 @@ class ProjectXMLWriter:
def __init__(self, path):
self._path = path
self._path = Path(path)
self._error = None
return
@@ -458,22 +507,25 @@ class ProjectXMLWriter:
})
# 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, "title", projData.title)
self._packListValue(xProject, "author", projData.authors)
self._packSingleValue(xProject, "saveCount", projData.saveCount)
self._packSingleValue(xProject, "autoCount", projData.autoCount)
self._packSingleValue(xProject, "editTime", editTime)
# Save Project Settings
xSettings = etree.SubElement(xRoot, "settings")
self._packSingleValue(xSettings, "doBackup", projData.doBackup)
self._packSingleValue(xSettings, "doBackup", yesNo(projData.doBackup))
self._packSingleValue(xSettings, "language", projData.language)
self._packSingleValue(xSettings, "spellCheck", projData.spellCheck)
self._packSingleValue(xSettings, "spellLang", projData.spellLang)
self._packSingleValue(xSettings, "novelWordCount", projData.currCounts[0])
self._packSingleValue(xSettings, "notesWordCount", projData.currCounts[1])
self._packSingleValue(xSettings, "spellChecking", projData.spellLang, attrib={
"auto": yesNo(projData.spellCheck)
})
self._packDictKeyValue(xSettings, "lastHandle", projData.lastHandle)
self._packDictKeyValue(xSettings, "autoReplace", projData.autoReplace)
self._packDictKeyValue(xSettings, "titleFormat", projData.titleFormat)
@@ -488,25 +540,27 @@ class ProjectXMLWriter:
self._packSingleValue(xImport, "entry", label, attrib=attrib)
# 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:
xItem = etree.SubElement(xContent, "item", attrib=item.get("itemAttr", {}))
etree.SubElement(xItem, "meta", attrib=item.get("metaAttr", {}))
xName = etree.SubElement(xItem, "name", attrib=item.get("nameAttr", {}))
xName.text = item["name"]
# Write the xml tree to file
saveFile = os.path.join(self._path, nwFiles.PROJ_FILE)
tempFile = os.path.join(self._path, nwFiles.PROJ_FILE+"~")
backFile = os.path.join(self._path, nwFiles.PROJ_FILE[:-3]+"bak")
# Write the XML tree to file
saveFile = self._path / nwFiles.PROJ_FILE
tempFile = saveFile.with_suffix(".tmp")
backFile = saveFile.with_suffix(".bak")
try:
with open(tempFile, mode="wb") as outFile:
outFile.write(etree.tostring(
xRoot,
pretty_print=True,
encoding="utf-8",
xml_declaration=True
))
tempFile.write_bytes(etree.tostring(
xRoot, pretty_print=True, encoding="utf-8", xml_declaration=True
))
except Exception as exc:
self._error = exc
return False
@@ -514,10 +568,10 @@ class ProjectXMLWriter:
# If we're here, the file was successfully saved,
# so let's sort out the temps and backups
try:
if os.path.isfile(saveFile):
os.replace(saveFile, backFile)
os.replace(tempFile, saveFile)
except OSError as exc:
if saveFile.exists():
saveFile.replace(backFile)
tempFile.replace(saveFile)
except Exception as exc:
self._error = exc
return False
@@ -530,14 +584,14 @@ class ProjectXMLWriter:
##
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.text = str(value) or ""
return
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:
xItem = etree.SubElement(xParent, name)
@@ -545,7 +599,7 @@ class ProjectXMLWriter:
return
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)
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/>.
"""
import os
import logging
from collections import namedtuple
from pathlib import Path
from novelwriter.error import logException
@@ -173,10 +173,10 @@ class NWSpellEnchant:
self._projDict = set()
self._projectDict = projectDict
if projectDict is None:
if not isinstance(projectDict, Path):
return False
if not os.path.isfile(projectDict):
if not projectDict.exists():
return False
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.common import numberToRoman, checkInt
from novelwriter.constants import nwConst, nwRegEx, nwUnicode
from novelwriter.core.document import NWDoc
logger = logging.getLogger(__name__)
@@ -305,7 +304,7 @@ class Tokenizer(ABC):
return False
if theText is None:
theText = NWDoc(self.theProject, theHandle).readDocument() or ""
theText = self.theProject.storage.getDocument(theHandle).readDocument() or ""
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/>.
"""
import os
import random
import logging
from pathlib import Path
from novelwriter.enum import nwItemClass, nwItemLayout
from novelwriter.error import logException
from novelwriter.common import checkHandle
@@ -140,16 +141,22 @@ class NWTree:
"""Write the convenience table of contents file in the root of
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 = []
tocLen = 0
for tHandle in self._treeOrder:
tItem = self.__getitem__(tHandle)
if tItem is None:
continue
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(
os.path.join("content", tFile),
str(Path("content") / tFile),
tItem.itemClass.name,
tItem.itemLayout.name,
tItem.itemName,
@@ -159,7 +166,7 @@ class NWTree:
try:
# 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:
outFile.write("\n")
outFile.write("Table of Contents\n")
+1 -2
View File
@@ -33,7 +33,6 @@ from PyQt5.QtWidgets import (
QListWidgetItem, QDialogButtonBox, QLabel, QGridLayout
)
from novelwriter.core import NWDoc
from novelwriter.custom import QHelpLabel, QSwitch
logger = logging.getLogger(__name__)
@@ -204,7 +203,7 @@ class GuiDocSplit(QDialog):
spLevel = self.splitLevel.currentData()
if not self._text:
inDoc = NWDoc(self.theProject, sHandle)
inDoc = self.theProject.storage.getDocument(sHandle)
self._text = (inDoc.readDocument() or "").splitlines()
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.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
+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/>.
"""
import os
import logging
import novelwriter
from pathlib import Path
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QDialog, QDialogButtonBox, QVBoxLayout, QHBoxLayout, QListWidget,
@@ -150,9 +151,11 @@ class GuiWordList(QDialog):
"""
self._saveGuiSettings()
dctFile = os.path.join(self.theProject.projMeta, nwFiles.PROJ_DICT)
tmpFile = dctFile + "~"
dctFile = self.theProject.storage.getMetaFile(nwFiles.PROJ_DICT)
if not isinstance(dctFile, Path):
return False
tmpFile = dctFile.with_suffix(".tmp")
try:
with open(tmpFile, mode="w", encoding="utf-8") as outFile:
for i in range(self.listBox.count()):
@@ -160,15 +163,14 @@ class GuiWordList(QDialog):
if item is not None:
outFile.write(item.text() + "\n")
tmpFile.replace(dctFile)
except Exception:
logger.error("Could not save new word list")
logException()
self.reject()
return False
if os.path.isfile(dctFile):
os.unlink(dctFile)
os.rename(tmpFile, dctFile)
self.accept()
return True
@@ -187,10 +189,12 @@ class GuiWordList(QDialog):
def _loadWordList(self):
"""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)
if not os.path.isfile(wordList):
self.listBox.clear()
if not wordList.exists():
logger.debug("No project dictionary file found")
return False
+7 -6
View File
@@ -50,10 +50,10 @@ from PyQt5.QtWidgets import (
QFrame
)
from novelwriter.core import NWDoc, NWSpellEnchant, countWords
from novelwriter.core import NWSpellEnchant, countWords
from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert, nwDocMode
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
logger = logging.getLogger(__name__)
@@ -339,7 +339,7 @@ class GuiDocEditor(QTextEdit):
document is new (empty string), we set up the editor for editing
the file.
"""
self._nwDocument = NWDoc(self.theProject, tHandle)
self._nwDocument = self.theProject.storage.getDocument(tHandle)
self._nwItem = self._nwDocument.getCurrentItem()
theDoc = self._nwDocument.readDocument()
@@ -470,8 +470,8 @@ class GuiDocEditor(QTextEdit):
return True
def saveText(self):
"""Save the text currently in the editor to the NWDoc object,
and update the NWItem meta data.
"""Save the text currently in the editor to the NWDocument
object, and update the NWItem meta data.
"""
if self._nwItem is None or self._nwDocument is None:
logger.error("Cannot save text as no document is open")
@@ -689,7 +689,8 @@ class GuiDocEditor(QTextEdit):
else:
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()
self.spellDictionaryChanged.emit(str(theLang), str(theProvider))
+1 -1
View File
@@ -824,7 +824,7 @@ class GuiMainMenu(QMenuBar):
# Tools > Backup
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)
# Tools > Export Project
+8 -33
View File
@@ -50,9 +50,9 @@ from novelwriter.dialogs import (
from novelwriter.tools import (
GuiBuildNovel, GuiLipsum, GuiProjectWizard, GuiWritingStats
)
from novelwriter.core import NWProject
from novelwriter.core import NWProject, ProjectBuilder
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.constants import nwFiles
@@ -365,30 +365,10 @@ class GuiMain(QMainWindow):
return False
logger.info("Creating new project")
if self.theProject.newProject(projData):
self.hasProject = True
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)
nwProject = ProjectBuilder(self)
if nwProject.buildProject(projData):
self.openProject(projPath)
else:
self.theProject.clearProject()
return False
return True
@@ -429,7 +409,7 @@ class GuiMain(QMainWindow):
if not msgYes:
doBackup = False
if doBackup:
self.theProject.zipIt(False)
self.theProject.backupProject(doNotify=False)
else:
saveOK = True
@@ -443,7 +423,6 @@ class GuiMain(QMainWindow):
self.idleRefTime = time()
self.idleTime = 0.0
self.theProject.index.clearIndex()
self.clearGUI()
self.hasProject = False
self._changeView(nwView.PROJECT)
@@ -519,9 +498,6 @@ class GuiMain(QMainWindow):
self.idleRefTime = time()
self.idleTime = 0.0
# Load the tag index
self.theProject.index.loadIndex()
# Update GUI
self._updateWindowTitle(self.theProject.data.name)
self.rebuildTrees()
@@ -573,8 +549,7 @@ class GuiMain(QMainWindow):
return False
self.projView.saveProjectTasks()
if self.theProject.saveProject(autoSave=autoSave):
self.theProject.index.saveIndex()
self.theProject.saveProject(autoSave=autoSave)
return True
@@ -1407,7 +1382,7 @@ class GuiMain(QMainWindow):
"""
doSave = self.hasProject
doSave &= self.theProject.projChanged
doSave &= self.theProject.projPath is not None
doSave &= self.theProject.storage.isOpen()
if doSave:
logger.debug("Autosaving project")
+10 -3
View File
@@ -29,6 +29,7 @@ import logging
import novelwriter
from time import time
from pathlib import Path
from datetime import datetime
from PyQt5.QtGui import (
@@ -1088,9 +1089,12 @@ class GuiBuildNovel(QDialog):
def _loadCache(self):
"""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
if os.path.isfile(buildCache):
if buildCache.exists():
logger.debug("Loading build cache")
try:
with open(buildCache, mode="r", encoding="utf-8") as inFile:
@@ -1115,7 +1119,10 @@ class GuiBuildNovel(QDialog):
def _saveCache(self):
"""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")
try:
with open(buildCache, mode="w+", encoding="utf-8") as outFile:
+3 -2
View File
@@ -28,6 +28,7 @@ import json
import logging
import novelwriter
from pathlib import Path
from datetime import datetime
from PyQt5.QtGui import QPixmap, QCursor
@@ -439,8 +440,8 @@ class GuiWritingStats(QDialog):
ttTime = 0
ttIdle = 0
logFile = os.path.join(self.theProject.projMeta, nwFiles.SESS_STATS)
if not os.path.isfile(logFile):
logFile = self.theProject.storage.getMetaFile(nwFiles.SESS_STATS)
if not isinstance(logFile, Path) or not logFile.exists():
logger.info("This project has no writing stats logfile")
return False
+51 -57
View File
@@ -1,21 +1,15 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 12:20:53">
<project>
<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="1448" autoCount="237" editTime="69737">
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>1409</saveCount>
<autoCount>236</autoCount>
<editTime>69427</editTime>
</project>
<settings>
<doBackup>False</doBackup>
<doBackup>no</doBackup>
<language>en_GB</language>
<spellCheck>True</spellCheck>
<spellLang>None</spellLang>
<novelWordCount>954</novelWordCount>
<notesWordCount>409</notesWordCount>
<spellChecking auto="yes">None</spellChecking>
<lastHandle>
<entry key="editor">636b6aa9b697b</entry>
<entry key="viewer">636b6aa9b697b</entry>
@@ -50,114 +44,114 @@
<entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry>
</importance>
</settings>
<content count="27">
<content items="27" novelWords="954" notesWords="409">
<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>
</item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H1" charCount="93" wordCount="19" paraCount="2" cursorPos="119"/>
<name status="sc24b8f" import="ia857f0" active="True">Title Page</name>
<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="False" heading="H0" charCount="251" wordCount="50" paraCount="2" cursorPos="277"/>
<name status="sf12341" import="ia857f0" active="True">Page</name>
<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="False" heading="H1" charCount="26" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s90e6c9" import="ia857f0" active="True">Part One</name>
<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="True" heading="H2" charCount="95" wordCount="18" paraCount="1" cursorPos="291"/>
<name status="sf24ce6" import="ia857f0" active="True">Chapter One</name>
<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="False" heading="H3" charCount="2687" wordCount="479" paraCount="14" cursorPos="67"/>
<name status="s90e6c9" import="ia857f0" active="True">Making a Scene</name>
<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="False" heading="H3" charCount="548" wordCount="108" paraCount="3" cursorPos="465"/>
<name status="s90e6c9" import="ia857f0" active="True">Another Scene</name>
<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="False" heading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="310"/>
<name status="s78ea90" import="ia857f0" active="True">Interlude</name>
<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="False" heading="H1" charCount="1909" wordCount="346" paraCount="7" cursorPos="0"/>
<name status="sf24ce6" import="ia857f0" active="False">A Note on Structure</name>
<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="True" heading="H2" charCount="139" wordCount="28" paraCount="1" cursorPos="188"/>
<name status="s90e6c9" import="ia857f0" active="True">Chapter Two</name>
<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="False" heading="H3" charCount="189" wordCount="37" paraCount="1" cursorPos="0"/>
<name status="s90e6c9" import="ia857f0" active="True">We Found John!</name>
<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="True"/>
<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="False" heading="H1" charCount="27" wordCount="5" paraCount="1" cursorPos="100"/>
<name status="sc24b8f" import="ia857f0" active="True">Title Page</name>
<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="False" heading="H2" charCount="299" wordCount="55" paraCount="2" cursorPos="104"/>
<name status="s90e6c9" import="ia857f0" active="True">Chapter One</name>
<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="True"/>
<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="True"/>
<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="False" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="sf12341" import="icfb3a5" active="True">John Smith</name>
<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="False" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="sf12341" import="i2d7a54" active="True">Jane Smith</name>
<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="True"/>
<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="False" heading="H1" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="sf12341" import="i56be10" active="True">Earth</name>
<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="False" heading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="sf12341" import="icfb3a5" active="True">Space</name>
<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="False" heading="H1" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="sf12341" import="i2d7a54" active="True">Mars</name>
<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="True"/>
<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="True"/>
<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="False" heading="H3" charCount="232" wordCount="42" paraCount="1" cursorPos="239"/>
<name status="s90e6c9" import="ia857f0" active="True">Old File</name>
<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="True"/>
<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="False" heading="H3" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="sf12341" import="ia857f0" active="True">Delete Me!</name>
<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>
+39 -23
View File
@@ -24,10 +24,12 @@ import sys
import pytest
import shutil
from pathlib import Path
from mock import MockGuiMain
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
@@ -62,6 +64,41 @@ def tmpDir():
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")
def refDir():
"""The folder where all the reference files are stored for verifying
@@ -95,7 +132,7 @@ def outDir(tmpDir):
def fncDir(tmpDir):
"""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):
shutil.rmtree(fncDir)
if not os.path.isdir(fncDir):
@@ -236,27 +273,6 @@ def nwLipsum(tmpDir):
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")
def ipsumText():
"""Return five paragraphs of Lorem Ipsum text.
+49 -50
View File
@@ -1,5 +1,5 @@
<?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>
<name>Sample Project</name>
<title>Sample Project</title>
@@ -14,25 +14,24 @@
<language>en_GB</language>
<spellCheck>True</spellCheck>
<spellLang>en_GB</spellLang>
<lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>636b6aa9b697b</lastViewed>
<lastNovel>7031beac91f75</lastNovel>
<lastOutline>7031beac91f75</lastOutline>
<lastWordCount>1363</lastWordCount>
<novelWordCount>954</novelWordCount>
<notesWordCount>409</notesWordCount>
<lastHandle>
<entry key="editor">636b6aa9b697b</entry>
<entry key="viewer">636b6aa9b697b</entry>
<entry key="novelTree">7031beac91f75</entry>
<entry key="outline">7031beac91f75</entry>
</lastHandle>
<autoReplace>
<entry key="A">B</entry>
<entry key="B">E</entry>
<entry key="C">D</entry>
</autoReplace>
<titleFormat>
<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>
<title>%title%</title>
<chapter>Chapter %chw%: %title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>Scene %ch%.%sc%: %title%</scene>
<section></section>
</titleFormat>
<status>
<entry key="sf12341" count="4" red="100" green="100" blue="100">New</entry>
@@ -56,56 +55,56 @@
<name status="sc24b8f" import="ia857f0">Novel</name>
</item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H1" charCount="93" wordCount="19" paraCount="2" cursorPos="119"/>
<name status="sc24b8f" import="ia857f0" active="True">Title Page</name>
<meta expanded="False" charCount="93" wordCount="19" paraCount="2" cursorPos="119"/>
<name status="sc24b8f" import="ia857f0" exported="True">Title Page</name>
</item>
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="251" wordCount="50" paraCount="2" cursorPos="277"/>
<name status="sf12341" import="ia857f0" active="True">Page</name>
<meta expanded="False" charCount="251" wordCount="50" paraCount="2" cursorPos="277"/>
<name status="sf12341" import="ia857f0" exported="True">Page</name>
</item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H1" charCount="26" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s90e6c9" import="ia857f0" active="True">Part One</name>
<meta expanded="False" charCount="26" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s90e6c9" import="ia857f0" exported="True">Part One</name>
</item>
<item handle="6a2d6d5f4f401" parent="7031beac91f75" root="7031beac91f75" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="True" heading="H2" charCount="95" wordCount="18" paraCount="1" cursorPos="291"/>
<name status="sf24ce6" import="ia857f0" active="True">Chapter One</name>
<meta expanded="True" charCount="95" wordCount="18" paraCount="1" cursorPos="291"/>
<name status="sf24ce6" import="ia857f0" exported="True">Chapter One</name>
</item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H3" charCount="2687" wordCount="479" paraCount="14" cursorPos="67"/>
<name status="s90e6c9" import="ia857f0" active="True">Making a Scene</name>
<meta expanded="False" charCount="2687" wordCount="479" paraCount="14" cursorPos="67"/>
<name status="s90e6c9" import="ia857f0" exported="True">Making a Scene</name>
</item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H3" charCount="548" wordCount="108" paraCount="3" cursorPos="465"/>
<name status="s90e6c9" import="ia857f0" active="True">Another Scene</name>
<meta expanded="False" charCount="548" wordCount="108" paraCount="3" cursorPos="465"/>
<name status="s90e6c9" import="ia857f0" exported="True">Another Scene</name>
</item>
<item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="310"/>
<name status="s78ea90" import="ia857f0" active="True">Interlude</name>
<meta expanded="False" charCount="617" wordCount="101" paraCount="3" cursorPos="310"/>
<name status="s78ea90" import="ia857f0" exported="True">Interlude</name>
</item>
<item handle="96b68994dfa3d" parent="7031beac91f75" root="7031beac91f75" order="5" type="FILE" class="NOVEL" layout="NOTE">
<meta expanded="False" heading="H1" charCount="1909" wordCount="346" paraCount="7" cursorPos="0"/>
<name status="sf24ce6" import="ia857f0" active="False">A Note on Structure</name>
<meta expanded="False" charCount="1909" wordCount="346" paraCount="7" cursorPos="0"/>
<name status="sf24ce6" import="ia857f0" exported="False">A Note on Structure</name>
</item>
<item handle="88706ddc78b1b" parent="7031beac91f75" root="7031beac91f75" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="True" heading="H2" charCount="139" wordCount="28" paraCount="1" cursorPos="188"/>
<name status="s90e6c9" import="ia857f0" active="True">Chapter Two</name>
<meta expanded="True" charCount="139" wordCount="28" paraCount="1" cursorPos="188"/>
<name status="s90e6c9" import="ia857f0" exported="True">Chapter Two</name>
</item>
<item handle="ae7339df26ded" parent="88706ddc78b1b" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H3" charCount="189" wordCount="37" paraCount="1" cursorPos="0"/>
<name status="s90e6c9" import="ia857f0" active="True">We Found John!</name>
<meta expanded="False" charCount="189" wordCount="37" paraCount="1" cursorPos="0"/>
<name status="s90e6c9" import="ia857f0" exported="True">We Found John!</name>
</item>
<item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL">
<meta expanded="True"/>
<name status="sf12341" import="ia857f0">Sequel</name>
</item>
<item handle="bacb7059e3083" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H1" charCount="27" wordCount="5" paraCount="1" cursorPos="100"/>
<name status="sc24b8f" import="ia857f0" active="True">Title Page</name>
<meta expanded="False" charCount="27" wordCount="5" paraCount="1" cursorPos="100"/>
<name status="sc24b8f" import="ia857f0" exported="True">Title Page</name>
</item>
<item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H2" charCount="299" wordCount="55" paraCount="2" cursorPos="104"/>
<name status="s90e6c9" import="ia857f0" active="True">Chapter One</name>
<meta expanded="False" charCount="299" wordCount="55" paraCount="2" cursorPos="104"/>
<name status="s90e6c9" import="ia857f0" exported="True">Chapter One</name>
</item>
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="2" type="ROOT" class="CHARACTER">
<meta expanded="True"/>
@@ -116,28 +115,28 @@
<name status="sf12341" import="ia857f0">Main Characters</name>
</item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="sf12341" import="icfb3a5" active="True">John Smith</name>
<meta expanded="False" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="sf12341" import="icfb3a5" exported="True">John Smith</name>
</item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="sf12341" import="i2d7a54" active="True">Jane Smith</name>
<meta expanded="False" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="sf12341" import="i2d7a54" exported="True">Jane Smith</name>
</item>
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD">
<meta expanded="True"/>
<name status="sf12341" import="ia857f0">Locations</name>
</item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H1" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="sf12341" import="i56be10" active="True">Earth</name>
<meta expanded="False" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="sf12341" import="i56be10" exported="True">Earth</name>
</item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="sf12341" import="icfb3a5" active="True">Space</name>
<meta expanded="False" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="sf12341" import="icfb3a5" exported="True">Space</name>
</item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H1" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="sf12341" import="i2d7a54" active="True">Mars</name>
<meta expanded="False" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="sf12341" import="i2d7a54" exported="True">Mars</name>
</item>
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="4" type="ROOT" class="ARCHIVE">
<meta expanded="True"/>
@@ -148,16 +147,16 @@
<name status="sf12341" import="ia857f0">Scenes</name>
</item>
<item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" root="6827118336ac1" order="0" type="FILE" class="ARCHIVE" layout="DOCUMENT">
<meta expanded="False" heading="H3" charCount="232" wordCount="42" paraCount="1" cursorPos="239"/>
<name status="s90e6c9" import="ia857f0" active="True">Old File</name>
<meta expanded="False" charCount="232" wordCount="42" paraCount="1" cursorPos="239"/>
<name status="s90e6c9" import="ia857f0" exported="True">Old File</name>
</item>
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="5" type="ROOT" class="TRASH">
<meta expanded="True"/>
<name status="sf12341" import="ia857f0">Trash</name>
</item>
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="98acd8c76c93a" order="0" type="FILE" class="TRASH" layout="DOCUMENT">
<meta expanded="False" heading="H3" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="sf12341" import="ia857f0" active="True">Delete Me!</name>
<meta expanded="False" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="sf12341" import="ia857f0" exported="True">Delete Me!</name>
</item>
</content>
</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'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-31 22:17:38">
<project>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 13:00:43">
<project id="5ac0df12-8b8b-476b-9905-21d33685687c" saveCount="38" autoCount="24" editTime="1900">
<name>Lorem Ipsum</name>
<title>Lorem Ipsum</title>
<author>lipsum.com</author>
<saveCount>32</saveCount>
<autoCount>24</autoCount>
<editTime>1889</editTime>
</project>
<settings>
<doBackup>False</doBackup>
<doBackup>no</doBackup>
<language>en_GB</language>
<spellCheck>False</spellCheck>
<spellLang>None</spellLang>
<totalWordCount>3847</totalWordCount>
<novelWordCount>3109</novelWordCount>
<notesWordCount>738</notesWordCount>
<spellChecking auto="no">None</spellChecking>
<lastHandle>
<entry key="editor">7a992350f3eb6</entry>
<entry key="viewer">None</entry>
@@ -46,90 +39,90 @@
<entry key="id6b1d0" count="0" red="50" green="200" blue="0">Main</entry>
</importance>
</settings>
<content count="21">
<content items="21" novelWords="3109" notesWords="738">
<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>
</item>
<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"/>
<name status="sedd043" import="i613591" active="True">Lorem Ipsum</name>
<meta expanded="no" heading="H1" charCount="230" wordCount="40" paraCount="3" cursorPos="148"/>
<name status="sedd043" import="i613591" active="yes">Lorem Ipsum</name>
</item>
<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"/>
<name status="sedd043" import="i613591" active="True">Front Matter</name>
<meta expanded="no" heading="H0" charCount="1058" wordCount="176" paraCount="2" cursorPos="43"/>
<name status="sedd043" import="i613591" active="yes">Front Matter</name>
</item>
<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"/>
<name status="s92a87b" import="i613591" active="True">Prologue</name>
<meta expanded="no" heading="H2" charCount="584" wordCount="92" paraCount="1" cursorPos="4"/>
<name status="s92a87b" import="i613591" active="yes">Prologue</name>
</item>
<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"/>
<name status="sbaa94f" import="i613591" active="True">Act One</name>
<meta expanded="no" heading="H1" charCount="35" wordCount="6" paraCount="1" cursorPos="42"/>
<name status="sbaa94f" import="i613591" active="yes">Act One</name>
</item>
<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>
</item>
<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"/>
<name status="s92a87b" import="i613591" active="True">Chapter One</name>
<meta expanded="no" heading="H2" charCount="419" wordCount="67" paraCount="1" cursorPos="56"/>
<name status="s92a87b" import="i613591" active="yes">Chapter One</name>
</item>
<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"/>
<name status="sedd043" import="i613591" active="True">Scene One</name>
<meta expanded="no" heading="H3" charCount="2758" wordCount="404" paraCount="4" cursorPos="1528"/>
<name status="sedd043" import="i613591" active="yes">Scene One</name>
</item>
<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"/>
<name status="sedd043" import="i613591" active="True">Scene Two</name>
<meta expanded="no" heading="H3" charCount="4043" wordCount="600" paraCount="6" cursorPos="2335"/>
<name status="sedd043" import="i613591" active="yes">Scene Two</name>
</item>
<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"/>
<name status="sbaa94f" import="i613591" active="False">Interlude</name>
<meta expanded="no" heading="H2" charCount="631" wordCount="109" paraCount="3" cursorPos="376"/>
<name status="sbaa94f" import="i613591" active="no">Interlude</name>
</item>
<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>
</item>
<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"/>
<name status="s92a87b" import="i613591" active="True">Chapter Two</name>
<meta expanded="no" heading="H2" charCount="477" wordCount="70" paraCount="1" cursorPos="56"/>
<name status="s92a87b" import="i613591" active="yes">Chapter Two</name>
</item>
<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"/>
<name status="sedd043" import="i613591" active="True">Scene Three</name>
<meta expanded="no" heading="H3" charCount="3006" wordCount="439" paraCount="4" cursorPos="57"/>
<name status="sedd043" import="i613591" active="yes">Scene Three</name>
</item>
<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"/>
<name status="sedd043" import="i613591" active="True">Scene Four</name>
<meta expanded="no" heading="H3" charCount="3839" wordCount="563" paraCount="6" cursorPos="56"/>
<name status="sedd043" import="i613591" active="yes">Scene Four</name>
</item>
<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"/>
<name status="sedd043" import="i613591" active="True">Scene Five</name>
<meta expanded="no" heading="H3" charCount="3644" wordCount="543" paraCount="5" cursorPos="351"/>
<name status="sedd043" import="i613591" active="yes">Scene Five</name>
</item>
<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>
</item>
<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"/>
<name status="sbaa94f" import="i613591" active="True">Mr. Nobody</name>
<meta expanded="no" heading="H1" charCount="1864" wordCount="284" paraCount="3" cursorPos="1883"/>
<name status="sbaa94f" import="i613591" active="yes">Mr. Nobody</name>
</item>
<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>
</item>
<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"/>
<name status="sbaa94f" import="i613591" active="True">Main</name>
<meta expanded="no" heading="H1" charCount="1369" wordCount="195" paraCount="2" cursorPos="1387"/>
<name status="sbaa94f" import="i613591" active="yes">Main</name>
</item>
<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>
</item>
<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"/>
<name status="sbaa94f" import="i613591" active="True">Ancient Europe</name>
<meta expanded="no" heading="H1" charCount="1770" wordCount="259" paraCount="3" cursorPos="1792"/>
<name status="sbaa94f" import="i613591" active="yes">Ancient Europe</name>
</item>
</content>
</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'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 11:53:22">
<project>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 13:05:22">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
<name>New Project</name>
<title>New Novel</title>
<author>Jane Doe</author>
<saveCount>2</saveCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<doBackup>yes</doBackup>
<language>None</language>
<spellCheck>False</spellCheck>
<spellLang>None</spellLang>
<novelWordCount>10</novelWordCount>
<notesWordCount>3</notesWordCount>
<spellChecking auto="no">None</spellChecking>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
@@ -42,50 +36,50 @@
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry>
</importance>
</settings>
<content count="11">
<content items="11" novelWords="10" notesWords="3">
<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>
</item>
<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>
</item>
<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>
</item>
<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>
</item>
<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"/>
<name status="s000000" import="i000004" active="True">Title Page</name>
<meta expanded="no" heading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Title Page</name>
</item>
<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>
</item>
<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"/>
<name status="s000000" import="i000004" active="True">New Chapter</name>
<meta expanded="no" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">New Chapter</name>
</item>
<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"/>
<name status="s000000" import="i000004" active="True">New Scene</name>
<meta expanded="no" heading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">New Scene</name>
</item>
<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>
</item>
<item handle="0000000000011" parent="0000000000010" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H2" charCount="5" wordCount="1" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Hello</name>
<meta expanded="no" heading="H2" charCount="5" wordCount="1" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Hello</name>
</item>
<item handle="0000000000012" parent="000000000000a" root="000000000000a" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H1" charCount="11" wordCount="3" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Jane</name>
<meta expanded="no" heading="H1" charCount="11" wordCount="3" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Jane</name>
</item>
</content>
</novelWriterXML>
@@ -1,20 +1,14 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 11:53:22">
<project>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 13:05:22">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
<name>New Project</name>
<title>New Novel</title>
<author>Jane Doe</author>
<saveCount>2</saveCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<doBackup>yes</doBackup>
<language>None</language>
<spellCheck>False</spellCheck>
<spellLang>None</spellLang>
<novelWordCount>9</novelWordCount>
<notesWordCount>0</notesWordCount>
<spellChecking auto="no">None</spellChecking>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
@@ -42,69 +36,69 @@
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry>
</importance>
</settings>
<content count="16">
<content items="16" novelWords="9" notesWords="0">
<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>
</item>
<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>
</item>
<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>
</item>
<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>
</item>
<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"/>
<name status="s000000" import="i000004" active="True">Title Page</name>
<meta expanded="no" heading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Title Page</name>
</item>
<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>
</item>
<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"/>
<name status="s000000" import="i000004" active="True">New Chapter</name>
<meta expanded="no" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">New Chapter</name>
</item>
<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"/>
<name status="s000000" import="i000004" active="True">New Scene</name>
<meta expanded="no" heading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">New Scene</name>
</item>
<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>
</item>
<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>
</item>
<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>
</item>
<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>
</item>
<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>
</item>
<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>
</item>
<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>
</item>
<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>
</item>
</content>
@@ -1,21 +1,15 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 11:53:22">
<project>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 13:05:22">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
<name>Test Custom</name>
<title>Test Novel</title>
<author>Jane Doe</author>
<author>John Doh</author>
<saveCount>1</saveCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<doBackup>yes</doBackup>
<language>None</language>
<spellCheck>False</spellCheck>
<spellLang>None</spellLang>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<spellChecking auto="no">None</spellChecking>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
@@ -43,93 +37,93 @@
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry>
</importance>
</settings>
<content count="22">
<content items="22" novelWords="0" notesWords="0">
<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>
</item>
<item handle="0000000000009" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Title Page</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Title Page</name>
</item>
<item handle="000000000000a" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Chapter 1</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Chapter 1</name>
</item>
<item handle="000000000000b" parent="000000000000a" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 1.1</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Scene 1.1</name>
</item>
<item handle="000000000000c" parent="000000000000a" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 1.2</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Scene 1.2</name>
</item>
<item handle="000000000000d" parent="000000000000a" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 1.3</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Scene 1.3</name>
</item>
<item handle="000000000000e" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Chapter 2</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Chapter 2</name>
</item>
<item handle="000000000000f" parent="000000000000e" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 2.1</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Scene 2.1</name>
</item>
<item handle="0000000000010" parent="000000000000e" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 2.2</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Scene 2.2</name>
</item>
<item handle="0000000000011" parent="000000000000e" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 2.3</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Scene 2.3</name>
</item>
<item handle="0000000000012" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Chapter 3</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Chapter 3</name>
</item>
<item handle="0000000000013" parent="0000000000012" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 3.1</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Scene 3.1</name>
</item>
<item handle="0000000000014" parent="0000000000012" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 3.2</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Scene 3.2</name>
</item>
<item handle="0000000000015" parent="0000000000012" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 3.3</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Scene 3.3</name>
</item>
<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>
</item>
<item handle="0000000000017" parent="0000000000016" root="0000000000016" order="0" type="FILE" class="PLOT" layout="NOTE">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Main Plot</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Main Plot</name>
</item>
<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>
</item>
<item handle="0000000000019" parent="0000000000018" root="0000000000018" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Protagonist</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Protagonist</name>
</item>
<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>
</item>
<item handle="000000000001b" parent="000000000001a" root="000000000001a" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Main Location</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Main Location</name>
</item>
<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>
</item>
<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>
</item>
</content>
@@ -1,21 +1,15 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 11:53:22">
<project>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 13:05:22">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
<name>Test Custom</name>
<title>Test Novel</title>
<author>Jane Doe</author>
<author>John Doh</author>
<saveCount>1</saveCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<doBackup>yes</doBackup>
<language>None</language>
<spellCheck>False</spellCheck>
<spellLang>None</spellLang>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<spellChecking auto="no">None</spellChecking>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
@@ -43,69 +37,69 @@
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry>
</importance>
</settings>
<content count="16">
<content items="16" novelWords="0" notesWords="0">
<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>
</item>
<item handle="0000000000009" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Title Page</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Title Page</name>
</item>
<item handle="000000000000a" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 1</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Scene 1</name>
</item>
<item handle="000000000000b" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 2</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Scene 2</name>
</item>
<item handle="000000000000c" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 3</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Scene 3</name>
</item>
<item handle="000000000000d" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 4</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Scene 4</name>
</item>
<item handle="000000000000e" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 5</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Scene 5</name>
</item>
<item handle="000000000000f" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Scene 6</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Scene 6</name>
</item>
<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>
</item>
<item handle="0000000000011" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="PLOT" layout="NOTE">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Main Plot</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Main Plot</name>
</item>
<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>
</item>
<item handle="0000000000013" parent="0000000000012" root="0000000000012" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Protagonist</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Protagonist</name>
</item>
<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>
</item>
<item handle="0000000000015" parent="0000000000014" root="0000000000014" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Main Location</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Main Location</name>
</item>
<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>
</item>
<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>
</item>
</content>
@@ -1,19 +1,13 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 11:53:22">
<project>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 13:05:22">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
<name>New Project</name>
<title>None</title>
<saveCount>2</saveCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
<title>New Project</title>
</project>
<settings>
<doBackup>True</doBackup>
<doBackup>yes</doBackup>
<language>None</language>
<spellCheck>False</spellCheck>
<spellLang>None</spellLang>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<spellChecking auto="no">None</spellChecking>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
@@ -41,37 +35,37 @@
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry>
</importance>
</settings>
<content count="8">
<content items="8" novelWords="0" notesWords="0">
<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>
</item>
<item handle="0000000000009" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Title Page</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Title Page</name>
</item>
<item handle="000000000000a" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">New Chapter</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">New Chapter</name>
</item>
<item handle="000000000000b" parent="000000000000a" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">New Scene</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">New Scene</name>
</item>
<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>
</item>
<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>
</item>
<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>
</item>
<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>
</item>
</content>
@@ -1,20 +1,14 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 12:01:37">
<project>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 12:58:26">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="4" autoCount="2" editTime="3">
<name>New Project</name>
<title>New Novel</title>
<author>Jane Doe</author>
<saveCount>4</saveCount>
<autoCount>2</autoCount>
<editTime>3</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<doBackup>yes</doBackup>
<language>None</language>
<spellCheck>True</spellCheck>
<spellLang>None</spellLang>
<novelWordCount>136</novelWordCount>
<notesWordCount>27</notesWordCount>
<spellChecking auto="yes">None</spellChecking>
<lastHandle>
<entry key="editor">000000000000f</entry>
<entry key="viewer">None</entry>
@@ -42,53 +36,53 @@
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry>
</importance>
</settings>
<content count="12">
<content items="12" novelWords="136" notesWords="27">
<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>
</item>
<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"/>
<name status="s000000" import="i000004" active="True">Title Page</name>
<meta expanded="no" heading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Title Page</name>
</item>
<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>
</item>
<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"/>
<name status="s000000" import="i000004" active="True">New Chapter</name>
<meta expanded="no" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">New Chapter</name>
</item>
<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"/>
<name status="s000000" import="i000004" active="True">New Scene</name>
<meta expanded="no" heading="H1" charCount="781" wordCount="129" paraCount="14" cursorPos="1010"/>
<name status="s000000" import="i000004" active="yes">New Scene</name>
</item>
<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>
</item>
<item handle="0000000000011" parent="0000000000009" root="0000000000009" order="0" type="FILE" class="PLOT" layout="NOTE">
<meta expanded="False" heading="H1" charCount="48" wordCount="10" paraCount="1" cursorPos="69"/>
<name status="s000000" import="i000004" active="True">New Note</name>
<meta expanded="no" heading="H1" charCount="48" wordCount="10" paraCount="1" cursorPos="69"/>
<name status="s000000" import="i000004" active="yes">New Note</name>
</item>
<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>
</item>
<item handle="0000000000010" parent="000000000000a" root="000000000000a" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H1" charCount="34" wordCount="8" paraCount="1" cursorPos="51"/>
<name status="s000000" import="i000004" active="True">New Note</name>
<meta expanded="no" heading="H1" charCount="34" wordCount="8" paraCount="1" cursorPos="51"/>
<name status="s000000" import="i000004" active="yes">New Note</name>
</item>
<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>
</item>
<item handle="0000000000012" parent="000000000000b" root="000000000000b" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H1" charCount="51" wordCount="9" paraCount="1" cursorPos="68"/>
<name status="s000000" import="i000004" active="True">New Note</name>
<meta expanded="no" heading="H1" charCount="51" wordCount="9" paraCount="1" cursorPos="68"/>
<name status="s000000" import="i000004" active="yes">New Note</name>
</item>
<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>
</item>
</content>
@@ -1,20 +1,14 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-11-01 11:53:30">
<project>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-11-07 12:57:21">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="2" autoCount="1" editTime="0">
<name>New Project</name>
<title>New Novel</title>
<author>Jane Doe</author>
<saveCount>2</saveCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<doBackup>yes</doBackup>
<language>None</language>
<spellCheck>False</spellCheck>
<spellLang>None</spellLang>
<novelWordCount>9</novelWordCount>
<notesWordCount>0</notesWordCount>
<spellChecking auto="no">None</spellChecking>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
@@ -42,37 +36,37 @@
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry>
</importance>
</settings>
<content count="8">
<content items="8" novelWords="9" notesWords="0">
<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>
</item>
<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"/>
<name status="s000000" import="i000004" active="True">Title Page</name>
<meta expanded="no" heading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">Title Page</name>
</item>
<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>
</item>
<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"/>
<name status="s000000" import="i000004" active="True">New Chapter</name>
<meta expanded="no" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">New Chapter</name>
</item>
<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"/>
<name status="s000000" import="i000004" active="True">New Scene</name>
<meta expanded="no" heading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="yes">New Scene</name>
</item>
<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>
</item>
<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>
</item>
<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>
</item>
</content>
File diff suppressed because it is too large Load Diff
+449 -317
View File
@@ -1,362 +1,494 @@
[
{
"handle": "7031beac91f75",
"parent": null,
"root": null,
"order": 0,
"heading": "H0",
"label": "Novel",
"type": "ROOT",
"class": "NOVEL",
"expanded": true,
"status": "s000002"
"name": "Novel",
"itemAttr": {
"handle": "7031beac91f75",
"parent": null,
"root": null,
"order": 0,
"type": "ROOT",
"class": "NOVEL"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000002"
}
},
{
"handle": "53b69b83cdafc",
"parent": "7031beac91f75",
"root": null,
"order": 0,
"heading": "H0",
"label": "Title Page",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 72,
"wordCount": 15,
"paraCount": 2,
"cursorPos": 78,
"status": "s000002"
"name": "Title Page",
"itemAttr": {
"handle": "53b69b83cdafc",
"parent": "7031beac91f75",
"root": null,
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"expanded": false,
"charCount": 72,
"wordCount": 15,
"paraCount": 2,
"cursorPos": 78
},
"nameAttr": {
"active": true,
"status": "s000002"
}
},
{
"handle": "974e400180a99",
"parent": "7031beac91f75",
"root": null,
"order": 1,
"heading": "H0",
"label": "Page",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 208,
"wordCount": 40,
"paraCount": 2,
"cursorPos": 213,
"status": "s000000"
"name": "Page",
"itemAttr": {
"handle": "974e400180a99",
"parent": "7031beac91f75",
"root": null,
"order": 1,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"expanded": false,
"charCount": 208,
"wordCount": 40,
"paraCount": 2,
"cursorPos": 213
},
"nameAttr": {
"active": true,
"status": "s000000"
}
},
{
"handle": "edca4be2fcaf8",
"parent": "7031beac91f75",
"root": null,
"order": 2,
"heading": "H0",
"label": "Part One",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 23,
"wordCount": 5,
"paraCount": 1,
"cursorPos": 0,
"status": "s000000"
"name": "Part One",
"itemAttr": {
"handle": "edca4be2fcaf8",
"parent": "7031beac91f75",
"root": null,
"order": 2,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"expanded": false,
"charCount": 23,
"wordCount": 5,
"paraCount": 1,
"cursorPos": 0
},
"nameAttr": {
"active": true,
"status": "s000000"
}
},
{
"handle": "e7ded148d6e4a",
"parent": "7031beac91f75",
"root": null,
"order": 3,
"heading": "H0",
"label": "A Folder",
"type": "FOLDER",
"class": "NOVEL",
"expanded": true,
"status": "s000003"
"name": "A Folder",
"itemAttr": {
"handle": "e7ded148d6e4a",
"parent": "7031beac91f75",
"root": null,
"order": 3,
"type": "FOLDER",
"class": "NOVEL"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000003"
}
},
{
"handle": "6a2d6d5f4f401",
"parent": "e7ded148d6e4a",
"root": null,
"order": 0,
"heading": "H0",
"label": "Chapter One",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 12,
"wordCount": 3,
"paraCount": 0,
"cursorPos": 215,
"status": "s000001"
"name": "Chapter One",
"itemAttr": {
"handle": "6a2d6d5f4f401",
"parent": "e7ded148d6e4a",
"root": null,
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"expanded": false,
"charCount": 12,
"wordCount": 3,
"paraCount": 0,
"cursorPos": 215
},
"nameAttr": {
"active": true,
"status": "s000001"
}
},
{
"handle": "636b6aa9b697b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 1,
"heading": "H0",
"label": "Making a Scene",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 1199,
"wordCount": 216,
"paraCount": 7,
"cursorPos": 527,
"status": "s000003"
"name": "Making a Scene",
"itemAttr": {
"handle": "636b6aa9b697b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 1,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"expanded": false,
"charCount": 1199,
"wordCount": 216,
"paraCount": 7,
"cursorPos": 527
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "bc0cbd2a407f3",
"parent": "e7ded148d6e4a",
"root": null,
"order": 2,
"heading": "H0",
"label": "Another Scene",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 476,
"wordCount": 93,
"paraCount": 3,
"cursorPos": 551,
"status": "s000003"
"name": "Another Scene",
"itemAttr": {
"handle": "bc0cbd2a407f3",
"parent": "e7ded148d6e4a",
"root": null,
"order": 2,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"expanded": false,
"charCount": 476,
"wordCount": 93,
"paraCount": 3,
"cursorPos": 551
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "ba8a28a246524",
"parent": "e7ded148d6e4a",
"root": null,
"order": 3,
"heading": "H0",
"label": "Interlude",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 633,
"wordCount": 101,
"paraCount": 3,
"cursorPos": 1238,
"status": "s000006"
"name": "Interlude",
"itemAttr": {
"handle": "ba8a28a246524",
"parent": "e7ded148d6e4a",
"root": null,
"order": 3,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"expanded": false,
"charCount": 633,
"wordCount": 101,
"paraCount": 3,
"cursorPos": 1238
},
"nameAttr": {
"active": true,
"status": "s000006"
}
},
{
"handle": "96b68994dfa3d",
"parent": "e7ded148d6e4a",
"root": null,
"order": 4,
"heading": "H0",
"label": "A Note on Structure",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": false,
"layout": "NOTE",
"charCount": 1692,
"wordCount": 313,
"paraCount": 6,
"cursorPos": 1721,
"status": "s000004"
"name": "A Note on Structure",
"itemAttr": {
"handle": "96b68994dfa3d",
"parent": "e7ded148d6e4a",
"root": null,
"order": 4,
"type": "FILE",
"class": "NOVEL",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"expanded": false,
"charCount": 1692,
"wordCount": 313,
"paraCount": 6,
"cursorPos": 1721
},
"nameAttr": {
"active": false,
"status": "s000004"
}
},
{
"handle": "88706ddc78b1b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 5,
"heading": "H0",
"label": "Chapter Two",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 139,
"wordCount": 28,
"paraCount": 1,
"cursorPos": 343,
"status": "s000003"
"name": "Chapter Two",
"itemAttr": {
"handle": "88706ddc78b1b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 5,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"expanded": false,
"charCount": 139,
"wordCount": 28,
"paraCount": 1,
"cursorPos": 343
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "ae7339df26ded",
"parent": "e7ded148d6e4a",
"root": null,
"order": 6,
"heading": "H0",
"label": "We Found John!",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 189,
"wordCount": 37,
"paraCount": 1,
"cursorPos": 224,
"status": "s000003"
"name": "We Found John!",
"itemAttr": {
"handle": "ae7339df26ded",
"parent": "e7ded148d6e4a",
"root": null,
"order": 6,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"expanded": false,
"charCount": 189,
"wordCount": 37,
"paraCount": 1,
"cursorPos": 224
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "f6622b4617424",
"parent": null,
"root": null,
"order": 1,
"heading": "H0",
"label": "Characters",
"type": "ROOT",
"class": "CHARACTER",
"expanded": true,
"import": null
"name": "Characters",
"itemAttr": {
"handle": "f6622b4617424",
"parent": null,
"root": null,
"order": 1,
"type": "ROOT",
"class": "CHARACTER"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
},
{
"handle": "f7e2d9f330615",
"parent": "f6622b4617424",
"root": null,
"order": 0,
"heading": "H0",
"label": "Main Characters",
"type": "FOLDER",
"class": "CHARACTER",
"expanded": true,
"import": null
"name": "Main Characters",
"itemAttr": {
"handle": "f7e2d9f330615",
"parent": "f6622b4617424",
"root": null,
"order": 0,
"type": "FOLDER",
"class": "CHARACTER"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
},
{
"handle": "14298de4d9524",
"parent": "f7e2d9f330615",
"root": null,
"order": 0,
"heading": "H0",
"label": "John Smith",
"type": "FILE",
"class": "CHARACTER",
"expanded": false,
"active": true,
"layout": "NOTE",
"charCount": 49,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 24,
"import": "i000008"
"name": "John Smith",
"itemAttr": {
"handle": "14298de4d9524",
"parent": "f7e2d9f330615",
"root": null,
"order": 0,
"type": "FILE",
"class": "CHARACTER",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"expanded": false,
"charCount": 49,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 24
},
"nameAttr": {
"active": true,
"import": "i000008"
}
},
{
"handle": "bb2c23b3c42cc",
"parent": "f7e2d9f330615",
"root": null,
"order": 1,
"heading": "H0",
"label": "Jane Smith",
"type": "FILE",
"class": "CHARACTER",
"expanded": false,
"active": true,
"layout": "NOTE",
"charCount": 55,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 25,
"import": "i000009"
"name": "Jane Smith",
"itemAttr": {
"handle": "bb2c23b3c42cc",
"parent": "f7e2d9f330615",
"root": null,
"order": 1,
"type": "FILE",
"class": "CHARACTER",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"expanded": false,
"charCount": 55,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 25
},
"nameAttr": {
"active": true,
"import": "i000009"
}
},
{
"handle": "15c4492bd5107",
"parent": null,
"root": null,
"order": 2,
"heading": "H0",
"label": "Locations",
"type": "ROOT",
"class": "WORLD",
"expanded": true,
"import": null
"name": "Locations",
"itemAttr": {
"handle": "15c4492bd5107",
"parent": null,
"root": null,
"order": 2,
"type": "ROOT",
"class": "WORLD"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
},
{
"handle": "b3e74dbc1f584",
"parent": "15c4492bd5107",
"root": null,
"order": 0,
"heading": "H0",
"label": "Earth",
"type": "FILE",
"class": "WORLD",
"expanded": false,
"active": true,
"layout": "NOTE",
"charCount": 76,
"wordCount": 15,
"paraCount": 1,
"cursorPos": 20,
"import": "i00000a"
"name": "Earth",
"itemAttr": {
"handle": "b3e74dbc1f584",
"parent": "15c4492bd5107",
"root": null,
"order": 0,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"expanded": false,
"charCount": 76,
"wordCount": 15,
"paraCount": 1,
"cursorPos": 20
},
"nameAttr": {
"active": true,
"import": "i00000a"
}
},
{
"handle": "f1471bef9f2ae",
"parent": "15c4492bd5107",
"root": null,
"order": 1,
"heading": "H0",
"label": "Space",
"type": "FILE",
"class": "WORLD",
"expanded": false,
"active": true,
"layout": "NOTE",
"charCount": 115,
"wordCount": 24,
"paraCount": 1,
"cursorPos": 133,
"import": "i000008"
"name": "Space",
"itemAttr": {
"handle": "f1471bef9f2ae",
"parent": "15c4492bd5107",
"root": null,
"order": 1,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"expanded": false,
"charCount": 115,
"wordCount": 24,
"paraCount": 1,
"cursorPos": 133
},
"nameAttr": {
"active": true,
"import": "i000008"
}
},
{
"handle": "5eaea4e8cdee8",
"parent": "15c4492bd5107",
"root": null,
"order": 2,
"heading": "H0",
"label": "Mars",
"type": "FILE",
"class": "WORLD",
"expanded": false,
"active": true,
"layout": "NOTE",
"charCount": 28,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 45,
"import": "i000009"
"name": "Mars",
"itemAttr": {
"handle": "5eaea4e8cdee8",
"parent": "15c4492bd5107",
"root": null,
"order": 2,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"expanded": false,
"charCount": 28,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 45
},
"nameAttr": {
"active": true,
"import": "i000009"
}
},
{
"handle": "98acd8c76c93a",
"parent": null,
"root": null,
"order": 3,
"heading": "H0",
"label": "Trash",
"type": "ROOT",
"class": "TRASH",
"expanded": true,
"import": null
"name": "Trash",
"itemAttr": {
"handle": "98acd8c76c93a",
"parent": null,
"root": null,
"order": 3,
"type": "ROOT",
"class": "TRASH"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
},
{
"handle": "b8136a5a774a0",
"parent": "98acd8c76c93a",
"root": null,
"order": 0,
"heading": "H0",
"label": "Delete Me!",
"type": "FILE",
"class": "NOVEL",
"expanded": false,
"active": true,
"layout": "DOCUMENT",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 36,
"status": "s000000"
"name": "Delete Me!",
"itemAttr": {
"handle": "b8136a5a774a0",
"parent": "98acd8c76c93a",
"root": null,
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"expanded": false,
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 36
},
"nameAttr": {
"active": true,
"status": "s000000"
}
}
]
]
+43 -49
View File
@@ -1,21 +1,15 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2020-05-28 09:59:15">
<project>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2020-05-28 09:59:15">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="0" autoCount="0" editTime="1000">
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>0</saveCount>
<autoCount>0</autoCount>
<editTime>1000</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<doBackup>yes</doBackup>
<language>None</language>
<spellCheck>True</spellCheck>
<spellLang>None</spellLang>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<spellChecking auto="yes">None</spellChecking>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
@@ -50,94 +44,94 @@
<entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry>
</importance>
</settings>
<content count="22">
<content items="22" novelWords="0" notesWords="0">
<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>
</item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="72" wordCount="15" paraCount="2" cursorPos="78"/>
<name status="s000002" import="i000007" active="True">Title Page</name>
<meta expanded="no" heading="H0" charCount="72" wordCount="15" paraCount="2" cursorPos="78"/>
<name status="s000002" import="i000007" active="yes">Title Page</name>
</item>
<item handle="974e400180a99" parent="7031beac91f75" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="208" wordCount="40" paraCount="2" cursorPos="213"/>
<name status="s000000" import="i000007" active="True">Page</name>
<meta expanded="no" heading="H0" charCount="208" wordCount="40" paraCount="2" cursorPos="213"/>
<name status="s000000" import="i000007" active="yes">Page</name>
</item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="23" wordCount="5" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000007" active="True">Part One</name>
<meta expanded="no" heading="H0" charCount="23" wordCount="5" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000007" active="yes">Part One</name>
</item>
<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>
</item>
<item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="12" wordCount="3" paraCount="0" cursorPos="215"/>
<name status="s000001" import="i000007" active="True">Chapter One</name>
<meta expanded="no" heading="H0" charCount="12" wordCount="3" paraCount="0" cursorPos="215"/>
<name status="s000001" import="i000007" active="yes">Chapter One</name>
</item>
<item handle="636b6aa9b697b" parent="e7ded148d6e4a" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="1199" wordCount="216" paraCount="7" cursorPos="527"/>
<name status="s000003" import="i000007" active="True">Making a Scene</name>
<meta expanded="no" heading="H0" charCount="1199" wordCount="216" paraCount="7" cursorPos="527"/>
<name status="s000003" import="i000007" active="yes">Making a Scene</name>
</item>
<item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="476" wordCount="93" paraCount="3" cursorPos="551"/>
<name status="s000003" import="i000007" active="True">Another Scene</name>
<meta expanded="no" heading="H0" charCount="476" wordCount="93" paraCount="3" cursorPos="551"/>
<name status="s000003" import="i000007" active="yes">Another Scene</name>
</item>
<item handle="ba8a28a246524" parent="e7ded148d6e4a" root="None" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="633" wordCount="101" paraCount="3" cursorPos="1238"/>
<name status="s000006" import="i000007" active="True">Interlude</name>
<meta expanded="no" heading="H0" charCount="633" wordCount="101" paraCount="3" cursorPos="1238"/>
<name status="s000006" import="i000007" active="yes">Interlude</name>
</item>
<item handle="96b68994dfa3d" parent="e7ded148d6e4a" root="None" order="4" type="FILE" class="NOVEL" layout="NOTE">
<meta expanded="False" heading="H0" charCount="1692" wordCount="313" paraCount="6" cursorPos="1721"/>
<name status="s000004" import="i000007" active="False">A Note on Structure</name>
<meta expanded="no" heading="H0" charCount="1692" wordCount="313" paraCount="6" cursorPos="1721"/>
<name status="s000004" import="i000007" active="no">A Note on Structure</name>
</item>
<item handle="88706ddc78b1b" parent="e7ded148d6e4a" root="None" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<name status="s000003" import="i000007" active="True">Chapter Two</name>
<meta expanded="no" heading="H0" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<name status="s000003" import="i000007" active="yes">Chapter Two</name>
</item>
<item handle="ae7339df26ded" parent="e7ded148d6e4a" root="None" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="s000003" import="i000007" active="True">We Found John!</name>
<meta expanded="no" heading="H0" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="s000003" import="i000007" active="yes">We Found John!</name>
</item>
<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>
</item>
<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>
</item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="None" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="s000000" import="i000008" active="True">John Smith</name>
<meta expanded="no" heading="H0" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="s000000" import="i000008" active="yes">John Smith</name>
</item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="None" order="1" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="s000000" import="i000009" active="True">Jane Smith</name>
<meta expanded="no" heading="H0" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="s000000" import="i000009" active="yes">Jane Smith</name>
</item>
<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>
</item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="None" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="s000000" import="i00000a" active="True">Earth</name>
<meta expanded="no" heading="H0" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="s000000" import="i00000a" active="yes">Earth</name>
</item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="None" order="1" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="s000000" import="i000008" active="True">Space</name>
<meta expanded="no" heading="H0" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="s000000" import="i000008" active="yes">Space</name>
</item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="None" order="2" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="s000000" import="i000009" active="True">Mars</name>
<meta expanded="no" heading="H0" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="s000000" import="i000009" active="yes">Mars</name>
</item>
<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>
</item>
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="36"/>
<name status="s000000" import="i000007" active="True">Delete Me!</name>
<meta expanded="no" heading="H0" charCount="0" wordCount="0" paraCount="0" cursorPos="36"/>
<name status="s000000" import="i000007" active="yes">Delete Me!</name>
</item>
</content>
</novelWriterXML>
+433 -301
View File
@@ -1,346 +1,478 @@
[
{
"handle": "7031beac91f75",
"parent": null,
"root": null,
"order": 0,
"heading": "H0",
"label": "Novel",
"type": "ROOT",
"class": "NOVEL",
"expanded": true,
"status": "s000002"
"name": "Novel",
"itemAttr": {
"handle": "7031beac91f75",
"parent": null,
"root": null,
"order": 0,
"type": "ROOT",
"class": "NOVEL"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000002"
}
},
{
"handle": "53b69b83cdafc",
"parent": "7031beac91f75",
"root": null,
"order": 0,
"heading": "H0",
"label": "Title Page",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 72,
"wordCount": 15,
"paraCount": 2,
"cursorPos": 78,
"status": "s000002"
"name": "Title Page",
"itemAttr": {
"handle": "53b69b83cdafc",
"parent": "7031beac91f75",
"root": null,
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 72,
"wordCount": 15,
"paraCount": 2,
"cursorPos": 78
},
"nameAttr": {
"active": true,
"status": "s000002"
}
},
{
"handle": "974e400180a99",
"parent": "7031beac91f75",
"root": null,
"order": 1,
"heading": "H0",
"label": "Page",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 210,
"wordCount": 40,
"paraCount": 2,
"cursorPos": 213,
"status": "s000000"
"name": "Page",
"itemAttr": {
"handle": "974e400180a99",
"parent": "7031beac91f75",
"root": null,
"order": 1,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 210,
"wordCount": 40,
"paraCount": 2,
"cursorPos": 213
},
"nameAttr": {
"active": true,
"status": "s000000"
}
},
{
"handle": "edca4be2fcaf8",
"parent": "7031beac91f75",
"root": null,
"order": 2,
"heading": "H0",
"label": "Part One",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 23,
"wordCount": 5,
"paraCount": 1,
"cursorPos": 0,
"status": "s000000"
"name": "Part One",
"itemAttr": {
"handle": "edca4be2fcaf8",
"parent": "7031beac91f75",
"root": null,
"order": 2,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 23,
"wordCount": 5,
"paraCount": 1,
"cursorPos": 0
},
"nameAttr": {
"active": true,
"status": "s000000"
}
},
{
"handle": "e7ded148d6e4a",
"parent": "7031beac91f75",
"root": null,
"order": 3,
"heading": "H0",
"label": "A Folder",
"type": "FOLDER",
"class": "NOVEL",
"expanded": true,
"status": "s000003"
"name": "A Folder",
"itemAttr": {
"handle": "e7ded148d6e4a",
"parent": "7031beac91f75",
"root": null,
"order": 3,
"type": "FOLDER",
"class": "NOVEL"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000003"
}
},
{
"handle": "6a2d6d5f4f401",
"parent": "e7ded148d6e4a",
"root": null,
"order": 0,
"heading": "H0",
"label": "Chapter One",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 12,
"wordCount": 3,
"paraCount": 0,
"cursorPos": 215,
"status": "s000001"
"name": "Chapter One",
"itemAttr": {
"handle": "6a2d6d5f4f401",
"parent": "e7ded148d6e4a",
"root": null,
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 12,
"wordCount": 3,
"paraCount": 0,
"cursorPos": 215
},
"nameAttr": {
"active": true,
"status": "s000001"
}
},
{
"handle": "636b6aa9b697b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 1,
"heading": "H0",
"label": "Making a Scene",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 1483,
"wordCount": 263,
"paraCount": 8,
"cursorPos": 1086,
"status": "s000003"
"name": "Making a Scene",
"itemAttr": {
"handle": "636b6aa9b697b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 1,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 1483,
"wordCount": 263,
"paraCount": 8,
"cursorPos": 1086
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "bc0cbd2a407f3",
"parent": "e7ded148d6e4a",
"root": null,
"order": 2,
"heading": "H0",
"label": "Another Scene",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 476,
"wordCount": 93,
"paraCount": 3,
"cursorPos": 428,
"status": "s000003"
"name": "Another Scene",
"itemAttr": {
"handle": "bc0cbd2a407f3",
"parent": "e7ded148d6e4a",
"root": null,
"order": 2,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 476,
"wordCount": 93,
"paraCount": 3,
"cursorPos": 428
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "ba8a28a246524",
"parent": "e7ded148d6e4a",
"root": null,
"order": 3,
"heading": "H0",
"label": "Interlude",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 633,
"wordCount": 101,
"paraCount": 3,
"cursorPos": 1238,
"status": "s000006"
"name": "Interlude",
"itemAttr": {
"handle": "ba8a28a246524",
"parent": "e7ded148d6e4a",
"root": null,
"order": 3,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 633,
"wordCount": 101,
"paraCount": 3,
"cursorPos": 1238
},
"nameAttr": {
"active": true,
"status": "s000006"
}
},
{
"handle": "96b68994dfa3d",
"parent": "e7ded148d6e4a",
"root": null,
"order": 4,
"heading": "H0",
"label": "A Note on Structure",
"type": "FILE",
"class": "NOVEL",
"active": false,
"layout": "NOTE",
"charCount": 1692,
"wordCount": 313,
"paraCount": 6,
"cursorPos": 1721,
"status": "s000004"
"name": "A Note on Structure",
"itemAttr": {
"handle": "96b68994dfa3d",
"parent": "e7ded148d6e4a",
"root": null,
"order": 4,
"type": "FILE",
"class": "NOVEL",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 1692,
"wordCount": 313,
"paraCount": 6,
"cursorPos": 1721
},
"nameAttr": {
"active": false,
"status": "s000004"
}
},
{
"handle": "88706ddc78b1b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 5,
"heading": "H0",
"label": "Chapter Two",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 139,
"wordCount": 28,
"paraCount": 1,
"cursorPos": 343,
"status": "s000003"
"name": "Chapter Two",
"itemAttr": {
"handle": "88706ddc78b1b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 5,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 139,
"wordCount": 28,
"paraCount": 1,
"cursorPos": 343
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "ae7339df26ded",
"parent": "e7ded148d6e4a",
"root": null,
"order": 6,
"heading": "H0",
"label": "We Found John!",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 189,
"wordCount": 37,
"paraCount": 1,
"cursorPos": 224,
"status": "s000003"
"name": "We Found John!",
"itemAttr": {
"handle": "ae7339df26ded",
"parent": "e7ded148d6e4a",
"root": null,
"order": 6,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 189,
"wordCount": 37,
"paraCount": 1,
"cursorPos": 224
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "f6622b4617424",
"parent": null,
"root": null,
"order": 1,
"heading": "H0",
"label": "Characters",
"type": "ROOT",
"class": "CHARACTER",
"expanded": true,
"import": null
"name": "Characters",
"itemAttr": {
"handle": "f6622b4617424",
"parent": null,
"root": null,
"order": 1,
"type": "ROOT",
"class": "CHARACTER"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
},
{
"handle": "f7e2d9f330615",
"parent": "f6622b4617424",
"root": null,
"order": 0,
"heading": "H0",
"label": "Main Characters",
"type": "FOLDER",
"class": "CHARACTER",
"expanded": true,
"import": null
"name": "Main Characters",
"itemAttr": {
"handle": "f7e2d9f330615",
"parent": "f6622b4617424",
"root": null,
"order": 0,
"type": "FOLDER",
"class": "CHARACTER"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
},
{
"handle": "14298de4d9524",
"parent": "f7e2d9f330615",
"root": null,
"order": 0,
"heading": "H0",
"label": "John Smith",
"type": "FILE",
"class": "CHARACTER",
"active": true,
"layout": "NOTE",
"charCount": 49,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 24,
"import": "i000008"
"name": "John Smith",
"itemAttr": {
"handle": "14298de4d9524",
"parent": "f7e2d9f330615",
"root": null,
"order": 0,
"type": "FILE",
"class": "CHARACTER",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 49,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 24
},
"nameAttr": {
"active": true,
"import": "i000008"
}
},
{
"handle": "bb2c23b3c42cc",
"parent": "f7e2d9f330615",
"root": null,
"order": 1,
"heading": "H0",
"label": "Jane Smith",
"type": "FILE",
"class": "CHARACTER",
"active": true,
"layout": "NOTE",
"charCount": 55,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 25,
"import": "i000009"
"name": "Jane Smith",
"itemAttr": {
"handle": "bb2c23b3c42cc",
"parent": "f7e2d9f330615",
"root": null,
"order": 1,
"type": "FILE",
"class": "CHARACTER",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 55,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 25
},
"nameAttr": {
"active": true,
"import": "i000009"
}
},
{
"handle": "15c4492bd5107",
"parent": null,
"root": null,
"order": 2,
"heading": "H0",
"label": "Locations",
"type": "ROOT",
"class": "WORLD",
"expanded": true,
"import": null
"name": "Locations",
"itemAttr": {
"handle": "15c4492bd5107",
"parent": null,
"root": null,
"order": 2,
"type": "ROOT",
"class": "WORLD"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
},
{
"handle": "b3e74dbc1f584",
"parent": "15c4492bd5107",
"root": null,
"order": 0,
"heading": "H0",
"label": "Earth",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 76,
"wordCount": 15,
"paraCount": 1,
"cursorPos": 20,
"import": "i00000a"
"name": "Earth",
"itemAttr": {
"handle": "b3e74dbc1f584",
"parent": "15c4492bd5107",
"root": null,
"order": 0,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 76,
"wordCount": 15,
"paraCount": 1,
"cursorPos": 20
},
"nameAttr": {
"active": true,
"import": "i00000a"
}
},
{
"handle": "f1471bef9f2ae",
"parent": "15c4492bd5107",
"root": null,
"order": 1,
"heading": "H0",
"label": "Space",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 115,
"wordCount": 24,
"paraCount": 1,
"cursorPos": 133,
"import": "i000008"
"name": "Space",
"itemAttr": {
"handle": "f1471bef9f2ae",
"parent": "15c4492bd5107",
"root": null,
"order": 1,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 115,
"wordCount": 24,
"paraCount": 1,
"cursorPos": 133
},
"nameAttr": {
"active": true,
"import": "i000008"
}
},
{
"handle": "5eaea4e8cdee8",
"parent": "15c4492bd5107",
"root": null,
"order": 2,
"heading": "H0",
"label": "Mars",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 28,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 45,
"import": "i000009"
"name": "Mars",
"itemAttr": {
"handle": "5eaea4e8cdee8",
"parent": "15c4492bd5107",
"root": null,
"order": 2,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 28,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 45
},
"nameAttr": {
"active": true,
"import": "i000009"
}
},
{
"handle": "98acd8c76c93a",
"parent": null,
"root": null,
"order": 3,
"heading": "H0",
"label": "Trash",
"type": "ROOT",
"class": "TRASH",
"expanded": true,
"import": null
"name": "Trash",
"itemAttr": {
"handle": "98acd8c76c93a",
"parent": null,
"root": null,
"order": 3,
"type": "ROOT",
"class": "TRASH"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
},
{
"handle": "b8136a5a774a0",
"parent": "98acd8c76c93a",
"root": null,
"order": 0,
"heading": "H0",
"label": "Delete Me!",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 30,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 36,
"status": "s000000"
"name": "Delete Me!",
"itemAttr": {
"handle": "b8136a5a774a0",
"parent": "98acd8c76c93a",
"root": null,
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 30,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 36
},
"nameAttr": {
"active": true,
"status": "s000000"
}
}
]
]
+43 -49
View File
@@ -1,21 +1,15 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2020-06-26 21:20:24">
<project>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2020-06-26 21:20:24">
<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>
<saveCount>5</saveCount>
<autoCount>10</autoCount>
<editTime>1000</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<doBackup>yes</doBackup>
<language>None</language>
<spellCheck>True</spellCheck>
<spellLang>None</spellLang>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<spellChecking auto="yes">None</spellChecking>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
@@ -50,94 +44,94 @@
<entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry>
</importance>
</settings>
<content count="22">
<content items="22" novelWords="0" notesWords="0">
<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>
</item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="72" wordCount="15" paraCount="2" cursorPos="78"/>
<name status="s000002" import="i000007" active="True">Title Page</name>
<meta expanded="no" heading="H0" charCount="72" wordCount="15" paraCount="2" cursorPos="78"/>
<name status="s000002" import="i000007" active="yes">Title Page</name>
</item>
<item handle="974e400180a99" parent="7031beac91f75" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="210" wordCount="40" paraCount="2" cursorPos="213"/>
<name status="s000000" import="i000007" active="True">Page</name>
<meta expanded="no" heading="H0" charCount="210" wordCount="40" paraCount="2" cursorPos="213"/>
<name status="s000000" import="i000007" active="yes">Page</name>
</item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="23" wordCount="5" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000007" active="True">Part One</name>
<meta expanded="no" heading="H0" charCount="23" wordCount="5" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000007" active="yes">Part One</name>
</item>
<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>
</item>
<item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="12" wordCount="3" paraCount="0" cursorPos="215"/>
<name status="s000001" import="i000007" active="True">Chapter One</name>
<meta expanded="no" heading="H0" charCount="12" wordCount="3" paraCount="0" cursorPos="215"/>
<name status="s000001" import="i000007" active="yes">Chapter One</name>
</item>
<item handle="636b6aa9b697b" parent="e7ded148d6e4a" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="1483" wordCount="263" paraCount="8" cursorPos="1086"/>
<name status="s000003" import="i000007" active="True">Making a Scene</name>
<meta expanded="no" heading="H0" charCount="1483" wordCount="263" paraCount="8" cursorPos="1086"/>
<name status="s000003" import="i000007" active="yes">Making a Scene</name>
</item>
<item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="476" wordCount="93" paraCount="3" cursorPos="428"/>
<name status="s000003" import="i000007" active="True">Another Scene</name>
<meta expanded="no" heading="H0" charCount="476" wordCount="93" paraCount="3" cursorPos="428"/>
<name status="s000003" import="i000007" active="yes">Another Scene</name>
</item>
<item handle="ba8a28a246524" parent="e7ded148d6e4a" root="None" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="633" wordCount="101" paraCount="3" cursorPos="1238"/>
<name status="s000006" import="i000007" active="True">Interlude</name>
<meta expanded="no" heading="H0" charCount="633" wordCount="101" paraCount="3" cursorPos="1238"/>
<name status="s000006" import="i000007" active="yes">Interlude</name>
</item>
<item handle="96b68994dfa3d" parent="e7ded148d6e4a" root="None" order="4" type="FILE" class="NOVEL" layout="NOTE">
<meta expanded="False" heading="H0" charCount="1692" wordCount="313" paraCount="6" cursorPos="1721"/>
<name status="s000004" import="i000007" active="False">A Note on Structure</name>
<meta expanded="no" heading="H0" charCount="1692" wordCount="313" paraCount="6" cursorPos="1721"/>
<name status="s000004" import="i000007" active="no">A Note on Structure</name>
</item>
<item handle="88706ddc78b1b" parent="e7ded148d6e4a" root="None" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<name status="s000003" import="i000007" active="True">Chapter Two</name>
<meta expanded="no" heading="H0" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<name status="s000003" import="i000007" active="yes">Chapter Two</name>
</item>
<item handle="ae7339df26ded" parent="e7ded148d6e4a" root="None" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="s000003" import="i000007" active="True">We Found John!</name>
<meta expanded="no" heading="H0" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="s000003" import="i000007" active="yes">We Found John!</name>
</item>
<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>
</item>
<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>
</item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="None" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="s000000" import="i000008" active="True">John Smith</name>
<meta expanded="no" heading="H0" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="s000000" import="i000008" active="yes">John Smith</name>
</item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="None" order="1" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="s000000" import="i000009" active="True">Jane Smith</name>
<meta expanded="no" heading="H0" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="s000000" import="i000009" active="yes">Jane Smith</name>
</item>
<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>
</item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="None" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="s000000" import="i00000a" active="True">Earth</name>
<meta expanded="no" heading="H0" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="s000000" import="i00000a" active="yes">Earth</name>
</item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="None" order="1" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="s000000" import="i000008" active="True">Space</name>
<meta expanded="no" heading="H0" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="s000000" import="i000008" active="yes">Space</name>
</item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="None" order="2" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="s000000" import="i000009" active="True">Mars</name>
<meta expanded="no" heading="H0" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="s000000" import="i000009" active="yes">Mars</name>
</item>
<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>
</item>
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s000000" import="i000007" active="True">Delete Me!</name>
<meta expanded="no" heading="H0" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s000000" import="i000007" active="yes">Delete Me!</name>
</item>
</content>
</novelWriterXML>
+486 -336
View File
@@ -1,387 +1,537 @@
[
{
"handle": "7031beac91f75",
"parent": null,
"root": null,
"order": 0,
"heading": "H0",
"label": "Novel",
"type": "ROOT",
"class": "NOVEL",
"expanded": true,
"status": "s000002"
"name": "Novel",
"itemAttr": {
"handle": "7031beac91f75",
"parent": null,
"root": null,
"order": 0,
"type": "ROOT",
"class": "NOVEL"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000002"
}
},
{
"handle": "53b69b83cdafc",
"parent": "7031beac91f75",
"root": null,
"order": 0,
"heading": "H0",
"label": "Title Page",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 241,
"wordCount": 42,
"paraCount": 3,
"cursorPos": 252,
"status": "s000002"
"name": "Title Page",
"itemAttr": {
"handle": "53b69b83cdafc",
"parent": "7031beac91f75",
"root": null,
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 241,
"wordCount": 42,
"paraCount": 3,
"cursorPos": 252
},
"nameAttr": {
"active": true,
"status": "s000002"
}
},
{
"handle": "974e400180a99",
"parent": "7031beac91f75",
"root": null,
"order": 1,
"heading": "H0",
"label": "Page",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 125,
"wordCount": 26,
"paraCount": 2,
"cursorPos": 127,
"status": "s000000"
"name": "Page",
"itemAttr": {
"handle": "974e400180a99",
"parent": "7031beac91f75",
"root": null,
"order": 1,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 125,
"wordCount": 26,
"paraCount": 2,
"cursorPos": 127
},
"nameAttr": {
"active": true,
"status": "s000000"
}
},
{
"handle": "edca4be2fcaf8",
"parent": "7031beac91f75",
"root": null,
"order": 2,
"heading": "H0",
"label": "Part One",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 26,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 30,
"status": "s000000"
"name": "Part One",
"itemAttr": {
"handle": "edca4be2fcaf8",
"parent": "7031beac91f75",
"root": null,
"order": 2,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 26,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 30
},
"nameAttr": {
"active": true,
"status": "s000000"
}
},
{
"handle": "e7ded148d6e4a",
"parent": "7031beac91f75",
"root": null,
"order": 3,
"heading": "H0",
"label": "A Folder",
"type": "FOLDER",
"class": "NOVEL",
"expanded": true,
"status": "s000003"
"name": "A Folder",
"itemAttr": {
"handle": "e7ded148d6e4a",
"parent": "7031beac91f75",
"root": null,
"order": 3,
"type": "FOLDER",
"class": "NOVEL"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000003"
}
},
{
"handle": "6a2d6d5f4f401",
"parent": "e7ded148d6e4a",
"root": null,
"order": 0,
"heading": "H0",
"label": "Chapter One",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 75,
"wordCount": 14,
"paraCount": 1,
"cursorPos": 279,
"status": "s000001"
"name": "Chapter One",
"itemAttr": {
"handle": "6a2d6d5f4f401",
"parent": "e7ded148d6e4a",
"root": null,
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 75,
"wordCount": 14,
"paraCount": 1,
"cursorPos": 279
},
"nameAttr": {
"active": true,
"status": "s000001"
}
},
{
"handle": "636b6aa9b697b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 1,
"heading": "H0",
"label": "Making a Scene",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 2429,
"wordCount": 432,
"paraCount": 14,
"cursorPos": 61,
"status": "s000003"
"name": "Making a Scene",
"itemAttr": {
"handle": "636b6aa9b697b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 1,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 2429,
"wordCount": 432,
"paraCount": 14,
"cursorPos": 61
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "bc0cbd2a407f3",
"parent": "e7ded148d6e4a",
"root": null,
"order": 2,
"heading": "H0",
"label": "Another Scene",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 476,
"wordCount": 93,
"paraCount": 3,
"cursorPos": 577,
"status": "s000003"
"name": "Another Scene",
"itemAttr": {
"handle": "bc0cbd2a407f3",
"parent": "e7ded148d6e4a",
"root": null,
"order": 2,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 476,
"wordCount": 93,
"paraCount": 3,
"cursorPos": 577
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "ba8a28a246524",
"parent": "e7ded148d6e4a",
"root": null,
"order": 3,
"heading": "H0",
"label": "Interlude",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 617,
"wordCount": 101,
"paraCount": 3,
"cursorPos": 1137,
"status": "s000000"
"name": "Interlude",
"itemAttr": {
"handle": "ba8a28a246524",
"parent": "e7ded148d6e4a",
"root": null,
"order": 3,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 617,
"wordCount": 101,
"paraCount": 3,
"cursorPos": 1137
},
"nameAttr": {
"active": true,
"status": "s000000"
}
},
{
"handle": "96b68994dfa3d",
"parent": "e7ded148d6e4a",
"root": null,
"order": 4,
"heading": "H0",
"label": "A Note on Structure",
"type": "FILE",
"class": "NOVEL",
"active": false,
"layout": "NOTE",
"charCount": 1692,
"wordCount": 313,
"paraCount": 6,
"cursorPos": 1110,
"status": "s000004"
"name": "A Note on Structure",
"itemAttr": {
"handle": "96b68994dfa3d",
"parent": "e7ded148d6e4a",
"root": null,
"order": 4,
"type": "FILE",
"class": "NOVEL",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 1692,
"wordCount": 313,
"paraCount": 6,
"cursorPos": 1110
},
"nameAttr": {
"active": false,
"status": "s000004"
}
},
{
"handle": "88706ddc78b1b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 5,
"heading": "H0",
"label": "Chapter Two",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 139,
"wordCount": 28,
"paraCount": 1,
"cursorPos": 343,
"status": "s000003"
"name": "Chapter Two",
"itemAttr": {
"handle": "88706ddc78b1b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 5,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 139,
"wordCount": 28,
"paraCount": 1,
"cursorPos": 343
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "ae7339df26ded",
"parent": "e7ded148d6e4a",
"root": null,
"order": 6,
"heading": "H0",
"label": "We Found John!",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 189,
"wordCount": 37,
"paraCount": 1,
"cursorPos": 224,
"status": "s000003"
"name": "We Found John!",
"itemAttr": {
"handle": "ae7339df26ded",
"parent": "e7ded148d6e4a",
"root": null,
"order": 6,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 189,
"wordCount": 37,
"paraCount": 1,
"cursorPos": 224
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "f6622b4617424",
"parent": null,
"root": null,
"order": 1,
"heading": "H0",
"label": "Characters",
"type": "ROOT",
"class": "CHARACTER",
"expanded": true,
"import": null
"name": "Characters",
"itemAttr": {
"handle": "f6622b4617424",
"parent": null,
"root": null,
"order": 1,
"type": "ROOT",
"class": "CHARACTER"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
},
{
"handle": "f7e2d9f330615",
"parent": "f6622b4617424",
"root": null,
"order": 0,
"heading": "H0",
"label": "Main Characters",
"type": "FOLDER",
"class": "CHARACTER",
"expanded": true,
"import": null
"name": "Main Characters",
"itemAttr": {
"handle": "f7e2d9f330615",
"parent": "f6622b4617424",
"root": null,
"order": 0,
"type": "FOLDER",
"class": "CHARACTER"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
},
{
"handle": "14298de4d9524",
"parent": "f7e2d9f330615",
"root": null,
"order": 0,
"heading": "H0",
"label": "John Smith",
"type": "FILE",
"class": "CHARACTER",
"active": true,
"layout": "NOTE",
"charCount": 49,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 24,
"import": "i000008"
"name": "John Smith",
"itemAttr": {
"handle": "14298de4d9524",
"parent": "f7e2d9f330615",
"root": null,
"order": 0,
"type": "FILE",
"class": "CHARACTER",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 49,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 24
},
"nameAttr": {
"active": true,
"import": "i000008"
}
},
{
"handle": "bb2c23b3c42cc",
"parent": "f7e2d9f330615",
"root": null,
"order": 1,
"heading": "H0",
"label": "Jane Smith",
"type": "FILE",
"class": "CHARACTER",
"active": true,
"layout": "NOTE",
"charCount": 55,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 25,
"import": "i000009"
"name": "Jane Smith",
"itemAttr": {
"handle": "bb2c23b3c42cc",
"parent": "f7e2d9f330615",
"root": null,
"order": 1,
"type": "FILE",
"class": "CHARACTER",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 55,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 25
},
"nameAttr": {
"active": true,
"import": "i000009"
}
},
{
"handle": "15c4492bd5107",
"parent": null,
"root": null,
"order": 2,
"heading": "H0",
"label": "Locations",
"type": "ROOT",
"class": "WORLD",
"expanded": true,
"import": null
"name": "Locations",
"itemAttr": {
"handle": "15c4492bd5107",
"parent": null,
"root": null,
"order": 2,
"type": "ROOT",
"class": "WORLD"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
},
{
"handle": "b3e74dbc1f584",
"parent": "15c4492bd5107",
"root": null,
"order": 0,
"heading": "H0",
"label": "Earth",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 76,
"wordCount": 15,
"paraCount": 1,
"cursorPos": 20,
"import": "i00000a"
"name": "Earth",
"itemAttr": {
"handle": "b3e74dbc1f584",
"parent": "15c4492bd5107",
"root": null,
"order": 0,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 76,
"wordCount": 15,
"paraCount": 1,
"cursorPos": 20
},
"nameAttr": {
"active": true,
"import": "i00000a"
}
},
{
"handle": "f1471bef9f2ae",
"parent": "15c4492bd5107",
"root": null,
"order": 1,
"heading": "H0",
"label": "Space",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 115,
"wordCount": 24,
"paraCount": 1,
"cursorPos": 133,
"import": "i000008"
"name": "Space",
"itemAttr": {
"handle": "f1471bef9f2ae",
"parent": "15c4492bd5107",
"root": null,
"order": 1,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 115,
"wordCount": 24,
"paraCount": 1,
"cursorPos": 133
},
"nameAttr": {
"active": true,
"import": "i000008"
}
},
{
"handle": "5eaea4e8cdee8",
"parent": "15c4492bd5107",
"root": null,
"order": 2,
"heading": "H0",
"label": "Mars",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 28,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 45,
"import": "i000009"
"name": "Mars",
"itemAttr": {
"handle": "5eaea4e8cdee8",
"parent": "15c4492bd5107",
"root": null,
"order": 2,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 28,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 45
},
"nameAttr": {
"active": true,
"import": "i000009"
}
},
{
"handle": "6827118336ac1",
"parent": null,
"root": null,
"order": 3,
"heading": "H0",
"label": "Outtakes",
"type": "ROOT",
"class": "ARCHIVE",
"expanded": true,
"status": null
"name": "Outtakes",
"itemAttr": {
"handle": "6827118336ac1",
"parent": null,
"root": null,
"order": 3,
"type": "ROOT",
"class": "ARCHIVE"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": null
}
},
{
"handle": "ae9bf3c3ea159",
"parent": "6827118336ac1",
"root": null,
"order": 0,
"heading": "H0",
"label": "Scenes",
"type": "FOLDER",
"class": "ARCHIVE",
"expanded": true,
"status": null
"name": "Scenes",
"itemAttr": {
"handle": "ae9bf3c3ea159",
"parent": "6827118336ac1",
"root": null,
"order": 0,
"type": "FOLDER",
"class": "ARCHIVE"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": null
}
},
{
"handle": "8a5deb88c0e97",
"parent": "ae9bf3c3ea159",
"root": null,
"order": 0,
"heading": "H0",
"label": "Old File",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 315,
"wordCount": 55,
"paraCount": 1,
"cursorPos": 322,
"status": "s000003"
"name": "Old File",
"itemAttr": {
"handle": "8a5deb88c0e97",
"parent": "ae9bf3c3ea159",
"root": null,
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 315,
"wordCount": 55,
"paraCount": 1,
"cursorPos": 322
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "98acd8c76c93a",
"parent": null,
"root": null,
"order": 4,
"heading": "H0",
"label": "Trash",
"type": "ROOT",
"class": "TRASH",
"expanded": true,
"import": null
"name": "Trash",
"itemAttr": {
"handle": "98acd8c76c93a",
"parent": null,
"root": null,
"order": 4,
"type": "ROOT",
"class": "TRASH"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
},
{
"handle": "b8136a5a774a0",
"parent": "98acd8c76c93a",
"root": null,
"order": 0,
"heading": "H0",
"label": "Delete Me!",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 30,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 36,
"status": "s000000"
"name": "Delete Me!",
"itemAttr": {
"handle": "b8136a5a774a0",
"parent": "98acd8c76c93a",
"root": null,
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 30,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 36
},
"nameAttr": {
"active": true,
"status": "s000000"
}
}
]
]
+47 -53
View File
@@ -1,21 +1,15 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2021-08-30 23:33:44">
<project>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2021-08-30 23:33:44">
<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>
<saveCount>5</saveCount>
<autoCount>10</autoCount>
<editTime>1000</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<doBackup>yes</doBackup>
<language>en_GB</language>
<spellCheck>True</spellCheck>
<spellLang>en_GB</spellLang>
<novelWordCount>840</novelWordCount>
<notesWordCount>376</notesWordCount>
<spellChecking auto="yes">en_GB</spellChecking>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
@@ -50,106 +44,106 @@
<entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry>
</importance>
</settings>
<content count="25">
<content items="25" novelWords="840" notesWords="376">
<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>
</item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="241" wordCount="42" paraCount="3" cursorPos="252"/>
<name status="s000002" import="i000007" active="True">Title Page</name>
<meta expanded="no" heading="H0" charCount="241" wordCount="42" paraCount="3" cursorPos="252"/>
<name status="s000002" import="i000007" active="yes">Title Page</name>
</item>
<item handle="974e400180a99" parent="7031beac91f75" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="125" wordCount="26" paraCount="2" cursorPos="127"/>
<name status="s000000" import="i000007" active="True">Page</name>
<meta expanded="no" heading="H0" charCount="125" wordCount="26" paraCount="2" cursorPos="127"/>
<name status="s000000" import="i000007" active="yes">Page</name>
</item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="26" wordCount="6" paraCount="1" cursorPos="30"/>
<name status="s000000" import="i000007" active="True">Part One</name>
<meta expanded="no" heading="H0" charCount="26" wordCount="6" paraCount="1" cursorPos="30"/>
<name status="s000000" import="i000007" active="yes">Part One</name>
</item>
<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>
</item>
<item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="75" wordCount="14" paraCount="1" cursorPos="279"/>
<name status="s000001" import="i000007" active="True">Chapter One</name>
<meta expanded="no" heading="H0" charCount="75" wordCount="14" paraCount="1" cursorPos="279"/>
<name status="s000001" import="i000007" active="yes">Chapter One</name>
</item>
<item handle="636b6aa9b697b" parent="e7ded148d6e4a" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="2429" wordCount="432" paraCount="14" cursorPos="61"/>
<name status="s000003" import="i000007" active="True">Making a Scene</name>
<meta expanded="no" heading="H0" charCount="2429" wordCount="432" paraCount="14" cursorPos="61"/>
<name status="s000003" import="i000007" active="yes">Making a Scene</name>
</item>
<item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="476" wordCount="93" paraCount="3" cursorPos="577"/>
<name status="s000003" import="i000007" active="True">Another Scene</name>
<meta expanded="no" heading="H0" charCount="476" wordCount="93" paraCount="3" cursorPos="577"/>
<name status="s000003" import="i000007" active="yes">Another Scene</name>
</item>
<item handle="ba8a28a246524" parent="e7ded148d6e4a" root="None" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="617" wordCount="101" paraCount="3" cursorPos="1137"/>
<name status="s000000" import="i000007" active="True">Interlude</name>
<meta expanded="no" heading="H0" charCount="617" wordCount="101" paraCount="3" cursorPos="1137"/>
<name status="s000000" import="i000007" active="yes">Interlude</name>
</item>
<item handle="96b68994dfa3d" parent="e7ded148d6e4a" root="None" order="4" type="FILE" class="NOVEL" layout="NOTE">
<meta expanded="False" heading="H0" charCount="1692" wordCount="313" paraCount="6" cursorPos="1110"/>
<name status="s000004" import="i000007" active="False">A Note on Structure</name>
<meta expanded="no" heading="H0" charCount="1692" wordCount="313" paraCount="6" cursorPos="1110"/>
<name status="s000004" import="i000007" active="no">A Note on Structure</name>
</item>
<item handle="88706ddc78b1b" parent="e7ded148d6e4a" root="None" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<name status="s000003" import="i000007" active="True">Chapter Two</name>
<meta expanded="no" heading="H0" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<name status="s000003" import="i000007" active="yes">Chapter Two</name>
</item>
<item handle="ae7339df26ded" parent="e7ded148d6e4a" root="None" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="s000003" import="i000007" active="True">We Found John!</name>
<meta expanded="no" heading="H0" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="s000003" import="i000007" active="yes">We Found John!</name>
</item>
<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>
</item>
<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>
</item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="None" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="s000000" import="i000008" active="True">John Smith</name>
<meta expanded="no" heading="H0" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="s000000" import="i000008" active="yes">John Smith</name>
</item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="None" order="1" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="s000000" import="i000009" active="True">Jane Smith</name>
<meta expanded="no" heading="H0" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="s000000" import="i000009" active="yes">Jane Smith</name>
</item>
<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>
</item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="None" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="s000000" import="i00000a" active="True">Earth</name>
<meta expanded="no" heading="H0" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="s000000" import="i00000a" active="yes">Earth</name>
</item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="None" order="1" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="s000000" import="i000008" active="True">Space</name>
<meta expanded="no" heading="H0" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="s000000" import="i000008" active="yes">Space</name>
</item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="None" order="2" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="s000000" import="i000009" active="True">Mars</name>
<meta expanded="no" heading="H0" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="s000000" import="i000009" active="yes">Mars</name>
</item>
<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>
</item>
<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>
</item>
<item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="315" wordCount="55" paraCount="1" cursorPos="322"/>
<name status="s000003" import="i000007" active="True">Old File</name>
<meta expanded="no" heading="H0" charCount="315" wordCount="55" paraCount="1" cursorPos="322"/>
<name status="s000003" import="i000007" active="yes">Old File</name>
</item>
<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>
</item>
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s000000" import="i000007" active="True">Delete Me!</name>
<meta expanded="no" heading="H0" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s000000" import="i000007" active="yes">Delete Me!</name>
</item>
</content>
</novelWriterXML>
+486 -336
View File
@@ -1,387 +1,537 @@
[
{
"handle": "7031beac91f75",
"parent": null,
"root": null,
"order": 0,
"heading": "H0",
"label": "Novel",
"type": "ROOT",
"class": "NOVEL",
"expanded": true,
"status": "s000002"
"name": "Novel",
"itemAttr": {
"handle": "7031beac91f75",
"parent": null,
"root": null,
"order": 0,
"type": "ROOT",
"class": "NOVEL"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000002"
}
},
{
"handle": "53b69b83cdafc",
"parent": "7031beac91f75",
"root": null,
"order": 0,
"heading": "H0",
"label": "Title Page",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 93,
"wordCount": 19,
"paraCount": 2,
"cursorPos": 2,
"status": "s000002"
"name": "Title Page",
"itemAttr": {
"handle": "53b69b83cdafc",
"parent": "7031beac91f75",
"root": null,
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 93,
"wordCount": 19,
"paraCount": 2,
"cursorPos": 2
},
"nameAttr": {
"active": true,
"status": "s000002"
}
},
{
"handle": "974e400180a99",
"parent": "7031beac91f75",
"root": null,
"order": 1,
"heading": "H0",
"label": "Page",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 186,
"wordCount": 39,
"paraCount": 2,
"cursorPos": 212,
"status": "s000000"
"name": "Page",
"itemAttr": {
"handle": "974e400180a99",
"parent": "7031beac91f75",
"root": null,
"order": 1,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 186,
"wordCount": 39,
"paraCount": 2,
"cursorPos": 212
},
"nameAttr": {
"active": true,
"status": "s000000"
}
},
{
"handle": "edca4be2fcaf8",
"parent": "7031beac91f75",
"root": null,
"order": 2,
"heading": "H0",
"label": "Part One",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 26,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 33,
"status": "s000000"
"name": "Part One",
"itemAttr": {
"handle": "edca4be2fcaf8",
"parent": "7031beac91f75",
"root": null,
"order": 2,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 26,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 33
},
"nameAttr": {
"active": true,
"status": "s000000"
}
},
{
"handle": "e7ded148d6e4a",
"parent": "7031beac91f75",
"root": null,
"order": 3,
"heading": "H0",
"label": "A Folder",
"type": "FOLDER",
"class": "NOVEL",
"expanded": true,
"status": "s000003"
"name": "A Folder",
"itemAttr": {
"handle": "e7ded148d6e4a",
"parent": "7031beac91f75",
"root": null,
"order": 3,
"type": "FOLDER",
"class": "NOVEL"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000003"
}
},
{
"handle": "6a2d6d5f4f401",
"parent": "e7ded148d6e4a",
"root": null,
"order": 0,
"heading": "H0",
"label": "Chapter One",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 75,
"wordCount": 14,
"paraCount": 1,
"cursorPos": 279,
"status": "s000001"
"name": "Chapter One",
"itemAttr": {
"handle": "6a2d6d5f4f401",
"parent": "e7ded148d6e4a",
"root": null,
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 75,
"wordCount": 14,
"paraCount": 1,
"cursorPos": 279
},
"nameAttr": {
"active": true,
"status": "s000001"
}
},
{
"handle": "636b6aa9b697b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 1,
"heading": "H0",
"label": "Making a Scene",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 2429,
"wordCount": 432,
"paraCount": 14,
"cursorPos": 62,
"status": "s000003"
"name": "Making a Scene",
"itemAttr": {
"handle": "636b6aa9b697b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 1,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 2429,
"wordCount": 432,
"paraCount": 14,
"cursorPos": 62
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "bc0cbd2a407f3",
"parent": "e7ded148d6e4a",
"root": null,
"order": 2,
"heading": "H0",
"label": "Another Scene",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 476,
"wordCount": 93,
"paraCount": 3,
"cursorPos": 577,
"status": "s000003"
"name": "Another Scene",
"itemAttr": {
"handle": "bc0cbd2a407f3",
"parent": "e7ded148d6e4a",
"root": null,
"order": 2,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 476,
"wordCount": 93,
"paraCount": 3,
"cursorPos": 577
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "ba8a28a246524",
"parent": "e7ded148d6e4a",
"root": null,
"order": 3,
"heading": "H0",
"label": "Interlude",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 617,
"wordCount": 101,
"paraCount": 3,
"cursorPos": 4,
"status": "s000000"
"name": "Interlude",
"itemAttr": {
"handle": "ba8a28a246524",
"parent": "e7ded148d6e4a",
"root": null,
"order": 3,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 617,
"wordCount": 101,
"paraCount": 3,
"cursorPos": 4
},
"nameAttr": {
"active": true,
"status": "s000000"
}
},
{
"handle": "96b68994dfa3d",
"parent": "e7ded148d6e4a",
"root": null,
"order": 4,
"heading": "H0",
"label": "A Note on Structure",
"type": "FILE",
"class": "NOVEL",
"active": false,
"layout": "NOTE",
"charCount": 1692,
"wordCount": 313,
"paraCount": 6,
"cursorPos": 1110,
"status": "s000004"
"name": "A Note on Structure",
"itemAttr": {
"handle": "96b68994dfa3d",
"parent": "e7ded148d6e4a",
"root": null,
"order": 4,
"type": "FILE",
"class": "NOVEL",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 1692,
"wordCount": 313,
"paraCount": 6,
"cursorPos": 1110
},
"nameAttr": {
"active": false,
"status": "s000004"
}
},
{
"handle": "88706ddc78b1b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 5,
"heading": "H0",
"label": "Chapter Two",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 139,
"wordCount": 28,
"paraCount": 1,
"cursorPos": 343,
"status": "s000003"
"name": "Chapter Two",
"itemAttr": {
"handle": "88706ddc78b1b",
"parent": "e7ded148d6e4a",
"root": null,
"order": 5,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 139,
"wordCount": 28,
"paraCount": 1,
"cursorPos": 343
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "ae7339df26ded",
"parent": "e7ded148d6e4a",
"root": null,
"order": 6,
"heading": "H0",
"label": "We Found John!",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 189,
"wordCount": 37,
"paraCount": 1,
"cursorPos": 224,
"status": "s000003"
"name": "We Found John!",
"itemAttr": {
"handle": "ae7339df26ded",
"parent": "e7ded148d6e4a",
"root": null,
"order": 6,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 189,
"wordCount": 37,
"paraCount": 1,
"cursorPos": 224
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "f6622b4617424",
"parent": null,
"root": null,
"order": 1,
"heading": "H0",
"label": "Characters",
"type": "ROOT",
"class": "CHARACTER",
"expanded": true,
"import": null
"name": "Characters",
"itemAttr": {
"handle": "f6622b4617424",
"parent": null,
"root": null,
"order": 1,
"type": "ROOT",
"class": "CHARACTER"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
},
{
"handle": "f7e2d9f330615",
"parent": "f6622b4617424",
"root": null,
"order": 0,
"heading": "H0",
"label": "Main Characters",
"type": "FOLDER",
"class": "CHARACTER",
"expanded": true,
"import": null
"name": "Main Characters",
"itemAttr": {
"handle": "f7e2d9f330615",
"parent": "f6622b4617424",
"root": null,
"order": 0,
"type": "FOLDER",
"class": "CHARACTER"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
},
{
"handle": "14298de4d9524",
"parent": "f7e2d9f330615",
"root": null,
"order": 0,
"heading": "H0",
"label": "John Smith",
"type": "FILE",
"class": "CHARACTER",
"active": true,
"layout": "NOTE",
"charCount": 49,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 24,
"import": "i000008"
"name": "John Smith",
"itemAttr": {
"handle": "14298de4d9524",
"parent": "f7e2d9f330615",
"root": null,
"order": 0,
"type": "FILE",
"class": "CHARACTER",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 49,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 24
},
"nameAttr": {
"active": true,
"import": "i000008"
}
},
{
"handle": "bb2c23b3c42cc",
"parent": "f7e2d9f330615",
"root": null,
"order": 1,
"heading": "H0",
"label": "Jane Smith",
"type": "FILE",
"class": "CHARACTER",
"active": true,
"layout": "NOTE",
"charCount": 55,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 25,
"import": "i000009"
"name": "Jane Smith",
"itemAttr": {
"handle": "bb2c23b3c42cc",
"parent": "f7e2d9f330615",
"root": null,
"order": 1,
"type": "FILE",
"class": "CHARACTER",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 55,
"wordCount": 9,
"paraCount": 1,
"cursorPos": 25
},
"nameAttr": {
"active": true,
"import": "i000009"
}
},
{
"handle": "15c4492bd5107",
"parent": null,
"root": null,
"order": 2,
"heading": "H0",
"label": "Locations",
"type": "ROOT",
"class": "WORLD",
"expanded": true,
"import": null
"name": "Locations",
"itemAttr": {
"handle": "15c4492bd5107",
"parent": null,
"root": null,
"order": 2,
"type": "ROOT",
"class": "WORLD"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
},
{
"handle": "b3e74dbc1f584",
"parent": "15c4492bd5107",
"root": null,
"order": 0,
"heading": "H0",
"label": "Earth",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 76,
"wordCount": 15,
"paraCount": 1,
"cursorPos": 20,
"import": "i00000a"
"name": "Earth",
"itemAttr": {
"handle": "b3e74dbc1f584",
"parent": "15c4492bd5107",
"root": null,
"order": 0,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 76,
"wordCount": 15,
"paraCount": 1,
"cursorPos": 20
},
"nameAttr": {
"active": true,
"import": "i00000a"
}
},
{
"handle": "f1471bef9f2ae",
"parent": "15c4492bd5107",
"root": null,
"order": 1,
"heading": "H0",
"label": "Space",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 115,
"wordCount": 24,
"paraCount": 1,
"cursorPos": 133,
"import": "i000008"
"name": "Space",
"itemAttr": {
"handle": "f1471bef9f2ae",
"parent": "15c4492bd5107",
"root": null,
"order": 1,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 115,
"wordCount": 24,
"paraCount": 1,
"cursorPos": 133
},
"nameAttr": {
"active": true,
"import": "i000008"
}
},
{
"handle": "5eaea4e8cdee8",
"parent": "15c4492bd5107",
"root": null,
"order": 2,
"heading": "H0",
"label": "Mars",
"type": "FILE",
"class": "WORLD",
"active": true,
"layout": "NOTE",
"charCount": 28,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 45,
"import": "i000009"
"name": "Mars",
"itemAttr": {
"handle": "5eaea4e8cdee8",
"parent": "15c4492bd5107",
"root": null,
"order": 2,
"type": "FILE",
"class": "WORLD",
"layout": "NOTE"
},
"metaAttr": {
"heading": "H0",
"charCount": 28,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 45
},
"nameAttr": {
"active": true,
"import": "i000009"
}
},
{
"handle": "6827118336ac1",
"parent": null,
"root": null,
"order": 3,
"heading": "H0",
"label": "Archive",
"type": "ROOT",
"class": "ARCHIVE",
"expanded": true,
"status": "s000000"
"name": "Archive",
"itemAttr": {
"handle": "6827118336ac1",
"parent": null,
"root": null,
"order": 3,
"type": "ROOT",
"class": "ARCHIVE"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000000"
}
},
{
"handle": "ae9bf3c3ea159",
"parent": "6827118336ac1",
"root": null,
"order": 0,
"heading": "H0",
"label": "Scenes",
"type": "FOLDER",
"class": "ARCHIVE",
"expanded": true,
"status": "s000000"
"name": "Scenes",
"itemAttr": {
"handle": "ae9bf3c3ea159",
"parent": "6827118336ac1",
"root": null,
"order": 0,
"type": "FOLDER",
"class": "ARCHIVE"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"status": "s000000"
}
},
{
"handle": "8a5deb88c0e97",
"parent": "ae9bf3c3ea159",
"root": null,
"order": 0,
"heading": "H0",
"label": "Old File",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 314,
"wordCount": 55,
"paraCount": 1,
"cursorPos": 322,
"status": "s000003"
"name": "Old File",
"itemAttr": {
"handle": "8a5deb88c0e97",
"parent": "ae9bf3c3ea159",
"root": null,
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 314,
"wordCount": 55,
"paraCount": 1,
"cursorPos": 322
},
"nameAttr": {
"active": true,
"status": "s000003"
}
},
{
"handle": "98acd8c76c93a",
"parent": null,
"root": null,
"order": 4,
"heading": "H0",
"label": "Trash",
"type": "ROOT",
"class": "TRASH",
"expanded": true,
"import": null
"name": "Trash",
"itemAttr": {
"handle": "98acd8c76c93a",
"parent": null,
"root": null,
"order": 4,
"type": "ROOT",
"class": "TRASH"
},
"metaAttr": {
"heading": "H0",
"expanded": true
},
"nameAttr": {
"import": null
}
},
{
"handle": "b8136a5a774a0",
"parent": "98acd8c76c93a",
"root": null,
"order": 0,
"heading": "H0",
"label": "Delete Me!",
"type": "FILE",
"class": "NOVEL",
"active": true,
"layout": "DOCUMENT",
"charCount": 30,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 36,
"status": "s000000"
"name": "Delete Me!",
"itemAttr": {
"handle": "b8136a5a774a0",
"parent": "98acd8c76c93a",
"root": null,
"order": 0,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
},
"metaAttr": {
"heading": "H0",
"charCount": 30,
"wordCount": 6,
"paraCount": 1,
"cursorPos": 36
},
"nameAttr": {
"active": true,
"status": "s000000"
}
}
]
]
+47 -53
View File
@@ -1,21 +1,15 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-25 18:26:15">
<project>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" timeStamp="2022-10-25 18:26:15">
<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>
<saveCount>5</saveCount>
<autoCount>10</autoCount>
<editTime>1000</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<doBackup>yes</doBackup>
<language>en_GB</language>
<spellCheck>True</spellCheck>
<spellLang>en_GB</spellLang>
<novelWordCount>830</novelWordCount>
<notesWordCount>376</notesWordCount>
<spellChecking auto="yes">en_GB</spellChecking>
<lastHandle>
<entry key="editor">None</entry>
<entry key="viewer">None</entry>
@@ -50,106 +44,106 @@
<entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry>
</importance>
</settings>
<content count="25">
<content items="25" novelWords="830" notesWords="376">
<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>
</item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="93" wordCount="19" paraCount="2" cursorPos="2"/>
<name status="s000002" import="i000007" active="True">Title Page</name>
<meta expanded="no" heading="H0" charCount="93" wordCount="19" paraCount="2" cursorPos="2"/>
<name status="s000002" import="i000007" active="yes">Title Page</name>
</item>
<item handle="974e400180a99" parent="7031beac91f75" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="186" wordCount="39" paraCount="2" cursorPos="212"/>
<name status="s000000" import="i000007" active="True">Page</name>
<meta expanded="no" heading="H0" charCount="186" wordCount="39" paraCount="2" cursorPos="212"/>
<name status="s000000" import="i000007" active="yes">Page</name>
</item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="26" wordCount="6" paraCount="1" cursorPos="33"/>
<name status="s000000" import="i000007" active="True">Part One</name>
<meta expanded="no" heading="H0" charCount="26" wordCount="6" paraCount="1" cursorPos="33"/>
<name status="s000000" import="i000007" active="yes">Part One</name>
</item>
<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>
</item>
<item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="75" wordCount="14" paraCount="1" cursorPos="279"/>
<name status="s000001" import="i000007" active="True">Chapter One</name>
<meta expanded="no" heading="H0" charCount="75" wordCount="14" paraCount="1" cursorPos="279"/>
<name status="s000001" import="i000007" active="yes">Chapter One</name>
</item>
<item handle="636b6aa9b697b" parent="e7ded148d6e4a" root="None" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="2429" wordCount="432" paraCount="14" cursorPos="62"/>
<name status="s000003" import="i000007" active="True">Making a Scene</name>
<meta expanded="no" heading="H0" charCount="2429" wordCount="432" paraCount="14" cursorPos="62"/>
<name status="s000003" import="i000007" active="yes">Making a Scene</name>
</item>
<item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" root="None" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="476" wordCount="93" paraCount="3" cursorPos="577"/>
<name status="s000003" import="i000007" active="True">Another Scene</name>
<meta expanded="no" heading="H0" charCount="476" wordCount="93" paraCount="3" cursorPos="577"/>
<name status="s000003" import="i000007" active="yes">Another Scene</name>
</item>
<item handle="ba8a28a246524" parent="e7ded148d6e4a" root="None" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="617" wordCount="101" paraCount="3" cursorPos="4"/>
<name status="s000000" import="i000007" active="True">Interlude</name>
<meta expanded="no" heading="H0" charCount="617" wordCount="101" paraCount="3" cursorPos="4"/>
<name status="s000000" import="i000007" active="yes">Interlude</name>
</item>
<item handle="96b68994dfa3d" parent="e7ded148d6e4a" root="None" order="4" type="FILE" class="NOVEL" layout="NOTE">
<meta expanded="False" heading="H0" charCount="1692" wordCount="313" paraCount="6" cursorPos="1110"/>
<name status="s000004" import="i000007" active="False">A Note on Structure</name>
<meta expanded="no" heading="H0" charCount="1692" wordCount="313" paraCount="6" cursorPos="1110"/>
<name status="s000004" import="i000007" active="no">A Note on Structure</name>
</item>
<item handle="88706ddc78b1b" parent="e7ded148d6e4a" root="None" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<name status="s000003" import="i000007" active="True">Chapter Two</name>
<meta expanded="no" heading="H0" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<name status="s000003" import="i000007" active="yes">Chapter Two</name>
</item>
<item handle="ae7339df26ded" parent="e7ded148d6e4a" root="None" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="s000003" import="i000007" active="True">We Found John!</name>
<meta expanded="no" heading="H0" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="s000003" import="i000007" active="yes">We Found John!</name>
</item>
<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>
</item>
<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>
</item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="None" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="s000000" import="i000008" active="True">John Smith</name>
<meta expanded="no" heading="H0" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
<name status="s000000" import="i000008" active="yes">John Smith</name>
</item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="None" order="1" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" heading="H0" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="s000000" import="i000009" active="True">Jane Smith</name>
<meta expanded="no" heading="H0" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="s000000" import="i000009" active="yes">Jane Smith</name>
</item>
<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>
</item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="None" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="s000000" import="i00000a" active="True">Earth</name>
<meta expanded="no" heading="H0" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
<name status="s000000" import="i00000a" active="yes">Earth</name>
</item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="None" order="1" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="s000000" import="i000008" active="True">Space</name>
<meta expanded="no" heading="H0" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
<name status="s000000" import="i000008" active="yes">Space</name>
</item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="None" order="2" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="False" heading="H0" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="s000000" import="i000009" active="True">Mars</name>
<meta expanded="no" heading="H0" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="s000000" import="i000009" active="yes">Mars</name>
</item>
<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>
</item>
<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>
</item>
<item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="314" wordCount="55" paraCount="1" cursorPos="322"/>
<name status="s000003" import="i000007" active="True">Old File</name>
<meta expanded="no" heading="H0" charCount="314" wordCount="55" paraCount="1" cursorPos="322"/>
<name status="s000003" import="i000007" active="yes">Old File</name>
</item>
<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>
</item>
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="None" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" heading="H0" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s000000" import="i000007" active="True">Delete Me!</name>
<meta expanded="no" heading="H0" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="s000000" import="i000007" active="yes">Delete Me!</name>
</item>
</content>
</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.common import (
checkStringNone, checkString, checkInt, checkFloat, checkBool, checkHandle,
isHandle, isTitleTag, isItemClass, isItemType, isItemLayout, hexToInt,
minmax, checkIntTuple, formatInt, formatTimeStamp, formatTime, simplified,
splitVersionNumber, transferCase, fuzzyTime, numberToRoman, jsonEncode,
readTextFile, makeFileNameSafe, ensureFolder, sha256sum, getGuiItem,
NWConfigParser
checkUuid, isHandle, isTitleTag, isItemClass, isItemType, isItemLayout,
hexToInt, minmax, checkIntTuple, formatInt, formatTimeStamp, formatTime,
simplified, yesNo, splitVersionNumber, transferCase, fuzzyTime,
numberToRoman, jsonEncode, readTextFile, makeFileNameSafe, ensureFolder,
sha256sum, getGuiItem, NWConfigParser
)
@@ -105,16 +105,41 @@ def testBaseCommon_CheckBool():
bool, or integer 1 or 0, are returned as bool. Otherwise, the
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("False", True) is False
assert checkBool("Boo", False) is False
assert checkBool("Boo", True) is True
assert checkBool(None, True) is True
assert checkBool(None, False) is False
assert checkBool("true", False) is True
assert checkBool("false", True) is False
assert checkBool("Yes", False) is True
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(1, False) is True
# Inalid Integers
assert checkBool(2, True) is True
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(1.0, False) is False
assert checkBool(2.0, True) is True
@@ -137,6 +162,20 @@ def 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
def testBaseCommon_IsHandle():
"""Test the isHandle function.
@@ -317,6 +356,34 @@ def 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
def testBaseCommon_SplitVersionNumber():
"""Test the splitVersionNumber function.
@@ -20,20 +20,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import uuid
import pytest
from shutil import copyfile
from zipfile import ZipFile
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.doctools import DocMerger, DocSplitter
from novelwriter.core.document import NWDoc
from novelwriter.core.coretools import DocMerger, DocSplitter, ProjectBuilder
@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.
"""
theProject = NWProject(mockGUI)
@@ -118,11 +120,11 @@ def testCoreDocTools_DocMerger(monkeypatch, mockGUI, fncDir, outDir, refDir, moc
# Just for debugging
docMerger.writeTargetDoc()
# END Test testCoreDocTools_DocMerger
# END Test testCoreTools_DocMerger
@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.
"""
theProject = NWProject(mockGUI)
@@ -161,7 +163,7 @@ def testCoreDocTools_DocSplitter(monkeypatch, mockGUI, fncDir, outDir, refDir, m
docText = "\n\n".join(docData)
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].setImport(C.iMain)
@@ -258,4 +260,160 @@ def testCoreDocTools_DocSplitter(monkeypatch, mockGUI, fncDir, outDir, refDir, m
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
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/>.
"""
import os
import pytest
from mock import causeOSError
@@ -27,62 +26,74 @@ from tools import C, buildTestProject, readFile, writeFile
from novelwriter.enum import nwItemClass, nwItemLayout
from novelwriter.core.project import NWProject
from novelwriter.core.document import NWDoc
from novelwriter.core.document import NWDocument
@pytest.mark.core
def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncDir, mockRnd):
"""Test loading and saving a document with the NWDoc class.
def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd):
"""Test loading and saving a document with the NWDocument class.
"""
theProject = NWProject(mockGUI)
mockRnd.reset()
buildTestProject(theProject, fncDir)
buildTestProject(theProject, fncPath)
# Read Document
# =============
# Not a valid handle
theDoc = NWDoc(theProject, "stuff")
theDoc = NWDocument(theProject, "stuff")
assert bool(theDoc) is False
assert theDoc.readDocument() is None
# Non-existent handle
theDoc = NWDoc(theProject, C.hInvalid)
theDoc = NWDocument(theProject, C.hInvalid)
assert theDoc.readDocument() 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
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
theDoc = NWDoc(theProject, C.hSceneDoc)
theDoc = NWDocument(theProject, C.hSceneDoc)
assert theDoc.readDocument() is None
assert theDoc.getError() == "OSError: Mock OSError"
# Load the text
theDoc = NWDoc(theProject, C.hSceneDoc)
theDoc = NWDocument(theProject, C.hSceneDoc)
assert theDoc.readDocument() == "### New Scene\n\n"
# Try to open a new (non-existent) file
xHandle = theProject.newFile("New File", C.hNovelRoot)
theDoc = NWDoc(theProject, xHandle)
theDoc = NWDocument(theProject, xHandle)
assert bool(theDoc) is True
assert repr(theDoc) == f"<NWDoc handle={xHandle}>"
assert repr(theDoc) == f"<NWDocument handle={xHandle}>"
assert theDoc.readDocument() == ""
# 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"
theDoc = NWDoc(theProject, xHandle)
theDoc = NWDocument(theProject, xHandle)
assert theDoc.readDocument(xHandle) == ""
assert theDoc.writeDocument(theText) is True
# Save again to ensure temp file and previous file is handled
assert theDoc.writeDocument(theText)
assert theDoc.writeDocument(theText) is True
# Check file content
docPath = os.path.join(fncDir, "content", xHandle+".nwd")
docPath = fncPath / "content" / f"{xHandle}.nwd"
assert readFile(docPath) == (
"%%~name: New File\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
with monkeypatch.context() as mp:
mp.setattr("os.replace", causeOSError)
mp.setattr("pathlib.Path.replace", causeOSError)
assert theDoc.writeDocument(theText) is False
assert theDoc.getError() == "OSError: Mock OSError"
@@ -128,41 +139,47 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncDir, mockRnd):
# Delete Document
# ===============
# Delete the last document
theDoc = NWDoc(theProject, "stuff")
# Delete a non-existing document
theDoc = NWDocument(theProject, "stuff")
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
with monkeypatch.context() as mp:
mp.setattr("os.unlink", causeOSError)
theDoc = NWDoc(theProject, xHandle)
mp.setattr("pathlib.Path.unlink", causeOSError)
theDoc = NWDocument(theProject, xHandle)
assert theDoc.deleteDocument() is False
assert theDoc.getError() == "OSError: Mock OSError"
# Make the delete pass
theDoc = NWDoc(theProject, xHandle)
theDoc = NWDocument(theProject, xHandle)
assert theDoc.deleteDocument() is True
assert not os.path.isfile(docPath)
assert not docPath.exists()
# END Test testCoreDocument_Load
@pytest.mark.core
def testCoreDocument_Methods(mockGUI, fncDir, mockRnd):
"""Test other methods of the NWDoc class.
def testCoreDocument_Methods(mockGUI, fncPath, mockRnd):
"""Test other methods of the NWDocument class.
"""
theProject = NWProject(mockGUI)
mockRnd.reset()
buildTestProject(theProject, fncDir)
buildTestProject(theProject, fncPath)
theDoc = NWDoc(theProject, C.hSceneDoc)
docPath = os.path.join(fncDir, "content", C.hSceneDoc+".nwd")
theDoc = NWDocument(theProject, C.hSceneDoc)
docPath = fncPath / "content" / f"{C.hSceneDoc}.nwd"
assert theDoc.readDocument() == "### New Scene\n\n"
# Check location
assert theDoc.getFileLocation() == docPath
assert theDoc.getFileLocation() == str(docPath)
# Check the item
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/>.
"""
import os
import json
import pytest
from shutil import copyfile
from pathlib import Path
from mock import causeException
from tools import C, buildTestProject, cmpFiles, writeFile
from novelwriter.enum import nwItemClass, nwItemLayout
from novelwriter.constants import nwFiles
from novelwriter.core.index import NWIndex, countWords, TagsIndex
from novelwriter.core.project import NWProject
@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
the index cache file.
"""
projFile = os.path.join(nwLipsum, "meta", "tagsIndex.json")
testFile = os.path.join(outDir, "coreIndex_LoadSave_tagsIndex.json")
compFile = os.path.join(refDir, "coreIndex_LoadSave_tagsIndex.json")
projFile = Path(nwLipsum) / "meta" / nwFiles.INDEX_FILE
testFile = tstPaths.outDir / "coreIndex_LoadSave_tagsIndex.json"
compFile = tstPaths.refDir / "coreIndex_LoadSave_tagsIndex.json"
theProject = NWProject(mockGUI)
assert theProject.openProject(nwLipsum)
@@ -61,6 +62,11 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, outDir, refDir):
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
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeException)
@@ -85,6 +91,11 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, outDir, refDir):
assert theIndex._tagsIndex._tags == {}
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
with monkeypatch.context() as mp:
mp.setattr(json, "load", causeException)
@@ -752,7 +763,6 @@ def testCoreIndex_ExtractData(mockGUI, fncDir, mockRnd):
assert theIndex.saveIndex() is True
assert theProject.saveProject() is True
assert theProject.closeProject() is True
# Header Record
bHandle = "0000000000000"
@@ -764,6 +774,8 @@ def testCoreIndex_ExtractData(mockGUI, fncDir, mockRnd):
("T000001", "H1", "Hello World!"), ("T000011", "H1", "Hello World!")
]
assert theProject.closeProject() is True
# END Test testCoreIndex_ExtractData
+73 -55
View File
@@ -509,23 +509,29 @@ def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
# File
theItem = NWItem(theProject)
assert theItem.unpack({
"label": "A File",
"handle": "0000000000003",
"parent": "0000000000002",
"root": "0000000000001",
"order": 1,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": True,
"status": None,
"import": None,
"heading": "H1",
"charCount": 100,
"wordCount": 20,
"paraCount": 2,
"cursorPos": 50,
"active": False,
"name": "A File",
"itemAttr": {
"handle": "0000000000003",
"parent": "0000000000002",
"root": "0000000000001",
"order": 1,
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT",
},
"metaAttr": {
"expanded": True,
"heading": "H1",
"charCount": 100,
"wordCount": 20,
"paraCount": 2,
"cursorPos": 50,
},
"nameAttr": {
"status": None,
"import": None,
"active": False,
},
}) is True
assert theItem.itemName == "A File"
@@ -558,7 +564,7 @@ def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
"layout": "DOCUMENT",
},
"metaAttr": {
"expanded": "True",
"expanded": "yes",
"heading": "H1",
"charCount": "100",
"wordCount": "20",
@@ -568,30 +574,36 @@ def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
"nameAttr": {
"status": "s000000",
"import": "i000001",
"active": "False",
"active": "no",
}
}
# Folder
theItem = NWItem(theProject)
assert theItem.unpack({
"label": "A Folder",
"handle": "0000000000003",
"parent": "0000000000002",
"root": "0000000000001",
"order": 1,
"type": "FOLDER",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": True,
"status": "",
"import": "",
"heading": "H1",
"charCount": 100,
"wordCount": 20,
"paraCount": 2,
"cursorPos": 50,
"active": True,
"name": "A Folder",
"itemAttr": {
"handle": "0000000000003",
"parent": "0000000000002",
"root": "0000000000001",
"order": 1,
"type": "FOLDER",
"class": "NOVEL",
"layout": "DOCUMENT",
},
"metaAttr": {
"expanded": True,
"heading": "H1",
"charCount": 100,
"wordCount": 20,
"paraCount": 2,
"cursorPos": 50,
},
"nameAttr": {
"status": "",
"import": "",
"active": True,
}
}) is True
assert theItem.itemName == "A Folder"
@@ -623,7 +635,7 @@ def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
"class": "NOVEL",
},
"metaAttr": {
"expanded": "True",
"expanded": "yes",
},
"nameAttr": {
"status": "s000000",
@@ -634,23 +646,29 @@ def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
# Root
theItem = NWItem(theProject)
assert theItem.unpack({
"label": "A Novel",
"handle": "0000000000003",
"parent": "0000000000002",
"root": "0000000000001",
"order": 1,
"type": "ROOT",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": True,
"status": None,
"import": None,
"heading": "H1",
"charCount": 100,
"wordCount": 20,
"paraCount": 2,
"cursorPos": 50,
"active": True,
"name": "A Novel",
"itemAttr": {
"handle": "0000000000003",
"parent": "0000000000002",
"root": "0000000000001",
"order": 1,
"type": "ROOT",
"class": "NOVEL",
"layout": "DOCUMENT",
},
"metaAttr": {
"expanded": True,
"heading": "H1",
"charCount": 100,
"wordCount": 20,
"paraCount": 2,
"cursorPos": 50,
},
"nameAttr": {
"status": None,
"import": None,
"active": True,
},
}) is True
assert theItem.itemName == "A Novel"
@@ -682,7 +700,7 @@ def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
"class": "NOVEL",
},
"metaAttr": {
"expanded": "True",
"expanded": "yes",
},
"nameAttr": {
"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/>.
"""
import os
import json
import pytest
from mock import causeOSError
from tools import writeFile
from novelwriter.constants import nwFiles
from novelwriter.core.options import OptionState
from novelwriter.core.project import NWProject
from novelwriter.constants import nwFiles
from novelwriter.gui.noveltree import NovelTreeColumn
@pytest.mark.core
def testCoreOptions_LoadSave(monkeypatch, mockGUI, tmpDir):
def testCoreOptions_LoadSave(monkeypatch, mockGUI, fncPath):
"""Test loading and saving from the OptionState class.
"""
theProject = NWProject(mockGUI)
theOpts = OptionState(theProject)
metaDir = fncPath / "meta"
metaDir.mkdir()
# Write a test file
optFile = os.path.join(tmpDir, nwFiles.OPTS_FILE)
writeFile(optFile, json.dumps({
optFile = metaDir / nwFiles.OPTS_FILE
optFile.write_text(json.dumps({
"GuiBuildNovel": {
"winWidth": 1000,
"winHeight": 700,
@@ -52,22 +54,22 @@ def testCoreOptions_LoadSave(monkeypatch, mockGUI, tmpDir):
"MockGroup": {
"mockItem": None,
},
}))
}), encoding="utf-8")
# Load and save with no path set
theProject.projMeta = None
assert not theOpts.loadSettings()
assert not theOpts.saveSettings()
theProject.storage._runtimePath = None
assert theOpts.loadSettings() is False
assert theOpts.saveSettings() is False
# Set path
theProject.projMeta = tmpDir
assert theProject.projMeta == tmpDir
theProject.storage._runtimePath = fncPath
assert theProject.storage.getMetaFile(nwFiles.OPTS_FILE) == optFile
# Cause open() to fail
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert not theOpts.loadSettings()
assert not theOpts.saveSettings()
assert theOpts.loadSettings() is False
assert theOpts.saveSettings() is False
# Load proper
assert theOpts.loadSettings()
@@ -108,9 +110,11 @@ def testCoreOptions_SetGet(mockGUI):
theProject = NWProject(mockGUI)
theOpts = OptionState(theProject)
nwColHidden = NovelTreeColumn.HIDDEN
# Set invalid values
assert not theOpts.setValue("MockGroup", "mockItem", None)
assert not theOpts.setValue("GuiBuildNovel", "mockItem", None)
assert theOpts.setValue("MockGroup", "mockItem", None) is False
assert theOpts.setValue("GuiBuildNovel", "mockItem", None) is False
# Set valid value
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", "addNovel", True)
assert theOpts.setValue("GuiBuildNovel", "textFont", "Cantarell")
assert theOpts.setValue("GuiNovelView", "lastCol", nwColHidden)
# Generic get, doesn't check type
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.getBool("GuiBuildNovel", "addNovel", None) is True
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
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 os
import pytest
import shutil
from shutil import copyfile
from datetime import datetime
from mock import causeOSError
from tools import cmpFiles, writeFile
from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProjectData
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
from novelwriter.core.projectdata import NWProjectData
class MockProject:
@@ -40,13 +39,14 @@ class MockProject:
@pytest.mark.core
def testCoreProjectXML_ReadCurrent(monkeypatch, filesDir, fncDir, outDir, refDir):
def testCoreProjectXML_ReadCurrent(monkeypatch, tstPaths, fncPath):
"""Test reading the current XML file format.
"""
refFile = os.path.join(filesDir, "nwProject-1.4.nwx")
xmlFile = os.path.join(fncDir, "nwProject-1.4.nwx")
bakFile = os.path.join(fncDir, "nwProject-1.4.bak")
outFile = os.path.join(fncDir, "nwProject.nwx")
refFile = tstPaths.filesDir / "nwProject-1.5.nwx"
tstFile = tstPaths.outDir / "ProjectXML_ReadCurrent.nwx"
xmlFile = fncPath / "nwProject-1.5.nwx"
bakFile = fncPath / "nwProject-1.5.bak"
outFile = fncPath / "nwProject.nwx"
xmlReader = ProjectXMLReader(xmlFile)
assert xmlReader.state == XMLReadState.NO_ACTION
@@ -81,7 +81,7 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, filesDir, fncDir, outDir, refDir
# Check parsing of unkown sections
writeFile(xmlFile, (
"<novelWriterXML fileVersion='1.4'>"
"<novelWriterXML fileVersion='1.5'>"
" <project>"
" <stuff></stuff>"
" </project>"
@@ -125,13 +125,13 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, filesDir, fncDir, outDir, refDir
content = []
# Parse a valid, complete file
shutil.copy(refFile, xmlFile)
copyfile(refFile, xmlFile)
assert xmlReader.read(data, content) is True
assert xmlReader.state == XMLReadState.PARSED_OK
assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0104
assert xmlReader.xmlVersion == 0x0105
assert xmlReader.appVersion == "2.0-rc1"
assert xmlReader.hexVersion == "0x020000c1"
assert xmlReader.hexVersion == 0x020000c1
# Check loaded data
assert data.name == "Sample Project"
@@ -199,8 +199,8 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, filesDir, fncDir, outDir, refDir
assert data.itemImport.count("i56be10") == 1
# Compare content
dumpFile = os.path.join(outDir, "projectXML_ReadCurrent.json")
compFile = os.path.join(refDir, "projectXML_ReadCurrent.json")
dumpFile = tstPaths.outDir / "projectXML_ReadCurrent.json"
compFile = tstPaths.refDir / "projectXML_ReadCurrent.json"
with open(dumpFile, mode="w", encoding="utf-8") as dump:
json.dump(content, dump, indent=2)
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
timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp())
xmlWriter = ProjectXMLWriter(fncDir)
xmlWriter = ProjectXMLWriter(fncPath)
# Fail saving
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 str(xmlWriter.error) == "Mock OSError"
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 str(xmlWriter.error) == "Mock OSError"
# Successful save (should be twice)
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True
assert cmpFiles(outFile, xmlFile)
copyfile(outFile, tstFile)
assert cmpFiles(tstFile, refFile)
# END Test testCoreProjectXML_ReadCurrent
@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.
"""
refFile = os.path.join(filesDir, "nwProject-1.0.nwx")
xmlFile = os.path.join(fncDir, "nwProject-1.0.nwx")
outFile = os.path.join(fncDir, "nwProject.nwx")
shutil.copy(refFile, xmlFile)
refFile = tstPaths.filesDir / "nwProject-1.0.nwx"
xmlFile = fncPath / "nwProject-1.0.nwx"
outFile = fncPath / "nwProject.nwx"
copyfile(refFile, xmlFile)
xmlReader = ProjectXMLReader(xmlFile)
assert xmlReader.state == XMLReadState.NO_ACTION
@@ -256,7 +257,7 @@ def testCoreProjectXML_ReadLegacy10(filesDir, fncDir, outDir, refDir, mockRnd):
assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0100
assert xmlReader.appVersion == "0.6.1"
assert xmlReader.hexVersion == "0x000601f0"
assert xmlReader.hexVersion == 0x000601f0
# Check loaded data
assert data.name == "Sample Project"
@@ -324,8 +325,8 @@ def testCoreProjectXML_ReadLegacy10(filesDir, fncDir, outDir, refDir, mockRnd):
assert data.itemImport.count("i00000a") == 0
# Compare content
dumpFile = os.path.join(outDir, "projectXML_ReadLegacy10.json")
compFile = os.path.join(refDir, "projectXML_ReadLegacy10.json")
dumpFile = tstPaths.outDir / "projectXML_ReadLegacy10.json"
compFile = tstPaths.refDir / "projectXML_ReadLegacy10.json"
with open(dumpFile, mode="w", encoding="utf-8") as dump:
json.dump(content, dump, indent=2)
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
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
compFile = os.path.join(refDir, "projectXML_ReadLegacy10.nwx")
assert cmpFiles(outFile, compFile)
testFile = tstPaths.outDir / "projectXML_ReadLegacy10.nwx"
compFile = tstPaths.refDir / "projectXML_ReadLegacy10.nwx"
copyfile(outFile, testFile)
assert cmpFiles(testFile, compFile)
# END Test testCoreProjectXML_ReadLegacy10
@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.
"""
refFile = os.path.join(filesDir, "nwProject-1.1.nwx")
xmlFile = os.path.join(fncDir, "nwProject-1.1.nwx")
outFile = os.path.join(fncDir, "nwProject.nwx")
shutil.copy(refFile, xmlFile)
refFile = tstPaths.filesDir / "nwProject-1.1.nwx"
xmlFile = fncPath / "nwProject-1.1.nwx"
outFile = fncPath / "nwProject.nwx"
copyfile(refFile, xmlFile)
xmlReader = ProjectXMLReader(xmlFile)
assert xmlReader.state == XMLReadState.NO_ACTION
@@ -395,7 +399,7 @@ def testCoreProjectXML_ReadLegacy11(filesDir, fncDir, outDir, refDir, mockRnd):
assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0101
assert xmlReader.appVersion == "0.9.2"
assert xmlReader.hexVersion == "0x000902f0"
assert xmlReader.hexVersion == 0x000902f0
# Check loaded data
assert data.name == "Sample Project"
@@ -463,8 +467,8 @@ def testCoreProjectXML_ReadLegacy11(filesDir, fncDir, outDir, refDir, mockRnd):
assert data.itemImport.count("i00000a") == 0
# Compare content
dumpFile = os.path.join(outDir, "projectXML_ReadLegacy11.json")
compFile = os.path.join(refDir, "projectXML_ReadLegacy11.json")
dumpFile = tstPaths.outDir / "projectXML_ReadLegacy11.json"
compFile = tstPaths.refDir / "projectXML_ReadLegacy11.json"
with open(dumpFile, mode="w", encoding="utf-8") as dump:
json.dump(content, dump, indent=2)
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
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
compFile = os.path.join(refDir, "projectXML_ReadLegacy11.nwx")
assert cmpFiles(outFile, compFile)
testFile = tstPaths.outDir / "projectXML_ReadLegacy11.nwx"
compFile = tstPaths.refDir / "projectXML_ReadLegacy11.nwx"
copyfile(outFile, testFile)
assert cmpFiles(testFile, compFile)
# END Test testCoreProjectXML_ReadLegacy11
@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.
"""
refFile = os.path.join(filesDir, "nwProject-1.2.nwx")
xmlFile = os.path.join(fncDir, "nwProject-1.2.nwx")
outFile = os.path.join(fncDir, "nwProject.nwx")
shutil.copy(refFile, xmlFile)
refFile = tstPaths.filesDir / "nwProject-1.2.nwx"
xmlFile = fncPath / "nwProject-1.2.nwx"
outFile = fncPath / "nwProject.nwx"
copyfile(refFile, xmlFile)
xmlReader = ProjectXMLReader(xmlFile)
assert xmlReader.state == XMLReadState.NO_ACTION
@@ -534,7 +541,7 @@ def testCoreProjectXML_ReadLegacy12(filesDir, fncDir, outDir, refDir, mockRnd):
assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0102
assert xmlReader.appVersion == "1.4.2"
assert xmlReader.hexVersion == "0x010402f0"
assert xmlReader.hexVersion == 0x010402f0
# Check loaded data
assert data.name == "Sample Project"
@@ -602,8 +609,8 @@ def testCoreProjectXML_ReadLegacy12(filesDir, fncDir, outDir, refDir, mockRnd):
assert data.itemImport.count("i00000a") == 0
# Compare content
dumpFile = os.path.join(outDir, "projectXML_ReadLegacy12.json")
compFile = os.path.join(refDir, "projectXML_ReadLegacy12.json")
dumpFile = tstPaths.outDir / "projectXML_ReadLegacy12.json"
compFile = tstPaths.refDir / "projectXML_ReadLegacy12.json"
with open(dumpFile, mode="w", encoding="utf-8") as dump:
json.dump(content, dump, indent=2)
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
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
compFile = os.path.join(refDir, "projectXML_ReadLegacy12.nwx")
assert cmpFiles(outFile, compFile)
testFile = tstPaths.outDir / "projectXML_ReadLegacy12.nwx"
compFile = tstPaths.refDir / "projectXML_ReadLegacy12.nwx"
copyfile(outFile, testFile)
assert cmpFiles(testFile, compFile)
# END Test testCoreProjectXML_ReadLegacy12
@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.
"""
refFile = os.path.join(filesDir, "nwProject-1.3.nwx")
xmlFile = os.path.join(fncDir, "nwProject-1.3.nwx")
outFile = os.path.join(fncDir, "nwProject.nwx")
shutil.copy(refFile, xmlFile)
refFile = tstPaths.filesDir / "nwProject-1.3.nwx"
xmlFile = fncPath / "nwProject-1.3.nwx"
outFile = fncPath / "nwProject.nwx"
copyfile(refFile, xmlFile)
xmlReader = ProjectXMLReader(xmlFile)
assert xmlReader.state == XMLReadState.NO_ACTION
@@ -676,7 +686,7 @@ def testCoreProjectXML_ReadLegacy13(filesDir, fncDir, outDir, refDir, mockRnd):
assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0103
assert xmlReader.appVersion == "1.6.6"
assert xmlReader.hexVersion == "0x010606f0"
assert xmlReader.hexVersion == 0x010606f0
# Check loaded data
assert data.name == "Sample Project"
@@ -744,8 +754,8 @@ def testCoreProjectXML_ReadLegacy13(filesDir, fncDir, outDir, refDir, mockRnd):
assert data.itemImport.count("i00000a") == 0
# Compare content
dumpFile = os.path.join(outDir, "projectXML_ReadLegacy13.json")
compFile = os.path.join(refDir, "projectXML_ReadLegacy13.json")
dumpFile = tstPaths.outDir / "projectXML_ReadLegacy13.json"
compFile = tstPaths.refDir / "projectXML_ReadLegacy13.json"
with open(dumpFile, mode="w", encoding="utf-8") as dump:
json.dump(content, dump, indent=2)
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
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
compFile = os.path.join(refDir, "projectXML_ReadLegacy13.nwx")
assert cmpFiles(outFile, compFile)
testFile = tstPaths.outDir / "projectXML_ReadLegacy13.nwx"
compFile = tstPaths.refDir / "projectXML_ReadLegacy13.nwx"
copyfile(outFile, testFile)
assert cmpFiles(testFile, compFile)
# 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/>.
"""
import os
import sys
import pytest
@@ -63,10 +62,10 @@ def testCoreSpell_FakeEnchant(monkeypatch):
@pytest.mark.core
def testCoreSpell_Enchant(monkeypatch, fncDir):
def testCoreSpell_Enchant(monkeypatch, fncPath):
"""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")
# Break the enchant package, and check error handling
@@ -134,13 +133,13 @@ def testCoreSpell_Enchant(monkeypatch, fncDir):
@pytest.mark.core
def testCoreSpell_SessionWords(fncDir):
def testCoreSpell_SessionWords(fncPath):
"""Test the handling of the custom word list in the spell checker.
New project sessions should not inherit the project word list from
other sessions, so this test checks that they don't bleed through.
"""
wList1 = os.path.join(fncDir, "wordlist1.txt")
wList2 = os.path.join(fncDir, "wordlist2.txt")
wList1 = fncPath / "wordlist1.txt"
wList2 = fncPath / "wordlist2.txt"
writeFile(wList1, "a_word\nb_word\nc_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 novelwriter.core.project import NWProject
from novelwriter.core.document import NWDoc
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")
nDoc = NWDoc(theProject, C.hSceneDoc)
nDoc = theProject.storage.getDocument(C.hSceneDoc)
assert nDoc.writeDocument(docText)
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/>.
"""
import os
import pytest
import random
from pathlib import Path
from tools import readFile
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
@@ -394,7 +395,7 @@ def testCoreTree_Reorder(mockGUI, mockItems):
@pytest.mark.core
def testCoreTree_ToCFile(monkeypatch, mockGUI, mockItems, tmpDir):
def testCoreTree_ToCFile(monkeypatch, mockGUI, mockItems, tmpPath):
"""Test writing the ToC.txt file.
"""
theProject = NWProject(mockGUI)
@@ -411,24 +412,23 @@ def testCoreTree_ToCFile(monkeypatch, mockGUI, mockItems, tmpDir):
"""Return True for items that are files in novelWriter and
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
return dItem.itemType == nwItemType.FILE
monkeypatch.setattr("os.path.isfile", mockIsFile)
monkeypatch.setattr("pathlib.Path.is_file", mockIsFile)
theProject.projContent = "content"
theProject.projPath = None
assert not theTree.writeToCFile()
theProject._storage._runtimePath = None
assert theTree.writeToCFile() is False
theProject.projPath = tmpDir
assert theTree.writeToCFile()
theProject._storage._runtimePath = tmpPath
assert theTree.writeToCFile() is True
pathA = os.path.join("content", "c000000000001.nwd")
pathB = os.path.join("content", "c000000000002.nwd")
pathC = os.path.join("content", "b000000000002.nwd")
pathA = str(Path("content") / "c000000000001.nwd")
pathB = str(Path("content") / "c000000000002.nwd")
pathC = str(Path("content") / "b000000000002.nwd")
assert readFile(os.path.join(tmpDir, nwFiles.TOC_TXT)) == (
assert readFile(tmpPath / nwFiles.TOC_TXT) == (
"\n"
"Table of Contents\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._treeRoots) == 0
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.title == ""
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._treeRoots) == 4
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.title == "New Novel"
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/>.
"""
import os
import pytest
from tools import C, buildTestProject, writeFile
from pathlib import Path
from tools import C, buildTestProject
from PyQt5.QtGui import QFocusEvent
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.newTreeItem(nwItemType.FILE)
writeFile(
os.path.join(nwGUI.theProject.projContent, "0000000000010.nwd"),
"# Jane Doe\n\n@tag: Jane\n\n"
)
writeFile(
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 = nwGUI.theProject.storage.contentPath
assert isinstance(contentPath, Path)
(contentPath / "0000000000010.nwd").write_text(
"# Jane Doe\n\n@tag: Jane\n\n", encoding="utf-8"
)
(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
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 novelwriter.enum import nwItemLayout, nwItemType, nwItemClass
from novelwriter.core import NWDoc
from novelwriter.gui.projtree import GuiProjectTree
from novelwriter.dialogs.docmerge import GuiDocMerge
from novelwriter.dialogs.docsplit import GuiDocSplit
@@ -714,7 +713,7 @@ def testGuiProjTree_MergeDocuments(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, i
# The merge goes through
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
# =====================
@@ -733,9 +732,9 @@ def testGuiProjTree_MergeDocuments(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, i
# Successful merge, and move to trash
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 len(NWDoc(theProject, hChapter1).readDocument()) > lenAll
assert len(theProject.storage.getDocument(hChapter1).readDocument()) > lenAll
assert theProject.tree.isTrash(hSceneOne11)
assert theProject.tree.isTrash(hSceneOne12)
+1 -2
View File
@@ -25,7 +25,6 @@ import pytest
from tools import C, buildTestProject
from novelwriter.enum import nwState
from novelwriter.core.document import NWDoc
@pytest.mark.gui
@@ -34,7 +33,7 @@ def testGuiStatusBar_Main(qtbot, nwGUI, fncProj, mockRnd):
"""
buildTestProject(nwGUI, fncProj)
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")
nwGUI.projView.projTree.revealNewTreeItem(cHandle)
nwGUI.rebuildIndex(beQuiet=True)
+8 -15
View File
@@ -25,7 +25,7 @@ import shutil
from PyQt5.QtWidgets import qApp
XML_IGNORE = ("<novelWriterXML", "<saveCount", "<autoCount", "<editTime")
XML_IGNORE = ("<novelWriterXML", "<project")
class C:
@@ -156,7 +156,7 @@ def buildTestProject(theObject, projPath):
object as the parent.
"""
from novelwriter.enum import nwItemClass
from novelwriter.core import NWProject, NWDoc
from novelwriter.core import NWProject
if isinstance(theObject, NWProject):
theGUI = None
@@ -166,17 +166,10 @@ def buildTestProject(theObject, projPath):
theProject = theObject.theProject
theProject.clearProject()
theProject.setProjectPath(projPath, newProject=True)
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.storage.openProjectInPlace(projPath)
theProject.setDefaultStatusImport()
theProject.data.setUuid("d0f3fe10-c6e6-4310-8bfd-181eb4224eed")
theProject.data.setName("New Project")
theProject.data.setTitle("New Novel")
theProject.data.setAuthors("Jane Doe")
@@ -193,15 +186,15 @@ def buildTestProject(theObject, projPath):
xHandle[7] = theProject.newFile("New Chapter", 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")
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"))
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"))
theProject.index.reIndexHandle(xHandle[8])