diff --git a/sources/container/qetcontainer.cpp b/sources/container/qetcontainer.cpp index 79050a8cd..dbc6d3eea 100644 --- a/sources/container/qetcontainer.cpp +++ b/sources/container/qetcontainer.cpp @@ -388,34 +388,39 @@ QList QetContainer::split(const QDomDocument &project, const QStr for (QDomElement diagram : childElements(root, QStringLiteral("diagram"))) dropComputed(diagram, own_uuids, defined); - //The parts of the folios kept as they were, and their pictures + //The parts of the folios, in the order of the folios whether they + //were kept as they were or built: which folios were built must not + //change the file. The names of the parts kept are taken first, so a + //folio built cannot take one. QMap pictures; //name -> png, once each - for (QDomElement diagram : childElements(root, QStringLiteral("diagram"))) - { + const QList diagrams = childElements(root, QStringLiteral("diagram")); + for (const QDomElement &diagram : diagrams) { const QString kept = diagram.attribute(QStringLiteral("qetz-kept-part")); - if (kept.isEmpty()) continue; - if (!kept_from.contains(kept)) { - setError(error, QStringLiteral("the part \"%1\" of a folio kept as it was is missing").arg(kept)); - return {}; - } - taken.insert(kept); - const QByteArray part = kept_from.value(kept); - static const QRegularExpression image_file(QStringLiteral("%1=\"(images/[^\"]+)\"").arg(ImageFileAttribute)); - for (const auto &match : image_file.globalMatch(QString::fromUtf8(part))) { - const QString name = match.captured(1); - if (!kept_from.contains(name)) { - setError(error, QStringLiteral("the picture \"%1\" of a folio kept as it was is missing").arg(name)); - return {}; - } - pictures.insert(name, kept_from.value(name)); - } - parts.append(QetZip::Entry{kept, part, true}); - root.replaceChild(placeholder(document, kept), diagram); + if (!kept.isEmpty()) taken.insert(kept); } - - for (QDomElement diagram : childElements(root, QStringLiteral("diagram"))) + for (QDomElement diagram : diagrams) { ++folio; + const QString kept = diagram.attribute(QStringLiteral("qetz-kept-part")); + if (!kept.isEmpty()) { + if (!kept_from.contains(kept)) { + setError(error, QStringLiteral("the part \"%1\" of a folio kept as it was is missing").arg(kept)); + return {}; + } + const QByteArray part = kept_from.value(kept); + static const QRegularExpression image_file(QStringLiteral("%1=\"(images/[^\"]+)\"").arg(ImageFileAttribute)); + for (const auto &match : image_file.globalMatch(QString::fromUtf8(part))) { + const QString name = match.captured(1); + if (!kept_from.contains(name)) { + setError(error, QStringLiteral("the picture \"%1\" of a folio kept as it was is missing").arg(name)); + return {}; + } + pictures.insert(name, kept_from.value(name)); + } + parts.append(QetZip::Entry{kept, part, true}); + root.replaceChild(placeholder(document, kept), diagram); + continue; + } for (QDomElement images : childElements(diagram, QStringLiteral("images"))) { for (QDomElement image : childElements(images, QStringLiteral("image"))) { const QDomText text = image.firstChild().toText(); diff --git a/tests/qttest/fixtures/every_feature.qet b/tests/qttest/fixtures/every_feature.qet new file mode 100644 index 000000000..c5d372169 --- /dev/null +++ b/tests/qttest/fixtures/every_feature.qet @@ -0,0 +1,1893 @@ + + + + + + + + +Created by potrace 1.10, written by Peter Selinger 2001-2011 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %date + + + + + + %date1 + + + + + %date + + + + + + + %title + + + + + + %filename + + + + + + %filename + %label4 + + + + + + + + + + + + + + + + + + DESSINE + + + + + %dessin + + + + + + %label5 + + + + + %rev + + + + + Emissionne + EMIS + + + + + %date2 + + + + + + + + CONTROL + + + + + %control + + + + + %label3 + + + + + + + + %{folio-id} + + + + + + REV + + + + + + MODIFICATION + + + + + %date + + + + + + FIRME + + + + + APROUV + + + + + %aprouv + + + + + %label1 + + + + + %label2 + + + + + ORIGINE + + + + + + %{folio-total} + + + + + + + + 10/2/26 + 02-10-2026 + 2026-10-02 + free_text_width + 18:39 + + + + + + + ACME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ACME + + + + + + + + + + + + + + + + + + + + 1 + + 1 + + 01 + + 001 + a + + + + + + + + + + + Aux 1 + A comment + Description + Designation + K%prefix%M%LM%l%c%F%sequ_1%sequf_1%seqtf_1%seqhf_1%seqa_1 + {8e52a483-68df-4a9e-9a81-c008e47aa9a6} + Motor start + KKPlant 0Cabinet 0C1F11101001a + Cabinet 0 + MM-1 + Maker + REF-1 + Plant 0 + 2 + Supplier + 24V DC + pcs + + + + A2 + #aa5500 + + + Free text + + + A comment + comment + + + KKPlant 0Cabinet 0C1F11101001a / A comment + %{label} / %{comment} + + + A1 + + + KKPlant 0Cabinet 0C1F11101001a + label + + + + + + + + + + + + + + + + + + + + + label + + + + + + + 2 + + 2 + + 02 + + 002 + b + + + + + + + + + K%prefix%M%LM%l%c%F%sequ_1%sequf_1%seqtf_1%seqhf_1%seqa_1 + {8e52a483-68df-4a9e-9a81-c008e47aa9a6} + KXPlant 0Cabinet 0E4F12202002b + + + + + + + KXPlant 0Cabinet 0E4F12202002b + label + + + + + + + + + + + + + + + + + + label + + + + + + + + + + + + + + + + + + Aux auxiliary1 + Aux auxiliary2 + Aux auxiliary3 + Aux auxiliary4 + Category + 2A + 20 + Aux description_auxiliary1 + Aux description_auxiliary2 + Aux description_auxiliary3 + Aux description_auxiliary4 + Aux designation_auxiliary1 + Aux designation_auxiliary2 + Aux designation_auxiliary3 + Aux designation_auxiliary4 + 10 + Aux machine_manufacturer_reference_auxiliary1 + Aux machine_manufacturer_reference_auxiliary2 + Aux machine_manufacturer_reference_auxiliary3 + Aux machine_manufacturer_reference_auxiliary4 + Aux manufacturer_auxiliary1 + Aux manufacturer_auxiliary2 + Aux manufacturer_auxiliary3 + Aux manufacturer_auxiliary4 + Aux manufacturer_reference_auxiliary1 + Aux manufacturer_reference_auxiliary2 + Aux manufacturer_reference_auxiliary3 + Aux manufacturer_reference_auxiliary4 + Model + Notes + 1 + 1 + 1 + 1 + Aux supplier_auxiliary1 + Aux supplier_auxiliary2 + Aux supplier_auxiliary3 + Aux supplier_auxiliary4 + Aux unity_auxiliary1 + Aux unity_auxiliary2 + Aux unity_auxiliary3 + Aux unity_auxiliary4 + 24V + + + + + label + + + + + + + + + + + + + + + + + + label + + + ÖLFLEX 100 + + + + + + + + + + + + + + + 2-D0 + label + + + + + + + + + + + + + 1 + 1 + 1 + 01 + 01 + 001 + 001 + a + + + + + 2 + 2 + 2 + 02 + 02 + 002 + 002 + b + + + + + 3 + 3 + 3 + 03 + 03 + 003 + 003 + c + + + + + 5 + 5 + 5 + 05 + 05 + 005 + 005 + e + + + + + 4 + 4 + 4 + 04 + 04 + 004 + 004 + d + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1-D11 + label + + + + + + + + + + + + + + + + KKPlant 0Cabinet 0C1F11101001a + label + + + + + + + 3 + + 3 + + 03 + + 003 + c + + + + + + + + Aux 1 + Description + Designation + K%prefix%M%LM%l%c%F%sequ_1%sequf_1%seqtf_1%seqhf_1%seqa_1 + {8e52a483-68df-4a9e-9a81-c008e47aa9a6} + Motor start + KKPlant 1Cabinet 1B6F23303003c + MM-1 + Maker + REF-1 + Plant 0 + 2 + Supplier + pcs + + + + A2 + + + A1 + + + KKPlant 1Cabinet 1B6F23303003c + label + + + Free text + + + + comment + + + KKPlant 1Cabinet 1B6F23303003c / + %{label} / %{comment} + + + + + + + + + + + + + + + 1 + + + + + + + + A2 + + + A1 + + + + label + + + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAICAIAAAB/FOjAAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAGklEQVQYlWNkYGgQYGAgHrEwKDCQBEY1EAMA3O4CbvRC5WAAAAAASUVORK5CYII= + + + + + + + + 5;5;10;5 + + + + nomenclature + SELECT label, designation, manufacturer, manufacturer_reference, quantity FROM element_nomenclature_view WHERE ( element_type = 'terminal' OR element_type = 'thumbnail' OR element_type = 'simple' OR element_type = 'master' OR element_type = 'slave') ORDER BY label, designation, manufacturer, manufacturer_reference, quantity + + + Liberation Sans,9,-1,0,50,0,0,0,0,0,Regular + AlignLeft + + + + + + + + 5;5;10;5 + + + + summary + SELECT pos, title, author, folio FROM project_summary_view + + + Liberation Sans,9,-1,0,50,0,0,0,0,0,Regular + AlignLeft + + + + + + + + + + + + + 5;5;10;5 + + + + + + + + + + + Plant 0 + Cabinet 0 + X1 + + + + + + + + + + + + + + + + + + + + + + Imported elements + + + + Test + + + + + + ملف + Bobina + Cívka relé + Relæ + Spule + Πηνίο + Coil + Bobina + Käämi + Bobine + Zavojnica + Tekercs + Bobina + コイル + 코일 + Ороомог + Spole + Spoel + Relais spoel (algemeen) + Cewka + Bobina + Bobina + Bobina + Обмотка + Cievka + Tuljava + Каlem + Spole + Bobin + Котушка + 三线圈 + + + plc + + Author: The QElectroTech team +License: see http://qelectrotech.org/wiki/doc/elements_license + + + + + A2 + + + A1 + + + + + label + + + + + + + + + + كهربائي + Elèctric + Elektrotechnika + Elektrisk + Elektrik + Ηλεκτρικά + Electric + Eléctrica + Sähkö + Electrique + Električno + Elektromos + Elettrica + 電気 + 전기 + Цахилгаан + Elektrisk + Elektrotechniek + Elektrisch + Elektrotechnika + Elétrico + Elétrica + Electric + Электротехника + Elektrické + Elektrika + Електрика + El + Elektrik + Електрика + 10_电气 + 电气工程 + + + + متعدد الأقطاب + Multifilar + Vícežilový + Flere ledere + Allpolig + Πολυγραμμικό + All-pole + Multifilar + Yleisnapa + Multifilaire + Univerzalni + Többvonalas szimbólumok + Multifilare + 複線 + 다-극 + Бүх туйл + Alle poler + Veel polig + All-pole + Schematy wieloliniowe + Multipolar + Multifilar + Bipolar + Многополюсные + Všepólové + Vsi poli + Свe-полни + Allpolig + Çok kutuplu + Багатополюсні + 10_全极 + + + + المرحّلات والموصّلات وجهات الاتصال + Relés, contactors i contactes + Relé, stykače a kontakty + Relæer, kontaktorer og kontakter + Relais, Schütze und Kontakte + Ηλεκτρονόμοι και επαφές + Relays, contactors and contacts + Relevadores, contactores y contactos + Releet, kontaktorit ja koskettimet + Relais, contacteurs et contacts + Releji, kontaktori i kontakti + Relék, kontaktorok és érintkezők + Relè, contattori e contatti + 継電器、接触器、接点 + 릴레이, 커넥터및 접점 + Реле, контактор болон контакт + Reléer, kontaktorere og kontakter + Relais, contactoren en contacten + Relais, contacteurs en contacten + Przekaźniki i styczniki + Relés, contatores e contactos + Relés, Contatores e contatos + Relee, contactoare și contacte + Реле, пускатели и контакты + Relé, stykače a kontakty + Releji, kontaktorji in kontakti + Релеји, контактори и контакти + Reläer, kontaktorer och kontakter + Röleler, kontaktörler ve kontaklar + Реле, контактори та контакти + 继电器、接触器与触点 + + + + لفه + Bobines + cívka + spole + Spule + σπείρα + coil + bobina + Käämi + bobine + zavojnica + tekercs + bobina + コイル + 코일 + ороомог + Spole + spoel + Spoel + cewka + bobina + bobina + bobina + катушка + Cievka + tuljava + калем + spole + bobin + котушка + 线圈 + + + + + + ملف + Bobina + Cívka relé + Relæ + Spule + Πηνίο + Coil + Bobina + Käämi + Bobine + Zavojnica + Tekercs + Bobina + コイル + 코일 + Ороомог + Spole + Spoel + Relais spoel (algemeen) + Cewka + Bobina + Bobina + Bobina + Обмотка + Cievka + Tuljava + Каlem + Spole + Bobin + Котушка + 三线圈 + + + coil + + Author: The QElectroTech team +License: see http://qelectrotech.org/wiki/doc/elements_license + + + + + A2 + + + A1 + + + + + label + + + + + + + + + + تماسات مع إحالات + Contactes amb referències creuades + Kontakty s křížovými odkazy + Kontaker med krydsreference + Kontakte mit Querverweis + Επαφές με παραπομπές + Contacts with cross referencing + Contactos con referencia cruzada + Kosketimet lehtiviittauksella + Contacts avec référence croisée + Kontakti s unakrsnim referenciranjem + Érintkezők kereszthivatkozásokkal + Contatti con riferimenti incrociati + 相互参照付き接点 + 교차 참조 접점 + Харилцан холбоос бүхий контакт + Kontakter med kryssreferanse + Contacten met kruisreferentie + Contacten met kruisverwijzing + Zestyki (automatyczne oznaczenia) + Contactos com renvoi de folio + Contatos com referencia cruzada + Contacte cu renvoi de folio + Контакты с перекрестными ссылками + Kontakty s krížovým odkazom + Kontakti s sklici + Контакти са унакрсним референцирањем + Kontakter med bladhänvisning + Çapraz referanslı kontaklar + Контакти з перехресними посиланнями + 触点交叉引用 + + + + مساعد + Auxiliars + Pomocné kontakty + Hjælpe kontakter + Hilfsglieder + Βοηθητικές επαφές + Auxiliary + Contactos auxiliares + Apupiiri + Auxilliaires + Pomoćni + Segéd + Contatti ausiliari + 補助 + 보조 접점 + Туслах + Hjelpe + Hulpcontacten + Hulpcontacten + Zestyki pomocnicze + Auxiliar + Auxiliares + Auxiliare + Внешние + Pomocné + Pomožni + Помоћни + Hjälp + Yardımcı + Допоміжні + 辅助触点 + + + + + + مُلامس بسيط (NO) + Contacte senzill + Spínací kontakt + Enkel kontakt + Schliesser + Απλή επαφή, ανοικτή + Simple contact + Contactor simple + Yksinkertainen kosketin + Contact simple + Jednostavni kontakt + Egyszerű érintkező + Contatto semplice (NO) + 単純接点 + 단순 접점 + Энгийн контакт + Enkel kontakt + contact enkel + Eenvoudig contact + Zestyk zwierny + Contacto simples (NO) + Contato simples + Contact simplu + Проктой контакт (НР) + Jednoduchý kontakt + Enostavni kontakt + Прости контакт + Enkelkontakt + Basit kontak + Простий контакт + 简单连接器 + + + simple + NO + 1 + + Author: The QElectroTech team +License: see http://qelectrotech.org/wiki/doc/elements_license + + + + + label + + + + + + + + + + + + مُلامس بسيط (NC) + Contacte simple (NC) + Rozpínací kontakt + Enkel kontakt (NC) + Öffner + Απλή επαφή, κλειστή + Simple contact (NC) + Contactor simple (NC) + Yksinkertainen kosketin (NC) + Contact simple (NF) + Jednostavni kontakt (NC) + Egyszerű érintkező (NC) + Contatto semplice (NC) + 単純接点 (NC) + 단순 접점 (NC) + Энгийн контакт (NC) + Enkel kontakt (NC) + contact normaal gesloten enkel simpel (NC) + Eenvoudig contact (NC) + Zestyk rozwierny + Contacto simples (NC) + Contato simples (NF) + Contact simplu (NC) + Проктой контакт (НЗ) + Jednoduchý kontakt (NC) + Enostavni kontakt (NC) + Прости контакт (NC) + Enkelkontakt (NC) + Basit kontak (NC) + Простий контакт (NC) + 简单常闭连接器 + + + simple + NC + 1 + + Author: The QElectroTech team +License: see http://qelectrotech.org/wiki/doc/elements_license + + + + + + + label + + + + + + + + + + قدرة + Potència + Silové kontakty + Hoved kontaktor + Leistung + Ισχύος + Power + Potencia + Teho + Puissance + Snaga + Teljesítmény + Potenza + 電力 + 전력 접점 + Цахилгаан + Kraft + Last contacten + Vermogen + Styczniki + Potência + Potencia + Putere + Силовые + Výkonové + Moč + Снага + Effekt + Güç + Живлення + 功率触点 + + + + + + مُلامس قدرة - كونتكتور (NO) + Contactar el contactor de potència + Silový kontakt 4p + Hovedkontakt kontaktor + Leistungsschliesser 4-polig + Επαφές ρελέ ισχύος, ανοικτές + Contact power contactor + Interruptor de potencia contactor + Koskettimen teho kontaktorille + Contact puissance contacteur + Kontakt snažnog kontaktora + Teljesítmény kontaktor érintkező + 4 Contatti NA di potenza + 電力用接触器接点 + 전력 접점 접촉기 + Контакт хүчний контактор + Kontakt kraftkontaktor + Last contact (NO) + Vermogenscontact contacteur + Stycznik + Interruptor de potência contactor + Contator de potência + Contact de putere contactor + Контакт контактора (НР) + Výkonový kontakt stýkača + Kontakt močnostnega kontaktorja + Контакт снаге контактора + Kontakt effektkontaktor + Güç kontağı kontaktör + Силовий контакт контактора + 常开功率触点6 + + + power + NO + 4 + + Author: The QElectroTech team +License: see http://qelectrotech.org/wiki/doc/elements_license + + + + + + + + + + + + + + + label + + + + + + + + + + + + + + + + + + + + + + + + + + + + طرفيات توصيل + Borns i regletes de borns + Svorky + Klemmer og klemmerækker + Klemmen und Klemmleisten + Ακροδέκτες και σειρές ακροδεκτών + Terminals and terminal strips + Terminales + Rimaelittimet + Bornes et borniers + Stezaljke i redne stezaljke + Sorkapcsok + Terminali + 端子と端子台 + 단자 및 단자대 + Клемм ба клеммын хавтан + Rekkeklemmer og klemlister + Klemmenlijst + Klemmen en klemmenstroken + Złącza i zaciski + Terminais + Bornes e borneiras + Borne și borniere + Клеммы и клеммники + Svorky a svorkovnice + Vrstne sponke in sponke + Клеме и редне клеме + Plintar och plintrader + Klemensler ve klemens şeritleri + Клеми та клемні колодки + 接线端子与端子排 + + + + + + طرف توصيل + Born de continuïtat + Spojovací svorka + Klemme + Klemme + Ακροδέκτης + Terminal block + Terminal de unión + Liitinrima + Borne continuité + Stezaljka + Sorkapocs + Terminale di unione + 端子ブロック + 단자 블록 + Клеммын блок + Rekkeklemme + klem doorgang + Klemmenblok + Zacisk + Terminal de combinação + Borne de combinacao + Bloc de borne + Клеммное соединение + Svorkovnica + Vrstna sponka + Клемни блок + Plint + Klemens bloğu + Клемна колодка + 2号接线端子 + + + generic + generic + + Author: The QElectroTech team +License: see http://qelectrotech.org/wiki/doc/elements_license + + + + + + + + label + + + + + + + + + + + كابلات وتوصيلات + Cables i cablejat + Kabely a vodiče + Kabler og ledninger + Kabel und Verdrahtung + Καλώδια και καλωδίωση + Cables and wiring + Cables y alambres + Kaapelit ja johdotus + Câbles et filerie + Kabeli i ožičenje + Kábelek és vezetékek + Cavi e collegamenti + ケーブルと配線 + 케이블 및 배선 + Кабель ба утас + Kabler og ledninger + Kabels en draden + Kabels en bedrading + Przewody i kable + Cabos e cablagem + Cabos e fiação + Cabluri și cablare + Кабели и провода + Káble a vedenie + Kabli in ožičenje + Каблови и ожичење + Kablar och ledningsdragning + Kablolar ve kablolama + Кабелі та проводка + 电缆与布线 + + + + + + كابل 3 أسلاك + Cable 3 fils + Kabel 3x + Kabel 3 ledere + Kabel 3x + Καλώδιο 3 αγωγών + Cable 3 wires + Cable 3 alambres + Kaapeli 3 johdinta + Cable 3 brins + Kabel 3 žica + Kábel 3 + PE + Cavo a 3 conduttori + ケーブル 3 本 + 케이블 3심 + Кабель 3 утас + Kabel 3 ledere + Kabel 3 draden + Kabel 3 draden + Oznaczenie przewodu trójżyłowego + Cabo 3 fios + Cabo 3 condutores + Cablu 3 fire + Кабель 3 жилы + Kábel 3 vodičov + Kabel 3 žic + Кабл 3 жица + Kabel 3 trådar + Kablo 3 tel + Кабель 3 жил + 3线电缆 + + edited with "QET_ElementScaler" + + + + + label + + + ÖLFLEX 100 + + + + + + + + + + + + + + إحالات + Referències als fulls + Odkazování listů + Ark henvisninger + Folienverweise + Αναφορές σελίδας + Folio referencing + Referencias + Lehtiviittaus + Reports de folio + Referenciranje folija + Tervlap hivatkozások + Riferimenti + フォリオ参照 + 도면 간 참조 + Хуудасны холбоос + Folio-referanse + Referenties + Folioverwijzing + Odsyłacze + Renvoi de folio + Referências + Renvoi de folio + Точки разрыва + Odkazy na listy + Sklici na list + Референцирање фолија + Bladhänvisning + Sayfa referansı + Посилання на аркуші + 图页引用 + + + + + + الصفحة التالية + Enllaç sortint + Další list + Går pil + Nächste Folie + Επόμενη σελίδα + Going arrow + Folio siguiente + Lähtevä nuoli + Folio suivant + Odlazna strelica + Elmenő hivatkozás + Foglio successivo + 出矢印 + 나가는 화살표 + Явж буй сум + Går pil + Referentie volgend + Gaande pijl + Wyjście adresowe + Seta de saída + Seta de saída (vai) + Săgeată de ieșire + Следующая страница + Odchádzajúca šípka + Odhodna puščica + Одлазна стрелица + Utgående pil + Giden ok + Стрілка виходу + 02引出箭头 + + Author: The QElectroTech team +License: see http://qelectrotech.org/wiki/doc/elements_license + + + + + + label + + + + + + + + + + الصفحة السابقة + Enllaç entrant + Předchozí list + Kommer pil + Vorherige Folie + Προηγούμενη σελίδα + Coming arrow + folio anterior + Saapuva nuoli + Folio précédent + Dolazna strelica + Beérkező hivatkozás + Foglio precedente + 入り矢印 + 들어오는 화살표 + Ирж буй сум + Kommer pil + Referentie Vorig + Komende pijl + Wejście adresowe + Seta de entrada + Seta de entrada (vem) + Săgeată de intrare + Предыдущая страница + Prichádzajúca šípka + Prihajajoča puščica + Долазна стрелица + Inkommande pil + Gelen ok + Стрілка входу + 01引入箭头 + + Author: The QElectroTech team +License: see http://qelectrotech.org/wiki/doc/elements_license + + + + + + label + + + + + + + + + + + diff --git a/tests/qttest/tst_resaveunchanged.cpp b/tests/qttest/tst_resaveunchanged.cpp index fe428c90b..026e5ee38 100644 --- a/tests/qttest/tst_resaveunchanged.cpp +++ b/tests/qttest/tst_resaveunchanged.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -35,7 +36,8 @@ class tst_resaveunchanged : public QObject // --resave @p in to a new file, in a sandbox of its own (so a running // QElectroTech cannot answer instead); returns the new file's path. - QString resave(const QString &in, const QString &suffix = QStringLiteral(".qet")) + QString resave(const QString &in, const QString &suffix = QStringLiteral(".qet"), + bool eager = false) { //Always p.qet, in its own folder: an untitled project takes its //file name as title, so two names would make two projects. @@ -48,6 +50,11 @@ class tst_resaveunchanged : public QObject env.insert(QStringLiteral("HOME"), home); env.insert(QStringLiteral("XDG_CONFIG_HOME"), home + QStringLiteral("/config")); env.insert(QStringLiteral("XDG_DATA_HOME"), home + QStringLiteral("/data")); + if (eager) { + env.insert(QStringLiteral("QET_EAGER_FOLIOS"), QStringLiteral("1")); //every folio built on opening + } else { + env.remove(QStringLiteral("QET_EAGER_FOLIOS")); + } QProcess proc; proc.setProcessEnvironment(env); proc.start(QStringLiteral(QET_TEST_BINARY_PATH), {QStringLiteral("--resave"), in, out}); @@ -385,6 +392,117 @@ private slots: QCOMPARE(without_save_stamps(read(back)), without_save_stamps(read(straight))); } + // The round trip between the formats (#1512): every example, and + // fixtures/every_feature.qet, a project using every feature + // QElectroTech saves (built through its script API, the few things the + // API cannot set added in the form QElectroTech saves them, then opened + // and saved by QElectroTech). For each, the save stamps aside: + // - a .qetz saved again is the same file; + // - a .qetz saved as a .qet is the project saved straight as a .qet; + // - that .qet saved as a .qetz is the project saved straight as a + // .qetz, part by part, its database row by row; + // - lazy folios save what every folio built saves, in both formats. + void roundTripBetweenFormats_data() + { + QTest::addColumn("project"); + const QDir examples(QStringLiteral(QET_EXAMPLES_DIR)); + for (const QString &name : examples.entryList({QStringLiteral("*.qet")}, QDir::Files, QDir::Name)) + QTest::newRow(name.toUtf8().constData()) << examples.filePath(name); + QTest::newRow("every_feature.qet") << QFINDTESTDATA("fixtures/every_feature.qet"); + } + void roundTripBetweenFormats() + { + QFETCH(QString, project); + const QString qet = resave(project); + QVERIFY2(!qet.isEmpty(), "--resave as .qet failed"); + const QString qetz = resave(project, QStringLiteral(".qetz")); + QVERIFY2(!qetz.isEmpty(), "--resave as .qetz failed"); + + const QString qetz_again = resave(qetz, QStringLiteral(".qetz")); + QVERIFY2(!qetz_again.isEmpty(), "--resave of the .qetz failed"); + QVERIFY2(read(qetz_again) == read(qetz), "a .qetz saved again changed"); + + const QString back = resave(qetz); + QVERIFY2(!back.isEmpty(), "--resave of the .qetz as .qet failed"); + QCOMPARE(withoutSaveStamps(read(back)), withoutSaveStamps(read(qet))); + + const QString zipped = resave(qet, QStringLiteral(".qetz")); + QVERIFY2(!zipped.isEmpty(), "--resave of the .qet as .qetz failed"); + QCOMPARE(containerContent(zipped), containerContent(qetz)); + + const QString eager = resave(project, QStringLiteral(".qet"), true); + QVERIFY2(!eager.isEmpty(), "--resave with every folio built failed"); + QCOMPARE(withoutSaveStamps(read(eager)), withoutSaveStamps(read(qet))); + const QString eager_zipped = resave(project, QStringLiteral(".qetz"), true); + QVERIFY2(!eager_zipped.isEmpty(), "--resave as .qetz with every folio built failed"); + QCOMPARE(containerContent(eager_zipped), containerContent(qetz)); + } + + static QString withoutSaveStamps(const QByteArray &xml) + { + static const QRegularExpression stamp(QStringLiteral( + "\\s*]*name=\"saved(filename|filepath|filenamedir|date|date-us|date-eu|time)\"[^>]*>[^<]*")); + return QString::fromUtf8(xml).remove(stamp); + } + + // What the .qetz @p path holds, its save stamps aside: one line per + // part, an XML part as its text, project.sqlite as its rows + static QStringList containerContent(const QString &path) + { + QList entries; + if (!QetZip::fromBytes(read(path), &entries)) return {QStringLiteral("not a zip")}; + QStringList content; + for (const QetZip::Entry &entry : std::as_const(entries)) { + if (entry.name == QLatin1String("project.sqlite")) { + for (const QString &row : databaseRows(entry.data)) + content << entry.name + QLatin1Char(' ') + row; + } else if (entry.name.endsWith(QLatin1String(".xml"))) { + content << entry.name + QLatin1Char(' ') + withoutSaveStamps(entry.data); + } else { + content << entry.name + QLatin1Char(' ') + + QString::fromLatin1(QCryptographicHash::hash(entry.data, QCryptographicHash::Sha1).toHex()); + } + } + return content; + } + + // Every row of every table of the SQLite database @p database, but the + // save stamps, as text + static QStringList databaseRows(const QByteArray &database) + { + sqlite3 *db = nullptr; + sqlite3_open(":memory:", &db); + unsigned char *copy = static_cast(sqlite3_malloc64(sqlite3_uint64(database.size()))); + memcpy(copy, database.constData(), size_t(database.size())); + sqlite3_deserialize(db, "main", copy, database.size(), database.size(), SQLITE_DESERIALIZE_FREEONCLOSE); + QStringList tables; + sqlite3_stmt *list = nullptr; + sqlite3_prepare_v2(db, "SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name", -1, &list, nullptr); + while (list && sqlite3_step(list) == SQLITE_ROW) + tables << QString::fromUtf8(reinterpret_cast(sqlite3_column_text(list, 0))); + sqlite3_finalize(list); + static const QRegularExpression stamp(QStringLiteral( + "\\bsaved(filename|filepath|filenamedir|date|date-us|date-eu|time)\\b")); + QStringList rows; + for (const QString &table : std::as_const(tables)) { + sqlite3_stmt *select = nullptr; + sqlite3_prepare_v2(db, QStringLiteral("SELECT * FROM \"%1\"").arg(table).toUtf8().constData(), + -1, &select, nullptr); + while (select && sqlite3_step(select) == SQLITE_ROW) { + QStringList values; + for (int c = 0 ; c < sqlite3_column_count(select) ; ++c) { + const unsigned char *text = sqlite3_column_text(select, c); + values << (text ? QString::fromUtf8(reinterpret_cast(text)) : QStringLiteral("NULL")); + } + const QString row = table + QLatin1Char('|') + values.join(QLatin1Char('|')); + if (!row.contains(stamp)) rows << row; + } + sqlite3_finalize(select); + } + sqlite3_close(db); + return rows; + } + // The tables of the database in the .qetz @p path: the document's // engineering data only, each fact once -- not QElectroTech's own // project database, built from it when the project opens