From 544823a1cf6729b37fd4c45036806627e76d3b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Fri, 18 Sep 2026 11:55:07 +0200 Subject: [PATCH] [DeckList] Inline deck metadata XML serialization Fold DeckList::Metadata::readElement and write back into deck_list.cpp alongside isEmpty(), and drop the separate deck_list_metadata_xml translation unit. The metadata arms are instance methods of the nested Metadata struct, so keeping them in the same file as its other method keeps the class from being scattered across two .cpp files; the rest of the refactor (readElement as a thin dispatcher, element-wise reads, clamped playmat params) is unchanged. --- libcockatrice_deck_list/CMakeLists.txt | 1 - .../libcockatrice/deck_list/deck_list.cpp | 95 ++++++++++++++++ .../deck_list/deck_list_metadata_xml.cpp | 105 ------------------ 3 files changed, 95 insertions(+), 106 deletions(-) delete mode 100644 libcockatrice_deck_list/libcockatrice/deck_list/deck_list_metadata_xml.cpp diff --git a/libcockatrice_deck_list/CMakeLists.txt b/libcockatrice_deck_list/CMakeLists.txt index 2fc36b3b4..0c487466c 100644 --- a/libcockatrice_deck_list/CMakeLists.txt +++ b/libcockatrice_deck_list/CMakeLists.txt @@ -27,7 +27,6 @@ add_library( libcockatrice/deck_list/tree/inner_deck_list_node.cpp libcockatrice/deck_list/deck_list.cpp libcockatrice/deck_list/deck_list_history_manager.cpp - libcockatrice/deck_list/deck_list_metadata_xml.cpp libcockatrice/deck_list/deck_list_node_tree.cpp libcockatrice/deck_list/deck_list_plain_text_parser.cpp libcockatrice/deck_list/playmat_resolver.cpp diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index 717f04979..0013cb475 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -15,11 +15,106 @@ static const QString CURRENT_SIDEBOARD_PLAN_KEY = ""; +/** + * @brief Parses a floating point XML attribute into a clamped playmat parameter. + * + * Falls back to @p fallback when the attribute is missing or malformed, so + * malformed deck files cannot produce degenerate art rectangles (e.g. a zoom + * of 0 dividing by zero). + * + * @param valueString Raw attribute text. + * @param fallback Value used when the text cannot be parsed. + * @param min Lower clamp bound. + * @param max Upper clamp bound. + * @return The parsed value clamped to [min, max], or @p fallback. + */ +static double parseClampedParam(const QString &valueString, double fallback, double min, double max) +{ + bool ok = false; + const double value = valueString.toDouble(&ok); + if (!ok) { + return fallback; + } + return qBound(min, value, max); +} + bool DeckList::Metadata::isEmpty() const { return name.isEmpty() && comments.isEmpty() && bannerCard.isEmpty() && tags.isEmpty() && playmat.card.isEmpty(); } +bool DeckList::Metadata::readElement(QXmlStreamReader *xml, const QString &childName) +{ + if (childName == "lastLoadedTimestamp") { + lastLoadedTimestamp = xml->readElementText(); + } else if (childName == "deckname") { + name = xml->readElementText(); + } else if (childName == "format") { + gameFormat = xml->readElementText(); + } else if (childName == "comments") { + comments = xml->readElementText(); + } else if (childName == "bannerCard") { + QString providerId = xml->attributes().value("providerId").toString(); + QString cardName = xml->readElementText(); + bannerCard = {cardName, providerId}; + } else if (childName == "playmatCard") { + QString providerId = xml->attributes().value("providerId").toString(); + // Attributes are read before readElementText consumes the element. + QString marginLStr = xml->attributes().value("marginPctL").toString(); + QString marginRStr = xml->attributes().value("marginPctR").toString(); + QString vOffStr = xml->attributes().value("verticalOffset").toString(); + QString zoomStr = xml->attributes().value("zoom").toString(); + QString cardName = xml->readElementText(); + playmat.card = {cardName, providerId}; + // Clamp to the same ranges as the settings dialog and the remote + // player-properties path so malformed deck files cannot produce + // degenerate art rectangles (e.g. a zoom of 0 dividing by zero). + playmat.params.marginPctL = parseClampedParam(marginLStr, 0.07, 0.0, 0.95); + playmat.params.marginPctR = parseClampedParam(marginRStr, 0.07, 0.0, 0.95); + playmat.params.verticalOffset = parseClampedParam(vOffStr, 0.33, 0.0, 1.0); + playmat.params.zoom = parseClampedParam(zoomStr, 1.0, 0.1, 4.0); + } else if (childName == "tags") { + tags.clear(); // Clear existing tags + while (xml->readNextStartElement()) { + if (xml->name().toString() == "tag") { + tags.append(xml->readElementText()); + } + } + } else { + return false; + } + return true; +} + +void DeckList::Metadata::write(QXmlStreamWriter *xml) const +{ + xml->writeTextElement("lastLoadedTimestamp", lastLoadedTimestamp); + xml->writeTextElement("deckname", name); + xml->writeTextElement("format", gameFormat); + xml->writeStartElement("bannerCard"); + xml->writeAttribute("providerId", bannerCard.providerId); + xml->writeCharacters(bannerCard.name); + xml->writeEndElement(); + if (!playmat.card.isEmpty()) { + xml->writeStartElement("playmatCard"); + xml->writeAttribute("providerId", playmat.card.providerId); + xml->writeAttribute("marginPctL", QString::number(playmat.params.marginPctL, 'f', 4)); + xml->writeAttribute("marginPctR", QString::number(playmat.params.marginPctR, 'f', 4)); + xml->writeAttribute("verticalOffset", QString::number(playmat.params.verticalOffset, 'f', 4)); + xml->writeAttribute("zoom", QString::number(playmat.params.zoom, 'f', 4)); + xml->writeCharacters(playmat.card.name); + xml->writeEndElement(); + } + xml->writeTextElement("comments", comments); + + // Write tags + xml->writeStartElement("tags"); + for (const QString &tag : tags) { + xml->writeTextElement("tag", tag); + } + xml->writeEndElement(); +} + DeckList::DeckList() { } diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_metadata_xml.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_metadata_xml.cpp deleted file mode 100644 index 3a71369a1..000000000 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list_metadata_xml.cpp +++ /dev/null @@ -1,105 +0,0 @@ -#include "deck_list.h" - -#include -#include -#include - -namespace -{ - -/** - * @brief Parses a floating point XML attribute into a clamped playmat parameter. - * - * Falls back to @p fallback when the attribute is missing or malformed, so - * malformed deck files cannot produce degenerate art rectangles (e.g. a zoom - * of 0 dividing by zero). - * - * @param valueString Raw attribute text. - * @param fallback Value used when the text cannot be parsed. - * @param min Lower clamp bound. - * @param max Upper clamp bound. - * @return The parsed value clamped to [min, max], or @p fallback. - */ -double parseClampedParam(const QString &valueString, double fallback, double min, double max) -{ - bool ok = false; - const double value = valueString.toDouble(&ok); - if (!ok) { - return fallback; - } - return qBound(min, value, max); -} - -} // namespace - -bool DeckList::Metadata::readElement(QXmlStreamReader *xml, const QString &childName) -{ - if (childName == "lastLoadedTimestamp") { - lastLoadedTimestamp = xml->readElementText(); - } else if (childName == "deckname") { - name = xml->readElementText(); - } else if (childName == "format") { - gameFormat = xml->readElementText(); - } else if (childName == "comments") { - comments = xml->readElementText(); - } else if (childName == "bannerCard") { - QString providerId = xml->attributes().value("providerId").toString(); - QString cardName = xml->readElementText(); - bannerCard = {cardName, providerId}; - } else if (childName == "playmatCard") { - QString providerId = xml->attributes().value("providerId").toString(); - // Attributes are read before readElementText consumes the element. - QString marginLStr = xml->attributes().value("marginPctL").toString(); - QString marginRStr = xml->attributes().value("marginPctR").toString(); - QString vOffStr = xml->attributes().value("verticalOffset").toString(); - QString zoomStr = xml->attributes().value("zoom").toString(); - QString cardName = xml->readElementText(); - playmat.card = {cardName, providerId}; - // Clamp to the same ranges as the settings dialog and the remote - // player-properties path so malformed deck files cannot produce - // degenerate art rectangles (e.g. a zoom of 0 dividing by zero). - playmat.params.marginPctL = parseClampedParam(marginLStr, 0.07, 0.0, 0.95); - playmat.params.marginPctR = parseClampedParam(marginRStr, 0.07, 0.0, 0.95); - playmat.params.verticalOffset = parseClampedParam(vOffStr, 0.33, 0.0, 1.0); - playmat.params.zoom = parseClampedParam(zoomStr, 1.0, 0.1, 4.0); - } else if (childName == "tags") { - tags.clear(); // Clear existing tags - while (xml->readNextStartElement()) { - if (xml->name().toString() == "tag") { - tags.append(xml->readElementText()); - } - } - } else { - return false; - } - return true; -} - -void DeckList::Metadata::write(QXmlStreamWriter *xml) const -{ - xml->writeTextElement("lastLoadedTimestamp", lastLoadedTimestamp); - xml->writeTextElement("deckname", name); - xml->writeTextElement("format", gameFormat); - xml->writeStartElement("bannerCard"); - xml->writeAttribute("providerId", bannerCard.providerId); - xml->writeCharacters(bannerCard.name); - xml->writeEndElement(); - if (!playmat.card.isEmpty()) { - xml->writeStartElement("playmatCard"); - xml->writeAttribute("providerId", playmat.card.providerId); - xml->writeAttribute("marginPctL", QString::number(playmat.params.marginPctL, 'f', 4)); - xml->writeAttribute("marginPctR", QString::number(playmat.params.marginPctR, 'f', 4)); - xml->writeAttribute("verticalOffset", QString::number(playmat.params.verticalOffset, 'f', 4)); - xml->writeAttribute("zoom", QString::number(playmat.params.zoom, 'f', 4)); - xml->writeCharacters(playmat.card.name); - xml->writeEndElement(); - } - xml->writeTextElement("comments", comments); - - // Write tags - xml->writeStartElement("tags"); - for (const QString &tag : tags) { - xml->writeTextElement("tag", tag); - } - xml->writeEndElement(); -}