diff --git a/SabreTools.Core/Filter/Enums.cs b/SabreTools.Core/Filter/Enums.cs
new file mode 100644
index 00000000..7e893737
--- /dev/null
+++ b/SabreTools.Core/Filter/Enums.cs
@@ -0,0 +1,41 @@
+namespace SabreTools.Core.Filter
+{
+ ///
+ /// Determines how a filter group should be applied
+ ///
+ public enum GroupType
+ {
+ ///
+ /// Default value, does nothing
+ ///
+ NONE,
+
+ ///
+ /// All must pass for the group to pass
+ ///
+ AND,
+
+ ///
+ /// Any must pass for the group to pass
+ ///
+ OR,
+ }
+
+ ///
+ /// Determines what operation is being done
+ ///
+ public enum Operation
+ {
+ ///
+ /// Default value, does nothing
+ ///
+ NONE,
+
+ Equals,
+ NotEquals,
+ GreaterThan,
+ GreaterThanOrEqual,
+ LessThan,
+ LessThanOrEqual,
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Core/Filter/FilterGroup.cs b/SabreTools.Core/Filter/FilterGroup.cs
new file mode 100644
index 00000000..fa36765d
--- /dev/null
+++ b/SabreTools.Core/Filter/FilterGroup.cs
@@ -0,0 +1,136 @@
+using System.Collections.Generic;
+using SabreTools.Models.Metadata;
+
+namespace SabreTools.Core.Filter
+{
+ ///
+ /// Represents a set of filters and groups
+ ///
+ public class FilterGroup
+ {
+ ///
+ /// All standalone filters in the group
+ ///
+ public readonly List Subfilters = [];
+
+ ///
+ /// All filter groups contained in the group
+ ///
+ public readonly List Subgroups = [];
+
+ ///
+ /// How to apply the group filters
+ ///
+ public readonly GroupType GroupType;
+
+ public FilterGroup(GroupType groupType)
+ {
+ GroupType = groupType;
+ }
+
+ public FilterGroup(FilterObject[] filters, GroupType groupType)
+ {
+ Subfilters.AddRange(filters);
+ GroupType = groupType;
+ }
+
+ public FilterGroup(FilterGroup[] groups, GroupType groupType)
+ {
+ Subgroups.AddRange(groups);
+ GroupType = groupType;
+ }
+
+ public FilterGroup(FilterObject[] filters, FilterGroup[] groups, GroupType groupType)
+ {
+ Subfilters.AddRange(filters);
+ Subgroups.AddRange(groups);
+ GroupType = groupType;
+ }
+
+ #region Matching
+
+ ///
+ /// Determine if a DictionaryBase object matches the group
+ ///
+ public bool Matches(DictionaryBase dictionaryBase)
+ {
+ return GroupType switch
+ {
+ GroupType.AND => MatchesAnd(dictionaryBase),
+ GroupType.OR => MatchesOr(dictionaryBase),
+ _ => false,
+ };
+ }
+
+ ///
+ /// Determines if a value matches all filters
+ ///
+ private bool MatchesAnd(DictionaryBase dictionaryBase)
+ {
+ // Run standalone filters
+ foreach (var filter in Subfilters)
+ {
+ // One failed match fails the group
+ if (!filter.Matches(dictionaryBase))
+ return false;
+ }
+
+ // Run filter subgroups
+ foreach (var group in Subgroups)
+ {
+ // One failed match fails the group
+ if (!group.Matches(dictionaryBase))
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Determines if a value matches any filters
+ ///
+ private bool MatchesOr(DictionaryBase dictionaryBase)
+ {
+ // Run standalone filters
+ foreach (var filter in Subfilters)
+ {
+ // One successful match passes the group
+ if (filter.Matches(dictionaryBase))
+ return true;
+ }
+
+ // Run filter subgroups
+ foreach (var group in Subgroups)
+ {
+ // One successful match passes the group
+ if (group.Matches(dictionaryBase))
+ return true;
+ }
+
+ return false;
+ }
+
+ #endregion
+
+ #region Helpers
+
+ ///
+ /// Derive a group type from the input string, if possible
+ ///
+ private static GroupType GetGroupType(string? groupType)
+ {
+ return groupType?.ToLowerInvariant() switch
+ {
+ "&" => GroupType.AND,
+ "&&" => GroupType.AND,
+
+ "|" => GroupType.OR,
+ "||" => GroupType.OR,
+
+ _ => GroupType.NONE,
+ };
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Core/Filter/FilterKey.cs b/SabreTools.Core/Filter/FilterKey.cs
index e7321ee5..f7e1eefe 100644
--- a/SabreTools.Core/Filter/FilterKey.cs
+++ b/SabreTools.Core/Filter/FilterKey.cs
@@ -210,6 +210,5 @@ namespace SabreTools.Core.Filter
string? constantMatch = Array.Find(constants, c => string.Equals(c, localFieldName, StringComparison.OrdinalIgnoreCase));
return constantMatch;
}
-
}
}
\ No newline at end of file
diff --git a/SabreTools.Core/Filter/Operation.cs b/SabreTools.Core/Filter/Operation.cs
deleted file mode 100644
index 2de1cd82..00000000
--- a/SabreTools.Core/Filter/Operation.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace SabreTools.Core.Filter
-{
- ///
- /// Determines what operation is being done
- ///
- public enum Operation
- {
- ///
- /// Default value, does nothing
- ///
- NONE,
-
- Equals,
- NotEquals,
- GreaterThan,
- GreaterThanOrEqual,
- LessThan,
- LessThanOrEqual,
- }
-}
\ No newline at end of file