From 09bca01d6dcc53884b82d1cf2ab1706bfd7107a2 Mon Sep 17 00:00:00 2001 From: TakenPt Date: Sat, 24 Feb 2024 16:50:50 +0900 Subject: [PATCH 1/6] wip --- Epub/KoeBook.Epub/EpubDocumentException.cs | 13 + Epub/KoeBook.Epub/KoeBook.Epub.csproj | 1 + Epub/KoeBook.Epub/ScrapingAozora.cs | 571 +++++++++++++++++++++ Epub/KoeBook.Epub/ScrapingHelper.cs | 47 ++ 4 files changed, 632 insertions(+) create mode 100644 Epub/KoeBook.Epub/EpubDocumentException.cs create mode 100644 Epub/KoeBook.Epub/ScrapingAozora.cs create mode 100644 Epub/KoeBook.Epub/ScrapingHelper.cs diff --git a/Epub/KoeBook.Epub/EpubDocumentException.cs b/Epub/KoeBook.Epub/EpubDocumentException.cs new file mode 100644 index 0000000..e546ee5 --- /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..68f626e 100644 --- a/Epub/KoeBook.Epub/KoeBook.Epub.csproj +++ b/Epub/KoeBook.Epub/KoeBook.Epub.csproj @@ -7,6 +7,7 @@ + diff --git a/Epub/KoeBook.Epub/ScrapingAozora.cs b/Epub/KoeBook.Epub/ScrapingAozora.cs new file mode 100644 index 0000000..3ac1726 --- /dev/null +++ b/Epub/KoeBook.Epub/ScrapingAozora.cs @@ -0,0 +1,571 @@ +using AngleSharp; +using AngleSharp.Dom; +using AngleSharp.Html.Dom; +using AngleSharp.Io; +using System.IO; +using static KoeBook.Epub.ScrapingHelper; + + +namespace KoeBook.Epub +{ + public partial class ScrapingAozora + { + 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) + { + // 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 = TextReplace(TextProcess(midashi, document))}); + chapterExist = true; + } + if ((MidashiId - previousMidashiId) == 10) + { + checkChapter(document); + document.Chapters[^1].Sections.Add(new Section(TextProcess(midashi, document))); + 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) + { + 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 (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()); + } + if (chapterNum > 0) + { + document.Chapters[chapterNum].Sections[sectionNum].Elements.RemoveAt(document.Chapters[chapterNum].Sections[sectionNum].Elements.Count - 1); + } + 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 += TextReplace(TextProcess(midashi, document)); + 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)) + { + paragraph.Text += TextReplace(TextProcess(element, document)); + } + } + 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 += TextReplace(TextProcess(element, document)); + } + } + } + + } 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, document) + "の画像"; + } + } else + { + if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph)) + { + paragraph.Text = TextProcess(element, document ) + "の画像"; + } + } + + } 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)) + { + paragraph.Text += TextReplace(element.InnerHtml); + 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)) + { + paragraph.Text += TextReplace(TextProcess(element, document)); + } + // 想定していない構造が見つかったことをログに出力した方が良い? + } + } 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 += TextReplace(TextProcess(element, document)); + } + // 想定していない構造が見つかったことをログに出力した方が良い? + } + + 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)) + { + paragraph.Text += TextReplace(nextNode.Text()); + } + + } 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 string TextProcess(IElement element, EpubDocument document) + { + 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,})(.{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..1d4d4aa --- /dev/null +++ b/Epub/KoeBook.Epub/ScrapingHelper.cs @@ -0,0 +1,47 @@ +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; + } +} From dfe767bdf6db9b905e261e9b413ab5cf6d171c27 Mon Sep 17 00:00:00 2001 From: TakenPt Date: Sat, 24 Feb 2024 19:57:05 +0900 Subject: [PATCH 2/6] wip --- Epub/KoeBook.Epub/EpubDocumentException.cs | 2 +- Epub/KoeBook.Epub/KoeBook.Epub.csproj | 1 + Epub/KoeBook.Epub/ScrapingAozora.cs | 184 +++++++++++++++------ Epub/KoeBook.Epub/ScrapingHelper.cs | 42 ++++- Epub/KoeBook.Epub/ScrapingNarou.cs | 64 +++++++ 5 files changed, 241 insertions(+), 52 deletions(-) create mode 100644 Epub/KoeBook.Epub/ScrapingNarou.cs diff --git a/Epub/KoeBook.Epub/EpubDocumentException.cs b/Epub/KoeBook.Epub/EpubDocumentException.cs index e546ee5..d83bb62 100644 --- a/Epub/KoeBook.Epub/EpubDocumentException.cs +++ b/Epub/KoeBook.Epub/EpubDocumentException.cs @@ -8,6 +8,6 @@ namespace KoeBook.Epub { public class EpubDocumentException : Exception { - public EpubDocumentException(string? message) : base(message) {} + public EpubDocumentException(string? message) : base(message) { } } } diff --git a/Epub/KoeBook.Epub/KoeBook.Epub.csproj b/Epub/KoeBook.Epub/KoeBook.Epub.csproj index 68f626e..8f3071f 100644 --- a/Epub/KoeBook.Epub/KoeBook.Epub.csproj +++ b/Epub/KoeBook.Epub/KoeBook.Epub.csproj @@ -8,6 +8,7 @@ + diff --git a/Epub/KoeBook.Epub/ScrapingAozora.cs b/Epub/KoeBook.Epub/ScrapingAozora.cs index 3ac1726..c8a4498 100644 --- a/Epub/KoeBook.Epub/ScrapingAozora.cs +++ b/Epub/KoeBook.Epub/ScrapingAozora.cs @@ -47,7 +47,7 @@ public async Task ScrapingAsync(string url, string coverFilePath, var contents = doc.QuerySelectorAll(".midashi_anchor"); // 目次からEpubDocumentを構成 - List contentsIds = new List() {0}; + List contentsIds = new List() { 0 }; // Chapter, Section が存在するとき、それぞれtrue chapterExist = false; sectionExist = false; @@ -61,22 +61,23 @@ public async Task ScrapingAsync(string url, string coverFilePath, var MidashiId = int.Parse(midashi.Id.Replace("midashi", "")); if ((MidashiId - previousMidashiId) == 100) { - document.Chapters.Add(new Chapter() { Title = TextReplace(TextProcess(midashi, document))}); + 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, document))); + document.Chapters[^1].Sections.Add(new Section(TextProcess(midashi))); sectionExist = true; } contentsIds.Add(MidashiId); previousMidashiId = MidashiId; } } - } else + } + else { - document.Chapters.Add(new Chapter() { Title = null}); + document.Chapters.Add(new Chapter() { Title = null }); document.Chapters[^1].Sections.Add(new Section(bookTitle.InnerHtml)); } @@ -105,12 +106,13 @@ public async Task ScrapingAsync(string url, string coverFilePath, document.Chapters[chapterNum].Sections[sectionNum].Elements.Add(new Paragraph()); } - } else if (element.TagName == "DIV") + } + else if (element.TagName == "DIV") { var midashi = element.QuerySelector(".midashi_anchor"); if (midashi != null) { - if(midashi.Id != null) + if (midashi.Id != null) { if (int.TryParse(midashi.Id.Replace("midashi", ""), out var midashiId)) { @@ -120,7 +122,7 @@ public async Task ScrapingAsync(string url, string coverFilePath, switch (contentsIds[contentsId] - contentsIds[contentsId - 1]) { case 100: - if (chapterNum > 0) + if (chapterNum >= 0 && sectionNum >= 0) { document.Chapters[chapterNum].Sections[sectionNum].Elements.RemoveAt(document.Chapters[chapterNum].Sections[sectionNum].Elements.Count - 1); } @@ -128,12 +130,12 @@ public async Task ScrapingAsync(string url, string coverFilePath, sectionNum = -1; break; case 10: - if ( chapterNum == -1) + if (chapterNum == -1) { chapterNum++; sectionNum = -1; } - if (sectionNum > 0) + if (chapterNum >= 0 && sectionNum >= 0) { document.Chapters[chapterNum].Sections[sectionNum].Elements.RemoveAt(document.Chapters[chapterNum].Sections[sectionNum].Elements.Count - 1); } @@ -142,7 +144,8 @@ public async Task ScrapingAsync(string url, string coverFilePath, default: break; } - } else //小見出し、行中小見出しの処理 + } + else //小見出し、行中小見出しの処理 { if (chapterNum == -1) { @@ -150,10 +153,6 @@ public async Task ScrapingAsync(string url, string coverFilePath, { document.Chapters.Insert(0, new Chapter()); } - if (chapterNum > 0) - { - document.Chapters[chapterNum].Sections[sectionNum].Elements.RemoveAt(document.Chapters[chapterNum].Sections[sectionNum].Elements.Count - 1); - } chapterNum++; sectionNum = -1; } @@ -169,19 +168,32 @@ public async Task ScrapingAsync(string url, string coverFilePath, checkParagraph(document, chapterNum, sectionNum); if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph)) { - paragraph.Text += TextReplace(TextProcess(midashi, document)); + 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 + } + else { throw new EpubDocumentException($"Unexpected id of Anchor tag was found: id = {midashi.Id}"); } - } else + } + else { throw new EpubDocumentException("Unecpected structure of HTML File: div tag with class=\"midashi_anchor\", but id=\"midashi___\" exist"); } - } else + } + else { if (element.ClassName == "caption") { @@ -189,7 +201,19 @@ public async Task ScrapingAsync(string url, string coverFilePath, checkParagraph(document, chapterNum, sectionNum); if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph)) { - paragraph.Text += TextReplace(TextProcess(element, document)); + 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 @@ -215,12 +239,20 @@ public async Task ScrapingAsync(string url, string coverFilePath, checkParagraph(document, chapterNum, sectionNum); if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph)) { - paragraph.Text += TextReplace(TextProcess(element, document)); + 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") + } + else if (element.TagName == "IMG") { if (element is IHtmlImageElement img) { @@ -281,7 +313,8 @@ public async Task ScrapingAsync(string url, string coverFilePath, } } } - } else if (element.TagName == "SPAN") + } + else if (element.TagName == "SPAN") { if (element.ClassName == "caption") { @@ -289,17 +322,19 @@ public async Task ScrapingAsync(string url, string coverFilePath, { if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^2] is Paragraph paragraph)) { - paragraph.Text = TextProcess(element, document) + "の画像"; + paragraph.Text = TextProcess(element) + "の画像"; } - } else + } + else { if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph)) { - paragraph.Text = TextProcess(element, document ) + "の画像"; + paragraph.Text = TextProcess(element) + "の画像"; } } - - } else if (element.ClassName == "notes") + + } + else if (element.ClassName == "notes") { switch (element.InnerHtml) { @@ -317,12 +352,19 @@ public async Task ScrapingAsync(string url, string coverFilePath, checkParagraph(document, chapterNum, sectionNum); if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph)) { - paragraph.Text += TextReplace(element.InnerHtml); - document.Chapters[chapterNum].Sections[sectionNum].Elements.Add(new 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 + } + else { if (chapterNum == -1) { @@ -345,11 +387,24 @@ public async Task ScrapingAsync(string url, string coverFilePath, checkParagraph(document, chapterNum, sectionNum); if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph)) { - paragraph.Text += TextReplace(TextProcess(element, document)); + 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 + } + else { if (chapterNum == -1) { @@ -372,7 +427,21 @@ public async Task ScrapingAsync(string url, string coverFilePath, checkParagraph(document, chapterNum, sectionNum); if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph)) { - paragraph.Text += TextReplace(TextProcess(element, document)); + 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]; + } } // 想定していない構造が見つかったことをログに出力した方が良い? } @@ -406,14 +475,28 @@ public async Task ScrapingAsync(string url, string coverFilePath, checkParagraph(document, chapterNum, sectionNum); if ((document.Chapters[chapterNum].Sections[sectionNum].Elements[^1] is Paragraph paragraph)) { - paragraph.Text += TextReplace(nextNode.Text()); + 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 + } + else { previous = false; } - } else + } + else { previous = false; } @@ -425,7 +508,8 @@ public async Task ScrapingAsync(string url, string coverFilePath, if (checkEpubDocument(document)) { Console.WriteLine("Success"); - } else + } + else { Console.WriteLine("False"); } @@ -447,7 +531,8 @@ private bool checkEpubDocument(EpubDocument document) Console.WriteLine($"{document.Chapters.IndexOf(chapter)}, {chapter.Sections.IndexOf(section)}, {section.Elements.IndexOf(element)}"); return false; } - } else if (element is Picture picture) + } + else if (element is Picture picture) { if (picture.PictureFilePath == null) { @@ -461,13 +546,14 @@ private bool checkEpubDocument(EpubDocument document) return true; } - private string TextProcess(IElement element, EpubDocument document) + private static string TextProcess(IElement element) { string text = ""; if (element.ChildElementCount == 0) { text += TextReplace(element.InnerHtml); - } else + } + else { var rubies = element.QuerySelectorAll("ruby"); if (rubies.Length > 0) @@ -488,15 +574,17 @@ private string TextProcess(IElement element, EpubDocument document) { if (item.QuerySelectorAll("img").Length > 0) { - if(item.QuerySelector("rt") != null) + if (item.QuerySelector("rt") != null) { text += TextReplace(item.QuerySelector("rt")!.TextContent); } - } else + } + else { text += TextReplace(item.OuterHtml); } - } else + } + else { if ((item.TextContent != "\n") && (!string.IsNullOrEmpty(item.TextContent))) { @@ -511,7 +599,8 @@ private string TextProcess(IElement element, EpubDocument document) } } } - } else if(element.TagName == "RUBY") + } + else if (element.TagName == "RUBY") { if (element.QuerySelectorAll("img").Length > 0) { @@ -524,7 +613,8 @@ private string TextProcess(IElement element, EpubDocument document) { text += TextReplace(element.OuterHtml); } - } else + } + else { text += TextReplace(element.TextContent); } @@ -543,7 +633,7 @@ private static string TextReplace(string text) returnText = returnText.Replace("\n", ""); return returnText; } - + private static string GetCardUrl(string url) { diff --git a/Epub/KoeBook.Epub/ScrapingHelper.cs b/Epub/KoeBook.Epub/ScrapingHelper.cs index 1d4d4aa..ec342f6 100644 --- a/Epub/KoeBook.Epub/ScrapingHelper.cs +++ b/Epub/KoeBook.Epub/ScrapingHelper.cs @@ -11,9 +11,7 @@ internal static void checkChapter(EpubDocument document) { document.Chapters.Add(new Chapter() { Title = null }); } - return; - } internal static void checkSection(EpubDocument document, int ChapterNum) @@ -26,11 +24,12 @@ internal static void checkSection(EpubDocument document, int ChapterNum) if (document.Chapters[ChapterNum].Title != null) { document.Chapters[ChapterNum].Sections.Add(new Section(document.Chapters[ChapterNum].Title!)); - } else + } + else { document.Chapters[ChapterNum].Sections.Add(new Section(document.Title)); } - + } return; } @@ -44,4 +43,39 @@ internal static void checkParagraph(EpubDocument document, int chapterNum, int s } 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/ScrapingNarou.cs b/Epub/KoeBook.Epub/ScrapingNarou.cs new file mode 100644 index 0000000..e138d40 --- /dev/null +++ b/Epub/KoeBook.Epub/ScrapingNarou.cs @@ -0,0 +1,64 @@ +using AngleSharp; +using AngleSharp.Dom; +using AngleSharp.Html.Dom; +using AngleSharp.Io; +using System.IO; +using System.Net.Http.Json; +using static KoeBook.Epub.ScrapingHelper; + +namespace KoeBook.Epub +{ + public partial class ScrapingNarouService + { + public ScrapingNarouService(IHttpClientFactory httpClientFactory) + { + _httpCliantFactory = httpClientFactory; + } + + private readonly IHttpClientFactory _httpCliantFactory; + + 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("novel_title"); + //if (bookTitle is null) + //{ + // throw new EpubDocumentException($"Failed to get title properly.\nYou may be able to get proper URL at {(url)}"); + //} + + // auther の取得 + var bookAuther = doc.QuerySelector(".novel_writername a"); + if (bookAuther is null) + { + throw new EpubDocumentException($"Failed to get auther properly.\nYou may be able to get proper URL at {url}"); + } + + + + var result = await _httpCliantFactory.CreateClient().GetAsync("https://api.syosetu.com/novelapi/api/?of=ga-nt&ncode=n9669bk&out=json", ct).ConfigureAwait(false); + var test = await result.Content.ReadAsStringAsync().ConfigureAwait(false); + if (result.IsSuccessStatusCode) + { + var content = await result.Content.ReadFromJsonAsync(ct).ConfigureAwait(false); + if (true) + { + + } + } + else + { + throw new EpubDocumentException("Url may be not Correct"); + } + + return 1; + + } + + public record BookInfo(string allallcount, string noveltype, string general_all_no); + + } +} From 52d0c0d0fb0a2dc73623d8b033d2fed36ac0fa71 Mon Sep 17 00:00:00 2001 From: TakenPt Date: Sat, 24 Feb 2024 21:42:02 +0900 Subject: [PATCH 3/6] wip --- Epub/KoeBook.Epub/ScrapingAozora.cs | 5 +++-- Epub/KoeBook.Epub/Service/IScrapingService.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Epub/KoeBook.Epub/ScrapingAozora.cs b/Epub/KoeBook.Epub/ScrapingAozora.cs index c8a4498..9ed6733 100644 --- a/Epub/KoeBook.Epub/ScrapingAozora.cs +++ b/Epub/KoeBook.Epub/ScrapingAozora.cs @@ -2,13 +2,14 @@ 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 + public partial class ScrapingAozora: IScrapingService { private int chapterNum; private int sectionNum; @@ -37,7 +38,7 @@ public async Task ScrapingAsync(string url, string coverFilePath, } // EpubDocument の生成 - var document = new EpubDocument(TextReplace(bookTitle.InnerHtml), TextReplace(bookAuther.InnerHtml), coverFilePath) + var document = new EpubDocument(TextReplace(bookTitle.InnerHtml), TextReplace(bookAuther.InnerHtml), coverFilePath, id) { // EpubDocument.Chapters の生成 Chapters = new List() 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); } From 1a7be9c888dfc5b8e3ce11b3dc4ff54a7dd36124 Mon Sep 17 00:00:00 2001 From: miyaji255 <84168445+miyaji255@users.noreply.github.com> Date: Sat, 24 Feb 2024 22:04:20 +0900 Subject: [PATCH 4/6] format --- Epub/KoeBook.Epub/ScrapingAozora.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Epub/KoeBook.Epub/ScrapingAozora.cs b/Epub/KoeBook.Epub/ScrapingAozora.cs index 9ed6733..a991c7e 100644 --- a/Epub/KoeBook.Epub/ScrapingAozora.cs +++ b/Epub/KoeBook.Epub/ScrapingAozora.cs @@ -9,7 +9,7 @@ namespace KoeBook.Epub { - public partial class ScrapingAozora: IScrapingService + public partial class ScrapingAozora : IScrapingService { private int chapterNum; private int sectionNum; From e5dbd1527b912f9579f1544faa26876ea9c58ab3 Mon Sep 17 00:00:00 2001 From: miyaji255 <84168445+miyaji255@users.noreply.github.com> Date: Sat, 24 Feb 2024 22:35:26 +0900 Subject: [PATCH 5/6] =?UTF-8?q?#17=20ScrapingNarou=E3=82=92=E5=89=8A?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Epub/KoeBook.Epub/ScrapingNarou.cs | 64 ------------------------------ 1 file changed, 64 deletions(-) delete mode 100644 Epub/KoeBook.Epub/ScrapingNarou.cs diff --git a/Epub/KoeBook.Epub/ScrapingNarou.cs b/Epub/KoeBook.Epub/ScrapingNarou.cs deleted file mode 100644 index e138d40..0000000 --- a/Epub/KoeBook.Epub/ScrapingNarou.cs +++ /dev/null @@ -1,64 +0,0 @@ -using AngleSharp; -using AngleSharp.Dom; -using AngleSharp.Html.Dom; -using AngleSharp.Io; -using System.IO; -using System.Net.Http.Json; -using static KoeBook.Epub.ScrapingHelper; - -namespace KoeBook.Epub -{ - public partial class ScrapingNarouService - { - public ScrapingNarouService(IHttpClientFactory httpClientFactory) - { - _httpCliantFactory = httpClientFactory; - } - - private readonly IHttpClientFactory _httpCliantFactory; - - 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("novel_title"); - //if (bookTitle is null) - //{ - // throw new EpubDocumentException($"Failed to get title properly.\nYou may be able to get proper URL at {(url)}"); - //} - - // auther の取得 - var bookAuther = doc.QuerySelector(".novel_writername a"); - if (bookAuther is null) - { - throw new EpubDocumentException($"Failed to get auther properly.\nYou may be able to get proper URL at {url}"); - } - - - - var result = await _httpCliantFactory.CreateClient().GetAsync("https://api.syosetu.com/novelapi/api/?of=ga-nt&ncode=n9669bk&out=json", ct).ConfigureAwait(false); - var test = await result.Content.ReadAsStringAsync().ConfigureAwait(false); - if (result.IsSuccessStatusCode) - { - var content = await result.Content.ReadFromJsonAsync(ct).ConfigureAwait(false); - if (true) - { - - } - } - else - { - throw new EpubDocumentException("Url may be not Correct"); - } - - return 1; - - } - - public record BookInfo(string allallcount, string noveltype, string general_all_no); - - } -} From e9652197ff3c78a795a98ee4fd8dc6fee8fc925c Mon Sep 17 00:00:00 2001 From: miyaji255 <84168445+miyaji255@users.noreply.github.com> Date: Sat, 24 Feb 2024 23:40:34 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=8F?= =?UTF-8?q?=E3=83=B3=E3=83=89=E3=83=AA=E3=83=B3=E3=82=B0=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KoeBook.Test/KoeBook.Test.csproj | 1 - .../KoeBook.Unsafe.csproj | 7 ++-- KoeBook.Unsafe/NativeMethods.json | 4 ++ KoeBook.Unsafe/NativeMethods.txt | 1 + KoeBook.sln | 38 +++++++++---------- KoeBook/App.xaml.cs | 28 ++++++++++++-- KoeBook/Components/Dialog/DialogService.cs | 11 ++++++ .../Dialog/SharedContentDialog.xaml.cs | 2 +- KoeBook/Contracts/Services/IDialogService.cs | 6 +++ KoeBook/KoeBook.csproj | 1 + .../Services/GenerationTaskRunnerService.cs | 25 +++++++++--- KoeBook/ViewModels/GenerationTaskViewModel.cs | 2 +- KoeBook/ViewModels/MainViewModel.cs | 2 +- 13 files changed, 93 insertions(+), 35 deletions(-) rename KoeBook.Common/KoeBook.Common.csproj => KoeBook.Unsafe/KoeBook.Unsafe.csproj (54%) create mode 100644 KoeBook.Unsafe/NativeMethods.json create mode 100644 KoeBook.Unsafe/NativeMethods.txt 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;