Skip to content

Commit

Permalink
Add BookCoverReaderOptions.Epub2MetadataIgnoreMissingManifestItem (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
vers-one authored Dec 25, 2024
1 parent 54aca8c commit 6162eee
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 44 deletions.
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
89 changes: 55 additions & 34 deletions Source/VersOne.Epub.Test/Unit/Readers/BookCoverReaderTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using VersOne.Epub.Internal;
using VersOne.Epub.Options;
using VersOne.Epub.Schema;
using VersOne.Epub.Test.Unit.Mocks;

Expand Down Expand Up @@ -52,15 +53,15 @@ public void ReadBookCoverForEpub2WithCoverInGuideTest()
epubVersion: EpubVersion.EPUB_2,
guide: new EpubGuide
(
items: new List<EpubGuideReference>()
{
new EpubGuideReference
items:
[
new
(
type: "cover",
title: null,
href: LOCAL_COVER_FILE_NAME
)
}
]
)
);
EpubLocalByteContentFileRef expectedCoverImageFileRef = CreateLocalTestImageFileRef();
Expand All @@ -75,15 +76,15 @@ public void ReadBookCoverForEpub2WithCoverInGuideReferencingNonExistingImageTest
epubVersion: EpubVersion.EPUB_2,
guide: new EpubGuide
(
items: new List<EpubGuideReference>()
{
new EpubGuideReference
items:
[
new
(
type: "cover",
title: null,
href: LOCAL_COVER_FILE_NAME
)
}
]
)
);
TestSuccessfulReadOperation(epubSchema, null);
Expand Down Expand Up @@ -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
Expand All @@ -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()
{
Expand All @@ -169,15 +186,15 @@ public void ReadBookCoverForEpub2WithNoCoverInMetadataAndGuideTest()
epubVersion: EpubVersion.EPUB_2,
guide: new EpubGuide
(
items: new List<EpubGuideReference>()
{
new EpubGuideReference
items:
[
new
(
type: "test-type",
title: null,
href: "test.jpg"
)
}
]
)
);
TestSuccessfulReadOperation(epubSchema, null);
Expand All @@ -191,15 +208,15 @@ public void ReadBookCoverForEpub3WithNoCoverInManifestTest()
epubVersion: EpubVersion.EPUB_3,
manifest: new EpubManifest
(
items: new List<EpubManifestItem>()
{
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",
Expand All @@ -209,7 +226,7 @@ public void ReadBookCoverForEpub3WithNoCoverInManifestTest()
EpubManifestProperty.NAV
}
)
}
]
)
);
TestSuccessfulReadOperation(epubSchema, null);
Expand All @@ -224,10 +241,10 @@ public void ReadBookCoverForEpub3WithMissingManifestItemImageTest()
id: "cover-image",
href: LOCAL_COVER_FILE_NAME,
mediaType: COVER_FILE_CONTENT_MIME_TYPE,
properties: new List<EpubManifestProperty>()
{
properties:
[
EpubManifestProperty.COVER_IMAGE
}
]
));
TestFailingReadOperation(epubSchema);
}
Expand Down Expand Up @@ -260,15 +277,14 @@ public void ReadBookCoverForEpub2WithRemoteGuideItemImageTest()
epubVersion: EpubVersion.EPUB_2,
guide: new EpubGuide
(
items: new List<EpubGuideReference>()
{
new EpubGuideReference
(
items:
[
new (
type: "cover",
title: null,
href: REMOTE_COVER_FILE_HREF
)
}
]
)
);
EpubRemoteByteContentFileRef remoteTestImageFileRef = CreateRemoteTestImageFileRef();
Expand All @@ -285,30 +301,35 @@ public void ReadBookCoverForEpub3WithRemoteManifestItemImageTest()
id: "cover-image",
href: REMOTE_COVER_FILE_HREF,
mediaType: COVER_FILE_CONTENT_MIME_TYPE,
properties: new List<EpubManifestProperty>()
{
properties:
[
EpubManifestProperty.COVER_IMAGE
}
]
));
EpubRemoteByteContentFileRef remoteTestImageFileRef = CreateRemoteTestImageFileRef();
EpubContentCollectionRef<EpubLocalByteContentFileRef, EpubRemoteByteContentFileRef> 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<EpubLocalByteContentFileRef, EpubRemoteByteContentFileRef> imageContentRefs =
expectedLocalCoverImageFileRef != null
? CreateImageContentRefs(localImageFileRef: expectedLocalCoverImageFileRef)
: new EpubContentCollectionRef<EpubLocalByteContentFileRef, EpubRemoteByteContentFileRef>();
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<EpubLocalByteContentFileRef, EpubRemoteByteContentFileRef>? imageContentRefs = null)
private static void TestFailingReadOperation(
EpubSchema epubSchema, EpubContentCollectionRef<EpubLocalByteContentFileRef, EpubRemoteByteContentFileRef>? imageContentRefs = null,
BookCoverReaderOptions? bookCoverReaderOptions = null)
{
imageContentRefs ??= new EpubContentCollectionRef<EpubLocalByteContentFileRef, EpubRemoteByteContentFileRef>();
Assert.Throws<EpubPackageException>(() => BookCoverReader.ReadBookCover(epubSchema, imageContentRefs));
Assert.Throws<EpubPackageException>(() =>
BookCoverReader.ReadBookCover(epubSchema, imageContentRefs, bookCoverReaderOptions ?? new BookCoverReaderOptions()));
}

private static EpubSchema CreateEmptyEpubSchema(EpubVersion epubVersion, EpubManifest? manifest = null, EpubGuide? guide = null)
Expand Down
32 changes: 32 additions & 0 deletions Source/VersOne.Epub/Options/BookCoverReaderOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace VersOne.Epub.Options
{
/// <summary>
/// Various options to configure the behavior of the EPUB book cover reader which is used for loading the EPUB book cover image.
/// </summary>
public class BookCoverReaderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="BookCoverReaderOptions"/> class.
/// </summary>
/// <param name="preset">An optional preset to initialize the <see cref="BookCoverReaderOptions" /> class with a predefined set of options.</param>
public BookCoverReaderOptions(EpubReaderOptionsPreset? preset = null)
{
switch (preset)
{
case EpubReaderOptionsPreset.RELAXED:
case EpubReaderOptionsPreset.IGNORE_ALL_ERRORS:
Epub2MetadataIgnoreMissingManifestItem = true;
break;
}
}

/// <summary>
/// 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 <c>false</c> 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 <c>false</c>.
/// </summary>
public bool Epub2MetadataIgnoreMissingManifestItem { get; set; }
}
}
6 changes: 6 additions & 0 deletions Source/VersOne.Epub/Options/EpubReaderOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@ public class EpubReaderOptions
/// <param name="preset">An optional preset to initialize the <see cref="EpubReaderOptions" /> class with a predefined set of options.</param>
public EpubReaderOptions(EpubReaderOptionsPreset? preset = null)
{
BookCoverReaderOptions = new BookCoverReaderOptions(preset);
PackageReaderOptions = new PackageReaderOptions(preset);
ContentReaderOptions = new ContentReaderOptions(preset);
ContentDownloaderOptions = new ContentDownloaderOptions(preset);
Epub2NcxReaderOptions = new Epub2NcxReaderOptions(preset);
XmlReaderOptions = new XmlReaderOptions(preset);
}

/// <summary>
/// Gets or sets EPUB content reader options which is used for loading the EPUB book cover image.
/// </summary>
public BookCoverReaderOptions BookCoverReaderOptions { get; set; }

/// <summary>
/// Gets or sets EPUB OPF package reader options.
/// </summary>
Expand Down
30 changes: 21 additions & 9 deletions Source/VersOne.Epub/Readers/BookCoverReader.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -9,31 +10,34 @@ namespace VersOne.Epub.Internal
internal static class BookCoverReader
{
public static EpubLocalByteContentFileRef? ReadBookCover(
EpubSchema epubSchema, EpubContentCollectionRef<EpubLocalByteContentFileRef, EpubRemoteByteContentFileRef> imageContentRefs)
EpubSchema epubSchema, EpubContentCollectionRef<EpubLocalByteContentFileRef, EpubRemoteByteContentFileRef> 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<EpubLocalByteContentFileRef, EpubRemoteByteContentFileRef> imageContentRefs)
EpubSchema epubSchema, EpubContentCollectionRef<EpubLocalByteContentFileRef, EpubRemoteByteContentFileRef> 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<EpubLocalByteContentFileRef, EpubRemoteByteContentFileRef> imageContentRefs)
EpubSchema epubSchema, EpubContentCollectionRef<EpubLocalByteContentFileRef, EpubRemoteByteContentFileRef> imageContentRefs,
BookCoverReaderOptions bookCoverReaderOptions)
{
List<EpubMetadataMeta> metaItems = epubSchema.Package.Metadata.MetaItems;
if (!metaItems.Any())
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion Source/VersOne.Epub/Readers/ContentReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public EpubContentRef ParseContentMap(EpubSchema epubSchema, IZipFile epubFile)
EpubContentCollectionRef<EpubLocalByteContentFileRef, EpubRemoteByteContentFileRef> fonts = new(fontsLocal.AsReadOnly(), fontsRemote.AsReadOnly());
EpubContentCollectionRef<EpubLocalByteContentFileRef, EpubRemoteByteContentFileRef> audio = new(audioLocal.AsReadOnly(), audioRemote.AsReadOnly());
EpubContentCollectionRef<EpubLocalContentFileRef, EpubRemoteContentFileRef> 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);
}

Expand Down

0 comments on commit 6162eee

Please sign in to comment.