using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace Unity.VisualScripting
{
public static class SearchUtility
{
private static bool Compare(char a, char b)
{
return char.ToLowerInvariant(a) == char.ToLowerInvariant(b);
}
private static bool Match(string query, string haystack, bool strictOnly, float strictWeight, float looseWeight, out float score, bool[] indices)
{
score = 0;
var isStreaking = false;
var haystackIndex = 0;
var matchedAny = false;
for (var queryIndex = 0; queryIndex < query.Length; queryIndex++)
{
var queryCharacter = query[queryIndex];
if (StringUtility.IsWordDelimiter(queryCharacter))
{
continue;
}
var matched = false;
for (; haystackIndex < haystack.Length; haystackIndex++)
{
var haystackCharacter = haystack[haystackIndex];
if (StringUtility.IsWordDelimiter(haystackCharacter))
{
isStreaking = false;
continue;
}
var matchesLoose = Compare(queryCharacter, haystackCharacter);
var matchesStrict = matchesLoose && (isStreaking || StringUtility.IsWordBeginning(haystack, haystackIndex));
var matches = strictOnly ? matchesStrict : matchesLoose;
if (matches)
{
score += matchesStrict ? strictWeight : looseWeight;
matched = true;
if (indices != null)
{
indices[haystackIndex] = true;
}
isStreaking = true;
haystackIndex++;
break;
}
else
{
isStreaking = false;
}
}
if (matched)
{
matchedAny = true;
}
else
{
return false;
}
}
if (!matchedAny)
{
return false;
}
score /= query.Length;
return true;
}
private static float? Score(string query, string haystack, bool strictOnly, float strictWeight, float looseWeight)
{
if (Match(query, haystack, strictOnly, strictWeight, looseWeight, out var score, null))
{
return score;
}
else
{
return null;
}
}
private static bool[] Indices(string query, string haystack, bool strictOnly)
{
bool[] indices = new bool[haystack.Length];
if (Match(query, haystack, strictOnly, 0, 0, out var score, indices))
{
return indices;
}
else
{
return null;
}
}
public static float Relevance(string query, string haystack)
{
// Configurable score weights
var strictWeight = 1;
var looseWeight = 0.25f;
var shortnessWeight = 1;
// Calculate the strict score first, then the loose score if the former fails
var score = Score(query, haystack, true, strictWeight, looseWeight) ?? Score(query, haystack, false, strictWeight, looseWeight);
if (score == null)
{
// If loose matching fails as well, return a failure
return -1;
}
// Calculate the shortness factor
var shortness = (float)query.Length / haystack.Length;
// Sum and normalize scores
var scoreWeight = Mathf.Max(strictWeight, looseWeight);
var maxWeight = scoreWeight + shortnessWeight;
var totalWeight = (score.Value * scoreWeight) + (shortness * shortnessWeight);
return totalWeight / maxWeight;
}
static float Relevance(string query, string haystack, string formerHaystack)
{
// Configurable score weights
var strictWeight = 1;
var looseWeight = 0.25f;
var shortnessWeight = 1;
// Calculate the strict score first, then the loose score if the former fails
var score = ComputeScore(query, haystack, strictWeight, looseWeight);
score = Math.Max(score, ComputeScore(query, formerHaystack, strictWeight, looseWeight));
if (score == -1)
{
// If loose matching fails as well, return a failure
return -1;
}
// Calculate the shortness factor
var shortness = (float)query.Length / haystack.Length;
// Sum and normalize scores
var scoreWeight = Mathf.Max(strictWeight, looseWeight);
var maxWeight = scoreWeight + shortnessWeight;
var totalWeight = (score * scoreWeight) + (shortness * shortnessWeight);
return totalWeight / maxWeight;
}
static float ComputeScore(string query, string haystack, float strictWeight, float looseWeight)
{
var score = Score(query, haystack, true, strictWeight, looseWeight) ?? Score(query, haystack, false, strictWeight, looseWeight);
return score ?? -1;
}
public static bool Matches(string query, string haystack)
{
return Matches(Relevance(query, haystack));
}
public static bool Matches(float relevance)
{
return relevance > 0;
}
public static bool Matches(ISearchResult result)
{
return Matches(result.relevance);
}
public static string HighlightQuery(string haystack, string query, string openTag = "", string closeTag = "")
{
if (string.IsNullOrEmpty(query))
{
return haystack;
}
bool[] matchingIndices = Indices(query, haystack, true) ?? Indices(query, haystack, false);
if (matchingIndices == null)
{
return haystack;
}
var sb = new StringBuilder();
var isHighlighted = false;
var wasHighlighted = false;
for (var i = 0; i < haystack.Length; i++)
{
var character = haystack[i];
isHighlighted = matchingIndices[i];
if (!wasHighlighted && isHighlighted)
{
sb.Append(openTag);
}
else if (wasHighlighted && !isHighlighted)
{
sb.Append(closeTag);
}
sb.Append(character);
wasHighlighted = isHighlighted;
}
if (isHighlighted)
{
sb.Append(closeTag);
}
return sb.ToString();
}
public static IEnumerable> OrderableSearchFilter(this IEnumerable enumeration,
Func getResult,
string query,
Func getHaystack)
{
return enumeration.Select(item => new SearchResult(getResult(item), Relevance(query, getHaystack(item))))
.Where(result => Matches(result.relevance));
}
public static IEnumerable> OrderableSearchFilter(this IEnumerable enumeration,
string query,
Func haystack)
{
return OrderableSearchFilter(enumeration, r => r, query, haystack);
}
public static IEnumerable> OrderableSearchFilter(this IEnumerable enumeration,
Func getResult,
string query,
Func getHaystack,
Func getFormerHaystack)
{
return enumeration.Select(item => new SearchResult(getResult(item), Relevance(query, getHaystack(item), getFormerHaystack(item))))
.Where(result => Matches(result.relevance));
}
public static IEnumerable> OrderableSearchFilter(this IEnumerable enumeration,
string query,
Func haystack,
Func formerHaystack)
{
return OrderableSearchFilter(enumeration, r => r, query, haystack, formerHaystack);
}
public static IEnumerable OrderByRelevance(this IEnumerable> results)
{
return results.OrderByDescending(osr => osr.relevance).Select(osr => osr.result);
}
public static IEnumerable