DictionaryBase is no more, bon voyage

This commit is contained in:
Matt Nadareski
2026-04-04 23:13:36 -04:00
parent e31be83832
commit bf852ef038
69 changed files with 167 additions and 850 deletions

View File

@@ -2515,7 +2515,7 @@ namespace SabreTools.Data.Extensions.Test
var other = new Disk();
self.FillMissingHashes(other);
Assert.Empty(self);
Assert.False(self.HasHashes());
}
[Fact]
@@ -2537,7 +2537,7 @@ namespace SabreTools.Data.Extensions.Test
var self = new Media();
var other = new Media();
self.FillMissingHashes(other);
Assert.Empty(self);
Assert.False(self.HasHashes());
}
[Fact]
@@ -2561,7 +2561,7 @@ namespace SabreTools.Data.Extensions.Test
var self = new Rom();
var other = new Rom();
self.FillMissingHashes(other);
Assert.Empty(self);
Assert.False(self.HasHashes());
}
[Fact]

View File

@@ -6,7 +6,7 @@ namespace SabreTools.Data.Models.Metadata
/// <summary>
/// Format-agnostic representation of item data
/// </summary>
public class DatItem : DictionaryBase
public class DatItem
{
/// <summary>
/// Quick accessor to item type, if it exists

View File

@@ -1,263 +0,0 @@
using System.Collections.Generic;
namespace SabreTools.Data.Models.Metadata
{
/// <summary>
/// Specialized dictionary base for item types
/// </summary>
public abstract class DictionaryBase : Dictionary<string, object?>
{
#region Read
/// <summary>
/// Read a key as the specified type, returning null on error
/// </summary>
public T? Read<T>(string key)
{
try
{
if (!ValidateReadKey(key))
return default;
if (this[key] is not T)
return default;
return (T?)this[key];
}
catch
{
return default;
}
}
/// <summary>
/// Read a key as a bool, returning null on error
/// </summary>
/// TODO: Determine if this can be removed
public bool? ReadBool(string key)
{
if (!ValidateReadKey(key))
return null;
bool? asBool = Read<bool?>(key);
if (asBool is not null)
return asBool;
string? asString = Read<string>(key);
return asString?.ToLowerInvariant() switch
{
"true" or "yes" => true,
"false" or "no" => false,
_ => null,
};
}
/// <summary>
/// Read a key as a double, returning null on error
/// </summary>
/// TODO: Determine if this can be removed
public double? ReadDouble(string key)
{
if (!ValidateReadKey(key))
return null;
double? asDouble = Read<double?>(key);
if (asDouble is not null)
return asDouble;
float? asFloat = Read<float?>(key);
if (asFloat is not null)
return asFloat;
#if NET5_0_OR_GREATER
System.Half? asHalf = Read<System.Half?>(key);
if (asHalf is not null)
return (double?)asHalf;
#endif
string? asString = Read<string>(key);
if (asString is not null && double.TryParse(asString, out double asStringDouble))
return asStringDouble;
return null;
}
/// <summary>
/// Read a key as a long, returning null on error
/// </summary>
/// <remarks>TODO: Add logic to convert SI suffixes and hex</remarks>
/// TODO: Determine if this can be removed
public long? ReadLong(string key)
{
if (!ValidateReadKey(key))
return null;
long? asLong = Read<long?>(key);
if (asLong is not null)
return asLong;
int? asInt = Read<int?>(key);
if (asInt is not null)
return asInt;
short? asShort = Read<short?>(key);
if (asShort is not null)
return asShort;
string? asString = Read<string>(key);
if (asString is not null && long.TryParse(asString, out long asStringLong))
return asStringLong;
return null;
}
/// <summary>
/// Read a key as a string, returning null on error
/// </summary>
public string? ReadString(string key)
{
if (!ValidateReadKey(key))
return null;
string? asString = Read<string>(key);
if (asString is not null)
return asString;
string[]? asArray = Read<string[]>(key);
if (asArray is not null)
#if NETFRAMEWORK || NETSTANDARD2_0
return string.Join(",", asArray);
#else
return string.Join(',', asArray);
#endif
// TODO: Add byte array conversion here
// TODO: Add byte array read helper
return this[key]!.ToString();
}
/// <summary>
/// Read a key as a T[], returning null on error
/// </summary>
public T[]? ReadArray<T>(string key)
{
if (!ValidateReadKey(key))
return null;
var items = Read<T[]>(key);
if (items is not null)
return items;
var single = Read<T>(key);
if (single is not null)
return [single];
return null;
}
/// <summary>
/// Read a key as a string[], returning null on error
/// </summary>
/// TODO: Determine if this can be removed
public string[]? ReadStringArray(string key)
{
if (!ValidateReadKey(key))
return null;
string[]? asArray = Read<string[]>(key);
if (asArray is not null)
return asArray;
string? asString = Read<string>(key);
if (asString is not null)
return [asString];
asString = this[key]!.ToString();
if (asString is not null)
return [asString];
return null;
}
/// <summary>
/// Check if a key is valid for read
/// </summary>
private bool ValidateReadKey(string key)
{
if (string.IsNullOrEmpty(key))
return false;
else if (!ContainsKey(key))
return false;
else if (this[key] is null)
return false;
return true;
}
#endregion
#region Write
/// <summary>
/// Remove a key, if possible
/// </summary>
public new bool Remove(string? fieldName)
{
// If the item or field name are missing, we can't do anything
if (string.IsNullOrEmpty(fieldName))
return false;
// If the key doesn't exist, then it's already removed
if (!ContainsKey(fieldName!))
return true;
// Remove the key
base.Remove(fieldName!);
return true;
}
/// <summary>
/// Replace a field from another item
/// </summary>
public bool Replace(DictionaryBase? from, string fieldName)
{
// If the source item is invalid
if (from is null)
return false;
// If the field name is missing, we can't do anything
if (string.IsNullOrEmpty(fieldName))
return false;
// If the types of the items are not the same, we can't do anything
if (from.GetType() != GetType())
return false;
// If the key doesn't exist in the source, we can't do anything
if (!from.TryGetValue(fieldName!, out var value))
return false;
// Set the key
this[fieldName!] = value;
return true;
}
/// <summary>
/// Write a key as the specified type, returning false on error
/// </summary>
/// <param name="fieldName">Field to set</param>
/// <param name="value">Value to set</param>
/// <returns>True if the value was set, false otherwise</returns>
public bool Write<T>(string fieldName, T? value)
{
// Invalid field cannot be processed
if (fieldName is null)
return false;
// Set the value based on the type
this[fieldName] = value;
return true;
}
#endregion
}
}

View File

@@ -6,7 +6,7 @@ namespace SabreTools.Data.Models.Metadata
/// <summary>
/// Format-agnostic representation of metadata header data
/// </summary>
public class Header : DictionaryBase, ICloneable, IEquatable<Header>
public class Header : ICloneable, IEquatable<Header>
{
#region Properties
@@ -37,6 +37,8 @@ namespace SabreTools.Data.Models.Metadata
public string? EmulatorVersion { get; set; }
public string? FileName { get; set; }
/// <remarks>(none|split|merged|nonmerged|fullmerged|device|full) "split"</remarks>
public MergingFlag ForceMerging { get; set; }
@@ -135,6 +137,7 @@ namespace SabreTools.Data.Models.Metadata
obj.Description = Description;
obj.Email = Email;
obj.EmulatorVersion = EmulatorVersion;
obj.FileName = FileName;
obj.ForceMerging = ForceMerging;
obj.ForceNodump = ForceNodump;
obj.ForcePacking = ForcePacking;
@@ -231,6 +234,11 @@ namespace SabreTools.Data.Models.Metadata
else if (EmulatorVersion is not null && !EmulatorVersion.Equals(other.EmulatorVersion, StringComparison.OrdinalIgnoreCase))
return false;
if ((FileName is null) ^ (other.FileName is null))
return false;
else if (FileName is not null && !FileName.Equals(other.FileName, StringComparison.OrdinalIgnoreCase))
return false;
if (ForceMerging != other.ForceMerging)
return false;

View File

@@ -4,13 +4,8 @@ using Newtonsoft.Json;
namespace SabreTools.Data.Models.Metadata
{
[JsonObject("infosource"), XmlRoot("infosource")]
public class InfoSource : DictionaryBase
public class InfoSource
{
#region Keys
/// <remarks>string[]</remarks>
public const string SourceKey = "source";
#endregion
public string[]? Source { get; set; }
}
}

View File

@@ -5,7 +5,7 @@ namespace SabreTools.Data.Models.Metadata
/// <summary>
/// Format-agnostic representation of game, machine, and set data
/// </summary>
public class Machine : DictionaryBase, ICloneable, IEquatable<Machine>
public class Machine : ICloneable, IEquatable<Machine>
{
#region Properties

View File

@@ -6,7 +6,7 @@ namespace SabreTools.Data.Models.Metadata
/// </summary>
/// TODO: ICloneable
/// TODO: IComparable<MetadataFile>
public class MetadataFile : DictionaryBase
public class MetadataFile
{
public Header? Header { get; set; }

View File

@@ -25,7 +25,7 @@ namespace SabreTools.Metadata.DatFiles.Test
[Fact]
public void ConvertFromMetadata_Empty()
{
Data.Models.Metadata.MetadataFile? item = [];
Data.Models.Metadata.MetadataFile? item = new Data.Models.Metadata.MetadataFile();
DatFile datFile = new Formats.Logiqx(null, useGame: false);
datFile.ConvertFromMetadata(item, "filename", indexId: 0, keep: true, statsOnly: false, filterRunner: null);
@@ -211,6 +211,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Description = "description",
Email = "email",
EmulatorVersion = "emulatorversion",
FileName = "filename",
ForceMerging = Data.Models.Metadata.MergingFlag.Merged,
ForceNodump = Data.Models.Metadata.NodumpFlag.Required,
ForcePacking = Data.Models.Metadata.PackingFlag.Zip,
@@ -549,7 +550,7 @@ namespace SabreTools.Metadata.DatFiles.Test
{
Endianness = Data.Models.Metadata.Endianness.Big,
Name = "name",
Rom = [[]],
Rom = [new Data.Models.Metadata.Rom()],
Size = 12345,
Width = Data.Models.Metadata.Width.Long,
};
@@ -575,7 +576,7 @@ namespace SabreTools.Metadata.DatFiles.Test
{
return new Data.Models.Metadata.DiskArea
{
Disk = [[]],
Disk = [new Data.Models.Metadata.Disk()],
Name = "name",
};
}
@@ -716,7 +717,7 @@ namespace SabreTools.Metadata.DatFiles.Test
{
DataArea = [CreateMetadataDataArea()],
DiskArea = [CreateMetadataDiskArea()],
DipSwitch = [[]],
DipSwitch = [new Data.Models.Metadata.DipSwitch()],
Feature = [CreateMetadataFeature()],
Interface = "interface",
Name = "name",

View File

@@ -360,6 +360,7 @@ namespace SabreTools.Metadata.DatFiles.Test
Assert.Equal("description", header.Description);
Assert.Equal("email", header.Email);
Assert.Equal("emulatorversion", header.EmulatorVersion);
Assert.Equal("filename", header.FileName);
Assert.Equal(Data.Models.Metadata.MergingFlag.Merged, header.ForceMerging);
Assert.Equal(Data.Models.Metadata.NodumpFlag.Required, header.ForceNodump);
Assert.Equal(Data.Models.Metadata.PackingFlag.Zip, header.ForcePacking);

View File

@@ -239,7 +239,7 @@ namespace SabreTools.Metadata.DatFiles
// Convert items in the machine
if (item.Adjuster is not null)
{
var items = item.Adjuster ?? [];
var items = item.Adjuster;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -251,7 +251,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.Archive is not null)
{
var items = item.Archive ?? [];
var items = item.Archive;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -263,7 +263,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.BiosSet is not null)
{
var items = item.BiosSet ?? [];
var items = item.BiosSet;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -275,7 +275,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.Chip is not null)
{
var items = item.Chip ?? [];
var items = item.Chip;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -287,7 +287,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.Configuration is not null)
{
var items = item.Configuration ?? [];
var items = item.Configuration;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -299,7 +299,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.Device is not null)
{
var items = item.Device ?? [];
var items = item.Device;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -311,7 +311,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.DeviceRef is not null)
{
var items = item.DeviceRef ?? [];
var items = item.DeviceRef;
// Do not filter these due to later use
Array.ForEach(items, item =>
{
@@ -323,7 +323,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.DipSwitch is not null)
{
var items = item.DipSwitch ?? [];
var items = item.DipSwitch;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -335,7 +335,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.Disk is not null)
{
var items = item.Disk ?? [];
var items = item.Disk;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -347,7 +347,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.Display is not null)
{
var items = item.Display ?? [];
var items = item.Display;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -366,7 +366,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.Dump is not null)
{
var items = item.Dump ?? [];
var items = item.Dump;
for (int i = 0; i < items.Length; i++)
{
var datItem = new Rom(items[i], machine, source, i);
@@ -391,7 +391,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.Feature is not null)
{
var items = item.Feature ?? [];
var items = item.Feature;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -403,7 +403,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.Info is not null)
{
var items = item.Info ?? [];
var items = item.Info;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -422,7 +422,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.Media is not null)
{
var items = item.Media ?? [];
var items = item.Media;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -434,13 +434,13 @@ namespace SabreTools.Metadata.DatFiles
if (item.Part is not null)
{
var items = item.Part ?? [];
var items = item.Part;
ProcessItems(items, machine, machineIndex: 0, source, sourceIndex, statsOnly, filterRunner);
}
if (item.Port is not null)
{
var items = item.Port ?? [];
var items = item.Port;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -452,7 +452,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.RamOption is not null)
{
var items = item.RamOption ?? [];
var items = item.RamOption;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -464,7 +464,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.Release is not null)
{
var items = item.Release ?? [];
var items = item.Release;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -476,7 +476,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.Rom is not null)
{
var items = item.Rom ?? [];
var items = item.Rom;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -491,7 +491,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.Sample is not null)
{
var items = item.Sample ?? [];
var items = item.Sample;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -503,7 +503,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.SharedFeat is not null)
{
var items = item.SharedFeat ?? [];
var items = item.SharedFeat;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -515,7 +515,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.Slot is not null)
{
var items = item.Slot ?? [];
var items = item.Slot;
// Do not filter these due to later use
Array.ForEach(items, item =>
{
@@ -527,7 +527,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.SoftwareList is not null)
{
var items = item.SoftwareList ?? [];
var items = item.SoftwareList;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{
@@ -546,7 +546,7 @@ namespace SabreTools.Metadata.DatFiles
if (item.Video is not null)
{
var items = item.Video ?? [];
var items = item.Video;
var filtered = filterRunner is null ? items : Array.FindAll(items, i => filterRunner.Run(item));
Array.ForEach(filtered, item =>
{

View File

@@ -270,25 +270,19 @@ namespace SabreTools.Metadata.DatFiles
// Create the part in the dictionary, if needed
if (!partItems.ContainsKey(partName))
partItems[partName] = [];
partItems[partName] = new();
// Copy over string values
partItems[partName].Name = partName;
if (partItems[partName].Interface == null)
partItems[partName].Interface = partItem.Interface;
// Clear any empty fields
ClearEmptyKeys(partItems[partName]);
// If the item has a DataArea mapping
if (dataAreaMappings.TryGetValue(partItem, out (Data.Models.Metadata.DataArea, Data.Models.Metadata.Rom) dataAreaMap))
{
// Get the mapped items
var (dataArea, romItem) = dataAreaMap;
// Clear any empty fields
ClearEmptyKeys(romItem);
// Get the data area name and skip if there's none
string? dataAreaName = dataArea.Name;
if (dataAreaName is not null)
@@ -306,16 +300,15 @@ namespace SabreTools.Metadata.DatFiles
}
else
{
aggregateDataArea = [];
aggregateDataArea.Endianness = dataArea.Endianness;
aggregateDataArea.Name = dataArea.Name;
aggregateDataArea.Size = dataArea.Size;
aggregateDataArea.Width = dataArea.Width;
aggregateDataArea = new()
{
Endianness = dataArea.Endianness,
Name = dataArea.Name,
Size = dataArea.Size,
Width = dataArea.Width,
};
}
// Clear any empty fields
ClearEmptyKeys(aggregateDataArea);
// Get existing roms as a list
var romsArr = aggregateDataArea.Rom ?? [];
List<Data.Models.Metadata.Rom> roms = [.. romsArr];
@@ -343,9 +336,6 @@ namespace SabreTools.Metadata.DatFiles
// Get the mapped items
var (diskArea, diskItem) = diskAreaMap;
// Clear any empty fields
ClearEmptyKeys(diskItem);
// Get the disk area name and skip if there's none
string? diskAreaName = diskArea.Name;
if (diskAreaName is not null)
@@ -363,13 +353,10 @@ namespace SabreTools.Metadata.DatFiles
}
else
{
aggregateDiskArea = [];
aggregateDiskArea = new();
aggregateDiskArea.Name = diskArea.Name;
}
// Clear any empty fields
ClearEmptyKeys(aggregateDiskArea);
// Get existing disks as a list
var disksArr = aggregateDiskArea.Disk ?? [];
List<Data.Models.Metadata.Disk> disks = [.. disksArr];
@@ -398,9 +385,6 @@ namespace SabreTools.Metadata.DatFiles
var dipSwitchesArr = partItems[partName].DipSwitch ?? [];
List<Data.Models.Metadata.DipSwitch> dipSwitches = [.. dipSwitchesArr];
// Clear any empty fields
ClearEmptyKeys(dipSwitchItem);
// Add the dipswitch
dipSwitches.Add(dipSwitchItem);
@@ -415,9 +399,6 @@ namespace SabreTools.Metadata.DatFiles
var featuresArr = partItems[partName].Feature ?? [];
List<Data.Models.Metadata.Feature> features = [.. featuresArr];
// Clear any empty fields
ClearEmptyKeys(featureItem);
// Add the feature
features.Add(featureItem);
@@ -646,25 +627,19 @@ namespace SabreTools.Metadata.DatFiles
// Create the part in the dictionary, if needed
if (!partItems.ContainsKey(partName))
partItems[partName] = [];
partItems[partName] = new();
// Copy over string values
partItems[partName].Name = partName;
if (partItems[partName].Interface == null)
partItems[partName].Interface = partItem.Interface;
// Clear any empty fields
ClearEmptyKeys(partItems[partName]);
// If the item has a DataArea mapping
if (dataAreaMappings.TryGetValue(partItem, out (Data.Models.Metadata.DataArea, Data.Models.Metadata.Rom) dataAreaMap))
{
// Get the mapped items
var (dataArea, romItem) = dataAreaMap;
// Clear any empty fields
ClearEmptyKeys(romItem);
// Get the data area name and skip if there's none
string? dataAreaName = dataArea.Name;
if (dataAreaName is not null)
@@ -682,16 +657,13 @@ namespace SabreTools.Metadata.DatFiles
}
else
{
aggregateDataArea = [];
aggregateDataArea = new();
aggregateDataArea.Endianness = dataArea.Endianness;
aggregateDataArea.Name = dataArea.Name;
aggregateDataArea.Size = dataArea.Size;
aggregateDataArea.Width = dataArea.Width;
}
// Clear any empty fields
ClearEmptyKeys(aggregateDataArea);
// Get existing roms as a list
var romsArr = aggregateDataArea.Rom ?? [];
List<Data.Models.Metadata.Rom> roms = [.. romsArr];
@@ -719,9 +691,6 @@ namespace SabreTools.Metadata.DatFiles
// Get the mapped items
var (diskArea, diskItem) = diskAreaMap;
// Clear any empty fields
ClearEmptyKeys(diskItem);
// Get the disk area name and skip if there's none
string? diskAreaName = diskArea.Name;
if (diskAreaName is not null)
@@ -739,13 +708,10 @@ namespace SabreTools.Metadata.DatFiles
}
else
{
aggregateDiskArea = [];
aggregateDiskArea = new();
aggregateDiskArea.Name = diskArea.Name;
}
// Clear any empty fields
ClearEmptyKeys(aggregateDiskArea);
// Get existing disks as a list
var disksArr = aggregateDiskArea.Disk ?? [];
List<Data.Models.Metadata.Disk> disks = [.. disksArr];
@@ -774,9 +740,6 @@ namespace SabreTools.Metadata.DatFiles
var dipSwitchesArr = partItems[partName].DipSwitch ?? [];
List<Data.Models.Metadata.DipSwitch> dipSwitches = [.. dipSwitchesArr];
// Clear any empty fields
ClearEmptyKeys(dipSwitchItem);
// Add the dipswitch
dipSwitches.Add(dipSwitchItem);
@@ -791,9 +754,6 @@ namespace SabreTools.Metadata.DatFiles
var featuresArr = partItems[partName].Feature ?? [];
List<Data.Models.Metadata.Feature> features = [.. featuresArr];
// Clear any empty fields
ClearEmptyKeys(featureItem);
// Add the feature
features.Add(featureItem);
@@ -953,19 +913,6 @@ namespace SabreTools.Metadata.DatFiles
return romItem;
}
/// <summary>
/// Clear empty keys from a DictionaryBase object
/// </summary>
private static void ClearEmptyKeys(Data.Models.Metadata.DictionaryBase obj)
{
string[] fieldNames = [.. obj.Keys];
foreach (string fieldName in fieldNames)
{
if (obj[fieldName] is null)
obj.Remove(fieldName);
}
}
#endregion
}
}

View File

@@ -96,10 +96,11 @@ namespace SabreTools.Metadata.DatFiles
set => _internal.EmulatorVersion = value;
}
/// <summary>
/// External name of the DAT
/// </summary>
public string? FileName { get; set; }
public string? FileName
{
get => _internal.FileName;
set => _internal.FileName = value;
}
public MergingFlag ForceMerging
{
@@ -332,7 +333,6 @@ namespace SabreTools.Metadata.DatFiles
public object Clone() => new DatHeader(GetInternalClone())
{
DatFormat = DatFormat,
FileName = FileName,
};
/// <summary>

View File

@@ -52,7 +52,7 @@ namespace SabreTools.Metadata.DatItems.Test
/// <inheritdoc/>
public override TestDatItemModel GetInternalClone()
{
return (_internal as TestDatItemModel)?.Clone() as TestDatItemModel ?? [];
return (_internal as TestDatItemModel)?.Clone() as TestDatItemModel ?? new();
}
/// <inheritdoc/>

View File

@@ -71,7 +71,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Adjuster GetInternalClone()
{
var adjusterItem = (_internal as Data.Models.Metadata.Adjuster)?.Clone() as Data.Models.Metadata.Adjuster ?? [];
var adjusterItem = (_internal as Data.Models.Metadata.Adjuster)?.Clone() as Data.Models.Metadata.Adjuster ?? new();
if (Condition is not null)
adjusterItem.Condition = Condition.GetInternalClone();

View File

@@ -54,7 +54,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Analog GetInternalClone()
=> (_internal as Data.Models.Metadata.Analog)?.Clone() as Data.Models.Metadata.Analog ?? [];
=> (_internal as Data.Models.Metadata.Analog)?.Clone() as Data.Models.Metadata.Analog ?? new();
#endregion

View File

@@ -245,7 +245,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Archive GetInternalClone()
=> (_internal as Data.Models.Metadata.Archive)?.Clone() as Data.Models.Metadata.Archive ?? [];
=> (_internal as Data.Models.Metadata.Archive)?.Clone() as Data.Models.Metadata.Archive ?? new();
#endregion

View File

@@ -66,7 +66,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.BiosSet GetInternalClone()
=> (_internal as Data.Models.Metadata.BiosSet)?.Clone() as Data.Models.Metadata.BiosSet ?? [];
=> (_internal as Data.Models.Metadata.BiosSet)?.Clone() as Data.Models.Metadata.BiosSet ?? new();
#endregion

View File

@@ -48,7 +48,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Blank GetInternalClone()
=> (_internal as Data.Models.Metadata.Blank)?.Clone() as Data.Models.Metadata.Blank ?? [];
=> (_internal as Data.Models.Metadata.Blank)?.Clone() as Data.Models.Metadata.Blank ?? new();
#endregion

View File

@@ -84,7 +84,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Chip GetInternalClone()
=> (_internal as Data.Models.Metadata.Chip)?.Clone() as Data.Models.Metadata.Chip ?? [];
=> (_internal as Data.Models.Metadata.Chip)?.Clone() as Data.Models.Metadata.Chip ?? new();
#endregion

View File

@@ -72,7 +72,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Condition GetInternalClone()
=> (_internal as Data.Models.Metadata.Condition)?.Clone() as Data.Models.Metadata.Condition ?? [];
=> (_internal as Data.Models.Metadata.Condition)?.Clone() as Data.Models.Metadata.Condition ?? new();
#endregion

View File

@@ -66,7 +66,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.ConfLocation GetInternalClone()
=> (_internal as Data.Models.Metadata.ConfLocation)?.Clone() as Data.Models.Metadata.ConfLocation ?? [];
=> (_internal as Data.Models.Metadata.ConfLocation)?.Clone() as Data.Models.Metadata.ConfLocation ?? new();
#endregion

View File

@@ -77,7 +77,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.ConfSetting GetInternalClone()
{
var confSettingItem = (_internal as Data.Models.Metadata.ConfSetting)?.Clone() as Data.Models.Metadata.ConfSetting ?? [];
var confSettingItem = (_internal as Data.Models.Metadata.ConfSetting)?.Clone() as Data.Models.Metadata.ConfSetting ?? new();
if (Condition is not null)
confSettingItem.Condition = Condition.GetInternalClone();

View File

@@ -94,7 +94,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Configuration GetInternalClone()
{
var configurationItem = (_internal as Data.Models.Metadata.Configuration)?.Clone() as Data.Models.Metadata.Configuration ?? [];
var configurationItem = (_internal as Data.Models.Metadata.Configuration)?.Clone() as Data.Models.Metadata.Configuration ?? new();
if (Condition is not null)
configurationItem.Condition = Condition.GetInternalClone();

View File

@@ -120,7 +120,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Control GetInternalClone()
=> (_internal as Data.Models.Metadata.Control)?.Clone() as Data.Models.Metadata.Control ?? [];
=> (_internal as Data.Models.Metadata.Control)?.Clone() as Data.Models.Metadata.Control ?? new();
#endregion

View File

@@ -84,7 +84,7 @@ namespace SabreTools.Metadata.DatItems.Formats
public override Data.Models.Metadata.DataArea GetInternalClone()
{
var partItem = (_internal as Data.Models.Metadata.DataArea)?.Clone() as Data.Models.Metadata.DataArea ?? [];
var partItem = (_internal as Data.Models.Metadata.DataArea)?.Clone() as Data.Models.Metadata.DataArea ?? new();
if (Rom is not null)
partItem.Rom = Array.ConvertAll(Rom, rom => rom.GetInternalClone());

View File

@@ -98,7 +98,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Device GetInternalClone()
{
var deviceItem = (_internal as Data.Models.Metadata.Device)?.Clone() as Data.Models.Metadata.Device ?? [];
var deviceItem = (_internal as Data.Models.Metadata.Device)?.Clone() as Data.Models.Metadata.Device ?? new();
deviceItem.DeviceType = DeviceType;
deviceItem.FixedImage = FixedImage;

View File

@@ -54,7 +54,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.DeviceRef GetInternalClone()
=> (_internal as Data.Models.Metadata.DeviceRef)?.Clone() as Data.Models.Metadata.DeviceRef ?? [];
=> (_internal as Data.Models.Metadata.DeviceRef)?.Clone() as Data.Models.Metadata.DeviceRef ?? new();
#endregion

View File

@@ -66,7 +66,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.DipLocation GetInternalClone()
=> (_internal as Data.Models.Metadata.DipLocation)?.Clone() as Data.Models.Metadata.DipLocation ?? [];
=> (_internal as Data.Models.Metadata.DipLocation)?.Clone() as Data.Models.Metadata.DipLocation ?? new();
#endregion

View File

@@ -121,7 +121,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.DipSwitch GetInternalClone()
{
var dipSwitchItem = (_internal as Data.Models.Metadata.DipSwitch)?.Clone() as Data.Models.Metadata.DipSwitch ?? [];
var dipSwitchItem = (_internal as Data.Models.Metadata.DipSwitch)?.Clone() as Data.Models.Metadata.DipSwitch ?? new();
if (Condition is not null)
dipSwitchItem.Condition = Condition.GetInternalClone();

View File

@@ -77,7 +77,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.DipValue GetInternalClone()
{
var dipValueItem = (_internal as Data.Models.Metadata.DipValue)?.Clone() as Data.Models.Metadata.DipValue ?? [];
var dipValueItem = (_internal as Data.Models.Metadata.DipValue)?.Clone() as Data.Models.Metadata.DipValue ?? new();
if (Condition is not null)
dipValueItem.Condition = Condition.GetInternalClone();

View File

@@ -143,7 +143,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Disk GetInternalClone()
=> (_internal as Data.Models.Metadata.Disk)?.Clone() as Data.Models.Metadata.Disk ?? [];
=> (_internal as Data.Models.Metadata.Disk)?.Clone() as Data.Models.Metadata.Disk ?? new();
/// <summary>
/// Convert a disk to the closest Rom approximation

View File

@@ -66,7 +66,7 @@ namespace SabreTools.Metadata.DatItems.Formats
public override Data.Models.Metadata.DiskArea GetInternalClone()
{
var partItem = (_internal as Data.Models.Metadata.DiskArea)?.Clone() as Data.Models.Metadata.DiskArea ?? [];
var partItem = (_internal as Data.Models.Metadata.DiskArea)?.Clone() as Data.Models.Metadata.DiskArea ?? new();
if (Disk is not null)
partItem.Disk = Array.ConvertAll(Disk, rom => rom.GetInternalClone());

View File

@@ -161,7 +161,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Display GetInternalClone()
=> (_internal as Data.Models.Metadata.Display)?.Clone() as Data.Models.Metadata.Display ?? [];
=> (_internal as Data.Models.Metadata.Display)?.Clone() as Data.Models.Metadata.Display ?? new();
#endregion

View File

@@ -120,7 +120,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Driver GetInternalClone()
=> (_internal as Data.Models.Metadata.Driver)?.Clone() as Data.Models.Metadata.Driver ?? [];
=> (_internal as Data.Models.Metadata.Driver)?.Clone() as Data.Models.Metadata.Driver ?? new();
#endregion

View File

@@ -54,7 +54,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Extension GetInternalClone()
=> (_internal as Data.Models.Metadata.Extension)?.Clone() as Data.Models.Metadata.Extension ?? [];
=> (_internal as Data.Models.Metadata.Extension)?.Clone() as Data.Models.Metadata.Extension ?? new();
#endregion

View File

@@ -78,7 +78,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Feature GetInternalClone()
=> (_internal as Data.Models.Metadata.Feature)?.Clone() as Data.Models.Metadata.Feature ?? [];
=> (_internal as Data.Models.Metadata.Feature)?.Clone() as Data.Models.Metadata.Feature ?? new();
#endregion

View File

@@ -60,7 +60,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Info GetInternalClone()
=> (_internal as Data.Models.Metadata.Info)?.Clone() as Data.Models.Metadata.Info ?? [];
=> (_internal as Data.Models.Metadata.Info)?.Clone() as Data.Models.Metadata.Info ?? new();
#endregion

View File

@@ -96,7 +96,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Input GetInternalClone()
{
var inputItem = (_internal as Data.Models.Metadata.Input)?.Clone() as Data.Models.Metadata.Input ?? [];
var inputItem = (_internal as Data.Models.Metadata.Input)?.Clone() as Data.Models.Metadata.Input ?? new();
if (Control is not null)
inputItem.Control = Array.ConvertAll(Control, control => control.GetInternalClone());

View File

@@ -60,7 +60,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Instance GetInternalClone()
=> (_internal as Data.Models.Metadata.Instance)?.Clone() as Data.Models.Metadata.Instance ?? [];
=> (_internal as Data.Models.Metadata.Instance)?.Clone() as Data.Models.Metadata.Instance ?? new();
#endregion

View File

@@ -96,7 +96,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Media GetInternalClone()
=> (_internal as Data.Models.Metadata.Media)?.Clone() as Data.Models.Metadata.Media ?? [];
=> (_internal as Data.Models.Metadata.Media)?.Clone() as Data.Models.Metadata.Media ?? new();
/// <summary>
/// Convert a media to the closest Rom approximation

View File

@@ -35,6 +35,6 @@ namespace SabreTools.Metadata.DatItems.Formats
/// Internal Original model
/// </summary>
[JsonIgnore]
private readonly Data.Models.Metadata.Original _internal = [];
private readonly Data.Models.Metadata.Original _internal = new();
}
}

View File

@@ -97,7 +97,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Part GetInternalClone()
{
var partItem = (_internal as Data.Models.Metadata.Part)?.Clone() as Data.Models.Metadata.Part ?? [];
var partItem = (_internal as Data.Models.Metadata.Part)?.Clone() as Data.Models.Metadata.Part ?? new();
if (DataArea is not null)
partItem.DataArea = Array.ConvertAll(DataArea, dataArea => dataArea.GetInternalClone());

View File

@@ -83,7 +83,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Feature GetInternalClone()
=> (_internal as Data.Models.Metadata.Feature)?.Clone() as Data.Models.Metadata.Feature ?? [];
=> (_internal as Data.Models.Metadata.Feature)?.Clone() as Data.Models.Metadata.Feature ?? new();
#endregion

View File

@@ -66,7 +66,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Port GetInternalClone()
{
var portItem = (_internal as Data.Models.Metadata.Port)?.Clone() as Data.Models.Metadata.Port ?? [];
var portItem = (_internal as Data.Models.Metadata.Port)?.Clone() as Data.Models.Metadata.Port ?? new();
if (Analog is not null)
portItem.Analog = Array.ConvertAll(Analog, analog => analog.GetInternalClone());

View File

@@ -66,7 +66,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.RamOption GetInternalClone()
=> (_internal as Data.Models.Metadata.RamOption)?.Clone() as Data.Models.Metadata.RamOption ?? [];
=> (_internal as Data.Models.Metadata.RamOption)?.Clone() as Data.Models.Metadata.RamOption ?? new();
#endregion

View File

@@ -78,7 +78,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Release GetInternalClone()
=> (_internal as Data.Models.Metadata.Release)?.Clone() as Data.Models.Metadata.Release ?? [];
=> (_internal as Data.Models.Metadata.Release)?.Clone() as Data.Models.Metadata.Release ?? new();
#endregion

View File

@@ -143,7 +143,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.ReleaseDetails GetInternalClone()
=> (_internal as Data.Models.Metadata.ReleaseDetails)?.Clone() as Data.Models.Metadata.ReleaseDetails ?? [];
=> (_internal as Data.Models.Metadata.ReleaseDetails)?.Clone() as Data.Models.Metadata.ReleaseDetails ?? new();
#endregion

View File

@@ -899,7 +899,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Rom GetInternalClone()
=> (_internal as Data.Models.Metadata.Rom)?.Clone() as Data.Models.Metadata.Rom ?? [];
=> (_internal as Data.Models.Metadata.Rom)?.Clone() as Data.Models.Metadata.Rom ?? new();
#endregion

View File

@@ -54,7 +54,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Sample GetInternalClone()
=> (_internal as Data.Models.Metadata.Sample)?.Clone() as Data.Models.Metadata.Sample ?? [];
=> (_internal as Data.Models.Metadata.Sample)?.Clone() as Data.Models.Metadata.Sample ?? new();
#endregion

View File

@@ -132,7 +132,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Serials GetInternalClone()
=> (_internal as Data.Models.Metadata.Serials)?.Clone() as Data.Models.Metadata.Serials ?? [];
=> (_internal as Data.Models.Metadata.Serials)?.Clone() as Data.Models.Metadata.Serials ?? new();
#endregion

View File

@@ -60,7 +60,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.SharedFeat GetInternalClone()
=> (_internal as Data.Models.Metadata.SharedFeat)?.Clone() as Data.Models.Metadata.SharedFeat ?? [];
=> (_internal as Data.Models.Metadata.SharedFeat)?.Clone() as Data.Models.Metadata.SharedFeat ?? new();
#endregion

View File

@@ -66,7 +66,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Slot GetInternalClone()
{
var slotItem = (_internal as Data.Models.Metadata.Slot)?.Clone() as Data.Models.Metadata.Slot ?? [];
var slotItem = (_internal as Data.Models.Metadata.Slot)?.Clone() as Data.Models.Metadata.Slot ?? new();
if (SlotOption is not null)
slotItem.SlotOption = Array.ConvertAll(SlotOption, slotOption => slotOption.GetInternalClone());

View File

@@ -66,7 +66,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.SlotOption GetInternalClone()
=> (_internal as Data.Models.Metadata.SlotOption)?.Clone() as Data.Models.Metadata.SlotOption ?? [];
=> (_internal as Data.Models.Metadata.SlotOption)?.Clone() as Data.Models.Metadata.SlotOption ?? new();
#endregion

View File

@@ -72,7 +72,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.SoftwareList GetInternalClone()
=> (_internal as Data.Models.Metadata.SoftwareList)?.Clone() as Data.Models.Metadata.SoftwareList ?? [];
=> (_internal as Data.Models.Metadata.SoftwareList)?.Clone() as Data.Models.Metadata.SoftwareList ?? new();
#endregion

View File

@@ -54,7 +54,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.Sound GetInternalClone()
=> (_internal as Data.Models.Metadata.Sound)?.Clone() as Data.Models.Metadata.Sound ?? [];
=> (_internal as Data.Models.Metadata.Sound)?.Clone() as Data.Models.Metadata.Sound ?? new();
#endregion

View File

@@ -192,7 +192,7 @@ namespace SabreTools.Metadata.DatItems.Formats
/// <inheritdoc/>
public override Data.Models.Metadata.SourceDetails GetInternalClone()
=> (_internal as Data.Models.Metadata.SourceDetails)?.Clone() as Data.Models.Metadata.SourceDetails ?? [];
=> (_internal as Data.Models.Metadata.SourceDetails)?.Clone() as Data.Models.Metadata.SourceDetails ?? new();
#endregion

View File

@@ -386,12 +386,12 @@ namespace SabreTools.Metadata.DatItems
public Machine()
{
_internal = [];
_internal = new();
}
public Machine(Data.Models.Metadata.Machine machine)
{
_internal = machine.Clone() as Data.Models.Metadata.Machine ?? [];
_internal = machine.Clone() as Data.Models.Metadata.Machine ?? new();
// Clear all lists
_internal.Adjuster = null;
@@ -449,7 +449,7 @@ namespace SabreTools.Metadata.DatItems
/// <summary>
/// Get a clone of the current internal model
/// </summary>
public Data.Models.Metadata.Machine GetInternalClone() => (_internal.Clone() as Data.Models.Metadata.Machine) ?? [];
public Data.Models.Metadata.Machine GetInternalClone() => (_internal.Clone() as Data.Models.Metadata.Machine) ?? new();
#endregion

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
using SabreTools.Data.Models.Metadata;
namespace SabreTools.Metadata.Filter
{
@@ -65,14 +64,14 @@ namespace SabreTools.Metadata.Filter
#region Matching
/// <summary>
/// Determine if a DictionaryBase object matches the group
/// Determine if a object matches the group
/// </summary>
public bool Matches(DictionaryBase dictionaryBase)
public bool Matches(object obj)
{
return GroupType switch
{
GroupType.AND => MatchesAnd(dictionaryBase),
GroupType.OR => MatchesOr(dictionaryBase),
GroupType.AND => MatchesAnd(obj),
GroupType.OR => MatchesOr(obj),
GroupType.NONE => false,
_ => false,
@@ -82,13 +81,13 @@ namespace SabreTools.Metadata.Filter
/// <summary>
/// Determines if a value matches all filters
/// </summary>
private bool MatchesAnd(DictionaryBase dictionaryBase)
private bool MatchesAnd(object obj)
{
// Run standalone filters
foreach (var filter in _subfilters)
{
// One failed match fails the group
if (!filter.Matches(dictionaryBase))
if (!filter.Matches(obj))
return false;
}
@@ -96,7 +95,7 @@ namespace SabreTools.Metadata.Filter
foreach (var group in _subgroups)
{
// One failed match fails the group
if (!group.Matches(dictionaryBase))
if (!group.Matches(obj))
return false;
}
@@ -106,13 +105,13 @@ namespace SabreTools.Metadata.Filter
/// <summary>
/// Determines if a value matches any filters
/// </summary>
private bool MatchesOr(DictionaryBase dictionaryBase)
private bool MatchesOr(object obj)
{
// Run standalone filters
foreach (var filter in _subfilters)
{
// One successful match passes the group
if (filter.Matches(dictionaryBase))
if (filter.Matches(obj))
return true;
}
@@ -120,7 +119,7 @@ namespace SabreTools.Metadata.Filter
foreach (var group in _subgroups)
{
// One successful match passes the group
if (group.Matches(dictionaryBase))
if (group.Matches(obj))
return true;
}

View File

@@ -54,18 +54,18 @@ namespace SabreTools.Metadata.Filter
#region Matching
/// <summary>
/// Determine if a DictionaryBase object matches the key and value
/// Determine if a object matches the key and value
/// </summary>
public bool Matches(DictionaryBase dictionaryBase)
public bool Matches(object obj)
{
return 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),
Operation.Equals => MatchesEqual(obj),
Operation.NotEquals => MatchesNotEqual(obj),
Operation.GreaterThan => MatchesGreaterThan(obj),
Operation.GreaterThanOrEqual => MatchesGreaterThanOrEqual(obj),
Operation.LessThan => MatchesLessThan(obj),
Operation.LessThanOrEqual => MatchesLessThanOrEqual(obj),
Operation.NONE => false,
_ => false,
@@ -75,10 +75,10 @@ namespace SabreTools.Metadata.Filter
/// <summary>
/// Determines if a value matches exactly
/// </summary>
private bool MatchesEqual(DictionaryBase dictionaryBase)
private bool MatchesEqual(object obj)
{
// Process the check value
if (!GetCheckValue(dictionaryBase, Key.FieldName, out string? checkValue))
if (!GetCheckValue(obj, Key.FieldName, out string? checkValue))
return string.IsNullOrEmpty(Value);
// If a null value is expected
@@ -117,10 +117,10 @@ namespace SabreTools.Metadata.Filter
/// <summary>
/// Determines if a value does not match exactly
/// </summary>
private bool MatchesNotEqual(DictionaryBase dictionaryBase)
private bool MatchesNotEqual(object obj)
{
// Process the check value
if (!GetCheckValue(dictionaryBase, Key.FieldName, out string? checkValue))
if (!GetCheckValue(obj, Key.FieldName, out string? checkValue))
return string.IsNullOrEmpty(Value);
// If a null value is expected
@@ -159,10 +159,10 @@ namespace SabreTools.Metadata.Filter
/// <summary>
/// Determines if a value is strictly greater than
/// </summary>
private bool MatchesGreaterThan(DictionaryBase dictionaryBase)
private bool MatchesGreaterThan(object obj)
{
// Process the check value
if (!GetCheckValue(dictionaryBase, Key.FieldName, out string? checkValue))
if (!GetCheckValue(obj, Key.FieldName, out string? checkValue))
return string.IsNullOrEmpty(Value);
// Null is always failure
@@ -191,10 +191,10 @@ namespace SabreTools.Metadata.Filter
/// <summary>
/// Determines if a value is greater than or equal
/// </summary>
private bool MatchesGreaterThanOrEqual(DictionaryBase dictionaryBase)
private bool MatchesGreaterThanOrEqual(object obj)
{
// Process the check value
if (!GetCheckValue(dictionaryBase, Key.FieldName, out string? checkValue))
if (!GetCheckValue(obj, Key.FieldName, out string? checkValue))
return string.IsNullOrEmpty(Value);
// Null is always failure
@@ -223,10 +223,10 @@ namespace SabreTools.Metadata.Filter
/// <summary>
/// Determines if a value is strictly less than
/// </summary>
private bool MatchesLessThan(DictionaryBase dictionaryBase)
private bool MatchesLessThan(object obj)
{
// Process the check value
if (!GetCheckValue(dictionaryBase, Key.FieldName, out string? checkValue))
if (!GetCheckValue(obj, Key.FieldName, out string? checkValue))
return string.IsNullOrEmpty(Value);
// Null is always failure
@@ -255,10 +255,10 @@ namespace SabreTools.Metadata.Filter
/// <summary>
/// Determines if a value is less than or equal
/// </summary>
private bool MatchesLessThanOrEqual(DictionaryBase dictionaryBase)
private bool MatchesLessThanOrEqual(object obj)
{
// Process the check value
if (!GetCheckValue(dictionaryBase, Key.FieldName, out string? checkValue))
if (!GetCheckValue(obj, Key.FieldName, out string? checkValue))
return string.IsNullOrEmpty(Value);
// Null is always failure
@@ -334,12 +334,13 @@ namespace SabreTools.Metadata.Filter
}
/// <summary>
/// Get the check value for a field from a DictionaryBase
/// Get the check value for a field
/// </summary>
private static bool GetCheckValue(DictionaryBase dictionaryBase, string fieldName, out string? checkValue)
/// TODO: Figure out how to not have this hardcoded
private static bool GetCheckValue(object obj, string fieldName, out string? checkValue)
{
// Handle type-specific properties
switch (dictionaryBase)
switch (obj)
{
case Adjuster item when fieldName == "default":
checkValue = item.Default.FromYesNo();
@@ -802,6 +803,9 @@ namespace SabreTools.Metadata.Filter
case Header item when fieldName == "emulatorversion":
checkValue = item.EmulatorVersion;
return true;
case Header item when fieldName == "filename":
checkValue = item.FileName;
return true;
case Header item when fieldName == "forcemerging":
checkValue = item.ForceMerging.AsStringValue();
return true;
@@ -1689,15 +1693,8 @@ namespace SabreTools.Metadata.Filter
}
// If the key doesn't exist, we count it as null
if (!dictionaryBase.ContainsKey(fieldName))
{
checkValue = null;
return false;
}
// If the value in the dictionary is null
checkValue = dictionaryBase.ReadString(fieldName);
return true;
checkValue = null;
return false;
}
/// <summary>

View File

@@ -30,15 +30,15 @@ namespace SabreTools.Metadata.Filter
}
/// <summary>
/// Run filtering on a DictionaryBase item
/// Run filtering on an item
/// </summary>
public bool Run(DictionaryBase dictionaryBase)
public bool Run(object obj)
{
string? itemName = dictionaryBase switch
string? itemName = obj switch
{
Header => "header",
Machine => "machine",
DatItem => TypeHelper.GetXmlRootAttributeElementName(dictionaryBase.GetType()),
DatItem => TypeHelper.GetXmlRootAttributeElementName(obj.GetType()),
_ => null,
};
@@ -56,7 +56,7 @@ namespace SabreTools.Metadata.Filter
continue;
// If we don't get a match, it's a failure
bool matchOne = Filters[filterKey].Matches(dictionaryBase);
bool matchOne = Filters[filterKey].Matches(obj);
if (!matchOne)
return false;
}

View File

@@ -1,257 +0,0 @@
using SabreTools.Data.Models.Metadata;
using Xunit;
namespace SabreTools.Metadata.Test
{
public class ModelBackedItemTests
{
#region Private Testing Classes
/// <summary>
/// Testing implementation of DictionaryBase
/// </summary>
private class TestDictionaryBase : DictionaryBase
{
public const string NameKey = "__NAME__";
}
/// <summary>
/// Testing implementation of ModelBackedItem
/// </summary>
private class TestModelBackedItem : ModelBackedItem<TestDictionaryBase>
{
#region Comparision Methods
/// <inheritdoc/>
public override bool Equals(ModelBackedItem? other)
{
// If other is null
if (other is null)
return false;
// If the type is mismatched
if (other is not TestModelBackedItem otherItem)
return false;
// Compare internal models
return _internal.Equals(otherItem._internal);
}
/// <inheritdoc/>
public override bool Equals(ModelBackedItem<TestDictionaryBase>? other)
{
// If other is null
if (other is null)
return false;
// If the type is mismatched
if (other is not TestModelBackedItem otherItem)
return false;
// Compare internal models
return _internal.Equals(otherItem._internal);
}
#endregion
}
/// <summary>
/// Alternate testing implementation of ModelBackedItem
/// </summary>
private class TestModelAltBackedItem : ModelBackedItem<TestDictionaryBase>
{
#region Comparision Methods
/// <inheritdoc/>
public override bool Equals(ModelBackedItem? other)
{
// If other is null
if (other is null)
return false;
// If the type is mismatched
if (other is not TestModelAltBackedItem otherItem)
return false;
// Compare internal models
return _internal[TestDictionaryBase.NameKey] == otherItem._internal[TestDictionaryBase.NameKey];
}
/// <inheritdoc/>
public override bool Equals(ModelBackedItem<TestDictionaryBase>? other)
{
// If other is null
if (other is null)
return false;
// If the type is mismatched
if (other is not TestModelAltBackedItem otherItem)
return false;
// Compare internal models
return _internal[TestDictionaryBase.NameKey] == otherItem._internal[TestDictionaryBase.NameKey];
}
#endregion
}
#endregion
#region Remove
[Fact]
public void Remove_NullItem_False()
{
TestModelBackedItem? modelBackedItem = null;
string? fieldName = TestDictionaryBase.NameKey;
bool? actual = modelBackedItem?.Remove(fieldName);
Assert.Null(actual);
}
[Fact]
public void Remove_EmptyFieldName_False()
{
var modelBackedItem = new TestModelBackedItem();
string? fieldName = string.Empty;
bool actual = modelBackedItem.Remove(fieldName);
Assert.False(actual);
}
[Fact]
public void Remove_MissingKey_True()
{
var modelBackedItem = new TestModelBackedItem();
string? fieldName = TestDictionaryBase.NameKey;
bool actual = modelBackedItem.Remove(fieldName);
Assert.True(actual);
Assert.Null(modelBackedItem.ReadString(fieldName));
}
[Fact]
public void Remove_ValidKey_True()
{
var modelBackedItem = new TestModelBackedItem();
modelBackedItem.Write(TestDictionaryBase.NameKey, "value");
string? fieldName = TestDictionaryBase.NameKey;
bool actual = modelBackedItem.Remove(fieldName);
Assert.True(actual);
Assert.Null(modelBackedItem.ReadString(fieldName));
}
#endregion
#region Replace
[Fact]
public void Replace_NullFrom_False()
{
TestModelBackedItem? from = null;
var to = new TestModelBackedItem();
string? fieldName = TestDictionaryBase.NameKey;
bool actual = to.Replace(from, fieldName);
Assert.False(actual);
}
[Fact]
public void Replace_NullTo_False()
{
TestModelBackedItem? from = null;
TestModelBackedItem? to = new TestModelBackedItem();
string? fieldName = TestDictionaryBase.NameKey;
bool actual = to.Replace(from, fieldName);
Assert.False(actual);
}
[Fact]
public void Replace_EmptyFieldName_False()
{
TestModelBackedItem? from = new TestModelBackedItem();
TestModelBackedItem? to = new TestModelBackedItem();
string? fieldName = string.Empty;
bool actual = to.Replace(from, fieldName);
Assert.False(actual);
}
[Fact]
public void Replace_MissingKey_False()
{
TestModelBackedItem? from = new TestModelBackedItem();
TestModelBackedItem? to = new TestModelBackedItem();
string? fieldName = TestDictionaryBase.NameKey;
bool actual = to.Replace(from, fieldName);
Assert.False(actual);
}
[Fact]
public void Replace_ValidKey_True()
{
TestModelBackedItem? from = new TestModelBackedItem();
from.Write(TestDictionaryBase.NameKey, "value");
TestModelBackedItem? to = new TestModelBackedItem();
string? fieldName = TestDictionaryBase.NameKey;
bool actual = to.Replace(from, fieldName);
Assert.True(actual);
Assert.Equal("value", to.ReadString(TestDictionaryBase.NameKey));
}
#endregion
#region WriteWithValidation
[Fact]
public void WriteWithValidation_NullItem_False()
{
TestModelBackedItem? modelBackedItem = null;
string? fieldName = TestDictionaryBase.NameKey;
object value = "value";
bool? actual = modelBackedItem?.WriteWithValidation(fieldName, value);
Assert.Null(actual);
}
[Fact]
public void WriteWithValidation_EmptyFieldName_False()
{
TestModelBackedItem? modelBackedItem = new TestModelBackedItem();
string? fieldName = string.Empty;
object value = "value";
bool actual = modelBackedItem.WriteWithValidation(fieldName, value);
Assert.False(actual);
}
[Fact]
public void WriteWithValidation_MissingKey_False()
{
TestModelBackedItem? modelBackedItem = new TestModelBackedItem();
string? fieldName = "sha1";
object value = "value";
bool actual = modelBackedItem.WriteWithValidation(fieldName, value);
Assert.False(actual);
}
[Fact]
public void WriteWithValidation_InvalidKey_True()
{
TestModelBackedItem? modelBackedItem = new TestModelBackedItem();
modelBackedItem.Write(TestDictionaryBase.NameKey, "old");
string? fieldName = "INVALID";
object value = "value";
bool actual = modelBackedItem.WriteWithValidation(fieldName, value);
Assert.False(actual);
Assert.Null(modelBackedItem.ReadString(fieldName));
}
[Fact]
public void WriteWithValidation_ValidKey_True()
{
TestModelBackedItem? modelBackedItem = new TestModelBackedItem();
modelBackedItem.Write(TestDictionaryBase.NameKey, "old");
string? fieldName = TestDictionaryBase.NameKey;
object value = "value";
bool actual = modelBackedItem.WriteWithValidation(fieldName, value);
Assert.True(actual);
Assert.Equal(value, modelBackedItem.ReadString(fieldName));
}
#endregion
}
}

View File

@@ -3,7 +3,7 @@
namespace SabreTools.Metadata
{
/// <summary>
/// Represents an item that's backed by a DictionaryBase item
/// Represents an item that's backed by a constructable item
/// </summary>
public abstract class ModelBackedItem : IEquatable<ModelBackedItem>
{

View File

@@ -5,9 +5,9 @@ using Newtonsoft.Json;
namespace SabreTools.Metadata
{
/// <summary>
/// Represents an item that's backed by a DictionaryBase item
/// Represents an item that's backed by a constructable item
/// </summary>
public abstract class ModelBackedItem<T> : ModelBackedItem, IEquatable<ModelBackedItem<T>> where T : Data.Models.Metadata.DictionaryBase
public abstract class ModelBackedItem<T> : ModelBackedItem, IEquatable<ModelBackedItem<T>> where T : new()
{
/// <summary>
/// Internal model wrapped by this DatItem
@@ -19,98 +19,7 @@ namespace SabreTools.Metadata
public ModelBackedItem()
{
_internal = Activator.CreateInstance<T>();
}
#endregion
#region Accessors
/// <summary>
/// Get the value from a field based on the type provided
/// </summary>
/// <typeparam name="U">Type of the value to get from the internal model</typeparam>
/// <param name="fieldName">Field to retrieve</param>
/// <returns>Value from the field, if possible</returns>
public U? Read<U>(string fieldName)
=> _internal.Read<U>(fieldName);
/// <summary>
/// Get the value from a field based on the type provided
/// </summary>
/// <param name="fieldName">Field to retrieve</param>
/// <returns>Value from the field, if possible</returns>
/// TODO: Determine if this can be removed
public bool? ReadBool(string fieldName)
=> _internal.ReadBool(fieldName);
/// <summary>
/// Get the value from a field based on the type provided
/// </summary>
/// <param name="fieldName">Field to retrieve</param>
/// <returns>Value from the field, if possible</returns>
/// TODO: Determine if this can be removed
public double? ReadDouble(string fieldName)
=> _internal.ReadDouble(fieldName);
/// <summary>
/// Get the value from a field based on the type provided
/// </summary>
/// <param name="fieldName">Field to retrieve</param>
/// <returns>Value from the field, if possible</returns>
/// TODO: Determine if this can be removed
public long? ReadLong(string fieldName)
=> _internal.ReadLong(fieldName);
/// <summary>
/// Get the value from a field based on the type provided
/// </summary>
/// <param name="fieldName">Field to retrieve</param>
/// <returns>Value from the field, if possible</returns>
public string? ReadString(string fieldName)
=> _internal.ReadString(fieldName);
/// <summary>
/// Get the value from a field based on the type provided
/// </summary>
/// <param name="fieldName">Field to retrieve</param>
/// <returns>Value from the field, if possible</returns>
/// TODO: Determine if this can be removed
public string[]? ReadStringArray(string fieldName)
=> _internal.ReadStringArray(fieldName);
/// <summary>
/// Set the value from a field based on the type provided
/// </summary>
/// <typeparam name="U">Type of the value to set in the internal model</typeparam>
/// <param name="fieldName">Field to set</param>
/// <param name="value">Value to set</param>
/// <returns>True if the value was set, false otherwise</returns>
public bool Write<U>(string fieldName, U? value)
=> _internal.Write(fieldName, value);
/// <summary>
/// Set a field from the backing item validating the field is expected
/// </summary>
/// TODO: Figure out how to add this to DictionaryBase
public bool WriteWithValidation(string fieldName, object value)
{
// If the item or field name are missing, we can't do anything
if (string.IsNullOrEmpty(fieldName))
return false;
// Retrieve the list of valid fields for the item
var constants = TypeHelper.GetConstants(_internal.GetType());
if (constants is null)
return false;
// Get the value that matches the field name provided
string? realField = Array.Find(constants, c => string.Equals(c, fieldName, StringComparison.OrdinalIgnoreCase));
if (realField is null)
return false;
// Set the field with the new value
return _internal.Write(realField, value);
_internal = new T();
}
#endregion
@@ -121,21 +30,5 @@ namespace SabreTools.Metadata
public abstract bool Equals(ModelBackedItem<T>? other);
#endregion
#region Manipulation
/// <summary>
/// Remove a field from the backing item
/// </summary>
public bool Remove(string fieldName)
=> _internal.Remove(fieldName);
/// <summary>
/// Replace a field from another ModelBackedItem
/// </summary>
public bool Replace(ModelBackedItem<T>? from, string fieldName)
=> _internal.Replace(from?._internal, fieldName);
#endregion
}
}

View File

@@ -352,7 +352,7 @@ namespace SabreTools.Serialization.CrossModel
{
var info = new Info();
var sources = item.Read<string[]>(Data.Models.Metadata.InfoSource.SourceKey);
var sources = item.Source;
if (sources is not null && sources.Length > 0)
info.Source = [.. sources];

View File

@@ -337,10 +337,7 @@ namespace SabreTools.Serialization.CrossModel
var sources = item.Source;
if (sources is not null && sources.Length > 0)
{
string[] sourcesCopy = [.. sources];
infoSource[Data.Models.Metadata.InfoSource.SourceKey] = sourcesCopy;
}
infoSource.Source = [.. sources];
return infoSource;
}

View File

@@ -37,7 +37,7 @@ namespace SabreTools.Serialization.CrossModel
/// </summary>
private static Data.Models.Metadata.Header ConvertHeaderToInternalModel(Datafile item)
{
var header = item.Header is not null ? ConvertHeaderToInternalModel(item.Header) : [];
var header = item.Header is not null ? ConvertHeaderToInternalModel(item.Header) : new();
header.Build = item.Build;
header.Debug = item.Debug;

View File

@@ -66,8 +66,7 @@ namespace SabreTools.Serialization.CrossModel
};
}
if (item.RefName != null
|| item.EmulatorVersion != null)
if (item.RefName != null || item.EmulatorVersion != null)
{
metadataFile.Emulator = new Emulator
{

View File

@@ -76,7 +76,7 @@ namespace SabreTools.Serialization.CrossModel
{
var row = new Row
{
FileName = header?.ReadString("FILENAME"), // TODO: Make this an actual key to retrieve
FileName = header?.FileName,
InternalName = header?.Name,
Description = header?.Description,
GameName = parent.Name,
@@ -104,7 +104,7 @@ namespace SabreTools.Serialization.CrossModel
{
var row = new Row
{
FileName = header?.ReadString("FILENAME"), // TODO: Make this an actual key to retrieve on an item -- OriginalFilename
FileName = header?.FileName,
InternalName = header?.Name,
Description = header?.Description,
GameName = parent.Name,
@@ -131,7 +131,7 @@ namespace SabreTools.Serialization.CrossModel
{
var row = new Row
{
FileName = header?.ReadString("FILENAME"), // TODO: Make this an actual key to retrieve
FileName = header?.FileName,
InternalName = header?.Name,
Description = header?.Description,
GameName = parent.Name,

View File

@@ -35,7 +35,7 @@ namespace SabreTools.Serialization.CrossModel
if (item.Row is not null && item.Row.Length > 0)
{
var first = item.Row[0];
header["FILENAME"] = first.FileName; // TODO: Make this an actual key to retrieve on an item -- OriginalFilename
header.FileName = first.FileName;
header.Name = first.FileName;
header.Description = first.Description;
}