mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-17 11:24:14 +02:00
Fix a .qetz that cannot be opened after only a folio's title changed
With a .qetz and lazy folios (both the default), changing only the
title, author, date or revision of a folio holding a picture, while that
folio was not displayed, then saving, gave a .qetz that refused to open:
'picture "images/<sha>.png" is missing'.
Such a folio is saved from its items as read, with its new header
(Diagram::deferredXmlWithHeader()). Read from a .qetz, its <image> names
its picture's file (qetz-file) instead of holding the picture, and
QetContainer::split() takes out only pictures held inline: the file was
never written, while the folio still named it. The folio now gets its
pictures written in, from the bytes the project keeps
(QETProject::containerPicture()), as a built folio writes them.
On master, a picture on folio 2 of grafcet, iso_sfc_example, 741,
tremie_vibrante or Projet_vierge is lost this way; found on three forum
projects by comparing lazy saves with saves of every folio built. Saving
as .qet was not affected.
New tst_qetzheaderpictures (grafcet.qet: picture on folio 2, saved as
.qetz; its title changed with folios built when needed; reopened with
its picture and title): fails without this change ("picture ... is
missing"). ctest 107/107.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -208,6 +208,13 @@ QString QetContainer::pictureFile(const QDomElement &image)
|
|||||||
return image.attribute(ImageFileAttribute);
|
return image.attribute(ImageFileAttribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QetContainer::inlinePicture(QDomElement image, const QByteArray &bytes)
|
||||||
|
{
|
||||||
|
image.removeAttribute(ImageFileAttribute);
|
||||||
|
image.insertBefore(image.ownerDocument().createTextNode(QString::fromLatin1(bytes.toBase64())),
|
||||||
|
image.firstChild());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@brief QetContainer::ownUuids
|
@brief QetContainer::ownUuids
|
||||||
@return true if every folio, symbol and wire of @p project has a uuid
|
@return true if every folio, symbol and wire of @p project has a uuid
|
||||||
|
|||||||
@@ -84,6 +84,9 @@ namespace QetContainer
|
|||||||
QString *error = nullptr);
|
QString *error = nullptr);
|
||||||
/// The file join() left the picture of @p image in, or an empty string
|
/// The file join() left the picture of @p image in, or an empty string
|
||||||
QString pictureFile(const QDomElement &image);
|
QString pictureFile(const QDomElement &image);
|
||||||
|
/// @p image, which names its picture's file (pictureFile()), given
|
||||||
|
/// the picture itself, @p bytes, as a document read whole has it
|
||||||
|
void inlinePicture(QDomElement image, const QByteArray &bytes);
|
||||||
|
|
||||||
/// Take what QElectroTech computes out of a folio (format 4)
|
/// Take what QElectroTech computes out of a folio (format 4)
|
||||||
void dropComputed(QDomElement diagram, bool terminal_lists);
|
void dropComputed(QDomElement diagram, bool terminal_lists);
|
||||||
|
|||||||
@@ -881,6 +881,17 @@ QDomElement Diagram::deferredXmlWithHeader(QDomDocument &document)
|
|||||||
for (QDomElement child = items.firstChildElement() ; !child.isNull() ; child = child.nextSiblingElement())
|
for (QDomElement child = items.firstChildElement() ; !child.isNull() ; child = child.nextSiblingElement())
|
||||||
if (!header_children.contains(child.tagName()))
|
if (!header_children.contains(child.tagName()))
|
||||||
folio.appendChild(document.importNode(child, true));
|
folio.appendChild(document.importNode(child, true));
|
||||||
|
//Its pictures written in, as building it writes them. A folio read
|
||||||
|
//from a .qetz names its pictures' files instead, which the next
|
||||||
|
//.qetz written from this document would leave out: that project
|
||||||
|
//then refused to open, "picture ... is missing" (F118).
|
||||||
|
for (QDomElement image : QET::findInDomElement(folio, QStringLiteral("images"), QStringLiteral("image"))) {
|
||||||
|
const QString file = QetContainer::pictureFile(image);
|
||||||
|
if (file.isEmpty() || !m_project) continue;
|
||||||
|
const QByteArray bytes = m_project->containerPicture(file);
|
||||||
|
if (!bytes.isEmpty())
|
||||||
|
QetContainer::inlinePicture(image, bytes);
|
||||||
|
}
|
||||||
return folio;
|
return folio;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -943,6 +943,18 @@ if(QET_HAS_SCRIPTING)
|
|||||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\""
|
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\""
|
||||||
"QET_EXAMPLES_DIR=\"${QET_DIR}/examples\"")
|
"QET_EXAMPLES_DIR=\"${QET_DIR}/examples\"")
|
||||||
|
|
||||||
|
# A .qetz folio whose title only changed keeps its pictures when saved
|
||||||
|
# with folios built when needed (F118), on examples/grafcet.qet.
|
||||||
|
add_executable(
|
||||||
|
tst_qetzheaderpictures
|
||||||
|
tst_qetzheaderpictures.cpp)
|
||||||
|
add_test(NAME tst_qetzheaderpictures COMMAND tst_qetzheaderpictures)
|
||||||
|
add_dependencies(tst_qetzheaderpictures qelectrotech)
|
||||||
|
target_link_libraries(tst_qetzheaderpictures PRIVATE Qt::Test)
|
||||||
|
target_compile_definitions(tst_qetzheaderpictures PRIVATE
|
||||||
|
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\""
|
||||||
|
"QET_EXAMPLES_DIR=\"${QET_DIR}/examples\"")
|
||||||
|
|
||||||
# qet.elementTerminals() lists each terminal's uuid and qet.terminalIndex()
|
# qet.elementTerminals() lists each terminal's uuid and qet.terminalIndex()
|
||||||
# turns element uuid + terminal uuid back into the index the conductor
|
# turns element uuid + terminal uuid back into the index the conductor
|
||||||
# calls take, on examples/perceuse.qet (two terminals at one point).
|
# calls take, on examples/perceuse.qet (two terminals at one point).
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#include <QtTest>
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QProcessEnvironment>
|
||||||
|
#include <QTemporaryDir>
|
||||||
|
|
||||||
|
// A folio of a .qetz whose title only changed is saved from its items as
|
||||||
|
// read, not built (Diagram::deferredXmlWithHeader()). Those items name
|
||||||
|
// their pictures' files in the .qetz, which the new .qetz left out: the
|
||||||
|
// project saved then refused to open, 'picture "images/..." is missing'
|
||||||
|
// (FINDINGS F118). Runs scripts through the real binary's --run on a copy
|
||||||
|
// of examples/grafcet.qet given a picture on its second folio.
|
||||||
|
class tst_qetzheaderpictures : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
QTemporaryDir m_dir;
|
||||||
|
|
||||||
|
// Run @p script on @p project; its output
|
||||||
|
QString run(const QString &script, const QString &project, bool every_folio_built)
|
||||||
|
{
|
||||||
|
const QString path = m_dir.filePath(QStringLiteral("script.js"));
|
||||||
|
QFile f(path);
|
||||||
|
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) return QString();
|
||||||
|
f.write(script.toUtf8());
|
||||||
|
f.close();
|
||||||
|
const QString home = m_dir.filePath(QStringLiteral("home"));
|
||||||
|
QDir().mkpath(home);
|
||||||
|
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||||
|
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
|
||||||
|
env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1"));
|
||||||
|
env.insert(QStringLiteral("HOME"), home);
|
||||||
|
env.insert(QStringLiteral("XDG_CONFIG_HOME"), home + QStringLiteral("/config"));
|
||||||
|
env.insert(QStringLiteral("XDG_DATA_HOME"), home + QStringLiteral("/data"));
|
||||||
|
env.insert(QStringLiteral("TMPDIR"), m_dir.path());
|
||||||
|
if (every_folio_built)
|
||||||
|
env.insert(QStringLiteral("QET_EAGER_FOLIOS"), QStringLiteral("1"));
|
||||||
|
else
|
||||||
|
env.remove(QStringLiteral("QET_EAGER_FOLIOS"));
|
||||||
|
QProcess proc;
|
||||||
|
proc.setProcessEnvironment(env);
|
||||||
|
proc.start(QStringLiteral(QET_TEST_BINARY_PATH), {QStringLiteral("--run"), path, project});
|
||||||
|
proc.waitForFinished(120000);
|
||||||
|
return QString::fromUtf8(proc.readAllStandardOutput() + proc.readAllStandardError());
|
||||||
|
}
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void initTestCase()
|
||||||
|
{
|
||||||
|
QVERIFY(m_dir.isValid());
|
||||||
|
QVERIFY(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void titleChangeKeepsPictures()
|
||||||
|
{
|
||||||
|
// A 1x1 PNG
|
||||||
|
const QString png = m_dir.filePath(QStringLiteral("dot.png"));
|
||||||
|
QFile p(png);
|
||||||
|
QVERIFY(p.open(QIODevice::WriteOnly));
|
||||||
|
p.write(QByteArray::fromBase64("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="));
|
||||||
|
p.close();
|
||||||
|
const QString with_picture = m_dir.filePath(QStringLiteral("picture.qetz"));
|
||||||
|
const QString titled = m_dir.filePath(QStringLiteral("titled.qetz"));
|
||||||
|
QString out = run(QStringLiteral("qet.log('ADDED ' + qet.addImage(1, '%1', 100, 100));"
|
||||||
|
"qet.save('%2');").arg(png, with_picture),
|
||||||
|
QStringLiteral(QET_EXAMPLES_DIR "/grafcet.qet"), true);
|
||||||
|
QVERIFY2(out.contains(QStringLiteral("ADDED 0")), qPrintable(out));
|
||||||
|
QVERIFY(QFile::exists(with_picture));
|
||||||
|
|
||||||
|
// Only the title of the folio with the picture, folios built
|
||||||
|
// when needed
|
||||||
|
out = run(QStringLiteral("qet.log('SET ' + qet.setFolioProperty(1, 'title', 'Edited'));"
|
||||||
|
"qet.save('%1');").arg(titled), with_picture, false);
|
||||||
|
QVERIFY2(out.contains(QStringLiteral("SET true")), qPrintable(out));
|
||||||
|
QVERIFY(QFile::exists(titled));
|
||||||
|
|
||||||
|
// Opens, with its picture and the new title
|
||||||
|
out = run(QStringLiteral("qet.log('PICTURES ' + qet.images(1).length);"
|
||||||
|
"qet.log('TITLE ' + qet.folioTitle(1));"), titled, true);
|
||||||
|
QVERIFY2(out.contains(QStringLiteral("PICTURES 1")), qPrintable(out));
|
||||||
|
QVERIFY2(out.contains(QStringLiteral("TITLE Edited")), qPrintable(out));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QTEST_APPLESS_MAIN(tst_qetzheaderpictures)
|
||||||
|
|
||||||
|
#include "tst_qetzheaderpictures.moc"
|
||||||
Reference in New Issue
Block a user