From e16385db4358777085065291e6ed014176d8ba51 Mon Sep 17 00:00:00 2001
From: vers-one <12114169+vers-one@users.noreply.github.com>
Date: Fri, 29 Apr 2022 00:06:00 -0400
Subject: [PATCH] Expose physical file paths in EPUB archive for all content
files
---
Source/.editorconfig | 3 +
.../VersOne.Epub.ConsoleDemo.csproj | 2 +-
.../Controls/BookHtmlContent.cs | 67 +++++++++++++++++--
.../VersOne.Epub.WpfDemo/Models/BookModel.cs | 4 +-
.../Properties/AssemblyInfo.cs | 4 +-
.../VersOne.Epub.WpfDemo.csproj | 5 ++
.../ViewModels/BookViewModel.cs | 23 ++++++-
.../ViewModels/HtmlContentFileViewModel.cs | 9 ++-
.../VersOne.Epub.WpfDemo/Views/BookView.xaml | 15 +++--
Source/VersOne.Epub.WpfDemo/packages.config | 1 +
.../VersOne.Epub/Entities/EpubContentFile.cs | 1 +
Source/VersOne.Epub/EpubReader.cs | 2 +
.../RefEntities/EpubContentFileRef.cs | 10 ++-
Source/VersOne.Epub/VersOne.Epub.csproj | 2 +-
Source/VersOne.Epub/VersOne.Epub.nuspec | 4 +-
15 files changed, 128 insertions(+), 24 deletions(-)
diff --git a/Source/.editorconfig b/Source/.editorconfig
index c4ce8d4..4b1a9ff 100644
--- a/Source/.editorconfig
+++ b/Source/.editorconfig
@@ -18,6 +18,9 @@ dotnet_diagnostic.IDE0060.severity = error
# IDE0090: Use 'new(...)'
dotnet_diagnostic.IDE0090.severity = error
+# S1168: Empty arrays and collections should be returned instead of null
+dotnet_diagnostic.S1168.severity = none
+
# S3963: "static" fields should be initialized inline
dotnet_diagnostic.S3963.severity = none
diff --git a/Source/VersOne.Epub.ConsoleDemo/VersOne.Epub.ConsoleDemo.csproj b/Source/VersOne.Epub.ConsoleDemo/VersOne.Epub.ConsoleDemo.csproj
index 6cbeb34..3551dac 100644
--- a/Source/VersOne.Epub.ConsoleDemo/VersOne.Epub.ConsoleDemo.csproj
+++ b/Source/VersOne.Epub.ConsoleDemo/VersOne.Epub.ConsoleDemo.csproj
@@ -8,7 +8,7 @@
vers, 2015-2022
- 3.1.0
+ 3.1.1
True
True
diff --git a/Source/VersOne.Epub.WpfDemo/Controls/BookHtmlContent.cs b/Source/VersOne.Epub.WpfDemo/Controls/BookHtmlContent.cs
index 37504d2..3b20d8c 100644
--- a/Source/VersOne.Epub.WpfDemo/Controls/BookHtmlContent.cs
+++ b/Source/VersOne.Epub.WpfDemo/Controls/BookHtmlContent.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.IO.Compression;
using System.IO.Packaging;
using System.Windows;
using System.Windows.Media;
@@ -13,6 +14,7 @@ namespace VersOne.Epub.WpfDemo.Controls
{
public class BookHtmlContent : HtmlPanel
{
+ public static readonly DependencyProperty EpubArchiveProperty = DependencyProperty.Register("EpubArchive", typeof(ZipArchive), typeof(BookHtmlContent));
public static readonly DependencyProperty HtmlContentFileProperty = DependencyProperty.Register("HtmlContentFile", typeof(HtmlContentFileViewModel), typeof(BookHtmlContent), new PropertyMetadata(OnHtmlContentFileChanged));
public static readonly DependencyProperty AnchorProperty = DependencyProperty.Register("Anchor", typeof(string), typeof(BookHtmlContent), new PropertyMetadata(OnAnchorChanged));
@@ -27,6 +29,18 @@ public BookHtmlContent()
queuedScrollToAnchor = null;
}
+ public ZipArchive EpubArchive
+ {
+ get
+ {
+ return (ZipArchive)GetValue(EpubArchiveProperty);
+ }
+ set
+ {
+ SetValue(EpubArchiveProperty, value);
+ }
+ }
+
public HtmlContentFileViewModel HtmlContentFile
{
get
@@ -53,8 +67,8 @@ public string Anchor
protected override void OnImageLoad(HtmlImageLoadEventArgs e)
{
- string imageFilePath = GetFullPath(HtmlContentFile.HtmlFilePath, e.Src);
- if (HtmlContentFile.Images.TryGetValue(imageFilePath, out byte[] imageContent))
+ byte[] imageContent = GetImageContent(e.Src);
+ if (imageContent != null)
{
using (MemoryStream imageStream = new MemoryStream(imageContent))
{
@@ -73,8 +87,8 @@ protected override void OnImageLoad(HtmlImageLoadEventArgs e)
protected override void OnStylesheetLoad(HtmlStylesheetLoadEventArgs e)
{
- string styleSheetFilePath = GetFullPath(HtmlContentFile.HtmlFilePath, e.Src);
- if (HtmlContentFile.StyleSheets.TryGetValue(styleSheetFilePath, out string styleSheetContent))
+ string styleSheetContent = GetStyleSheetContent(e.Src);
+ if (styleSheetContent != null)
{
e.SetStyleSheet = styleSheetContent;
}
@@ -123,6 +137,45 @@ private static void OnHtmlContentFileChanged(DependencyObject dependencyObject,
bookHtmlContent.Text = bookHtmlContent.HtmlContentFile.HtmlContent;
}
+ private byte[] GetImageContent(string imageFilePath)
+ {
+ if (HtmlContentFile.Images.TryGetValue(GetFullPath(HtmlContentFile.HtmlFilePathInEpubManifest, imageFilePath), out byte[] imageContent))
+ {
+ return imageContent;
+ }
+ ZipArchiveEntry zipArchiveEntry = EpubArchive.GetEntry(GetFullPath(HtmlContentFile.HtmlFilePathInEpubArchive, imageFilePath));
+ if (zipArchiveEntry != null)
+ {
+ imageContent = new byte[(int)zipArchiveEntry.Length];
+ using (Stream zipArchiveEntryStream = zipArchiveEntry.Open())
+ using (MemoryStream memoryStream = new MemoryStream(imageContent))
+ {
+ zipArchiveEntryStream.CopyTo(memoryStream);
+ }
+ return imageContent;
+ }
+ return null;
+ }
+
+ private string GetStyleSheetContent(string styleSheetFilePath)
+ {
+ if (HtmlContentFile.StyleSheets.TryGetValue(GetFullPath(HtmlContentFile.HtmlFilePathInEpubManifest, styleSheetFilePath), out string styleSheetContent))
+ {
+ return styleSheetContent;
+ }
+ ZipArchiveEntry zipArchiveEntry = EpubArchive.GetEntry(GetFullPath(HtmlContentFile.HtmlFilePathInEpubArchive, styleSheetFilePath));
+ if (zipArchiveEntry != null)
+ {
+ using (Stream zipArchiveEntryStream = zipArchiveEntry.Open())
+ using (StreamReader streamReader = new StreamReader(zipArchiveEntryStream))
+ {
+ styleSheetContent = streamReader.ReadToEnd();
+ }
+ return styleSheetContent;
+ }
+ return null;
+ }
+
private string GetFullPath(string htmlFilePath, string relativePath)
{
if (relativePath.StartsWith("/"))
@@ -136,7 +189,11 @@ private string GetFullPath(string htmlFilePath, string relativePath)
basePath = Path.GetDirectoryName(basePath);
}
string fullPath = String.Concat(basePath.Replace('\\', '/'), '/', relativePath);
- return fullPath.Length > 1 ? fullPath.Substring(1) : String.Empty;
+ if (fullPath.StartsWith("/"))
+ {
+ fullPath = fullPath.Length > 1 ? fullPath.Substring(1) : String.Empty;
+ }
+ return fullPath;
}
private void RegisterFonts()
diff --git a/Source/VersOne.Epub.WpfDemo/Models/BookModel.cs b/Source/VersOne.Epub.WpfDemo/Models/BookModel.cs
index 9442693..58bb936 100644
--- a/Source/VersOne.Epub.WpfDemo/Models/BookModel.cs
+++ b/Source/VersOne.Epub.WpfDemo/Models/BookModel.cs
@@ -34,8 +34,8 @@ public List GetReadingOrder(EpubBook epubBook)
List result = new List();
foreach (EpubTextContentFile epubHtmlFile in epubBook.ReadingOrder)
{
- HtmlContentFileViewModel htmlContentFileViewModel = new HtmlContentFileViewModel(epubHtmlFile.FileName, epubHtmlFile.Content, images,
- styleSheets, fonts);
+ HtmlContentFileViewModel htmlContentFileViewModel = new HtmlContentFileViewModel(epubHtmlFile.FileName, epubHtmlFile.FilePathInEpubArchive,
+ epubHtmlFile.Content, images, styleSheets, fonts);
result.Add(htmlContentFileViewModel);
}
return result;
diff --git a/Source/VersOne.Epub.WpfDemo/Properties/AssemblyInfo.cs b/Source/VersOne.Epub.WpfDemo/Properties/AssemblyInfo.cs
index e72d8cb..775e2d4 100644
--- a/Source/VersOne.Epub.WpfDemo/Properties/AssemblyInfo.cs
+++ b/Source/VersOne.Epub.WpfDemo/Properties/AssemblyInfo.cs
@@ -5,7 +5,7 @@
[assembly: AssemblyTitle("VersOne.Epub.WpfDemo")]
[assembly: AssemblyDescription("WPF demo application for VersOne.Epub library")]
[assembly: AssemblyCopyright("Unlicense ")]
-[assembly: AssemblyVersion("3.1.0.0")]
-[assembly: AssemblyFileVersion("3.1.0.0")]
+[assembly: AssemblyVersion("3.1.1.0")]
+[assembly: AssemblyFileVersion("3.1.1.0")]
[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]
[assembly: ComVisible(false)]
diff --git a/Source/VersOne.Epub.WpfDemo/VersOne.Epub.WpfDemo.csproj b/Source/VersOne.Epub.WpfDemo/VersOne.Epub.WpfDemo.csproj
index e8f7361..0832f33 100644
--- a/Source/VersOne.Epub.WpfDemo/VersOne.Epub.WpfDemo.csproj
+++ b/Source/VersOne.Epub.WpfDemo/VersOne.Epub.WpfDemo.csproj
@@ -37,6 +37,7 @@
prompt
4
True
+ bin\Release\VersOne.Epub.WpfDemo.xml
Resources\Book_icon.ico
@@ -53,6 +54,10 @@
+
+ ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll
+
+
diff --git a/Source/VersOne.Epub.WpfDemo/ViewModels/BookViewModel.cs b/Source/VersOne.Epub.WpfDemo/ViewModels/BookViewModel.cs
index 883ee38..da413f8 100644
--- a/Source/VersOne.Epub.WpfDemo/ViewModels/BookViewModel.cs
+++ b/Source/VersOne.Epub.WpfDemo/ViewModels/BookViewModel.cs
@@ -1,4 +1,5 @@
using System.Collections.ObjectModel;
+using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
@@ -14,6 +15,7 @@ public class BookViewModel : ViewModel
private bool isLoading;
private ObservableCollection navigation;
private ObservableCollection readingOrder;
+ private ZipArchive currentEpubArchive;
private HtmlContentFileViewModel currentHtmlContentFile;
private HtmlContentFileViewModel previousHtmlContentFile;
private HtmlContentFileViewModel nextHtmlContentFile;
@@ -26,6 +28,7 @@ public BookViewModel(int bookId)
{
bookModel = new BookModel();
isLoading = true;
+ currentEpubArchive = null;
currentHtmlContentFile = null;
previousHtmlContentFile = null;
nextHtmlContentFile = null;
@@ -75,6 +78,19 @@ private set
}
}
+ public ZipArchive CurrentEpubArchive
+ {
+ get
+ {
+ return currentEpubArchive;
+ }
+ set
+ {
+ currentEpubArchive = value;
+ NotifyPropertyChanged();
+ }
+ }
+
public HtmlContentFileViewModel CurrentHtmlContentFile
{
get
@@ -156,6 +172,11 @@ public ICommand NextCommand
private void BookOpened(Task task)
{
EpubBook epubBook = task.Result;
+ if (currentEpubArchive != null)
+ {
+ currentEpubArchive.Dispose();
+ }
+ CurrentEpubArchive = ZipFile.OpenRead(epubBook.FilePath);
Navigation = new ObservableCollection(bookModel.GetNavigation(epubBook));
ReadingOrder = new ObservableCollection(bookModel.GetReadingOrder(epubBook));
if (ReadingOrder.Any())
@@ -176,7 +197,7 @@ private void Navigate(NavigationItemViewModel navigationItemViewModel)
navigationItemViewModel.IsTreeItemExpanded = true;
if (navigationItemViewModel.IsLink)
{
- Navigate(ReadingOrder.FirstOrDefault(htmlContentFile => htmlContentFile.HtmlFilePath == navigationItemViewModel.FilePath));
+ Navigate(ReadingOrder.FirstOrDefault(htmlContentFile => htmlContentFile.HtmlFilePathInEpubManifest == navigationItemViewModel.FilePath));
CurrentAnchor = navigationItemViewModel.Anchor;
}
}
diff --git a/Source/VersOne.Epub.WpfDemo/ViewModels/HtmlContentFileViewModel.cs b/Source/VersOne.Epub.WpfDemo/ViewModels/HtmlContentFileViewModel.cs
index 8b66ffb..aa92328 100644
--- a/Source/VersOne.Epub.WpfDemo/ViewModels/HtmlContentFileViewModel.cs
+++ b/Source/VersOne.Epub.WpfDemo/ViewModels/HtmlContentFileViewModel.cs
@@ -4,16 +4,19 @@ namespace VersOne.Epub.WpfDemo.ViewModels
{
public class HtmlContentFileViewModel : ViewModel
{
- public HtmlContentFileViewModel(string htmlFilePath, string htmlContent, Dictionary images, Dictionary styleSheets, Dictionary fonts)
+ public HtmlContentFileViewModel(string htmlFilePathInEpubManifest, string htmlFilePathInEpubArchive, string htmlContent, Dictionary images,
+ Dictionary styleSheets, Dictionary fonts)
{
- HtmlFilePath = htmlFilePath;
+ HtmlFilePathInEpubManifest = htmlFilePathInEpubManifest;
+ HtmlFilePathInEpubArchive = htmlFilePathInEpubArchive;
HtmlContent = htmlContent;
Images = images;
StyleSheets = styleSheets;
Fonts = fonts;
}
- public string HtmlFilePath { get; }
+ public string HtmlFilePathInEpubManifest { get; }
+ public string HtmlFilePathInEpubArchive { get; }
public string HtmlContent { get; }
public Dictionary Images { get; }
public Dictionary StyleSheets { get; }
diff --git a/Source/VersOne.Epub.WpfDemo/Views/BookView.xaml b/Source/VersOne.Epub.WpfDemo/Views/BookView.xaml
index 43f3c4d..0b58c83 100644
--- a/Source/VersOne.Epub.WpfDemo/Views/BookView.xaml
+++ b/Source/VersOne.Epub.WpfDemo/Views/BookView.xaml
@@ -23,26 +23,28 @@
-
+
-
+
+ BorderThickness="0,0,1,0" />
-
+
@@ -61,6 +63,7 @@
-
+
diff --git a/Source/VersOne.Epub.WpfDemo/packages.config b/Source/VersOne.Epub.WpfDemo/packages.config
index 228ef62..21b612a 100644
--- a/Source/VersOne.Epub.WpfDemo/packages.config
+++ b/Source/VersOne.Epub.WpfDemo/packages.config
@@ -6,4 +6,5 @@
+
\ No newline at end of file
diff --git a/Source/VersOne.Epub/Entities/EpubContentFile.cs b/Source/VersOne.Epub/Entities/EpubContentFile.cs
index 3da6239..0e08494 100644
--- a/Source/VersOne.Epub/Entities/EpubContentFile.cs
+++ b/Source/VersOne.Epub/Entities/EpubContentFile.cs
@@ -3,6 +3,7 @@
public abstract class EpubContentFile
{
public string FileName { get; set; }
+ public string FilePathInEpubArchive { get; set; }
public EpubContentType ContentType { get; set; }
public string ContentMimeType { get; set; }
}
diff --git a/Source/VersOne.Epub/EpubReader.cs b/Source/VersOne.Epub/EpubReader.cs
index ac183c2..9fca898 100644
--- a/Source/VersOne.Epub/EpubReader.cs
+++ b/Source/VersOne.Epub/EpubReader.cs
@@ -199,6 +199,7 @@ private static async Task> ReadTextConte
EpubTextContentFile textContentFile = new EpubTextContentFile
{
FileName = textContentFileRef.Value.FileName,
+ FilePathInEpubArchive = textContentFileRef.Value.FilePathInEpubArchive,
ContentType = textContentFileRef.Value.ContentType,
ContentMimeType = textContentFileRef.Value.ContentMimeType
};
@@ -223,6 +224,7 @@ private static async Task ReadByteContentFile(EpubContentFi
EpubByteContentFile result = new EpubByteContentFile
{
FileName = contentFileRef.FileName,
+ FilePathInEpubArchive = contentFileRef.FilePathInEpubArchive,
ContentType = contentFileRef.ContentType,
ContentMimeType = contentFileRef.ContentMimeType
};
diff --git a/Source/VersOne.Epub/RefEntities/EpubContentFileRef.cs b/Source/VersOne.Epub/RefEntities/EpubContentFileRef.cs
index a566367..1e287d0 100644
--- a/Source/VersOne.Epub/RefEntities/EpubContentFileRef.cs
+++ b/Source/VersOne.Epub/RefEntities/EpubContentFileRef.cs
@@ -19,6 +19,14 @@ protected EpubContentFileRef(EpubBookRef epubBookRef)
public EpubContentType ContentType { get; set; }
public string ContentMimeType { get; set; }
+ public string FilePathInEpubArchive
+ {
+ get
+ {
+ return ZipPathUtils.Combine(epubBookRef.Schema.ContentDirectoryPath, FileName);
+ }
+ }
+
public byte[] ReadContentAsBytes()
{
return ReadContentAsBytesAsync().Result;
@@ -61,7 +69,7 @@ private IZipFileEntry GetContentFileEntry()
{
throw new Exception("EPUB parsing error: file name of the specified content file is empty.");
}
- string contentFilePath = ZipPathUtils.Combine(epubBookRef.Schema.ContentDirectoryPath, FileName);
+ string contentFilePath = FilePathInEpubArchive;
IZipFileEntry contentFileEntry = epubBookRef.EpubFile.GetEntry(contentFilePath);
if (contentFileEntry == null)
{
diff --git a/Source/VersOne.Epub/VersOne.Epub.csproj b/Source/VersOne.Epub/VersOne.Epub.csproj
index fa16637..98d1e79 100644
--- a/Source/VersOne.Epub/VersOne.Epub.csproj
+++ b/Source/VersOne.Epub/VersOne.Epub.csproj
@@ -6,7 +6,7 @@
vers, 2015-2022
- 3.1.0
+ 3.1.1
false
True
false
diff --git a/Source/VersOne.Epub/VersOne.Epub.nuspec b/Source/VersOne.Epub/VersOne.Epub.nuspec
index 1d5689a..0a4fa51 100644
--- a/Source/VersOne.Epub/VersOne.Epub.nuspec
+++ b/Source/VersOne.Epub/VersOne.Epub.nuspec
@@ -2,14 +2,14 @@
VersOne.Epub
- 3.1.0
+ 3.1.1
EPUB reader
.NET library for reading EPUB files
vers
false
Unlicense
https://github.com/vers-one/EpubReader
- Some configuration options to handle malformed EPUB files.
+ All content files now expose their physical file path in EPUB archive.
Unlicense <http://unlicense.org>
epub