mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-13 16:34:14 +02:00
Keep terminal strips in a .qetz project's database too
The project's terminal strips -- strip information, the terminals of each physical terminal by level, bridges and what they bridge -- move to four tables of project.sqlite, when rebuilding them from the rows gives the same XML exactly (as QElectroTech writes them). Anything else, or strips that carry text a save would not write, stays in project.xml. Test: tst_qetcontainer::terminalStripsInDatabase (two strips with a bridge and a two-level terminal on industrial.qet's own terminals: out of project.xml, back exactly; stays in the XML when the rebuild is broken). Outside ctest: a strip fixture saved as .qetz then .qet by QElectroTech equals its straight resave, and the strips are rows any SQLite tool reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
This commit is contained in:
@@ -90,7 +90,16 @@ QString schema()
|
||||
" name TEXT, value TEXT, attrs TEXT, PRIMARY KEY (element_key, ord));"
|
||||
"CREATE TABLE link (element_key TEXT NOT NULL REFERENCES element, ord INTEGER NOT NULL,"
|
||||
" linked_uuid TEXT, attrs TEXT, PRIMARY KEY (element_key, ord));"
|
||||
"CREATE TABLE conductor (key TEXT PRIMARY KEY, folio_id TEXT NOT NULL REFERENCES folio, %3);")
|
||||
"CREATE TABLE conductor (key TEXT PRIMARY KEY, folio_id TEXT NOT NULL REFERENCES folio, %3);"
|
||||
"CREATE TABLE terminal_strip (uuid TEXT PRIMARY KEY, ord INTEGER NOT NULL UNIQUE,"
|
||||
" installation TEXT, location TEXT, name TEXT, comment TEXT, description TEXT);"
|
||||
"CREATE TABLE strip_terminal (strip_uuid TEXT NOT NULL REFERENCES terminal_strip,"
|
||||
" pos INTEGER NOT NULL, level INTEGER NOT NULL, element_uuid TEXT,"
|
||||
" PRIMARY KEY (strip_uuid, pos, level));"
|
||||
"CREATE TABLE strip_bridge (uuid TEXT PRIMARY KEY, strip_uuid TEXT NOT NULL REFERENCES terminal_strip,"
|
||||
" ord INTEGER NOT NULL, color TEXT);"
|
||||
"CREATE TABLE strip_bridge_terminal (bridge_uuid TEXT NOT NULL REFERENCES strip_bridge,"
|
||||
" ord INTEGER NOT NULL, element_uuid TEXT, PRIMARY KEY (bridge_uuid, ord));")
|
||||
.arg(columns(FolioData), columns(ElementData), columns(ConductorData));
|
||||
}
|
||||
|
||||
@@ -273,6 +282,133 @@ void put(QDomElement e, const QStringList &names, const QVariantList &values, in
|
||||
if (!values.at(from + i).isNull()) e.setAttribute(names.at(i), values.at(from + i).toString());
|
||||
}
|
||||
|
||||
//Same tags, attributes, text and child order
|
||||
bool sameTree(const QDomNode &a, const QDomNode &b)
|
||||
{
|
||||
if (a.nodeType() != b.nodeType() || a.nodeName() != b.nodeName()) return false;
|
||||
if (!a.isElement() && a.nodeValue() != b.nodeValue()) return false;
|
||||
if (a.isElement()) {
|
||||
const QDomNamedNodeMap am = a.attributes(), bm = b.attributes();
|
||||
if (am.count() != bm.count()) return false;
|
||||
for (int i = 0 ; i < am.count() ; ++i) {
|
||||
const QDomAttr x = am.item(i).toAttr();
|
||||
if (!bm.contains(x.name()) || bm.namedItem(x.name()).nodeValue() != x.value()) return false;
|
||||
}
|
||||
}
|
||||
QDomNode ca = a.firstChild(), cb = b.firstChild();
|
||||
for ( ; !ca.isNull() && !cb.isNull() ; ca = ca.nextSibling(), cb = cb.nextSibling())
|
||||
if (!sameTree(ca, cb)) return false;
|
||||
return ca.isNull() && cb.isNull();
|
||||
}
|
||||
|
||||
bool hasWhitespaceText(const QDomNode &node)
|
||||
{
|
||||
for (QDomNode n = node.firstChild() ; !n.isNull() ; n = n.nextSibling()) {
|
||||
if (n.isText() && n.nodeValue().trimmed().isEmpty()) return true;
|
||||
if (hasWhitespaceText(n)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const QStringList StripData = {
|
||||
QStringLiteral("installation"), QStringLiteral("location"), QStringLiteral("name"),
|
||||
QStringLiteral("comment"), QStringLiteral("description")};
|
||||
|
||||
struct StripRows
|
||||
{
|
||||
QList<QVariantList> strips, terminals, bridges, bridge_terminals;
|
||||
};
|
||||
|
||||
//<terminal_strips> as TerminalStrip::toXml() writes it, from @p rows
|
||||
QDomElement stripsXml(QDomDocument &document, const StripRows &rows)
|
||||
{
|
||||
QDomElement root = document.createElement(QStringLiteral("terminal_strips"));
|
||||
for (const QVariantList &st : rows.strips) {
|
||||
const QString uuid = st.at(0).toString();
|
||||
QDomElement strip = root.appendChild(document.createElement(QStringLiteral("terminal_strip"))).toElement();
|
||||
QDomElement data = strip.appendChild(document.createElement(QStringLiteral("terminal_strip_data"))).toElement();
|
||||
data.setAttribute(QStringLiteral("uuid"), uuid);
|
||||
QDomElement infos = data.appendChild(document.createElement(QStringLiteral("informations"))).toElement();
|
||||
for (int i = 0 ; i < StripData.size() ; ++i) {
|
||||
const QVariant v = st.at(2 + i);
|
||||
if (v.isNull()) continue;
|
||||
QDomElement info = infos.appendChild(document.createElement(QStringLiteral("information"))).toElement();
|
||||
info.setAttribute(QStringLiteral("name"), StripData.at(i));
|
||||
info.appendChild(document.createTextNode(v.toString()));
|
||||
}
|
||||
QDomElement layout = strip.appendChild(document.createElement(QStringLiteral("layout"))).toElement();
|
||||
QDomElement physical;
|
||||
int last_pos = -1;
|
||||
for (const QVariantList &t : rows.terminals) {
|
||||
if (t.at(0).toString() != uuid) continue;
|
||||
if (t.at(1).toInt() != last_pos) {
|
||||
physical = layout.appendChild(document.createElement(QStringLiteral("physical_terminal"))).toElement();
|
||||
last_pos = t.at(1).toInt();
|
||||
}
|
||||
QDomElement real = physical.appendChild(document.createElement(QStringLiteral("real_terminal"))).toElement();
|
||||
if (!t.at(3).isNull()) real.setAttribute(QStringLiteral("element_uuid"), t.at(3).toString());
|
||||
}
|
||||
for (const QVariantList &b : rows.bridges) {
|
||||
if (b.at(1).toString() != uuid) continue;
|
||||
QDomElement bridge = strip.appendChild(document.createElement(QStringLiteral("terminal_strip_bridge"))).toElement();
|
||||
bridge.setAttribute(QStringLiteral("uuid"), b.at(0).toString());
|
||||
if (!b.at(3).isNull()) bridge.setAttribute(QStringLiteral("color"), b.at(3).toString());
|
||||
QDomElement reals = bridge.appendChild(document.createElement(QStringLiteral("real_terminals"))).toElement();
|
||||
for (const QVariantList &bt : rows.bridge_terminals) {
|
||||
if (bt.at(0).toString() != b.at(0).toString()) continue;
|
||||
QDomElement real = reals.appendChild(document.createElement(QStringLiteral("real_terminal"))).toElement();
|
||||
if (!bt.at(2).isNull()) real.setAttribute(QStringLiteral("uuid"), bt.at(2).toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
//The rows of @p block, if rebuilding them gives @p block back exactly
|
||||
bool stripRows(const QDomElement &block, StripRows *rows)
|
||||
{
|
||||
if (block.attributes().count() || hasWhitespaceText(block)) return false;
|
||||
QSet<QString> strip_uuids, bridge_uuids;
|
||||
int ord = 0;
|
||||
for (QDomElement strip = block.firstChildElement() ; !strip.isNull() ; strip = strip.nextSiblingElement()) {
|
||||
const QDomElement data = strip.firstChildElement(QStringLiteral("terminal_strip_data"));
|
||||
const QString uuid = data.attribute(QStringLiteral("uuid"));
|
||||
if (uuid.isEmpty() || strip_uuids.contains(uuid)) return false;
|
||||
strip_uuids.insert(uuid);
|
||||
QHash<QString, QString> info;
|
||||
for (QDomElement i = data.firstChildElement(QStringLiteral("informations")).firstChildElement() ;
|
||||
!i.isNull() ; i = i.nextSiblingElement())
|
||||
info.insert(i.attribute(QStringLiteral("name")), i.text());
|
||||
QVariantList row{uuid, ord++};
|
||||
for (const QString &n : StripData) row << (info.contains(n) ? QVariant(info.value(n)) : QVariant());
|
||||
rows->strips << row;
|
||||
int pos = 0;
|
||||
for (QDomElement phy = strip.firstChildElement(QStringLiteral("layout")).firstChildElement() ;
|
||||
!phy.isNull() ; phy = phy.nextSiblingElement(), ++pos) {
|
||||
int level = 0;
|
||||
for (QDomElement real = phy.firstChildElement() ; !real.isNull() ; real = real.nextSiblingElement())
|
||||
rows->terminals << QVariantList{uuid, pos, level++, real.hasAttribute(QStringLiteral("element_uuid"))
|
||||
? QVariant(real.attribute(QStringLiteral("element_uuid"))) : QVariant()};
|
||||
}
|
||||
int b_ord = 0;
|
||||
for (QDomElement br = strip.firstChildElement(QStringLiteral("terminal_strip_bridge")) ;
|
||||
!br.isNull() ; br = br.nextSiblingElement(QStringLiteral("terminal_strip_bridge"))) {
|
||||
const QString buuid = br.attribute(QStringLiteral("uuid"));
|
||||
if (buuid.isEmpty() || bridge_uuids.contains(buuid)) return false;
|
||||
bridge_uuids.insert(buuid);
|
||||
rows->bridges << QVariantList{buuid, uuid, b_ord++, br.hasAttribute(QStringLiteral("color"))
|
||||
? QVariant(br.attribute(QStringLiteral("color"))) : QVariant()};
|
||||
int k = 0;
|
||||
for (QDomElement real = br.firstChildElement(QStringLiteral("real_terminals")).firstChildElement() ;
|
||||
!real.isNull() ; real = real.nextSiblingElement())
|
||||
rows->bridge_terminals << QVariantList{buuid, k++, real.hasAttribute(QStringLiteral("uuid"))
|
||||
? QVariant(real.attribute(QStringLiteral("uuid"))) : QVariant()};
|
||||
}
|
||||
}
|
||||
QDomDocument scratch;
|
||||
return sameTree(block, stripsXml(scratch, *rows));
|
||||
}
|
||||
|
||||
QList<QDomElement> children(const QDomElement &parent, const QString &tag)
|
||||
{
|
||||
QList<QDomElement> list;
|
||||
@@ -432,6 +568,22 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error)
|
||||
}
|
||||
}
|
||||
|
||||
//Terminal strips, when their rows rebuild them exactly
|
||||
const QDomElement strips = project.firstChildElement(QStringLiteral("terminal_strips"));
|
||||
StripRows strip_rows;
|
||||
if (!strips.isNull() && stripRows(strips, &strip_rows)) {
|
||||
Statement st(db.m_db, QStringLiteral("INSERT INTO terminal_strip VALUES (?,?,?,?,?,?,?)"));
|
||||
Statement tt(db.m_db, QStringLiteral("INSERT INTO strip_terminal VALUES (?,?,?,?)"));
|
||||
Statement bt(db.m_db, QStringLiteral("INSERT INTO strip_bridge VALUES (?,?,?,?)"));
|
||||
Statement btt(db.m_db, QStringLiteral("INSERT INTO strip_bridge_terminal VALUES (?,?,?)"));
|
||||
for (const QVariantList &r : strip_rows.strips) ok = ok && st.run(r);
|
||||
for (const QVariantList &r : strip_rows.terminals) ok = ok && tt.run(r);
|
||||
for (const QVariantList &r : strip_rows.bridges) ok = ok && bt.run(r);
|
||||
for (const QVariantList &r : strip_rows.bridge_terminals) ok = ok && btt.run(r);
|
||||
ok = ok && meta.run({QStringLiteral("terminal_strips"), QStringLiteral("1")});
|
||||
leaveMarker(strips);
|
||||
}
|
||||
|
||||
if (!ok || !db.exec(QStringLiteral("COMMIT"))) {
|
||||
setError(error, QStringLiteral("cannot fill the project database: %1").arg(db.error()));
|
||||
return {};
|
||||
@@ -474,7 +626,8 @@ bool QetContainerDb::restore(QDomElement project, const QByteArray &database, QS
|
||||
db.exec(QStringLiteral("PRAGMA trusted_schema = OFF"));
|
||||
db.exec(QStringLiteral("PRAGMA query_only = ON"));
|
||||
for (const char *table : {"meta", "project_property", "folio", "folio_property",
|
||||
"element", "element_info", "link", "conductor"}) {
|
||||
"element", "element_info", "link", "conductor", "terminal_strip",
|
||||
"strip_terminal", "strip_bridge", "strip_bridge_terminal"}) {
|
||||
Statement kind(db.m_db, QStringLiteral("SELECT type FROM sqlite_master WHERE name = ?"));
|
||||
kind.bind({QString::fromLatin1(table)});
|
||||
if (!kind.next() || kind.value(0).toString() != QLatin1String("table")) {
|
||||
@@ -513,6 +666,26 @@ bool QetContainerDb::restore(QDomElement project, const QByteArray &database, QS
|
||||
}
|
||||
}
|
||||
|
||||
if (meta.contains(QStringLiteral("terminal_strips"))) {
|
||||
StripRows rows;
|
||||
auto read = [&db](const QString &sql, int columns, QList<QVariantList> *out) {
|
||||
Statement s(db.m_db, sql);
|
||||
while (s.next()) {
|
||||
QVariantList r;
|
||||
for (int i = 0 ; i < columns ; ++i) r << s.value(i);
|
||||
*out << r;
|
||||
}
|
||||
};
|
||||
read(QStringLiteral("SELECT * FROM terminal_strip ORDER BY ord"), 7, &rows.strips);
|
||||
read(QStringLiteral("SELECT * FROM strip_terminal ORDER BY strip_uuid, pos, level"), 4, &rows.terminals);
|
||||
read(QStringLiteral("SELECT * FROM strip_bridge ORDER BY ord"), 4, &rows.bridges);
|
||||
read(QStringLiteral("SELECT * FROM strip_bridge_terminal ORDER BY bridge_uuid, ord"), 3, &rows.bridge_terminals);
|
||||
if (!replaceMarker(project, stripsXml(document, rows))) {
|
||||
setError(error, QStringLiteral("the terminal strips have no place"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (QDomElement d : children(project, QStringLiteral("diagram")))
|
||||
{
|
||||
const QString id = d.attribute(FolioAttribute);
|
||||
|
||||
@@ -181,6 +181,46 @@ private slots:
|
||||
sqlite3_close(db);
|
||||
}
|
||||
|
||||
// Terminal strips move into the database, and come back exactly:
|
||||
// industrial.qet with two strips on its own terminal symbols, parsed as
|
||||
// a save leaves it (no whitespace text).
|
||||
void terminalStripsInDatabase()
|
||||
{
|
||||
QByteArray xml = read(QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
|
||||
const QByteArray strips =
|
||||
"<terminal_strips>"
|
||||
"<terminal_strip><terminal_strip_data uuid=\"{11111111-0000-4000-8000-000000000001}\"><informations>"
|
||||
"<information name=\"installation\">=A1</information><information name=\"location\">+CAB1</information>"
|
||||
"<information name=\"name\">X1</information><information name=\"comment\">main & aux</information>"
|
||||
"</informations></terminal_strip_data>"
|
||||
"<layout><physical_terminal><real_terminal element_uuid=\"{c2180165-8d35-44eb-83c4-100395451915}\"/></physical_terminal>"
|
||||
"<physical_terminal><real_terminal element_uuid=\"{05f681ee-04dd-489d-afc8-a646ec6016f9}\"/>"
|
||||
"<real_terminal element_uuid=\"{a33935cf-671a-480f-9497-1a632eab7cbd}\"/></physical_terminal></layout>"
|
||||
"<terminal_strip_bridge uuid=\"{22222222-0000-4000-8000-000000000001}\" color=\"#ff0000\"><real_terminals>"
|
||||
"<real_terminal uuid=\"{c2180165-8d35-44eb-83c4-100395451915}\"/>"
|
||||
"<real_terminal uuid=\"{05f681ee-04dd-489d-afc8-a646ec6016f9}\"/></real_terminals></terminal_strip_bridge>"
|
||||
"</terminal_strip>"
|
||||
"<terminal_strip><terminal_strip_data uuid=\"{11111111-0000-4000-8000-000000000002}\"><informations>"
|
||||
"<information name=\"name\">X2</information></informations></terminal_strip_data>"
|
||||
"<layout><physical_terminal><real_terminal element_uuid=\"{c96008ed-1c74-4c0a-836a-bd8458491a16}\"/></physical_terminal></layout>"
|
||||
"</terminal_strip></terminal_strips>";
|
||||
const int at = xml.indexOf("<collection");
|
||||
QVERIFY(at > 0);
|
||||
xml.insert(at, strips);
|
||||
QDomDocument original;
|
||||
QVERIFY(original.setContent(xml)); // as a save leaves it: no whitespace text
|
||||
QString error;
|
||||
const QList<QetZip::Entry> entries = QetContainer::split(original, QString(), &error);
|
||||
QVERIFY2(!entries.isEmpty(), qPrintable(error));
|
||||
for (const QetZip::Entry &e : entries)
|
||||
if (e.name == QLatin1String("project.xml"))
|
||||
QVERIFY2(!e.data.contains("<terminal_strip>"), "the strips stayed in project.xml");
|
||||
QDomDocument back;
|
||||
QVERIFY2(QetContainer::join(entries, &back, &error), qPrintable(error));
|
||||
const QString d = difference(original.documentElement(), back.documentElement(), QString());
|
||||
QVERIFY2(d.isEmpty(), qPrintable(d));
|
||||
}
|
||||
|
||||
// The folio pictures leave the folios, once each
|
||||
void picturesAreFiles()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user