using System.Text.RegularExpressions; using Beam.Abstractions; using HtmlAgilityPack; namespace Beam.Dynamic; public enum SearchStrategy { DepthFirst, BreadthFirst, } public enum SearchStringDefaultSelection { First, Last, GreatestChildren, Any, None, } public class SearchStringOptions { public required bool SearchStringIsRegex { get; set; } public required bool SearchInBody { get; set; } public required string? SearchInAttribute { get; set; } /// /// Only used when both and are false/null, or no match is found for the search criteria. /// public SearchStringDefaultSelection DefaultSelection { get; set; } = SearchStringDefaultSelection.First; /// /// Only used when is false. /// public IEqualityComparer UseComparer { get; set; } = StringComparer.CurrentCulture; } public class SelectDataProvider : IComposableDataProvider, IManySelectionComposableDataProvider { public SearchStrategy SearchStrategyType { get; set; } = SearchStrategy.DepthFirst; public SearchStringOptions SearchStringOptions { get; set; } = new SearchStringOptions() { SearchStringIsRegex = false, SearchInBody = true, SearchInAttribute = null }; public string? SearchString { get; set; } public IBinding? Content { get; set; } /// /// Returns the first node that matches the search criteria. /// /// /// public HtmlNode? Get(HtmlDocument document) { return Select(document); } /// /// Returns the first child node that matches the search criteria. /// /// /// public HtmlNode? Get(HtmlNode node) { return Select(node); } public HtmlNode? Get(HtmlNode[] node) { throw new NotSupportedException(); } public HtmlNode[]? _Select(HtmlNode node) { LinkedList searchSet = new(); LinkedListNode currentNode = searchSet.AddLast(node); HashSet visited = [node]; void breadthFirst(HtmlNode node) { foreach (var child in node.ChildNodes) { if (visited.Contains(child)) continue; searchSet.AddLast(child); visited.Add(child); } } void depthFirst(HtmlNode node) { foreach (var child in node.ChildNodes.Reverse()) { if (visited.Contains(child)) continue; searchSet.AddAfter(currentNode, child); visited.Add(child); } } Action enqueueStartegy = SearchStrategyType switch { SearchStrategy.BreadthFirst => breadthFirst, SearchStrategy.DepthFirst => depthFirst, _ => throw new NotSupportedException() }; var bestCandidate = currentNode.Value; List selected = []; do { var n = currentNode.Value; if (SearchStringOptions.SearchInBody) if (SearchStringOptions.SearchStringIsRegex && Regex.IsMatch(n.InnerText ?? "", SearchString ?? "")) selected.Add(n); else if (SearchStringOptions.UseComparer.Equals(n.InnerText, SearchString ?? "")) selected.Add(n); if (SearchStringOptions.SearchInAttribute is not null) if (SearchStringOptions.SearchStringIsRegex && n.GetAttributeValue(SearchStringOptions.SearchInAttribute, null) != null && Regex.IsMatch(n.GetAttributeValue(SearchStringOptions.SearchInAttribute, ""), SearchString ?? "")) selected.Add(n); else if (SearchStringOptions.UseComparer.Equals(n.GetAttributeValue(SearchStringOptions.SearchInAttribute, null), SearchString ?? "")) selected.Add(n); switch (SearchStringOptions.DefaultSelection) { case SearchStringDefaultSelection.GreatestChildren: if (n.ChildNodes.Count > bestCandidate.ChildNodes.Count) bestCandidate = n; break; case SearchStringDefaultSelection.Last: bestCandidate = n; break; case SearchStringDefaultSelection.Any: case SearchStringDefaultSelection.First: case SearchStringDefaultSelection.None: default: break; } enqueueStartegy(n); } while ((currentNode = currentNode.Next!) != null); if (selected.Count == 0 && SearchStringOptions.DefaultSelection != SearchStringDefaultSelection.None) selected.Add(bestCandidate); return selected.ToArray(); } public HtmlNode? Select(HtmlDocument document) { return Select(Content?.Select(document) ?? document.DocumentNode); } public HtmlNode? Select(HtmlNode node) { return _Select(node)?.FirstOrDefault(); } public HtmlNode? ManyGet(HtmlNode[] node) { throw new NotSupportedException(); } public HtmlNode[]? SelectMany(HtmlDocument doc) { return _Select(Content?.Select(doc) ?? doc.DocumentNode); } public HtmlNode[]? SelectMany(HtmlNode[] node) { return node.SelectMany(x => _Select(x) ?? []).ToArray(); } }