From 2f94724df361e958917b5a3f0f2672793577a1a8 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sun, 11 Oct 2026 22:15:20 +1300 Subject: [PATCH] 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 --- sources/qetgraphicsitem/element.cpp | 36 +++++++++++++++++++++----- sources/qetproject.cpp | 16 ++++++++---- sources/qetproject.h | 4 +++ tests/qttest/lazy_folios_probe.cpp | 39 +++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 11 deletions(-) diff --git a/sources/qetgraphicsitem/element.cpp b/sources/qetgraphicsitem/element.cpp index 6a59bbcbc..43511ae37 100644 --- a/sources/qetgraphicsitem/element.cpp +++ b/sources/qetgraphicsitem/element.cpp @@ -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> links; + if (keepsSavedLinks() && diagram() && diagram()->project()) { + const QSet waiting = diagram()->project()->waitingLinkTargets(); + QSet 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); diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index 0e1a2b726..bfc52f86a 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -2327,17 +2327,21 @@ QDomDocument QETProject::toXml(QHash *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 waiting; QSet 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 *kept_folios, QStringList } else { loadFolios(); } + m_waiting_link_targets = unloadedFolioCount() ? symbolsOnUnbuiltFolios() : QSet(); //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 *kept_folios, QStringList // Write the elements collection. project_root.appendChild(m_elements_collection->root().cloneNode(true)); + m_waiting_link_targets.clear(); return(xml_doc); } diff --git a/sources/qetproject.h b/sources/qetproject.h index 18e061505..3a51c0d1f 100644 --- a/sources/qetproject.h +++ b/sources/qetproject.h @@ -133,6 +133,9 @@ class QETProject : public QObject void buildFoliosHolding(ElementData::Types kinds, bool free_only); void buildFoliosHoldingSymbols(const QSet &symbols); QSet symbolsOnUnbuiltFolios() const; + /// While saving: the symbols on folios not built a built symbol + /// may be saved linked to (Element::toXml()) + QSet waitingLinkTargets() const {return m_waiting_link_targets;} QSet tableChainFolios(Diagram *folio) const; void loadFolios() const; void buildFolios(const QList &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 m_waiting_link_targets; QHash m_container_files; QStringList m_folio_files; QList> m_folio_headers; diff --git a/tests/qttest/lazy_folios_probe.cpp b/tests/qttest/lazy_folios_probe.cpp index 79a5815b9..1704a0a9d 100644 --- a/tests/qttest/lazy_folios_probe.cpp +++ b/tests/qttest/lazy_folios_probe.cpp @@ -23,6 +23,9 @@ #include "../../sources/qetgraphicsitem/element.h" #include "../../sources/autoNum/elementautonumschemecommand.h" #include "../../sources/elementprovider.h" +#include "../../sources/container/qetzip.h" +#include +#include #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 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 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) {