291 lines
9.6 KiB
Python
291 lines
9.6 KiB
Python
# -*- coding: utf-8 -*
|
||
"""novelWriter Project Wrapper
|
||
|
||
novelWriter – Project Wrapper
|
||
===============================
|
||
Class holding a project
|
||
|
||
File History:
|
||
Created: 2018-09-29 [0.0.1]
|
||
|
||
"""
|
||
|
||
import logging
|
||
import nw
|
||
|
||
from os import path, mkdir
|
||
from lxml import etree
|
||
from hashlib import sha256
|
||
from datetime import datetime
|
||
from time import time
|
||
|
||
from nw.enum import nwItemType, nwItemClass
|
||
from nw.project.item import NWItem
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
class NWProject():
|
||
|
||
def __init__(self):
|
||
|
||
# Internal
|
||
self.mainConf = nw.CONFIG
|
||
|
||
# Project Settings
|
||
self.projTree = {}
|
||
self.treeOrder = []
|
||
self.projPath = None
|
||
self.projFile = "nwProject.nwx"
|
||
self.projName = ""
|
||
self.bookTitle = ""
|
||
self.bookAuthors = []
|
||
|
||
return
|
||
|
||
def buildProjectTree(self):
|
||
return True
|
||
|
||
def newRoot(self, rootName, rootClass):
|
||
newItem = NWItem()
|
||
newItem.setName(rootName)
|
||
newItem.setType(nwItemType.ROOT)
|
||
newItem.setClass(rootClass)
|
||
self._appendItem(None,None,newItem)
|
||
return newItem.itemHandle
|
||
|
||
def newFolder(self, folderName, folderClass, pHandle):
|
||
newItem = NWItem()
|
||
newItem.setName(folderName)
|
||
newItem.setType(nwItemType.FOLDER)
|
||
newItem.setClass(folderClass)
|
||
self._appendItem(None,pHandle,newItem)
|
||
return newItem.itemHandle
|
||
|
||
def newFile(self, fileName, fileClass, pHandle):
|
||
newItem = NWItem()
|
||
newItem.setName(fileName)
|
||
newItem.setType(nwItemType.FILE)
|
||
newItem.setClass(fileClass)
|
||
self._appendItem(None,pHandle,newItem)
|
||
return newItem.itemHandle
|
||
|
||
def newProject(self):
|
||
self.projTree = {}
|
||
self.treeOrder = []
|
||
self.projPath = None
|
||
self.projFile = "nwProject.nwx"
|
||
self.projName = ""
|
||
self.bookTitle = ""
|
||
self.bookAuthors = []
|
||
hNovel = self.newRoot("Novel", nwItemClass.NOVEL)
|
||
hChars = self.newRoot("Characters",nwItemClass.CHARACTER)
|
||
hWorld = self.newRoot("World", nwItemClass.WORLD)
|
||
hChapt = self.newFolder("New Chapter", nwItemClass.CHAPTER ,hNovel)
|
||
hScene = self.newFile("New Scene", nwItemClass.NONE, hChapt)
|
||
return
|
||
|
||
def openProject(self, fileName):
|
||
|
||
if not path.isfile(fileName):
|
||
fileName = path.join(fileName, "nwProject.nwx")
|
||
if not path.isfile(fileName):
|
||
logger.error("File not found: %s" % fileName)
|
||
return False
|
||
|
||
self.projPath = path.dirname(fileName)
|
||
logger.debug("Opening project: %s" % self.projPath)
|
||
|
||
nwXML = etree.parse(fileName)
|
||
xRoot = nwXML.getroot()
|
||
|
||
nwxRoot = xRoot.tag
|
||
appVersion = xRoot.attrib["appVersion"]
|
||
fileVersion = xRoot.attrib["fileVersion"]
|
||
|
||
logger.verbose("XML root is %s" % nwxRoot)
|
||
logger.verbose("File version is %s" % fileVersion)
|
||
|
||
if not nwxRoot == "novelWriterXML" or not fileVersion == "1.0":
|
||
logger.error("Project file does not appear to be a novelWriterXML file version 1.0")
|
||
return False
|
||
|
||
for xChild in xRoot:
|
||
if xChild.tag == "project":
|
||
logger.debug("Found project meta")
|
||
for xItem in xChild:
|
||
if xItem.text is None: continue
|
||
if xItem.tag == "name":
|
||
logger.verbose("Working Title: '%s'" % xItem.text)
|
||
self.projName = xItem.text
|
||
elif xItem.tag == "title":
|
||
logger.verbose("Title is '%s'" % xItem.text)
|
||
self.bookTitle = xItem.text
|
||
elif xItem.tag == "author":
|
||
logger.verbose("Author: '%s'" % xItem.text)
|
||
self.bookAuthors.append(xItem.text)
|
||
elif xChild.tag == "content":
|
||
logger.debug("Found project content")
|
||
for xItem in xChild:
|
||
itemAttrib = xItem.attrib
|
||
if "handle" in xItem.attrib:
|
||
tHandle = itemAttrib["handle"]
|
||
else:
|
||
logger.error("Skipping entry missing handle")
|
||
continue
|
||
if "parent" in xItem.attrib:
|
||
pHandle = itemAttrib["parent"]
|
||
else:
|
||
pHandle = None
|
||
nwItem = NWItem()
|
||
for xValue in xItem:
|
||
nwItem.setFromTag(xValue.tag,xValue.text)
|
||
self._appendItem(tHandle,pHandle,nwItem)
|
||
|
||
self.mainConf.setRecent(self.projPath)
|
||
|
||
return True
|
||
|
||
def saveProject(self):
|
||
|
||
if self.projPath is None:
|
||
logger.error("Project path not set, cannot save.")
|
||
return False
|
||
|
||
if not path.isdir(self.projPath):
|
||
logger.info("Created folder %s" % self.projPath)
|
||
mkdir(self.projPath)
|
||
|
||
logger.debug("Saving project: %s" % self.projPath)
|
||
|
||
# Root element and project details
|
||
logger.debug("Writing project meta")
|
||
nwXML = etree.Element("novelWriterXML",attrib={
|
||
"fileVersion" : "1.0",
|
||
"appVersion" : str(nw.__version__),
|
||
"timeStamp" : datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||
})
|
||
xProject = etree.SubElement(nwXML,"project")
|
||
xProjName = etree.SubElement(xProject,"name")
|
||
xProjName.text = self.projName
|
||
xBookTitle = etree.SubElement(xProject,"title")
|
||
xBookTitle.text = self.bookTitle
|
||
for bookAuthor in self.bookAuthors:
|
||
if bookAuthor == "": continue
|
||
xBookAuthor = etree.SubElement(xProject,"author")
|
||
xBookAuthor.text = bookAuthor
|
||
|
||
# Save Tree Content
|
||
logger.debug("Writing project content")
|
||
xContent = etree.SubElement(nwXML,"content",attrib={"count":str(len(self.projTree))})
|
||
itemIdx = 0
|
||
for tHandle in self.treeOrder:
|
||
nwItem = self.projTree[tHandle]
|
||
xItem = etree.SubElement(xContent,"item",attrib={
|
||
"handle" : str(tHandle),
|
||
"parent" : str(nwItem.parHandle),
|
||
"order" : str(nwItem.itemOrder),
|
||
})
|
||
xItemValue = etree.SubElement(xItem,"name")
|
||
xItemValue.text = str(nwItem.itemName)
|
||
xItemValue = etree.SubElement(xItem,"type")
|
||
xItemValue.text = str(nwItem.itemType.name)
|
||
xItemValue = etree.SubElement(xItem,"class")
|
||
xItemValue.text = str(nwItem.itemClass.name)
|
||
xItemValue = etree.SubElement(xItem,"expanded")
|
||
xItemValue.text = str(nwItem.isExpanded)
|
||
|
||
# Write the xml tree to file
|
||
with open(path.join(self.projPath,self.projFile),"wb") as outFile:
|
||
outFile.write(etree.tostring(
|
||
nwXML,
|
||
pretty_print = True,
|
||
encoding = "utf-8",
|
||
xml_declaration = True
|
||
))
|
||
|
||
self.mainConf.setRecent(self.projPath)
|
||
|
||
return True
|
||
|
||
#
|
||
# Set Functions
|
||
#
|
||
|
||
def setProjectPath(self, projPath):
|
||
self.projPath = projPath
|
||
return True
|
||
|
||
def setProjectName(self, projName):
|
||
self.projName = projName.strip()
|
||
return True
|
||
|
||
def setBookTitle(self, bookTitle):
|
||
self.bookTitle = bookTitle.strip()
|
||
return True
|
||
|
||
def setBookAuthors(self, bookAuthors):
|
||
self.bookAuthors = []
|
||
for bookAuthor in bookAuthors.split("\n"):
|
||
bookAuthor = bookAuthor.strip()
|
||
if bookAuthor == "":
|
||
continue
|
||
self.bookAuthors.append(bookAuthor)
|
||
return True
|
||
|
||
def setTreeOrder(self, newOrder):
|
||
if len(self.treeOrder) != len(newOrder):
|
||
logger.warning("Size of new and old tree order does not match")
|
||
self.treeOrder = newOrder
|
||
return True
|
||
|
||
#
|
||
# Get Functions
|
||
#
|
||
|
||
def getItem(self, tHandle):
|
||
if tHandle in self.projTree:
|
||
return self.projTree[tHandle]
|
||
logger.error("No tree item with handle %s" % str(tHandle))
|
||
return None
|
||
|
||
#
|
||
# Internal Functions
|
||
#
|
||
|
||
def _appendItem(self, tHandle, pHandle, nwItem):
|
||
tHandle = self._checkString(tHandle,self._makeHandle(),False)
|
||
pHandle = self._checkString(pHandle,None,True)
|
||
logger.verbose("Adding entry %s with parent %s" % (str(tHandle),str(pHandle)))
|
||
nwItem.setHandle(tHandle)
|
||
nwItem.setParent(pHandle)
|
||
self.projTree[tHandle] = nwItem
|
||
self.treeOrder.append(tHandle)
|
||
return
|
||
|
||
def _makeHandle(self, addSeed=""):
|
||
newSeed = str(time()) + addSeed
|
||
logger.verbose("Generating handle with seed '%s'" % newSeed)
|
||
itemHandle = sha256(newSeed.encode()).hexdigest()[0:13]
|
||
if itemHandle in self.projTree.keys():
|
||
logger.warning("Duplicate handle encountered! Retrying ...")
|
||
itemHandle = self._makeHandle(addSeed+"!")
|
||
return itemHandle
|
||
|
||
def _checkString(self,checkValue,defaultValue,allowNone=False):
|
||
if allowNone:
|
||
if checkValue == None: return None
|
||
if checkValue == "None": return None
|
||
if isinstance(checkValue,str): return str(checkValue)
|
||
return defaultValue
|
||
|
||
def _checkInt(self,checkValue,defaultValue,allowNone=False):
|
||
if allowNone:
|
||
if checkValue == None: return None
|
||
if checkValue == "None": return None
|
||
try:
|
||
return int(checkValue)
|
||
except:
|
||
return defaultValue
|
||
|
||
# END Class NWProject
|