From ebf7118f89134e6b5853ea6d006cd996c599bf2e Mon Sep 17 00:00:00 2001 From: Arvydas Sidorenko Date: Sun, 6 May 2018 22:09:52 +0200 Subject: [PATCH] Implemented EpubChapter.Previous / Next. --- EpubSharp.Tests/EpubReaderTests.cs | 34 ++++++ EpubSharp.Tests/EpubTests.cs | 2 +- EpubSharp.Tests/EpubWriterTests.cs | 2 +- EpubSharp/EpubBook.cs | 5 +- EpubSharp/EpubReader.cs | 176 ++++++++++++++++------------- EpubSharp/EpubSharp.csproj | 6 +- EpubSharp/EpubWriter.cs | 8 +- EpubSharp/Misc/Href.cs | 6 +- 8 files changed, 147 insertions(+), 92 deletions(-) diff --git a/EpubSharp.Tests/EpubReaderTests.cs b/EpubSharp.Tests/EpubReaderTests.cs index 3412b59..455d7ff 100644 --- a/EpubSharp.Tests/EpubReaderTests.cs +++ b/EpubSharp.Tests/EpubReaderTests.cs @@ -303,6 +303,7 @@ public void ReadIOSHackersHandbookTest() { var book = EpubReader.Read(Cwd.Combine(@"Samples/epub-assorted/iOS Hackers Handbook.epub")); book.TableOfContents.Should().HaveCount(14); + book.TableOfContents.SelectMany(e => e.SubChapters).Concat(book.TableOfContents).Should().HaveCount(78); book.TableOfContents[0].AbsolutePath.Should().Be("/OEBPS/9781118240755cover.xhtml"); book.TableOfContents[1].AbsolutePath.Should().Be("/OEBPS/9781118240755c01.xhtml"); book.TableOfContents[1].SubChapters.Should().HaveCount(6); @@ -320,5 +321,38 @@ public void SetsChapterParents() chapter.SubChapters.All(e => e.Parent == chapter).Should().BeTrue(); } } + + [Fact] + public void SetsChapterPreviousNext() + { + var book = EpubReader.Read(Cwd.Combine(@"Samples/epub-assorted/iOS Hackers Handbook.epub")); + + EpubChapter previousChapter = null; + var currentChapter = book.TableOfContents[0]; + currentChapter.Previous.Should().Be(previousChapter); + + for (var i = 1; i <= 77; ++i) + { + previousChapter = currentChapter; + currentChapter = currentChapter.Next; + + previousChapter.Next.Should().Be(currentChapter); + currentChapter.Previous.Should().Be(previousChapter); + } + + EpubChapter nextChapter = null; + currentChapter.Next.Should().Be(nextChapter); + + for (var i = 1; i <= 77; ++i) + { + nextChapter = currentChapter; + currentChapter = currentChapter.Previous; + + currentChapter.Next.Should().Be(nextChapter); + nextChapter.Previous.Should().Be(currentChapter); + } + + currentChapter.Previous.Should().BeNull(); + } } } diff --git a/EpubSharp.Tests/EpubTests.cs b/EpubSharp.Tests/EpubTests.cs index 5b79723..e630660 100644 --- a/EpubSharp.Tests/EpubTests.cs +++ b/EpubSharp.Tests/EpubTests.cs @@ -155,7 +155,7 @@ private void AssertContentFile(EpubFile expected, EpubFile actual) private void AssertChapter(EpubChapter expected, EpubChapter actual) { - Assert.Equal(expected.FileName, actual.FileName); + Assert.Equal(expected.RelativePath, actual.RelativePath); Assert.Equal(expected.HashLocation, actual.HashLocation); Assert.Equal(expected.Title, actual.Title); diff --git a/EpubSharp.Tests/EpubWriterTests.cs b/EpubSharp.Tests/EpubWriterTests.cs index 45e2c78..a2285d5 100644 --- a/EpubSharp.Tests/EpubWriterTests.cs +++ b/EpubSharp.Tests/EpubWriterTests.cs @@ -145,7 +145,7 @@ public void CanAddChapterTest() for (var i = 0; i < chapters.Length; ++i) { Assert.Equal(chapters[i].Title, epub.TableOfContents[i].Title); - Assert.Equal(chapters[i].FileName, epub.TableOfContents[i].FileName); + Assert.Equal(chapters[i].RelativePath, epub.TableOfContents[i].RelativePath); Assert.Equal(chapters[i].HashLocation, epub.TableOfContents[i].HashLocation); Assert.Equal(0, chapters[i].SubChapters.Count); Assert.Equal(0, epub.TableOfContents[i].SubChapters.Count); diff --git a/EpubSharp/EpubBook.cs b/EpubSharp/EpubBook.cs index cd246ab..c555b96 100644 --- a/EpubSharp/EpubBook.cs +++ b/EpubSharp/EpubBook.cs @@ -48,10 +48,13 @@ public class EpubChapter { public string Id { get; set; } public string AbsolutePath { get; set; } - public string FileName { get; set; } + public string RelativePath { get; set; } public string HashLocation { get; set; } public string Title { get; set; } + public EpubChapter Parent { get; set; } + public EpubChapter Previous { get; set; } + public EpubChapter Next { get; set; } public IList SubChapters { get; set; } = new List(); public override string ToString() diff --git a/EpubSharp/EpubReader.cs b/EpubSharp/EpubReader.cs index 678c345..9e8ddc4 100644 --- a/EpubSharp/EpubReader.cs +++ b/EpubSharp/EpubReader.cs @@ -13,7 +13,7 @@ namespace EpubSharp public static class EpubReader { public static EpubBook Read(string filePath, Encoding encoding = null) - { + { if (filePath == null) throw new ArgumentNullException(nameof(filePath)); if (encoding == null) encoding = Constants.DefaultEncoding; @@ -35,7 +35,7 @@ public static EpubBook Read(Stream stream, bool leaveOpen, Encoding encoding = n { if (stream == null) throw new ArgumentNullException(nameof(stream)); if (encoding == null) encoding = Constants.DefaultEncoding; - + using (var archive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen, encoding)) { var format = new EpubFormat { Ocf = OcfReader.Read(archive.LoadXml(Constants.OcfPath)) }; @@ -47,23 +47,23 @@ public static EpubBook Read(Stream stream, bool leaveOpen, Encoding encoding = n { throw new EpubParseException("Epub OCF doesn't specify a root file."); } - + format.Opf = OpfReader.Read(archive.LoadXml(format.Paths.OpfAbsolutePath)); - + var navPath = format.Opf.FindNavPath(); if (navPath != null) { format.Paths.NavAbsolutePath = navPath.ToAbsolutePath(format.Paths.OpfAbsolutePath); format.Nav = NavReader.Read(archive.LoadHtml(format.Paths.NavAbsolutePath)); } - + var ncxPath = format.Opf.FindNcxPath(); if (ncxPath != null) { format.Paths.NcxAbsolutePath = ncxPath.ToAbsolutePath(format.Paths.OpfAbsolutePath); format.Ncx = NcxReader.Read(archive.LoadXml(format.Paths.NcxAbsolutePath)); } - + var book = new EpubBook { Format = format }; book.Resources = LoadResources(archive, book); book.SpecialResources = LoadSpecialResources(archive, book); @@ -72,7 +72,7 @@ public static EpubBook Read(Stream stream, bool leaveOpen, Encoding encoding = n return book; } } - + private static byte[] LoadCoverImage(EpubBook book) { if (book == null) throw new ArgumentNullException(nameof(book)); @@ -103,7 +103,7 @@ private static List LoadChapters(EpubBook book) { return LoadChaptersFromNcx(book.Format.Paths.NcxAbsolutePath, book.Format.Ncx.NavMap.NavPoints); } - + return new List(); } @@ -113,20 +113,23 @@ private static List LoadChaptersFromNav(string navAbsolutePath, XEl var ns = element.Name.Namespace; var result = new List(); + var previous = parentChapter; var ol = element.Element(ns + NavElements.Ol); if (ol == null) - { return result; - } foreach (var li in ol.Elements(ns + NavElements.Li)) { var chapter = new EpubChapter { - Parent = parentChapter + Parent = parentChapter, + Previous = previous }; + if (previous != null) + previous.Next = chapter; + var link = li.Element(ns + NavElements.A); if (link != null) { @@ -140,9 +143,9 @@ private static List LoadChaptersFromNav(string navAbsolutePath, XEl if (url != null) { var href = new Href(url); - chapter.FileName = href.Filename; + chapter.RelativePath = href.Path; chapter.HashLocation = href.HashLocation; - chapter.AbsolutePath = chapter.FileName.ToAbsolutePath(navAbsolutePath); + chapter.AbsolutePath = chapter.RelativePath.ToAbsolutePath(navAbsolutePath); } var titleTextElement = li.Descendants().FirstOrDefault(e => !string.IsNullOrWhiteSpace(e.Value)); @@ -156,6 +159,8 @@ private static List LoadChaptersFromNav(string navAbsolutePath, XEl chapter.SubChapters = LoadChaptersFromNav(navAbsolutePath, li, chapter); } result.Add(chapter); + + previous = chapter.SubChapters.Any() ? chapter.SubChapters.Last() : chapter; } } @@ -165,15 +170,28 @@ private static List LoadChaptersFromNav(string navAbsolutePath, XEl private static List LoadChaptersFromNcx(string ncxAbsolutePath, IEnumerable navigationPoints, EpubChapter parentChapter = null) { var result = new List(); + var previous = parentChapter; + foreach (var navigationPoint in navigationPoints) { - var chapter = new EpubChapter { Title = navigationPoint.NavLabelText, Parent = parentChapter }; + var chapter = new EpubChapter + { + Title = navigationPoint.NavLabelText, + Parent = parentChapter, + Previous = previous + }; + + if (previous != null) + previous.Next = chapter; + var href = new Href(navigationPoint.ContentSrc); - chapter.FileName = href.Filename; - chapter.AbsolutePath = href.Filename.ToAbsolutePath(ncxAbsolutePath); + chapter.RelativePath = href.Path; + chapter.AbsolutePath = href.Path.ToAbsolutePath(ncxAbsolutePath); chapter.HashLocation = href.HashLocation; chapter.SubChapters = LoadChaptersFromNcx(ncxAbsolutePath, navigationPoint.NavPoints, chapter); result.Add(chapter); + + previous = chapter.SubChapters.Any() ? chapter.SubChapters.Last() : chapter; } return result; } @@ -213,80 +231,80 @@ private static EpubResources LoadResources(ZipArchive epubArchive, EpubBook book case EpubContentType.Xml: case EpubContentType.Dtbook: case EpubContentType.DtbookNcx: - { - var file = new EpubTextFile { - AbsolutePath = path, - Href = href, - MimeType = mimeType, - ContentType = contentType - }; - - resources.All.Add(file); - - using (var stream = entry.Open()) - { - file.Content = stream.ReadToEnd(); - } + var file = new EpubTextFile + { + AbsolutePath = path, + Href = href, + MimeType = mimeType, + ContentType = contentType + }; - switch (contentType) - { - case EpubContentType.Xhtml11: - resources.Html.Add(file); - break; - case EpubContentType.Css: - resources.Css.Add(file); - break; - default: - resources.Other.Add(file); - break; + resources.All.Add(file); + + using (var stream = entry.Open()) + { + file.Content = stream.ReadToEnd(); } - break; - } + + switch (contentType) + { + case EpubContentType.Xhtml11: + resources.Html.Add(file); + break; + case EpubContentType.Css: + resources.Css.Add(file); + break; + default: + resources.Other.Add(file); + break; + } + break; + } default: - { - var file = new EpubByteFile - { - AbsolutePath = path, - Href = href, - MimeType = mimeType, - ContentType = contentType - }; - - resources.All.Add(file); - - using (var stream = entry.Open()) { - if (stream == null) + var file = new EpubByteFile { - throw new EpubException($"Incorrect EPUB file: content file \"{href}\" specified in manifest is not found"); - } + AbsolutePath = path, + Href = href, + MimeType = mimeType, + ContentType = contentType + }; + + resources.All.Add(file); - using (var memoryStream = new MemoryStream((int) entry.Length)) + using (var stream = entry.Open()) { - stream.CopyTo(memoryStream); - file.Content = memoryStream.ToArray(); + if (stream == null) + { + throw new EpubException($"Incorrect EPUB file: content file \"{href}\" specified in manifest is not found"); + } + + using (var memoryStream = new MemoryStream((int)entry.Length)) + { + stream.CopyTo(memoryStream); + file.Content = memoryStream.ToArray(); + } } - } - switch (contentType) - { - case EpubContentType.ImageGif: - case EpubContentType.ImageJpeg: - case EpubContentType.ImagePng: - case EpubContentType.ImageSvg: - resources.Images.Add(file); - break; - case EpubContentType.FontTruetype: - case EpubContentType.FontOpentype: - resources.Fonts.Add(file); - break; - default: - resources.Other.Add(file); - break; + switch (contentType) + { + case EpubContentType.ImageGif: + case EpubContentType.ImageJpeg: + case EpubContentType.ImagePng: + case EpubContentType.ImageSvg: + resources.Images.Add(file); + break; + case EpubContentType.FontTruetype: + case EpubContentType.FontOpentype: + resources.Fonts.Add(file); + break; + default: + resources.Other.Add(file); + break; + } + break; } - break; - } } } diff --git a/EpubSharp/EpubSharp.csproj b/EpubSharp/EpubSharp.csproj index 089d7d2..fa53eae 100644 --- a/EpubSharp/EpubSharp.csproj +++ b/EpubSharp/EpubSharp.csproj @@ -6,13 +6,13 @@ Arvydas Sidorenko epub epubs book books 2.0 3.0 3.1 EpubSharp.dll - 1.1.5.11 + 1.1.5.13 A library for reading and writing EPUB files. https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/EPUB_silk_icon.svg/2000px-EPUB_silk_icon.svg.png https://github.com/Asido/EpubSharp/ Copyright 2018 - 1.1.5.11 - 1.1.5.11 + 1.1.5.13 + 1.1.5.13 diff --git a/EpubSharp/EpubWriter.cs b/EpubSharp/EpubWriter.cs index 5e83c6d..c4ddf80 100644 --- a/EpubSharp/EpubWriter.cs +++ b/EpubSharp/EpubWriter.cs @@ -233,7 +233,7 @@ public EpubChapter AddChapter(string title, string html, string fileId = null) { Id = fileId, Title = title, - FileName = file.AbsolutePath + RelativePath = file.AbsolutePath }; } @@ -245,10 +245,10 @@ public void ClearChapters() foreach (var item in spineItems) { var href = new Href(item.Href); - if (otherItems.All(e => new Href(e.Href).Filename != href.Filename)) + if (otherItems.All(e => new Href(e.Href).Path != href.Path)) { // The HTML file is not referenced by anything outside spine item, thus can be removed from the archive. - var file = resources.Html.Single(e => e.Href == href.Filename); + var file = resources.Html.Single(e => e.Href == href.Path); resources.Html.Remove(file); } @@ -267,7 +267,7 @@ public void ClearChapters() { format.Opf.Manifest.Items.Remove(item); - var image = resources.Images.Single(e => e.Href == new Href(item.Href).Filename); + var image = resources.Images.Single(e => e.Href == new Href(item.Href).Path); resources.Images.Remove(image); } } diff --git a/EpubSharp/Misc/Href.cs b/EpubSharp/Misc/Href.cs index 938ed8c..a5f2d43 100644 --- a/EpubSharp/Misc/Href.cs +++ b/EpubSharp/Misc/Href.cs @@ -4,7 +4,7 @@ namespace EpubSharp { internal class Href { - public readonly string Filename; + public readonly string Path; public readonly string HashLocation; public Href(string href) @@ -14,11 +14,11 @@ public Href(string href) var contentSourceAnchorCharIndex = href.IndexOf('#'); if (contentSourceAnchorCharIndex == -1) { - Filename = href; + Path = href; } else { - Filename = href.Substring(0, contentSourceAnchorCharIndex); + Path = href.Substring(0, contentSourceAnchorCharIndex); HashLocation = href.Substring(contentSourceAnchorCharIndex + 1); } }