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();
}
}