diff --git a/SabreTools.Core/Filter/ExtraIniItem.cs b/SabreTools.Core/Filter/ExtraIniItem.cs index f1adf758..0aad3d12 100644 --- a/SabreTools.Core/Filter/ExtraIniItem.cs +++ b/SabreTools.Core/Filter/ExtraIniItem.cs @@ -24,6 +24,13 @@ namespace SabreTools.Core.Filter #region Constructors + public ExtraIniItem(string key, string ini) + { + Key = new FilterKey(key); + if (!PopulateFromFile(ini)) + Mappings.Clear(); + } + public ExtraIniItem(string itemName, string fieldName, string ini) { Key = new FilterKey(itemName, fieldName); diff --git a/SabreTools.Core/Filter/FilterKey.cs b/SabreTools.Core/Filter/FilterKey.cs index f6a412f1..94c99805 100644 --- a/SabreTools.Core/Filter/FilterKey.cs +++ b/SabreTools.Core/Filter/FilterKey.cs @@ -1,3 +1,8 @@ +using System; +using System.Linq; +using SabreTools.Core.Tools; +using SabreTools.Models.Metadata; + namespace SabreTools.Core.Filter { /// @@ -16,15 +21,196 @@ namespace SabreTools.Core.Filter public readonly string FieldName; /// - /// Discrete value constructor + /// Validating combined key constructor + /// + public FilterKey(string? key) + { + if (!ParseFilterId(key, out string itemName, out string fieldName)) + throw new ArgumentException(nameof(key)); + + ItemName = itemName; + FieldName = fieldName; + } + + /// + /// Validating discrete value constructor /// public FilterKey(string itemName, string fieldName) { + if (!ParseFilterId(ref itemName, ref fieldName)) + throw new ArgumentException(nameof(itemName)); + ItemName = itemName; FieldName = fieldName; } /// public override string ToString() => $"{ItemName}.{FieldName}"; + + /// + /// Parse a filter ID string into the item name and field name, if possible + /// + private static bool ParseFilterId(string? itemFieldString, out string itemName, out string fieldName) + { + // Set default values + itemName = string.Empty; fieldName = string.Empty; + + // If we don't have a filter ID, we can't do anything + if (itemFieldString == null) + return false; + + // If we only have one part, we can't do anything + string[] splitFilter = itemFieldString.Split('.'); + if (splitFilter.Length != 2) + return false; + + // Set and sanitize the filter ID + itemName = splitFilter[0]; + fieldName = splitFilter[1]; + return ParseFilterId(ref itemName, ref fieldName); + } + + /// + /// Parse a filter ID string into the item name and field name, if possible + /// + private static bool ParseFilterId(ref string itemName, ref string fieldName) + { + // If we don't have a filter ID, we can't do anything + if (string.IsNullOrEmpty(itemName) || string.IsNullOrEmpty(fieldName)) + return false; + + // Return santized values based on the split ID + return itemName.ToLowerInvariant() switch + { + // Header + "header" => ParseHeaderFilterId(ref itemName, ref fieldName), + + // Machine + "game" => ParseMachineFilterId(ref itemName, ref fieldName), + "machine" => ParseMachineFilterId(ref itemName, ref fieldName), + "resource" => ParseMachineFilterId(ref itemName, ref fieldName), + "set" => ParseMachineFilterId(ref itemName, ref fieldName), + + // DatItem + "datitem" => ParseDatItemFilterId(ref itemName, ref fieldName), + "item" => ParseDatItemFilterId(ref itemName, ref fieldName), + _ => ParseDatItemFilterId(ref itemName, ref fieldName), + }; + } + + /// + /// Parse and validate header fields + /// + private static bool ParseHeaderFilterId(ref string itemName, ref string fieldName) + { + // Get the set of constants + var constants = TypeHelper.GetConstants(typeof(Header)); + if (constants == null) + return false; + + // Get if there's a match to the constant + string localFieldName = fieldName; + string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, localFieldName, StringComparison.OrdinalIgnoreCase)); + if (constantMatch == null) + return false; + + // Return the sanitized ID + itemName = MetadataFile.HeaderKey; + fieldName = constantMatch; + return true; + } + + /// + /// Parse and validate machine/game fields + /// + private static bool ParseMachineFilterId(ref string itemName, ref string fieldName) + { + // Get the set of constants + var constants = TypeHelper.GetConstants(typeof(Machine)); + if (constants == null) + return false; + + // Get if there's a match to the constant + string localFieldName = fieldName; + string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, localFieldName, StringComparison.OrdinalIgnoreCase)); + if (constantMatch == null) + return false; + + // Return the sanitized ID + itemName = MetadataFile.MachineKey; + fieldName = constantMatch; + return true; + } + + /// + /// Parse and validate item fields + /// + private static bool ParseDatItemFilterId(ref string itemName, ref string fieldName) + { + // Special case if the item name is reserved + if (string.Equals(itemName, "datitem", StringComparison.OrdinalIgnoreCase) + || string.Equals(itemName, "item", StringComparison.OrdinalIgnoreCase)) + { + // Get all item types + var itemTypes = TypeHelper.GetDatItemTypeNames(); + + // If we get any matches + string localFieldName = fieldName; + string? matchedType = itemTypes.FirstOrDefault(t => DatItemContainsField(t, localFieldName)); + if (matchedType != null) + { + // Check for a matching field + string? matchedField = GetMatchingField(matchedType, fieldName); + if (matchedField == null) + return false; + + itemName = "item"; + fieldName = matchedField; + return true; + } + } + else + { + // Check for a matching field + string? matchedField = GetMatchingField(itemName, fieldName); + if (matchedField == null) + return false; + + itemName = itemName.ToLowerInvariant(); + fieldName = matchedField; + return true; + } + + // Nothing was found + return false; + } + + /// + /// Determine if an item type contains a field + /// + private static bool DatItemContainsField(string itemName, string fieldName) + => GetMatchingField(itemName, fieldName) != null; + + /// + /// Determine if an item type contains a field + /// + private static string? GetMatchingField(string itemName, string fieldName) + { + // Get the correct item type + var itemType = TypeHelper.GetDatItemType(itemName.ToLowerInvariant()); + if (itemType == null) + return null; + + // Get the set of constants + var constants = TypeHelper.GetConstants(itemType); + if (constants == null) + return null; + + // Get if there's a match to the constant + string localFieldName = fieldName; + string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, localFieldName, StringComparison.OrdinalIgnoreCase)); + return constantMatch; + } + } } \ No newline at end of file diff --git a/SabreTools.Core/Filter/FilterObject.cs b/SabreTools.Core/Filter/FilterObject.cs index 40fef5e9..4191bf62 100644 --- a/SabreTools.Core/Filter/FilterObject.cs +++ b/SabreTools.Core/Filter/FilterObject.cs @@ -31,30 +31,21 @@ namespace SabreTools.Core.Filter if (!SplitFilterString(filterString, out var keyItem, out Operation operation, out var value)) throw new ArgumentOutOfRangeException(nameof(filterString)); - if (!FilterParser.ParseFilterId(keyItem, out string itemName, out string fieldName)) - throw new ArgumentOutOfRangeException(nameof(filterString)); - - Key = new FilterKey(itemName, fieldName); + Key = new FilterKey(keyItem); Value = value; Operation = operation; } public FilterObject(string itemField, string? value, string? operation) { - if (!FilterParser.ParseFilterId(itemField, out string itemName, out string fieldName)) - throw new ArgumentOutOfRangeException(nameof(value)); - - Key = new FilterKey(itemName, fieldName); + Key = new FilterKey(itemField); Value = value; Operation = GetOperation(operation); } public FilterObject(string itemField, string? value, Operation operation) { - if (!FilterParser.ParseFilterId(itemField, out string itemName, out string fieldName)) - throw new ArgumentOutOfRangeException(nameof(value)); - - Key = new FilterKey(itemName, fieldName); + Key = new FilterKey(itemField); Value = value; Operation = operation; } diff --git a/SabreTools.Core/Filter/FilterParser.cs b/SabreTools.Core/Filter/FilterParser.cs deleted file mode 100644 index 2f112f66..00000000 --- a/SabreTools.Core/Filter/FilterParser.cs +++ /dev/null @@ -1,175 +0,0 @@ -using System; -using System.Linq; -using SabreTools.Core.Tools; -using SabreTools.Models.Metadata; - -namespace SabreTools.Core.Filter -{ - public static class FilterParser - { - /// - /// Parse a filter ID string into the item name and field name, if possible - /// - public static bool ParseFilterId(string? itemFieldString, out string itemName, out string fieldName) - { - // Set default values - itemName = string.Empty; fieldName = string.Empty; - - // If we don't have a filter ID, we can't do anything - if (itemFieldString == null) - return false; - - // If we only have one part, we can't do anything - string[] splitFilter = itemFieldString.Split('.'); - if (splitFilter.Length != 2) - return false; - - // Set and sanitize the filter ID - itemName = splitFilter[0]; - fieldName = splitFilter[1]; - return ParseFilterId(ref itemName, ref fieldName); - } - - /// - /// Parse a filter ID string into the item name and field name, if possible - /// - public static bool ParseFilterId(ref string itemName, ref string fieldName) - { - // If we don't have a filter ID, we can't do anything - if (string.IsNullOrEmpty(itemName) || string.IsNullOrEmpty(fieldName)) - return false; - - // Return santized values based on the split ID - return itemName.ToLowerInvariant() switch - { - // Header - "header" => ParseHeaderFilterId(ref itemName, ref fieldName), - - // Machine - "game" => ParseMachineFilterId(ref itemName, ref fieldName), - "machine" => ParseMachineFilterId(ref itemName, ref fieldName), - "resource" => ParseMachineFilterId(ref itemName, ref fieldName), - "set" => ParseMachineFilterId(ref itemName, ref fieldName), - - // DatItem - "datitem" => ParseDatItemFilterId(ref itemName, ref fieldName), - "item" => ParseDatItemFilterId(ref itemName, ref fieldName), - _ => ParseDatItemFilterId(ref itemName, ref fieldName), - }; - } - - /// - /// Parse and validate header fields - /// - private static bool ParseHeaderFilterId(ref string itemName, ref string fieldName) - { - // Get the set of constants - var constants = TypeHelper.GetConstants(typeof(Header)); - if (constants == null) - return false; - - // Get if there's a match to the constant - string localFieldName = fieldName; - string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, localFieldName, StringComparison.OrdinalIgnoreCase)); - if (constantMatch == null) - return false; - - // Return the sanitized ID - itemName = MetadataFile.HeaderKey; - fieldName = constantMatch; - return true; - } - - /// - /// Parse and validate machine/game fields - /// - private static bool ParseMachineFilterId(ref string itemName, ref string fieldName) - { - // Get the set of constants - var constants = TypeHelper.GetConstants(typeof(Machine)); - if (constants == null) - return false; - - // Get if there's a match to the constant - string localFieldName = fieldName; - string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, localFieldName, StringComparison.OrdinalIgnoreCase)); - if (constantMatch == null) - return false; - - // Return the sanitized ID - itemName = MetadataFile.MachineKey; - fieldName = constantMatch; - return true; - } - - /// - /// Parse and validate item fields - /// - private static bool ParseDatItemFilterId(ref string itemName, ref string fieldName) - { - // Special case if the item name is reserved - if (string.Equals(itemName, "datitem", StringComparison.OrdinalIgnoreCase) - || string.Equals(itemName, "item", StringComparison.OrdinalIgnoreCase)) - { - // Get all item types - var itemTypes = TypeHelper.GetDatItemTypeNames(); - - // If we get any matches - string localFieldName = fieldName; - string? matchedType = itemTypes.FirstOrDefault(t => DatItemContainsField(t, localFieldName)); - if (matchedType != null) - { - // Check for a matching field - string? matchedField = GetMatchingField(matchedType, fieldName); - if (matchedField == null) - return false; - - itemName = "item"; - fieldName = matchedField; - return true; - } - } - else - { - // Check for a matching field - string? matchedField = GetMatchingField(itemName, fieldName); - if (matchedField == null) - return false; - - itemName = itemName.ToLowerInvariant(); - fieldName = matchedField; - return true; - } - - // Nothing was found - return false; - } - - /// - /// Determine if an item type contains a field - /// - private static bool DatItemContainsField(string itemName, string fieldName) - => GetMatchingField(itemName, fieldName) != null; - - /// - /// Determine if an item type contains a field - /// - private static string? GetMatchingField(string itemName, string fieldName) - { - // Get the correct item type - var itemType = TypeHelper.GetDatItemType(itemName.ToLowerInvariant()); - if (itemType == null) - return null; - - // Get the set of constants - var constants = TypeHelper.GetConstants(itemType); - if (constants == null) - return null; - - // Get if there's a match to the constant - string localFieldName = fieldName; - string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, localFieldName, StringComparison.OrdinalIgnoreCase)); - return constantMatch; - } - } -} diff --git a/SabreTools.DatFiles/Setter.cs b/SabreTools.DatFiles/Setter.cs index ef45f0f9..855145d0 100644 --- a/SabreTools.DatFiles/Setter.cs +++ b/SabreTools.DatFiles/Setter.cs @@ -109,25 +109,14 @@ namespace SabreTools.DatFiles /// Key for the remover to be set private bool SetSetter(FilterKey key, string value) { - // Split the key values for validation - string itemName = key.ItemName; - string fieldName = key.FieldName; - - // Get the parser pair out of it, if possible - if (!FilterParser.ParseFilterId(ref itemName, ref fieldName)) - return false; - - // Set the values back on the key - key = new FilterKey(itemName, fieldName); - - switch (itemName) + switch (key.ItemName) { case Models.Metadata.MetadataFile.HeaderKey: - HeaderFieldMappings[fieldName] = value; + HeaderFieldMappings[key.FieldName] = value; return true; case Models.Metadata.MetadataFile.MachineKey: - MachineFieldMappings[fieldName] = value; + MachineFieldMappings[key.FieldName] = value; return true; default: diff --git a/SabreTools.Filtering/ExtraIni.cs b/SabreTools.Filtering/ExtraIni.cs index ece47577..335c74e8 100644 --- a/SabreTools.Filtering/ExtraIni.cs +++ b/SabreTools.Filtering/ExtraIni.cs @@ -66,8 +66,7 @@ namespace SabreTools.Filtering string fieldString = inputTrimmed.Split(':')[0].ToLowerInvariant().Trim('"', ' ', '\t'); string fileString = inputTrimmed.Substring(fieldString.Length + 1).Trim('"', ' ', '\t'); - FilterParser.ParseFilterId(fieldString, out string itemName, out string fieldName); - var item = new ExtraIniItem(itemName, fieldName, fileString); + var item = new ExtraIniItem(fieldString, fileString); if (item.Mappings.Count > 0) Items.Add(item); } diff --git a/SabreTools.Filtering/Remover.cs b/SabreTools.Filtering/Remover.cs index 506cb1c4..a4900d8e 100644 --- a/SabreTools.Filtering/Remover.cs +++ b/SabreTools.Filtering/Remover.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Compress.Support.Filters; using SabreTools.Core.Filter; using SabreTools.DatFiles; using SabreTools.IO.Logging; @@ -15,17 +16,17 @@ namespace SabreTools.Filtering /// /// List of header fields to exclude from writing /// - public List HeaderFieldNames { get; } = []; + public readonly List HeaderFieldNames = []; /// /// List of machine fields to exclude from writing /// - public List MachineFieldNames { get; } = []; + public readonly List MachineFieldNames = []; /// /// List of fields to exclude from writing /// - public Dictionary> ItemFieldNames { get; } = []; + public readonly Dictionary> ItemFieldNames = []; #endregion @@ -92,25 +93,30 @@ namespace SabreTools.Filtering return false; // Get the parser pair out of it, if possible - if (!FilterParser.ParseFilterId(field, out string type, out string key)) - return false; - - switch (type) + try { - case Models.Metadata.MetadataFile.HeaderKey: - HeaderFieldNames.Add(key); - return true; + var key = new FilterKey(field); + switch (key.ItemName) + { + case Models.Metadata.MetadataFile.HeaderKey: + HeaderFieldNames.Add(key.FieldName); + return true; - case Models.Metadata.MetadataFile.MachineKey: - MachineFieldNames.Add(key); - return true; + case Models.Metadata.MetadataFile.MachineKey: + MachineFieldNames.Add(key.FieldName); + return true; - default: - if (!ItemFieldNames.ContainsKey(type)) - ItemFieldNames[type] = []; + default: + if (!ItemFieldNames.ContainsKey(key.ItemName)) + ItemFieldNames[key.ItemName] = []; - ItemFieldNames[type].Add(key); - return true; + ItemFieldNames[key.ItemName].Add(key.FieldName); + return true; + } + } + catch + { + return false; } } diff --git a/SabreTools/Features/BaseFeature.cs b/SabreTools/Features/BaseFeature.cs index ca476b97..16cba3ef 100644 --- a/SabreTools/Features/BaseFeature.cs +++ b/SabreTools/Features/BaseFeature.cs @@ -2024,12 +2024,15 @@ Some special strings that can be used: foreach (string fieldName in GetList(features, UpdateFieldListValue)) { // Ensure the field is valid - if (!FilterParser.ParseFilterId(fieldName, out string itemType, out string key)) - continue; - if (itemType != Models.Metadata.MetadataFile.MachineKey) - continue; + try + { + var key = new FilterKey(fieldName); + if (key.ItemName != Models.Metadata.MetadataFile.MachineKey) + continue; - updateFields.Add(key); + updateFields.Add(key.FieldName); + } + catch { } } return updateFields; @@ -2044,15 +2047,18 @@ Some special strings that can be used: foreach (string fieldName in GetList(features, UpdateFieldListValue)) { // Ensure the field is valid - if (!FilterParser.ParseFilterId(fieldName, out string itemType, out string key)) - continue; - if (itemType == Models.Metadata.MetadataFile.HeaderKey || itemType == Models.Metadata.MetadataFile.MachineKey) - continue; + try + { + var key = new FilterKey(fieldName); + if (key.ItemName == Models.Metadata.MetadataFile.HeaderKey || key.ItemName == Models.Metadata.MetadataFile.MachineKey) + continue; - if (!updateFields.ContainsKey(itemType)) - updateFields[itemType] = []; + if (!updateFields.ContainsKey(key.ItemName)) + updateFields[key.ItemName] = []; - updateFields[itemType].Add(key); + updateFields[key.ItemName].Add(key.FieldName); + } + catch { } } return updateFields; diff --git a/SabreTools/Features/Batch.cs b/SabreTools/Features/Batch.cs index c1db61a0..2a0613da 100644 --- a/SabreTools/Features/Batch.cs +++ b/SabreTools/Features/Batch.cs @@ -315,18 +315,22 @@ Reset the internal state: reset();"; } // Read in the individual arguments - bool parsed = FilterParser.ParseFilterId(Arguments[0], out _, out _); + string itemFieldString = Arguments[0]; string extraFile = Arguments[1]; // If we had an invalid input, log and continue - if (!parsed) + try { - string message = $"{Arguments[0]} was an invalid field name"; + _ = new FilterKey(itemFieldString); + } + catch + { + string message = $"{itemFieldString} was an invalid field name"; return (false, message); } if (!File.Exists(extraFile)) { - string message = $"{Arguments[1]} was an invalid file name"; + string message = $"{extraFile} was an invalid file name"; return (false, message); } @@ -337,12 +341,12 @@ Reset the internal state: reset();"; public override void Process(BatchState batchState) { // Read in the individual arguments - FilterParser.ParseFilterId(Arguments[0], out string itemName, out string fieldName); + string key = Arguments[0]; string extraFile = Arguments[1]; // Create the extra INI var extraIni = new ExtraIni(); - var extraIniItem = new ExtraIniItem(itemName, fieldName, extraFile); + var extraIniItem = new ExtraIniItem(key, extraFile); extraIni.Items.Add(extraIniItem); // Apply the extra INI blindly @@ -375,7 +379,7 @@ Reset the internal state: reset();"; } // Read in the individual arguments - bool parsed = FilterParser.ParseFilterId(Arguments[0], out _, out _); + string itemFieldString = Arguments[0]; bool? filterRemove = false; if (Arguments.Count >= 3) filterRemove = Arguments[2].AsYesNo(); @@ -384,9 +388,13 @@ Reset the internal state: reset();"; filterPerMachine = Arguments[3].AsYesNo(); // If we had an invalid input, log and continue - if (!parsed) + try { - string message = $"{Arguments[0]} was an invalid field name"; + _ = new FilterKey(itemFieldString); + } + catch + { + string message = $"{itemFieldString} was an invalid field name"; return (false, message); } if (filterRemove == null) @@ -804,12 +812,18 @@ Reset the internal state: reset();"; } // Read in the individual arguments - bool parsed = FilterParser.ParseFilterId(Arguments[0], out string type, out _); + string itemFieldString = Arguments[0]; // If we had an invalid input, log and continue - if (!parsed || !string.Equals(type, Models.Metadata.MetadataFile.HeaderKey, StringComparison.OrdinalIgnoreCase)) + try { - string message = $"{Arguments[0]} was an invalid field name"; + var key = new FilterKey(itemFieldString); + if (!string.Equals(key.ItemName, Models.Metadata.MetadataFile.HeaderKey, StringComparison.OrdinalIgnoreCase)) + throw new Exception(); + } + catch + { + string message = $"{itemFieldString} was an invalid field name"; return (false, message); } @@ -824,8 +838,7 @@ Reset the internal state: reset();"; string value = Arguments[1]; var setter = new Setter(); - FilterParser.ParseFilterId(Arguments[0], out string itemName, out string fieldName); - setter.PopulateSetters(new FilterKey(itemName, fieldName), value); + setter.PopulateSetters(new FilterKey(field), value); // Set the header field setter.SetFields(batchState.DatFile.Header);