From 0ea0e231bf71a113d51fa9ba1147b245536ba3bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Mon, 21 Sep 2026 13:21:00 +0200 Subject: [PATCH] [Card] Add a Tag attribute to the card filter tree --- .../libcockatrice/filters/filter_card.cpp | 2 ++ .../libcockatrice/filters/filter_card.h | 1 + .../libcockatrice/filters/filter_tree.cpp | 13 +++++++++++++ .../libcockatrice/filters/filter_tree.h | 1 + tests/carddatabase/filter_string_test.cpp | 18 ++++++++++++++++++ 5 files changed, 35 insertions(+) diff --git a/libcockatrice_filters/libcockatrice/filters/filter_card.cpp b/libcockatrice_filters/libcockatrice/filters/filter_card.cpp index 5fdce7ae0..409182b5f 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_card.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_card.cpp @@ -84,6 +84,8 @@ const QString CardFilter::attrName(Attr a) return tr("Loyalty"); case AttrFormat: return tr("Format"); + case AttrTag: + return tr("Tag"); case AttrMainType: return tr("Main Type"); case AttrSubType: diff --git a/libcockatrice_filters/libcockatrice/filters/filter_card.h b/libcockatrice_filters/libcockatrice/filters/filter_card.h index bb1e0ef53..2eab5508c 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_card.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_card.h @@ -43,6 +43,7 @@ public: AttrMainType, AttrSubType, AttrFormat, + AttrTag, AttrEnd, }; diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp index a5d91d9d3..1c0d488e1 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp @@ -3,6 +3,7 @@ #include "filter_card.h" #include +#include template FilterTreeNode *FilterTreeBranch::nodeAt(int i) const { @@ -341,6 +342,16 @@ bool FilterItem::acceptFormat(const CardInfoPtr info) const return info->getLegalityProp(term.toLower()) == "legal"; } +bool FilterItem::acceptTag(const CardInfoPtr info) const +{ + const QString stored = info->getProperty(Mtg::Tags); + if (stored.isEmpty()) { + return false; + } + // Tags are stored space-separated; match whole slugs, not substrings. + return stored.split(" ", Qt::SkipEmptyParts).contains(term.trimmed(), Qt::CaseInsensitive); +} + bool FilterItem::acceptLoyalty(const CardInfoPtr info) const { if (info->getLoyalty().isEmpty()) { @@ -495,6 +506,8 @@ bool FilterItem::acceptCardAttr(const CardInfoPtr info, return acceptLoyalty(info); case CardFilter::AttrFormat: return acceptFormat(info); + case CardFilter::AttrTag: + return acceptTag(info); case CardFilter::AttrMainType: return acceptMainType(info); case CardFilter::AttrSubType: diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.h b/libcockatrice_filters/libcockatrice/filters/filter_tree.h index dd47a1ebc..3f9f0f8a4 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.h @@ -228,6 +228,7 @@ public: [[nodiscard]] bool acceptCardAttr(CardInfoPtr info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const; [[nodiscard]] bool acceptFormat(CardInfoPtr info) const; + [[nodiscard]] bool acceptTag(CardInfoPtr info) const; [[nodiscard]] bool relationCheck(int cardInfo) const; }; diff --git a/tests/carddatabase/filter_string_test.cpp b/tests/carddatabase/filter_string_test.cpp index 400b67caa..5a3e6156d 100644 --- a/tests/carddatabase/filter_string_test.cpp +++ b/tests/carddatabase/filter_string_test.cpp @@ -3,6 +3,7 @@ #include "gtest/gtest.h" #include +#include #include #include @@ -160,6 +161,23 @@ TEST_F(CardQuery, TagQueryTreatsCommasAsPartOfTheSlug) ASSERT_FALSE(FilterString("tags:ramp,removal").check(tagged)); } +TEST_F(CardQuery, FilterTreeTagAttribute) +{ + const CardData tagged = taggedCard(); + + FilterTree matching; + matching.termNode(CardFilter::AttrTag, CardFilter::TypeAnd, "ramp"); + ASSERT_TRUE(matching.acceptsCard(tagged, CardSearchLanguage{})); + + FilterTree partial; + partial.termNode(CardFilter::AttrTag, CardFilter::TypeAnd, "ram"); + ASSERT_FALSE(partial.acceptsCard(tagged, CardSearchLanguage{})); + + FilterTree missing; + missing.termNode(CardFilter::AttrTag, CardFilter::TypeAnd, "squirrel"); + ASSERT_FALSE(missing.acceptsCard(tagged, CardSearchLanguage{})); +} + TEST_F(CardQuery, TagQueryFalseWhenCardHasNoTags) { ASSERT_FALSE(FilterString("tags:ramp").check(cat));