From 5b7f2b2b555857f1d0dab08bf920f308519c5227 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 1 Nov 2022 17:14:57 +0100 Subject: [PATCH] Add test coverage of the project xml class --- novelwriter/core/project.py | 2 + novelwriter/core/projectxml.py | 29 +- tests/files/nwProject-1.0.nwx | 2 +- tests/files/nwProject-1.1.nwx | 8 +- tests/files/nwProject-1.2.nwx | 12 +- tests/files/nwProject-1.3.nwx | 10 +- tests/files/nwProject-1.4.nwx | 163 ++++ tests/reference/projectXML_ReadCurrent.json | 515 ++++++++++++ tests/reference/projectXML_ReadLegacy10.json | 362 +++++++++ tests/reference/projectXML_ReadLegacy10.nwx | 143 ++++ tests/reference/projectXML_ReadLegacy11.json | 346 ++++++++ tests/reference/projectXML_ReadLegacy11.nwx | 143 ++++ tests/reference/projectXML_ReadLegacy12.json | 387 +++++++++ tests/reference/projectXML_ReadLegacy12.nwx | 155 ++++ tests/reference/projectXML_ReadLegacy13.json | 387 +++++++++ tests/reference/projectXML_ReadLegacy13.nwx | 155 ++++ tests/test_core/test_core_projectxml.py | 798 +++++++++++++++++++ 17 files changed, 3584 insertions(+), 33 deletions(-) create mode 100644 tests/files/nwProject-1.4.nwx create mode 100644 tests/reference/projectXML_ReadCurrent.json create mode 100644 tests/reference/projectXML_ReadLegacy10.json create mode 100644 tests/reference/projectXML_ReadLegacy10.nwx create mode 100644 tests/reference/projectXML_ReadLegacy11.json create mode 100644 tests/reference/projectXML_ReadLegacy11.nwx create mode 100644 tests/reference/projectXML_ReadLegacy12.json create mode 100644 tests/reference/projectXML_ReadLegacy12.nwx create mode 100644 tests/reference/projectXML_ReadLegacy13.json create mode 100644 tests/reference/projectXML_ReadLegacy13.nwx create mode 100644 tests/test_core/test_core_projectxml.py diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index 4f192955..cf48c23d 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -1576,8 +1576,10 @@ class NWProjectData: """ 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): diff --git a/novelwriter/core/projectxml.py b/novelwriter/core/projectxml.py index 69ed973a..dc08573a 100644 --- a/novelwriter/core/projectxml.py +++ b/novelwriter/core/projectxml.py @@ -58,9 +58,8 @@ class XMLReadState(Enum): CANNOT_PARSE = 3 NOT_NWX_FILE = 4 UNKNOWN_VERSION = 5 - PARSING_ERROR = 6 - PARSED_OK = 7 - WAS_LEGACY = 8 + PARSED_OK = 6 + WAS_LEGACY = 7 # END Class XMLReadState @@ -173,6 +172,7 @@ class ProjectXMLReader: self._state = XMLReadState.CANNOT_PARSE return False else: + self._state = XMLReadState.CANNOT_PARSE return False xRoot = xml.getroot() @@ -191,27 +191,22 @@ 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("appVersion", "")) + self._hexVersion = str(xRoot.attrib.get("hexVersion", "")) self._timeStamp = str(xRoot.attrib.get("timeStamp", "")) - status = True for xSection in xRoot: if xSection.tag == "project": - status &= self._parseProjectMeta(xSection, projData) + self._parseProjectMeta(xSection, projData) elif xSection.tag == "settings": - status &= self._parseProjectSettings(xSection, projData) + self._parseProjectSettings(xSection, projData) elif xSection.tag == "content": if self._version >= 0x0104: - status &= self._parseProjectContent(xSection, projContent) + self._parseProjectContent(xSection, projContent) else: - status &= self._parseProjectContentLegacy(xSection, projContent, projData) + self._parseProjectContentLegacy(xSection, projContent, projData) else: logger.warning("Ignored in xml", xSection.tag) - if not status: - self._state = XMLReadState.PARSING_ERROR - return False - if self._version == 0x0104: self._state = XMLReadState.PARSED_OK else: @@ -245,7 +240,7 @@ class ProjectXMLReader: else: logger.warning("Ignored in xml", xItem.tag) - return True + return def _parseProjectSettings(self, xSection, projData): """Parse the settings section of the XML file. @@ -284,7 +279,7 @@ class ProjectXMLReader: else: logger.warning("Ignored in xml", xItem.tag) - return True + return def _parseProjectContent(self, xSection, projContent): """Parse the content section of the XML file. @@ -326,7 +321,7 @@ class ProjectXMLReader: else: logger.warning("Ignored item in xml", xItem.tag) - return True + return def _parseProjectContentLegacy(self, xSection, projContent, projData): """Parse the content section of the XML file for older versions. @@ -394,7 +389,7 @@ class ProjectXMLReader: else: logger.warning("Ignored in xml", xItem.tag) - return True + return def _parseStatusImport(self, xItem, sObject): """Parse a status or importance entry. diff --git a/tests/files/nwProject-1.0.nwx b/tests/files/nwProject-1.0.nwx index 80465ec6..6d1b1990 100644 --- a/tests/files/nwProject-1.0.nwx +++ b/tests/files/nwProject-1.0.nwx @@ -11,7 +11,7 @@ True True 636b6aa9b697b - ba8a28a246524 + 636b6aa9b697b 914 B diff --git a/tests/files/nwProject-1.1.nwx b/tests/files/nwProject-1.1.nwx index 9e244c8b..d31ab3ff 100644 --- a/tests/files/nwProject-1.1.nwx +++ b/tests/files/nwProject-1.1.nwx @@ -5,12 +5,12 @@ Sample Project Jane Smith Jay Doh - 408 - 71 - 15120 + 5 + 10 + 1000 - False + True True True 636b6aa9b697b diff --git a/tests/files/nwProject-1.2.nwx b/tests/files/nwProject-1.2.nwx index af9892e8..e511afc9 100644 --- a/tests/files/nwProject-1.2.nwx +++ b/tests/files/nwProject-1.2.nwx @@ -5,15 +5,15 @@ Sample Project Jane Smith Jay Doh - 1122 - 191 - 53749 + 5 + 10 + 1000 - False - en + True + en_GB True - None + en_GB True 636b6aa9b697b 636b6aa9b697b diff --git a/tests/files/nwProject-1.3.nwx b/tests/files/nwProject-1.3.nwx index 74b42a6c..6c1d3a2e 100644 --- a/tests/files/nwProject-1.3.nwx +++ b/tests/files/nwProject-1.3.nwx @@ -5,15 +5,15 @@ Sample Project Jane Smith Jay Doh - 1312 - 199 - 65207 + 5 + 10 + 1000 - False + True en_GB True - None + en_GB True 636b6aa9b697b 636b6aa9b697b diff --git a/tests/files/nwProject-1.4.nwx b/tests/files/nwProject-1.4.nwx new file mode 100644 index 00000000..706b2266 --- /dev/null +++ b/tests/files/nwProject-1.4.nwx @@ -0,0 +1,163 @@ + + + + Sample Project + Sample Project + Jane Smith + Jay Doh + 5 + 10 + 1000 + + + True + en_GB + True + en_GB + 954 + 409 + + 636b6aa9b697b + 636b6aa9b697b + 7031beac91f75 + 7031beac91f75 + + + B + E + D + + + %title% + Chapter %chw%: %title% + %title% + Scene %ch%.%sc%: %title% + + + + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished + + + None + Minor + Major + Main + + + + + + Novel + + + + Title Page + + + + Page + + + + Part One + + + + Chapter One + + + + Making a Scene + + + + Another Scene + + + + Interlude + + + + A Note on Structure + + + + Chapter Two + + + + We Found John! + + + + Sequel + + + + Title Page + + + + Chapter One + + + + Characters + + + + Main Characters + + + + John Smith + + + + Jane Smith + + + + Locations + + + + Earth + + + + Space + + + + Mars + + + + Archive + + + + Scenes + + + + Old File + + + + Trash + + + + Delete Me! + + + diff --git a/tests/reference/projectXML_ReadCurrent.json b/tests/reference/projectXML_ReadCurrent.json new file mode 100644 index 00000000..141c3c3a --- /dev/null +++ b/tests/reference/projectXML_ReadCurrent.json @@ -0,0 +1,515 @@ +[ + { + "handle": "7031beac91f75", + "parent": null, + "root": "7031beac91f75", + "order": 0, + "type": "ROOT", + "class": "NOVEL", + "layout": "NO_LAYOUT", + "expanded": true, + "heading": "H0", + "charCount": 0, + "wordCount": 0, + "paraCount": 0, + "cursorPos": 0, + "label": "Novel", + "status": "sc24b8f", + "import": "ia857f0", + "active": false + }, + { + "handle": "53b69b83cdafc", + "parent": "7031beac91f75", + "root": "7031beac91f75", + "order": 0, + "type": "FILE", + "class": "NOVEL", + "layout": "DOCUMENT", + "expanded": false, + "heading": "H1", + "charCount": 93, + "wordCount": 19, + "paraCount": 2, + "cursorPos": 119, + "label": "Title Page", + "status": "sc24b8f", + "import": "ia857f0", + "active": true + }, + { + "handle": "974e400180a99", + "parent": "7031beac91f75", + "root": "7031beac91f75", + "order": 1, + "type": "FILE", + "class": "NOVEL", + "layout": "DOCUMENT", + "expanded": false, + "heading": "H0", + "charCount": 251, + "wordCount": 50, + "paraCount": 2, + "cursorPos": 277, + "label": "Page", + "status": "sf12341", + "import": "ia857f0", + "active": true + }, + { + "handle": "edca4be2fcaf8", + "parent": "7031beac91f75", + "root": "7031beac91f75", + "order": 2, + "type": "FILE", + "class": "NOVEL", + "layout": "DOCUMENT", + "expanded": false, + "heading": "H1", + "charCount": 26, + "wordCount": 6, + "paraCount": 1, + "cursorPos": 36, + "label": "Part One", + "status": "s90e6c9", + "import": "ia857f0", + "active": true + }, + { + "handle": "6a2d6d5f4f401", + "parent": "7031beac91f75", + "root": "7031beac91f75", + "order": 3, + "type": "FILE", + "class": "NOVEL", + "layout": "DOCUMENT", + "expanded": true, + "heading": "H2", + "charCount": 95, + "wordCount": 18, + "paraCount": 1, + "cursorPos": 291, + "label": "Chapter One", + "status": "sf24ce6", + "import": "ia857f0", + "active": true + }, + { + "handle": "636b6aa9b697b", + "parent": "6a2d6d5f4f401", + "root": "7031beac91f75", + "order": 0, + "type": "FILE", + "class": "NOVEL", + "layout": "DOCUMENT", + "expanded": false, + "heading": "H3", + "charCount": 2687, + "wordCount": 479, + "paraCount": 14, + "cursorPos": 67, + "label": "Making a Scene", + "status": "s90e6c9", + "import": "ia857f0", + "active": true + }, + { + "handle": "bc0cbd2a407f3", + "parent": "6a2d6d5f4f401", + "root": "7031beac91f75", + "order": 1, + "type": "FILE", + "class": "NOVEL", + "layout": "DOCUMENT", + "expanded": false, + "heading": "H3", + "charCount": 548, + "wordCount": 108, + "paraCount": 3, + "cursorPos": 465, + "label": "Another Scene", + "status": "s90e6c9", + "import": "ia857f0", + "active": true + }, + { + "handle": "ba8a28a246524", + "parent": "7031beac91f75", + "root": "7031beac91f75", + "order": 4, + "type": "FILE", + "class": "NOVEL", + "layout": "DOCUMENT", + "expanded": false, + "heading": "H2", + "charCount": 617, + "wordCount": 101, + "paraCount": 3, + "cursorPos": 310, + "label": "Interlude", + "status": "s78ea90", + "import": "ia857f0", + "active": true + }, + { + "handle": "96b68994dfa3d", + "parent": "7031beac91f75", + "root": "7031beac91f75", + "order": 5, + "type": "FILE", + "class": "NOVEL", + "layout": "NOTE", + "expanded": false, + "heading": "H1", + "charCount": 1909, + "wordCount": 346, + "paraCount": 7, + "cursorPos": 0, + "label": "A Note on Structure", + "status": "sf24ce6", + "import": "ia857f0", + "active": false + }, + { + "handle": "88706ddc78b1b", + "parent": "7031beac91f75", + "root": "7031beac91f75", + "order": 6, + "type": "FILE", + "class": "NOVEL", + "layout": "DOCUMENT", + "expanded": true, + "heading": "H2", + "charCount": 139, + "wordCount": 28, + "paraCount": 1, + "cursorPos": 188, + "label": "Chapter Two", + "status": "s90e6c9", + "import": "ia857f0", + "active": true + }, + { + "handle": "ae7339df26ded", + "parent": "88706ddc78b1b", + "root": "7031beac91f75", + "order": 0, + "type": "FILE", + "class": "NOVEL", + "layout": "DOCUMENT", + "expanded": false, + "heading": "H3", + "charCount": 189, + "wordCount": 37, + "paraCount": 1, + "cursorPos": 0, + "label": "We Found John!", + "status": "s90e6c9", + "import": "ia857f0", + "active": true + }, + { + "handle": "e5e47ebf63b1c", + "parent": null, + "root": "e5e47ebf63b1c", + "order": 1, + "type": "ROOT", + "class": "NOVEL", + "layout": "NO_LAYOUT", + "expanded": true, + "heading": "H0", + "charCount": 0, + "wordCount": 0, + "paraCount": 0, + "cursorPos": 0, + "label": "Sequel", + "status": "sf12341", + "import": "ia857f0", + "active": false + }, + { + "handle": "bacb7059e3083", + "parent": "e5e47ebf63b1c", + "root": "e5e47ebf63b1c", + "order": 0, + "type": "FILE", + "class": "NOVEL", + "layout": "DOCUMENT", + "expanded": false, + "heading": "H1", + "charCount": 27, + "wordCount": 5, + "paraCount": 1, + "cursorPos": 100, + "label": "Title Page", + "status": "sc24b8f", + "import": "ia857f0", + "active": true + }, + { + "handle": "a520879ca0b45", + "parent": "e5e47ebf63b1c", + "root": "e5e47ebf63b1c", + "order": 1, + "type": "FILE", + "class": "NOVEL", + "layout": "DOCUMENT", + "expanded": false, + "heading": "H2", + "charCount": 299, + "wordCount": 55, + "paraCount": 2, + "cursorPos": 104, + "label": "Chapter One", + "status": "s90e6c9", + "import": "ia857f0", + "active": true + }, + { + "handle": "f6622b4617424", + "parent": null, + "root": "f6622b4617424", + "order": 2, + "type": "ROOT", + "class": "CHARACTER", + "layout": "NO_LAYOUT", + "expanded": true, + "heading": "H0", + "charCount": 0, + "wordCount": 0, + "paraCount": 0, + "cursorPos": 0, + "label": "Characters", + "status": "sf12341", + "import": "ia857f0", + "active": false + }, + { + "handle": "f7e2d9f330615", + "parent": "f6622b4617424", + "root": "f6622b4617424", + "order": 0, + "type": "FOLDER", + "class": "CHARACTER", + "layout": "NO_LAYOUT", + "expanded": true, + "heading": "H0", + "charCount": 0, + "wordCount": 0, + "paraCount": 0, + "cursorPos": 0, + "label": "Main Characters", + "status": "sf12341", + "import": "ia857f0", + "active": false + }, + { + "handle": "14298de4d9524", + "parent": "f7e2d9f330615", + "root": "f6622b4617424", + "order": 0, + "type": "FILE", + "class": "CHARACTER", + "layout": "NOTE", + "expanded": false, + "heading": "H1", + "charCount": 49, + "wordCount": 9, + "paraCount": 1, + "cursorPos": 24, + "label": "John Smith", + "status": "sf12341", + "import": "icfb3a5", + "active": true + }, + { + "handle": "bb2c23b3c42cc", + "parent": "f7e2d9f330615", + "root": "f6622b4617424", + "order": 1, + "type": "FILE", + "class": "CHARACTER", + "layout": "NOTE", + "expanded": false, + "heading": "H1", + "charCount": 55, + "wordCount": 9, + "paraCount": 1, + "cursorPos": 25, + "label": "Jane Smith", + "status": "sf12341", + "import": "i2d7a54", + "active": true + }, + { + "handle": "15c4492bd5107", + "parent": null, + "root": "15c4492bd5107", + "order": 3, + "type": "ROOT", + "class": "WORLD", + "layout": "NO_LAYOUT", + "expanded": true, + "heading": "H0", + "charCount": 0, + "wordCount": 0, + "paraCount": 0, + "cursorPos": 0, + "label": "Locations", + "status": "sf12341", + "import": "ia857f0", + "active": false + }, + { + "handle": "b3e74dbc1f584", + "parent": "15c4492bd5107", + "root": "15c4492bd5107", + "order": 0, + "type": "FILE", + "class": "WORLD", + "layout": "NOTE", + "expanded": false, + "heading": "H1", + "charCount": 76, + "wordCount": 15, + "paraCount": 1, + "cursorPos": 20, + "label": "Earth", + "status": "sf12341", + "import": "i56be10", + "active": true + }, + { + "handle": "f1471bef9f2ae", + "parent": "15c4492bd5107", + "root": "15c4492bd5107", + "order": 1, + "type": "FILE", + "class": "WORLD", + "layout": "NOTE", + "expanded": false, + "heading": "H1", + "charCount": 115, + "wordCount": 24, + "paraCount": 1, + "cursorPos": 133, + "label": "Space", + "status": "sf12341", + "import": "icfb3a5", + "active": true + }, + { + "handle": "5eaea4e8cdee8", + "parent": "15c4492bd5107", + "root": "15c4492bd5107", + "order": 2, + "type": "FILE", + "class": "WORLD", + "layout": "NOTE", + "expanded": false, + "heading": "H1", + "charCount": 28, + "wordCount": 6, + "paraCount": 1, + "cursorPos": 45, + "label": "Mars", + "status": "sf12341", + "import": "i2d7a54", + "active": true + }, + { + "handle": "6827118336ac1", + "parent": null, + "root": "6827118336ac1", + "order": 4, + "type": "ROOT", + "class": "ARCHIVE", + "layout": "NO_LAYOUT", + "expanded": true, + "heading": "H0", + "charCount": 0, + "wordCount": 0, + "paraCount": 0, + "cursorPos": 0, + "label": "Archive", + "status": "sf12341", + "import": "ia857f0", + "active": false + }, + { + "handle": "ae9bf3c3ea159", + "parent": "6827118336ac1", + "root": "6827118336ac1", + "order": 0, + "type": "FOLDER", + "class": "ARCHIVE", + "layout": "NO_LAYOUT", + "expanded": true, + "heading": "H0", + "charCount": 0, + "wordCount": 0, + "paraCount": 0, + "cursorPos": 0, + "label": "Scenes", + "status": "sf12341", + "import": "ia857f0", + "active": false + }, + { + "handle": "8a5deb88c0e97", + "parent": "ae9bf3c3ea159", + "root": "6827118336ac1", + "order": 0, + "type": "FILE", + "class": "ARCHIVE", + "layout": "DOCUMENT", + "expanded": false, + "heading": "H3", + "charCount": 232, + "wordCount": 42, + "paraCount": 1, + "cursorPos": 239, + "label": "Old File", + "status": "s90e6c9", + "import": "ia857f0", + "active": true + }, + { + "handle": "98acd8c76c93a", + "parent": null, + "root": "98acd8c76c93a", + "order": 5, + "type": "ROOT", + "class": "TRASH", + "layout": "NO_LAYOUT", + "expanded": true, + "heading": "H0", + "charCount": 0, + "wordCount": 0, + "paraCount": 0, + "cursorPos": 0, + "label": "Trash", + "status": "sf12341", + "import": "ia857f0", + "active": false + }, + { + "handle": "b8136a5a774a0", + "parent": "98acd8c76c93a", + "root": "98acd8c76c93a", + "order": 0, + "type": "FILE", + "class": "TRASH", + "layout": "DOCUMENT", + "expanded": false, + "heading": "H3", + "charCount": 30, + "wordCount": 6, + "paraCount": 1, + "cursorPos": 36, + "label": "Delete Me!", + "status": "sf12341", + "import": "ia857f0", + "active": true + } +] \ No newline at end of file diff --git a/tests/reference/projectXML_ReadLegacy10.json b/tests/reference/projectXML_ReadLegacy10.json new file mode 100644 index 00000000..27e9da17 --- /dev/null +++ b/tests/reference/projectXML_ReadLegacy10.json @@ -0,0 +1,362 @@ +[ + { + "handle": "7031beac91f75", + "parent": null, + "root": null, + "order": 0, + "heading": "H0", + "label": "Novel", + "type": "ROOT", + "class": "NOVEL", + "expanded": true, + "status": "s000002" + }, + { + "handle": "53b69b83cdafc", + "parent": "7031beac91f75", + "root": null, + "order": 0, + "heading": "H0", + "label": "Title Page", + "type": "FILE", + "class": "NOVEL", + "expanded": false, + "active": true, + "layout": "DOCUMENT", + "charCount": 72, + "wordCount": 15, + "paraCount": 2, + "cursorPos": 78, + "status": "s000002" + }, + { + "handle": "974e400180a99", + "parent": "7031beac91f75", + "root": null, + "order": 1, + "heading": "H0", + "label": "Page", + "type": "FILE", + "class": "NOVEL", + "expanded": false, + "active": true, + "layout": "DOCUMENT", + "charCount": 208, + "wordCount": 40, + "paraCount": 2, + "cursorPos": 213, + "status": "s000000" + }, + { + "handle": "edca4be2fcaf8", + "parent": "7031beac91f75", + "root": null, + "order": 2, + "heading": "H0", + "label": "Part One", + "type": "FILE", + "class": "NOVEL", + "expanded": false, + "active": true, + "layout": "DOCUMENT", + "charCount": 23, + "wordCount": 5, + "paraCount": 1, + "cursorPos": 0, + "status": "s000000" + }, + { + "handle": "e7ded148d6e4a", + "parent": "7031beac91f75", + "root": null, + "order": 3, + "heading": "H0", + "label": "A Folder", + "type": "FOLDER", + "class": "NOVEL", + "expanded": true, + "status": "s000003" + }, + { + "handle": "6a2d6d5f4f401", + "parent": "e7ded148d6e4a", + "root": null, + "order": 0, + "heading": "H0", + "label": "Chapter One", + "type": "FILE", + "class": "NOVEL", + "expanded": false, + "active": true, + "layout": "DOCUMENT", + "charCount": 12, + "wordCount": 3, + "paraCount": 0, + "cursorPos": 215, + "status": "s000001" + }, + { + "handle": "636b6aa9b697b", + "parent": "e7ded148d6e4a", + "root": null, + "order": 1, + "heading": "H0", + "label": "Making a Scene", + "type": "FILE", + "class": "NOVEL", + "expanded": false, + "active": true, + "layout": "DOCUMENT", + "charCount": 1199, + "wordCount": 216, + "paraCount": 7, + "cursorPos": 527, + "status": "s000003" + }, + { + "handle": "bc0cbd2a407f3", + "parent": "e7ded148d6e4a", + "root": null, + "order": 2, + "heading": "H0", + "label": "Another Scene", + "type": "FILE", + "class": "NOVEL", + "expanded": false, + "active": true, + "layout": "DOCUMENT", + "charCount": 476, + "wordCount": 93, + "paraCount": 3, + "cursorPos": 551, + "status": "s000003" + }, + { + "handle": "ba8a28a246524", + "parent": "e7ded148d6e4a", + "root": null, + "order": 3, + "heading": "H0", + "label": "Interlude", + "type": "FILE", + "class": "NOVEL", + "expanded": false, + "active": true, + "layout": "DOCUMENT", + "charCount": 633, + "wordCount": 101, + "paraCount": 3, + "cursorPos": 1238, + "status": "s000006" + }, + { + "handle": "96b68994dfa3d", + "parent": "e7ded148d6e4a", + "root": null, + "order": 4, + "heading": "H0", + "label": "A Note on Structure", + "type": "FILE", + "class": "NOVEL", + "expanded": false, + "active": false, + "layout": "NOTE", + "charCount": 1692, + "wordCount": 313, + "paraCount": 6, + "cursorPos": 1721, + "status": "s000004" + }, + { + "handle": "88706ddc78b1b", + "parent": "e7ded148d6e4a", + "root": null, + "order": 5, + "heading": "H0", + "label": "Chapter Two", + "type": "FILE", + "class": "NOVEL", + "expanded": false, + "active": true, + "layout": "DOCUMENT", + "charCount": 139, + "wordCount": 28, + "paraCount": 1, + "cursorPos": 343, + "status": "s000003" + }, + { + "handle": "ae7339df26ded", + "parent": "e7ded148d6e4a", + "root": null, + "order": 6, + "heading": "H0", + "label": "We Found John!", + "type": "FILE", + "class": "NOVEL", + "expanded": false, + "active": true, + "layout": "DOCUMENT", + "charCount": 189, + "wordCount": 37, + "paraCount": 1, + "cursorPos": 224, + "status": "s000003" + }, + { + "handle": "f6622b4617424", + "parent": null, + "root": null, + "order": 1, + "heading": "H0", + "label": "Characters", + "type": "ROOT", + "class": "CHARACTER", + "expanded": true, + "import": null + }, + { + "handle": "f7e2d9f330615", + "parent": "f6622b4617424", + "root": null, + "order": 0, + "heading": "H0", + "label": "Main Characters", + "type": "FOLDER", + "class": "CHARACTER", + "expanded": true, + "import": null + }, + { + "handle": "14298de4d9524", + "parent": "f7e2d9f330615", + "root": null, + "order": 0, + "heading": "H0", + "label": "John Smith", + "type": "FILE", + "class": "CHARACTER", + "expanded": false, + "active": true, + "layout": "NOTE", + "charCount": 49, + "wordCount": 9, + "paraCount": 1, + "cursorPos": 24, + "import": "i000008" + }, + { + "handle": "bb2c23b3c42cc", + "parent": "f7e2d9f330615", + "root": null, + "order": 1, + "heading": "H0", + "label": "Jane Smith", + "type": "FILE", + "class": "CHARACTER", + "expanded": false, + "active": true, + "layout": "NOTE", + "charCount": 55, + "wordCount": 9, + "paraCount": 1, + "cursorPos": 25, + "import": "i000009" + }, + { + "handle": "15c4492bd5107", + "parent": null, + "root": null, + "order": 2, + "heading": "H0", + "label": "Locations", + "type": "ROOT", + "class": "WORLD", + "expanded": true, + "import": null + }, + { + "handle": "b3e74dbc1f584", + "parent": "15c4492bd5107", + "root": null, + "order": 0, + "heading": "H0", + "label": "Earth", + "type": "FILE", + "class": "WORLD", + "expanded": false, + "active": true, + "layout": "NOTE", + "charCount": 76, + "wordCount": 15, + "paraCount": 1, + "cursorPos": 20, + "import": "i00000a" + }, + { + "handle": "f1471bef9f2ae", + "parent": "15c4492bd5107", + "root": null, + "order": 1, + "heading": "H0", + "label": "Space", + "type": "FILE", + "class": "WORLD", + "expanded": false, + "active": true, + "layout": "NOTE", + "charCount": 115, + "wordCount": 24, + "paraCount": 1, + "cursorPos": 133, + "import": "i000008" + }, + { + "handle": "5eaea4e8cdee8", + "parent": "15c4492bd5107", + "root": null, + "order": 2, + "heading": "H0", + "label": "Mars", + "type": "FILE", + "class": "WORLD", + "expanded": false, + "active": true, + "layout": "NOTE", + "charCount": 28, + "wordCount": 6, + "paraCount": 1, + "cursorPos": 45, + "import": "i000009" + }, + { + "handle": "98acd8c76c93a", + "parent": null, + "root": null, + "order": 3, + "heading": "H0", + "label": "Trash", + "type": "ROOT", + "class": "TRASH", + "expanded": true, + "import": null + }, + { + "handle": "b8136a5a774a0", + "parent": "98acd8c76c93a", + "root": null, + "order": 0, + "heading": "H0", + "label": "Delete Me!", + "type": "FILE", + "class": "NOVEL", + "expanded": false, + "active": true, + "layout": "DOCUMENT", + "charCount": 0, + "wordCount": 0, + "paraCount": 0, + "cursorPos": 36, + "status": "s000000" + } +] \ No newline at end of file diff --git a/tests/reference/projectXML_ReadLegacy10.nwx b/tests/reference/projectXML_ReadLegacy10.nwx new file mode 100644 index 00000000..8e7f0a99 --- /dev/null +++ b/tests/reference/projectXML_ReadLegacy10.nwx @@ -0,0 +1,143 @@ + + + + Sample Project + Sample Project + Jane Smith + Jay Doh + 0 + 0 + 1000 + + + True + None + True + None + 0 + 0 + + None + None + None + None + + + B + E + D + + + %title% + Chapter %ch%: %title% + %title% + Scene %ch%.%sc%: %title% + + + + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished + + + None + Minor + Major + Main + + + + + + Novel + + + + Title Page + + + + Page + + + + Part One + + + + A Folder + + + + Chapter One + + + + Making a Scene + + + + Another Scene + + + + Interlude + + + + A Note on Structure + + + + Chapter Two + + + + We Found John! + + + + Characters + + + + Main Characters + + + + John Smith + + + + Jane Smith + + + + Locations + + + + Earth + + + + Space + + + + Mars + + + + Trash + + + + Delete Me! + + + diff --git a/tests/reference/projectXML_ReadLegacy11.json b/tests/reference/projectXML_ReadLegacy11.json new file mode 100644 index 00000000..16e84adc --- /dev/null +++ b/tests/reference/projectXML_ReadLegacy11.json @@ -0,0 +1,346 @@ +[ + { + "handle": "7031beac91f75", + "parent": null, + "root": null, + "order": 0, + "heading": "H0", + "label": "Novel", + "type": "ROOT", + "class": "NOVEL", + "expanded": true, + "status": "s000002" + }, + { + "handle": "53b69b83cdafc", + "parent": "7031beac91f75", + "root": null, + "order": 0, + "heading": "H0", + "label": "Title Page", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 72, + "wordCount": 15, + "paraCount": 2, + "cursorPos": 78, + "status": "s000002" + }, + { + "handle": "974e400180a99", + "parent": "7031beac91f75", + "root": null, + "order": 1, + "heading": "H0", + "label": "Page", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 210, + "wordCount": 40, + "paraCount": 2, + "cursorPos": 213, + "status": "s000000" + }, + { + "handle": "edca4be2fcaf8", + "parent": "7031beac91f75", + "root": null, + "order": 2, + "heading": "H0", + "label": "Part One", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 23, + "wordCount": 5, + "paraCount": 1, + "cursorPos": 0, + "status": "s000000" + }, + { + "handle": "e7ded148d6e4a", + "parent": "7031beac91f75", + "root": null, + "order": 3, + "heading": "H0", + "label": "A Folder", + "type": "FOLDER", + "class": "NOVEL", + "expanded": true, + "status": "s000003" + }, + { + "handle": "6a2d6d5f4f401", + "parent": "e7ded148d6e4a", + "root": null, + "order": 0, + "heading": "H0", + "label": "Chapter One", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 12, + "wordCount": 3, + "paraCount": 0, + "cursorPos": 215, + "status": "s000001" + }, + { + "handle": "636b6aa9b697b", + "parent": "e7ded148d6e4a", + "root": null, + "order": 1, + "heading": "H0", + "label": "Making a Scene", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 1483, + "wordCount": 263, + "paraCount": 8, + "cursorPos": 1086, + "status": "s000003" + }, + { + "handle": "bc0cbd2a407f3", + "parent": "e7ded148d6e4a", + "root": null, + "order": 2, + "heading": "H0", + "label": "Another Scene", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 476, + "wordCount": 93, + "paraCount": 3, + "cursorPos": 428, + "status": "s000003" + }, + { + "handle": "ba8a28a246524", + "parent": "e7ded148d6e4a", + "root": null, + "order": 3, + "heading": "H0", + "label": "Interlude", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 633, + "wordCount": 101, + "paraCount": 3, + "cursorPos": 1238, + "status": "s000006" + }, + { + "handle": "96b68994dfa3d", + "parent": "e7ded148d6e4a", + "root": null, + "order": 4, + "heading": "H0", + "label": "A Note on Structure", + "type": "FILE", + "class": "NOVEL", + "active": false, + "layout": "NOTE", + "charCount": 1692, + "wordCount": 313, + "paraCount": 6, + "cursorPos": 1721, + "status": "s000004" + }, + { + "handle": "88706ddc78b1b", + "parent": "e7ded148d6e4a", + "root": null, + "order": 5, + "heading": "H0", + "label": "Chapter Two", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 139, + "wordCount": 28, + "paraCount": 1, + "cursorPos": 343, + "status": "s000003" + }, + { + "handle": "ae7339df26ded", + "parent": "e7ded148d6e4a", + "root": null, + "order": 6, + "heading": "H0", + "label": "We Found John!", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 189, + "wordCount": 37, + "paraCount": 1, + "cursorPos": 224, + "status": "s000003" + }, + { + "handle": "f6622b4617424", + "parent": null, + "root": null, + "order": 1, + "heading": "H0", + "label": "Characters", + "type": "ROOT", + "class": "CHARACTER", + "expanded": true, + "import": null + }, + { + "handle": "f7e2d9f330615", + "parent": "f6622b4617424", + "root": null, + "order": 0, + "heading": "H0", + "label": "Main Characters", + "type": "FOLDER", + "class": "CHARACTER", + "expanded": true, + "import": null + }, + { + "handle": "14298de4d9524", + "parent": "f7e2d9f330615", + "root": null, + "order": 0, + "heading": "H0", + "label": "John Smith", + "type": "FILE", + "class": "CHARACTER", + "active": true, + "layout": "NOTE", + "charCount": 49, + "wordCount": 9, + "paraCount": 1, + "cursorPos": 24, + "import": "i000008" + }, + { + "handle": "bb2c23b3c42cc", + "parent": "f7e2d9f330615", + "root": null, + "order": 1, + "heading": "H0", + "label": "Jane Smith", + "type": "FILE", + "class": "CHARACTER", + "active": true, + "layout": "NOTE", + "charCount": 55, + "wordCount": 9, + "paraCount": 1, + "cursorPos": 25, + "import": "i000009" + }, + { + "handle": "15c4492bd5107", + "parent": null, + "root": null, + "order": 2, + "heading": "H0", + "label": "Locations", + "type": "ROOT", + "class": "WORLD", + "expanded": true, + "import": null + }, + { + "handle": "b3e74dbc1f584", + "parent": "15c4492bd5107", + "root": null, + "order": 0, + "heading": "H0", + "label": "Earth", + "type": "FILE", + "class": "WORLD", + "active": true, + "layout": "NOTE", + "charCount": 76, + "wordCount": 15, + "paraCount": 1, + "cursorPos": 20, + "import": "i00000a" + }, + { + "handle": "f1471bef9f2ae", + "parent": "15c4492bd5107", + "root": null, + "order": 1, + "heading": "H0", + "label": "Space", + "type": "FILE", + "class": "WORLD", + "active": true, + "layout": "NOTE", + "charCount": 115, + "wordCount": 24, + "paraCount": 1, + "cursorPos": 133, + "import": "i000008" + }, + { + "handle": "5eaea4e8cdee8", + "parent": "15c4492bd5107", + "root": null, + "order": 2, + "heading": "H0", + "label": "Mars", + "type": "FILE", + "class": "WORLD", + "active": true, + "layout": "NOTE", + "charCount": 28, + "wordCount": 6, + "paraCount": 1, + "cursorPos": 45, + "import": "i000009" + }, + { + "handle": "98acd8c76c93a", + "parent": null, + "root": null, + "order": 3, + "heading": "H0", + "label": "Trash", + "type": "ROOT", + "class": "TRASH", + "expanded": true, + "import": null + }, + { + "handle": "b8136a5a774a0", + "parent": "98acd8c76c93a", + "root": null, + "order": 0, + "heading": "H0", + "label": "Delete Me!", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 30, + "wordCount": 6, + "paraCount": 1, + "cursorPos": 36, + "status": "s000000" + } +] \ No newline at end of file diff --git a/tests/reference/projectXML_ReadLegacy11.nwx b/tests/reference/projectXML_ReadLegacy11.nwx new file mode 100644 index 00000000..fce991d3 --- /dev/null +++ b/tests/reference/projectXML_ReadLegacy11.nwx @@ -0,0 +1,143 @@ + + + + Sample Project + Sample Project + Jane Smith + Jay Doh + 5 + 10 + 1000 + + + True + None + True + None + 0 + 0 + + None + None + None + None + + + B + E + D + + + %title% + Chapter %ch%: %title% + %title% + Scene %ch%.%sc%: %title% + + + + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished + + + None + Minor + Major + Main + + + + + + Novel + + + + Title Page + + + + Page + + + + Part One + + + + A Folder + + + + Chapter One + + + + Making a Scene + + + + Another Scene + + + + Interlude + + + + A Note on Structure + + + + Chapter Two + + + + We Found John! + + + + Characters + + + + Main Characters + + + + John Smith + + + + Jane Smith + + + + Locations + + + + Earth + + + + Space + + + + Mars + + + + Trash + + + + Delete Me! + + + diff --git a/tests/reference/projectXML_ReadLegacy12.json b/tests/reference/projectXML_ReadLegacy12.json new file mode 100644 index 00000000..917cb633 --- /dev/null +++ b/tests/reference/projectXML_ReadLegacy12.json @@ -0,0 +1,387 @@ +[ + { + "handle": "7031beac91f75", + "parent": null, + "root": null, + "order": 0, + "heading": "H0", + "label": "Novel", + "type": "ROOT", + "class": "NOVEL", + "expanded": true, + "status": "s000002" + }, + { + "handle": "53b69b83cdafc", + "parent": "7031beac91f75", + "root": null, + "order": 0, + "heading": "H0", + "label": "Title Page", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 241, + "wordCount": 42, + "paraCount": 3, + "cursorPos": 252, + "status": "s000002" + }, + { + "handle": "974e400180a99", + "parent": "7031beac91f75", + "root": null, + "order": 1, + "heading": "H0", + "label": "Page", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 125, + "wordCount": 26, + "paraCount": 2, + "cursorPos": 127, + "status": "s000000" + }, + { + "handle": "edca4be2fcaf8", + "parent": "7031beac91f75", + "root": null, + "order": 2, + "heading": "H0", + "label": "Part One", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 26, + "wordCount": 6, + "paraCount": 1, + "cursorPos": 30, + "status": "s000000" + }, + { + "handle": "e7ded148d6e4a", + "parent": "7031beac91f75", + "root": null, + "order": 3, + "heading": "H0", + "label": "A Folder", + "type": "FOLDER", + "class": "NOVEL", + "expanded": true, + "status": "s000003" + }, + { + "handle": "6a2d6d5f4f401", + "parent": "e7ded148d6e4a", + "root": null, + "order": 0, + "heading": "H0", + "label": "Chapter One", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 75, + "wordCount": 14, + "paraCount": 1, + "cursorPos": 279, + "status": "s000001" + }, + { + "handle": "636b6aa9b697b", + "parent": "e7ded148d6e4a", + "root": null, + "order": 1, + "heading": "H0", + "label": "Making a Scene", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 2429, + "wordCount": 432, + "paraCount": 14, + "cursorPos": 61, + "status": "s000003" + }, + { + "handle": "bc0cbd2a407f3", + "parent": "e7ded148d6e4a", + "root": null, + "order": 2, + "heading": "H0", + "label": "Another Scene", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 476, + "wordCount": 93, + "paraCount": 3, + "cursorPos": 577, + "status": "s000003" + }, + { + "handle": "ba8a28a246524", + "parent": "e7ded148d6e4a", + "root": null, + "order": 3, + "heading": "H0", + "label": "Interlude", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 617, + "wordCount": 101, + "paraCount": 3, + "cursorPos": 1137, + "status": "s000000" + }, + { + "handle": "96b68994dfa3d", + "parent": "e7ded148d6e4a", + "root": null, + "order": 4, + "heading": "H0", + "label": "A Note on Structure", + "type": "FILE", + "class": "NOVEL", + "active": false, + "layout": "NOTE", + "charCount": 1692, + "wordCount": 313, + "paraCount": 6, + "cursorPos": 1110, + "status": "s000004" + }, + { + "handle": "88706ddc78b1b", + "parent": "e7ded148d6e4a", + "root": null, + "order": 5, + "heading": "H0", + "label": "Chapter Two", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 139, + "wordCount": 28, + "paraCount": 1, + "cursorPos": 343, + "status": "s000003" + }, + { + "handle": "ae7339df26ded", + "parent": "e7ded148d6e4a", + "root": null, + "order": 6, + "heading": "H0", + "label": "We Found John!", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 189, + "wordCount": 37, + "paraCount": 1, + "cursorPos": 224, + "status": "s000003" + }, + { + "handle": "f6622b4617424", + "parent": null, + "root": null, + "order": 1, + "heading": "H0", + "label": "Characters", + "type": "ROOT", + "class": "CHARACTER", + "expanded": true, + "import": null + }, + { + "handle": "f7e2d9f330615", + "parent": "f6622b4617424", + "root": null, + "order": 0, + "heading": "H0", + "label": "Main Characters", + "type": "FOLDER", + "class": "CHARACTER", + "expanded": true, + "import": null + }, + { + "handle": "14298de4d9524", + "parent": "f7e2d9f330615", + "root": null, + "order": 0, + "heading": "H0", + "label": "John Smith", + "type": "FILE", + "class": "CHARACTER", + "active": true, + "layout": "NOTE", + "charCount": 49, + "wordCount": 9, + "paraCount": 1, + "cursorPos": 24, + "import": "i000008" + }, + { + "handle": "bb2c23b3c42cc", + "parent": "f7e2d9f330615", + "root": null, + "order": 1, + "heading": "H0", + "label": "Jane Smith", + "type": "FILE", + "class": "CHARACTER", + "active": true, + "layout": "NOTE", + "charCount": 55, + "wordCount": 9, + "paraCount": 1, + "cursorPos": 25, + "import": "i000009" + }, + { + "handle": "15c4492bd5107", + "parent": null, + "root": null, + "order": 2, + "heading": "H0", + "label": "Locations", + "type": "ROOT", + "class": "WORLD", + "expanded": true, + "import": null + }, + { + "handle": "b3e74dbc1f584", + "parent": "15c4492bd5107", + "root": null, + "order": 0, + "heading": "H0", + "label": "Earth", + "type": "FILE", + "class": "WORLD", + "active": true, + "layout": "NOTE", + "charCount": 76, + "wordCount": 15, + "paraCount": 1, + "cursorPos": 20, + "import": "i00000a" + }, + { + "handle": "f1471bef9f2ae", + "parent": "15c4492bd5107", + "root": null, + "order": 1, + "heading": "H0", + "label": "Space", + "type": "FILE", + "class": "WORLD", + "active": true, + "layout": "NOTE", + "charCount": 115, + "wordCount": 24, + "paraCount": 1, + "cursorPos": 133, + "import": "i000008" + }, + { + "handle": "5eaea4e8cdee8", + "parent": "15c4492bd5107", + "root": null, + "order": 2, + "heading": "H0", + "label": "Mars", + "type": "FILE", + "class": "WORLD", + "active": true, + "layout": "NOTE", + "charCount": 28, + "wordCount": 6, + "paraCount": 1, + "cursorPos": 45, + "import": "i000009" + }, + { + "handle": "6827118336ac1", + "parent": null, + "root": null, + "order": 3, + "heading": "H0", + "label": "Outtakes", + "type": "ROOT", + "class": "ARCHIVE", + "expanded": true, + "status": null + }, + { + "handle": "ae9bf3c3ea159", + "parent": "6827118336ac1", + "root": null, + "order": 0, + "heading": "H0", + "label": "Scenes", + "type": "FOLDER", + "class": "ARCHIVE", + "expanded": true, + "status": null + }, + { + "handle": "8a5deb88c0e97", + "parent": "ae9bf3c3ea159", + "root": null, + "order": 0, + "heading": "H0", + "label": "Old File", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 315, + "wordCount": 55, + "paraCount": 1, + "cursorPos": 322, + "status": "s000003" + }, + { + "handle": "98acd8c76c93a", + "parent": null, + "root": null, + "order": 4, + "heading": "H0", + "label": "Trash", + "type": "ROOT", + "class": "TRASH", + "expanded": true, + "import": null + }, + { + "handle": "b8136a5a774a0", + "parent": "98acd8c76c93a", + "root": null, + "order": 0, + "heading": "H0", + "label": "Delete Me!", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 30, + "wordCount": 6, + "paraCount": 1, + "cursorPos": 36, + "status": "s000000" + } +] \ No newline at end of file diff --git a/tests/reference/projectXML_ReadLegacy12.nwx b/tests/reference/projectXML_ReadLegacy12.nwx new file mode 100644 index 00000000..9de6966b --- /dev/null +++ b/tests/reference/projectXML_ReadLegacy12.nwx @@ -0,0 +1,155 @@ + + + + Sample Project + Sample Project + Jane Smith + Jay Doh + 5 + 10 + 1000 + + + True + en_GB + True + en_GB + 840 + 376 + + None + None + None + None + + + B + E + D + + + %title% + Chapter %chw%: %title% + %title% + Scene %ch%.%sc%: %title% + + + + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished + + + None + Minor + Major + Main + + + + + + Novel + + + + Title Page + + + + Page + + + + Part One + + + + A Folder + + + + Chapter One + + + + Making a Scene + + + + Another Scene + + + + Interlude + + + + A Note on Structure + + + + Chapter Two + + + + We Found John! + + + + Characters + + + + Main Characters + + + + John Smith + + + + Jane Smith + + + + Locations + + + + Earth + + + + Space + + + + Mars + + + + Outtakes + + + + Scenes + + + + Old File + + + + Trash + + + + Delete Me! + + + diff --git a/tests/reference/projectXML_ReadLegacy13.json b/tests/reference/projectXML_ReadLegacy13.json new file mode 100644 index 00000000..55508758 --- /dev/null +++ b/tests/reference/projectXML_ReadLegacy13.json @@ -0,0 +1,387 @@ +[ + { + "handle": "7031beac91f75", + "parent": null, + "root": null, + "order": 0, + "heading": "H0", + "label": "Novel", + "type": "ROOT", + "class": "NOVEL", + "expanded": true, + "status": "s000002" + }, + { + "handle": "53b69b83cdafc", + "parent": "7031beac91f75", + "root": null, + "order": 0, + "heading": "H0", + "label": "Title Page", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 93, + "wordCount": 19, + "paraCount": 2, + "cursorPos": 2, + "status": "s000002" + }, + { + "handle": "974e400180a99", + "parent": "7031beac91f75", + "root": null, + "order": 1, + "heading": "H0", + "label": "Page", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 186, + "wordCount": 39, + "paraCount": 2, + "cursorPos": 212, + "status": "s000000" + }, + { + "handle": "edca4be2fcaf8", + "parent": "7031beac91f75", + "root": null, + "order": 2, + "heading": "H0", + "label": "Part One", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 26, + "wordCount": 6, + "paraCount": 1, + "cursorPos": 33, + "status": "s000000" + }, + { + "handle": "e7ded148d6e4a", + "parent": "7031beac91f75", + "root": null, + "order": 3, + "heading": "H0", + "label": "A Folder", + "type": "FOLDER", + "class": "NOVEL", + "expanded": true, + "status": "s000003" + }, + { + "handle": "6a2d6d5f4f401", + "parent": "e7ded148d6e4a", + "root": null, + "order": 0, + "heading": "H0", + "label": "Chapter One", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 75, + "wordCount": 14, + "paraCount": 1, + "cursorPos": 279, + "status": "s000001" + }, + { + "handle": "636b6aa9b697b", + "parent": "e7ded148d6e4a", + "root": null, + "order": 1, + "heading": "H0", + "label": "Making a Scene", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 2429, + "wordCount": 432, + "paraCount": 14, + "cursorPos": 62, + "status": "s000003" + }, + { + "handle": "bc0cbd2a407f3", + "parent": "e7ded148d6e4a", + "root": null, + "order": 2, + "heading": "H0", + "label": "Another Scene", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 476, + "wordCount": 93, + "paraCount": 3, + "cursorPos": 577, + "status": "s000003" + }, + { + "handle": "ba8a28a246524", + "parent": "e7ded148d6e4a", + "root": null, + "order": 3, + "heading": "H0", + "label": "Interlude", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 617, + "wordCount": 101, + "paraCount": 3, + "cursorPos": 4, + "status": "s000000" + }, + { + "handle": "96b68994dfa3d", + "parent": "e7ded148d6e4a", + "root": null, + "order": 4, + "heading": "H0", + "label": "A Note on Structure", + "type": "FILE", + "class": "NOVEL", + "active": false, + "layout": "NOTE", + "charCount": 1692, + "wordCount": 313, + "paraCount": 6, + "cursorPos": 1110, + "status": "s000004" + }, + { + "handle": "88706ddc78b1b", + "parent": "e7ded148d6e4a", + "root": null, + "order": 5, + "heading": "H0", + "label": "Chapter Two", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 139, + "wordCount": 28, + "paraCount": 1, + "cursorPos": 343, + "status": "s000003" + }, + { + "handle": "ae7339df26ded", + "parent": "e7ded148d6e4a", + "root": null, + "order": 6, + "heading": "H0", + "label": "We Found John!", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 189, + "wordCount": 37, + "paraCount": 1, + "cursorPos": 224, + "status": "s000003" + }, + { + "handle": "f6622b4617424", + "parent": null, + "root": null, + "order": 1, + "heading": "H0", + "label": "Characters", + "type": "ROOT", + "class": "CHARACTER", + "expanded": true, + "import": null + }, + { + "handle": "f7e2d9f330615", + "parent": "f6622b4617424", + "root": null, + "order": 0, + "heading": "H0", + "label": "Main Characters", + "type": "FOLDER", + "class": "CHARACTER", + "expanded": true, + "import": null + }, + { + "handle": "14298de4d9524", + "parent": "f7e2d9f330615", + "root": null, + "order": 0, + "heading": "H0", + "label": "John Smith", + "type": "FILE", + "class": "CHARACTER", + "active": true, + "layout": "NOTE", + "charCount": 49, + "wordCount": 9, + "paraCount": 1, + "cursorPos": 24, + "import": "i000008" + }, + { + "handle": "bb2c23b3c42cc", + "parent": "f7e2d9f330615", + "root": null, + "order": 1, + "heading": "H0", + "label": "Jane Smith", + "type": "FILE", + "class": "CHARACTER", + "active": true, + "layout": "NOTE", + "charCount": 55, + "wordCount": 9, + "paraCount": 1, + "cursorPos": 25, + "import": "i000009" + }, + { + "handle": "15c4492bd5107", + "parent": null, + "root": null, + "order": 2, + "heading": "H0", + "label": "Locations", + "type": "ROOT", + "class": "WORLD", + "expanded": true, + "import": null + }, + { + "handle": "b3e74dbc1f584", + "parent": "15c4492bd5107", + "root": null, + "order": 0, + "heading": "H0", + "label": "Earth", + "type": "FILE", + "class": "WORLD", + "active": true, + "layout": "NOTE", + "charCount": 76, + "wordCount": 15, + "paraCount": 1, + "cursorPos": 20, + "import": "i00000a" + }, + { + "handle": "f1471bef9f2ae", + "parent": "15c4492bd5107", + "root": null, + "order": 1, + "heading": "H0", + "label": "Space", + "type": "FILE", + "class": "WORLD", + "active": true, + "layout": "NOTE", + "charCount": 115, + "wordCount": 24, + "paraCount": 1, + "cursorPos": 133, + "import": "i000008" + }, + { + "handle": "5eaea4e8cdee8", + "parent": "15c4492bd5107", + "root": null, + "order": 2, + "heading": "H0", + "label": "Mars", + "type": "FILE", + "class": "WORLD", + "active": true, + "layout": "NOTE", + "charCount": 28, + "wordCount": 6, + "paraCount": 1, + "cursorPos": 45, + "import": "i000009" + }, + { + "handle": "6827118336ac1", + "parent": null, + "root": null, + "order": 3, + "heading": "H0", + "label": "Archive", + "type": "ROOT", + "class": "ARCHIVE", + "expanded": true, + "status": "s000000" + }, + { + "handle": "ae9bf3c3ea159", + "parent": "6827118336ac1", + "root": null, + "order": 0, + "heading": "H0", + "label": "Scenes", + "type": "FOLDER", + "class": "ARCHIVE", + "expanded": true, + "status": "s000000" + }, + { + "handle": "8a5deb88c0e97", + "parent": "ae9bf3c3ea159", + "root": null, + "order": 0, + "heading": "H0", + "label": "Old File", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 314, + "wordCount": 55, + "paraCount": 1, + "cursorPos": 322, + "status": "s000003" + }, + { + "handle": "98acd8c76c93a", + "parent": null, + "root": null, + "order": 4, + "heading": "H0", + "label": "Trash", + "type": "ROOT", + "class": "TRASH", + "expanded": true, + "import": null + }, + { + "handle": "b8136a5a774a0", + "parent": "98acd8c76c93a", + "root": null, + "order": 0, + "heading": "H0", + "label": "Delete Me!", + "type": "FILE", + "class": "NOVEL", + "active": true, + "layout": "DOCUMENT", + "charCount": 30, + "wordCount": 6, + "paraCount": 1, + "cursorPos": 36, + "status": "s000000" + } +] \ No newline at end of file diff --git a/tests/reference/projectXML_ReadLegacy13.nwx b/tests/reference/projectXML_ReadLegacy13.nwx new file mode 100644 index 00000000..7fc4cfea --- /dev/null +++ b/tests/reference/projectXML_ReadLegacy13.nwx @@ -0,0 +1,155 @@ + + + + Sample Project + Sample Project + Jane Smith + Jay Doh + 5 + 10 + 1000 + + + True + en_GB + True + en_GB + 830 + 376 + + None + None + None + None + + + B + E + D + + + %title% + Chapter %chw%: %title% + %title% + Scene %ch%.%sc%: %title% + + + + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished + + + None + Minor + Major + Main + + + + + + Novel + + + + Title Page + + + + Page + + + + Part One + + + + A Folder + + + + Chapter One + + + + Making a Scene + + + + Another Scene + + + + Interlude + + + + A Note on Structure + + + + Chapter Two + + + + We Found John! + + + + Characters + + + + Main Characters + + + + John Smith + + + + Jane Smith + + + + Locations + + + + Earth + + + + Space + + + + Mars + + + + Archive + + + + Scenes + + + + Old File + + + + Trash + + + + Delete Me! + + + diff --git a/tests/test_core/test_core_projectxml.py b/tests/test_core/test_core_projectxml.py new file mode 100644 index 00000000..6ad1174c --- /dev/null +++ b/tests/test_core/test_core_projectxml.py @@ -0,0 +1,798 @@ +""" +novelWriter – ProjectXMLReader/Writer Class Tester +================================================== + +This file is a part of novelWriter +Copyright 2018–2022, 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 . +""" + +import json +import os +import pytest +import shutil + +from datetime import datetime + +from mock import causeOSError +from tools import cmpFiles, writeFile + +from novelwriter.core.item import NWItem +from novelwriter.core.project import NWProjectData +from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState + + +class MockProject: + def setProjectChanged(self, *a): + pass + + +@pytest.mark.core +def testCoreProjectXML_ReadCurrent(monkeypatch, filesDir, fncDir, outDir, refDir): + """Test reading the current XML file format. + """ + refFile = os.path.join(filesDir, "nwProject-1.4.nwx") + xmlFile = os.path.join(fncDir, "nwProject-1.4.nwx") + bakFile = os.path.join(fncDir, "nwProject-1.4.bak") + outFile = os.path.join(fncDir, "nwProject.nwx") + + xmlReader = ProjectXMLReader(xmlFile) + assert xmlReader.state == XMLReadState.NO_ACTION + + data = NWProjectData(MockProject()) + content = [] + + # With no valid files, the read should fail + writeFile(xmlFile, "") + assert xmlReader.read(data, content) is False + assert xmlReader.state == XMLReadState.CANNOT_PARSE + + # Also add an invalid backup file + writeFile(bakFile, "") + assert xmlReader.read(data, content) is False + assert xmlReader.state == XMLReadState.CANNOT_PARSE + + # Add a valid backup file, that is not novelWriter + writeFile(bakFile, "") + assert xmlReader.read(data, content) is False + assert xmlReader.state == XMLReadState.NOT_NWX_FILE + + # Add a valid project file, that is not novelWriter + writeFile(xmlFile, "") + assert xmlReader.read(data, content) is False + assert xmlReader.state == XMLReadState.NOT_NWX_FILE + + # Add a valid novelwriter file without a file version + writeFile(xmlFile, "") + assert xmlReader.read(data, content) is False + assert xmlReader.state == XMLReadState.UNKNOWN_VERSION + + # Check parsing of unkown sections + writeFile(xmlFile, ( + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + )) + assert xmlReader.read(data, content) is True + assert xmlReader.state == XMLReadState.PARSED_OK + + writeFile(xmlFile, ( + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + )) + assert xmlReader.read(data, content) is True + assert xmlReader.state == XMLReadState.WAS_LEGACY + + # Reset data objects + data = NWProjectData(MockProject()) + content = [] + + # Parse a valid, complete file + shutil.copy(refFile, xmlFile) + assert xmlReader.read(data, content) is True + assert xmlReader.state == XMLReadState.PARSED_OK + assert xmlReader.xmlRoot == "novelWriterXML" + assert xmlReader.xmlVersion == 0x0104 + assert xmlReader.appVersion == "2.0-rc1" + assert xmlReader.hexVersion == "0x020000c1" + + # Check loaded data + assert data.name == "Sample Project" + assert data.title == "Sample Project" + assert data.authors == ["Jane Smith", "Jay Doh"] + assert data.saveCount == 5 + assert data.autoCount == 10 + assert data.editTime == 1000 + + assert data.doBackup is True + assert data.language == "en_GB" + assert data.spellCheck is True + assert data.spellLang == "en_GB" + assert data.initCounts == (954, 409) + assert data.currCounts == (954, 409) + + assert data.getLastHandle("editor") == "636b6aa9b697b" + assert data.getLastHandle("viewer") == "636b6aa9b697b" + assert data.getLastHandle("novelTree") == "7031beac91f75" + assert data.getLastHandle("outline") == "7031beac91f75" + + assert data.getTitleFormat("title") == "%title%" + assert data.getTitleFormat("chapter") == "Chapter %chw%: %title%" + assert data.getTitleFormat("unnumbered") == "%title%" + assert data.getTitleFormat("scene") == "Scene %ch%.%sc%: %title%" + assert data.getTitleFormat("section") == "" + + assert data.itemStatus.name("sf12341") == "New" + assert data.itemStatus.name("sf24ce6") == "Notes" + assert data.itemStatus.name("sc24b8f") == "Started" + assert data.itemStatus.name("s90e6c9") == "1st Draft" + assert data.itemStatus.name("sd51c5b") == "2nd Draft" + assert data.itemStatus.name("s8ae72a") == "3rd Draft" + assert data.itemStatus.name("s78ea90") == "Finished" + + assert data.itemImport.name("ia857f0") == "None" + assert data.itemImport.name("icfb3a5") == "Minor" + assert data.itemImport.name("i2d7a54") == "Major" + assert data.itemImport.name("i56be10") == "Main" + + assert data.itemStatus.cols("sf12341") == (100, 100, 100) + assert data.itemStatus.cols("sf24ce6") == (200, 50, 0) + assert data.itemStatus.cols("sc24b8f") == (182, 60, 0) + assert data.itemStatus.cols("s90e6c9") == (193, 129, 0) + assert data.itemStatus.cols("sd51c5b") == (193, 129, 0) + assert data.itemStatus.cols("s8ae72a") == (193, 129, 0) + assert data.itemStatus.cols("s78ea90") == (58, 180, 58) + + assert data.itemImport.cols("ia857f0") == (100, 100, 100) + assert data.itemImport.cols("icfb3a5") == (0, 122, 188) + assert data.itemImport.cols("i2d7a54") == (21, 0, 180) + assert data.itemImport.cols("i56be10") == (117, 0, 175) + + assert data.itemStatus.count("sf12341") == 4 + assert data.itemStatus.count("sf24ce6") == 2 + assert data.itemStatus.count("sc24b8f") == 3 + assert data.itemStatus.count("s90e6c9") == 7 + assert data.itemStatus.count("sd51c5b") == 0 + assert data.itemStatus.count("s8ae72a") == 0 + assert data.itemStatus.count("s78ea90") == 1 + + assert data.itemImport.count("ia857f0") == 5 + assert data.itemImport.count("icfb3a5") == 2 + assert data.itemImport.count("i2d7a54") == 2 + assert data.itemImport.count("i56be10") == 1 + + # Compare content + dumpFile = os.path.join(outDir, "projectXML_ReadCurrent.json") + compFile = os.path.join(refDir, "projectXML_ReadCurrent.json") + with open(dumpFile, mode="w", encoding="utf-8") as dump: + json.dump(content, dump, indent=2) + assert cmpFiles(dumpFile, compFile) + + packedContent = [] + mockProject = MockProject() + mockProject.__setattr__("data", data) + for entry in content: + item = NWItem(mockProject) + item.unpack(entry) + packedContent.append(item.pack()) + + # Save the project again, which should produce an identical project xml + timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp()) + xmlWriter = ProjectXMLWriter(fncDir) + + # Fail saving + with monkeypatch.context() as mp: + mp.setattr("builtins.open", causeOSError) + assert xmlWriter.write(data, packedContent, timeStamp, 1000) is False + assert str(xmlWriter.error) == "Mock OSError" + + with monkeypatch.context() as mp: + mp.setattr("os.replace", causeOSError) + assert xmlWriter.write(data, packedContent, timeStamp, 1000) is False + assert str(xmlWriter.error) == "Mock OSError" + + # Successful save (should be twice) + assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True + assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True + assert cmpFiles(outFile, xmlFile) + +# END Test testCoreProjectXML_ReadCurrent + + +@pytest.mark.core +def testCoreProjectXML_ReadLegacy10(filesDir, fncDir, outDir, refDir, mockRnd): + """Test reading the version 1.0 XML file format. + """ + refFile = os.path.join(filesDir, "nwProject-1.0.nwx") + xmlFile = os.path.join(fncDir, "nwProject-1.0.nwx") + outFile = os.path.join(fncDir, "nwProject.nwx") + shutil.copy(refFile, xmlFile) + + xmlReader = ProjectXMLReader(xmlFile) + assert xmlReader.state == XMLReadState.NO_ACTION + + data = NWProjectData(MockProject()) + content = [] + + assert xmlReader.read(data, content) is True + assert xmlReader.state == XMLReadState.WAS_LEGACY + assert xmlReader.xmlRoot == "novelWriterXML" + assert xmlReader.xmlVersion == 0x0100 + assert xmlReader.appVersion == "0.6.1" + assert xmlReader.hexVersion == "0x000601f0" + + # Check loaded data + assert data.name == "Sample Project" + assert data.title == "Sample Project" + assert data.authors == ["Jane Smith", "Jay Doh"] + assert data.saveCount == 0 # Doesn't exist in 1.0 + assert data.autoCount == 0 # Doesn't exist in 1.0 + assert data.editTime == 0 # Doesn't exist in 1.0 + + assert data.doBackup is True + assert data.language is None # Doesn't exist in 1.0 + assert data.spellCheck is True + assert data.spellLang is None # Doesn't exist in 1.0 + assert data.initCounts == (0, 0) + assert data.currCounts == (0, 0) + + assert data.getLastHandle("editor") is None # Dropped by conversion + assert data.getLastHandle("viewer") is None # Dropped by conversion + assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.0 + assert data.getLastHandle("outline") is None # Doesn't exist in 1.0 + + assert data.getTitleFormat("title") == "%title%" + assert data.getTitleFormat("chapter") == "Chapter %ch%: %title%" + assert data.getTitleFormat("unnumbered") == "%title%" + assert data.getTitleFormat("scene") == "Scene %ch%.%sc%: %title%" + assert data.getTitleFormat("section") == "" + + assert data.itemStatus.name("s000000") == "New" + assert data.itemStatus.name("s000001") == "Notes" + assert data.itemStatus.name("s000002") == "Started" + assert data.itemStatus.name("s000003") == "1st Draft" + assert data.itemStatus.name("s000004") == "2nd Draft" + assert data.itemStatus.name("s000005") == "3rd Draft" + assert data.itemStatus.name("s000006") == "Finished" + + assert data.itemImport.name("i000007") == "None" + assert data.itemImport.name("i000008") == "Minor" + assert data.itemImport.name("i000009") == "Major" + assert data.itemImport.name("i00000a") == "Main" + + assert data.itemStatus.cols("s000000") == (100, 100, 100) + assert data.itemStatus.cols("s000001") == (200, 50, 0) + assert data.itemStatus.cols("s000002") == (182, 60, 0) + assert data.itemStatus.cols("s000003") == (193, 129, 0) + assert data.itemStatus.cols("s000004") == (193, 129, 0) + assert data.itemStatus.cols("s000005") == (193, 129, 0) + assert data.itemStatus.cols("s000006") == (58, 180, 58) + + assert data.itemImport.cols("i000007") == (100, 100, 100) + assert data.itemImport.cols("i000008") == (0, 122, 188) + assert data.itemImport.cols("i000009") == (21, 0, 180) + assert data.itemImport.cols("i00000a") == (117, 0, 175) + + assert data.itemStatus.count("s000000") == 0 + assert data.itemStatus.count("s000001") == 0 + assert data.itemStatus.count("s000002") == 0 + assert data.itemStatus.count("s000003") == 0 + assert data.itemStatus.count("s000004") == 0 + assert data.itemStatus.count("s000005") == 0 + assert data.itemStatus.count("s000006") == 0 + + assert data.itemImport.count("i000007") == 0 + assert data.itemImport.count("i000008") == 0 + assert data.itemImport.count("i000009") == 0 + assert data.itemImport.count("i00000a") == 0 + + # Compare content + dumpFile = os.path.join(outDir, "projectXML_ReadLegacy10.json") + compFile = os.path.join(refDir, "projectXML_ReadLegacy10.json") + with open(dumpFile, mode="w", encoding="utf-8") as dump: + json.dump(content, dump, indent=2) + assert cmpFiles(dumpFile, compFile) + + packedContent = [] + mockProject = MockProject() + mockProject.__setattr__("data", data) + status = {} + for entry in content: + item = NWItem(mockProject) + item.unpack(entry) + status[item.itemHandle] = item.getImportStatus(incIcon=False)[0] + packedContent.append(item.pack()) + + assert status == { + "7031beac91f75": "Started", + "53b69b83cdafc": "Started", + "974e400180a99": "New", + "edca4be2fcaf8": "New", + "e7ded148d6e4a": "1st Draft", + "6a2d6d5f4f401": "Notes", + "636b6aa9b697b": "1st Draft", + "bc0cbd2a407f3": "1st Draft", + "ba8a28a246524": "Finished", + "96b68994dfa3d": "2nd Draft", + "88706ddc78b1b": "1st Draft", + "ae7339df26ded": "1st Draft", + "f6622b4617424": "None", + "f7e2d9f330615": "None", + "14298de4d9524": "Minor", + "bb2c23b3c42cc": "Major", + "15c4492bd5107": "None", + "b3e74dbc1f584": "Main", + "f1471bef9f2ae": "Minor", + "5eaea4e8cdee8": "Major", + "98acd8c76c93a": "None", + "b8136a5a774a0": "New", + } + + # Save the project again, which should produce an identical project xml + timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp()) + xmlWriter = ProjectXMLWriter(fncDir) + assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True + compFile = os.path.join(refDir, "projectXML_ReadLegacy10.nwx") + assert cmpFiles(outFile, compFile) + +# END Test testCoreProjectXML_ReadLegacy10 + + +@pytest.mark.core +def testCoreProjectXML_ReadLegacy11(filesDir, fncDir, outDir, refDir, mockRnd): + """Test reading the version 1.1 XML file format. + """ + refFile = os.path.join(filesDir, "nwProject-1.1.nwx") + xmlFile = os.path.join(fncDir, "nwProject-1.1.nwx") + outFile = os.path.join(fncDir, "nwProject.nwx") + shutil.copy(refFile, xmlFile) + + xmlReader = ProjectXMLReader(xmlFile) + assert xmlReader.state == XMLReadState.NO_ACTION + + data = NWProjectData(MockProject()) + content = [] + + assert xmlReader.read(data, content) is True + assert xmlReader.state == XMLReadState.WAS_LEGACY + assert xmlReader.xmlRoot == "novelWriterXML" + assert xmlReader.xmlVersion == 0x0101 + assert xmlReader.appVersion == "0.9.2" + assert xmlReader.hexVersion == "0x000902f0" + + # Check loaded data + assert data.name == "Sample Project" + assert data.title == "Sample Project" + assert data.authors == ["Jane Smith", "Jay Doh"] + assert data.saveCount == 5 + assert data.autoCount == 10 + assert data.editTime == 1000 + + assert data.doBackup is True + assert data.language is None # Doesn't exist in 1.1 + assert data.spellCheck is True + assert data.spellLang is None # Doesn't exist in 1.1 + assert data.initCounts == (0, 0) + assert data.currCounts == (0, 0) + + assert data.getLastHandle("editor") is None # Dropped by conversion + assert data.getLastHandle("viewer") is None # Dropped by conversion + assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.1 + assert data.getLastHandle("outline") is None # Doesn't exist in 1.1 + + assert data.getTitleFormat("title") == "%title%" + assert data.getTitleFormat("chapter") == "Chapter %ch%: %title%" + assert data.getTitleFormat("unnumbered") == "%title%" + assert data.getTitleFormat("scene") == "Scene %ch%.%sc%: %title%" + assert data.getTitleFormat("section") == "" + + assert data.itemStatus.name("s000000") == "New" + assert data.itemStatus.name("s000001") == "Notes" + assert data.itemStatus.name("s000002") == "Started" + assert data.itemStatus.name("s000003") == "1st Draft" + assert data.itemStatus.name("s000004") == "2nd Draft" + assert data.itemStatus.name("s000005") == "3rd Draft" + assert data.itemStatus.name("s000006") == "Finished" + + assert data.itemImport.name("i000007") == "None" + assert data.itemImport.name("i000008") == "Minor" + assert data.itemImport.name("i000009") == "Major" + assert data.itemImport.name("i00000a") == "Main" + + assert data.itemStatus.cols("s000000") == (100, 100, 100) + assert data.itemStatus.cols("s000001") == (200, 50, 0) + assert data.itemStatus.cols("s000002") == (182, 60, 0) + assert data.itemStatus.cols("s000003") == (193, 129, 0) + assert data.itemStatus.cols("s000004") == (193, 129, 0) + assert data.itemStatus.cols("s000005") == (193, 129, 0) + assert data.itemStatus.cols("s000006") == (58, 180, 58) + + assert data.itemImport.cols("i000007") == (100, 100, 100) + assert data.itemImport.cols("i000008") == (0, 122, 188) + assert data.itemImport.cols("i000009") == (21, 0, 180) + assert data.itemImport.cols("i00000a") == (117, 0, 175) + + assert data.itemStatus.count("s000000") == 0 + assert data.itemStatus.count("s000001") == 0 + assert data.itemStatus.count("s000002") == 0 + assert data.itemStatus.count("s000003") == 0 + assert data.itemStatus.count("s000004") == 0 + assert data.itemStatus.count("s000005") == 0 + assert data.itemStatus.count("s000006") == 0 + + assert data.itemImport.count("i000007") == 0 + assert data.itemImport.count("i000008") == 0 + assert data.itemImport.count("i000009") == 0 + assert data.itemImport.count("i00000a") == 0 + + # Compare content + dumpFile = os.path.join(outDir, "projectXML_ReadLegacy11.json") + compFile = os.path.join(refDir, "projectXML_ReadLegacy11.json") + with open(dumpFile, mode="w", encoding="utf-8") as dump: + json.dump(content, dump, indent=2) + assert cmpFiles(dumpFile, compFile) + + packedContent = [] + mockProject = MockProject() + mockProject.__setattr__("data", data) + status = {} + for entry in content: + item = NWItem(mockProject) + item.unpack(entry) + status[item.itemHandle] = item.getImportStatus(incIcon=False)[0] + packedContent.append(item.pack()) + + assert status == { + "7031beac91f75": "Started", + "53b69b83cdafc": "Started", + "974e400180a99": "New", + "edca4be2fcaf8": "New", + "e7ded148d6e4a": "1st Draft", + "6a2d6d5f4f401": "Notes", + "636b6aa9b697b": "1st Draft", + "bc0cbd2a407f3": "1st Draft", + "ba8a28a246524": "Finished", + "96b68994dfa3d": "2nd Draft", + "88706ddc78b1b": "1st Draft", + "ae7339df26ded": "1st Draft", + "f6622b4617424": "None", + "f7e2d9f330615": "None", + "14298de4d9524": "Minor", + "bb2c23b3c42cc": "Major", + "15c4492bd5107": "None", + "b3e74dbc1f584": "Main", + "f1471bef9f2ae": "Minor", + "5eaea4e8cdee8": "Major", + "98acd8c76c93a": "None", + "b8136a5a774a0": "New", + } + + # Save the project again, which should produce an identical project xml + timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp()) + xmlWriter = ProjectXMLWriter(fncDir) + assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True + compFile = os.path.join(refDir, "projectXML_ReadLegacy11.nwx") + assert cmpFiles(outFile, compFile) + +# END Test testCoreProjectXML_ReadLegacy11 + + +@pytest.mark.core +def testCoreProjectXML_ReadLegacy12(filesDir, fncDir, outDir, refDir, mockRnd): + """Test reading the version 1.2 XML file format. + """ + refFile = os.path.join(filesDir, "nwProject-1.2.nwx") + xmlFile = os.path.join(fncDir, "nwProject-1.2.nwx") + outFile = os.path.join(fncDir, "nwProject.nwx") + shutil.copy(refFile, xmlFile) + + xmlReader = ProjectXMLReader(xmlFile) + assert xmlReader.state == XMLReadState.NO_ACTION + + data = NWProjectData(MockProject()) + content = [] + + assert xmlReader.read(data, content) is True + assert xmlReader.state == XMLReadState.WAS_LEGACY + assert xmlReader.xmlRoot == "novelWriterXML" + assert xmlReader.xmlVersion == 0x0102 + assert xmlReader.appVersion == "1.4.2" + assert xmlReader.hexVersion == "0x010402f0" + + # Check loaded data + assert data.name == "Sample Project" + assert data.title == "Sample Project" + assert data.authors == ["Jane Smith", "Jay Doh"] + assert data.saveCount == 5 + assert data.autoCount == 10 + assert data.editTime == 1000 + + assert data.doBackup is True + assert data.language == "en_GB" + assert data.spellCheck is True + assert data.spellLang == "en_GB" + assert data.initCounts == (840, 376) + assert data.currCounts == (840, 376) + + assert data.getLastHandle("editor") is None # Dropped by conversion + assert data.getLastHandle("viewer") is None # Dropped by conversion + assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.2 + assert data.getLastHandle("outline") is None # Doesn't exist in 1.2 + + assert data.getTitleFormat("title") == "%title%" + assert data.getTitleFormat("chapter") == "Chapter %chw%: %title%" + assert data.getTitleFormat("unnumbered") == "%title%" + assert data.getTitleFormat("scene") == "Scene %ch%.%sc%: %title%" + assert data.getTitleFormat("section") == "" + + assert data.itemStatus.name("s000000") == "New" + assert data.itemStatus.name("s000001") == "Notes" + assert data.itemStatus.name("s000002") == "Started" + assert data.itemStatus.name("s000003") == "1st Draft" + assert data.itemStatus.name("s000004") == "2nd Draft" + assert data.itemStatus.name("s000005") == "3rd Draft" + assert data.itemStatus.name("s000006") == "Finished" + + assert data.itemImport.name("i000007") == "None" + assert data.itemImport.name("i000008") == "Minor" + assert data.itemImport.name("i000009") == "Major" + assert data.itemImport.name("i00000a") == "Main" + + assert data.itemStatus.cols("s000000") == (100, 100, 100) + assert data.itemStatus.cols("s000001") == (200, 50, 0) + assert data.itemStatus.cols("s000002") == (182, 60, 0) + assert data.itemStatus.cols("s000003") == (193, 129, 0) + assert data.itemStatus.cols("s000004") == (193, 129, 0) + assert data.itemStatus.cols("s000005") == (193, 129, 0) + assert data.itemStatus.cols("s000006") == (58, 180, 58) + + assert data.itemImport.cols("i000007") == (100, 100, 100) + assert data.itemImport.cols("i000008") == (0, 122, 188) + assert data.itemImport.cols("i000009") == (21, 0, 180) + assert data.itemImport.cols("i00000a") == (117, 0, 175) + + assert data.itemStatus.count("s000000") == 0 + assert data.itemStatus.count("s000001") == 0 + assert data.itemStatus.count("s000002") == 0 + assert data.itemStatus.count("s000003") == 0 + assert data.itemStatus.count("s000004") == 0 + assert data.itemStatus.count("s000005") == 0 + assert data.itemStatus.count("s000006") == 0 + + assert data.itemImport.count("i000007") == 0 + assert data.itemImport.count("i000008") == 0 + assert data.itemImport.count("i000009") == 0 + assert data.itemImport.count("i00000a") == 0 + + # Compare content + dumpFile = os.path.join(outDir, "projectXML_ReadLegacy12.json") + compFile = os.path.join(refDir, "projectXML_ReadLegacy12.json") + with open(dumpFile, mode="w", encoding="utf-8") as dump: + json.dump(content, dump, indent=2) + assert cmpFiles(dumpFile, compFile) + + packedContent = [] + mockProject = MockProject() + mockProject.__setattr__("data", data) + status = {} + for entry in content: + item = NWItem(mockProject) + item.unpack(entry) + status[item.itemHandle] = item.getImportStatus(incIcon=False)[0] + packedContent.append(item.pack()) + + assert status == { + "7031beac91f75": "Started", + "53b69b83cdafc": "Started", + "974e400180a99": "New", + "edca4be2fcaf8": "New", + "e7ded148d6e4a": "1st Draft", + "6a2d6d5f4f401": "Notes", + "636b6aa9b697b": "1st Draft", + "bc0cbd2a407f3": "1st Draft", + "ba8a28a246524": "New", + "96b68994dfa3d": "2nd Draft", + "88706ddc78b1b": "1st Draft", + "ae7339df26ded": "1st Draft", + "f6622b4617424": "None", + "f7e2d9f330615": "None", + "14298de4d9524": "Minor", + "bb2c23b3c42cc": "Major", + "15c4492bd5107": "None", + "b3e74dbc1f584": "Main", + "f1471bef9f2ae": "Minor", + "5eaea4e8cdee8": "Major", + "6827118336ac1": "New", # Is now treated as novel-like + "ae9bf3c3ea159": "New", # Is now treated as novel-like + "8a5deb88c0e97": "1st Draft", + "98acd8c76c93a": "None", + "b8136a5a774a0": "New", + } + + # Save the project again, which should produce an identical project xml + timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp()) + xmlWriter = ProjectXMLWriter(fncDir) + assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True + compFile = os.path.join(refDir, "projectXML_ReadLegacy12.nwx") + assert cmpFiles(outFile, compFile) + +# END Test testCoreProjectXML_ReadLegacy12 + + +@pytest.mark.core +def testCoreProjectXML_ReadLegacy13(filesDir, fncDir, outDir, refDir, mockRnd): + """Test reading the version 1.3 XML file format. + """ + refFile = os.path.join(filesDir, "nwProject-1.3.nwx") + xmlFile = os.path.join(fncDir, "nwProject-1.3.nwx") + outFile = os.path.join(fncDir, "nwProject.nwx") + shutil.copy(refFile, xmlFile) + + xmlReader = ProjectXMLReader(xmlFile) + assert xmlReader.state == XMLReadState.NO_ACTION + + data = NWProjectData(MockProject()) + content = [] + + assert xmlReader.read(data, content) is True + assert xmlReader.state == XMLReadState.WAS_LEGACY + assert xmlReader.xmlRoot == "novelWriterXML" + assert xmlReader.xmlVersion == 0x0103 + assert xmlReader.appVersion == "1.6.6" + assert xmlReader.hexVersion == "0x010606f0" + + # Check loaded data + assert data.name == "Sample Project" + assert data.title == "Sample Project" + assert data.authors == ["Jane Smith", "Jay Doh"] + assert data.saveCount == 5 + assert data.autoCount == 10 + assert data.editTime == 1000 + + assert data.doBackup is True + assert data.language == "en_GB" + assert data.spellCheck is True + assert data.spellLang == "en_GB" + assert data.initCounts == (830, 376) + assert data.currCounts == (830, 376) + + assert data.getLastHandle("editor") is None # Dropped by conversion + assert data.getLastHandle("viewer") is None # Dropped by conversion + assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.3 + assert data.getLastHandle("outline") is None # Doesn't exist in 1.3 + + assert data.getTitleFormat("title") == "%title%" + assert data.getTitleFormat("chapter") == "Chapter %chw%: %title%" + assert data.getTitleFormat("unnumbered") == "%title%" + assert data.getTitleFormat("scene") == "Scene %ch%.%sc%: %title%" + assert data.getTitleFormat("section") == "" + + assert data.itemStatus.name("s000000") == "New" + assert data.itemStatus.name("s000001") == "Notes" + assert data.itemStatus.name("s000002") == "Started" + assert data.itemStatus.name("s000003") == "1st Draft" + assert data.itemStatus.name("s000004") == "2nd Draft" + assert data.itemStatus.name("s000005") == "3rd Draft" + assert data.itemStatus.name("s000006") == "Finished" + + assert data.itemImport.name("i000007") == "None" + assert data.itemImport.name("i000008") == "Minor" + assert data.itemImport.name("i000009") == "Major" + assert data.itemImport.name("i00000a") == "Main" + + assert data.itemStatus.cols("s000000") == (100, 100, 100) + assert data.itemStatus.cols("s000001") == (200, 50, 0) + assert data.itemStatus.cols("s000002") == (182, 60, 0) + assert data.itemStatus.cols("s000003") == (193, 129, 0) + assert data.itemStatus.cols("s000004") == (193, 129, 0) + assert data.itemStatus.cols("s000005") == (193, 129, 0) + assert data.itemStatus.cols("s000006") == (58, 180, 58) + + assert data.itemImport.cols("i000007") == (100, 100, 100) + assert data.itemImport.cols("i000008") == (0, 122, 188) + assert data.itemImport.cols("i000009") == (21, 0, 180) + assert data.itemImport.cols("i00000a") == (117, 0, 175) + + assert data.itemStatus.count("s000000") == 0 + assert data.itemStatus.count("s000001") == 0 + assert data.itemStatus.count("s000002") == 0 + assert data.itemStatus.count("s000003") == 0 + assert data.itemStatus.count("s000004") == 0 + assert data.itemStatus.count("s000005") == 0 + assert data.itemStatus.count("s000006") == 0 + + assert data.itemImport.count("i000007") == 0 + assert data.itemImport.count("i000008") == 0 + assert data.itemImport.count("i000009") == 0 + assert data.itemImport.count("i00000a") == 0 + + # Compare content + dumpFile = os.path.join(outDir, "projectXML_ReadLegacy13.json") + compFile = os.path.join(refDir, "projectXML_ReadLegacy13.json") + with open(dumpFile, mode="w", encoding="utf-8") as dump: + json.dump(content, dump, indent=2) + assert cmpFiles(dumpFile, compFile) + + packedContent = [] + mockProject = MockProject() + mockProject.__setattr__("data", data) + status = {} + for entry in content: + item = NWItem(mockProject) + item.unpack(entry) + status[item.itemHandle] = item.getImportStatus(incIcon=False)[0] + packedContent.append(item.pack()) + + assert status == { + "7031beac91f75": "Started", + "53b69b83cdafc": "Started", + "974e400180a99": "New", + "edca4be2fcaf8": "New", + "e7ded148d6e4a": "1st Draft", + "6a2d6d5f4f401": "Notes", + "636b6aa9b697b": "1st Draft", + "bc0cbd2a407f3": "1st Draft", + "ba8a28a246524": "New", + "96b68994dfa3d": "2nd Draft", + "88706ddc78b1b": "1st Draft", + "ae7339df26ded": "1st Draft", + "f6622b4617424": "None", + "f7e2d9f330615": "None", + "14298de4d9524": "Minor", + "bb2c23b3c42cc": "Major", + "15c4492bd5107": "None", + "b3e74dbc1f584": "Main", + "f1471bef9f2ae": "Minor", + "5eaea4e8cdee8": "Major", + "6827118336ac1": "New", # Is now treated as novel-like + "ae9bf3c3ea159": "New", # Is now treated as novel-like + "8a5deb88c0e97": "1st Draft", + "98acd8c76c93a": "None", + "b8136a5a774a0": "New", + } + + # Save the project again, which should produce an identical project xml + timeStamp = int(datetime.fromisoformat(xmlReader.timeStamp).timestamp()) + xmlWriter = ProjectXMLWriter(fncDir) + assert xmlWriter.write(data, packedContent, timeStamp, 1000) is True + compFile = os.path.join(refDir, "projectXML_ReadLegacy13.nwx") + assert cmpFiles(outFile, compFile) + +# END Test testCoreProjectXML_ReadLegacy13