mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-15 01:54:14 +02:00
Merge master into feat/qetz-db-truth-v2
#1470 keeps a .qetz's pictures out of the document; the index path still reads them in, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -169,13 +169,19 @@ bool QetContainer::isZip(const QByteArray &content)
|
||||
The project document held in @p content: unzipped and joined for a
|
||||
.qetz, parsed for a .qet.
|
||||
*/
|
||||
bool QetContainer::readDocument(const QByteArray &content, QDomDocument *document, QString *error)
|
||||
bool QetContainer::readDocument(const QByteArray &content, QDomDocument *document,
|
||||
QString *error, Pictures *pictures)
|
||||
{
|
||||
if (!isZip(content))
|
||||
return parse(content, document, error);
|
||||
QList<QetZip::Entry> entries;
|
||||
return QetZip::fromBytes(content, &entries, error)
|
||||
&& join(entries, document, error);
|
||||
&& join(entries, document, error, nullptr, pictures);
|
||||
}
|
||||
|
||||
QString QetContainer::pictureFile(const QDomElement &image)
|
||||
{
|
||||
return image.attribute(ImageFileAttribute);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -394,9 +400,11 @@ bool QetContainer::readFolio(const QHash<QString, QByteArray> &files, const QStr
|
||||
Refused, with @p error set: not a .qetz, made for a later QElectroTech
|
||||
(the manifest's min-reader), or a part missing or unreadable. Entries
|
||||
nothing names are ignored, so a later format may add some.
|
||||
With @p pictures, the pictures stay out of the document: see the
|
||||
namespace.
|
||||
*/
|
||||
bool QetContainer::join(const QList<QetZip::Entry> &entries, QDomDocument *project, QString *error,
|
||||
QStringList *folio_files)
|
||||
QStringList *folio_files, Pictures *pictures)
|
||||
{
|
||||
QHash<QString, QByteArray> files;
|
||||
for (const QetZip::Entry &entry : entries)
|
||||
@@ -451,7 +459,8 @@ bool QetContainer::join(const QList<QetZip::Entry> &entries, QDomDocument *proje
|
||||
part.parentNode().replaceChild(project->importNode(document.documentElement(), true), part);
|
||||
}
|
||||
|
||||
//Pictures back to base64, as the image's first child
|
||||
//Pictures back to base64, as the image's first child, or into
|
||||
//pictures
|
||||
const QDomNodeList images = project->elementsByTagName(QStringLiteral("image"));
|
||||
for (int i = 0 ; i < images.size() ; ++i)
|
||||
{
|
||||
@@ -462,6 +471,10 @@ bool QetContainer::join(const QList<QetZip::Entry> &entries, QDomDocument *proje
|
||||
setError(error, QStringLiteral("picture \"%1\" is missing").arg(file));
|
||||
return false;
|
||||
}
|
||||
if (pictures) {
|
||||
pictures->insert(file, files.value(file));
|
||||
continue;
|
||||
}
|
||||
image.removeAttribute(ImageFileAttribute);
|
||||
image.insertBefore(project->createTextNode(QString::fromLatin1(files.value(file).toBase64())),
|
||||
image.firstChild());
|
||||
|
||||
@@ -43,10 +43,17 @@
|
||||
|
||||
join(split(d)) is d. Nothing here knows what the parts mean: the
|
||||
project is read and written by the same code as a .qet file.
|
||||
|
||||
Given a Pictures map, join() leaves the pictures out of the document:
|
||||
each <image> keeps the name of its file, pictureFile(), and the bytes
|
||||
go in the map. Turning them back into base64 text costs seconds on a
|
||||
project with large pictures, and the folio would only decode it again.
|
||||
*/
|
||||
namespace QetContainer
|
||||
{
|
||||
extern const char MimeType[];
|
||||
/// Picture bytes by file name, see join()
|
||||
using Pictures = QHash<QString, QByteArray>;
|
||||
/// The format written; files of a later format are refused by join()
|
||||
/// unless their min-reader allows this one.
|
||||
constexpr int Format = 3;
|
||||
@@ -57,13 +64,16 @@ namespace QetContainer
|
||||
const QByteArray &index = QByteArray(),
|
||||
const QHash<QString, QByteArray> &kept_from = QHash<QString, QByteArray>());
|
||||
bool join(const QList<QetZip::Entry> &entries, QDomDocument *project,
|
||||
QString *error = nullptr, QStringList *folio_files = nullptr);
|
||||
QString *error = nullptr, QStringList *folio_files = nullptr,
|
||||
Pictures *pictures = nullptr);
|
||||
/// A folio part of @p files, which join() left out, with its
|
||||
/// pictures and its engineering data back (stage D2)
|
||||
/// @p reader, if any, reads the engineering data of @p files
|
||||
bool readFolio(const QHash<QString, QByteArray> &files, const QString &file,
|
||||
QDomDocument *folio, QString *error = nullptr,
|
||||
QetContainerDb::FolioReader *reader = nullptr);
|
||||
/// The file join() left the picture of @p image in, or an empty string
|
||||
QString pictureFile(const QDomElement &image);
|
||||
|
||||
/// Take what QElectroTech computes out of a folio (format 3)
|
||||
void dropComputed(QDomElement diagram, bool terminal_lists);
|
||||
@@ -72,7 +82,8 @@ namespace QetContainer
|
||||
/// true if @p content is a zip, so a .qetz rather than a .qet
|
||||
bool isZip(const QByteArray &content);
|
||||
/// The project document in @p content, a .qet or a .qetz
|
||||
bool readDocument(const QByteArray &content, QDomDocument *document, QString *error = nullptr);
|
||||
bool readDocument(const QByteArray &content, QDomDocument *document,
|
||||
QString *error = nullptr, Pictures *pictures = nullptr);
|
||||
}
|
||||
|
||||
#endif // QETCONTAINER_H
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <QFile>
|
||||
#include <QSaveFile>
|
||||
#include <QSet>
|
||||
#include <QtEndian>
|
||||
|
||||
namespace {
|
||||
void setError(QString *error, const QString &text)
|
||||
@@ -44,6 +45,42 @@ bool safeName(const QString &name)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//Built with MINIZ_NO_TIME, miniz leaves each entry's DOS date at 0,
|
||||
//day 0 of month 0, which is no date: unzip shows 30 Nov 1979, other
|
||||
//tools 13 Dec 1901. Give every entry 1 Jan 1980 00:00 instead, the
|
||||
//first valid DOS date, so the bytes still do not depend on the time.
|
||||
constexpr quint16 EntryDosDate = (1 << 5) | 1;
|
||||
|
||||
bool setEntryDates(QByteArray *bytes)
|
||||
{
|
||||
char *data = bytes->data();
|
||||
mz_zip_archive zip;
|
||||
mz_zip_zero_struct(&zip);
|
||||
if (!mz_zip_reader_init_mem(&zip, data, size_t(bytes->size()), 0)) return false;
|
||||
|
||||
bool ok = true;
|
||||
const mz_uint count = mz_zip_reader_get_num_files(&zip);
|
||||
for (mz_uint i = 0 ; i < count && ok ; ++i)
|
||||
{
|
||||
mz_zip_archive_file_stat stat;
|
||||
if (!mz_zip_reader_file_stat(&zip, i, &stat)) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
//date at offset 12 of the local header, 14 of the central one
|
||||
const quint64 local = stat.m_local_header_ofs + 12;
|
||||
const quint64 central = zip.m_central_directory_file_ofs + stat.m_central_dir_ofs + 14;
|
||||
if (local + 2 > quint64(bytes->size()) || central + 2 > quint64(bytes->size())) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
qToLittleEndian<quint16>(EntryDosDate, data + local);
|
||||
qToLittleEndian<quint16>(EntryDosDate, data + central);
|
||||
}
|
||||
mz_zip_reader_end(&zip);
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,8 +124,12 @@ QByteArray QetZip::toBytes(const QList<Entry> &entries, QString *error)
|
||||
mz_zip_writer_end(&zip);
|
||||
return {};
|
||||
}
|
||||
const QByteArray bytes(static_cast<const char *>(buffer), qsizetype(size));
|
||||
QByteArray bytes(static_cast<const char *>(buffer), qsizetype(size));
|
||||
mz_zip_writer_end(&zip); //frees buffer
|
||||
if (!setEntryDates(&bytes)) {
|
||||
setError(error, QStringLiteral("cannot date the zip entries"));
|
||||
return {};
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
megabytes, not gigabytes -- and written through QSaveFile, so a failed
|
||||
save leaves the previous file as it was.
|
||||
|
||||
Entries carry no timestamp: saving the same project twice gives the
|
||||
same bytes.
|
||||
Every entry is dated 1 Jan 1980 00:00, not the time of saving: saving
|
||||
the same project twice gives the same bytes.
|
||||
*/
|
||||
namespace QetZip
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user