diff --git a/SabreTools.Core/Filter/FieldManipulator.cs b/SabreTools.Core/Filter/FieldManipulator.cs index 9b6113f9..e1b8b288 100644 --- a/SabreTools.Core/Filter/FieldManipulator.cs +++ b/SabreTools.Core/Filter/FieldManipulator.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using System.Text.RegularExpressions; using SabreTools.Core.Tools; using SabreTools.Models.Metadata; @@ -8,36 +7,6 @@ namespace SabreTools.Core.Filter { public static class FieldManipulator { - /// - /// Regex pattern to match scene dates - /// - private const string _sceneDateRegex = @"([0-9]{2}\.[0-9]{2}\.[0-9]{2}-)(.*?-.*?)"; - - /// - /// Replace the machine name with the description - /// - /// Original machine name on success, null on error - public static string? DescriptionToName(Machine? machine) - { - // If the machine is missing, we can't do anything - if (machine == null) - return null; - - // Get both the current name and description - string? name = machine.ReadString(Header.NameKey); - string? description = machine.ReadString(Header.DescriptionKey); - - // Sanitize the description string - description = description? - .Replace('/', '_') - .Replace("\"", "''") - .Replace(":", " -"); - - // Replace the name with the description - machine[Header.NameKey] = description; - return name; - } - /// /// Remove a field from a given DictionaryBase /// @@ -96,27 +65,5 @@ namespace SabreTools.Core.Filter dictionaryBase[fieldName!] = value; return true; } - - /// - /// Strip the dates from the beginning of scene-style machine name and description - /// - public static bool StripSceneDates(Machine? machine) - { - // If the machine is missing, we can't do anything - if (machine == null) - return false; - - // Strip dates from the name field - string? name = machine.ReadString(Header.NameKey); - if (name != null && Regex.IsMatch(name, _sceneDateRegex)) - machine[Header.NameKey] = Regex.Replace(name, _sceneDateRegex, @"$2"); - - // Strip dates from the description field - string? description = machine.ReadString(Header.DescriptionKey); - if (description != null && Regex.IsMatch(description, _sceneDateRegex)) - machine[Header.DescriptionKey] = Regex.Replace(description, _sceneDateRegex, @"$2"); - - return true; - } } } \ No newline at end of file diff --git a/SabreTools.Core/Filter/FilterObject.cs b/SabreTools.Core/Filter/FilterObject.cs index c7acc7d0..e0ae74a0 100644 --- a/SabreTools.Core/Filter/FilterObject.cs +++ b/SabreTools.Core/Filter/FilterObject.cs @@ -14,26 +14,24 @@ namespace SabreTools.Core.Filter /// /// Key name for the filter /// - public string[] Key { get; } + public readonly string[] Key; /// /// Value to match in the filter /// - public string? Value { get; } + public readonly string? Value; /// /// Operation on how to match the filter /// - public Operation Operation { get; } + public readonly Operation Operation; public FilterObject(string filterString) { - (string? keyItem, Operation operation, string? value) = SplitFilterString(filterString); - if (keyItem == null) + if (!SplitFilterString(filterString, out var keyItem, out Operation operation, out var value)) throw new ArgumentOutOfRangeException(nameof(filterString)); - (string? itemName, string? fieldName) = FilterParser.ParseFilterId(keyItem); - if (itemName == null || fieldName == null) + if (!FilterParser.ParseFilterId(keyItem, out var itemName, out var fieldName)) throw new ArgumentOutOfRangeException(nameof(filterString)); Key = [itemName, fieldName]; @@ -43,8 +41,7 @@ namespace SabreTools.Core.Filter public FilterObject(string itemField, string? value, string? operation) { - (string? itemName, string? fieldName) = FilterParser.ParseFilterId(itemField); - if (itemName == null || fieldName == null) + if (!FilterParser.ParseFilterId(itemField, out var itemName, out var fieldName)) throw new ArgumentOutOfRangeException(nameof(value)); Key = [itemName, fieldName]; @@ -54,11 +51,10 @@ namespace SabreTools.Core.Filter public FilterObject(string itemField, string? value, Operation operation) { - (string? itemName, string? fieldName) = FilterParser.ParseFilterId(itemField); - if (itemName == null || fieldName == null) + if (!FilterParser.ParseFilterId(itemField, out var itemName, out var fieldName)) throw new ArgumentOutOfRangeException(nameof(value)); - Key = [itemName, fieldName]; + Key = [itemName!, fieldName!]; Value = value; Operation = operation; } @@ -396,10 +392,13 @@ namespace SabreTools.Core.Filter /// /// Derive a key, operation, and value from the input string, if possible /// - private static (string?, Operation, string?) SplitFilterString(string? filterString) + private static bool SplitFilterString(string? filterString, out string? key, out Operation operation, out string? value) { + // Set default values + key = null; operation = Operation.NONE; value = null; + if (filterString == null) - return (null, Operation.NONE, null); + return false; // Trim quotations, if necessary if (filterString.StartsWith("\"")) @@ -408,13 +407,13 @@ namespace SabreTools.Core.Filter // Split the string using regex var match = Regex.Match(filterString, @"^(?[a-zA-Z.]+)(?[=!:><]{1,2})(?.*)$"); if (!match.Success) - return (null, Operation.NONE, null); + return false; - string itemField = match.Groups["itemField"].Value; - Operation operation = GetOperation(match.Groups["operation"].Value); - string value = match.Groups["value"].Value; + key = match.Groups["itemField"].Value; + operation = GetOperation(match.Groups["operation"].Value); + value = match.Groups["value"].Value; - return (itemField, operation, value); + return true; } #endregion diff --git a/SabreTools.Core/Filter/FilterParser.cs b/SabreTools.Core/Filter/FilterParser.cs index b28b4884..2f112f66 100644 --- a/SabreTools.Core/Filter/FilterParser.cs +++ b/SabreTools.Core/Filter/FilterParser.cs @@ -10,125 +10,166 @@ namespace SabreTools.Core.Filter /// /// Parse a filter ID string into the item name and field name, if possible /// - public static (string?, string?) ParseFilterId(string itemFieldString) + 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 (string.IsNullOrEmpty(itemFieldString)) - return (null, null); + 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 (null, null); + return false; - return ParseFilterId(splitFilter[0], splitFilter[1]); + // 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 (string?, string?) ParseFilterId(string itemName, string? fieldName) + 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 (null, null); + return false; // Return santized values based on the split ID return itemName.ToLowerInvariant() switch { // Header - "header" => ParseHeaderFilterId(fieldName!), + "header" => ParseHeaderFilterId(ref itemName, ref fieldName), // Machine - "game" => ParseMachineFilterId(fieldName!), - "machine" => ParseMachineFilterId(fieldName!), - "resource" => ParseMachineFilterId(fieldName!), - "set" => ParseMachineFilterId(fieldName!), + "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(fieldName!), - "item" => ParseDatItemFilterId(fieldName!), - _ => ParseDatItemFilterId(itemName, fieldName!), + "datitem" => ParseDatItemFilterId(ref itemName, ref fieldName), + "item" => ParseDatItemFilterId(ref itemName, ref fieldName), + _ => ParseDatItemFilterId(ref itemName, ref fieldName), }; } /// /// Parse and validate header fields /// - private static (string?, string?) ParseHeaderFilterId(string fieldName) + 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 (null, null); + return false; // Get if there's a match to the constant - string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, fieldName, StringComparison.OrdinalIgnoreCase)); + string localFieldName = fieldName; + string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, localFieldName, StringComparison.OrdinalIgnoreCase)); if (constantMatch == null) - return (null, null); + return false; // Return the sanitized ID - return (MetadataFile.HeaderKey, constantMatch); + itemName = MetadataFile.HeaderKey; + fieldName = constantMatch; + return true; } /// /// Parse and validate machine/game fields /// - private static (string?, string?) ParseMachineFilterId(string fieldName) + 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 (null, null); + return false; // Get if there's a match to the constant - string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, fieldName, StringComparison.OrdinalIgnoreCase)); + string localFieldName = fieldName; + string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, localFieldName, StringComparison.OrdinalIgnoreCase)); if (constantMatch == null) - return (null, null); + return false; // Return the sanitized ID - return (MetadataFile.MachineKey, constantMatch); + itemName = MetadataFile.MachineKey; + fieldName = constantMatch; + return true; } /// /// Parse and validate item fields /// - private static (string?, string?) ParseDatItemFilterId(string fieldName) + private static bool ParseDatItemFilterId(ref string itemName, ref string fieldName) { - // Get all item types - var itemTypes = TypeHelper.GetDatItemTypeNames(); + // 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? match = itemTypes.FirstOrDefault(t => t != null && ParseDatItemFilterId(t, fieldName) != (null, null)); - if (match != null) - return ("item", ParseDatItemFilterId(match, fieldName).Item2); + // 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 (null, null); + return false; } /// - /// Parse and validate item fields + /// Determine if an item type contains a field /// - private static (string?, string?) ParseDatItemFilterId(string itemName, string fieldName) + 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, null); + return null; // Get the set of constants var constants = TypeHelper.GetConstants(itemType); if (constants == null) - return (null, null); + return null; // Get if there's a match to the constant - string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, fieldName, StringComparison.OrdinalIgnoreCase)); - if (constantMatch == null) - return (null, null); - - // Return the sanitized ID - return (itemName.ToLowerInvariant(), constantMatch); + string localFieldName = fieldName; + string? constantMatch = constants.FirstOrDefault(c => string.Equals(c, localFieldName, StringComparison.OrdinalIgnoreCase)); + return constantMatch; } } } diff --git a/SabreTools.Core/Filter/FilterRunner.cs b/SabreTools.Core/Filter/FilterRunner.cs index bdf36da2..32e19503 100644 --- a/SabreTools.Core/Filter/FilterRunner.cs +++ b/SabreTools.Core/Filter/FilterRunner.cs @@ -20,7 +20,7 @@ namespace SabreTools.Core.Filter if (filters == null) throw new ArgumentNullException(nameof(filters)); - this.Filters = filters; + Filters = filters; } public FilterRunner(string[]? filterStrings) @@ -34,7 +34,7 @@ namespace SabreTools.Core.Filter filters.Add(new FilterObject(filterString)); } - this.Filters = [.. filters]; + Filters = [.. filters]; } /// @@ -51,7 +51,7 @@ namespace SabreTools.Core.Filter }; // Loop through and run each filter in order - foreach (var filter in this.Filters) + foreach (var filter in Filters) { // If the filter isn't for this object type, skip if (filter.Key[0] != itemName) diff --git a/SabreTools.Core/Tools/TypeHelper.cs b/SabreTools.Core/Tools/TypeHelper.cs index 53fca007..cd719d96 100644 --- a/SabreTools.Core/Tools/TypeHelper.cs +++ b/SabreTools.Core/Tools/TypeHelper.cs @@ -42,7 +42,7 @@ namespace SabreTools.Core.Tools /// /// Attempt to get all DatItem types /// - public static string?[] GetDatItemTypeNames() + public static string[] GetDatItemTypeNames() { List typeNames = []; diff --git a/SabreTools.DatFiles/Setter.cs b/SabreTools.DatFiles/Setter.cs index 44de8922..747c83e2 100644 --- a/SabreTools.DatFiles/Setter.cs +++ b/SabreTools.DatFiles/Setter.cs @@ -114,8 +114,7 @@ namespace SabreTools.DatFiles return false; // Get the parser pair out of it, if possible - (string? type, string? key) = FilterParser.ParseFilterId(field); - if (type == null || key == null) + if (!FilterParser.ParseFilterId(field, out string type, out string key)) return false; switch (type) diff --git a/SabreTools.Filtering/ExtraIni.cs b/SabreTools.Filtering/ExtraIni.cs index 59bb153b..771f8b6a 100644 --- a/SabreTools.Filtering/ExtraIni.cs +++ b/SabreTools.Filtering/ExtraIni.cs @@ -68,7 +68,8 @@ namespace SabreTools.Filtering string fieldString = inputTrimmed.Split(':')[0].ToLowerInvariant().Trim('"', ' ', '\t'); string fileString = inputTrimmed.Substring(fieldString.Length + 1).Trim('"', ' ', '\t'); - item.FieldName = FilterParser.ParseFilterId(fieldString); + FilterParser.ParseFilterId(fieldString, out string itemName, out string fieldName); + item.FieldName = (itemName, fieldName); if (item.PopulateFromFile(fileString)) Items.Add(item); } diff --git a/SabreTools.Filtering/Remover.cs b/SabreTools.Filtering/Remover.cs index f64d2ba7..506cb1c4 100644 --- a/SabreTools.Filtering/Remover.cs +++ b/SabreTools.Filtering/Remover.cs @@ -92,8 +92,7 @@ namespace SabreTools.Filtering return false; // Get the parser pair out of it, if possible - (string? type, string? key) = FilterParser.ParseFilterId(field); - if (type == null || key == null) + if (!FilterParser.ParseFilterId(field, out string type, out string key)) return false; switch (type) diff --git a/SabreTools/Features/BaseFeature.cs b/SabreTools/Features/BaseFeature.cs index d0817b9a..ca476b97 100644 --- a/SabreTools/Features/BaseFeature.cs +++ b/SabreTools/Features/BaseFeature.cs @@ -2023,9 +2023,13 @@ Some special strings that can be used: List updateFields = []; foreach (string fieldName in GetList(features, UpdateFieldListValue)) { - (string? itemType, string? key) = FilterParser.ParseFilterId(fieldName); - if (itemType == Models.Metadata.MetadataFile.MachineKey && key != null) - updateFields.Add(key); + // Ensure the field is valid + if (!FilterParser.ParseFilterId(fieldName, out string itemType, out string key)) + continue; + if (itemType != Models.Metadata.MetadataFile.MachineKey) + continue; + + updateFields.Add(key); } return updateFields; @@ -2039,14 +2043,16 @@ Some special strings that can be used: Dictionary> updateFields = []; foreach (string fieldName in GetList(features, UpdateFieldListValue)) { - (string? itemType, string? key) = FilterParser.ParseFilterId(fieldName); - if (itemType != null && itemType != Models.Metadata.MetadataFile.HeaderKey && itemType != Models.Metadata.MetadataFile.MachineKey && key != null) - { - if (!updateFields.ContainsKey(itemType)) - updateFields[itemType] = []; + // 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; - updateFields[itemType].Add(key); - } + if (!updateFields.ContainsKey(itemType)) + updateFields[itemType] = []; + + updateFields[itemType].Add(key); } return updateFields; diff --git a/SabreTools/Features/Batch.cs b/SabreTools/Features/Batch.cs index 5e11300f..0b588ffd 100644 --- a/SabreTools/Features/Batch.cs +++ b/SabreTools/Features/Batch.cs @@ -315,11 +315,11 @@ Reset the internal state: reset();"; } // Read in the individual arguments - (string? type, string? key) = FilterParser.ParseFilterId(Arguments[0]); + bool parsed = FilterParser.ParseFilterId(Arguments[0], out _, out _); string extraFile = Arguments[1]; // If we had an invalid input, log and continue - if (type == null && key == null) + if (!parsed) { string message = $"{Arguments[0]} was an invalid field name"; return (false, message); @@ -337,12 +337,12 @@ Reset the internal state: reset();"; public override void Process(BatchState batchState) { // Read in the individual arguments - (string?, string?) fieldName = FilterParser.ParseFilterId(Arguments[0]); + FilterParser.ParseFilterId(Arguments[0], out string itemName, out string fieldName); string extraFile = Arguments[1]; // Create the extra INI var extraIni = new ExtraIni(); - var extraIniItem = new ExtraIniItem() { FieldName = fieldName }; + var extraIniItem = new ExtraIniItem() { FieldName = (itemName, fieldName) }; extraIniItem.PopulateFromFile(extraFile); extraIni.Items.Add(extraIniItem); @@ -376,7 +376,7 @@ Reset the internal state: reset();"; } // Read in the individual arguments - (string? type, string? key) = FilterParser.ParseFilterId(Arguments[0]); + bool parsed = FilterParser.ParseFilterId(Arguments[0], out _, out _); bool? filterRemove = false; if (Arguments.Count >= 3) filterRemove = Arguments[2].AsYesNo(); @@ -385,7 +385,7 @@ Reset the internal state: reset();"; filterPerMachine = Arguments[3].AsYesNo(); // If we had an invalid input, log and continue - if (type == null && key == null) + if (!parsed) { string message = $"{Arguments[0]} was an invalid field name"; return (false, message); @@ -805,10 +805,10 @@ Reset the internal state: reset();"; } // Read in the individual arguments - (string? type, string? key) = FilterParser.ParseFilterId(Arguments[0]); + bool parsed = FilterParser.ParseFilterId(Arguments[0], out string type, out _); // If we had an invalid input, log and continue - if ((type == null || !string.Equals(type, Models.Metadata.MetadataFile.HeaderKey, StringComparison.OrdinalIgnoreCase)) && key == null) + if (!parsed || !string.Equals(type, Models.Metadata.MetadataFile.HeaderKey, StringComparison.OrdinalIgnoreCase)) { string message = $"{Arguments[0]} was an invalid field name"; return (false, message);