using System; using SabreTools.Data.Models.Metadata; namespace SabreTools.Metadata.Filter { /// /// Represents a single filter key /// public class FilterKey { #region Properties /// /// Item name associated with the filter /// public readonly string ItemName; /// /// Field name associated with the filter /// public readonly string FieldName; #endregion #region Constants /// /// Cached item type names for filter selection /// #if NET5_0_OR_GREATER private static readonly string[] _datItemTypeNames = Enum.GetNames(); #else private static readonly string[] _datItemTypeNames = Enum.GetNames(typeof(ItemType)); #endif #region Per-Type Accepted Keys /// /// Known keys for Adjuster /// private static readonly string[] _adjusterKeys = [ "condition.mask", "condition.relation", "condition.tag", "condition.value", "default", "name" ]; /// /// Known keys for Archive /// private static readonly string[] _archiveKeys = [ "additional", "adult", "alt", "bios", "categories", "clone", "clonetag", "complete", "dat", "datternote", "description", "devstatus", "gameid1", "gameid2", "langchecked", "languages", "licensed", "listed", "mergeof", "mergename", "name", "namealt", "number", "physical", "pirate", "private", "region", "regparent", "showlang", "special1", "special2", "stickynote", "version1", "version2", ]; /// /// Known keys for BiosSet /// private static readonly string[] _biossetKeys = [ "default", "description", "name", ]; /// /// Known keys for Chip /// private static readonly string[] _chipKeys = [ "chiptype", "clock", "flags", "name", "soundonly", "tag", ]; /// /// Known keys for Configuration /// private static readonly string[] _configurationKeys = [ "condition.mask", "condition.relation", "condition.tag", "condition.value", "mask", "name", "tag", ]; /// /// Known keys for ConfLocation /// private static readonly string[] _confLocationKeys = [ "inverted", "name", "number", ]; /// /// Known keys for ConfSetting /// private static readonly string[] _confSettingKeys = [ "condition.mask", "condition.relation", "condition.tag", "condition.value", "default", "name", "value", ]; /// /// Known keys for Control /// private static readonly string[] _controlKeys = [ "buttons", "controltype", "keydelta", "maximum", "minimum", "player", "reqbuttons", "reverse", "sensitivity", "ways", "ways2", "ways3", ]; /// /// Known keys for DataArea /// private static readonly string[] _dataAreaKeys = [ "endianness", "name", "size", "width", ]; /// /// Known keys for Device /// private static readonly string[] _deviceKeys = [ "devicetype", "extension.name", "fixedimage", "instance.briefname", "instance.name", "interface", "mandatory", "tag", ]; /// /// Known keys for DeviceRef /// private static readonly string[] _deviceRefKeys = [ "name", ]; /// /// Known keys for DipLocation /// private static readonly string[] _dipLocationKeys = [ "inverted", "name", "number", ]; /// /// Known keys for DipSwitch /// private static readonly string[] _dipSwitchKeys = [ "condition.mask", "condition.relation", "condition.tag", "condition.value", "default", "mask", "name", "tag", ]; /// /// Known keys for DipSwitch /// private static readonly string[] _dipValueKeys = [ "condition.mask", "condition.relation", "condition.tag", "condition.value", "default", "name", "value", ]; /// /// Known keys for Disk /// private static readonly string[] _diskKeys = [ "flags", "index", "md5", "merge", "name", "optional", "region", "sha1", "status", "writable", ]; /// /// Known keys for DiskArea /// private static readonly string[] _diskAreaKeys = [ "name", ]; /// /// Known keys for Display /// private static readonly string[] _displayKeys = [ "aspectx", "aspecty", "displaytype", "flipx", "freq", "hbend", "hbstart", "height", "htotal", "orientation", "pixclock", "refresh", "rotate", "screen", "tag", "vbend", "vbstart", "vtotal", "width", "x", "y", ]; /// /// Known keys for Driver /// private static readonly string[] _driverKeys = [ "blit", "cocktail", "color", "emulation", "incomplete", "nosoundhardware", "palettesize", "requiresartwork", "savestate", "sound", "status", "unofficial", ]; /// /// Known keys for Feature/PartFeature /// private static readonly string[] _featureKeys = [ "featuretype", "name", "overall", "status", "value", ]; /// /// Known keys for Header /// private static readonly string[] _headerKeys = [ "author", "biosmode", "build", "category", "comment", "date", "datversion", "debug", "description", "email", "emulatorversion", "filename", "forcemerging", "forcenodump", "forcepacking", "forcezipping", "header", "headerskipper", "homepage", "id", "imfolder", "lockbiosmode", "lockrommode", "locksamplemode", "mameconfig", "name", "notes", "plugin", "refname", "rommode", "romtitle", "rootdir", "samplemode", "schemalocation", "screenshotsheight", "screenshotswidth", "skipper", "system", "timestamp", "type", "url", "version", ]; /// /// Known keys for Info /// private static readonly string[] _infoKeys = [ "name", "value", ]; /// /// Known keys for Input /// private static readonly string[] _inputKeys = [ "buttons", "coins", "control", "controlattr", "players", "service", "tilt", ]; /// /// Known keys for Machine /// private static readonly string[] _machineKeys = [ "board", "buttons", "category", "cloneof", "comment", "company", "control", "crc", "country", "description", "developer", "dirname", "displaycount", "displaytype", "duplicateid", "emulator", "enabled", "extra", "favorite", "genmsxid", "genre", "hash", "history", "id", "im1crc", "im2crc", "imagenumber", "isbios", "isdevice", "ismechanical", "language", "location", "manufacturer", "name", "notes", "playedcount", "playedtime", "players", "publisher", "ratings", "rebuildto", "relatedto", "releasenumber", "romof", "rotation", "runnable", "sampleof", "savetype", "score", "source", "sourcefile", "sourcerom", "status", "subgenre", "supported", "system", "tags", "titleid", "type", "url", "year", ]; /// /// Known keys for Media /// private static readonly string[] _mediaKeys = [ "md5", "name", "sha1", "sha256", "spamsum", ]; /// /// Known keys for Original /// private static readonly string[] _originalKeys = [ "content", "value", ]; /// /// Known keys for Part /// private static readonly string[] _partKeys = [ "interface", "name", ]; /// /// Known keys for Port /// private static readonly string[] _portKeys = [ "analog.mask", "tag", ]; /// /// Known keys for RamOption /// private static readonly string[] _ramOptionKeys = [ "content", "default", "name", ]; /// /// Known keys for Release /// private static readonly string[] _releaseKeys = [ "date", "default", "language", "name", "region", ]; /// /// Known keys for ReleaseDetails /// private static readonly string[] _releaseDetailsKeys = [ "appendtonumber", "archivename", "category", "comment", "date", "dirname", "group", "id", "nfocrc", "nfoname", "nfosize", "origin", "originalformat", "region", "rominfo", "tool", ]; /// /// Known keys for Rom /// private static readonly string[] _romKeys = [ "album", "alt_romname", "alt_title", "altromname", "alttitle", "artist", "asr_detected_lang", "asr_detected_lang_conf", "asr_transcribed_lang", "asrdetectedlang", "asrdetectedlangconf", "asrtranscribedlang", "bios", "bitrate", "bittorrentmagnethash", "btih", "cloth_cover_detection_module_version", "clothcoverdetectionmoduleversion", "collection-catalog-number", "collectioncatalognumber", "comment", "crc", "crc16", "crc32", "crc64", "creator", "date", "dispose", "extension", "filecount", "fileisavailable", "flags", "format", "header", "height", "hocr_char_to_word_hocr_version", "hocr_char_to_word_module_version", "hocr_fts_text_hocr_version", "hocr_fts_text_module_version", "hocr_pageindex_hocr_version", "hocr_pageindex_module_version", "hocrchartowordhocrversion", "hocrchartowordmoduleversion", "hocrftstexthocrversion", "hocrftstextmoduleversion", "hocrpageindexhocrversion", "hocrpageindexmoduleversion", "inverted", "lastmodifiedtime", "length", "loadflag", "matrix_number", "matrixnumber", "md2", "md4", "md5", "mediatype", "merge", "mia", "mtime", "name", "ocr", "ocr_converted", "ocr_detected_lang", "ocr_detected_lang_conf", "ocr_detected_script", "ocr_detected_script_conf", "ocr_module_version", "ocr_parameters", "offset", "openmsxmediatype", "openmsxtype", "optional", "original", "pdf_module_version", "pdfmoduleversion", "preview-image", "previewimage", "publisher", "region", "remark", "ripemd128", "ripemd160", "rotation", "serial", "sha1", "sha256", "sha384", "sha512", "size", "soundonly", "source", "spamsum", "start", "status", "summation", "tesseractocr", "tesseractocrconverted", "tesseractocrdetectedlang", "tesseractocrdetectedlangconf", "tesseractocrdetectedscript", "tesseractocrdetectedscriptconf", "tesseractocrmoduleversion", "tesseractocrparameters", "title", "track", "value", "whisper_asr_module_version", "whisper_model_hash", "whisper_model_name", "whisper_version", "whisperasrmoduleversion", "whispermodelhash", "whispermodelname", "whisperversion", "width", "word_conf_0_10", "word_conf_11_20", "word_conf_21_30", "word_conf_31_40", "word_conf_41_50", "word_conf_51_60", "word_conf_61_70", "word_conf_71_80", "word_conf_81_90", "word_conf_91_100", "wordconfidenceinterval0to10", "wordconfidenceinterval11to20", "wordconfidenceinterval21to30", "wordconfidenceinterval31to40", "wordconfidenceinterval41to50", "wordconfidenceinterval51to60", "wordconfidenceinterval61to70", "wordconfidenceinterval71to80", "wordconfidenceinterval81to90", "wordconfidenceinterval91to100", "xxhash3128", "xxhash364", ]; /// /// Known keys for Sample /// private static readonly string[] _sampleKeys = [ "name", ]; /// /// Known keys for Serials /// private static readonly string[] _serialsKeys = [ "boxbarcode", "boxserial", "chipserial", "digitalserial1", "digitalserial2", "lockoutserial", "mediaserial1", "mediaserial2", "mediaserial3", "mediastamp", "pcbserial", "romchipserial1", "romchipserial2", "savechipserial", ]; /// /// Known keys for SharedFeat /// private static readonly string[] _sharedFeatKeys = [ "name", "value", ]; /// /// Known keys for Slot /// private static readonly string[] _slotKeys = [ "name", ]; /// /// Known keys for SlotOption /// private static readonly string[] _slotOptionKeys = [ "default", "devname", "name", ]; /// /// Known keys for SoftwareList /// private static readonly string[] _softwareListKeys = [ "filter", "name", "status", "tag", ]; /// /// Known keys for Sound /// private static readonly string[] _soundKeys = [ "channels", ]; /// /// Known keys for SourceDetails /// private static readonly string[] _sourceDetailsKeys = [ "appendtonumber", "comment1", "comment2", "dumpdate", "dumpdateinfo", "dumper", "id", "link1", "link1public", "link2", "link2public", "link3", "link3public", "mediatitle", "nodump", "origin", "originalformat", "project", "region", "releasedate", "releasedateinfo", "rominfo", "section", "tool", ]; /// /// Known keys for Video /// private static readonly string[] _videoKeys = [ "aspectx", "aspecty", "displaytype", "freq", "height", "orientation", "refresh", "rotate", "screen", "width", "x", "y", ]; #endregion #endregion /// /// Validating combined key constructor /// public FilterKey(string? key) { if (!ParseFilterId(key, out string itemName, out string fieldName)) throw new ArgumentException($"{nameof(key)} could not be parsed", 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)} was not recognized", 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 (string.IsNullOrEmpty(itemFieldString)) 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 = string.Join(".", splitFilter, 1, splitFilter.Length - 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 if there's a match to a property string localFieldName = fieldName; string? propertyMatch = Array.Find(_headerKeys, c => string.Equals(c, localFieldName, StringComparison.OrdinalIgnoreCase)); if (propertyMatch is null) return false; // Return the sanitized ID itemName = "header"; fieldName = propertyMatch.ToLowerInvariant(); return true; } /// /// Parse and validate machine/game fields /// private static bool ParseMachineFilterId(ref string itemName, ref string fieldName) { // Get if there's a match to a property string localFieldName = fieldName; string? propertyMatch = Array.Find(_machineKeys, c => string.Equals(c, localFieldName, StringComparison.OrdinalIgnoreCase)); if (propertyMatch is null) return false; // Return the sanitized ID itemName = "machine"; fieldName = propertyMatch.ToLowerInvariant(); 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)) { // Handle item type if (string.Equals(fieldName, "type", StringComparison.OrdinalIgnoreCase)) { itemName = "item"; fieldName = "type"; return true; } // If we get any matches string localFieldName = fieldName; string? matchedType = Array.Find(_datItemTypeNames, t => DatItemContainsField(t, localFieldName)); if (matchedType is not null) { // Check for a matching field string? matchedField = GetMatchingField(matchedType, fieldName); if (matchedField is null) return false; itemName = "item"; fieldName = matchedField; return true; } } else { // Check for a matching field string? matchedField = GetMatchingField(itemName, fieldName); if (matchedField is 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) is not null; /// /// Determine if an item type contains a field /// private static string? GetMatchingField(string itemName, string fieldName) { // Get the set of properties string[]? properties = itemName.ToLowerInvariant() switch { "adjuster" => _adjusterKeys, "archive" => _archiveKeys, "biosset" => _biossetKeys, "chip" => _chipKeys, "configuration" => _configurationKeys, "conflocation" => _confLocationKeys, "confsetting" => _confSettingKeys, "control" => _controlKeys, "dataarea" => _dataAreaKeys, "device" => _deviceKeys, "deviceref" => _deviceRefKeys, "diplocation" => _dipLocationKeys, "dipswitch" => _dipSwitchKeys, "dipvalue" => _dipValueKeys, "disk" => _diskKeys, "diskarea" => _diskAreaKeys, "display" => _displayKeys, "driver" => _driverKeys, "feature" or "partfeature" => _featureKeys, "game" or "machine" or "resource" or "set" => _machineKeys, "header" => _headerKeys, "info" => _infoKeys, "input" => _inputKeys, "media" => _mediaKeys, "original" => _originalKeys, "part" => _partKeys, "port" => _portKeys, "ramoption" => _ramOptionKeys, "release" => _releaseKeys, "releasedetails" => _releaseDetailsKeys, "rom" => _romKeys, "sample" => _sampleKeys, "serials" => _serialsKeys, "sharedfeat" => _sharedFeatKeys, "slot" => _slotKeys, "slotoption" => _slotOptionKeys, "softwarelist" => _softwareListKeys, "sound" => _soundKeys, "sourcedetails" => _sourceDetailsKeys, "video" => _videoKeys, _ => null, }; if (properties is null) return null; // Get if there's a match to a property string? propertyMatch = Array.Find(properties, c => string.Equals(c, fieldName, StringComparison.OrdinalIgnoreCase)); return propertyMatch?.ToLowerInvariant(); } } }