using System; using System.Linq; using System.Text.RegularExpressions; using SabreTools.Core.Tools; using SabreTools.Models.Internal; namespace SabreTools.Filter { /// /// Represents a single filtering object /// public class FilterObject { /// /// Key name for the filter /// public string[] Key { get; init; } /// /// Value to match in the filter /// public string? Value { get; init; } /// /// Operation on how to match the filter /// public Operation Operation { get; init; } public FilterObject(string filterString) { (string? keyItem, Operation operation, string? value) = SplitFilterString(filterString); if (keyItem == null) throw new ArgumentOutOfRangeException(nameof(filterString)); (string? itemName, string? fieldName) = FilterParser.ParseFilterId(keyItem); if (itemName == null || fieldName == null) throw new ArgumentOutOfRangeException(nameof(filterString)); this.Key = new string[] { itemName, fieldName }; this.Value = value; this.Operation = operation; } public FilterObject(string itemField, string? value, string? operation) { (string? itemName, string? fieldName) = FilterParser.ParseFilterId(itemField); if (itemName == null || fieldName == null) throw new ArgumentOutOfRangeException(nameof(value)); this.Key = new string[] { itemName, fieldName }; this.Value = value; this.Operation = GetOperation(operation); } public FilterObject(string itemField, string? value, Operation operation) { (string? itemName, string? fieldName) = FilterParser.ParseFilterId(itemField); if (itemName == null || fieldName == null) throw new ArgumentOutOfRangeException(nameof(value)); this.Key = new string[] { itemName, fieldName }; this.Value = value; this.Operation = operation; } #region Matching /// /// Determine if a DictionaryBase object matches the key and value /// public bool Matches(DictionaryBase dictionaryBase) { // TODO: Add validation of dictionary base type from this.Key[0] return this.Operation switch { Operation.Equals => MatchesEqual(dictionaryBase), Operation.NotEquals => MatchesNotEqual(dictionaryBase), Operation.GreaterThan => MatchesGreaterThan(dictionaryBase), Operation.GreaterThanOrEqual => MatchesGreaterThanOrEqual(dictionaryBase), Operation.LessThan => MatchesLessThan(dictionaryBase), Operation.LessThanOrEqual => MatchesLessThanOrEqual(dictionaryBase), _ => false, }; } /// /// Determines if a value matches exactly /// /// /// TODO: Add regex matching to this method /// private bool MatchesEqual(DictionaryBase dictionaryBase) { if (!dictionaryBase.ContainsKey(this.Key[1])) return this.Value == null; string? checkValue = dictionaryBase.ReadString(this.Key[1]); if (NumberHelper.IsNumeric(checkValue) && NumberHelper.IsNumeric(this.Value)) { long? checkValueLong = NumberHelper.ConvertToInt64(checkValue); long? matchValueLong = NumberHelper.ConvertToInt64(checkValue); if (checkValueLong != null && matchValueLong != null) return checkValueLong == matchValueLong; double? checkValueDouble = NumberHelper.ConvertToDouble(checkValue); double? matchValueDouble = NumberHelper.ConvertToDouble(checkValue); if (checkValueDouble != null && matchValueDouble != null) return checkValueDouble == matchValueDouble; } return checkValue == this.Value; } /// /// Determines if a value does not match exactly /// /// /// TODO: Add regex matching to this method /// private bool MatchesNotEqual(DictionaryBase dictionaryBase) { if (!dictionaryBase.ContainsKey(this.Key[1])) return this.Value != null; string? checkValue = dictionaryBase.ReadString(this.Key[1]); if (NumberHelper.IsNumeric(checkValue) && NumberHelper.IsNumeric(this.Value)) { long? checkValueLong = NumberHelper.ConvertToInt64(checkValue); long? matchValueLong = NumberHelper.ConvertToInt64(checkValue); if (checkValueLong != null && matchValueLong != null) return checkValueLong != matchValueLong; double? checkValueDouble = NumberHelper.ConvertToDouble(checkValue); double? matchValueDouble = NumberHelper.ConvertToDouble(checkValue); if (checkValueDouble != null && matchValueDouble != null) return checkValueDouble != matchValueDouble; } return checkValue != this.Value; } /// /// Determines if a value is strictly greater than /// private bool MatchesGreaterThan(DictionaryBase dictionaryBase) { if (!dictionaryBase.ContainsKey(this.Key[1])) return false; long? checkLongValue = dictionaryBase.ReadLong(this.Key[1]); if (checkLongValue != null) { long? matchValue = NumberHelper.ConvertToInt64(this.Value); if (matchValue == null) return false; return checkLongValue > matchValue; } double? checkDoubleValue = dictionaryBase.ReadDouble(this.Key[1]); if (checkDoubleValue != null) { double? matchValue = NumberHelper.ConvertToDouble(this.Value); if (matchValue == null) return false; return checkDoubleValue > matchValue; } return false; } /// /// Determines if a value is greater than or equal /// private bool MatchesGreaterThanOrEqual(DictionaryBase dictionaryBase) { if (!dictionaryBase.ContainsKey(this.Key[1])) return false; long? checkLongValue = dictionaryBase.ReadLong(this.Key[1]); if (checkLongValue != null) { long? matchValue = NumberHelper.ConvertToInt64(this.Value); if (matchValue == null) return false; return checkLongValue >= matchValue; } double? checkDoubleValue = dictionaryBase.ReadDouble(this.Key[1]); if (checkDoubleValue != null) { double? matchValue = NumberHelper.ConvertToDouble(this.Value); if (matchValue == null) return false; return checkDoubleValue >= matchValue; } return false; } /// /// Determines if a value is strictly less than /// private bool MatchesLessThan(DictionaryBase dictionaryBase) { if (!dictionaryBase.ContainsKey(this.Key[1])) return false; long? checkLongValue = dictionaryBase.ReadLong(this.Key[1]); if (checkLongValue != null) { long? matchValue = NumberHelper.ConvertToInt64(this.Value); if (matchValue == null) return false; return checkLongValue < matchValue; } double? checkDoubleValue = dictionaryBase.ReadDouble(this.Key[1]); if (checkDoubleValue != null) { double? matchValue = NumberHelper.ConvertToDouble(this.Value); if (matchValue == null) return false; return checkDoubleValue < matchValue; } return false; } /// /// Determines if a value is less than or equal /// private bool MatchesLessThanOrEqual(DictionaryBase dictionaryBase) { if (!dictionaryBase.ContainsKey(this.Key[1])) return false; long? checkLongValue = dictionaryBase.ReadLong(this.Key[1]); if (checkLongValue != null) { long? matchValue = NumberHelper.ConvertToInt64(this.Value); if (matchValue == null) return false; return checkLongValue <= matchValue; } double? checkDoubleValue = dictionaryBase.ReadDouble(this.Key[1]); if (checkDoubleValue != null) { double? matchValue = NumberHelper.ConvertToDouble(this.Value); if (matchValue == null) return false; return checkDoubleValue <= matchValue; } return false; } #endregion #region Helpers /// /// Derive an operation from the input string, if possible /// private static Operation GetOperation(string? operation) { return operation?.ToLowerInvariant() switch { "=" => Operation.Equals, "==" => Operation.Equals, ":" => Operation.Equals, "::" => Operation.Equals, "!" => Operation.NotEquals, "!=" => Operation.NotEquals, "!:" => Operation.NotEquals, ">" => Operation.GreaterThan, ">=" => Operation.GreaterThanOrEqual, "<" => Operation.LessThan, "<=" => Operation.LessThanOrEqual, _ => Operation.NONE, }; } /// /// Derive a key, operation, and value from the input string, if possible /// private static (string?, Operation, string?) SplitFilterString(string? filterString) { if (filterString == null) return (null, Operation.NONE, null); // Split the string using regex var match = Regex.Match(filterString, @"^(?[a-zA-Z.]+)(?[=!:><]{1,2})(?.*)$"); if (!match.Success) return (null, Operation.NONE, null); string itemField = match.Groups["itemField"].Value; Operation operation = GetOperation(match.Groups["operation"].Value); string value = match.Groups["value"].Value; return (itemField, operation, value); } #endregion } }