Save a new title, author or revision without building the folio (stage D5)

A folio not built whose own data changed was built to save, with the
folios its links reach. When what changed is only title block fields
nothing on a folio is computed from -- its title, author, date,
revision index, version, file name, template; a formula reads the
folio's label, plant, location, extra fields and grid -- its items as
read are what building it would write: it is saved from them with its
own data as it is now (Diagram::deferredXmlWithHeader()), .qet and
.qetz alike, with the information of its symbols changed through the
project database (or built, when one of those needs it). Any other
change still builds it.

136-folio .qetz, revision index and author set on every folio by a
script: 0.8 s, saved in 6.8 s, no folio built; with every folio built,
the same file. tst_lazyfolios: a new revision on every folio of a .qet
and of a .qetz saved as when built, building only the folios holding a
table (fails, 47 built, when every change counts); a folio whose label
changed is built (fails when none counts). ctest 96/96 eager and lazy;
342 corpus projects save the same in all four arms and export the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-10-12 17:54:37 +13:00
parent 244de05421
commit b151f52428
4 changed files with 154 additions and 2 deletions
+51
View File
@@ -830,6 +830,57 @@ QList<Element *> Diagram::buildSymbols(const QSet<QUuid> &uuids)
return built;
}
namespace {
//The title block fields nothing on a folio is computed from: a formula
//reads the folio's label, plant, location, extra fields and grid
//(autonum::FormulaContext), none of these
const QStringList ShownOnlyFields = {
QStringLiteral("title"), QStringLiteral("author"), QStringLiteral("date"),
QStringLiteral("indexrev"), QStringLiteral("version"), QStringLiteral("filename"),
QStringLiteral("titleblocktemplate"), QStringLiteral("titleblocktemplateCollection")};
QString headerWithoutShownOnly(const QString &header)
{
QDomDocument document;
document.setContent(header);
QDomElement root = document.documentElement();
for (const QString &field : ShownOnlyFields) root.removeAttribute(field);
return document.toString();
}
}
/**
@brief Diagram::headerChangeShowsOnly
@return true if what changed in this folio's own data since it was read
(headerChanged()) is only title block fields nothing on the folio is
computed from (its title, author, date, revision, file name, template):
its items, as read, are what building it would write
*/
bool Diagram::headerChangeShowsOnly()
{
return headerWithoutShownOnly(headerXml()) == headerWithoutShownOnly(m_opened_header);
}
/**
@brief Diagram::deferredXmlWithHeader
@return this folio, not built, as it saves: its own data as it is now
(writeHeader()) and its items as read (deferredXml()), in @p document --
when its items are what building it would write (headerChangeShowsOnly())
*/
QDomElement Diagram::deferredXmlWithHeader(QDomDocument &document)
{
QDomElement folio = document.createElement(QStringLiteral("diagram"));
writeHeader(document, folio);
QSet<QString> header_children;
for (QDomElement child = folio.firstChildElement() ; !child.isNull() ; child = child.nextSiblingElement())
header_children.insert(child.tagName());
const QDomElement items = deferredXml();
for (QDomElement child = items.firstChildElement() ; !child.isNull() ; child = child.nextSiblingElement())
if (!header_children.contains(child.tagName()))
folio.appendChild(document.importNode(child, true));
return folio;
}
/**
@brief Diagram::deferredXml
@return the XML of this folio's items while they are not built: kept
+2
View File
@@ -240,6 +240,8 @@ class Diagram : public QGraphicsScene
/// headerChanged() (QETProject::toXml())
void rememberHeader() {m_opened_header = headerXml();}
bool headerChanged() {return headerXml() != m_opened_header;}
bool headerChangeShowsOnly();
QDomElement deferredXmlWithHeader(QDomDocument &document);
void ensureLoaded();
bool buildDeferred();
//The last argument is only for a copy fragment: when it is
+28 -2
View File
@@ -2374,9 +2374,17 @@ QDomDocument QETProject::toXml(QHash<int, QByteArray> *kept_folios, QStringList
changed << diagram;
}
buildFolios(changed);
for (Diagram *diagram : std::as_const(m_diagrams_list))
if (diagram->headerChanged())
//-- unless it changed only in what nothing is computed from (its
//title, date, revision...): written from its items as read
//(Diagram::deferredXmlWithHeader())
for (Diagram *diagram : std::as_const(m_diagrams_list)) {
if (!diagram->headerChanged()) continue;
bool edits_need_build = false;
if (!diagram->isLoaded())
m_data_base.unbuiltEditsOnFolio(diagram->uuid(), &edits_need_build);
if (diagram->isLoaded() || !diagram->headerChangeShowsOnly() || edits_need_build)
buildFolioToShow(diagram);
}
//A table filled from the database saves the size its rows
//give it, which any edit can change
if (m_edited_since_open)
@@ -2490,6 +2498,24 @@ QDomDocument QETProject::toXml(QHash<int, QByteArray> *kept_folios, QStringList
<< "["
<< diagram
<< "]";
if (keep && !diagram->isLoaded() && diagram->headerChanged()) {
QDomElement xml_diagram = diagram->deferredXmlWithHeader(xml_doc);
//Its symbols whose information changed while it was not built
const QHash<QUuid, DiagramContext> edits = m_data_base.unbuiltEditsOnFolio(diagram->uuid());
for (const QDomElement &list : QET::findInDomElement(xml_diagram, QStringLiteral("elements")))
for (QDomElement element = list.firstChildElement(QStringLiteral("element")) ; !element.isNull() ;
element = element.nextSiblingElement(QStringLiteral("element"))) {
const auto edit = edits.constFind(QUuid(element.attribute(QStringLiteral("uuid"))));
if (edit == edits.constEnd()) continue;
QDomElement information = xml_doc.createElement(QStringLiteral("elementInformations"));
edit->toXml(information, QStringLiteral("elementInformation"));
const QDomElement old_information = element.firstChildElement(QStringLiteral("elementInformations"));
element.replaceChild(information, old_information); //it has one (unbuiltEditsOnFolio())
}
xml_diagram.setAttribute("order", order_num ++);
project_root.appendChild(xml_diagram);
continue;
}
if (keep && !diagram->isLoaded() && kept_parts) {
auto placeholder = xml_doc.createElement(QStringLiteral("diagram"));
placeholder.setAttribute(QStringLiteral("qetz-kept-part"), diagram->deferredPart());
+73
View File
@@ -49,6 +49,73 @@
#include <QDomDocument>
static void check(bool value, const char *message) { if (!value) throw std::runtime_error(message); }
static QStringList sortedLines(QString xml, const QString &path);
//A new revision on every folio (its title block's revision index and
//author), saved lazily and with every folio built: what saving builds,
//and whether the two saves hold the same. A change to folio
//@p label_folio's label (what formulas read) when it is not -1
static void revisionEveryFolio(const QString &source, const QString &output, const QString &name,
const QString &path, int label_folio = -1)
{
QString error;
qputenv("QET_LAZY_FOLIOS", "1");
QETProject revised(source);
const int unbuilt = revised.unloadedFolioCount();
QetScriptApi api(&revised);
for (int i = 0 ; i < api.folioCount() ; ++i) {
check(api.setFolioProperty(i, "indexrev", "B"), "a folio's revision set");
check(api.setFolioProperty(i, "author", "Revised"), "a folio's author set");
}
if (label_folio >= 0)
check(api.setFolioProperty(label_folio, "folio", "X%id"), "a folio's label set");
check(revised.unloadedFolioCount() == unbuilt, "setting the folios' title blocks builds none");
int with_tables = 0;
for (Diagram *folio : revised.folios())
if (!folio->isLoaded() && revised.unbuiltFolioHasTables(folio)) ++with_tables;
const bool qetz = QETProject::isContainerPath(source);
const QString lazy_path = output + "/" + name + (qetz ? ".qetz" : ".qet");
if (qetz) check(revised.writeContainerFile(lazy_path, &error), "revised project saved");
else { revised.setFilePath(lazy_path); check(revised.write().isOk(), "revised project saved"); }
const int built = unbuilt - revised.unloadedFolioCount();
std::printf("%s: new revision on every folio, %d folios built by saving, %d hold a table%s\n",
qPrintable(name), built, with_tables,
label_folio >= 0 ? ", one label changed" : "");
if (label_folio < 0)
check(built <= with_tables, "saving a new revision builds only the folios holding a table");
else
check(revised.folios().at(label_folio)->isLoaded(), "a folio whose label changed is built to save");
qunsetenv("QET_LAZY_FOLIOS");
const QString usual_path = output + "/" + name + "-usual" + (qetz ? ".qetz" : ".qet");
{
QETProject usual(source);
QetScriptApi usual_api(&usual);
for (int i = 0 ; i < usual_api.folioCount() ; ++i) {
usual_api.setFolioProperty(i, "indexrev", "B");
usual_api.setFolioProperty(i, "author", "Revised");
}
if (label_folio >= 0) usual_api.setFolioProperty(label_folio, "folio", "X%id");
if (qetz) check(usual.writeContainerFile(usual_path, &error), "revision saved when built");
else { usual.setFilePath(usual_path); check(usual.write().isOk(), "revision saved when built"); }
}
QETProject lazy_back(lazy_path), usual_back(usual_path);
qputenv("QET_LAZY_FOLIOS", "1");
//What a save says of itself (file name, path, time) left out
auto lines = [&path](QETProject &project) {
QStringList list = sortedLines(project.toXml().toString(), path);
list.removeIf([](const QString &line) { return line.contains(QLatin1String("name=\"saved")); });
return list;
};
const QStringList lazy_lines = lines(lazy_back), usual_lines = lines(usual_back);
if (lazy_lines != usual_lines) {
int shown = 0;
for (const QString &line : usual_lines)
if (!lazy_lines.contains(line) && shown++ < 8) std::printf(" when built: %s\n", qPrintable(line.left(300)));
for (const QString &line : lazy_lines)
if (!usual_lines.contains(line) && shown++ < 16) std::printf(" lazily: %s\n", qPrintable(line.left(300)));
}
check(lazy_lines == usual_lines, "a new revision saved as when built");
}
//Every symbol's numbering, in a stable order
static QStringList numbering(const QETProject &project)
@@ -380,6 +447,7 @@ int main(int argc, char **argv)
for (const QString &title : titles)
check(gaps(numbered, title) == gaps(eager, title), "a scheme's gaps as when built");
check(numbered.unloadedFolioCount() == folios.size() - strip_folios.size(), "numbering read without building");
revisionEveryFolio(path, output, "revised-qet", path);
{
//Wire numbers exported from the database filled from the
//document, as when built (D5)
@@ -880,6 +948,11 @@ int main(int argc, char **argv)
"a symbol changed before its folio was built shows the change");
}
//A new revision on every folio: saved from the folios as read,
//not built; a label change builds its folio (D5)
revisionEveryFolio(zipped, output, "revised", path);
revisionEveryFolio(zipped, output, "relabelled", path, busiest);
//The wire numbers and wiring list exports, and the terminal
//numbering dialog: as when built, building only the folios
//holding a terminal (D5)