Skip to content

Add the configuration option to ignore the error for missing manifest item referenced by EPUB 2 cover metadata #121

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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