using System; using System.Text.RegularExpressions; using SabreTools.Data.Extensions; using SabreTools.Data.Models.Metadata; using SabreTools.Text.Extensions; namespace SabreTools.Metadata.Filter { /// /// Represents a single filtering object /// /// TODO: Add ability to have a set of values that are accepted public class FilterObject { /// /// Item key associated with the filter /// public readonly FilterKey Key; /// /// Value to match in the filter /// public readonly string? Value; /// /// Operation on how to match the filter /// public readonly Operation Operation; public FilterObject(string? filterString) { if (!SplitFilterString(filterString, out var keyItem, out Operation operation, out var value)) throw new ArgumentException($"{nameof(filterString)} could not be parsed", nameof(filterString)); Key = new FilterKey(keyItem); Value = value; Operation = operation; } public FilterObject(string itemField, string? value, string? operation) { Key = new FilterKey(itemField); Value = value; Operation = GetOperation(operation); } public FilterObject(string itemField, string? value, Operation operation) { Key = new FilterKey(itemField); Value = value; Operation = operation; } #region Matching /// /// Determine if a DictionaryBase object matches the key and value /// public bool Matches(DictionaryBase dictionaryBase) { 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.NONE => false, _ => false, }; } /// /// Determines if a value matches exactly /// private bool MatchesEqual(DictionaryBase dictionaryBase) { // Process the check value if (!GetCheckValue(dictionaryBase, Key.FieldName, out string? checkValue)) return string.IsNullOrEmpty(Value); // If a null value is expected if (checkValue is null) return string.IsNullOrEmpty(Value); // If we have both a potentally boolean check and value bool? checkValueBool = checkValue.AsYesNo(); bool? matchValueBool = Value.AsYesNo(); if (checkValueBool is not null && matchValueBool is not null) return checkValueBool == matchValueBool; // If we have both a potentially numeric check and value if (NumberHelper.IsNumeric(checkValue) && NumberHelper.IsNumeric(Value)) { // Check Int64 values long? checkValueLong = NumberHelper.ConvertToInt64(checkValue); long? matchValueLong = NumberHelper.ConvertToInt64(Value); if (checkValueLong is not null && matchValueLong is not null) return checkValueLong == matchValueLong; // Check Double values double? checkValueDouble = NumberHelper.ConvertToDouble(checkValue); double? matchValueDouble = NumberHelper.ConvertToDouble(Value); if (checkValueDouble is not null && matchValueDouble is not null) return checkValueDouble == matchValueDouble; } // If the value might contain valid Regex if (Value is not null && ContainsRegex(Value)) return Regex.IsMatch(checkValue, Value); return string.Equals(checkValue, Value, StringComparison.OrdinalIgnoreCase); } /// /// Determines if a value does not match exactly /// private bool MatchesNotEqual(DictionaryBase dictionaryBase) { // Process the check value if (!GetCheckValue(dictionaryBase, Key.FieldName, out string? checkValue)) return string.IsNullOrEmpty(Value); // If a null value is expected if (checkValue is null) return !string.IsNullOrEmpty(Value); // If we have both a potentally boolean check and value bool? checkValueBool = checkValue.AsYesNo(); bool? matchValueBool = Value.AsYesNo(); if (checkValueBool is not null && matchValueBool is not null) return checkValueBool != matchValueBool; // If we have both a potentially numeric check and value if (NumberHelper.IsNumeric(checkValue) && NumberHelper.IsNumeric(Value)) { // Check Int64 values long? checkValueLong = NumberHelper.ConvertToInt64(checkValue); long? matchValueLong = NumberHelper.ConvertToInt64(Value); if (checkValueLong is not null && matchValueLong is not null) return checkValueLong != matchValueLong; // Check Double values double? checkValueDouble = NumberHelper.ConvertToDouble(checkValue); double? matchValueDouble = NumberHelper.ConvertToDouble(Value); if (checkValueDouble is not null && matchValueDouble is not null) return checkValueDouble != matchValueDouble; } // If the value might contain valid Regex if (Value is not null && ContainsRegex(Value)) return !Regex.IsMatch(checkValue, Value); return !string.Equals(checkValue, Value, StringComparison.OrdinalIgnoreCase); } /// /// Determines if a value is strictly greater than /// private bool MatchesGreaterThan(DictionaryBase dictionaryBase) { // Process the check value if (!GetCheckValue(dictionaryBase, Key.FieldName, out string? checkValue)) return string.IsNullOrEmpty(Value); // Null is always failure if (checkValue is null) return false; // If we have both a potentially numeric check and value if (NumberHelper.IsNumeric(checkValue) && NumberHelper.IsNumeric(Value)) { // Check Int64 values long? checkValueLong = NumberHelper.ConvertToInt64(checkValue); long? matchValueLong = NumberHelper.ConvertToInt64(Value); if (checkValueLong is not null && matchValueLong is not null) return checkValueLong > matchValueLong; // Check Double values double? checkValueDouble = NumberHelper.ConvertToDouble(checkValue); double? matchValueDouble = NumberHelper.ConvertToDouble(Value); if (checkValueDouble is not null && matchValueDouble is not null) return checkValueDouble > matchValueDouble; } return false; } /// /// Determines if a value is greater than or equal /// private bool MatchesGreaterThanOrEqual(DictionaryBase dictionaryBase) { // Process the check value if (!GetCheckValue(dictionaryBase, Key.FieldName, out string? checkValue)) return string.IsNullOrEmpty(Value); // Null is always failure if (checkValue is null) return false; // If we have both a potentially numeric check and value if (NumberHelper.IsNumeric(checkValue) && NumberHelper.IsNumeric(Value)) { // Check Int64 values long? checkValueLong = NumberHelper.ConvertToInt64(checkValue); long? matchValueLong = NumberHelper.ConvertToInt64(Value); if (checkValueLong is not null && matchValueLong is not null) return checkValueLong >= matchValueLong; // Check Double values double? checkValueDouble = NumberHelper.ConvertToDouble(checkValue); double? matchValueDouble = NumberHelper.ConvertToDouble(Value); if (checkValueDouble is not null && matchValueDouble is not null) return checkValueDouble >= matchValueDouble; } return false; } /// /// Determines if a value is strictly less than /// private bool MatchesLessThan(DictionaryBase dictionaryBase) { // Process the check value if (!GetCheckValue(dictionaryBase, Key.FieldName, out string? checkValue)) return string.IsNullOrEmpty(Value); // Null is always failure if (checkValue is null) return false; // If we have both a potentially numeric check and value if (NumberHelper.IsNumeric(checkValue) && NumberHelper.IsNumeric(Value)) { // Check Int64 values long? checkValueLong = NumberHelper.ConvertToInt64(checkValue); long? matchValueLong = NumberHelper.ConvertToInt64(Value); if (checkValueLong is not null && matchValueLong is not null) return checkValueLong < matchValueLong; // Check Double values double? checkValueDouble = NumberHelper.ConvertToDouble(checkValue); double? matchValueDouble = NumberHelper.ConvertToDouble(Value); if (checkValueDouble is not null && matchValueDouble is not null) return checkValueDouble < matchValueDouble; } return false; } /// /// Determines if a value is less than or equal /// private bool MatchesLessThanOrEqual(DictionaryBase dictionaryBase) { // Process the check value if (!GetCheckValue(dictionaryBase, Key.FieldName, out string? checkValue)) return string.IsNullOrEmpty(Value); // Null is always failure if (checkValue is null) return false; // If we have both a potentially numeric check and value if (NumberHelper.IsNumeric(checkValue) && NumberHelper.IsNumeric(Value)) { // Check Int64 values long? checkValueLong = NumberHelper.ConvertToInt64(checkValue); long? matchValueLong = NumberHelper.ConvertToInt64(Value); if (checkValueLong is not null && matchValueLong is not null) return checkValueLong <= matchValueLong; // Check Double values double? checkValueDouble = NumberHelper.ConvertToDouble(checkValue); double? matchValueDouble = NumberHelper.ConvertToDouble(Value); if (checkValueDouble is not null && matchValueDouble is not null) return checkValueDouble <= matchValueDouble; } return false; } #endregion #region Helpers /// /// Determine if a value may contain regex for matching /// /// /// If a value contains one of the following characters: /// ^ $ * ? + /// Then it will attempt to check if the value is regex or not. /// If none of those characters exist, then value will assumed /// not to be regex. /// private static bool ContainsRegex(string? value) { // If the value is missing, it can't be regex if (value is null) return false; // If we find a special character, try parsing as regex #if NETCOREAPP || NETSTANDARD2_1_OR_GREATER if (value.Contains('^') || value.Contains('$') || value.Contains('*') || value.Contains('?') || value.Contains('+')) #else if (value.Contains("^") || value.Contains("$") || value.Contains("*") || value.Contains("?") || value.Contains("+")) #endif { try { _ = new Regex(value); return true; } catch { return false; } } return false; } /// /// Get the check value for a field from a DictionaryBase /// private static bool GetCheckValue(DictionaryBase dictionaryBase, string fieldName, out string? checkValue) { // Handle the common name field if (fieldName == "name") { checkValue = dictionaryBase.GetName(); return true; } // Handle type-specific properties switch (dictionaryBase) { case Adjuster item when fieldName == "default": checkValue = item.Default.FromYesNo(); return true; case Analog item when fieldName == "mask": checkValue = item.Mask; return true; case Archive item when fieldName == "description": checkValue = item.Description; return true; case BiosSet item when fieldName == "default": checkValue = item.Default.FromYesNo(); return true; case BiosSet item when fieldName == "description": checkValue = item.Description; return true; case Chip item when fieldName == "type": checkValue = item.ChipType?.AsStringValue(); return true; case Chip item when fieldName == "clock": checkValue = item.Clock?.ToString(); return true; case Chip item when fieldName == "flags": checkValue = item.Flags; return true; case Chip item when fieldName == "soundonly": checkValue = item.SoundOnly.FromYesNo(); return true; case Chip item when fieldName == "tag": checkValue = item.Tag; return true; case Condition item when fieldName == "mask": checkValue = item.Mask; return true; case Condition item when fieldName == "relation": checkValue = item.Relation?.AsStringValue(); return true; case Condition item when fieldName == "tag": checkValue = item.Tag; return true; case Condition item when fieldName == "value": checkValue = item.Value; return true; case Configuration item when fieldName == "mask": checkValue = item.Mask; return true; case Configuration item when fieldName == "tag": checkValue = item.Tag; return true; case ConfLocation item when fieldName == "inverted": checkValue = item.Inverted.FromYesNo(); return true; case ConfLocation item when fieldName == "number": checkValue = item.Number?.ToString(); return true; case ConfSetting item when fieldName == "default": checkValue = item.Default.FromYesNo(); return true; case ConfSetting item when fieldName == "value": checkValue = item.Value; return true; case Control item when fieldName == "buttons": checkValue = item.Buttons?.ToString(); return true; case Control item when fieldName == "type": checkValue = item.ControlType?.AsStringValue(); return true; case Control item when fieldName == "keydelta": checkValue = item.KeyDelta?.ToString(); return true; case Control item when fieldName == "maximum": checkValue = item.Maximum?.ToString(); return true; case Control item when fieldName == "minimum": checkValue = item.Minimum?.ToString(); return true; case Control item when fieldName == "player": checkValue = item.Player?.ToString(); return true; case Control item when fieldName == "reqbuttons": checkValue = item.ReqButtons?.ToString(); return true; case Control item when fieldName == "reverse": checkValue = item.Reverse.FromYesNo(); return true; case Control item when fieldName == "sensitivity": checkValue = item.Sensitivity?.ToString(); return true; case Control item when fieldName == "ways": checkValue = item.Ways; return true; case Control item when fieldName == "ways2": checkValue = item.Ways2; return true; case Control item when fieldName == "ways3": checkValue = item.Ways3; return true; case DataArea item when fieldName == "endianness": checkValue = item.Endianness?.AsStringValue(); return true; case DataArea item when fieldName == "size": checkValue = item.Size?.ToString(); return true; case DataArea item when fieldName == "width": checkValue = item.Width?.AsStringValue(); return true; case Device item when fieldName == "type": checkValue = item.DeviceType?.AsStringValue(); return true; case Device item when fieldName == "fixedimage": checkValue = item.FixedImage; return true; case Device item when fieldName == "interface": checkValue = item.Interface; return true; case Device item when fieldName == "mandatory": checkValue = item.Mandatory.FromYesNo(); return true; case Device item when fieldName == "tag": checkValue = item.Tag; return true; case DipLocation item when fieldName == "inverted": checkValue = item.Inverted.FromYesNo(); return true; case DipLocation item when fieldName == "number": checkValue = item.Number?.ToString(); return true; case DipSwitch item when fieldName == "default": checkValue = item.Default.FromYesNo(); return true; case DipSwitch item when fieldName == "mask": checkValue = item.Mask; return true; case DipSwitch item when fieldName == "tag": checkValue = item.Tag; return true; case DipValue item when fieldName == "default": checkValue = item.Default.FromYesNo(); return true; case DipValue item when fieldName == "value": checkValue = item.Value; return true; case Disk item when fieldName == "optional": checkValue = item.Optional.FromYesNo(); return true; case Disk item when fieldName == "status": checkValue = item.Status?.AsStringValue(); return true; case Disk item when fieldName == "writable": checkValue = item.Writable.FromYesNo(); return true; case Display item when fieldName == "aspectx": checkValue = item.AspectX?.ToString(); return true; case Display item when fieldName == "aspecty": checkValue = item.AspectY?.ToString(); return true; case Display item when fieldName == "flipx": checkValue = item.FlipX.FromYesNo(); return true; case Display item when fieldName == "hbend": checkValue = item.HBEnd?.ToString(); return true; case Display item when fieldName == "hbstart": checkValue = item.HBStart?.ToString(); return true; case Display item when fieldName == "height" || fieldName == "y": checkValue = item.Height?.ToString(); return true; case Display item when fieldName == "htotal": checkValue = item.HTotal?.ToString(); return true; case Display item when fieldName == "pixclock": checkValue = item.PixClock?.ToString(); return true; case Display item when fieldName == "refresh" || fieldName == "freq": checkValue = item.Refresh?.ToString(); return true; case Display item when fieldName == "rotate" || fieldName == "orientation": checkValue = item.Rotate?.AsStringValue(); return true; case Display item when fieldName == "type" || fieldName == "screen": checkValue = item.DisplayType?.AsStringValue(); return true; case Display item when fieldName == "tag": checkValue = item.Tag; return true; case Display item when fieldName == "vbend": checkValue = item.VBEnd?.ToString(); return true; case Display item when fieldName == "vbstart": checkValue = item.VBStart?.ToString(); return true; case Display item when fieldName == "vtotal": checkValue = item.VTotal?.ToString(); return true; case Display item when fieldName == "width" || fieldName == "x": checkValue = item.Width?.ToString(); return true; case Driver item when fieldName == "blit": checkValue = item.Blit?.AsStringValue(); return true; case Driver item when fieldName == "cocktail": checkValue = item.Cocktail?.AsStringValue(); return true; case Driver item when fieldName == "color": checkValue = item.Color?.AsStringValue(); return true; case Driver item when fieldName == "emulation": checkValue = item.Emulation?.AsStringValue(); return true; case Driver item when fieldName == "incomplete": checkValue = item.Incomplete.FromYesNo(); return true; case Driver item when fieldName == "nosoundhardware": checkValue = item.NoSoundHardware.FromYesNo(); return true; case Driver item when fieldName == "palettesize": checkValue = item.PaletteSize; return true; case Driver item when fieldName == "requiresartwork": checkValue = item.RequiresArtwork.FromYesNo(); return true; case Driver item when fieldName == "savestate": checkValue = item.SaveState?.AsStringValue(); return true; case Driver item when fieldName == "sound": checkValue = item.Sound?.AsStringValue(); return true; case Driver item when fieldName == "status": checkValue = item.Status?.AsStringValue(); return true; case Driver item when fieldName == "unofficial": checkValue = item.Unofficial.FromYesNo(); return true; case Feature item when fieldName == "overall": checkValue = item.Overall?.AsStringValue(); return true; case Feature item when fieldName == "status": checkValue = item.Status?.AsStringValue(); return true; case Feature item when fieldName == "type": checkValue = item.FeatureType?.AsStringValue(); return true; case Feature item when fieldName == "value": checkValue = item.Value; return true; case Header item when fieldName == "author": checkValue = item.Author; return true; case Header item when fieldName == "biosmode": checkValue = item.BiosMode.AsStringValue(); return true; case Header item when fieldName == "build": checkValue = item.Build; return true; case Header item when fieldName == "category": checkValue = item.Category; return true; case Header item when fieldName == "comment": checkValue = item.Comment; return true; case Header item when fieldName == "date": checkValue = item.Date; return true; case Header item when fieldName == "datversion": checkValue = item.DatVersion; return true; case Header item when fieldName == "debug": checkValue = item.Debug.FromYesNo(); return true; case Header item when fieldName == "description": checkValue = item.Description; return true; case Header item when fieldName == "email": checkValue = item.Email; return true; case Header item when fieldName == "emulatorversion": checkValue = item.EmulatorVersion; return true; case Header item when fieldName == "forcemerging": checkValue = item.ForceMerging.AsStringValue(); return true; case Header item when fieldName == "forcenodump": checkValue = item.ForceNodump.AsStringValue(); return true; case Header item when fieldName == "forcepacking": checkValue = item.ForcePacking.AsStringValue(); return true; case Header item when fieldName == "forcezipping": checkValue = item.ForceZipping.FromYesNo(); return true; case Header item when fieldName == "homepage": checkValue = item.Homepage; return true; case Header item when fieldName == "id": checkValue = item.Id; return true; case Header item when fieldName == "lockbiosmode": checkValue = item.LockBiosMode.FromYesNo(); return true; case Header item when fieldName == "lockrommode": checkValue = item.LockRomMode.FromYesNo(); return true; case Header item when fieldName == "locksamplemode": checkValue = item.LockSampleMode.FromYesNo(); return true; case Header item when fieldName == "mameconfig": checkValue = item.MameConfig; return true; case Header item when fieldName == "notes": checkValue = item.Notes; return true; case Header item when fieldName == "plugin": checkValue = item.Plugin; return true; case Header item when fieldName == "refname": checkValue = item.RefName; return true; case Header item when fieldName == "rommode": checkValue = item.RomMode.AsStringValue(); return true; case Header item when fieldName == "romtitle": checkValue = item.RomTitle; return true; case Header item when fieldName == "rootdir": checkValue = item.RootDir; return true; case Header item when fieldName == "samplemode": checkValue = item.SampleMode.AsStringValue(); return true; case Header item when fieldName == "system": checkValue = item.System; return true; case Header item when fieldName == "timestamp": checkValue = item.Timestamp; return true; case Header item when fieldName == "type": checkValue = item.Timestamp; return true; case Header item when fieldName == "url": checkValue = item.Url; return true; case Header item when fieldName == "version": checkValue = item.Version; return true; case Info item when fieldName == "value": checkValue = item.Value; return true; case Input item when fieldName == "buttons": checkValue = item.Buttons?.ToString(); return true; case Input item when fieldName == "coins": checkValue = item.Coins?.ToString(); return true; case Input item when fieldName == "players": checkValue = item.Players?.ToString(); return true; case Input item when fieldName == "service": checkValue = item.Service.FromYesNo(); return true; case Input item when fieldName == "tilt": checkValue = item.Tilt.FromYesNo(); return true; case Machine item when fieldName == "description": checkValue = item.Description; return true; case Machine item when fieldName == "isbios": checkValue = item.IsBios.FromYesNo(); return true; case Machine item when fieldName == "isdevice": checkValue = item.IsDevice.FromYesNo(); return true; case Machine item when fieldName == "ismechanical": checkValue = item.IsMechanical.FromYesNo(); return true; case Machine item when fieldName == "runnable": checkValue = item.Runnable?.AsStringValue(); return true; case Machine item when fieldName == "supported": checkValue = item.Supported?.AsStringValue(); return true; case Original item when fieldName == "content": checkValue = item.Content; return true; case Original item when fieldName == "value": checkValue = item.Value.FromYesNo(); return true; case Part item when fieldName == "interface": checkValue = item.Interface; return true; case Port item when fieldName == "tag": checkValue = item.Tag; return true; case RamOption item when fieldName == "content": checkValue = item.Content; return true; case RamOption item when fieldName == "default": checkValue = item.Default.FromYesNo(); return true; case Release item when fieldName == "date": checkValue = item.Date; return true; case Release item when fieldName == "default": checkValue = item.Default.FromYesNo(); return true; case Release item when fieldName == "language": checkValue = item.Language; return true; case Release item when fieldName == "region": checkValue = item.Region; return true; case Rom item when fieldName == "dispose": checkValue = item.Dispose.FromYesNo(); return true; case Rom item when fieldName == "filecount": checkValue = item.FileCount?.ToString(); return true; case Rom item when fieldName == "fileisavailable": checkValue = item.FileIsAvailable.FromYesNo(); return true; case Rom item when fieldName == "inverted": checkValue = item.Inverted.FromYesNo(); return true; case Rom item when fieldName == "loadflag": checkValue = item.LoadFlag?.AsStringValue(); return true; case Rom item when fieldName == "mediatype": checkValue = item.OpenMSXMediaType?.AsStringValue(); return true; case Rom item when fieldName == "mia": checkValue = item.MIA.FromYesNo(); return true; case Rom item when fieldName == "optional": checkValue = item.Optional.FromYesNo(); return true; case Rom item when fieldName == "size": checkValue = item.Size?.ToString(); return true; case Rom item when fieldName == "soundonly": checkValue = item.SoundOnly.FromYesNo(); return true; case Rom item when fieldName == "status": checkValue = item.Status?.AsStringValue(); return true; case Rom item when fieldName == "value": checkValue = item.Value; return true; case SharedFeat item when fieldName == "value": checkValue = item.Value; return true; case SlotOption item when fieldName == "default": checkValue = item.Default.FromYesNo(); return true; case SlotOption item when fieldName == "devname": checkValue = item.DevName; return true; case SoftwareList item when fieldName == "filter": checkValue = item.Filter; return true; case SoftwareList item when fieldName == "status": checkValue = item.Status?.AsStringValue(); return true; case SoftwareList item when fieldName == "tag": checkValue = item.Tag; return true; case Sound item when fieldName == "channels": checkValue = item.Channels?.ToString(); return true; case Video item when fieldName == "aspectx": checkValue = item.AspectX?.ToString(); return true; case Video item when fieldName == "aspecty": checkValue = item.AspectY?.ToString(); return true; case Video item when fieldName == "height" || fieldName == "y": checkValue = item.Height?.ToString(); return true; case Video item when fieldName == "orientation" || fieldName == "rotate": checkValue = item.Orientation?.AsStringValue(); return true; case Video item when fieldName == "refresh" || fieldName == "freq": checkValue = item.Refresh?.ToString(); return true; case Video item when fieldName == "screen" || fieldName == "type": checkValue = item.Screen?.AsStringValue(); return true; case Video item when fieldName == "width" || fieldName == "x": checkValue = item.Width?.ToString(); return true; // Fallthrough to Dictionary-based checking default: break; } // 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; } /// /// Derive an operation from the input string, if possible /// private static Operation GetOperation(string? operation) { return operation?.ToLowerInvariant() switch { "=" => Operation.Equals, "=:" => Operation.Equals, "==" => Operation.Equals, ":" => Operation.Equals, "::" => Operation.Equals, ":=" => Operation.Equals, "!" => Operation.NotEquals, "!=" => Operation.NotEquals, "!:" => Operation.NotEquals, ">" => Operation.GreaterThan, ">:" => Operation.GreaterThanOrEqual, ">=" => Operation.GreaterThanOrEqual, "!<" => Operation.GreaterThanOrEqual, "<" => Operation.LessThan, "<:" => Operation.LessThanOrEqual, "<=" => Operation.LessThanOrEqual, "!>" => Operation.LessThanOrEqual, _ => Operation.NONE, }; } /// /// Derive a key, operation, and value from the input string, if possible /// 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 (string.IsNullOrEmpty(filterString)) return false; // Trim quotations, if necessary #if NETCOREAPP || NETSTANDARD2_1_OR_GREATER if (filterString!.StartsWith('\"')) filterString = filterString[1..^1]; #else if (filterString!.StartsWith("\"")) filterString = filterString.Substring(1, filterString.Length - 2); #endif // Split the string using regex var match = Regex.Match(filterString, @"^(?[a-zA-Z._]+)(?[=!:><]{1,2})(?.*)$", RegexOptions.Compiled); if (!match.Success) return false; key = match.Groups["itemField"].Value; operation = GetOperation(match.Groups["operation"].Value); // Only non-zero length values are counted as non-null if (match.Groups["value"]?.Value?.Length > 0) value = match.Groups["value"].Value; return true; } #endregion } }