diff --git a/Epub/KoeBook.Epub/EpubDocumentException.cs b/Epub/KoeBook.Epub/EpubDocumentException.cs
new file mode 100644
index 0000000..d83bb62
--- /dev/null
+++ b/Epub/KoeBook.Epub/EpubDocumentException.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace KoeBook.Epub
+{
+ public class EpubDocumentException : Exception
+ {
+ public EpubDocumentException(string? message) : base(message) { }
+ }
+}
diff --git a/Epub/KoeBook.Epub/KoeBook.Epub.csproj b/Epub/KoeBook.Epub/KoeBook.Epub.csproj
index 8737da9..8f3071f 100644
--- a/Epub/KoeBook.Epub/KoeBook.Epub.csproj
+++ b/Epub/KoeBook.Epub/KoeBook.Epub.csproj
@@ -7,6 +7,8 @@
+
+
diff --git a/Epub/KoeBook.Epub/ScrapingAozora.cs b/Epub/KoeBook.Epub/ScrapingAozora.cs
new file mode 100644
index 0000000..a991c7e
--- /dev/null
+++ b/Epub/KoeBook.Epub/ScrapingAozora.cs
@@ -0,0 +1,662 @@
+using AngleSharp;
+using AngleSharp.Dom;
+using AngleSharp.Html.Dom;
+using AngleSharp.Io;
+using KoeBook.Epub.Service;
+using System.IO;
+using static KoeBook.Epub.ScrapingHelper;
+
+
+namespace KoeBook.Epub
+{
+ public partial class ScrapingAozora : IScrapingService
+ {
+ private int chapterNum;
+ private int sectionNum;
+ private bool chapterExist = false;
+ private bool sectionExist = false;
+
+
+ public async Task ScrapingAsync(string url, string coverFilePath, string imageDirectory, Guid id, CancellationToken ct)
+ {
+ var config = Configuration.Default.WithDefaultLoader();
+ using var context = BrowsingContext.New(config);
+ var doc = await context.OpenAsync(url, ct).ConfigureAwait(false);
+
+ // title の取得
+ var bookTitle = doc.QuerySelector(".title");
+ if (bookTitle is null)
+ {
+ throw new EpubDocumentException($"Failed to get title properly.\nYou may be able to get proper URL at {GetCardUrl(url)}");
+ }
+
+ // auther の取得
+ var bookAuther = doc.QuerySelector(".author");
+ if (bookAuther is null)
+ {
+ throw new EpubDocumentException($"Failed to get auther properly.\nYou may be able to get proper URL at {GetCardUrl(url)}");
+ }
+
+ // EpubDocument の生成
+ var document = new EpubDocument(TextReplace(bookTitle.InnerHtml), TextReplace(bookAuther.InnerHtml), coverFilePath, id)
+ {
+ // EpubDocument.Chapters の生成
+ Chapters = new List()
+ };
+
+ // 目次を取得
+ var contents = doc.QuerySelectorAll(".midashi_anchor");
+
+ // 目次からEpubDocumentを構成
+ List contentsIds = new List() { 0 };
+ // Chapter, Section が存在するとき、それぞれtrue
+ chapterExist = false;
+ sectionExist = false;
+ if (contents.Length != 0)
+ {
+ int previousMidashiId = 0;
+ foreach (var midashi in contents)
+ {
+ if (midashi.Id != null)
+ {
+ var MidashiId = int.Parse(midashi.Id.Replace("midashi", ""));
+ if ((MidashiId - previousMidashiId) == 100)
+ {
+ document.Chapters.Add(new Chapter() { Title = TextProcess(midashi) });
+ chapterExist = true;
+ }
+ if ((MidashiId - previousMidashiId) == 10)
+ {
+ checkChapter(document);
+ document.Chapters[^1].Sections.Add(new Section(TextProcess(midashi)));
+ sectionExist = true;
+ }
+ contentsIds.Add(MidashiId);
+ previousMidashiId = MidashiId;
+ }
+ }
+ }
+ else
+ {
+ document.Chapters.Add(new Chapter() { Title = null });
+ document.Chapters[^1].Sections.Add(new Section(bookTitle.InnerHtml));
+ }
+
+ // 本文を取得
+ var mainText = doc.QuerySelector(".main_text")!;
+
+
+ // 本文を分割しながらEpubDocumntに格納
+ // 直前のNodeを確認した操作で、その内容をParagraphに追加した場合、true
+ bool previous = false;
+ // 各ChapterとSection のインデックス
+ chapterNum = -1;
+ sectionNum = -1;
+
+ // 直前のimgタグにaltがなかったときtrueになる。
+ bool skipCaption = false;
+
+ foreach (var element in mainText.Children)
+ {
+ var nextNode = element.NextSibling;
+ if (element.TagName == "BR")
+ {
+ if (previous == true)
+ {
+ checkSection(document, chapterNum);
+ document.Chapters[chapterNum].Sections[sectionNum].Elements.Add(new Paragraph());
+ }
+
+ }
+ else if (element.TagName == "DIV")
+ {
+ var midashi = element.QuerySelector(".midashi_anchor");
+ if (midashi != null)
+ {
+ if (midashi.Id != null)
+ {
+ if (int.TryParse(midashi.Id.Replace("midashi", ""), out var midashiId))
+ {
+ if (contentsIds.Contains(midashiId))
+ {
+ var contentsId = contentsIds.IndexOf(midashiId);
+ switch (contentsIds[contentsId] - contentsIds[contentsId - 1])
+ {
+ case 100:
+ if (chapterNum >= 0 && sectionNum >= 0)
+ {
+ document.Chapters[chapterNum].Sections[sectionNum].Elements.RemoveAt(document.Chapters[chapterNum].Sections[sectionNum].Elements.Count - 1);
+ }
+ chapterNum++;
+ sectionNum = -1;
+ break;
+ case 10:
+ if (chapterNum == -1)
+ {
+ chapterNum++;
+ sectionNum = -1;
+ }
+ if (chapterNum >= 0 && sectionNum >= 0)
+ {
+ document.Chapters[chapterNum].Sections[sectionNum].Elements.RemoveAt(document.Chapters[chapterNum].Sections[sectionNum].Elements.Count - 1);
+ }
+ sectionNum++;
+ break;
+ default:
+ break;
+ }
+ }
+ else //小見出し、行中小見出しの処理
+ {
+ if (chapterNum == -1)
+ {
+ if (chapterExist)
+ {
+ document.Chapters.Insert(0, new Chapter());
+ }
+ chapterNum++;
+ sectionNum = -1;
+ }
+ if (sectionNum == -1)
+ {
+ if (sectionExist)
+ {
+ checkChapter(document);
+ document.Chapters[^1].Sections.Insert(0, new Section("___"));
+ }
+ sectionNum++;
+ }
+ checkParagraph(document, chapterNum, sectionNum);
+ if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph))
+ {
+ paragraph.Text += TextProcess(midashi);
+ document.Chapters[chapterNum].Sections[sectionNum].Elements.Add(new Paragraph());
+
+ foreach (var splitText in SplitBrace(TextProcess(midashi)))
+ {
+ if (document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph1)
+ {
+ paragraph1.Text += splitText;
+ }
+ document.Chapters[chapterNum].Sections[sectionNum].Elements.Add(new Paragraph());
+ }
+
+ }
+ }
+ }
+ else
+ {
+ throw new EpubDocumentException($"Unexpected id of Anchor tag was found: id = {midashi.Id}");
+ }
+ }
+ else
+ {
+ throw new EpubDocumentException("Unecpected structure of HTML File: div tag with class=\"midashi_anchor\", but id=\"midashi___\" exist");
+ }
+ }
+ else
+ {
+ if (element.ClassName == "caption")
+ {
+ // https://www.aozora.gr.jp/annotation/graphics.html#:~:text=%3Cdiv%20class%3D%22caption%22%3E を処理するための部分
+ checkParagraph(document, chapterNum, sectionNum);
+ if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph))
+ {
+ var split = SplitBrace(TextProcess(element));
+ for (int i = 0; i < split.Count - 1; i++)
+ {
+ if (document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph1)
+ {
+ paragraph1.Text += split[i];
+ }
+ document.Chapters[chapterNum].Sections[sectionNum].Elements.Add(new Paragraph());
+ }
+ if (document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph2)
+ {
+ paragraph2.Text += split[^1];
+ }
+ }
+ }
+ else
+ {
+ if (chapterNum == -1)
+ {
+ if (chapterExist)
+ {
+ document.Chapters.Insert(0, new Chapter());
+ }
+ chapterNum++;
+ sectionNum = -1;
+ }
+ if (sectionNum == -1)
+ {
+ if (sectionExist)
+ {
+ checkChapter(document);
+ document.Chapters[^1].Sections.Insert(0, new Section("___"));
+ }
+ sectionNum++;
+ }
+ checkParagraph(document, chapterNum, sectionNum);
+ if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph))
+ {
+ foreach (var splitText in SplitBrace(TextProcess(element)))
+ {
+ if (document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph1)
+ {
+ paragraph1.Text += splitText;
+ }
+ document.Chapters[chapterNum].Sections[sectionNum].Elements.Add(new Paragraph());
+ }
+ }
+ }
+ }
+
+ }
+ else if (element.TagName == "IMG")
+ {
+ if (element is IHtmlImageElement img)
+ {
+ if (chapterNum == -1)
+ {
+ if (chapterExist)
+ {
+ document.Chapters.Insert(0, new Chapter());
+ }
+ chapterNum++;
+ sectionNum = -1;
+ }
+ if (sectionNum == -1)
+ {
+ if (sectionExist)
+ {
+ checkChapter(document);
+ document.Chapters[^1].Sections.Insert(0, new Section("___"));
+ }
+ sectionNum++;
+ }
+
+ if (element.ClassName != "gaiji")
+ {
+ if (img.Source != null)
+ {
+ // 画像のダウンロード
+ var loader = context.GetService();
+ if (loader != null)
+ {
+ var downloading = loader.FetchAsync(new DocumentRequest(new Url(img.Source)));
+ ct.Register(() => downloading.Cancel());
+ var response = await downloading.Task.ConfigureAwait(false);
+ using var ms = new MemoryStream();
+ await response.Content.CopyToAsync(ms, ct).ConfigureAwait(false);
+ var filePass = imageDirectory + FileUrlToFileName().Replace(img.Source, "$1");
+ File.WriteAllBytes(filePass, ms.ToArray());
+ checkSection(document, chapterNum);
+ if (document.Chapters[chapterNum].Sections[sectionNum].Elements.Count > 1)
+ {
+ document.Chapters[chapterNum].Sections[sectionNum].Elements.Insert(document.Chapters[chapterNum].Sections[sectionNum].Elements.Count - 1, new Picture(filePass));
+ }
+ }
+ }
+ if (img.AlternativeText != null)
+ {
+ checkParagraph(document, chapterNum, sectionNum);
+ if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph))
+ {
+ paragraph.Text += TextReplace(img.AlternativeText);
+ document.Chapters[chapterNum].Sections[sectionNum].Elements.Add(new Paragraph());
+ }
+ skipCaption = false;
+ }
+ else
+ {
+ skipCaption = true;
+ }
+ }
+ }
+ }
+ else if (element.TagName == "SPAN")
+ {
+ if (element.ClassName == "caption")
+ {
+ if (skipCaption)
+ {
+ if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^2] is Paragraph paragraph))
+ {
+ paragraph.Text = TextProcess(element) + "の画像";
+ }
+ }
+ else
+ {
+ if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph))
+ {
+ paragraph.Text = TextProcess(element) + "の画像";
+ }
+ }
+
+ }
+ else if (element.ClassName == "notes")
+ {
+ switch (element.InnerHtml)
+ {
+ case "[#改丁]":
+ break;
+ case "[#改ページ]":
+ break;
+ case "[#改見開き]":
+ break;
+ case "[#改段]":
+ break;
+ case "[#ページの左右中央]":
+ break;
+ default:
+ checkParagraph(document, chapterNum, sectionNum);
+ if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph))
+ {
+ foreach (var splitText in SplitBrace(TextProcess(element)))
+ {
+ if (document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph1)
+ {
+ paragraph1.Text += splitText;
+ }
+ document.Chapters[chapterNum].Sections[sectionNum].Elements.Add(new Paragraph());
+ }
+ }
+ break;
+ }
+ }
+ else
+ {
+ if (chapterNum == -1)
+ {
+ if (chapterExist)
+ {
+ document.Chapters.Insert(0, new Chapter());
+ }
+ chapterNum++;
+ sectionNum = -1;
+ }
+ if (sectionNum == -1)
+ {
+ if (sectionExist)
+ {
+ checkChapter(document);
+ document.Chapters[^1].Sections.Insert(0, new Section("___"));
+ }
+ sectionNum++;
+ }
+ checkParagraph(document, chapterNum, sectionNum);
+ if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph))
+ {
+ var split = SplitBrace(TextProcess(element));
+ for (int i = 0; i < split.Count - 1; i++)
+ {
+ if (document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph1)
+ {
+ paragraph1.Text += split[i];
+ }
+ document.Chapters[chapterNum].Sections[sectionNum].Elements.Add(new Paragraph());
+ }
+ if (document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph2)
+ {
+ paragraph2.Text += split[^1];
+ }
+ }
+ // 想定していない構造が見つかったことをログに出力した方が良い?
+ }
+ }
+ else
+ {
+ if (chapterNum == -1)
+ {
+ if (chapterExist)
+ {
+ document.Chapters.Insert(0, new Chapter());
+ }
+ chapterNum++;
+ sectionNum = -1;
+ }
+ if (sectionNum == -1)
+ {
+ if (sectionExist)
+ {
+ checkChapter(document);
+ document.Chapters[^1].Sections.Insert(0, new Section("___"));
+ }
+ sectionNum++;
+ }
+ checkParagraph(document, chapterNum, sectionNum);
+ if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph))
+ {
+ paragraph.Text += TextProcess(element);
+
+ var split = SplitBrace(TextProcess(element));
+ for (int i = 0; i < split.Count - 1; i++)
+ {
+ if (document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph1)
+ {
+ paragraph1.Text += split[i];
+ }
+ document.Chapters[chapterNum].Sections[sectionNum].Elements.Add(new Paragraph());
+ }
+ if (document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph2)
+ {
+ paragraph2.Text += split[^1];
+ }
+ }
+ // 想定していない構造が見つかったことをログに出力した方が良い?
+ }
+
+ if (nextNode != null)
+ {
+ if (nextNode.NodeType == NodeType.Text)
+ {
+ if (nextNode.Text() != "\n")
+ {
+ previous = true;
+
+ if (chapterNum == -1)
+ {
+ if (chapterExist)
+ {
+ document.Chapters.Insert(0, new Chapter());
+ }
+ chapterNum++;
+ sectionNum = -1;
+ }
+ if (sectionNum == -1)
+ {
+ if (sectionExist)
+ {
+ checkChapter(document);
+ document.Chapters[^1].Sections.Insert(0, new Section("___"));
+ }
+ sectionNum++;
+ }
+ checkParagraph(document, chapterNum, sectionNum);
+ if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph))
+ {
+ var split = SplitBrace(TextReplace(nextNode.Text()));
+ for (int i = 0; i < split.Count - 1; i++)
+ {
+ if (document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph1)
+ {
+ paragraph1.Text += split[i];
+ }
+ document.Chapters[chapterNum].Sections[sectionNum].Elements.Add(new Paragraph());
+ }
+ if (document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph2)
+ {
+ paragraph2.Text += split[^1];
+ }
+ }
+
+ }
+ else
+ {
+ previous = false;
+ }
+ }
+ else
+ {
+ previous = false;
+ }
+ }
+ }
+
+ document.Chapters[^1].Sections[^1].Elements.RemoveAt(document.Chapters[^1].Sections[^1].Elements.Count - 1);
+
+ if (checkEpubDocument(document))
+ {
+ Console.WriteLine("Success");
+ }
+ else
+ {
+ Console.WriteLine("False");
+ }
+ return document;
+ }
+
+ private bool checkEpubDocument(EpubDocument document)
+ {
+ foreach (var chapter in document.Chapters)
+ {
+ foreach (var section in chapter.Sections)
+ {
+ foreach (var element in section.Elements)
+ {
+ if (element is Paragraph paragraph)
+ {
+ if (paragraph.Text == null)
+ {
+ Console.WriteLine($"{document.Chapters.IndexOf(chapter)}, {chapter.Sections.IndexOf(section)}, {section.Elements.IndexOf(element)}");
+ return false;
+ }
+ }
+ else if (element is Picture picture)
+ {
+ if (picture.PictureFilePath == null)
+ {
+ Console.WriteLine($"{document.Chapters.IndexOf(chapter)}, {chapter.Sections.IndexOf(section)}, {section.Elements.IndexOf(element)}");
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ private static string TextProcess(IElement element)
+ {
+ string text = "";
+ if (element.ChildElementCount == 0)
+ {
+ text += TextReplace(element.InnerHtml);
+ }
+ else
+ {
+ var rubies = element.QuerySelectorAll("ruby");
+ if (rubies.Length > 0)
+ {
+ if (element.Children[0].PreviousSibling is INode node)
+ {
+ if (node.NodeType == NodeType.Text)
+ {
+ if (node.Text() != "\n")
+ {
+ text += TextReplace(node.Text());
+ }
+ }
+ }
+ foreach (var item in element.Children)
+ {
+ if (item.TagName == "RUBY")
+ {
+ if (item.QuerySelectorAll("img").Length > 0)
+ {
+ if (item.QuerySelector("rt") != null)
+ {
+ text += TextReplace(item.QuerySelector("rt")!.TextContent);
+ }
+ }
+ else
+ {
+ text += TextReplace(item.OuterHtml);
+ }
+ }
+ else
+ {
+ if ((item.TextContent != "\n") && (!string.IsNullOrEmpty(item.TextContent)))
+ {
+ text += TextReplace(item.TextContent);
+ }
+ }
+ if (item.NextSibling != null)
+ {
+ if ((item.NextSibling.TextContent != "\n") && (!string.IsNullOrEmpty(item.NextSibling.TextContent)))
+ {
+ text += TextReplace(item.NextSibling.Text());
+ }
+ }
+ }
+ }
+ else if (element.TagName == "RUBY")
+ {
+ if (element.QuerySelectorAll("img").Length > 0)
+ {
+ if (element.QuerySelector("rt") != null)
+ {
+ text += TextReplace(element.QuerySelector("rt")!.TextContent);
+ }
+ }
+ else
+ {
+ text += TextReplace(element.OuterHtml);
+ }
+ }
+ else
+ {
+ text += TextReplace(element.TextContent);
+ }
+ }
+ return text;
+ }
+
+
+ // ローマ数字、改行の置換をまとめて行う。
+ private static string TextReplace(string text)
+ {
+ string returnText = text;
+ returnText = RomanNumImg().Replace(returnText, "$1");
+ returnText = RomanNumText1().Replace(returnText, "$1");
+ returnText = RomanNumText2().Replace(returnText, "$1");
+ returnText = returnText.Replace("\n", "");
+ return returnText;
+ }
+
+
+ private static string GetCardUrl(string url)
+ {
+ return UrlBookToCard().Replace(url, "$1card$2$3");
+ }
+
+ [System.Text.RegularExpressions.GeneratedRegex(@"(https://www\.aozora\.gr\.jp/cards/\d{6}/)files/(\d{1,})_\d{1,}(\.html)")]
+ private static partial System.Text.RegularExpressions.Regex UrlBookToCard();
+
+ [System.Text.RegularExpressions.GeneratedRegex(@"")]
+ private static partial System.Text.RegularExpressions.Regex RomanNumImg();
+
+ [System.Text.RegularExpressions.GeneratedRegex(@"※[#ローマ数字(\d{1,})、1-1\d.{0,}]")]
+ private static partial System.Text.RegularExpressions.Regex RomanNumText1();
+
+ [System.Text.RegularExpressions.GeneratedRegex(@"※(ローマ数字(\d.{1,})、1-1\d.{0,})")]
+ private static partial System.Text.RegularExpressions.Regex RomanNumText2();
+
+ [System.Text.RegularExpressions.GeneratedRegex(@"http.{1,}/([^/]{0,}\.[^/]{1,})")]
+ private static partial System.Text.RegularExpressions.Regex FileUrlToFileName();
+
+ [System.Text.RegularExpressions.GeneratedRegex(@"(.{1,})")]
+ private static partial System.Text.RegularExpressions.Regex RubyToText();
+ }
+}
diff --git a/Epub/KoeBook.Epub/ScrapingHelper.cs b/Epub/KoeBook.Epub/ScrapingHelper.cs
new file mode 100644
index 0000000..ec342f6
--- /dev/null
+++ b/Epub/KoeBook.Epub/ScrapingHelper.cs
@@ -0,0 +1,81 @@
+using System.Net;
+using System.Reflection.Metadata;
+
+namespace KoeBook.Epub;
+
+internal static class ScrapingHelper
+{
+ internal static void checkChapter(EpubDocument document)
+ {
+ if (document.Chapters.Count == 0)
+ {
+ document.Chapters.Add(new Chapter() { Title = null });
+ }
+ return;
+ }
+
+ internal static void checkSection(EpubDocument document, int ChapterNum)
+ {
+
+ checkChapter(document);
+
+ if (document.Chapters[ChapterNum].Sections.Count == 0)
+ {
+ if (document.Chapters[ChapterNum].Title != null)
+ {
+ document.Chapters[ChapterNum].Sections.Add(new Section(document.Chapters[ChapterNum].Title!));
+ }
+ else
+ {
+ document.Chapters[ChapterNum].Sections.Add(new Section(document.Title));
+ }
+
+ }
+ return;
+ }
+
+ internal static void checkParagraph(EpubDocument document, int chapterNum, int sectionNum)
+ {
+ checkSection(document, chapterNum);
+ if (document.Chapters[chapterNum].Sections[sectionNum].Elements.Count == 0)
+ {
+ document.Chapters[chapterNum].Sections[sectionNum].Elements.Add(new Paragraph());
+ }
+ return;
+ }
+
+ public static List SplitBrace(string text)
+ {
+ var result = new List();
+ int bracket = 0;
+ var brackets = new List();
+ foreach (char c in text)
+ {
+ if (c == '「') bracket++;
+ if (c == '」') bracket--;
+ brackets.Add(bracket);
+ }
+ var mn = Math.Min(0, brackets.Min());
+ int startIdx = 0;
+ for (int i = 0; i < brackets.Count; i++)
+ {
+ brackets[i] -= mn;
+ if (text[i] == '「' && brackets[i] == 1 && i != 0)
+ {
+ result.Add(text[startIdx..(i - startIdx)]);
+ startIdx = i;
+ }
+ if (text[i] == '」' && brackets[i] == 0 && i != 0)
+ {
+ result.Add(text[startIdx..(i - startIdx + 1)]);
+ startIdx = i + 1;
+ }
+ }
+ if (startIdx != text.Length - 1)
+ {
+ result.Add(text[startIdx..]);
+ }
+
+ return result;
+ }
+}
diff --git a/Epub/KoeBook.Epub/Service/IScrapingService.cs b/Epub/KoeBook.Epub/Service/IScrapingService.cs
index c66e568..c78791d 100644
--- a/Epub/KoeBook.Epub/Service/IScrapingService.cs
+++ b/Epub/KoeBook.Epub/Service/IScrapingService.cs
@@ -2,5 +2,5 @@
public interface IScrapingService
{
- public Task ScrapingAsync(string url, string coverFil9lePath, Guid id, CancellationToken ct);
+ public Task ScrapingAsync(string url, string coverFillePath, string imageDirectory, Guid id, CancellationToken ct);
}
diff --git a/KoeBook.Test/KoeBook.Test.csproj b/KoeBook.Test/KoeBook.Test.csproj
index 1b7908a..c085021 100644
--- a/KoeBook.Test/KoeBook.Test.csproj
+++ b/KoeBook.Test/KoeBook.Test.csproj
@@ -18,7 +18,6 @@
-
diff --git a/KoeBook.Common/KoeBook.Common.csproj b/KoeBook.Unsafe/KoeBook.Unsafe.csproj
similarity index 54%
rename from KoeBook.Common/KoeBook.Common.csproj
rename to KoeBook.Unsafe/KoeBook.Unsafe.csproj
index 683628f..f796119 100644
--- a/KoeBook.Common/KoeBook.Common.csproj
+++ b/KoeBook.Unsafe/KoeBook.Unsafe.csproj
@@ -4,12 +4,13 @@
net8.0
enable
enable
+ true
-
-
-
+
+ all
+
diff --git a/KoeBook.Unsafe/NativeMethods.json b/KoeBook.Unsafe/NativeMethods.json
new file mode 100644
index 0000000..7591544
--- /dev/null
+++ b/KoeBook.Unsafe/NativeMethods.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://aka.ms/CsWin32.schema.json",
+ "public": true
+}
diff --git a/KoeBook.Unsafe/NativeMethods.txt b/KoeBook.Unsafe/NativeMethods.txt
new file mode 100644
index 0000000..3428e7b
--- /dev/null
+++ b/KoeBook.Unsafe/NativeMethods.txt
@@ -0,0 +1 @@
+MessageBox
diff --git a/KoeBook.sln b/KoeBook.sln
index c63240a..90e4b56 100644
--- a/KoeBook.sln
+++ b/KoeBook.sln
@@ -7,11 +7,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KoeBook", "KoeBook\KoeBook.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KoeBook.Core", "KoeBook.Core\KoeBook.Core.csproj", "{50444E76-C6A7-40AF-879C-0AD88A287510}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KoeBook.Common", "KoeBook.Common\KoeBook.Common.csproj", "{1E5B40AE-1D42-40D0-85D1-E921B17BFA69}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KoeBook.Epub", "Epub\KoeBook.Epub\KoeBook.Epub.csproj", "{1DE55F58-E4F3-4077-A241-AFFD736A2B01}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KoeBook.Test", "KoeBook.Test\KoeBook.Test.csproj", "{67CEB31C-B026-499A-83B4-528914EABDBF}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KoeBook.Test", "KoeBook.Test\KoeBook.Test.csproj", "{67CEB31C-B026-499A-83B4-528914EABDBF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KoeBook.Unsafe", "KoeBook.Unsafe\KoeBook.Unsafe.csproj", "{30AE8B29-D2D1-4D46-9A5E-68A3F4339892}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -65,22 +65,6 @@ Global
{50444E76-C6A7-40AF-879C-0AD88A287510}.Release|x64.Build.0 = Release|x64
{50444E76-C6A7-40AF-879C-0AD88A287510}.Release|x86.ActiveCfg = Release|x86
{50444E76-C6A7-40AF-879C-0AD88A287510}.Release|x86.Build.0 = Release|x86
- {1E5B40AE-1D42-40D0-85D1-E921B17BFA69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {1E5B40AE-1D42-40D0-85D1-E921B17BFA69}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {1E5B40AE-1D42-40D0-85D1-E921B17BFA69}.Debug|arm64.ActiveCfg = Debug|Any CPU
- {1E5B40AE-1D42-40D0-85D1-E921B17BFA69}.Debug|arm64.Build.0 = Debug|Any CPU
- {1E5B40AE-1D42-40D0-85D1-E921B17BFA69}.Debug|x64.ActiveCfg = Debug|Any CPU
- {1E5B40AE-1D42-40D0-85D1-E921B17BFA69}.Debug|x64.Build.0 = Debug|Any CPU
- {1E5B40AE-1D42-40D0-85D1-E921B17BFA69}.Debug|x86.ActiveCfg = Debug|Any CPU
- {1E5B40AE-1D42-40D0-85D1-E921B17BFA69}.Debug|x86.Build.0 = Debug|Any CPU
- {1E5B40AE-1D42-40D0-85D1-E921B17BFA69}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {1E5B40AE-1D42-40D0-85D1-E921B17BFA69}.Release|Any CPU.Build.0 = Release|Any CPU
- {1E5B40AE-1D42-40D0-85D1-E921B17BFA69}.Release|arm64.ActiveCfg = Release|Any CPU
- {1E5B40AE-1D42-40D0-85D1-E921B17BFA69}.Release|arm64.Build.0 = Release|Any CPU
- {1E5B40AE-1D42-40D0-85D1-E921B17BFA69}.Release|x64.ActiveCfg = Release|Any CPU
- {1E5B40AE-1D42-40D0-85D1-E921B17BFA69}.Release|x64.Build.0 = Release|Any CPU
- {1E5B40AE-1D42-40D0-85D1-E921B17BFA69}.Release|x86.ActiveCfg = Release|Any CPU
- {1E5B40AE-1D42-40D0-85D1-E921B17BFA69}.Release|x86.Build.0 = Release|Any CPU
{1DE55F58-E4F3-4077-A241-AFFD736A2B01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1DE55F58-E4F3-4077-A241-AFFD736A2B01}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1DE55F58-E4F3-4077-A241-AFFD736A2B01}.Debug|arm64.ActiveCfg = Debug|Any CPU
@@ -113,6 +97,22 @@ Global
{67CEB31C-B026-499A-83B4-528914EABDBF}.Release|x64.Build.0 = Release|Any CPU
{67CEB31C-B026-499A-83B4-528914EABDBF}.Release|x86.ActiveCfg = Release|Any CPU
{67CEB31C-B026-499A-83B4-528914EABDBF}.Release|x86.Build.0 = Release|Any CPU
+ {30AE8B29-D2D1-4D46-9A5E-68A3F4339892}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {30AE8B29-D2D1-4D46-9A5E-68A3F4339892}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {30AE8B29-D2D1-4D46-9A5E-68A3F4339892}.Debug|arm64.ActiveCfg = Debug|Any CPU
+ {30AE8B29-D2D1-4D46-9A5E-68A3F4339892}.Debug|arm64.Build.0 = Debug|Any CPU
+ {30AE8B29-D2D1-4D46-9A5E-68A3F4339892}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {30AE8B29-D2D1-4D46-9A5E-68A3F4339892}.Debug|x64.Build.0 = Debug|Any CPU
+ {30AE8B29-D2D1-4D46-9A5E-68A3F4339892}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {30AE8B29-D2D1-4D46-9A5E-68A3F4339892}.Debug|x86.Build.0 = Debug|Any CPU
+ {30AE8B29-D2D1-4D46-9A5E-68A3F4339892}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {30AE8B29-D2D1-4D46-9A5E-68A3F4339892}.Release|Any CPU.Build.0 = Release|Any CPU
+ {30AE8B29-D2D1-4D46-9A5E-68A3F4339892}.Release|arm64.ActiveCfg = Release|Any CPU
+ {30AE8B29-D2D1-4D46-9A5E-68A3F4339892}.Release|arm64.Build.0 = Release|Any CPU
+ {30AE8B29-D2D1-4D46-9A5E-68A3F4339892}.Release|x64.ActiveCfg = Release|Any CPU
+ {30AE8B29-D2D1-4D46-9A5E-68A3F4339892}.Release|x64.Build.0 = Release|Any CPU
+ {30AE8B29-D2D1-4D46-9A5E-68A3F4339892}.Release|x86.ActiveCfg = Release|Any CPU
+ {30AE8B29-D2D1-4D46-9A5E-68A3F4339892}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/KoeBook/App.xaml.cs b/KoeBook/App.xaml.cs
index b41e3ea..3458991 100644
--- a/KoeBook/App.xaml.cs
+++ b/KoeBook/App.xaml.cs
@@ -1,6 +1,8 @@
-using KoeBook.Activation;
+using FastEnumUtility;
+using KoeBook.Activation;
using KoeBook.Components.Dialog;
using KoeBook.Contracts.Services;
+using KoeBook.Core;
using KoeBook.Core.Contracts.Services;
using KoeBook.Core.Services;
using KoeBook.Core.Services.Mocks;
@@ -14,7 +16,10 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.UI.Xaml;
-using Microsoft.Extensions.Http;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.UI.WindowsAndMessaging;
+using WinRT.Interop;
namespace KoeBook;
@@ -122,8 +127,23 @@ public App()
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
- // TODO: Log and handle exceptions as appropriate.
- // https://docs.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.application.unhandledexception.
+ var hwnd = WindowNative.GetWindowHandle(MainWindow);
+ if (e.Exception is EbookException ebookException)
+ {
+ PInvoke.MessageBox((HWND)hwnd,
+ $"ラーが発生しました。KoeBookを終了します。\n{ebookException.ExceptionType.GetEnumMemberValue()}",
+ "KoeBookからのお知らせ",
+ MESSAGEBOX_STYLE.MB_OK | MESSAGEBOX_STYLE.MB_ICONWARNING);
+ }
+ else
+ {
+ PInvoke.MessageBox((HWND)hwnd,
+ $"不明なエラーが発生しました。KoeBookを終了します。\n{e.Exception.Message}\n\n{e.Exception.StackTrace}",
+ "KoeBookからのお知らせ",
+ MESSAGEBOX_STYLE.MB_OK | MESSAGEBOX_STYLE.MB_ICONWARNING);
+ }
+ e.Handled = true;
+ Current.Exit();
}
protected override async void OnLaunched(LaunchActivatedEventArgs args)
diff --git a/KoeBook/Components/Dialog/DialogService.cs b/KoeBook/Components/Dialog/DialogService.cs
index ccd31e5..02ee94d 100644
--- a/KoeBook/Components/Dialog/DialogService.cs
+++ b/KoeBook/Components/Dialog/DialogService.cs
@@ -42,4 +42,15 @@ public Task ShowAsync(
{
return ShowAsync(title, new DialogContentControl(content), primaryText, closeText, defaultButton, cancellationToken);
}
+
+ public Task ShowInfoAsync(string title, string content, string primaryText, CancellationToken cancellationToken)
+ {
+ var dialog = new SharedContentDialog(title, primaryText, null, ContentDialogButton.Primary)
+ {
+ XamlRoot = App.MainWindow.Content.XamlRoot,
+ Content = new DialogContentControl(content),
+ RequestedTheme = _themeSelectorService.Theme
+ };
+ return dialog.ShowAsync().AsTask(cancellationToken);
+ }
}
diff --git a/KoeBook/Components/Dialog/SharedContentDialog.xaml.cs b/KoeBook/Components/Dialog/SharedContentDialog.xaml.cs
index 51ad157..b1e58f3 100644
--- a/KoeBook/Components/Dialog/SharedContentDialog.xaml.cs
+++ b/KoeBook/Components/Dialog/SharedContentDialog.xaml.cs
@@ -4,7 +4,7 @@ namespace KoeBook.Components.Dialog;
public sealed partial class SharedContentDialog : ContentDialog
{
- public SharedContentDialog(string title, string primaryText, string closeText, ContentDialogButton defaultButton)
+ public SharedContentDialog(string title, string primaryText, string? closeText, ContentDialogButton defaultButton)
{
Title = title;
PrimaryButtonText = primaryText;
diff --git a/KoeBook/Contracts/Services/IDialogService.cs b/KoeBook/Contracts/Services/IDialogService.cs
index 2410c1a..36f771f 100644
--- a/KoeBook/Contracts/Services/IDialogService.cs
+++ b/KoeBook/Contracts/Services/IDialogService.cs
@@ -19,6 +19,12 @@ Task ShowAsync(
string closeText,
ContentDialogButton defaultButton,
CancellationToken cancellationToken);
+
+ Task ShowInfoAsync(
+ string title,
+ string content,
+ string primaryText,
+ CancellationToken cancellationToken);
}
public static class DialogServiceEx
diff --git a/KoeBook/KoeBook.csproj b/KoeBook/KoeBook.csproj
index 89918c3..a83ccf0 100644
--- a/KoeBook/KoeBook.csproj
+++ b/KoeBook/KoeBook.csproj
@@ -40,6 +40,7 @@
+
diff --git a/KoeBook/Services/GenerationTaskRunnerService.cs b/KoeBook/Services/GenerationTaskRunnerService.cs
index c7ed954..5669da4 100644
--- a/KoeBook/Services/GenerationTaskRunnerService.cs
+++ b/KoeBook/Services/GenerationTaskRunnerService.cs
@@ -1,4 +1,6 @@
-using KoeBook.Contracts.Services;
+using FastEnumUtility;
+using KoeBook.Contracts.Services;
+using KoeBook.Core;
using KoeBook.Core.Contracts.Services;
using KoeBook.Core.Models;
using KoeBook.Models;
@@ -9,19 +11,22 @@ namespace KoeBook.Services;
public class GenerationTaskRunnerService
{
private readonly IGenerationTaskService _taskService;
-
private readonly IAnalyzerService _analyzerService;
-
private readonly IEpubGenerateService _epubGenService;
-
+ private readonly IDialogService _dialogService;
private readonly string _tempFolder = ApplicationData.Current.TemporaryFolder.Path;
- public GenerationTaskRunnerService(IGenerationTaskService taskService, IAnalyzerService analyzerService, IEpubGenerateService epubGenService)
+ public GenerationTaskRunnerService(
+ IGenerationTaskService taskService,
+ IAnalyzerService analyzerService,
+ IEpubGenerateService epubGenService,
+ IDialogService dialogService)
{
_taskService = taskService;
_taskService.OnTasksChanged += TasksChanged;
_analyzerService = analyzerService;
_epubGenService = epubGenService;
+ _dialogService = dialogService;
}
private async void TasksChanged(GenerationTask task, ChangedEvents changedEvents)
@@ -62,6 +67,11 @@ private async ValueTask RunAsync(GenerationTask task)
{
task.State = GenerationState.Failed;
}
+ catch (EbookException e)
+ {
+ task.State = GenerationState.Failed;
+ await _dialogService.ShowInfoAsync("生成失敗", e.ExceptionType.GetEnumMemberValue()!, "OK", default);
+ }
catch
{
task.State = GenerationState.Failed;
@@ -83,6 +93,11 @@ public async void RunGenerateEpubAsync(GenerationTask task)
{
task.State = GenerationState.Failed;
}
+ catch (EbookException e)
+ {
+ task.State = GenerationState.Failed;
+ await _dialogService.ShowInfoAsync("生成失敗", e.ExceptionType.GetEnumMemberValue()!, "OK", default);
+ }
catch
{
task.State = GenerationState.Failed;
diff --git a/KoeBook/ViewModels/GenerationTaskViewModel.cs b/KoeBook/ViewModels/GenerationTaskViewModel.cs
index b6d2fa3..cae8c09 100644
--- a/KoeBook/ViewModels/GenerationTaskViewModel.cs
+++ b/KoeBook/ViewModels/GenerationTaskViewModel.cs
@@ -31,7 +31,7 @@ private async Task StopTask(CancellationToken cancellationToken)
if (Task is null)
return;
- var result = await _dialogService.ShowAsync("KoeBookからのお知らせ", "実行中のタスクをキャンセルし、削除します。この操作は戻せません。", "削除", cancellationToken);
+ var result = await _dialogService.ShowAsync("タスクを削除します", "実行中のタスクをキャンセルし、削除します。この操作は戻せません。", "削除", cancellationToken);
if (result != ContentDialogResult.Primary)
return;
_runner.StopTask(Task);
diff --git a/KoeBook/ViewModels/MainViewModel.cs b/KoeBook/ViewModels/MainViewModel.cs
index ce235f2..14f9ac4 100644
--- a/KoeBook/ViewModels/MainViewModel.cs
+++ b/KoeBook/ViewModels/MainViewModel.cs
@@ -114,7 +114,7 @@ public async void BeforeTextChanging(TextBox _, TextBoxBeforeTextChangingEventAr
if (EbookFilePath is null || string.IsNullOrEmpty(args.NewText))
return;
- var result = await _dialogService.ShowAsync("外部URLから使用する場合、ローカルファイルは無視されます。", default);
+ var result = await _dialogService.ShowAsync("ローカルファイルを無視します", "外部URLから使用する場合、ローカルファイルは無視されます。", default);
if (result == ContentDialogResult.Primary)
{
EbookFilePath = null;