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:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Beam.Abstractions;
|
||||
|
||||
namespace Beam.Dynamic {
|
||||
public class ConstantStateChanger : IStateChangeBehaviour {
|
||||
public void Apply(IState state, object stimulus) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
+183
-182
@@ -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 aren’t 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 aren’t 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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
// );
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}) {}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user