Skip to content

Commit

Permalink
Merge pull request #38 from OUCC/feat/#36
Browse files Browse the repository at this point in the history
xmlの小説の読み込み部分の作成
  • Loading branch information
miyaji255 authored Apr 29, 2024
2 parents 5d64784 + e31a665 commit 8cc3b6d
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 28 deletions.
34 changes: 34 additions & 0 deletions Epub/KoeBook.Epub/Services/AiStoryAnalyzerService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using KoeBook.Epub.Contracts.Services;
using KoeBook.Epub.Models;
using KoeBook.Models;

namespace KoeBook.Epub.Services;

public partial class AiStoryAnalyzerService(ISplitBraceService splitBraceService)
{
private readonly ISplitBraceService _splitBraceService = splitBraceService;


public EpubDocument CreateEpubDocument(AiStory aiStory, Guid id)
{
int sectionNumber = 1;
return new EpubDocument(aiStory.Title, "AI", "", id)
{
Chapters = [new Chapter()
{
Sections = aiStory.Sections.Select(s => new Section($"{sectionNumber++}")
{
Elements = s.Paragraphs.SelectMany(p =>
_splitBraceService.SplitBrace(p.GetText())
.Zip(_splitBraceService.SplitBrace(p.GetScript()))
.Select(Element (p) => new Paragraph
{
Text = p.First,
ScriptLine = new(p.Second, "", "")
})
).ToList(),
}).ToList(),
}]
};
}
}
39 changes: 28 additions & 11 deletions Epub/KoeBook.Epub/Services/AnalyzerService.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
using System.Text;
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
using KoeBook.Core;
using KoeBook.Core.Contracts.Services;
using KoeBook.Core.Models;
using KoeBook.Epub.Contracts.Services;
using KoeBook.Epub.Models;
using KoeBook.Models;

namespace KoeBook.Epub.Services;

public partial class AnalyzerService(IScraperSelectorService scrapingService, IEpubDocumentStoreService epubDocumentStoreService, ILlmAnalyzerService llmAnalyzerService) : IAnalyzerService
public partial class AnalyzerService(
IScraperSelectorService scrapingService,
IEpubDocumentStoreService epubDocumentStoreService,
ILlmAnalyzerService llmAnalyzerService,
AiStoryAnalyzerService aiStoryAnalyzerService) : IAnalyzerService
{
private readonly IScraperSelectorService _scrapingService = scrapingService;
private readonly IEpubDocumentStoreService _epubDocumentStoreService = epubDocumentStoreService;
private readonly ILlmAnalyzerService _llmAnalyzerService = llmAnalyzerService;
private readonly AiStoryAnalyzerService _aiStoryAnalyzerService = aiStoryAnalyzerService;

public async ValueTask<BookScripts> AnalyzeAsync(BookProperties bookProperties, string tempDirectory, CancellationToken cancellationToken)
{
Expand All @@ -22,26 +29,36 @@ public async ValueTask<BookScripts> AnalyzeAsync(BookProperties bookProperties,
await fs.WriteAsync(CoverFile.ToArray(), cancellationToken);
await fs.FlushAsync(cancellationToken);

EpubDocument? document;
var rubyReplaced = false;
EpubDocument document;
try
{
document = await _scrapingService.ScrapingAsync(bookProperties.Source, coverFilePath, tempDirectory, bookProperties.Id, cancellationToken);
}
catch (EbookException)
{
throw;
switch (bookProperties)
{
case { SourceType: SourceType.Url or SourceType.FilePath, Source: string uri }:
document = await _scrapingService.ScrapingAsync(uri, coverFilePath, tempDirectory, bookProperties.Id, cancellationToken);
break;
case { SourceType: SourceType.AiStory, Source: AiStory aiStory }:
document = _aiStoryAnalyzerService.CreateEpubDocument(aiStory, bookProperties.Id);
rubyReplaced = true;
break;
default:
throw new UnreachableException($"SourceType: {bookProperties.SourceType}, Source: {bookProperties.Source}");
}
}
catch (EbookException) { throw; }
catch (Exception ex)
{
EbookException.Throw(ExceptionType.WebScrapingFailed, innerException: ex);
return default;
throw new EbookException(ExceptionType.WebScrapingFailed, innerException: ex);
}
_epubDocumentStoreService.Register(document, cancellationToken);

var scriptLines = document.Chapters.SelectMany(c => c.Sections)
.SelectMany(s => s.Elements)
.OfType<Paragraph>()
.Select(p =>
.Select<Paragraph, ScriptLine>(rubyReplaced
? p => p.ScriptLine!
: p =>
{
// ルビを置換
var line = ReplaceBaseTextWithRuby(p.Text);
Expand Down
3 changes: 3 additions & 0 deletions KoeBook.Core/EbookException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,7 @@ public enum ExceptionType
/// </summary>
[EnumMember(Value = "無効なURLです")]
InvalidUrl,

[EnumMember(Value = "不正なXMLです")]
InvalidXml,
}
52 changes: 52 additions & 0 deletions KoeBook.Core/Models/AiStory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Xml.Serialization;

namespace KoeBook.Models;

[XmlRoot("Book")]
public record AiStory(
[property: XmlElement("Title", typeof(string), IsNullable = false)] string Title,
[property: XmlArray("Content", IsNullable = false), XmlArrayItem("Section", IsNullable = false)] AiStory.Section[] Sections)
{
private AiStory() : this("", []) { }

public record Section(
[property: XmlArrayItem("Paragraph", IsNullable = false)] Paragraph[] Paragraphs)
{
private Section() : this([]) { }
}


public record Paragraph(
[property: XmlElement("Text", typeof(TextElement), IsNullable = false), XmlElement("Ruby", typeof(Ruby), IsNullable = false)] InlineElement[] Inlines)
{
private Paragraph() : this([]) { }

public string GetText() => string.Concat(Inlines.Select(e => e.Text));

public string GetScript() => string.Concat(Inlines.Select(e => e.Script));
}

public abstract record class InlineElement
{
public abstract string Text { get; }
public abstract string Script { get; }
}

public record TextElement([property: XmlText] string InnerText) : InlineElement
{
private TextElement() : this("") { }

public override string Text => InnerText;
public override string Script => InnerText;
}

public record Ruby(
[property: XmlElement("Rb", IsNullable = false)] string Rb,
[property: XmlElement("Rt", IsNullable = false)] string Rt) : InlineElement
{
private Ruby() : this("", "") { }

public override string Text => Rb;
public override string Script => Rt;
}
}
31 changes: 26 additions & 5 deletions KoeBook.Core/Models/BookProperties.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
namespace KoeBook.Core.Models;
using KoeBook.Models;

namespace KoeBook.Core.Models;

/// <summary>
/// 読み上げる本の情報
/// </summary>
public class BookProperties(Guid id, string source, SourceType sourceType)
public class BookProperties
{
public Guid Id { get; } = id;
public BookProperties(Guid id, string source, SourceType sourceType)
{
if (sourceType != SourceType.FilePath && sourceType != SourceType.Url)
throw new ArgumentException($"{nameof(sourceType)}{nameof(SourceType.FilePath)}{nameof(SourceType.Url)}である必要があります。");
Id = id;
Source = source;
SourceType = sourceType;
}

public BookProperties(Guid id, AiStory aiStory)
{
Id = id;
Source = aiStory;
SourceType = SourceType.AiStory;
}

public Guid Id { get; }

public string Source { get; } = source;
/// <summary>
/// UriまたはAiStory
/// </summary>
public object Source { get; }

public SourceType SourceType { get; } = sourceType;
public SourceType SourceType { get; }
}
3 changes: 3 additions & 0 deletions KoeBook.Core/Models/SourceType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ public enum SourceType

[EnumMember(Value = "ローカルファイル")]
FilePath,

[EnumMember(Value = "AI生成")]
AiStory,
}
48 changes: 39 additions & 9 deletions KoeBook/Models/GenerationTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,57 @@

namespace KoeBook.Models;

public partial class GenerationTask(Guid id, string source, SourceType sourceType, bool skipEdit) : ObservableObject
public partial class GenerationTask : ObservableObject
{
public Guid Id { get; } = id;
public GenerationTask(Guid id, string source, SourceType sourceType, bool skipEdit)
{
if (sourceType != SourceType.FilePath && sourceType != SourceType.Url)
throw new ArgumentException($"{nameof(sourceType)}{nameof(SourceType.FilePath)}{nameof(SourceType.Url)}である必要があります。");
Id = id;
_rawSource = source;
SourceType = sourceType;
_skipEdit = skipEdit;
_title = sourceType == SourceType.FilePath ? Path.GetFileName(source) : source;
}

public GenerationTask(Guid id, AiStory aiStory, bool skipEdit)
{
Id = id;
_rawSource = aiStory;
SourceType = SourceType.AiStory;
_skipEdit = skipEdit;
_title = aiStory.Title;
}

public BookProperties ToBookProperties()
{
return SourceType == SourceType.AiStory
? new BookProperties(Id, (AiStory)_rawSource)
: new BookProperties(Id, Source, SourceType);
}

public Guid Id { get; }

public CancellationTokenSource CancellationTokenSource { get; } = new();

public CancellationToken CancellationToken => CancellationTokenSource.Token;

public string Source { get; } = source;
public string Source => _rawSource is string uri ? uri : "AI生成";

private readonly object _rawSource;

public SourceType SourceType { get; } = sourceType;
public SourceType SourceType { get; }

public string SourceDescription => SourceType switch
{
SourceType.Url => "URL",
SourceType.FilePath => "ファイルパス",
SourceType.AiStory => "AI生成",
_ => string.Empty,
};

[ObservableProperty]
private string _title = sourceType == SourceType.FilePath ? Path.GetFileName(source) : source;
private string _title;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ProgressText))]
Expand All @@ -36,7 +66,7 @@ public partial class GenerationTask(Guid id, string source, SourceType sourceTyp

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(StateText))]
[NotifyPropertyChangedFor(nameof(SkipEditChangable))]
[NotifyPropertyChangedFor(nameof(SkipEditChangeable))]
[NotifyPropertyChangedFor(nameof(Editable))]
private GenerationState _state;

Expand All @@ -49,17 +79,17 @@ public bool SkipEdit
get => _skipEdit;
set
{
if (_skipEdit != value && SkipEditChangable)
if (_skipEdit != value && SkipEditChangeable)
{
OnPropertyChanging(nameof(SkipEdit));
_skipEdit = value;
OnPropertyChanged(nameof(SkipEdit));
}
}
}
private bool _skipEdit = skipEdit;
private bool _skipEdit;

public bool SkipEditChangable => State < GenerationState.Editting;
public bool SkipEditChangeable => State < GenerationState.Editting;

public bool Editable => State == GenerationState.Editting;

Expand Down
2 changes: 1 addition & 1 deletion KoeBook/Services/GenerationTaskRunnerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ private async ValueTask RunAsyncCore(GenerationTask task, bool firstStep)
{
if (firstStep)
{
var scripts = await _analyzerService.AnalyzeAsync(new(task.Id, task.Source, task.SourceType), tempDirectory, task.CancellationToken);
var scripts = await _analyzerService.AnalyzeAsync(task.ToBookProperties(), tempDirectory, task.CancellationToken);
task.BookScripts = scripts;
task.State = GenerationState.Editting;
task.Progress = 0;
Expand Down
3 changes: 2 additions & 1 deletion KoeBook/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public static IHostBuilder UseCoreStartup(this IHostBuilder builder)
.AddKeyedSingleton<IScrapingClientService, ScrapingClientService>(nameof(ScrapingNaroService))
.AddSingleton<IScraperSelectorService, ScraperSelectorService>()
.AddSingleton<IScrapingService, ScrapingAozoraService>()
.AddSingleton<IScrapingService, ScrapingNaroService>();
.AddSingleton<IScrapingService, ScrapingNaroService>()
.AddSingleton<AiStoryAnalyzerService>();
services.AddSingleton<IEpubCreateService, EpubCreateService>();
services.AddSingleton<ISplitBraceService, SplitBraceService>();
services.AddSingleton<IFileExtensionService, FileExtensionService>();
Expand Down
2 changes: 1 addition & 1 deletion KoeBook/Views/EditDetailsTab.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
Margin="{StaticResource XXSmallLeftTopRightBottomMargin}"
OffContent="編集する"
OnContent="編集しない"
IsEnabled="{x:Bind ViewModel.Task.SkipEditChangable, Mode=OneWay}"
IsEnabled="{x:Bind ViewModel.Task.SkipEditChangeable, Mode=OneWay}"
IsOn="{x:Bind ViewModel.Task.SkipEdit, Mode=TwoWay}"/>

<TextBlock
Expand Down

0 comments on commit 8cc3b6d

Please sign in to comment.