Save a .qetz without building partners: symbols save the links they wait for (stage D5a)

A built symbol whose partner is on a folio not built yet (QET_LAZY_FOLIOS)
now saves that link too, in the order it was saved with
(Element::toXml(); QETProject::waitingLinkTargets() keeps it to partners
the project has, so a link to a symbol gone is dropped as it always was).
With that, and with no partner's text in a .qetz's folio files (D3c),
saving a .qetz no longer builds the folios of the partners built folios
wait for; table chains still are (a table saves the table before it), and
a .qet saved as read (QET_LAZY_SAVE_KEPT) still builds them, its folio
files holding partners' texts.

industrial.qetz: an untouched save builds no folio (47 of 47 stay not
built; 19 before), after everyday edits on its busiest folio 29 stay not
built (7 before).

tst_lazyfolios compares the links an edited .qetz saved without building
holds, symbol by symbol in their order, with those the same edits saved
with every folio built -- it fails when waiting links are not saved (the
drawing alone cannot tell: links are kept on both sides, and a symbol
reread gets its partner's back, in another order). Corpus, 342 projects:
a .qetz saved without building, read and saved as .qet = eager = master.
ctest 96/96 with and without QET_LAZY_FOLIOS=1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-10-11 22:15:20 +13:00
parent 8ad33f6281
commit 2f94724df3
4 changed files with 84 additions and 11 deletions
+30 -6
View File
@@ -1250,17 +1250,41 @@ QDomElement Element::toXml(
//if this element is linked to other elements,
//save the uuid of each other elements
if (! isFree()) {
//A partner on a folio not built yet (QET_LAZY_FOLIOS), which this
//symbol will be linked to, is saved too, in the order the symbol was
//saved with: saving needs no partner built (stage D5a)
QList<std::pair<QUuid, int>> links;
if (keepsSavedLinks() && diagram() && diagram()->project()) {
const QSet<QUuid> waiting = diagram()->project()->waitingLinkTargets();
QSet<Element *> written;
for (const auto &link_info : std::as_const(tmp_uuids_link)) {
Element *linked = nullptr;
for (Element *elmt : std::as_const(connected_elements))
if (elmt->uuid() == link_info.uuid && !written.contains(elmt)) { linked = elmt; break; }
if (linked) {
written.insert(linked);
links.append({linked->uuid(), m_group_index_map.value(linked, -1)});
} else if (waiting.contains(link_info.uuid)) {
links.append({link_info.uuid, link_info.group_index});
}
}
for (Element *elmt : std::as_const(connected_elements))
if (!written.contains(elmt))
links.append({elmt->uuid(), m_group_index_map.value(elmt, -1)});
} else {
for (Element *elmt : std::as_const(connected_elements))
links.append({elmt->uuid(), m_group_index_map.value(elmt, -1)});
}
if (!links.isEmpty()) {
QDomElement links_uuids = document.createElement(QStringLiteral("links_uuids"));
foreach (Element *elmt, connected_elements) {
for (const auto &link : std::as_const(links)) {
QDomElement link_uuid =
document.createElement(QStringLiteral("link_uuid"));
link_uuid.setAttribute(QStringLiteral("uuid"), elmt->uuid().toString());
link_uuid.setAttribute(QStringLiteral("uuid"), link.first.toString());
// Save group index if assigned (for slave->master links)
int gi = m_group_index_map.value(elmt, -1);
if (gi >= 0) {
link_uuid.setAttribute(QStringLiteral("group_index"), gi);
if (link.second >= 0) {
link_uuid.setAttribute(QStringLiteral("group_index"), link.second);
}
links_uuids.appendChild(link_uuid);
+11 -5
View File
@@ -2327,17 +2327,21 @@ QDomDocument QETProject::toXml(QHash<int, QByteArray> *kept_folios, QStringList
QetGraphicsTableItem::xmlTagName()).isEmpty())
buildFolioToShow(diagram);
//A built folio is written from its items, which need their
//partners: a symbol saves its links, a report the position of
//its partner. Build the folios of every partner still waiting,
//and of every table chain, until none is
//partners: a table saves the table before it, and in a .qet a
//report the position of its partner. Build the folios of every
//table chain, and for a .qet of every partner still waiting,
//until none is. A symbol saves the partners it waits for itself
//(Element::toXml()), and a .qetz keeps no partner's text (D3c):
//saving a .qetz builds no partner (stage D5a)
for (int built = -1 ; built != unloadedFolioCount() ; )
{
built = unloadedFolioCount();
QSet<QUuid> waiting;
QSet<Diagram *> chained;
for (Diagram *diagram : builtFolios()) {
for (Element *element : diagram->elements())
waiting.unite(element->pendingLinks());
if (kept_folios)
for (Element *element : diagram->elements())
waiting.unite(element->pendingLinks());
for (Diagram *other : tableChainFolios(diagram))
if (!other->isLoaded()) chained.insert(other);
}
@@ -2351,6 +2355,7 @@ QDomDocument QETProject::toXml(QHash<int, QByteArray> *kept_folios, QStringList
} else {
loadFolios();
}
m_waiting_link_targets = unloadedFolioCount() ? symbolsOnUnbuiltFolios() : QSet<QUuid>();
//QET_CHECK_ELEMENT_INFO_STORE=1: say whether the stores of symbol
//information, folio title blocks and wire properties agree with every placed symbol, folio and
//wire, at each save. A test and debugging aid; it changes nothing.
@@ -2478,6 +2483,7 @@ QDomDocument QETProject::toXml(QHash<int, QByteArray> *kept_folios, QStringList
// Write the elements collection.
project_root.appendChild(m_elements_collection->root().cloneNode(true));
m_waiting_link_targets.clear();
return(xml_doc);
}
+4
View File
@@ -133,6 +133,9 @@ class QETProject : public QObject
void buildFoliosHolding(ElementData::Types kinds, bool free_only);
void buildFoliosHoldingSymbols(const QSet<QUuid> &symbols);
QSet<QUuid> symbolsOnUnbuiltFolios() const;
/// While saving: the symbols on folios not built a built symbol
/// may be saved linked to (Element::toXml())
QSet<QUuid> waitingLinkTargets() const {return m_waiting_link_targets;}
QSet<Diagram *> tableChainFolios(Diagram *folio) const;
void loadFolios() const;
void buildFolios(const QList<Diagram *> &folios) const;
@@ -540,6 +543,7 @@ class QETProject : public QObject
/// Saving a .qetz may copy the part of a folio not built
/// (toXml(), stage D4)
bool m_kept_parts_trusted = false;
QSet<QUuid> m_waiting_link_targets;
QHash<QString, QByteArray> m_container_files;
QStringList m_folio_files;
QList<std::pair<QUuid, QString>> m_folio_headers;
+39
View File
@@ -23,6 +23,9 @@
#include "../../sources/qetgraphicsitem/element.h"
#include "../../sources/autoNum/elementautonumschemecommand.h"
#include "../../sources/elementprovider.h"
#include "../../sources/container/qetzip.h"
#include <QSqlDatabase>
#include <QMap>
#include "../../sources/TerminalStrip/terminalstrip.h"
#include "../../sources/ui/elementpropertieswidget.h"
#include "../../sources/conductorautonumerotation.h"
@@ -193,6 +196,38 @@ static QStringList sortedLines(QString xml, const QString &path)
return list;
}
//The saved links of a .qetz, symbol by symbol in their order: what a
//symbol saves of its partners, as its file holds it
static QStringList savedLinks(const QString &qetz, const QString &path)
{
QFile file(qetz); file.open(QIODevice::ReadOnly);
QList<QetZip::Entry> entries;
QString error;
check(QetZip::fromBytes(file.readAll(), &entries, &error), "a .qetz read");
QTemporaryDir dir;
const QString db_path = dir.filePath(QStringLiteral("project.sqlite"));
for (const QetZip::Entry &entry : entries)
if (entry.name == QLatin1String("project.sqlite")) {
QFile db(db_path); db.open(QIODevice::WriteOnly); db.write(entry.data);
}
QMap<QString, QStringList> links; //symbol -> its partners in order
{
QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), QStringLiteral("saved_links"));
db.setDatabaseName(db_path);
check(db.open(), "a .qetz's database opened");
QSqlQuery query(db);
check(query.exec(QStringLiteral("SELECT element_key, linked_uuid FROM doc_link ORDER BY element_key, ord")),
"saved links read");
while (query.next())
links[query.value(0).toString()] << query.value(1).toString();
}
QSqlDatabase::removeDatabase(QStringLiteral("saved_links"));
QStringList rows;
for (auto it = links.cbegin() ; it != links.cend() ; ++it)
rows << it.key() + QStringLiteral(": ") + it.value().join(QLatin1Char(','));
return sortedLines(rows.join(QLatin1Char('\n')), path);
}
//The rows of the project database's tables, in their order, read as
//every caller reads them (newQuery(): brought up to date first)
static QStringList tables(QETProject &project)
@@ -680,6 +715,10 @@ int main(int argc, char **argv)
qputenv("QET_LAZY_FOLIOS", "1");
check(sortedLines(edited_back.toXml().toString(), path) == sortedLines(usual_back.toXml().toString(), path),
"an edited .qetz saved without building holds what the edits made");
//Each symbol's links as saved, partners on folios not built
//included, in their order (D5a)
check(savedLinks(edited_path, path) == savedLinks(usual_path, path),
"an edited .qetz saved without building keeps every symbol's links");
}
std::printf("PASS: %d folios\n", int(folios.size()));
} catch (const std::exception &error) {