mirror of
https://github.com/olivierkes/manuskript.git
synced 2024-09-30 09:17:41 +13:00
33ac127173
While tackling issue #529, I stumbled across the odd behaviour that re-compressing the archive with 7-Zip broke what should be a valid Manuskript project. After investigation it turned out that the code that loads the texts sensibly expects there to only be files tracked in the files dictionary. It is completely valid for a zip file to contain entries describing the contained directories. The logical fix is to simply avoid adding these directory entries to our files dictionary in the first place.
292 lines
9.3 KiB
Python
292 lines
9.3 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
|
|
|
|
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"))
|
|
print("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)
|
|
|
|
# print(qApp.tr("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.pickle" in files:
|
|
settings.load(files["settings.pickle"], fromString=True)
|
|
else:
|
|
errors.append("settings.pickle")
|
|
|
|
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."""
|
|
|
|
# print(qApp.tr("Loading {}... ").format(xml), end="")
|
|
|
|
if not fromString:
|
|
try:
|
|
tree = ET.parse(xml)
|
|
except:
|
|
print("Failed.")
|
|
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"])
|
|
|
|
# print(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)
|