From 67fadc9fd588c6056ccc3bc09f0eb7b26aad50db Mon Sep 17 00:00:00 2001 From: vers-one <12114169+vers-one@users.noreply.github.com> Date: Fri, 19 May 2023 02:16:43 -0400 Subject: [PATCH] NavigationReader fix: get content files via absolute file paths (#92) --- .github/workflows/build.yml | 2 +- .github/workflows/generate-docs.yml | 2 +- .github/workflows/publish-to-nuget.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 2 +- .../Comparers/CollectionComparer.cs | 4 +- .../Comparers/EpubContentComparer.cs | 4 +- .../Comparers/EpubContentRefComparer.cs | 4 +- .../Comparers/EpubNavigationItemComparer.cs | 2 +- .../CustomSerialization/CustomTypes.cs | 18 +- .../Deserializers/ListDeserializer.cs | 18 +- .../TestCases/BugFixes/53/testcases.json | 6 +- .../TestCases/BugFixes/55/testcases.json | 6 +- .../TestCases/BugFixes/57/testcases.json | 6 +- .../Features/RemoteContent/testcases.json | 160 +++-- .../InvalidManifestItems/EPUB2/testcases.json | 14 +- .../InvalidManifestItems/EPUB3/testcases.json | 16 +- .../testcases.json | 16 +- .../TestCases/Typical/EPUB2/testcases.json | 84 +-- .../TestCases/Typical/EPUB3/testcases.json | 88 +-- .../Workarounds/Xml11/testcases.json | 6 +- .../Integration/Types/TestCase.cs | 4 +- .../EpubContentFileRefMetadataTests.cs | 2 +- .../{ => Base}/EpubContentFileRefTests.cs | 2 +- .../{ => Base}/EpubContentFileTests.cs | 32 +- .../EpubContentCollectionRefTests.cs | 566 ++++++++++++++++++ .../Collections/EpubContentCollectionTests.cs | 509 ++++++++++++++++ .../EpubLocalContentLoaderTests.cs | 2 +- .../EpubRemoteContentLoaderTests.cs | 2 +- .../Unit/Entities/EpubBookRefTests.cs | 38 +- .../Entities/EpubNavigationItemLinkTests.cs | 46 +- .../Entities/EpubNavigationItemRefTests.cs | 2 +- .../Unit/Entities/EpubNavigationItemTests.cs | 2 +- .../EpubContentCollectionExceptionTests.cs | 30 + .../EpubContentCollectionRefExceptionTests.cs | 30 + .../Unit/Readers/BookCoverReaderTests.cs | 8 +- .../Unit/Readers/ContentReaderTests.cs | 536 +++++------------ .../Unit/Readers/NavigationReaderTests.cs | 97 ++- .../Unit/Readers/PackageReaderTests.cs | 38 ++ .../Unit/Readers/SmilClockParserTests.cs | 2 +- .../Unit/Readers/SpineReaderTests.cs | 53 +- .../Unit/TestData/TestEpubContent.cs | 330 +++------- .../Unit/TestData/TestEpubContentRef.cs | 316 +++------- ...UtilsTests.cs => ContentPathUtilsTests.cs} | 22 +- .../VersOne.Epub.WpfDemo/Models/BookModel.cs | 6 +- .../Collections/EpubContentCollection.cs | 238 +++++++- .../Collections/EpubContentCollectionRef.cs | 238 +++++++- .../Content/Loaders/EpubLocalContentLoader.cs | 2 +- .../Remote/EpubRemoteByteContentFile.cs | 6 +- .../Content/Remote/EpubRemoteContentFile.cs | 7 +- .../Remote/EpubRemoteTextContentFile.cs | 6 +- .../Entities/EpubNavigationItemLink.cs | 40 +- .../Exceptions/Epub2NcxException.cs | 2 +- .../Exceptions/Epub3NavException.cs | 2 +- .../Exceptions/EpubContainerException.cs | 2 +- .../EpubContentCollectionException.cs | 38 ++ .../EpubContentCollectionRefException.cs | 38 ++ .../EpubContentDownloaderException.cs | 2 +- .../Exceptions/EpubContentException.cs | 2 +- .../Exceptions/EpubPackageException.cs | 2 +- .../Exceptions/EpubReaderException.cs | 2 +- .../Exceptions/EpubSchemaException.cs | 2 +- .../Exceptions/EpubSmilException.cs | 2 +- .../VersOne.Epub/Readers/BookCoverReader.cs | 19 +- Source/VersOne.Epub/Readers/BookReader.cs | 86 +-- Source/VersOne.Epub/Readers/ContentReader.cs | 59 +- Source/VersOne.Epub/Readers/Epub2NcxReader.cs | 2 +- .../Readers/Epub3NavDocumentReader.cs | 2 +- .../VersOne.Epub/Readers/NavigationReader.cs | 32 +- Source/VersOne.Epub/Readers/PackageReader.cs | 12 + Source/VersOne.Epub/Readers/SchemaReader.cs | 2 +- .../VersOne.Epub/Readers/SmilClockParser.cs | 2 +- Source/VersOne.Epub/Readers/SmilReader.cs | 2 +- Source/VersOne.Epub/Readers/SpineReader.cs | 9 +- .../{ZipPathUtils.cs => ContentPathUtils.cs} | 4 +- 75 files changed, 2605 insertions(+), 1394 deletions(-) rename Source/VersOne.Epub.Test/Unit/Content/{ => Base}/EpubContentFileRefMetadataTests.cs (95%) rename Source/VersOne.Epub.Test/Unit/Content/{ => Base}/EpubContentFileRefTests.cs (98%) rename Source/VersOne.Epub.Test/Unit/Content/{ => Base}/EpubContentFileTests.cs (87%) create mode 100644 Source/VersOne.Epub.Test/Unit/Content/Collections/EpubContentCollectionRefTests.cs create mode 100644 Source/VersOne.Epub.Test/Unit/Content/Collections/EpubContentCollectionTests.cs rename Source/VersOne.Epub.Test/Unit/Content/{ => Loaders}/EpubLocalContentLoaderTests.cs (98%) rename Source/VersOne.Epub.Test/Unit/Content/{ => Loaders}/EpubRemoteContentLoaderTests.cs (98%) create mode 100644 Source/VersOne.Epub.Test/Unit/Exceptions/EpubContentCollectionExceptionTests.cs create mode 100644 Source/VersOne.Epub.Test/Unit/Exceptions/EpubContentCollectionRefExceptionTests.cs rename Source/VersOne.Epub.Test/Unit/Utils/{ZipPathUtilsTests.cs => ContentPathUtilsTests.cs} (64%) create mode 100644 Source/VersOne.Epub/Exceptions/EpubContentCollectionException.cs create mode 100644 Source/VersOne.Epub/Exceptions/EpubContentCollectionRefException.cs rename Source/VersOne.Epub/Utils/{ZipPathUtils.cs => ContentPathUtils.cs} (85%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd868e0..fae54b6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ jobs: - name: Install .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7 + dotnet-version: 7.0.203 - name: Setup MSBuild uses: microsoft/setup-msbuild@v1.1.3 - name: Restore dependencies diff --git a/.github/workflows/generate-docs.yml b/.github/workflows/generate-docs.yml index 31c1ead..a6e0cbd 100644 --- a/.github/workflows/generate-docs.yml +++ b/.github/workflows/generate-docs.yml @@ -25,7 +25,7 @@ jobs: - name: Install .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7 + dotnet-version: 7.0.203 - name: Restore dependencies run: dotnet restore .\Source\VersOne.Epub\VersOne.Epub.csproj - name: Install DocFX diff --git a/.github/workflows/publish-to-nuget.yml b/.github/workflows/publish-to-nuget.yml index e4726c6..339d328 100644 --- a/.github/workflows/publish-to-nuget.yml +++ b/.github/workflows/publish-to-nuget.yml @@ -16,7 +16,7 @@ jobs: - name: Install .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7 + dotnet-version: 7.0.203 - name: Setup MSBuild uses: microsoft/setup-msbuild@v1.1.3 - name: Restore dependencies diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a9ae43b..7208754 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: - name: Install .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7 + dotnet-version: 7.0.203 - name: Setup MSBuild uses: microsoft/setup-msbuild@v1.1.3 - name: Restore dependencies diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eee1130..752d9e7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: - name: Install .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7 + dotnet-version: 7.0.203 - name: Build test project and its dependencies run: dotnet build Source\VersOne.Epub.Test -c $env:Configuration - name: Run unit tests diff --git a/Source/VersOne.Epub.Test/Comparers/CollectionComparer.cs b/Source/VersOne.Epub.Test/Comparers/CollectionComparer.cs index 064444c..315c035 100644 --- a/Source/VersOne.Epub.Test/Comparers/CollectionComparer.cs +++ b/Source/VersOne.Epub.Test/Comparers/CollectionComparer.cs @@ -11,7 +11,7 @@ public static void CompareCollections(IList expected, IList actual, Act } } - public static void CompareDictionaries(IDictionary expected, IDictionary actual, Action elementValueComprarer) + public static void CompareDictionaries(IDictionary expected, IDictionary actual, Action elementValueComparer) { Assert.Equal(expected.Count, actual.Count); foreach (KeyValuePair expectedKeyValuePair in expected) @@ -20,7 +20,7 @@ public static void CompareDictionaries(IDictionary e TValue expectedValue = expectedKeyValuePair.Value; if (actual.TryGetValue(expectedKey, out TValue? actualValue)) { - elementValueComprarer(expectedValue, actualValue); + elementValueComparer(expectedValue, actualValue); } else { diff --git a/Source/VersOne.Epub.Test/Comparers/EpubContentComparer.cs b/Source/VersOne.Epub.Test/Comparers/EpubContentComparer.cs index 4d28247..e3bfafb 100644 --- a/Source/VersOne.Epub.Test/Comparers/EpubContentComparer.cs +++ b/Source/VersOne.Epub.Test/Comparers/EpubContentComparer.cs @@ -84,8 +84,8 @@ private static void CompareContentCollections CreateTypes() ( optionalProperties: new() { - { nameof(EpubContentCollection.Local), PropertyDefaultValue.EMPTY_DICTIONARY }, - { nameof(EpubContentCollection.Remote), PropertyDefaultValue.EMPTY_DICTIONARY } + { nameof(EpubContentCollection.Local), PropertyDefaultValue.EMPTY_ARRAY }, + { nameof(EpubContentCollection.Remote), PropertyDefaultValue.EMPTY_ARRAY } } ); yield return CreateType> ( optionalProperties: new() { - { nameof(EpubContentCollection.Local), PropertyDefaultValue.EMPTY_DICTIONARY }, - { nameof(EpubContentCollection.Remote), PropertyDefaultValue.EMPTY_DICTIONARY } + { nameof(EpubContentCollection.Local), PropertyDefaultValue.EMPTY_ARRAY }, + { nameof(EpubContentCollection.Remote), PropertyDefaultValue.EMPTY_ARRAY } } ); yield return CreateType> ( optionalProperties: new() { - { nameof(EpubContentCollection.Local), PropertyDefaultValue.EMPTY_DICTIONARY }, - { nameof(EpubContentCollection.Remote), PropertyDefaultValue.EMPTY_DICTIONARY } + { nameof(EpubContentCollection.Local), PropertyDefaultValue.EMPTY_ARRAY }, + { nameof(EpubContentCollection.Remote), PropertyDefaultValue.EMPTY_ARRAY } } ); yield return CreateType @@ -515,7 +515,8 @@ private static IEnumerable CreateTypes() ignoredProperties: new() { nameof(EpubRemoteByteContentFile.ContentFileType), - nameof(EpubRemoteByteContentFile.ContentLocation) + nameof(EpubRemoteByteContentFile.ContentLocation), + nameof(EpubRemoteByteContentFile.Url) } ); yield return CreateType @@ -524,7 +525,8 @@ private static IEnumerable CreateTypes() ignoredProperties: new() { nameof(EpubRemoteTextContentFile.ContentFileType), - nameof(EpubRemoteTextContentFile.ContentLocation) + nameof(EpubRemoteTextContentFile.ContentLocation), + nameof(EpubRemoteTextContentFile.Url) } ); yield return CreateType diff --git a/Source/VersOne.Epub.Test/Integration/JsonUtils/Deserializers/ListDeserializer.cs b/Source/VersOne.Epub.Test/Integration/JsonUtils/Deserializers/ListDeserializer.cs index 96835df..702f756 100644 --- a/Source/VersOne.Epub.Test/Integration/JsonUtils/Deserializers/ListDeserializer.cs +++ b/Source/VersOne.Epub.Test/Integration/JsonUtils/Deserializers/ListDeserializer.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.ObjectModel; using System.Text.Json; namespace VersOne.Epub.Test.Integration.JsonUtils.Deserializers @@ -6,24 +7,33 @@ namespace VersOne.Epub.Test.Integration.JsonUtils.Deserializers internal class ListDeserializer : TypeDeserializer { private readonly Type listType; + private readonly bool isReadOnlyCollection; + private readonly Type listItemType; private readonly Lazy listItemTypeDeserializer; public ListDeserializer(Type listType, TypeDeserializerCollection typeDeserializerCollection) { + if (!listType.IsGenericType) + { + throw new ArgumentException($"{listType.Name} is not a generic List type."); + } this.listType = listType; - Type listItemType = listType.IsGenericType ? listType.GetGenericArguments().First() : throw new ArgumentException($"{listType.Name} is not a generic List type."); + isReadOnlyCollection = listType.GetGenericTypeDefinition() == typeof(ReadOnlyCollection<>); + listItemType = listType.GetGenericArguments().First(); listItemTypeDeserializer = new Lazy(() => typeDeserializerCollection.GetDeserializer(listItemType)); } public override object? Deserialize(JsonElement jsonElement, JsonSerializationContext jsonSerializationContext) { Assert.Equal(JsonValueKind.Array, jsonElement.ValueKind); - IList? result = Activator.CreateInstance(listType, new object[] { jsonElement.GetArrayLength() }) as IList; - Assert.NotNull(result); + IList? list = Activator.CreateInstance(isReadOnlyCollection ? typeof(List<>).MakeGenericType(listItemType) : listType, + new object[] { jsonElement.GetArrayLength() }) as IList; + Assert.NotNull(list); foreach (JsonElement serializedListItem in jsonElement.EnumerateArray()) { - result.Add(listItemTypeDeserializer.Value.Deserialize(serializedListItem, jsonSerializationContext)); + list.Add(listItemTypeDeserializer.Value.Deserialize(serializedListItem, jsonSerializationContext)); } + object? result = isReadOnlyCollection ? Activator.CreateInstance(listType, new object[] { list }) : list; return result; } } diff --git a/Source/VersOne.Epub.Test/Integration/TestCases/BugFixes/53/testcases.json b/Source/VersOne.Epub.Test/Integration/TestCases/BugFixes/53/testcases.json index ac9724b..8339525 100644 --- a/Source/VersOne.Epub.Test/Integration/TestCases/BugFixes/53/testcases.json +++ b/Source/VersOne.Epub.Test/Integration/TestCases/BugFixes/53/testcases.json @@ -85,11 +85,11 @@ }, "Content": { "AllFiles": { - "Local": { - "toc.ncx": { + "Local": [ + { "$ref": 1 } - } + ] } } } diff --git a/Source/VersOne.Epub.Test/Integration/TestCases/BugFixes/55/testcases.json b/Source/VersOne.Epub.Test/Integration/TestCases/BugFixes/55/testcases.json index 3195e7a..728d371 100644 --- a/Source/VersOne.Epub.Test/Integration/TestCases/BugFixes/55/testcases.json +++ b/Source/VersOne.Epub.Test/Integration/TestCases/BugFixes/55/testcases.json @@ -79,11 +79,11 @@ }, "Content": { "AllFiles": { - "Local": { - "toc.ncx": { + "Local": [ + { "$ref": 1 } - } + ] } } } diff --git a/Source/VersOne.Epub.Test/Integration/TestCases/BugFixes/57/testcases.json b/Source/VersOne.Epub.Test/Integration/TestCases/BugFixes/57/testcases.json index b95c5dd..45330ae 100644 --- a/Source/VersOne.Epub.Test/Integration/TestCases/BugFixes/57/testcases.json +++ b/Source/VersOne.Epub.Test/Integration/TestCases/BugFixes/57/testcases.json @@ -63,11 +63,11 @@ }, "Content": { "AllFiles": { - "Local": { - "toc.ncx": { + "Local": [ + { "$ref": 1 } - } + ] } } } diff --git a/Source/VersOne.Epub.Test/Integration/TestCases/Features/RemoteContent/testcases.json b/Source/VersOne.Epub.Test/Integration/TestCases/Features/RemoteContent/testcases.json index 10b60f4..4131d8f 100644 --- a/Source/VersOne.Epub.Test/Integration/TestCases/Features/RemoteContent/testcases.json +++ b/Source/VersOne.Epub.Test/Integration/TestCases/Features/RemoteContent/testcases.json @@ -52,7 +52,6 @@ "$id": 6, "$type": "EpubRemoteTextContentFile", "$content": null, - "Url": "https://os.vers.one/EpubReader/toc.html", "Key": "https://os.vers.one/EpubReader/toc.html", "ContentType": "XHTML_1_1", "ContentMimeType": "application/xhtml+xml" @@ -61,7 +60,6 @@ "$id": 7, "$type": "EpubRemoteTextContentFile", "$content": null, - "Url": "https://os.vers.one/EpubReader/styles/docfx.css", "Key": "https://os.vers.one/EpubReader/styles/docfx.css", "ContentType": "CSS", "ContentMimeType": "text/css" @@ -70,7 +68,6 @@ "$id": 8, "$type": "EpubRemoteByteContentFile", "$content": null, - "Url": "https://os.vers.one/EpubReader/images/logo.svg", "Key": "https://os.vers.one/EpubReader/images/logo.svg", "ContentType": "IMAGE_SVG", "ContentMimeType": "image/svg+xml" @@ -79,7 +76,6 @@ "$id": 9, "$type": "EpubRemoteByteContentFile", "$content": null, - "Url": "https://os.vers.one/EpubReader/fonts/glyphicons-halflings-regular.ttf", "Key": "https://os.vers.one/EpubReader/fonts/glyphicons-halflings-regular.ttf", "ContentType": "FONT_TRUETYPE", "ContentMimeType": "application/x-font-truetype" @@ -205,88 +201,88 @@ "$ref": 2 }, "Html": { - "Local": { - "chapter.html": { + "Local": [ + { "$ref": 1 }, - "toc.html": { + { "$ref": 2 } - }, - "Remote": { - "https://os.vers.one/EpubReader/toc.html": { + ], + "Remote": [ + { "$ref": 6 } - } + ] }, "Css": { - "Local": { - "styles.css": { + "Local": [ + { "$ref": 3 } - }, - "Remote": { - "https://os.vers.one/EpubReader/styles/docfx.css": { + ], + "Remote": [ + { "$ref": 7 } - } + ] }, "Images": { - "Local": { - "cover.jpg": { + "Local": [ + { "$ref": 4 } - }, - "Remote": { - "https://os.vers.one/EpubReader/images/logo.svg": { + ], + "Remote": [ + { "$ref": 8 } - } + ] }, "Fonts": { - "Local": { - "font.ttf": { + "Local": [ + { "$ref": 5 } - }, - "Remote": { - "https://os.vers.one/EpubReader/fonts/glyphicons-halflings-regular.ttf": { + ], + "Remote": [ + { "$ref": 9 } - } + ] }, "AllFiles": { - "Local": { - "chapter.html": { + "Local": [ + { "$ref": 1 }, - "toc.html": { + { "$ref": 2 }, - "styles.css": { + { "$ref": 3 }, - "cover.jpg": { + { "$ref": 4 }, - "font.ttf": { + { "$ref": 5 } - }, - "Remote": { - "https://os.vers.one/EpubReader/toc.html": { + ], + "Remote": [ + { "$ref": 6 }, - "https://os.vers.one/EpubReader/styles/docfx.css": { + { "$ref": 7 }, - "https://os.vers.one/EpubReader/images/logo.svg": { + { "$ref": 8 }, - "https://os.vers.one/EpubReader/fonts/glyphicons-halflings-regular.ttf": { + { "$ref": 9 } - } + ] } } } @@ -349,7 +345,6 @@ "$id": 15, "$type": "EpubRemoteTextContentFile", "$content": "https://os.vers.one/EpubReader/toc.html", - "Url": "https://os.vers.one/EpubReader/toc.html", "Key": "https://os.vers.one/EpubReader/toc.html", "ContentType": "XHTML_1_1", "ContentMimeType": "application/xhtml+xml" @@ -358,7 +353,6 @@ "$id": 16, "$type": "EpubRemoteTextContentFile", "$content": "https://os.vers.one/EpubReader/styles/docfx.css", - "Url": "https://os.vers.one/EpubReader/styles/docfx.css", "Key": "https://os.vers.one/EpubReader/styles/docfx.css", "ContentType": "CSS", "ContentMimeType": "text/css" @@ -367,7 +361,6 @@ "$id": 17, "$type": "EpubRemoteByteContentFile", "$content": "https://os.vers.one/EpubReader/images/logo.svg", - "Url": "https://os.vers.one/EpubReader/images/logo.svg", "Key": "https://os.vers.one/EpubReader/images/logo.svg", "ContentType": "IMAGE_SVG", "ContentMimeType": "image/svg+xml" @@ -376,7 +369,6 @@ "$id": 18, "$type": "EpubRemoteByteContentFile", "$content": "https://os.vers.one/EpubReader/fonts/glyphicons-halflings-regular.ttf", - "Url": "https://os.vers.one/EpubReader/fonts/glyphicons-halflings-regular.ttf", "Key": "https://os.vers.one/EpubReader/fonts/glyphicons-halflings-regular.ttf", "ContentType": "FONT_TRUETYPE", "ContentMimeType": "application/x-font-truetype" @@ -502,88 +494,88 @@ "$ref": 11 }, "Html": { - "Local": { - "chapter.html": { + "Local": [ + { "$ref": 10 }, - "toc.html": { + { "$ref": 11 } - }, - "Remote": { - "https://os.vers.one/EpubReader/toc.html": { + ], + "Remote": [ + { "$ref": 15 } - } + ] }, "Css": { - "Local": { - "styles.css": { + "Local": [ + { "$ref": 12 } - }, - "Remote": { - "https://os.vers.one/EpubReader/styles/docfx.css": { + ], + "Remote": [ + { "$ref": 16 } - } + ] }, "Images": { - "Local": { - "cover.jpg": { + "Local": [ + { "$ref": 13 } - }, - "Remote": { - "https://os.vers.one/EpubReader/images/logo.svg": { + ], + "Remote": [ + { "$ref": 17 } - } + ] }, "Fonts": { - "Local": { - "font.ttf": { + "Local": [ + { "$ref": 14 } - }, - "Remote": { - "https://os.vers.one/EpubReader/fonts/glyphicons-halflings-regular.ttf": { + ], + "Remote": [ + { "$ref": 18 } - } + ] }, "AllFiles": { - "Local": { - "chapter.html": { + "Local": [ + { "$ref": 10 }, - "toc.html": { + { "$ref": 11 }, - "styles.css": { + { "$ref": 12 }, - "cover.jpg": { + { "$ref": 13 }, - "font.ttf": { + { "$ref": 14 } - }, - "Remote": { - "https://os.vers.one/EpubReader/toc.html": { + ], + "Remote": [ + { "$ref": 15 }, - "https://os.vers.one/EpubReader/styles/docfx.css": { + { "$ref": 16 }, - "https://os.vers.one/EpubReader/images/logo.svg": { + { "$ref": 17 }, - "https://os.vers.one/EpubReader/fonts/glyphicons-halflings-regular.ttf": { + { "$ref": 18 } - } + ] } } } diff --git a/Source/VersOne.Epub.Test/Integration/TestCases/Malformed/InvalidManifestItems/EPUB2/testcases.json b/Source/VersOne.Epub.Test/Integration/TestCases/Malformed/InvalidManifestItems/EPUB2/testcases.json index 1503cd4..0ec1ec8 100644 --- a/Source/VersOne.Epub.Test/Integration/TestCases/Malformed/InvalidManifestItems/EPUB2/testcases.json +++ b/Source/VersOne.Epub.Test/Integration/TestCases/Malformed/InvalidManifestItems/EPUB2/testcases.json @@ -90,21 +90,21 @@ }, "Content": { "Html": { - "Local": { - "chapter1.html": { + "Local": [ + { "$ref": 1 } - } + ] }, "AllFiles": { - "Local": { - "chapter1.html": { + "Local": [ + { "$ref": 1 }, - "toc.ncx": { + { "$ref": 2 } - } + ] } } } diff --git a/Source/VersOne.Epub.Test/Integration/TestCases/Malformed/InvalidManifestItems/EPUB3/testcases.json b/Source/VersOne.Epub.Test/Integration/TestCases/Malformed/InvalidManifestItems/EPUB3/testcases.json index 32e93c3..1ec30c1 100644 --- a/Source/VersOne.Epub.Test/Integration/TestCases/Malformed/InvalidManifestItems/EPUB3/testcases.json +++ b/Source/VersOne.Epub.Test/Integration/TestCases/Malformed/InvalidManifestItems/EPUB3/testcases.json @@ -107,24 +107,24 @@ "$ref": 2 }, "Html": { - "Local": { - "chapter1.html": { + "Local": [ + { "$ref": 1 }, - "toc.html": { + { "$ref": 2 } - } + ] }, "AllFiles": { - "Local": { - "chapter1.html": { + "Local": [ + { "$ref": 1 }, - "toc.html": { + { "$ref": 2 } - } + ] } } } diff --git a/Source/VersOne.Epub.Test/Integration/TestCases/Malformed/MissingContentForNavigationPoint/testcases.json b/Source/VersOne.Epub.Test/Integration/TestCases/Malformed/MissingContentForNavigationPoint/testcases.json index 1054cdd..0a06303 100644 --- a/Source/VersOne.Epub.Test/Integration/TestCases/Malformed/MissingContentForNavigationPoint/testcases.json +++ b/Source/VersOne.Epub.Test/Integration/TestCases/Malformed/MissingContentForNavigationPoint/testcases.json @@ -48,7 +48,7 @@ "Type": "LINK", "Title": "Chapter 1", "Link": { - "ContentFileName": "chapter1.html", + "ContentFileUrl": "chapter1.html", "ContentFilePath": "Content/chapter1.html", "Anchor": null }, @@ -117,21 +117,21 @@ }, "Content": { "Html": { - "Local": { - "chapter1.html": { + "Local": [ + { "$ref": 1 } - } + ] }, "AllFiles": { - "Local": { - "chapter1.html": { + "Local": [ + { "$ref": 1 }, - "toc.ncx": { + { "$ref": 2 } - } + ] } } } diff --git a/Source/VersOne.Epub.Test/Integration/TestCases/Typical/EPUB2/testcases.json b/Source/VersOne.Epub.Test/Integration/TestCases/Typical/EPUB2/testcases.json index 6ba80e0..f2867b9 100644 --- a/Source/VersOne.Epub.Test/Integration/TestCases/Typical/EPUB2/testcases.json +++ b/Source/VersOne.Epub.Test/Integration/TestCases/Typical/EPUB2/testcases.json @@ -159,7 +159,7 @@ "Type": "LINK", "Title": "Chapter 1", "Link": { - "ContentFileName": "chapter1.html", + "ContentFileUrl": "chapter1.html", "ContentFilePath": "Content/chapter1.html", "Anchor": null }, @@ -171,7 +171,7 @@ "Type": "LINK", "Title": "Chapter 1.1", "Link": { - "ContentFileName": "chapter1.html", + "ContentFileUrl": "chapter1.html", "ContentFilePath": "Content/chapter1.html", "Anchor": "section-1" }, @@ -183,7 +183,7 @@ "Type": "LINK", "Title": "Chapter 1.2", "Link": { - "ContentFileName": "chapter1.html", + "ContentFileUrl": "chapter1.html", "ContentFilePath": "Content/chapter1.html", "Anchor": "section-2" }, @@ -197,7 +197,7 @@ "Type": "LINK", "Title": "Chapter 2", "Link": { - "ContentFileName": "chapter2.html", + "ContentFileUrl": "chapter2.html", "ContentFilePath": "Content/chapter2.html", "Anchor": null }, @@ -209,7 +209,7 @@ "Type": "LINK", "Title": "Chapter 3", "Link": { - "ContentFileName": "chapter3.html", + "ContentFileUrl": "chapter3.html", "ContentFilePath": "Content/chapter3.html", "Anchor": null }, @@ -698,103 +698,103 @@ "$ref": 7 }, "Html": { - "Local": { - "front.html": { + "Local": [ + { "$ref": 1 }, - "cover.html": { + { "$ref": 2 }, - "chapter1.html": { + { "$ref": 3 }, - "chapter2.html": { + { "$ref": 4 }, - "chapter3.html": { + { "$ref": 5 } - } + ] }, "Css": { - "Local": { - "styles.css": { + "Local": [ + { "$ref": 6 } - } + ] }, "Images": { - "Local": { - "cover.jpg": { + "Local": [ + { "$ref": 7 }, - "image.jpg": { + { "$ref": 8 } - } + ] }, "Fonts": { - "Local": { - "font.ttf": { + "Local": [ + { "$ref": 9 } - } + ] }, "Audio": { - "Local": { - "chapter1-audio.mp3": { + "Local": [ + { "$ref": 10 }, - "title.mp3": { + { "$ref": 11 } - } + ] }, "AllFiles": { - "Local": { - "front.html": { + "Local": [ + { "$ref": 1 }, - "cover.html": { + { "$ref": 2 }, - "chapter1.html": { + { "$ref": 3 }, - "chapter2.html": { + { "$ref": 4 }, - "chapter3.html": { + { "$ref": 5 }, - "styles.css": { + { "$ref": 6 }, - "cover.jpg": { + { "$ref": 7 }, - "image.jpg": { + { "$ref": 8 }, - "font.ttf": { + { "$ref": 9 }, - "chapter1-audio.mp3": { + { "$ref": 10 }, - "title.mp3": { + { "$ref": 11 }, - "chapter2.xml": { + { "$ref": 12 }, - "chapter3.xml": { + { "$ref": 13 }, - "toc.ncx": { + { "$ref": 14 } - } + ] } } } diff --git a/Source/VersOne.Epub.Test/Integration/TestCases/Typical/EPUB3/testcases.json b/Source/VersOne.Epub.Test/Integration/TestCases/Typical/EPUB3/testcases.json index a6b30ab..f8e3a31 100644 --- a/Source/VersOne.Epub.Test/Integration/TestCases/Typical/EPUB3/testcases.json +++ b/Source/VersOne.Epub.Test/Integration/TestCases/Typical/EPUB3/testcases.json @@ -197,7 +197,7 @@ "Type": "LINK", "Title": "Chapter 1", "Link": { - "ContentFileName": "chapter1.html", + "ContentFileUrl": "chapter1.html", "ContentFilePath": "Content/chapter1.html", "Anchor": null }, @@ -215,7 +215,7 @@ "Type": "LINK", "Title": "Chapter 2", "Link": { - "ContentFileName": "chapter2.html", + "ContentFileUrl": "chapter2.html", "ContentFilePath": "Content/chapter2.html", "Anchor": null }, @@ -227,7 +227,7 @@ "Type": "LINK", "Title": "Chapter 3", "Link": { - "ContentFileName": "chapter3.html", + "ContentFileUrl": "chapter3.html", "ContentFilePath": "Content/chapter3.html", "Anchor": null }, @@ -947,115 +947,115 @@ "$ref": 6 }, "Html": { - "Local": { - "front.html": { + "Local": [ + { "$ref": 1 }, - "cover.html": { + { "$ref": 2 }, - "chapter1.html": { + { "$ref": 3 }, - "chapter2.html": { + { "$ref": 4 }, - "chapter3.html": { + { "$ref": 5 }, - "toc.html": { + { "$ref": 6 } - } + ] }, "Css": { - "Local": { - "styles.css": { + "Local": [ + { "$ref": 7 } - } + ] }, "Images": { - "Local": { - "cover.jpg": { + "Local": [ + { "$ref": 8 }, - "image.jpg": { + { "$ref": 9 } - } + ] }, "Fonts": { - "Local": { - "font.ttf": { + "Local": [ + { "$ref": 10 } - } + ] }, "Audio": { - "Local": { - "chapter1-audio.mp3": { + "Local": [ + { "$ref": 11 }, - "title.mp3": { + { "$ref": 12 } - } + ] }, "AllFiles": { - "Local": { - "front.html": { + "Local": [ + { "$ref": 1 }, - "cover.html": { + { "$ref": 2 }, - "chapter1.html": { + { "$ref": 3 }, - "chapter2.html": { + { "$ref": 4 }, - "chapter3.html": { + { "$ref": 5 }, - "toc.html": { + { "$ref": 6 }, - "styles.css": { + { "$ref": 7 }, - "cover.jpg": { + { "$ref": 8 }, - "image.jpg": { + { "$ref": 9 }, - "font.ttf": { + { "$ref": 10 }, - "chapter1-audio.mp3": { + { "$ref": 11 }, - "title.mp3": { + { "$ref": 12 }, - "chapter1.smil": { + { "$ref": 13 }, - "chapter2.xml": { + { "$ref": 14 }, - "chapter3.xml": { + { "$ref": 15 }, - "book.atom": { + { "$ref": 16 }, - "toc.ncx": { + { "$ref": 17 } - } + ] } } } 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 ad39b25..88b6d80 100644 --- a/Source/VersOne.Epub.Test/Integration/TestCases/Workarounds/Xml11/testcases.json +++ b/Source/VersOne.Epub.Test/Integration/TestCases/Workarounds/Xml11/testcases.json @@ -75,11 +75,11 @@ }, "Content": { "AllFiles": { - "Local": { - "toc.ncx": { + "Local": [ + { "$ref": 1 } - } + ] } } } diff --git a/Source/VersOne.Epub.Test/Integration/Types/TestCase.cs b/Source/VersOne.Epub.Test/Integration/Types/TestCase.cs index 267e5d5..4b8215a 100644 --- a/Source/VersOne.Epub.Test/Integration/Types/TestCase.cs +++ b/Source/VersOne.Epub.Test/Integration/Types/TestCase.cs @@ -27,11 +27,11 @@ public TestCase(string name, EpubReaderOptions? options = null, List result = new(epubBook.Content.AllFiles.Local.Count + epubBook.Content.AllFiles.Remote.Count); - foreach (EpubContentFile epubContentFile in epubBook.Content.AllFiles.Local.Values) + foreach (EpubContentFile epubContentFile in epubBook.Content.AllFiles.Local) { result.Add(epubContentFile); } - foreach (EpubContentFile epubContentFile in epubBook.Content.AllFiles.Remote.Values) + foreach (EpubContentFile epubContentFile in epubBook.Content.AllFiles.Remote) { result.Add(epubContentFile); } diff --git a/Source/VersOne.Epub.Test/Unit/Content/EpubContentFileRefMetadataTests.cs b/Source/VersOne.Epub.Test/Unit/Content/Base/EpubContentFileRefMetadataTests.cs similarity index 95% rename from Source/VersOne.Epub.Test/Unit/Content/EpubContentFileRefMetadataTests.cs rename to Source/VersOne.Epub.Test/Unit/Content/Base/EpubContentFileRefMetadataTests.cs index 267e787..c574846 100644 --- a/Source/VersOne.Epub.Test/Unit/Content/EpubContentFileRefMetadataTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Content/Base/EpubContentFileRefMetadataTests.cs @@ -1,4 +1,4 @@ -namespace VersOne.Epub.Test.Unit.Content +namespace VersOne.Epub.Test.Unit.Content.Base { public class EpubContentFileRefMetadataTests { diff --git a/Source/VersOne.Epub.Test/Unit/Content/EpubContentFileRefTests.cs b/Source/VersOne.Epub.Test/Unit/Content/Base/EpubContentFileRefTests.cs similarity index 98% rename from Source/VersOne.Epub.Test/Unit/Content/EpubContentFileRefTests.cs rename to Source/VersOne.Epub.Test/Unit/Content/Base/EpubContentFileRefTests.cs index f5450a4..906611c 100644 --- a/Source/VersOne.Epub.Test/Unit/Content/EpubContentFileRefTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Content/Base/EpubContentFileRefTests.cs @@ -1,6 +1,6 @@ using VersOne.Epub.Test.Unit.Mocks; -namespace VersOne.Epub.Test.Unit.Content +namespace VersOne.Epub.Test.Unit.Content.Base { public class EpubContentFileRefTests { diff --git a/Source/VersOne.Epub.Test/Unit/Content/EpubContentFileTests.cs b/Source/VersOne.Epub.Test/Unit/Content/Base/EpubContentFileTests.cs similarity index 87% rename from Source/VersOne.Epub.Test/Unit/Content/EpubContentFileTests.cs rename to Source/VersOne.Epub.Test/Unit/Content/Base/EpubContentFileTests.cs index 30dfb12..26dfb90 100644 --- a/Source/VersOne.Epub.Test/Unit/Content/EpubContentFileTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Content/Base/EpubContentFileTests.cs @@ -1,4 +1,4 @@ -namespace VersOne.Epub.Test.Unit.Content +namespace VersOne.Epub.Test.Unit.Content.Base { public class EpubContentFileTests { @@ -46,7 +46,7 @@ public void LocalByteContentFileConstructorTest() [Fact(DisplayName = "Constructing a EpubRemoteTextContentFile instance with non-null parameters should succeed")] public void RemoteTextContentFileConstructorTest() { - EpubRemoteTextContentFile epubRemoteTextContentFile = new(REMOTE_TEXT_FILE_HREF, TEXT_FILE_CONTENT_TYPE, TEXT_FILE_CONTENT_MIME_TYPE, REMOTE_TEXT_FILE_HREF, TEXT_FILE_CONTENT); + EpubRemoteTextContentFile epubRemoteTextContentFile = new(REMOTE_TEXT_FILE_HREF, TEXT_FILE_CONTENT_TYPE, TEXT_FILE_CONTENT_MIME_TYPE, TEXT_FILE_CONTENT); Assert.Equal(REMOTE_TEXT_FILE_HREF, epubRemoteTextContentFile.Key); Assert.Equal(TEXT_FILE_CONTENT_TYPE, epubRemoteTextContentFile.ContentType); Assert.Equal(TEXT_FILE_CONTENT_MIME_TYPE, epubRemoteTextContentFile.ContentMimeType); @@ -59,7 +59,7 @@ public void RemoteTextContentFileConstructorTest() [Fact(DisplayName = "Constructing a EpubRemoteByteContentFile instance with non-null parameters should succeed")] public void RemoteByteContentFileConstructorTest() { - EpubRemoteByteContentFile epubRemoteByteContentFile = new(REMOTE_BYTE_FILE_HREF, BYTE_FILE_CONTENT_TYPE, BYTE_FILE_CONTENT_MIME_TYPE, REMOTE_BYTE_FILE_HREF, BYTE_FILE_CONTENT); + EpubRemoteByteContentFile epubRemoteByteContentFile = new(REMOTE_BYTE_FILE_HREF, BYTE_FILE_CONTENT_TYPE, BYTE_FILE_CONTENT_MIME_TYPE, BYTE_FILE_CONTENT); Assert.Equal(REMOTE_BYTE_FILE_HREF, epubRemoteByteContentFile.Key); Assert.Equal(BYTE_FILE_CONTENT_TYPE, epubRemoteByteContentFile.ContentType); Assert.Equal(BYTE_FILE_CONTENT_MIME_TYPE, epubRemoteByteContentFile.ContentMimeType); @@ -87,14 +87,14 @@ public void LocalByteContentFileConstructorWithNullKeyTest() public void RemoteTextContentFileConstructorWithNullKeyTest() { Assert.Throws(() => - new EpubRemoteTextContentFile(null!, TEXT_FILE_CONTENT_TYPE, TEXT_FILE_CONTENT_MIME_TYPE, REMOTE_TEXT_FILE_HREF, TEXT_FILE_CONTENT)); + new EpubRemoteTextContentFile(null!, TEXT_FILE_CONTENT_TYPE, TEXT_FILE_CONTENT_MIME_TYPE, TEXT_FILE_CONTENT)); } [Fact(DisplayName = "EpubRemoteByteContentFile constructor should throw ArgumentNullException if key parameter is null")] public void RemoteByteContentFileConstructorWithNullKeyTest() { Assert.Throws(() => - new EpubRemoteByteContentFile(null!, BYTE_FILE_CONTENT_TYPE, BYTE_FILE_CONTENT_MIME_TYPE, REMOTE_BYTE_FILE_HREF, BYTE_FILE_CONTENT)); + new EpubRemoteByteContentFile(null!, BYTE_FILE_CONTENT_TYPE, BYTE_FILE_CONTENT_MIME_TYPE, BYTE_FILE_CONTENT)); } [Fact(DisplayName = "EpubLocalTextContentFile constructor should throw ArgumentNullException if contentMimeType parameter is null")] @@ -115,14 +115,14 @@ public void LocalByteContentFileConstructorWithNullContentMimeTypeTest() public void RemoteTextContentFileConstructorWithNullContentMimeTypeTest() { Assert.Throws(() => - new EpubRemoteTextContentFile(REMOTE_TEXT_FILE_HREF, TEXT_FILE_CONTENT_TYPE, null!, REMOTE_TEXT_FILE_HREF, TEXT_FILE_CONTENT)); + new EpubRemoteTextContentFile(REMOTE_TEXT_FILE_HREF, TEXT_FILE_CONTENT_TYPE, null!, TEXT_FILE_CONTENT)); } [Fact(DisplayName = "EpubRemoteByteContentFile constructor should throw ArgumentNullException if contentMimeType parameter is null")] public void RemoteByteContentFileConstructorWithNullContentMimeTypeTest() { Assert.Throws(() => - new EpubRemoteByteContentFile(REMOTE_BYTE_FILE_HREF, BYTE_FILE_CONTENT_TYPE, null!, REMOTE_BYTE_FILE_HREF, BYTE_FILE_CONTENT)); + new EpubRemoteByteContentFile(REMOTE_BYTE_FILE_HREF, BYTE_FILE_CONTENT_TYPE, null!, BYTE_FILE_CONTENT)); } [Fact(DisplayName = "EpubLocalTextContentFile constructor should throw ArgumentNullException if filePath parameter is null")] @@ -139,20 +139,6 @@ public void LocalByteContentFileConstructorWithNullFilePathTest() new EpubLocalByteContentFile(LOCAL_BYTE_FILE_NAME, BYTE_FILE_CONTENT_TYPE, BYTE_FILE_CONTENT_MIME_TYPE, null!, BYTE_FILE_CONTENT)); } - [Fact(DisplayName = "EpubRemoteTextContentFile constructor should throw ArgumentNullException if url parameter is null")] - public void RemoteTextContentFileConstructorWithNullUrlTest() - { - Assert.Throws(() => - new EpubRemoteTextContentFile(REMOTE_TEXT_FILE_HREF, TEXT_FILE_CONTENT_TYPE, TEXT_FILE_CONTENT_MIME_TYPE, null!, TEXT_FILE_CONTENT)); - } - - [Fact(DisplayName = "EpubRemoteByteContentFile constructor should throw ArgumentNullException if url parameter is null")] - public void RemoteByteContentFileConstructorWithNullUrlTest() - { - Assert.Throws(() => - new EpubRemoteByteContentFile(REMOTE_BYTE_FILE_HREF, BYTE_FILE_CONTENT_TYPE, BYTE_FILE_CONTENT_MIME_TYPE, null!, BYTE_FILE_CONTENT)); - } - [Fact(DisplayName = "EpubLocalTextContentFile constructor should throw ArgumentNullException if content parameter is null")] public void LocalTextContentFileConstructorWithNullContentTest() { @@ -170,7 +156,7 @@ public void LocalByteContentFileConstructorWithNullContentTest() [Fact(DisplayName = "Constructing a EpubRemoteTextContentFile instance with null content parameter should succeed")] public void RemoteTextContentFileConstructorWithNullContentTest() { - EpubRemoteTextContentFile epubRemoteTextContentFile = new(REMOTE_TEXT_FILE_HREF, TEXT_FILE_CONTENT_TYPE, TEXT_FILE_CONTENT_MIME_TYPE, REMOTE_TEXT_FILE_HREF, null); + EpubRemoteTextContentFile epubRemoteTextContentFile = new(REMOTE_TEXT_FILE_HREF, TEXT_FILE_CONTENT_TYPE, TEXT_FILE_CONTENT_MIME_TYPE, null); Assert.Equal(REMOTE_TEXT_FILE_HREF, epubRemoteTextContentFile.Key); Assert.Equal(TEXT_FILE_CONTENT_TYPE, epubRemoteTextContentFile.ContentType); Assert.Equal(TEXT_FILE_CONTENT_MIME_TYPE, epubRemoteTextContentFile.ContentMimeType); @@ -183,7 +169,7 @@ public void RemoteTextContentFileConstructorWithNullContentTest() [Fact(DisplayName = "Constructing a EpubRemoteByteContentFile instance with null content parameter should succeed")] public void RemoteByteContentFileConstructorWithNullContentTest() { - EpubRemoteByteContentFile epubRemoteByteContentFile = new(REMOTE_BYTE_FILE_HREF, BYTE_FILE_CONTENT_TYPE, BYTE_FILE_CONTENT_MIME_TYPE, REMOTE_BYTE_FILE_HREF, null); + EpubRemoteByteContentFile epubRemoteByteContentFile = new(REMOTE_BYTE_FILE_HREF, BYTE_FILE_CONTENT_TYPE, BYTE_FILE_CONTENT_MIME_TYPE, null); Assert.Equal(REMOTE_BYTE_FILE_HREF, epubRemoteByteContentFile.Key); Assert.Equal(BYTE_FILE_CONTENT_TYPE, epubRemoteByteContentFile.ContentType); Assert.Equal(BYTE_FILE_CONTENT_MIME_TYPE, epubRemoteByteContentFile.ContentMimeType); diff --git a/Source/VersOne.Epub.Test/Unit/Content/Collections/EpubContentCollectionRefTests.cs b/Source/VersOne.Epub.Test/Unit/Content/Collections/EpubContentCollectionRefTests.cs new file mode 100644 index 0000000..0563748 --- /dev/null +++ b/Source/VersOne.Epub.Test/Unit/Content/Collections/EpubContentCollectionRefTests.cs @@ -0,0 +1,566 @@ +using System.Collections.ObjectModel; +using VersOne.Epub.Test.Comparers; +using VersOne.Epub.Test.Unit.Mocks; +using VersOne.Epub.Test.Unit.TestData; +using static VersOne.Epub.Test.Unit.TestData.TestEpubData; + +namespace VersOne.Epub.Test.Unit.Content.Collections +{ + public class EpubContentCollectionRefTests + { + private static ReadOnlyCollection Local + { + get + { + List list = new() + { + TestEpubContentRef.Chapter1FileRef, + TestEpubContentRef.Chapter2FileRef + }; + return list.AsReadOnly(); + } + } + + private static ReadOnlyCollection Remote + { + get + { + List list = new() + { + TestEpubContentRef.RemoteHtmlContentFileRef, + TestEpubContentRef.RemoteCssContentFileRef + }; + return list.AsReadOnly(); + } + } + + private static IEpubContentLoader ContentLoader => new TestEpubContentLoader(); + + [Fact(DisplayName = "Constructing a EpubContentCollectionRef instance with default parameters should succeed")] + public void ConstructorWithNonNullParametersTest() + { + EpubContentCollectionRef epubContentCollectionRef = new(); + Assert.NotNull(epubContentCollectionRef.Local); + Assert.Empty(epubContentCollectionRef.Local); + Assert.NotNull(epubContentCollectionRef.Remote); + Assert.Empty(epubContentCollectionRef.Remote); + } + + [Fact(DisplayName = "Constructing a EpubContentCollectionRef instance with null local parameter should succeed")] + public void ConstructorWithNullLocalParameterTest() + { + EpubContentCollectionRef epubContentCollectionRef = new(null, Remote); + Assert.NotNull(epubContentCollectionRef.Local); + Assert.Empty(epubContentCollectionRef.Local); + CompareRemoteTextRefReadOnlyCollections(Remote, epubContentCollectionRef.Remote); + } + + [Fact(DisplayName = "Constructing a EpubContentCollectionRef instance with null remote parameter should succeed")] + public void ConstructorWithNullRemoteParameterTest() + { + EpubContentCollectionRef epubContentCollectionRef = new(Local, null); + CompareLocalTextRefReadOnlyCollections(Local, epubContentCollectionRef.Local); + Assert.NotNull(epubContentCollectionRef.Remote); + Assert.Empty(epubContentCollectionRef.Remote); + } + + [Fact(DisplayName = "Constructor should throw EpubPackageException if local parameter contains content files with duplicate keys")] + public void ConstructorWithLocalDuplicateKeysTest() + { + string duplicateKey = CHAPTER1_FILE_NAME; + ReadOnlyCollection localWithDuplicateKeys = new + ( + new List() + { + new EpubLocalTextContentFileRef + ( + metadata: new EpubContentFileRefMetadata + ( + key: duplicateKey, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + filePath: CHAPTER1_FILE_PATH, + epubContentLoader: ContentLoader + ), + new EpubLocalTextContentFileRef + ( + metadata: new EpubContentFileRefMetadata + ( + key: duplicateKey, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + filePath: CHAPTER2_FILE_PATH, + epubContentLoader: ContentLoader + ) + } + ); + Assert.Throws(() => new EpubContentCollectionRef(localWithDuplicateKeys, Remote)); + } + + [Fact(DisplayName = "Constructor should throw EpubPackageException if local parameter contains content files with duplicate file paths")] + public void ConstructorWithLocalDuplicateFilePathsTest() + { + string duplicateFilePath = CHAPTER1_FILE_PATH; + ReadOnlyCollection localWithDuplicateFilePaths = new + ( + new List() + { + new EpubLocalTextContentFileRef + ( + metadata: new EpubContentFileRefMetadata + ( + key: CHAPTER1_FILE_NAME, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + filePath: duplicateFilePath, + epubContentLoader: ContentLoader + ), + new EpubLocalTextContentFileRef + ( + metadata: new EpubContentFileRefMetadata + ( + key: CHAPTER2_FILE_NAME, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + filePath: duplicateFilePath, + epubContentLoader: ContentLoader + ) + } + ); + Assert.Throws(() => new EpubContentCollectionRef(localWithDuplicateFilePaths, Remote)); + } + + [Fact(DisplayName = "Constructor should throw EpubPackageException if remote parameter contains content files with duplicate URLs")] + public void ConstructorWithRemoteDuplicateUrlsTest() + { + string duplicateKey = REMOTE_HTML_CONTENT_FILE_HREF; + ReadOnlyCollection remoteWithDuplicateKeys = new + ( + new List() + { + new EpubRemoteTextContentFileRef + ( + metadata: new EpubContentFileRefMetadata + ( + key: duplicateKey, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + epubContentLoader: ContentLoader + ), + new EpubRemoteTextContentFileRef + ( + metadata: new EpubContentFileRefMetadata + ( + key: duplicateKey, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + epubContentLoader: ContentLoader + ) + } + ); + Assert.Throws(() => new EpubContentCollectionRef(Local, remoteWithDuplicateKeys)); + } + + [Fact(DisplayName = "ContainsLocalFileRefWithKey should return true if the local file reference with the given key exists and false otherwise")] + public void ContainsLocalFileWithKeyWithNonNullKeyTest() + { + string existingKey = CHAPTER1_FILE_NAME; + string nonExistingKey = CHAPTER2_FILE_NAME; + ReadOnlyCollection local = new + ( + new List() + { + new EpubLocalTextContentFileRef + ( + metadata: new EpubContentFileRefMetadata + ( + key: existingKey, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + filePath: CHAPTER1_FILE_PATH, + epubContentLoader: ContentLoader + ) + } + ); + EpubContentCollectionRef epubContentCollectionRef = new(local, Remote); + Assert.True(epubContentCollectionRef.ContainsLocalFileRefWithKey(existingKey)); + Assert.False(epubContentCollectionRef.ContainsLocalFileRefWithKey(nonExistingKey)); + } + + [Fact(DisplayName = "ContainsLocalFileRefWithKey should throw ArgumentNullException if key argument is null")] + public void ContainsLocalFileWithKeyWithNullKeyTest() + { + EpubContentCollectionRef epubContentCollectionRef = new(Local, Remote); + Assert.Throws(() => epubContentCollectionRef.ContainsLocalFileRefWithKey(null!)); + } + + [Fact(DisplayName = "GetLocalFileRefByKey should return the local file reference with the given key if it exists")] + public void GetLocalFileByKeyWithExistingKeyTest() + { + string existingKey = CHAPTER1_FILE_NAME; + EpubLocalTextContentFileRef expectedFileRef = new + ( + metadata: new EpubContentFileRefMetadata + ( + key: existingKey, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + filePath: CHAPTER1_FILE_PATH, + epubContentLoader: ContentLoader + ); + ReadOnlyCollection local = new + ( + new List() + { + expectedFileRef + } + ); + EpubContentCollectionRef epubContentCollectionRef = new(local, Remote); + EpubLocalTextContentFileRef actualFileRef = epubContentCollectionRef.GetLocalFileRefByKey(existingKey); + EpubContentRefComparer.CompareEpubLocalContentFileRefs(expectedFileRef, actualFileRef); + } + + [Fact(DisplayName = "GetLocalFileRefByKey should throw EpubContentCollectionRefException if the local file reference with the given key doesn't exist")] + public void GetLocalFileByKeyWithNonExistingKeyTest() + { + string nonExistingKey = CHAPTER2_FILE_NAME; + ReadOnlyCollection local = new + ( + new List() + { + new EpubLocalTextContentFileRef + ( + metadata: new EpubContentFileRefMetadata + ( + key: CHAPTER1_FILE_NAME, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + filePath: CHAPTER1_FILE_PATH, + epubContentLoader: ContentLoader + ) + } + ); + EpubContentCollectionRef epubContentCollectionRef = new(local, Remote); + Assert.Throws(() => epubContentCollectionRef.GetLocalFileRefByKey(nonExistingKey)); + } + + [Fact(DisplayName = "GetLocalFileRefByKey should throw ArgumentNullException if key argument is null")] + public void GetLocalFileByKeyWithNullKeyTest() + { + EpubContentCollectionRef epubContentCollectionRef = new(Local, Remote); + Assert.Throws(() => epubContentCollectionRef.GetLocalFileRefByKey(null!)); + } + + [Fact(DisplayName = "TryGetLocalFileRefByKey should return true with the local file reference with the given key if it exists and false with null reference otherwise")] + public void TryGetLocalFileByKeyWithNonNullKeyTest() + { + string existingKey = CHAPTER1_FILE_NAME; + string nonExistingKey = CHAPTER2_FILE_NAME; + EpubLocalTextContentFileRef expectedFileRef = new + ( + metadata: new EpubContentFileRefMetadata + ( + key: existingKey, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + filePath: CHAPTER1_FILE_PATH, + epubContentLoader: ContentLoader + ); + ReadOnlyCollection local = new + ( + new List() + { + expectedFileRef + } + ); + EpubContentCollectionRef epubContentCollectionRef = new(local, Remote); + Assert.True(epubContentCollectionRef.TryGetLocalFileRefByKey(existingKey, out EpubLocalTextContentFileRef actualFileRefForExistingKey)); + EpubContentRefComparer.CompareEpubLocalContentFileRefs(expectedFileRef, actualFileRefForExistingKey); + Assert.False(epubContentCollectionRef.TryGetLocalFileRefByKey(nonExistingKey, out EpubLocalTextContentFileRef actualFileRefForNonExistingKey)); + Assert.Null(actualFileRefForNonExistingKey); + } + + [Fact(DisplayName = "TryGetLocalFileRefByKey should throw ArgumentNullException if key argument is null")] + public void TryGetLocalFileByKeyWithNullKeyTest() + { + EpubContentCollectionRef epubContentCollectionRef = new(Local, Remote); + Assert.Throws(() => epubContentCollectionRef.TryGetLocalFileRefByKey(null!, out _)); + } + + [Fact(DisplayName = "ContainsLocalFileRefWithFilePath should return true if the local file reference with the given file path exists and false otherwise")] + public void ContainsLocalFileWithFilePathWithNonNullFilePathTest() + { + string existingFilePath = CHAPTER1_FILE_PATH; + string nonExistingFilePath = CHAPTER2_FILE_PATH; + ReadOnlyCollection local = new + ( + new List() + { + new EpubLocalTextContentFileRef + ( + metadata: new EpubContentFileRefMetadata + ( + key: CHAPTER1_FILE_NAME, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + filePath: existingFilePath, + epubContentLoader: ContentLoader + ) + } + ); + EpubContentCollectionRef epubContentCollectionRef = new(local, Remote); + Assert.True(epubContentCollectionRef.ContainsLocalFileRefWithFilePath(existingFilePath)); + Assert.False(epubContentCollectionRef.ContainsLocalFileRefWithFilePath(nonExistingFilePath)); + } + + [Fact(DisplayName = "ContainsLocalFileRefWithFilePath should throw ArgumentNullException if filePath argument is null")] + public void ContainsLocalFileWithFilePathWithNullFilePathTest() + { + EpubContentCollectionRef epubContentCollectionRef = new(Local, Remote); + Assert.Throws(() => epubContentCollectionRef.ContainsLocalFileRefWithFilePath(null!)); + } + + [Fact(DisplayName = "GetLocalFileRefByFilePath should return the local file reference with the given file path if it exists")] + public void GetLocalFileByFilePathWithExistingFilePathTest() + { + string existingFilePath = CHAPTER1_FILE_PATH; + EpubLocalTextContentFileRef expectedFileRef = new + ( + metadata: new EpubContentFileRefMetadata + ( + key: CHAPTER1_FILE_NAME, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + filePath: existingFilePath, + epubContentLoader: ContentLoader + ); + ReadOnlyCollection local = new + ( + new List() + { + expectedFileRef + } + ); + EpubContentCollectionRef epubContentCollectionRef = new(local, Remote); + EpubLocalTextContentFileRef actualFileRef = epubContentCollectionRef.GetLocalFileRefByFilePath(existingFilePath); + EpubContentRefComparer.CompareEpubLocalContentFileRefs(expectedFileRef, actualFileRef); + } + + [Fact(DisplayName = "GetLocalFileRefByFilePath should throw EpubContentCollectionRefException if the local file reference with the given file path doesn't exist")] + public void GetLocalFileByFilePathWithNonExistingFilePathTest() + { + string nonExistingFilePath = CHAPTER2_FILE_PATH; + ReadOnlyCollection local = new + ( + new List() + { + new EpubLocalTextContentFileRef + ( + metadata: new EpubContentFileRefMetadata + ( + key: CHAPTER1_FILE_NAME, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + filePath: CHAPTER1_FILE_PATH, + epubContentLoader: ContentLoader + ) + } + ); + EpubContentCollectionRef epubContentCollectionRef = new(local, Remote); + Assert.Throws(() => epubContentCollectionRef.GetLocalFileRefByFilePath(nonExistingFilePath)); + } + + [Fact(DisplayName = "GetLocalFileRefByFilePath should throw ArgumentNullException if filePath argument is null")] + public void GetLocalFileByFilePathWithNullFilePathTest() + { + EpubContentCollectionRef epubContentCollectionRef = new(Local, Remote); + Assert.Throws(() => epubContentCollectionRef.GetLocalFileRefByFilePath(null!)); + } + + [Fact(DisplayName = "TryGetLocalFileRefByFilePath should return true with the local file reference with the given file path if it exists and false with null reference otherwise")] + public void TryGetLocalFileByFilePathWithNonNullFilePathTest() + { + string existingFilePath = CHAPTER1_FILE_PATH; + string nonExistingFilePath = CHAPTER2_FILE_PATH; + EpubLocalTextContentFileRef expectedFileRef = new + ( + metadata: new EpubContentFileRefMetadata + ( + key: CHAPTER1_FILE_NAME, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + filePath: existingFilePath, + epubContentLoader: ContentLoader + ); + ReadOnlyCollection local = new + ( + new List() + { + expectedFileRef + } + ); + EpubContentCollectionRef epubContentCollectionRef = new(local, Remote); + Assert.True(epubContentCollectionRef.TryGetLocalFileRefByFilePath(existingFilePath, out EpubLocalTextContentFileRef actualFileRefForExistingFilePath)); + EpubContentRefComparer.CompareEpubLocalContentFileRefs(expectedFileRef, actualFileRefForExistingFilePath); + Assert.False(epubContentCollectionRef.TryGetLocalFileRefByFilePath(nonExistingFilePath, out EpubLocalTextContentFileRef actualFileRefForNonExistingFilePath)); + Assert.Null(actualFileRefForNonExistingFilePath); + } + + [Fact(DisplayName = "TryGetLocalFileRefByFilePath should throw ArgumentNullException if filePath argument is null")] + public void TryGetLocalFileByFilePathWithNullFilePathTest() + { + EpubContentCollectionRef epubContentCollectionRef = new(Local, Remote); + Assert.Throws(() => epubContentCollectionRef.TryGetLocalFileRefByFilePath(null!, out _)); + } + + [Fact(DisplayName = "ContainsRemoteFileRefWithUrl should return true if the remote file reference with the given URL exists and false otherwise")] + public void ContainsRemoteFileWithUrlWithNonNullUrlTest() + { + string existingUrl = REMOTE_HTML_CONTENT_FILE_HREF; + string nonExistingUrl = REMOTE_CSS_CONTENT_FILE_HREF; + ReadOnlyCollection remote = new + ( + new List() + { + new EpubRemoteTextContentFileRef + ( + metadata: new EpubContentFileRefMetadata + ( + key: existingUrl, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + epubContentLoader: ContentLoader + ) + } + ); + EpubContentCollectionRef epubContentCollectionRef = new(Local, remote); + Assert.True(epubContentCollectionRef.ContainsRemoteFileRefWithUrl(existingUrl)); + Assert.False(epubContentCollectionRef.ContainsRemoteFileRefWithUrl(nonExistingUrl)); + } + + [Fact(DisplayName = "ContainsRemoteFileRefWithUrl should throw ArgumentNullException if url argument is null")] + public void ContainsRemoteFileWithUrlWithNullUrlTest() + { + EpubContentCollectionRef epubContentCollectionRef = new(Local, Remote); + Assert.Throws(() => epubContentCollectionRef.ContainsRemoteFileRefWithUrl(null!)); + } + + [Fact(DisplayName = "GetRemoteFileRefByUrl should return the remote file reference with the given URL if it exists")] + public void GetRemoteFileByUrlWithExistingUrlTest() + { + string existingUrl = REMOTE_HTML_CONTENT_FILE_HREF; + EpubRemoteTextContentFileRef expectedFileRef = new + ( + metadata: new EpubContentFileRefMetadata + ( + key: existingUrl, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + epubContentLoader: ContentLoader + ); + ReadOnlyCollection remote = new + ( + new List() + { + expectedFileRef + } + ); + EpubContentCollectionRef epubContentCollectionRef = new(Local, remote); + EpubRemoteTextContentFileRef actualFileRef = epubContentCollectionRef.GetRemoteFileRefByUrl(existingUrl); + EpubContentRefComparer.CompareEpubRemoteContentFileRefs(expectedFileRef, actualFileRef); + } + + [Fact(DisplayName = "GetRemoteFileRefByUrl should throw EpubContentCollectionRefException if the remote file reference with the given URL doesn't exist")] + public void GetRemoteFileByUrlWithNonExistingUrlTest() + { + string nonExistingUrl = REMOTE_CSS_CONTENT_FILE_HREF; + ReadOnlyCollection remote = new + ( + new List() + { + new EpubRemoteTextContentFileRef + ( + metadata: new EpubContentFileRefMetadata + ( + key: REMOTE_HTML_CONTENT_FILE_HREF, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + epubContentLoader: ContentLoader + ) + } + ); + EpubContentCollectionRef epubContentCollectionRef = new(Local, remote); + Assert.Throws(() => epubContentCollectionRef.GetRemoteFileRefByUrl(nonExistingUrl)); + } + + [Fact(DisplayName = "GetRemoteFileRefByUrl should throw ArgumentNullException if url argument is null")] + public void GetRemoteFileByUrlWithNullUrlTest() + { + EpubContentCollectionRef epubContentCollectionRef = new(Local, Remote); + Assert.Throws(() => epubContentCollectionRef.GetRemoteFileRefByUrl(null!)); + } + + [Fact(DisplayName = "TryGetRemoteFileRefByUrl should return true with the remote file reference with the given URL if it exists and false with null reference otherwise")] + public void TryGetRemoteFileByUrlWithNonNullUrlTest() + { + string existingUrl = REMOTE_HTML_CONTENT_FILE_HREF; + string nonExistingUrl = REMOTE_CSS_CONTENT_FILE_HREF; + EpubRemoteTextContentFileRef expectedFileRef = new + ( + metadata: new EpubContentFileRefMetadata + ( + key: existingUrl, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE + ), + epubContentLoader: ContentLoader + ); + ReadOnlyCollection remote = new + ( + new List() + { + expectedFileRef + } + ); + EpubContentCollectionRef epubContentCollectionRef = new(Local, remote); + Assert.True(epubContentCollectionRef.TryGetRemoteFileRefByUrl(existingUrl, out EpubRemoteTextContentFileRef actualFileRefForExistingUrl)); + EpubContentRefComparer.CompareEpubRemoteContentFileRefs(expectedFileRef, actualFileRefForExistingUrl); + Assert.False(epubContentCollectionRef.TryGetRemoteFileRefByUrl(nonExistingUrl, out EpubRemoteTextContentFileRef actualFileRefForNonExistingUrl)); + Assert.Null(actualFileRefForNonExistingUrl); + } + + [Fact(DisplayName = "TryGetRemoteFileRefByUrl should throw ArgumentNullException if url argument is null")] + public void TryGetRemoteFileByUrlWithNullUrlTest() + { + EpubContentCollectionRef epubContentCollectionRef = new(Local, Remote); + Assert.Throws(() => epubContentCollectionRef.TryGetRemoteFileRefByUrl(null!, out _)); + } + + private static void CompareLocalTextRefReadOnlyCollections(ReadOnlyCollection expected, ReadOnlyCollection actual) + { + CollectionComparer.CompareCollections(expected, actual, EpubContentRefComparer.CompareEpubLocalContentFileRefs); + } + + private static void CompareRemoteTextRefReadOnlyCollections(ReadOnlyCollection expected, ReadOnlyCollection actual) + { + CollectionComparer.CompareCollections(expected, actual, EpubContentRefComparer.CompareEpubRemoteContentFileRefs); + } + } +} diff --git a/Source/VersOne.Epub.Test/Unit/Content/Collections/EpubContentCollectionTests.cs b/Source/VersOne.Epub.Test/Unit/Content/Collections/EpubContentCollectionTests.cs new file mode 100644 index 0000000..e605c8c --- /dev/null +++ b/Source/VersOne.Epub.Test/Unit/Content/Collections/EpubContentCollectionTests.cs @@ -0,0 +1,509 @@ +using System.Collections.ObjectModel; +using VersOne.Epub.Test.Comparers; +using VersOne.Epub.Test.Unit.TestData; +using static VersOne.Epub.Test.Unit.TestData.TestEpubData; + +namespace VersOne.Epub.Test.Unit.Content.Collections +{ + public class EpubContentCollectionTests + { + private static ReadOnlyCollection Local + { + get + { + List list = new() + { + TestEpubContent.Chapter1File, + TestEpubContent.Chapter2File + }; + return list.AsReadOnly(); + } + } + + private static ReadOnlyCollection Remote + { + get + { + List list = new() + { + TestEpubContent.RemoteHtmlContentFile, + TestEpubContent.RemoteCssContentFile + }; + return list.AsReadOnly(); + } + } + + [Fact(DisplayName = "Constructing a EpubContentCollection instance with default parameters should succeed")] + public void ConstructorWithNonNullParametersTest() + { + EpubContentCollection epubContentCollection = new(); + Assert.NotNull(epubContentCollection.Local); + Assert.Empty(epubContentCollection.Local); + Assert.NotNull(epubContentCollection.Remote); + Assert.Empty(epubContentCollection.Remote); + } + + [Fact(DisplayName = "Constructing a EpubContentCollection instance with null local parameter should succeed")] + public void ConstructorWithNullLocalParameterTest() + { + EpubContentCollection epubContentCollection = new(null, Remote); + Assert.NotNull(epubContentCollection.Local); + Assert.Empty(epubContentCollection.Local); + CompareRemoteTextReadOnlyCollections(Remote, epubContentCollection.Remote); + } + + [Fact(DisplayName = "Constructing a EpubContentCollection instance with null remote parameter should succeed")] + public void ConstructorWithNullRemoteParameterTest() + { + EpubContentCollection epubContentCollection = new(Local, null); + CompareLocalTextReadOnlyCollections(Local, epubContentCollection.Local); + Assert.NotNull(epubContentCollection.Remote); + Assert.Empty(epubContentCollection.Remote); + } + + [Fact(DisplayName = "Constructor should throw EpubPackageException if local parameter contains content files with duplicate keys")] + public void ConstructorWithLocalDuplicateKeysTest() + { + string duplicateKey = CHAPTER1_FILE_NAME; + ReadOnlyCollection localWithDuplicateKeys = new + ( + new List() + { + new EpubLocalTextContentFile + ( + key: duplicateKey, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE, + filePath: CHAPTER1_FILE_PATH, + content: TestEpubFiles.CHAPTER1_FILE_CONTENT + ), + new EpubLocalTextContentFile + ( + key: duplicateKey, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE, + filePath: CHAPTER2_FILE_PATH, + content: TestEpubFiles.CHAPTER2_FILE_CONTENT + ) + } + ); + Assert.Throws(() => new EpubContentCollection(localWithDuplicateKeys, Remote)); + } + + [Fact(DisplayName = "Constructor should throw EpubPackageException if local parameter contains content files with duplicate file paths")] + public void ConstructorWithLocalDuplicateFilePathsTest() + { + string duplicateFilePath = CHAPTER1_FILE_PATH; + ReadOnlyCollection localWithDuplicateFilePaths = new + ( + new List() + { + new EpubLocalTextContentFile + ( + key: CHAPTER1_FILE_NAME, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE, + filePath: duplicateFilePath, + content: TestEpubFiles.CHAPTER1_FILE_CONTENT + ), + new EpubLocalTextContentFile + ( + key: CHAPTER2_FILE_NAME, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE, + filePath: duplicateFilePath, + content: TestEpubFiles.CHAPTER2_FILE_CONTENT + ) + } + ); + Assert.Throws(() => new EpubContentCollection(localWithDuplicateFilePaths, Remote)); + } + + [Fact(DisplayName = "Constructor should throw EpubPackageException if remote parameter contains content files with duplicate URLs")] + public void ConstructorWithRemoteDuplicateUrlsTest() + { + string duplicateKey = REMOTE_HTML_CONTENT_FILE_HREF; + ReadOnlyCollection remoteWithDuplicateKeys = new + ( + new List() + { + new EpubRemoteTextContentFile + ( + key: duplicateKey, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE, + content: TestEpubFiles.REMOTE_HTML_FILE_CONTENT + ), + new EpubRemoteTextContentFile + ( + key: duplicateKey, + contentType: CSS_CONTENT_TYPE, + contentMimeType: CSS_CONTENT_MIME_TYPE, + content: TestEpubFiles.REMOTE_CSS_FILE_CONTENT + ) + } + ); + Assert.Throws(() => new EpubContentCollection(Local, remoteWithDuplicateKeys)); + } + + [Fact(DisplayName = "ContainsLocalFileWithKey should return true if the local file with the given key exists and false otherwise")] + public void ContainsLocalFileWithKeyWithNonNullKeyTest() + { + string existingKey = CHAPTER1_FILE_NAME; + string nonExistingKey = CHAPTER2_FILE_NAME; + ReadOnlyCollection local = new + ( + new List() + { + new EpubLocalTextContentFile + ( + key: existingKey, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE, + filePath: CHAPTER1_FILE_PATH, + content: TestEpubFiles.CHAPTER1_FILE_CONTENT + ) + } + ); + EpubContentCollection epubContentCollection = new(local, Remote); + Assert.True(epubContentCollection.ContainsLocalFileWithKey(existingKey)); + Assert.False(epubContentCollection.ContainsLocalFileWithKey(nonExistingKey)); + } + + [Fact(DisplayName = "ContainsLocalFileWithKey should throw ArgumentNullException if key argument is null")] + public void ContainsLocalFileWithKeyWithNullKeyTest() + { + EpubContentCollection epubContentCollection = new(Local, Remote); + Assert.Throws(() => epubContentCollection.ContainsLocalFileWithKey(null!)); + } + + [Fact(DisplayName = "GetLocalFileByKey should return the local file with the given key if it exists")] + public void GetLocalFileByKeyWithExistingKeyTest() + { + string existingKey = CHAPTER1_FILE_NAME; + EpubLocalTextContentFile expectedFile = new + ( + key: existingKey, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE, + filePath: CHAPTER1_FILE_PATH, + content: TestEpubFiles.CHAPTER1_FILE_CONTENT + ); + ReadOnlyCollection local = new + ( + new List() + { + expectedFile + } + ); + EpubContentCollection epubContentCollection = new(local, Remote); + EpubLocalTextContentFile actualFile = epubContentCollection.GetLocalFileByKey(existingKey); + EpubContentComparer.CompareEpubLocalTextContentFiles(expectedFile, actualFile); + } + + [Fact(DisplayName = "GetLocalFileByKey should throw EpubContentCollectionException if the local file with the given key doesn't exist")] + public void GetLocalFileByKeyWithNonExistingKeyTest() + { + string nonExistingKey = CHAPTER2_FILE_NAME; + ReadOnlyCollection local = new + ( + new List() + { + new EpubLocalTextContentFile + ( + key: CHAPTER1_FILE_NAME, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE, + filePath: CHAPTER1_FILE_PATH, + content: TestEpubFiles.CHAPTER1_FILE_CONTENT + ) + } + ); + EpubContentCollection epubContentCollection = new(local, Remote); + Assert.Throws(() => epubContentCollection.GetLocalFileByKey(nonExistingKey)); + } + + [Fact(DisplayName = "GetLocalFileByKey should throw ArgumentNullException if key argument is null")] + public void GetLocalFileByKeyWithNullKeyTest() + { + EpubContentCollection epubContentCollection = new(Local, Remote); + Assert.Throws(() => epubContentCollection.GetLocalFileByKey(null!)); + } + + [Fact(DisplayName = "TryGetLocalFileByKey should return true with the local file with the given key if it exists and false with null reference otherwise")] + public void TryGetLocalFileByKeyWithNonNullKeyTest() + { + string existingKey = CHAPTER1_FILE_NAME; + string nonExistingKey = CHAPTER2_FILE_NAME; + EpubLocalTextContentFile expectedFile = new + ( + key: existingKey, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE, + filePath: CHAPTER1_FILE_PATH, + content: TestEpubFiles.CHAPTER1_FILE_CONTENT + ); + ReadOnlyCollection local = new + ( + new List() + { + expectedFile + } + ); + EpubContentCollection epubContentCollection = new(local, Remote); + Assert.True(epubContentCollection.TryGetLocalFileByKey(existingKey, out EpubLocalTextContentFile actualFileForExistingKey)); + EpubContentComparer.CompareEpubLocalTextContentFiles(expectedFile, actualFileForExistingKey); + Assert.False(epubContentCollection.TryGetLocalFileByKey(nonExistingKey, out EpubLocalTextContentFile actualFileForNonExistingKey)); + Assert.Null(actualFileForNonExistingKey); + } + + [Fact(DisplayName = "TryGetLocalFileByKey should throw ArgumentNullException if key argument is null")] + public void TryGetLocalFileByKeyWithNullKeyTest() + { + EpubContentCollection epubContentCollection = new(Local, Remote); + Assert.Throws(() => epubContentCollection.TryGetLocalFileByKey(null!, out _)); + } + + [Fact(DisplayName = "ContainsLocalFileWithFilePath should return true if the local file with the given file path exists and false otherwise")] + public void ContainsLocalFileWithFilePathWithNonNullFilePathTest() + { + string existingFilePath = CHAPTER1_FILE_PATH; + string nonExistingFilePath = CHAPTER2_FILE_PATH; + ReadOnlyCollection local = new + ( + new List() + { + new EpubLocalTextContentFile + ( + key: CHAPTER1_FILE_NAME, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE, + filePath: existingFilePath, + content: TestEpubFiles.CHAPTER1_FILE_CONTENT + ) + } + ); + EpubContentCollection epubContentCollection = new(local, Remote); + Assert.True(epubContentCollection.ContainsLocalFileWithFilePath(existingFilePath)); + Assert.False(epubContentCollection.ContainsLocalFileWithFilePath(nonExistingFilePath)); + } + + [Fact(DisplayName = "ContainsLocalFileWithFilePath should throw ArgumentNullException if filePath argument is null")] + public void ContainsLocalFileWithFilePathWithNullFilePathTest() + { + EpubContentCollection epubContentCollection = new(Local, Remote); + Assert.Throws(() => epubContentCollection.ContainsLocalFileWithFilePath(null!)); + } + + [Fact(DisplayName = "GetLocalFileByFilePath should return the local file with the given file path if it exists")] + public void GetLocalFileByFilePathWithExistingFilePathTest() + { + string existingFilePath = CHAPTER1_FILE_PATH; + EpubLocalTextContentFile expectedFile = new + ( + key: CHAPTER1_FILE_NAME, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE, + filePath: existingFilePath, + content: TestEpubFiles.CHAPTER1_FILE_CONTENT + ); + ReadOnlyCollection local = new + ( + new List() + { + expectedFile + } + ); + EpubContentCollection epubContentCollection = new(local, Remote); + EpubLocalTextContentFile actualFile = epubContentCollection.GetLocalFileByFilePath(existingFilePath); + EpubContentComparer.CompareEpubLocalTextContentFiles(expectedFile, actualFile); + } + + [Fact(DisplayName = "GetLocalFileByFilePath should throw EpubContentCollectionException if the local file with the given file path doesn't exist")] + public void GetLocalFileByFilePathWithNonExistingFilePathTest() + { + string nonExistingFilePath = CHAPTER2_FILE_PATH; + ReadOnlyCollection local = new + ( + new List() + { + new EpubLocalTextContentFile + ( + key: CHAPTER1_FILE_NAME, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE, + filePath: CHAPTER1_FILE_PATH, + content: TestEpubFiles.CHAPTER1_FILE_CONTENT + ) + } + ); + EpubContentCollection epubContentCollection = new(local, Remote); + Assert.Throws(() => epubContentCollection.GetLocalFileByFilePath(nonExistingFilePath)); + } + + [Fact(DisplayName = "GetLocalFileByFilePath should throw ArgumentNullException if filePath argument is null")] + public void GetLocalFileByFilePathWithNullFilePathTest() + { + EpubContentCollection epubContentCollection = new(Local, Remote); + Assert.Throws(() => epubContentCollection.GetLocalFileByFilePath(null!)); + } + + [Fact(DisplayName = "TryGetLocalFileByFilePath should return true with the local file with the given file path if it exists and false with null reference otherwise")] + public void TryGetLocalFileByFilePathWithNonNullFilePathTest() + { + string existingFilePath = CHAPTER1_FILE_PATH; + string nonExistingFilePath = CHAPTER2_FILE_PATH; + EpubLocalTextContentFile expectedFile = new + ( + key: CHAPTER1_FILE_NAME, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE, + filePath: existingFilePath, + content: TestEpubFiles.CHAPTER1_FILE_CONTENT + ); + ReadOnlyCollection local = new + ( + new List() + { + expectedFile + } + ); + EpubContentCollection epubContentCollection = new(local, Remote); + Assert.True(epubContentCollection.TryGetLocalFileByFilePath(existingFilePath, out EpubLocalTextContentFile actualFileForExistingFilePath)); + EpubContentComparer.CompareEpubLocalTextContentFiles(expectedFile, actualFileForExistingFilePath); + Assert.False(epubContentCollection.TryGetLocalFileByFilePath(nonExistingFilePath, out EpubLocalTextContentFile actualFileForNonExistingFilePath)); + Assert.Null(actualFileForNonExistingFilePath); + } + + [Fact(DisplayName = "TryGetLocalFileByFilePath should throw ArgumentNullException if filePath argument is null")] + public void TryGetLocalFileByFilePathWithNullFilePathTest() + { + EpubContentCollection epubContentCollection = new(Local, Remote); + Assert.Throws(() => epubContentCollection.TryGetLocalFileByFilePath(null!, out _)); + } + + [Fact(DisplayName = "ContainsRemoteFileWithUrl should return true if the remote file with the given URL exists and false otherwise")] + public void ContainsRemoteFileWithUrlWithNonNullUrlTest() + { + string existingUrl = REMOTE_HTML_CONTENT_FILE_HREF; + string nonExistingUrl = REMOTE_CSS_CONTENT_FILE_HREF; + ReadOnlyCollection remote = new + ( + new List() + { + new EpubRemoteTextContentFile + ( + 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)); + Assert.False(epubContentCollection.ContainsRemoteFileWithUrl(nonExistingUrl)); + } + + [Fact(DisplayName = "ContainsRemoteFileWithUrl should throw ArgumentNullException if url argument is null")] + public void ContainsRemoteFileWithUrlWithNullUrlTest() + { + EpubContentCollection epubContentCollection = new(Local, Remote); + Assert.Throws(() => epubContentCollection.ContainsRemoteFileWithUrl(null!)); + } + + [Fact(DisplayName = "GetRemoteFileByUrl should return the remote file with the given URL if it exists")] + public void GetRemoteFileByUrlWithExistingUrlTest() + { + string existingUrl = REMOTE_HTML_CONTENT_FILE_HREF; + EpubRemoteTextContentFile expectedFile = new + ( + key: existingUrl, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE, + content: TestEpubFiles.REMOTE_HTML_FILE_CONTENT + ); + ReadOnlyCollection remote = new + ( + new List() + { + expectedFile + } + ); + EpubContentCollection epubContentCollection = new(Local, remote); + EpubRemoteTextContentFile actualFile = epubContentCollection.GetRemoteFileByUrl(existingUrl); + EpubContentComparer.CompareEpubRemoteTextContentFiles(expectedFile, actualFile); + } + + [Fact(DisplayName = "GetRemoteFileByUrl should throw EpubContentCollectionException if the remote file with the given URL doesn't exist")] + public void GetRemoteFileByUrlWithNonExistingUrlTest() + { + string nonExistingUrl = REMOTE_CSS_CONTENT_FILE_HREF; + ReadOnlyCollection remote = new + ( + new List() + { + new EpubRemoteTextContentFile + ( + 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)); + } + + [Fact(DisplayName = "GetRemoteFileByUrl should throw ArgumentNullException if url argument is null")] + public void GetRemoteFileByUrlWithNullUrlTest() + { + EpubContentCollection epubContentCollection = new(Local, Remote); + Assert.Throws(() => epubContentCollection.GetRemoteFileByUrl(null!)); + } + + [Fact(DisplayName = "TryGetRemoteFileByUrl should return true with the remote file with the given URL if it exists and false with null reference otherwise")] + public void TryGetRemoteFileByUrlWithNonNullUrlTest() + { + string existingUrl = REMOTE_HTML_CONTENT_FILE_HREF; + string nonExistingUrl = REMOTE_CSS_CONTENT_FILE_HREF; + EpubRemoteTextContentFile expectedFile = new + ( + key: existingUrl, + contentType: HTML_CONTENT_TYPE, + contentMimeType: HTML_CONTENT_MIME_TYPE, + content: TestEpubFiles.REMOTE_HTML_FILE_CONTENT + ); + ReadOnlyCollection remote = new + ( + new List() + { + expectedFile + } + ); + EpubContentCollection epubContentCollection = new(Local, remote); + Assert.True(epubContentCollection.TryGetRemoteFileByUrl(existingUrl, out EpubRemoteTextContentFile actualFileForExistingUrl)); + EpubContentComparer.CompareEpubRemoteTextContentFiles(expectedFile, actualFileForExistingUrl); + Assert.False(epubContentCollection.TryGetRemoteFileByUrl(nonExistingUrl, out EpubRemoteTextContentFile actualFileForNonExistingUrl)); + Assert.Null(actualFileForNonExistingUrl); + } + + [Fact(DisplayName = "TryGetRemoteFileByUrl should throw ArgumentNullException if url argument is null")] + public void TryGetRemoteFileByUrlWithNullUrlTest() + { + EpubContentCollection epubContentCollection = new(Local, Remote); + Assert.Throws(() => epubContentCollection.TryGetRemoteFileByUrl(null!, out _)); + } + + private static void CompareLocalTextReadOnlyCollections(ReadOnlyCollection expected, ReadOnlyCollection actual) + { + CollectionComparer.CompareCollections(expected, actual, EpubContentComparer.CompareEpubLocalTextContentFiles); + } + + private static void CompareRemoteTextReadOnlyCollections(ReadOnlyCollection expected, ReadOnlyCollection actual) + { + CollectionComparer.CompareCollections(expected, actual, EpubContentComparer.CompareEpubRemoteTextContentFiles); + } + } +} diff --git a/Source/VersOne.Epub.Test/Unit/Content/EpubLocalContentLoaderTests.cs b/Source/VersOne.Epub.Test/Unit/Content/Loaders/EpubLocalContentLoaderTests.cs similarity index 98% rename from Source/VersOne.Epub.Test/Unit/Content/EpubLocalContentLoaderTests.cs rename to Source/VersOne.Epub.Test/Unit/Content/Loaders/EpubLocalContentLoaderTests.cs index 35061d1..3dd19f0 100644 --- a/Source/VersOne.Epub.Test/Unit/Content/EpubLocalContentLoaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Content/Loaders/EpubLocalContentLoaderTests.cs @@ -3,7 +3,7 @@ using VersOne.Epub.Options; using VersOne.Epub.Test.Unit.Mocks; -namespace VersOne.Epub.Test.Unit.Content +namespace VersOne.Epub.Test.Unit.Content.Loaders { public class EpubLocalContentLoaderTests { diff --git a/Source/VersOne.Epub.Test/Unit/Content/EpubRemoteContentLoaderTests.cs b/Source/VersOne.Epub.Test/Unit/Content/Loaders/EpubRemoteContentLoaderTests.cs similarity index 98% rename from Source/VersOne.Epub.Test/Unit/Content/EpubRemoteContentLoaderTests.cs rename to Source/VersOne.Epub.Test/Unit/Content/Loaders/EpubRemoteContentLoaderTests.cs index 1260b8f..e1eb135 100644 --- a/Source/VersOne.Epub.Test/Unit/Content/EpubRemoteContentLoaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Content/Loaders/EpubRemoteContentLoaderTests.cs @@ -3,7 +3,7 @@ using VersOne.Epub.Options; using VersOne.Epub.Test.Unit.Mocks; -namespace VersOne.Epub.Test.Unit.Content +namespace VersOne.Epub.Test.Unit.Content.Loaders { public class EpubRemoteContentLoaderTests { diff --git a/Source/VersOne.Epub.Test/Unit/Entities/EpubBookRefTests.cs b/Source/VersOne.Epub.Test/Unit/Entities/EpubBookRefTests.cs index 0bca5bf..d21bb12 100644 --- a/Source/VersOne.Epub.Test/Unit/Entities/EpubBookRefTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Entities/EpubBookRefTests.cs @@ -159,7 +159,7 @@ public void GetReadingOrderTest() EpubBookRef epubBookRef = CreateEpubBookRefWithReadingOrder(HTML_FILE_NAME, HTML_FILE_PATH); List expectedReadingOrder = new() { - epubBookRef.Content.Html.Local[HTML_FILE_NAME] + epubBookRef.Content.Html.GetLocalFileRefByKey(HTML_FILE_NAME) }; List actualReadingOrder = epubBookRef.GetReadingOrder(); Assert.Equal(expectedReadingOrder, actualReadingOrder); @@ -171,7 +171,7 @@ public async void GetReadingOrderAsyncTest() EpubBookRef epubBookRef = CreateEpubBookRefWithReadingOrder(HTML_FILE_NAME, HTML_FILE_PATH); List expectedReadingOrder = new() { - epubBookRef.Content.Html.Local[HTML_FILE_NAME] + epubBookRef.Content.Html.GetLocalFileRefByKey(HTML_FILE_NAME) }; List actualReadingOrder = await epubBookRef.GetReadingOrderAsync(); Assert.Equal(expectedReadingOrder, actualReadingOrder); @@ -183,7 +183,7 @@ public void GetNavigationTest() EpubBookRef epubBookRef = CreateEpubBookRefWithNavigation(HTML_FILE_NAME, HTML_FILE_PATH, TEST_ANCHOR_TEXT); List expectedNavigationItems = new() { - CreateTestNavigationLink(TEST_ANCHOR_TEXT, HTML_FILE_NAME, epubBookRef.Content.Html.Local[HTML_FILE_NAME]) + CreateTestNavigationLink(TEST_ANCHOR_TEXT, HTML_FILE_NAME, epubBookRef.Content.Html.GetLocalFileRefByKey(HTML_FILE_NAME)) }; List? actualNavigationItems = epubBookRef.GetNavigation(); EpubNavigationItemRefComparer.CompareNavigationItemRefLists(expectedNavigationItems, actualNavigationItems); @@ -195,7 +195,7 @@ public async void GetNavigationAsyncTest() EpubBookRef epubBookRef = CreateEpubBookRefWithNavigation(HTML_FILE_NAME, HTML_FILE_PATH, TEST_ANCHOR_TEXT); List expectedNavigationItems = new() { - CreateTestNavigationLink(TEST_ANCHOR_TEXT, HTML_FILE_NAME, epubBookRef.Content.Html.Local[HTML_FILE_NAME]) + CreateTestNavigationLink(TEST_ANCHOR_TEXT, HTML_FILE_NAME, epubBookRef.Content.Html.GetLocalFileRefByKey(HTML_FILE_NAME)) }; List? actualNavigationItems = await epubBookRef.GetNavigationAsync(); EpubNavigationItemRefComparer.CompareNavigationItemRefLists(expectedNavigationItems, actualNavigationItems); @@ -263,6 +263,10 @@ private static EpubBookRef CreateEpubBookRefWithCover(byte[] coverFileContent) private static EpubBookRef CreateEpubBookRefWithReadingOrder(string htmlFileName, string htmlFilePath) { EpubLocalTextContentFileRef htmlFileRef = CreateTestHtmlFileRef(htmlFileName, htmlFilePath); + List htmlLocal = new() + { + htmlFileRef + }; return CreateEpubBookRef ( epubFile: new TestZipFile(), @@ -291,16 +295,7 @@ private static EpubBookRef CreateEpubBookRefWithReadingOrder(string htmlFileName ), content: new EpubContentRef ( - html: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - htmlFileName, - htmlFileRef - } - } - ) + html: new EpubContentCollectionRef(htmlLocal.AsReadOnly()) ) ); } @@ -309,6 +304,10 @@ private static EpubBookRef CreateEpubBookRefWithNavigation(string htmlFileName, { EpubLocalTextContentFileRef htmlFileRef = CreateTestHtmlFileRef(htmlFileName, htmlFilePath); EpubLocalTextContentFileRef navigationFileRef = CreateTestHtmlFileRef("toc.html", $"{CONTENT_DIRECTORY_PATH}/toc.html"); + List htmlLocal = new() + { + htmlFileRef + }; return CreateEpubBookRef ( epubFile: new TestZipFile(), @@ -341,16 +340,7 @@ private static EpubBookRef CreateEpubBookRefWithNavigation(string htmlFileName, content: new EpubContentRef ( navigationHtmlFile: navigationFileRef, - html: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - htmlFileName, - htmlFileRef - } - } - ) + html: new EpubContentCollectionRef(htmlLocal.AsReadOnly()) ) ); } diff --git a/Source/VersOne.Epub.Test/Unit/Entities/EpubNavigationItemLinkTests.cs b/Source/VersOne.Epub.Test/Unit/Entities/EpubNavigationItemLinkTests.cs index 3c4b26b..fd45165 100644 --- a/Source/VersOne.Epub.Test/Unit/Entities/EpubNavigationItemLinkTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Entities/EpubNavigationItemLinkTests.cs @@ -2,61 +2,75 @@ { public class EpubNavigationItemLinkTests { - private const string CONTENT_FILE_URL = "../content/chapter1.html#section1"; + private const string CONTENT_FILE_URL_WITH_ANCHOR = "../content/chapter1.html#section1"; + private const string CONTENT_FILE_URL_WITHOUT_ANCHOR = "../content/chapter1.html"; + private const string REMOTE_FILE_URL = "https://example.com/books/123/chapter1.html"; private const string BASE_DIRECTORY_PATH = "OPS/toc"; - private const string CONTENT_FILE_NAME = "../content/chapter1.html"; + private const string CONTENT_FILE_URL = "../content/chapter1.html"; private const string CONTENT_FILE_PATH = "OPS/content/chapter1.html"; private const string ANCHOR = "section1"; - [Fact(DisplayName = "Constructing a EpubNavigationItemLink instance with non-null contentFileName, contentFilePath, and anchor parameters should succeed")] + [Fact(DisplayName = "Constructing a EpubNavigationItemLink instance with non-null contentFileUrl, contentFilePath, and anchor parameters should succeed")] public void ExplicitConstructorWithNonNullParametersTest() { - EpubNavigationItemLink epubNavigationItemLink = new(CONTENT_FILE_NAME, CONTENT_FILE_PATH, ANCHOR); - Assert.Equal(CONTENT_FILE_NAME, epubNavigationItemLink.ContentFileName); + EpubNavigationItemLink epubNavigationItemLink = new(CONTENT_FILE_URL_WITHOUT_ANCHOR, CONTENT_FILE_PATH, ANCHOR); + Assert.Equal(CONTENT_FILE_URL, epubNavigationItemLink.ContentFileUrl); Assert.Equal(CONTENT_FILE_PATH, epubNavigationItemLink.ContentFilePath); Assert.Equal(ANCHOR, epubNavigationItemLink.Anchor); } - [Fact(DisplayName = "Constructor should throw ArgumentNullException if contentFileName parameter is null")] - public void ExplicitConstructorWithNullContentFileNameTest() + [Fact(DisplayName = "Constructor should throw ArgumentNullException if contentFileUrl parameter is null")] + public void ExplicitConstructorWithNullContentFileUrlWithoutAnchorTest() { Assert.Throws(() => new EpubNavigationItemLink(null!, CONTENT_FILE_PATH, ANCHOR)); } + [Fact(DisplayName = "Constructor should throw ArgumentException if contentFileUrl parameter is a remote URL")] + public void ExplicitConstructorWithRemoteContentFileUrlWithoutAnchorTest() + { + Assert.Throws(() => new EpubNavigationItemLink(REMOTE_FILE_URL, CONTENT_FILE_PATH, ANCHOR)); + } + [Fact(DisplayName = "Constructor should throw ArgumentNullException if contentFilePath parameter is null")] public void ExplicitConstructorWithNullContentFilePathTest() { - Assert.Throws(() => new EpubNavigationItemLink(CONTENT_FILE_NAME, null!, ANCHOR)); + Assert.Throws(() => new EpubNavigationItemLink(CONTENT_FILE_URL_WITHOUT_ANCHOR, null!, ANCHOR)); } [Fact(DisplayName = "Constructing a EpubNavigationItemLink instance with null anchor parameter should succeed")] public void ExplicitConstructorWithNullAnchorTest() { - EpubNavigationItemLink epubNavigationItemLink = new(CONTENT_FILE_NAME, CONTENT_FILE_PATH, null); - Assert.Equal(CONTENT_FILE_NAME, epubNavigationItemLink.ContentFileName); + EpubNavigationItemLink epubNavigationItemLink = new(CONTENT_FILE_URL_WITHOUT_ANCHOR, CONTENT_FILE_PATH, null); + Assert.Equal(CONTENT_FILE_URL, epubNavigationItemLink.ContentFileUrl); Assert.Equal(CONTENT_FILE_PATH, epubNavigationItemLink.ContentFilePath); Assert.Null(epubNavigationItemLink.Anchor); } - [Fact(DisplayName = "Constructing a EpubNavigationItemLink instance with non-null contentFileUrl and baseDirectoryPath parameters should succeed")] + [Fact(DisplayName = "Constructing a EpubNavigationItemLink instance with non-null contentFileUrlWithAnchor and baseDirectoryPath parameters should succeed")] public void UrlConstructorWithNonNullParametersTest() { - EpubNavigationItemLink epubNavigationItemLink = new(CONTENT_FILE_URL, BASE_DIRECTORY_PATH); - Assert.Equal(CONTENT_FILE_NAME, epubNavigationItemLink.ContentFileName); + EpubNavigationItemLink epubNavigationItemLink = new(CONTENT_FILE_URL_WITH_ANCHOR, BASE_DIRECTORY_PATH); + Assert.Equal(CONTENT_FILE_URL, epubNavigationItemLink.ContentFileUrl); Assert.Equal(CONTENT_FILE_PATH, epubNavigationItemLink.ContentFilePath); Assert.Equal(ANCHOR, epubNavigationItemLink.Anchor); } - [Fact(DisplayName = "Constructor should throw ArgumentNullException if contentFileUrl parameter is null")] - public void UrlConstructorWithNullContentFileUrlTest() + [Fact(DisplayName = "Constructor should throw ArgumentNullException if contentFileUrlWithAnchor parameter is null")] + public void UrlConstructorWithNullContentFileUrlWithAnchorTest() { Assert.Throws(() => new EpubNavigationItemLink(null!, BASE_DIRECTORY_PATH)); } + [Fact(DisplayName = "Constructor should throw ArgumentException if contentFileUrlWithAnchor parameter is a remote URL")] + public void UrlConstructorWithRemoteContentFileUrlWithAnchorTest() + { + Assert.Throws(() => new EpubNavigationItemLink(REMOTE_FILE_URL, BASE_DIRECTORY_PATH)); + } + [Fact(DisplayName = "Constructor should throw ArgumentNullException if baseDirectoryPath parameter is null")] public void UrlConstructorWithNullBaseDirectoryPathTest() { - Assert.Throws(() => new EpubNavigationItemLink(CONTENT_FILE_URL, null!)); + Assert.Throws(() => new EpubNavigationItemLink(CONTENT_FILE_URL_WITH_ANCHOR, null!)); } } } diff --git a/Source/VersOne.Epub.Test/Unit/Entities/EpubNavigationItemRefTests.cs b/Source/VersOne.Epub.Test/Unit/Entities/EpubNavigationItemRefTests.cs index afebb89..dabf046 100644 --- a/Source/VersOne.Epub.Test/Unit/Entities/EpubNavigationItemRefTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Entities/EpubNavigationItemRefTests.cs @@ -11,7 +11,7 @@ public class EpubNavigationItemRefTests private static EpubNavigationItemLink Link => new ( - contentFileUrl: "../content/chapter1.html#section1", + contentFileUrlWithAnchor: "../content/chapter1.html#section1", baseDirectoryPath: "OPS/toc" ); diff --git a/Source/VersOne.Epub.Test/Unit/Entities/EpubNavigationItemTests.cs b/Source/VersOne.Epub.Test/Unit/Entities/EpubNavigationItemTests.cs index ff02bc5..9e12dee 100644 --- a/Source/VersOne.Epub.Test/Unit/Entities/EpubNavigationItemTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Entities/EpubNavigationItemTests.cs @@ -11,7 +11,7 @@ public class EpubNavigationItemTests private static EpubNavigationItemLink Link => new ( - contentFileUrl: "../content/chapter1.html#section1", + contentFileUrlWithAnchor: "../content/chapter1.html#section1", baseDirectoryPath: "OPS/toc" ); diff --git a/Source/VersOne.Epub.Test/Unit/Exceptions/EpubContentCollectionExceptionTests.cs b/Source/VersOne.Epub.Test/Unit/Exceptions/EpubContentCollectionExceptionTests.cs new file mode 100644 index 0000000..dc9ab68 --- /dev/null +++ b/Source/VersOne.Epub.Test/Unit/Exceptions/EpubContentCollectionExceptionTests.cs @@ -0,0 +1,30 @@ +namespace VersOne.Epub.Test.Unit.Exceptions +{ + public class EpubContentCollectionExceptionTests + { + private const string TEST_MESSAGE = "test message"; + + [Fact(DisplayName = "Creating EpubContentCollectionException without arguments should succeed")] + public void CreateWithNoArgumentsTest() + { + _ = new EpubContentCollectionException(); + } + + [Fact(DisplayName = "Creating EpubContentCollectionException with message should succeed")] + public void CreateWithMessageTest() + { + EpubContentCollectionException epubContentCollectionException = new(TEST_MESSAGE); + Assert.Equal(TEST_MESSAGE, epubContentCollectionException.Message); + } + + [Fact(DisplayName = "Creating EpubContentCollectionException with message and inner exception should succeed")] + public void CreateWithMessageAndInnerExceptionTest() + { + Exception innerException = new(); + EpubContentCollectionException epubContentCollectionException = new(TEST_MESSAGE, innerException); + Assert.Equal(TEST_MESSAGE, epubContentCollectionException.Message); + Assert.Equal(innerException, epubContentCollectionException.InnerException); + } + + } +} diff --git a/Source/VersOne.Epub.Test/Unit/Exceptions/EpubContentCollectionRefExceptionTests.cs b/Source/VersOne.Epub.Test/Unit/Exceptions/EpubContentCollectionRefExceptionTests.cs new file mode 100644 index 0000000..391d809 --- /dev/null +++ b/Source/VersOne.Epub.Test/Unit/Exceptions/EpubContentCollectionRefExceptionTests.cs @@ -0,0 +1,30 @@ +namespace VersOne.Epub.Test.Unit.Exceptions +{ + public class EpubContentCollectionRefExceptionTests + { + private const string TEST_MESSAGE = "test message"; + + [Fact(DisplayName = "Creating EpubContentCollectionRefException without arguments should succeed")] + public void CreateWithNoArgumentsTest() + { + _ = new EpubContentCollectionRefException(); + } + + [Fact(DisplayName = "Creating EpubContentCollectionRefException with message should succeed")] + public void CreateWithMessageTest() + { + EpubContentCollectionRefException epubContentCollectionRefException = new(TEST_MESSAGE); + Assert.Equal(TEST_MESSAGE, epubContentCollectionRefException.Message); + } + + [Fact(DisplayName = "Creating EpubContentCollectionRefException with message and inner exception should succeed")] + public void CreateWithMessageAndInnerExceptionTest() + { + Exception innerException = new(); + EpubContentCollectionRefException epubContentCollectionRefException = new(TEST_MESSAGE, innerException); + Assert.Equal(TEST_MESSAGE, epubContentCollectionRefException.Message); + Assert.Equal(innerException, epubContentCollectionRefException.InnerException); + } + + } +} diff --git a/Source/VersOne.Epub.Test/Unit/Readers/BookCoverReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/BookCoverReaderTests.cs index 80741f1..46cc497 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/BookCoverReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/BookCoverReaderTests.cs @@ -346,15 +346,17 @@ private static EpubRemoteByteContentFileRef CreateRemoteTestImageFileRef() private static EpubContentCollectionRef CreateImageContentRefs( EpubLocalByteContentFileRef? localImageFileRef = null, EpubRemoteByteContentFileRef? remoteImageFileRef = null) { - EpubContentCollectionRef result = new(); + List local = new(); + List remote = new(); if (localImageFileRef != null) { - result.Local[localImageFileRef.Key] = localImageFileRef; + local.Add(localImageFileRef); } if (remoteImageFileRef != null) { - result.Remote[remoteImageFileRef.Key] = remoteImageFileRef; + remote.Add(remoteImageFileRef); } + EpubContentCollectionRef result = new(local.AsReadOnly(), remote.AsReadOnly()); return result; } } diff --git a/Source/VersOne.Epub.Test/Unit/Readers/ContentReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/ContentReaderTests.cs index c1db1a5..2348d06 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/ContentReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/ContentReaderTests.cs @@ -336,336 +336,120 @@ 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() + { + expectedHtmlFileRef, + expectedTocFileRef + }; + List expectedHtmlRemote = new() + { + expectedRemoteHtmlFileRef + }; + List expectedCssLocal = new() + { + expectedCssFileRef + }; + List expectedCssRemote = new() + { + expectedRemoteCssFileRef + }; + List expectedImagesLocal = new() + { + expectedGifFileRef, + expectedJpgFileRef, + expectedPngFileRef, + expectedSvgFileRef, + expectedWebpFileRef, + expectedCoverFileRef + }; + List expectedImagesRemote = new() + { + expectedRemoteJpgFileRef + }; + List expectedFontsLocal = new() + { + expectedTtf1FileRef, + expectedTtf2FileRef, + expectedTtf3FileRef, + expectedOtf1FileRef, + expectedOtf2FileRef, + expectedOtf3FileRef, + expectedSfnt1FileRef, + expectedSfnt2FileRef, + expectedWoff11FileRef, + expectedWoff12FileRef, + expectedWoff2FileRef + }; + List expectedFontsRemote = new() + { + expectedRemoteTtfFileRef + }; + List expectedAudioLocal = new() + { + expectedMp3FileRef, + expectedMp4FileRef, + expectedOgg1FileRef, + expectedOgg2FileRef + }; + List expectedAudioRemote = new() + { + expectedRemoteMp3FileRef + }; + List expectedAllFilesLocal = new() + { + expectedHtmlFileRef, + expectedDtbFileRef, + expectedNcxFileRef, + expectedOebFileRef, + expectedXmlFileRef, + expectedCssFileRef, + expectedOebCssFileRef, + expectedJs1FileRef, + expectedJs2FileRef, + expectedJs3FileRef, + expectedGifFileRef, + expectedJpgFileRef, + expectedPngFileRef, + expectedSvgFileRef, + expectedWebpFileRef, + expectedTtf1FileRef, + expectedTtf2FileRef, + expectedTtf3FileRef, + expectedOtf1FileRef, + expectedOtf2FileRef, + expectedOtf3FileRef, + expectedSfnt1FileRef, + expectedSfnt2FileRef, + expectedWoff11FileRef, + expectedWoff12FileRef, + expectedWoff2FileRef, + expectedSmilFileRef, + expectedMp3FileRef, + expectedMp4FileRef, + expectedOgg1FileRef, + expectedOgg2FileRef, + expectedVideoFileRef, + expectedCoverFileRef, + expectedTocFileRef + }; + List expectedAllFilesRemote = new() + { + expectedRemoteHtmlFileRef, + expectedRemoteCssFileRef, + expectedRemoteJpgFileRef, + expectedRemoteTtfFileRef, + expectedRemoteMp3FileRef + }; EpubContentRef expectedContentMap = new ( cover: expectedCoverFileRef, navigationHtmlFile: expectedTocFileRef, - html: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - "text.html", - expectedHtmlFileRef - }, - { - "toc.html", - expectedTocFileRef - } - }, - remote: new Dictionary() - { - { - "https://example.com/books/123/test.html", - expectedRemoteHtmlFileRef - } - } - ), - css: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - "styles.css", - expectedCssFileRef - } - }, - remote: new Dictionary() - { - { - "https://example.com/books/123/test.css", - expectedRemoteCssFileRef - } - } - ), - images: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - "image.gif", - expectedGifFileRef - }, - { - "image.jpg", - expectedJpgFileRef - }, - { - "image.png", - expectedPngFileRef - }, - { - "image.svg", - expectedSvgFileRef - }, - { - "image.webp", - expectedWebpFileRef - }, - { - "cover.jpg", - expectedCoverFileRef - } - }, - remote: new Dictionary() - { - { - "https://example.com/books/123/image.jpg", - expectedRemoteJpgFileRef - } - } - ), - fonts: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - "font1.ttf", - expectedTtf1FileRef - }, - { - "font2.ttf", - expectedTtf2FileRef - }, - { - "font3.ttf", - expectedTtf3FileRef - }, - { - "font1.otf", - expectedOtf1FileRef - }, - { - "font2.otf", - expectedOtf2FileRef - }, - { - "font3.otf", - expectedOtf3FileRef - }, - { - "font.aat", - expectedSfnt1FileRef - }, - { - "font.sil", - expectedSfnt2FileRef - }, - { - "font1.woff", - expectedWoff11FileRef - }, - { - "font2.woff", - expectedWoff12FileRef - }, - { - "font.woff2", - expectedWoff2FileRef - } - }, - remote: new Dictionary() - { - { - "https://example.com/books/123/font.ttf", - expectedRemoteTtfFileRef - } - } - ), - audio: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - "audio.mp3", - expectedMp3FileRef - }, - { - "audio.mp4", - expectedMp4FileRef - }, - { - "audio1.opus", - expectedOgg1FileRef - }, - { - "audio2.opus", - expectedOgg2FileRef - } - }, - remote: new Dictionary() - { - { - "https://example.com/books/123/audio.mp3", - expectedRemoteMp3FileRef - } - } - ), - allFiles: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - "text.html", - expectedHtmlFileRef - }, - { - "doc.dtb", - expectedDtbFileRef - }, - { - "toc.ncx", - expectedNcxFileRef - }, - { - "oeb.html", - expectedOebFileRef - }, - { - "file.xml", - expectedXmlFileRef - }, - { - "styles.css", - expectedCssFileRef - }, - { - "oeb.css", - expectedOebCssFileRef - }, - { - "script1.js", - expectedJs1FileRef - }, - { - "script2.js", - expectedJs2FileRef - }, - { - "script3.js", - expectedJs3FileRef - }, - { - "image.gif", - expectedGifFileRef - }, - { - "image.jpg", - expectedJpgFileRef - }, - { - "image.png", - expectedPngFileRef - }, - { - "image.svg", - expectedSvgFileRef - }, - { - "image.webp", - expectedWebpFileRef - }, - { - "font1.ttf", - expectedTtf1FileRef - }, - { - "font2.ttf", - expectedTtf2FileRef - }, - { - "font3.ttf", - expectedTtf3FileRef - }, - { - "font1.otf", - expectedOtf1FileRef - }, - { - "font2.otf", - expectedOtf2FileRef - }, - { - "font3.otf", - expectedOtf3FileRef - }, - { - "font.aat", - expectedSfnt1FileRef - }, - { - "font.sil", - expectedSfnt2FileRef - }, - { - "font1.woff", - expectedWoff11FileRef - }, - { - "font2.woff", - expectedWoff12FileRef - }, - { - "font.woff2", - expectedWoff2FileRef - }, - { - "narration.smil", - expectedSmilFileRef - }, - { - "audio.mp3", - expectedMp3FileRef - }, - { - "audio.mp4", - expectedMp4FileRef - }, - { - "audio1.opus", - expectedOgg1FileRef - }, - { - "audio2.opus", - expectedOgg2FileRef - }, - { - "video.mp4", - expectedVideoFileRef - }, - { - "cover.jpg", - expectedCoverFileRef - }, - { - "toc.html", - expectedTocFileRef - } - }, - remote: new Dictionary() - { - { - "https://example.com/books/123/test.html", - expectedRemoteHtmlFileRef - }, - { - "https://example.com/books/123/test.css", - expectedRemoteCssFileRef - }, - { - "https://example.com/books/123/image.jpg", - expectedRemoteJpgFileRef - }, - { - "https://example.com/books/123/font.ttf", - expectedRemoteTtfFileRef - }, - { - "https://example.com/books/123/audio.mp3", - expectedRemoteMp3FileRef - } - } - ) + html: new EpubContentCollectionRef(expectedHtmlLocal.AsReadOnly(), expectedHtmlRemote.AsReadOnly()), + css: new EpubContentCollectionRef(expectedCssLocal.AsReadOnly(), expectedCssRemote.AsReadOnly()), + images: new EpubContentCollectionRef(expectedImagesLocal.AsReadOnly(), expectedImagesRemote.AsReadOnly()), + fonts: new EpubContentCollectionRef(expectedFontsLocal.AsReadOnly(), expectedFontsRemote.AsReadOnly()), + audio: new EpubContentCollectionRef(expectedAudioLocal.AsReadOnly(), expectedAudioRemote.AsReadOnly()), + allFiles: new EpubContentCollectionRef(expectedAllFilesLocal.AsReadOnly(), expectedAllFilesRemote.AsReadOnly()) ); ContentReader contentReader = new(environmentDependencies); EpubContentRef actualContentMap = contentReader.ParseContentMap(epubSchema, new TestZipFile()); @@ -698,42 +482,24 @@ 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() + { + expectedFileRef1 + }; + List expectedCssRemote = new() + { + expectedFileRef2 + }; + List expectedAllFilesRemote = new() + { + expectedFileRef1, + expectedFileRef2 + }; EpubContentRef expectedContentMap = new ( - html: new EpubContentCollectionRef - ( - remote: new Dictionary() - { - { - "https://example.com/books/123/test.html", - expectedFileRef1 - } - } - ), - css: new EpubContentCollectionRef - ( - remote: new Dictionary() - { - { - "https://example.com/books/123/test.css", - expectedFileRef2 - } - } - ), - allFiles: new EpubContentCollectionRef - ( - remote: new Dictionary() - { - { - "https://example.com/books/123/test.html", - expectedFileRef1 - }, - { - "https://example.com/books/123/test.css", - expectedFileRef2 - } - } - ) + html: new EpubContentCollectionRef(null, expectedHtmlRemote.AsReadOnly()), + css: new EpubContentCollectionRef(null, expectedCssRemote.AsReadOnly()), + allFiles: new EpubContentCollectionRef(null, expectedAllFilesRemote.AsReadOnly()) ); ContentReader contentReader = new(environmentDependencies); EpubContentRef actualContentMap = contentReader.ParseContentMap(epubSchema, new TestZipFile()); @@ -766,42 +532,24 @@ public void ParseContentMapWithRemoteByteContentFilesTest() ); 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() + { + expectedFileRef1 + }; + List expectedFontsRemote = new() + { + expectedFileRef2 + }; + List expectedAllFilesRemote = new() + { + expectedFileRef1, + expectedFileRef2 + }; EpubContentRef expectedContentMap = new ( - images: new EpubContentCollectionRef - ( - remote: new Dictionary() - { - { - "https://example.com/books/123/image.jpg", - expectedFileRef1 - } - } - ), - fonts: new EpubContentCollectionRef - ( - remote: new Dictionary() - { - { - "https://example.com/books/123/font.ttf", - expectedFileRef2 - } - } - ), - allFiles: new EpubContentCollectionRef - ( - remote: new Dictionary() - { - { - "https://example.com/books/123/image.jpg", - expectedFileRef1 - }, - { - "https://example.com/books/123/font.ttf", - expectedFileRef2 - } - } - ) + images: new EpubContentCollectionRef(null, expectedImagesRemote.AsReadOnly()), + fonts: new EpubContentCollectionRef(null, expectedFontsRemote.AsReadOnly()), + allFiles: new EpubContentCollectionRef(null, expectedAllFilesRemote.AsReadOnly()) ); ContentReader contentReader = new(environmentDependencies); EpubContentRef actualContentMap = contentReader.ParseContentMap(epubSchema, new TestZipFile()); diff --git a/Source/VersOne.Epub.Test/Unit/Readers/NavigationReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/NavigationReaderTests.cs index aafc8b7..fc7dd7f 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/NavigationReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/NavigationReaderTests.cs @@ -133,7 +133,7 @@ public void GetNavigationItemsForEpub2WithFullNcxTest() } [Fact(DisplayName = "GetNavigationItems should throw a Epub2NcxException if an NCX navigation point has no navigation labels")] - public void GetNavigationItemsForEpub2WithoutNavigationLabelsFile() + public void GetNavigationItemsForEpub2WithoutNavigationLabelsTest() { EpubSchema epubSchema = new ( @@ -173,8 +173,56 @@ public void GetNavigationItemsForEpub2WithoutNavigationLabelsFile() Assert.Throws(() => NavigationReader.GetNavigationItems(epubSchema, epubContentRef)); } + [Fact(DisplayName = "GetNavigationItems should throw a Epub2NcxException if an NCX navigation point points to a remote resource")] + public void GetNavigationItemsForEpub2WithRemoteNavigationPointSourceTest() + { + string remoteFileHref = "https://example.com/books/123/chapter1.html"; + EpubSchema epubSchema = new + ( + package: CreateEmptyPackage(EpubVersion.EPUB_2), + epub2Ncx: new Epub2Ncx + ( + filePath: NCX_FILE_PATH, + head: new Epub2NcxHead(), + docTitle: null, + docAuthors: null, + navMap: new Epub2NcxNavigationMap + ( + items: new List() + { + new Epub2NcxNavigationPoint + ( + id: String.Empty, + @class: null, + playOrder: null, + navigationLabels: new List() + { + new Epub2NcxNavigationLabel + ( + text: "Test label" + ) + }, + content: new Epub2NcxContent + ( + source: remoteFileHref + ), + childNavigationPoints: null + ) + } + ), + pageList: null, + navLists: null + ), + epub3NavDocument: null, + mediaOverlays: null, + contentDirectoryPath: CONTENT_DIRECTORY_PATH + ); + EpubContentRef epubContentRef = new(); + Assert.Throws(() => NavigationReader.GetNavigationItems(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 GetNavigationItemsForEpub2WithMissingContentFile() + public void GetNavigationItemsForEpub2WithMissingContentFileTest() { EpubSchema epubSchema = new ( @@ -608,7 +656,7 @@ public void GetNavigationItemsForEpub3NavWithNullOrEmptyTitlesTest() EpubNavigationItemRefComparer.CompareNavigationItemRefLists(expectedNavigationItems, actualNavigationItems); } - [Fact(DisplayName = "GetNavigationItems should throw a Epub3NavException if the file referenced by a EPUB 3 navigational Li item is missing in the EpubContentRef")] + [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() { EpubSchema epubSchema = new @@ -649,7 +697,7 @@ public void GetNavigationItemsForEpub3WithMissingContentFile() Assert.Throws(() => NavigationReader.GetNavigationItems(epubSchema, epubContentRef)); } - [Fact(DisplayName = "GetNavigationItems should throw EpubPackageException if the content file referenced by a navigation item is a remote resource")] + [Fact(DisplayName = "GetNavigationItems should throw Epub3NavException if a EPUB 3 navigation anchor points to a remote resource")] public void GetNavigationItemsWithRemoteContentFileTest() { string remoteFileHref = "https://example.com/books/123/chapter1.html"; @@ -686,30 +734,16 @@ public void GetNavigationItemsWithRemoteContentFileTest() contentDirectoryPath: CONTENT_DIRECTORY_PATH ); EpubLocalTextContentFileRef testNavigationHtmlFileRef = CreateTestNavigationFile(); - EpubContentFileRefMetadata testTextContentFileRefMetadata = new(remoteFileHref, EpubContentType.XHTML_1_1, "application/xhtml+xml"); - EpubRemoteTextContentFileRef testTextContentFileRef = new(testTextContentFileRefMetadata, new TestEpubContentLoader()); + List htmlLocal = new() + { + testNavigationHtmlFileRef + }; EpubContentRef epubContentRef = new ( navigationHtmlFile: testNavigationHtmlFileRef, - html: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - testNavigationHtmlFileRef.Key, - testNavigationHtmlFileRef - } - }, - remote: new Dictionary() - { - { - testTextContentFileRef.Key, - testTextContentFileRef - } - } - ) + html: new EpubContentCollectionRef(htmlLocal.AsReadOnly()) ); - Assert.Throws(() => NavigationReader.GetNavigationItems(epubSchema, epubContentRef)); + Assert.Throws(() => NavigationReader.GetNavigationItems(epubSchema, epubContentRef)); } private static EpubLocalTextContentFileRef CreateTestNavigationFile() @@ -762,19 +796,20 @@ private static EpubNavigationItemRef CreateNavigationHeader(string title) private static EpubContentRef CreateContentRef(EpubLocalTextContentFileRef? navigationHtmlFile, params EpubLocalTextContentFileRef[] htmlFiles) { - EpubContentRef result = new - ( - navigationHtmlFile: navigationHtmlFile, - html: new EpubContentCollectionRef() - ); + List local = new(); if (navigationHtmlFile != null) { - result.Html.Local[navigationHtmlFile.Key] = navigationHtmlFile; + local.Add(navigationHtmlFile); } foreach (EpubLocalTextContentFileRef htmlFile in htmlFiles) { - result.Html.Local[htmlFile.Key] = htmlFile; + local.Add(htmlFile); } + EpubContentRef result = new + ( + navigationHtmlFile: navigationHtmlFile, + html: new EpubContentCollectionRef(local.AsReadOnly()) + ); return result; } } diff --git a/Source/VersOne.Epub.Test/Unit/Readers/PackageReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/PackageReaderTests.cs index 200ce35..1fa5dd3 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/PackageReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/PackageReaderTests.cs @@ -187,6 +187,32 @@ public class PackageReaderTests """; + private const string OPF_FILE_WITH_DUPLICATING_MANIFEST_ITEM_IDS = $""" + + + + + + + + + + + """; + + private const string OPF_FILE_WITH_DUPLICATING_MANIFEST_ITEM_HREFS = $""" + + + + + + + + + + + """; + private const string EPUB2_OPF_FILE_WITHOUT_SPINE_TOC = $""" @@ -753,6 +779,18 @@ public async void ReadPackageWithoutManifestItemMediaTypeWithSkippingInvalidMani 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 void ReadPackageWithDuplicatingManifestItemIdsTest() + { + await TestFailingReadOperationWithNullPackageReaderOptions(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 void ReadPackageWithDuplicatingManifestItemHrefsTest() + { + await TestFailingReadOperationWithNullPackageReaderOptions(OPF_FILE_WITH_DUPLICATING_MANIFEST_ITEM_HREFS); + } + [Fact(DisplayName = "Trying to read EPUB 2 OPF package without 'toc' attribute in the spine XML node should fail with EpubPackageException")] public async void ReadEpub2PackageWithoutSpineTocTest() { diff --git a/Source/VersOne.Epub.Test/Unit/Readers/SmilClockParserTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/SmilClockParserTests.cs index d2acccc..91b5802 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/SmilClockParserTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/SmilClockParserTests.cs @@ -1,4 +1,4 @@ -using VersOne.Epub.Readers; +using VersOne.Epub.Internal; using VersOne.Epub.Test.Comparers; namespace VersOne.Epub.Test.Unit.Readers diff --git a/Source/VersOne.Epub.Test/Unit/Readers/SpineReaderTests.cs b/Source/VersOne.Epub.Test/Unit/Readers/SpineReaderTests.cs index bca30af..2244ec0 100644 --- a/Source/VersOne.Epub.Test/Unit/Readers/SpineReaderTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Readers/SpineReaderTests.cs @@ -56,22 +56,14 @@ public void GetReadingOrderForTypicalSpineTest() ); EpubLocalTextContentFileRef expectedHtmlFileRef1 = CreateTestHtmlFileRef("chapter1.html"); EpubLocalTextContentFileRef expectedHtmlFileRef2 = CreateTestHtmlFileRef("chapter2.html"); + List expectedHtmlLocal = new() + { + expectedHtmlFileRef1, + expectedHtmlFileRef2 + }; EpubContentRef epubContentRef = new ( - html: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - "chapter1.html", - expectedHtmlFileRef1 - }, - { - "chapter2.html", - expectedHtmlFileRef2 - } - } - ) + html: new EpubContentCollectionRef(expectedHtmlLocal.AsReadOnly()) ); List expectedReadingOrder = new() { @@ -175,27 +167,22 @@ public void GetReadingOrderWithRemoteHtmlContentFileTest() } ) ); - EpubContentRef epubContentRef = new - ( - html: new EpubContentCollectionRef + List htmlRemote = new() + { + new EpubRemoteTextContentFileRef ( - remote: new Dictionary() - { - { - remoteFileHref, - new EpubRemoteTextContentFileRef - ( - metadata: new EpubContentFileRefMetadata - ( - key: remoteFileHref, - contentType: EpubContentType.XHTML_1_1, - contentMimeType: "application/xhtml+xml" - ), - epubContentLoader: new TestEpubContentLoader() - ) - } - } + metadata: new EpubContentFileRefMetadata + ( + key: remoteFileHref, + contentType: EpubContentType.XHTML_1_1, + contentMimeType: "application/xhtml+xml" + ), + epubContentLoader: new TestEpubContentLoader() ) + }; + EpubContentRef epubContentRef = new + ( + html: new EpubContentCollectionRef(null, htmlRemote.AsReadOnly()) ); Assert.Throws(() => SpineReader.GetReadingOrder(epubSchema, epubContentRef)); } diff --git a/Source/VersOne.Epub.Test/Unit/TestData/TestEpubContent.cs b/Source/VersOne.Epub.Test/Unit/TestData/TestEpubContent.cs index 074ff87..955adfb 100644 --- a/Source/VersOne.Epub.Test/Unit/TestData/TestEpubContent.cs +++ b/Source/VersOne.Epub.Test/Unit/TestData/TestEpubContent.cs @@ -6,249 +6,113 @@ internal static class TestEpubContent { public static EpubContent CreateMinimalTestEpubContentWithNavigation() { + List htmlLocal = new() + { + MinimalNavFile + }; + List allFilesLocal = new() + { + MinimalNavFile + }; return new EpubContent ( cover: null, navigationHtmlFile: MinimalNavFile, - html: new EpubContentCollection - ( - local: new Dictionary() - { - { - NAV_FILE_NAME, - MinimalNavFile - } - } - ), + html: new EpubContentCollection(htmlLocal.AsReadOnly()), css: new EpubContentCollection(), images: new EpubContentCollection(), fonts: new EpubContentCollection(), audio: new EpubContentCollection(), - allFiles: new EpubContentCollection - ( - local: new Dictionary() - { - { - NAV_FILE_NAME, - MinimalNavFile - } - } - ) + allFiles: new EpubContentCollection(allFilesLocal.AsReadOnly()) ); } public static EpubContent CreateFullTestEpubContent(bool populateRemoteFilesContents) { + List htmlLocal = new() + { + Chapter1File, + Chapter2File, + FullNavFile + }; + List htmlRemote = new() + { + populateRemoteFilesContents ? RemoteHtmlContentFile : RemoteHtmlContentFileWithNoContent + }; + List cssLocal = new() + { + Styles1File, + Styles2File + }; + List cssRemote = new() + { + populateRemoteFilesContents ? RemoteCssContentFile : RemoteCssContentFileWithNoContent + }; + List imagesLocal = new() + { + Image1File, + Image2File, + CoverFile + }; + List imagesRemote = new() + { + populateRemoteFilesContents ? RemoteImageContentFile : RemoteImageContentFileWithNoContent + }; + List fontsLocal = new() + { + Font1File, + Font2File + }; + List fontsRemote = new() + { + populateRemoteFilesContents ? RemoteFontContentFile : RemoteFontContentFileWithNoContent + }; + List audioLocal = new() + { + Audio1File, + Audio2File + }; + List audioRemote = new() + { + populateRemoteFilesContents ? RemoteAudioContentFile : RemoteAudioContentFileWithNoContent + }; + List allFilesLocal = new() + { + Chapter1File, + Chapter2File, + FullNavFile, + Styles1File, + Styles2File, + Image1File, + Image2File, + CoverFile, + Font1File, + Font2File, + Audio1File, + Audio2File, + VideoFile, + NcxFile + }; + List allFilesRemote = new() + { + populateRemoteFilesContents ? RemoteHtmlContentFile : RemoteHtmlContentFileWithNoContent, + populateRemoteFilesContents? RemoteCssContentFile : RemoteCssContentFileWithNoContent, + populateRemoteFilesContents? RemoteImageContentFile : RemoteImageContentFileWithNoContent, + populateRemoteFilesContents? RemoteFontContentFile : RemoteFontContentFileWithNoContent, + populateRemoteFilesContents? RemoteAudioContentFile : RemoteAudioContentFileWithNoContent, + populateRemoteFilesContents? RemoteXmlContentFile : RemoteXmlContentFileWithNoContent, + populateRemoteFilesContents? RemoteVideoContentFile : RemoteVideoContentFileWithNoContent + }; return new ( cover: CoverFile, navigationHtmlFile: FullNavFile, - html: new EpubContentCollection - ( - local: new Dictionary() - { - { - CHAPTER1_FILE_NAME, - Chapter1File - }, - { - CHAPTER2_FILE_NAME, - Chapter2File - }, - { - NAV_FILE_NAME, - FullNavFile - } - }, - remote: new Dictionary() - { - { - REMOTE_HTML_CONTENT_FILE_HREF, - populateRemoteFilesContents ? RemoteHtmlContentFile : RemoteHtmlContentFileWithNoContent - } - } - ), - css: new EpubContentCollection - ( - local: new Dictionary() - { - { - STYLES1_FILE_NAME, - Styles1File - }, - { - STYLES2_FILE_NAME, - Styles2File - } - }, - remote: new Dictionary() - { - { - REMOTE_CSS_CONTENT_FILE_HREF, - populateRemoteFilesContents ? RemoteCssContentFile : RemoteCssContentFileWithNoContent - } - } - ), - images: new EpubContentCollection - ( - local: new Dictionary() - { - { - IMAGE1_FILE_NAME, - Image1File - }, - { - IMAGE2_FILE_NAME, - Image2File - }, - { - COVER_FILE_NAME, - CoverFile - } - }, - remote: new Dictionary() - { - { - REMOTE_IMAGE_CONTENT_FILE_HREF, - populateRemoteFilesContents ? RemoteImageContentFile : RemoteImageContentFileWithNoContent - } - } - ), - fonts: new EpubContentCollection - ( - local: new Dictionary() - { - { - FONT1_FILE_NAME, - Font1File - }, - { - FONT2_FILE_NAME, - Font2File - } - }, - remote: new Dictionary() - { - { - REMOTE_FONT_CONTENT_FILE_HREF, - populateRemoteFilesContents ? RemoteFontContentFile : RemoteFontContentFileWithNoContent - } - } - ), - audio: new EpubContentCollection - ( - local: new Dictionary() - { - { - AUDIO1_FILE_NAME, - Audio1File - }, - { - AUDIO2_FILE_NAME, - Audio2File - } - }, - remote: new Dictionary() - { - { - REMOTE_AUDIO_CONTENT_FILE_HREF, - populateRemoteFilesContents ? RemoteAudioContentFile : RemoteAudioContentFileWithNoContent - } - } - ), - allFiles: new EpubContentCollection - ( - local: new Dictionary() - { - { - CHAPTER1_FILE_NAME, - Chapter1File - }, - { - CHAPTER2_FILE_NAME, - Chapter2File - }, - { - STYLES1_FILE_NAME, - Styles1File - }, - { - STYLES2_FILE_NAME, - Styles2File - }, - { - IMAGE1_FILE_NAME, - Image1File - }, - { - IMAGE2_FILE_NAME, - Image2File - }, - { - FONT1_FILE_NAME, - Font1File - }, - { - FONT2_FILE_NAME, - Font2File - }, - { - AUDIO1_FILE_NAME, - Audio1File - }, - { - AUDIO2_FILE_NAME, - Audio2File - }, - { - VIDEO_FILE_NAME, - VideoFile - }, - { - NAV_FILE_NAME, - FullNavFile - }, - { - COVER_FILE_NAME, - CoverFile - }, - { - NCX_FILE_NAME, - NcxFile - } - }, - remote: new Dictionary() - { - { - REMOTE_HTML_CONTENT_FILE_HREF, - populateRemoteFilesContents ? RemoteHtmlContentFile : RemoteHtmlContentFileWithNoContent - }, - { - REMOTE_CSS_CONTENT_FILE_HREF, - populateRemoteFilesContents ? RemoteCssContentFile : RemoteCssContentFileWithNoContent - }, - { - REMOTE_IMAGE_CONTENT_FILE_HREF, - populateRemoteFilesContents ? RemoteImageContentFile : RemoteImageContentFileWithNoContent - }, - { - REMOTE_FONT_CONTENT_FILE_HREF, - populateRemoteFilesContents ? RemoteFontContentFile : RemoteFontContentFileWithNoContent - }, - { - REMOTE_XML_CONTENT_FILE_HREF, - populateRemoteFilesContents ? RemoteXmlContentFile : RemoteXmlContentFileWithNoContent - }, - { - REMOTE_AUDIO_CONTENT_FILE_HREF, - populateRemoteFilesContents ? RemoteAudioContentFile : RemoteAudioContentFileWithNoContent - }, - { - REMOTE_VIDEO_CONTENT_FILE_HREF, - populateRemoteFilesContents ? RemoteVideoContentFile : RemoteVideoContentFileWithNoContent - } - } - ) + html: new EpubContentCollection(htmlLocal.AsReadOnly(), htmlRemote.AsReadOnly()), + css: new EpubContentCollection(cssLocal.AsReadOnly(), cssRemote.AsReadOnly()), + images: new EpubContentCollection(imagesLocal.AsReadOnly(), imagesRemote.AsReadOnly()), + fonts: new EpubContentCollection(fontsLocal.AsReadOnly(), fontsRemote.AsReadOnly()), + audio: new EpubContentCollection(audioLocal.AsReadOnly(), audioRemote.AsReadOnly()), + allFiles: new EpubContentCollection(allFilesLocal.AsReadOnly(), allFilesRemote.AsReadOnly()) ); } @@ -368,7 +232,6 @@ public static EpubContent CreateFullTestEpubContent(bool populateRemoteFilesCont key: REMOTE_HTML_CONTENT_FILE_HREF, contentType: HTML_CONTENT_TYPE, contentMimeType: HTML_CONTENT_MIME_TYPE, - url: REMOTE_HTML_CONTENT_FILE_HREF, content: TestEpubFiles.REMOTE_HTML_FILE_CONTENT ); @@ -378,7 +241,6 @@ public static EpubContent CreateFullTestEpubContent(bool populateRemoteFilesCont key: REMOTE_HTML_CONTENT_FILE_HREF, contentType: HTML_CONTENT_TYPE, contentMimeType: HTML_CONTENT_MIME_TYPE, - url: REMOTE_HTML_CONTENT_FILE_HREF, content: null ); @@ -388,7 +250,6 @@ public static EpubContent CreateFullTestEpubContent(bool populateRemoteFilesCont key: REMOTE_CSS_CONTENT_FILE_HREF, contentType: CSS_CONTENT_TYPE, contentMimeType: CSS_CONTENT_MIME_TYPE, - url: REMOTE_CSS_CONTENT_FILE_HREF, content: TestEpubFiles.REMOTE_CSS_FILE_CONTENT ); @@ -398,7 +259,6 @@ public static EpubContent CreateFullTestEpubContent(bool populateRemoteFilesCont key: REMOTE_CSS_CONTENT_FILE_HREF, contentType: CSS_CONTENT_TYPE, contentMimeType: CSS_CONTENT_MIME_TYPE, - url: REMOTE_CSS_CONTENT_FILE_HREF, content: null ); @@ -408,7 +268,6 @@ public static EpubContent CreateFullTestEpubContent(bool populateRemoteFilesCont key: REMOTE_IMAGE_CONTENT_FILE_HREF, contentType: IMAGE_CONTENT_TYPE, contentMimeType: IMAGE_CONTENT_MIME_TYPE, - url: REMOTE_IMAGE_CONTENT_FILE_HREF, content: TestEpubFiles.REMOTE_IMAGE_FILE_CONTENT ); @@ -418,7 +277,6 @@ public static EpubContent CreateFullTestEpubContent(bool populateRemoteFilesCont key: REMOTE_IMAGE_CONTENT_FILE_HREF, contentType: IMAGE_CONTENT_TYPE, contentMimeType: IMAGE_CONTENT_MIME_TYPE, - url: REMOTE_IMAGE_CONTENT_FILE_HREF, content: null ); @@ -428,7 +286,6 @@ public static EpubContent CreateFullTestEpubContent(bool populateRemoteFilesCont key: REMOTE_FONT_CONTENT_FILE_HREF, contentType: FONT_CONTENT_TYPE, contentMimeType: FONT_CONTENT_MIME_TYPE, - url: REMOTE_FONT_CONTENT_FILE_HREF, content: TestEpubFiles.REMOTE_FONT_FILE_CONTENT ); @@ -438,7 +295,6 @@ public static EpubContent CreateFullTestEpubContent(bool populateRemoteFilesCont key: REMOTE_FONT_CONTENT_FILE_HREF, contentType: FONT_CONTENT_TYPE, contentMimeType: FONT_CONTENT_MIME_TYPE, - url: REMOTE_FONT_CONTENT_FILE_HREF, content: null ); @@ -448,7 +304,6 @@ public static EpubContent CreateFullTestEpubContent(bool populateRemoteFilesCont key: REMOTE_XML_CONTENT_FILE_HREF, contentType: XML_CONTENT_TYPE, contentMimeType: XML_CONTENT_MIME_TYPE, - url: REMOTE_XML_CONTENT_FILE_HREF, content: TestEpubFiles.REMOTE_XML_FILE_CONTENT ); @@ -458,7 +313,6 @@ public static EpubContent CreateFullTestEpubContent(bool populateRemoteFilesCont key: REMOTE_XML_CONTENT_FILE_HREF, contentType: XML_CONTENT_TYPE, contentMimeType: XML_CONTENT_MIME_TYPE, - url: REMOTE_XML_CONTENT_FILE_HREF, content: null ); @@ -468,7 +322,6 @@ public static EpubContent CreateFullTestEpubContent(bool populateRemoteFilesCont key: REMOTE_AUDIO_CONTENT_FILE_HREF, contentType: AUDIO_CONTENT_TYPE, contentMimeType: AUDIO_MPEG_CONTENT_MIME_TYPE, - url: REMOTE_AUDIO_CONTENT_FILE_HREF, content: TestEpubFiles.REMOTE_AUDIO_FILE_CONTENT ); @@ -478,7 +331,6 @@ public static EpubContent CreateFullTestEpubContent(bool populateRemoteFilesCont key: REMOTE_AUDIO_CONTENT_FILE_HREF, contentType: AUDIO_CONTENT_TYPE, contentMimeType: AUDIO_MPEG_CONTENT_MIME_TYPE, - url: REMOTE_AUDIO_CONTENT_FILE_HREF, content: null ); @@ -488,7 +340,6 @@ public static EpubContent CreateFullTestEpubContent(bool populateRemoteFilesCont key: REMOTE_VIDEO_CONTENT_FILE_HREF, contentType: OTHER_CONTENT_TYPE, contentMimeType: VIDEO_MP4_CONTENT_MIME_TYPE, - url: REMOTE_VIDEO_CONTENT_FILE_HREF, content: TestEpubFiles.REMOTE_VIDEO_FILE_CONTENT ); @@ -498,7 +349,6 @@ public static EpubContent CreateFullTestEpubContent(bool populateRemoteFilesCont key: REMOTE_VIDEO_CONTENT_FILE_HREF, contentType: OTHER_CONTENT_TYPE, contentMimeType: VIDEO_MP4_CONTENT_MIME_TYPE, - url: REMOTE_VIDEO_CONTENT_FILE_HREF, content: null ); diff --git a/Source/VersOne.Epub.Test/Unit/TestData/TestEpubContentRef.cs b/Source/VersOne.Epub.Test/Unit/TestData/TestEpubContentRef.cs index fe599e8..987357c 100644 --- a/Source/VersOne.Epub.Test/Unit/TestData/TestEpubContentRef.cs +++ b/Source/VersOne.Epub.Test/Unit/TestData/TestEpubContentRef.cs @@ -7,249 +7,113 @@ internal static class TestEpubContentRef { public static EpubContentRef CreateMinimalTestEpubContentRefWithNavigation() { + List htmlLocal = new() + { + NavFileRef + }; + List allFilesLocal = new() + { + NavFileRef + }; return new ( cover: null, navigationHtmlFile: NavFileRef, - html: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - NAV_FILE_NAME, - NavFileRef - } - } - ), + html: new EpubContentCollectionRef(htmlLocal.AsReadOnly()), css: new EpubContentCollectionRef(), images: new EpubContentCollectionRef(), fonts: new EpubContentCollectionRef(), audio: new EpubContentCollectionRef(), - allFiles: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - NAV_FILE_NAME, - NavFileRef - } - } - ) + allFiles: new EpubContentCollectionRef(allFilesLocal.AsReadOnly()) ); } public static EpubContentRef CreateFullTestEpubContentRef() { + List htmlLocal = new() + { + Chapter1FileRef, + Chapter2FileRef, + NavFileRef + }; + List htmlRemote = new() + { + RemoteHtmlContentFileRef + }; + List cssLocal = new() + { + Styles1FileRef, + Styles2FileRef + }; + List cssRemote = new() + { + RemoteCssContentFileRef + }; + List imagesLocal = new() + { + Image1FileRef, + Image2FileRef, + CoverFileRef + }; + List imagesRemote = new() + { + RemoteImageContentFileRef + }; + List fontsLocal = new() + { + Font1FileRef, + Font2FileRef + }; + List fontsRemote = new() + { + RemoteFontContentFileRef + }; + List audioLocal = new() + { + Audio1FileRef, + Audio2FileRef + }; + List audioRemote = new() + { + RemoteAudioContentFileRef + }; + List allFilesLocal = new() + { + Chapter1FileRef, + Chapter2FileRef, + Styles1FileRef, + Styles2FileRef, + Image1FileRef, + Image2FileRef, + Font1FileRef, + Font2FileRef, + Audio1FileRef, + Audio2FileRef, + VideoFileRef, + NavFileRef, + CoverFileRef, + NcxFileRef + }; + List allFilesRemote = new() + { + RemoteHtmlContentFileRef, + RemoteCssContentFileRef, + RemoteImageContentFileRef, + RemoteFontContentFileRef, + RemoteXmlContentFileRef, + RemoteAudioContentFileRef, + RemoteVideoContentFileRef + }; return new EpubContentRef ( cover: CoverFileRef, navigationHtmlFile: NavFileRef, - html: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - CHAPTER1_FILE_NAME, - Chapter1FileRef - }, - { - CHAPTER2_FILE_NAME, - Chapter2FileRef - }, - { - NAV_FILE_NAME, - NavFileRef - } - }, - remote: new Dictionary() - { - { - REMOTE_HTML_CONTENT_FILE_HREF, - RemoteHtmlContentFileRef - } - } - ), - css: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - STYLES1_FILE_NAME, - Styles1FileRef - }, - { - STYLES2_FILE_NAME, - Styles2FileRef - } - }, - remote: new Dictionary() - { - { - REMOTE_CSS_CONTENT_FILE_HREF, - RemoteCssContentFileRef - } - } - ), - images: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - IMAGE1_FILE_NAME, - Image1FileRef - }, - { - IMAGE2_FILE_NAME, - Image2FileRef - }, - { - COVER_FILE_NAME, - CoverFileRef - } - }, - remote: new Dictionary() - { - { - REMOTE_IMAGE_CONTENT_FILE_HREF, - RemoteImageContentFileRef - } - } - ), - fonts: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - FONT1_FILE_NAME, - Font1FileRef - }, - { - FONT2_FILE_NAME, - Font2FileRef - } - }, - remote: new Dictionary() - { - { - REMOTE_FONT_CONTENT_FILE_HREF, - RemoteFontContentFileRef - } - } - ), - audio: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - AUDIO1_FILE_NAME, - Audio1FileRef - }, - { - AUDIO2_FILE_NAME, - Audio2FileRef - } - }, - remote: new Dictionary() - { - { - REMOTE_AUDIO_CONTENT_FILE_HREF, - RemoteAudioContentFileRef - } - } - ), - allFiles: new EpubContentCollectionRef - ( - local: new Dictionary() - { - { - CHAPTER1_FILE_NAME, - Chapter1FileRef - }, - { - CHAPTER2_FILE_NAME, - Chapter2FileRef - }, - { - STYLES1_FILE_NAME, - Styles1FileRef - }, - { - STYLES2_FILE_NAME, - Styles2FileRef - }, - { - IMAGE1_FILE_NAME, - Image1FileRef - }, - { - IMAGE2_FILE_NAME, - Image2FileRef - }, - { - FONT1_FILE_NAME, - Font1FileRef - }, - { - FONT2_FILE_NAME, - Font2FileRef - }, - { - AUDIO1_FILE_NAME, - Audio1FileRef - }, - { - AUDIO2_FILE_NAME, - Audio2FileRef - }, - { - VIDEO_FILE_NAME, - VideoFileRef - }, - { - NAV_FILE_NAME, - NavFileRef - }, - { - COVER_FILE_NAME, - CoverFileRef - }, - { - NCX_FILE_NAME, - NcxFileRef - } - }, - remote: new Dictionary() - { - { - REMOTE_HTML_CONTENT_FILE_HREF, - RemoteHtmlContentFileRef - }, - { - REMOTE_CSS_CONTENT_FILE_HREF, - RemoteCssContentFileRef - }, - { - REMOTE_IMAGE_CONTENT_FILE_HREF, - RemoteImageContentFileRef - }, - { - REMOTE_FONT_CONTENT_FILE_HREF, - RemoteFontContentFileRef - }, - { - REMOTE_XML_CONTENT_FILE_HREF, - RemoteXmlContentFileRef - }, - { - REMOTE_AUDIO_CONTENT_FILE_HREF, - RemoteAudioContentFileRef - }, - { - REMOTE_VIDEO_CONTENT_FILE_HREF, - RemoteVideoContentFileRef - } - } - ) + html: new EpubContentCollectionRef(htmlLocal.AsReadOnly(), htmlRemote.AsReadOnly()), + css: new EpubContentCollectionRef(cssLocal.AsReadOnly(), cssRemote.AsReadOnly()), + images: new EpubContentCollectionRef(imagesLocal.AsReadOnly(), imagesRemote.AsReadOnly()), + fonts: new EpubContentCollectionRef(fontsLocal.AsReadOnly(), fontsRemote.AsReadOnly()), + audio: new EpubContentCollectionRef(audioLocal.AsReadOnly(), audioRemote.AsReadOnly()), + allFiles: new EpubContentCollectionRef(allFilesLocal.AsReadOnly(), allFilesRemote.AsReadOnly()) ); } diff --git a/Source/VersOne.Epub.Test/Unit/Utils/ZipPathUtilsTests.cs b/Source/VersOne.Epub.Test/Unit/Utils/ContentPathUtilsTests.cs similarity index 64% rename from Source/VersOne.Epub.Test/Unit/Utils/ZipPathUtilsTests.cs rename to Source/VersOne.Epub.Test/Unit/Utils/ContentPathUtilsTests.cs index 966a83c..058693e 100644 --- a/Source/VersOne.Epub.Test/Unit/Utils/ZipPathUtilsTests.cs +++ b/Source/VersOne.Epub.Test/Unit/Utils/ContentPathUtilsTests.cs @@ -2,8 +2,24 @@ namespace VersOne.Epub.Test.Unit.Utils { - public class ZipPathUtilsTests + public class ContentPathUtilsTests { + [Theory(DisplayName = "Testing if the specified path is a local path should succeed")] + [InlineData("Directory/File.html", true)] + [InlineData("", true)] + [InlineData("https://example.com/books/123/chapter1.html", false)] + public void IsLocalPathWithNonNullPathTest(string path, bool expectedResult) + { + bool actualResult = ContentPathUtils.IsLocalPath(path); + Assert.Equal(expectedResult, actualResult); + } + + [Fact(DisplayName = "IsLocalPath should throw ArgumentNullException if path parameter is null")] + public void IsLocalPathWithNullPathTest() + { + Assert.Throws(() => ContentPathUtils.IsLocalPath(null!)); + } + [Theory(DisplayName = "Getting the directory path for a valid file path should succeed")] [InlineData("Directory/File.html", "Directory")] [InlineData("Directory/Subdirectory/File.html", "Directory/Subdirectory")] @@ -14,7 +30,7 @@ public class ZipPathUtilsTests [InlineData("../File.html", "..")] public void GetDirectoryPathTest(string filePath, string expectedDirectoryPath) { - string actualDirectoryPath = ZipPathUtils.GetDirectoryPath(filePath); + string actualDirectoryPath = ContentPathUtils.GetDirectoryPath(filePath); Assert.Equal(expectedDirectoryPath, actualDirectoryPath); } @@ -34,7 +50,7 @@ public void GetDirectoryPathTest(string filePath, string expectedDirectoryPath) [InlineData(null, null, null)] public void CombineTest(string directory, string fileName, string expectedResult) { - string actualResult = ZipPathUtils.Combine(directory, fileName); + string actualResult = ContentPathUtils.Combine(directory, fileName); Assert.Equal(expectedResult, actualResult); } } diff --git a/Source/VersOne.Epub.WpfDemo/Models/BookModel.cs b/Source/VersOne.Epub.WpfDemo/Models/BookModel.cs index 9bea8eb..89dbb9c 100644 --- a/Source/VersOne.Epub.WpfDemo/Models/BookModel.cs +++ b/Source/VersOne.Epub.WpfDemo/Models/BookModel.cs @@ -34,9 +34,9 @@ public List GetNavigation(EpubBook epubBook) public List GetReadingOrder(EpubBook epubBook) { - Dictionary images = epubBook.Content.Images.Local.ToDictionary(imageFile => imageFile.Key, imageFile => imageFile.Value.Content); - Dictionary styleSheets = epubBook.Content.Css.Local.ToDictionary(cssFile => cssFile.Key, cssFile => cssFile.Value.Content); - Dictionary fonts = epubBook.Content.Fonts.Local.ToDictionary(fontFile => fontFile.Key, fontFile => fontFile.Value.Content); + Dictionary images = epubBook.Content.Images.Local.ToDictionary(imageFile => imageFile.Key, imageFile => imageFile.Content); + Dictionary styleSheets = epubBook.Content.Css.Local.ToDictionary(cssFile => cssFile.Key, cssFile => cssFile.Content); + Dictionary fonts = epubBook.Content.Fonts.Local.ToDictionary(fontFile => fontFile.Key, fontFile => fontFile.Content); List result = new List(); foreach (EpubLocalTextContentFile epubHtmlFile in epubBook.ReadingOrder) { diff --git a/Source/VersOne.Epub/Content/Collections/EpubContentCollection.cs b/Source/VersOne.Epub/Content/Collections/EpubContentCollection.cs index 110d604..6e6a120 100644 --- a/Source/VersOne.Epub/Content/Collections/EpubContentCollection.cs +++ b/Source/VersOne.Epub/Content/Collections/EpubContentCollection.cs @@ -1,37 +1,251 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; namespace VersOne.Epub { /// /// A container for a subset of content files within the EPUB book. /// - /// The type of the content files stored within the dictionary. - /// The type of the content files stored within the dictionary. + /// The type of the content files stored within the collection. + /// The type of the content files stored within the collection. public class EpubContentCollection where TLocalContentFile : EpubLocalContentFile where TRemoteContentFile : EpubRemoteContentFile { + private readonly Dictionary localByKey; + private readonly Dictionary localByFilePath; + private readonly Dictionary remoteByUrl; + /// /// Initializes a new instance of the class. /// /// Local content files to be stored within this container. /// Remote content files to be stored within this container. - public EpubContentCollection(Dictionary? local = null, Dictionary? remote = null) + public EpubContentCollection(ReadOnlyCollection? local = null, ReadOnlyCollection? remote = null) + { + Local = local ?? new ReadOnlyCollection(new List()); + Remote = remote ?? new ReadOnlyCollection(new List()); + localByKey = new Dictionary(); + localByFilePath = new Dictionary(); + foreach (TLocalContentFile localContentFile in Local) + { + if (localByKey.ContainsKey(localContentFile.Key)) + { + throw new EpubPackageException($"Incorrect EPUB manifest: item with href = \"{localContentFile.Key}\" is not unique."); + } + localByKey.Add(localContentFile.Key, localContentFile); + if (localByFilePath.ContainsKey(localContentFile.FilePath)) + { + throw new EpubPackageException($"Incorrect EPUB manifest: item with absolute file path = \"{localContentFile.FilePath}\" is not unique."); + } + localByFilePath.Add(localContentFile.FilePath, localContentFile); + } + remoteByUrl = new Dictionary(); + foreach (TRemoteContentFile remoteContentFile in Remote) + { + if (remoteByUrl.ContainsKey(remoteContentFile.Url)) + { + throw new EpubPackageException($"Incorrect EPUB manifest: item with href = \"{remoteContentFile.Url}\" is not unique."); + } + remoteByUrl.Add(remoteContentFile.Url, remoteContentFile); + } + } + + /// + /// Gets a collection of local content files stored within this container. + /// + public ReadOnlyCollection Local { get; } + + /// + /// Gets a collection of remote content files stored within this container. + /// + public ReadOnlyCollection Remote { get; } + + /// + /// Determines whether a local content file with the specified value exists in this container. + /// + /// The value of the local content file to locate in this container. + /// + /// true if the local content file with the specified value exists in this container; otherwise, false. + /// + /// is null. + public bool ContainsLocalFileWithKey(string key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + return localByKey.ContainsKey(key); + } + + /// + /// Gets the local content file with the specified value. + /// + /// The of the local content file to get. + /// The local content file with the specified value. + /// is null. + /// + /// Local content file with the specified value does not exist in this container. + /// + public TLocalContentFile GetLocalFileByKey(string key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + try + { + return localByKey[key]; + } + catch (KeyNotFoundException) + { + throw new EpubContentCollectionException($"Local content file with key = \"{key}\" does not exist in this container."); + } + } + + /// + /// Gets the local content file with the specified value. + /// + /// The of the local content file to get. + /// + /// When this method returns, contains the local content file with the specified value, + /// if such local content file exists in the container; otherwise, null. + /// + /// 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) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + return localByKey.TryGetValue(key, out localContentFile); + } + + /// + /// Determines whether a local content file with the specified value exists in this container. + /// + /// The value of the local content file to locate in this container. + /// + /// true if the local content file with the specified value exists in this container; otherwise, false. + /// + /// is null. + public bool ContainsLocalFileWithFilePath(string filePath) + { + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } + return localByFilePath.ContainsKey(filePath); + } + + /// + /// Gets the local content file with the specified value. + /// + /// The of the local content file to get. + /// The local content file with the specified value. + /// is null. + /// + /// Local content file with the specified value does not exist in this container. + /// + public TLocalContentFile GetLocalFileByFilePath(string filePath) { - Local = local ?? new Dictionary(); - Remote = remote ?? new Dictionary(); + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } + try + { + return localByFilePath[filePath]; + } + catch (KeyNotFoundException) + { + throw new EpubContentCollectionException($"Local content file with file path = \"{filePath}\" does not exist in this container."); + } } /// - /// Gets a collection of key-value pairs where the key is the value of the property of the content file - /// and the value is the content file itself. This collection contains only the local content files stored within this container. + /// Gets the local content file with the specified value. /// - public Dictionary Local { get; } + /// The of the local content file to get. + /// + /// When this method returns, contains the local content file with the specified value, + /// if such local content file exists in the container; otherwise, null. + /// + /// 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) + { + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } + return localByFilePath.TryGetValue(filePath, out localContentFile); + } /// - /// Gets a collection of key-value pairs where the key is the value of the property of the content file - /// and the value is the content file itself. This collection contains only the remote content files stored within this container. + /// Determines whether a remote content file with the specified value exists in this container. /// - public Dictionary Remote { get; } + /// The value of the remote content file to locate in this container. + /// + /// true if the remote content file with the specified value exists in this container; otherwise, false. + /// + /// is null. + public bool ContainsRemoteFileWithUrl(string url) + { + if (url == null) + { + throw new ArgumentNullException(nameof(url)); + } + return remoteByUrl.ContainsKey(url); + } + + /// + /// Gets the remote content file with the specified value. + /// + /// The of the remote content file to get. + /// The remote content file with the specified value. + /// is null. + /// + /// Remote content file with the specified value does not exist in this container. + /// + public TRemoteContentFile GetRemoteFileByUrl(string url) + { + if (url == null) + { + throw new ArgumentNullException(nameof(url)); + } + try + { + return remoteByUrl[url]; + } + catch (KeyNotFoundException) + { + throw new EpubContentCollectionException($"Remote content file with URL = \"{url}\" does not exist in this container."); + } + } + + /// + /// Gets the remote content file with the specified value. + /// + /// The of the remote content file to get. + /// + /// When this method returns, contains the remote content file with the specified value, + /// if such remote content file exists in the container; otherwise, null. + /// + /// 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) + { + if (url == null) + { + throw new ArgumentNullException(nameof(url)); + } + return remoteByUrl.TryGetValue(url, out remoteContentFile); + } } } diff --git a/Source/VersOne.Epub/Content/Collections/EpubContentCollectionRef.cs b/Source/VersOne.Epub/Content/Collections/EpubContentCollectionRef.cs index 8bdd580..740e941 100644 --- a/Source/VersOne.Epub/Content/Collections/EpubContentCollectionRef.cs +++ b/Source/VersOne.Epub/Content/Collections/EpubContentCollectionRef.cs @@ -1,37 +1,251 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; namespace VersOne.Epub { /// /// A container for a subset of content file references within the EPUB book. /// - /// The type of the content file references stored within the dictionary. - /// The type of the content file references stored within the dictionary. + /// The type of the content file references stored within the collection. + /// The type of the content file references stored within the collection. public class EpubContentCollectionRef where TLocalContentFileRef : EpubLocalContentFileRef where TRemoteContentFileRef : EpubRemoteContentFileRef { + private readonly Dictionary localByKey; + private readonly Dictionary localByFilePath; + private readonly Dictionary remoteByUrl; + /// /// Initializes a new instance of the class. /// /// Local content file references to be stored within this container. /// Remote content file references to be stored within this container. - public EpubContentCollectionRef(Dictionary? local = null, Dictionary? remote = null) + public EpubContentCollectionRef(ReadOnlyCollection? local = null, ReadOnlyCollection? remote = null) + { + Local = local ?? new ReadOnlyCollection(new List()); + Remote = remote ?? new ReadOnlyCollection(new List()); + localByKey = new Dictionary(); + localByFilePath = new Dictionary(); + foreach (TLocalContentFileRef localContentFileRef in Local) + { + if (localByKey.ContainsKey(localContentFileRef.Key)) + { + throw new EpubPackageException($"Incorrect EPUB manifest: item with href = \"{localContentFileRef.Key}\" is not unique."); + } + localByKey.Add(localContentFileRef.Key, localContentFileRef); + if (localByFilePath.ContainsKey(localContentFileRef.FilePath)) + { + throw new EpubPackageException($"Incorrect EPUB manifest: item with absolute file path = \"{localContentFileRef.FilePath}\" is not unique."); + } + localByFilePath.Add(localContentFileRef.FilePath, localContentFileRef); + } + remoteByUrl = new Dictionary(); + foreach (TRemoteContentFileRef remoteContentFileRef in Remote) + { + if (remoteByUrl.ContainsKey(remoteContentFileRef.Url)) + { + throw new EpubPackageException($"Incorrect EPUB manifest: item with href = \"{remoteContentFileRef.Url}\" is not unique."); + } + remoteByUrl.Add(remoteContentFileRef.Url, remoteContentFileRef); + } + } + + /// + /// Gets a collection of local content file references stored within this container. + /// + public ReadOnlyCollection Local { get; } + + /// + /// Gets a collection of remote content file references stored within this container. + /// + public ReadOnlyCollection Remote { get; } + + /// + /// Determines whether a local content file reference with the specified value exists in this container. + /// + /// The value of the local content file reference to locate in this container. + /// + /// true if the local content file reference with the specified value exists in this container; otherwise, false. + /// + /// is null. + public bool ContainsLocalFileRefWithKey(string key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + return localByKey.ContainsKey(key); + } + + /// + /// Gets the local content file reference with the specified value. + /// + /// The of the local content file reference to get. + /// The local content file reference with the specified value. + /// is null. + /// + /// Local content file reference with the specified value does not exist in this container. + /// + public TLocalContentFileRef GetLocalFileRefByKey(string key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + try + { + return localByKey[key]; + } + catch (KeyNotFoundException) + { + throw new EpubContentCollectionRefException($"Local content file reference with key = \"{key}\" does not exist in this container."); + } + } + + /// + /// Gets the local content file reference with the specified value. + /// + /// The of the local content file reference to get. + /// + /// When this method returns, contains the local content file reference with the specified value, + /// if such local content file reference exists in the container; otherwise, null. + /// + /// 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) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + return localByKey.TryGetValue(key, out localContentFileRef); + } + + /// + /// Determines whether a local content file reference with the specified value exists in this container. + /// + /// The value of the local content file reference to locate in this container. + /// + /// true if the local content file reference with the specified value exists in this container; otherwise, false. + /// + /// is null. + public bool ContainsLocalFileRefWithFilePath(string filePath) + { + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } + return localByFilePath.ContainsKey(filePath); + } + + /// + /// Gets the local content file reference with the specified value. + /// + /// The of the local content file reference to get. + /// The local content file reference with the specified value. + /// is null. + /// + /// Local content file reference with the specified value does not exist in this container. + /// + public TLocalContentFileRef GetLocalFileRefByFilePath(string filePath) { - Local = local ?? new Dictionary(); - Remote = remote ?? new Dictionary(); + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } + try + { + return localByFilePath[filePath]; + } + catch (KeyNotFoundException) + { + throw new EpubContentCollectionRefException($"Local content file reference with file path = \"{filePath}\" does not exist in this container."); + } } /// - /// Gets a collection of key-value pairs where the key is the value of the property of the content file reference - /// and the value is the content file reference itself. This collection contains only the local content file references stored within this container. + /// Gets the local content file reference with the specified value. /// - public Dictionary Local { get; } + /// The of the local content file reference to get. + /// + /// When this method returns, contains the local content file reference with the specified value, + /// if such local content file reference exists in the container; otherwise, null. + /// + /// 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) + { + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } + return localByFilePath.TryGetValue(filePath, out localContentFileRef); + } /// - /// Gets a collection of key-value pairs where the key is the value of the property of the content file reference - /// and the value is the content file reference itself. This collection contains only the remote content file references stored within this container. + /// Determines whether a remote content file reference with the specified value exists in this container. /// - public Dictionary Remote { get; } + /// The value of the remote content file reference to locate in this container. + /// + /// true if the remote content file reference with the specified value exists in this container; otherwise, false. + /// + /// is null. + public bool ContainsRemoteFileRefWithUrl(string url) + { + if (url == null) + { + throw new ArgumentNullException(nameof(url)); + } + return remoteByUrl.ContainsKey(url); + } + + /// + /// Gets the remote content file reference with the specified value. + /// + /// The of the remote content file reference to get. + /// The remote content file reference with the specified value. + /// is null. + /// + /// Remote content file reference with the specified value does not exist in this container. + /// + public TRemoteContentFileRef GetRemoteFileRefByUrl(string url) + { + if (url == null) + { + throw new ArgumentNullException(nameof(url)); + } + try + { + return remoteByUrl[url]; + } + catch (KeyNotFoundException) + { + throw new EpubContentCollectionRefException($"Remote content file reference with URL = \"{url}\" does not exist in this container."); + } + } + + /// + /// Gets the remote content file reference with the specified value. + /// + /// The of the remote content file reference to get. + /// + /// When this method returns, contains the remote content file reference with the specified value, + /// if such remote content file reference exists in the container; otherwise, null. + /// + /// 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) + { + if (url == null) + { + throw new ArgumentNullException(nameof(url)); + } + return remoteByUrl.TryGetValue(url, out remoteContentFileRef); + } } } diff --git a/Source/VersOne.Epub/Content/Loaders/EpubLocalContentLoader.cs b/Source/VersOne.Epub/Content/Loaders/EpubLocalContentLoader.cs index d45fa05..4a0e0ab 100644 --- a/Source/VersOne.Epub/Content/Loaders/EpubLocalContentLoader.cs +++ b/Source/VersOne.Epub/Content/Loaders/EpubLocalContentLoader.cs @@ -58,7 +58,7 @@ private IZipFileEntry GetContentFileEntry(EpubContentFileRefMetadata contentFile throw new ObjectDisposedException(nameof(epubFile), "EPUB file stored within this local content file loader has been disposed."); } ReplacementContentFileEntry? newReplacementContentFileEntry = null; - string contentFilePath = ZipPathUtils.Combine(contentDirectoryPath, contentFileRefMetadata.Key); + string contentFilePath = ContentPathUtils.Combine(contentDirectoryPath, contentFileRefMetadata.Key); IZipFileEntry? contentFileEntry = epubFile.GetEntry(contentFilePath); if (contentFileEntry == null) { diff --git a/Source/VersOne.Epub/Content/Remote/EpubRemoteByteContentFile.cs b/Source/VersOne.Epub/Content/Remote/EpubRemoteByteContentFile.cs index b5267b2..7d03103 100644 --- a/Source/VersOne.Epub/Content/Remote/EpubRemoteByteContentFile.cs +++ b/Source/VersOne.Epub/Content/Remote/EpubRemoteByteContentFile.cs @@ -15,13 +15,11 @@ public class EpubRemoteByteContentFile : EpubRemoteContentFile /// The content file key as it is declared in the EPUB manifest (in the property). /// The type of the content of the file. /// The MIME type of the content of the file. - /// The absolute URI of the remote content file (as it is specified in the EPUB manifest). /// The content of the file. /// The parameter is null. - /// The parameter is null. /// The parameter is null. - public EpubRemoteByteContentFile(string key, EpubContentType contentType, string contentMimeType, string url, byte[]? content) - : base(key, contentType, contentMimeType, url) + public EpubRemoteByteContentFile(string key, EpubContentType contentType, string contentMimeType, byte[]? content) + : base(key, contentType, contentMimeType) { Content = content; } diff --git a/Source/VersOne.Epub/Content/Remote/EpubRemoteContentFile.cs b/Source/VersOne.Epub/Content/Remote/EpubRemoteContentFile.cs index 34f8005..bd1b7a4 100644 --- a/Source/VersOne.Epub/Content/Remote/EpubRemoteContentFile.cs +++ b/Source/VersOne.Epub/Content/Remote/EpubRemoteContentFile.cs @@ -15,19 +15,16 @@ public abstract class EpubRemoteContentFile : EpubContentFile /// The content file key as it is declared in the EPUB manifest (in the property). /// The type of the content of the file. /// The MIME type of the content of the file. - /// The absolute URI of the remote content file (as it is specified in the EPUB manifest). /// The parameter is null. - /// The parameter is null. - protected EpubRemoteContentFile(string key, EpubContentType contentType, string contentMimeType, string url) + protected EpubRemoteContentFile(string key, EpubContentType contentType, string contentMimeType) : base(key, contentType, contentMimeType) { - Url = url ?? throw new ArgumentNullException(nameof(url)); } /// /// Gets the absolute URI of the remote content file (as it is specified in the EPUB manifest). /// - public string Url { get; } + public string Url => Key; /// /// Gets the location of the content file which is always for remote content files. diff --git a/Source/VersOne.Epub/Content/Remote/EpubRemoteTextContentFile.cs b/Source/VersOne.Epub/Content/Remote/EpubRemoteTextContentFile.cs index 9678b75..3fda4ab 100644 --- a/Source/VersOne.Epub/Content/Remote/EpubRemoteTextContentFile.cs +++ b/Source/VersOne.Epub/Content/Remote/EpubRemoteTextContentFile.cs @@ -15,12 +15,10 @@ public class EpubRemoteTextContentFile : EpubRemoteContentFile /// The content file key as it is declared in the EPUB manifest (in the property). /// The type of the content of the file. /// The MIME type of the content of the file. - /// The absolute URI of the remote content file (as it is specified in the EPUB manifest). /// The content of the file. /// The parameter is null. - /// The parameter is null. - public EpubRemoteTextContentFile(string key, EpubContentType contentType, string contentMimeType, string url, string? content) - : base(key, contentType, contentMimeType, url) + public EpubRemoteTextContentFile(string key, EpubContentType contentType, string contentMimeType, string? content) + : base(key, contentType, contentMimeType) { Content = content; } diff --git a/Source/VersOne.Epub/Entities/EpubNavigationItemLink.cs b/Source/VersOne.Epub/Entities/EpubNavigationItemLink.cs index 2fa95c9..d6896e9 100644 --- a/Source/VersOne.Epub/Entities/EpubNavigationItemLink.cs +++ b/Source/VersOne.Epub/Entities/EpubNavigationItemLink.cs @@ -9,38 +9,50 @@ namespace VersOne.Epub public class EpubNavigationItemLink { /// - /// Initializes a new instance of the class using a specified content file URL and a specified base directory path. + /// Initializes a new instance of the class using a specified content file URL with anchor and a specified base directory path. /// - /// Relative file path or a URL of the content file with an optional anchor (as it is specified in the EPUB manifest). + /// Relative file path of the content file with an optional anchor (as it is specified in the EPUB manifest). /// The path of the directory within the EPUB archive which acts as a base directory for a relative content file path. - /// The parameter is null. + /// The parameter is null. /// The parameter is null. - public EpubNavigationItemLink(string contentFileUrl, string baseDirectoryPath) + /// The parameter points to a remote URL. + public EpubNavigationItemLink(string contentFileUrlWithAnchor, string baseDirectoryPath) { - if (contentFileUrl == null) + if (contentFileUrlWithAnchor == null) { - throw new ArgumentNullException(nameof(contentFileUrl)); + throw new ArgumentNullException(nameof(contentFileUrlWithAnchor)); } if (baseDirectoryPath == null) { throw new ArgumentNullException(nameof(baseDirectoryPath)); } - UrlParser urlParser = new(contentFileUrl); - ContentFileName = urlParser.Path; - ContentFilePath = ZipPathUtils.Combine(baseDirectoryPath, ContentFileName); + if (!ContentPathUtils.IsLocalPath(contentFileUrlWithAnchor)) + { + throw new ArgumentException($"\"{contentFileUrlWithAnchor}\" points to a remote resource."); + } + UrlParser urlParser = new(contentFileUrlWithAnchor); + ContentFileUrl = urlParser.Path; + ContentFilePath = ContentPathUtils.Combine(baseDirectoryPath, ContentFileUrl); Anchor = urlParser.Anchor; } /// - /// Initializes a new instance of the class with specified content file name, content file path in EPUB archive, and anchor. + /// Initializes a new instance of the class with specified content file URL without anchor, content file path, and anchor. /// - /// The file path portion of the content file URL. + /// The file path portion of the content file URL without anchor. /// The file path portion of the content file URL converted to the absolute file path within the EPUB archive. /// The anchor portion of the content file URL. - public EpubNavigationItemLink(string contentFileName, string contentFilePath, string? anchor) + /// The parameter is null. + /// The parameter is null. + /// The parameter points to a remote URL. + public EpubNavigationItemLink(string contentFileUrl, string contentFilePath, string? anchor) { - ContentFileName = contentFileName ?? throw new ArgumentNullException(nameof(contentFileName)); + ContentFileUrl = contentFileUrl ?? throw new ArgumentNullException(nameof(contentFileUrl)); ContentFilePath = contentFilePath ?? throw new ArgumentNullException(nameof(contentFilePath)); + if (!ContentPathUtils.IsLocalPath(contentFileUrl)) + { + throw new ArgumentException($"\"{contentFileUrl}\" points to a remote resource."); + } Anchor = anchor; } @@ -48,7 +60,7 @@ public EpubNavigationItemLink(string contentFileName, string contentFilePath, st /// Gets the file path portion of the content file URL. /// For example, if the content file URL is '../content/chapter1.html#section1', then the value of this property will be '../content/chapter1.html'. /// - public string ContentFileName { get; } + public string ContentFileUrl { get; } /// /// Gets the file path portion of the content file URL converted to the absolute file path within the EPUB archive. diff --git a/Source/VersOne.Epub/Exceptions/Epub2NcxException.cs b/Source/VersOne.Epub/Exceptions/Epub2NcxException.cs index 98a9f27..9e6ca95 100644 --- a/Source/VersOne.Epub/Exceptions/Epub2NcxException.cs +++ b/Source/VersOne.Epub/Exceptions/Epub2NcxException.cs @@ -30,7 +30,7 @@ public Epub2NcxException(string message) /// /// The error message that explains the reason for the exception. /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. - public Epub2NcxException(string message, Exception innerException) + public Epub2NcxException(string message, Exception? innerException) : base(message, innerException, EpubSchemaFileType.EPUB2_NCX) { } diff --git a/Source/VersOne.Epub/Exceptions/Epub3NavException.cs b/Source/VersOne.Epub/Exceptions/Epub3NavException.cs index 25bb6bc..0f08ce1 100644 --- a/Source/VersOne.Epub/Exceptions/Epub3NavException.cs +++ b/Source/VersOne.Epub/Exceptions/Epub3NavException.cs @@ -30,7 +30,7 @@ public Epub3NavException(string message) /// /// The error message that explains the reason for the exception. /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. - public Epub3NavException(string message, Exception innerException) + public Epub3NavException(string message, Exception? innerException) : base(message, innerException, EpubSchemaFileType.EPUB3_NAV_DOCUMENT) { } diff --git a/Source/VersOne.Epub/Exceptions/EpubContainerException.cs b/Source/VersOne.Epub/Exceptions/EpubContainerException.cs index 33e2a10..cd56aa1 100644 --- a/Source/VersOne.Epub/Exceptions/EpubContainerException.cs +++ b/Source/VersOne.Epub/Exceptions/EpubContainerException.cs @@ -30,7 +30,7 @@ public EpubContainerException(string message) /// /// The error message that explains the reason for the exception. /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. - public EpubContainerException(string message, Exception innerException) + public EpubContainerException(string message, Exception? innerException) : base(message, innerException, EpubSchemaFileType.META_INF_CONTAINER) { } diff --git a/Source/VersOne.Epub/Exceptions/EpubContentCollectionException.cs b/Source/VersOne.Epub/Exceptions/EpubContentCollectionException.cs new file mode 100644 index 0000000..98b8c27 --- /dev/null +++ b/Source/VersOne.Epub/Exceptions/EpubContentCollectionException.cs @@ -0,0 +1,38 @@ +using System; + +namespace VersOne.Epub +{ + /// + /// Represents an exception thrown by the EpubReader due to an invalid operation + /// performed on an instance of the class. + /// + public class EpubContentCollectionException : EpubReaderException + { + /// + /// Initializes a new instance of the class. + /// + public EpubContentCollectionException() + { + } + + /// + /// Initializes a new instance of the class with a specified error. + /// + /// The message that describes the error. + public EpubContentCollectionException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception + /// that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. + public EpubContentCollectionException(string message, Exception? innerException) + : base(message, innerException) + { + } + } +} diff --git a/Source/VersOne.Epub/Exceptions/EpubContentCollectionRefException.cs b/Source/VersOne.Epub/Exceptions/EpubContentCollectionRefException.cs new file mode 100644 index 0000000..375a77e --- /dev/null +++ b/Source/VersOne.Epub/Exceptions/EpubContentCollectionRefException.cs @@ -0,0 +1,38 @@ +using System; + +namespace VersOne.Epub +{ + /// + /// Represents an exception thrown by the EpubReader due to an invalid operation + /// performed on an instance of the class. + /// + public class EpubContentCollectionRefException : EpubReaderException + { + /// + /// Initializes a new instance of the class. + /// + public EpubContentCollectionRefException() + { + } + + /// + /// Initializes a new instance of the class with a specified error. + /// + /// The message that describes the error. + public EpubContentCollectionRefException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception + /// that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. + public EpubContentCollectionRefException(string message, Exception? innerException) + : base(message, innerException) + { + } + } +} diff --git a/Source/VersOne.Epub/Exceptions/EpubContentDownloaderException.cs b/Source/VersOne.Epub/Exceptions/EpubContentDownloaderException.cs index 89a4fae..866bd33 100644 --- a/Source/VersOne.Epub/Exceptions/EpubContentDownloaderException.cs +++ b/Source/VersOne.Epub/Exceptions/EpubContentDownloaderException.cs @@ -34,7 +34,7 @@ public EpubContentDownloaderException(string message, string remoteContentFileUr /// The error message that explains the reason for the exception. /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. /// The absolute URI of the remote content file that caused the error. - public EpubContentDownloaderException(string message, Exception innerException, string remoteContentFileUrl) + public EpubContentDownloaderException(string message, Exception? innerException, string remoteContentFileUrl) : base(message, innerException) { RemoteContentFileUrl = remoteContentFileUrl; diff --git a/Source/VersOne.Epub/Exceptions/EpubContentException.cs b/Source/VersOne.Epub/Exceptions/EpubContentException.cs index 6b67070..957744f 100644 --- a/Source/VersOne.Epub/Exceptions/EpubContentException.cs +++ b/Source/VersOne.Epub/Exceptions/EpubContentException.cs @@ -34,7 +34,7 @@ public EpubContentException(string message, string contentFilePath) /// The error message that explains the reason for the exception. /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. /// The path of the content file that caused the error. - public EpubContentException(string message, Exception innerException, string contentFilePath) + public EpubContentException(string message, Exception? innerException, string contentFilePath) : base(message, innerException) { ContentFilePath = contentFilePath; diff --git a/Source/VersOne.Epub/Exceptions/EpubPackageException.cs b/Source/VersOne.Epub/Exceptions/EpubPackageException.cs index c23aeaf..c30e158 100644 --- a/Source/VersOne.Epub/Exceptions/EpubPackageException.cs +++ b/Source/VersOne.Epub/Exceptions/EpubPackageException.cs @@ -30,7 +30,7 @@ public EpubPackageException(string message) /// /// The error message that explains the reason for the exception. /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. - public EpubPackageException(string message, Exception innerException) + public EpubPackageException(string message, Exception? innerException) : base(message, innerException, EpubSchemaFileType.OPF_PACKAGE) { } diff --git a/Source/VersOne.Epub/Exceptions/EpubReaderException.cs b/Source/VersOne.Epub/Exceptions/EpubReaderException.cs index 9b0152f..ff7df96 100644 --- a/Source/VersOne.Epub/Exceptions/EpubReaderException.cs +++ b/Source/VersOne.Epub/Exceptions/EpubReaderException.cs @@ -29,7 +29,7 @@ protected EpubReaderException(string message) /// /// The error message that explains the reason for the exception. /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. - protected EpubReaderException(string message, Exception innerException) + protected EpubReaderException(string message, Exception? innerException) : base(message, innerException) { } diff --git a/Source/VersOne.Epub/Exceptions/EpubSchemaException.cs b/Source/VersOne.Epub/Exceptions/EpubSchemaException.cs index c5f67fc..46cf164 100644 --- a/Source/VersOne.Epub/Exceptions/EpubSchemaException.cs +++ b/Source/VersOne.Epub/Exceptions/EpubSchemaException.cs @@ -34,7 +34,7 @@ protected EpubSchemaException(string message, EpubSchemaFileType schemaFileType) /// The error message that explains the reason for the exception. /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. /// The type of the schema file that caused the error. - protected EpubSchemaException(string message, Exception innerException, EpubSchemaFileType schemaFileType) + protected EpubSchemaException(string message, Exception? innerException, EpubSchemaFileType schemaFileType) : base(message, innerException) { SchemaFileType = schemaFileType; diff --git a/Source/VersOne.Epub/Exceptions/EpubSmilException.cs b/Source/VersOne.Epub/Exceptions/EpubSmilException.cs index b7501bf..bd06757 100644 --- a/Source/VersOne.Epub/Exceptions/EpubSmilException.cs +++ b/Source/VersOne.Epub/Exceptions/EpubSmilException.cs @@ -34,7 +34,7 @@ public EpubSmilException(string message, string smilFilePath) /// The error message that explains the reason for the exception. /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. /// The path of the SMIL file that caused the error. - public EpubSmilException(string message, Exception innerException, string smilFilePath) + public EpubSmilException(string message, Exception? innerException, string smilFilePath) : base(message, innerException) { SmilFilePath = smilFilePath; diff --git a/Source/VersOne.Epub/Readers/BookCoverReader.cs b/Source/VersOne.Epub/Readers/BookCoverReader.cs index 5bbbc06..97f6471 100644 --- a/Source/VersOne.Epub/Readers/BookCoverReader.cs +++ b/Source/VersOne.Epub/Readers/BookCoverReader.cs @@ -50,16 +50,10 @@ internal static class BookCoverReader throw new EpubPackageException("Incorrect EPUB metadata: cover item content is missing."); } EpubManifestItem coverManifestItem = - epubSchema.Package.Manifest.Items.FirstOrDefault(manifestItem => manifestItem.Id.CompareOrdinalIgnoreCase(coverMetaItem.Content)); - if (coverManifestItem == null) - { + epubSchema.Package.Manifest.Items.FirstOrDefault(manifestItem => manifestItem.Id.CompareOrdinalIgnoreCase(coverMetaItem.Content)) ?? throw new EpubPackageException($"Incorrect EPUB manifest: item with ID = \"{coverMetaItem.Content}\" is missing."); - } - EpubLocalByteContentFileRef? result = GetCoverImageContentRef(imageContentRefs, coverManifestItem.Href); - if (result == null) - { + EpubLocalByteContentFileRef result = GetCoverImageContentRef(imageContentRefs, coverManifestItem.Href) ?? throw new EpubPackageException($"Incorrect EPUB manifest: item with href = \"{coverManifestItem.Href}\" is missing."); - } return result; } @@ -92,22 +86,19 @@ internal static class BookCoverReader { return null; } - EpubLocalByteContentFileRef? result = GetCoverImageContentRef(imageContentRefs, coverManifestItem.Href); - if (result == null) - { + EpubLocalByteContentFileRef result = GetCoverImageContentRef(imageContentRefs, coverManifestItem.Href) ?? throw new EpubPackageException($"Incorrect EPUB manifest: item with href = \"{coverManifestItem.Href}\" is missing."); - } return result; } private static EpubLocalByteContentFileRef? GetCoverImageContentRef( EpubContentCollectionRef imageContentRefs, string coverImageContentFileKey) { - if (imageContentRefs.Remote.ContainsKey(coverImageContentFileKey)) + if (imageContentRefs.ContainsRemoteFileRefWithUrl(coverImageContentFileKey)) { throw new EpubPackageException($"Incorrect EPUB manifest: EPUB cover image \"{coverImageContentFileKey}\" cannot be a remote resource."); } - if (!imageContentRefs.Local.TryGetValue(coverImageContentFileKey, out EpubLocalByteContentFileRef coverImageContentFileRef)) + if (!imageContentRefs.TryGetLocalFileRefByKey(coverImageContentFileKey, out EpubLocalByteContentFileRef coverImageContentFileRef)) { return null; } diff --git a/Source/VersOne.Epub/Readers/BookReader.cs b/Source/VersOne.Epub/Readers/BookReader.cs index bd67637..86aced5 100644 --- a/Source/VersOne.Epub/Readers/BookReader.cs +++ b/Source/VersOne.Epub/Readers/BookReader.cs @@ -46,7 +46,7 @@ public async Task ReadBookAsync(Stream stream) private static List ReadReadingOrder(EpubContent epubContent, List htmlContentFileRefs) { - return htmlContentFileRefs.Select(htmlContentFileRef => epubContent.Html.Local[htmlContentFileRef.Key]).ToList(); + return htmlContentFileRefs.Select(htmlContentFileRef => epubContent.Html.GetLocalFileByKey(htmlContentFileRef.Key)).ToList(); } private static List ReadNavigation(EpubContent epubContent, List navigationItemRefs) @@ -61,7 +61,7 @@ private static List ReadNavigation(EpubContent epubContent, if (navigationItemRef.HtmlContentFileRef != null) { - htmlContentFile = epubContent.Html.Local[navigationItemRef.HtmlContentFileRef.Key]; + htmlContentFile = epubContent.Html.GetLocalFileByKey(navigationItemRef.HtmlContentFileRef.Key); } List nestedItems = ReadNavigation(epubContent, navigationItemRef.NestedItems); result.Add(new EpubNavigationItem(type, title, link, htmlContentFile, nestedItems)); @@ -118,58 +118,68 @@ private async Task ReadContent(EpubContentRef contentRef) EpubContentCollection images = await ReadByteContentFiles(contentRef.Images).ConfigureAwait(false); EpubContentCollection fonts = await ReadByteContentFiles(contentRef.Fonts).ConfigureAwait(false); EpubContentCollection audio = await ReadByteContentFiles(contentRef.Audio).ConfigureAwait(false); - EpubContentCollection allFiles = new(); - foreach (KeyValuePair localTextContentFile in html.Local.Concat(css.Local)) + List allFilesLocal = new(); + HashSet allFilesLocalKeys = new(); + List allFilesRemote = new(); + HashSet allFilesRemoteKeys = new(); + foreach (EpubLocalTextContentFile localTextContentFile in html.Local.Concat(css.Local)) { - allFiles.Local.Add(localTextContentFile.Key, localTextContentFile.Value); + allFilesLocal.Add(localTextContentFile); + allFilesLocalKeys.Add(localTextContentFile.Key); } - foreach (KeyValuePair remoteTextContentFile in html.Remote.Concat(css.Remote)) + foreach (EpubRemoteTextContentFile remoteTextContentFile in html.Remote.Concat(css.Remote)) { - allFiles.Remote.Add(remoteTextContentFile.Key, remoteTextContentFile.Value); + allFilesRemote.Add(remoteTextContentFile); + allFilesRemoteKeys.Add(remoteTextContentFile.Key); } - foreach (KeyValuePair localByteContentFile in images.Local.Concat(fonts.Local).Concat(audio.Local)) + foreach (EpubLocalByteContentFile localByteContentFile in images.Local.Concat(fonts.Local).Concat(audio.Local)) { - allFiles.Local.Add(localByteContentFile.Key, localByteContentFile.Value); + allFilesLocal.Add(localByteContentFile); + allFilesLocalKeys.Add(localByteContentFile.Key); } - foreach (KeyValuePair remoteByteContentFile in images.Remote.Concat(fonts.Remote).Concat(audio.Remote)) + foreach (EpubRemoteByteContentFile remoteByteContentFile in images.Remote.Concat(fonts.Remote).Concat(audio.Remote)) { - allFiles.Remote.Add(remoteByteContentFile.Key, remoteByteContentFile.Value); + allFilesRemote.Add(remoteByteContentFile); + allFilesRemoteKeys.Add(remoteByteContentFile.Key); } - foreach (KeyValuePair localContentFileRef in contentRef.AllFiles.Local) + foreach (EpubLocalContentFileRef localContentFileRef in contentRef.AllFiles.Local) { - if (!allFiles.Local.ContainsKey(localContentFileRef.Key)) + if (!allFilesLocalKeys.Contains(localContentFileRef.Key)) { - if (localContentFileRef.Value is EpubLocalTextContentFileRef) + if (localContentFileRef is EpubLocalTextContentFileRef) { - allFiles.Local.Add(localContentFileRef.Key, await ReadLocalTextContentFile(localContentFileRef.Value).ConfigureAwait(false)); + allFilesLocal.Add(await ReadLocalTextContentFile(localContentFileRef).ConfigureAwait(false)); } else { - allFiles.Local.Add(localContentFileRef.Key, await ReadLocalByteContentFile(localContentFileRef.Value).ConfigureAwait(false)); + allFilesLocal.Add(await ReadLocalByteContentFile(localContentFileRef).ConfigureAwait(false)); } + allFilesLocalKeys.Add(localContentFileRef.Key); } } - foreach (KeyValuePair remoteContentFileRef in contentRef.AllFiles.Remote) + foreach (EpubRemoteContentFileRef remoteContentFileRef in contentRef.AllFiles.Remote) { - if (!allFiles.Remote.ContainsKey(remoteContentFileRef.Key)) + if (!allFilesRemoteKeys.Contains(remoteContentFileRef.Key)) { - if (remoteContentFileRef.Value is EpubRemoteTextContentFileRef) + if (remoteContentFileRef is EpubRemoteTextContentFileRef) { - allFiles.Remote.Add(remoteContentFileRef.Key, await DownloadRemoteTextContentFile(remoteContentFileRef.Value).ConfigureAwait(false)); + allFilesRemote.Add(await DownloadRemoteTextContentFile(remoteContentFileRef).ConfigureAwait(false)); } else { - allFiles.Remote.Add(remoteContentFileRef.Key, await DownloadRemoteByteContentFile(remoteContentFileRef.Value).ConfigureAwait(false)); + allFilesRemote.Add(await DownloadRemoteByteContentFile(remoteContentFileRef).ConfigureAwait(false)); } + allFilesRemoteKeys.Add(remoteContentFileRef.Key); } } + EpubContentCollection allFiles = new(allFilesLocal.AsReadOnly(), allFilesRemote.AsReadOnly()); if (contentRef.Cover != null) { - cover = images.Local[contentRef.Cover.Key]; + cover = images.GetLocalFileByKey(contentRef.Cover.Key); } if (contentRef.NavigationHtmlFile != null) { - navigationHtmlFile = html.Local[contentRef.NavigationHtmlFile.Key]; + navigationHtmlFile = html.GetLocalFileByKey(contentRef.NavigationHtmlFile.Key); } return new(cover, navigationHtmlFile, html, css, images, fonts, audio, allFiles); } @@ -177,30 +187,34 @@ private async Task ReadContent(EpubContentRef contentRef) private async Task> ReadTextContentFiles( EpubContentCollectionRef textContentFileCollectionRef) { - EpubContentCollection result = new(); - foreach (KeyValuePair localTextContentFileRef in textContentFileCollectionRef.Local) + List local = new(); + List remote = new(); + foreach (EpubLocalTextContentFileRef localTextContentFileRef in textContentFileCollectionRef.Local) { - result.Local.Add(localTextContentFileRef.Key, await ReadLocalTextContentFile(localTextContentFileRef.Value).ConfigureAwait(false)); + local.Add(await ReadLocalTextContentFile(localTextContentFileRef).ConfigureAwait(false)); } - foreach (KeyValuePair remoteTextContentFileRef in textContentFileCollectionRef.Remote) + foreach (EpubRemoteTextContentFileRef remoteTextContentFileRef in textContentFileCollectionRef.Remote) { - result.Remote.Add(remoteTextContentFileRef.Key, await DownloadRemoteTextContentFile(remoteTextContentFileRef.Value).ConfigureAwait(false)); + remote.Add(await DownloadRemoteTextContentFile(remoteTextContentFileRef).ConfigureAwait(false)); } + EpubContentCollection result = new(local.AsReadOnly(), remote.AsReadOnly()); return result; } private async Task> ReadByteContentFiles( EpubContentCollectionRef byteContentFileCollectionRef) { - EpubContentCollection result = new(); - foreach (KeyValuePair localByteContentFileRef in byteContentFileCollectionRef.Local) + List local = new(); + List remote = new(); + foreach (EpubLocalByteContentFileRef localByteContentFileRef in byteContentFileCollectionRef.Local) { - result.Local.Add(localByteContentFileRef.Key, await ReadLocalByteContentFile(localByteContentFileRef.Value).ConfigureAwait(false)); + local.Add(await ReadLocalByteContentFile(localByteContentFileRef).ConfigureAwait(false)); } - foreach (KeyValuePair remoteByteContentFileRef in byteContentFileCollectionRef.Remote) + foreach (EpubRemoteByteContentFileRef remoteByteContentFileRef in byteContentFileCollectionRef.Remote) { - result.Remote.Add(remoteByteContentFileRef.Key, await DownloadRemoteByteContentFile(remoteByteContentFileRef.Value).ConfigureAwait(false)); + remote.Add(await DownloadRemoteByteContentFile(remoteByteContentFileRef).ConfigureAwait(false)); } + EpubContentCollection result = new(local.AsReadOnly(), remote.AsReadOnly()); return result; } @@ -209,13 +223,12 @@ private async Task DownloadRemoteTextContentFile(Epub string key = remoteContentFileRef.Key; EpubContentType contentType = remoteContentFileRef.ContentType; string contentMimeType = remoteContentFileRef.ContentMimeType; - string url = remoteContentFileRef.Url; string? content = null; if (epubReaderOptions.ContentDownloaderOptions != null && epubReaderOptions.ContentDownloaderOptions.DownloadContent) { content = await remoteContentFileRef.DownloadContentAsTextAsync().ConfigureAwait(false); } - return new(key, contentType, contentMimeType, url, content); + return new(key, contentType, contentMimeType, content); } private async Task DownloadRemoteByteContentFile(EpubRemoteContentFileRef remoteContentFileRef) @@ -223,13 +236,12 @@ private async Task DownloadRemoteByteContentFile(Epub string key = remoteContentFileRef.Key; EpubContentType contentType = remoteContentFileRef.ContentType; string contentMimeType = remoteContentFileRef.ContentMimeType; - string url = remoteContentFileRef.Url; byte[]? content = null; if (epubReaderOptions.ContentDownloaderOptions != null && epubReaderOptions.ContentDownloaderOptions.DownloadContent) { content = await remoteContentFileRef.DownloadContentAsBytesAsync().ConfigureAwait(false); } - return new(key, contentType, contentMimeType, url, content); + return new(key, contentType, contentMimeType, content); } } } diff --git a/Source/VersOne.Epub/Readers/ContentReader.cs b/Source/VersOne.Epub/Readers/ContentReader.cs index 3f895c7..176f1a6 100644 --- a/Source/VersOne.Epub/Readers/ContentReader.cs +++ b/Source/VersOne.Epub/Readers/ContentReader.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using VersOne.Epub.Environment; using VersOne.Epub.Options; using VersOne.Epub.Schema; @@ -20,18 +21,24 @@ public EpubContentRef ParseContentMap(EpubSchema epubSchema, IZipFile epubFile) { EpubLocalByteContentFileRef? cover; EpubLocalTextContentFileRef? navigationHtmlFile = null; - EpubContentCollectionRef html = new(); - EpubContentCollectionRef css = new(); - EpubContentCollectionRef images = new(); - EpubContentCollectionRef fonts = new(); - EpubContentCollectionRef audio = new(); - EpubContentCollectionRef allFiles = new(); + List htmlLocal = new(); + List htmlRemote = new(); + List cssLocal = new(); + List cssRemote = new(); + List imagesLocal = new(); + List imagesRemote = new(); + List fontsLocal = new(); + List fontsRemote = new(); + List audioLocal = new(); + List audioRemote = new(); + List allFilesLocal = new(); + List allFilesRemote = new(); EpubLocalContentLoader localContentLoader = new(environmentDependencies, epubFile, epubSchema.ContentDirectoryPath, epubReaderOptions.ContentReaderOptions); EpubRemoteContentLoader? remoteContentLoader = null; foreach (EpubManifestItem manifestItem in epubSchema.Package.Manifest.Items) { string href = manifestItem.Href; - EpubContentLocation contentLocation = href.Contains("://") ? EpubContentLocation.REMOTE : EpubContentLocation.LOCAL; + EpubContentLocation contentLocation = ContentPathUtils.IsLocalPath(href) ? EpubContentLocation.LOCAL : EpubContentLocation.REMOTE; string contentMimeType = manifestItem.MediaType; EpubContentType contentType = GetContentTypeByContentMimeType(contentMimeType); string contentDirectoryPath = epubSchema.ContentDirectoryPath; @@ -49,22 +56,22 @@ public EpubContentRef ParseContentMap(EpubSchema epubSchema, IZipFile epubFile) case EpubContentType.SCRIPT: if (contentLocation == EpubContentLocation.LOCAL) { - string contentFilePath = ZipPathUtils.Combine(contentDirectoryPath, href); + string contentFilePath = ContentPathUtils.Combine(contentDirectoryPath, href); EpubLocalTextContentFileRef localTextContentFile = new(contentFileRefMetadata, contentFilePath, localContentLoader); switch (contentType) { case EpubContentType.XHTML_1_1: - html.Local[href] = localTextContentFile; + htmlLocal.Add(localTextContentFile); if (navigationHtmlFile == null && manifestItem.Properties != null && manifestItem.Properties.Contains(EpubManifestProperty.NAV)) { navigationHtmlFile = localTextContentFile; } break; case EpubContentType.CSS: - css.Local[href] = localTextContentFile; + cssLocal.Add(localTextContentFile); break; } - allFiles.Local[href] = localTextContentFile; + allFilesLocal.Add(localTextContentFile); } else { @@ -73,23 +80,23 @@ public EpubContentRef ParseContentMap(EpubSchema epubSchema, IZipFile epubFile) switch (contentType) { case EpubContentType.XHTML_1_1: - html.Remote[href] = remoteTextContentFile; if (manifestItem.Properties != null && manifestItem.Properties.Contains(EpubManifestProperty.NAV)) { throw new EpubPackageException($"Incorrect EPUB manifest: EPUB 3 navigation document \"{href}\" cannot be a remote resource."); } + htmlRemote.Add(remoteTextContentFile); break; case EpubContentType.CSS: - css.Remote[href] = remoteTextContentFile; + cssRemote.Add(remoteTextContentFile); break; } - allFiles.Remote[href] = remoteTextContentFile; + allFilesRemote.Add(remoteTextContentFile); } break; default: if (contentLocation == EpubContentLocation.LOCAL) { - string contentFilePath = ZipPathUtils.Combine(contentDirectoryPath, href); + string contentFilePath = ContentPathUtils.Combine(contentDirectoryPath, href); EpubLocalByteContentFileRef localByteContentFile = new(contentFileRefMetadata, contentFilePath, localContentLoader); switch (contentType) { @@ -98,22 +105,22 @@ public EpubContentRef ParseContentMap(EpubSchema epubSchema, IZipFile epubFile) case EpubContentType.IMAGE_PNG: case EpubContentType.IMAGE_SVG: case EpubContentType.IMAGE_WEBP: - images.Local[href] = localByteContentFile; + imagesLocal.Add(localByteContentFile); break; case EpubContentType.FONT_TRUETYPE: case EpubContentType.FONT_OPENTYPE: case EpubContentType.FONT_SFNT: case EpubContentType.FONT_WOFF: case EpubContentType.FONT_WOFF2: - fonts.Local[href] = localByteContentFile; + fontsLocal.Add(localByteContentFile); break; case EpubContentType.AUDIO_MP3: case EpubContentType.AUDIO_MP4: case EpubContentType.AUDIO_OGG: - audio.Local[href] = localByteContentFile; + audioLocal.Add(localByteContentFile); break; } - allFiles.Local[href] = localByteContentFile; + allFilesLocal.Add(localByteContentFile); } else { @@ -126,26 +133,32 @@ public EpubContentRef ParseContentMap(EpubSchema epubSchema, IZipFile epubFile) case EpubContentType.IMAGE_PNG: case EpubContentType.IMAGE_SVG: case EpubContentType.IMAGE_WEBP: - images.Remote[href] = remoteByteContentFile; + imagesRemote.Add(remoteByteContentFile); break; case EpubContentType.FONT_TRUETYPE: case EpubContentType.FONT_OPENTYPE: case EpubContentType.FONT_SFNT: case EpubContentType.FONT_WOFF: case EpubContentType.FONT_WOFF2: - fonts.Remote[href] = remoteByteContentFile; + fontsRemote.Add(remoteByteContentFile); break; case EpubContentType.AUDIO_MP3: case EpubContentType.AUDIO_MP4: case EpubContentType.AUDIO_OGG: - audio.Remote[href] = remoteByteContentFile; + audioRemote.Add(remoteByteContentFile); break; } - allFiles.Remote[href] = remoteByteContentFile; + allFilesRemote.Add(remoteByteContentFile); } break; } } + EpubContentCollectionRef html = new(htmlLocal.AsReadOnly(), htmlRemote.AsReadOnly()); + EpubContentCollectionRef css = new(cssLocal.AsReadOnly(), cssRemote.AsReadOnly()); + EpubContentCollectionRef images = new(imagesLocal.AsReadOnly(), imagesRemote.AsReadOnly()); + EpubContentCollectionRef fonts = new(fontsLocal.AsReadOnly(), fontsRemote.AsReadOnly()); + EpubContentCollectionRef audio = new(audioLocal.AsReadOnly(), audioRemote.AsReadOnly()); + EpubContentCollectionRef allFiles = new(allFilesLocal.AsReadOnly(), allFilesRemote.AsReadOnly()); cover = BookCoverReader.ReadBookCover(epubSchema, images); return new(cover, navigationHtmlFile, html, css, images, fonts, audio, allFiles); } diff --git a/Source/VersOne.Epub/Readers/Epub2NcxReader.cs b/Source/VersOne.Epub/Readers/Epub2NcxReader.cs index e923a70..5038b8c 100644 --- a/Source/VersOne.Epub/Readers/Epub2NcxReader.cs +++ b/Source/VersOne.Epub/Readers/Epub2NcxReader.cs @@ -32,7 +32,7 @@ public Epub2NcxReader(EpubReaderOptions? epubReaderOptions = null) { throw new Epub2NcxException($"EPUB parsing error: TOC item {tocId} not found in EPUB manifest."); } - string tocFileEntryPath = ZipPathUtils.Combine(contentDirectoryPath, tocManifestItem.Href); + string tocFileEntryPath = ContentPathUtils.Combine(contentDirectoryPath, tocManifestItem.Href); IZipFileEntry? tocFileEntry = epubFile.GetEntry(tocFileEntryPath); if (tocFileEntry == null) { diff --git a/Source/VersOne.Epub/Readers/Epub3NavDocumentReader.cs b/Source/VersOne.Epub/Readers/Epub3NavDocumentReader.cs index f8cc769..614c867 100644 --- a/Source/VersOne.Epub/Readers/Epub3NavDocumentReader.cs +++ b/Source/VersOne.Epub/Readers/Epub3NavDocumentReader.cs @@ -34,7 +34,7 @@ public Epub3NavDocumentReader(EpubReaderOptions? epubReaderOptions = null) throw new Epub3NavException("EPUB parsing error: NAV item not found in EPUB manifest."); } } - string navFileEntryPath = ZipPathUtils.Combine(contentDirectoryPath, navManifestItem.Href); + string navFileEntryPath = ContentPathUtils.Combine(contentDirectoryPath, navManifestItem.Href); IZipFileEntry navFileEntry = epubFile.GetEntry(navFileEntryPath) ?? throw new Epub3NavException($"EPUB parsing error: navigation file {navFileEntryPath} not found in the EPUB file."); if (navFileEntry.Length > Int32.MaxValue) diff --git a/Source/VersOne.Epub/Readers/NavigationReader.cs b/Source/VersOne.Epub/Readers/NavigationReader.cs index d1d2fb8..f4dc11b 100644 --- a/Source/VersOne.Epub/Readers/NavigationReader.cs +++ b/Source/VersOne.Epub/Readers/NavigationReader.cs @@ -41,9 +41,14 @@ private static List GetNavigationItems(EpubSchema epubSch 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; - EpubNavigationItemLink link = new(navigationPoint.Content.Source, epubSchema.ContentDirectoryPath); - EpubLocalTextContentFileRef? htmlContentFileRef = GetHtmlContentFileRef(epubContentRef, link.ContentFileName) ?? - throw new Epub2NcxException($"Incorrect EPUB 2 NCX: content source \"{navigationPoint.Content.Source}\" not found in EPUB manifest."); + string source = Uri.UnescapeDataString(navigationPoint.Content.Source); + if (!ContentPathUtils.IsLocalPath(source)) + { + throw new Epub2NcxException($"Incorrect EPUB 2 NCX: content source \"{source}\" cannot be a remote resource."); + } + EpubNavigationItemLink link = new(source, epubSchema.ContentDirectoryPath); + EpubLocalTextContentFileRef? htmlContentFileRef = GetLocalHtmlContentFileRef(epubContentRef, link.ContentFilePath) ?? + throw new Epub2NcxException($"Incorrect EPUB 2 NCX: content source \"{source}\" not found in EPUB manifest."); List nestedItems = GetNavigationItems(epubSchema, epubContentRef, navigationPoint.ChildNavigationPoints); result.Add(new EpubNavigationItemRef(type, title, link, htmlContentFileRef, nestedItems)); } @@ -56,7 +61,7 @@ private static List GetNavigationItems(EpubSchema epubSch List result; if (epub3Nav != null) { - string epub3NavigationBaseDirectoryPath = ZipPathUtils.GetDirectoryPath(epub3NavDocumentFilePath); + string epub3NavigationBaseDirectoryPath = ContentPathUtils.GetDirectoryPath(epub3NavDocumentFilePath); if (epub3Nav.Head != null) { result = new List(); @@ -95,12 +100,17 @@ private static List GetNavigationItems(EpubSchema epubSch List nestedItems = GetNavigationItems(epubSchema, epubContentRef, epub3NavLi.ChildOl, epub3NavigationBaseDirectoryPath); if (navAnchor.Href != null) { + string href = Uri.UnescapeDataString(navAnchor.Href); + if (!ContentPathUtils.IsLocalPath(href)) + { + throw new Epub3NavException($"Incorrect EPUB 3 navigation document: anchor href \"{href}\" cannot be a remote resource."); + } type = EpubNavigationItemType.LINK; - link = new(navAnchor.Href, epub3NavigationBaseDirectoryPath); - htmlContentFileRef = GetHtmlContentFileRef(epubContentRef, link.ContentFileName); + link = new(href, epub3NavigationBaseDirectoryPath); + htmlContentFileRef = GetLocalHtmlContentFileRef(epubContentRef, link.ContentFilePath); if (htmlContentFileRef == null) { - throw new Epub3NavException($"Incorrect EPUB 3 navigation document: target for anchor href \"{navAnchor.Href}\" not found in EPUB manifest."); + throw new Epub3NavException($"Incorrect EPUB 3 navigation document: target for anchor href \"{href}\" not found in EPUB manifest."); } } else @@ -122,13 +132,9 @@ private static List GetNavigationItems(EpubSchema epubSch return result; } - private static EpubLocalTextContentFileRef? GetHtmlContentFileRef(EpubContentRef epubContentRef, string contentFileKey) + private static EpubLocalTextContentFileRef? GetLocalHtmlContentFileRef(EpubContentRef epubContentRef, string localContentFilePath) { - if (epubContentRef.Html.Remote.ContainsKey(contentFileKey)) - { - throw new EpubPackageException($"Incorrect EPUB manifest: item \"{contentFileKey}\" referenced in the navigation file cannot be a remote resource."); - } - if (!epubContentRef.Html.Local.TryGetValue(contentFileKey, out EpubLocalTextContentFileRef htmlContentFileRef)) + if (!epubContentRef.Html.TryGetLocalFileRefByFilePath(localContentFilePath, out EpubLocalTextContentFileRef htmlContentFileRef)) { return null; } diff --git a/Source/VersOne.Epub/Readers/PackageReader.cs b/Source/VersOne.Epub/Readers/PackageReader.cs index bbbc4bb..37cddf3 100644 --- a/Source/VersOne.Epub/Readers/PackageReader.cs +++ b/Source/VersOne.Epub/Readers/PackageReader.cs @@ -93,6 +93,8 @@ private static EpubManifest ReadManifest(XElement manifestNode, PackageReaderOpt 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 (manifestItemNode.CompareNameTo("item")) @@ -164,6 +166,16 @@ private static EpubManifest ReadManifest(XElement manifestNode, PackageReaderOpt } 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."); + } + manifestItemHrefs.Add(href); items.Add(new EpubManifestItem(manifestItemId, href, mediaType, mediaOverlay, requiredNamespace, requiredModules, fallback, fallbackStyle, properties)); } } diff --git a/Source/VersOne.Epub/Readers/SchemaReader.cs b/Source/VersOne.Epub/Readers/SchemaReader.cs index c6b8633..4a0c19e 100644 --- a/Source/VersOne.Epub/Readers/SchemaReader.cs +++ b/Source/VersOne.Epub/Readers/SchemaReader.cs @@ -19,7 +19,7 @@ public async Task ReadSchemaAsync(IZipFile epubFile) { RootFilePathReader rootFilePathReader = new(epubReaderOptions); string rootFilePath = await rootFilePathReader.GetRootFilePathAsync(epubFile).ConfigureAwait(false); - string contentDirectoryPath = ZipPathUtils.GetDirectoryPath(rootFilePath); + string contentDirectoryPath = ContentPathUtils.GetDirectoryPath(rootFilePath); PackageReader packageReader = new(epubReaderOptions); EpubPackage package = await packageReader.ReadPackageAsync(epubFile, rootFilePath).ConfigureAwait(false); Epub2NcxReader epub2NcxReader = new(epubReaderOptions); diff --git a/Source/VersOne.Epub/Readers/SmilClockParser.cs b/Source/VersOne.Epub/Readers/SmilClockParser.cs index de9e5a3..f1c9413 100644 --- a/Source/VersOne.Epub/Readers/SmilClockParser.cs +++ b/Source/VersOne.Epub/Readers/SmilClockParser.cs @@ -1,7 +1,7 @@ using System; using System.Globalization; -namespace VersOne.Epub.Readers +namespace VersOne.Epub.Internal { internal static class SmilClockParser { diff --git a/Source/VersOne.Epub/Readers/SmilReader.cs b/Source/VersOne.Epub/Readers/SmilReader.cs index e890a16..e940083 100644 --- a/Source/VersOne.Epub/Readers/SmilReader.cs +++ b/Source/VersOne.Epub/Readers/SmilReader.cs @@ -25,7 +25,7 @@ public async Task> ReadAllSmilDocumentsAsync(IZipFile epubFile, strin List result = new(); foreach (EpubManifestItem smilManifestItem in package.Manifest.Items.Where(manifestItem => manifestItem.MediaType.CompareOrdinalIgnoreCase("application/smil+xml"))) { - string smilFilePath = ZipPathUtils.Combine(contentDirectoryPath, smilManifestItem.Href); + string smilFilePath = ContentPathUtils.Combine(contentDirectoryPath, smilManifestItem.Href); Smil smil = await ReadSmilAsync(epubFile, smilFilePath); result.Add(smil); } diff --git a/Source/VersOne.Epub/Readers/SpineReader.cs b/Source/VersOne.Epub/Readers/SpineReader.cs index c645e49..ef516e3 100644 --- a/Source/VersOne.Epub/Readers/SpineReader.cs +++ b/Source/VersOne.Epub/Readers/SpineReader.cs @@ -11,16 +11,13 @@ public static List GetReadingOrder(EpubSchema epubS List result = new(); foreach (EpubSpineItemRef spineItemRef in epubSchema.Package.Spine.Items) { - EpubManifestItem manifestItem = epubSchema.Package.Manifest.Items.FirstOrDefault(item => item.Id == spineItemRef.IdRef); - if (manifestItem == null) - { + EpubManifestItem manifestItem = epubSchema.Package.Manifest.Items.FirstOrDefault(item => item.Id == spineItemRef.IdRef) ?? throw new EpubPackageException($"Incorrect EPUB spine: item with IdRef = \"{spineItemRef.IdRef}\" is missing in the manifest."); - } - if (epubContentRef.Html.Remote.ContainsKey(manifestItem.Href)) + if (epubContentRef.Html.ContainsRemoteFileRefWithUrl(manifestItem.Href)) { throw new EpubPackageException($"Incorrect EPUB manifest: EPUB spine item \"{manifestItem.Href}\" cannot be a remote resource."); } - if (!epubContentRef.Html.Local.TryGetValue(manifestItem.Href, out EpubLocalTextContentFileRef htmlContentFileRef)) + if (!epubContentRef.Html.TryGetLocalFileRefByKey(manifestItem.Href, out EpubLocalTextContentFileRef htmlContentFileRef)) { throw new EpubPackageException($"Incorrect EPUB manifest: item with href = \"{spineItemRef.IdRef}\" is missing in the book."); } diff --git a/Source/VersOne.Epub/Utils/ZipPathUtils.cs b/Source/VersOne.Epub/Utils/ContentPathUtils.cs similarity index 85% rename from Source/VersOne.Epub/Utils/ZipPathUtils.cs rename to Source/VersOne.Epub/Utils/ContentPathUtils.cs index dceb1b6..d0195dd 100644 --- a/Source/VersOne.Epub/Utils/ZipPathUtils.cs +++ b/Source/VersOne.Epub/Utils/ContentPathUtils.cs @@ -2,10 +2,12 @@ namespace VersOne.Epub.Internal { - internal static class ZipPathUtils + internal static class ContentPathUtils { private const string DIRECTORY_UP = "../"; + public static bool IsLocalPath(string path) => path != null ? !path.Contains("://") : throw new ArgumentNullException(nameof(path)); + public static string GetDirectoryPath(string filePath) { int lastSlashIndex = filePath.LastIndexOf('/');