mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 22:33:05 +08:00
Constrain operator parsing better
This commit is contained in:
parent
26736d990f
commit
e46543a4a9
@ -194,5 +194,14 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
|
Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
|
||||||
Assert.AreEqual("double\"quote", filterCriteria.Artist.SearchTerm);
|
Assert.AreEqual("double\"quote", filterCriteria.Artist.SearchTerm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOperatorParsing()
|
||||||
|
{
|
||||||
|
const string query = "artist=><something";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual("><something", filterCriteria.Artist.SearchTerm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
17
osu.Game/Screens/Select/Filter/Operator.cs
Normal file
17
osu.Game/Screens/Select/Filter/Operator.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Select.Filter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines logical operators that can be used in the song select search box keyword filters.
|
||||||
|
/// </summary>
|
||||||
|
public enum Operator
|
||||||
|
{
|
||||||
|
Less,
|
||||||
|
LessOrEqual,
|
||||||
|
Equal,
|
||||||
|
GreaterOrEqual,
|
||||||
|
Greater
|
||||||
|
}
|
||||||
|
}
|
@ -5,13 +5,14 @@ using System;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Screens.Select.Filter;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Select
|
namespace osu.Game.Screens.Select
|
||||||
{
|
{
|
||||||
internal static class FilterQueryParser
|
internal static class FilterQueryParser
|
||||||
{
|
{
|
||||||
private static readonly Regex query_syntax_regex = new Regex(
|
private static readonly Regex query_syntax_regex = new Regex(
|
||||||
@"\b(?<key>\w+)(?<op>[=:><]+)(?<value>("".*"")|(\S*))",
|
@"\b(?<key>\w+)(?<op>(:|=|(>|<)(:|=)?))(?<value>("".*"")|(\S*))",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
internal static void ApplyQueries(FilterCriteria criteria, string query)
|
internal static void ApplyQueries(FilterCriteria criteria, string query)
|
||||||
@ -19,7 +20,7 @@ namespace osu.Game.Screens.Select
|
|||||||
foreach (Match match in query_syntax_regex.Matches(query))
|
foreach (Match match in query_syntax_regex.Matches(query))
|
||||||
{
|
{
|
||||||
var key = match.Groups["key"].Value.ToLower();
|
var key = match.Groups["key"].Value.ToLower();
|
||||||
var op = match.Groups["op"].Value;
|
var op = parseOperator(match.Groups["op"].Value);
|
||||||
var value = match.Groups["value"].Value;
|
var value = match.Groups["value"].Value;
|
||||||
|
|
||||||
if (tryParseKeywordCriteria(criteria, key, value, op))
|
if (tryParseKeywordCriteria(criteria, key, value, op))
|
||||||
@ -29,57 +30,72 @@ namespace osu.Game.Screens.Select
|
|||||||
criteria.SearchText = query;
|
criteria.SearchText = query;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool tryParseKeywordCriteria(FilterCriteria criteria, string key, string value, string op)
|
private static bool tryParseKeywordCriteria(FilterCriteria criteria, string key, string value, Operator op)
|
||||||
{
|
{
|
||||||
switch (key)
|
switch (key)
|
||||||
{
|
{
|
||||||
case "stars" when parseFloatWithPoint(value, out var stars):
|
case "stars" when parseFloatWithPoint(value, out var stars):
|
||||||
updateCriteriaRange(ref criteria.StarDifficulty, op, stars, 0.01f / 2);
|
return updateCriteriaRange(ref criteria.StarDifficulty, op, stars, 0.01f / 2);
|
||||||
break;
|
|
||||||
|
|
||||||
case "ar" when parseFloatWithPoint(value, out var ar):
|
case "ar" when parseFloatWithPoint(value, out var ar):
|
||||||
updateCriteriaRange(ref criteria.ApproachRate, op, ar, 0.1f / 2);
|
return updateCriteriaRange(ref criteria.ApproachRate, op, ar, 0.1f / 2);
|
||||||
break;
|
|
||||||
|
|
||||||
case "dr" when parseFloatWithPoint(value, out var dr):
|
case "dr" when parseFloatWithPoint(value, out var dr):
|
||||||
case "hp" when parseFloatWithPoint(value, out dr):
|
case "hp" when parseFloatWithPoint(value, out dr):
|
||||||
updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2);
|
return updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2);
|
||||||
break;
|
|
||||||
|
|
||||||
case "cs" when parseFloatWithPoint(value, out var cs):
|
case "cs" when parseFloatWithPoint(value, out var cs):
|
||||||
updateCriteriaRange(ref criteria.CircleSize, op, cs, 0.1f / 2);
|
return updateCriteriaRange(ref criteria.CircleSize, op, cs, 0.1f / 2);
|
||||||
break;
|
|
||||||
|
|
||||||
case "bpm" when parseDoubleWithPoint(value, out var bpm):
|
case "bpm" when parseDoubleWithPoint(value, out var bpm):
|
||||||
updateCriteriaRange(ref criteria.BPM, op, bpm, 0.01d / 2);
|
return updateCriteriaRange(ref criteria.BPM, op, bpm, 0.01d / 2);
|
||||||
break;
|
|
||||||
|
|
||||||
case "length" when parseDoubleWithPoint(value.TrimEnd('m', 's', 'h'), out var length):
|
case "length" when parseDoubleWithPoint(value.TrimEnd('m', 's', 'h'), out var length):
|
||||||
var scale = getLengthScale(value);
|
var scale = getLengthScale(value);
|
||||||
updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0);
|
return updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0);
|
||||||
break;
|
|
||||||
|
|
||||||
case "divisor" when parseInt(value, out var divisor):
|
case "divisor" when parseInt(value, out var divisor):
|
||||||
updateCriteriaRange(ref criteria.BeatDivisor, op, divisor);
|
return updateCriteriaRange(ref criteria.BeatDivisor, op, divisor);
|
||||||
break;
|
|
||||||
|
|
||||||
case "status" when Enum.TryParse<BeatmapSetOnlineStatus>(value, true, out var statusValue):
|
case "status" when Enum.TryParse<BeatmapSetOnlineStatus>(value, true, out var statusValue):
|
||||||
updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue);
|
return updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue);
|
||||||
break;
|
|
||||||
|
|
||||||
case "creator":
|
case "creator":
|
||||||
updateCriteriaText(ref criteria.Creator, op, value);
|
return updateCriteriaText(ref criteria.Creator, op, value);
|
||||||
break;
|
|
||||||
|
|
||||||
case "artist":
|
case "artist":
|
||||||
updateCriteriaText(ref criteria.Artist, op, value);
|
return updateCriteriaText(ref criteria.Artist, op, value);
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
private static Operator parseOperator(string value)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case "=":
|
||||||
|
case ":":
|
||||||
|
return Operator.Equal;
|
||||||
|
|
||||||
|
case "<":
|
||||||
|
return Operator.Less;
|
||||||
|
|
||||||
|
case "<=":
|
||||||
|
case "<:":
|
||||||
|
return Operator.LessOrEqual;
|
||||||
|
|
||||||
|
case ">":
|
||||||
|
return Operator.Greater;
|
||||||
|
|
||||||
|
case ">=":
|
||||||
|
case ">:":
|
||||||
|
return Operator.GreaterOrEqual;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value), $"Unsupported operator {value}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getLengthScale(string value) =>
|
private static int getLengthScale(string value) =>
|
||||||
@ -97,120 +113,119 @@ namespace osu.Game.Screens.Select
|
|||||||
private static bool parseInt(string value, out int result) =>
|
private static bool parseInt(string value, out int result) =>
|
||||||
int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result);
|
int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result);
|
||||||
|
|
||||||
private static void updateCriteriaText(ref FilterCriteria.OptionalTextFilter textFilter, string op, string value)
|
private static bool updateCriteriaText(ref FilterCriteria.OptionalTextFilter textFilter, Operator op, string value)
|
||||||
{
|
{
|
||||||
switch (op)
|
switch (op)
|
||||||
{
|
{
|
||||||
case "=":
|
case Operator.Equal:
|
||||||
case ":":
|
|
||||||
textFilter.SearchTerm = value.Trim('"');
|
textFilter.SearchTerm = value.Trim('"');
|
||||||
break;
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void updateCriteriaRange(ref FilterCriteria.OptionalRange<float> range, string op, float value, float tolerance = 0.05f)
|
private static bool updateCriteriaRange(ref FilterCriteria.OptionalRange<float> range, Operator op, float value, float tolerance = 0.05f)
|
||||||
{
|
{
|
||||||
switch (op)
|
switch (op)
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
case "=":
|
case Operator.Equal:
|
||||||
case ":":
|
|
||||||
range.Min = value - tolerance;
|
range.Min = value - tolerance;
|
||||||
range.Max = value + tolerance;
|
range.Max = value + tolerance;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ">":
|
case Operator.Greater:
|
||||||
range.Min = value + tolerance;
|
range.Min = value + tolerance;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ">=":
|
case Operator.GreaterOrEqual:
|
||||||
case ">:":
|
|
||||||
range.Min = value - tolerance;
|
range.Min = value - tolerance;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "<":
|
case Operator.Less:
|
||||||
range.Max = value - tolerance;
|
range.Max = value - tolerance;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "<=":
|
case Operator.LessOrEqual:
|
||||||
case "<:":
|
|
||||||
range.Max = value + tolerance;
|
range.Max = value + tolerance;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void updateCriteriaRange(ref FilterCriteria.OptionalRange<double> range, string op, double value, double tolerance = 0.05)
|
private static bool updateCriteriaRange(ref FilterCriteria.OptionalRange<double> range, Operator op, double value, double tolerance = 0.05)
|
||||||
{
|
{
|
||||||
switch (op)
|
switch (op)
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
case "=":
|
case Operator.Equal:
|
||||||
case ":":
|
|
||||||
range.Min = value - tolerance;
|
range.Min = value - tolerance;
|
||||||
range.Max = value + tolerance;
|
range.Max = value + tolerance;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ">":
|
case Operator.Greater:
|
||||||
range.Min = value + tolerance;
|
range.Min = value + tolerance;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ">=":
|
case Operator.GreaterOrEqual:
|
||||||
case ">:":
|
|
||||||
range.Min = value - tolerance;
|
range.Min = value - tolerance;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "<":
|
case Operator.Less:
|
||||||
range.Max = value - tolerance;
|
range.Max = value - tolerance;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "<=":
|
case Operator.LessOrEqual:
|
||||||
case "<:":
|
|
||||||
range.Max = value + tolerance;
|
range.Max = value + tolerance;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void updateCriteriaRange<T>(ref FilterCriteria.OptionalRange<T> range, string op, T value)
|
private static bool updateCriteriaRange<T>(ref FilterCriteria.OptionalRange<T> range, Operator op, T value)
|
||||||
where T : struct
|
where T : struct
|
||||||
{
|
{
|
||||||
switch (op)
|
switch (op)
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
case "=":
|
case Operator.Equal:
|
||||||
case ":":
|
|
||||||
range.IsLowerInclusive = range.IsUpperInclusive = true;
|
range.IsLowerInclusive = range.IsUpperInclusive = true;
|
||||||
range.Min = value;
|
range.Min = value;
|
||||||
range.Max = value;
|
range.Max = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ">":
|
case Operator.Greater:
|
||||||
range.IsLowerInclusive = false;
|
range.IsLowerInclusive = false;
|
||||||
range.Min = value;
|
range.Min = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ">=":
|
case Operator.GreaterOrEqual:
|
||||||
case ">:":
|
|
||||||
range.IsLowerInclusive = true;
|
range.IsLowerInclusive = true;
|
||||||
range.Min = value;
|
range.Min = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "<":
|
case Operator.Less:
|
||||||
range.IsUpperInclusive = false;
|
range.IsUpperInclusive = false;
|
||||||
range.Max = value;
|
range.Max = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "<=":
|
case Operator.LessOrEqual:
|
||||||
case "<:":
|
|
||||||
range.IsUpperInclusive = true;
|
range.IsUpperInclusive = true;
|
||||||
range.Max = value;
|
range.Max = value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user