using System.Net; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Html; namespace JobsMedical.Web.Services; /// Wraps query terms in <mark> for result highlighting (HTML-safe). public static class SearchHighlight { public static HtmlString Mark(string? text, string? query) { if (string.IsNullOrEmpty(text)) return HtmlString.Empty; var encoded = WebUtility.HtmlEncode(text); if (string.IsNullOrWhiteSpace(query)) return new HtmlString(encoded); var terms = query.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .Where(t => t.Length >= 2) .Select(t => Regex.Escape(WebUtility.HtmlEncode(t))) .Distinct() .ToList(); if (terms.Count == 0) return new HtmlString(encoded); var pattern = string.Join("|", terms); var marked = Regex.Replace(encoded, pattern, m => $"{m.Value}", RegexOptions.IgnoreCase); return new HtmlString(marked); } /// /// Elasticsearch-style highlight fragment: finds the first matching term in , /// returns a window of ± chars around it with the terms marked and ellipses /// at the cut points. Empty when nothing matches (so callers can hide the line). /// public static HtmlString Snippet(string? text, string? query, int radius = 70) { if (string.IsNullOrWhiteSpace(text) || string.IsNullOrWhiteSpace(query)) return HtmlString.Empty; var terms = query.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .Where(t => t.Length >= 2).ToList(); if (terms.Count == 0) return HtmlString.Empty; var flat = Regex.Replace(text, @"\s+", " ").Trim(); int idx = -1, matchLen = 0; foreach (var t in terms) { var i = flat.IndexOf(t, StringComparison.OrdinalIgnoreCase); if (i >= 0 && (idx < 0 || i < idx)) { idx = i; matchLen = t.Length; } } if (idx < 0) return HtmlString.Empty; var start = Math.Max(0, idx - radius); var end = Math.Min(flat.Length, idx + matchLen + radius); var slice = flat.Substring(start, end - start).Trim(); var prefix = start > 0 ? "…" : ""; var suffix = end < flat.Length ? "…" : ""; return new HtmlString(prefix + Mark(slice, query).Value + suffix); } }