refactor: unify binding & data provider interfaces

- Removed BindingType enum and all related logic from Binding.
- Made Binding implement new IBinding and IKeyed interfaces.
- Moved node selection logic to IBinding.Select; removed Resolve* methods from Binding.
- Added new IBinding interface for XPath/CssPath selection.
- Refactored IDataProvider to generic IDataProvider<T>; removed GetNode.
- Updated ListContentDataProvider and ParagraphedContentDataProvider to use IBinding.
- Added new ContentsDataProvider, ContentsArrayDataProvider, and DropDownDataProvider for flexible data extraction.
- Updated DataBindings to use IDataProvider<T> properties instead of Binding.
- Updated all usages to new interfaces and patterns.
This commit is contained in:
qwsdcvghyu89
2025-06-30 23:31:39 +03:00
parent 87360d75ab
commit 849bdcd089
18 changed files with 448 additions and 129 deletions
+2 -52
View File
@@ -1,69 +1,19 @@
using aeqw89.DataKeys;
using HtmlAgilityPack;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Beam.Dynamic {
public class Binding(DataKey<Binding> key) : IKeyed<Binding> {
public class Binding(DataKey<Binding> key) : IBinding, IKeyed<Binding> {
public Binding(string key) : this(new DataKey<Binding>(key)) { }
public Binding() : this("") { }
[JsonRequired]
public DataKey<Binding> Key { get; set; } = key;
[JsonRequired]
public BindingType Type { get; set; }
public string? ArrayDelimiters { get; set; }
public string? XPath { get; set; }
public string? CssPath { get; set; }
public string? Text { get; set; }
private IDataProvider? Provider_;
public IDataProvider? Provider {
get => Provider_;
set {
if (value is null)
return;
if (value is not IDataProvider)
throw new InvalidOperationException();
var constructor = value.GetType().GetConstructor([]);
if (!constructor?.IsPublic ?? true)
throw new InvalidOperationException();
Provider_ = value;
}
}
public HtmlNode? ResolveNode(HtmlDocument doc) {
if (XPath is not null)
return doc.DocumentNode.SelectSingleNode(XPath);
if (CssPath is not null)
return doc.DocumentNode.ThenByClasses(CssPath.Split('/'));
if (Provider is not null)
return Provider.GetNode(doc);
return null;
}
public string ResolveString(HtmlDocument doc) {
if (XPath is not null)
return doc.DocumentNode.SelectSingleNode(XPath)?.InnerText ?? "";
if (CssPath is not null)
return doc.DocumentNode.ThenByClasses(CssPath.Split('/'))?.InnerText ?? "";
if (Provider is not null)
return Provider.Get(doc);
return "";
}
public string[] ResolveArray(HtmlDocument doc) {
if (Type is not BindingType.Array)
return [];
var str = ResolveString(doc);
return str.Split(ArrayDelimiters);
}
public dynamic? Resolve(HtmlDocument doc) => Type switch {
BindingType.Single => ResolveString(doc),
BindingType.Array => ResolveArray(doc),
BindingType.UseProvider => Provider?.Get(doc),
_ => null
};
}
}
-7
View File
@@ -1,7 +0,0 @@
namespace Beam.Dynamic {
public enum BindingType {
Single,
Array,
UseProvider
}
}
+15
View File
@@ -0,0 +1,15 @@
using HtmlAgilityPack;
namespace Beam.Dynamic {
public class ContentsArrayDataProvider : ContentsDataProvider, IDataProvider<string[]> {
public string[] ArrayDelimiters { get; set; } = [";"];
string[] IDataProvider<string[]>.Get(HtmlDocument document) {
if (Content is null)
return [];
return Content.Select(document)?.InnerText?.Split(ArrayDelimiters, StringSplitOptions.RemoveEmptyEntries) ?? [];
}
}
}
+20
View File
@@ -0,0 +1,20 @@
using HtmlAgilityPack;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Beam.Dynamic {
public class ContentsDataProvider : IDataProvider<string> {
public IBinding? Content { get; set; }
public string Get(HtmlDocument document) {
if (Content is null)
return "";
return Content.Select(document)?.InnerText ?? "";
}
}
}
+12 -12
View File
@@ -2,21 +2,21 @@
namespace Beam.Dynamic {
public record class DataBindings {
public Binding? Title { get; set; }
public Binding? Authors { get; set; }
public Binding? Description { get; set; }
public Binding? Content { get; set; }
public Binding? Language { get; set; }
public Binding? Tags { get; set; }
public IDataProvider<string>? Title { get; set; }
public IDataProvider<string[]>? Authors { get; set; }
public IDataProvider<string>? Description { get; set; }
public IDataProvider<string>? Content { get; set; }
public IDataProvider<string[]>? Language { get; set; }
public IDataProvider<string[]>? Tags { get; set; }
public virtual ResolvedBindings Resolve(HtmlDocument doc) {
return new ResolvedBindings() {
Title = Title?.Resolve(doc),
Authors = Authors?.Resolve(doc) ?? Array.Empty<string>(),
Language = Language?.Resolve(doc) ?? Array.Empty<string>(),
Content = Content?.Resolve(doc),
Description = Description?.Resolve(doc),
Tags = Tags?.Resolve(doc) ?? Array.Empty<string>()
Title = Title?.Get(doc),
Authors = Authors?.Get(doc) ?? [],
Language = Language?.Get(doc),
Content = Content?.Get(doc),
Description = Description?.Get(doc),
Tags = Tags?.Get(doc) ?? []
};
}
}
+41
View File
@@ -0,0 +1,41 @@
using HtmlAgilityPack;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace Beam.Dynamic {
public class DropDownDataProvider
: IDataProvider<string>,
IDataProvider<string[]>,
IDataProvider<SourceLink[]> {
public IBinding? Content { get; set; }
public SourceLink[] Get(HtmlDocument document) {
if (Content is null)
return [];
var node = Content.Select(document);
if (node is null)
return [];
List<SourceLink> links = [];
foreach (var child in node.ChildNodes.Where(x => x.Name == "option")) {
var childValue = child.GetAttributeValue("value", null);
if (!Uri.TryCreate(childValue, UriKind.Absolute, out _))
continue;
links.Add(new SourceLink(childValue));
}
return links.ToArray();
}
string[] IDataProvider<string[]>.Get(HtmlDocument document) {
return this.Get(document).Select(x => x.Link.AbsoluteUri).ToArray();
}
string IDataProvider<string>.Get(HtmlDocument document) {
return JsonSerializer.Serialize(this.Get(document));
}
}
}
+24
View File
@@ -0,0 +1,24 @@
using HtmlAgilityPack;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace Beam.Dynamic {
[JsonDerivedType(typeof(Binding), "binding")]
public interface IBinding {
string? XPath { get; set; }
string? CssPath { get; set; }
HtmlNode? Select(HtmlDocument doc) {
if (XPath is not null)
return doc.DocumentNode.SelectSingleNode(XPath);
if (CssPath is not null)
return doc.DocumentNode.ThenByClasses(CssPath.Split('/'));
return null;
}
}
}
+9 -5
View File
@@ -1,10 +1,14 @@
using HtmlAgilityPack;
using System.Text.Json.Serialization;
namespace Beam.Dynamic {
[System.Text.Json.Serialization.JsonDerivedType(typeof(ParagraphedContentDataProvider), "paragraphed-data-provider")]
[System.Text.Json.Serialization.JsonDerivedType(typeof(ListContentDataProvider), "list-data-provider")]
public interface IDataProvider {
public string Get(HtmlDocument document);
public HtmlNode? GetNode(HtmlDocument document);
[JsonDerivedType(typeof(ParagraphedContentDataProvider), "paragraphed")]
[JsonDerivedType(typeof(ListContentDataProvider), "list")]
[JsonDerivedType(typeof(ContentsArrayDataProvider), "array")]
[JsonDerivedType(typeof(ContentsDataProvider), "single")]
[JsonDerivedType(typeof(DropDownDataProvider), "dropdown")]
public interface IDataProvider<T> {
public T Get(HtmlDocument document);
//public HtmlNode? GetNode(HtmlDocument document);
}
}
+3 -7
View File
@@ -2,14 +2,14 @@
using System.Text;
namespace Beam.Dynamic {
public class ListContentDataProvider : IDataProvider {
public Binding? Content { get; set; }
public class ListContentDataProvider : IDataProvider<string> {
public IBinding? Content { get; set; }
public string Get(HtmlDocument document) {
if (Content is null)
return "";
var node = Content.ResolveNode(document);
var node = Content.Select(document);
if (node is null)
return "";
@@ -23,9 +23,5 @@ namespace Beam.Dynamic {
content.Append(node.ChildNodes.Last().InnerText.Trim());
return content.ToString();
}
public HtmlNode? GetNode(HtmlDocument document) {
return Content?.ResolveNode(document);
}
}
}
@@ -6,14 +6,14 @@ using System.Text;
using System.Threading.Tasks;
namespace Beam.Dynamic {
public class ParagraphedContentDataProvider : IDataProvider {
public Binding? Content { get; set; }
public class ParagraphedContentDataProvider : IDataProvider<string> {
public IBinding? Content { get; set; }
public string Get(HtmlDocument document) {
if (Content is null)
return "";
var node = Content.ResolveNode(document);
var node = Content.Select(document);
if (node is null)
return "";
@@ -26,10 +26,5 @@ namespace Beam.Dynamic {
return content.ToString();
}
public HtmlNode? GetNode(HtmlDocument document) {
return Content?.ResolveNode(document);
}
}
}