using System.Text.RegularExpressions;
using System.Collections.Generic;
public static void Main()
var appTypes = new List<string>() { "QPP/QPX", "Android-App", "Firmware" };
if (Compatibility.TryParse(">3", appTypes, 4, out Compatibility compat, out string error))
compat.Incompatible = true;
Console.WriteLine("{0}, incompatible = {1}", compat, compat.Incompatible);
Console.WriteLine(compat.IsCompatible("3.3.3"));
Console.WriteLine(error);
public static readonly List<int> FirmwareAssets = new List<int> { 4, 5, 8 };
private static readonly Regex _conditionPattern = new Regex(@"^(EQ_|LOW_|LOWEQ_|HIG_|HIGEQ_|LIK_|>=|>|<=|<)?(\d+(\.(\d+|\*))+)$");
private static readonly IDictionary<string, string> _dbOperators = new Dictionary<string, string>
public string Asset { get; set; }
public string Target { get; set; }
public string Operator { get; set; }
public Version Version { get; set; }
public static bool TryParse(string condition, int asset, IEnumerable<string> appTypes, out Condition result, out string error)
bool selfCompat = FirmwareAssets.Contains(asset);
string[] parts = condition.Trim('"').Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
error = $"Invalid condition \"{condition}\"";
error = $"Condition \"{condition}\" must only contain the firmware version condition";
else if (parts.First().Contains("Firmware"))
error = $"Condition \"{condition}\" must only contain the terminal model and firmware version condition";
error = $"Condition \"{condition}\" must contain asset type, file name, and file version";
else if (appTypes == null || !appTypes.Any(r => parts.First().StartsWith(r)))
error = $"Condition \"{condition}\" contains an invalid app type";
if (error != null) return false;
string actualCondition = parts.Last().Trim(' ');
Match conditionMatch = _conditionPattern.Match(actualCondition);
if (!conditionMatch.Success)
error = $"Unexpected condition \"{condition}\"";
string rawOp = conditionMatch.Groups[3].Value == ".*" ? "*" : conditionMatch.Groups[1].Value;
string rawVer = conditionMatch.Groups[2].Value;
string @operator = GetOperatorSymbol(rawOp, true);
error = $"Invalid condition operator \"{rawOp}\"";
@operator = GetOperatorSymbol(@operator, true);
if (!Version.TryParse(@operator == "*" ? rawVer.Replace(".*", "") : rawVer, out Version version))
error = $"Invalid condition version \"{rawVer}\"";
Asset = parts.Length == 3 ? parts.First() : null,
Target = parts.Length > 1 ? parts.Skip(parts.Length - 2).First() : null,
public string ToDescription(bool viewable = true)
var sb = new StringBuilder();
bool complex = Asset != null || Target != null;
if (complex) sb.Append('"');
if (Asset != null) sb.Append(Asset).Append(':');
if (Target != null) sb.Append(Target).Append(':');
string @operator = GetOperatorSymbol(Operator, viewable);
sb.Append(Version).Append(".*");
sb.Append(@operator).Append(Version);
sb.Append(@operator).Append(Version);
if (complex) sb.Append('"');
public override string ToString()
private static string GetOperatorSymbol(string @operator, bool viewable)
KeyValuePair<string, string> operatorPair = _dbOperators.FirstOrDefault(p => p.Value == @operator || p.Key == @operator);
return viewable ? operatorPair.Key : operatorPair.Value;
public bool TestCondition(Version fileVersion, string target)
return fileVersion == Version;
return fileVersion > Version;
return fileVersion >= Version;
return fileVersion < Version;
return fileVersion <= Version;
return fileVersion.Major == Version.Major &&
(Version.Minor == -1 || fileVersion.Minor == Version.Minor) &&
(Version.Build == -1 || fileVersion.Build == Version.Build) &&
(Version.Revision == -1 || fileVersion.Revision == Version.Revision);
public class Compatibility
private IEnumerable<Condition> _conditions;
public bool Incompatible { get; set; } = true;
public const string NaCondition = "NA";
public Compatibility(IEnumerable<Condition> conditions)
_conditions = conditions;
public IEnumerable<Condition> Conditions
public static bool TryParse(string compatibility, IEnumerable<string> appTypes, int asset, out Compatibility result, out string error)
var errors = new List<string>();
var parsedConditions = new List<Condition>();
if (string.IsNullOrWhiteSpace(compatibility) || compatibility.Trim() == NaCondition)
result = new Compatibility(parsedConditions);
string[] conditions = compatibility.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string c in conditions)
if (string.IsNullOrWhiteSpace(c)) continue;
if (Condition.TryParse(c.Trim(), asset, appTypes, out Condition condition, out string conditionError))
parsedConditions.Add(condition);
errors.Add(conditionError);
error = string.Join(". ", errors);
result = new Compatibility(parsedConditions);
public bool IsCompatible(Version fileVersion, string target = null)
if (_conditions.Any(c => c.TestCondition(fileVersion, target)))
public bool IsCompatible(string fileVersion, string target = null)
return !Version.TryParse(fileVersion, out Version parsedVersion) ||
IsCompatible(parsedVersion, target);
public override string ToString()
public string ToDescription(bool viewable = true)
var sb = new StringBuilder();
return viewable ? string.Empty : NaCondition;
return string.Join(",", _conditions.Select(c => c.ToDescription(viewable)));