diff --git a/Source/VersOne.Epub.Test/Comparers/EpubBookComparer.cs b/Source/VersOne.Epub.Test/Comparers/EpubBookComparer.cs index ebedf3e..4ed1ff3 100644 --- a/Source/VersOne.Epub.Test/Comparers/EpubBookComparer.cs +++ b/Source/VersOne.Epub.Test/Comparers/EpubBookComparer.cs @@ -2,8 +2,13 @@ { internal static class EpubBookComparer { - public static void CompareEpubBooks(EpubBook expected, EpubBook actual) + public static void CompareEpubBooks(EpubBook? expected, EpubBook? actual) { + if (expected == null) + { + Assert.Null(actual); + return; + } Assert.NotNull(actual); Assert.Equal(expected.FilePath, actual.FilePath); Assert.Equal(expected.Title, actual.Title); diff --git a/Source/VersOne.Epub.Test/Comparers/EpubBookRefComparer.cs b/Source/VersOne.Epub.Test/Comparers/EpubBookRefComparer.cs index df8d96a..2790120 100644 --- a/Source/VersOne.Epub.Test/Comparers/EpubBookRefComparer.cs +++ b/Source/VersOne.Epub.Test/Comparers/EpubBookRefComparer.cs @@ -2,8 +2,13 @@ { internal static class EpubBookRefComparer { - public static void CompareEpubBookRefs(EpubBookRef expected, EpubBookRef actual) + public static void CompareEpubBookRefs(EpubBookRef? expected, EpubBookRef? actual) { + if (expected == null) + { + Assert.Null(actual); + return; + } Assert.NotNull(actual); Assert.Equal(expected.FilePath, actual.FilePath); Assert.Equal(expected.Title, actual.Title); diff --git a/Source/VersOne.Epub.Test/Comparers/EpubPackageComparer.cs b/Source/VersOne.Epub.Test/Comparers/EpubPackageComparer.cs index d820f8f..6d98686 100644 --- a/Source/VersOne.Epub.Test/Comparers/EpubPackageComparer.cs +++ b/Source/VersOne.Epub.Test/Comparers/EpubPackageComparer.cs @@ -4,8 +4,13 @@ namespace VersOne.Epub.Test.Comparers { internal static class EpubPackageComparer { - public static void CompareEpubPackages(EpubPackage expected, EpubPackage actual) + public static void CompareEpubPackages(EpubPackage? expected, EpubPackage? actual) { + if (expected == null) + { + Assert.Null(actual); + return; + } Assert.NotNull(actual); Assert.Equal(expected.UniqueIdentifier, actual.UniqueIdentifier); Assert.Equal(expected.EpubVersion, actual.EpubVersion); diff --git a/Source/VersOne.Epub.Test/Comparers/EpubSchemaComparer.cs b/Source/VersOne.Epub.Test/Comparers/EpubSchemaComparer.cs index 1f3f9a4..42eade2 100644 --- a/Source/VersOne.Epub.Test/Comparers/EpubSchemaComparer.cs +++ b/Source/VersOne.Epub.Test/Comparers/EpubSchemaComparer.cs @@ -2,8 +2,13 @@ { internal static class EpubSchemaComparer { - public static void CompareEpubSchemas(EpubSchema expected, EpubSchema actual) + public static void CompareEpubSchemas(EpubSchema? expected, EpubSchema? actual) { + if (expected == null) + { + Assert.Null(actual); + return; + } Assert.NotNull(actual); Assert.Equal(expected.ContentDirectoryPath, actual.ContentDirectoryPath); EpubPackageComparer.CompareEpubPackages(expected.Package, actual.Package); diff --git a/Source/VersOne.Epub.Test/Comparers/SmilComparers.cs b/Source/VersOne.Epub.Test/Comparers/SmilComparers.cs index 19774d9..90d4358 100644 --- a/Source/VersOne.Epub.Test/Comparers/SmilComparers.cs +++ b/Source/VersOne.Epub.Test/Comparers/SmilComparers.cs @@ -11,8 +11,13 @@ public static void CompareSmilLists(List expected, List actual) CollectionComparer.CompareCollections(expected, actual, CompareSmils); } - public static void CompareSmils(Smil expected, Smil actual) + public static void CompareSmils(Smil? expected, Smil? actual) { + if (expected == null) + { + Assert.Null(actual); + return; + } Assert.NotNull(actual); Assert.Equal(expected.Id, actual.Id); Assert.Equal(expected.Version, actual.Version); diff --git a/Source/VersOne.Epub.Test/Integration/Runner/TestRunner.cs b/Source/VersOne.Epub.Test/Integration/Runner/TestRunner.cs index 4831ede..94e7aed 100644 --- a/Source/VersOne.Epub.Test/Integration/Runner/TestRunner.cs +++ b/Source/VersOne.Epub.Test/Integration/Runner/TestRunner.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using VersOne.Epub.Options; using VersOne.Epub.Test.Comparers; using VersOne.Epub.Test.Integration.CustomSerialization; using VersOne.Epub.Test.Integration.Types; @@ -31,9 +32,10 @@ public void Run(string testCaseDirectoryPath) Assert.NotNull(testCases); foreach (TestCase testCase in testCases) { + EpubReaderOptions epubReaderOptions = testCase.Options ?? new EpubReaderOptions(); if (testCase.ExpectedResult != null) { - EpubBook epubBook = EpubReader.ReadBook(testEpubPath, testCase.Options); + EpubBook? epubBook = EpubReader.ReadBook(testEpubPath, epubReaderOptions); EpubBookComparer.CompareEpubBooks(testCase.ExpectedResult, epubBook); } else if (testCase.ExpectedException != null) @@ -41,12 +43,12 @@ public void Run(string testCaseDirectoryPath) bool exceptionThrown = false; try { - EpubReader.ReadBook(testEpubPath, testCase.Options); + EpubReader.ReadBook(testEpubPath, epubReaderOptions); } catch (Exception actualException) { exceptionThrown = true; - Assert.Equal(actualException.GetType().Name, testCase.ExpectedException.Type); + Assert.Equal(testCase.ExpectedException.Type, actualException.GetType().Name); if (testCase.ExpectedException.Message != null) { Assert.Equal(actualException.Message, testCase.ExpectedException.Message); diff --git a/Source/VersOne.Epub.Test/Integration/TestCases/Workarounds/Xml11/testcases.json b/Source/VersOne.Epub.Test/Integration/TestCases/Workarounds/Xml11/testcases.json index 88b6d80..d75102f 100644 --- a/Source/VersOne.Epub.Test/Integration/TestCases/Workarounds/Xml11/testcases.json +++ b/Source/VersOne.Epub.Test/Integration/TestCases/Workarounds/Xml11/testcases.json @@ -4,7 +4,8 @@ "Options": null, "ExpectedResult": null, "ExpectedException": { - "Type": "XmlException" + "Type": "EpubContainerException", + "Message": "EPUB parsing error: EPUB OCF container file is not a valid XML file." } }, { diff --git a/Source/VersOne.Epub.Test/Unit/Content/Collections/EpubContentCollectionRefTests.cs b/Source/VersOne.Epub.Test/Unit/Content/Collections/EpubContentCollectionRefTests.cs index 0563748..f6d91ab 100644 --- a/Source/VersOne.Epub.Test/Unit/Content/Collections/EpubContentCollectionRefTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Content/Collections/EpubContentCollectionRefTests.cs @@ -70,9 +70,8 @@ public void ConstructorWithLocalDuplicateKeysTest() string duplicateKey = CHAPTER1_FILE_NAME; ReadOnlyCollection localWithDuplicateKeys = new ( - new List() - { - new EpubLocalTextContentFileRef + [ + new ( metadata: new EpubContentFileRefMetadata ( @@ -83,7 +82,7 @@ public void ConstructorWithLocalDuplicateKeysTest() filePath: CHAPTER1_FILE_PATH, epubContentLoader: ContentLoader ), - new EpubLocalTextContentFileRef + new ( metadata: new EpubContentFileRefMetadata ( @@ -94,7 +93,7 @@ public void ConstructorWithLocalDuplicateKeysTest() filePath: CHAPTER2_FILE_PATH, epubContentLoader: ContentLoader ) - } + ] ); Assert.Throws(() => new EpubContentCollectionRef(localWithDuplicateKeys, Remote)); } @@ -105,9 +104,8 @@ public void ConstructorWithLocalDuplicateFilePathsTest() string duplicateFilePath = CHAPTER1_FILE_PATH; ReadOnlyCollection localWithDuplicateFilePaths = new ( - new List() - { - new EpubLocalTextContentFileRef + [ + new ( metadata: new EpubContentFileRefMetadata ( @@ -118,7 +116,7 @@ public void ConstructorWithLocalDuplicateFilePathsTest() filePath: duplicateFilePath, epubContentLoader: ContentLoader ), - new EpubLocalTextContentFileRef + new ( metadata: new EpubContentFileRefMetadata ( @@ -129,7 +127,7 @@ public void ConstructorWithLocalDuplicateFilePathsTest() filePath: duplicateFilePath, epubContentLoader: ContentLoader ) - } + ] ); Assert.Throws(() => new EpubContentCollectionRef(localWithDuplicateFilePaths, Remote)); } @@ -140,9 +138,8 @@ public void ConstructorWithRemoteDuplicateUrlsTest() string duplicateKey = REMOTE_HTML_CONTENT_FILE_HREF; ReadOnlyCollection remoteWithDuplicateKeys = new ( - new List() - { - new EpubRemoteTextContentFileRef + [ + new ( metadata: new EpubContentFileRefMetadata ( @@ -152,7 +149,7 @@ public void ConstructorWithRemoteDuplicateUrlsTest() ), epubContentLoader: ContentLoader ), - new EpubRemoteTextContentFileRef + new ( metadata: new EpubContentFileRefMetadata ( @@ -162,7 +159,7 @@ public void ConstructorWithRemoteDuplicateUrlsTest() ), epubContentLoader: ContentLoader ) - } + ] ); Assert.Throws(() => new EpubContentCollectionRef(Local, remoteWithDuplicateKeys)); } @@ -174,9 +171,8 @@ public void ContainsLocalFileWithKeyWithNonNullKeyTest() string nonExistingKey = CHAPTER2_FILE_NAME; ReadOnlyCollection local = new ( - new List() - { - new EpubLocalTextContentFileRef + [ + new ( metadata: new EpubContentFileRefMetadata ( @@ -187,7 +183,7 @@ public void ContainsLocalFileWithKeyWithNonNullKeyTest() filePath: CHAPTER1_FILE_PATH, epubContentLoader: ContentLoader ) - } + ] ); EpubContentCollectionRef epubContentCollectionRef = new(local, Remote); Assert.True(epubContentCollectionRef.ContainsLocalFileRefWithKey(existingKey)); @@ -218,10 +214,9 @@ public void GetLocalFileByKeyWithExistingKeyTest() ); ReadOnlyCollection local = new ( - new List() - { + [ expectedFileRef - } + ] ); EpubContentCollectionRef epubContentCollectionRef = new(local, Remote); EpubLocalTextContentFileRef actualFileRef = epubContentCollectionRef.GetLocalFileRefByKey(existingKey); @@ -234,11 +229,11 @@ public void GetLocalFileByKeyWithNonExistingKeyTest() string nonExistingKey = CHAPTER2_FILE_NAME; ReadOnlyCollection local = new ( - new List() - { - new EpubLocalTextContentFileRef + [ + new ( - metadata: new EpubContentFileRefMetadata + metadata: + new ( key: CHAPTER1_FILE_NAME, contentType: HTML_CONTENT_TYPE, @@ -247,7 +242,7 @@ public void GetLocalFileByKeyWithNonExistingKeyTest() filePath: CHAPTER1_FILE_PATH, epubContentLoader: ContentLoader ) - } + ] ); EpubContentCollectionRef epubContentCollectionRef = new(local, Remote); Assert.Throws(() => epubContentCollectionRef.GetLocalFileRefByKey(nonExistingKey)); @@ -278,15 +273,14 @@ public void TryGetLocalFileByKeyWithNonNullKeyTest() ); ReadOnlyCollection local = new ( - new List() - { + [ expectedFileRef - } + ] ); EpubContentCollectionRef epubContentCollectionRef = new(local, Remote); - Assert.True(epubContentCollectionRef.TryGetLocalFileRefByKey(existingKey, out EpubLocalTextContentFileRef actualFileRefForExistingKey)); + Assert.True(epubContentCollectionRef.TryGetLocalFileRefByKey(existingKey, out EpubLocalTextContentFileRef? actualFileRefForExistingKey)); EpubContentRefComparer.CompareEpubLocalContentFileRefs(expectedFileRef, actualFileRefForExistingKey); - Assert.False(epubContentCollectionRef.TryGetLocalFileRefByKey(nonExistingKey, out EpubLocalTextContentFileRef actualFileRefForNonExistingKey)); + Assert.False(epubContentCollectionRef.TryGetLocalFileRefByKey(nonExistingKey, out EpubLocalTextContentFileRef? actualFileRefForNonExistingKey)); Assert.Null(actualFileRefForNonExistingKey); } @@ -304,11 +298,11 @@ public void ContainsLocalFileWithFilePathWithNonNullFilePathTest() string nonExistingFilePath = CHAPTER2_FILE_PATH; ReadOnlyCollection local = new ( - new List() - { - new EpubLocalTextContentFileRef + [ + new ( - metadata: new EpubContentFileRefMetadata + metadata: + new ( key: CHAPTER1_FILE_NAME, contentType: HTML_CONTENT_TYPE, @@ -317,7 +311,7 @@ public void ContainsLocalFileWithFilePathWithNonNullFilePathTest() filePath: existingFilePath, epubContentLoader: ContentLoader ) - } + ] ); EpubContentCollectionRef epubContentCollectionRef = new(local, Remote); Assert.True(epubContentCollectionRef.ContainsLocalFileRefWithFilePath(existingFilePath)); @@ -337,7 +331,8 @@ public void GetLocalFileByFilePathWithExistingFilePathTest() string existingFilePath = CHAPTER1_FILE_PATH; EpubLocalTextContentFileRef expectedFileRef = new ( - metadata: new EpubContentFileRefMetadata + metadata: + new ( key: CHAPTER1_FILE_NAME, contentType: HTML_CONTENT_TYPE, @@ -348,10 +343,9 @@ public void GetLocalFileByFilePathWithExistingFilePathTest() ); ReadOnlyCollection local = new ( - new List() - { + [ expectedFileRef - } + ] ); EpubContentCollectionRef epubContentCollectionRef = new(local, Remote); EpubLocalTextContentFileRef actualFileRef = epubContentCollectionRef.GetLocalFileRefByFilePath(existingFilePath); @@ -364,11 +358,11 @@ public void GetLocalFileByFilePathWithNonExistingFilePathTest() string nonExistingFilePath = CHAPTER2_FILE_PATH; ReadOnlyCollection local = new ( - new List() - { - new EpubLocalTextContentFileRef + [ + new ( - metadata: new EpubContentFileRefMetadata + metadata: + new ( key: CHAPTER1_FILE_NAME, contentType: HTML_CONTENT_TYPE, @@ -377,7 +371,7 @@ public void GetLocalFileByFilePathWithNonExistingFilePathTest() filePath: CHAPTER1_FILE_PATH, epubContentLoader: ContentLoader ) - } + ] ); EpubContentCollectionRef epubContentCollectionRef = new(local, Remote); Assert.Throws(() => epubContentCollectionRef.GetLocalFileRefByFilePath(nonExistingFilePath)); @@ -397,7 +391,8 @@ public void TryGetLocalFileByFilePathWithNonNullFilePathTest() string nonExistingFilePath = CHAPTER2_FILE_PATH; EpubLocalTextContentFileRef expectedFileRef = new ( - metadata: new EpubContentFileRefMetadata + metadata: + new ( key: CHAPTER1_FILE_NAME, contentType: HTML_CONTENT_TYPE, @@ -408,15 +403,14 @@ public void TryGetLocalFileByFilePathWithNonNullFilePathTest() ); ReadOnlyCollection local = new ( - new List() - { + [ expectedFileRef - } + ] ); EpubContentCollectionRef epubContentCollectionRef = new(local, Remote); - Assert.True(epubContentCollectionRef.TryGetLocalFileRefByFilePath(existingFilePath, out EpubLocalTextContentFileRef actualFileRefForExistingFilePath)); + Assert.True(epubContentCollectionRef.TryGetLocalFileRefByFilePath(existingFilePath, out EpubLocalTextContentFileRef? actualFileRefForExistingFilePath)); EpubContentRefComparer.CompareEpubLocalContentFileRefs(expectedFileRef, actualFileRefForExistingFilePath); - Assert.False(epubContentCollectionRef.TryGetLocalFileRefByFilePath(nonExistingFilePath, out EpubLocalTextContentFileRef actualFileRefForNonExistingFilePath)); + Assert.False(epubContentCollectionRef.TryGetLocalFileRefByFilePath(nonExistingFilePath, out EpubLocalTextContentFileRef? actualFileRefForNonExistingFilePath)); Assert.Null(actualFileRefForNonExistingFilePath); } @@ -434,11 +428,11 @@ public void ContainsRemoteFileWithUrlWithNonNullUrlTest() string nonExistingUrl = REMOTE_CSS_CONTENT_FILE_HREF; ReadOnlyCollection remote = new ( - new List() - { - new EpubRemoteTextContentFileRef + [ + new ( - metadata: new EpubContentFileRefMetadata + metadata: + new ( key: existingUrl, contentType: HTML_CONTENT_TYPE, @@ -446,7 +440,7 @@ public void ContainsRemoteFileWithUrlWithNonNullUrlTest() ), epubContentLoader: ContentLoader ) - } + ] ); EpubContentCollectionRef epubContentCollectionRef = new(Local, remote); Assert.True(epubContentCollectionRef.ContainsRemoteFileRefWithUrl(existingUrl)); @@ -466,7 +460,8 @@ public void GetRemoteFileByUrlWithExistingUrlTest() string existingUrl = REMOTE_HTML_CONTENT_FILE_HREF; EpubRemoteTextContentFileRef expectedFileRef = new ( - metadata: new EpubContentFileRefMetadata + metadata: + new ( key: existingUrl, contentType: HTML_CONTENT_TYPE, @@ -476,10 +471,9 @@ public void GetRemoteFileByUrlWithExistingUrlTest() ); ReadOnlyCollection remote = new ( - new List() - { + [ expectedFileRef - } + ] ); EpubContentCollectionRef epubContentCollectionRef = new(Local, remote); EpubRemoteTextContentFileRef actualFileRef = epubContentCollectionRef.GetRemoteFileRefByUrl(existingUrl); @@ -492,11 +486,11 @@ public void GetRemoteFileByUrlWithNonExistingUrlTest() string nonExistingUrl = REMOTE_CSS_CONTENT_FILE_HREF; ReadOnlyCollection remote = new ( - new List() - { - new EpubRemoteTextContentFileRef + [ + new ( - metadata: new EpubContentFileRefMetadata + metadata: + new ( key: REMOTE_HTML_CONTENT_FILE_HREF, contentType: HTML_CONTENT_TYPE, @@ -504,7 +498,7 @@ public void GetRemoteFileByUrlWithNonExistingUrlTest() ), epubContentLoader: ContentLoader ) - } + ] ); EpubContentCollectionRef epubContentCollectionRef = new(Local, remote); Assert.Throws(() => epubContentCollectionRef.GetRemoteFileRefByUrl(nonExistingUrl)); @@ -522,9 +516,11 @@ public void TryGetRemoteFileByUrlWithNonNullUrlTest() { string existingUrl = REMOTE_HTML_CONTENT_FILE_HREF; string nonExistingUrl = REMOTE_CSS_CONTENT_FILE_HREF; - EpubRemoteTextContentFileRef expectedFileRef = new + EpubRemoteTextContentFileRef expectedFileRef = + new ( - metadata: new EpubContentFileRefMetadata + metadata: + new ( key: existingUrl, contentType: HTML_CONTENT_TYPE, @@ -534,15 +530,14 @@ public void TryGetRemoteFileByUrlWithNonNullUrlTest() ); ReadOnlyCollection remote = new ( - new List() - { + [ expectedFileRef - } + ] ); EpubContentCollectionRef epubContentCollectionRef = new(Local, remote); - Assert.True(epubContentCollectionRef.TryGetRemoteFileRefByUrl(existingUrl, out EpubRemoteTextContentFileRef actualFileRefForExistingUrl)); + Assert.True(epubContentCollectionRef.TryGetRemoteFileRefByUrl(existingUrl, out EpubRemoteTextContentFileRef? actualFileRefForExistingUrl)); EpubContentRefComparer.CompareEpubRemoteContentFileRefs(expectedFileRef, actualFileRefForExistingUrl); - Assert.False(epubContentCollectionRef.TryGetRemoteFileRefByUrl(nonExistingUrl, out EpubRemoteTextContentFileRef actualFileRefForNonExistingUrl)); + Assert.False(epubContentCollectionRef.TryGetRemoteFileRefByUrl(nonExistingUrl, out EpubRemoteTextContentFileRef? actualFileRefForNonExistingUrl)); Assert.Null(actualFileRefForNonExistingUrl); } diff --git a/Source/VersOne.Epub.Test/Unit/Content/Collections/EpubContentCollectionTests.cs b/Source/VersOne.Epub.Test/Unit/Content/Collections/EpubContentCollectionTests.cs index e605c8c..8446645 100644 --- a/Source/VersOne.Epub.Test/Unit/Content/Collections/EpubContentCollectionTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Content/Collections/EpubContentCollectionTests.cs @@ -67,9 +67,8 @@ public void ConstructorWithLocalDuplicateKeysTest() string duplicateKey = CHAPTER1_FILE_NAME; ReadOnlyCollection localWithDuplicateKeys = new ( - new List() - { - new EpubLocalTextContentFile + [ + new ( key: duplicateKey, contentType: HTML_CONTENT_TYPE, @@ -77,7 +76,7 @@ public void ConstructorWithLocalDuplicateKeysTest() filePath: CHAPTER1_FILE_PATH, content: TestEpubFiles.CHAPTER1_FILE_CONTENT ), - new EpubLocalTextContentFile + new ( key: duplicateKey, contentType: HTML_CONTENT_TYPE, @@ -85,7 +84,7 @@ public void ConstructorWithLocalDuplicateKeysTest() filePath: CHAPTER2_FILE_PATH, content: TestEpubFiles.CHAPTER2_FILE_CONTENT ) - } + ] ); Assert.Throws(() => new EpubContentCollection(localWithDuplicateKeys, Remote)); } @@ -96,9 +95,8 @@ public void ConstructorWithLocalDuplicateFilePathsTest() string duplicateFilePath = CHAPTER1_FILE_PATH; ReadOnlyCollection localWithDuplicateFilePaths = new ( - new List() - { - new EpubLocalTextContentFile + [ + new ( key: CHAPTER1_FILE_NAME, contentType: HTML_CONTENT_TYPE, @@ -106,7 +104,7 @@ public void ConstructorWithLocalDuplicateFilePathsTest() filePath: duplicateFilePath, content: TestEpubFiles.CHAPTER1_FILE_CONTENT ), - new EpubLocalTextContentFile + new ( key: CHAPTER2_FILE_NAME, contentType: HTML_CONTENT_TYPE, @@ -114,7 +112,7 @@ public void ConstructorWithLocalDuplicateFilePathsTest() filePath: duplicateFilePath, content: TestEpubFiles.CHAPTER2_FILE_CONTENT ) - } + ] ); Assert.Throws(() => new EpubContentCollection(localWithDuplicateFilePaths, Remote)); } @@ -125,23 +123,22 @@ public void ConstructorWithRemoteDuplicateUrlsTest() string duplicateKey = REMOTE_HTML_CONTENT_FILE_HREF; ReadOnlyCollection remoteWithDuplicateKeys = new ( - new List() - { - new EpubRemoteTextContentFile + [ + new ( key: duplicateKey, contentType: HTML_CONTENT_TYPE, contentMimeType: HTML_CONTENT_MIME_TYPE, content: TestEpubFiles.REMOTE_HTML_FILE_CONTENT ), - new EpubRemoteTextContentFile + new ( key: duplicateKey, contentType: CSS_CONTENT_TYPE, contentMimeType: CSS_CONTENT_MIME_TYPE, content: TestEpubFiles.REMOTE_CSS_FILE_CONTENT ) - } + ] ); Assert.Throws(() => new EpubContentCollection(Local, remoteWithDuplicateKeys)); } @@ -153,9 +150,8 @@ public void ContainsLocalFileWithKeyWithNonNullKeyTest() string nonExistingKey = CHAPTER2_FILE_NAME; ReadOnlyCollection local = new ( - new List() - { - new EpubLocalTextContentFile + [ + new ( key: existingKey, contentType: HTML_CONTENT_TYPE, @@ -163,7 +159,7 @@ public void ContainsLocalFileWithKeyWithNonNullKeyTest() filePath: CHAPTER1_FILE_PATH, content: TestEpubFiles.CHAPTER1_FILE_CONTENT ) - } + ] ); EpubContentCollection epubContentCollection = new(local, Remote); Assert.True(epubContentCollection.ContainsLocalFileWithKey(existingKey)); @@ -191,10 +187,9 @@ public void GetLocalFileByKeyWithExistingKeyTest() ); ReadOnlyCollection local = new ( - new List() - { + [ expectedFile - } + ] ); EpubContentCollection epubContentCollection = new(local, Remote); EpubLocalTextContentFile actualFile = epubContentCollection.GetLocalFileByKey(existingKey); @@ -207,9 +202,8 @@ public void GetLocalFileByKeyWithNonExistingKeyTest() string nonExistingKey = CHAPTER2_FILE_NAME; ReadOnlyCollection local = new ( - new List() - { - new EpubLocalTextContentFile + [ + new ( key: CHAPTER1_FILE_NAME, contentType: HTML_CONTENT_TYPE, @@ -217,7 +211,7 @@ public void GetLocalFileByKeyWithNonExistingKeyTest() filePath: CHAPTER1_FILE_PATH, content: TestEpubFiles.CHAPTER1_FILE_CONTENT ) - } + ] ); EpubContentCollection epubContentCollection = new(local, Remote); Assert.Throws(() => epubContentCollection.GetLocalFileByKey(nonExistingKey)); @@ -245,15 +239,14 @@ public void TryGetLocalFileByKeyWithNonNullKeyTest() ); ReadOnlyCollection local = new ( - new List() - { + [ expectedFile - } + ] ); EpubContentCollection epubContentCollection = new(local, Remote); - Assert.True(epubContentCollection.TryGetLocalFileByKey(existingKey, out EpubLocalTextContentFile actualFileForExistingKey)); + Assert.True(epubContentCollection.TryGetLocalFileByKey(existingKey, out EpubLocalTextContentFile? actualFileForExistingKey)); EpubContentComparer.CompareEpubLocalTextContentFiles(expectedFile, actualFileForExistingKey); - Assert.False(epubContentCollection.TryGetLocalFileByKey(nonExistingKey, out EpubLocalTextContentFile actualFileForNonExistingKey)); + Assert.False(epubContentCollection.TryGetLocalFileByKey(nonExistingKey, out EpubLocalTextContentFile? actualFileForNonExistingKey)); Assert.Null(actualFileForNonExistingKey); } @@ -271,9 +264,8 @@ public void ContainsLocalFileWithFilePathWithNonNullFilePathTest() string nonExistingFilePath = CHAPTER2_FILE_PATH; ReadOnlyCollection local = new ( - new List() - { - new EpubLocalTextContentFile + [ + new ( key: CHAPTER1_FILE_NAME, contentType: HTML_CONTENT_TYPE, @@ -281,7 +273,7 @@ public void ContainsLocalFileWithFilePathWithNonNullFilePathTest() filePath: existingFilePath, content: TestEpubFiles.CHAPTER1_FILE_CONTENT ) - } + ] ); EpubContentCollection epubContentCollection = new(local, Remote); Assert.True(epubContentCollection.ContainsLocalFileWithFilePath(existingFilePath)); @@ -309,10 +301,9 @@ public void GetLocalFileByFilePathWithExistingFilePathTest() ); ReadOnlyCollection local = new ( - new List() - { + [ expectedFile - } + ] ); EpubContentCollection epubContentCollection = new(local, Remote); EpubLocalTextContentFile actualFile = epubContentCollection.GetLocalFileByFilePath(existingFilePath); @@ -325,9 +316,8 @@ public void GetLocalFileByFilePathWithNonExistingFilePathTest() string nonExistingFilePath = CHAPTER2_FILE_PATH; ReadOnlyCollection local = new ( - new List() - { - new EpubLocalTextContentFile + [ + new ( key: CHAPTER1_FILE_NAME, contentType: HTML_CONTENT_TYPE, @@ -335,7 +325,7 @@ public void GetLocalFileByFilePathWithNonExistingFilePathTest() filePath: CHAPTER1_FILE_PATH, content: TestEpubFiles.CHAPTER1_FILE_CONTENT ) - } + ] ); EpubContentCollection epubContentCollection = new(local, Remote); Assert.Throws(() => epubContentCollection.GetLocalFileByFilePath(nonExistingFilePath)); @@ -363,15 +353,14 @@ public void TryGetLocalFileByFilePathWithNonNullFilePathTest() ); ReadOnlyCollection local = new ( - new List() - { + [ expectedFile - } + ] ); EpubContentCollection epubContentCollection = new(local, Remote); - Assert.True(epubContentCollection.TryGetLocalFileByFilePath(existingFilePath, out EpubLocalTextContentFile actualFileForExistingFilePath)); + Assert.True(epubContentCollection.TryGetLocalFileByFilePath(existingFilePath, out EpubLocalTextContentFile? actualFileForExistingFilePath)); EpubContentComparer.CompareEpubLocalTextContentFiles(expectedFile, actualFileForExistingFilePath); - Assert.False(epubContentCollection.TryGetLocalFileByFilePath(nonExistingFilePath, out EpubLocalTextContentFile actualFileForNonExistingFilePath)); + Assert.False(epubContentCollection.TryGetLocalFileByFilePath(nonExistingFilePath, out EpubLocalTextContentFile? actualFileForNonExistingFilePath)); Assert.Null(actualFileForNonExistingFilePath); } @@ -389,16 +378,15 @@ public void ContainsRemoteFileWithUrlWithNonNullUrlTest() string nonExistingUrl = REMOTE_CSS_CONTENT_FILE_HREF; ReadOnlyCollection remote = new ( - new List() - { - new EpubRemoteTextContentFile + [ + new ( key: existingUrl, contentType: HTML_CONTENT_TYPE, contentMimeType: HTML_CONTENT_MIME_TYPE, content: TestEpubFiles.REMOTE_HTML_FILE_CONTENT ) - } + ] ); EpubContentCollection epubContentCollection = new(Local, remote); Assert.True(epubContentCollection.ContainsRemoteFileWithUrl(existingUrl)); @@ -425,10 +413,9 @@ public void GetRemoteFileByUrlWithExistingUrlTest() ); ReadOnlyCollection remote = new ( - new List() - { + [ expectedFile - } + ] ); EpubContentCollection epubContentCollection = new(Local, remote); EpubRemoteTextContentFile actualFile = epubContentCollection.GetRemoteFileByUrl(existingUrl); @@ -441,16 +428,15 @@ public void GetRemoteFileByUrlWithNonExistingUrlTest() string nonExistingUrl = REMOTE_CSS_CONTENT_FILE_HREF; ReadOnlyCollection remote = new ( - new List() - { - new EpubRemoteTextContentFile + [ + new ( key: REMOTE_HTML_CONTENT_FILE_HREF, contentType: HTML_CONTENT_TYPE, contentMimeType: HTML_CONTENT_MIME_TYPE, content: TestEpubFiles.REMOTE_HTML_FILE_CONTENT ) - } + ] ); EpubContentCollection epubContentCollection = new(Local, remote); Assert.Throws(() => epubContentCollection.GetRemoteFileByUrl(nonExistingUrl)); @@ -477,15 +463,14 @@ public void TryGetRemoteFileByUrlWithNonNullUrlTest() ); ReadOnlyCollection remote = new ( - new List() - { + [ expectedFile - } + ] ); EpubContentCollection epubContentCollection = new(Local, remote); - Assert.True(epubContentCollection.TryGetRemoteFileByUrl(existingUrl, out EpubRemoteTextContentFile actualFileForExistingUrl)); + Assert.True(epubContentCollection.TryGetRemoteFileByUrl(existingUrl, out EpubRemoteTextContentFile? actualFileForExistingUrl)); EpubContentComparer.CompareEpubRemoteTextContentFiles(expectedFile, actualFileForExistingUrl); - Assert.False(epubContentCollection.TryGetRemoteFileByUrl(nonExistingUrl, out EpubRemoteTextContentFile actualFileForNonExistingUrl)); + Assert.False(epubContentCollection.TryGetRemoteFileByUrl(nonExistingUrl, out EpubRemoteTextContentFile? actualFileForNonExistingUrl)); Assert.Null(actualFileForNonExistingUrl); } diff --git a/Source/VersOne.Epub.Test/Unit/Readers/BookCoverReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/BookCoverReaderTests.cs index 39fbc88..9fcdb01 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/BookCoverReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/BookCoverReaderTests.cs @@ -94,16 +94,18 @@ public void ReadBookCoverForEpub2WithCoverInGuideReferencingNonExistingImageTest public void ReadBookCoverForEpub3WithCoverInManifestTest() { EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_3); - epubSchema.Package.Manifest.Items.Add(new EpubManifestItem - ( - id: "cover-image", - href: LOCAL_COVER_FILE_NAME, - mediaType: COVER_FILE_CONTENT_MIME_TYPE, - properties: new List() - { - EpubManifestProperty.COVER_IMAGE - } - )); + epubSchema.Package.Manifest.Items.Add( + new + ( + id: "cover-image", + href: LOCAL_COVER_FILE_NAME, + mediaType: COVER_FILE_CONTENT_MIME_TYPE, + properties: + [ + EpubManifestProperty.COVER_IMAGE + ] + ) + ); EpubLocalByteContentFileRef expectedCoverImageFileRef = CreateLocalTestImageFileRef(); TestSuccessfulReadOperation(epubSchema, expectedCoverImageFileRef); } @@ -112,47 +114,105 @@ public void ReadBookCoverForEpub3WithCoverInManifestTest() public void ReadBookCoverForEpub2WithNoCoverMetaItemTest() { EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_2); - epubSchema.Package.Metadata.MetaItems.Add(new EpubMetadataMeta - ( - name: "other-item", - content: "some content" - )); + epubSchema.Package.Metadata.MetaItems.Add( + new + ( + name: "other-item", + content: "some content" + ) + ); TestSuccessfulReadOperation(epubSchema, null); } - [Fact(DisplayName = "ReadBookCover should throw EpubPackageException if the Content property in the cover meta item is empty")] - public void ReadBookCoverForEpub2WithEmptyCoverMetaItemContentTest() + [Fact(DisplayName = "ReadBookCover should throw EpubPackageException if the Content property in the cover meta item is empty and BookCoverReaderOptions is null")] + public void ReadBookCoverForEpub2WithEmptyCoverMetaItemContentAndDefaultOptionsTest() { EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_2); - epubSchema.Package.Metadata.MetaItems.Add(new EpubMetadataMeta - ( - name: "cover", - content: String.Empty - )); + epubSchema.Package.Metadata.MetaItems.Add( + new + ( + name: "cover", + content: String.Empty + ) + ); TestFailingReadOperation(epubSchema); } - [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() + [Fact(DisplayName = "ReadBookCover should return null if the Content property in the cover meta item is empty and Epub2MetadataIgnoreMissingContent = true")] + public void ReadBookCoverForEpub2WithEmptyCoverMetaItemContentAndEpub2MetadataIgnoreMissingContentTest() { EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_2); - epubSchema.Package.Metadata.MetaItems.Add(new EpubMetadataMeta - ( - name: "cover", - content: "cover-image" - )); + epubSchema.Package.Metadata.MetaItems.Add( + new + ( + name: "cover", + content: String.Empty + ) + ); + BookCoverReaderOptions bookCoverReaderOptions = new() + { + Epub2MetadataIgnoreMissingContent = true + }; + TestSuccessfulReadOperation(epubSchema, null, bookCoverReaderOptions); + } + + [Fact(DisplayName = "ReadBookCover should throw EpubPackageException if the Content property in the cover meta item is a whitespace sequence and BookCoverReaderOptions is null")] + public void ReadBookCoverForEpub2WithWhiteSpaceCoverMetaItemContentAndDefaultOptionsTest() + { + EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_2); + epubSchema.Package.Metadata.MetaItems.Add( + new + ( + name: "cover", + content: " " + ) + ); 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() + [Fact(DisplayName = "ReadBookCover should return null if the Content property in the cover meta item is a whitespace sequence and Epub2MetadataIgnoreMissingContent = true")] + public void ReadBookCoverForEpub2WithWhiteSpaceCoverMetaItemContentAndEpub2MetadataIgnoreMissingContentTest() { EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_2); - epubSchema.Package.Metadata.MetaItems.Add(new EpubMetadataMeta - ( - name: "cover", - content: "cover-image" - )); + epubSchema.Package.Metadata.MetaItems.Add( + new + ( + name: "cover", + content: " " + ) + ); + BookCoverReaderOptions bookCoverReaderOptions = new() + { + Epub2MetadataIgnoreMissingContent = true + }; + TestSuccessfulReadOperation(epubSchema, null, bookCoverReaderOptions); + } + + [Fact(DisplayName = "ReadBookCover should throw EpubPackageException if the manifest item with the ID specified in the cover meta item is missing and BookCoverReaderOptions is null")] + public void ReadBookCoverForEpub2WithMissingManifestItemAndDefaultOptionsTest() + { + EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_2); + epubSchema.Package.Metadata.MetaItems.Add( + new + ( + name: "cover", + content: "cover-image" + ) + ); + TestFailingReadOperation(epubSchema); + } + + [Fact(DisplayName = "ReadBookCover should return null if the manifest item with the ID specified in the cover meta item is missing and Epub2MetadataIgnoreMissingManifestItem = true")] + public void ReadBookCoverForEpub2WithMissingManifestItemAndEpub2MetadataIgnoreMissingManifestItemTest() + { + EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_2); + epubSchema.Package.Metadata.MetaItems.Add( + new + ( + name: "cover", + content: "cover-image" + ) + ); BookCoverReaderOptions bookCoverReaderOptions = new() { Epub2MetadataIgnoreMissingManifestItem = true @@ -160,24 +220,54 @@ public void ReadBookCoverForEpub2WithMissingManifestItemWithIgnoringErrorOptionT 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() + [Fact(DisplayName = "ReadBookCover should throw EpubPackageException if the image referenced by the cover manifest item is missing in the EPUB 2 file and BookCoverReaderOptions is null")] + public void ReadBookCoverForEpub2WithMissingManifestItemImageAndDefaultOptionsTest() { EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_2); - epubSchema.Package.Metadata.MetaItems.Add(new EpubMetadataMeta - ( - name: "cover", - content: "cover-image" - )); - epubSchema.Package.Manifest.Items.Add(new EpubManifestItem - ( - id: "cover-image", - href: LOCAL_COVER_FILE_NAME, - mediaType: COVER_FILE_CONTENT_MIME_TYPE - )); + epubSchema.Package.Metadata.MetaItems.Add( + new + ( + name: "cover", + content: "cover-image" + ) + ); + epubSchema.Package.Manifest.Items.Add( + new + ( + id: "cover-image", + href: LOCAL_COVER_FILE_NAME, + mediaType: COVER_FILE_CONTENT_MIME_TYPE + ) + ); TestFailingReadOperation(epubSchema); } + [Fact(DisplayName = "ReadBookCover should return null if the image referenced by the cover manifest item is missing in the EPUB 2 file and Epub2MetadataIgnoreMissingContentFile = true")] + public void ReadBookCoverForEpub2WithMissingManifestItemImageAndEpub2MetadataIgnoreMissingContentFileTest() + { + EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_2); + epubSchema.Package.Metadata.MetaItems.Add( + new + ( + name: "cover", + content: "cover-image" + ) + ); + epubSchema.Package.Manifest.Items.Add( + new + ( + id: "cover-image", + href: LOCAL_COVER_FILE_NAME, + mediaType: COVER_FILE_CONTENT_MIME_TYPE + ) + ); + BookCoverReaderOptions bookCoverReaderOptions = new() + { + Epub2MetadataIgnoreMissingContentFile = true + }; + TestSuccessfulReadOperation(epubSchema, null, bookCoverReaderOptions); + } + [Fact(DisplayName = "ReadBookCover should return null if a EPUB 2 book has no cover in the metadata and no cover references in the guide")] public void ReadBookCoverForEpub2WithNoCoverInMetadataAndGuideTest() { @@ -232,45 +322,102 @@ public void ReadBookCoverForEpub3WithNoCoverInManifestTest() TestSuccessfulReadOperation(epubSchema, null); } - [Fact(DisplayName = "ReadBookCover should throw EpubPackageException if the image referenced by the cover manifest item is missing in the EPUB 3 file")] - public void ReadBookCoverForEpub3WithMissingManifestItemImageTest() + [Fact(DisplayName = "ReadBookCover should throw EpubPackageException if the image referenced by the cover manifest item is missing in the EPUB 3 file and BookCoverReaderOptions is null")] + public void ReadBookCoverForEpub3WithMissingManifestItemImageAndDefaultOptionsTest() { EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_3); - epubSchema.Package.Manifest.Items.Add(new EpubManifestItem - ( - id: "cover-image", - href: LOCAL_COVER_FILE_NAME, - mediaType: COVER_FILE_CONTENT_MIME_TYPE, - properties: - [ - EpubManifestProperty.COVER_IMAGE - ] - )); + epubSchema.Package.Manifest.Items.Add( + new + ( + id: "cover-image", + href: LOCAL_COVER_FILE_NAME, + mediaType: COVER_FILE_CONTENT_MIME_TYPE, + properties: + [ + EpubManifestProperty.COVER_IMAGE + ] + ) + ); TestFailingReadOperation(epubSchema); } - [Fact(DisplayName = "ReadBookCover should throw EpubPackageException if the image referenced by the cover manifest item in the EPUB 2 file is a remote resource")] - public void ReadBookCoverForEpub2WithRemoteManifestItemImageTest() + [Fact(DisplayName = "ReadBookCover should return null if the image referenced by the cover manifest item is missing in the EPUB 3 file and Epub3IgnoreMissingContentFile = true")] + public void ReadBookCoverForEpub3WithMissingManifestItemImageAndEpub3IgnoreMissingContentFileTest() + { + EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_3); + epubSchema.Package.Manifest.Items.Add( + new + ( + id: "cover-image", + href: LOCAL_COVER_FILE_NAME, + mediaType: COVER_FILE_CONTENT_MIME_TYPE, + properties: + [ + EpubManifestProperty.COVER_IMAGE + ] + ) + ); + BookCoverReaderOptions bookCoverReaderOptions = new() + { + Epub3IgnoreMissingContentFile = true + }; + TestSuccessfulReadOperation(epubSchema, null, bookCoverReaderOptions); + } + + [Fact(DisplayName = "ReadBookCover should throw EpubPackageException if the image referenced by the cover manifest item in the EPUB 2 file is a remote resource and BookCoverReaderOptions is null")] + public void ReadBookCoverForEpub2WithRemoteManifestItemImageAndDefaultOptionsTest() { EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_2); - epubSchema.Package.Metadata.MetaItems.Add(new EpubMetadataMeta - ( - name: "cover", - content: "cover-image" - )); - epubSchema.Package.Manifest.Items.Add(new EpubManifestItem - ( - id: "cover-image", - href: REMOTE_COVER_FILE_HREF, - mediaType: COVER_FILE_CONTENT_MIME_TYPE - )); + epubSchema.Package.Metadata.MetaItems.Add( + new + ( + name: "cover", + content: "cover-image" + ) + ); + epubSchema.Package.Manifest.Items.Add( + new + ( + id: "cover-image", + href: REMOTE_COVER_FILE_HREF, + mediaType: COVER_FILE_CONTENT_MIME_TYPE + ) + ); EpubRemoteByteContentFileRef remoteTestImageFileRef = CreateRemoteTestImageFileRef(); EpubContentCollectionRef imageContentRefs = CreateImageContentRefs(remoteImageFileRef: remoteTestImageFileRef); TestFailingReadOperation(epubSchema, imageContentRefs); } - [Fact(DisplayName = "ReadBookCover should throw EpubPackageException if the image referenced by the cover guide item in the EPUB 2 file is a remote resource")] - public void ReadBookCoverForEpub2WithRemoteGuideItemImageTest() + [Fact(DisplayName = "ReadBookCover should return null if the image referenced by the cover manifest item in the EPUB 2 file is a remote resource and IgnoreRemoteContentFileError = true")] + public void ReadBookCoverForEpub2WithRemoteManifestItemImageAndIgnoreRemoteContentFileErrorTest() + { + EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_2); + epubSchema.Package.Metadata.MetaItems.Add( + new + ( + name: "cover", + content: "cover-image" + ) + ); + epubSchema.Package.Manifest.Items.Add( + new + ( + id: "cover-image", + href: REMOTE_COVER_FILE_HREF, + mediaType: COVER_FILE_CONTENT_MIME_TYPE + ) + ); + EpubRemoteByteContentFileRef remoteTestImageFileRef = CreateRemoteTestImageFileRef(); + EpubContentCollectionRef imageContentRefs = CreateImageContentRefs(remoteImageFileRef: remoteTestImageFileRef); + BookCoverReaderOptions bookCoverReaderOptions = new() + { + IgnoreRemoteContentFileError = true + }; + TestSuccessfulReadOperation(epubSchema, null, bookCoverReaderOptions, imageContentRefs); + } + + [Fact(DisplayName = "ReadBookCover should throw EpubPackageException if the image referenced by the cover guide item in the EPUB 2 file is a remote resource and BookCoverReaderOptions is null")] + public void ReadBookCoverForEpub2WithRemoteGuideItemImageAndDefaultOptionsTest() { EpubSchema epubSchema = CreateEmptyEpubSchema ( @@ -279,7 +426,8 @@ public void ReadBookCoverForEpub2WithRemoteGuideItemImageTest() ( items: [ - new ( + new + ( type: "cover", title: null, href: REMOTE_COVER_FILE_HREF @@ -292,8 +440,36 @@ public void ReadBookCoverForEpub2WithRemoteGuideItemImageTest() TestFailingReadOperation(epubSchema, imageContentRefs); } - [Fact(DisplayName = "ReadBookCover should throw EpubPackageException if the image referenced by the cover manifest item in the EPUB 3 file is a remote resource")] - public void ReadBookCoverForEpub3WithRemoteManifestItemImageTest() + [Fact(DisplayName = "ReadBookCover should return null if the image referenced by the cover guide item in the EPUB 2 file is a remote resource and IgnoreRemoteContentFileError = true")] + public void ReadBookCoverForEpub2WithRemoteGuideItemImageAndIgnoreRemoteContentFileErrorTest() + { + EpubSchema epubSchema = CreateEmptyEpubSchema + ( + epubVersion: EpubVersion.EPUB_2, + guide: new EpubGuide + ( + items: + [ + new + ( + type: "cover", + title: null, + href: REMOTE_COVER_FILE_HREF + ) + ] + ) + ); + EpubRemoteByteContentFileRef remoteTestImageFileRef = CreateRemoteTestImageFileRef(); + EpubContentCollectionRef imageContentRefs = CreateImageContentRefs(remoteImageFileRef: remoteTestImageFileRef); + BookCoverReaderOptions bookCoverReaderOptions = new() + { + IgnoreRemoteContentFileError = true + }; + TestSuccessfulReadOperation(epubSchema, null, bookCoverReaderOptions, imageContentRefs); + } + + [Fact(DisplayName = "ReadBookCover should throw EpubPackageException if the image referenced by the cover manifest item in the EPUB 3 file is a remote resource and BookCoverReaderOptions is null")] + public void ReadBookCoverForEpub3WithRemoteManifestItemImageAndDefaultOptionsTest() { EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_3); epubSchema.Package.Manifest.Items.Add(new EpubManifestItem @@ -311,10 +487,34 @@ public void ReadBookCoverForEpub3WithRemoteManifestItemImageTest() TestFailingReadOperation(epubSchema, imageContentRefs); } + [Fact(DisplayName = "ReadBookCover should return null if the image referenced by the cover manifest item in the EPUB 3 file is a remote resource and IgnoreRemoteContentFileError = true")] + public void ReadBookCoverForEpub3WithRemoteManifestItemImageAndIgnoreRemoteContentFileErrorTest() + { + EpubSchema epubSchema = CreateEmptyEpubSchema(EpubVersion.EPUB_3); + epubSchema.Package.Manifest.Items.Add(new EpubManifestItem + ( + id: "cover-image", + href: REMOTE_COVER_FILE_HREF, + mediaType: COVER_FILE_CONTENT_MIME_TYPE, + properties: + [ + EpubManifestProperty.COVER_IMAGE + ] + )); + EpubRemoteByteContentFileRef remoteTestImageFileRef = CreateRemoteTestImageFileRef(); + EpubContentCollectionRef imageContentRefs = CreateImageContentRefs(remoteImageFileRef: remoteTestImageFileRef); + BookCoverReaderOptions bookCoverReaderOptions = new() + { + IgnoreRemoteContentFileError = true + }; + TestSuccessfulReadOperation(epubSchema, null, bookCoverReaderOptions, imageContentRefs); + } + private static void TestSuccessfulReadOperation(EpubSchema epubSchema, EpubLocalByteContentFileRef? expectedLocalCoverImageFileRef, - BookCoverReaderOptions? bookCoverReaderOptions = null) + BookCoverReaderOptions? bookCoverReaderOptions = null, + EpubContentCollectionRef? imageContentRefs = null) { - EpubContentCollectionRef imageContentRefs = + imageContentRefs ??= expectedLocalCoverImageFileRef != null ? CreateImageContentRefs(localImageFileRef: expectedLocalCoverImageFileRef) : new EpubContentCollectionRef(); diff --git a/Source/VersOne.Epub.Test/Unit/Readers/BookReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/BookReaderTests.cs index 117f446..ef7d800 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/BookReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/BookReaderTests.cs @@ -16,6 +16,15 @@ public BookReaderTests() environmentDependencies = new TestEnvironmentDependencies(); } + private static EpubReaderOptions EpubReaderOptionsWithIgnoreMissingPackageFile => + new() + { + PackageReaderOptions = new PackageReaderOptions() + { + IgnoreMissingPackageFile = true + } + }; + [Fact(DisplayName = "Constructing a BookReader instance with non-null constructor parameters should succeed")] public void ConstructorWithNonNullParametersTest() { @@ -41,7 +50,7 @@ public void ReadMinimalBookFromFileTest() environmentDependencies.FileSystem = new TestFileSystem(EPUB_FILE_PATH, testEpubFile); EpubBook expectedEpubBook = TestEpubBooks.CreateMinimalTestEpubBook(EPUB_FILE_PATH); BookReader bookReader = new(environmentDependencies, null); - EpubBook actualEpubBook = bookReader.ReadBook(EPUB_FILE_PATH); + EpubBook? actualEpubBook = bookReader.ReadBook(EPUB_FILE_PATH); EpubBookComparer.CompareEpubBooks(expectedEpubBook, actualEpubBook); } @@ -59,7 +68,7 @@ public void ReadMinimalEpub2BookWithoutNavigationFromFileTest() } }; BookReader bookReader = new(environmentDependencies, epubReaderOptions); - EpubBook actualEpubBook = bookReader.ReadBook(EPUB_FILE_PATH); + EpubBook? actualEpubBook = bookReader.ReadBook(EPUB_FILE_PATH); EpubBookComparer.CompareEpubBooks(expectedEpubBook, actualEpubBook); } @@ -70,7 +79,7 @@ public void ReadBookFromFileWithoutDownloadingRemoteFilesTest() environmentDependencies.FileSystem = new TestFileSystem(EPUB_FILE_PATH, testEpubFile); EpubBook expectedEpubBook = TestEpubBooks.CreateFullTestEpubBook(EPUB_FILE_PATH, populateRemoteFilesContents: false); BookReader bookReader = new(environmentDependencies, null); - EpubBook actualEpubBook = bookReader.ReadBook(EPUB_FILE_PATH); + EpubBook? actualEpubBook = bookReader.ReadBook(EPUB_FILE_PATH); EpubBookComparer.CompareEpubBooks(expectedEpubBook, actualEpubBook); } @@ -81,7 +90,7 @@ public async Task ReadBookFromFileAsyncWithoutDownloadingRemoteFilesTest() environmentDependencies.FileSystem = new TestFileSystem(EPUB_FILE_PATH, testEpubFile); EpubBook expectedEpubBook = TestEpubBooks.CreateFullTestEpubBook(EPUB_FILE_PATH, populateRemoteFilesContents: false); BookReader bookReader = new(environmentDependencies, null); - EpubBook actualEpubBook = await bookReader.ReadBookAsync(EPUB_FILE_PATH); + EpubBook? actualEpubBook = await bookReader.ReadBookAsync(EPUB_FILE_PATH); EpubBookComparer.CompareEpubBooks(expectedEpubBook, actualEpubBook); } @@ -93,7 +102,7 @@ public void ReadBookFromStreamWithoutDownloadingRemoteFilesTest() environmentDependencies.FileSystem = new TestFileSystem(epubFileStream, testEpubFile); EpubBook expectedEpubBook = TestEpubBooks.CreateFullTestEpubBook(null, populateRemoteFilesContents: false); BookReader bookReader = new(environmentDependencies, null); - EpubBook actualEpubBook = bookReader.ReadBook(epubFileStream); + EpubBook? actualEpubBook = bookReader.ReadBook(epubFileStream); EpubBookComparer.CompareEpubBooks(expectedEpubBook, actualEpubBook); } @@ -105,10 +114,52 @@ public async Task ReadBookFromStreamAsyncWithoutDownloadingRemoteFilesTest() environmentDependencies.FileSystem = new TestFileSystem(epubFileStream, testEpubFile); EpubBook expectedEpubBook = TestEpubBooks.CreateFullTestEpubBook(null, populateRemoteFilesContents: false); BookReader bookReader = new(environmentDependencies, null); - EpubBook actualEpubBook = await bookReader.ReadBookAsync(epubFileStream); + EpubBook? actualEpubBook = await bookReader.ReadBookAsync(epubFileStream); EpubBookComparer.CompareEpubBooks(expectedEpubBook, actualEpubBook); } + [Fact(DisplayName = "ReadBook(string) should return null if BookRefReader returned a null EpubBookRef")] + public void ReadBookFromFileWithNullEpubBookRefTest() + { + TestZipFile testEpubFile = TestEpubFiles.CreateTestEpubFileWithoutPackage(); + environmentDependencies.FileSystem = new TestFileSystem(EPUB_FILE_PATH, testEpubFile); + BookReader bookReader = new(environmentDependencies, EpubReaderOptionsWithIgnoreMissingPackageFile); + EpubBook? actualEpubBook = bookReader.ReadBook(EPUB_FILE_PATH); + Assert.Null(actualEpubBook); + } + + [Fact(DisplayName = "ReadBookAsync(string) should return null if BookRefReader returned a null EpubBookRef")] + public async Task ReadBookFromFileAsyncWithNullEpubBookRefTest() + { + TestZipFile testEpubFile = TestEpubFiles.CreateTestEpubFileWithoutPackage(); + environmentDependencies.FileSystem = new TestFileSystem(EPUB_FILE_PATH, testEpubFile); + BookReader bookReader = new(environmentDependencies, EpubReaderOptionsWithIgnoreMissingPackageFile); + EpubBook? actualEpubBook = await bookReader.ReadBookAsync(EPUB_FILE_PATH); + Assert.Null(actualEpubBook); + } + + [Fact(DisplayName = "ReadBook(Stream) should return null if BookRefReader returned a null EpubBookRef")] + public void ReadBookFromStreamWithNullEpubBookRefTest() + { + TestZipFile testEpubFile = TestEpubFiles.CreateTestEpubFileWithoutPackage(); + MemoryStream epubFileStream = new(); + environmentDependencies.FileSystem = new TestFileSystem(epubFileStream, testEpubFile); + BookReader bookReader = new(environmentDependencies, EpubReaderOptionsWithIgnoreMissingPackageFile); + EpubBook? actualEpubBook = bookReader.ReadBook(epubFileStream); + Assert.Null(actualEpubBook); + } + + [Fact(DisplayName = "ReadBookAsync(Stream) should return null if BookRefReader returned a null EpubBookRef")] + public async Task ReadBookFromStreamAsyncWithNullEpubBookRefTest() + { + TestZipFile testEpubFile = TestEpubFiles.CreateTestEpubFileWithoutPackage(); + MemoryStream epubFileStream = new(); + environmentDependencies.FileSystem = new TestFileSystem(epubFileStream, testEpubFile); + BookReader bookReader = new(environmentDependencies, EpubReaderOptionsWithIgnoreMissingPackageFile); + EpubBook? actualEpubBook = await bookReader.ReadBookAsync(epubFileStream); + Assert.Null(actualEpubBook); + } + [Fact(DisplayName = "Reading a full EPUB book from a file synchronously with downloading remote files should succeed")] public void ReadBookFromFileWithDownloadingRemoteFilesTest() { @@ -117,7 +168,7 @@ public void ReadBookFromFileWithDownloadingRemoteFilesTest() EpubBook expectedEpubBook = TestEpubBooks.CreateFullTestEpubBook(EPUB_FILE_PATH, populateRemoteFilesContents: true); EpubReaderOptions epubReaderOptions = CreateEpubReaderOptionsToDownloadRemoteFiles(); BookReader bookReader = new(environmentDependencies, epubReaderOptions); - EpubBook actualEpubBook = bookReader.ReadBook(EPUB_FILE_PATH); + EpubBook? actualEpubBook = bookReader.ReadBook(EPUB_FILE_PATH); EpubBookComparer.CompareEpubBooks(expectedEpubBook, actualEpubBook); } @@ -129,7 +180,7 @@ public async Task ReadBookFromFileAsyncWithDownloadingRemoteFilesTest() EpubBook expectedEpubBook = TestEpubBooks.CreateFullTestEpubBook(EPUB_FILE_PATH, populateRemoteFilesContents: true); EpubReaderOptions epubReaderOptions = CreateEpubReaderOptionsToDownloadRemoteFiles(); BookReader bookReader = new(environmentDependencies, epubReaderOptions); - EpubBook actualEpubBook = await bookReader.ReadBookAsync(EPUB_FILE_PATH); + EpubBook? actualEpubBook = await bookReader.ReadBookAsync(EPUB_FILE_PATH); EpubBookComparer.CompareEpubBooks(expectedEpubBook, actualEpubBook); } @@ -142,7 +193,7 @@ public void ReadBookFromStreamWithDownloadingRemoteFilesTest() EpubBook expectedEpubBook = TestEpubBooks.CreateFullTestEpubBook(null, populateRemoteFilesContents: true); EpubReaderOptions epubReaderOptions = CreateEpubReaderOptionsToDownloadRemoteFiles(); BookReader bookReader = new(environmentDependencies, epubReaderOptions); - EpubBook actualEpubBook = bookReader.ReadBook(epubFileStream); + EpubBook? actualEpubBook = bookReader.ReadBook(epubFileStream); EpubBookComparer.CompareEpubBooks(expectedEpubBook, actualEpubBook); } @@ -155,7 +206,7 @@ public async Task ReadBookFromStreamAsyncWithDownloadingRemoteFilesTest() EpubBook expectedEpubBook = TestEpubBooks.CreateFullTestEpubBook(null, populateRemoteFilesContents: true); EpubReaderOptions epubReaderOptions = CreateEpubReaderOptionsToDownloadRemoteFiles(); BookReader bookReader = new(environmentDependencies, epubReaderOptions); - EpubBook actualEpubBook = await bookReader.ReadBookAsync(epubFileStream); + EpubBook? actualEpubBook = await bookReader.ReadBookAsync(epubFileStream); EpubBookComparer.CompareEpubBooks(expectedEpubBook, actualEpubBook); } @@ -170,7 +221,7 @@ public void ReadBookFromFileWithNullContentDownloaderOptionsTest() ContentDownloaderOptions = null! }; BookReader bookReader = new(environmentDependencies, epubReaderOptions); - EpubBook actualEpubBook = bookReader.ReadBook(EPUB_FILE_PATH); + EpubBook? actualEpubBook = bookReader.ReadBook(EPUB_FILE_PATH); EpubBookComparer.CompareEpubBooks(expectedEpubBook, actualEpubBook); } diff --git a/Source/VersOne.Epub.Test/Unit/Readers/BookRefReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/BookRefReaderTests.cs index 549201d..a7b0bf8 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/BookRefReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/BookRefReaderTests.cs @@ -16,6 +16,15 @@ public BookRefReaderTests() environmentDependencies = new TestEnvironmentDependencies(); } + private static EpubReaderOptions EpubReaderOptionsWithIgnoreMissingPackageFile => + new() + { + PackageReaderOptions = new PackageReaderOptions() + { + IgnoreMissingPackageFile = true + } + }; + [Fact(DisplayName = "Constructing a BookRefReader instance with non-null constructor parameters should succeed")] public void ConstructorWithNonNullParametersTest() { @@ -41,29 +50,29 @@ public void OpenMinimalBookFromFileTest() environmentDependencies.FileSystem = new TestFileSystem(EPUB_FILE_PATH, testEpubFile); EpubBookRef expectedEpubBookRef = TestEpubBookRefs.CreateMinimalTestEpubBookRef(testEpubFile, EPUB_FILE_PATH); BookRefReader bookRefReader = new(environmentDependencies, null); - EpubBookRef actualEpubBookRef = bookRefReader.OpenBook(EPUB_FILE_PATH); + EpubBookRef? actualEpubBookRef = bookRefReader.OpenBook(EPUB_FILE_PATH); EpubBookRefComparer.CompareEpubBookRefs(expectedEpubBookRef, actualEpubBookRef); } - [Fact(DisplayName = "Opening a full EPUB book from a file asynchronously should succeed")] - public async Task OpenBookFromFileAsyncTest() + [Fact(DisplayName = "Opening a full EPUB book from a file synchronously should succeed")] + public void OpenBookFromFileTest() { TestZipFile testEpubFile = TestEpubFiles.CreateFullTestEpubFile(); environmentDependencies.FileSystem = new TestFileSystem(EPUB_FILE_PATH, testEpubFile); EpubBookRef expectedEpubBookRef = TestEpubBookRefs.CreateFullTestEpubBookRef(testEpubFile, EPUB_FILE_PATH); BookRefReader bookRefReader = new(environmentDependencies, null); - EpubBookRef actualEpubBookRef = await bookRefReader.OpenBookAsync(EPUB_FILE_PATH); + EpubBookRef? actualEpubBookRef = bookRefReader.OpenBook(EPUB_FILE_PATH); EpubBookRefComparer.CompareEpubBookRefs(expectedEpubBookRef, actualEpubBookRef); } - [Fact(DisplayName = "Opening a full EPUB book from a file synchronously should succeed")] - public void OpenBookFromFileTest() + [Fact(DisplayName = "Opening a full EPUB book from a file asynchronously should succeed")] + public async Task OpenBookFromFileAsyncTest() { TestZipFile testEpubFile = TestEpubFiles.CreateFullTestEpubFile(); environmentDependencies.FileSystem = new TestFileSystem(EPUB_FILE_PATH, testEpubFile); EpubBookRef expectedEpubBookRef = TestEpubBookRefs.CreateFullTestEpubBookRef(testEpubFile, EPUB_FILE_PATH); BookRefReader bookRefReader = new(environmentDependencies, null); - EpubBookRef actualEpubBookRef = bookRefReader.OpenBook(EPUB_FILE_PATH); + EpubBookRef? actualEpubBookRef = await bookRefReader.OpenBookAsync(EPUB_FILE_PATH); EpubBookRefComparer.CompareEpubBookRefs(expectedEpubBookRef, actualEpubBookRef); } @@ -75,7 +84,7 @@ public void OpenBookFromStreamTest() environmentDependencies.FileSystem = new TestFileSystem(epubFileStream, testEpubFile); EpubBookRef expectedEpubBookRef = TestEpubBookRefs.CreateFullTestEpubBookRef(testEpubFile, null); BookRefReader bookRefReader = new(environmentDependencies, null); - EpubBookRef actualEpubBookRef = bookRefReader.OpenBook(epubFileStream); + EpubBookRef? actualEpubBookRef = bookRefReader.OpenBook(epubFileStream); EpubBookRefComparer.CompareEpubBookRefs(expectedEpubBookRef, actualEpubBookRef); } @@ -87,10 +96,52 @@ public async Task OpenBookFromStreamAsyncTest() environmentDependencies.FileSystem = new TestFileSystem(epubFileStream, testEpubFile); EpubBookRef expectedEpubBookRef = TestEpubBookRefs.CreateFullTestEpubBookRef(testEpubFile, null); BookRefReader bookRefReader = new(environmentDependencies, null); - EpubBookRef actualEpubBookRef = await bookRefReader.OpenBookAsync(epubFileStream); + EpubBookRef? actualEpubBookRef = await bookRefReader.OpenBookAsync(epubFileStream); EpubBookRefComparer.CompareEpubBookRefs(expectedEpubBookRef, actualEpubBookRef); } + [Fact(DisplayName = "OpenBook(string) should return null if SchemaReader returned a null schema")] + public void OpenBookFromFileWillNullSchemaTest() + { + TestZipFile testEpubFile = TestEpubFiles.CreateTestEpubFileWithoutPackage(); + environmentDependencies.FileSystem = new TestFileSystem(EPUB_FILE_PATH, testEpubFile); + BookRefReader bookRefReader = new(environmentDependencies, EpubReaderOptionsWithIgnoreMissingPackageFile); + EpubBookRef? actualEpubBookRef = bookRefReader.OpenBook(EPUB_FILE_PATH); + Assert.Null(actualEpubBookRef); + } + + [Fact(DisplayName = "OpenBookAsync(string) should return null if SchemaReader returned a null schema")] + public async Task OpenBookFromFileAsyncWillNullSchemaTest() + { + TestZipFile testEpubFile = TestEpubFiles.CreateTestEpubFileWithoutPackage(); + environmentDependencies.FileSystem = new TestFileSystem(EPUB_FILE_PATH, testEpubFile); + BookRefReader bookRefReader = new(environmentDependencies, EpubReaderOptionsWithIgnoreMissingPackageFile); + EpubBookRef? actualEpubBookRef = await bookRefReader.OpenBookAsync(EPUB_FILE_PATH); + Assert.Null(actualEpubBookRef); + } + + [Fact(DisplayName = "OpenBook(Stream) should return null if SchemaReader returned a null schema")] + public void OpenBookFromStreamWillNullSchemaTest() + { + TestZipFile testEpubFile = TestEpubFiles.CreateTestEpubFileWithoutPackage(); + MemoryStream epubFileStream = new(); + environmentDependencies.FileSystem = new TestFileSystem(epubFileStream, testEpubFile); + BookRefReader bookRefReader = new(environmentDependencies, EpubReaderOptionsWithIgnoreMissingPackageFile); + EpubBookRef? actualEpubBookRef = bookRefReader.OpenBook(epubFileStream); + Assert.Null(actualEpubBookRef); + } + + [Fact(DisplayName = "OpenBookAsync(Stream) should return null if SchemaReader returned a null schema")] + public async Task OpenBookFromStreamAsyncWillNullSchemaTest() + { + TestZipFile testEpubFile = TestEpubFiles.CreateTestEpubFileWithoutPackage(); + MemoryStream epubFileStream = new(); + environmentDependencies.FileSystem = new TestFileSystem(epubFileStream, testEpubFile); + BookRefReader bookRefReader = new(environmentDependencies, EpubReaderOptionsWithIgnoreMissingPackageFile); + EpubBookRef? actualEpubBookRef = await bookRefReader.OpenBookAsync(epubFileStream); + Assert.Null(actualEpubBookRef); + } + [Fact(DisplayName = "OpenBook should throw FileNotFoundException if the specified file does not exist")] public void OpenBookFromFileWithMissingFileTest() { diff --git a/Source/VersOne.Epub.Test/Unit/Readers/ContainerFileReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/ContainerFileReaderTests.cs new file mode 100644 index 0000000..bf4f28d --- /dev/null +++ b/Source/VersOne.Epub.Test/Unit/Readers/ContainerFileReaderTests.cs @@ -0,0 +1,169 @@ +using System.Xml; +using VersOne.Epub.Internal; +using VersOne.Epub.Options; +using VersOne.Epub.Test.Unit.Mocks; + +namespace VersOne.Epub.Test.Unit.Readers +{ + public class ContainerFileReaderTests + { + private const string EPUB_CONTAINER_FILE_PATH = "META-INF/container.xml"; + private const string OPF_PACKAGE_FILE_PATH_FOR_CORRECT_CONTAINER_FILE = "OEBPS/content.opf"; + + private const string CORRECT_CONTAINER_FILE = $""" + + + + + + + """; + + private const string INCORRECT_CONTAINER_FILE_NO_FULL_PATH_ATTRIBUTE = $""" + + + + + + + """; + + private const string INCORRECT_CONTAINER_FILE_NO_ROOTFILE_ELEMENT = $""" + + + + + + """; + + private const string INCORRECT_CONTAINER_FILE_NO_ROOTFILES_ELEMENT = $""" + + + + """; + + private const string INCORRECT_CONTAINER_FILE_NO_CONTAINER_ELEMENT = $""" + + + """; + + [Fact(DisplayName = "Constructing a ContainerFileReader instance with a non-null epubReaderOptions parameter should succeed")] + public void ConstructorWithNonNullEpubReaderOptionsTest() + { + _ = new ContainerFileReader(new EpubReaderOptions()); + } + + [Fact(DisplayName = "Constructing a ContainerFileReader instance with a null epubReaderOptions parameter should succeed")] + public void ConstructorWithNullEpubReaderOptionsTest() + { + _ = new ContainerFileReader(null); + } + + [Fact(DisplayName = "Constructing a ContainerFileReader instance with epubReaderOptions.ContainerFileReaderOptions = null should succeed")] + public void ConstructorWithNullPackageReaderOptionsTest() + { + EpubReaderOptions epubReaderOptions = new() + { + ContainerFileReaderOptions = null! + }; + _ = new ContainerFileReader(epubReaderOptions); + } + + [Fact(DisplayName = "Getting OPF package file path from a ZIP archive with a correct container file should succeed")] + public async Task GetPackageFilePathAsyncWithCorrectContainerFileTest() + { + TestZipFile testZipFile = new(); + testZipFile.AddEntry(EPUB_CONTAINER_FILE_PATH, CORRECT_CONTAINER_FILE); + ContainerFileReader containerFileReader = new(); + string? actualPackageFilePath = await containerFileReader.GetPackageFilePathAsync(testZipFile); + Assert.Equal(OPF_PACKAGE_FILE_PATH_FOR_CORRECT_CONTAINER_FILE, actualPackageFilePath); + } + + [Fact(DisplayName = "GetPackageFilePathAsync should throw EpubContainerException if the ZIP archive doesn't have the container file and no ContainerFileReaderOptions are provided")] + public async Task GetPackageFilePathAsyncWithNoContainerFileAndDefaultOptionsTest() + { + TestZipFile testZipFile = new(); + ContainerFileReader containerFileReader = new(); + await Assert.ThrowsAsync(() => containerFileReader.GetPackageFilePathAsync(testZipFile)); + } + + [Fact(DisplayName = "GetPackageFilePathAsync should return null if the ZIP archive doesn't have the container file and IgnoreMissingContainerFile = true")] + public async Task GetPackageFilePathAsyncWithNoContainerFileAndIgnoreMissingContainerFileTest() + { + TestZipFile testZipFile = new(); + EpubReaderOptions epubReaderOptions = new() + { + ContainerFileReaderOptions = new() + { + IgnoreMissingContainerFile = true + } + }; + ContainerFileReader containerFileReader = new(epubReaderOptions); + string? actualPackageFilePath = await containerFileReader.GetPackageFilePathAsync(testZipFile); + Assert.Null(actualPackageFilePath); + } + + [Fact(DisplayName = "GetPackageFilePathAsync should throw EpubContainerException with an inner XmlException if the container document is not a valid XML file and no ContainerFileReaderOptions are provided")] + public async Task GetPackageFilePathAsyncWithInvalidXmlFileAndDefaultOptionsTest() + { + TestZipFile testZipFile = new(); + testZipFile.AddEntry(EPUB_CONTAINER_FILE_PATH, "not a valid XML file"); + ContainerFileReader containerFileReader = new(); + EpubContainerException outerException = + await Assert.ThrowsAsync(() => containerFileReader.GetPackageFilePathAsync(testZipFile)); + Assert.NotNull(outerException.InnerException); + Assert.Equal(typeof(XmlException), outerException.InnerException.GetType()); + } + + [Fact(DisplayName = "GetPackageFilePathAsync should return null if the container document is not a valid XML file and IgnoreContainerFileIsNotValidXmlError = true")] + public async Task GetPackageFilePathAsyncWithInvalidXmlFileAndIgnoreContainerFileIsNotValidXmlErrorTest() + { + TestZipFile testZipFile = new(); + testZipFile.AddEntry(EPUB_CONTAINER_FILE_PATH, "not a valid XML file"); + EpubReaderOptions epubReaderOptions = new() + { + ContainerFileReaderOptions = new() + { + IgnoreContainerFileIsNotValidXmlError = true + } + }; + ContainerFileReader containerFileReader = new(epubReaderOptions); + string? actualPackageFilePath = await containerFileReader.GetPackageFilePathAsync(testZipFile); + Assert.Null(actualPackageFilePath); + } + + [Theory(DisplayName = "GetPackageFilePathAsync should throw EpubContainerException if the container document doesn't have the OPF package file path and no ContainerFileReaderOptions are provided")] + [InlineData(INCORRECT_CONTAINER_FILE_NO_FULL_PATH_ATTRIBUTE)] + [InlineData(INCORRECT_CONTAINER_FILE_NO_ROOTFILE_ELEMENT)] + [InlineData(INCORRECT_CONTAINER_FILE_NO_ROOTFILES_ELEMENT)] + [InlineData(INCORRECT_CONTAINER_FILE_NO_CONTAINER_ELEMENT)] + public async Task GetPackageFilePathAsyncWithIncorrectContainerAndDefaultOptionsFileTest(string containerFileContent) + { + TestZipFile testZipFile = new(); + testZipFile.AddEntry(EPUB_CONTAINER_FILE_PATH, containerFileContent); + ContainerFileReader containerFileReader = new(); + await Assert.ThrowsAsync(() => containerFileReader.GetPackageFilePathAsync(testZipFile)); + } + + [Theory(DisplayName = "GetPackageFilePathAsync should return null if the container document doesn't have the OPF package file path and IgnoreMissingPackageFilePathError = true")] + [InlineData(INCORRECT_CONTAINER_FILE_NO_FULL_PATH_ATTRIBUTE)] + [InlineData(INCORRECT_CONTAINER_FILE_NO_ROOTFILE_ELEMENT)] + [InlineData(INCORRECT_CONTAINER_FILE_NO_ROOTFILES_ELEMENT)] + [InlineData(INCORRECT_CONTAINER_FILE_NO_CONTAINER_ELEMENT)] + public async Task GetPackageFilePathAsyncWithIncorrectContainerAndIgnoreMissingPackageFilePathErrorTest(string containerFileContent) + { + TestZipFile testZipFile = new(); + testZipFile.AddEntry(EPUB_CONTAINER_FILE_PATH, containerFileContent); + EpubReaderOptions epubReaderOptions = new() + { + ContainerFileReaderOptions = new() + { + IgnoreMissingPackageFilePathError = true + } + }; + ContainerFileReader containerFileReader = new(epubReaderOptions); + string? actualPackageFilePath = await containerFileReader.GetPackageFilePathAsync(testZipFile); + Assert.Null(actualPackageFilePath); + } + } +} diff --git a/Source/VersOne.Epub.Test/Unit/Readers/ContentReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/ContentReaderTests.cs index c606555..2eb3d01 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/ContentReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/ContentReaderTests.cs @@ -50,8 +50,8 @@ public void ParseContentMapWithFullEpubSchemaTest() ( manifest: new EpubManifest ( - items: new List() - { + items: + [ new ( id: "item-html", @@ -255,20 +255,20 @@ public void ParseContentMapWithFullEpubSchemaTest() id: "item-cover", href: "cover.jpg", mediaType: "image/jpeg", - properties: new List - { + properties: + [ EpubManifestProperty.COVER_IMAGE - } + ] ), new ( id: "item-toc", href: "toc.html", mediaType: "application/xhtml+xml", - properties: new List - { + properties: + [ EpubManifestProperty.NAV - } + ] ), new ( @@ -300,7 +300,7 @@ public void ParseContentMapWithFullEpubSchemaTest() href: "https://example.com/books/123/audio.mp3", mediaType: "audio/mpeg" ) - } + ] ) ); EpubLocalTextContentFileRef expectedHtmlFileRef = CreateLocalTextFileRef("text.html", EpubContentType.XHTML_1_1, "application/xhtml+xml"); @@ -343,25 +343,25 @@ public void ParseContentMapWithFullEpubSchemaTest() EpubRemoteByteContentFileRef expectedRemoteJpgFileRef = CreateRemoteByteFileRef("https://example.com/books/123/image.jpg", EpubContentType.IMAGE_JPEG, "image/jpeg"); EpubRemoteByteContentFileRef expectedRemoteTtfFileRef = CreateRemoteByteFileRef("https://example.com/books/123/font.ttf", EpubContentType.FONT_TRUETYPE, "font/truetype"); EpubRemoteByteContentFileRef expectedRemoteMp3FileRef = CreateRemoteByteFileRef("https://example.com/books/123/audio.mp3", EpubContentType.AUDIO_MP3, "audio/mpeg"); - List expectedHtmlLocal = new() - { + List expectedHtmlLocal = + [ expectedHtmlFileRef, expectedTocFileRef - }; - List expectedHtmlRemote = new() - { + ]; + List expectedHtmlRemote = + [ expectedRemoteHtmlFileRef - }; - List expectedCssLocal = new() - { + ]; + List expectedCssLocal = + [ expectedCssFileRef - }; - List expectedCssRemote = new() - { + ]; + List expectedCssRemote = + [ expectedRemoteCssFileRef - }; - List expectedImagesLocal = new() - { + ]; + List expectedImagesLocal = + [ expectedGifFileRef, expectedJpgFileRef, expectedPngFileRef, @@ -369,13 +369,13 @@ public void ParseContentMapWithFullEpubSchemaTest() expectedWebpFileRef, expectedBmpFileRef, expectedCoverFileRef - }; - List expectedImagesRemote = new() - { + ]; + List expectedImagesRemote = + [ expectedRemoteJpgFileRef - }; - List expectedFontsLocal = new() - { + ]; + List expectedFontsLocal = + [ expectedTtf1FileRef, expectedTtf2FileRef, expectedTtf3FileRef, @@ -387,24 +387,24 @@ public void ParseContentMapWithFullEpubSchemaTest() expectedWoff11FileRef, expectedWoff12FileRef, expectedWoff2FileRef - }; - List expectedFontsRemote = new() - { + ]; + List expectedFontsRemote = + [ expectedRemoteTtfFileRef - }; - List expectedAudioLocal = new() - { + ]; + List expectedAudioLocal = + [ expectedMp3FileRef, expectedMp4FileRef, expectedOgg1FileRef, expectedOgg2FileRef - }; - List expectedAudioRemote = new() - { + ]; + List expectedAudioRemote = + [ expectedRemoteMp3FileRef - }; - List expectedAllFilesLocal = new() - { + ]; + List expectedAllFilesLocal = + [ expectedHtmlFileRef, expectedDtbFileRef, expectedNcxFileRef, @@ -440,15 +440,15 @@ public void ParseContentMapWithFullEpubSchemaTest() expectedVideoFileRef, expectedCoverFileRef, expectedTocFileRef - }; - List expectedAllFilesRemote = new() - { + ]; + List expectedAllFilesRemote = + [ expectedRemoteHtmlFileRef, expectedRemoteCssFileRef, expectedRemoteJpgFileRef, expectedRemoteTtfFileRef, expectedRemoteMp3FileRef - }; + ]; EpubContentRef expectedContentMap = new ( cover: expectedCoverFileRef, @@ -491,19 +491,19 @@ public void ParseContentMapWithRemoteTextContentFilesTest() ); EpubRemoteTextContentFileRef expectedFileRef1 = CreateRemoteTextFileRef("https://example.com/books/123/test.html", EpubContentType.XHTML_1_1, "application/xhtml+xml"); EpubRemoteTextContentFileRef expectedFileRef2 = CreateRemoteTextFileRef("https://example.com/books/123/test.css", EpubContentType.CSS, "text/css"); - List expectedHtmlRemote = new() - { + List expectedHtmlRemote = + [ expectedFileRef1 - }; - List expectedCssRemote = new() - { + ]; + List expectedCssRemote = + [ expectedFileRef2 - }; - List expectedAllFilesRemote = new() - { + ]; + List expectedAllFilesRemote = + [ expectedFileRef1, expectedFileRef2 - }; + ]; EpubContentRef expectedContentMap = new ( html: new EpubContentCollectionRef(null, expectedHtmlRemote.AsReadOnly()), @@ -522,8 +522,8 @@ public void ParseContentMapWithRemoteByteContentFilesTest() ( manifest: new EpubManifest ( - items: new List() - { + items: + [ new ( id: "item-1", @@ -536,24 +536,24 @@ public void ParseContentMapWithRemoteByteContentFilesTest() href: "https://example.com/books/123/font.ttf", mediaType: "font/truetype" ) - } + ] ) ); EpubRemoteByteContentFileRef expectedFileRef1 = CreateRemoteByteFileRef("https://example.com/books/123/image.jpg", EpubContentType.IMAGE_JPEG, "image/jpeg"); EpubRemoteByteContentFileRef expectedFileRef2 = CreateRemoteByteFileRef("https://example.com/books/123/font.ttf", EpubContentType.FONT_TRUETYPE, "font/truetype"); - List expectedImagesRemote = new() - { + List expectedImagesRemote = + [ expectedFileRef1 - }; - List expectedFontsRemote = new() - { + ]; + List expectedFontsRemote = + [ expectedFileRef2 - }; - List expectedAllFilesRemote = new() - { + ]; + List expectedAllFilesRemote = + [ expectedFileRef1, expectedFileRef2 - }; + ]; EpubContentRef expectedContentMap = new ( images: new EpubContentCollectionRef(null, expectedImagesRemote.AsReadOnly()), @@ -565,32 +565,80 @@ public void ParseContentMapWithRemoteByteContentFilesTest() EpubContentRefComparer.CompareEpubContentRefs(expectedContentMap, actualContentMap); } - [Fact(DisplayName = "ParseContentMap should throw EpubPackageException if EPUB 3 navigation document is a remote resource")] - public void ParseContentMapWithRemoteNavigationDocumentTest() + [Fact(DisplayName = "ParseContentMap should throw EpubPackageException if EPUB 3 navigation document is a remote resource and EpubReaderOptions is null")] + public void ParseContentMapWithRemoteNavigationDocumentAndDefaultOptionsTest() { EpubSchema epubSchema = CreateEpubSchema ( manifest: new EpubManifest ( - items: new List() - { + items: + [ new ( id: "item-toc", href: "http://example.com/toc.html", mediaType: "application/xhtml+xml", - properties: new List - { + properties: + [ EpubManifestProperty.NAV - } + ] ) - } + ] ) ); ContentReader contentReader = new(environmentDependencies); Assert.Throws(() => contentReader.ParseContentMap(epubSchema, new TestZipFile())); } + [Fact(DisplayName = "ParseContentMap should succeed if EPUB 3 navigation document is a remote resource and IgnoreRemoteEpub3NavigationFileError = true")] + public void ParseContentMapWithRemoteNavigationDocumentAndIgnoreRemoteEpub3NavigationFileErrorTest() + { + EpubSchema epubSchema = CreateEpubSchema + ( + manifest: new EpubManifest + ( + items: + [ + new + ( + id: "item-toc", + href: "http://example.com/toc.html", + mediaType: "application/xhtml+xml", + properties: + [ + EpubManifestProperty.NAV + ] + ) + ] + ) + ); + EpubRemoteTextContentFileRef expectedNavigationFileRef = CreateRemoteTextFileRef("http://example.com/toc.html", EpubContentType.XHTML_1_1, "application/xhtml+xml"); + List expectedHtmlRemote = + [ + expectedNavigationFileRef + ]; + List expectedAllFilesRemote = + [ + expectedNavigationFileRef + ]; + EpubContentRef expectedContentMap = new + ( + html: new EpubContentCollectionRef(null, expectedHtmlRemote.AsReadOnly()), + allFiles: new EpubContentCollectionRef(null, expectedAllFilesRemote.AsReadOnly()) + ); + EpubReaderOptions epubReaderOptions = new() + { + ContentReaderOptions = new() + { + IgnoreRemoteEpub3NavigationFileError = true + } + }; + ContentReader contentReader = new(environmentDependencies, epubReaderOptions); + EpubContentRef actualContentMap = contentReader.ParseContentMap(epubSchema, new TestZipFile()); + EpubContentRefComparer.CompareEpubContentRefs(expectedContentMap, actualContentMap); + } + private static EpubSchema CreateEpubSchema(EpubManifest? manifest = null) { return new diff --git a/Source/VersOne.Epub.Test/Unit/Readers/Epub2NcxReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/Epub2NcxReaderTests.cs index f30b382..e6568fa 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/Epub2NcxReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/Epub2NcxReaderTests.cs @@ -1,4 +1,5 @@ -using VersOne.Epub.Internal; +using System.Xml; +using VersOne.Epub.Internal; using VersOne.Epub.Options; using VersOne.Epub.Schema; using VersOne.Epub.Test.Comparers; @@ -132,6 +133,8 @@ public class Epub2NcxReaderTests private const string NCX_FILE_WITHOUT_HEAD_ELEMENT = """ + + """; @@ -140,6 +143,7 @@ public class Epub2NcxReaderTests + """; @@ -249,6 +253,9 @@ public class Epub2NcxReaderTests + + Chapter 1 + @@ -394,15 +401,15 @@ public class Epub2NcxReaderTests metadata: new EpubMetadata(), manifest: new EpubManifest ( - items: new List() - { + items: + [ new ( id: TOC_ID, href: NCX_FILE_NAME, mediaType: "application/x-dtbncx+xml" ) - } + ] ), spine: new EpubSpine ( @@ -429,8 +436,8 @@ public class Epub2NcxReaderTests filePath: NCX_FILE_PATH, head: new Epub2NcxHead ( - items: new List - { + items: + [ new ( name: "dtb:uid", @@ -462,25 +469,25 @@ public class Epub2NcxReaderTests content: "https://example.com/books/123/ncx", scheme: "URI" ) - } + ] ), docTitle: "Test title", - docAuthors: new List() - { + docAuthors: + [ "John Doe", "Jane Doe" - }, + ], navMap: new Epub2NcxNavigationMap ( - items: new List() - { + items: + [ new ( id: "navpoint-1", @class: "chapter", playOrder: "1", - navigationLabels: new List() - { + navigationLabels: + [ new ( text: "Chapter 1" @@ -489,26 +496,26 @@ public class Epub2NcxReaderTests ( text: "Capitolo 1" ) - }, + ], content: new Epub2NcxContent ( id: "content-1", source: "chapter1.html" ), - childNavigationPoints: new List() - { + childNavigationPoints: + [ new ( id: "navpoint-1-1", @class: "section", playOrder: null, - navigationLabels: new List() - { + navigationLabels: + [ new ( text: "Chapter 1.1" ) - }, + ], content: new Epub2NcxContent ( id: "content-1-1", @@ -521,13 +528,13 @@ public class Epub2NcxReaderTests id: "navpoint-1-2", @class: "section", playOrder: null, - navigationLabels: new List() - { + navigationLabels: + [ new ( text: "Chapter 1.2" ) - }, + ], content: new Epub2NcxContent ( id: "content-1-2", @@ -535,29 +542,29 @@ public class Epub2NcxReaderTests ), childNavigationPoints: null ) - } + ] ), new ( id: "navpoint-2", - navigationLabels: new List() - { + navigationLabels: + [ new ( text: "Chapter 2" ) - }, + ], content: new Epub2NcxContent ( source: "chapter2.html" ) ) - } + ] ), pageList: new Epub2NcxPageList ( - items: new List() - { + items: + [ new ( id: "page-target-1", @@ -565,8 +572,8 @@ public class Epub2NcxReaderTests type: Epub2NcxPageTargetType.FRONT, @class: "front-matter", playOrder: "1", - navigationLabels: new List() - { + navigationLabels: + [ new ( text: "1" @@ -575,7 +582,7 @@ public class Epub2NcxReaderTests ( text: "I" ) - }, + ], content: new Epub2NcxContent ( source: "front.html" @@ -584,29 +591,29 @@ public class Epub2NcxReaderTests new ( type: Epub2NcxPageTargetType.NORMAL, - navigationLabels: new List() - { + navigationLabels: + [ new ( text: "2" ) - }, + ], content: new Epub2NcxContent ( id: "content-2", source: "chapter1.html#page-2" ) ) - } + ] ), - navLists: new List() - { + navLists: + [ new ( id: "navlist-1", @class: "navlist-illustrations", - navigationLabels: new List() - { + navigationLabels: + [ new ( text: "List of Illustrations" @@ -615,17 +622,17 @@ public class Epub2NcxReaderTests ( text: "Illustrazioni" ) - }, - navigationTargets: new List() - { + ], + navigationTargets: + [ new ( id: "navtarget-1", value: "Illustration 1", @class: "illustration", playOrder: "1", - navigationLabels: new List() - { + navigationLabels: + [ new ( text: "Illustration 1" @@ -634,57 +641,57 @@ public class Epub2NcxReaderTests ( text: "Illustrazione 1" ) - }, + ], content: new Epub2NcxContent ( source: "chapter1.html#illustration-1" ) ) - } + ] ), new ( id: "navlist-2", @class: "navlist-tables", - navigationLabels: new List() - { + navigationLabels: + [ new ( text: "List of Tables" ) - }, - navigationTargets: new List() - { + ], + navigationTargets: + [ new ( id: "navtarget-2", - navigationLabels: new List() - { + navigationLabels: + [ new ( text: "Tables" ) - }, + ], content: null ), new ( id: "navtarget-3", - navigationLabels: new List() - { + navigationLabels: + [ new ( text: "Table 1" ) - }, + ], content: new Epub2NcxContent ( source: "chapter1.html#table-1" ) ) - } + ] ) - } + ] ); private static Epub2Ncx MinimalEpub2NcxWithUnknownPageTargetType => @@ -697,24 +704,24 @@ public class Epub2NcxReaderTests navMap: new Epub2NcxNavigationMap(), pageList: new Epub2NcxPageList ( - items: new List() - { + items: + [ new ( type: Epub2NcxPageTargetType.UNKNOWN, - navigationLabels: new List() - { + navigationLabels: + [ new ( text: "1" ) - }, + ], content: new Epub2NcxContent ( source: "chapter1.html#page-1" ) ) - } + ] ), navLists: null ); @@ -731,6 +738,15 @@ public void ConstructorWithNullEpubReaderOptionsTest() _ = new Epub2NcxReader(null); } + [Fact(DisplayName = "Constructing a Epub2NcxReader instance with a null Epub2NcxReaderOptions property inside the epubReaderOptions parameter should succeed")] + public void ConstructorWithNullEpub2NcxReaderOptionsTest() + { + _ = new Epub2NcxReader(new EpubReaderOptions + { + Epub2NcxReaderOptions = null! + }); + } + [Fact(DisplayName = "Reading a minimal NCX file should succeed")] public async Task ReadEpub2NcxAsyncWithMinimalNcxFileTest() { @@ -761,8 +777,8 @@ public async Task ReadEpub2NcxAsyncWithoutTocTest() Assert.Null(epub2Ncx); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if EpubPackage is missing the manifest item referenced by the spine TOC")] - public async Task ReadEpub2NcxAsyncWithoutTocManifestItemTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if EpubPackage is missing the manifest item referenced by the spine TOC and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutTocManifestItemAndDefaultOptionsTest() { TestZipFile testZipFile = new(); EpubPackage epubPackage = new @@ -781,16 +797,60 @@ public async Task ReadEpub2NcxAsyncWithoutTocManifestItemTest() await Assert.ThrowsAsync(() => epub2NcxReader.ReadEpub2NcxAsync(testZipFile, CONTENT_DIRECTORY_PATH, epubPackage)); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if EPUB file is missing the NCX file specified in the EpubPackage")] - public async Task ReadEpub2NcxAsyncWithoutNcxFileTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should return null if EpubPackage is missing the manifest item referenced by the spine TOC and IgnoreMissingTocManifestItemError = true")] + public async Task ReadEpub2NcxAsyncWithoutTocManifestItemAndIgnoreMissingTocManifestItemErrorTest() + { + TestZipFile testZipFile = new(); + EpubPackage epubPackage = new + ( + uniqueIdentifier: null, + epubVersion: EpubVersion.EPUB_2, + metadata: new EpubMetadata(), + manifest: new EpubManifest(), + spine: new EpubSpine + ( + toc: TOC_ID + ), + guide: null + ); + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new() + { + IgnoreMissingTocManifestItemError = true + } + }; + Epub2NcxReader epub2NcxReader = new(epubReaderOptions); + Epub2Ncx? actualEpub2Ncx = await epub2NcxReader.ReadEpub2NcxAsync(testZipFile, CONTENT_DIRECTORY_PATH, epubPackage); + Assert.Null(actualEpub2Ncx); + } + + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if EPUB file is missing the NCX file specified in the EpubPackage and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutNcxFileAndDefaultOptionsTest() { TestZipFile testZipFile = new(); Epub2NcxReader epub2NcxReader = new(); await Assert.ThrowsAsync(() => epub2NcxReader.ReadEpub2NcxAsync(testZipFile, CONTENT_DIRECTORY_PATH, MinimalEpubPackageWithNcx)); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if the NCX file is larger than 2 GB")] - public async Task ReadEpub2NcxAsyncWithLargeNcxFileTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should return null if EPUB file is missing the NCX file specified in the EpubPackage and IgnoreMissingTocFileError = true")] + public async Task ReadEpub2NcxAsyncWithoutNcxFileAndIgnoreMissingTocFileErrorTest() + { + TestZipFile testZipFile = new(); + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new() + { + IgnoreMissingTocFileError = true + } + }; + Epub2NcxReader epub2NcxReader = new(epubReaderOptions); + Epub2Ncx? actualEpub2Ncx = await epub2NcxReader.ReadEpub2NcxAsync(testZipFile, CONTENT_DIRECTORY_PATH, MinimalEpubPackageWithNcx); + Assert.Null(actualEpub2Ncx); + } + + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if the NCX file is larger than 2 GB and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithLargeNcxFileAndDefaultOptionsTest() { TestZipFile testZipFile = new(); testZipFile.AddEntry(NCX_FILE_PATH, new Test4GbZipFileEntry()); @@ -798,42 +858,165 @@ public async Task ReadEpub2NcxAsyncWithLargeNcxFileTest() await Assert.ThrowsAsync(() => epub2NcxReader.ReadEpub2NcxAsync(testZipFile, CONTENT_DIRECTORY_PATH, MinimalEpubPackageWithNcx)); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if the NCX file has no 'ncx' XML element")] - public async Task ReadEpub2NcxAsyncWithoutNcxElementTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should return null if the NCX file is larger than 2 GB and IgnoreTocFileIsTooLargeError = true")] + public async Task ReadEpub2NcxAsyncWithLargeNcxFileAndIgnoreTocFileIsTooLargeErrorTest() + { + TestZipFile testZipFile = new(); + testZipFile.AddEntry(NCX_FILE_PATH, new Test4GbZipFileEntry()); + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new() + { + IgnoreTocFileIsTooLargeError = true + } + }; + Epub2NcxReader epub2NcxReader = new(epubReaderOptions); + Epub2Ncx? actualEpub2Ncx = await epub2NcxReader.ReadEpub2NcxAsync(testZipFile, CONTENT_DIRECTORY_PATH, MinimalEpubPackageWithNcx); + Assert.Null(actualEpub2Ncx); + } + + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException with an inner XmlException if the NCX file is not a valid XML file and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithInvalidXhtmlFileAndDefaultOptionsTest() + { + TestZipFile testZipFile = CreateTestZipFileWithNcxFile("not a valid XML file"); + Epub2NcxReader epub2NcxReader = new(new EpubReaderOptions()); + Epub2NcxException outerException = + await Assert.ThrowsAsync(() => + epub2NcxReader.ReadEpub2NcxAsync(testZipFile, CONTENT_DIRECTORY_PATH, MinimalEpubPackageWithNcx)); + Assert.NotNull(outerException.InnerException); + Assert.Equal(typeof(XmlException), outerException.InnerException.GetType()); + } + + [Fact(DisplayName = "ReadEpub2NcxAsync should return null if the NCX file is not a valid XML file and IgnoreTocFileIsNotValidXmlError = true")] + public async Task ReadEpub2NcxAsyncWithInvalidXhtmlFileAndIgnoreTocFileIsNotValidXmlErrorTest() + { + TestZipFile testZipFile = CreateTestZipFileWithNcxFile("not a valid XML file"); + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new() + { + IgnoreTocFileIsNotValidXmlError = true + } + }; + Epub2NcxReader epub2NcxReader = new(epubReaderOptions); + Epub2Ncx? actualEpub2Ncx = await epub2NcxReader.ReadEpub2NcxAsync(testZipFile, CONTENT_DIRECTORY_PATH, MinimalEpubPackageWithNcx); + Assert.Null(actualEpub2Ncx); + } + + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if the NCX file has no 'ncx' XML element and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutNcxElementAndDefaultOptionsTest() { await TestFailingReadOperation(NCX_FILE_WITHOUT_NCX_ELEMENT); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if the NCX file has no 'head' XML element")] - public async Task ReadEpub2NcxAsyncWithoutHeadElementTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should return null if the NCX file has no 'ncx' XML element and IgnoreMissingNcxElementError = true")] + public async Task ReadEpub2NcxAsyncWithoutNcxElementAndIgnoreMissingNcxElementErrorTest() + { + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new() + { + IgnoreMissingNcxElementError = true + } + }; + await TestSuccessfulReadOperation(NCX_FILE_WITHOUT_NCX_ELEMENT, null, epubReaderOptions); + } + + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if the NCX file has no 'head' XML element and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutHeadElementAndDefaultOptionsTest() { await TestFailingReadOperation(NCX_FILE_WITHOUT_HEAD_ELEMENT); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if the NCX file has no 'docTitle' XML element")] - public async Task ReadEpub2NcxAsyncWithoutDocTitleElementTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should succeed if the NCX file has no 'head' XML element and IgnoreMissingHeadElementError = true")] + public async Task ReadEpub2NcxAsyncWithoutHeadElementAndIgnoreMissingHeadElementErrorTest() + { + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new() + { + IgnoreMissingHeadElementError = true + } + }; + await TestSuccessfulReadOperation(NCX_FILE_WITHOUT_HEAD_ELEMENT, MinimalEpub2Ncx, epubReaderOptions); + } + + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if the NCX file has no 'docTitle' XML element and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutDocTitleElementAndDefaultOptionsTest() { await TestFailingReadOperation(NCX_FILE_WITHOUT_DOCTITLE_ELEMENT); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if the NCX file has no 'navMap' XML element")] - public async Task ReadEpub2NcxAsyncWithoutNavMapElementTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should succeed if the NCX file has no 'docTitle' XML element and IgnoreMissingDocTitleElementError = true")] + public async Task ReadEpub2NcxAsyncWithoutDocTitleElementAndIgnoreMissingDocTitleElementErrorTest() + { + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new() + { + IgnoreMissingDocTitleElementError = true + } + }; + await TestSuccessfulReadOperation(NCX_FILE_WITHOUT_DOCTITLE_ELEMENT, MinimalEpub2Ncx, epubReaderOptions); + } + + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if the NCX file has no 'navMap' XML element and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutNavMapElementAndDefaultOptionsTest() { await TestFailingReadOperation(NCX_FILE_WITHOUT_NAVMAP_ELEMENT); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'meta' XML element has no 'name' attribute")] - public async Task ReadEpub2NcxAsyncWithoutMetaNameTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should succeed if the NCX file has no 'navMap' XML element and IgnoreMissingNavMapElementError = true")] + public async Task ReadEpub2NcxAsyncWithoutNavMapElementAndIgnoreMissingNavMapElementErrorTest() + { + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new() + { + IgnoreMissingNavMapElementError = true + } + }; + await TestSuccessfulReadOperation(NCX_FILE_WITHOUT_NAVMAP_ELEMENT, MinimalEpub2Ncx, epubReaderOptions); + } + + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'meta' XML element has no 'name' attribute and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutMetaNameAndDefaultOptionsTest() { await TestFailingReadOperation(NCX_FILE_WITHOUT_META_NAME_ATTRIBUTE); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'meta' XML element has no 'content' attribute")] - public async Task ReadEpub2NcxAsyncWithoutMetaContentTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should succeed if a 'meta' XML element has no 'name' attribute and SkipInvalidMetaElements = true")] + public async Task ReadEpub2NcxAsyncWithoutMetaNameAndSkipInvalidMetaElementsTest() + { + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new() + { + SkipInvalidMetaElements = true + } + }; + await TestSuccessfulReadOperation(NCX_FILE_WITHOUT_META_NAME_ATTRIBUTE, MinimalEpub2Ncx, epubReaderOptions); + } + + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'meta' XML element has no 'content' attribute and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutMetaContentAndDefaultOptionsTest() { await TestFailingReadOperation(NCX_FILE_WITHOUT_META_CONTENT_ATTRIBUTE); } + [Fact(DisplayName = "ReadEpub2NcxAsync should succeed if a 'meta' XML element has no 'content' attribute and SkipInvalidMetaElements = true")] + public async Task ReadEpub2NcxAsyncWithoutMetaContentAndSkipInvalidMetaElementsTest() + { + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new() + { + SkipInvalidMetaElements = true + } + }; + await TestSuccessfulReadOperation(NCX_FILE_WITHOUT_META_CONTENT_ATTRIBUTE, MinimalEpub2Ncx, epubReaderOptions); + } + [Fact(DisplayName = "Reading an NCX file with unknown XML element in the 'docTitle' element should succeed")] public async Task ReadEpub2NcxAsyncWithUnknownElementInDocTitleTest() { @@ -846,36 +1029,65 @@ public async Task ReadEpub2NcxAsyncWithUnknownElementInDocAuthorTest() await TestSuccessfulReadOperation(MINIMAL_NCX_FILE_WITH_UNKNOWN_ELEMENT_IN_DOCAUTHOR, MinimalEpub2Ncx); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'navpoint' XML element has no 'id' attribute")] - public async Task ReadEpub2NcxAsyncWithoutNavPointIdTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'navpoint' XML element has no 'id' attribute and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutNavPointIdAndDefaultOptionsTest() { await TestFailingReadOperation(NCX_FILE_WITHOUT_NAVPOINT_ID_ATTRIBUTE); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'navpoint' XML element has no 'navlabel' elements")] - public async Task ReadEpub2NcxAsyncWithoutNavPointNavLabelsTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should succeed Epub2NcxException if a 'navpoint' XML element has no 'id' attribute and SkipNavigationPointsWithMissingIds = true")] + public async Task ReadEpub2NcxAsyncWithoutNavPointIdAndSkipNavigationPointsWithMissingIdsTest() { - await TestFailingReadOperation(NCX_FILE_WITHOUT_NAVPOINT_NAVLABEL_ELEMENTS); + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new Epub2NcxReaderOptions() + { + SkipNavigationPointsWithMissingIds = true + } + }; + await TestSuccessfulReadOperation(NCX_FILE_WITHOUT_NAVPOINT_ID_ATTRIBUTE, MinimalEpub2Ncx, epubReaderOptions); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'navpoint' XML element has no 'content' element")] - public async Task ReadEpub2NcxAsyncWithoutNavPointContentTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'navpoint' XML element has no 'navlabel' elements and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutNavPointNavLabelsAndDefaultOptionsTest() { - await TestFailingReadOperation(NCX_FILE_WITHOUT_NAVPOINT_CONTENT_ELEMENT); + await TestFailingReadOperation(NCX_FILE_WITHOUT_NAVPOINT_NAVLABEL_ELEMENTS); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'navpoint' XML element has no 'content' element and Epub2NcxReaderOptions is null")] - public async Task ReadEpub2NcxAsyncWithoutNavPointContentAndWithNullEpub2NcxReaderOptionsTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should succeed Epub2NcxException if a 'navpoint' XML element has no 'navlabel' elements and AllowNavigationPointsWithoutLabels = true")] + public async Task ReadEpub2NcxAsyncWithoutNavPointNavLabelsAndAllowNavigationPointsWithoutLabelsTest() { + Epub2Ncx expectedEpub2Ncx = MinimalEpub2Ncx; + expectedEpub2Ncx.NavMap.Items.Add( + new + ( + id: "navpoint-1", + navigationLabels: null, + content: + new + ( + source: "chapter1.html" + ) + ) + ); EpubReaderOptions epubReaderOptions = new() { - Epub2NcxReaderOptions = null! + Epub2NcxReaderOptions = new Epub2NcxReaderOptions() + { + AllowNavigationPointsWithoutLabels = true + } }; - await TestFailingReadOperation(NCX_FILE_WITHOUT_NAVPOINT_CONTENT_ELEMENT, epubReaderOptions); + await TestSuccessfulReadOperation(NCX_FILE_WITHOUT_NAVPOINT_NAVLABEL_ELEMENTS, expectedEpub2Ncx, epubReaderOptions); } - [Fact(DisplayName = "Reading an NCX file without 'content' element in a 'navpoint' XML element with IgnoreMissingContentForNavigationPoints = true should succeed")] - public async Task ReadEpub2NcxAsyncWithoutNavPointContentWithIgnoreMissingContentForNavigationPointsTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'navpoint' XML element has no 'content' element and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutNavPointContentAndDefaultOptionsTest() + { + await TestFailingReadOperation(NCX_FILE_WITHOUT_NAVPOINT_CONTENT_ELEMENT); + } + + [Fact(DisplayName = "ReadEpub2NcxAsync should succeed Epub2NcxException if a 'navpoint' XML element has no 'content' element and IgnoreMissingContentForNavigationPoints = true")] + public async Task ReadEpub2NcxAsyncWithoutNavPointContentAndIgnoreMissingContentForNavigationPointsTest() { EpubReaderOptions epubReaderOptions = new() { @@ -887,91 +1099,314 @@ public async Task ReadEpub2NcxAsyncWithoutNavPointContentWithIgnoreMissingConten await TestSuccessfulReadOperation(NCX_FILE_WITHOUT_NAVPOINT_CONTENT_ELEMENT, MinimalEpub2Ncx, epubReaderOptions); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'navlabel' XML element has no 'text' element")] - public async Task ReadEpub2NcxAsyncWithoutNavLabelTextTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'navlabel' XML element has no 'text' element and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutNavLabelTextAndDefaultOptionsTest() { await TestFailingReadOperation(NCX_FILE_WITHOUT_NAVLABEL_TEXT_ELEMENT); } + [Fact(DisplayName = "ReadEpub2NcxAsync should succeed if a 'navlabel' XML element has no 'text' element and SkipInvalidNavigationLabels = true")] + public async Task ReadEpub2NcxAsyncWithoutNavLabelTextAndSkipInvalidNavigationLabelsTest() + { + Epub2Ncx expectedEpub2Ncx = MinimalEpub2Ncx; + expectedEpub2Ncx.NavMap.Items.Add( + new + ( + id: "navpoint-1", + navigationLabels: + [ + new + ( + text: "Chapter 1" + ) + ], + content: + new + ( + source: "chapter1.html" + ) + ) + ); + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new Epub2NcxReaderOptions() + { + SkipInvalidNavigationLabels = true + } + }; + await TestSuccessfulReadOperation(NCX_FILE_WITHOUT_NAVLABEL_TEXT_ELEMENT, expectedEpub2Ncx, epubReaderOptions); + } + [Fact(DisplayName = "Reading an NCX file with a URI-escaped 'src' attribute in a 'content' XML element should succeed")] public async Task ReadEpub2NcxAsyncWithEscapedContentSrcTest() { - Epub2Ncx expectedEpub2Ncx = new + Epub2Ncx expectedEpub2Ncx = MinimalEpub2Ncx; + expectedEpub2Ncx.NavMap.Items.Add( + new + ( + id: "navpoint-1", + navigationLabels: + [ + new + ( + text: "Chapter 1" + ) + ], + content: + new + ( + source: "chapter1.html" + ) + ) + ); + await TestSuccessfulReadOperation(NCX_FILE_WITH_ESCAPED_CONTENT_SRC_ATTRIBUTE, expectedEpub2Ncx); + } + + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'content' XML element has no 'src' attribute and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutContentSrcAndDefaultOptionsTest() + { + await TestFailingReadOperation(NCX_FILE_WITHOUT_CONTENT_SRC_ATTRIBUTE); + } + + [Fact(DisplayName = "ReadEpub2NcxAsync should succeed if a 'content' XML element has no 'src' attribute and SkipInvalidNavigationContent = true")] + public async Task ReadEpub2NcxAsyncWithoutContentSrcAndSkipInvalidNavigationContentTest() + { + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new Epub2NcxReaderOptions() + { + SkipInvalidNavigationContent = true + } + }; + await TestSuccessfulReadOperation(NCX_FILE_WITHOUT_CONTENT_SRC_ATTRIBUTE, MinimalEpub2Ncx, epubReaderOptions); + } + + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'pageTarget' XML element has no 'type' attribute and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutPageTargetTypeAndDefaultOptionsTest() + { + await TestFailingReadOperation(NCX_FILE_WITHOUT_PAGETARGET_TYPE_ATTRIBUTE); + } + + [Fact(DisplayName = "ReadEpub2NcxAsync should succeed if a 'pageTarget' XML element has no 'type' attribute and ReplaceMissingPageTargetTypesWithUnknown = true")] + public async Task ReadEpub2NcxAsyncWithoutPageTargetTypeAndReplaceMissingPageTargetTypesWithUnknownTest() + { + Epub2Ncx expectedEpub2Ncx = + new ( filePath: NCX_FILE_PATH, head: new Epub2NcxHead(), docTitle: null, docAuthors: null, - navMap: new Epub2NcxNavigationMap + navMap: new Epub2NcxNavigationMap(), + pageList: + new ( - items: new List() - { + items: + [ new ( - id: "navpoint-1", - navigationLabels: new List() - { + type: Epub2NcxPageTargetType.UNKNOWN, + navigationLabels: + [ new ( - text: "Chapter 1" + text: "1" ) - }, - content: new Epub2NcxContent + ], + content: + new ( - source: "chapter1.html" + source: "chapter1.html#page-1" ) ) - } + ] ), - pageList: null, navLists: null ); - await TestSuccessfulReadOperation(NCX_FILE_WITH_ESCAPED_CONTENT_SRC_ATTRIBUTE, expectedEpub2Ncx); + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new Epub2NcxReaderOptions() + { + ReplaceMissingPageTargetTypesWithUnknown = true + } + }; + await TestSuccessfulReadOperation(NCX_FILE_WITHOUT_PAGETARGET_TYPE_ATTRIBUTE, expectedEpub2Ncx, epubReaderOptions); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'content' XML element has no 'src' attribute")] - public async Task ReadEpub2NcxAsyncWithoutContentSrcTest() + [Fact(DisplayName = "Reading an NCX file with unknown value of the 'type' attribute of a 'pageTarget' XML element should succeed")] + public async Task ReadEpub2NcxAsyncWithUnknownPageTargetTypeTest() { - await TestFailingReadOperation(NCX_FILE_WITHOUT_CONTENT_SRC_ATTRIBUTE); + await TestSuccessfulReadOperation(MINIMAL_NCX_FILE_WITH_UNKNOWN_PAGETARGET_TYPE, MinimalEpub2NcxWithUnknownPageTargetType); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'pageTarget' XML element has no 'type' attribute")] - public async Task ReadEpub2NcxAsyncWithoutPageTargetTypeTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'pageTarget' XML element has no 'navlabel' elements and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutPageTargetNavLabelsAndDefaultOptionsTest() { - await TestFailingReadOperation(NCX_FILE_WITHOUT_PAGETARGET_TYPE_ATTRIBUTE); + await TestFailingReadOperation(NCX_FILE_WITHOUT_PAGETARGET_NAVLABEL_ELEMENTS); } - [Fact(DisplayName = "Reading an NCX file with unknown value of the 'type' attribute of a 'pageTarget' XML element should succeed")] - public async Task ReadEpub2NcxAsyncWithUnknownPageTargetTypeTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should succeed if a 'pageTarget' XML element has no 'navlabel' elements and AllowNavigationPageTargetsWithoutLabels = true")] + public async Task ReadEpub2NcxAsyncWithoutPageTargetNavLabelsAndAllowNavigationPageTargetsWithoutLabelsTest() { - await TestSuccessfulReadOperation(MINIMAL_NCX_FILE_WITH_UNKNOWN_PAGETARGET_TYPE, MinimalEpub2NcxWithUnknownPageTargetType); + Epub2Ncx expectedEpub2Ncx = + new + ( + filePath: NCX_FILE_PATH, + head: new Epub2NcxHead(), + docTitle: null, + docAuthors: null, + navMap: new Epub2NcxNavigationMap(), + pageList: + new + ( + items: + [ + new + ( + type: Epub2NcxPageTargetType.NORMAL, + navigationLabels: null, + content: + new + ( + source: "chapter1.html#page-1" + ) + ) + ] + ), + navLists: null + ); + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new Epub2NcxReaderOptions() + { + AllowNavigationPageTargetsWithoutLabels = true + } + }; + await TestSuccessfulReadOperation(NCX_FILE_WITHOUT_PAGETARGET_NAVLABEL_ELEMENTS, expectedEpub2Ncx, epubReaderOptions); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'pageTarget' XML element has no 'navlabel' elements")] - public async Task ReadEpub2NcxAsyncWithoutPageTargetNavLabelsTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'navList' XML element has no 'navlabel' elements and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutNavListNavLabelsAndDefaultOptionsTest() { - await TestFailingReadOperation(NCX_FILE_WITHOUT_PAGETARGET_NAVLABEL_ELEMENTS); + await TestFailingReadOperation(NCX_FILE_WITHOUT_NAVLIST_NAVLABEL_ELEMENTS); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'navList' XML element has no 'navlabel' elements")] - public async Task ReadEpub2NcxAsyncWithoutNavListNavLabelsTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should succeed if a 'navList' XML element has no 'navlabel' elements and AllowNavigationListsWithoutLabels = true")] + public async Task ReadEpub2NcxAsyncWithoutNavListNavLabelsAndAllowNavigationListsWithoutLabelsTest() { - await TestFailingReadOperation(NCX_FILE_WITHOUT_NAVLIST_NAVLABEL_ELEMENTS); + Epub2Ncx expectedEpub2Ncx = MinimalEpub2Ncx; + expectedEpub2Ncx.NavLists.Add( + new + ( + id: "navlist-1", + @class: null, + navigationLabels: null, + navigationTargets: + [ + new + ( + id: "navtarget-1", + navigationLabels: + [ + new + ( + text: "Tables" + ) + ], + content: null + ) + ] + ) + ); + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new Epub2NcxReaderOptions() + { + AllowNavigationListsWithoutLabels = true + } + }; + await TestSuccessfulReadOperation(NCX_FILE_WITHOUT_NAVLIST_NAVLABEL_ELEMENTS, expectedEpub2Ncx, epubReaderOptions); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'navTarget' XML element has no 'id' attribute")] - public async Task ReadEpub2NcxAsyncWithoutNavTargetIdTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'navTarget' XML element has no 'id' attribute and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutNavTargetIdAndDefaultOptionsTest() { await TestFailingReadOperation(NCX_FILE_WITHOUT_NAVTARGET_ID_ATTRIBUTE); } - [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'navTarget' XML element has no 'navlabel' elements")] - public async Task ReadEpub2NcxAsyncWithoutNavTargetNavLabelsTest() + [Fact(DisplayName = "ReadEpub2NcxAsync should succeed if a 'navTarget' XML element has no 'id' attribute and SkipInvalidNavigationTargets = true")] + public async Task ReadEpub2NcxAsyncWithoutNavTargetIdAndSkipInvalidNavigationTargetsTest() + { + Epub2Ncx expectedEpub2Ncx = MinimalEpub2Ncx; + expectedEpub2Ncx.NavLists.Add( + new + ( + id: "navlist-1", + @class: null, + navigationLabels: + [ + new + ( + text: "List of Tables" + ) + ], + navigationTargets: null + ) + ); + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new Epub2NcxReaderOptions() + { + SkipInvalidNavigationTargets = true + } + }; + await TestSuccessfulReadOperation(NCX_FILE_WITHOUT_NAVTARGET_ID_ATTRIBUTE, expectedEpub2Ncx, epubReaderOptions); + } + + [Fact(DisplayName = "ReadEpub2NcxAsync should throw Epub2NcxException if a 'navTarget' XML element has no 'navlabel' elements and no Epub2NcxReaderOptions are provided")] + public async Task ReadEpub2NcxAsyncWithoutNavTargetNavLabelsAndDefaultOptionsTest() { await TestFailingReadOperation(NCX_FILE_WITHOUT_NAVTARGET_NAVLABEL_ELEMENTS); } - private static async Task TestSuccessfulReadOperation(string ncxFileContent, Epub2Ncx expectedEpub2Ncx, EpubReaderOptions? epubReaderOptions = null) + [Fact(DisplayName = "ReadEpub2NcxAsync should succeed if a 'navTarget' XML element has no 'navlabel' elements and AllowNavigationTargetsWithoutLabels = true")] + public async Task ReadEpub2NcxAsyncWithoutNavTargetNavLabelsAndAllowNavigationTargetsWithoutLabelsTest() + { + Epub2Ncx expectedEpub2Ncx = MinimalEpub2Ncx; + expectedEpub2Ncx.NavLists.Add( + new + ( + id: "navlist-1", + @class: null, + navigationLabels: + [ + new + ( + text: "List of Tables" + ) + ], + navigationTargets: + [ + new + ( + id: "navtarget-1", + navigationLabels: null, + content: null + ) + ] + ) + ); + EpubReaderOptions epubReaderOptions = new() + { + Epub2NcxReaderOptions = new Epub2NcxReaderOptions() + { + AllowNavigationTargetsWithoutLabels = true + } + }; + await TestSuccessfulReadOperation(NCX_FILE_WITHOUT_NAVTARGET_NAVLABEL_ELEMENTS, expectedEpub2Ncx, epubReaderOptions); + } + + private static async Task TestSuccessfulReadOperation(string ncxFileContent, Epub2Ncx? expectedEpub2Ncx, EpubReaderOptions? epubReaderOptions = null) { TestZipFile testZipFile = CreateTestZipFileWithNcxFile(ncxFileContent); Epub2NcxReader epub2NcxReader = new(epubReaderOptions ?? new EpubReaderOptions()); diff --git a/Source/VersOne.Epub.Test/Unit/Readers/Epub3NavDocumentReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/Epub3NavDocumentReaderTests.cs index 61a2c76..163404a 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/Epub3NavDocumentReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/Epub3NavDocumentReaderTests.cs @@ -1,4 +1,5 @@ -using VersOne.Epub.Internal; +using System.Xml; +using VersOne.Epub.Internal; using VersOne.Epub.Options; using VersOne.Epub.Schema; using VersOne.Epub.Test.Comparers; @@ -202,19 +203,19 @@ public class Epub3NavDocumentReaderTests metadata: new EpubMetadata(), manifest: new EpubManifest ( - items: new List() - { + items: + [ new ( id: "nav", href: NAV_FILE_NAME, mediaType: "application/xhtml+xml", - properties: new List() - { + properties: + [ EpubManifestProperty.NAV - } + ] ) - } + ] ), spine: new EpubSpine(), guide: null @@ -230,8 +231,8 @@ public class Epub3NavDocumentReaderTests new ( filePath: NAV_FILE_PATH, - navs: new List() - { + navs: + [ new ( type: Epub3StructuralSemanticsProperty.TOC, @@ -240,8 +241,8 @@ public class Epub3NavDocumentReaderTests ol: new Epub3NavOl ( isHidden: false, - lis: new List() - { + lis: + [ new ( span: new Epub3NavSpan @@ -253,11 +254,12 @@ public class Epub3NavDocumentReaderTests childOl: new Epub3NavOl ( isHidden: false, - lis: new List() - { + lis: + [ new ( - anchor: new Epub3NavAnchor + anchor: + new ( href: "chapter1.html", title: "Test anchor title", @@ -265,19 +267,20 @@ public class Epub3NavDocumentReaderTests text: "Chapter 1" ) ) - } + ] ) ), new ( - anchor: new Epub3NavAnchor + anchor: + new ( type: Epub3StructuralSemanticsProperty.LOI, href: "illustrations.html", text: "List of illustrations" ) ) - } + ] ) ), new @@ -288,8 +291,8 @@ public class Epub3NavDocumentReaderTests ol: new Epub3NavOl ( isHidden: true, - lis: new List() - { + lis: + [ new ( anchor: new Epub3NavAnchor @@ -298,18 +301,18 @@ public class Epub3NavDocumentReaderTests text: "1" ) ) - } + ] ) ) - } + ] ); private static Epub3NavDocument MinimalEpub3NavDocumentWithHeader => new ( filePath: NAV_FILE_PATH, - navs: new List() - { + navs: + [ new ( type: Epub3StructuralSemanticsProperty.TOC, @@ -317,7 +320,7 @@ public class Epub3NavDocumentReaderTests head: "Test header", ol: new Epub3NavOl() ) - } + ] ); public static IEnumerable ReadEpub3NavDocumentAsyncWithMinimalNavFileWithHeaderTestData @@ -345,6 +348,15 @@ public void ConstructorWithNullEpubReaderOptionsTest() _ = new Epub3NavDocumentReader(null); } + [Fact(DisplayName = "Constructing a Epub3NavDocumentReader instance with a null Epub3NavDocumentReaderOptions property inside the epubReaderOptions parameter should succeed")] + public void ConstructorWithNullEpub3NavDocumentReaderOptionsTest() + { + _ = new Epub3NavDocumentReader(new EpubReaderOptions + { + Epub3NavDocumentReaderOptions = null! + }); + } + [Fact(DisplayName = "Reading a minimal NAV file should succeed")] public async Task ReadEpub3NavDocumentAsyncWithMinimalNavFileTest() { @@ -364,8 +376,8 @@ public async Task ReadEpub3NavDocumentAsyncWithMinimalNavFileWithHeaderTest(stri await TestSuccessfulReadOperation(navFileContent, expectedEpub3NavDocument); } - [Fact(DisplayName = "ReadEpub3NavDocumentAsync should throw Epub3NavException if EpubPackage is missing the NAV item and EpubVersion is not EPUB_2")] - public async Task ReadEpub3NavDocumentAsyncForEpub3WithoutNavManifestItemTest() + [Fact(DisplayName = "ReadEpub3NavDocumentAsync should throw Epub3NavException if EpubPackage is missing the NAV item and EpubVersion is not EPUB_2 and no Epub3NavDocumentReaderOptions are provided")] + public async Task ReadEpub3NavDocumentAsyncForEpub3WithoutNavManifestItemAndDefaultOptionsTest() { TestZipFile testZipFile = new(); EpubPackage epubPackage = new @@ -375,15 +387,15 @@ public async Task ReadEpub3NavDocumentAsyncForEpub3WithoutNavManifestItemTest() metadata: new EpubMetadata(), manifest: new EpubManifest ( - items: new List() - { + items: + [ new ( id: "test", href: "test.html", mediaType: "application/xhtml+xml" ) - } + ] ), spine: new EpubSpine(), guide: null @@ -392,6 +404,43 @@ public async Task ReadEpub3NavDocumentAsyncForEpub3WithoutNavManifestItemTest() await Assert.ThrowsAsync(() => epub3NavDocumentReader.ReadEpub3NavDocumentAsync(testZipFile, CONTENT_DIRECTORY_PATH, epubPackage)); } + [Fact(DisplayName = "ReadEpub3NavDocumentAsync should return null if EpubPackage is missing the NAV item and EpubVersion is not EPUB_2 and IgnoreMissingNavManifestItemError = true")] + public async Task ReadEpub3NavDocumentAsyncForEpub3WithoutNavManifestItemAndIgnoreMissingNavManifestItemErrorTest() + { + TestZipFile testZipFile = new(); + EpubPackage epubPackage = new + ( + uniqueIdentifier: null, + epubVersion: EpubVersion.EPUB_3, + metadata: new EpubMetadata(), + manifest: new EpubManifest + ( + items: + [ + new + ( + id: "test", + href: "test.html", + mediaType: "application/xhtml+xml" + ) + ] + ), + spine: new EpubSpine(), + guide: null + ); + EpubReaderOptions epubReaderOptions = new() + { + Epub3NavDocumentReaderOptions = new() + { + IgnoreMissingNavManifestItemError = true + } + }; + Epub3NavDocumentReader epub3NavDocumentReader = new(epubReaderOptions); + Epub3NavDocument? actualEpub3NavDocument = + await epub3NavDocumentReader.ReadEpub3NavDocumentAsync(testZipFile, CONTENT_DIRECTORY_PATH, epubPackage); + Assert.Null(actualEpub3NavDocument); + } + [Fact(DisplayName = "ReadEpub3NavDocumentAsync should return null if EpubPackage is missing the NAV item and EpubVersion is EPUB_2")] public async Task ReadEpub3NavDocumentAsyncForEpub2WithoutNavManifestItemTest() { @@ -406,20 +455,38 @@ public async Task ReadEpub3NavDocumentAsyncForEpub2WithoutNavManifestItemTest() guide: null ); Epub3NavDocumentReader epub3NavDocumentReader = new(); - Epub3NavDocument? epub3NavDocument = await epub3NavDocumentReader.ReadEpub3NavDocumentAsync(testZipFile, CONTENT_DIRECTORY_PATH, epubPackage); - Assert.Null(epub3NavDocument); + Epub3NavDocument? actualEpub3NavDocument = + await epub3NavDocumentReader.ReadEpub3NavDocumentAsync(testZipFile, CONTENT_DIRECTORY_PATH, epubPackage); + Assert.Null(actualEpub3NavDocument); } - [Fact(DisplayName = "ReadEpub3NavDocumentAsync should throw Epub3NavException if EPUB file is missing the NAV file specified in the EpubPackage")] - public async Task ReadEpub3NavDocumentAsyncWithoutNavFileTest() + [Fact(DisplayName = "ReadEpub3NavDocumentAsync should throw Epub3NavException if EPUB file is missing the NAV file specified in the EpubPackage and no Epub3NavDocumentReaderOptions are provided")] + public async Task ReadEpub3NavDocumentAsyncWithoutNavFileAndDefaultOptionsTest() { TestZipFile testZipFile = new(); Epub3NavDocumentReader epub3NavDocumentReader = new(); await Assert.ThrowsAsync(() => epub3NavDocumentReader.ReadEpub3NavDocumentAsync(testZipFile, CONTENT_DIRECTORY_PATH, MinimalEpubPackageWithNav)); } - [Fact(DisplayName = "ReadEpub3NavDocumentAsync should throw Epub3NavException if the NCX file is larger than 2 GB")] - public async Task ReadEpub3NavDocumentAsyncWithLargeNavFileTest() + [Fact(DisplayName = "ReadEpub3NavDocumentAsync should return null if EPUB file is missing the NAV file specified in the EpubPackage and IgnoreMissingNavFileError = true")] + public async Task ReadEpub3NavDocumentAsyncWithoutNavFileAndIgnoreMissingNavFileErrorTest() + { + TestZipFile testZipFile = new(); + EpubReaderOptions epubReaderOptions = new() + { + Epub3NavDocumentReaderOptions = new() + { + IgnoreMissingNavFileError = true + } + }; + Epub3NavDocumentReader epub3NavDocumentReader = new(epubReaderOptions); + Epub3NavDocument? actualEpub3NavDocument = + await epub3NavDocumentReader.ReadEpub3NavDocumentAsync(testZipFile, CONTENT_DIRECTORY_PATH, MinimalEpubPackageWithNav); + Assert.Null(actualEpub3NavDocument); + } + + [Fact(DisplayName = "ReadEpub3NavDocumentAsync should throw Epub3NavException if the NAV file is larger than 2 GB and no Epub3NavDocumentReaderOptions are provided")] + public async Task ReadEpub3NavDocumentAsyncWithLargeNavFileAndDefaultOptionsTest() { TestZipFile testZipFile = new(); testZipFile.AddEntry(NAV_FILE_PATH, new Test4GbZipFileEntry()); @@ -427,57 +494,161 @@ public async Task ReadEpub3NavDocumentAsyncWithLargeNavFileTest() await Assert.ThrowsAsync(() => epub3NavDocumentReader.ReadEpub3NavDocumentAsync(testZipFile, CONTENT_DIRECTORY_PATH, MinimalEpubPackageWithNav)); } - [Fact(DisplayName = "ReadEpub3NavDocumentAsync should throw Epub3NavException if the NAV file is missing the 'html' XML element")] - public async Task ReadEpub3NavDocumentAsyncWithoutHtmlElement() + [Fact(DisplayName = "ReadEpub3NavDocumentAsync should return null if the NAV file is larger than 2 GB and IgnoreNavFileIsTooLargeError = true")] + public async Task ReadEpub3NavDocumentAsyncWithLargeNavFileAndIgnoreNavFileIsTooLargeErrorTest() + { + TestZipFile testZipFile = new(); + testZipFile.AddEntry(NAV_FILE_PATH, new Test4GbZipFileEntry()); + EpubReaderOptions epubReaderOptions = new() + { + Epub3NavDocumentReaderOptions = new() + { + IgnoreNavFileIsTooLargeError = true + } + }; + Epub3NavDocumentReader epub3NavDocumentReader = new(epubReaderOptions); + Epub3NavDocument? actualEpub3NavDocument = + await epub3NavDocumentReader.ReadEpub3NavDocumentAsync(testZipFile, CONTENT_DIRECTORY_PATH, MinimalEpubPackageWithNav); + Assert.Null(actualEpub3NavDocument); + } + + [Fact(DisplayName = "ReadEpub3NavDocumentAsync should throw Epub3NavException with an inner XmlException if the NAV file is not a valid XHTML file and no Epub3NavDocumentReaderOptions are provided")] + public async Task ReadEpub3NavDocumentAsyncWithInvalidXhtmlFileAndDefaultOptionsTest() + { + TestZipFile testZipFile = CreateTestZipFileWithNavFile("not a valid XHTML file"); + Epub3NavDocumentReader epub3NavDocumentReader = new(); + Epub3NavException outerException = + await Assert.ThrowsAsync(() => + epub3NavDocumentReader.ReadEpub3NavDocumentAsync(testZipFile, CONTENT_DIRECTORY_PATH, MinimalEpubPackageWithNav)); + Assert.NotNull(outerException.InnerException); + Assert.Equal(typeof(XmlException), outerException.InnerException.GetType()); + } + + [Fact(DisplayName = "ReadEpub3NavDocumentAsync should return null if the NAV file is not a valid XHTML file and IgnoreNavFileIsNotValidXmlError = true")] + public async Task ReadEpub3NavDocumentAsyncWithInvalidXhtmlFileAndIgnoreNavFileIsNotValidXmlErrorTest() + { + EpubReaderOptions epubReaderOptions = new() + { + Epub3NavDocumentReaderOptions = new() + { + IgnoreNavFileIsNotValidXmlError = true + } + }; + await TestSuccessfulReadOperation("not a valid XHTML file", null, epubReaderOptions); + } + + [Fact(DisplayName = "ReadEpub3NavDocumentAsync should throw Epub3NavException if the NAV file is missing the 'html' XML element and no Epub3NavDocumentReaderOptions are provided")] + public async Task ReadEpub3NavDocumentAsyncWithoutHtmlElementAndDefaultOptionsTest() { await TestFailingReadOperation(NAV_FILE_WITHOUT_HTML_ELEMENT); } - [Fact(DisplayName = "ReadEpub3NavDocumentAsync should throw Epub3NavException if the NAV file is missing the 'body' XML element")] - public async Task ReadEpub3NavDocumentAsyncWithoutBodyElement() + [Fact(DisplayName = "ReadEpub3NavDocumentAsync should return null if the NAV file is missing the 'html' XML element and IgnoreMissingHtmlElementError = true")] + public async Task ReadEpub3NavDocumentAsyncWithoutHtmlElementAndIgnoreMissingHtmlElementErrorTest() + { + EpubReaderOptions epubReaderOptions = new() + { + Epub3NavDocumentReaderOptions = new() + { + IgnoreMissingHtmlElementError = true + } + }; + await TestSuccessfulReadOperation(NAV_FILE_WITHOUT_HTML_ELEMENT, null, epubReaderOptions); + } + + [Fact(DisplayName = "ReadEpub3NavDocumentAsync should throw Epub3NavException if the NAV file is missing the 'body' XML element and no Epub3NavDocumentReaderOptions are provided")] + public async Task ReadEpub3NavDocumentAsyncWithoutBodyElementAndDefaultOptionsTest() { await TestFailingReadOperation(NAV_FILE_WITHOUT_BODY_ELEMENT); } - [Fact(DisplayName = "ReadEpub3NavDocumentAsync should throw Epub3NavException if the NAV file is missing the top 'ol' XML element")] - public async Task ReadEpub3NavDocumentAsyncWithoutTopOlElement() + [Fact(DisplayName = "ReadEpub3NavDocumentAsync should return null if the NAV file is missing the 'body' XML element and IgnoreMissingBodyElementError = true")] + public async Task ReadEpub3NavDocumentAsyncWithoutBodyElementAndIgnoreMissingBodyElementErrorTest() + { + EpubReaderOptions epubReaderOptions = new() + { + Epub3NavDocumentReaderOptions = new() + { + IgnoreMissingBodyElementError = true + } + }; + await TestSuccessfulReadOperation(NAV_FILE_WITHOUT_BODY_ELEMENT, null, epubReaderOptions); + } + + [Fact(DisplayName = "ReadEpub3NavDocumentAsync should throw Epub3NavException if the NAV file is missing the top 'ol' XML element and no Epub3NavDocumentReaderOptions are provided")] + public async Task ReadEpub3NavDocumentAsyncWithoutTopOlElementAndDefaultOptionsTest() { await TestFailingReadOperation(NAV_FILE_WITHOUT_TOP_OL_ELEMENT); } - [Fact(DisplayName = "ReadEpub3NavDocumentAsync should throw Epub3NavException if the NAV file has an empty 'li' XML element")] - public async Task ReadEpub3NavDocumentAsyncWithEmptyLiElement() + [Fact(DisplayName = "ReadEpub3NavDocumentAsync should succeed if the NAV file is missing the top 'ol' XML element and SkipNavsWithMissingOlElements = true")] + public async Task ReadEpub3NavDocumentAsyncWithoutTopOlElementAndSkipNavsWithMissingOlElementsTest() + { + EpubReaderOptions epubReaderOptions = new() + { + Epub3NavDocumentReaderOptions = new() + { + SkipNavsWithMissingOlElements = true + } + }; + await TestSuccessfulReadOperation(NAV_FILE_WITHOUT_TOP_OL_ELEMENT, MinimalEpub3NavDocument, epubReaderOptions); + } + + [Fact(DisplayName = "ReadEpub3NavDocumentAsync should throw Epub3NavException if the NAV file has an empty 'li' XML element and no Epub3NavDocumentReaderOptions are provided")] + public async Task ReadEpub3NavDocumentAsyncWithEmptyLiElementAndDefaultOptionsTest() { await TestFailingReadOperation(NAV_FILE_WITH_EMPTY_LI_ELEMENT); } + [Fact(DisplayName = "ReadEpub3NavDocumentAsync should succeed if the NAV file has an empty 'li' XML element and SkipInvalidLiElements = true")] + public async Task ReadEpub3NavDocumentAsyncWithEmptyLiElementAndSkipInvalidLiElementsTest() + { + Epub3NavDocument expectedEpub3NavDocument = MinimalEpub3NavDocument; + expectedEpub3NavDocument.Navs.Add( + new + ( + type: Epub3StructuralSemanticsProperty.TOC, + ol: new() + ) + ); + EpubReaderOptions epubReaderOptions = new() + { + Epub3NavDocumentReaderOptions = new() + { + SkipInvalidLiElements = true + } + }; + await TestSuccessfulReadOperation(NAV_FILE_WITH_EMPTY_LI_ELEMENT, expectedEpub3NavDocument, epubReaderOptions); + } + [Fact(DisplayName = "Reading a NAV file with a URI-escaped 'href' attribute in an 'a' XML element should succeed")] public async Task ReadEpub3NavDocumentAsyncWithEscapedAHrefTest() { Epub3NavDocument expectedEpub3NavDocument = new ( filePath: NAV_FILE_PATH, - navs: new List() - { + navs: + [ new ( type: Epub3StructuralSemanticsProperty.TOC, ol: new Epub3NavOl ( - lis: new List() - { + lis: + [ new ( - anchor: new Epub3NavAnchor + anchor: + new ( href: "chapter1.html", text: "Chapter 1" ) ) - } + ] ) ) - } + ] ); await TestSuccessfulReadOperation(NAV_FILE_WITH_ESCAPED_HREF_IN_A_ELEMENT, expectedEpub3NavDocument); } @@ -500,7 +671,7 @@ public async Task ReadEpub3NavDocumentAsyncWithNonEpubNavElementWithoutTypeTest( await TestSuccessfulReadOperation(MINIMAL_NAV_FILE_WITHOUT_TYPE_IN_NAV_ELEMENT, MinimalEpub3NavDocument); } - private static async Task TestSuccessfulReadOperation(string navFileContent, Epub3NavDocument expectedEpub3NavDocument, EpubReaderOptions? epubReaderOptions = null) + private static async Task TestSuccessfulReadOperation(string navFileContent, Epub3NavDocument? expectedEpub3NavDocument, EpubReaderOptions? epubReaderOptions = null) { TestZipFile testZipFile = CreateTestZipFileWithNavFile(navFileContent); Epub3NavDocumentReader epub3NavDocumentReader = new(epubReaderOptions ?? new EpubReaderOptions()); diff --git a/Source/VersOne.Epub.Test/Unit/Readers/MetadataReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/MetadataReaderTests.cs index 25e58c5..628b86e 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/MetadataReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/MetadataReaderTests.cs @@ -1,5 +1,6 @@ using System.Xml.Linq; using VersOne.Epub.Internal; +using VersOne.Epub.Options; using VersOne.Epub.Schema; using VersOne.Epub.Test.Comparers; @@ -69,26 +70,26 @@ public class MetadataReaderTests private static EpubMetadata FullMetadata => new ( - titles: new List() - { - new EpubMetadataTitle + titles: + [ + new ( title: "Test title 1", id: "title-1", textDirection: EpubTextDirection.LEFT_TO_RIGHT, language: "en" ), - new EpubMetadataTitle + new ( title: "Test title 2", id: "title-2", textDirection: EpubTextDirection.RIGHT_TO_LEFT, language: "is" ) - }, - creators: new List() - { - new EpubMetadataCreator + ], + creators: + [ + new ( role: "author", id: "creator-1", @@ -97,7 +98,7 @@ public class MetadataReaderTests textDirection: EpubTextDirection.LEFT_TO_RIGHT, language: "en" ), - new EpubMetadataCreator + new ( role: "author", id: "creator-2", @@ -106,61 +107,61 @@ public class MetadataReaderTests textDirection: EpubTextDirection.RIGHT_TO_LEFT, language: "is" ) - }, - subjects: new List() - { - new EpubMetadataSubject + ], + subjects: + [ + new ( subject: "Test subject 1", id: "subject-1", textDirection: EpubTextDirection.LEFT_TO_RIGHT, language: "en" ), - new EpubMetadataSubject + new ( subject: "Test subject 2", id: "subject-2", textDirection: EpubTextDirection.RIGHT_TO_LEFT, language: "is" ) - }, - descriptions: new List() - { - new EpubMetadataDescription + ], + descriptions: + [ + new ( description: "Test description 1", id: "description-1", textDirection: EpubTextDirection.LEFT_TO_RIGHT, language: "en" ), - new EpubMetadataDescription + new ( description: "Test description 2", id: "description-2", textDirection: EpubTextDirection.RIGHT_TO_LEFT, language: "is" ) - }, - publishers: new List() - { - new EpubMetadataPublisher + ], + publishers: + [ + new ( publisher: "Test publisher 1", id: "publisher-1", textDirection: EpubTextDirection.LEFT_TO_RIGHT, language: "en" ), - new EpubMetadataPublisher + new ( publisher: "Test publisher 2", id: "publisher-2", textDirection: EpubTextDirection.RIGHT_TO_LEFT, language: "is" ) - }, - contributors: new List() - { - new EpubMetadataContributor + ], + contributors: + [ + new ( role: "editor", id: "contributor-1", @@ -169,7 +170,7 @@ public class MetadataReaderTests textDirection: EpubTextDirection.LEFT_TO_RIGHT, language: "en" ), - new EpubMetadataContributor + new ( role: "editor", id: "contributor-2", @@ -178,207 +179,207 @@ public class MetadataReaderTests textDirection: EpubTextDirection.RIGHT_TO_LEFT, language: "is" ) - }, - dates: new List() - { - new EpubMetadataDate + ], + dates: + [ + new ( @event: "creation", id: "date-1", date: "2021-12-31T23:59:59.123456Z" ), - new EpubMetadataDate + new ( @event: "publication", id: "date-2", date: "2022-01-23" ) - }, - types: new List() - { - new EpubMetadataType + ], + types: + [ + new ( type: "dictionary", id: "type-1" ), - new EpubMetadataType + new ( type: "preview", id: "type-2" ) - }, - formats: new List() - { - new EpubMetadataFormat + ], + formats: + [ + new ( format: "format-1", id: "format-1" ), - new EpubMetadataFormat + new ( format: "format-2", id: "format-2" ) - }, - identifiers: new List() - { - new EpubMetadataIdentifier + ], + identifiers: + [ + new ( identifier: "https://example.com/books/123", id: "identifier-1", scheme: "URI" ), - new EpubMetadataIdentifier + new ( identifier: "9781234567890", id: "identifier-2", scheme: "ISBN" ) - }, - sources: new List() - { - new EpubMetadataSource + ], + sources: + [ + new ( source: "https://example.com/books/123/content-1.html", id: "source-1" ), - new EpubMetadataSource + new ( source: "https://example.com/books/123/content-2.html", id: "source-2" ) - }, - languages: new List() - { - new EpubMetadataLanguage + ], + languages: + [ + new ( language: "en", id: "language-1" ), - new EpubMetadataLanguage + new ( language: "is", id: "language-2" ) - }, - relations: new List() - { - new EpubMetadataRelation + ], + relations: + [ + new ( relation: "https://example.com/books/123/related-1.html", id: "relation-1", textDirection: EpubTextDirection.LEFT_TO_RIGHT, language: "en" ), - new EpubMetadataRelation + new ( relation: "https://example.com/books/123/related-2.html", id: "relation-2", textDirection: EpubTextDirection.RIGHT_TO_LEFT, language: "is" ) - }, - coverages: new List() - { - new EpubMetadataCoverage + ], + coverages: + [ + new ( coverage: "New York", id: "coverage-1", textDirection: EpubTextDirection.LEFT_TO_RIGHT, language: "en" ), - new EpubMetadataCoverage + new ( coverage: "1700-1850", id: "coverage-2", textDirection: EpubTextDirection.RIGHT_TO_LEFT, language: "is" ) - }, - rights: new List() - { - new EpubMetadataRights + ], + rights: + [ + new ( rights: "Public domain in the USA", id: "rights-1", textDirection: EpubTextDirection.LEFT_TO_RIGHT, language: "en" ), - new EpubMetadataRights + new ( rights: "All rights reserved", id: "rights-2", textDirection: EpubTextDirection.RIGHT_TO_LEFT, language: "is" ) - }, - links: new List() - { - new EpubMetadataLink + ], + links: + [ + new ( id: "link-1", href: "front.html#meta-json", mediaType: "application/xhtml+xml", properties: null, refines: null, - relationships: new List() - { + relationships: + [ EpubMetadataLinkRelationship.RECORD - }, + ], hrefLanguage: "en" ), - new EpubMetadataLink + new ( id: "link-2", href: "https://example.com/onix/123", mediaType: "application/xml", - properties: new List() - { + properties: + [ EpubMetadataLinkProperty.ONIX - }, + ], refines: null, - relationships: new List() - { + relationships: + [ EpubMetadataLinkRelationship.RECORD, EpubMetadataLinkRelationship.ONIX_RECORD - }, + ], hrefLanguage: null ), - new EpubMetadataLink + new ( id: "link-3", href: "book.atom", mediaType: "application/atom+xml;type=entry;profile=opds-catalog", properties: null, refines: null, - relationships: new List() - { + relationships: + [ EpubMetadataLinkRelationship.RECORD - }, + ], hrefLanguage: null ), - new EpubMetadataLink + new ( id: "link-4", href: "title.mp3", mediaType: "audio/mpeg", properties: null, refines: "#title-1", - relationships: new List() - { + relationships: + [ EpubMetadataLinkRelationship.VOICING - }, + ], hrefLanguage: null ) - }, - metaItems: new List() - { - new EpubMetadataMeta + ], + metaItems: + [ + new ( name: "cover", content: "cover-image" ), - new EpubMetadataMeta + new ( name: null, content: "landscape", @@ -387,7 +388,7 @@ public class MetadataReaderTests property: "rendition:orientation", scheme: null ), - new EpubMetadataMeta + new ( name: null, content: "123", @@ -398,7 +399,7 @@ public class MetadataReaderTests textDirection: EpubTextDirection.LEFT_TO_RIGHT, language: "en" ), - new EpubMetadataMeta + new ( name: null, content: "Brynjólfur Sveinsson", @@ -409,7 +410,7 @@ public class MetadataReaderTests textDirection: EpubTextDirection.RIGHT_TO_LEFT, language: "is" ) - } + ] ); [Fact(DisplayName = "Reading a minimal metadata XML should succeed")] @@ -424,30 +425,61 @@ public void ReadFullMetadataTest() TestSuccessfulReadOperation(FULL_METADATA_XML, FullMetadata); } - [Fact(DisplayName = "Trying to read metadata XML without 'href' attribute in a metadata link XML node should fail with EpubPackageException")] - public void ReadPackageWithoutMetadataLinkHrefTest() + [Fact(DisplayName = "ReadMetadata should throw EpubPackageException when a 'link' XML node doesn't have the 'href' attribute and no MetadataReaderOptions are provided")] + public void ReadPackageWithoutMetadataLinkHrefAndDefaultOptionsTest() { TestFailingReadOperation(METADATA_XML_WITHOUT_HREF_IN_METADATA_LINK); } - [Fact(DisplayName = "Trying to read metadata XML without 'rel' attribute in a metadata link XML node should fail with EpubPackageException")] - public void ReadPackageWithoutMetadataLinkRelTest() + [Fact(DisplayName = "ReadMetadata should skip 'link' XML nodes without the 'href' attribute when SkipLinksWithoutHrefs = true")] + public void ReadPackageWithoutMetadataLinkHrefAndSkipLinksWithoutHrefsTest() + { + MetadataReaderOptions metadataReaderOptions = new() + { + SkipLinksWithoutHrefs = true + }; + TestSuccessfulReadOperation(METADATA_XML_WITHOUT_HREF_IN_METADATA_LINK, MinimalMetadata, metadataReaderOptions); + } + + [Fact(DisplayName = "ReadMetadata should throw EpubPackageException when a 'link' XML node doesn't have the 'rel' attribute and no MetadataReaderOptions are provided")] + public void ReadPackageWithoutMetadataLinkRelAndDefaultOptionsTest() { TestFailingReadOperation(METADATA_XML_WITHOUT_REL_IN_METADATA_LINK); } + [Fact(DisplayName = "ReadMetadata should succeed when a 'link' XML node doesn't have the 'rel' attribute and IgnoreLinkWithoutRelError = true")] + public void ReadPackageWithoutMetadataLinkRelAndIgnoreLinkWithoutRelErrorTest() + { + EpubMetadata expectedEpubMetadata = + new + ( + links: + [ + new + ( + href: "chapter.html" + ) + ] + ); + MetadataReaderOptions metadataReaderOptions = new() + { + IgnoreLinkWithoutRelError = true + }; + TestSuccessfulReadOperation(METADATA_XML_WITHOUT_REL_IN_METADATA_LINK, expectedEpubMetadata, metadataReaderOptions); + } - private static void TestSuccessfulReadOperation(string metadataXml, EpubMetadata expectedEpubMetadata) + private static void TestSuccessfulReadOperation(string metadataXml, EpubMetadata expectedEpubMetadata, + MetadataReaderOptions? metadataReaderOptions = null) { XElement metadataNode = XElement.Parse(metadataXml); - EpubMetadata actualEpubMetadata = MetadataReader.ReadMetadata(metadataNode); + EpubMetadata actualEpubMetadata = MetadataReader.ReadMetadata(metadataNode, metadataReaderOptions ?? new MetadataReaderOptions()); EpubMetadataComparer.CompareEpubMetadatas(expectedEpubMetadata, actualEpubMetadata); } private static void TestFailingReadOperation(string metadataXml) { XElement metadataNode = XElement.Parse(metadataXml); - Assert.Throws(() => MetadataReader.ReadMetadata(metadataNode)); + Assert.Throws(() => MetadataReader.ReadMetadata(metadataNode, new MetadataReaderOptions())); } } } diff --git a/Source/VersOne.Epub.Test/Unit/Readers/NavigationReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/NavigationReaderTests.cs index b77c5af..408a497 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/NavigationReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/NavigationReaderTests.cs @@ -1,4 +1,5 @@ using VersOne.Epub.Internal; +using VersOne.Epub.Options; using VersOne.Epub.Schema; using VersOne.Epub.Test.Comparers; using VersOne.Epub.Test.Unit.Mocks; @@ -13,6 +14,196 @@ public class NavigationReaderTests private const string NAV_FILE_NAME = "toc.html"; private const string NAV_FILE_PATH = $"{CONTENT_DIRECTORY_PATH}/{NAV_FILE_NAME}"; + private static EpubSchema Epub2SchemaWithoutNavigationLabels => + new + ( + package: CreateEmptyPackage(EpubVersion.EPUB_2), + epub2Ncx: new + ( + filePath: NCX_FILE_PATH, + head: new(), + docTitle: null, + docAuthors: null, + navMap: new + ( + items: + [ + new + ( + id: String.Empty, + @class: null, + playOrder: null, + navigationLabels: [], + content: new + ( + source: "chapter1.html" + ), + childNavigationPoints: null + ) + ] + ), + pageList: null, + navLists: null + ), + epub3NavDocument: null, + mediaOverlays: null, + contentDirectoryPath: CONTENT_DIRECTORY_PATH + ); + + private static EpubSchema Epub2SchemaWithRemoteNavigationPoint => + new + ( + package: CreateEmptyPackage(EpubVersion.EPUB_2), + epub2Ncx: new + ( + filePath: NCX_FILE_PATH, + head: new(), + docTitle: null, + docAuthors: null, + navMap: new + ( + items: + [ + new + ( + id: String.Empty, + @class: null, + playOrder: null, + navigationLabels: + [ + new + ( + text: "Test label" + ) + ], + content: new + ( + source: "https://example.com/books/123/chapter1.html" + ), + childNavigationPoints: null + ) + ] + ), + pageList: null, + navLists: null + ), + epub3NavDocument: null, + mediaOverlays: null, + contentDirectoryPath: CONTENT_DIRECTORY_PATH + ); + + private static EpubSchema Epub2SchemaWithMissingContentFile => + new + ( + package: CreateEmptyPackage(EpubVersion.EPUB_2), + epub2Ncx: new + ( + filePath: NCX_FILE_PATH, + head: new(), + docTitle: null, + docAuthors: null, + navMap: new + ( + items: + [ + new + ( + id: String.Empty, + @class: null, + playOrder: null, + navigationLabels: + [ + new + ( + text: "Test label" + ) + ], + content: new + ( + source: "chapter1.html" + ), + childNavigationPoints: null + ) + ] + ), + pageList: null, + navLists: null + ), + epub3NavDocument: null, + mediaOverlays: null, + contentDirectoryPath: CONTENT_DIRECTORY_PATH + ); + + private static EpubSchema Epub3SchemaWithRemoteNavigationPoint => + new + ( + package: CreateEmptyPackage(EpubVersion.EPUB_3), + epub2Ncx: null, + epub3NavDocument: new + ( + filePath: NAV_FILE_PATH, + navs: + [ + new + ( + type: Epub3StructuralSemanticsProperty.TOC, + ol: new + ( + lis: + [ + new + ( + anchor: new + ( + text: "Test text", + href: "https://example.com/books/123/chapter1.html" + ) + ) + ] + ) + ) + ] + ), + mediaOverlays: null, + contentDirectoryPath: CONTENT_DIRECTORY_PATH + ); + + private static EpubSchema Epub3SchemaWithMissingContentFile => + new + ( + package: CreateEmptyPackage(EpubVersion.EPUB_3), + epub2Ncx: null, + epub3NavDocument: new + ( + filePath: NAV_FILE_PATH, + navs: + [ + new + ( + type: Epub3StructuralSemanticsProperty.TOC, + isHidden: false, + head: null, + ol: new + ( + lis: + [ + new + ( + anchor: new + ( + text: "Test text", + href: "chapter1.html" + ) + ) + ] + ) + ) + ] + ), + mediaOverlays: null, + contentDirectoryPath: CONTENT_DIRECTORY_PATH + ); + [Fact(DisplayName = "GetNavigationItems should return null for EPUB 2 schemas without NCX file")] public void GetNavigationItemsForEpub2WithoutNcxTest() { @@ -25,8 +216,7 @@ public void GetNavigationItemsForEpub2WithoutNcxTest() contentDirectoryPath: CONTENT_DIRECTORY_PATH ); EpubContentRef epubContentRef = new(); - List? navigationItems = NavigationReader.GetNavigationItems(epubSchema, epubContentRef); - Assert.Null(navigationItems); + TestSuccessfulOperation(epubSchema, epubContentRef, null); } [Fact(DisplayName = "Getting navigation items for EPUB 2 schemas with minimal NCX file should succeed")] @@ -51,8 +241,7 @@ public void GetNavigationItemsForEpub2WithMinimalNcxTest() ); EpubContentRef epubContentRef = new(); List expectedNavigationItems = []; - List? actualNavigationItems = NavigationReader.GetNavigationItems(epubSchema, epubContentRef); - EpubNavigationItemRefComparer.CompareNavigationItemRefLists(expectedNavigationItems, actualNavigationItems); + TestSuccessfulOperation(epubSchema, epubContentRef, expectedNavigationItems); } [Fact(DisplayName = "Getting navigation items for EPUB 2 schemas with full NCX file should succeed")] @@ -128,8 +317,7 @@ public void GetNavigationItemsForEpub2WithFullNcxTest() [ expectedNavigationItem1 ]; - List? actualNavigationItems = NavigationReader.GetNavigationItems(epubSchema, epubContentRef); - EpubNavigationItemRefComparer.CompareNavigationItemRefLists(expectedNavigationItems, actualNavigationItems); + TestSuccessfulOperation(epubSchema, epubContentRef, expectedNavigationItems); } [Fact(DisplayName = "Getting navigation items for EPUB 2 schemas with relative file paths within the NCX file should succeed")] @@ -268,144 +456,74 @@ public void GetNavigationItemsForEpub2WithRelativePathsTest() expectedNavigationItem4, expectedNavigationItem5 ]; - List? actualNavigationItems = NavigationReader.GetNavigationItems(epubSchema, epubContentRef); - EpubNavigationItemRefComparer.CompareNavigationItemRefLists(expectedNavigationItems, actualNavigationItems); + TestSuccessfulOperation(epubSchema, epubContentRef, expectedNavigationItems); } - [Fact(DisplayName = "GetNavigationItems should throw a Epub2NcxException if an NCX navigation point has no navigation labels")] - public void GetNavigationItemsForEpub2WithoutNavigationLabelsTest() + [Fact(DisplayName = "GetNavigationItems should throw a Epub2NcxException if an NCX navigation point has no navigation labels and no NavigationReaderOptions are provided")] + public void GetNavigationItemsForEpub2WithoutNavigationLabelsAndDefaultOptionsTest() { - EpubSchema epubSchema = new - ( - package: CreateEmptyPackage(EpubVersion.EPUB_2), - epub2Ncx: new - ( - filePath: NCX_FILE_PATH, - head: new(), - docTitle: null, - docAuthors: null, - navMap: new - ( - items: - [ - new - ( - id: String.Empty, - @class: null, - playOrder: null, - navigationLabels: [], - content: new - ( - source: "chapter1.html" - ), - childNavigationPoints: null - ) - ] - ), - pageList: null, - navLists: null - ), - epub3NavDocument: null, - mediaOverlays: null, - contentDirectoryPath: CONTENT_DIRECTORY_PATH - ); + EpubSchema epubSchema = Epub2SchemaWithoutNavigationLabels; EpubContentRef epubContentRef = new(); - Assert.Throws(() => NavigationReader.GetNavigationItems(epubSchema, epubContentRef)); + TestEpub2FailingReadOperation(epubSchema, epubContentRef); } - [Fact(DisplayName = "GetNavigationItems should throw a Epub2NcxException if an NCX navigation point points to a remote resource")] - public void GetNavigationItemsForEpub2WithRemoteNavigationPointSourceTest() + [Fact(DisplayName = "GetNavigationItems should succeed if an NCX navigation point has no navigation labels and AllowEpub2NavigationItemsWithEmptyTitles = true")] + public void GetNavigationItemsForEpub2WithoutNavigationLabelsAndAllowEpub2NavigationItemsWithEmptyTitlesTest() { - string remoteFileHref = "https://example.com/books/123/chapter1.html"; - EpubSchema epubSchema = new - ( - package: CreateEmptyPackage(EpubVersion.EPUB_2), - epub2Ncx: new - ( - filePath: NCX_FILE_PATH, - head: new(), - docTitle: null, - docAuthors: null, - navMap: new - ( - items: - [ - new - ( - id: String.Empty, - @class: null, - playOrder: null, - navigationLabels: - [ - new - ( - text: "Test label" - ) - ], - content: new - ( - source: remoteFileHref - ), - childNavigationPoints: null - ) - ] - ), - pageList: null, - navLists: null - ), - epub3NavDocument: null, - mediaOverlays: null, - contentDirectoryPath: CONTENT_DIRECTORY_PATH - ); + EpubSchema epubSchema = Epub2SchemaWithoutNavigationLabels; + EpubLocalTextContentFileRef testTextContentFileRef = CreateTestHtmlFile(CONTENT_DIRECTORY_PATH, "chapter1.html"); + EpubContentRef epubContentRef = CreateContentRef(null, testTextContentFileRef); + List expectedNavigationItems = + [ + CreateNavigationLink(String.Empty, CONTENT_DIRECTORY_PATH, "chapter1.html", testTextContentFileRef) + ]; + NavigationReaderOptions navigationReaderOptions = new() + { + AllowEpub2NavigationItemsWithEmptyTitles = true + }; + TestSuccessfulOperation(epubSchema, epubContentRef, expectedNavigationItems, navigationReaderOptions); + } + + [Fact(DisplayName = "GetNavigationItems should throw a Epub2NcxException if an NCX navigation point points to a remote resource and no NavigationReaderOptions are provided")] + public void GetNavigationItemsForEpub2WithRemoteNavigationPointSourceAndDefaultOptionsTest() + { + EpubSchema epubSchema = Epub2SchemaWithRemoteNavigationPoint; EpubContentRef epubContentRef = new(); - Assert.Throws(() => NavigationReader.GetNavigationItems(epubSchema, epubContentRef)); + TestEpub2FailingReadOperation(epubSchema, epubContentRef); } - [Fact(DisplayName = "GetNavigationItems should throw a Epub2NcxException if the file referenced by an NCX navigation point is missing in the EpubContentRef")] - public void GetNavigationItemsForEpub2WithMissingContentFileTest() + [Fact(DisplayName = "GetNavigationItems should skip NCX navigation points pointing to remote resources when SkipRemoteNavigationItems = true")] + public void GetNavigationItemsForEpub2WithRemoteNavigationPointSourceAndSkipRemoteNavigationItemsTest() { - EpubSchema epubSchema = new - ( - package: CreateEmptyPackage(EpubVersion.EPUB_2), - epub2Ncx: new - ( - filePath: NCX_FILE_PATH, - head: new(), - docTitle: null, - docAuthors: null, - navMap: new - ( - items: - [ - new - ( - id: String.Empty, - @class: null, - playOrder: null, - navigationLabels: - [ - new - ( - text: "Test label" - ) - ], - content: new - ( - source: "chapter1.html" - ), - childNavigationPoints: null - ) - ] - ), - pageList: null, - navLists: null - ), - epub3NavDocument: null, - mediaOverlays: null, - contentDirectoryPath: CONTENT_DIRECTORY_PATH - ); + EpubSchema epubSchema = Epub2SchemaWithRemoteNavigationPoint; EpubContentRef epubContentRef = new(); - Assert.Throws(() => NavigationReader.GetNavigationItems(epubSchema, epubContentRef)); + List expectedNavigationItems = []; + NavigationReaderOptions navigationReaderOptions = new() + { + SkipRemoteNavigationItems = true + }; + TestSuccessfulOperation(epubSchema, epubContentRef, expectedNavigationItems, navigationReaderOptions); + } + + [Fact(DisplayName = "GetNavigationItems should throw a Epub2NcxException if the file referenced by an NCX navigation point is missing in the EpubContentRef and no NavigationReaderOptions are provided")] + public void GetNavigationItemsForEpub2WithMissingContentFileAndDefaultOptionsTest() + { + EpubSchema epubSchema = Epub2SchemaWithMissingContentFile; + EpubContentRef epubContentRef = new(); + TestEpub2FailingReadOperation(epubSchema, epubContentRef); + } + + [Fact(DisplayName = "GetNavigationItems should skip NCX navigation points referencing files that are missing in the EpubContentRef when SkipNavigationItemsReferencingMissingContent = true")] + public void GetNavigationItemsForEpub2WithMissingContentFileAndSkipNavigationItemsReferencingMissingContentTest() + { + EpubSchema epubSchema = Epub2SchemaWithMissingContentFile; + EpubContentRef epubContentRef = new(); + List expectedNavigationItems = []; + NavigationReaderOptions navigationReaderOptions = new() + { + SkipNavigationItemsReferencingMissingContent = true + }; + TestSuccessfulOperation(epubSchema, epubContentRef, expectedNavigationItems, navigationReaderOptions); } [Fact(DisplayName = "Getting navigation items for EPUB 3 schemas with minimal NAV file should succeed")] @@ -424,8 +542,7 @@ public void GetNavigationItemsForEpub3WithMinimalNavTest() ); EpubContentRef epubContentRef = new(); List expectedNavigationItems = []; - List? actualNavigationItems = NavigationReader.GetNavigationItems(epubSchema, epubContentRef); - EpubNavigationItemRefComparer.CompareNavigationItemRefLists(expectedNavigationItems, actualNavigationItems); + TestSuccessfulOperation(epubSchema, epubContentRef, expectedNavigationItems); } [Fact(DisplayName = "Getting navigation items for EPUB 3 schemas with full NAV file should succeed")] @@ -524,8 +641,7 @@ public void GetNavigationItemsForEpub3WithFullNavTest() [ expectedNavigationItem1 ]; - List? actualNavigationItems = NavigationReader.GetNavigationItems(epubSchema, epubContentRef); - EpubNavigationItemRefComparer.CompareNavigationItemRefLists(expectedNavigationItems, actualNavigationItems); + TestSuccessfulOperation(epubSchema, epubContentRef, expectedNavigationItems); } [Fact(DisplayName = "Getting navigation items for EPUB 3 schemas with relative file paths within the NAV file should succeed")] @@ -618,8 +734,7 @@ public void GetNavigationItemsForEpub3WithRelativePathsTest() [ expectedNavigationItem1 ]; - List? actualNavigationItems = NavigationReader.GetNavigationItems(epubSchema, epubContentRef); - EpubNavigationItemRefComparer.CompareNavigationItemRefLists(expectedNavigationItems, actualNavigationItems); + TestSuccessfulOperation(epubSchema, epubContentRef, expectedNavigationItems); } [Fact(DisplayName = "Getting navigation items for EPUB 3 NAV file without a header should succeed")] @@ -666,8 +781,7 @@ public void GetNavigationItemsForEpub3NavWithoutHeaderTest() [ CreateNavigationLink("Test text", "chapter1.html", testTextContentFileRef) ]; - List? actualNavigationItems = NavigationReader.GetNavigationItems(epubSchema, epubContentRef); - EpubNavigationItemRefComparer.CompareNavigationItemRefLists(expectedNavigationItems, actualNavigationItems); + TestSuccessfulOperation(epubSchema, epubContentRef, expectedNavigationItems); } [Fact(DisplayName = "Getting navigation items for EPUB 3 NAV file with empty Lis should succeed")] @@ -701,8 +815,7 @@ public void GetNavigationItemsForEpub3NavWithEmptyLisTest() EpubLocalTextContentFileRef testNavigationHtmlFileRef = CreateTestNavigationFile(); EpubContentRef epubContentRef = CreateContentRef(testNavigationHtmlFileRef); List expectedNavigationItems = []; - List? actualNavigationItems = NavigationReader.GetNavigationItems(epubSchema, epubContentRef); - EpubNavigationItemRefComparer.CompareNavigationItemRefLists(expectedNavigationItems, actualNavigationItems); + TestSuccessfulOperation(epubSchema, epubContentRef, expectedNavigationItems); } [Fact(DisplayName = "Getting navigation items for EPUB 3 NAV file with null anchor href should succeed")] @@ -746,8 +859,7 @@ public void GetNavigationItemsForEpub3NavWithNullAnchorHrefTest() [ CreateNavigationHeader("Null href test") ]; - List? actualNavigationItems = NavigationReader.GetNavigationItems(epubSchema, epubContentRef); - EpubNavigationItemRefComparer.CompareNavigationItemRefLists(expectedNavigationItems, actualNavigationItems); + TestSuccessfulOperation(epubSchema, epubContentRef, expectedNavigationItems); } [Fact(DisplayName = "Getting navigation items for EPUB 3 NAV file with null or empty titles should succeed")] @@ -886,87 +998,30 @@ public void GetNavigationItemsForEpub3NavWithNullOrEmptyTitlesTest() CreateNavigationHeader(String.Empty), CreateNavigationHeader(String.Empty), ]; - List? actualNavigationItems = NavigationReader.GetNavigationItems(epubSchema, epubContentRef); - EpubNavigationItemRefComparer.CompareNavigationItemRefLists(expectedNavigationItems, actualNavigationItems); + TestSuccessfulOperation(epubSchema, epubContentRef, expectedNavigationItems); } - [Fact(DisplayName = "GetNavigationItems should throw a Epub3NavException if the file referenced by a EPUB 3 navigation Li item is missing in the EpubContentRef")] - public void GetNavigationItemsForEpub3WithMissingContentFile() + [Fact(DisplayName = "GetNavigationItems should throw Epub3NavException if a EPUB 3 navigation anchor points to a remote resource and no NavigationReaderOptions are provided")] + public void GetNavigationItemsWithRemoteContentFileAndDefaultOptionsTest() { - EpubSchema epubSchema = new + EpubSchema epubSchema = Epub3SchemaWithRemoteNavigationPoint; + EpubLocalTextContentFileRef testNavigationHtmlFileRef = CreateTestNavigationFile(); + List htmlLocal = + [ + testNavigationHtmlFileRef + ]; + EpubContentRef epubContentRef = new ( - package: CreateEmptyPackage(EpubVersion.EPUB_3), - epub2Ncx: null, - epub3NavDocument: new - ( - filePath: NAV_FILE_PATH, - navs: - [ - new - ( - type: Epub3StructuralSemanticsProperty.TOC, - isHidden: false, - head: null, - ol: new - ( - lis: - [ - new - ( - anchor: new - ( - text: "Test text", - href: "chapter1.html" - ) - ) - ] - ) - ) - ] - ), - mediaOverlays: null, - contentDirectoryPath: CONTENT_DIRECTORY_PATH + navigationHtmlFile: testNavigationHtmlFileRef, + html: new EpubContentCollectionRef(htmlLocal.AsReadOnly()) ); - EpubContentRef epubContentRef = new(); - Assert.Throws(() => NavigationReader.GetNavigationItems(epubSchema, epubContentRef)); + TestEpub3FailingReadOperation(epubSchema, epubContentRef); } - [Fact(DisplayName = "GetNavigationItems should throw Epub3NavException if a EPUB 3 navigation anchor points to a remote resource")] - public void GetNavigationItemsWithRemoteContentFileTest() + [Fact(DisplayName = "GetNavigationItems should skip EPUB 3 navigation anchor points pointing to remote resources when SkipRemoteNavigationItems = true")] + public void GetNavigationItemsWithRemoteContentFileAndSkipRemoteNavigationItemsTest() { - string remoteFileHref = "https://example.com/books/123/chapter1.html"; - EpubSchema epubSchema = new - ( - package: CreateEmptyPackage(EpubVersion.EPUB_3), - epub2Ncx: null, - epub3NavDocument: new - ( - filePath: NAV_FILE_PATH, - navs: - [ - new - ( - type: Epub3StructuralSemanticsProperty.TOC, - ol: new - ( - lis: - [ - new - ( - anchor: new - ( - text: "Test text", - href: remoteFileHref - ) - ) - ] - ) - ) - ] - ), - mediaOverlays: null, - contentDirectoryPath: CONTENT_DIRECTORY_PATH - ); + EpubSchema epubSchema = Epub3SchemaWithRemoteNavigationPoint; EpubLocalTextContentFileRef testNavigationHtmlFileRef = CreateTestNavigationFile(); List htmlLocal = [ @@ -977,7 +1032,33 @@ public void GetNavigationItemsWithRemoteContentFileTest() navigationHtmlFile: testNavigationHtmlFileRef, html: new EpubContentCollectionRef(htmlLocal.AsReadOnly()) ); - Assert.Throws(() => NavigationReader.GetNavigationItems(epubSchema, epubContentRef)); + List expectedNavigationItems = []; + NavigationReaderOptions navigationReaderOptions = new() + { + SkipRemoteNavigationItems = true + }; + TestSuccessfulOperation(epubSchema, epubContentRef, expectedNavigationItems, navigationReaderOptions); + } + + [Fact(DisplayName = "GetNavigationItems should throw a Epub3NavException if the file referenced by a EPUB 3 navigation Li item is missing in the EpubContentRef and no NavigationReaderOptions are provided")] + public void GetNavigationItemsForEpub3WithMissingContentAndDefaultOptionsFile() + { + EpubSchema epubSchema = Epub3SchemaWithMissingContentFile; + EpubContentRef epubContentRef = new(); + TestEpub3FailingReadOperation(epubSchema, epubContentRef); + } + + [Fact(DisplayName = "GetNavigationItems should skip EPUB 3 navigation Li items referencing files that are missing in the EpubContentRef when SkipNavigationItemsReferencingMissingContent = true")] + public void GetNavigationItemsForEpub3WithMissingContentAndSkipNavigationItemsReferencingMissingContentFile() + { + EpubSchema epubSchema = Epub3SchemaWithMissingContentFile; + EpubContentRef epubContentRef = new(); + List expectedNavigationItems = []; + NavigationReaderOptions navigationReaderOptions = new() + { + SkipNavigationItemsReferencingMissingContent = true + }; + TestSuccessfulOperation(epubSchema, epubContentRef, expectedNavigationItems, navigationReaderOptions); } private static EpubLocalTextContentFileRef CreateTestNavigationFile() @@ -1056,5 +1137,23 @@ private static EpubContentRef CreateContentRef(EpubLocalTextContentFileRef? navi ); return result; } + + private static void TestSuccessfulOperation(EpubSchema epubSchema, EpubContentRef epubContentRef, + List? expectedNavigationItems, NavigationReaderOptions? navigationReaderOptions = null) + { + List? actualNavigationItems = + NavigationReader.GetNavigationItems(epubSchema, epubContentRef, navigationReaderOptions ?? new NavigationReaderOptions()); + EpubNavigationItemRefComparer.CompareNavigationItemRefLists(expectedNavigationItems, actualNavigationItems); + } + + private static void TestEpub2FailingReadOperation(EpubSchema epubSchema, EpubContentRef epubContentRef) + { + Assert.Throws(() => NavigationReader.GetNavigationItems(epubSchema, epubContentRef, new NavigationReaderOptions())); + } + + private static void TestEpub3FailingReadOperation(EpubSchema epubSchema, EpubContentRef epubContentRef) + { + Assert.Throws(() => NavigationReader.GetNavigationItems(epubSchema, epubContentRef, new NavigationReaderOptions())); + } } } diff --git a/Source/VersOne.Epub.Test/Unit/Readers/PackageReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/PackageReaderTests.cs index 1c4df6f..06b0a92 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/PackageReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/PackageReaderTests.cs @@ -1,4 +1,5 @@ -using VersOne.Epub.Internal; +using System.Xml; +using VersOne.Epub.Internal; using VersOne.Epub.Options; using VersOne.Epub.Schema; using VersOne.Epub.Test.Comparers; @@ -330,50 +331,50 @@ public class PackageReaderTests epubVersion: EpubVersion.EPUB_3, metadata: new EpubMetadata ( - titles: new List() - { + titles: + [ new ( title: "Test title" ) - }, - creators: new List() - { + ], + creators: + [ new ( creator: "John Doe" ) - }, - identifiers: new List() - { + ], + identifiers: + [ new ( identifier: "9781234567890", id: "book-uid" ) - }, - languages: new List() - { + ], + languages: + [ new ( language: "en" ) - }, - metaItems: new List() - { + ], + metaItems: + [ new ( name: null, content: "2021-12-31T00:00:00Z", property: "dcterms:modified" ) - } + ] ), manifest: new EpubManifest ( id: "manifest-id", - items: new List() - { + items: + [ new ( id: "item-front", @@ -391,10 +392,10 @@ public class PackageReaderTests id: "cover-image", href: "cover.jpg", mediaType: "image/jpeg", - properties: new List() - { + properties: + [ EpubManifestProperty.COVER_IMAGE - } + ] ), new ( @@ -491,10 +492,10 @@ public class PackageReaderTests id: "item-toc", href: "toc.html", mediaType: "application/xhtml+xml", - properties: new List - { + properties: + [ EpubManifestProperty.NAV - } + ] ), new ( @@ -502,15 +503,15 @@ public class PackageReaderTests href: "toc.ncx", mediaType: "application/x-dtbncx+xml" ) - } + ] ), spine: new EpubSpine ( id: "spine", pageProgressionDirection: EpubPageProgressionDirection.LEFT_TO_RIGHT, toc: "ncx", - items: new List() - { + items: + [ new ( id: "itemref-1", @@ -534,44 +535,44 @@ public class PackageReaderTests id: "itemref-4", idRef: "item-2", isLinear: true, - properties: new List() - { + properties: + [ EpubSpineProperty.PAGE_SPREAD_LEFT - } + ] ), new ( id: "itemref-5", idRef: "item-3", isLinear: true, - properties: new List() - { + properties: + [ EpubSpineProperty.PAGE_SPREAD_RIGHT - } + ] ) - } + ] ), guide: new EpubGuide ( - items: new List() - { + items: + [ new ( type: "toc", title: "Contents", href: "toc.html" ) - } + ] ), - collections: new List() - { + collections: + [ new ( role: "http://example.org/roles/group", metadata: new EpubMetadata ( - titles: new List() - { + titles: + [ new ( title: "Test title for collection 1", @@ -579,17 +580,17 @@ public class PackageReaderTests textDirection: EpubTextDirection.LEFT_TO_RIGHT, language: "en" ) - } + ] ), - nestedCollections: new List() - { + nestedCollections: + [ new ( role: "http://example.org/roles/unit", metadata: new EpubMetadata ( - titles: new List() - { + titles: + [ new ( title: "Test title for collection 2", @@ -597,39 +598,39 @@ public class PackageReaderTests textDirection: EpubTextDirection.AUTO, language: "is" ) - } + ] ), nestedCollections: new List(), - links: new List(), + links: [], id: "collection-2", textDirection: EpubTextDirection.RIGHT_TO_LEFT, language: "is" ) - }, - links: new List() - { + ], + links: + [ new ( href: "https://example.com/onix/123", id: "collection-1-link", mediaType: "application/xml", - properties: new List() - { + properties: + [ EpubMetadataLinkProperty.ONIX - }, - relationships: new List() - { + ], + relationships: + [ EpubMetadataLinkRelationship.RECORD, EpubMetadataLinkRelationship.ONIX_RECORD - }, + ], hrefLanguage: null ) - }, + ], id: "collection-1", textDirection: EpubTextDirection.LEFT_TO_RIGHT, language: "en" ) - }, + ], id: "package-id", textDirection: EpubTextDirection.LEFT_TO_RIGHT, prefix: "foaf: http://xmlns.com/foaf/spec/", @@ -647,6 +648,20 @@ public class PackageReaderTests guide: null ); + private static EpubPackage Epub2PackageWithEmptyGuide => + new + ( + uniqueIdentifier: null, + epubVersion: EpubVersion.EPUB_2, + metadata: new EpubMetadata(), + manifest: new EpubManifest(), + spine: new EpubSpine + ( + toc: "ncx" + ), + guide: new EpubGuide() + ); + public static IEnumerable ReadMinimalPackageAsyncTestData { get @@ -677,6 +692,16 @@ public void ConstructorWithNullEpubReaderOptionsTest() _ = new PackageReader(null); } + [Fact(DisplayName = "Constructing a PackageReader instance with epubReaderOptions.PackageReaderOptions = null should succeed")] + public void ConstructorWithNullPackageReaderOptionsTest() + { + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = null! + }; + _ = new PackageReader(epubReaderOptions); + } + [Theory(DisplayName = "Reading a minimal OPF package should succeed")] [MemberData(nameof(ReadMinimalPackageAsyncTestData))] public async Task ReadMinimalPackageAsyncTest(string opfFileContent, EpubPackage expectedEpubPackage) @@ -691,8 +716,8 @@ public async Task ReadFullPackageAsyncTest(string opfFileContent, EpubPackage ex await TestSuccessfulReadOperation(opfFileContent, expectedEpubPackage); } - [Fact(DisplayName = "Trying to read OPF package from the EPUB file with no OPF package should fail with EpubContainerException")] - public async Task ReadPackageWithNoOpfFileTest() + [Fact(DisplayName = "Trying to read OPF package from the EPUB file with no OPF package with default EpubReaderOptions should fail with EpubContainerException")] + public async Task ReadPackageWithNoOpfFileAndDefaultOptionsTest() { TestZipFile testZipFile = new(); testZipFile.AddEntry(CONTAINER_FILE_PATH, CONTAINER_FILE); @@ -700,52 +725,165 @@ public async Task ReadPackageWithNoOpfFileTest() await Assert.ThrowsAsync(() => packageReader.ReadPackageAsync(testZipFile, OPF_FILE_PATH)); } - [Fact(DisplayName = "Trying to read OPF package with non-supported EPUB version should fail with EpubPackageException")] - public async Task ReadPackageWithNonSupportedEpubVersionTest() + [Fact(DisplayName = "Trying to read OPF package from the EPUB file with no OPF package with IgnoreMissingPackageFile = true should succeed")] + public async Task ReadPackageWithNoOpfFileAndIgnoreMissingPackageFileTest() { - await TestFailingReadOperation(OPF_FILE_WITH_NON_SUPPORTED_EPUB_VERSION); + TestZipFile testZipFile = new(); + testZipFile.AddEntry(CONTAINER_FILE_PATH, CONTAINER_FILE); + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new PackageReaderOptions() + { + IgnoreMissingPackageFile = true + } + }; + PackageReader packageReader = new(epubReaderOptions); + EpubPackage? epubPackage = await packageReader.ReadPackageAsync(testZipFile, OPF_FILE_PATH); + Assert.Null(epubPackage); + } + + [Fact(DisplayName = "ReadPackageAsync should throw EpubContainerException with an inner XmlException if the OPF package is not a valid XML file and no PackageReaderOptions are provided")] + public async Task ReadPackageWithInvalidXmlFileAndDefaultOptionsTest() + { + TestZipFile testZipFile = CreateTestZipFileWithOpfFile("not a valid XHTML file"); + PackageReader packageReader = new(); + EpubContainerException outerException = + await Assert.ThrowsAsync(() => packageReader.ReadPackageAsync(testZipFile, OPF_FILE_PATH)); + Assert.NotNull(outerException.InnerException); + Assert.Equal(typeof(XmlException), outerException.InnerException.GetType()); + } + + [Fact(DisplayName = "ReadPackageAsync should return null if the OPF package is not a valid XML file and IgnorePackageFileIsNotValidXmlError = true")] + public async Task ReadPackageWithInvalidXmlFileAndIgnorePackageFileIsNotValidXmlErrorTest() + { + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new PackageReaderOptions() + { + IgnorePackageFileIsNotValidXmlError = true + } + }; + await TestSuccessfulReadOperation("not a valid XHTML file", null, epubReaderOptions); } - [Fact(DisplayName = "Trying to read OPF package without 'package' XML node should fail with EpubPackageException")] - public async Task ReadPackageWithoutPackageNodeTest() + [Fact(DisplayName = "Trying to read OPF package without 'package' XML node with default EpubReaderOptions should fail with EpubPackageException")] + public async Task ReadPackageWithoutPackageNodeAndDefaultOptionsTest() { await TestFailingReadOperation(OPF_FILE_WITHOUT_PACKAGE); } - [Fact(DisplayName = "Trying to read OPF package without 'metadata' XML node should fail with EpubPackageException")] - public async Task ReadPackageWithoutMetadataNodeTest() + [Fact(DisplayName = "Trying to read OPF package without 'package' XML node with IgnoreMissingPackageNode = true should succeed")] + public async Task ReadPackageWithoutPackageNodeAndIgnoreMissingPackageNodeTest() + { + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new PackageReaderOptions() + { + IgnoreMissingPackageNode = true + } + }; + await TestSuccessfulReadOperation(OPF_FILE_WITHOUT_PACKAGE, null, epubReaderOptions); + } + + [Fact(DisplayName = "Trying to read OPF package without 'version' attribute in a package XML node with default EpubReaderOptions should fail with EpubPackageException")] + public async Task ReadPackageWithoutVersionAndDefaultOptionsTest() + { + await TestFailingReadOperation(OPF_FILE_WITHOUT_VERSION_IN_PACKAGE); + } + + [Fact(DisplayName = "Trying to read OPF package without 'version' attribute in a package XML node with FallbackEpubVersion should succeed")] + public async Task ReadPackageWithoutVersionAndFallbackEpubVersionTest() + { + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new PackageReaderOptions() + { + FallbackEpubVersion = EpubVersion.EPUB_3 + } + }; + await TestSuccessfulReadOperation(OPF_FILE_WITHOUT_VERSION_IN_PACKAGE, MinimalEpub3Package, epubReaderOptions); + } + + [Fact(DisplayName = "Trying to read OPF package with non-supported EPUB version with default EpubReaderOptions should fail with EpubPackageException")] + public async Task ReadPackageWithNonSupportedEpubVersionAndDefaultOptionsTest() + { + await TestFailingReadOperation(OPF_FILE_WITH_NON_SUPPORTED_EPUB_VERSION); + } + + [Fact(DisplayName = "Trying to read OPF package with non-supported EPUB version with FallbackEpubVersion should succeed")] + public async Task ReadPackageWithNonSupportedEpubVersionAndFallbackEpubVersionTest() + { + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new PackageReaderOptions() + { + FallbackEpubVersion = EpubVersion.EPUB_3 + } + }; + await TestSuccessfulReadOperation(OPF_FILE_WITH_NON_SUPPORTED_EPUB_VERSION, MinimalEpub3Package, epubReaderOptions); + } + + [Fact(DisplayName = "Trying to read OPF package without 'metadata' XML node with default EpubReaderOptions should fail with EpubPackageException")] + public async Task ReadPackageWithoutMetadataNodeAndDefaultOptionsTest() { await TestFailingReadOperation(OPF_FILE_WITHOUT_METADATA); } - [Fact(DisplayName = "Trying to read OPF package without 'manifest' XML node should fail with EpubPackageException")] - public async Task ReadPackageWithoutManifestNodeTest() + [Fact(DisplayName = "Trying to read OPF package without 'metadata' XML node with IgnoreMissingMetadataNode = true should succeed")] + public async Task ReadPackageWithoutMetadataNodeAndIgnoreMissingMetadataNodeTest() + { + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new PackageReaderOptions() + { + IgnoreMissingMetadataNode = true + } + }; + await TestSuccessfulReadOperation(OPF_FILE_WITHOUT_METADATA, MinimalEpub3Package, epubReaderOptions); + } + + [Fact(DisplayName = "Trying to read OPF package without 'manifest' XML node with default EpubReaderOptions should fail with EpubPackageException")] + public async Task ReadPackageWithoutManifestNodeAndDefaultOptionsTest() { await TestFailingReadOperation(OPF_FILE_WITHOUT_MANIFEST); } - [Fact(DisplayName = "Trying to read OPF package without 'spine' XML node should fail with EpubPackageException")] - public async Task ReadPackageWithoutSpineNodeTest() + [Fact(DisplayName = "Trying to read OPF package without 'manifest' XML node with IgnoreMissingManifestNode = true should succeed")] + public async Task ReadPackageWithoutManifestNodeAndIgnoreMissingManifestNodeTest() { - await TestFailingReadOperation(OPF_FILE_WITHOUT_SPINE); + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new PackageReaderOptions() + { + IgnoreMissingManifestNode = true + } + }; + await TestSuccessfulReadOperation(OPF_FILE_WITHOUT_MANIFEST, MinimalEpub3Package, epubReaderOptions); } - [Fact(DisplayName = "Trying to read OPF package without 'version' attribute in a package XML node should fail with EpubPackageException")] - public async Task ReadPackageWithoutVersionTest() + [Fact(DisplayName = "Trying to read OPF package without 'spine' XML node with default EpubReaderOptions should fail with EpubPackageException")] + public async Task ReadPackageWithoutSpineNodeAndDefaultOptionsTest() { - await TestFailingReadOperation(OPF_FILE_WITHOUT_VERSION_IN_PACKAGE); + await TestFailingReadOperation(OPF_FILE_WITHOUT_SPINE); } - [Fact(DisplayName = "Trying to read OPF package without 'id' attribute in a manifest item XML node should fail with EpubPackageException")] - public async Task ReadPackageWithoutManifestItemIdTest() + [Fact(DisplayName = "Trying to read OPF package without 'spine' XML node with IgnoreMissingSpineNode = true should succeed")] + public async Task ReadPackageWithoutSpineNodeAndIgnoreMissingSpineNodeTest() { - await TestFailingReadOperation(OPF_FILE_WITHOUT_ID_IN_MANIFEST_ITEM); + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new PackageReaderOptions() + { + IgnoreMissingSpineNode = true + } + }; + await TestSuccessfulReadOperation(OPF_FILE_WITHOUT_SPINE, MinimalEpub3Package, epubReaderOptions); } - [Fact(DisplayName = "Trying to read OPF package without 'id' attribute in a manifest item XML node and null PackageReaderOptions should fail with EpubPackageException")] - public async Task ReadPackageWithoutManifestItemIdWithNullPackageReaderOptionsTest() + [Fact(DisplayName = "Trying to read OPF package without 'id' attribute in a manifest item XML node with default EpubReaderOptions should fail with EpubPackageException")] + public async Task ReadPackageWithoutManifestItemIdAndDefaultOptionsTest() { - await TestFailingReadOperationWithNullPackageReaderOptions(OPF_FILE_WITHOUT_ID_IN_MANIFEST_ITEM); + await TestFailingReadOperation(OPF_FILE_WITHOUT_ID_IN_MANIFEST_ITEM); } [Fact(DisplayName = "Trying to read OPF package without 'id' attribute in a manifest item XML node with SkipInvalidManifestItems = true should succeed")] @@ -757,104 +895,141 @@ public async Task ReadPackageWithoutManifestItemIdWithSkippingInvalidManifestIte [Fact(DisplayName = "Read an OPF package with a URI-escaped 'href' attribute in a manifest item XML node should succeed")] public async Task ReadPackageWithEscapedManifestItemHrefTest() { - EpubPackage expectedPackage = new - ( - uniqueIdentifier: null, - epubVersion: EpubVersion.EPUB_3, - metadata: new EpubMetadata(), - manifest: new EpubManifest + EpubPackage expectedPackage = MinimalEpub3Package; + expectedPackage.Manifest.Items.Add( + new EpubManifestItem ( - id: null, - items: new List() - { - new - ( - id: "item-1", - href: "chapter1.html", - mediaType: "application/xhtml+xml" - ) - } - ), - spine: new EpubSpine(), - guide: null + id: "item-1", + href: "chapter1.html", + mediaType: "application/xhtml+xml" + ) ); await TestSuccessfulReadOperationWithSkippingInvalidManifestItems(OPF_FILE_WITH_ESCAPED_HREF_IN_MANIFEST_ITEM, expectedPackage); } - [Fact(DisplayName = "Trying to read OPF package without 'href' attribute in a manifest item XML node should fail with EpubPackageException")] - public async Task ReadPackageWithoutManifestItemHrefTest() + [Fact(DisplayName = "Trying to read OPF package without 'href' attribute in a manifest item XML node with default EpubReaderOptions should fail with EpubPackageException")] + public async Task ReadPackageWithoutManifestItemHrefAndDefaultOptionsTest() { await TestFailingReadOperation(OPF_FILE_WITHOUT_HREF_IN_MANIFEST_ITEM); } - [Fact(DisplayName = "Trying to read OPF package without 'href' attribute in a manifest item XML node and null PackageReaderOptions should fail with EpubPackageException")] - public async Task ReadPackageWithoutManifestItemHrefWithNullPackageReaderOptionsTest() - { - await TestFailingReadOperationWithNullPackageReaderOptions(OPF_FILE_WITHOUT_HREF_IN_MANIFEST_ITEM); - } - [Fact(DisplayName = "Trying to read OPF package without 'href' attribute in a manifest item XML node with SkipInvalidManifestItems = true should succeed")] public async Task ReadPackageWithoutManifestItemHrefWithSkippingInvalidManifestItemsTest() { await TestSuccessfulReadOperationWithSkippingInvalidManifestItems(OPF_FILE_WITHOUT_HREF_IN_MANIFEST_ITEM, MinimalEpub3Package); } - [Fact(DisplayName = "Trying to read OPF package without 'media-type' attribute in a manifest item XML node should fail with EpubPackageException")] - public async Task ReadPackageWithoutManifestItemMediaTypeTest() + [Fact(DisplayName = "Trying to read OPF package without 'media-type' attribute in a manifest item XML node with default EpubReaderOptions should fail with EpubPackageException")] + public async Task ReadPackageWithoutManifestItemMediaTypeAndDefaultOptionsTest() { await TestFailingReadOperation(OPF_FILE_WITHOUT_MEDIA_TYPE_IN_MANIFEST_ITEM); } - [Fact(DisplayName = "Trying to read OPF package without 'media-type' attribute in a manifest item XML node and null PackageReaderOptions should fail with EpubPackageException")] - public async Task ReadPackageWithoutManifestItemMediaTypeWithNullPackageReaderOptionsTest() - { - await TestFailingReadOperationWithNullPackageReaderOptions(OPF_FILE_WITHOUT_MEDIA_TYPE_IN_MANIFEST_ITEM); - } - [Fact(DisplayName = "Trying to read OPF package without 'media-type' attribute in a manifest item XML node with SkipInvalidManifestItems = true should succeed")] public async Task ReadPackageWithoutManifestItemMediaTypeWithSkippingInvalidManifestItemsTest() { await TestSuccessfulReadOperationWithSkippingInvalidManifestItems(OPF_FILE_WITHOUT_MEDIA_TYPE_IN_MANIFEST_ITEM, MinimalEpub3Package); } - [Fact(DisplayName = "Trying to read OPF package with duplicating 'id' attributes in manifest item XML nodes should fail with EpubPackageException")] - public async Task ReadPackageWithDuplicatingManifestItemIdsTest() + [Fact(DisplayName = "Trying to read OPF package with duplicating 'id' attributes in manifest item XML nodes with default EpubReaderOptions should fail with EpubPackageException")] + public async Task ReadPackageWithDuplicatingManifestItemIdsAndDefaultOptionsTest() { - await TestFailingReadOperationWithNullPackageReaderOptions(OPF_FILE_WITH_DUPLICATING_MANIFEST_ITEM_IDS); + await TestFailingReadOperation(OPF_FILE_WITH_DUPLICATING_MANIFEST_ITEM_IDS); } - [Fact(DisplayName = "Trying to read OPF package with duplicating 'href' attributes in manifest item XML nodes should fail with EpubPackageException")] - public async Task ReadPackageWithDuplicatingManifestItemHrefsTest() + [Fact(DisplayName = "Trying to read OPF package with duplicating 'id' attributes in manifest item XML nodes with SkipDuplicateManifestItemIds = true should succeed")] + public async Task ReadPackageWithDuplicatingManifestItemIdsAndSkipDuplicateManifestItemIdsTest() { - await TestFailingReadOperationWithNullPackageReaderOptions(OPF_FILE_WITH_DUPLICATING_MANIFEST_ITEM_HREFS); + EpubPackage expectedPackage = MinimalEpub3Package; + expectedPackage.Manifest.Items.Add( + new EpubManifestItem + ( + id: "unique", + href: "chapter1.html", + mediaType: "application/xhtml+xml" + ) + ); + expectedPackage.Manifest.Items.Add( + new EpubManifestItem + ( + id: "duplicate", + href: "chapter2.html", + mediaType: "application/xhtml+xml" + ) + ); + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new PackageReaderOptions() + { + SkipDuplicateManifestItemIds = true + } + }; + await TestSuccessfulReadOperation(OPF_FILE_WITH_DUPLICATING_MANIFEST_ITEM_IDS, expectedPackage, epubReaderOptions); } - [Fact(DisplayName = "Trying to read EPUB 2 OPF package without 'toc' attribute in the spine XML node should fail with EpubPackageException")] - public async Task ReadEpub2PackageWithoutSpineTocTest() + [Fact(DisplayName = "Trying to read OPF package with duplicating 'href' attributes in manifest item XML nodes with default EpubReaderOptions should fail with EpubPackageException")] + public async Task ReadPackageWithDuplicatingManifestItemHrefsAndDefaultOptionsTest() { - await TestFailingReadOperation(EPUB2_OPF_FILE_WITHOUT_SPINE_TOC); + await TestFailingReadOperation(OPF_FILE_WITH_DUPLICATING_MANIFEST_ITEM_HREFS); } - [Fact(DisplayName = "Trying to read EPUB 2 OPF package without 'toc' attribute in the spine XML node and null PackageReaderOptions should fail with EpubPackageException")] - public async Task ReadEpub2PackageWithoutSpineTocWithNullPackageReaderOptionsTest() + [Fact(DisplayName = "Trying to read OPF package with duplicating 'href' attributes in manifest item XML nodes with SkipDuplicateManifestHrefs = true should succeed")] + public async Task ReadPackageWithDuplicatingManifestItemHrefsAndSkipDuplicateManifestHrefsTest() { - await TestFailingReadOperationWithNullPackageReaderOptions(EPUB2_OPF_FILE_WITHOUT_SPINE_TOC); + EpubPackage expectedPackage = MinimalEpub3Package; + expectedPackage.Manifest.Items.Add( + new EpubManifestItem + ( + id: "item-1", + href: "unique.html", + mediaType: "application/xhtml+xml" + ) + ); + expectedPackage.Manifest.Items.Add( + new EpubManifestItem + ( + id: "item-2", + href: "duplicate.html", + mediaType: "application/xhtml+xml" + ) + ); + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new PackageReaderOptions() + { + SkipDuplicateManifestHrefs = true + } + }; + await TestSuccessfulReadOperation(OPF_FILE_WITH_DUPLICATING_MANIFEST_ITEM_HREFS, expectedPackage, epubReaderOptions); } - [Fact(DisplayName = "Trying to read EPUB 2 OPF package with empty 'toc' attribute in the spine XML node should fail with EpubPackageException")] - public async Task ReadEpub2PackageWithEmptySpineTocTest() + [Fact(DisplayName = "Trying to read EPUB 2 OPF package without 'toc' attribute in the spine XML node with default EpubReaderOptions should fail with EpubPackageException")] + public async Task ReadEpub2PackageWithoutSpineTocAndDefaultOptionsTest() { - await TestFailingReadOperation(EPUB2_OPF_FILE_WITH_EMPTY_SPINE_TOC); + await TestFailingReadOperation(EPUB2_OPF_FILE_WITHOUT_SPINE_TOC); } - [Fact(DisplayName = "Trying to read EPUB 2 OPF package with empty 'toc' attribute in the spine XML node and null PackageReaderOptions should fail with EpubPackageException")] - public async Task ReadEpub2PackageWithEmptySpineTocWithNullPackageReaderOptionsTest() + [Fact(DisplayName = "Trying to read EPUB 2 OPF package without 'toc' attribute in the spine XML node with IgnoreMissingToc = true should succeed")] + public async Task ReadEpub2PackageWithoutSpineTocAndIgnoreMissingTocTest() { - await TestFailingReadOperationWithNullPackageReaderOptions(EPUB2_OPF_FILE_WITH_EMPTY_SPINE_TOC); + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new PackageReaderOptions() + { + IgnoreMissingToc = true + } + }; + await TestSuccessfulReadOperation(EPUB2_OPF_FILE_WITHOUT_SPINE_TOC, Epub2PackageWithoutSpineToc, epubReaderOptions); } - [Fact(DisplayName = "Trying to read EPUB 2 OPF package without 'toc' attribute in the spine XML node with IgnoreMissingToc = true should succeed")] - public async Task ReadEpub2PackageWithoutSpineTocWithIgnoreMissingTocTest() + [Fact(DisplayName = "Trying to read EPUB 2 OPF package with empty 'toc' attribute in the spine XML node with default EpubReaderOptions should fail with EpubPackageException")] + public async Task ReadEpub2PackageWithEmptySpineTocAndDefaultOptionsTest() + { + await TestFailingReadOperation(EPUB2_OPF_FILE_WITH_EMPTY_SPINE_TOC); + } + + [Fact(DisplayName = "Trying to read EPUB 2 OPF package with empty 'toc' attribute in the spine XML node with IgnoreMissingToc = true should succeed")] + public async Task ReadEpub2PackageWithEmptySpineTocAndIgnoreMissingTocTest() { EpubReaderOptions epubReaderOptions = new() { @@ -863,38 +1038,90 @@ public async Task ReadEpub2PackageWithoutSpineTocWithIgnoreMissingTocTest() IgnoreMissingToc = true } }; - await TestSuccessfulReadOperation(EPUB2_OPF_FILE_WITHOUT_SPINE_TOC, Epub2PackageWithoutSpineToc, epubReaderOptions); + await TestSuccessfulReadOperation(EPUB2_OPF_FILE_WITH_EMPTY_SPINE_TOC, Epub2PackageWithoutSpineToc, epubReaderOptions); } - [Fact(DisplayName = "Trying to read OPF package without 'idref' attribute in a spine item ref XML node should fail with EpubPackageException")] - public async Task ReadPackageWithoutSpineItemRefIdRefTest() + [Fact(DisplayName = "Trying to read OPF package without 'idref' attribute in a spine item ref XML node with default EpubReaderOptions should fail with EpubPackageException")] + public async Task ReadPackageWithoutSpineItemRefIdRefAndDefaultOptionsTest() { await TestFailingReadOperation(OPF_FILE_WITHOUT_IDREF_IN_SPINE_ITEMREF); } - [Fact(DisplayName = "Trying to read OPF package without 'type' attribute in a guide reference XML node should fail with EpubPackageException")] - public async Task ReadPackageWithoutGuideReferenceTypeTest() + [Fact(DisplayName = "Trying to read OPF package without 'idref' attribute in a spine item ref XML node with SkipInvalidSpineItems = true should succeed")] + public async Task ReadPackageWithoutSpineItemRefIdRefAndSkipInvalidSpineItemsTest() + { + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new PackageReaderOptions() + { + SkipInvalidSpineItems = true + } + }; + await TestSuccessfulReadOperation(OPF_FILE_WITHOUT_IDREF_IN_SPINE_ITEMREF, MinimalEpub3Package, epubReaderOptions); + } + + [Fact(DisplayName = "Trying to read OPF package without 'type' attribute in a guide reference XML node with default EpubReaderOptions should fail with EpubPackageException")] + public async Task ReadPackageWithoutGuideReferenceTypeAndDefaultOptionsTest() { await TestFailingReadOperation(OPF_FILE_WITHOUT_TYPE_IN_GUIDE_REFERENCE); } - [Fact(DisplayName = "Trying to read OPF package without 'href' attribute in a guide reference XML node should fail with EpubPackageException")] - public async Task ReadPackageWithoutGuideReferenceHrefTest() + [Fact(DisplayName = "Trying to read OPF package without 'type' attribute in a guide reference XML node with SkipInvalidGuideReferences = true should succeed")] + public async Task ReadPackageWithoutGuideReferenceTypeAndSkipInvalidGuideReferencesTest() + { + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new PackageReaderOptions() + { + SkipInvalidGuideReferences = true + } + }; + await TestSuccessfulReadOperation(OPF_FILE_WITHOUT_TYPE_IN_GUIDE_REFERENCE, Epub2PackageWithEmptyGuide, epubReaderOptions); + } + + [Fact(DisplayName = "Trying to read OPF package without 'href' attribute in a guide reference XML node with default EpubReaderOptions should fail with EpubPackageException")] + public async Task ReadPackageWithoutGuideReferenceHrefAndDefaultOptionsTest() { await TestFailingReadOperation(OPF_FILE_WITHOUT_HREF_IN_GUIDE_REFERENCE); } - [Fact(DisplayName = "Trying to read OPF package without 'role' attribute in a collection XML node should fail with EpubPackageException")] - public async Task ReadPackageWithoutCollectionRoleTest() + [Fact(DisplayName = "Trying to read OPF package without 'href' attribute in a guide reference XML node with SkipInvalidGuideReferences = true should succeed")] + public async Task ReadPackageWithoutGuideReferenceHrefAndSkipInvalidGuideReferencesTest() + { + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new PackageReaderOptions() + { + SkipInvalidGuideReferences = true + } + }; + await TestSuccessfulReadOperation(OPF_FILE_WITHOUT_HREF_IN_GUIDE_REFERENCE, Epub2PackageWithEmptyGuide, epubReaderOptions); + } + + [Fact(DisplayName = "Trying to read OPF package without 'role' attribute in a collection XML node with default EpubReaderOptions should fail with EpubPackageException")] + public async Task ReadPackageWithoutCollectionRoleAndDefaultOptionsTest() { await TestFailingReadOperation(OPF_FILE_WITHOUT_ROLE_IN_COLLECTION); } - private static async Task TestSuccessfulReadOperation(string opfFileContent, EpubPackage expectedEpubPackage, EpubReaderOptions? epubReaderOptions = null) + [Fact(DisplayName = "Trying to read OPF package without 'role' attribute in a collection XML node with SkipInvalidCollections = true should succeed")] + public async Task ReadPackageWithoutCollectionRoleAndSkipInvalidCollectionsTest() + { + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new PackageReaderOptions() + { + SkipInvalidCollections = true + } + }; + await TestSuccessfulReadOperation(OPF_FILE_WITHOUT_ROLE_IN_COLLECTION, MinimalEpub3Package, epubReaderOptions); + } + + private static async Task TestSuccessfulReadOperation(string opfFileContent, EpubPackage? expectedEpubPackage, EpubReaderOptions? epubReaderOptions = null) { TestZipFile testZipFile = CreateTestZipFileWithOpfFile(opfFileContent); PackageReader packageReader = new(epubReaderOptions ?? new EpubReaderOptions()); - EpubPackage actualEpubPackage = await packageReader.ReadPackageAsync(testZipFile, OPF_FILE_PATH); + EpubPackage? actualEpubPackage = await packageReader.ReadPackageAsync(testZipFile, OPF_FILE_PATH); EpubPackageComparer.CompareEpubPackages(expectedEpubPackage, actualEpubPackage); } @@ -910,20 +1137,22 @@ private static Task TestSuccessfulReadOperationWithSkippingInvalidManifestItems( return TestSuccessfulReadOperation(opfFileContent, expectedEpubPackage, epubReaderOptions); } - private static async Task TestFailingReadOperation(string opfFileContent, EpubReaderOptions? epubReaderOptions = null) - { - TestZipFile testZipFile = CreateTestZipFileWithOpfFile(opfFileContent); - PackageReader packageReader = new(epubReaderOptions ?? new EpubReaderOptions()); - await Assert.ThrowsAsync(() => packageReader.ReadPackageAsync(testZipFile, OPF_FILE_PATH)); - } - - private static Task TestFailingReadOperationWithNullPackageReaderOptions(string opfFileContent) + private static async Task TestFailingReadOperation(string opfFileContent) { - EpubReaderOptions epubReaderOptions = new() + EpubReaderOptions epubReaderOptions = new(); + await TestFailingReadOperation(opfFileContent, epubReaderOptions); + epubReaderOptions = new() { PackageReaderOptions = null! }; - return TestFailingReadOperation(opfFileContent, epubReaderOptions); + await TestFailingReadOperation(opfFileContent, epubReaderOptions); + } + + private static async Task TestFailingReadOperation(string opfFileContent, EpubReaderOptions? epubReaderOptions) + { + TestZipFile testZipFile = CreateTestZipFileWithOpfFile(opfFileContent); + PackageReader packageReader = new(epubReaderOptions); + await Assert.ThrowsAsync(() => packageReader.ReadPackageAsync(testZipFile, OPF_FILE_PATH)); } private static TestZipFile CreateTestZipFileWithOpfFile(string opfFileContent) diff --git a/Source/VersOne.Epub.Test/Unit/Readers/RootFilePathReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/RootFilePathReaderTests.cs deleted file mode 100644 index 7378bf7..0000000 --- a/Source/VersOne.Epub.Test/Unit/Readers/RootFilePathReaderTests.cs +++ /dev/null @@ -1,92 +0,0 @@ -using VersOne.Epub.Internal; -using VersOne.Epub.Options; -using VersOne.Epub.Test.Unit.Mocks; - -namespace VersOne.Epub.Test.Unit.Readers -{ - public class RootFilePathReaderTests - { - private const string EPUB_CONTAINER_FILE_PATH = "META-INF/container.xml"; - private const string ROOT_FILE_PATH_FOR_CORRECT_CONTAINER_FILE = "OEBPS/content.opf"; - - private const string CORRECT_CONTAINER_FILE = $""" - - - - - - - """; - - private const string INCORRECT_CONTAINER_FILE_NO_FULL_PATH_ATTRIBUTE = $""" - - - - - - - """; - - private const string INCORRECT_CONTAINER_FILE_NO_ROOTFILE_ELEMENT = $""" - - - - - - """; - - private const string INCORRECT_CONTAINER_FILE_NO_ROOTFILES_ELEMENT = $""" - - - - """; - - private const string INCORRECT_CONTAINER_FILE_NO_CONTAINER_ELEMENT = $""" - - - """; - - [Fact(DisplayName = "Constructing a RootFilePathReader instance with a non-null epubReaderOptions parameter should succeed")] - public void ConstructorWithNonNullEpubReaderOptionsTest() - { - _ = new RootFilePathReader(new EpubReaderOptions()); - } - - [Fact(DisplayName = "Constructing a RootFilePathReader instance with a null epubReaderOptions parameter should succeed")] - public void ConstructorWithNullEpubReaderOptionsTest() - { - _ = new RootFilePathReader(null); - } - - [Fact(DisplayName = "Getting root file path from a ZIP archive with a correct container file should succeed")] - public async Task TestGetRootFilePathAsyncWithCorrectContainerFile() - { - TestZipFile testZipFile = new(); - testZipFile.AddEntry(EPUB_CONTAINER_FILE_PATH, CORRECT_CONTAINER_FILE); - RootFilePathReader rootFilePathReader = new(); - string actualRootFilePath = await rootFilePathReader.GetRootFilePathAsync(testZipFile); - Assert.Equal(ROOT_FILE_PATH_FOR_CORRECT_CONTAINER_FILE, actualRootFilePath); - } - - [Fact(DisplayName = "Getting root file path from a ZIP archive without a container file should throw EpubContainerException")] - public async Task TestGetRootFilePathAsyncWithNoContainerFile() - { - TestZipFile testZipFile = new(); - RootFilePathReader rootFilePathReader = new(); - await Assert.ThrowsAsync(() => rootFilePathReader.GetRootFilePathAsync(testZipFile)); - } - - [Theory(DisplayName = "Getting root file path from a ZIP archive with an incorrect container file should throw EpubContainerException")] - [InlineData(INCORRECT_CONTAINER_FILE_NO_FULL_PATH_ATTRIBUTE)] - [InlineData(INCORRECT_CONTAINER_FILE_NO_ROOTFILE_ELEMENT)] - [InlineData(INCORRECT_CONTAINER_FILE_NO_ROOTFILES_ELEMENT)] - [InlineData(INCORRECT_CONTAINER_FILE_NO_CONTAINER_ELEMENT)] - public async Task TestGetRootFilePathAsyncWithIncorrectContainerFile(string containerFileContent) - { - TestZipFile testZipFile = new(); - testZipFile.AddEntry(EPUB_CONTAINER_FILE_PATH, containerFileContent); - RootFilePathReader rootFilePathReader = new(); - await Assert.ThrowsAsync(() => rootFilePathReader.GetRootFilePathAsync(testZipFile)); - } - } -} diff --git a/Source/VersOne.Epub.Test/Unit/Readers/SchemaReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/SchemaReaderTests.cs index d3872c1..4e04c9d 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/SchemaReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/SchemaReaderTests.cs @@ -3,6 +3,7 @@ using VersOne.Epub.Schema; using VersOne.Epub.Test.Comparers; using VersOne.Epub.Test.Unit.Mocks; +using VersOne.Epub.Test.Unit.TestData; namespace VersOne.Epub.Test.Unit.Readers { @@ -98,16 +99,16 @@ public class SchemaReaderTests - - - - - - @@ -142,33 +143,33 @@ public async Task ReadSchemaAsyncTest() epubVersion: EpubVersion.EPUB_3, metadata: new EpubMetadata ( - titles: new List() - { + titles: + [ new ( title: "Test title" ) - }, - creators: new List() - { + ], + creators: + [ new ( creator: "John Doe" ) - }, - identifiers: new List() - { + ], + identifiers: + [ new ( identifier: "9781234567890", id: "book-uid" ) - } + ] ), manifest: new EpubManifest ( - items: new List() - { + items: + [ new ( id: "item-1", @@ -186,10 +187,10 @@ public async Task ReadSchemaAsyncTest() id: "item-toc", href: NAV_FILE_NAME, mediaType: "application/xhtml+xml", - properties: new List() - { + properties: + [ EpubManifestProperty.NAV - } + ] ), new ( @@ -197,13 +198,13 @@ public async Task ReadSchemaAsyncTest() href: NCX_FILE_NAME, mediaType: "application/x-dtbncx+xml" ) - } + ] ), spine: new EpubSpine ( toc: "ncx", - items: new List() - { + items: + [ new ( id: "itemref-1", @@ -216,7 +217,7 @@ public async Task ReadSchemaAsyncTest() idRef: "item-2", isLinear: true ) - } + ] ), guide: null ), @@ -235,24 +236,24 @@ public async Task ReadSchemaAsyncTest() } ), docTitle: "Test title", - docAuthors: new List() - { + docAuthors: + [ "John Doe" - }, + ], navMap: new Epub2NcxNavigationMap ( - items: new List() - { + items: + [ new ( id: "navpoint-1", - navigationLabels: new List() - { + navigationLabels: + [ new ( text: "Chapter 1" ) - }, + ], content: new Epub2NcxContent ( source: "chapter1.html" @@ -261,33 +262,33 @@ public async Task ReadSchemaAsyncTest() new ( id: "navpoint-2", - navigationLabels: new List() - { + navigationLabels: + [ new ( text: "Chapter 2" ) - }, + ], content: new Epub2NcxContent ( source: "chapter2.html" ) ) - } + ] ) ), epub3NavDocument: new Epub3NavDocument ( filePath: NAV_FILE_PATH, - navs: new List() - { + navs: + [ new ( type: Epub3StructuralSemanticsProperty.TOC, ol: new Epub3NavOl ( - lis: new List() - { + lis: + [ new ( anchor: new Epub3NavAnchor @@ -304,13 +305,13 @@ public async Task ReadSchemaAsyncTest() text: "Chapter 2" ) ) - } + ] ) ) - } + ] ), - mediaOverlays: new List() - { + mediaOverlays: + [ new ( id: null, @@ -322,9 +323,9 @@ public async Task ReadSchemaAsyncTest() id: null, epubTypes: null, epubTextRef: null, - seqs: new List(), - pars: new List() - { + seqs: [], + pars: + [ new ( id: "sentence1", @@ -376,15 +377,47 @@ public async Task ReadSchemaAsyncTest() clipEnd: "30s" ) ) - } + ] ) ) - }, + ], contentDirectoryPath: CONTENT_DIRECTORY_PATH ); SchemaReader schemaReader = new(); - EpubSchema actualEpubSchema = await schemaReader.ReadSchemaAsync(testZipFile); + EpubSchema? actualEpubSchema = await schemaReader.ReadSchemaAsync(testZipFile); EpubSchemaComparer.CompareEpubSchemas(expectedEpubSchema, actualEpubSchema); } + + [Fact(DisplayName = "ReadSchemaAsync should return null if the ContainerFileReader returned a null package file path")] + public async Task ReadSchemaAsyncWithNullPackageFilePathTest() + { + TestZipFile testZipFile = new(); + EpubReaderOptions epubReaderOptions = new() + { + ContainerFileReaderOptions = new() + { + IgnoreMissingContainerFile = true + } + }; + SchemaReader schemaReader = new(epubReaderOptions); + EpubSchema? actualEpubSchema = await schemaReader.ReadSchemaAsync(testZipFile); + Assert.Null(actualEpubSchema); + } + + [Fact(DisplayName = "ReadSchemaAsync should return null if the PackageReader returned a null package")] + public async Task ReadSchemaAsyncWithNullPackageTest() + { + TestZipFile testZipFile = TestEpubFiles.CreateTestEpubFileWithoutPackage(); + EpubReaderOptions epubReaderOptions = new() + { + PackageReaderOptions = new() + { + IgnoreMissingPackageFile = true + } + }; + SchemaReader schemaReader = new(epubReaderOptions); + EpubSchema? actualEpubSchema = await schemaReader.ReadSchemaAsync(testZipFile); + Assert.Null(actualEpubSchema); + } } } diff --git a/Source/VersOne.Epub.Test/Unit/Readers/SmilReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/SmilReaderTests.cs index 1c1afc1..a2f1f57 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/SmilReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/SmilReaderTests.cs @@ -1,4 +1,5 @@ -using System.Xml.Linq; +using System.Xml; +using System.Xml.Linq; using VersOne.Epub.Internal; using VersOne.Epub.Options; using VersOne.Epub.Schema; @@ -65,6 +66,18 @@ public class SmilReaderTests """; + private const string SMIL_FILE_WITHOUT_SMIL_VERSION = """ + + + + + + + + + """; + private const string SMIL_FILE_WITH_WRONG_SMIL_VERSION = """ @@ -160,9 +173,9 @@ public class SmilReaderTests id: null, epubTypes: null, epubTextRef: null, - seqs: new List(), - pars: new List() - { + seqs: [], + pars: + [ new ( id: null, @@ -180,7 +193,7 @@ public class SmilReaderTests clipEnd: "10s" ) ) - } + ] ) ); @@ -196,48 +209,48 @@ public class SmilReaderTests ( metadata: new SmilMetadata ( - items: new List() - { + items: + [ new(SmilXmlNamespace + "item1", "value1"), new(SmilXmlNamespace + "item2", "value2") - } + ] ) ), body: new SmilBody ( id: "body", - epubTypes: new List() - { + epubTypes: + [ Epub3StructuralSemanticsProperty.BODYMATTER - }, + ], epubTextRef: "chapter1.html", - seqs: new List() - { + seqs: + [ new ( id: "seq1", - epubTypes: new List() - { + epubTypes: + [ Epub3StructuralSemanticsProperty.CHAPTER - }, + ], epubTextRef: "chapter1.html#section1", - seqs: new List() - { + seqs: + [ new ( id: "seq2", epubTypes: null, epubTextRef: "chapter1.html#figure1", - seqs: new List(), - pars: new List() - { + seqs: [], + pars: + [ new ( id: "par3", - epubTypes: new List() - { + epubTypes: + [ Epub3StructuralSemanticsProperty.FIGURE - }, + ], text: new SmilText ( id: "text3", @@ -254,10 +267,10 @@ public class SmilReaderTests new ( id: "par4", - epubTypes: new List() - { + epubTypes: + [ Epub3StructuralSemanticsProperty.TITLE - }, + ], text: new SmilText ( id: "text4", @@ -271,11 +284,11 @@ public class SmilReaderTests clipEnd: "0:24:50.010" ) ) - } + ] ) - }, - pars: new List() - { + ], + pars: + [ new ( id: "par1", @@ -310,12 +323,10 @@ public class SmilReaderTests clipEnd: "0:24:15.000" ) ) - } + ] ) - }, - pars: new List() - { - } + ], + pars: [] ) ); @@ -331,9 +342,9 @@ public class SmilReaderTests id: null, epubTypes: null, epubTextRef: null, - seqs: new List(), - pars: new List() - { + seqs: [], + pars: + [ new ( id: null, @@ -351,10 +362,83 @@ public class SmilReaderTests clipEnd: "10s" ) ) - } + ] + ) + ); + + private static Smil MinimalSmilWithEmptyBody => + new + ( + id: null, + version: SmilVersion.SMIL_3, + epubPrefix: null, + head: null, + body: new SmilBody + ( + id: null, + epubTypes: null, + epubTextRef: null, + seqs: [], + pars: [] + ) + ); + + private static Smil MinimalSmilWithEmptySeq => + new + ( + id: null, + version: SmilVersion.SMIL_3, + epubPrefix: null, + head: null, + body: new SmilBody + ( + id: null, + epubTypes: null, + epubTextRef: null, + seqs: + [ + new + ( + id: null, + epubTypes: null, + epubTextRef: null, + seqs: [], + pars: [] + ) + ], + pars: [] ) ); + private static Smil MinimalSmilWithoutAudio => + new + ( + id: null, + version: SmilVersion.SMIL_3, + epubPrefix: null, + head: null, + body: new SmilBody + ( + id: null, + epubTypes: null, + epubTextRef: null, + seqs: [], + pars: + [ + new + ( + id: null, + epubTypes: null, + text: new SmilText + ( + id: null, + src: "chapter1.html#paragraph1" + ), + audio: null + ) + ] + ) + ); [Fact(DisplayName = "Constructing a SmilReader instance with a non-null epubReaderOptions parameter should succeed")] public void ConstructorWithNonNullEpubReaderOptionsTest() @@ -368,6 +452,15 @@ public void ConstructorWithNullEpubReaderOptionsTest() _ = new SmilReader(null); } + [Fact(DisplayName = "Constructing a SmilReader instance with a null SmilReaderOptions property inside the epubReaderOptions parameter should succeed")] + public void ConstructorWithNullSmilReaderOptionsTest() + { + _ = new SmilReader(new EpubReaderOptions + { + SmilReaderOptions = null! + }); + } + [Fact(DisplayName = "Reading a minimal SMIL file should succeed")] public async Task ReadSmilAsyncWithMinimalSmilFileTest() { @@ -392,21 +485,37 @@ public async Task ReadAllSmilDocumentsAsyncTest() testEpubPackage.Manifest.Items.Add(new EpubManifestItem("smil1", chapter1SmilFileName, "application/smil+xml")); testEpubPackage.Manifest.Items.Add(new EpubManifestItem("smil2", chapter2SmilFileName, "application/smil+xml")); SmilReader smilReader = new(); - List expectedSmils = new() { MinimalSmil, FullSmil }; + List expectedSmils = [MinimalSmil, FullSmil]; List actualSmils = await smilReader.ReadAllSmilDocumentsAsync(testZipFile, CONTENT_DIRECTORY_PATH, testEpubPackage); SmilComparers.CompareSmilLists(expectedSmils, actualSmils); } - [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if EPUB file is missing the specified SMIL file")] - public async Task ReadSmilAsyncWithoutSmilFileTest() + [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if EPUB file is missing the specified SMIL file and no SmilReaderOptions are provided")] + public async Task ReadSmilAsyncWithoutSmilFileAndDefaultOptionsTest() { TestZipFile testZipFile = new(); SmilReader smilReader = new(); await Assert.ThrowsAsync(() => smilReader.ReadSmilAsync(testZipFile, SMIL_FILE_PATH)); } - [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the SMIL file is larger than 2 GB")] - public async Task ReadSmilAsyncWithLargeSmilFileTest() + [Fact(DisplayName = "ReadSmilAsync should return null if EPUB file is missing the specified SMIL file and IgnoreMissingSmilFileError = true")] + public async Task ReadSmilAsyncWithoutSmilFileAndIgnoreMissingSmilFileErrorTest() + { + TestZipFile testZipFile = new(); + EpubReaderOptions epubReaderOptions = new() + { + SmilReaderOptions = new() + { + IgnoreMissingSmilFileError = true + } + }; + SmilReader smilReader = new(epubReaderOptions); + Smil? actualSmil = await smilReader.ReadSmilAsync(testZipFile, SMIL_FILE_PATH); + Assert.Null(actualSmil); + } + + [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the SMIL file is larger than 2 GB and no SmilReaderOptions are provided")] + public async Task ReadSmilAsyncWithLargeSmilFileAndDefaultOptionsTest() { TestZipFile testZipFile = new(); testZipFile.AddEntry(SMIL_FILE_PATH, new Test4GbZipFileEntry()); @@ -414,65 +523,231 @@ public async Task ReadSmilAsyncWithLargeSmilFileTest() await Assert.ThrowsAsync(() => smilReader.ReadSmilAsync(testZipFile, SMIL_FILE_PATH)); } - [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the SMIL file has no 'smil' XML element")] - public async Task ReadSmilAsyncWithoutSmilElementTest() + [Fact(DisplayName = "ReadSmilAsync should return null if the SMIL file is larger than 2 GB and IgnoreSmilFileIsTooLargeError = true")] + public async Task ReadSmilAsyncWithLargeSmilFileAndIgnoreSmilFileIsTooLargeErrorTest() + { + TestZipFile testZipFile = new(); + testZipFile.AddEntry(SMIL_FILE_PATH, new Test4GbZipFileEntry()); + EpubReaderOptions epubReaderOptions = new() + { + SmilReaderOptions = new() + { + IgnoreSmilFileIsTooLargeError = true + } + }; + SmilReader smilReader = new(epubReaderOptions); + Smil? actualSmil = await smilReader.ReadSmilAsync(testZipFile, SMIL_FILE_PATH); + Assert.Null(actualSmil); + } + + [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException with an inner XmlException if the SMIL file is not a valid XML file and no SmilReaderOptions are provided")] + public async Task ReadEpub3NavDocumentAsyncWithInvalidXhtmlFileAndDefaultOptionsTest() + { + TestZipFile testZipFile = CreateTestZipFileWithSmilFile("not a valid XML file"); + SmilReader smilReader = new(); + EpubSmilException outerException = await Assert.ThrowsAsync(() => smilReader.ReadSmilAsync(testZipFile, SMIL_FILE_PATH)); + Assert.NotNull(outerException.InnerException); + Assert.Equal(typeof(XmlException), outerException.InnerException.GetType()); + } + + [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException with an inner XmlException if the SMIL file is not a valid XML file and IgnoreSmilFileIsNotValidXmlError = true")] + public async Task ReadEpub3NavDocumentAsyncWithInvalidXhtmlFileAndIgnoreSmilFileIsNotValidXmlErrorTest() + { + EpubReaderOptions epubReaderOptions = new() + { + SmilReaderOptions = new() + { + IgnoreSmilFileIsNotValidXmlError = true + } + }; + await TestSuccessfulReadOperation("not a valid XML file", null, epubReaderOptions); + } + + [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the SMIL file has no 'smil' XML element and no SmilReaderOptions are provided")] + public async Task ReadSmilAsyncWithoutSmilElementAndDefaultOptionsTest() { await TestFailingReadOperation(SMIL_FILE_WITHOUT_SMIL_ELEMENT); } - [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the SMIL version in the file is not 3.0")] - public async Task ReadSmilAsyncWithWrongSmilVersionTest() + [Fact(DisplayName = "ReadSmilAsync should return null if the SMIL file has no 'smil' XML element and IgnoreMissingSmilElementError = true")] + public async Task ReadSmilAsyncWithoutSmilElementAndIgnoreMissingSmilElementErrorTest() + { + EpubReaderOptions epubReaderOptions = new() + { + SmilReaderOptions = new() + { + IgnoreMissingSmilElementError = true + } + }; + await TestSuccessfulReadOperation(SMIL_FILE_WITHOUT_SMIL_ELEMENT, null, epubReaderOptions); + } + + [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the SMIL version is missing in the file and no SmilReaderOptions are provided")] + public async Task ReadSmilAsyncWithoutSmilVersionAndDefaultOptionsTest() + { + await TestFailingReadOperation(SMIL_FILE_WITHOUT_SMIL_VERSION); + } + + [Fact(DisplayName = "ReadSmilAsync should succeed if the SMIL version is missing in the file and IgnoreMissingSmilVersionError = true")] + public async Task ReadSmilAsyncWithoutSmilVersionAndIgnoreMissingSmilVersionErrorTest() + { + EpubReaderOptions epubReaderOptions = new() + { + SmilReaderOptions = new() + { + IgnoreMissingSmilVersionError = true + } + }; + await TestSuccessfulReadOperation(SMIL_FILE_WITHOUT_SMIL_VERSION, MinimalSmil, epubReaderOptions); + } + + [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the SMIL version in the file is not 3.0 and no SmilReaderOptions are provided")] + public async Task ReadSmilAsyncWithWrongSmilVersionAndDefaultOptionsTest() { await TestFailingReadOperation(SMIL_FILE_WITH_WRONG_SMIL_VERSION); } - [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the 'smil' XML element has no 'body' element")] - public async Task ReadSmilAsyncWithoutBodyElementTest() + [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the SMIL version in the file is not 3.0 and IgnoreUnsupportedSmilVersionError = true")] + public async Task ReadSmilAsyncWithWrongSmilVersionAndIgnoreUnsupportedSmilVersionErrorTest() + { + EpubReaderOptions epubReaderOptions = new() + { + SmilReaderOptions = new() + { + IgnoreUnsupportedSmilVersionError = true + } + }; + await TestSuccessfulReadOperation(SMIL_FILE_WITH_WRONG_SMIL_VERSION, MinimalSmil, epubReaderOptions); + } + + [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the 'smil' XML element has no 'body' element and no SmilReaderOptions are provided")] + public async Task ReadSmilAsyncWithoutBodyElementAndDefaultOptionsTest() { await TestFailingReadOperation(SMIL_FILE_WITHOUT_BODY_ELEMENT); } + [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the 'smil' XML element has no 'body' element and IgnoreMissingBodyElementError = true")] + public async Task ReadSmilAsyncWithoutBodyElementAndIgnoreMissingBodyElementErrorTest() + { + EpubReaderOptions epubReaderOptions = new() + { + SmilReaderOptions = new() + { + IgnoreMissingBodyElementError = true + } + }; + await TestSuccessfulReadOperation(SMIL_FILE_WITHOUT_BODY_ELEMENT, null, epubReaderOptions); + } + [Fact(DisplayName = "Non-metadata XML elements in the 'head' element should be ignored")] - public async Task ReadSmilAsyncWithNonMetadataElementsInHeadTest() + public async Task ReadSmilAsyncWithNonMetadataElementsInHeadAndDefaultOptionsTest() { await TestSuccessfulReadOperation(MINIMAL_SMIL_FILE_WITH_NON_METADATA_ELEMENT_IN_HEAD, MinimalSmilWithEmptyHead); } - [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the 'body' XML element has neither 'seq' nor 'par' elements")] - public async Task ReadSmilAsyncWithoutSeqAndParElementsInBodyTest() + [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the 'body' XML element has neither 'seq' nor 'par' elements and no SmilReaderOptions are provided")] + public async Task ReadSmilAsyncWithoutSeqAndParElementsInBodyAndDefaultOptionsTest() { await TestFailingReadOperation(SMIL_FILE_WITHOUT_SEQ_AND_PAR_ELEMENTS_IN_BODY); } - [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the 'seq' XML element has neither 'seq' nor 'par' elements")] - public async Task ReadSmilAsyncWithoutSeqAndParElementsInSeqTest() + [Fact(DisplayName = "ReadSmilAsync should succeed if the 'body' XML element has neither 'seq' nor 'par' elements and IgnoreBodyMissingSeqOrParElementsError = true")] + public async Task ReadSmilAsyncWithoutSeqAndParElementsInBodyAndIgnoreBodyMissingSeqOrParElementsErrorTest() + { + EpubReaderOptions epubReaderOptions = new() + { + SmilReaderOptions = new() + { + IgnoreBodyMissingSeqOrParElementsError = true + } + }; + await TestSuccessfulReadOperation(SMIL_FILE_WITHOUT_SEQ_AND_PAR_ELEMENTS_IN_BODY, MinimalSmilWithEmptyBody, epubReaderOptions); + } + + [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the 'seq' XML element has neither 'seq' nor 'par' elements and no SmilReaderOptions are provided")] + public async Task ReadSmilAsyncWithoutSeqAndParElementsInSeqAndDefaultOptionsTest() { await TestFailingReadOperation(SMIL_FILE_WITHOUT_SEQ_AND_PAR_ELEMENTS_IN_SEQ); } - [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the 'par' XML element has no 'text' element")] - public async Task ReadSmilAsyncWithoutTextElementInParTest() + [Fact(DisplayName = "ReadSmilAsync should succeed if the 'seq' XML element has neither 'seq' nor 'par' elements and IgnoreSeqMissingSeqOrParElementsError = true")] + public async Task ReadSmilAsyncWithoutSeqAndParElementsInSeqAndIgnoreSeqMissingSeqOrParElementsErrorTest() + { + EpubReaderOptions epubReaderOptions = new() + { + SmilReaderOptions = new() + { + IgnoreSeqMissingSeqOrParElementsError = true + } + }; + await TestSuccessfulReadOperation(SMIL_FILE_WITHOUT_SEQ_AND_PAR_ELEMENTS_IN_SEQ, MinimalSmilWithEmptySeq, epubReaderOptions); + } + + [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the 'par' XML element has no 'text' element and no SmilReaderOptions are provided")] + public async Task ReadSmilAsyncWithoutTextElementInParAndDefaultOptionsTest() { await TestFailingReadOperation(SMIL_FILE_WITHOUT_TEXT_ELEMENT_IN_PAR); } - [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the 'text' XML element has no 'src' attribute")] - public async Task ReadSmilAsyncWithoutTextSrcAttributeTest() + [Fact(DisplayName = "ReadSmilAsync should skip 'par' XML elements without 'text' elements when SkipParsWithoutTextElements = true")] + public async Task ReadSmilAsyncWithoutTextElementInParAndSkipParsWithoutTextElementsTest() + { + EpubReaderOptions epubReaderOptions = new() + { + SmilReaderOptions = new() + { + SkipParsWithoutTextElements = true, + IgnoreBodyMissingSeqOrParElementsError = true + } + }; + await TestSuccessfulReadOperation(SMIL_FILE_WITHOUT_TEXT_ELEMENT_IN_PAR, MinimalSmilWithEmptyBody, epubReaderOptions); + } + + [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the 'text' XML element has no 'src' attribute and no SmilReaderOptions are provided")] + public async Task ReadSmilAsyncWithoutTextSrcAttributeAndDefaultOptionsTest() { await TestFailingReadOperation(SMIL_FILE_WITHOUT_TEXT_SRC_ATTRIBUTE); } - [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the 'audio' XML element has no 'src' attribute")] - public async Task ReadSmilAsyncWithoutAudioSrcAttributeTest() + [Fact(DisplayName = "ReadSmilAsync should 'text' XML elements without 'src' attributes when SkipTextsWithoutSrcAttributes = true")] + public async Task ReadSmilAsyncWithoutTextSrcAttributeAndSkipTextsWithoutSrcAttributesTest() + { + EpubReaderOptions epubReaderOptions = new() + { + SmilReaderOptions = new() + { + SkipTextsWithoutSrcAttributes = true, + SkipParsWithoutTextElements = true, + IgnoreBodyMissingSeqOrParElementsError = true + } + }; + await TestSuccessfulReadOperation(SMIL_FILE_WITHOUT_TEXT_SRC_ATTRIBUTE, MinimalSmilWithEmptyBody, epubReaderOptions); + } + + [Fact(DisplayName = "ReadSmilAsync should throw EpubSmilException if the 'audio' XML element has no 'src' attribute and no SmilReaderOptions are provided")] + public async Task ReadSmilAsyncWithoutAudioSrcAttributeAndDefaultOptionsTest() { await TestFailingReadOperation(SMIL_FILE_WITHOUT_AUDIO_SRC_ATTRIBUTE); } - private static async Task TestSuccessfulReadOperation(string smilFileContent, Smil expectedSmil) + [Fact(DisplayName = "ReadSmilAsync should skip 'audio' XML elements without 'src' attributes when SkipAudiosWithoutSrcAttributes = true")] + public async Task ReadSmilAsyncWithoutAudioSrcAttributeAndSkipAudiosWithoutSrcAttributesTest() + { + EpubReaderOptions epubReaderOptions = new() + { + SmilReaderOptions = new() + { + SkipAudiosWithoutSrcAttributes = true + } + }; + await TestSuccessfulReadOperation(SMIL_FILE_WITHOUT_AUDIO_SRC_ATTRIBUTE, MinimalSmilWithoutAudio, epubReaderOptions); + } + + private static async Task TestSuccessfulReadOperation(string smilFileContent, Smil? expectedSmil, EpubReaderOptions? epubReaderOptions = null) { TestZipFile testZipFile = CreateTestZipFileWithSmilFile(smilFileContent); - SmilReader smilReader = new(); - Smil actualSmil = await smilReader.ReadSmilAsync(testZipFile, SMIL_FILE_PATH); + SmilReader smilReader = new(epubReaderOptions ?? new EpubReaderOptions()); + Smil? actualSmil = await smilReader.ReadSmilAsync(testZipFile, SMIL_FILE_PATH); SmilComparers.CompareSmils(expectedSmil, actualSmil); } diff --git a/Source/VersOne.Epub.Test/Unit/Readers/SpineReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/SpineReaderTests.cs index 862e3d6..6bad7d0 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/SpineReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/SpineReaderTests.cs @@ -7,22 +7,10 @@ namespace VersOne.Epub.Test.Unit.Readers { public class SpineReaderTests { - [Fact(DisplayName = "Getting reading order for a minimal EPUB spine should succeed")] - public void GetReadingOrderForMinimalSpineTest() - { - EpubSchema epubSchema = CreateEpubSchema(); - EpubContentRef epubContentRef = new(); - List expectedReadingOrder = []; - List actualReadingOrder = SpineReader.GetReadingOrder(epubSchema, epubContentRef, new SpineReaderOptions()); - Assert.Equal(expectedReadingOrder, actualReadingOrder); - } - - [Fact(DisplayName = "Getting reading order for a typical EPUB spine should succeed")] - public void GetReadingOrderForTypicalSpineTest() - { - EpubSchema epubSchema = CreateEpubSchema + private static EpubSchema MinimalEpubSchema => + CreateEpubSchema ( - manifest: new EpubManifest + manifest: new ( items: [ @@ -31,56 +19,25 @@ public void GetReadingOrderForTypicalSpineTest() id: "item-1", href: "chapter1.html", mediaType: "application/xhtml+xml" - ), - new - ( - id: "item-2", - href: "chapter2.html", - mediaType: "application/xhtml+xml" ) ] ), - spine: new EpubSpine + spine: new ( items: [ new ( idRef: "item-1" - ), - new - ( - idRef: "item-2" ) ] ) ); - EpubLocalTextContentFileRef expectedHtmlFileRef1 = CreateTestHtmlFileRef("chapter1.html"); - EpubLocalTextContentFileRef expectedHtmlFileRef2 = CreateTestHtmlFileRef("chapter2.html"); - List expectedHtmlLocal = - [ - expectedHtmlFileRef1, - expectedHtmlFileRef2 - ]; - EpubContentRef epubContentRef = new - ( - html: new EpubContentCollectionRef(expectedHtmlLocal.AsReadOnly()) - ); - List expectedReadingOrder = - [ - expectedHtmlFileRef1, - expectedHtmlFileRef2 - ]; - List actualReadingOrder = SpineReader.GetReadingOrder(epubSchema, epubContentRef, new SpineReaderOptions()); - Assert.Equal(expectedReadingOrder, actualReadingOrder); - } - [Fact(DisplayName = "GetReadingOrder should throw EpubPackageException if there is no manifest item with ID matching to the ID ref of a spine item and SpineReaderOptions.IgnoreMissingManifestItems is false")] - public void GetReadingOrderWithMissingManifestItemWithoutIgnoringErrorsTest() - { - EpubSchema epubSchema = CreateEpubSchema + private static EpubSchema EpubSchemaWithMissingManifestItem => + CreateEpubSchema ( - manifest: new EpubManifest + manifest: new ( items: [ @@ -92,7 +49,7 @@ public void GetReadingOrderWithMissingManifestItemWithoutIgnoringErrorsTest() ) ] ), - spine: new EpubSpine + spine: new ( items: [ @@ -103,28 +60,23 @@ public void GetReadingOrderWithMissingManifestItemWithoutIgnoringErrorsTest() ] ) ); - EpubContentRef epubContentRef = new(); - Assert.Throws(() => SpineReader.GetReadingOrder(epubSchema, epubContentRef, new SpineReaderOptions())); - } - [Fact(DisplayName = "GetReadingOrder should skip non-existent manifest items if SpineReaderOptions.IgnoreMissingManifestItems is true")] - public void GetReadingOrderWithMissingManifestItemWithIgnoringErrorsTest() - { - EpubSchema epubSchema = CreateEpubSchema + private static EpubSchema EpubSchemaWithRemoteFile => + CreateEpubSchema ( - manifest: new EpubManifest + manifest: new ( items: [ new ( - id: "item-2", - href: "chapter2.html", + id: "item-1", + href: "https://example.com/books/123/chapter1.html", mediaType: "application/xhtml+xml" ) ] ), - spine: new EpubSpine + spine: new ( items: [ @@ -135,18 +87,19 @@ public void GetReadingOrderWithMissingManifestItemWithIgnoringErrorsTest() ] ) ); + + [Fact(DisplayName = "Getting reading order for a minimal EPUB spine should succeed")] + public void GetReadingOrderForMinimalSpineTest() + { + EpubSchema epubSchema = CreateEpubSchema(); EpubContentRef epubContentRef = new(); - SpineReaderOptions spineReaderOptions = new() - { - IgnoreMissingManifestItems = true - }; List expectedReadingOrder = []; - List actualReadingOrder = SpineReader.GetReadingOrder(epubSchema, epubContentRef, spineReaderOptions); + List actualReadingOrder = SpineReader.GetReadingOrder(epubSchema, epubContentRef, new SpineReaderOptions()); Assert.Equal(expectedReadingOrder, actualReadingOrder); } - [Fact(DisplayName = "GetReadingOrder should throw EpubPackageException if there is no HTML content file referenced by a manifest item")] - public void GetReadingOrderWithMissingHtmlContentFileTest() + [Fact(DisplayName = "Getting reading order for a typical EPUB spine should succeed")] + public void GetReadingOrderForTypicalSpineTest() { EpubSchema epubSchema = CreateEpubSchema ( @@ -159,73 +112,116 @@ public void GetReadingOrderWithMissingHtmlContentFileTest() id: "item-1", href: "chapter1.html", mediaType: "application/xhtml+xml" - ) - ] - ), - spine: new - ( - items: - [ - new - ( - idRef: "item-1" - ) - ] - ) - ); - EpubContentRef epubContentRef = new(); - Assert.Throws(() => SpineReader.GetReadingOrder(epubSchema, epubContentRef, new SpineReaderOptions())); - } - - [Fact(DisplayName = "GetReadingOrder should throw EpubPackageException if the HTML content file referenced by a spine item is a remote resource")] - public void GetReadingOrderWithRemoteHtmlContentFileTest() - { - string remoteFileHref = "https://example.com/books/123/chapter1.html"; - EpubSchema epubSchema = CreateEpubSchema - ( - manifest: new - ( - items: - [ + ), new ( - id: "item-1", - href: remoteFileHref, + id: "item-2", + href: "chapter2.html", mediaType: "application/xhtml+xml" ) ] ), - spine: new + spine: new EpubSpine ( items: [ new ( idRef: "item-1" + ), + new + ( + idRef: "item-2" ) ] ) ); - List htmlRemote = + EpubLocalTextContentFileRef expectedHtmlFileRef1 = CreateTestHtmlFileRef("chapter1.html"); + EpubLocalTextContentFileRef expectedHtmlFileRef2 = CreateTestHtmlFileRef("chapter2.html"); + List expectedHtmlLocal = [ - new - ( - metadata: new - ( - key: remoteFileHref, - contentType: EpubContentType.XHTML_1_1, - contentMimeType: "application/xhtml+xml" - ), - epubContentLoader: new TestEpubContentLoader() - ) + expectedHtmlFileRef1, + expectedHtmlFileRef2 ]; EpubContentRef epubContentRef = new ( - html: new EpubContentCollectionRef(null, htmlRemote.AsReadOnly()) + html: new EpubContentCollectionRef(expectedHtmlLocal.AsReadOnly()) ); + List expectedReadingOrder = + [ + expectedHtmlFileRef1, + expectedHtmlFileRef2 + ]; + List actualReadingOrder = SpineReader.GetReadingOrder(epubSchema, epubContentRef, new SpineReaderOptions()); + Assert.Equal(expectedReadingOrder, actualReadingOrder); + } + + [Fact(DisplayName = "GetReadingOrder should throw EpubPackageException if there is no manifest item with ID matching to the ID ref of a spine item and no SpineReaderOptions are provided")] + public void GetReadingOrderWithMissingManifestItemAndDefaultOptionsTest() + { + EpubSchema epubSchema = EpubSchemaWithMissingManifestItem; + EpubContentRef epubContentRef = new(); Assert.Throws(() => SpineReader.GetReadingOrder(epubSchema, epubContentRef, new SpineReaderOptions())); } + [Fact(DisplayName = "GetReadingOrder should skip non-existent manifest items when IgnoreMissingManifestItems = true")] + public void GetReadingOrderWithMissingManifestItemAndIgnoreMissingManifestItemsTest() + { + EpubSchema epubSchema = EpubSchemaWithMissingManifestItem; + EpubContentRef epubContentRef = new(); + SpineReaderOptions spineReaderOptions = new() + { + IgnoreMissingManifestItems = true + }; + List expectedReadingOrder = []; + List actualReadingOrder = SpineReader.GetReadingOrder(epubSchema, epubContentRef, spineReaderOptions); + Assert.Equal(expectedReadingOrder, actualReadingOrder); + } + + [Fact(DisplayName = "GetReadingOrder should throw EpubPackageException if the HTML content file referenced by a spine item is a remote resource and no SpineReaderOptions are provided")] + public void GetReadingOrderWithRemoteHtmlContentFileAndDefaultOptionsTest() + { + EpubSchema epubSchema = EpubSchemaWithRemoteFile; + EpubContentRef epubContentRef = CreateEpubContentRefForTestRemoteFile(epubSchema.Package.Manifest.Items[0].Href); + Assert.Throws(() => SpineReader.GetReadingOrder(epubSchema, epubContentRef, new SpineReaderOptions())); + } + + [Fact(DisplayName = "GetReadingOrder should skip spine items referencing remote HTML content files when SkipSpineItemsReferencingRemoteContent = true")] + public void GetReadingOrderWithRemoteHtmlContentFileAndSkipSpineItemsReferencingRemoteContentTest() + { + EpubSchema epubSchema = EpubSchemaWithRemoteFile; + EpubContentRef epubContentRef = CreateEpubContentRefForTestRemoteFile(epubSchema.Package.Manifest.Items[0].Href); + SpineReaderOptions spineReaderOptions = new() + { + SkipSpineItemsReferencingRemoteContent = true + }; + List expectedReadingOrder = []; + List actualReadingOrder = SpineReader.GetReadingOrder(epubSchema, epubContentRef, spineReaderOptions); + Assert.Equal(expectedReadingOrder, actualReadingOrder); + } + + [Fact(DisplayName = "GetReadingOrder should throw EpubPackageException if there is no HTML content file referenced by a manifest item and no SpineReaderOptions are provided")] + public void GetReadingOrderWithMissingHtmlContentFileAndDefaultOptionsTest() + { + EpubSchema epubSchema = MinimalEpubSchema; + EpubContentRef epubContentRef = new(); + Assert.Throws(() => SpineReader.GetReadingOrder(epubSchema, epubContentRef, new SpineReaderOptions())); + } + + [Fact(DisplayName = "GetReadingOrder should skip spine items referencing missing HTML content files when IgnoreMissingContentFiles = true")] + public void GetReadingOrderWithMissingHtmlContentFileAndIgnoreMissingContentFilesTest() + { + EpubSchema epubSchema = MinimalEpubSchema; + EpubContentRef epubContentRef = new(); + SpineReaderOptions spineReaderOptions = new() + { + IgnoreMissingContentFiles = true + }; + List expectedReadingOrder = []; + List actualReadingOrder = SpineReader.GetReadingOrder(epubSchema, epubContentRef, spineReaderOptions); + Assert.Equal(expectedReadingOrder, actualReadingOrder); + } + private static EpubSchema CreateEpubSchema(EpubManifest? manifest = null, EpubSpine? spine = null) { return new @@ -234,9 +230,9 @@ private static EpubSchema CreateEpubSchema(EpubManifest? manifest = null, EpubSp ( uniqueIdentifier: null, epubVersion: EpubVersion.EPUB_3, - metadata: new EpubMetadata(), - manifest: manifest ?? new EpubManifest(), - spine: spine ?? new EpubSpine(), + metadata: new(), + manifest: manifest ?? new(), + spine: spine ?? new(), guide: null ), epub2Ncx: null, @@ -250,5 +246,27 @@ private static EpubLocalTextContentFileRef CreateTestHtmlFileRef(string fileName { return new(new EpubContentFileRefMetadata(fileName, EpubContentType.XHTML_1_1, "application/xhtml+xml"), fileName, new TestEpubContentLoader()); } + + private static EpubContentRef CreateEpubContentRefForTestRemoteFile(string fileUrl) + { + List htmlRemote = + [ + new + ( + metadata: new + ( + key: fileUrl, + contentType: EpubContentType.XHTML_1_1, + contentMimeType: "application/xhtml+xml" + ), + epubContentLoader: new TestEpubContentLoader() + ) + ]; + EpubContentRef epubContentRef = new + ( + html: new EpubContentCollectionRef(null, htmlRemote.AsReadOnly()) + ); + return epubContentRef; + } } } diff --git a/Source/VersOne.Epub.Test/Unit/Utils/VersionUtilsTests.cs b/Source/VersOne.Epub.Test/Unit/Schema/Opf/Package/EpubVersionTests.cs similarity index 66% rename from Source/VersOne.Epub.Test/Unit/Utils/VersionUtilsTests.cs rename to Source/VersOne.Epub.Test/Unit/Schema/Opf/Package/EpubVersionTests.cs index 0badd15..5cafcc5 100644 --- a/Source/VersOne.Epub.Test/Unit/Utils/VersionUtilsTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Schema/Opf/Package/EpubVersionTests.cs @@ -1,19 +1,18 @@ -using VersOne.Epub.Internal; -using VersOne.Epub.Schema; - -namespace VersOne.Epub.Test.Unit.Utils -{ - public class VersionUtilsTests - { - [Theory(DisplayName = "Converting EpubVersion enumeration to string should succeed")] - [InlineData(EpubVersion.EPUB_2, "2")] - [InlineData(EpubVersion.EPUB_3, "3")] - [InlineData(EpubVersion.EPUB_3_1, "3.1")] - [InlineData((EpubVersion)0, "0")] - public void GetVersionStringTest(EpubVersion epubVersion, string expectedVersionString) - { - string actualVersionString = VersionUtils.GetVersionString(epubVersion); - Assert.Equal(expectedVersionString, actualVersionString); - } - } -} +using VersOne.Epub.Schema; + +namespace VersOne.Epub.Test.Unit.Schema.Opf.Package +{ + public class EpubVersionTests + { + [Theory(DisplayName = "Converting EpubVersion enumeration to string should succeed")] + [InlineData(EpubVersion.EPUB_2, "2")] + [InlineData(EpubVersion.EPUB_3, "3")] + [InlineData(EpubVersion.EPUB_3_1, "3.1")] + [InlineData((EpubVersion)0, "0")] + public void GetVersionStringTest(EpubVersion epubVersion, string expectedVersionString) + { + string actualVersionString = epubVersion.GetVersionString(); + Assert.Equal(expectedVersionString, actualVersionString); + } + } +} diff --git a/Source/VersOne.Epub.Test/Unit/TestData/TestEpubFiles.cs b/Source/VersOne.Epub.Test/Unit/TestData/TestEpubFiles.cs index 882d931..33ebaf4 100644 --- a/Source/VersOne.Epub.Test/Unit/TestData/TestEpubFiles.cs +++ b/Source/VersOne.Epub.Test/Unit/TestData/TestEpubFiles.cs @@ -134,33 +134,33 @@ internal static class TestEpubFiles public const string STYLES2_FILE_CONTENT = ".text{color:#020202}"; - public static readonly byte[] IMAGE1_FILE_CONTENT = new byte[] { 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x01 }; + public static readonly byte[] IMAGE1_FILE_CONTENT = [0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x01]; - public static readonly byte[] IMAGE2_FILE_CONTENT = new byte[] { 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x02 }; + public static readonly byte[] IMAGE2_FILE_CONTENT = [0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x02]; - public static readonly byte[] COVER_FILE_CONTENT = new byte[] { 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0xff }; + public static readonly byte[] COVER_FILE_CONTENT = [0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0xff]; - public static readonly byte[] FONT1_FILE_CONTENT = new byte[] { 0x00, 0x01, 0x00, 0x01 }; + public static readonly byte[] FONT1_FILE_CONTENT = [0x00, 0x01, 0x00, 0x01]; - public static readonly byte[] FONT2_FILE_CONTENT = new byte[] { 0x00, 0x01, 0x00, 0x02 }; + public static readonly byte[] FONT2_FILE_CONTENT = [0x00, 0x01, 0x00, 0x02]; - public static readonly byte[] AUDIO_FILE_CONTENT = new byte[] { 0x49, 0x44, 0x33, 0x03 }; + public static readonly byte[] AUDIO_FILE_CONTENT = [0x49, 0x44, 0x33, 0x03]; - public static readonly byte[] VIDEO_FILE_CONTENT = new byte[] { 0x00, 0x00, 0x00, 0x20 }; + public static readonly byte[] VIDEO_FILE_CONTENT = [0x00, 0x00, 0x00, 0x20]; public const string REMOTE_HTML_FILE_CONTENT = "Remote HTML file

Remote HTML file content

"; public const string REMOTE_CSS_FILE_CONTENT = ".remote-text{color:#030303}"; - public static readonly byte[] REMOTE_IMAGE_FILE_CONTENT = new byte[] { 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x03 }; + public static readonly byte[] REMOTE_IMAGE_FILE_CONTENT = [0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x03]; - public static readonly byte[] REMOTE_FONT_FILE_CONTENT = new byte[] { 0x00, 0x01, 0x00, 0x03 }; + public static readonly byte[] REMOTE_FONT_FILE_CONTENT = [0x00, 0x01, 0x00, 0x03]; public const string REMOTE_XML_FILE_CONTENT = "Remote XML file"; - public static readonly byte[] REMOTE_AUDIO_FILE_CONTENT = new byte[] { 0x49, 0x44, 0x33, 0x04 }; + public static readonly byte[] REMOTE_AUDIO_FILE_CONTENT = [0x49, 0x44, 0x33, 0x04]; - public static readonly byte[] REMOTE_VIDEO_FILE_CONTENT = new byte[] { 0x00, 0x00, 0x00, 0x21 }; + public static readonly byte[] REMOTE_VIDEO_FILE_CONTENT = [0x00, 0x00, 0x00, 0x21]; public static TestZipFile CreateMinimalTestEpubFile() { @@ -200,5 +200,12 @@ public static TestZipFile CreateFullTestEpubFile() result.AddEntry(NCX_FILE_PATH, NCX_FILE_CONTENT); return result; } + + public static TestZipFile CreateTestEpubFileWithoutPackage() + { + TestZipFile result = new(); + result.AddEntry(CONTAINER_FILE_PATH, CONTAINER_FILE_CONTENT); + return result; + } } } diff --git a/Source/VersOne.Epub.Test/Unit/Utils/TaskExtensionMethodsTests.cs b/Source/VersOne.Epub.Test/Unit/Utils/TaskExtensionMethodsTests.cs new file mode 100644 index 0000000..0e1d8d3 --- /dev/null +++ b/Source/VersOne.Epub.Test/Unit/Utils/TaskExtensionMethodsTests.cs @@ -0,0 +1,28 @@ +using VersOne.Epub.Utils; + +namespace VersOne.Epub.Test.Unit.Utils +{ + public class TaskExtensionMethodsTests + { + [Fact(DisplayName = "ExecuteAndUnwrapAggregateException should return the task result if it doesn't throw any exceptions")] + public void ExecuteAndUnwrapAggregateExceptionWithNoExceptionTest() + { + Task task = Task.Run(() => 1); + Assert.Equal(1, task.ExecuteAndUnwrapAggregateException()); + } + + [Fact(DisplayName = "ExecuteAndUnwrapAggregateException should rethrow the exception thrown by the task")] + public void ExecuteAndUnwrapAggregateExceptionWithInnerExceptionTest() + { + static async Task TestAsyncFunction(bool throwException) + { + if (throwException) + { + throw new InvalidOperationException(); + } + return 1; + } + Assert.Throws(() => TestAsyncFunction(throwException: true).ExecuteAndUnwrapAggregateException()); + } + } +} diff --git a/Source/VersOne.Epub/Content/Collections/EpubContentCollection.cs b/Source/VersOne.Epub/Content/Collections/EpubContentCollection.cs index 6e6a120..39b1291 100644 --- a/Source/VersOne.Epub/Content/Collections/EpubContentCollection.cs +++ b/Source/VersOne.Epub/Content/Collections/EpubContentCollection.cs @@ -115,7 +115,7 @@ public TLocalContentFile GetLocalFileByKey(string key) /// true if the local content file with the specified value exists in this container; otherwise, false. /// /// is null. - public bool TryGetLocalFileByKey(string key, out TLocalContentFile localContentFile) + public bool TryGetLocalFileByKey(string key, out TLocalContentFile? localContentFile) { if (key == null) { @@ -177,7 +177,7 @@ public TLocalContentFile GetLocalFileByFilePath(string filePath) /// true if the local content file with the specified value exists in this container; otherwise, false. /// /// is null. - public bool TryGetLocalFileByFilePath(string filePath, out TLocalContentFile localContentFile) + public bool TryGetLocalFileByFilePath(string filePath, out TLocalContentFile? localContentFile) { if (filePath == null) { @@ -239,7 +239,7 @@ public TRemoteContentFile GetRemoteFileByUrl(string url) /// true if the remote content file with the specified value exists in this container; otherwise, false. /// /// is null. - public bool TryGetRemoteFileByUrl(string url, out TRemoteContentFile remoteContentFile) + public bool TryGetRemoteFileByUrl(string url, out TRemoteContentFile? remoteContentFile) { if (url == null) { diff --git a/Source/VersOne.Epub/Content/Collections/EpubContentCollectionRef.cs b/Source/VersOne.Epub/Content/Collections/EpubContentCollectionRef.cs index 740e941..cdb4ef6 100644 --- a/Source/VersOne.Epub/Content/Collections/EpubContentCollectionRef.cs +++ b/Source/VersOne.Epub/Content/Collections/EpubContentCollectionRef.cs @@ -115,7 +115,7 @@ public TLocalContentFileRef GetLocalFileRefByKey(string key) /// true if the local content file reference with the specified value exists in this container; otherwise, false. /// /// is null. - public bool TryGetLocalFileRefByKey(string key, out TLocalContentFileRef localContentFileRef) + public bool TryGetLocalFileRefByKey(string key, out TLocalContentFileRef? localContentFileRef) { if (key == null) { @@ -177,7 +177,7 @@ public TLocalContentFileRef GetLocalFileRefByFilePath(string filePath) /// true if the local content file reference with the specified value exists in this container; otherwise, false. /// /// is null. - public bool TryGetLocalFileRefByFilePath(string filePath, out TLocalContentFileRef localContentFileRef) + public bool TryGetLocalFileRefByFilePath(string filePath, out TLocalContentFileRef? localContentFileRef) { if (filePath == null) { @@ -239,7 +239,7 @@ public TRemoteContentFileRef GetRemoteFileRefByUrl(string url) /// true if the remote content file reference with the specified value exists in this container; otherwise, false. /// /// is null. - public bool TryGetRemoteFileRefByUrl(string url, out TRemoteContentFileRef remoteContentFileRef) + public bool TryGetRemoteFileRefByUrl(string url, out TRemoteContentFileRef? remoteContentFileRef) { if (url == null) { diff --git a/Source/VersOne.Epub/Content/Loaders/EpubLocalContentLoader.cs b/Source/VersOne.Epub/Content/Loaders/EpubLocalContentLoader.cs index 4a0e0ab..1d8e742 100644 --- a/Source/VersOne.Epub/Content/Loaders/EpubLocalContentLoader.cs +++ b/Source/VersOne.Epub/Content/Loaders/EpubLocalContentLoader.cs @@ -49,7 +49,7 @@ public override Task GetContentStreamAsync(EpubContentFileRefMetadata co private IZipFileEntry GetContentFileEntry(EpubContentFileRefMetadata contentFileRefMetadata) { - if (replacementContentFileEntries.TryGetValue(contentFileRefMetadata.Key, out ReplacementContentFileEntry existingReplacementContentFileEntry)) + if (replacementContentFileEntries.TryGetValue(contentFileRefMetadata.Key, out ReplacementContentFileEntry? existingReplacementContentFileEntry)) { return existingReplacementContentFileEntry; } diff --git a/Source/VersOne.Epub/Content/Loaders/EpubRemoteContentLoader.cs b/Source/VersOne.Epub/Content/Loaders/EpubRemoteContentLoader.cs index 4620a99..d3f54ee 100644 --- a/Source/VersOne.Epub/Content/Loaders/EpubRemoteContentLoader.cs +++ b/Source/VersOne.Epub/Content/Loaders/EpubRemoteContentLoader.cs @@ -18,7 +18,7 @@ public EpubRemoteContentLoader(IEnvironmentDependencies environmentDependencies, this.contentDownloaderOptions = contentDownloaderOptions ?? new ContentDownloaderOptions(); contentDownloader = this.contentDownloaderOptions.CustomContentDownloader ?? EnvironmentDependencies.ContentDownloader; userAgent = this.contentDownloaderOptions.DownloaderUserAgent ?? - "EpubReader/" + typeof(EpubRemoteContentLoader).GetTypeInfo().Assembly.GetCustomAttribute().InformationalVersion; + "EpubReader/" + typeof(EpubRemoteContentLoader).GetTypeInfo().Assembly.GetCustomAttribute()!.InformationalVersion; } public override async Task LoadContentAsBytesAsync(EpubContentFileRefMetadata contentFileRefMetadata) diff --git a/Source/VersOne.Epub/Entities/EpubBookRef.cs b/Source/VersOne.Epub/Entities/EpubBookRef.cs index 33583cc..eedfe9c 100644 --- a/Source/VersOne.Epub/Entities/EpubBookRef.cs +++ b/Source/VersOne.Epub/Entities/EpubBookRef.cs @@ -163,7 +163,7 @@ public async Task> GetReadingOrderAsync() /// public async Task?> GetNavigationAsync() { - return await Task.Run(() => NavigationReader.GetNavigationItems(Schema, Content)).ConfigureAwait(false); + return await Task.Run(() => NavigationReader.GetNavigationItems(Schema, Content, EpubReaderOptions.NavigationReaderOptions)).ConfigureAwait(false); } /// diff --git a/Source/VersOne.Epub/Environment/Implementation/ZipFile.cs b/Source/VersOne.Epub/Environment/Implementation/ZipFile.cs index 1137977..b548721 100644 --- a/Source/VersOne.Epub/Environment/Implementation/ZipFile.cs +++ b/Source/VersOne.Epub/Environment/Implementation/ZipFile.cs @@ -26,7 +26,7 @@ public ZipFile(ZipArchive zipArchive) { throw new ObjectDisposedException(nameof(ZipFile)); } - ZipArchiveEntry zipArchiveEntry = zipArchive.GetEntry(entryName); + ZipArchiveEntry? zipArchiveEntry = zipArchive.GetEntry(entryName); return zipArchiveEntry != null ? new ZipFileEntry(zipArchiveEntry) : null; } diff --git a/Source/VersOne.Epub/EpubReader.cs b/Source/VersOne.Epub/EpubReader.cs index c0229bf..37bd477 100644 --- a/Source/VersOne.Epub/EpubReader.cs +++ b/Source/VersOne.Epub/EpubReader.cs @@ -8,7 +8,8 @@ namespace VersOne.Epub { /// - /// The main entry point of the EpubReader library. The methods in this class let the consumer to open/read EPUB books from a file or a . + /// The main entry point of the EpubReader library. The methods in this class let the consumer to open/read EPUB books from a file + /// or a . /// public static class EpubReader { @@ -23,12 +24,19 @@ static EpubReader() /// Opens the book synchronously without reading its content. The object returned by this method holds a handle to the EPUB file. /// /// Path to the EPUB file. - /// - /// A preset to configure the behavior of the EPUB reader. - /// Default value is EpubReaderOptionsPreset.RELAXED. - /// /// EPUB book reference. This object holds a handle to the EPUB file. - public static EpubBookRef OpenBook(string filePath, EpubReaderOptionsPreset epubReaderOptionsPreset = EpubReaderOptionsPreset.RELAXED) + public static EpubBookRef OpenBook(string filePath) + { + return OpenBook(filePath, EpubReaderOptionsPreset.STRICT)!; + } + + /// + /// Opens the book synchronously without reading its content. The object returned by this method holds a handle to the EPUB file. + /// + /// Path to the EPUB file. + /// A preset to configure the behavior of the EPUB reader. + /// EPUB book reference. This object holds a handle to the EPUB file. + public static EpubBookRef? OpenBook(string filePath, EpubReaderOptionsPreset epubReaderOptionsPreset) { BookRefReader bookRefReader = new(environmentDependencies, new EpubReaderOptions(epubReaderOptionsPreset)); return bookRefReader.OpenBook(filePath); @@ -40,7 +48,7 @@ public static EpubBookRef OpenBook(string filePath, EpubReaderOptionsPreset epub /// Path to the EPUB file. /// Various options to configure the behavior of the EPUB reader. /// EPUB book reference. This object holds a handle to the EPUB file. - public static EpubBookRef OpenBook(string filePath, EpubReaderOptions? epubReaderOptions) + public static EpubBookRef? OpenBook(string filePath, EpubReaderOptions epubReaderOptions) { BookRefReader bookRefReader = new(environmentDependencies, epubReaderOptions); return bookRefReader.OpenBook(filePath); @@ -50,12 +58,19 @@ public static EpubBookRef OpenBook(string filePath, EpubReaderOptions? epubReade /// Opens the book synchronously without reading its content. The object returned by this method holds a handle to the EPUB file. /// /// Seekable stream containing the EPUB file. - /// - /// A preset to configure the behavior of the EPUB reader. - /// Default value is EpubReaderOptionsPreset.RELAXED. - /// /// EPUB book reference. This object holds a handle to the EPUB file. - public static EpubBookRef OpenBook(Stream stream, EpubReaderOptionsPreset epubReaderOptionsPreset = EpubReaderOptionsPreset.RELAXED) + public static EpubBookRef OpenBook(Stream stream) + { + return OpenBook(stream, EpubReaderOptionsPreset.STRICT)!; + } + + /// + /// Opens the book synchronously without reading its content. The object returned by this method holds a handle to the EPUB file. + /// + /// Seekable stream containing the EPUB file. + /// A preset to configure the behavior of the EPUB reader. + /// EPUB book reference. This object holds a handle to the EPUB file. + public static EpubBookRef? OpenBook(Stream stream, EpubReaderOptionsPreset epubReaderOptionsPreset) { BookRefReader bookRefReader = new(environmentDependencies, new EpubReaderOptions(epubReaderOptionsPreset)); return bookRefReader.OpenBook(stream); @@ -67,7 +82,7 @@ public static EpubBookRef OpenBook(Stream stream, EpubReaderOptionsPreset epubRe /// Seekable stream containing the EPUB file. /// Various options to configure the behavior of the EPUB reader. /// EPUB book reference. This object holds a handle to the EPUB file. - public static EpubBookRef OpenBook(Stream stream, EpubReaderOptions? epubReaderOptions) + public static EpubBookRef? OpenBook(Stream stream, EpubReaderOptions epubReaderOptions) { BookRefReader bookRefReader = new(environmentDependencies, epubReaderOptions); return bookRefReader.OpenBook(stream); @@ -77,12 +92,19 @@ public static EpubBookRef OpenBook(Stream stream, EpubReaderOptions? epubReaderO /// Opens the book asynchronously without reading its content. The object returned by this method holds a handle to the EPUB file. /// /// Path to the EPUB file. - /// - /// A preset to configure the behavior of the EPUB reader. - /// Default value is EpubReaderOptionsPreset.RELAXED. - /// /// EPUB book reference. This object holds a handle to the EPUB file. - public static Task OpenBookAsync(string filePath, EpubReaderOptionsPreset epubReaderOptionsPreset = EpubReaderOptionsPreset.RELAXED) + public static Task OpenBookAsync(string filePath) + { + return OpenBookAsync(filePath, EpubReaderOptionsPreset.STRICT)!; + } + + /// + /// Opens the book asynchronously without reading its content. The object returned by this method holds a handle to the EPUB file. + /// + /// Path to the EPUB file. + /// A preset to configure the behavior of the EPUB reader. + /// EPUB book reference. This object holds a handle to the EPUB file. + public static Task OpenBookAsync(string filePath, EpubReaderOptionsPreset epubReaderOptionsPreset) { BookRefReader bookRefReader = new(environmentDependencies, new EpubReaderOptions(epubReaderOptionsPreset)); return bookRefReader.OpenBookAsync(filePath); @@ -94,7 +116,7 @@ public static Task OpenBookAsync(string filePath, EpubReaderOptions /// Path to the EPUB file. /// Various options to configure the behavior of the EPUB reader. /// EPUB book reference. This object holds a handle to the EPUB file. - public static Task OpenBookAsync(string filePath, EpubReaderOptions? epubReaderOptions) + public static Task OpenBookAsync(string filePath, EpubReaderOptions epubReaderOptions) { BookRefReader bookRefReader = new(environmentDependencies, epubReaderOptions); return bookRefReader.OpenBookAsync(filePath); @@ -104,12 +126,19 @@ public static Task OpenBookAsync(string filePath, EpubReaderOptions /// Opens the book asynchronously without reading its content. The object returned by this method holds a handle to the EPUB file. /// /// Seekable stream containing the EPUB file. - /// - /// A preset to configure the behavior of the EPUB reader. - /// Default value is EpubReaderOptionsPreset.RELAXED. - /// /// EPUB book reference. This object holds a handle to the EPUB file. - public static Task OpenBookAsync(Stream stream, EpubReaderOptionsPreset epubReaderOptionsPreset = EpubReaderOptionsPreset.RELAXED) + public static Task OpenBookAsync(Stream stream) + { + return OpenBookAsync(stream, EpubReaderOptionsPreset.STRICT)!; + } + + /// + /// Opens the book asynchronously without reading its content. The object returned by this method holds a handle to the EPUB file. + /// + /// Seekable stream containing the EPUB file. + /// A preset to configure the behavior of the EPUB reader. + /// EPUB book reference. This object holds a handle to the EPUB file. + public static Task OpenBookAsync(Stream stream, EpubReaderOptionsPreset epubReaderOptionsPreset) { BookRefReader bookRefReader = new(environmentDependencies, new EpubReaderOptions(epubReaderOptionsPreset)); return bookRefReader.OpenBookAsync(stream); @@ -121,115 +150,155 @@ public static Task OpenBookAsync(Stream stream, EpubReaderOptionsPr /// Seekable stream containing the EPUB file. /// Various options to configure the behavior of the EPUB reader. /// EPUB book reference. This object holds a handle to the EPUB file. - public static Task OpenBookAsync(Stream stream, EpubReaderOptions? epubReaderOptions) + public static Task OpenBookAsync(Stream stream, EpubReaderOptions epubReaderOptions) { BookRefReader bookRefReader = new(environmentDependencies, epubReaderOptions); return bookRefReader.OpenBookAsync(stream); } /// - /// Opens the book synchronously and reads all of its content into the memory. The object returned by this method does not retain a handle to the EPUB file. + /// Opens the book synchronously and reads all of its content into the memory. + /// The object returned by this method does not retain a handle to the EPUB file. /// /// Path to the EPUB file. - /// - /// A preset to configure the behavior of the EPUB reader. - /// Default value is EpubReaderOptionsPreset.RELAXED. - /// /// EPUB book with all its content. This object does not retain a handle to the EPUB file. - public static EpubBook ReadBook(string filePath, EpubReaderOptionsPreset epubReaderOptionsPreset = EpubReaderOptionsPreset.RELAXED) + public static EpubBook ReadBook(string filePath) + { + return ReadBook(filePath, EpubReaderOptionsPreset.STRICT)!; + } + + /// + /// Opens the book synchronously and reads all of its content into the memory. + /// The object returned by this method does not retain a handle to the EPUB file. + /// + /// Path to the EPUB file. + /// A preset to configure the behavior of the EPUB reader. + /// EPUB book with all its content. This object does not retain a handle to the EPUB file. + public static EpubBook? ReadBook(string filePath, EpubReaderOptionsPreset epubReaderOptionsPreset) { BookReader bookReader = new(environmentDependencies, new EpubReaderOptions(epubReaderOptionsPreset)); return bookReader.ReadBook(filePath); } /// - /// Opens the book synchronously and reads all of its content into the memory. The object returned by this method does not retain a handle to the EPUB file. + /// Opens the book synchronously and reads all of its content into the memory. + /// The object returned by this method does not retain a handle to the EPUB file. /// /// Path to the EPUB file. /// Various options to configure the behavior of the EPUB reader. /// EPUB book with all its content. This object does not retain a handle to the EPUB file. - public static EpubBook ReadBook(string filePath, EpubReaderOptions? epubReaderOptions) + public static EpubBook? ReadBook(string filePath, EpubReaderOptions epubReaderOptions) { BookReader bookReader = new(environmentDependencies, epubReaderOptions); return bookReader.ReadBook(filePath); } /// - /// Opens the book synchronously and reads all of its content into the memory. The object returned by this method does not retain a handle to the EPUB file. + /// Opens the book synchronously and reads all of its content into the memory. + /// The object returned by this method does not retain a handle to the EPUB file. + /// + /// Seekable stream containing the EPUB file. + /// EPUB book with all its content. This object does not retain a handle to the EPUB file. + public static EpubBook ReadBook(Stream stream) + { + return ReadBook(stream, EpubReaderOptionsPreset.STRICT)!; + } + + /// + /// Opens the book synchronously and reads all of its content into the memory. + /// The object returned by this method does not retain a handle to the EPUB file. /// /// Seekable stream containing the EPUB file. - /// - /// A preset to configure the behavior of the EPUB reader. - /// Default value is EpubReaderOptionsPreset.RELAXED. - /// + /// A preset to configure the behavior of the EPUB reader. /// EPUB book with all its content. This object does not retain a handle to the EPUB file. - public static EpubBook ReadBook(Stream stream, EpubReaderOptionsPreset epubReaderOptionsPreset = EpubReaderOptionsPreset.RELAXED) + public static EpubBook? ReadBook(Stream stream, EpubReaderOptionsPreset epubReaderOptionsPreset) { BookReader bookReader = new(environmentDependencies, new EpubReaderOptions(epubReaderOptionsPreset)); return bookReader.ReadBook(stream); } /// - /// Opens the book synchronously and reads all of its content into the memory. The object returned by this method does not retain a handle to the EPUB file. + /// Opens the book synchronously and reads all of its content into the memory. + /// The object returned by this method does not retain a handle to the EPUB file. /// /// Seekable stream containing the EPUB file. /// Various options to configure the behavior of the EPUB reader. /// EPUB book with all its content. This object does not retain a handle to the EPUB file. - public static EpubBook ReadBook(Stream stream, EpubReaderOptions? epubReaderOptions) + public static EpubBook? ReadBook(Stream stream, EpubReaderOptions epubReaderOptions) { BookReader bookReader = new(environmentDependencies, epubReaderOptions); return bookReader.ReadBook(stream); } /// - /// Opens the book asynchronously and reads all of its content into the memory. The object returned by this method does not retain a handle to the EPUB file. + /// Opens the book asynchronously and reads all of its content into the memory. + /// The object returned by this method does not retain a handle to the EPUB file. + /// + /// Path to the EPUB file. + /// EPUB book with all its content. This object does not retain a handle to the EPUB file. + public static Task ReadBookAsync(string filePath) + { + return ReadBookAsync(filePath, EpubReaderOptionsPreset.STRICT)!; + } + + /// + /// Opens the book asynchronously and reads all of its content into the memory. + /// The object returned by this method does not retain a handle to the EPUB file. /// /// Path to the EPUB file. - /// - /// A preset to configure the behavior of the EPUB reader. - /// Default value is EpubReaderOptionsPreset.RELAXED. - /// + /// A preset to configure the behavior of the EPUB reader. /// EPUB book with all its content. This object does not retain a handle to the EPUB file. - public static Task ReadBookAsync(string filePath, EpubReaderOptionsPreset epubReaderOptionsPreset = EpubReaderOptionsPreset.RELAXED) + public static Task ReadBookAsync(string filePath, EpubReaderOptionsPreset epubReaderOptionsPreset) { BookReader bookReader = new(environmentDependencies, new EpubReaderOptions(epubReaderOptionsPreset)); return bookReader.ReadBookAsync(filePath); } /// - /// Opens the book asynchronously and reads all of its content into the memory. The object returned by this method does not retain a handle to the EPUB file. + /// Opens the book asynchronously and reads all of its content into the memory. + /// The object returned by this method does not retain a handle to the EPUB file. /// /// Path to the EPUB file. /// Various options to configure the behavior of the EPUB reader. /// EPUB book with all its content. This object does not retain a handle to the EPUB file. - public static Task ReadBookAsync(string filePath, EpubReaderOptions? epubReaderOptions) + public static Task ReadBookAsync(string filePath, EpubReaderOptions epubReaderOptions) { BookReader bookReader = new(environmentDependencies, epubReaderOptions); return bookReader.ReadBookAsync(filePath); } /// - /// Opens the book asynchronously and reads all of its content into the memory. The object returned by this method does not retain a handle to the EPUB file. + /// Opens the book asynchronously and reads all of its content into the memory. + /// The object returned by this method does not retain a handle to the EPUB file. + /// + /// Seekable stream containing the EPUB file. + /// EPUB book with all its content. This object does not retain a handle to the EPUB file. + public static Task ReadBookAsync(Stream stream) + { + return ReadBookAsync(stream, EpubReaderOptionsPreset.STRICT)!; + } + + /// + /// Opens the book asynchronously and reads all of its content into the memory. + /// The object returned by this method does not retain a handle to the EPUB file. /// /// Seekable stream containing the EPUB file. - /// - /// A preset to configure the behavior of the EPUB reader. - /// Default value is EpubReaderOptionsPreset.RELAXED. - /// + /// A preset to configure the behavior of the EPUB reader. /// EPUB book with all its content. This object does not retain a handle to the EPUB file. - public static Task ReadBookAsync(Stream stream, EpubReaderOptionsPreset epubReaderOptionsPreset = EpubReaderOptionsPreset.RELAXED) + public static Task ReadBookAsync(Stream stream, EpubReaderOptionsPreset epubReaderOptionsPreset) { BookReader bookReader = new(environmentDependencies, new EpubReaderOptions(epubReaderOptionsPreset)); return bookReader.ReadBookAsync(stream); } /// - /// Opens the book asynchronously and reads all of its content into the memory. The object returned by this method does not retain a handle to the EPUB file. + /// Opens the book asynchronously and reads all of its content into the memory. + /// The object returned by this method does not retain a handle to the EPUB file. /// /// Seekable stream containing the EPUB file. /// Various options to configure the behavior of the EPUB reader. /// EPUB book with all its content. This object does not retain a handle to the EPUB file. - public static Task ReadBookAsync(Stream stream, EpubReaderOptions? epubReaderOptions) + public static Task ReadBookAsync(Stream stream, EpubReaderOptions epubReaderOptions) { BookReader bookReader = new(environmentDependencies, epubReaderOptions); return bookReader.ReadBookAsync(stream); diff --git a/Source/VersOne.Epub/Options/BookCoverReaderOptions.cs b/Source/VersOne.Epub/Options/BookCoverReaderOptions.cs index 7bb33c9..d9d5af7 100644 --- a/Source/VersOne.Epub/Options/BookCoverReaderOptions.cs +++ b/Source/VersOne.Epub/Options/BookCoverReaderOptions.cs @@ -6,7 +6,7 @@ public class BookCoverReaderOptions { /// - /// Initializes a new instance of the class. + /// 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) @@ -21,12 +21,92 @@ public BookCoverReaderOptions(EpubReaderOptionsPreset? preset = null) } /// + /// + /// Gets or sets a value indicating whether EPUB 2 book cover reader should ignore the error when the EPUB 2 cover metadata item + /// contains an empty or a whitespace value. + /// + /// + /// If it's set to false and the EPUB 2 cover metadata node is empty or contains only whitespace characters, then + /// the "Incorrect EPUB metadata: cover item content is missing." exception will be thrown. + /// This exception can be suppressed by setting this property to true, + /// in which case the EPUB 2 book cover reader will ignore the invalid cover metadata item. The cover information may still be read from + /// the EPUB 2 guide, if it's present there. + /// + /// + /// Default value is false. + /// + /// + public bool Epub2MetadataIgnoreMissingContent { get; set; } + + /// + /// /// 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. + /// the "Incorrect EPUB manifest: item with ID = ... referenced in EPUB 2 cover metadata is missing" exception will be thrown. + /// This exception can be suppressed by setting this property to true, + /// in which case the EPUB 2 book cover reader will ignore the invalid manifest item. The cover information may still be read from + /// the EPUB 2 guide, if it's present there. + /// + /// /// Default value is false. + /// /// public bool Epub2MetadataIgnoreMissingManifestItem { get; set; } + + /// + /// + /// Gets or sets a value indicating whether EPUB 2 book cover reader should ignore the error when the image content file referenced by + /// the EPUB 2 cover metadata item is missing. + /// + /// + /// If it's set to false and the content file referenced by the manifest item which in turn is referenced by + /// the EPUB 2 cover metadata item is not present, then the "Incorrect EPUB manifest: item with href = ... is missing." exception will be thrown. + /// This exception can be suppressed by setting this property to true, + /// in which case the EPUB 2 book cover reader will ignore the invalid item. The cover information may still be read from + /// the EPUB 2 guide, if it's present there. + /// + /// + /// Default value is false. + /// + /// + public bool Epub2MetadataIgnoreMissingContentFile { get; set; } + + /// + /// + /// Gets or sets a value indicating whether EPUB 3 book cover reader should ignore the error when the image content file referenced by + /// the EPUB 3 cover manifest item is missing. + /// + /// + /// If it's set to false and the content file referenced by the cover manifest item is not present, + /// then the "Incorrect EPUB manifest: item with href = ... is missing." exception will be thrown. + /// This exception can be suppressed by setting this property to true, + /// in which case the EPUB 3 book cover reader will ignore the invalid item. + /// + /// + /// Default value is false. + /// + /// + public bool Epub3IgnoreMissingContentFile { get; set; } + + /// + /// + /// Gets or sets a value indicating whether EPUB book cover reader should ignore the error when the EPUB cover manifest item + /// is referencing a remote resource. + /// + /// + /// If it's set to false and the content file referenced by the cover manifest item is a remote resource + /// (i.e. the 'href' attribute of the manifest item is a remote URL), + /// then the "Incorrect EPUB manifest: EPUB cover image ... cannot be a remote resource." exception will be thrown. + /// This exception can be suppressed by setting this property to true, + /// in which case the EPUB book cover reader will ignore the invalid item. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreRemoteContentFileError { get; set; } } } diff --git a/Source/VersOne.Epub/Options/ContainerFileReaderOptions.cs b/Source/VersOne.Epub/Options/ContainerFileReaderOptions.cs new file mode 100644 index 0000000..69d2664 --- /dev/null +++ b/Source/VersOne.Epub/Options/ContainerFileReaderOptions.cs @@ -0,0 +1,76 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Xml; + +namespace VersOne.Epub.Options +{ + /// + /// Various options to configure the behavior of the EPUB OCF container file reader. + /// + public class ContainerFileReaderOptions + { + /// + /// Initializes a new instance of the class. + /// + /// + /// An optional preset to initialize the class with a predefined set of options. + /// + [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Temporarily ignore unused 'preset' parameter.")] + public ContainerFileReaderOptions(EpubReaderOptionsPreset? preset = null) + { + } + + /// + /// + /// Gets or sets a value indicating whether the container file reader should ignore the missing EPUB OCF container file error. + /// + /// + /// The EPUB OCF container is the 'META-INF/container.xml' file inside the EPUB archive. + /// If this property is set to false and the container file is not present, + /// then the "EPUB parsing error: "META-INF/container.xml" file not found in the EPUB file." exception will be thrown. + /// This exception can be suppressed by setting this property to true. However, since the OCF container is the main file + /// describing the location of the OPF package file, the class methods will return null in this case. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingContainerFile { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the container reader should ignore the error when the EPUB OCF container file is not a valid XML file. + /// + /// + /// If it's set to false and an XML parsing error has occurred while trying to open the OCF container file, + /// then the "EPUB parsing error: EPUB OCF container file is not a valid XML file." exception will be thrown + /// with the original available through the property. + /// This exception can be suppressed by setting this property to true. However, since the OCF container is the main file + /// describing the location of the OPF package file, the class methods will return null in this case. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreContainerFileIsNotValidXmlError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the container reader should ignore the error when the OPF package file path is missing + /// in the OCF container document. + /// + /// + /// The path to the OPF package file is determined by the 'full-path' attribute of the + /// 'urn:oasis:names:tc:opendocument:xmlns:container:container/rootfiles/rootfile' XML element. + /// If it's set to false and the OPF package file is not found at the specified attribute, + /// then the "EPUB parsing error: OPF package file path not found in the EPUB container." exception will be thrown. + /// This exception can be suppressed by setting this property to true. However, since the OPF package is the main file + /// describing the EPUB book, the class methods will return null in this case. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingPackageFilePathError { get; set; } + } +} diff --git a/Source/VersOne.Epub/Options/ContentDownloaderOptions.cs b/Source/VersOne.Epub/Options/ContentDownloaderOptions.cs index 3955cc0..d7fc85c 100644 --- a/Source/VersOne.Epub/Options/ContentDownloaderOptions.cs +++ b/Source/VersOne.Epub/Options/ContentDownloaderOptions.cs @@ -21,24 +21,45 @@ public ContentDownloaderOptions(EpubReaderOptionsPreset? preset = null) } /// + /// /// Gets or sets a value indicating whether the content downloader should download remote resources specified in the EPUB manifest. - /// If it's set to false, then and will always be null - /// and will throw a "Downloading remote content is prohibited by the ContentDownloaderOptions.DownloadContent option" exception. + /// + /// + /// If it's set to false, then and + /// will always be null and will throw + /// a "Downloading remote content is prohibited by the ContentDownloaderOptions.DownloadContent option" exception. + /// + /// /// Default value is false. + /// /// public bool DownloadContent { get; set; } /// - /// Gets or sets the user agent presented by the content downloader. This value is used by the built-in downloader to set the User-Agent header of the HTTP request. - /// If this value is set to null, then the following user agent value will be used: "EpubReader/version" where "version" is the current version of the EpubReader library. + /// + /// Gets or sets the user agent presented by the content downloader. + /// + /// + /// This value is used by the built-in downloader to set the User-Agent header of the HTTP request. + /// If this value is set to null, then the following user agent value will be used: "EpubReader/version" where "version" is + /// the current version of the EpubReader library. + /// + /// /// Default value is null. + /// /// public string? DownloaderUserAgent { get; set; } /// + /// /// Gets or sets a reference to the custom content downloader. + /// + /// /// If it's set to null, the built-in content downloader will be used to download remote content files. + /// + /// /// Default value is null. + /// /// public IContentDownloader? CustomContentDownloader { get; set; } } diff --git a/Source/VersOne.Epub/Options/ContentReaderOptions.cs b/Source/VersOne.Epub/Options/ContentReaderOptions.cs index cb8286e..5c662e0 100644 --- a/Source/VersOne.Epub/Options/ContentReaderOptions.cs +++ b/Source/VersOne.Epub/Options/ContentReaderOptions.cs @@ -23,6 +23,24 @@ public ContentReaderOptions(EpubReaderOptionsPreset? preset = null) /// public event EventHandler? ContentFileMissing; + /// + /// + /// Gets or sets a value indicating whether content reader should ignore the error when the EPUB 3 navigation manifest item + /// is referencing a remote resource. + /// + /// + /// If it's set to false and the content file referenced by the navigation manifest item is a remote resource + /// (i.e. the 'href' attribute of the manifest item is a remote URL), + /// then the "Incorrect EPUB manifest: EPUB 3 navigation document ... cannot be a remote resource." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the content reader will ignore the error. + /// The navigation content file will still be added to the collection of remote HTML/XHTML content files, even if the error was ignored. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreRemoteEpub3NavigationFileError { get; set; } + internal void RaiseContentFileMissingEvent(ContentFileMissingEventArgs contentFileMissingEventArgs) { ContentFileMissing?.Invoke(this, contentFileMissingEventArgs); diff --git a/Source/VersOne.Epub/Options/Epub2NcxReaderOptions.cs b/Source/VersOne.Epub/Options/Epub2NcxReaderOptions.cs index 372d364..5101711 100644 --- a/Source/VersOne.Epub/Options/Epub2NcxReaderOptions.cs +++ b/Source/VersOne.Epub/Options/Epub2NcxReaderOptions.cs @@ -1,4 +1,7 @@ -using System.Diagnostics.CodeAnalysis; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Xml; +using VersOne.Epub.Schema; namespace VersOne.Epub.Options { @@ -18,11 +21,307 @@ public Epub2NcxReaderOptions(EpubReaderOptionsPreset? preset = null) } /// - /// Gets or sets a value indicating whether the EPUB 2 NCX reader should ignore missing content attribute on the navigation points in the NCX. - /// If it's set to false and the content attribute is not present, then the "navigation point X should contain at least one navigation label" exception will be thrown. - /// Otherwise all navigation points without the content attribute and all their child navigation points will be excluded from the navigation map. + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should ignore the error when the manifest item referenced by + /// the EPUB spine's 'toc' attribute is missing. + /// + /// + /// If it's set to false and the TOC manifest item is missing in the EPUB manifest, + /// then the "EPUB parsing error: TOC item ... not found in EPUB manifest." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will treat the EPUB file + /// as if it doesn't have a EPUB 2 NCX document. + /// + /// /// Default value is false. + /// + /// + public bool IgnoreMissingTocManifestItemError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should ignore the error when the TOC file referenced by + /// the TOC manifest item is missing in the EPUB file. + /// + /// + /// If it's set to false and the content file referenced by the TOC manifest item is not present, + /// then the "EPUB parsing error: TOC file ... not found in the EPUB file." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will treat the EPUB file + /// as if it doesn't have a EPUB 2 NCX document. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingTocFileError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should ignore the error when the TOC file is larger than 2 GB. + /// + /// + /// If it's set to false and the content file referenced by the TOC manifest item is larger than 2 GB, + /// then the "EPUB parsing error: TOC file ... is larger than 2 GB." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will treat the EPUB file + /// as if it doesn't have a EPUB 2 NCX document. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreTocFileIsTooLargeError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should ignore the error when the TOC file is not a valid XML file. + /// + /// + /// If it's set to false and an XML parsing error has occurred while trying to open the TOC file, + /// then the "EPUB parsing error: TOC file is not a valid XML file." exception will be thrown with the original + /// available through the property. + /// This exception can be suppressed by setting this property to true, in which case the reader will treat the EPUB file + /// as if it doesn't have a EPUB 2 NCX document. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreTocFileIsNotValidXmlError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should ignore the error when the TOC file is missing the 'ncx' XML element. + /// + /// + /// If it's set to false and the 'ncx' XML element is not present, + /// then the "EPUB parsing error: TOC file does not contain ncx element." exception will be thrown. + /// This exception can be suppressed by setting this property to true. However, since 'ncx' is the top-level XML element + /// in the EPUB 2 NCX file, the reader will treat this case as if the EPUB file doesn't have a EPUB 2 NCX document. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingNcxElementError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should ignore the error when the TOC file is missing the 'head' XML element. + /// + /// + /// If it's set to false and the 'head' XML element is not present, + /// then the "EPUB parsing error: TOC file does not contain head element." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will create an empty + /// object for the property. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingHeadElementError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should ignore the error when the TOC file is missing the 'docTitle' XML element. + /// + /// + /// If it's set to false and the 'docTitle' XML element is not present, + /// then the "EPUB parsing error: TOC file does not contain docTitle element." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will set + /// the property to null. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingDocTitleElementError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should ignore the error when the TOC file is missing the 'navMap' XML element. + /// + /// + /// If it's set to false and the 'navMap' XML element is not present, + /// then the "EPUB parsing error: TOC file does not contain navMap element." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will create an empty + /// object for the property. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingNavMapElementError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should skip 'meta' XML elements that are missing required attributes + /// ('name' and 'content'). + /// + /// + /// If it's set to false and one of the required attributes is not present, + /// then one of the "Incorrect EPUB navigation meta: meta ... is missing." exceptions will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the items with the missing attributes will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipInvalidMetaElements { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should skip navigation points that are missing the 'id' attribute. + /// + /// + /// If it's set to false and the 'id' attribute is not present, + /// then the "Incorrect EPUB navigation point: point ID is missing." exception will be thrown. + /// This exception can be suppressed by setting this property to true, + /// in which case the navigation points with the missing attribute will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipNavigationPointsWithMissingIds { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should ignore the error when a navigation point doesn't have any labels. + /// + /// + /// If it's set to false and a navigation point without any labels is found, + /// then the "EPUB parsing error: navigation point ... should contain at least one navigation label." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will skip the label count validation. + /// + /// + /// Default value is false. + /// + /// + public bool AllowNavigationPointsWithoutLabels { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should skip navigation points that are missing the 'content' XML element. + /// + /// + /// If it's set to false and the 'content' element is not present, + /// then the "EPUB parsing error: navigation point ... should contain content." exception will be thrown. + /// This exception can be suppressed by setting this property to true, + /// in which case the navigation points with the missing content will be skipped. + /// + /// + /// Default value is false. + /// /// public bool IgnoreMissingContentForNavigationPoints { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should skip navigation labels that are missing the 'text' XML element. + /// + /// + /// If it's set to false and the 'text' element is not present, + /// then the "Incorrect EPUB navigation label: label text element is missing." exception will be thrown. + /// This exception can be suppressed by setting this property to true, + /// in which case the navigation labels with the missing text will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipInvalidNavigationLabels { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should skip navigation content elements that are missing the 'src' attribute. + /// + /// + /// If it's set to false and the 'src' attribute is not present, + /// then the "Incorrect EPUB navigation content: content source is missing." exception will be thrown. + /// This exception can be suppressed by setting this property to true, + /// in which case both the navigation content with the missing attribute and the element it belongs to + /// (navigation point, navigation target, or navigation page target) will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipInvalidNavigationContent { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should replace missing navigation page target types with unknown types. + /// + /// + /// If it's set to false and the 'type' attribute is not present on a 'pageTarget' XML node, + /// then the "Incorrect EPUB navigation page target: page target type is missing." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will ignore the error + /// and will set the property to . + /// + /// + /// Default value is false. + /// + /// + public bool ReplaceMissingPageTargetTypesWithUnknown { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should ignore the error when a navigation page target doesn't have any labels. + /// + /// + /// If it's set to false and a navigation page target without any labels is found, + /// then the "Incorrect EPUB navigation page target: at least one navLabel element is required." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will skip the label count validation. + /// + /// + /// Default value is false. + /// + /// + public bool AllowNavigationPageTargetsWithoutLabels { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should ignore the error when a navigation list doesn't have any labels. + /// + /// + /// If it's set to false and a navigation list without any labels is found, + /// then the "Incorrect EPUB navigation list: at least one navLabel element is required." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will skip the label count validation. + /// + /// + /// Default value is false. + /// + /// + public bool AllowNavigationListsWithoutLabels { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should skip navigation targets that are missing the 'id' attribute. + /// + /// + /// If it's set to false and the 'id' attribute is not present, + /// then the "Incorrect EPUB navigation target: navigation target ID is missing." exception will be thrown. + /// This exception can be suppressed by setting this property to true, + /// in which case the navigation targets with the missing attribute will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipInvalidNavigationTargets { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 2 NCX reader should ignore the error when a navigation target doesn't have any labels. + /// + /// + /// If it's set to false and a navigation target without any labels is found, + /// then the "Incorrect EPUB navigation target: at least one navLabel element is required." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will skip the label count validation. + /// + /// + /// Default value is false. + /// + /// + public bool AllowNavigationTargetsWithoutLabels { get; set; } } } diff --git a/Source/VersOne.Epub/Options/Epub3NavDocumentReaderOptions.cs b/Source/VersOne.Epub/Options/Epub3NavDocumentReaderOptions.cs new file mode 100644 index 0000000..9d684a0 --- /dev/null +++ b/Source/VersOne.Epub/Options/Epub3NavDocumentReaderOptions.cs @@ -0,0 +1,158 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Xml; + +namespace VersOne.Epub.Options +{ + /// + /// Various options to configure the behavior of the EPUB 3 navigation document reader. + /// + public class Epub3NavDocumentReaderOptions + { + /// + /// Initializes a new instance of the class. + /// + /// + /// An optional preset to initialize the class with a predefined set of options. + /// + [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Temporarily ignore unused 'preset' parameter.")] + public Epub3NavDocumentReaderOptions(EpubReaderOptionsPreset? preset = null) + { + } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 3 navigation document reader should ignore the error when the book doesn't have + /// a manifest item with the 'nav' property. + /// + /// + /// If it's set to false and the NAV manifest item is missing in the EPUB manifest, + /// then the "EPUB parsing error: NAV item not found in EPUB manifest." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will treat the EPUB file + /// as if it doesn't have a EPUB 3 navigation document. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingNavManifestItemError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 3 navigation document reader should ignore the error when the NAV file referenced by + /// the NAV manifest item is missing in the EPUB file. + /// + /// + /// If it's set to false and the content file referenced by the NAV manifest item is not present, + /// then the "EPUB parsing error: navigation file ... not found in the EPUB file." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will treat the EPUB file + /// as if it doesn't have a EPUB 3 navigation document. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingNavFileError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 3 navigation document reader should ignore the error when the NAV file is larger than 2 GB. + /// + /// + /// If it's set to false and the content file referenced by the NAV manifest item is larger than 2 GB, + /// then the "EPUB parsing error: navigation file ... is larger than 2 GB." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will treat the EPUB file + /// as if it doesn't have a EPUB 3 navigation document. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreNavFileIsTooLargeError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 3 navigation document reader should ignore the error + /// when the NAV file is not a valid XHTML file. + /// + /// + /// If it's set to false and an XML parsing error has occurred while trying to open the NAV file, + /// then the "EPUB parsing error: navigation file is not a valid XHTML file." exception will be thrown + /// with the original available through the property. + /// This exception can be suppressed by setting this property to true, in which case the reader will treat the EPUB file + /// as if it doesn't have a EPUB 3 navigation document. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreNavFileIsNotValidXmlError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 3 navigation document reader should ignore the error + /// when the NAV file is missing the 'html' element. + /// + /// + /// If it's set to false and the 'html' element is not present, + /// then the "EPUB parsing error: navigation file does not contain html element." exception will be thrown. + /// This exception can be suppressed by setting this property to true. However, since 'html' is the top-level element + /// in the navigation document, the reader will treat this case as if the EPUB file doesn't have a EPUB 3 navigation document. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingHtmlElementError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 3 navigation document reader should ignore the error + /// when the NAV file is missing the 'body' element. + /// + /// + /// If it's set to false and the 'body' element is not present, + /// then the "EPUB parsing error: navigation file does not contain body element." exception will be thrown. + /// This exception can be suppressed by setting this property to true. However, since 'body' is an essential element + /// in the navigation document, the reader will treat this case as if the EPUB file doesn't have a EPUB 3 navigation document. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingBodyElementError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 3 navigation document reader should skip + /// 'nav' elements that are missing a child 'ol' element. + /// + /// + /// If it's set to false and the 'ol' element is not present inside a 'nav' element, + /// then the "EPUB parsing error: 'nav' element in the navigation file does not contain a required 'ol' element." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case those 'nav' elements will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipNavsWithMissingOlElements { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB 3 navigation document reader should skip + /// 'li' elements that are missing required child elements. + /// + /// + /// If it's set to false and neither the 'a' element nor the 'span' element are present inside an 'li' element, + /// then the "EPUB parsing error: 'li' element in the navigation file must contain either an 'a' element or a 'span' element." + /// exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case those 'li' elements will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipInvalidLiElements { get; set; } + } +} diff --git a/Source/VersOne.Epub/Options/EpubReaderOptions.cs b/Source/VersOne.Epub/Options/EpubReaderOptions.cs index bbd6b10..7a85de4 100644 --- a/Source/VersOne.Epub/Options/EpubReaderOptions.cs +++ b/Source/VersOne.Epub/Options/EpubReaderOptions.cs @@ -11,13 +11,18 @@ public class EpubReaderOptions /// An optional preset to initialize the class with a predefined set of options. public EpubReaderOptions(EpubReaderOptionsPreset? preset = null) { - PackageReaderOptions = new PackageReaderOptions(preset); - ContentReaderOptions = new ContentReaderOptions(preset); - ContentDownloaderOptions = new ContentDownloaderOptions(preset); - BookCoverReaderOptions = new BookCoverReaderOptions(preset); - SpineReaderOptions = new SpineReaderOptions(preset); - Epub2NcxReaderOptions = new Epub2NcxReaderOptions(preset); - XmlReaderOptions = new XmlReaderOptions(preset); + PackageReaderOptions = new(preset); + ContainerFileReaderOptions = new(preset); + ContentReaderOptions = new(preset); + ContentDownloaderOptions = new(preset); + BookCoverReaderOptions = new(preset); + SpineReaderOptions = new(preset); + Epub2NcxReaderOptions = new(preset); + Epub3NavDocumentReaderOptions = new(preset); + MetadataReaderOptions = new(preset); + NavigationReaderOptions = new(preset); + SmilReaderOptions = new(preset); + XmlReaderOptions = new(preset); } /// @@ -25,6 +30,11 @@ public EpubReaderOptions(EpubReaderOptionsPreset? preset = null) /// public PackageReaderOptions PackageReaderOptions { get; set; } + /// + /// Gets or sets EPUB OCF container file reader options. + /// + public ContainerFileReaderOptions ContainerFileReaderOptions { get; set; } + /// /// Gets or sets EPUB content reader options which is used for loading local content files. /// @@ -50,6 +60,26 @@ public EpubReaderOptions(EpubReaderOptionsPreset? preset = null) /// public Epub2NcxReaderOptions Epub2NcxReaderOptions { get; set; } + /// + /// Gets or sets EPUB 3 navigation document reader options. + /// + public Epub3NavDocumentReaderOptions Epub3NavDocumentReaderOptions { get; set; } + + /// + /// Gets or sets EPUB book meta information reader options. + /// + public MetadataReaderOptions MetadataReaderOptions { get; set; } + + /// + /// Gets or sets EPUB navigation reader options. + /// + public NavigationReaderOptions NavigationReaderOptions { get; set; } + + /// + /// Gets or sets SMIL (EPUB media overlay) document reader options. + /// + public SmilReaderOptions SmilReaderOptions { get; set; } + /// /// Gets or sets XML reader options. /// diff --git a/Source/VersOne.Epub/Options/MetadataReaderOptions.cs b/Source/VersOne.Epub/Options/MetadataReaderOptions.cs new file mode 100644 index 0000000..d54eaff --- /dev/null +++ b/Source/VersOne.Epub/Options/MetadataReaderOptions.cs @@ -0,0 +1,53 @@ +using System.Diagnostics.CodeAnalysis; +using VersOne.Epub.Schema; + +namespace VersOne.Epub.Options +{ + /// + /// Various options to configure the behavior of the EPUB book meta information reader. + /// + public class MetadataReaderOptions + { + /// + /// Initializes a new instance of the class. + /// + /// An optional preset to initialize the class with a predefined set of options. + [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Temporarily ignore unused 'preset' parameter.")] + public MetadataReaderOptions(EpubReaderOptionsPreset? preset = null) + { + } + + /// + /// + /// Gets or sets a value indicating whether the EPUB book meta information reader should skip 'link' XML elements + /// that are missing the required 'href' attribute. + /// + /// + /// If it's set to false and the 'href' attribute is not present, + /// then the "Incorrect EPUB metadata link: href is missing." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case those 'link' XML elements will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipLinksWithoutHrefs { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB book meta information reader should ignore the error + /// when a 'link' XML element is missing the 'rel' attribute. + /// + /// + /// If it's set to false and the 'rel' attribute is not present, + /// then the "Incorrect EPUB metadata link: rel is missing." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will ignore this error + /// and will initialize the property with an empty list. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreLinkWithoutRelError { get; set; } + } +} diff --git a/Source/VersOne.Epub/Options/NavigationReaderOptions.cs b/Source/VersOne.Epub/Options/NavigationReaderOptions.cs new file mode 100644 index 0000000..fbcd9c9 --- /dev/null +++ b/Source/VersOne.Epub/Options/NavigationReaderOptions.cs @@ -0,0 +1,76 @@ +using System.Diagnostics.CodeAnalysis; +using VersOne.Epub.Schema; + +namespace VersOne.Epub.Options +{ + /// + /// Various options to configure the behavior of the EPUB navigation reader. + /// + public class NavigationReaderOptions + { + /// + /// Initializes a new instance of the class. + /// + /// An optional preset to initialize the class with a predefined set of options. + [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Temporarily ignore unused 'preset' parameter.")] + public NavigationReaderOptions(EpubReaderOptionsPreset? preset = null) + { + } + + /// + /// + /// Gets or sets a value indicating whether the EPUB navigation reader should ignore the error + /// when a EPUB 2 navigation point has no navigation labels. + /// + /// + /// If it's set to false and collection is empty, + /// then the "Incorrect EPUB 2 NCX: navigation point ... should contain at least one navigation label." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will ignore this error + /// and will set the property to an empty string. + /// + /// + /// This property has no effect on EPUB 3 navigation item validation + /// as the EPUB 3 specification doesn't require the navigation items to have titles. + /// + /// + /// Default value is false. + /// + /// + public bool AllowEpub2NavigationItemsWithEmptyTitles { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB navigation reader should skip navigation items that are referencing remote resources. + /// + /// + /// If it's set to false and a navigation item is pointing to an external URL outside of the EPUB book, + /// then the "Incorrect EPUB 2 NCX: content source ... cannot be a remote resource." exception (for EPUB 2 NCX documents) or + /// the "Incorrect EPUB 3 navigation document: anchor href ... cannot be a remote resource." exception (for EPUB 3 navigation documents) + /// will be thrown. + /// This exception can be suppressed by setting this property to true, in which case those navigation items will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipRemoteNavigationItems { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the EPUB navigation reader should skip navigation items referencing local content + /// which is missing in the EPUB manifest. + /// + /// + /// If it's set to false and a navigation item is pointing to a URL inside the EPUB book which is not present in the EPUB manifest, + /// then the "Incorrect EPUB 2 NCX: content source ... not found in EPUB manifest." exception (for EPUB 2 NCX documents) or + /// the "Incorrect EPUB 3 navigation document: target for anchor href ... not found in EPUB manifest." exception (for EPUB 3 navigation documents) + /// will be thrown. + /// This exception can be suppressed by setting this property to true, in which case those navigation items will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipNavigationItemsReferencingMissingContent { get; set; } + } +} diff --git a/Source/VersOne.Epub/Options/PackageReaderOptions.cs b/Source/VersOne.Epub/Options/PackageReaderOptions.cs index f6e31b2..bac73ef 100644 --- a/Source/VersOne.Epub/Options/PackageReaderOptions.cs +++ b/Source/VersOne.Epub/Options/PackageReaderOptions.cs @@ -1,4 +1,7 @@ -using System.Diagnostics.CodeAnalysis; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Xml; +using VersOne.Epub.Schema; namespace VersOne.Epub.Options { @@ -19,17 +22,236 @@ public PackageReaderOptions(EpubReaderOptionsPreset? preset = null) } /// - /// Gets or sets a value indicating whether the package reader should ignore missing TOC attribute in the EPUB 2 spine. - /// If it's set to false and the TOC attribute is not present, then the "Incorrect EPUB spine: TOC is missing" exception will be thrown. + /// + /// Gets or sets a value indicating whether the package reader should ignore the missing OPF package file error. + /// + /// + /// The path to the OPF package file is defined by the 'rootfile' element in the 'META-INF/container.xml' file. + /// If this property is set to false and the OPF package file is not present, + /// then the "EPUB parsing error: OPF package file not found in the EPUB file." exception will be thrown. + /// This exception can be suppressed by setting this property to true. However, since the OPF package is the main file + /// describing the EPUB book, the class methods will return null in this case. + /// + /// /// Default value is false. + /// + /// + public bool IgnoreMissingPackageFile { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the package reader should ignore the error when the package file is not a valid XML file. + /// + /// + /// If it's set to false and an XML parsing error has occurred while trying to open the OPF package file, + /// then the "EPUB parsing error: package file is not a valid XML file." exception will be thrown + /// with the original available through the property. + /// This exception can be suppressed by setting this property to true. However, since the OPF package is the main file + /// describing the EPUB book, the class methods will return null in this case. + /// + /// + /// Default value is false. + /// + /// + public bool IgnorePackageFileIsNotValidXmlError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the package reader should ignore the missing 'package' XML element in the OPF package file. + /// + /// + /// If this property is set to false and the 'package' XML element is not present, + /// then the "EPUB parsing error: package XML element not found in the package file." exception will be thrown. + /// This exception can be suppressed by setting this property to true. However, since 'package' is the top-level XML element + /// in the OPF package file, the class methods will return null in this case. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingPackageNode { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the package reader should use a fallback EPUB version when the 'version' attribute + /// of the 'package' XML element in the OPF package file is missing or when the attribute's value is set to an unsupported EPUB version. + /// + /// + /// If this property is set to null and the 'version' attribute is not present, + /// then the "EPUB parsing error: EPUB version is not specified in the package." exception will be thrown. + /// Alternatively, if the attribute's value is set to an unsupported EPUB version, + /// then the "Unsupported EPUB version: ..." exception will be thrown. + /// Those exceptions can be suppressed by setting this property to an explicit fallback EPUB version (e.g. ), + /// in which case the rest of the EPUB schema will be parsed according to that EPUB version specification. + /// + /// + /// Default value is null. + /// + /// + public EpubVersion? FallbackEpubVersion { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the package reader should ignore the missing 'metadata' XML element in the OPF package file. + /// + /// + /// If this property is set to false and the 'metadata' XML element is not present, + /// then the "EPUB parsing error: metadata not found in the package." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case all properties of the + /// object will be set to the default / empty values. Common metadata fields include the book's titles, the authors, and the descriptions, + /// so even if this exception is suppressed, the , the , + /// and the properties will be empty. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingMetadataNode { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the package reader should ignore the missing 'manifest' XML element in the OPF package file. + /// + /// + /// If this property is set to false and the 'manifest' XML element is not present, + /// then the "EPUB parsing error: manifest not found in the package." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case all properties of the + /// object will be set to the default / empty values. The EPUB manifest contains the list of content files within the EPUB book + /// (HTML and CSS files, images, fonts, and so on), so even if this exception is suppressed, + /// the EPUB book returned by the will have no content. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingManifestNode { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the package reader should ignore the missing 'spine' XML element in the OPF package file. + /// + /// + /// If this property is set to false and the 'spine' XML element is not present, + /// then the "EPUB parsing error: spine not found in the package." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case all properties of the + /// object will be set to the default / empty values. The EPUB spine determines the reading order within the EPUB book + /// so even if this exception is suppressed, the property will be empty. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingSpineNode { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the package reader should ignore the missing TOC attribute in the EPUB 2 spine. + /// + /// + /// If it's set to false and the TOC attribute is not present, then the "Incorrect EPUB spine: TOC is missing." exception will be thrown. + /// This property has no effect if the EPUB version of the book is not or if the 'spine' XML element + /// is missing in the OPF package file and the property is set to true. + /// + /// + /// Default value is false. + /// /// public bool IgnoreMissingToc { get; set; } /// - /// Gets or sets a value indicating whether the package reader should skip EPUB manifest items that are missing required attributes (id, href, or media-type). - /// If it's set to false and one of the required attributes is not present, then one of the "Incorrect EPUB manifest: item ... is missing" exceptions will be thrown. + /// + /// Gets or sets a value indicating whether the package reader should skip EPUB manifest items that are missing required attributes + /// ('id', 'href', or 'media-type'). + /// + /// + /// If it's set to false and one of the required attributes is not present, + /// then one of the "Incorrect EPUB manifest: item ... is missing." exceptions will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the items with the missing attributes will be skipped. + /// + /// /// Default value is false. + /// /// public bool SkipInvalidManifestItems { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the package reader should skip EPUB manifest items that have duplicate ID values. + /// + /// + /// If it's set to false and an item with a duplicate ID was found, + /// then the "Incorrect EPUB manifest: item with ID = ... is not unique." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case only the first item within each duplicate item set + /// will be added to the collection and the others will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipDuplicateManifestItemIds { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the package reader should skip EPUB manifest items that have duplicate 'href' attribute values. + /// + /// + /// If it's set to false and an item with a duplicate href value was found, + /// then the "Incorrect EPUB manifest: item with href = ... is not unique." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case only the first item within each duplicate item set + /// will be added to the collection and the others will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipDuplicateManifestHrefs { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the package reader should skip EPUB spine items that are missing the required 'idref' attribute. + /// + /// + /// If it's set to false and the required attribute is not present, + /// then the "Incorrect EPUB spine: item ID ref is missing." exception will be thrown. + /// This exception can be suppressed by setting this property to true, + /// in which case the items with the missing 'idref' attribute will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipInvalidSpineItems { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the package reader should skip EPUB guide items that are missing required attributes ('type' and 'href'). + /// + /// + /// If it's set to false and one of the required attributes is not present, + /// then one of the "Incorrect EPUB guide: item ... is missing." exceptions will be thrown. + /// This exception can be suppressed by setting this property to true, + /// in which case the items with the missing attributes will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipInvalidGuideReferences { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the package reader should skip EPUB collections that are missing the required 'role' attribute. + /// + /// + /// If it's set to false and the required attribute is not present, + /// then the "Incorrect EPUB collection: collection role is missing." exception will be thrown. + /// This exception can be suppressed by setting this property to true, + /// in which case the collections with the missing 'role' attribute will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipInvalidCollections { get; set; } } } diff --git a/Source/VersOne.Epub/Options/SmilReaderOptions.cs b/Source/VersOne.Epub/Options/SmilReaderOptions.cs new file mode 100644 index 0000000..619f6f5 --- /dev/null +++ b/Source/VersOne.Epub/Options/SmilReaderOptions.cs @@ -0,0 +1,217 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Xml; + +namespace VersOne.Epub.Options +{ + /// + /// Various options to configure the behavior of the SMIL (EPUB media overlay) document reader. + /// + public class SmilReaderOptions + { + /// + /// Initializes a new instance of the class. + /// + /// An optional preset to initialize the class with a predefined set of options. + [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Temporarily ignore unused 'preset' parameter.")] + public SmilReaderOptions(EpubReaderOptionsPreset? preset = null) + { + } + + /// + /// + /// Gets or sets a value indicating whether the SMIL document reader should ignore the error when the SMIL file referenced by + /// a SMIL manifest item is missing in the EPUB file. + /// + /// + /// If it's set to false and the content file referenced by a SMIL manifest item is not present, + /// then the "EPUB parsing error: SMIL file ... not found in the EPUB file." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will skip this SMIL file. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingSmilFileError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the SMIL document reader should ignore the error when a SMIL file is larger than 2 GB. + /// + /// + /// If it's set to false and the content file referenced by a SMIL manifest item is larger than 2 GB, + /// then the "EPUB parsing error: SMIL file ... is larger than 2 GB." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will skip this SMIL file. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreSmilFileIsTooLargeError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the SMIL document reader should ignore the error when a SMIL file is not a valid XML file. + /// + /// + /// If it's set to false and an XML parsing error has occurred while trying to open a SMIL file, + /// then the "EPUB parsing error: SMIL file ... is not a valid XML file." exception will be thrown + /// with the original available through the property. + /// This exception can be suppressed by setting this property to true, in which case the reader will skip this SMIL file. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreSmilFileIsNotValidXmlError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the SMIL document reader should ignore the error + /// when a SMIL file is missing the 'smil' XML element. + /// + /// + /// If it's set to false and the 'smil' XML element is not present, + /// then the "SMIL parsing error: smil XML element is missing in the file." exception will be thrown. + /// This exception can be suppressed by setting this property to true. However, since 'smil' is the top-level XML element + /// in the SMIL document, the reader will will skip this SMIL file in this case. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingSmilElementError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the SMIL document reader should ignore the error when the SMIL version is missing in the SMIL file. + /// + /// + /// If it's set to false and the 'smil' XML element is missing the 'version' attribute, + /// then the "SMIL parsing error: SMIL version is missing." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will ignore this error + /// and use SMIL 3.0 specification to parse the rest of the SMIL document. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingSmilVersionError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the SMIL document reader should ignore the error + /// when SMIL version declared in the SMIL file is not supported by the reader. + /// + /// + /// If it's set to false and the 'smil' XML element's 'version' attribute contains a version + /// other than supported by the reader (currently, only '3.0'), + /// then the "SMIL parsing error: unsupported SMIL version: ... ." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader will ignore this error + /// and use SMIL 3.0 specification to parse the rest of the SMIL document. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreUnsupportedSmilVersionError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the SMIL document reader should ignore the error + /// when a SMIL file is missing the 'body' XML element. + /// + /// + /// If it's set to false and the 'body' XML element is not present, + /// then the "SMIL parsing error: body XML element is missing in the file." exception will be thrown. + /// This exception can be suppressed by setting this property to true. However, since 'body' is an essential XML element + /// in the SMIL document, the reader will will skip this SMIL file in this case. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingBodyElementError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the SMIL document reader should ignore the error + /// when the 'body' XML element is missing a child 'seq' / 'par' XML element. + /// + /// + /// If it's set to false and the 'body' XML element has neither a 'seq' XML element nor a 'par' XML element, + /// then the "SMIL parsing error: body XML element must contain at least one seq or par XML element." exception will be thrown. + /// This exception can be suppressed by setting this property to true. However, since 'seq' and 'par' are + /// the only container elements for the text and audio data in the SMIL file, the resulting parsed document will have no usable content. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreBodyMissingSeqOrParElementsError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the SMIL document reader should ignore the error + /// when a 'seq' XML element is missing a child 'seq' / 'par' XML element. + /// + /// + /// If it's set to false and a 'seq' XML element has neither a nested 'seq' XML element nor a 'par' XML element, + /// then the "SMIL parsing error: seq XML element must contain at least one nested seq or par XML element." exception will be thrown. + /// This exception can be suppressed by setting this property to true. However, since 'seq' and 'par' are + /// the only container elements for the text and audio data in the SMIL file, + /// the resulting parsed SMIL sequence object will have no usable content. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreSeqMissingSeqOrParElementsError { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the SMIL document reader should skip 'par' XML elements + /// that are missing a child 'text' XML element. + /// + /// + /// If it's set to false and the 'text' XML element is not present inside a 'par' XML element, + /// then the "SMIL parsing error: par XML element must contain one text XML element." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case those 'par' XML elements will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipParsWithoutTextElements { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the SMIL document reader should skip 'text' XML elements that are missing the 'src' attribute. + /// + /// + /// If it's set to false and a 'text' XML element is missing the 'src' attribute, + /// then the "SMIL parsing error: text XML element must have an src attribute." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case those 'text' XML elements will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipTextsWithoutSrcAttributes { get; set; } + + /// + /// + /// Gets or sets a value indicating whether the SMIL document reader should skip 'audio' XML elements that are missing the 'src' attribute. + /// + /// + /// If it's set to false and an 'audio' XML element is missing the 'src' attribute, + /// then the "SMIL parsing error: audio XML element must have an src attribute." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case those 'audio' XML elements will be skipped. + /// + /// + /// Default value is false. + /// + /// + public bool SkipAudiosWithoutSrcAttributes { get; set; } + } +} diff --git a/Source/VersOne.Epub/Options/SpineReaderOptions.cs b/Source/VersOne.Epub/Options/SpineReaderOptions.cs index 62bba55..d9df156 100644 --- a/Source/VersOne.Epub/Options/SpineReaderOptions.cs +++ b/Source/VersOne.Epub/Options/SpineReaderOptions.cs @@ -9,7 +9,7 @@ namespace VersOne.Epub.Options public class SpineReaderOptions { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// An optional preset to initialize the class with a predefined set of options. [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Temporarily ignore unused 'preset' parameter.")] @@ -18,12 +18,64 @@ public SpineReaderOptions(EpubReaderOptionsPreset? preset = null) } /// + /// /// Gets or sets a value indicating whether EPUB spine reader should ignore the error when the manifest item referenced by /// a EPUB spine item is missing. + /// + /// /// If it's set to false and the manifest item with the given ID is not present, then - /// the "Incorrect EPUB spine: item with IdRef = "..." is missing in the manifest" exception will be thrown. + /// the "Incorrect EPUB spine: item with IdRef = ... is missing in the manifest" exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader + /// will skip the invalid spine item. As a result, the collection + /// and the data returned by the and + /// the methods will be missing the text content + /// referenced by the invalid spine item. + /// + /// /// Default value is false. + /// /// public bool IgnoreMissingManifestItems { get; set; } + + /// + /// + /// Gets or sets a value indicating whether EPUB spine reader should skip EPUB spine items referencing remote HTML files. + /// + /// + /// If it's set to false and a EPUB spine item is referencing a manifest item + /// which is pointing to an external URL outside of the EPUB book, + /// then the "Incorrect EPUB manifest: EPUB spine item ... cannot be a remote resource." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader + /// will skip the invalid spine item. As a result, the collection + /// and the data returned by the and + /// the methods will be missing the text content + /// referenced by the invalid spine item. + /// + /// + /// Default value is false. + /// + /// + public bool SkipSpineItemsReferencingRemoteContent { get; set; } + + /// + /// + /// Gets or sets a value indicating whether EPUB spine reader should ignore the error when the content file referenced by + /// a EPUB spine item is missing. + /// + /// + /// If it's set to false and and a EPUB spine item is referencing a manifest item + /// which is pointing to a non-existent HTML file within the EPUB book, + /// then the "Incorrect EPUB manifest: HTML content file with href = ... is missing in the book." exception will be thrown. + /// This exception can be suppressed by setting this property to true, in which case the reader + /// will skip the invalid spine item. As a result, the collection + /// and the data returned by the and + /// the methods will be missing the text content + /// referenced by the invalid spine item. + /// + /// + /// Default value is false. + /// + /// + public bool IgnoreMissingContentFiles { get; set; } } } diff --git a/Source/VersOne.Epub/Readers/BookCoverReader.cs b/Source/VersOne.Epub/Readers/BookCoverReader.cs index 975725a..22dc458 100644 --- a/Source/VersOne.Epub/Readers/BookCoverReader.cs +++ b/Source/VersOne.Epub/Readers/BookCoverReader.cs @@ -16,7 +16,7 @@ internal static class BookCoverReader EpubLocalByteContentFileRef? result; if (epubSchema.Package.EpubVersion == EpubVersion.EPUB_3 || epubSchema.Package.EpubVersion == EpubVersion.EPUB_3_1) { - result = ReadEpub3Cover(epubSchema, imageContentRefs); + result = ReadEpub3Cover(epubSchema, imageContentRefs, bookCoverReaderOptions); result ??= ReadEpub2Cover(epubSchema, imageContentRefs, bookCoverReaderOptions); } else @@ -31,7 +31,7 @@ internal static class BookCoverReader BookCoverReaderOptions bookCoverReaderOptions) { EpubLocalByteContentFileRef? result = ReadEpub2CoverFromMetadata(epubSchema, imageContentRefs, bookCoverReaderOptions); - result ??= ReadEpub2CoverFromGuide(epubSchema, imageContentRefs); + result ??= ReadEpub2CoverFromGuide(epubSchema, imageContentRefs, bookCoverReaderOptions); return result; } @@ -44,13 +44,17 @@ internal static class BookCoverReader { return null; } - EpubMetadataMeta coverMetaItem = metaItems.Find(metaItem => metaItem.Name.CompareOrdinalIgnoreCase("cover")); + EpubMetadataMeta? coverMetaItem = metaItems.Find(metaItem => metaItem.Name.CompareOrdinalIgnoreCase("cover")); if (coverMetaItem == null) { return null; } - if (String.IsNullOrEmpty(coverMetaItem.Content)) + if (String.IsNullOrWhiteSpace(coverMetaItem.Content)) { + if (bookCoverReaderOptions.Epub2MetadataIgnoreMissingContent) + { + return null; + } throw new EpubPackageException("Incorrect EPUB metadata: cover item content is missing."); } EpubManifestItem? coverManifestItem = @@ -64,13 +68,22 @@ internal static class BookCoverReader 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) ?? + GetCoverImageContentRef(imageContentRefs, coverManifestItem.Href, bookCoverReaderOptions, + out EpubLocalByteContentFileRef? coverImageContentRef, out bool isCoverRemote); + if (coverImageContentRef == null && !isCoverRemote) + { + if (bookCoverReaderOptions.Epub2MetadataIgnoreMissingContentFile) + { + return null; + } throw new EpubPackageException($"Incorrect EPUB manifest: item with href = \"{coverManifestItem.Href}\" is missing."); - return result; + } + return coverImageContentRef; } private static EpubLocalByteContentFileRef? ReadEpub2CoverFromGuide( - EpubSchema epubSchema, EpubContentCollectionRef imageContentRefs) + EpubSchema epubSchema, EpubContentCollectionRef imageContentRefs, + BookCoverReaderOptions bookCoverReaderOptions) { if (epubSchema.Package.Guide != null) { @@ -78,10 +91,11 @@ internal static class BookCoverReader { if (guideReference.Type.ToLowerInvariant() == "cover") { - EpubLocalByteContentFileRef? coverImageContentFileRef = GetCoverImageContentRef(imageContentRefs, guideReference.Href); - if (coverImageContentFileRef != null) + GetCoverImageContentRef(imageContentRefs, guideReference.Href, bookCoverReaderOptions, + out EpubLocalByteContentFileRef? coverImageContentRef, out _); + if (coverImageContentRef != null) { - return coverImageContentFileRef; + return coverImageContentRef; } } } @@ -90,31 +104,47 @@ internal static class BookCoverReader } private static EpubLocalByteContentFileRef? ReadEpub3Cover( - EpubSchema epubSchema, EpubContentCollectionRef imageContentRefs) + EpubSchema epubSchema, EpubContentCollectionRef imageContentRefs, + BookCoverReaderOptions bookCoverReaderOptions) { - EpubManifestItem coverManifestItem = epubSchema.Package.Manifest.Items.Find(manifestItem => manifestItem.Properties != null && + EpubManifestItem? coverManifestItem = epubSchema.Package.Manifest.Items.Find(manifestItem => manifestItem.Properties != null && manifestItem.Properties.Contains(EpubManifestProperty.COVER_IMAGE)); if (coverManifestItem == null || coverManifestItem.Href == null) { return null; } - EpubLocalByteContentFileRef result = GetCoverImageContentRef(imageContentRefs, coverManifestItem.Href) ?? + GetCoverImageContentRef(imageContentRefs, coverManifestItem.Href, bookCoverReaderOptions, + out EpubLocalByteContentFileRef? coverImageContentRef, out bool isCoverRemote); + if (coverImageContentRef == null && !isCoverRemote) + { + if (bookCoverReaderOptions.Epub3IgnoreMissingContentFile) + { + return null; + } throw new EpubPackageException($"Incorrect EPUB manifest: item with href = \"{coverManifestItem.Href}\" is missing."); - return result; + } + return coverImageContentRef; } - private static EpubLocalByteContentFileRef? GetCoverImageContentRef( - EpubContentCollectionRef imageContentRefs, string coverImageContentFileKey) + private static void GetCoverImageContentRef( + EpubContentCollectionRef imageContentRefs, string coverImageContentFileKey, + BookCoverReaderOptions bookCoverReaderOptions, out EpubLocalByteContentFileRef? coverImageContentRef, out bool isRemoteFileFound) { if (imageContentRefs.ContainsRemoteFileRefWithUrl(coverImageContentFileKey)) { + if (bookCoverReaderOptions.IgnoreRemoteContentFileError) + { + coverImageContentRef = null; + isRemoteFileFound = true; + return; + } throw new EpubPackageException($"Incorrect EPUB manifest: EPUB cover image \"{coverImageContentFileKey}\" cannot be a remote resource."); } - if (!imageContentRefs.TryGetLocalFileRefByKey(coverImageContentFileKey, out EpubLocalByteContentFileRef coverImageContentFileRef)) + isRemoteFileFound = false; + if (!imageContentRefs.TryGetLocalFileRefByKey(coverImageContentFileKey, out coverImageContentRef)) { - return null; + coverImageContentRef = null; } - return coverImageContentFileRef; } } } diff --git a/Source/VersOne.Epub/Readers/BookReader.cs b/Source/VersOne.Epub/Readers/BookReader.cs index 86aced5..b0c9da5 100644 --- a/Source/VersOne.Epub/Readers/BookReader.cs +++ b/Source/VersOne.Epub/Readers/BookReader.cs @@ -20,28 +20,28 @@ public BookReader(IEnvironmentDependencies environmentDependencies, EpubReaderOp this.epubReaderOptions = epubReaderOptions ?? new EpubReaderOptions(); } - public EpubBook ReadBook(string filePath) + public EpubBook? ReadBook(string filePath) { return ReadBookAsync(filePath).ExecuteAndUnwrapAggregateException(); } - public EpubBook ReadBook(Stream stream) + public EpubBook? ReadBook(Stream stream) { return ReadBookAsync(stream).ExecuteAndUnwrapAggregateException(); } - public async Task ReadBookAsync(string filePath) + public async Task ReadBookAsync(string filePath) { BookRefReader bookRefReader = new(environmentDependencies, epubReaderOptions); - EpubBookRef epubBookRef = await bookRefReader.OpenBookAsync(filePath).ConfigureAwait(false); - return await ReadBookAsync(epubBookRef).ConfigureAwait(false); + EpubBookRef? epubBookRef = await bookRefReader.OpenBookAsync(filePath).ConfigureAwait(false); + return epubBookRef != null ? await ReadBookAsync(epubBookRef).ConfigureAwait(false) : null; } - public async Task ReadBookAsync(Stream stream) + public async Task ReadBookAsync(Stream stream) { BookRefReader bookRefReader = new(environmentDependencies, epubReaderOptions); - EpubBookRef epubBookRef = await bookRefReader.OpenBookAsync(stream).ConfigureAwait(false); - return await ReadBookAsync(epubBookRef).ConfigureAwait(false); + EpubBookRef? epubBookRef = await bookRefReader.OpenBookAsync(stream).ConfigureAwait(false); + return epubBookRef != null ? await ReadBookAsync(epubBookRef).ConfigureAwait(false) : null; } private static List ReadReadingOrder(EpubContent epubContent, List htmlContentFileRefs) diff --git a/Source/VersOne.Epub/Readers/BookRefReader.cs b/Source/VersOne.Epub/Readers/BookRefReader.cs index c569901..396815c 100644 --- a/Source/VersOne.Epub/Readers/BookRefReader.cs +++ b/Source/VersOne.Epub/Readers/BookRefReader.cs @@ -20,17 +20,17 @@ public BookRefReader(IEnvironmentDependencies environmentDependencies, EpubReade this.epubReaderOptions = epubReaderOptions ?? new EpubReaderOptions(); } - public EpubBookRef OpenBook(string filePath) + public EpubBookRef? OpenBook(string filePath) { return OpenBookAsync(filePath).ExecuteAndUnwrapAggregateException(); } - public EpubBookRef OpenBook(Stream stream) + public EpubBookRef? OpenBook(Stream stream) { return OpenBookAsync(stream).ExecuteAndUnwrapAggregateException(); } - public Task OpenBookAsync(string filePath) + public Task OpenBookAsync(string filePath) { if (!environmentDependencies.FileSystem.FileExists(filePath)) { @@ -39,15 +39,19 @@ public Task OpenBookAsync(string filePath) return OpenBookAsync(GetZipFile(filePath), filePath); } - public Task OpenBookAsync(Stream stream) + public Task OpenBookAsync(Stream stream) { return OpenBookAsync(GetZipFile(stream), null); } - private async Task OpenBookAsync(IZipFile epubFile, string? filePath) + private async Task OpenBookAsync(IZipFile epubFile, string? filePath) { SchemaReader schemaReader = new(epubReaderOptions); - EpubSchema schema = await schemaReader.ReadSchemaAsync(epubFile).ConfigureAwait(false); + EpubSchema? schema = await schemaReader.ReadSchemaAsync(epubFile).ConfigureAwait(false); + if (schema == null) + { + return null; + } string title = schema.Package.Metadata.Titles.FirstOrDefault()?.Title ?? String.Empty; List authorList = schema.Package.Metadata.Creators.Select(creator => creator.Creator).ToList(); string author = String.Join(", ", authorList); diff --git a/Source/VersOne.Epub/Readers/ContainerFileReader.cs b/Source/VersOne.Epub/Readers/ContainerFileReader.cs new file mode 100644 index 0000000..844799e --- /dev/null +++ b/Source/VersOne.Epub/Readers/ContainerFileReader.cs @@ -0,0 +1,61 @@ +using System.IO; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; +using VersOne.Epub.Environment; +using VersOne.Epub.Options; + +namespace VersOne.Epub.Internal +{ + internal class ContainerFileReader + { + private readonly EpubReaderOptions epubReaderOptions; + private readonly ContainerFileReaderOptions containerFileReaderOptions; + + public ContainerFileReader(EpubReaderOptions? epubReaderOptions = null) + { + this.epubReaderOptions = epubReaderOptions ?? new EpubReaderOptions(); + this.containerFileReaderOptions = this.epubReaderOptions.ContainerFileReaderOptions ?? new ContainerFileReaderOptions(); + } + + public async Task GetPackageFilePathAsync(IZipFile epubFile) + { + const string EPUB_CONTAINER_FILE_PATH = "META-INF/container.xml"; + IZipFileEntry? containerFileEntry = epubFile.GetEntry(EPUB_CONTAINER_FILE_PATH); + if (containerFileEntry == null) + { + if (containerFileReaderOptions.IgnoreMissingContainerFile) + { + return null; + } + throw new EpubContainerException($"EPUB parsing error: \"{EPUB_CONTAINER_FILE_PATH}\" file not found in the EPUB file."); + } + XDocument containerDocument; + try + { + using Stream containerStream = containerFileEntry.Open(); + containerDocument = await XmlUtils.LoadDocumentAsync(containerStream, epubReaderOptions.XmlReaderOptions).ConfigureAwait(false); + } + catch (XmlException xmlException) + { + if (containerFileReaderOptions.IgnoreContainerFileIsNotValidXmlError) + { + return null; + } + throw new EpubContainerException("EPUB parsing error: EPUB OCF container file is not a valid XML file.", xmlException); + } + XNamespace cnsNamespace = "urn:oasis:names:tc:opendocument:xmlns:container"; + XAttribute? fullPathAttribute = containerDocument.Element(cnsNamespace + "container")?.Element(cnsNamespace + "rootfiles")?. + Element(cnsNamespace + "rootfile")?.Attribute("full-path"); + if (fullPathAttribute == null) + { + if (containerFileReaderOptions.IgnoreMissingPackageFilePathError) + { + return null; + } + throw new EpubContainerException("EPUB parsing error: OPF package file path not found in the EPUB container."); + } + return fullPathAttribute.Value; + } + } +} diff --git a/Source/VersOne.Epub/Readers/ContentReader.cs b/Source/VersOne.Epub/Readers/ContentReader.cs index 8abd9d3..af0b927 100644 --- a/Source/VersOne.Epub/Readers/ContentReader.cs +++ b/Source/VersOne.Epub/Readers/ContentReader.cs @@ -80,7 +80,8 @@ public EpubContentRef ParseContentMap(EpubSchema epubSchema, IZipFile epubFile) switch (contentType) { case EpubContentType.XHTML_1_1: - if (manifestItem.Properties != null && manifestItem.Properties.Contains(EpubManifestProperty.NAV)) + if (manifestItem.Properties != null && manifestItem.Properties.Contains(EpubManifestProperty.NAV) && + !epubReaderOptions.ContentReaderOptions.IgnoreRemoteEpub3NavigationFileError) { throw new EpubPackageException($"Incorrect EPUB manifest: EPUB 3 navigation document \"{href}\" cannot be a remote resource."); } diff --git a/Source/VersOne.Epub/Readers/Epub2NcxReader.cs b/Source/VersOne.Epub/Readers/Epub2NcxReader.cs index a76f0c6..17c24ab 100644 --- a/Source/VersOne.Epub/Readers/Epub2NcxReader.cs +++ b/Source/VersOne.Epub/Readers/Epub2NcxReader.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using System.Xml; using System.Xml.Linq; using VersOne.Epub.Environment; using VersOne.Epub.Options; @@ -14,10 +15,12 @@ namespace VersOne.Epub.Internal internal class Epub2NcxReader { private readonly EpubReaderOptions epubReaderOptions; + private readonly Epub2NcxReaderOptions epub2NcxReaderOptions; public Epub2NcxReader(EpubReaderOptions? epubReaderOptions = null) { this.epubReaderOptions = epubReaderOptions ?? new EpubReaderOptions(); + epub2NcxReaderOptions = this.epubReaderOptions.Epub2NcxReaderOptions ?? new Epub2NcxReaderOptions(); } public async Task ReadEpub2NcxAsync(IZipFile epubFile, string contentDirectoryPath, EpubPackage package) @@ -27,25 +30,68 @@ public Epub2NcxReader(EpubReaderOptions? epubReaderOptions = null) { return null; } - EpubManifestItem tocManifestItem = package.Manifest.Items.Find(item => item.Id.CompareOrdinalIgnoreCase(tocId)) ?? + EpubManifestItem? tocManifestItem = package.Manifest.Items.Find(item => item.Id.CompareOrdinalIgnoreCase(tocId)); + if (tocManifestItem == null) + { + if (epub2NcxReaderOptions.IgnoreMissingTocManifestItemError) + { + return null; + } throw new Epub2NcxException($"EPUB parsing error: TOC item {tocId} not found in EPUB manifest."); + } string tocFileEntryPath = ContentPathUtils.Combine(contentDirectoryPath, tocManifestItem.Href); - IZipFileEntry? tocFileEntry = epubFile.GetEntry(tocFileEntryPath) ?? + IZipFileEntry? tocFileEntry = epubFile.GetEntry(tocFileEntryPath); + if (tocFileEntry == null) + { + if (epub2NcxReaderOptions.IgnoreMissingTocFileError) + { + return null; + } throw new Epub2NcxException($"EPUB parsing error: TOC file {tocFileEntryPath} not found in the EPUB file."); + } if (tocFileEntry.Length > Int32.MaxValue) { + if (epub2NcxReaderOptions.IgnoreTocFileIsTooLargeError) + { + return null; + } throw new Epub2NcxException($"EPUB parsing error: TOC file {tocFileEntryPath} is larger than 2 GB."); } XDocument containerDocument; - using (Stream containerStream = tocFileEntry.Open()) + try { + using Stream containerStream = tocFileEntry.Open(); containerDocument = await XmlUtils.LoadDocumentAsync(containerStream, epubReaderOptions.XmlReaderOptions).ConfigureAwait(false); } + catch (XmlException xmlException) + { + if (epub2NcxReaderOptions.IgnoreTocFileIsNotValidXmlError) + { + return null; + } + throw new Epub2NcxException("EPUB parsing error: TOC file is not a valid XML file.", xmlException); + } XNamespace ncxNamespace = "http://www.daisy.org/z3986/2005/ncx/"; - XElement ncxNode = containerDocument.Element(ncxNamespace + "ncx") ?? throw new Epub2NcxException("EPUB parsing error: TOC file does not contain ncx element."); - XElement headNode = ncxNode.Element(ncxNamespace + "head") ?? throw new Epub2NcxException("EPUB parsing error: TOC file does not contain head element."); - Epub2NcxHead navigationHead = ReadNavigationHead(headNode); - XElement docTitleNode = ncxNode.Element(ncxNamespace + "docTitle") ?? throw new Epub2NcxException("EPUB parsing error: TOC file does not contain docTitle element."); + XElement? ncxNode = containerDocument.Element(ncxNamespace + "ncx"); + if (ncxNode == null) + { + if (epub2NcxReaderOptions.IgnoreMissingNcxElementError) + { + return null; + } + throw new Epub2NcxException("EPUB parsing error: TOC file does not contain ncx element."); + } + XElement? headNode = ncxNode.Element(ncxNamespace + "head"); + if (headNode == null && !epub2NcxReaderOptions.IgnoreMissingHeadElementError) + { + throw new Epub2NcxException("EPUB parsing error: TOC file does not contain head element."); + } + Epub2NcxHead navigationHead = ReadNavigationHead(headNode, epub2NcxReaderOptions); + XElement? docTitleNode = ncxNode.Element(ncxNamespace + "docTitle"); + if (docTitleNode == null && !epub2NcxReaderOptions.IgnoreMissingDocTitleElementError) + { + throw new Epub2NcxException("EPUB parsing error: TOC file does not contain docTitle element."); + } string? docTitle = ReadNavigationDocTitle(docTitleNode); List docAuthors = new(); foreach (XElement docAuthorNode in ncxNode.Elements(ncxNamespace + "docAuthor")) @@ -56,70 +102,88 @@ public Epub2NcxReader(EpubReaderOptions? epubReaderOptions = null) docAuthors.Add(navigationDocAuthor); } } - XElement navMapNode = ncxNode.Element(ncxNamespace + "navMap") ?? throw new Epub2NcxException("EPUB parsing error: TOC file does not contain navMap element."); - Epub2NcxNavigationMap navMap = ReadNavigationMap(navMapNode, epubReaderOptions.Epub2NcxReaderOptions); - XElement pageListNode = ncxNode.Element(ncxNamespace + "pageList"); + XElement? navMapNode = ncxNode.Element(ncxNamespace + "navMap"); + if (navMapNode == null && !epub2NcxReaderOptions.IgnoreMissingNavMapElementError) + { + throw new Epub2NcxException("EPUB parsing error: TOC file does not contain navMap element."); + } + Epub2NcxNavigationMap navMap = ReadNavigationMap(navMapNode, epub2NcxReaderOptions); + XElement? pageListNode = ncxNode.Element(ncxNamespace + "pageList"); Epub2NcxPageList? pageList = null; if (pageListNode != null) { - pageList = ReadNavigationPageList(pageListNode); + pageList = ReadNavigationPageList(pageListNode, epub2NcxReaderOptions); } List navLists = new(); foreach (XElement navigationListNode in ncxNode.Elements(ncxNamespace + "navList")) { - Epub2NcxNavigationList navigationList = ReadNavigationList(navigationListNode); + Epub2NcxNavigationList navigationList = ReadNavigationList(navigationListNode, epub2NcxReaderOptions); navLists.Add(navigationList); } return new(tocFileEntryPath, navigationHead, docTitle, docAuthors, navMap, pageList, navLists); } - private static Epub2NcxHead ReadNavigationHead(XElement headNode) + private static Epub2NcxHead ReadNavigationHead(XElement? headNode, Epub2NcxReaderOptions epub2NcxReaderOptions) { List items = new(); - foreach (XElement metaNode in headNode.Elements()) + if (headNode != null) { - if (metaNode.CompareNameTo("meta")) + foreach (XElement metaNode in headNode.Elements()) { - string? name = null; - string? content = null; - string? scheme = null; - foreach (XAttribute metaNodeAttribute in metaNode.Attributes()) + if (metaNode.CompareNameTo("meta")) { - string attributeValue = metaNodeAttribute.Value; - switch (metaNodeAttribute.GetLowerCaseLocalName()) + string? name = null; + string? content = null; + string? scheme = null; + foreach (XAttribute metaNodeAttribute in metaNode.Attributes()) { - case "name": - name = attributeValue; - break; - case "content": - content = attributeValue; - break; - case "scheme": - scheme = attributeValue; - break; + string attributeValue = metaNodeAttribute.Value; + switch (metaNodeAttribute.GetLowerCaseLocalName()) + { + case "name": + name = attributeValue; + break; + case "content": + content = attributeValue; + break; + case "scheme": + scheme = attributeValue; + break; + } } + if (name == null) + { + if (epub2NcxReaderOptions.SkipInvalidMetaElements) + { + continue; + } + throw new Epub2NcxException("Incorrect EPUB navigation meta: meta name is missing."); + } + if (content == null) + { + if (epub2NcxReaderOptions.SkipInvalidMetaElements) + { + continue; + } + throw new Epub2NcxException("Incorrect EPUB navigation meta: meta content is missing."); + } + items.Add(new Epub2NcxHeadMeta(name, content, scheme)); } - if (name == null) - { - throw new Epub2NcxException("Incorrect EPUB navigation meta: meta name is missing."); - } - if (content == null) - { - throw new Epub2NcxException("Incorrect EPUB navigation meta: meta content is missing."); - } - items.Add(new Epub2NcxHeadMeta(name, content, scheme)); } } return new(items); } - private static string? ReadNavigationDocTitle(XElement docTitleNode) + private static string? ReadNavigationDocTitle(XElement? docTitleNode) { - foreach (XElement textNode in docTitleNode.Elements()) + if (docTitleNode != null) { - if (textNode.CompareNameTo("text")) + foreach (XElement textNode in docTitleNode.Elements()) { - return textNode.Value; + if (textNode.CompareNameTo("text")) + { + return textNode.Value; + } } } return null; @@ -137,17 +201,20 @@ private static Epub2NcxHead ReadNavigationHead(XElement headNode) return null; } - private static Epub2NcxNavigationMap ReadNavigationMap(XElement navigationMapNode, Epub2NcxReaderOptions epub2NcxReaderOptions) + private static Epub2NcxNavigationMap ReadNavigationMap(XElement? navigationMapNode, Epub2NcxReaderOptions epub2NcxReaderOptions) { List items = new(); - foreach (XElement navigationPointNode in navigationMapNode.Elements()) + if (navigationMapNode != null) { - if (navigationPointNode.CompareNameTo("navPoint")) + foreach (XElement navigationPointNode in navigationMapNode.Elements()) { - Epub2NcxNavigationPoint? navigationPoint = ReadNavigationPoint(navigationPointNode, epub2NcxReaderOptions); - if (navigationPoint != null) + if (navigationPointNode.CompareNameTo("navPoint")) { - items.Add(navigationPoint); + Epub2NcxNavigationPoint? navigationPoint = ReadNavigationPoint(navigationPointNode, epub2NcxReaderOptions); + if (navigationPoint != null) + { + items.Add(navigationPoint); + } } } } @@ -161,6 +228,7 @@ private static Epub2NcxNavigationMap ReadNavigationMap(XElement navigationMapNod string? playOrder = null; List navigationLabels = new(); Epub2NcxContent? content = null; + bool isContentNodePresent = false; List childNavigationPoints = new(); foreach (XAttribute navigationPointNodeAttribute in navigationPointNode.Attributes()) { @@ -180,6 +248,10 @@ private static Epub2NcxNavigationMap ReadNavigationMap(XElement navigationMapNod } if (id == null) { + if (epub2NcxReaderOptions.SkipNavigationPointsWithMissingIds) + { + return null; + } throw new Epub2NcxException("Incorrect EPUB navigation point: point ID is missing."); } foreach (XElement navigationPointChildNode in navigationPointNode.Elements()) @@ -187,11 +259,15 @@ private static Epub2NcxNavigationMap ReadNavigationMap(XElement navigationMapNod switch (navigationPointChildNode.GetLowerCaseLocalName()) { case "navlabel": - Epub2NcxNavigationLabel navigationLabel = ReadNavigationLabel(navigationPointChildNode); - navigationLabels.Add(navigationLabel); + Epub2NcxNavigationLabel? navigationLabel = ReadNavigationLabel(navigationPointChildNode, epub2NcxReaderOptions); + if (navigationLabel != null) + { + navigationLabels.Add(navigationLabel); + } break; case "content": - content = ReadNavigationContent(navigationPointChildNode); + content = ReadNavigationContent(navigationPointChildNode, epub2NcxReaderOptions); + isContentNodePresent = true; break; case "navpoint": Epub2NcxNavigationPoint? childNavigationPoint = ReadNavigationPoint(navigationPointChildNode, epub2NcxReaderOptions); @@ -202,33 +278,41 @@ private static Epub2NcxNavigationMap ReadNavigationMap(XElement navigationMapNod break; } } - if (!navigationLabels.Any()) + if (!navigationLabels.Any() && !epub2NcxReaderOptions.AllowNavigationPointsWithoutLabels) { throw new Epub2NcxException($"EPUB parsing error: navigation point \"{id}\" should contain at least one navigation label."); } if (content == null) { - if (epub2NcxReaderOptions != null && epub2NcxReaderOptions.IgnoreMissingContentForNavigationPoints) + if (isContentNodePresent) { return null; } - else + if (epub2NcxReaderOptions.IgnoreMissingContentForNavigationPoints) { - throw new Epub2NcxException($"EPUB parsing error: navigation point \"{id}\" should contain content."); + return null; } + throw new Epub2NcxException($"EPUB parsing error: navigation point \"{id}\" should contain content."); } return new(id, @class, playOrder, navigationLabels, content, childNavigationPoints); } - private static Epub2NcxNavigationLabel ReadNavigationLabel(XElement navigationLabelNode) + private static Epub2NcxNavigationLabel? ReadNavigationLabel(XElement navigationLabelNode, Epub2NcxReaderOptions epub2NcxReaderOptions) { - XElement navigationLabelTextNode = navigationLabelNode.Element(navigationLabelNode.Name.Namespace + "text") ?? + XElement? navigationLabelTextNode = navigationLabelNode.Element(navigationLabelNode.Name.Namespace + "text"); + if (navigationLabelTextNode == null) + { + if (epub2NcxReaderOptions.SkipInvalidNavigationLabels) + { + return null; + } throw new Epub2NcxException("Incorrect EPUB navigation label: label text element is missing."); + } string text = navigationLabelTextNode.Value; return new(text); } - private static Epub2NcxContent ReadNavigationContent(XElement navigationContentNode) + private static Epub2NcxContent? ReadNavigationContent(XElement navigationContentNode, Epub2NcxReaderOptions epub2NcxReaderOptions) { string? id = null; string? source = null; @@ -247,26 +331,30 @@ private static Epub2NcxContent ReadNavigationContent(XElement navigationContentN } if (source == null) { + if (epub2NcxReaderOptions.SkipInvalidNavigationContent) + { + return null; + } throw new Epub2NcxException("Incorrect EPUB navigation content: content source is missing."); } return new(id, source); } - private static Epub2NcxPageList ReadNavigationPageList(XElement navigationPageListNode) + private static Epub2NcxPageList ReadNavigationPageList(XElement navigationPageListNode, Epub2NcxReaderOptions epub2NcxReaderOptions) { List items = new(); foreach (XElement pageTargetNode in navigationPageListNode.Elements()) { if (pageTargetNode.CompareNameTo("pageTarget")) { - Epub2NcxPageTarget pageTarget = ReadNavigationPageTarget(pageTargetNode); + Epub2NcxPageTarget pageTarget = ReadNavigationPageTarget(pageTargetNode, epub2NcxReaderOptions); items.Add(pageTarget); } } return new(items); } - private static Epub2NcxPageTarget ReadNavigationPageTarget(XElement navigationPageTargetNode) + private static Epub2NcxPageTarget ReadNavigationPageTarget(XElement navigationPageTargetNode, Epub2NcxReaderOptions epub2NcxReaderOptions) { string? id = null; string? value = null; @@ -302,29 +390,39 @@ private static Epub2NcxPageTarget ReadNavigationPageTarget(XElement navigationPa } if (type == default) { - throw new Epub2NcxException("Incorrect EPUB navigation page target: page target type is missing."); + if (epub2NcxReaderOptions.ReplaceMissingPageTargetTypesWithUnknown) + { + type = Epub2NcxPageTargetType.UNKNOWN; + } + else + { + throw new Epub2NcxException("Incorrect EPUB navigation page target: page target type is missing."); + } } foreach (XElement navigationPageTargetChildNode in navigationPageTargetNode.Elements()) { switch (navigationPageTargetChildNode.GetLowerCaseLocalName()) { case "navlabel": - Epub2NcxNavigationLabel navigationLabel = ReadNavigationLabel(navigationPageTargetChildNode); - navigationLabels.Add(navigationLabel); + Epub2NcxNavigationLabel? navigationLabel = ReadNavigationLabel(navigationPageTargetChildNode, epub2NcxReaderOptions); + if (navigationLabel != null) + { + navigationLabels.Add(navigationLabel); + } break; case "content": - content = ReadNavigationContent(navigationPageTargetChildNode); + content = ReadNavigationContent(navigationPageTargetChildNode, epub2NcxReaderOptions); break; } } - if (!navigationLabels.Any()) + if (!navigationLabels.Any() && !epub2NcxReaderOptions.AllowNavigationPageTargetsWithoutLabels) { throw new Epub2NcxException("Incorrect EPUB navigation page target: at least one navLabel element is required."); } return new(id, value, type, @class, playOrder, navigationLabels, content); } - private static Epub2NcxNavigationList ReadNavigationList(XElement navigationListNode) + private static Epub2NcxNavigationList ReadNavigationList(XElement navigationListNode, Epub2NcxReaderOptions epub2NcxReaderOptions) { string? id = null; string? @class = null; @@ -348,23 +446,29 @@ private static Epub2NcxNavigationList ReadNavigationList(XElement navigationList switch (navigationListChildNode.GetLowerCaseLocalName()) { case "navlabel": - Epub2NcxNavigationLabel navigationLabel = ReadNavigationLabel(navigationListChildNode); - navigationLabels.Add(navigationLabel); + Epub2NcxNavigationLabel? navigationLabel = ReadNavigationLabel(navigationListChildNode, epub2NcxReaderOptions); + if (navigationLabel != null) + { + navigationLabels.Add(navigationLabel); + } break; case "navtarget": - Epub2NcxNavigationTarget navigationTarget = ReadNavigationTarget(navigationListChildNode); - navigationTargets.Add(navigationTarget); + Epub2NcxNavigationTarget? navigationTarget = ReadNavigationTarget(navigationListChildNode, epub2NcxReaderOptions); + if (navigationTarget != null) + { + navigationTargets.Add(navigationTarget); + } break; } } - if (!navigationLabels.Any()) + if (!navigationLabels.Any() && !epub2NcxReaderOptions.AllowNavigationListsWithoutLabels) { - throw new Epub2NcxException("Incorrect EPUB navigation page target: at least one navLabel element is required."); + throw new Epub2NcxException("Incorrect EPUB navigation list: at least one navLabel element is required."); } return new(id, @class, navigationLabels, navigationTargets); } - private static Epub2NcxNavigationTarget ReadNavigationTarget(XElement navigationTargetNode) + private static Epub2NcxNavigationTarget? ReadNavigationTarget(XElement navigationTargetNode, Epub2NcxReaderOptions epub2NcxReaderOptions) { string? id = null; string? @class = null; @@ -393,6 +497,10 @@ private static Epub2NcxNavigationTarget ReadNavigationTarget(XElement navigation } if (id == null) { + if (epub2NcxReaderOptions.SkipInvalidNavigationTargets) + { + return null; + } throw new Epub2NcxException("Incorrect EPUB navigation target: navigation target ID is missing."); } foreach (XElement navigationTargetChildNode in navigationTargetNode.Elements()) @@ -400,15 +508,18 @@ private static Epub2NcxNavigationTarget ReadNavigationTarget(XElement navigation switch (navigationTargetChildNode.GetLowerCaseLocalName()) { case "navlabel": - Epub2NcxNavigationLabel navigationLabel = ReadNavigationLabel(navigationTargetChildNode); - navigationLabels.Add(navigationLabel); + Epub2NcxNavigationLabel? navigationLabel = ReadNavigationLabel(navigationTargetChildNode, epub2NcxReaderOptions); + if (navigationLabel != null) + { + navigationLabels.Add(navigationLabel); + } break; case "content": - content = ReadNavigationContent(navigationTargetChildNode); + content = ReadNavigationContent(navigationTargetChildNode, epub2NcxReaderOptions); break; } } - if (!navigationLabels.Any()) + if (!navigationLabels.Any() && !epub2NcxReaderOptions.AllowNavigationTargetsWithoutLabels) { throw new Epub2NcxException("Incorrect EPUB navigation target: at least one navLabel element is required."); } diff --git a/Source/VersOne.Epub/Readers/Epub3NavDocumentReader.cs b/Source/VersOne.Epub/Readers/Epub3NavDocumentReader.cs index 1f7bdf7..f9af2b0 100644 --- a/Source/VersOne.Epub/Readers/Epub3NavDocumentReader.cs +++ b/Source/VersOne.Epub/Readers/Epub3NavDocumentReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; +using System.Xml; using System.Xml.Linq; using VersOne.Epub.Environment; using VersOne.Epub.Options; @@ -13,15 +14,18 @@ namespace VersOne.Epub.Internal internal class Epub3NavDocumentReader { private readonly EpubReaderOptions epubReaderOptions; + private readonly Epub3NavDocumentReaderOptions epub3NavDocumentReaderOptions; public Epub3NavDocumentReader(EpubReaderOptions? epubReaderOptions = null) { this.epubReaderOptions = epubReaderOptions ?? new EpubReaderOptions(); + epub3NavDocumentReaderOptions = this.epubReaderOptions.Epub3NavDocumentReaderOptions ?? new Epub3NavDocumentReaderOptions(); } public async Task ReadEpub3NavDocumentAsync(IZipFile epubFile, string contentDirectoryPath, EpubPackage package) { - EpubManifestItem navManifestItem = package.Manifest.Items.Find(item => item.Properties != null && item.Properties.Contains(EpubManifestProperty.NAV)); + EpubManifestItem? navManifestItem = + package.Manifest.Items.Find(item => item.Properties != null && item.Properties.Contains(EpubManifestProperty.NAV)); if (navManifestItem == null) { if (package.EpubVersion == EpubVersion.EPUB_2) @@ -30,30 +34,122 @@ public Epub3NavDocumentReader(EpubReaderOptions? epubReaderOptions = null) } else { + if (epub3NavDocumentReaderOptions.IgnoreMissingNavManifestItemError) + { + return null; + } throw new Epub3NavException("EPUB parsing error: NAV item not found in EPUB manifest."); } } string navFileEntryPath = ContentPathUtils.Combine(contentDirectoryPath, navManifestItem.Href); - IZipFileEntry navFileEntry = epubFile.GetEntry(navFileEntryPath) ?? + IZipFileEntry? navFileEntry = epubFile.GetEntry(navFileEntryPath); + if (navFileEntry == null) + { + if (epub3NavDocumentReaderOptions.IgnoreMissingNavFileError) + { + return null; + } throw new Epub3NavException($"EPUB parsing error: navigation file {navFileEntryPath} not found in the EPUB file."); + } if (navFileEntry.Length > Int32.MaxValue) { + if (epub3NavDocumentReaderOptions.IgnoreNavFileIsTooLargeError) + { + return null; + } throw new Epub3NavException($"EPUB parsing error: navigation file {navFileEntryPath} is larger than 2 GB."); } XDocument navDocument; - using (Stream containerStream = navFileEntry.Open()) + try { + using Stream containerStream = navFileEntry.Open(); navDocument = await XmlUtils.LoadDocumentAsync(containerStream, epubReaderOptions.XmlReaderOptions).ConfigureAwait(false); } - XNamespace xhtmlNamespace = navDocument.Root.Name.Namespace; - XElement htmlNode = navDocument.Element(xhtmlNamespace + "html") ?? throw new Epub3NavException("EPUB parsing error: navigation file does not contain html element."); - XElement bodyNode = htmlNode.Element(xhtmlNamespace + "body") ?? throw new Epub3NavException("EPUB parsing error: navigation file does not contain body element."); + catch (XmlException xmlException) + { + if (epub3NavDocumentReaderOptions.IgnoreNavFileIsNotValidXmlError) + { + return null; + } + throw new Epub3NavException("EPUB parsing error: navigation file is not a valid XHTML file.", xmlException); + } + XNamespace xhtmlNamespace = navDocument.Root!.Name.Namespace; + XElement? htmlNode = navDocument.Element(xhtmlNamespace + "html"); + if (htmlNode == null) + { + if (epub3NavDocumentReaderOptions.IgnoreMissingHtmlElementError) + { + return null; + } + throw new Epub3NavException("EPUB parsing error: navigation file does not contain html element."); + } + XElement? bodyNode = htmlNode.Element(xhtmlNamespace + "body"); + if (bodyNode == null) + { + if (epub3NavDocumentReaderOptions.IgnoreMissingBodyElementError) + { + return null; + } + throw new Epub3NavException("EPUB parsing error: navigation file does not contain body element."); + } List navs = new(); ReadEpub3NavsWithinContainerElement(bodyNode, navs); return new(navFileEntryPath, navs); } - private static void ReadEpub3NavsWithinContainerElement(XElement containerElement, List resultNavs) + private static Epub3NavAnchor ReadEpub3NavAnchor(XElement epub3NavAnchorNode) + { + string? href = null; + string text; + string? title = null; + string? alt = null; + Epub3StructuralSemanticsProperty? type = null; + foreach (XAttribute navAnchorNodeAttribute in epub3NavAnchorNode.Attributes()) + { + string attributeValue = navAnchorNodeAttribute.Value; + switch (navAnchorNodeAttribute.GetLowerCaseLocalName()) + { + case "href": + href = Uri.UnescapeDataString(attributeValue); + break; + case "title": + title = attributeValue; + break; + case "alt": + alt = attributeValue; + break; + case "type": + type = Epub3StructuralSemanticsPropertyParser.ParseProperty(attributeValue); + break; + } + } + text = epub3NavAnchorNode.Value.Trim(); + return new(href, text, title, alt, type); + } + + private static Epub3NavSpan ReadEpub3NavSpan(XElement epub3NavSpanNode) + { + string text; + string? title = null; + string? alt = null; + foreach (XAttribute navSpanNodeAttribute in epub3NavSpanNode.Attributes()) + { + string attributeValue = navSpanNodeAttribute.Value; + switch (navSpanNodeAttribute.GetLowerCaseLocalName()) + { + case "title": + title = attributeValue; + break; + case "alt": + alt = attributeValue; + break; + } + } + text = epub3NavSpanNode.Value.Trim(); + return new(text, title, alt); + } + + private void ReadEpub3NavsWithinContainerElement(XElement containerElement, List resultNavs) { foreach (XElement childElement in containerElement.Elements()) { @@ -72,7 +168,7 @@ private static void ReadEpub3NavsWithinContainerElement(XElement containerElemen } } - private static Epub3Nav? ReadEpub3Nav(XElement navNode) + private Epub3Nav? ReadEpub3Nav(XElement navNode) { Epub3StructuralSemanticsProperty? type = null; bool isHidden = false; @@ -114,12 +210,16 @@ private static void ReadEpub3NavsWithinContainerElement(XElement containerElemen } if (ol == null) { + if (epub3NavDocumentReaderOptions.SkipNavsWithMissingOlElements) + { + return null; + } throw new Epub3NavException("EPUB parsing error: 'nav' element in the navigation file does not contain a required 'ol' element."); } return new(type, isHidden, head, ol); } - private static Epub3NavOl ReadEpub3NavOl(XElement epub3NavOlNode) + private Epub3NavOl ReadEpub3NavOl(XElement epub3NavOlNode) { bool isHidden = false; List lis = new(); @@ -137,15 +237,18 @@ private static Epub3NavOl ReadEpub3NavOl(XElement epub3NavOlNode) switch (navOlChildNode.GetLowerCaseLocalName()) { case "li": - Epub3NavLi epub3NavLi = ReadEpub3NavLi(navOlChildNode); - lis.Add(epub3NavLi); + Epub3NavLi? epub3NavLi = ReadEpub3NavLi(navOlChildNode); + if (epub3NavLi != null) + { + lis.Add(epub3NavLi); + } break; } } return new(isHidden, lis); } - private static Epub3NavLi ReadEpub3NavLi(XElement epub3NavLiNode) + private Epub3NavLi? ReadEpub3NavLi(XElement epub3NavLiNode) { Epub3NavAnchor? anchor = null; Epub3NavSpan? span = null; @@ -170,61 +273,13 @@ private static Epub3NavLi ReadEpub3NavLi(XElement epub3NavLiNode) } if (anchor == null && span == null) { - throw new Epub3NavException("EPUB parsing error: 'li' element in the navigation file must contain either an 'a' element or a 'span' element."); - } - return new(anchor, span, childOl); - } - - private static Epub3NavAnchor ReadEpub3NavAnchor(XElement epub3NavAnchorNode) - { - string? href = null; - string text; - string? title = null; - string? alt = null; - Epub3StructuralSemanticsProperty? type = null; - foreach (XAttribute navAnchorNodeAttribute in epub3NavAnchorNode.Attributes()) - { - string attributeValue = navAnchorNodeAttribute.Value; - switch (navAnchorNodeAttribute.GetLowerCaseLocalName()) + if (epub3NavDocumentReaderOptions.SkipInvalidLiElements) { - case "href": - href = Uri.UnescapeDataString(attributeValue); - break; - case "title": - title = attributeValue; - break; - case "alt": - alt = attributeValue; - break; - case "type": - type = Epub3StructuralSemanticsPropertyParser.ParseProperty(attributeValue); - break; - } - } - text = epub3NavAnchorNode.Value.Trim(); - return new(href, text, title, alt, type); - } - - private static Epub3NavSpan ReadEpub3NavSpan(XElement epub3NavSpanNode) - { - string text; - string? title = null; - string? alt = null; - foreach (XAttribute navSpanNodeAttribute in epub3NavSpanNode.Attributes()) - { - string attributeValue = navSpanNodeAttribute.Value; - switch (navSpanNodeAttribute.GetLowerCaseLocalName()) - { - case "title": - title = attributeValue; - break; - case "alt": - alt = attributeValue; - break; + return null; } + throw new Epub3NavException("EPUB parsing error: 'li' element in the navigation file must contain either an 'a' element or a 'span' element."); } - text = epub3NavSpanNode.Value.Trim(); - return new(text, title, alt); + return new(anchor, span, childOl); } } } diff --git a/Source/VersOne.Epub/Readers/MetadataReader.cs b/Source/VersOne.Epub/Readers/MetadataReader.cs index 2747607..4f0a8ef 100644 --- a/Source/VersOne.Epub/Readers/MetadataReader.cs +++ b/Source/VersOne.Epub/Readers/MetadataReader.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Xml.Linq; +using VersOne.Epub.Options; using VersOne.Epub.Schema; using VersOne.Epub.Utils; @@ -7,7 +8,7 @@ namespace VersOne.Epub.Internal { internal static class MetadataReader { - public static EpubMetadata ReadMetadata(XElement metadataNode) + public static EpubMetadata ReadMetadata(XElement? metadataNode, MetadataReaderOptions metadataReaderOptions) { List titles = new(); List creators = new(); @@ -26,85 +27,91 @@ public static EpubMetadata ReadMetadata(XElement metadataNode) List rights = new(); List links = new(); List metaItems = new(); - foreach (XElement metadataItemNode in metadataNode.Elements()) + if (metadataNode != null) { - switch (metadataItemNode.GetLowerCaseLocalName()) + foreach (XElement metadataItemNode in metadataNode.Elements()) { - case "title": - EpubMetadataTitle title = ReadTitle(metadataItemNode); - titles.Add(title); - break; - case "creator": - EpubMetadataCreator creator = ReadCreator(metadataItemNode); - creators.Add(creator); - break; - case "subject": - EpubMetadataSubject subject = ReadSubject(metadataItemNode); - subjects.Add(subject); - break; - case "description": - EpubMetadataDescription description = ReadDescription(metadataItemNode); - descriptions.Add(description); - break; - case "publisher": - EpubMetadataPublisher publisher = ReadPublisher(metadataItemNode); - publishers.Add(publisher); - break; - case "contributor": - EpubMetadataContributor contributor = ReadContributor(metadataItemNode); - contributors.Add(contributor); - break; - case "date": - EpubMetadataDate date = ReadDate(metadataItemNode); - dates.Add(date); - break; - case "type": - EpubMetadataType type = ReadType(metadataItemNode); - types.Add(type); - break; - case "format": - EpubMetadataFormat format = ReadFormat(metadataItemNode); - formats.Add(format); - break; - case "identifier": - EpubMetadataIdentifier identifier = ReadIdentifier(metadataItemNode); - identifiers.Add(identifier); - break; - case "source": - EpubMetadataSource source = ReadSource(metadataItemNode); - sources.Add(source); - break; - case "language": - EpubMetadataLanguage language = ReadLanguage(metadataItemNode); - languages.Add(language); - break; - case "relation": - EpubMetadataRelation relation = ReadRelation(metadataItemNode); - relations.Add(relation); - break; - case "coverage": - EpubMetadataCoverage coverage = ReadCoverage(metadataItemNode); - coverages.Add(coverage); - break; - case "rights": - EpubMetadataRights rightsItem = ReadRightsItem(metadataItemNode); - rights.Add(rightsItem); - break; - case "link": - EpubMetadataLink link = ReadLink(metadataItemNode); - links.Add(link); - break; - case "meta": - EpubMetadataMeta meta = ReadMeta(metadataItemNode); - metaItems.Add(meta); - break; + switch (metadataItemNode.GetLowerCaseLocalName()) + { + case "title": + EpubMetadataTitle title = ReadTitle(metadataItemNode); + titles.Add(title); + break; + case "creator": + EpubMetadataCreator creator = ReadCreator(metadataItemNode); + creators.Add(creator); + break; + case "subject": + EpubMetadataSubject subject = ReadSubject(metadataItemNode); + subjects.Add(subject); + break; + case "description": + EpubMetadataDescription description = ReadDescription(metadataItemNode); + descriptions.Add(description); + break; + case "publisher": + EpubMetadataPublisher publisher = ReadPublisher(metadataItemNode); + publishers.Add(publisher); + break; + case "contributor": + EpubMetadataContributor contributor = ReadContributor(metadataItemNode); + contributors.Add(contributor); + break; + case "date": + EpubMetadataDate date = ReadDate(metadataItemNode); + dates.Add(date); + break; + case "type": + EpubMetadataType type = ReadType(metadataItemNode); + types.Add(type); + break; + case "format": + EpubMetadataFormat format = ReadFormat(metadataItemNode); + formats.Add(format); + break; + case "identifier": + EpubMetadataIdentifier identifier = ReadIdentifier(metadataItemNode); + identifiers.Add(identifier); + break; + case "source": + EpubMetadataSource source = ReadSource(metadataItemNode); + sources.Add(source); + break; + case "language": + EpubMetadataLanguage language = ReadLanguage(metadataItemNode); + languages.Add(language); + break; + case "relation": + EpubMetadataRelation relation = ReadRelation(metadataItemNode); + relations.Add(relation); + break; + case "coverage": + EpubMetadataCoverage coverage = ReadCoverage(metadataItemNode); + coverages.Add(coverage); + break; + case "rights": + EpubMetadataRights rightsItem = ReadRightsItem(metadataItemNode); + rights.Add(rightsItem); + break; + case "link": + EpubMetadataLink? link = ReadLink(metadataItemNode, metadataReaderOptions); + if (link != null) + { + links.Add(link); + } + break; + case "meta": + EpubMetadataMeta meta = ReadMeta(metadataItemNode); + metaItems.Add(meta); + break; + } } } return new(titles, creators, subjects, descriptions, publishers, contributors, dates, types, formats, identifiers, sources, languages, relations, coverages, rights, links, metaItems); } - public static EpubMetadataLink ReadLink(XElement linkNode) + public static EpubMetadataLink? ReadLink(XElement linkNode, MetadataReaderOptions metadataReaderOptions) { string? href = null; string? id = null; @@ -143,9 +150,13 @@ public static EpubMetadataLink ReadLink(XElement linkNode) } if (href == null) { + if (metadataReaderOptions.SkipLinksWithoutHrefs) + { + return null; + } throw new EpubPackageException("Incorrect EPUB metadata link: href is missing."); } - if (relationships == null) + if (relationships == null && !metadataReaderOptions.IgnoreLinkWithoutRelError) { throw new EpubPackageException("Incorrect EPUB metadata link: rel is missing."); } @@ -374,7 +385,7 @@ private static EpubMetadataMeta ReadMeta(XElement metaNode) private static void ReadMetadataItemWithIdAndContent(XElement metadataItemNode, out string? id, out string content) { - XAttribute idAttribute = metadataItemNode.Attribute("id"); + XAttribute? idAttribute = metadataItemNode.Attribute("id"); id = idAttribute?.Value; content = metadataItemNode.Value; } diff --git a/Source/VersOne.Epub/Readers/NavigationReader.cs b/Source/VersOne.Epub/Readers/NavigationReader.cs index 09f187b..34ad72f 100644 --- a/Source/VersOne.Epub/Readers/NavigationReader.cs +++ b/Source/VersOne.Epub/Readers/NavigationReader.cs @@ -1,37 +1,43 @@ using System; using System.Collections.Generic; using System.Linq; +using VersOne.Epub.Options; using VersOne.Epub.Schema; namespace VersOne.Epub.Internal { internal static class NavigationReader { - public static List? GetNavigationItems(EpubSchema epubSchema, EpubContentRef epubContentRef) + public static List? GetNavigationItems(EpubSchema epubSchema, EpubContentRef epubContentRef, + NavigationReaderOptions navigationReaderOptions) { if (epubSchema.Package.EpubVersion == EpubVersion.EPUB_2) { - return epubSchema.Epub2Ncx != null ? GetNavigationItems(epubSchema, epubContentRef, epubSchema.Epub2Ncx) : null; + return epubSchema.Epub2Ncx != null ? GetNavigationItems(epubSchema, epubContentRef, epubSchema.Epub2Ncx, navigationReaderOptions) : null; } else { - return epubSchema.Epub3NavDocument != null ? GetNavigationItems(epubSchema, epubContentRef, epubSchema.Epub3NavDocument) : null; + return epubSchema.Epub3NavDocument != null ? + GetNavigationItems(epubSchema, epubContentRef, epubSchema.Epub3NavDocument, navigationReaderOptions) : null; } } - public static List GetNavigationItems(EpubSchema epubSchema, EpubContentRef epubContentRef, Epub2Ncx epub2Ncx) + public static List GetNavigationItems(EpubSchema epubSchema, EpubContentRef epubContentRef, Epub2Ncx epub2Ncx, + NavigationReaderOptions navigationReaderOptions) { - return GetNavigationItems(epubSchema, epubContentRef, epub2Ncx.NavMap.Items, ContentPathUtils.GetDirectoryPath(epub2Ncx.FilePath)); + return GetNavigationItems(epubSchema, epubContentRef, epub2Ncx.NavMap.Items, ContentPathUtils.GetDirectoryPath(epub2Ncx.FilePath), + navigationReaderOptions); } - public static List GetNavigationItems(EpubSchema epubSchema, EpubContentRef epubContentRef, Epub3NavDocument epub3NavDocument) + public static List GetNavigationItems(EpubSchema epubSchema, EpubContentRef epubContentRef, Epub3NavDocument epub3NavDocument, + NavigationReaderOptions navigationReaderOptions) { return GetNavigationItems(epubSchema, epubContentRef, epub3NavDocument.Navs.Find(nav => nav.Type == Epub3StructuralSemanticsProperty.TOC), - ContentPathUtils.GetDirectoryPath(epub3NavDocument.FilePath)); + ContentPathUtils.GetDirectoryPath(epub3NavDocument.FilePath), navigationReaderOptions); } private static List GetNavigationItems(EpubSchema epubSchema, EpubContentRef epubContentRef, - List navigationPoints, string epub2NcxBaseDirectoryPath) + List navigationPoints, string epub2NcxBaseDirectoryPath, NavigationReaderOptions navigationReaderOptions) { List result = new(); if (navigationPoints != null) @@ -39,27 +45,52 @@ private static List GetNavigationItems(EpubSchema epubSch foreach (Epub2NcxNavigationPoint navigationPoint in navigationPoints) { EpubNavigationItemType type = EpubNavigationItemType.LINK; - Epub2NcxNavigationLabel? firstNavigationLabel = navigationPoint.NavigationLabels.FirstOrDefault() ?? - throw new Epub2NcxException($"Incorrect EPUB 2 NCX: navigation point \"{navigationPoint.Id}\" should contain at least one navigation label."); - string title = firstNavigationLabel.Text; + Epub2NcxNavigationLabel? firstNavigationLabel = navigationPoint.NavigationLabels.FirstOrDefault(); + string title; + if (firstNavigationLabel != null) + { + title = firstNavigationLabel.Text; + } + else + { + if (navigationReaderOptions.AllowEpub2NavigationItemsWithEmptyTitles) + { + title = String.Empty; + } + else + { + throw new Epub2NcxException($"Incorrect EPUB 2 NCX: navigation point \"{navigationPoint.Id}\" should contain at least one navigation label."); + } + } string source = navigationPoint.Content.Source; if (!ContentPathUtils.IsLocalPath(source)) { + if (navigationReaderOptions.SkipRemoteNavigationItems) + { + continue; + } throw new Epub2NcxException($"Incorrect EPUB 2 NCX: content source \"{source}\" cannot be a remote resource."); } EpubNavigationItemLink link = new(source, epub2NcxBaseDirectoryPath); - EpubLocalTextContentFileRef? htmlContentFileRef = GetLocalHtmlContentFileRef(epubContentRef, link.ContentFilePath) ?? + EpubLocalTextContentFileRef? htmlContentFileRef = GetLocalHtmlContentFileRef(epubContentRef, link.ContentFilePath); + if (htmlContentFileRef == null) + { + if (navigationReaderOptions.SkipNavigationItemsReferencingMissingContent) + { + continue; + } throw new Epub2NcxException($"Incorrect EPUB 2 NCX: content source \"{source}\" not found in EPUB manifest."); + } List nestedItems = GetNavigationItems(epubSchema, epubContentRef, navigationPoint.ChildNavigationPoints, - epub2NcxBaseDirectoryPath); + epub2NcxBaseDirectoryPath, navigationReaderOptions); result.Add(new EpubNavigationItemRef(type, title, link, htmlContentFileRef, nestedItems)); } } return result; } - private static List GetNavigationItems(EpubSchema epubSchema, EpubContentRef epubContentRef, Epub3Nav epub3Nav, - string epub3NavigationBaseDirectoryPath) + private static List GetNavigationItems(EpubSchema epubSchema, EpubContentRef epubContentRef, Epub3Nav? epub3Nav, + string epub3NavigationBaseDirectoryPath, NavigationReaderOptions navigationReaderOptions) { List result; if (epub3Nav != null) @@ -69,12 +100,13 @@ private static List GetNavigationItems(EpubSchema epubSch result = new List(); EpubNavigationItemType type = EpubNavigationItemType.HEADER; string title = epub3Nav.Head; - List nestedItems = GetNavigationItems(epubSchema, epubContentRef, epub3Nav.Ol, epub3NavigationBaseDirectoryPath); + List nestedItems = + GetNavigationItems(epubSchema, epubContentRef, epub3Nav.Ol, epub3NavigationBaseDirectoryPath, navigationReaderOptions); result.Add(new EpubNavigationItemRef(type, title, null, null, nestedItems)); } else { - result = GetNavigationItems(epubSchema, epubContentRef, epub3Nav.Ol, epub3NavigationBaseDirectoryPath); + result = GetNavigationItems(epubSchema, epubContentRef, epub3Nav.Ol, epub3NavigationBaseDirectoryPath, navigationReaderOptions); } } else @@ -85,7 +117,7 @@ private static List GetNavigationItems(EpubSchema epubSch } private static List GetNavigationItems(EpubSchema epubSchema, EpubContentRef epubContentRef, - Epub3NavOl? epub3NavOl, string epub3NavigationBaseDirectoryPath) + Epub3NavOl? epub3NavOl, string epub3NavigationBaseDirectoryPath, NavigationReaderOptions navigationReaderOptions) { List result = new(); if (epub3NavOl != null) @@ -99,12 +131,17 @@ private static List GetNavigationItems(EpubSchema epubSch string title = GetFirstNonEmptyHeader(navAnchor.Text, navAnchor.Title, navAnchor.Alt); EpubNavigationItemLink? link = null; EpubLocalTextContentFileRef? htmlContentFileRef = null; - List nestedItems = GetNavigationItems(epubSchema, epubContentRef, epub3NavLi.ChildOl, epub3NavigationBaseDirectoryPath); + List nestedItems = + GetNavigationItems(epubSchema, epubContentRef, epub3NavLi.ChildOl, epub3NavigationBaseDirectoryPath, navigationReaderOptions); if (navAnchor.Href != null) { string href = navAnchor.Href; if (!ContentPathUtils.IsLocalPath(href)) { + if (navigationReaderOptions.SkipRemoteNavigationItems) + { + continue; + } throw new Epub3NavException($"Incorrect EPUB 3 navigation document: anchor href \"{href}\" cannot be a remote resource."); } type = EpubNavigationItemType.LINK; @@ -112,6 +149,10 @@ private static List GetNavigationItems(EpubSchema epubSch htmlContentFileRef = GetLocalHtmlContentFileRef(epubContentRef, link.ContentFilePath); if (htmlContentFileRef == null) { + if (navigationReaderOptions.SkipNavigationItemsReferencingMissingContent) + { + continue; + } throw new Epub3NavException($"Incorrect EPUB 3 navigation document: target for anchor href \"{href}\" not found in EPUB manifest."); } } @@ -126,7 +167,8 @@ private static List GetNavigationItems(EpubSchema epubSch Epub3NavSpan navSpan = epub3NavLi.Span; EpubNavigationItemType type = EpubNavigationItemType.HEADER; string title = GetFirstNonEmptyHeader(navSpan.Text, navSpan.Title, navSpan.Alt); - List nestedItems = GetNavigationItems(epubSchema, epubContentRef, epub3NavLi.ChildOl, epub3NavigationBaseDirectoryPath); + List nestedItems = + GetNavigationItems(epubSchema, epubContentRef, epub3NavLi.ChildOl, epub3NavigationBaseDirectoryPath, navigationReaderOptions); result.Add(new EpubNavigationItemRef(type, title, null, null, nestedItems)); } } @@ -136,7 +178,8 @@ private static List GetNavigationItems(EpubSchema epubSch private static EpubLocalTextContentFileRef? GetLocalHtmlContentFileRef(EpubContentRef epubContentRef, string localContentFilePath) { - if (!epubContentRef.Html.TryGetLocalFileRefByFilePath(localContentFilePath, out EpubLocalTextContentFileRef htmlContentFileRef)) + if (!epubContentRef.Html.TryGetLocalFileRefByFilePath(localContentFilePath, out EpubLocalTextContentFileRef? htmlContentFileRef) || + htmlContentFileRef == null) { return null; } diff --git a/Source/VersOne.Epub/Readers/PackageReader.cs b/Source/VersOne.Epub/Readers/PackageReader.cs index 37cddf3..7411845 100644 --- a/Source/VersOne.Epub/Readers/PackageReader.cs +++ b/Source/VersOne.Epub/Readers/PackageReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; +using System.Xml; using System.Xml.Linq; using VersOne.Epub.Environment; using VersOne.Epub.Options; @@ -13,23 +14,49 @@ namespace VersOne.Epub.Internal internal class PackageReader { private readonly EpubReaderOptions epubReaderOptions; + private readonly PackageReaderOptions packageReaderOptions; public PackageReader(EpubReaderOptions? epubReaderOptions = null) { this.epubReaderOptions = epubReaderOptions ?? new EpubReaderOptions(); + packageReaderOptions = this.epubReaderOptions.PackageReaderOptions ?? new PackageReaderOptions(); } - public async Task ReadPackageAsync(IZipFile epubFile, string rootFilePath) + public async Task ReadPackageAsync(IZipFile epubFile, string packageFilePath) { - IZipFileEntry? rootFileEntry = epubFile.GetEntry(rootFilePath) ?? throw new EpubContainerException("EPUB parsing error: root file not found in the EPUB file."); - XDocument containerDocument; - using (Stream containerStream = rootFileEntry.Open()) + IZipFileEntry? packageFileEntry = epubFile.GetEntry(packageFilePath); + if (packageFileEntry == null) { - containerDocument = await XmlUtils.LoadDocumentAsync(containerStream, epubReaderOptions.XmlReaderOptions).ConfigureAwait(false); + if (packageReaderOptions.IgnoreMissingPackageFile) + { + return null; + } + throw new EpubContainerException("EPUB parsing error: OPF package file not found in the EPUB file."); + } + XDocument packageDocument; + try + { + using Stream packageFileStream = packageFileEntry.Open(); + packageDocument = await XmlUtils.LoadDocumentAsync(packageFileStream, epubReaderOptions.XmlReaderOptions).ConfigureAwait(false); + } + catch (XmlException xmlException) + { + if (packageReaderOptions.IgnorePackageFileIsNotValidXmlError) + { + return null; + } + throw new EpubContainerException("EPUB parsing error: package file is not a valid XML file.", xmlException); } XNamespace opfNamespace = "http://www.idpf.org/2007/opf"; - XElement packageNode = containerDocument.Element(opfNamespace + "package") ?? + XElement? packageNode = packageDocument.Element(opfNamespace + "package"); + if (packageNode == null) + { + if (packageReaderOptions.IgnoreMissingPackageNode) + { + return null; + } throw new EpubPackageException("EPUB parsing error: package XML element not found in the package file."); + } string? uniqueIdentifier = null; string? epubVersionString = null; string? id = null; @@ -61,25 +88,42 @@ public async Task ReadPackageAsync(IZipFile epubFile, string rootFi break; } } + EpubVersion epubVersion; if (epubVersionString == null) { - throw new EpubPackageException("EPUB parsing error: EPUB version is not specified in the package."); + epubVersion = packageReaderOptions.FallbackEpubVersion ?? + throw new EpubPackageException("EPUB parsing error: EPUB version is not specified in the package."); + } + else + { + epubVersion = epubVersionString switch + { + "2.0" => EpubVersion.EPUB_2, + "3.0" => EpubVersion.EPUB_3, + "3.1" => EpubVersion.EPUB_3_1, + _ => packageReaderOptions.FallbackEpubVersion ?? throw new EpubPackageException($"Unsupported EPUB version: \"{epubVersionString}\".") + }; + } + XElement? metadataNode = packageNode.Element(opfNamespace + "metadata"); + if (metadataNode == null && !packageReaderOptions.IgnoreMissingMetadataNode) + { + throw new EpubPackageException("EPUB parsing error: metadata not found in the package."); } - EpubVersion epubVersion = epubVersionString switch + EpubMetadata metadata = MetadataReader.ReadMetadata(metadataNode, epubReaderOptions.MetadataReaderOptions); + XElement? manifestNode = packageNode.Element(opfNamespace + "manifest"); + if (manifestNode == null && !packageReaderOptions.IgnoreMissingManifestNode) { - "2.0" => EpubVersion.EPUB_2, - "3.0" => EpubVersion.EPUB_3, - "3.1" => EpubVersion.EPUB_3_1, - _ => throw new EpubPackageException($"Unsupported EPUB version: \"{epubVersionString}\".") - }; - XElement metadataNode = packageNode.Element(opfNamespace + "metadata") ?? throw new EpubPackageException("EPUB parsing error: metadata not found in the package."); - EpubMetadata metadata = MetadataReader.ReadMetadata(metadataNode); - XElement manifestNode = packageNode.Element(opfNamespace + "manifest") ?? throw new EpubPackageException("EPUB parsing error: manifest not found in the package."); - EpubManifest manifest = ReadManifest(manifestNode, epubReaderOptions.PackageReaderOptions); - XElement spineNode = packageNode.Element(opfNamespace + "spine") ?? throw new EpubPackageException("EPUB parsing error: spine not found in the package."); - EpubSpine spine = ReadSpine(spineNode, epubVersion, epubReaderOptions.PackageReaderOptions); + throw new EpubPackageException("EPUB parsing error: manifest not found in the package."); + } + EpubManifest manifest = ReadManifest(manifestNode); + XElement? spineNode = packageNode.Element(opfNamespace + "spine"); + if (spineNode == null && !packageReaderOptions.IgnoreMissingSpineNode) + { + throw new EpubPackageException("EPUB parsing error: spine not found in the package."); + } + EpubSpine spine = ReadSpine(spineNode, epubVersion); EpubGuide? guide = null; - XElement guideNode = packageNode.Element(opfNamespace + "guide"); + XElement? guideNode = packageNode.Element(opfNamespace + "guide"); if (guideNode != null) { guide = ReadGuide(guideNode); @@ -88,163 +132,185 @@ public async Task ReadPackageAsync(IZipFile epubFile, string rootFi return new(uniqueIdentifier, epubVersion, metadata, manifest, spine, guide, collections, id, textDirection, prefix, language); } - private static EpubManifest ReadManifest(XElement manifestNode, PackageReaderOptions packageReaderOptions) + private EpubManifest ReadManifest(XElement? manifestNode) { - XAttribute? manifestIdAttribute = manifestNode.Attribute("id"); + XAttribute? manifestIdAttribute = manifestNode?.Attribute("id"); string? manifestId = manifestIdAttribute?.Value; List items = new(); HashSet manifestItemIds = new(); HashSet manifestItemHrefs = new(); - foreach (XElement manifestItemNode in manifestNode.Elements()) + if (manifestNode != null) { - if (manifestItemNode.CompareNameTo("item")) + foreach (XElement manifestItemNode in manifestNode.Elements()) { - string? manifestItemId = null; - string? href = null; - string? mediaType = null; - string? mediaOverlay = null; - string? requiredNamespace = null; - string? requiredModules = null; - string? fallback = null; - string? fallbackStyle = null; - List? properties = null; - foreach (XAttribute manifestItemNodeAttribute in manifestItemNode.Attributes()) + if (manifestItemNode.CompareNameTo("item")) { - string attributeValue = manifestItemNodeAttribute.Value; - switch (manifestItemNodeAttribute.GetLowerCaseLocalName()) + string? manifestItemId = null; + string? href = null; + string? mediaType = null; + string? mediaOverlay = null; + string? requiredNamespace = null; + string? requiredModules = null; + string? fallback = null; + string? fallbackStyle = null; + List? properties = null; + foreach (XAttribute manifestItemNodeAttribute in manifestItemNode.Attributes()) { - case "id": - manifestItemId = attributeValue; - break; - case "href": - href = Uri.UnescapeDataString(attributeValue); - break; - case "media-type": - mediaType = attributeValue; - break; - case "media-overlay": - mediaOverlay = attributeValue; - break; - case "required-namespace": - requiredNamespace = attributeValue; - break; - case "required-modules": - requiredModules = attributeValue; - break; - case "fallback": - fallback = attributeValue; - break; - case "fallback-style": - fallbackStyle = attributeValue; - break; - case "properties": - properties = EpubManifestPropertyParser.ParsePropertyList(attributeValue); - break; + string attributeValue = manifestItemNodeAttribute.Value; + switch (manifestItemNodeAttribute.GetLowerCaseLocalName()) + { + case "id": + manifestItemId = attributeValue; + break; + case "href": + href = Uri.UnescapeDataString(attributeValue); + break; + case "media-type": + mediaType = attributeValue; + break; + case "media-overlay": + mediaOverlay = attributeValue; + break; + case "required-namespace": + requiredNamespace = attributeValue; + break; + case "required-modules": + requiredModules = attributeValue; + break; + case "fallback": + fallback = attributeValue; + break; + case "fallback-style": + fallbackStyle = attributeValue; + break; + case "properties": + properties = EpubManifestPropertyParser.ParsePropertyList(attributeValue); + break; + } } - } - if (manifestItemId == null) - { - if (packageReaderOptions != null && packageReaderOptions.SkipInvalidManifestItems) + if (manifestItemId == null) { - continue; + if (packageReaderOptions.SkipInvalidManifestItems) + { + continue; + } + throw new EpubPackageException("Incorrect EPUB manifest: item ID is missing."); } - throw new EpubPackageException("Incorrect EPUB manifest: item ID is missing."); - } - if (href == null) - { - if (packageReaderOptions != null && packageReaderOptions.SkipInvalidManifestItems) + if (href == null) { - continue; + if (packageReaderOptions.SkipInvalidManifestItems) + { + continue; + } + throw new EpubPackageException("Incorrect EPUB manifest: item href is missing."); } - throw new EpubPackageException("Incorrect EPUB manifest: item href is missing."); - } - if (mediaType == null) - { - if (packageReaderOptions != null && packageReaderOptions.SkipInvalidManifestItems) + if (mediaType == null) { - continue; + if (packageReaderOptions.SkipInvalidManifestItems) + { + continue; + } + throw new EpubPackageException("Incorrect EPUB manifest: item media type is missing."); } - throw new EpubPackageException("Incorrect EPUB manifest: item media type is missing."); - } - if (manifestItemIds.Contains(manifestItemId)) - { - throw new EpubPackageException($"Incorrect EPUB manifest: item with ID = \"{manifestItemId}\" is not unique."); - } - manifestItemIds.Add(manifestItemId); - if (manifestItemHrefs.Contains(href)) - { - throw new EpubPackageException($"Incorrect EPUB manifest: item with href = \"{href}\" is not unique."); + if (manifestItemIds.Contains(manifestItemId)) + { + if (packageReaderOptions.SkipDuplicateManifestItemIds) + { + continue; + } + throw new EpubPackageException($"Incorrect EPUB manifest: item with ID = \"{manifestItemId}\" is not unique."); + } + manifestItemIds.Add(manifestItemId); + if (manifestItemHrefs.Contains(href)) + { + if (packageReaderOptions.SkipDuplicateManifestHrefs) + { + continue; + } + throw new EpubPackageException($"Incorrect EPUB manifest: item with href = \"{href}\" is not unique."); + } + manifestItemHrefs.Add(href); + items.Add(new EpubManifestItem(manifestItemId, href, mediaType, mediaOverlay, requiredNamespace, requiredModules, fallback, fallbackStyle, properties)); } - manifestItemHrefs.Add(href); - items.Add(new EpubManifestItem(manifestItemId, href, mediaType, mediaOverlay, requiredNamespace, requiredModules, fallback, fallbackStyle, properties)); } } return new(manifestId, items); } - private static EpubSpine ReadSpine(XElement spineNode, EpubVersion epubVersion, PackageReaderOptions packageReaderOptions) + private EpubSpine ReadSpine(XElement? spineNode, EpubVersion epubVersion) { string? spineId = null; EpubPageProgressionDirection? pageProgressionDirection = null; string? toc = null; List items = new(); - foreach (XAttribute spineNodeAttribute in spineNode.Attributes()) + if (spineNode != null) { - string attributeValue = spineNodeAttribute.Value; - switch (spineNodeAttribute.GetLowerCaseLocalName()) + foreach (XAttribute spineNodeAttribute in spineNode.Attributes()) { - case "id": - spineId = attributeValue; - break; - case "page-progression-direction": - pageProgressionDirection = EpubPageProgressionDirectionParser.Parse(attributeValue); - break; - case "toc": - toc = attributeValue; - break; + string attributeValue = spineNodeAttribute.Value; + switch (spineNodeAttribute.GetLowerCaseLocalName()) + { + case "id": + spineId = attributeValue; + break; + case "page-progression-direction": + pageProgressionDirection = EpubPageProgressionDirectionParser.Parse(attributeValue); + break; + case "toc": + toc = attributeValue; + break; + } } - } - if (epubVersion == EpubVersion.EPUB_2 && String.IsNullOrWhiteSpace(toc) && (packageReaderOptions == null || !packageReaderOptions.IgnoreMissingToc)) - { - throw new EpubPackageException("Incorrect EPUB spine: TOC is missing."); - } - foreach (XElement spineItemNode in spineNode.Elements()) - { - if (spineItemNode.CompareNameTo("itemref")) + if (epubVersion == EpubVersion.EPUB_2 && String.IsNullOrWhiteSpace(toc)) { - string? spineItemRefId = null; - string? idRef = null; - bool isLinear; - List? properties = null; - foreach (XAttribute spineItemNodeAttribute in spineItemNode.Attributes()) + if (!packageReaderOptions.IgnoreMissingToc) { - string attributeValue = spineItemNodeAttribute.Value; - switch (spineItemNodeAttribute.GetLowerCaseLocalName()) - { - case "id": - spineItemRefId = attributeValue; - break; - case "idref": - idRef = attributeValue; - break; - case "properties": - properties = EpubSpinePropertyParser.ParsePropertyList(attributeValue); - break; - } + throw new EpubPackageException("Incorrect EPUB spine: TOC is missing."); } - if (idRef == null) + toc = null; + } + foreach (XElement spineItemNode in spineNode.Elements()) + { + if (spineItemNode.CompareNameTo("itemref")) { - throw new EpubPackageException("Incorrect EPUB spine: item ID ref is missing."); + string? spineItemRefId = null; + string? idRef = null; + bool isLinear; + List? properties = null; + foreach (XAttribute spineItemNodeAttribute in spineItemNode.Attributes()) + { + string attributeValue = spineItemNodeAttribute.Value; + switch (spineItemNodeAttribute.GetLowerCaseLocalName()) + { + case "id": + spineItemRefId = attributeValue; + break; + case "idref": + idRef = attributeValue; + break; + case "properties": + properties = EpubSpinePropertyParser.ParsePropertyList(attributeValue); + break; + } + } + if (idRef == null) + { + if (packageReaderOptions.SkipInvalidSpineItems) + { + continue; + } + throw new EpubPackageException("Incorrect EPUB spine: item ID ref is missing."); + } + XAttribute? linearAttribute = spineItemNode.Attribute("linear"); + isLinear = linearAttribute == null || !linearAttribute.CompareValueTo("no"); + items.Add(new EpubSpineItemRef(spineItemRefId, idRef, isLinear, properties)); } - XAttribute linearAttribute = spineItemNode.Attribute("linear"); - isLinear = linearAttribute == null || !linearAttribute.CompareValueTo("no"); - items.Add(new EpubSpineItemRef(spineItemRefId, idRef, isLinear, properties)); } } return new(spineId, pageProgressionDirection, toc, items); } - private static EpubGuide ReadGuide(XElement guideNode) + private EpubGuide ReadGuide(XElement guideNode) { List items = new(); foreach (XElement guideReferenceNode in guideNode.Elements()) @@ -272,10 +338,18 @@ private static EpubGuide ReadGuide(XElement guideNode) } if (type == null) { + if (packageReaderOptions.SkipInvalidGuideReferences) + { + continue; + } throw new EpubPackageException("Incorrect EPUB guide: item type is missing."); } if (href == null) { + if (packageReaderOptions.SkipInvalidGuideReferences) + { + continue; + } throw new EpubPackageException("Incorrect EPUB guide: item href is missing."); } items.Add(new EpubGuideReference(type, title, href)); @@ -284,17 +358,21 @@ private static EpubGuide ReadGuide(XElement guideNode) return new(items); } - private static List ReadCollections(XElement packageNode) + private List ReadCollections(XElement packageNode) { List result = new(); foreach (XElement collectionNode in packageNode.Elements(packageNode.Name.Namespace + "collection")) { - result.Add(ReadCollection(collectionNode)); + EpubCollection? epubCollection = ReadCollection(collectionNode); + if (epubCollection != null) + { + result.Add(epubCollection); + } } return result; } - private static EpubCollection ReadCollection(XElement collectionNode) + private EpubCollection? ReadCollection(XElement collectionNode) { string? role = null; string? id = null; @@ -321,6 +399,10 @@ private static EpubCollection ReadCollection(XElement collectionNode) } if (role == null) { + if (packageReaderOptions.SkipInvalidCollections) + { + return null; + } throw new EpubPackageException("Incorrect EPUB collection: collection role is missing."); } EpubMetadata? metadata = null; @@ -331,15 +413,21 @@ private static EpubCollection ReadCollection(XElement collectionNode) switch (collectionChildNode.GetLowerCaseLocalName()) { case "metadata": - metadata = MetadataReader.ReadMetadata(collectionChildNode); + metadata = MetadataReader.ReadMetadata(collectionChildNode, epubReaderOptions.MetadataReaderOptions); break; case "collection": - EpubCollection nestedCollection = ReadCollection(collectionChildNode); - nestedCollections.Add(nestedCollection); + EpubCollection? nestedCollection = ReadCollection(collectionChildNode); + if (nestedCollection != null) + { + nestedCollections.Add(nestedCollection); + } break; case "link": - EpubMetadataLink link = MetadataReader.ReadLink(collectionChildNode); - links.Add(link); + EpubMetadataLink? link = MetadataReader.ReadLink(collectionChildNode, epubReaderOptions.MetadataReaderOptions); + if (link != null) + { + links.Add(link); + } break; } } diff --git a/Source/VersOne.Epub/Readers/RootFilePathReader.cs b/Source/VersOne.Epub/Readers/RootFilePathReader.cs deleted file mode 100644 index f1f45b9..0000000 --- a/Source/VersOne.Epub/Readers/RootFilePathReader.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.IO; -using System.Threading.Tasks; -using System.Xml.Linq; -using VersOne.Epub.Environment; -using VersOne.Epub.Options; - -namespace VersOne.Epub.Internal -{ - internal class RootFilePathReader - { - private readonly EpubReaderOptions epubReaderOptions; - - public RootFilePathReader(EpubReaderOptions? epubReaderOptions = null) - { - this.epubReaderOptions = epubReaderOptions ?? new EpubReaderOptions(); - } - - public async Task GetRootFilePathAsync(IZipFile epubFile) - { - const string EPUB_CONTAINER_FILE_PATH = "META-INF/container.xml"; - IZipFileEntry? containerFileEntry = epubFile.GetEntry(EPUB_CONTAINER_FILE_PATH) ?? - throw new EpubContainerException($"EPUB parsing error: \"{EPUB_CONTAINER_FILE_PATH}\" file not found in the EPUB file."); - XDocument containerDocument; - using (Stream containerStream = containerFileEntry.Open()) - { - containerDocument = await XmlUtils.LoadDocumentAsync(containerStream, epubReaderOptions.XmlReaderOptions).ConfigureAwait(false); - } - XNamespace cnsNamespace = "urn:oasis:names:tc:opendocument:xmlns:container"; - XAttribute? fullPathAttribute = - containerDocument.Element(cnsNamespace + "container")?.Element(cnsNamespace + "rootfiles")?.Element(cnsNamespace + "rootfile")?.Attribute("full-path") ?? - throw new EpubContainerException("EPUB parsing error: root file path not found in the EPUB container."); - return fullPathAttribute.Value; - } - } -} diff --git a/Source/VersOne.Epub/Readers/SchemaReader.cs b/Source/VersOne.Epub/Readers/SchemaReader.cs index 4a0c19e..4cf371f 100644 --- a/Source/VersOne.Epub/Readers/SchemaReader.cs +++ b/Source/VersOne.Epub/Readers/SchemaReader.cs @@ -15,13 +15,21 @@ public SchemaReader(EpubReaderOptions? epubReaderOptions = null) this.epubReaderOptions = epubReaderOptions ?? new EpubReaderOptions(); } - public async Task ReadSchemaAsync(IZipFile epubFile) + public async Task ReadSchemaAsync(IZipFile epubFile) { - RootFilePathReader rootFilePathReader = new(epubReaderOptions); - string rootFilePath = await rootFilePathReader.GetRootFilePathAsync(epubFile).ConfigureAwait(false); - string contentDirectoryPath = ContentPathUtils.GetDirectoryPath(rootFilePath); + ContainerFileReader containerFileReader = new(epubReaderOptions); + string? packageFilePath = await containerFileReader.GetPackageFilePathAsync(epubFile).ConfigureAwait(false); + if (packageFilePath == null) + { + return null; + } + string contentDirectoryPath = ContentPathUtils.GetDirectoryPath(packageFilePath); PackageReader packageReader = new(epubReaderOptions); - EpubPackage package = await packageReader.ReadPackageAsync(epubFile, rootFilePath).ConfigureAwait(false); + EpubPackage? package = await packageReader.ReadPackageAsync(epubFile, packageFilePath).ConfigureAwait(false); + if (package == null) + { + return null; + } Epub2NcxReader epub2NcxReader = new(epubReaderOptions); Epub2Ncx? epub2Ncx = await epub2NcxReader.ReadEpub2NcxAsync(epubFile, contentDirectoryPath, package).ConfigureAwait(false); Epub3NavDocumentReader epub3NavDocumentReader = new(epubReaderOptions); diff --git a/Source/VersOne.Epub/Readers/SmilReader.cs b/Source/VersOne.Epub/Readers/SmilReader.cs index e940083..b877eb8 100644 --- a/Source/VersOne.Epub/Readers/SmilReader.cs +++ b/Source/VersOne.Epub/Readers/SmilReader.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using System.Xml; using System.Xml.Linq; using VersOne.Epub.Environment; using VersOne.Epub.Options; @@ -14,45 +15,98 @@ namespace VersOne.Epub.Internal internal class SmilReader { private readonly EpubReaderOptions epubReaderOptions; + private readonly SmilReaderOptions smilReaderOptions; public SmilReader(EpubReaderOptions? epubReaderOptions = null) { this.epubReaderOptions = epubReaderOptions ?? new EpubReaderOptions(); + smilReaderOptions = this.epubReaderOptions.SmilReaderOptions ?? new SmilReaderOptions(); } public async Task> ReadAllSmilDocumentsAsync(IZipFile epubFile, string contentDirectoryPath, EpubPackage package) { List result = new(); - foreach (EpubManifestItem smilManifestItem in package.Manifest.Items.Where(manifestItem => manifestItem.MediaType.CompareOrdinalIgnoreCase("application/smil+xml"))) + foreach (EpubManifestItem smilManifestItem in + package.Manifest.Items.Where(manifestItem => manifestItem.MediaType.CompareOrdinalIgnoreCase("application/smil+xml"))) { string smilFilePath = ContentPathUtils.Combine(contentDirectoryPath, smilManifestItem.Href); - Smil smil = await ReadSmilAsync(epubFile, smilFilePath); - result.Add(smil); + Smil? smil = await ReadSmilAsync(epubFile, smilFilePath); + if (smil != null) + { + result.Add(smil); + } } return result; } - public async Task ReadSmilAsync(IZipFile epubFile, string smilFilePath) + public async Task ReadSmilAsync(IZipFile epubFile, string smilFilePath) { - IZipFileEntry smilFile = epubFile.GetEntry(smilFilePath) - ?? throw new EpubSmilException($"EPUB parsing error: SMIL file {smilFilePath} not found in the EPUB file.", smilFilePath); + IZipFileEntry? smilFile = epubFile.GetEntry(smilFilePath); + if (smilFile == null) + { + if (smilReaderOptions.IgnoreMissingSmilFileError) + { + return null; + } + throw new EpubSmilException($"EPUB parsing error: SMIL file {smilFilePath} not found in the EPUB file.", smilFilePath); + } if (smilFile.Length > Int32.MaxValue) { + if (smilReaderOptions.IgnoreSmilFileIsTooLargeError) + { + return null; + } throw new EpubSmilException($"EPUB parsing error: SMIL file {smilFilePath} is larger than 2 GB.", smilFilePath); } XDocument smilDocument; - using (Stream containerStream = smilFile.Open()) + try { + using Stream containerStream = smilFile.Open(); smilDocument = await XmlUtils.LoadDocumentAsync(containerStream, epubReaderOptions.XmlReaderOptions).ConfigureAwait(false); } + catch (XmlException xmlException) + { + if (smilReaderOptions.IgnoreSmilFileIsNotValidXmlError) + { + return null; + } + throw new EpubSmilException($"EPUB parsing error: SMIL file {smilFilePath} is not a valid XML file.", xmlException, smilFilePath); + } XNamespace smilNamespace = "http://www.w3.org/ns/SMIL"; - XElement smilNode = smilDocument.Element(smilNamespace + "smil") - ?? throw new EpubSmilException("SMIL parsing error: smil XML element is missing in the file.", smilFilePath); - Smil smil = ReadSmil(smilNode, smilFilePath); + XElement? smilNode = smilDocument.Element(smilNamespace + "smil"); + if (smilNode == null) + { + if (smilReaderOptions.IgnoreMissingSmilElementError) + { + return null; + } + throw new EpubSmilException("SMIL parsing error: smil XML element is missing in the file.", smilFilePath); + } + Smil? smil = ReadSmil(smilNode, smilFilePath); return smil; } - private static Smil ReadSmil(XElement smilNode, string smilFilePath) + private static SmilHead ReadHead(XElement headNode) + { + SmilMetadata? metadata = null; + foreach (XElement headChildNode in headNode.Elements()) + { + if (headChildNode.GetLowerCaseLocalName() == "metadata") + { + metadata = ReadMetadata(headChildNode); + break; + } + } + return new(metadata); + } + + private static SmilMetadata ReadMetadata(XElement metadataNode) + { + List items = metadataNode.Elements().ToList(); + return new(items); + } + + private Smil? ReadSmil(XElement smilNode, string smilFilePath) { string? id = null; string? smilVersionString = null; @@ -87,39 +141,45 @@ private static Smil ReadSmil(XElement smilNode, string smilFilePath) break; } } - SmilVersion version = smilVersionString switch + SmilVersion version; + switch (smilVersionString) { - "3.0" => SmilVersion.SMIL_3, - _ => throw new EpubSmilException($"SMIL parsing error: unsupported SMIL version: \"{smilVersionString}\".", smilFilePath) - }; - if (body == null) - { - throw new EpubSmilException("SMIL parsing error: body XML element is missing in the file.", smilFilePath); + case "3.0": + version = SmilVersion.SMIL_3; + break; + case null: + if (smilReaderOptions.IgnoreMissingSmilVersionError) + { + version = SmilVersion.SMIL_3; + } + else + { + throw new EpubSmilException($"SMIL parsing error: SMIL version is missing.", smilFilePath); + } + break; + default: + if (smilReaderOptions.IgnoreUnsupportedSmilVersionError) + { + version = SmilVersion.SMIL_3; + } + else + { + throw new EpubSmilException($"SMIL parsing error: unsupported SMIL version: \"{smilVersionString}\".", smilFilePath); + } + break; } - return new(id, version, epubPrefix, head, body); - } - - private static SmilHead ReadHead(XElement headNode) - { - SmilMetadata? metadata = null; - foreach (XElement headChildNode in headNode.Elements()) + if (body == null) { - if (headChildNode.GetLowerCaseLocalName() == "metadata") + if (smilReaderOptions.IgnoreMissingBodyElementError) { - metadata = ReadMetadata(headChildNode); - break; + return null; } + throw new EpubSmilException("SMIL parsing error: body XML element is missing in the file.", smilFilePath); } - return new(metadata); - } - - private static SmilMetadata ReadMetadata(XElement metadataNode) - { - List items = metadataNode.Elements().ToList(); - return new(items); + return new(id, version, epubPrefix, head, body); } - private static SmilBody ReadBody(XElement bodyNode, string smilFilePath) + private SmilBody ReadBody(XElement bodyNode, string smilFilePath) { string? id = null; List? epubTypes = null; @@ -151,19 +211,22 @@ private static SmilBody ReadBody(XElement bodyNode, string smilFilePath) seqs.Add(seq); break; case "par": - SmilPar par = ReadPar(bodyChildNode, smilFilePath); - pars.Add(par); + SmilPar? par = ReadPar(bodyChildNode, smilFilePath); + if (par != null) + { + pars.Add(par); + } break; } } - if (!seqs.Any() && !pars.Any()) + if (!seqs.Any() && !pars.Any() && !smilReaderOptions.IgnoreBodyMissingSeqOrParElementsError) { throw new EpubSmilException("SMIL parsing error: body XML element must contain at least one seq or par XML element.", smilFilePath); } return new(id, epubTypes, epubTextRef, seqs, pars); } - private static SmilSeq ReadSeq(XElement seqNode, string smilFilePath) + private SmilSeq ReadSeq(XElement seqNode, string smilFilePath) { string? id = null; List? epubTypes = null; @@ -195,19 +258,22 @@ private static SmilSeq ReadSeq(XElement seqNode, string smilFilePath) seqs.Add(seq); break; case "par": - SmilPar par = ReadPar(bodyChildNode, smilFilePath); - pars.Add(par); + SmilPar? par = ReadPar(bodyChildNode, smilFilePath); + if (par != null) + { + pars.Add(par); + } break; } } - if (!seqs.Any() && !pars.Any()) + if (!seqs.Any() && !pars.Any() && !smilReaderOptions.IgnoreSeqMissingSeqOrParElementsError) { throw new EpubSmilException("SMIL parsing error: seq XML element must contain at least one nested seq or par XML element.", smilFilePath); } return new(id, epubTypes, epubTextRef, seqs, pars); } - private static SmilPar ReadPar(XElement parNode, string smilFilePath) + private SmilPar? ReadPar(XElement parNode, string smilFilePath) { string? id = null; List? epubTypes = null; @@ -240,12 +306,16 @@ private static SmilPar ReadPar(XElement parNode, string smilFilePath) } if (text == null) { + if (smilReaderOptions.SkipParsWithoutTextElements) + { + return null; + } throw new EpubSmilException("SMIL parsing error: par XML element must contain one text XML element.", smilFilePath); } return new(id, epubTypes, text, audio); } - private static SmilText ReadText(XElement textNode, string smilFilePath) + private SmilText? ReadText(XElement textNode, string smilFilePath) { string? id = null; string? src = null; @@ -264,12 +334,16 @@ private static SmilText ReadText(XElement textNode, string smilFilePath) } if (src == null) { + if (smilReaderOptions.SkipTextsWithoutSrcAttributes) + { + return null; + } throw new EpubSmilException("SMIL parsing error: text XML element must have an src attribute.", smilFilePath); } return new(id, src); } - private static SmilAudio ReadAudio(XElement audioNode, string smilFilePath) + private SmilAudio? ReadAudio(XElement audioNode, string smilFilePath) { string? id = null; string? src = null; @@ -296,6 +370,10 @@ private static SmilAudio ReadAudio(XElement audioNode, string smilFilePath) } if (src == null) { + if (smilReaderOptions.SkipAudiosWithoutSrcAttributes) + { + return null; + } throw new EpubSmilException("SMIL parsing error: audio XML element must have an src attribute.", smilFilePath); } return new(id, src, clipBegin, clipEnd); diff --git a/Source/VersOne.Epub/Readers/SpineReader.cs b/Source/VersOne.Epub/Readers/SpineReader.cs index 912c174..e8bbee2 100644 --- a/Source/VersOne.Epub/Readers/SpineReader.cs +++ b/Source/VersOne.Epub/Readers/SpineReader.cs @@ -23,11 +23,20 @@ public static List GetReadingOrder( } if (epubContentRef.Html.ContainsRemoteFileRefWithUrl(manifestItem.Href)) { + if (spineReaderOptions.SkipSpineItemsReferencingRemoteContent) + { + continue; + } throw new EpubPackageException($"Incorrect EPUB manifest: EPUB spine item \"{manifestItem.Href}\" cannot be a remote resource."); } - if (!epubContentRef.Html.TryGetLocalFileRefByKey(manifestItem.Href, out EpubLocalTextContentFileRef htmlContentFileRef)) + if (!epubContentRef.Html.TryGetLocalFileRefByKey(manifestItem.Href, out EpubLocalTextContentFileRef? htmlContentFileRef) + || htmlContentFileRef == null) { - throw new EpubPackageException($"Incorrect EPUB manifest: item with href = \"{spineItemRef.IdRef}\" is missing in the book."); + if (spineReaderOptions.IgnoreMissingContentFiles) + { + continue; + } + throw new EpubPackageException($"Incorrect EPUB manifest: HTML content file with href = \"{manifestItem.Href}\" is missing in the book."); } result.Add(htmlContentFileRef); } diff --git a/Source/VersOne.Epub/Schema/Opf/Collections/EpubCollection.cs b/Source/VersOne.Epub/Schema/Opf/Collections/EpubCollection.cs index 90e2672..1eadaa9 100644 --- a/Source/VersOne.Epub/Schema/Opf/Collections/EpubCollection.cs +++ b/Source/VersOne.Epub/Schema/Opf/Collections/EpubCollection.cs @@ -15,7 +15,7 @@ namespace VersOne.Epub.Schema public class EpubCollection { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The role of this collection. /// The EPUB meta information included into this collection or null if the collection doesn't have any meta information. diff --git a/Source/VersOne.Epub/Schema/Opf/Package/EpubPackage.cs b/Source/VersOne.Epub/Schema/Opf/Package/EpubPackage.cs index 2ffc52e..defcc8a 100644 --- a/Source/VersOne.Epub/Schema/Opf/Package/EpubPackage.cs +++ b/Source/VersOne.Epub/Schema/Opf/Package/EpubPackage.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using VersOne.Epub.Internal; namespace VersOne.Epub.Schema { @@ -141,7 +140,7 @@ public EpubPackage(string? uniqueIdentifier, EpubVersion epubVersion, EpubMetada /// String representation of the property. public string GetVersionString() { - return VersionUtils.GetVersionString(EpubVersion); + return EpubVersion.GetVersionString(); } } } diff --git a/Source/VersOne.Epub/Schema/Opf/Package/EpubVersion.cs b/Source/VersOne.Epub/Schema/Opf/Package/EpubVersion.cs index 68a4e12..b0f26f3 100644 --- a/Source/VersOne.Epub/Schema/Opf/Package/EpubVersion.cs +++ b/Source/VersOne.Epub/Schema/Opf/Package/EpubVersion.cs @@ -1,5 +1,4 @@ -using System; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; namespace VersOne.Epub.Schema { @@ -15,31 +14,29 @@ public enum EpubVersion /// /// Version 2. Can be either EPUB 2.0 or EPUB 2.0.1. /// - [VersionString("2")] EPUB_2 = 2, /// - /// Version 3. Can be either EPUB 3.0, EPUB 3.0.1, or EPUB 3.2. + /// Version 3. Can be either EPUB 3.0, EPUB 3.0.1, EPUB 3.2, or EPUB 3.3. /// - [VersionString("3")] EPUB_3, /// /// Version 3.1. Represents the deprecated EPUB 3.1 standard. /// - [VersionString("3.1")] EPUB_3_1 } [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", - Justification = "Enum and attribute need to be close to each other to indicate that attribute applies only to this enum. The file needs to be named after enum.")] - internal class VersionStringAttribute : Attribute + Justification = "Enum and extension method need to be close to each other to avoid issues when the enum was changed without changing the extension method. The file needs to be named after enum.")] + internal static class VersionUtils { - public VersionStringAttribute(string version) + public static string GetVersionString(this EpubVersion epubVersion) => epubVersion switch { - Version = version; - } - - public string Version { get; } + EpubVersion.EPUB_2 => "2", + EpubVersion.EPUB_3 => "3", + EpubVersion.EPUB_3_1 => "3.1", + _ => epubVersion.ToString(), + }; } } diff --git a/Source/VersOne.Epub/Utils/TaskExtensionMethods.cs b/Source/VersOne.Epub/Utils/TaskExtensionMethods.cs index ab25af6..76c32ef 100644 --- a/Source/VersOne.Epub/Utils/TaskExtensionMethods.cs +++ b/Source/VersOne.Epub/Utils/TaskExtensionMethods.cs @@ -13,7 +13,7 @@ public static T ExecuteAndUnwrapAggregateException(this Task task) } catch (AggregateException aggregateException) { - throw aggregateException.InnerException; + throw aggregateException.GetBaseException(); } } } diff --git a/Source/VersOne.Epub/Utils/VersionUtils.cs b/Source/VersOne.Epub/Utils/VersionUtils.cs deleted file mode 100644 index f634977..0000000 --- a/Source/VersOne.Epub/Utils/VersionUtils.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Reflection; -using VersOne.Epub.Schema; - -namespace VersOne.Epub.Internal -{ - internal static class VersionUtils - { - public static string GetVersionString(EpubVersion epubVersion) - { - Type epubVersionType = typeof(EpubVersion); - FieldInfo fieldInfo = epubVersionType.GetRuntimeField(epubVersion.ToString()); - if (fieldInfo != null) - { - return fieldInfo.GetCustomAttribute().Version; - } - else - { - return epubVersion.ToString(); - } - } - } -}