Skip to content

Commit

Permalink
Implement project file format 1.4 (#993)
Browse files Browse the repository at this point in the history
* Update the item class with more compact item storage format
* Bump version and file version
* Update tests
* Add explicit test coverage for format conversions for item class
* Update the documentation
  • Loading branch information
vkbo authored Feb 20, 2022
1 parent be703c1 commit 045a595
Show file tree
Hide file tree
Showing 17 changed files with 666 additions and 1,389 deletions.
25 changes: 22 additions & 3 deletions docs/source/usage_projectformat.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,32 @@ changes require minor actions from the user.
The key changes in the formats are listed below, as well as the user actions required where
applicable.

.. caution::

When you update a project from one format version to the next, the project can no longer be
opened by a version of novelWriter prior to the version where the new file format was
introduced. You will get a notification about any updates to your project file format and will
have the option to decline the upgrade.


.. _a_prjfmt_1_4:

Format 1.4 Changes
==================

This project format was introduced in novelWriter version 1.7.

This format changes the way project items (folders, documents and notes) are stored. It is a more
compact format that is simpler and faster to parse, and easier to extend. The conversion is done
automatically the first time a project is loaded. No user action is required.


.. _a_prjfmt_1_3:

Format 1.3 Changes
==================

This project format vas introduces in novelWriter version 1.5.
This project format was introduced in novelWriter version 1.5.

With this format, the number of document layouts was reduced from 8 to 2. The conversion of
document layouts is performed automatically when the project is opened.
Expand Down Expand Up @@ -55,7 +74,7 @@ should be used only a few places in any given project. These are as follows:
Format 1.2 Changes
==================

This project format was introduces in novelWriter version 0.10.
This project format was introduced in novelWriter version 0.10.

With this format, the way auto-replace entries were stored in the main project XML file changed.
Opening an old project automatically converts the storage format up to and including version 1.1.1.
Expand All @@ -69,7 +88,7 @@ auto-replace is not being used, can still be opened in novelWriter as of version
Format 1.1 Changes
==================

This project format was introduces in novelWriter version 0.7.
This project format was introduced in novelWriter version 0.7.

With this format, the ``content`` folder was introduced in the project storage. Previously, all
novelWriter documents were saved in a series of folders numbered from ``data_0`` to ``data_f``.
Expand Down
4 changes: 2 additions & 2 deletions novelwriter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@
__author__ = "Veronica Berglyd Olsen"
__maintainer__ = "Veronica Berglyd Olsen"
__email__ = "code@vkbo.net"
__version__ = "1.6"
__hexversion__ = "0x010600f0"
__version__ = "1.7-alpha0"
__hexversion__ = "0x010700a0"
__date__ = "2022-02-20"
__status__ = "Stable"
__domain__ = "novelwriter.io"
Expand Down
63 changes: 40 additions & 23 deletions novelwriter/core/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,24 +139,32 @@ def cursorPos(self):
def packXML(self, xParent):
"""Pack all the data in the class instance into an XML object.
"""
xPack = etree.SubElement(xParent, "item", attrib={
"handle": str(self._handle),
"order": str(self._order),
"parent": str(self._parent),
})
self._subPack(xPack, "name", text=str(self._name))
self._subPack(xPack, "type", text=str(self._type.name))
self._subPack(xPack, "class", text=str(self._class.name))
self._subPack(xPack, "status", text=str(self._status))
itemAttrib = {}
itemAttrib["handle"] = str(self._handle)
itemAttrib["parent"] = str(self._parent)
itemAttrib["order"] = str(self._order)
itemAttrib["type"] = str(self._type.name)
itemAttrib["class"] = str(self._class.name)
if self._type == nwItemType.FILE:
self._subPack(xPack, "exported", text=str(self._exported))
self._subPack(xPack, "layout", text=str(self._layout.name))
self._subPack(xPack, "charCount", text=str(self._charCount), none=False)
self._subPack(xPack, "wordCount", text=str(self._wordCount), none=False)
self._subPack(xPack, "paraCount", text=str(self._paraCount), none=False)
self._subPack(xPack, "cursorPos", text=str(self._cursorPos), none=False)
itemAttrib["layout"] = str(self._layout.name)

metaAttrib = {}
if self._type == nwItemType.FILE:
metaAttrib["charCount"] = str(self._charCount)
metaAttrib["wordCount"] = str(self._wordCount)
metaAttrib["paraCount"] = str(self._paraCount)
metaAttrib["cursorPos"] = str(self._cursorPos)
else:
self._subPack(xPack, "expanded", text=str(self._expanded))
metaAttrib["expanded"] = str(self._expanded)

nameAttrib = {}
nameAttrib["status"] = str(self._status)
if self._type == nwItemType.FILE:
nameAttrib["exported"] = str(self._exported)

xPack = etree.SubElement(xParent, "item", attrib=itemAttrib)
self._subPack(xPack, "meta", attrib=metaAttrib)
self._subPack(xPack, "name", text=str(self._name), attrib=nameAttrib)

return

Expand All @@ -175,19 +183,31 @@ def unpackXML(self, xItem):

self.setParent(xItem.attrib.get("parent", None))
self.setOrder(xItem.attrib.get("order", 0))
self.setType(xItem.attrib.get("type", None))
self.setClass(xItem.attrib.get("class", None))
self.setLayout(xItem.attrib.get("layout", None))

tmpStatus = ""
for xValue in xItem:
if xValue.tag == "name":
if xValue.tag == "meta":
self.setExpanded(xValue.attrib.get("expanded", False))
self.setCharCount(xValue.attrib.get("charCount", 0))
self.setWordCount(xValue.attrib.get("wordCount", 0))
self.setParaCount(xValue.attrib.get("paraCount", 0))
self.setCursorPos(xValue.attrib.get("cursorPos", 0))
elif xValue.tag == "name":
self.setName(xValue.text)
self.setStatus(xValue.attrib.get("status", None))
self.setExported(xValue.attrib.get("exported", True))

# Legacy Format (1.3 and earlier)
elif xValue.tag == "status":
self.setStatus(xValue.text)
elif xValue.tag == "type":
self.setType(xValue.text)
elif xValue.tag == "class":
self.setClass(xValue.text)
elif xValue.tag == "layout":
self.setLayout(xValue.text)
elif xValue.tag == "status":
tmpStatus = xValue.text
elif xValue.tag == "expanded":
self.setExpanded(xValue.text)
elif xValue.tag == "exported":
Expand All @@ -206,9 +226,6 @@ def unpackXML(self, xItem):
# version of novelWriter that doesn't know the tag
logger.error("Unknown tag '%s'", xValue.tag)

# Guarantees that <status> is parsed after <class>
self.setStatus(tmpStatus)

return True

@staticmethod
Expand Down
7 changes: 5 additions & 2 deletions novelwriter/core/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@

class NWProject():

FILE_VERSION = "1.3"
FILE_VERSION = "1.4"

def __init__(self, theParent):

Expand Down Expand Up @@ -479,8 +479,11 @@ def openProject(self, fileName, overrideLock=False):
# 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. Introduced
# in version 1.7.

if fileVersion not in ("1.0", "1.1", "1.2", "1.3"):
if fileVersion not in ("1.0", "1.1", "1.2", "1.3", "1.4"):
self.theParent.makeAlert(self.tr(
"Unknown or unsupported novelWriter project file format. "
"The project cannot be opened by this version of novelWriter. "
Expand Down
Loading

0 comments on commit 045a595

Please sign in to comment.