manuskript/manuskript/load_save/version_0.py

299 lines
9.5 KiB
Python

#!/usr/bin/env python
# --!-- coding: utf8 --!--
# Version 0 of file saving format.
# Was used at the beginning and up until version XXX when
# it was superseded by Version 1, which is more open and flexible
import os
import zipfile
from PyQt5.QtCore import QModelIndex, Qt
from PyQt5.QtGui import QColor, QStandardItem
from PyQt5.QtWidgets import qApp
from lxml import etree as ET
from manuskript import settings
from manuskript.functions import iconColor, iconFromColorString, mainWindow
from manuskript.models.characterModel import Character, CharacterInfo
import logging
LOGGER = logging.getLogger(__name__)
try:
import zlib # Used with zipfile for compression
compression = zipfile.ZIP_DEFLATED
except:
compression = zipfile.ZIP_STORED
###########################################################################################
# SAVE
###########################################################################################
def saveProject():
"""
Saves the whole project. Call this function to save the project in Version 0 format.
"""
files = []
mw = mainWindow()
files.append((saveStandardItemModelXML(mw.mdlFlatData),
"flatModel.xml"))
LOGGER.error("File format 0 does not save characters!")
# files.append((saveStandardItemModelXML(mw.mdlCharacter),
# "perso.xml"))
files.append((saveStandardItemModelXML(mw.mdlWorld),
"world.xml"))
files.append((saveStandardItemModelXML(mw.mdlLabels),
"labels.xml"))
files.append((saveStandardItemModelXML(mw.mdlStatus),
"status.xml"))
files.append((saveStandardItemModelXML(mw.mdlPlots),
"plots.xml"))
files.append((mw.mdlOutline.saveToXML(),
"outline.xml"))
files.append((settings.save(),
"settings.pickle"))
saveFilesToZip(files, mw.currentProject)
def saveFilesToZip(files, zipname):
"""Saves given files to zipname.
files is actually a list of (content, filename)."""
zf = zipfile.ZipFile(zipname, mode="w")
for content, filename in files:
zf.writestr(filename, content, compress_type=compression)
zf.close()
def saveStandardItemModelXML(mdl, xml=None):
"""Saves the given QStandardItemModel to XML.
If xml (filename) is given, saves to xml. Otherwise returns as string."""
root = ET.Element("model")
root.attrib["version"] = qApp.applicationVersion()
# Header
header = ET.SubElement(root, "header")
vHeader = ET.SubElement(header, "vertical")
for x in range(mdl.rowCount()):
vH = ET.SubElement(vHeader, "label")
vH.attrib["row"] = str(x)
vH.attrib["text"] = str(mdl.headerData(x, Qt.Vertical))
hHeader = ET.SubElement(header, "horizontal")
for y in range(mdl.columnCount()):
hH = ET.SubElement(hHeader, "label")
hH.attrib["row"] = str(y)
hH.attrib["text"] = str(mdl.headerData(y, Qt.Horizontal))
# Data
data = ET.SubElement(root, "data")
saveItem(data, mdl)
# LOGGER.info("Saving to {}.".format(xml))
if xml:
ET.ElementTree(root).write(xml, encoding="UTF-8", xml_declaration=True, pretty_print=True)
else:
return ET.tostring(root, encoding="UTF-8", xml_declaration=True, pretty_print=True)
def saveItem(root, mdl, parent=QModelIndex()):
for x in range(mdl.rowCount(parent)):
row = ET.SubElement(root, "row")
row.attrib["row"] = str(x)
for y in range(mdl.columnCount(parent)):
col = ET.SubElement(row, "col")
col.attrib["col"] = str(y)
if mdl.data(mdl.index(x, y, parent), Qt.DecorationRole) != None:
color = iconColor(mdl.data(mdl.index(x, y, parent), Qt.DecorationRole)).name(QColor.HexArgb)
col.attrib["color"] = color if color != "#ff000000" else "#00000000"
if mdl.data(mdl.index(x, y, parent)) != "":
col.text = mdl.data(mdl.index(x, y, parent))
if mdl.hasChildren(mdl.index(x, y, parent)):
saveItem(col, mdl, mdl.index(x, y, parent))
###########################################################################################
# LOAD
###########################################################################################
def loadProject(project):
files = loadFilesFromZip(project)
mw = mainWindow()
errors = []
if "flatModel.xml" in files:
loadStandardItemModelXML(mw.mdlFlatData,
files["flatModel.xml"], fromString=True)
else:
errors.append("flatModel.xml")
if "perso.xml" in files:
loadStandardItemModelXMLForCharacters(mw.mdlCharacter, files["perso.xml"])
else:
errors.append("perso.xml")
if "world.xml" in files:
loadStandardItemModelXML(mw.mdlWorld,
files["world.xml"], fromString=True)
else:
errors.append("world.xml")
if "labels.xml" in files:
loadStandardItemModelXML(mw.mdlLabels,
files["labels.xml"], fromString=True)
else:
errors.append("labels.xml")
if "status.xml" in files:
loadStandardItemModelXML(mw.mdlStatus,
files["status.xml"], fromString=True)
else:
errors.append("status.xml")
if "plots.xml" in files:
loadStandardItemModelXML(mw.mdlPlots,
files["plots.xml"], fromString=True)
else:
errors.append("plots.xml")
if "outline.xml" in files:
mw.mdlOutline.loadFromXML(files["outline.xml"], fromString=True)
else:
errors.append("outline.xml")
if "settings.txt" in files:
settings.load(files["settings.txt"], fromString=True, protocol=0)
else:
errors.append("settings.txt")
if "settings.pickle" in files:
LOGGER.info("Pickle settings files are no longer supported for security reasons. You can delete it from your data.")
return errors
def loadFilesFromZip(zipname):
"""Returns the content of zipfile as a dict of filename:content."""
zf = zipfile.ZipFile(zipname)
files = {}
for f in zf.namelist():
# Some archiving programs (e.g. 7-Zip) also store entries for the directories when
# creating an archive. We have no use for these entries; skip them entirely.
if f[-1:] != '/':
files[os.path.normpath(f)] = zf.read(f)
return files
def loadStandardItemModelXML(mdl, xml, fromString=False):
"""Load data to a QStandardItemModel mdl from xml.
By default xml is a filename. If fromString=True, xml is a string containing the data."""
# LOGGER.info("Loading {}...".format(xml))
if not fromString:
try:
tree = ET.parse(xml)
except:
LOGGER.error("Failed to load XML for QStandardItemModel (%s).", xml)
return
else:
root = ET.fromstring(xml)
# root = tree.getroot()
# Header
hLabels = []
vLabels = []
for l in root.find("header").find("horizontal").findall("label"):
hLabels.append(l.attrib["text"])
for l in root.find("header").find("vertical").findall("label"):
vLabels.append(l.attrib["text"])
# LOGGER.debug(root.find("header").find("vertical").text)
# mdl.setVerticalHeaderLabels(vLabels)
# mdl.setHorizontalHeaderLabels(hLabels)
# Populates with empty items
for i in enumerate(vLabels):
row = []
for r in enumerate(hLabels):
row.append(QStandardItem())
mdl.appendRow(row)
# Data
data = root.find("data")
loadItem(data, mdl)
return True
def loadItem(root, mdl, parent=QModelIndex()):
for row in root:
r = int(row.attrib["row"])
for col in row:
c = int(col.attrib["col"])
item = mdl.itemFromIndex(mdl.index(r, c, parent))
if not item:
item = QStandardItem()
mdl.itemFromIndex(parent).setChild(r, c, item)
if col.text:
# mdl.setData(mdl.index(r, c, parent), col.text)
item.setText(col.text)
if "color" in col.attrib:
# mdl.itemFromIndex(mdl.index(r, c, parent)).setIcon(iconFromColorString(col.attrib["color"]))
item.setIcon(iconFromColorString(col.attrib["color"]))
if len(col) != 0:
# loadItem(col, mdl, mdl.index(r, c, parent))
loadItem(col, mdl, mdl.indexFromItem(item))
def loadStandardItemModelXMLForCharacters(mdl, xml):
"""
Loads a standardItemModel saved to XML by version 0, but for the new characterModel.
@param mdl: characterModel
@param xml: the content of the xml
@return: nothing
"""
mdl = mainWindow().mdlCharacter
root = ET.fromstring(xml)
data = root.find("data")
for row in data:
char = Character(mdl)
for col in row:
c = int(col.attrib["col"])
# Value
if col.text:
char._data[c] = col.text
# Color
if "color" in col.attrib:
char.setColor(QColor(col.attrib["color"]))
# Infos
if len(col) != 0:
for rrow in col:
info = CharacterInfo(char)
for ccol in rrow:
cc = int(ccol.attrib["col"])
if cc == 11 and ccol.text:
info.description = ccol.text
if cc == 12 and ccol.text:
info.value = ccol.text
char.infos.append(info)
mdl.characters.append(char)