using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace SabreTools.Metadata.Filter
{
///
/// Represents a set of filters and groups
///
public class FilterGroup
{
///
/// How to apply the group filters
///
public readonly GroupType GroupType;
///
/// All standalone filters in the group
///
private readonly List _subfilters = [];
///
/// All filter groups contained in the group
///
private readonly List _subgroups = [];
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 Accessors
///
/// Add a FilterObject to the set
///
public void AddFilter(FilterObject filter) => _subfilters.Add(filter);
///
/// Add a FilterGroup to the set
///
public void AddGroup(FilterGroup group) => _subgroups.Add(group);
#endregion
#region Matching
///
/// Determine if a object matches the group
///
public bool Matches(object obj)
{
return GroupType switch
{
GroupType.AND => MatchesAnd(obj),
GroupType.OR => MatchesOr(obj),
GroupType.NONE => false,
_ => false,
};
}
///
/// Determines if a value matches all filters
///
private bool MatchesAnd(object obj)
{
// Run standalone filters
foreach (var filter in _subfilters)
{
// One failed match fails the group
if (!filter.Matches(obj))
return false;
}
// Run filter subgroups
foreach (var group in _subgroups)
{
// One failed match fails the group
if (!group.Matches(obj))
return false;
}
return true;
}
///
/// Determines if a value matches any filters
///
private bool MatchesOr(object obj)
{
// Run standalone filters
foreach (var filter in _subfilters)
{
// One successful match passes the group
if (filter.Matches(obj))
return true;
}
// Run filter subgroups
foreach (var group in _subgroups)
{
// One successful match passes the group
if (group.Matches(obj))
return true;
}
return false;
}
#endregion
#region Helpers
#pragma warning disable IDE0051
///
/// 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,
};
}
#pragma warning restore IDE0051
#pragma warning disable IDE0051
///
/// Parse an input string into a filter group
///
private static void Parse(string? input)
{
// Tokenize the string
string[] tokens = Tokenize(input);
if (tokens.Length == 0)
return;
// Loop through the tokens and parse
for (int i = 0; i < tokens.Length; i++)
{
// TODO: Implement parsing
// - Opening parenthesis means a new group
// - Closing parenthesis means finalize group and return it
// - Current starting and ending with a parenthesis strips them off
// - Unbalanced parenthesis can only be found on parse
// - Failed parsing of FilterObjects(?)
// - Invalid FilterObjects(?)
}
}
#pragma warning restore IDE0051
///
/// Tokenize an input string for parsing
///
private static string[] Tokenize(string? input)
{
// Null inputs are ignored
if (input is null)
return [];
// Split the string into parseable pieces
// - Left and right parenthesis are separate
// - Operators & and | are separate
// - Key-value pairs are enforced for statements
// - Numbers can be a value without quotes
// - All other values require quotes
return Regex.Split(input, @"(\(|\)|[&|]{1,2}|[^\s()""]+[:!=]\d+|[^\s()""]+[:!=]{1,2}""[^""]*"")", RegexOptions.Compiled);
}
#endregion
}
}