refactor: modularize Beam into new projects and interfaces

- Introduced modularity by splitting Beam into new projects: Beam.Abstractions, Beam.Models, and Beam.Downloaders.
- Refactored existing classes into appropriate namespaces and projects.
- Replaced specific implementations with abstractions (e.g., SourceLinkBuilder to LinkBuilder, State to IState, etc.).
- Updated interfaces: added ITemplate, IArticleData, IDownloadReport, and others for improved extensibility.
- Removed deprecated classes like SourceLinkBuilder and StateChangerFactory.
- Enhanced link handling in downloaders by refactoring to use `string` over `SourceLink`.
- Consolidated shared logic under Beam.Abstractions.
This commit is contained in:
qwsdcvghyu89
2025-09-22 01:51:46 +10:00
parent a7d148a96f
commit 7ed05abdb8
128 changed files with 2058 additions and 1804 deletions
+1 -15
View File
@@ -6,7 +6,7 @@ using System.Text;
using System.Threading.Tasks;
namespace Beam.Dynamic {
public class AnchorCollectionDataProvider : IDataProvider<string[]>, IDataProvider<SourceLink[]> {
public class AnchorCollectionDataProvider : IDataProvider<string[]> {
public IBinding? Content { get; set; }
public string? RelativeTo { get; set; }
@@ -35,19 +35,5 @@ namespace Beam.Dynamic {
return links.Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
}
SourceLink[] IDataProvider<SourceLink[]>.Get(HtmlDocument document) {
var links = Get(document);
if (links.Length == 0)
return [];
List<SourceLink> slinks = [];
foreach (var link in links)
if (Uri.TryCreate(GetAbsolute(RelativeTo, link), UriKind.RelativeOrAbsolute, out _))
slinks.Add(new SourceLink(GetAbsolute(RelativeTo, link)));
return slinks.ToArray();
}
}
}
+1 -12
View File
@@ -6,7 +6,7 @@ using System.Text;
using System.Threading.Tasks;
namespace Beam.Dynamic {
public class AnchorDataProvider : IDataProvider<SourceLink>, IDataProvider<string> {
public class AnchorDataProvider : IDataProvider<string> {
public IBinding? Content { get; set; }
public string Get(HtmlDocument document) {
@@ -16,16 +16,5 @@ namespace Beam.Dynamic {
return Content.Select(document)?.GetAttributeValue("href", "") ?? "";
}
SourceLink IDataProvider<SourceLink>.Get(HtmlDocument document) {
var content = Get(document);
if (content is null)
return SourceLink.InvalidLink;
if (!Uri.TryCreate(content, UriKind.RelativeOrAbsolute, out _))
return SourceLink.InvalidLink;
return new SourceLink(content);
}
}
}
+46
View File
@@ -0,0 +1,46 @@
using aeqw89.DataKeys;
using Beam.Abstractions;
namespace Beam.Dynamic {
/// <summary>
/// <para>
/// A collection of specific useful methods and constants that facilitate the use of the application; allows other parts of the application to depend on architecture-specific arbitrary choices without compromising the Single-Responsibility principle or increasing redundant code.
/// </para>
/// </summary>
public partial interface IArchitecture {
///// <summary>
///// Gets the metadata associated with a <see cref="ResourceDictionary"/>
///// </summary>
///// <param name="web">The web client to use when downloading <see cref="WebResource"/>s</param>
///// <param name="pieceKey">The key of the <see cref="ResourceDictionary"/> stored in the <paramref name="sdd"/></param>
///// <param name="sdd">The <see cref="BeamDataDictionary"/> to be used to retrieve information</param>
///// <param name="logger">Optional logger for logging debug information</param>
///// <returns>A <see cref="DownloadContext{T}"/> object with the required information to perform the download</returns>
//public DownloadContext<IDocumentMetaData>? GetMeta(HtmlWeb web, DataKey<ResourceDictionary> pieceKey, BeamDataDictionary sdd, CancellationToken ct = default, ILogger? logger = null);
///// <summary>
///// Gets the <see cref="DownloadContext{T}"/> of the text record associated with <see cref="ResourceDictionary"/>
///// </summary>
///// <param name="web">The web client to use when downloading <see cref="WebResource"/>s</param>
///// <param name="pieceKey">The key of the <see cref="ResourceDictionary"/> stored in the <paramref name="sdd"/></param>
///// <param name="sdd">The <see cref="BeamDataDictionary"/> to be used to retrieve information</param>
///// <param name="metadata">Optional book metadata to include with the final text record</param>
///// <param name="logger">Optional logger for logging debug information</param>
///// <returns>A <see cref="DownloadContext{T}"/> object with the required information to perform the download</returns>
//public DownloadContext<IDocument>? GetTextRecord(HtmlWeb web, DataKey<ResourceDictionary> pieceKey, BeamDataDictionary sdd, IDocumentMetaData? metadata = null, CancellationToken ct = default, ILogger? logger = null);
/// <summary>
/// The <see cref="DataKey{IDocumentMetaData}"/> to use when looking for the chapter metadata
/// </summary>
public DataKey<IDocumentMetaData> ChapterKey { get; set; }
/// <summary>
/// The <see cref="DataKey{IDocumentMetaData}"/> to use when looking for the book metadata
/// </summary>
public DataKey<IDocumentMetaData> BookKey { get; set; }
/// <summary>
/// The default architecture
/// </summary>
public static IArchitecture Default => new MainArchitecture();
}
}
+4 -4
View File
@@ -7,13 +7,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="aeqw89.DataKeys" Version="2.0.1" />
<PackageReference Include="aeqw89.PersistentData" Version="1.1.0" />
<PackageReference Include="aeqw89.PersistentData" Version="1.3.3" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.72" />
<PackageReference Include="Microsoft.Recognizers.Text.Number" Version="1.8.13" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Beam\Beam.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
<ProjectReference Include="..\Beam.Abstractions\Beam.Abstractions.csproj" />
<ProjectReference Include="..\Beam.Exceptions\Beam.Exceptions.csproj" />
<ProjectReference Include="..\Beam.Models\Beam.Models.csproj" />
</ItemGroup>
</Project>
+39
View File
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Beam.Abstractions;
namespace Beam.Dynamic {
public static class CommonStateChangers {
public static IStateChangeBehaviour LastAsNumber => new NumberedStateChanger((x, i) => {
object last = x.GetState()[^1];
if (!int.TryParse(last.ToString(), out var number))
throw new InvalidOperationException(Exceptions.Exceptions.state_change_error); // TODO use more specific exception
x.GetState()[^1] = (number + i).ToString();
});
public static IStateChangeBehaviour Constant => new ConstantStateChanger();
public static IStateChangeBehaviour NthAsNumber(Index n, bool keepSuffix = true)
=> new NumberedStateChanger((x, i) => {
string? nth = x.GetState()[n]?.ToString();
string[] xState = x.GetState();
if (nth is null)
throw new InvalidOperationException(Exceptions.Exceptions.state_change_error); // TODO use more specific exception
if (!int.TryParse(nth, out var number))
if (keepSuffix) {
string[] split = nth.Split('.');
if (!int.TryParse(split[0], out number))
throw new InvalidOperationException(Exceptions.Exceptions.state_change_error); // TODO use more specific exception
xState[n] = (number + i) + split[1..].Aggregate((x, y) => $"{x}.{y}");
return;
} else
throw new InvalidOperationException(Exceptions.Exceptions.state_change_error); // TODO use more specific exception
xState[n] = (number + i).ToString();
});
}
}
+45
View File
@@ -0,0 +1,45 @@
using aeqw89.DataKeys;
using Beam.Abstractions;
using Beam.Models;
using HtmlAgilityPack;
namespace Beam.Dynamic;
public static class CommonTransformers {
public static AsyncTransformer<HtmlDocument, ArticleData> ArticleDataTransformer(DataBindings? binding) =>
(x) => {
return Task.FromResult(new ArticleData() {
Authors = binding?.Authors?.Get(x)?.Select(StringCleaner.Clean)?.ToArray() ?? [],
Name = StringCleaner.Clean(binding?.Title?.Get(x) ?? ""),
Categories = binding?.Tags?.Get(x)?.Select(StringCleaner.Clean)?.ToArray() ?? [],
Description = StringCleaner.Clean(binding?.Description?.Get(x) ?? "")
});
};
public static AsyncTransformer<HtmlDocument, TableOfContentsData>
TableOfContentsTransformer(DataBindings? binding) => (x) => {
return Task.FromResult(new TableOfContentsData() {
Authors = binding?.Authors?.Get(x)?.Select(StringCleaner.Clean)?.ToArray() ?? [],
Name = StringCleaner.Clean(binding?.Title?.Get(x) ?? ""),
Categories = binding?.Tags?.Get(x)?.Select(StringCleaner.Clean)?.ToArray() ?? [],
Description = StringCleaner.Clean(binding?.Description?.Get(x) ?? ""),
ContentLinks = binding?.TableOfContents?.Get(x) ?? [],
PagesLinks = binding?.PagesDropDown?.Get(x) ?? []
});
};
public static AsyncTransformer<HtmlDocument, StringDocument> DocumentTransformer(DataBindings? binding,
IDocumentMetaData? metaData = null) => (x) => {
var resolved = binding?.Resolve(x);
var articleData = new ArticleData() {
Name = StringCleaner.Clean(resolved?.Title),
};
Dictionary<DataKey<IDocumentMetaData>, IDocumentMetaData> meta = [];
meta.Add(IArchitecture.Default.ChapterKey, articleData);
if (metaData is not null)
meta.Add(IArchitecture.Default.BookKey, metaData);
return Task.FromResult(new StringDocument(Path.GetRandomFileName(), StringCleaner.Clean(resolved?.Content)) {
MetaData = meta
});
};
}
+9
View File
@@ -0,0 +1,9 @@
using Beam.Abstractions;
namespace Beam.Dynamic {
public class ConstantStateChanger : IStateChangeBehaviour {
public void Apply(IState state, object stimulus) {
return;
}
}
}
+183 -182
View File
@@ -1,191 +1,192 @@
using HtmlAgilityPack;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
using Beam.Abstractions;
using HtmlAgilityPack;
namespace Beam.Dynamic {
public record class DataBindings {
#region ---------------------- Common Bindings ----------------------
[JsonIgnore]
public IDataProvider<string>? Title {
get => Get<string>(nameof(Title));
set => Providers[nameof(Title)] = value;
}
[JsonIgnore]
public IDataProvider<string[]>? Authors {
get => Get<string[]>(nameof(Authors));
set => Providers[nameof(Authors)] = value;
}
[JsonIgnore]
public IDataProvider<string>? Description {
get => Get<string>(nameof(Description));
set => Providers[nameof(Description)] = value;
}
[JsonIgnore]
public IDataProvider<string>? Content {
get => Get<string>(nameof(Content));
set => Providers[nameof(Content)] = value;
}
[JsonIgnore]
public IDataProvider<string[]>? Language {
get => Get<string[]>(nameof(Language));
set => Providers[nameof(Language)] = value;
}
[JsonIgnore]
public IDataProvider<string[]>? Tags {
get => Get<string[]>(nameof(Tags));
set => Providers[nameof(Tags)] = value;
}
[JsonIgnore]
public IDataProvider<string>? Publisher {
get => Get<string>(nameof(Publisher));
set => Providers[nameof(Publisher)] = value;
}
[JsonIgnore]
public IDataProvider<DateTimeOffset>? PublicationDate {
get => Get<DateTimeOffset>(nameof(PublicationDate));
set => Providers[nameof(PublicationDate)] = value;
}
[JsonIgnore]
public IDataProvider<string>? ISBN {
get => Get<string>(nameof(ISBN));
set => Providers[nameof(ISBN)] = value;
}
[JsonIgnore]
public IDataProvider<int>? PageCount {
get => Get<int>(nameof(PageCount));
set => Providers[nameof(PageCount)] = value;
}
[JsonIgnore]
public IDataProvider<SourceLink>? CoverImage {
get => Get<SourceLink>(nameof(CoverImage));
set => Providers[nameof(CoverImage)] = value;
}
[JsonIgnore]
public IDataProvider<string[]>? Series {
get => Get<string[]>(nameof(Series));
set => Providers[nameof(Series)] = value;
}
[JsonIgnore]
public IDataProvider<int>? Edition {
get => Get<int>(nameof(Edition));
set => Providers[nameof(Edition)] = value;
}
[JsonIgnore]
public IDataProvider<string[]>? Contributors {
get => Get<string[]>(nameof(Contributors));
set => Providers[nameof(Contributors)] = value;
}
[JsonIgnore]
public IDataProvider<string[]>? Subjects {
get => Get<string[]>(nameof(Subjects));
set => Providers[nameof(Subjects)] = value;
}
[JsonIgnore]
public IDataProvider<string>? Rights {
get => Get<string>(nameof(Rights));
set => Providers[nameof(Rights)] = value;
}
[JsonIgnore]
public IDataProvider<SourceLink[]>? TableOfContents {
get => Get<SourceLink[]>(nameof(TableOfContents));
set => Providers[nameof(TableOfContents)] = value;
}
[JsonIgnore]
public IDataProvider<SourceLink[]>? PagesDropDown {
get => Get<SourceLink[]>(nameof(PagesDropDown));
set => Providers[nameof(PagesDropDown)] = value;
}
[JsonIgnore]
public IDataProvider<SourceLink>? NextPageButton {
get => Get<SourceLink>(nameof(NextPageButton));
set => Providers[nameof(NextPageButton)] = value;
}
[JsonIgnore]
public IDataProvider<SourceLink>? PreviousPageButton {
get => Get<SourceLink>(nameof(PreviousPageButton));
set => Providers[nameof(PreviousPageButton)] = value;
}
#endregion
namespace Beam.Dynamic;
public Dictionary<string, IDataProvider?> Providers { get; set; } = [];
public record class DataBindings : IDataBindings {
#region ---------------------- Common Bindings ----------------------
[JsonIgnore]
public IDataProvider<string>? Title {
get => Get<string>(nameof(Title));
set => Providers[nameof(Title)] = value;
}
[JsonIgnore]
public IDataProvider<string[]>? Authors {
get => Get<string[]>(nameof(Authors));
set => Providers[nameof(Authors)] = value;
}
[JsonIgnore]
public IDataProvider<string>? Description {
get => Get<string>(nameof(Description));
set => Providers[nameof(Description)] = value;
}
[JsonIgnore]
public IDataProvider<string>? Content {
get => Get<string>(nameof(Content));
set => Providers[nameof(Content)] = value;
}
[JsonIgnore]
public IDataProvider<string[]>? Language {
get => Get<string[]>(nameof(Language));
set => Providers[nameof(Language)] = value;
}
[JsonIgnore]
public IDataProvider<string[]>? Tags {
get => Get<string[]>(nameof(Tags));
set => Providers[nameof(Tags)] = value;
}
[JsonIgnore]
public IDataProvider<string>? Publisher {
get => Get<string>(nameof(Publisher));
set => Providers[nameof(Publisher)] = value;
}
[JsonIgnore]
public IDataProvider<DateTimeOffset>? PublicationDate {
get => Get<DateTimeOffset>(nameof(PublicationDate));
set => Providers[nameof(PublicationDate)] = value;
}
[JsonIgnore]
public IDataProvider<string>? ISBN {
get => Get<string>(nameof(ISBN));
set => Providers[nameof(ISBN)] = value;
}
[JsonIgnore]
public IDataProvider<int>? PageCount {
get => Get<int>(nameof(PageCount));
set => Providers[nameof(PageCount)] = value;
}
[JsonIgnore]
public IDataProvider<string>? CoverImage {
get => Get<string>(nameof(CoverImage));
set => Providers[nameof(CoverImage)] = value;
}
[JsonIgnore]
public IDataProvider<string[]>? Series {
get => Get<string[]>(nameof(Series));
set => Providers[nameof(Series)] = value;
}
[JsonIgnore]
public IDataProvider<int>? Edition {
get => Get<int>(nameof(Edition));
set => Providers[nameof(Edition)] = value;
}
[JsonIgnore]
public IDataProvider<string[]>? Contributors {
get => Get<string[]>(nameof(Contributors));
set => Providers[nameof(Contributors)] = value;
}
[JsonIgnore]
public IDataProvider<string[]>? Subjects {
get => Get<string[]>(nameof(Subjects));
set => Providers[nameof(Subjects)] = value;
}
[JsonIgnore]
public IDataProvider<string>? Rights {
get => Get<string>(nameof(Rights));
set => Providers[nameof(Rights)] = value;
}
[JsonIgnore]
public IDataProvider<string[]>? TableOfContents {
get => Get<string[]>(nameof(TableOfContents));
set => Providers[nameof(TableOfContents)] = value;
}
[JsonIgnore]
public IDataProvider<string[]>? PagesDropDown {
get => Get<string[]>(nameof(PagesDropDown));
set => Providers[nameof(PagesDropDown)] = value;
}
[JsonIgnore]
public IDataProvider<string>? NextPageButton {
get => Get<string>(nameof(NextPageButton));
set => Providers[nameof(NextPageButton)] = value;
}
[JsonIgnore]
public IDataProvider<string>? PreviousPageButton {
get => Get<string>(nameof(PreviousPageButton));
set => Providers[nameof(PreviousPageButton)] = value;
}
#endregion
private IDataProvider<T>? Get<T>(string key) {
if (Providers.TryGetValue(key, out var k) && k is IDataProvider<T> ks)
return ks;
return default;
}
public Dictionary<string, IDataProvider?> Providers { get; set; } = [];
public virtual ResolvedBindings Resolve(HtmlDocument doc) {
// explicit fields already handled below
var mappedKeys = new HashSet<string> {
nameof(Title), nameof(Authors), nameof(Description), nameof(Content),
nameof(Language), nameof(Tags), nameof(Publisher), nameof(PublicationDate),
nameof(ISBN), nameof(PageCount), nameof(CoverImage), nameof(Series),
nameof(Edition), nameof(Contributors), nameof(Subjects), nameof(Rights),
nameof(TableOfContents), nameof(PagesDropDown), nameof(NextPageButton),
nameof(PreviousPageButton)
};
var additional = new Dictionary<string, object?>();
foreach (var (key, provider) in Providers) {
if (!mappedKeys.Contains(key) && provider is not null) {
// dynamic call so any IDataProvider<T> works
additional[key] = ((dynamic)provider).Get(doc);
}
}
return new ResolvedBindings {
Title = Title?.Get(doc),
Authors = Authors?.Get(doc) ?? [],
Description = Description?.Get(doc),
Content = Content?.Get(doc),
Language = Language?.Get(doc),
Tags = Tags?.Get(doc) ?? [],
Publisher = Publisher?.Get(doc),
PublicationDate = PublicationDate?.Get(doc),
ISBN = ISBN?.Get(doc),
PageCount = PageCount?.Get(doc),
CoverImage = CoverImage?.Get(doc),
Series = Series?.Get(doc) ?? [],
Edition = Edition?.Get(doc),
Contributors = Contributors?.Get(doc) ?? [],
Subjects = Subjects?.Get(doc) ?? [],
Rights = Rights?.Get(doc),
TableOfContents = TableOfContents?.Get(doc) ?? [],
PagesDropDown = PagesDropDown?.Get(doc),
NextPageButton = NextPageButton?.Get(doc),
PreviousPageButton = PreviousPageButton?.Get(doc),
Additional = additional
};
}
private IDataProvider<T>? Get<T>(string key) {
if (Providers.TryGetValue(key, out var k) && k is IDataProvider<T> ks)
return ks;
return default;
}
public record class ResolvedBindings {
public string? Title { get; set; }
public string[]? Authors { get; set; }
public string? Description { get; set; }
public string? Content { get; set; }
public string[]? Language { get; set; }
public string[]? Tags { get; set; }
public string? Publisher { get; set; }
public DateTimeOffset? PublicationDate { get; set; }
public string? ISBN { get; set; }
public int? PageCount { get; set; }
public SourceLink? CoverImage { get; set; }
public string[]? Series { get; set; }
public int? Edition { get; set; }
public string[]? Contributors { get; set; }
public string[]? Subjects { get; set; }
public string? Rights { get; set; }
public SourceLink[]? TableOfContents { get; set; }
public SourceLink[]? PagesDropDown { get; set; }
public SourceLink? NextPageButton { get; set; }
public SourceLink? PreviousPageButton { get; set; }
public virtual ResolvedBindings Resolve(HtmlDocument doc) {
// explicit fields already handled below
var mappedKeys = new HashSet<string> {
nameof(Title), nameof(Authors), nameof(Description), nameof(Content),
nameof(Language), nameof(Tags), nameof(Publisher), nameof(PublicationDate),
nameof(ISBN), nameof(PageCount), nameof(CoverImage), nameof(Series),
nameof(Edition), nameof(Contributors), nameof(Subjects), nameof(Rights),
nameof(TableOfContents), nameof(PagesDropDown), nameof(NextPageButton),
nameof(PreviousPageButton)
};
/// <summary>
/// Values resolved from any providers whose keys arent represented
/// by the named properties above.
/// </summary>
public Dictionary<string, object?> Additional { get; set; } = [];
var additional = new Dictionary<string, object?>();
foreach (var (key, provider) in Providers) {
if (!mappedKeys.Contains(key) && provider is not null) {
// dynamic call so any IDataProvider<T> works
additional[key] = ((dynamic)provider).Get(doc);
}
}
return new ResolvedBindings {
Title = Title?.Get(doc),
Authors = Authors?.Get(doc) ?? [],
Description = Description?.Get(doc),
Content = Content?.Get(doc),
Language = Language?.Get(doc),
Tags = Tags?.Get(doc) ?? [],
Publisher = Publisher?.Get(doc),
PublicationDate = PublicationDate?.Get(doc),
ISBN = ISBN?.Get(doc),
PageCount = PageCount?.Get(doc),
CoverImage = CoverImage?.Get(doc),
Series = Series?.Get(doc) ?? [],
Edition = Edition?.Get(doc),
Contributors = Contributors?.Get(doc) ?? [],
Subjects = Subjects?.Get(doc) ?? [],
Rights = Rights?.Get(doc),
TableOfContents = TableOfContents?.Get(doc) ?? [],
PagesDropDown = PagesDropDown?.Get(doc),
NextPageButton = NextPageButton?.Get(doc),
PreviousPageButton = PreviousPageButton?.Get(doc),
Additional = additional
};
}
}
public record class ResolvedBindings {
public string? Title { get; set; }
public string[]? Authors { get; set; }
public string? Description { get; set; }
public string? Content { get; set; }
public string[]? Language { get; set; }
public string[]? Tags { get; set; }
public string? Publisher { get; set; }
public DateTimeOffset? PublicationDate { get; set; }
public string? ISBN { get; set; }
public int? PageCount { get; set; }
public string? CoverImage { get; set; }
public string[]? Series { get; set; }
public int? Edition { get; set; }
public string[]? Contributors { get; set; }
public string[]? Subjects { get; set; }
public string? Rights { get; set; }
public string[]? TableOfContents { get; set; }
public string[]? PagesDropDown { get; set; }
public string? NextPageButton { get; set; }
public string? PreviousPageButton { get; set; }
/// <summary>
/// Values resolved from any providers whose keys arent represented
/// by the named properties above.
/// </summary>
public Dictionary<string, object?> Additional { get; set; } = [];
}
@@ -0,0 +1,37 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
namespace Beam.Dynamic;
// [JsonDerivedType(typeof(ParagraphedContentDataProvider), "paragraphed")]
// [JsonDerivedType(typeof(ListContentDataProvider), "list")]
// [JsonDerivedType(typeof(ContentsArrayDataProvider), "array")]
// [JsonDerivedType(typeof(ContentsDataProvider), "single")]
// [JsonDerivedType(typeof(DropDownDataProvider), "dropdown")]
// [JsonDerivedType(typeof(AnchorCollectionDataProvider), "anchor-list")]
// [JsonDerivedType(typeof(AnchorDataProvider), "anchor")]
public class DataProviderJsonTypeInfoResolver : DefaultJsonTypeInfoResolver {
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) {
JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options);
Type basePointType = typeof(IDataProvider);
if (jsonTypeInfo.Type == basePointType) {
jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions {
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor,
DerivedTypes = {
new JsonDerivedType(typeof(ParagraphedContentDataProvider), "paragraphed"),
new JsonDerivedType(typeof(ListContentDataProvider), "list"),
new JsonDerivedType(typeof(ContentsArrayDataProvider), "array"),
new JsonDerivedType(typeof(ContentsDataProvider), "single"),
new JsonDerivedType(typeof(DropDownDataProvider), "dropdown"),
new JsonDerivedType(typeof(AnchorCollectionDataProvider), "anchor-list"),
new JsonDerivedType(typeof(AnchorDataProvider), "anchor")
}
};
}
return jsonTypeInfo;
}
}
+5 -8
View File
@@ -11,8 +11,7 @@ using System.Threading.Tasks;
namespace Beam.Dynamic {
public class DropDownDataProvider
: IDataProvider<string>,
IDataProvider<string[]>,
IDataProvider<SourceLink[]> {
IDataProvider<string[]> {
public IBinding? Content { get; set; }
public string? RelativeTo { get; set; }
@@ -27,26 +26,24 @@ namespace Beam.Dynamic {
return @base + '/' + relative;
}
public SourceLink[] Get(HtmlDocument document) {
public string[] Get(HtmlDocument document) {
if (Content is null)
return [];
var node = Content.Select(document);
if (node is null)
return [];
List<SourceLink> links = [];
List<string> links = [];
foreach (var child in node.ChildNodes.Where(x => x.Name == "option")) {
var childValue = child.GetAttributeValue("value", null);
if (!Uri.TryCreate(GetAbsolute(RelativeTo, childValue), UriKind.Absolute, out _))
continue;
links.Add(new SourceLink(GetAbsolute(RelativeTo, childValue)));
links.Add((GetAbsolute(RelativeTo, 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));
-21
View File
@@ -1,21 +0,0 @@
using HtmlAgilityPack;
using System.Text.Json.Serialization;
namespace Beam.Dynamic {
[JsonDerivedType(typeof(ParagraphedContentDataProvider), "paragraphed")]
[JsonDerivedType(typeof(ListContentDataProvider), "list")]
[JsonDerivedType(typeof(ContentsArrayDataProvider), "array")]
[JsonDerivedType(typeof(ContentsDataProvider), "single")]
[JsonDerivedType(typeof(DropDownDataProvider), "dropdown")]
[JsonDerivedType(typeof(AnchorCollectionDataProvider), "anchor-list")]
[JsonDerivedType(typeof(AnchorDataProvider), "anchor")]
public interface IDataProvider {
public string GetString(HtmlDocument document)
=> (this as IDataProvider<object>)?.Get(document)?.ToString() ?? "";
}
public interface IDataProvider<out T> : IDataProvider {
public T Get(HtmlDocument document);
//public HtmlNode? GetNode(HtmlDocument document);
}
}
+58
View File
@@ -0,0 +1,58 @@
using aeqw89.DataKeys;
using Beam.Abstractions;
namespace Beam.Dynamic {
public partial interface IArchitecture {
private class MainArchitecture : IArchitecture {
public MainArchitecture() { }
public DataKey<IDocumentMetaData> ChapterKey { get; set; } = new("ma:chapter");
public DataKey<IDocumentMetaData> BookKey { get; set; } = new("ma:book");
//public DownloadContext<IDocumentMetaData>? GetMeta(HtmlWeb web, DataKey<ResourceDictionary> pieceKey, BeamDataDictionary sdd, CancellationToken ct = default, ILogger? logger = null) {
// var piece = sdd.ResourceDictionaries[pieceKey].ToRecord(sdd); // retrieves novel data from the sdd
// var auxiliary = piece.AssociatedMetaSource?.ToRecord(sdd); // retrieves novel aux data from the sdd
// // null checks
// if (auxiliary is null) // aux is required to get metadata
// return null;
// if (piece?.Resource?.MetaTemplateInitialData is null) // sanity check to avoid null warnings
// return null;
// // gets the link for the novel's metadata using the auxillary data retrieved from the sdd
// var link = sdd.Templates[auxiliary.Resource.Key].Builder.Build(piece?.Resource?.MetaTemplateInitialData);
// var binding = auxiliary.Bindings;
// return new DownloadContext<IDocumentMetaData>(web, new(), [link], downloadLogger: logger);
//}
//public DownloadContext<IDocument>? GetTextRecord(HtmlWeb web, DataKey<ResourceDictionary> resKey, BeamDataDictionary sdd, IDocumentMetaData? metaData = null, CancellationToken ct = default, ILogger? logger = null) {
// var res = sdd.ResourceDictionaries[resKey].ToRecord(sdd); // retrieves the novel data from the sdd
// var aggregator = res.AssociatedSource?.ToRecord(sdd); // retrieves the aggregator (novel web source) from the sdd
// if (aggregator is null) // ensure aggergator data was retrieved successfully
// return null;
// if (res is null) // ensure novel data was retrieved successfully
// return null;
// var template = sdd.Templates[aggregator.Resource.Key]; // gets the link generator for the specified aggregator
// // creates a generative enumerable of type link from 'template'
// var sle = SourceLinkEnumerable.FromGenerator(new OrderedSourceLinkGenerator(
// template.Builder, new NumberedStateChanger(template.Factory.Behavior),
// res.Resource.TemplateInitialData));
// return new DownloadContext<IDocument>(web, new(), sle,
// retryReporter: new Progress<RetryReport>((x) => Console.WriteLine($"Retrying download of '{x.Link}' ({x.TryNumber}x)")),
// //downloadReporter: new Progress<DownloadReport>((x) => Console.WriteLine($"Downloaded ({x})")),
// asyncFailurePredicates: [
// //(x) => Task.FromResult(!x.DocumentNode.InnerHtml.Contains("<div id=\"chapter-container\" class=\"chapter-content\" itemprop=\"description\">"))
// ],
// timeOut: TimeSpan.FromSeconds(15),
// downloadLogger: logger
// );
//}
}
}
}
+22
View File
@@ -0,0 +1,22 @@
using Beam.Abstractions;
namespace Beam.Dynamic {
public class NumberedStateChanger(NumberedStateChanger.MoveState moveState) : IStateChangeBehaviour {
public delegate void MoveState(IState state, int amount);
public MoveState MoveStateDlgte { get; set; } = moveState;
public virtual void Apply(IState state, object stimulus) {
if (stimulus is not int amount)
throw new ArgumentException(string.Format(Exceptions.Exceptions.num_state_changer_stimulus_must_be_int, stimulus.GetType().Name), nameof(stimulus));
Apply(state, amount);
}
public virtual void Apply(IState state, int amount) {
MoveStateDlgte(state, amount);
}
public NumberedStateChanger(IStateChangeBehaviour behavior) : this((x, i) => {
behavior.Apply(x, i);
}) {}
}
}
+2 -1
View File
@@ -4,9 +4,10 @@ using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Beam.Abstractions;
namespace Beam.Dynamic {
public class StateChangerFactory {
public class StateChangerFactory : IStateChangerFactory {
[JsonIgnore]
public IStateChangeBehaviour Behavior => FactoryTable[StateChangerKey]();
@@ -10,7 +10,7 @@ using System.Threading.Tasks;
using System.Web;
namespace Beam.Dynamic {
public static partial class OnlineCleaner {
public static partial class StringCleaner {
[GeneratedRegex("&#x?[\\d\\w]{1,4};")]
public static partial Regex MochaBlendUnicodeEscapeSequence();