/// <summary> /// Performs a free text search on all <paramref name="searchables"/>. The <paramref name="searchText"/> will match on both the object /// and it's parental hierarchy e.g. "chi" "biochemistry" matches column "chi" in Catalogue "biochemistry" strongly. /// </summary> /// <param name="searchables">All available objects that can be searched (see <see cref="ICoreChildProvider.GetAllSearchables"/>)</param> /// <param name="searchText">Tokens to use separated by space e.g. "chi biochemistry CatalogueItem"</param> /// <param name="cancellationToken">Token for cancelling match scoring. This method will return null if cancellation is detected</param> /// <param name="showOnlyTypes">Optional (can be null) list of types to return results from. Not respected if <paramref name="searchText"/> includes type names</param> /// <returns></returns> public Dictionary <KeyValuePair <IMapsDirectlyToDatabaseTable, DescendancyList>, int> ScoreMatches(Dictionary <IMapsDirectlyToDatabaseTable, DescendancyList> searchables, string searchText, CancellationToken cancellationToken, List <Type> showOnlyTypes) { SetupRespectUserSettings(); //do short code substitutions e.g. ti for TableInfo if (!string.IsNullOrWhiteSpace(searchText)) { foreach (var kvp in ShortCodes) { searchText = Regex.Replace(searchText, $@"\b{kvp.Key}\b", kvp.Value.Name); } } //if user hasn't typed any explicit Type filters if (showOnlyTypes != null && TypeNames != null) { //add the explicit types only if the search text does not contain any explicit type names if (string.IsNullOrWhiteSpace(searchText) || !TypeNames.Intersect(searchText.Split(' '), StringComparer.CurrentCultureIgnoreCase).Any()) { foreach (var showOnlyType in showOnlyTypes) { searchText = searchText + " " + showOnlyType.Name; } } } //Search the tokens for also inclusions e.g. "Pipeline" becomes "Pipeline PipelineCompatibleWithUseCaseNode" if (!string.IsNullOrWhiteSpace(searchText)) { foreach (var s in searchText.Split(' ').ToArray()) { if (AlsoIncludes.ContainsKey(s)) { foreach (var v in AlsoIncludes[s]) { searchText += " " + v.Name; } } } } //if we have nothing to search for return no results if (string.IsNullOrWhiteSpace(searchText) && ID == null) { return(new Dictionary <KeyValuePair <IMapsDirectlyToDatabaseTable, DescendancyList>, int>()); } var tokens = (searchText ?? "").Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); var regexes = new List <Regex>(); //any token that 100% matches a type name is an explicitly typed token string[] explicitTypesRequested; if (TypeNames != null) { explicitTypesRequested = TypeNames.Intersect(tokens, StringComparer.CurrentCultureIgnoreCase).ToArray(); //else it's a regex foreach (string token in tokens.Except(TypeNames, StringComparer.CurrentCultureIgnoreCase)) { regexes.Add(new Regex(Regex.Escape(token), RegexOptions.IgnoreCase)); } } else { explicitTypesRequested = new string[0]; } if (cancellationToken.IsCancellationRequested) { return(null); } return(searchables.ToDictionary( s => s, score => ScoreMatches(score, regexes, explicitTypesRequested, cancellationToken) )); }