diff --git a/SabreTools.Filter/FilterParser.cs b/SabreTools.Filter/FilterParser.cs new file mode 100644 index 00000000..e71d1073 --- /dev/null +++ b/SabreTools.Filter/FilterParser.cs @@ -0,0 +1,141 @@ +using System; +using System.Linq; +using System.Reflection; +using SabreTools.Models.Internal; + +namespace SabreTools.Filter +{ + public static class FilterParser + { + /// + /// Parse a filter ID string into the item name and field name, if possible + /// + /// TODO: Have validation of fields done automatically + public static (string?, string?) ParseFilterId(string filterId) + { + // If we don't have a filter ID, we can't do anything + if (string.IsNullOrWhiteSpace(filterId)) + return (null, null); + + // If we only have one part, we can't do anything + string[] splitFilter = filterId.Split('.'); + if (splitFilter.Length != 2) + return (null, null); + + // Return santized values based on the split ID + return splitFilter[0].ToLowerInvariant() switch + { + // Header + "header" => ParseHeaderFilterId(splitFilter), + + // Machine + "game" => ParseMachineFilterId(splitFilter), + "machine" => ParseMachineFilterId(splitFilter), + "resource" => ParseMachineFilterId(splitFilter), + "set" => ParseMachineFilterId(splitFilter), + + // DatItem + // TODO: Implement parsers for all item types + _ => (null, null), + }; + } + + /// + /// Parse and validate header fields + /// + private static (string?, string?) ParseHeaderFilterId(string[] filterId) + { + // Get the set of constants + var constants = GetConstants(typeof(Header)); + if (constants == null) + return (null, null); + + // Get if there's a match to the constant + string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, filterId[1], StringComparison.OrdinalIgnoreCase)); + if (constantMatch == null) + return (null, null); + + // Filter out fields that can't be matched on + return constantMatch switch + { + Header.CanOpenKey => (null, null), + Header.ImagesKey => (null, null), + Header.InfosKey => (null, null), + Header.NewDatKey => (null, null), + Header.SearchKey => (null, null), + _ => (MetadataFile.HeaderKey, constantMatch), + }; + } + + /// + /// Parse and validate machine/game fields + /// + private static (string?, string?) ParseMachineFilterId(string[] filterId) + { + // Get the set of constants + var constants = GetConstants(typeof(Header)); + if (constants == null) + return (null, null); + + // Get if there's a match to the constant + string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, filterId[1], StringComparison.OrdinalIgnoreCase)); + if (constantMatch == null) + return (null, null); + + // Filter out fields that can't be matched on + return constantMatch switch + { + Machine.AdjusterKey => (null, null), + Machine.ArchiveKey => (null, null), + Machine.BiosSetKey => (null, null), + Machine.ChipKey => (null, null), + Machine.ConfigurationKey => (null, null), + Machine.ControlKey => (null, null), + Machine.DeviceKey => (null, null), + Machine.DeviceRefKey => (null, null), + Machine.DipSwitchKey => (null, null), + Machine.DiskKey => (null, null), + Machine.DisplayKey => (null, null), + Machine.DriverKey => (null, null), + Machine.DumpKey => (null, null), + Machine.FeatureKey => (null, null), + Machine.InfoKey => (null, null), + Machine.InputKey => (null, null), + Machine.MediaKey => (null, null), + Machine.PartKey => (null, null), + Machine.PortKey => (null, null), + Machine.RamOptionKey => (null, null), + Machine.ReleaseKey => (null, null), + Machine.RomKey => (null, null), + Machine.SampleKey => (null, null), + Machine.SharedFeatKey => (null, null), + Machine.SlotKey => (null, null), + Machine.SoftwareListKey => (null, null), + Machine.SoundKey => (null, null), + Machine.TruripKey => (null, null), + Machine.VideoKey => (null, null), + _ => (MetadataFile.MachineKey, constantMatch), + }; + } + + /// + /// Get constant values for the given type, if possible + /// + /// TODO: Create a NoFilter attribute for non-mappable filter IDs + private static string[]? GetConstants(Type? type) + { + if (type == null) + return null; + + var fields = type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy); + if (fields == null) + return null; + + return fields + .Where(f => f.IsLiteral && !f.IsInitOnly) + .Select(f => f.GetRawConstantValue() as string) + .Where(v => v != null) + .ToArray()!; + } + } +} diff --git a/SabreTools.Filter/SabreTools.Filter.csproj b/SabreTools.Filter/SabreTools.Filter.csproj new file mode 100644 index 00000000..abcdc453 --- /dev/null +++ b/SabreTools.Filter/SabreTools.Filter.csproj @@ -0,0 +1,12 @@ + + + + net6.0;net7.0 + enable + + + + + + + diff --git a/SabreTools.sln b/SabreTools.sln index e0d09f3b..eb6964ec 100644 --- a/SabreTools.sln +++ b/SabreTools.sln @@ -42,6 +42,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Models", "SabreT EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Serialization", "SabreTools.Serialization\SabreTools.Serialization.csproj", "{E610285C-10E6-4724-B22C-4EADC11789B1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Filter", "SabreTools.Filter\SabreTools.Filter.csproj", "{2A7A27A9-5FB9-4F6D-88F3-67120668A029}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -178,6 +180,14 @@ Global {E610285C-10E6-4724-B22C-4EADC11789B1}.Release|Any CPU.Build.0 = Release|Any CPU {E610285C-10E6-4724-B22C-4EADC11789B1}.Release|x64.ActiveCfg = Release|Any CPU {E610285C-10E6-4724-B22C-4EADC11789B1}.Release|x64.Build.0 = Release|Any CPU + {2A7A27A9-5FB9-4F6D-88F3-67120668A029}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A7A27A9-5FB9-4F6D-88F3-67120668A029}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A7A27A9-5FB9-4F6D-88F3-67120668A029}.Debug|x64.ActiveCfg = Debug|Any CPU + {2A7A27A9-5FB9-4F6D-88F3-67120668A029}.Debug|x64.Build.0 = Debug|Any CPU + {2A7A27A9-5FB9-4F6D-88F3-67120668A029}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A7A27A9-5FB9-4F6D-88F3-67120668A029}.Release|Any CPU.Build.0 = Release|Any CPU + {2A7A27A9-5FB9-4F6D-88F3-67120668A029}.Release|x64.ActiveCfg = Release|Any CPU + {2A7A27A9-5FB9-4F6D-88F3-67120668A029}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE