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:
+2
-52
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Beam.Dynamic {
|
||||
public enum BindingType {
|
||||
Single,
|
||||
Array,
|
||||
UseProvider
|
||||
}
|
||||
}
|
||||
@@ -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) ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 ?? "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) ?? []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user