diff --git a/Source/VersOne.Epub.Test/Unit/Options/BookCoverReaderOptionsTests.cs b/Source/VersOne.Epub.Test/Unit/Options/BookCoverReaderOptionsTests.cs new file mode 100644 index 0000000..a6b1aef --- /dev/null +++ b/Source/VersOne.Epub.Test/Unit/Options/BookCoverReaderOptionsTests.cs @@ -0,0 +1,47 @@ +using VersOne.Epub.Options; + +namespace VersOne.Epub.Test.Unit.Options +{ + public class BookCoverReaderOptionsTests + { + [Fact(DisplayName = "Constructing a BookCoverReaderOptions instance with a non-null preset parameter should succeed")] + public void ConstructorWithNonNullPresetTest() + { + _ = new BookCoverReaderOptions(EpubReaderOptionsPreset.RELAXED); + } + + [Fact(DisplayName = "Constructing a BookCoverReaderOptions instance with a null preset parameter should succeed")] + public void ConstructorWithNullPresetTest() + { + _ = new BookCoverReaderOptions(null); + } + + [Fact(DisplayName = "Constructing a BookCoverReaderOptions instance with a null preset parameter should initialize properties with the expected values.")] + public void InitializationWithNullPresetTest() + { + BookCoverReaderOptions bookCoverReaderOptions = new(null); + Assert.False(bookCoverReaderOptions.Epub2MetadataIgnoreMissingManifestItem); + } + + [Fact(DisplayName = "Constructing a BookCoverReaderOptions instance with the STRICT preset parameter should initialize properties with the expected values.")] + public void InitializationWithStrictPresetTest() + { + BookCoverReaderOptions bookCoverReaderOptions = new(EpubReaderOptionsPreset.STRICT); + Assert.False(bookCoverReaderOptions.Epub2MetadataIgnoreMissingManifestItem); + } + + [Fact(DisplayName = "Constructing a BookCoverReaderOptions instance with the RELAXED preset parameter should initialize properties with the expected values.")] + public void InitializationWithRelaxedPresetTest() + { + BookCoverReaderOptions bookCoverReaderOptions = new(EpubReaderOptionsPreset.RELAXED); + Assert.True(bookCoverReaderOptions.Epub2MetadataIgnoreMissingManifestItem); + } + + [Fact(DisplayName = "Constructing a BookCoverReaderOptions instance with the IGNORE_ALL_ERRORS preset parameter should initialize properties with the expected values.")] + public void InitializationWithIgnoreAllErrorsPresetTest() + { + BookCoverReaderOptions bookCoverReaderOptions = new(EpubReaderOptionsPreset.IGNORE_ALL_ERRORS); + Assert.True(bookCoverReaderOptions.Epub2MetadataIgnoreMissingManifestItem); + } + } +} diff --git a/Source/VersOne.Epub.Test/Unit/Readers/BookCoverReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/BookCoverReaderTests.cs index 46cc497..39fbc88 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/BookCoverReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/BookCoverReaderTests.cs @@ -1,4 +1,5 @@ using VersOne.Epub.Internal; +using VersOne.Epub.Options; using VersOne.Epub.Schema; using VersOne.Epub.Test.Unit.Mocks; @@ -52,15 +53,15 @@ public void ReadBookCoverForEpub2WithCoverInGuideTest() epubVersion: EpubVersion.EPUB_2, guide: new EpubGuide ( - items: new List() - { - new EpubGuideReference + items: + [ + new ( type: "cover", title: null, href: LOCAL_COVER_FILE_NAME ) - } + ] ) ); EpubLocalByteContentFileRef expectedCoverImageFileRef = CreateLocalTestImageFileRef(); @@ -75,15 +76,15 @@ public void ReadBookCoverForEpub2WithCoverInGuideReferencingNonExistingImageTest epubVersion: EpubVersion.EPUB_2, guide: new EpubGuide ( - items: new List() - { - new EpubGuideReference + items: + [ + new ( type: "cover", title: null, href: LOCAL_COVER_FILE_NAME ) - } + ] ) ); TestSuccessfulReadOperation(epubSchema, null); @@ -131,8 +132,8 @@ public void ReadBookCoverForEpub2WithEmptyCoverMetaItemContentTest() TestFailingReadOperation(epubSchema); } - [Fact(DisplayName = "ReadBookCover should throw EpubPackageException if the manifest item with the ID specified in the cover meta item is missing")] - public void ReadBookCoverForEpub2WithMissingManifestItemTest() + [Fact(DisplayName = "ReadBookCover should throw EpubPackageException if the manifest item with the ID specified in the cover meta item is missing and BookCoverReaderOptions.Epub2MetadataIgnoreMissingManifestItem is false")] + public void ReadBookCoverForEpub2WithMissingManifestItemWithoutIgnoringErrorOptionTest() { EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_2); epubSchema.Package.Metadata.MetaItems.Add(new EpubMetadataMeta @@ -143,6 +144,22 @@ public void ReadBookCoverForEpub2WithMissingManifestItemTest() TestFailingReadOperation(epubSchema); } + [Fact(DisplayName = "ReadBookCover should return null if the manifest item with the ID specified in the cover meta item is missing and BookCoverReaderOptions.Epub2MetadataIgnoreMissingManifestItem is false")] + public void ReadBookCoverForEpub2WithMissingManifestItemWithIgnoringErrorOptionTest() + { + EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_2); + epubSchema.Package.Metadata.MetaItems.Add(new EpubMetadataMeta + ( + name: "cover", + content: "cover-image" + )); + BookCoverReaderOptions bookCoverReaderOptions = new() + { + Epub2MetadataIgnoreMissingManifestItem = true + }; + TestSuccessfulReadOperation(epubSchema, null, bookCoverReaderOptions); + } + [Fact(DisplayName = "ReadBookCover should throw EpubPackageException if the image referenced by the cover manifest item is missing in the EPUB 2 file")] public void ReadBookCoverForEpub2WithMissingManifestItemImageTest() { @@ -169,15 +186,15 @@ public void ReadBookCoverForEpub2WithNoCoverInMetadataAndGuideTest() epubVersion: EpubVersion.EPUB_2, guide: new EpubGuide ( - items: new List() - { - new EpubGuideReference + items: + [ + new ( type: "test-type", title: null, href: "test.jpg" ) - } + ] ) ); TestSuccessfulReadOperation(epubSchema, null); @@ -191,15 +208,15 @@ public void ReadBookCoverForEpub3WithNoCoverInManifestTest() epubVersion: EpubVersion.EPUB_3, manifest: new EpubManifest ( - items: new List() - { - new EpubManifestItem + items: + [ + new ( id: "test-image", href: "test.jpg", mediaType: COVER_FILE_CONTENT_MIME_TYPE ), - new EpubManifestItem + new ( id: "test-item-with-property", href: "toc.html", @@ -209,7 +226,7 @@ public void ReadBookCoverForEpub3WithNoCoverInManifestTest() EpubManifestProperty.NAV } ) - } + ] ) ); TestSuccessfulReadOperation(epubSchema, null); @@ -224,10 +241,10 @@ public void ReadBookCoverForEpub3WithMissingManifestItemImageTest() id: "cover-image", href: LOCAL_COVER_FILE_NAME, mediaType: COVER_FILE_CONTENT_MIME_TYPE, - properties: new List() - { + properties: + [ EpubManifestProperty.COVER_IMAGE - } + ] )); TestFailingReadOperation(epubSchema); } @@ -260,15 +277,14 @@ public void ReadBookCoverForEpub2WithRemoteGuideItemImageTest() epubVersion: EpubVersion.EPUB_2, guide: new EpubGuide ( - items: new List() - { - new EpubGuideReference - ( + items: + [ + new ( type: "cover", title: null, href: REMOTE_COVER_FILE_HREF ) - } + ] ) ); EpubRemoteByteContentFileRef remoteTestImageFileRef = CreateRemoteTestImageFileRef(); @@ -285,30 +301,35 @@ public void ReadBookCoverForEpub3WithRemoteManifestItemImageTest() id: "cover-image", href: REMOTE_COVER_FILE_HREF, mediaType: COVER_FILE_CONTENT_MIME_TYPE, - properties: new List() - { + properties: + [ EpubManifestProperty.COVER_IMAGE - } + ] )); EpubRemoteByteContentFileRef remoteTestImageFileRef = CreateRemoteTestImageFileRef(); EpubContentCollectionRef imageContentRefs = CreateImageContentRefs(remoteImageFileRef: remoteTestImageFileRef); TestFailingReadOperation(epubSchema, imageContentRefs); } - private static void TestSuccessfulReadOperation(EpubSchema epubSchema, EpubLocalByteContentFileRef? expectedLocalCoverImageFileRef) + private static void TestSuccessfulReadOperation(EpubSchema epubSchema, EpubLocalByteContentFileRef? expectedLocalCoverImageFileRef, + BookCoverReaderOptions? bookCoverReaderOptions = null) { EpubContentCollectionRef imageContentRefs = expectedLocalCoverImageFileRef != null ? CreateImageContentRefs(localImageFileRef: expectedLocalCoverImageFileRef) : new EpubContentCollectionRef(); - EpubLocalByteContentFileRef? actualCoverImageFileRef = BookCoverReader.ReadBookCover(epubSchema, imageContentRefs); + EpubLocalByteContentFileRef? actualCoverImageFileRef = + BookCoverReader.ReadBookCover(epubSchema, imageContentRefs, bookCoverReaderOptions ?? new BookCoverReaderOptions()); Assert.Equal(expectedLocalCoverImageFileRef, actualCoverImageFileRef); } - private static void TestFailingReadOperation(EpubSchema epubSchema, EpubContentCollectionRef? imageContentRefs = null) + private static void TestFailingReadOperation( + EpubSchema epubSchema, EpubContentCollectionRef? imageContentRefs = null, + BookCoverReaderOptions? bookCoverReaderOptions = null) { imageContentRefs ??= new EpubContentCollectionRef(); - Assert.Throws(() => BookCoverReader.ReadBookCover(epubSchema, imageContentRefs)); + Assert.Throws(() => + BookCoverReader.ReadBookCover(epubSchema, imageContentRefs, bookCoverReaderOptions ?? new BookCoverReaderOptions())); } private static EpubSchema CreateEmptyEpubSchema(EpubVersion epubVersion, EpubManifest? manifest = null, EpubGuide? guide = null) diff --git a/Source/VersOne.Epub/Options/BookCoverReaderOptions.cs b/Source/VersOne.Epub/Options/BookCoverReaderOptions.cs new file mode 100644 index 0000000..7bb33c9 --- /dev/null +++ b/Source/VersOne.Epub/Options/BookCoverReaderOptions.cs @@ -0,0 +1,32 @@ +namespace VersOne.Epub.Options +{ + /// + /// Various options to configure the behavior of the EPUB book cover reader which is used for loading the EPUB book cover image. + /// + public class BookCoverReaderOptions + { + /// + /// Initializes a new instance of the class. + /// + /// An optional preset to initialize the class with a predefined set of options. + public BookCoverReaderOptions(EpubReaderOptionsPreset? preset = null) + { + switch (preset) + { + case EpubReaderOptionsPreset.RELAXED: + case EpubReaderOptionsPreset.IGNORE_ALL_ERRORS: + Epub2MetadataIgnoreMissingManifestItem = true; + break; + } + } + + /// + /// Gets or sets a value indicating whether EPUB 2 book cover reader should ignore the error when the manifest item referenced by + /// the EPUB 2 cover metadata item is missing. + /// If it's set to false and the manifest item with the given ID is not present, then + /// the "Incorrect EPUB manifest: item with ID = "..." referenced in EPUB 2 cover metadata is missing" exception will be thrown. + /// Default value is false. + /// + public bool Epub2MetadataIgnoreMissingManifestItem { get; set; } + } +} diff --git a/Source/VersOne.Epub/Options/EpubReaderOptions.cs b/Source/VersOne.Epub/Options/EpubReaderOptions.cs index 2f87bb4..1db3f85 100644 --- a/Source/VersOne.Epub/Options/EpubReaderOptions.cs +++ b/Source/VersOne.Epub/Options/EpubReaderOptions.cs @@ -11,6 +11,7 @@ public class EpubReaderOptions /// An optional preset to initialize the class with a predefined set of options. public EpubReaderOptions(EpubReaderOptionsPreset? preset = null) { + BookCoverReaderOptions = new BookCoverReaderOptions(preset); PackageReaderOptions = new PackageReaderOptions(preset); ContentReaderOptions = new ContentReaderOptions(preset); ContentDownloaderOptions = new ContentDownloaderOptions(preset); @@ -18,6 +19,11 @@ public EpubReaderOptions(EpubReaderOptionsPreset? preset = null) XmlReaderOptions = new XmlReaderOptions(preset); } + /// + /// Gets or sets EPUB content reader options which is used for loading the EPUB book cover image. + /// + public BookCoverReaderOptions BookCoverReaderOptions { get; set; } + /// /// Gets or sets EPUB OPF package reader options. /// diff --git a/Source/VersOne.Epub/Readers/BookCoverReader.cs b/Source/VersOne.Epub/Readers/BookCoverReader.cs index 06ffe1f..975725a 100644 --- a/Source/VersOne.Epub/Readers/BookCoverReader.cs +++ b/Source/VersOne.Epub/Readers/BookCoverReader.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using VersOne.Epub.Options; using VersOne.Epub.Schema; using VersOne.Epub.Utils; @@ -9,31 +10,34 @@ namespace VersOne.Epub.Internal internal static class BookCoverReader { public static EpubLocalByteContentFileRef? ReadBookCover( - EpubSchema epubSchema, EpubContentCollectionRef imageContentRefs) + EpubSchema epubSchema, EpubContentCollectionRef imageContentRefs, + BookCoverReaderOptions bookCoverReaderOptions) { EpubLocalByteContentFileRef? result; if (epubSchema.Package.EpubVersion == EpubVersion.EPUB_3 || epubSchema.Package.EpubVersion == EpubVersion.EPUB_3_1) { result = ReadEpub3Cover(epubSchema, imageContentRefs); - result ??= ReadEpub2Cover(epubSchema, imageContentRefs); + result ??= ReadEpub2Cover(epubSchema, imageContentRefs, bookCoverReaderOptions); } else { - result = ReadEpub2Cover(epubSchema, imageContentRefs); + result = ReadEpub2Cover(epubSchema, imageContentRefs, bookCoverReaderOptions); } return result; } private static EpubLocalByteContentFileRef? ReadEpub2Cover( - EpubSchema epubSchema, EpubContentCollectionRef imageContentRefs) + EpubSchema epubSchema, EpubContentCollectionRef imageContentRefs, + BookCoverReaderOptions bookCoverReaderOptions) { - EpubLocalByteContentFileRef? result = ReadEpub2CoverFromMetadata(epubSchema, imageContentRefs); + EpubLocalByteContentFileRef? result = ReadEpub2CoverFromMetadata(epubSchema, imageContentRefs, bookCoverReaderOptions); result ??= ReadEpub2CoverFromGuide(epubSchema, imageContentRefs); return result; } private static EpubLocalByteContentFileRef? ReadEpub2CoverFromMetadata( - EpubSchema epubSchema, EpubContentCollectionRef imageContentRefs) + EpubSchema epubSchema, EpubContentCollectionRef imageContentRefs, + BookCoverReaderOptions bookCoverReaderOptions) { List metaItems = epubSchema.Package.Metadata.MetaItems; if (!metaItems.Any()) @@ -49,9 +53,17 @@ internal static class BookCoverReader { throw new EpubPackageException("Incorrect EPUB metadata: cover item content is missing."); } - EpubManifestItem coverManifestItem = - epubSchema.Package.Manifest.Items.Find(manifestItem => manifestItem.Id.CompareOrdinalIgnoreCase(coverMetaItem.Content)) ?? - throw new EpubPackageException($"Incorrect EPUB manifest: item with ID = \"{coverMetaItem.Content}\" is missing."); + EpubManifestItem? coverManifestItem = + epubSchema.Package.Manifest.Items.Find(manifestItem => manifestItem.Id.CompareOrdinalIgnoreCase(coverMetaItem.Content)); + if (coverManifestItem == null) + { + if (bookCoverReaderOptions.Epub2MetadataIgnoreMissingManifestItem) + { + return null; + } + throw new EpubPackageException($"Incorrect EPUB manifest: item with ID = \"{coverMetaItem.Content}\"" + + " referenced in EPUB 2 cover metadata is missing."); + } EpubLocalByteContentFileRef result = GetCoverImageContentRef(imageContentRefs, coverManifestItem.Href) ?? throw new EpubPackageException($"Incorrect EPUB manifest: item with href = \"{coverManifestItem.Href}\" is missing."); return result; diff --git a/Source/VersOne.Epub/Readers/ContentReader.cs b/Source/VersOne.Epub/Readers/ContentReader.cs index b76dd66..8abd9d3 100644 --- a/Source/VersOne.Epub/Readers/ContentReader.cs +++ b/Source/VersOne.Epub/Readers/ContentReader.cs @@ -161,7 +161,7 @@ public EpubContentRef ParseContentMap(EpubSchema epubSchema, IZipFile epubFile) EpubContentCollectionRef fonts = new(fontsLocal.AsReadOnly(), fontsRemote.AsReadOnly()); EpubContentCollectionRef audio = new(audioLocal.AsReadOnly(), audioRemote.AsReadOnly()); EpubContentCollectionRef allFiles = new(allFilesLocal.AsReadOnly(), allFilesRemote.AsReadOnly()); - cover = BookCoverReader.ReadBookCover(epubSchema, images); + cover = BookCoverReader.ReadBookCover(epubSchema, images, epubReaderOptions.BookCoverReaderOptions); return new(cover, navigationHtmlFile, html, css, images, fonts, audio, allFiles); }