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