diff --git a/SabreTools.Filter/FilterObject.cs b/SabreTools.Filter/FilterObject.cs
new file mode 100644
index 00000000..1478a593
--- /dev/null
+++ b/SabreTools.Filter/FilterObject.cs
@@ -0,0 +1,256 @@
+using System;
+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 keyValue, string? operation)
+ {
+ (string? itemName, string? fieldName) = FilterParser.ParseFilterId(keyValue);
+ if (itemName == null)
+ throw new ArgumentOutOfRangeException(nameof(keyValue));
+
+ this.Key = itemName;
+ this.Value = fieldName;
+ this.Operation = GetOperation(operation);
+ }
+
+ public FilterObject(string keyValue, Operation operation)
+ {
+ (string? itemName, string? fieldName) = FilterParser.ParseFilterId(keyValue);
+ if (itemName == null)
+ throw new ArgumentOutOfRangeException(nameof(keyValue));
+
+ this.Key = itemName;
+ this.Value = fieldName;
+ this.Operation = operation;
+ }
+
+ public FilterObject(string key, string? value, string? operation)
+ {
+ (string? itemName, string? fieldName) = FilterParser.ParseFilterId(key, value);
+ if (itemName == null)
+ throw new ArgumentOutOfRangeException(nameof(fieldName));
+
+ this.Key = itemName;
+ this.Value = fieldName;
+ this.Operation = GetOperation(operation);
+ }
+
+ public FilterObject(string key, string? value, Operation operation)
+ {
+ (string? itemName, string? fieldName) = FilterParser.ParseFilterId(key, value);
+ if (itemName == null)
+ throw new ArgumentOutOfRangeException(nameof(fieldName));
+
+ this.Key = itemName;
+ this.Value = fieldName;
+ this.Operation = operation;
+ }
+
+ ///
+ /// Derive an operation from the input string, if possible
+ ///
+ private static Operation GetOperation(string? operation)
+ {
+ return operation?.ToLowerInvariant() switch
+ {
+ "=" => Operation.Equals,
+ "==" => Operation.Equals,
+
+ "!" => Operation.NotEquals,
+ "!=" => Operation.NotEquals,
+
+ ">" => Operation.GreaterThan,
+ ">=" => Operation.GreaterThanOrEqual,
+
+ "<" => Operation.LessThan,
+ "<=" => Operation.LessThanOrEqual,
+
+ _ => Operation.NONE,
+ };
+ }
+
+ #region Matching
+
+ ///
+ /// Determine if a DictionaryBase object matches the key and value
+ ///
+ public bool Matches(DictionaryBase dictionaryBase)
+ {
+ 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))
+ return this.Value == null;
+
+ string? checkValue = dictionaryBase.ReadString(this.Key);
+ 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))
+ return this.Value != null;
+
+ string? checkValue = dictionaryBase.ReadString(this.Key);
+ return checkValue != this.Value;
+ }
+
+ ///
+ /// Determines if a value is strictly greater than
+ ///
+ private bool MatchesGreaterThan(DictionaryBase dictionaryBase)
+ {
+ if (!dictionaryBase.ContainsKey(this.Key))
+ return false;
+
+ long? checkLongValue = dictionaryBase.ReadLong(this.Key);
+ if (checkLongValue != null)
+ {
+ if (!long.TryParse(this.Value, out long matchValue))
+ return false;
+
+ return checkLongValue > matchValue;
+ }
+
+ double? checkDoubleValue = dictionaryBase.ReadDouble(this.Key);
+ if (checkDoubleValue != null)
+ {
+ if (!double.TryParse(this.Value, out double matchValue))
+ 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))
+ return false;
+
+ long? checkLongValue = dictionaryBase.ReadLong(this.Key);
+ if (checkLongValue != null)
+ {
+ if (!long.TryParse(this.Value, out long matchValue))
+ return false;
+
+ return checkLongValue >= matchValue;
+ }
+
+ double? checkDoubleValue = dictionaryBase.ReadDouble(this.Key);
+ if (checkDoubleValue != null)
+ {
+ if (!double.TryParse(this.Value, out double matchValue))
+ 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))
+ return false;
+
+ long? checkLongValue = dictionaryBase.ReadLong(this.Key);
+ if (checkLongValue != null)
+ {
+ if (!long.TryParse(this.Value, out long matchValue))
+ return false;
+
+ return checkLongValue < matchValue;
+ }
+
+ double? checkDoubleValue = dictionaryBase.ReadDouble(this.Key);
+ if (checkDoubleValue != null)
+ {
+ if (!double.TryParse(this.Value, out double matchValue))
+ 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))
+ return false;
+
+ long? checkLongValue = dictionaryBase.ReadLong(this.Key);
+ if (checkLongValue != null)
+ {
+ if (!long.TryParse(this.Value, out long matchValue))
+ return false;
+
+ return checkLongValue <= matchValue;
+ }
+
+ double? checkDoubleValue = dictionaryBase.ReadDouble(this.Key);
+ if (checkDoubleValue != null)
+ {
+ if (!double.TryParse(this.Value, out double matchValue))
+ return false;
+
+ return checkDoubleValue <= matchValue;
+ }
+
+ return false;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Filter/FilterParser.cs b/SabreTools.Filter/FilterParser.cs
index dfb8f493..c6a74a6f 100644
--- a/SabreTools.Filter/FilterParser.cs
+++ b/SabreTools.Filter/FilterParser.cs
@@ -12,38 +12,50 @@ namespace SabreTools.Filter
///
/// Parse a filter ID string into the item name and field name, if possible
///
- public static (string?, string?) ParseFilterId(string filterId)
+ public static (string?, string?) ParseFilterId(string itemFieldString)
{
// If we don't have a filter ID, we can't do anything
- if (string.IsNullOrWhiteSpace(filterId))
+ if (string.IsNullOrWhiteSpace(itemFieldString))
return (null, null);
// If we only have one part, we can't do anything
- string[] splitFilter = filterId.Split('.');
+ string[] splitFilter = itemFieldString.Split('.');
if (splitFilter.Length != 2)
return (null, null);
+ return ParseFilterId(splitFilter[0], splitFilter[1]);
+ }
+
+ ///
+ /// Parse a filter ID string into the item name and field name, if possible
+ ///
+ public static (string?, string?) ParseFilterId(string itemName, string? fieldName)
+ {
+ // If we don't have a filter ID, we can't do anything
+ if (string.IsNullOrWhiteSpace(itemName) || string.IsNullOrWhiteSpace(fieldName))
+ return (null, null);
+
// Return santized values based on the split ID
- return splitFilter[0].ToLowerInvariant() switch
+ return itemName.ToLowerInvariant() switch
{
// Header
- "header" => ParseHeaderFilterId(splitFilter),
+ "header" => ParseHeaderFilterId(fieldName),
// Machine
- "game" => ParseMachineFilterId(splitFilter),
- "machine" => ParseMachineFilterId(splitFilter),
- "resource" => ParseMachineFilterId(splitFilter),
- "set" => ParseMachineFilterId(splitFilter),
+ "game" => ParseMachineFilterId(fieldName),
+ "machine" => ParseMachineFilterId(fieldName),
+ "resource" => ParseMachineFilterId(fieldName),
+ "set" => ParseMachineFilterId(fieldName),
// DatItem
- _ => ParseDatItemFilterId(splitFilter),
+ _ => ParseDatItemFilterId(itemName, fieldName),
};
}
///
/// Parse and validate header fields
///
- private static (string?, string?) ParseHeaderFilterId(string[] filterId)
+ private static (string?, string?) ParseHeaderFilterId(string fieldName)
{
// Get the set of constants
var constants = GetConstants(typeof(Header));
@@ -51,7 +63,7 @@ namespace SabreTools.Filter
return (null, null);
// Get if there's a match to the constant
- string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, filterId[1], StringComparison.OrdinalIgnoreCase));
+ string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, fieldName, StringComparison.OrdinalIgnoreCase));
if (constantMatch == null)
return (null, null);
@@ -62,7 +74,7 @@ namespace SabreTools.Filter
///
/// Parse and validate machine/game fields
///
- private static (string?, string?) ParseMachineFilterId(string[] filterId)
+ private static (string?, string?) ParseMachineFilterId(string fieldName)
{
// Get the set of constants
var constants = GetConstants(typeof(Machine));
@@ -70,7 +82,7 @@ namespace SabreTools.Filter
return (null, null);
// Get if there's a match to the constant
- string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, filterId[1], StringComparison.OrdinalIgnoreCase));
+ string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, fieldName, StringComparison.OrdinalIgnoreCase));
if (constantMatch == null)
return (null, null);
@@ -81,10 +93,10 @@ namespace SabreTools.Filter
///
/// Parse and validate item fields
///
- private static (string?, string?) ParseDatItemFilterId(string[] filterId)
+ private static (string?, string?) ParseDatItemFilterId(string itemName, string fieldName)
{
// Get the correct item type
- var itemType = GetDatItemType(filterId[0].ToLowerInvariant());
+ var itemType = GetDatItemType(itemName.ToLowerInvariant());
if (itemType == null)
return (null, null);
@@ -94,12 +106,12 @@ namespace SabreTools.Filter
return (null, null);
// Get if there's a match to the constant
- string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, filterId[1], StringComparison.OrdinalIgnoreCase));
+ string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, fieldName, StringComparison.OrdinalIgnoreCase));
if (constantMatch == null)
return (null, null);
// Return the sanitized ID
- return (filterId[0].ToLowerInvariant(), constantMatch);
+ return (itemName.ToLowerInvariant(), constantMatch);
}
///
diff --git a/SabreTools.Filter/Operation.cs b/SabreTools.Filter/Operation.cs
new file mode 100644
index 00000000..d9adaed5
--- /dev/null
+++ b/SabreTools.Filter/Operation.cs
@@ -0,0 +1,20 @@
+namespace SabreTools.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
diff --git a/SabreTools.Models/Internal/DictionaryBase.cs b/SabreTools.Models/Internal/DictionaryBase.cs
index b7c445d6..44f677b6 100644
--- a/SabreTools.Models/Internal/DictionaryBase.cs
+++ b/SabreTools.Models/Internal/DictionaryBase.cs
@@ -12,9 +12,7 @@ namespace SabreTools.Models.Internal
///
public T? Read(string key)
{
- if (string.IsNullOrWhiteSpace(key))
- return default;
- if (!ContainsKey(key))
+ if (!ValidateKey(key))
return default;
return (T?)this[key];
}
@@ -24,6 +22,9 @@ namespace SabreTools.Models.Internal
///
public bool? ReadBool(string key)
{
+ if (!ValidateKey(key))
+ return null;
+
bool? asBool = Read(key);
if (asBool != null)
return asBool;
@@ -44,6 +45,9 @@ namespace SabreTools.Models.Internal
///
public double? ReadDouble(string key)
{
+ if (!ValidateKey(key))
+ return null;
+
double? asDouble = Read(key);
if (asDouble != null)
return asDouble;
@@ -60,6 +64,9 @@ namespace SabreTools.Models.Internal
///
public long? ReadLong(string key)
{
+ if (!ValidateKey(key))
+ return null;
+
long? asLong = Read(key);
if (asLong != null)
return asLong;
@@ -76,6 +83,9 @@ namespace SabreTools.Models.Internal
///
public string? ReadString(string key)
{
+ if (!ValidateKey(key))
+ return null;
+
string? asString = Read(key);
if (asString != null)
return asString;
@@ -84,7 +94,7 @@ namespace SabreTools.Models.Internal
if (asArray != null)
return string.Join(',', asArray);
- return null;
+ return this[key]!.ToString();
}
///
@@ -92,6 +102,9 @@ namespace SabreTools.Models.Internal
///
public string[]? ReadStringArray(string key)
{
+ if (!ValidateKey(key))
+ return null;
+
string[]? asArray = Read(key);
if (asArray != null)
return asArray;
@@ -100,7 +113,26 @@ namespace SabreTools.Models.Internal
if (asString != null)
return new string[] { asString };
+ asString = this[key]!.ToString();
+ if (asString != null)
+ return new string[] { asString };
+
return null;
}
+
+ ///
+ /// Check if a key is valid
+ ///
+ private bool ValidateKey(string key)
+ {
+ if (string.IsNullOrWhiteSpace(key))
+ return false;
+ else if (!ContainsKey(key))
+ return false;
+ else if (this[key] == null)
+ return false;
+
+ return true;
+ }
}
}
\ No newline at end of file