diff --git a/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/EpubViewerControl.xaml b/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/EpubViewerControl.xaml
new file mode 100644
index 0000000..561d74d
--- /dev/null
+++ b/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/EpubViewerControl.xaml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/EpubViewerControl.xaml.cs b/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/EpubViewerControl.xaml.cs
new file mode 100644
index 0000000..97c3d79
--- /dev/null
+++ b/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/EpubViewerControl.xaml.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using VersOne.Epub;
+
+namespace QuickLook.Plugin.EpubViewer
+{
+ ///
+ /// Logica di interazione per EpubViewerControl.xaml
+ ///
+ public partial class EpubViewerControl : UserControl, INotifyPropertyChanged
+ {
+ private EpubBookRef epubBook;
+ private List chapterRefs;
+ private int currChapter;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public string Chapter => $"{chapterRefs?[currChapter].Title} ({currChapter + 1}/{chapterRefs?.Count})";
+
+ public EpubViewerControl()
+ {
+ InitializeComponent();
+
+ // design-time only
+ Resources.MergedDictionaries.Clear();
+
+ this.DataContext = this;
+ this.pagePanel.ImageLoad += PagePanel_ImageLoad;
+ }
+
+ private void PagePanel_ImageLoad(object sender, TheArtOfDev.HtmlRenderer.WPF.RoutedEvenArgs args)
+ {
+ var key = this.epubBook.Content.Images.Keys.FirstOrDefault(k => args.Data.Src.IndexOf(k, StringComparison.InvariantCultureIgnoreCase) >= 0);
+ if (key != null)
+ {
+ var img = ImageFromStream(this.epubBook.Content.Images[key].GetContentStream());
+ args.Data.Callback(img);
+ args.Handled = true;
+ }
+ }
+
+ ///
+ /// Get image by resource key.
+ ///
+ public static BitmapImage ImageFromStream(Stream stream)
+ {
+ var bitmapImage = new BitmapImage();
+ bitmapImage.BeginInit();
+ bitmapImage.StreamSource = stream;
+ bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
+ bitmapImage.EndInit();
+ // bitmapImage.Freeze();
+ return bitmapImage;
+ }
+
+ internal void SetContent(EpubBookRef epubBook)
+ {
+ this.epubBook = epubBook;
+ this.chapterRefs = Flatten(epubBook.GetChapters());
+ this.currChapter = 0;
+ this.pagePanel.Text = chapterRefs[currChapter].ReadHtmlContent();
+ OnPropertyChanged("Chapter");
+ }
+
+ private List Flatten(List list)
+ {
+ return list.SelectMany(l => new EpubChapterRef[] { l }.Concat(Flatten(l.SubChapters))).ToList();
+ }
+
+ private void NextButton_Click(object sender, RoutedEventArgs e)
+ {
+ this.NextChapter();
+ }
+
+ private async void NextChapter()
+ {
+ try
+ {
+ this.currChapter = Math.Min(this.currChapter + 1, chapterRefs.Count - 1);
+ this.pagePanel.Text = await chapterRefs[currChapter].ReadHtmlContentAsync();
+ if (chapterRefs[currChapter].Anchor != null)
+ {
+ this.pagePanel.ScrollToElement(chapterRefs[currChapter].Anchor);
+ }
+ OnPropertyChanged("Chapter");
+ }
+ catch (Exception ex)
+ {
+ this.pagePanel.Text = "Invalid chapter.
";
+ OnPropertyChanged("Chapter");
+ Debug.WriteLine(ex);
+ }
+ }
+
+ private void PrevButton_Click(object sender, RoutedEventArgs e)
+ {
+ this.PrevChapter();
+ }
+
+ private async void PrevChapter()
+ {
+ try
+ {
+ this.currChapter = Math.Max(this.currChapter - 1, 0);
+ this.pagePanel.Text = await chapterRefs[currChapter].ReadHtmlContentAsync();
+ if (chapterRefs[currChapter].Anchor != null)
+ {
+ this.pagePanel.ScrollToElement(chapterRefs[currChapter].Anchor);
+ }
+ OnPropertyChanged("Chapter");
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine(ex);
+ this.pagePanel.Text = "Invalid chapter.
";
+ OnPropertyChanged("Chapter");
+ }
+ }
+
+ // Create the OnPropertyChanged method to raise the event
+ protected void OnPropertyChanged(string name)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+ }
+
+ private void Grid_KeyDown(object sender, KeyEventArgs e)
+ {
+
+ if (e.Key == Key.Left)
+ {
+ this.PrevChapter();
+ e.Handled = true;
+ }
+ else if (e.Key == Key.Right)
+ {
+ this.NextChapter();
+ e.Handled = true;
+ }
+ else
+ {
+ e.Handled = false;
+ }
+ }
+ }
+}
diff --git a/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/Plugin.cs
new file mode 100644
index 0000000..9061c8c
--- /dev/null
+++ b/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/Plugin.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.ExceptionServices;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Threading;
+using QuickLook.Common.Plugin;
+using QuickLook.Plugin.HtmlViewer;
+using VersOne.Epub;
+
+namespace QuickLook.Plugin.EpubViewer
+{
+ public class Plugin : IViewer
+ {
+ private EpubViewerControl _panel;
+ public int Priority => int.MaxValue;
+
+ public void Init()
+ {
+ }
+
+ public bool CanHandle(string path)
+ {
+ return !Directory.Exists(path) && new[] { ".epub" }.Any(path.ToLower().EndsWith);
+ }
+
+ public void Cleanup()
+ {
+ }
+
+ public void Prepare(string path, ContextObject context)
+ {
+ context.PreferredSize = new Size { Width = 1000, Height = 600 };
+ }
+
+ public void View(string path, ContextObject context)
+ {
+ _panel = new EpubViewerControl();
+ context.ViewerContent = _panel;
+ Exception exception = null;
+
+ _panel.Dispatcher.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ // Opens a book
+ EpubBookRef epubBook = EpubReader.OpenBook(path);
+ context.Title = epubBook.Title;
+ _panel.SetContent(epubBook);
+
+ context.IsBusy = false;
+ }
+ catch (Exception e)
+ {
+ exception = e;
+ }
+ }), DispatcherPriority.Loaded).Wait();
+
+ if (exception != null)
+ ExceptionDispatchInfo.Capture(exception).Throw();
+ }
+ }
+}
diff --git a/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/Properties/AssemblyInfo.cs b/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..aca68fd
--- /dev/null
+++ b/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/Properties/AssemblyInfo.cs
@@ -0,0 +1,34 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// Le informazioni generali relative a un assembly sono controllate dal seguente
+// set di attributi. Modificare i valori di questi attributi per modificare le informazioni
+// associate a un assembly.
+[assembly: AssemblyTitle("QuickLook.Plugin.EpubViewer")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("QuickLook.Plugin.EpubViewer")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Se si imposta ComVisible su false, i tipi in questo assembly non saranno visibili
+// ai componenti COM. Se è necessario accedere a un tipo in questo assembly da
+// COM, impostare su true l'attributo ComVisible per tale tipo.
+[assembly: ComVisible(false)]
+
+// Se il progetto viene esposto a COM, il GUID seguente verrà utilizzato come ID della libreria dei tipi
+[assembly: Guid("260c9e70-0582-471f-bfb5-022cfe7984c8")]
+
+// Le informazioni sulla versione di un assembly sono costituite dai seguenti quattro valori:
+//
+// Versione principale
+// Versione secondaria
+// Numero di build
+// Revisione
+//
+// È possibile specificare tutti i valori oppure impostare valori predefiniti per i numeri relativi alla revisione e alla build
+// usando l'asterisco '*' come illustrato di seguito:
+// [assembly: AssemblyVersion("1.0.*")]
\ No newline at end of file
diff --git a/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/QuickLook.Plugin.EpubViewer.csproj b/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/QuickLook.Plugin.EpubViewer.csproj
new file mode 100644
index 0000000..3113937
--- /dev/null
+++ b/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/QuickLook.Plugin.EpubViewer.csproj
@@ -0,0 +1,89 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {260C9E70-0582-471F-BFB5-022CFE7984C8}
+ Library
+ Properties
+ QuickLook.Plugin.EpubViewer
+ QuickLook.Plugin.EpubViewer
+ v4.6.2
+ 512
+
+
+
+ true
+ full
+ false
+ ..\..\Build\Debug\QuickLook.Plugin\QuickLook.Plugin.EpubViewer\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\..\packages\HtmlRenderer.Core.1.5.0.6\lib\net45\HtmlRenderer.dll
+
+
+ ..\..\packages\HtmlRenderer.WPF.1.5.0.6\lib\net45\HtmlRenderer.WPF.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Properties\GitVersion.cs
+
+
+ EpubViewerControl.xaml
+
+
+
+
+
+
+ {85fdd6ba-871d-46c8-bd64-f6bb0cb5ea95}
+ QuickLook.Common
+
+
+ {ce22a1f3-7f2c-4ec8-bfde-b58d0eb625fc}
+ QuickLook.Plugin.HtmlViewer
+
+
+ {8bfc120e-7e59-437c-9196-595ab00025e1}
+ VersOne.Epub
+
+
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/packages.config b/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/packages.config
new file mode 100644
index 0000000..dd2ff0c
--- /dev/null
+++ b/QuickLook.Plugin/QuickLook.Plugin.EpubViewer/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/QuickLook.Plugin/VersOne.Epub/Entities/EpubBook.cs b/QuickLook.Plugin/VersOne.Epub/Entities/EpubBook.cs
new file mode 100644
index 0000000..b704d1a
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Entities/EpubBook.cs
@@ -0,0 +1,16 @@
+using System.Collections.Generic;
+
+namespace VersOne.Epub
+{
+ public class EpubBook
+ {
+ public string FilePath { get; set; }
+ public string Title { get; set; }
+ public string Author { get; set; }
+ public List AuthorList { get; set; }
+ public EpubSchema Schema { get; set; }
+ public EpubContent Content { get; set; }
+ public byte[] CoverImage { get; set; }
+ public List Chapters { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Entities/EpubByteContentFile.cs b/QuickLook.Plugin/VersOne.Epub/Entities/EpubByteContentFile.cs
new file mode 100644
index 0000000..1e88ec0
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Entities/EpubByteContentFile.cs
@@ -0,0 +1,7 @@
+namespace VersOne.Epub
+{
+ public class EpubByteContentFile : EpubContentFile
+ {
+ public byte[] Content { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Entities/EpubChapter.cs b/QuickLook.Plugin/VersOne.Epub/Entities/EpubChapter.cs
new file mode 100644
index 0000000..cf022f3
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Entities/EpubChapter.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+
+namespace VersOne.Epub
+{
+ public class EpubChapter
+ {
+ public string Title { get; set; }
+ public string ContentFileName { get; set; }
+ public string Anchor { get; set; }
+ public string HtmlContent { get; set; }
+ public List SubChapters { get; set; }
+
+ public override string ToString()
+ {
+ return String.Format("Title: {0}, Subchapter count: {1}", Title, SubChapters.Count);
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Entities/EpubContent.cs b/QuickLook.Plugin/VersOne.Epub/Entities/EpubContent.cs
new file mode 100644
index 0000000..c7ce4a5
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Entities/EpubContent.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+
+namespace VersOne.Epub
+{
+ public class EpubContent
+ {
+ public Dictionary Html { get; set; }
+ public Dictionary Css { get; set; }
+ public Dictionary Images { get; set; }
+ public Dictionary Fonts { get; set; }
+ public Dictionary AllFiles { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Entities/EpubContentFile.cs b/QuickLook.Plugin/VersOne.Epub/Entities/EpubContentFile.cs
new file mode 100644
index 0000000..3da6239
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Entities/EpubContentFile.cs
@@ -0,0 +1,9 @@
+namespace VersOne.Epub
+{
+ public abstract class EpubContentFile
+ {
+ public string FileName { get; set; }
+ public EpubContentType ContentType { get; set; }
+ public string ContentMimeType { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Entities/EpubContentType.cs b/QuickLook.Plugin/VersOne.Epub/Entities/EpubContentType.cs
new file mode 100644
index 0000000..77f3fa9
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Entities/EpubContentType.cs
@@ -0,0 +1,20 @@
+namespace VersOne.Epub
+{
+ public enum EpubContentType
+ {
+ XHTML_1_1 = 1,
+ DTBOOK,
+ DTBOOK_NCX,
+ OEB1_DOCUMENT,
+ XML,
+ CSS,
+ OEB1_CSS,
+ IMAGE_GIF,
+ IMAGE_JPEG,
+ IMAGE_PNG,
+ IMAGE_SVG,
+ FONT_TRUETYPE,
+ FONT_OPENTYPE,
+ OTHER
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Entities/EpubSchema.cs b/QuickLook.Plugin/VersOne.Epub/Entities/EpubSchema.cs
new file mode 100644
index 0000000..80f524f
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Entities/EpubSchema.cs
@@ -0,0 +1,11 @@
+using VersOne.Epub.Schema;
+
+namespace VersOne.Epub
+{
+ public class EpubSchema
+ {
+ public EpubPackage Package { get; set; }
+ public EpubNavigation Navigation { get; set; }
+ public string ContentDirectoryPath { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Entities/EpubTextContentFile.cs b/QuickLook.Plugin/VersOne.Epub/Entities/EpubTextContentFile.cs
new file mode 100644
index 0000000..642b1be
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Entities/EpubTextContentFile.cs
@@ -0,0 +1,7 @@
+namespace VersOne.Epub
+{
+ public class EpubTextContentFile : EpubContentFile
+ {
+ public string Content { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/EpubReader.cs b/QuickLook.Plugin/VersOne.Epub/EpubReader.cs
new file mode 100644
index 0000000..4f07c93
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/EpubReader.cs
@@ -0,0 +1,231 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Threading.Tasks;
+using VersOne.Epub.Internal;
+
+namespace VersOne.Epub
+{
+ public static class EpubReader
+ {
+ ///
+ /// Opens the book synchronously without reading its whole content. Holds the handle to the EPUB file.
+ ///
+ /// path to the EPUB file
+ ///
+ public static EpubBookRef OpenBook(string filePath)
+ {
+ return OpenBookAsync(filePath).Result;
+ }
+
+ ///
+ /// Opens the book synchronously without reading its whole content.
+ ///
+ /// path to the EPUB file
+ ///
+ public static EpubBookRef OpenBook(Stream stream)
+ {
+ return OpenBookAsync(stream).Result;
+ }
+
+ ///
+ /// Opens the book asynchronously without reading its whole content. Holds the handle to the EPUB file.
+ ///
+ /// path to the EPUB file
+ ///
+ public static Task OpenBookAsync(string filePath)
+ {
+ if (!File.Exists(filePath))
+ {
+ throw new FileNotFoundException("Specified epub file not found.", filePath);
+ }
+ return OpenBookAsync(GetZipArchive(filePath));
+ }
+
+ ///
+ /// Opens the book asynchronously without reading its whole content.
+ ///
+ /// path to the EPUB file
+ ///
+ public static Task OpenBookAsync(Stream stream)
+ {
+ return OpenBookAsync(GetZipArchive(stream));
+ }
+
+ ///
+ /// Opens the book synchronously and reads all of its content into the memory. Does not hold the handle to the EPUB file.
+ ///
+ /// path to the EPUB file
+ ///
+ public static EpubBook ReadBook(string filePath)
+ {
+ return ReadBookAsync(filePath).Result;
+ }
+
+ ///
+ /// Opens the book synchronously and reads all of its content into the memory. Does not hold the handle to the EPUB file.
+ ///
+ /// path to the EPUB file
+ ///
+ public static EpubBook ReadBook(Stream stream)
+ {
+ return ReadBookAsync(stream).Result;
+ }
+
+ ///
+ /// Opens the book asynchronously and reads all of its content into the memory. Does not hold the handle to the EPUB file.
+ ///
+ /// path to the EPUB file
+ ///
+ public static async Task ReadBookAsync(string filePath)
+ {
+ EpubBookRef epubBookRef = await OpenBookAsync(filePath).ConfigureAwait(false);
+ return await ReadBookAsync(epubBookRef).ConfigureAwait(false);
+ }
+
+ ///
+ /// Opens the book asynchronously and reads all of its content into the memory.
+ ///
+ /// path to the EPUB file
+ ///
+ public static async Task ReadBookAsync(Stream stream)
+ {
+ EpubBookRef epubBookRef = await OpenBookAsync(stream).ConfigureAwait(false);
+ return await ReadBookAsync(epubBookRef).ConfigureAwait(false);
+ }
+
+ private static async Task OpenBookAsync(ZipArchive zipArchive, string filePath = null)
+ {
+ EpubBookRef result = null;
+ try
+ {
+ result = new EpubBookRef(zipArchive);
+ result.FilePath = filePath;
+ result.Schema = await SchemaReader.ReadSchemaAsync(zipArchive).ConfigureAwait(false);
+ result.Title = result.Schema.Package.Metadata.Titles.FirstOrDefault() ?? String.Empty;
+ result.AuthorList = result.Schema.Package.Metadata.Creators.Select(creator => creator.Creator).ToList();
+ result.Author = String.Join(", ", result.AuthorList);
+ result.Content = await Task.Run(() => ContentReader.ParseContentMap(result)).ConfigureAwait(false);
+ return result;
+ }
+ catch
+ {
+ result?.Dispose();
+ throw;
+ }
+ }
+
+ private static async Task ReadBookAsync(EpubBookRef epubBookRef)
+ {
+ EpubBook result = new EpubBook();
+ using (epubBookRef)
+ {
+ result.FilePath = epubBookRef.FilePath;
+ result.Schema = epubBookRef.Schema;
+ result.Title = epubBookRef.Title;
+ result.AuthorList = epubBookRef.AuthorList;
+ result.Author = epubBookRef.Author;
+ result.Content = await ReadContent(epubBookRef.Content).ConfigureAwait(false);
+ result.CoverImage = await epubBookRef.ReadCoverAsync().ConfigureAwait(false);
+ List chapterRefs = await epubBookRef.GetChaptersAsync().ConfigureAwait(false);
+ result.Chapters = await ReadChapters(chapterRefs).ConfigureAwait(false);
+ }
+ return result;
+ }
+
+ private static ZipArchive GetZipArchive(string filePath)
+ {
+ return ZipFile.OpenRead(filePath);
+ }
+
+ private static ZipArchive GetZipArchive(Stream stream)
+ {
+ return new ZipArchive(stream, ZipArchiveMode.Read);
+ }
+
+ private static async Task ReadContent(EpubContentRef contentRef)
+ {
+ EpubContent result = new EpubContent();
+ result.Html = await ReadTextContentFiles(contentRef.Html).ConfigureAwait(false);
+ result.Css = await ReadTextContentFiles(contentRef.Css).ConfigureAwait(false);
+ result.Images = await ReadByteContentFiles(contentRef.Images).ConfigureAwait(false);
+ result.Fonts = await ReadByteContentFiles(contentRef.Fonts).ConfigureAwait(false);
+ result.AllFiles = new Dictionary();
+ foreach (KeyValuePair textContentFile in result.Html.Concat(result.Css))
+ {
+ result.AllFiles.Add(textContentFile.Key, textContentFile.Value);
+ }
+ foreach (KeyValuePair byteContentFile in result.Images.Concat(result.Fonts))
+ {
+ result.AllFiles.Add(byteContentFile.Key, byteContentFile.Value);
+ }
+ foreach (KeyValuePair contentFileRef in contentRef.AllFiles)
+ {
+ if (!result.AllFiles.ContainsKey(contentFileRef.Key))
+ {
+ result.AllFiles.Add(contentFileRef.Key, await ReadByteContentFile(contentFileRef.Value).ConfigureAwait(false));
+ }
+ }
+ return result;
+ }
+
+ private static async Task> ReadTextContentFiles(Dictionary textContentFileRefs)
+ {
+ Dictionary result = new Dictionary();
+ foreach (KeyValuePair textContentFileRef in textContentFileRefs)
+ {
+ EpubTextContentFile textContentFile = new EpubTextContentFile
+ {
+ FileName = textContentFileRef.Value.FileName,
+ ContentType = textContentFileRef.Value.ContentType,
+ ContentMimeType = textContentFileRef.Value.ContentMimeType
+ };
+ textContentFile.Content = await textContentFileRef.Value.ReadContentAsTextAsync().ConfigureAwait(false);
+ result.Add(textContentFileRef.Key, textContentFile);
+ }
+ return result;
+ }
+
+ private static async Task> ReadByteContentFiles(Dictionary byteContentFileRefs)
+ {
+ Dictionary result = new Dictionary();
+ foreach (KeyValuePair byteContentFileRef in byteContentFileRefs)
+ {
+ result.Add(byteContentFileRef.Key, await ReadByteContentFile(byteContentFileRef.Value).ConfigureAwait(false));
+ }
+ return result;
+ }
+
+ private static async Task ReadByteContentFile(EpubContentFileRef contentFileRef)
+ {
+ EpubByteContentFile result = new EpubByteContentFile
+ {
+ FileName = contentFileRef.FileName,
+ ContentType = contentFileRef.ContentType,
+ ContentMimeType = contentFileRef.ContentMimeType
+ };
+ result.Content = await contentFileRef.ReadContentAsBytesAsync().ConfigureAwait(false);
+ return result;
+ }
+
+ private static async Task> ReadChapters(List chapterRefs)
+ {
+ List result = new List();
+ foreach (EpubChapterRef chapterRef in chapterRefs)
+ {
+ EpubChapter chapter = new EpubChapter
+ {
+ Title = chapterRef.Title,
+ ContentFileName = chapterRef.ContentFileName,
+ Anchor = chapterRef.Anchor
+ };
+ chapter.HtmlContent = await chapterRef.ReadHtmlContentAsync().ConfigureAwait(false);
+ chapter.SubChapters = await ReadChapters(chapterRef.SubChapters).ConfigureAwait(false);
+ result.Add(chapter);
+ }
+ return result;
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Readers/BookCoverReader.cs b/QuickLook.Plugin/VersOne.Epub/Readers/BookCoverReader.cs
new file mode 100644
index 0000000..5f99b64
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Readers/BookCoverReader.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using VersOne.Epub.Schema;
+
+namespace VersOne.Epub.Internal
+{
+ internal static class BookCoverReader
+ {
+ public static async Task ReadBookCoverAsync(EpubBookRef bookRef)
+ {
+ List metaItems = bookRef.Schema.Package.Metadata.MetaItems;
+ if (metaItems == null || !metaItems.Any())
+ {
+ return null;
+ }
+ EpubMetadataMeta coverMetaItem = metaItems.FirstOrDefault(metaItem => String.Compare(metaItem.Name, "cover", StringComparison.OrdinalIgnoreCase) == 0);
+ if (coverMetaItem == null)
+ {
+ return null;
+ }
+ if (String.IsNullOrEmpty(coverMetaItem.Content))
+ {
+ throw new Exception("Incorrect EPUB metadata: cover item content is missing.");
+ }
+ EpubManifestItem coverManifestItem = bookRef.Schema.Package.Manifest.FirstOrDefault(manifestItem => String.Compare(manifestItem.Id, coverMetaItem.Content, StringComparison.OrdinalIgnoreCase) == 0);
+ if (coverManifestItem == null)
+ {
+ throw new Exception(String.Format("Incorrect EPUB manifest: item with ID = \"{0}\" is missing.", coverMetaItem.Content));
+ }
+ if (!bookRef.Content.Images.TryGetValue(coverManifestItem.Href, out EpubByteContentFileRef coverImageContentFileRef))
+ {
+ throw new Exception(String.Format("Incorrect EPUB manifest: item with href = \"{0}\" is missing.", coverManifestItem.Href));
+ }
+ byte[] coverImageContent = await coverImageContentFileRef.ReadContentAsBytesAsync().ConfigureAwait(false);
+ return coverImageContent;
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Readers/ChapterReader.cs b/QuickLook.Plugin/VersOne.Epub/Readers/ChapterReader.cs
new file mode 100644
index 0000000..32c2760
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Readers/ChapterReader.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using VersOne.Epub.Schema;
+
+namespace VersOne.Epub.Internal
+{
+ internal static class ChapterReader
+ {
+ public static List GetChapters(EpubBookRef bookRef)
+ {
+ // return GetChapters(bookRef, bookRef.Schema.Navigation.NavMap);
+ return GetChapters(bookRef, bookRef.Schema.Package.Spine, bookRef.Schema.Navigation.NavMap);
+ }
+
+ public static List GetChapters(EpubBookRef bookRef, EpubSpine spine, List navigationPoints)
+ {
+ List result = new List();
+ for (int s = 0; s < spine.Count; s++)
+ {
+ EpubSpineItemRef itemRef = spine[s];
+ string contentFileName;
+ string anchor;
+ contentFileName = WebUtility.UrlDecode(bookRef.Schema.Package.Manifest.FirstOrDefault(e => e.Id == itemRef.IdRef)?.Href);
+ anchor = null;
+ if (!bookRef.Content.Html.TryGetValue(contentFileName, out EpubTextContentFileRef htmlContentFileRef))
+ {
+ throw new Exception(String.Format("Incorrect EPUB manifest: item with href = \"{0}\" is missing.", contentFileName));
+ }
+ EpubChapterRef chapterRef = new EpubChapterRef(htmlContentFileRef);
+ chapterRef.ContentFileName = contentFileName;
+ chapterRef.Anchor = anchor;
+ chapterRef.Parent = null;
+ var navPoint = navigationPoints.LastOrDefault(nav => spine.Take(s + 1).Select(sp => bookRef.Schema.Package.Manifest.FirstOrDefault(e => e.Id == sp.IdRef)?.Href).Contains(nav.Content.Source.Split('#')[0]));
+ if (navPoint != null)
+ {
+ chapterRef.Title = navPoint.NavigationLabels.First().Text;
+ }
+ else
+ {
+ chapterRef.Title = $"Chapter {s + 1}";
+ }
+ chapterRef.SubChapters = new List();
+ result.Add(chapterRef);
+ }
+ return result;
+ }
+
+ public static List GetChapters(EpubBookRef bookRef, List navigationPoints, EpubChapterRef parentChapter = null)
+ {
+ List result = new List();
+ foreach (EpubNavigationPoint navigationPoint in navigationPoints)
+ {
+ string contentFileName;
+ string anchor;
+ int contentSourceAnchorCharIndex = navigationPoint.Content.Source.IndexOf('#');
+ if (contentSourceAnchorCharIndex == -1)
+ {
+ contentFileName = WebUtility.UrlDecode(navigationPoint.Content.Source);
+ anchor = null;
+ }
+ else
+ {
+ contentFileName = WebUtility.UrlDecode(navigationPoint.Content.Source.Substring(0, contentSourceAnchorCharIndex));
+ anchor = navigationPoint.Content.Source.Substring(contentSourceAnchorCharIndex + 1);
+ }
+ if (!bookRef.Content.Html.TryGetValue(contentFileName, out EpubTextContentFileRef htmlContentFileRef))
+ {
+ throw new Exception(String.Format("Incorrect EPUB manifest: item with href = \"{0}\" is missing.", contentFileName));
+ }
+ EpubChapterRef chapterRef = new EpubChapterRef(htmlContentFileRef);
+ chapterRef.ContentFileName = contentFileName;
+ chapterRef.Anchor = anchor;
+ chapterRef.Parent = parentChapter;
+ chapterRef.Title = navigationPoint.NavigationLabels.First().Text;
+ chapterRef.SubChapters = GetChapters(bookRef, navigationPoint.ChildNavigationPoints, chapterRef);
+ result.Add(chapterRef);
+ }
+ return result;
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Readers/ContentReader.cs b/QuickLook.Plugin/VersOne.Epub/Readers/ContentReader.cs
new file mode 100644
index 0000000..03be95f
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Readers/ContentReader.cs
@@ -0,0 +1,113 @@
+using System.Collections.Generic;
+using VersOne.Epub.Schema;
+
+namespace VersOne.Epub.Internal
+{
+ internal static class ContentReader
+ {
+ public static EpubContentRef ParseContentMap(EpubBookRef bookRef)
+ {
+ EpubContentRef result = new EpubContentRef
+ {
+ Html = new Dictionary(),
+ Css = new Dictionary(),
+ Images = new Dictionary(),
+ Fonts = new Dictionary(),
+ AllFiles = new Dictionary()
+ };
+ foreach (EpubManifestItem manifestItem in bookRef.Schema.Package.Manifest)
+ {
+ string fileName = manifestItem.Href;
+ string contentMimeType = manifestItem.MediaType;
+ EpubContentType contentType = GetContentTypeByContentMimeType(contentMimeType);
+ switch (contentType)
+ {
+ case EpubContentType.XHTML_1_1:
+ case EpubContentType.CSS:
+ case EpubContentType.OEB1_DOCUMENT:
+ case EpubContentType.OEB1_CSS:
+ case EpubContentType.XML:
+ case EpubContentType.DTBOOK:
+ case EpubContentType.DTBOOK_NCX:
+ EpubTextContentFileRef epubTextContentFile = new EpubTextContentFileRef(bookRef)
+ {
+ FileName = fileName,
+ ContentMimeType = contentMimeType,
+ ContentType = contentType
+ };
+ switch (contentType)
+ {
+ case EpubContentType.XHTML_1_1:
+ result.Html[fileName] = epubTextContentFile;
+ break;
+ case EpubContentType.CSS:
+ result.Css[fileName] = epubTextContentFile;
+ break;
+ }
+ result.AllFiles[fileName] = epubTextContentFile;
+ break;
+ default:
+ EpubByteContentFileRef epubByteContentFile = new EpubByteContentFileRef(bookRef)
+ {
+ FileName = fileName,
+ ContentMimeType = contentMimeType,
+ ContentType = contentType
+ };
+ switch (contentType)
+ {
+ case EpubContentType.IMAGE_GIF:
+ case EpubContentType.IMAGE_JPEG:
+ case EpubContentType.IMAGE_PNG:
+ case EpubContentType.IMAGE_SVG:
+ result.Images[fileName] = epubByteContentFile;
+ break;
+ case EpubContentType.FONT_TRUETYPE:
+ case EpubContentType.FONT_OPENTYPE:
+ result.Fonts[fileName] = epubByteContentFile;
+ break;
+ }
+ result.AllFiles[fileName] = epubByteContentFile;
+ break;
+ }
+ }
+ return result;
+ }
+
+ private static EpubContentType GetContentTypeByContentMimeType(string contentMimeType)
+ {
+ switch (contentMimeType.ToLowerInvariant())
+ {
+ case "application/xhtml+xml":
+ return EpubContentType.XHTML_1_1;
+ case "application/x-dtbook+xml":
+ return EpubContentType.DTBOOK;
+ case "application/x-dtbncx+xml":
+ return EpubContentType.DTBOOK_NCX;
+ case "text/x-oeb1-document":
+ return EpubContentType.OEB1_DOCUMENT;
+ case "application/xml":
+ return EpubContentType.XML;
+ case "text/css":
+ return EpubContentType.CSS;
+ case "text/x-oeb1-css":
+ return EpubContentType.OEB1_CSS;
+ case "image/gif":
+ return EpubContentType.IMAGE_GIF;
+ case "image/jpeg":
+ return EpubContentType.IMAGE_JPEG;
+ case "image/png":
+ return EpubContentType.IMAGE_PNG;
+ case "image/svg+xml":
+ return EpubContentType.IMAGE_SVG;
+ case "font/truetype":
+ return EpubContentType.FONT_TRUETYPE;
+ case "font/opentype":
+ return EpubContentType.FONT_OPENTYPE;
+ case "application/vnd.ms-opentype":
+ return EpubContentType.FONT_OPENTYPE;
+ default:
+ return EpubContentType.OTHER;
+ }
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Readers/NavigationReader.cs b/QuickLook.Plugin/VersOne.Epub/Readers/NavigationReader.cs
new file mode 100644
index 0000000..970ab11
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Readers/NavigationReader.cs
@@ -0,0 +1,408 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using VersOne.Epub.Schema;
+
+namespace VersOne.Epub.Internal
+{
+ internal static class NavigationReader
+ {
+ public static async Task ReadNavigationAsync(ZipArchive epubArchive, string contentDirectoryPath, EpubPackage package)
+ {
+ EpubNavigation result = new EpubNavigation();
+ string tocId = package.Spine.Toc;
+ if (String.IsNullOrEmpty(tocId))
+ {
+ throw new Exception("EPUB parsing error: TOC ID is empty.");
+ }
+ EpubManifestItem tocManifestItem = package.Manifest.FirstOrDefault(item => String.Compare(item.Id, tocId, StringComparison.OrdinalIgnoreCase) == 0);
+ if (tocManifestItem == null)
+ {
+ throw new Exception(String.Format("EPUB parsing error: TOC item {0} not found in EPUB manifest.", tocId));
+ }
+ string tocFileEntryPath = ZipPathUtils.Combine(contentDirectoryPath, tocManifestItem.Href);
+ ZipArchiveEntry tocFileEntry = epubArchive.GetEntry(tocFileEntryPath);
+ if (tocFileEntry == null)
+ {
+ throw new Exception(String.Format("EPUB parsing error: TOC file {0} not found in archive.", tocFileEntryPath));
+ }
+ if (tocFileEntry.Length > Int32.MaxValue)
+ {
+ throw new Exception(String.Format("EPUB parsing error: TOC file {0} is larger than 2 Gb.", tocFileEntryPath));
+ }
+ XDocument containerDocument;
+ using (Stream containerStream = tocFileEntry.Open())
+ {
+ containerDocument = await XmlUtils.LoadDocumentAsync(containerStream).ConfigureAwait(false);
+ }
+ XNamespace ncxNamespace = "http://www.daisy.org/z3986/2005/ncx/";
+ XElement ncxNode = containerDocument.Element(ncxNamespace + "ncx");
+ if (ncxNode == null)
+ {
+ throw new Exception("EPUB parsing error: TOC file does not contain ncx element.");
+ }
+ XElement headNode = ncxNode.Element(ncxNamespace + "head");
+ if (headNode == null)
+ {
+ throw new Exception("EPUB parsing error: TOC file does not contain head element.");
+ }
+ EpubNavigationHead navigationHead = ReadNavigationHead(headNode);
+ result.Head = navigationHead;
+ XElement docTitleNode = ncxNode.Element(ncxNamespace + "docTitle");
+ if (docTitleNode == null)
+ {
+ throw new Exception("EPUB parsing error: TOC file does not contain docTitle element.");
+ }
+ EpubNavigationDocTitle navigationDocTitle = ReadNavigationDocTitle(docTitleNode);
+ result.DocTitle = navigationDocTitle;
+ result.DocAuthors = new List();
+ foreach (XElement docAuthorNode in ncxNode.Elements(ncxNamespace + "docAuthor"))
+ {
+ EpubNavigationDocAuthor navigationDocAuthor = ReadNavigationDocAuthor(docAuthorNode);
+ result.DocAuthors.Add(navigationDocAuthor);
+ }
+ XElement navMapNode = ncxNode.Element(ncxNamespace + "navMap");
+ if (navMapNode == null)
+ {
+ throw new Exception("EPUB parsing error: TOC file does not contain navMap element.");
+ }
+ EpubNavigationMap navMap = ReadNavigationMap(navMapNode);
+ result.NavMap = navMap;
+ XElement pageListNode = ncxNode.Element(ncxNamespace + "pageList");
+ if (pageListNode != null)
+ {
+ EpubNavigationPageList pageList = ReadNavigationPageList(pageListNode);
+ result.PageList = pageList;
+ }
+ result.NavLists = new List();
+ foreach (XElement navigationListNode in ncxNode.Elements(ncxNamespace + "navList"))
+ {
+ EpubNavigationList navigationList = ReadNavigationList(navigationListNode);
+ result.NavLists.Add(navigationList);
+ }
+ return result;
+ }
+
+ private static EpubNavigationHead ReadNavigationHead(XElement headNode)
+ {
+ EpubNavigationHead result = new EpubNavigationHead();
+ foreach (XElement metaNode in headNode.Elements())
+ {
+ if (String.Compare(metaNode.Name.LocalName, "meta", StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ EpubNavigationHeadMeta meta = new EpubNavigationHeadMeta();
+ foreach (XAttribute metaNodeAttribute in metaNode.Attributes())
+ {
+ string attributeValue = metaNodeAttribute.Value;
+ switch (metaNodeAttribute.Name.LocalName.ToLowerInvariant())
+ {
+ case "name":
+ meta.Name = attributeValue;
+ break;
+ case "content":
+ meta.Content = attributeValue;
+ break;
+ case "scheme":
+ meta.Scheme = attributeValue;
+ break;
+ }
+ }
+ if (String.IsNullOrWhiteSpace(meta.Name))
+ {
+ throw new Exception("Incorrect EPUB navigation meta: meta name is missing.");
+ }
+ if (meta.Content == null)
+ {
+ throw new Exception("Incorrect EPUB navigation meta: meta content is missing.");
+ }
+ result.Add(meta);
+ }
+ }
+ return result;
+ }
+
+ private static EpubNavigationDocTitle ReadNavigationDocTitle(XElement docTitleNode)
+ {
+ EpubNavigationDocTitle result = new EpubNavigationDocTitle();
+ foreach (XElement textNode in docTitleNode.Elements())
+ {
+ if (String.Compare(textNode.Name.LocalName, "text", StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ result.Add(textNode.Value);
+ }
+ }
+ return result;
+ }
+
+ private static EpubNavigationDocAuthor ReadNavigationDocAuthor(XElement docAuthorNode)
+ {
+ EpubNavigationDocAuthor result = new EpubNavigationDocAuthor();
+ foreach (XElement textNode in docAuthorNode.Elements())
+ {
+ if (String.Compare(textNode.Name.LocalName, "text", StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ result.Add(textNode.Value);
+ }
+ }
+ return result;
+ }
+
+ private static EpubNavigationMap ReadNavigationMap(XElement navigationMapNode)
+ {
+ EpubNavigationMap result = new EpubNavigationMap();
+ foreach (XElement navigationPointNode in navigationMapNode.Elements())
+ {
+ if (String.Compare(navigationPointNode.Name.LocalName, "navPoint", StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ EpubNavigationPoint navigationPoint = ReadNavigationPoint(navigationPointNode);
+ result.Add(navigationPoint);
+ }
+ }
+ return result;
+ }
+
+ private static EpubNavigationPoint ReadNavigationPoint(XElement navigationPointNode)
+ {
+ EpubNavigationPoint result = new EpubNavigationPoint();
+ foreach (XAttribute navigationPointNodeAttribute in navigationPointNode.Attributes())
+ {
+ string attributeValue = navigationPointNodeAttribute.Value;
+ switch (navigationPointNodeAttribute.Name.LocalName.ToLowerInvariant())
+ {
+ case "id":
+ result.Id = attributeValue;
+ break;
+ case "class":
+ result.Class = attributeValue;
+ break;
+ case "playOrder":
+ result.PlayOrder = attributeValue;
+ break;
+ }
+ }
+ if (String.IsNullOrWhiteSpace(result.Id))
+ {
+ throw new Exception("Incorrect EPUB navigation point: point ID is missing.");
+ }
+ result.NavigationLabels = new List();
+ result.ChildNavigationPoints = new List();
+ foreach (XElement navigationPointChildNode in navigationPointNode.Elements())
+ {
+ switch (navigationPointChildNode.Name.LocalName.ToLowerInvariant())
+ {
+ case "navlabel":
+ EpubNavigationLabel navigationLabel = ReadNavigationLabel(navigationPointChildNode);
+ result.NavigationLabels.Add(navigationLabel);
+ break;
+ case "content":
+ EpubNavigationContent content = ReadNavigationContent(navigationPointChildNode);
+ result.Content = content;
+ break;
+ case "navpoint":
+ EpubNavigationPoint childNavigationPoint = ReadNavigationPoint(navigationPointChildNode);
+ result.ChildNavigationPoints.Add(childNavigationPoint);
+ break;
+ }
+ }
+ if (!result.NavigationLabels.Any())
+ {
+ throw new Exception(String.Format("EPUB parsing error: navigation point {0} should contain at least one navigation label.", result.Id));
+ }
+ if (result.Content == null)
+ {
+ throw new Exception(String.Format("EPUB parsing error: navigation point {0} should contain content.", result.Id));
+ }
+ return result;
+ }
+
+ private static EpubNavigationLabel ReadNavigationLabel(XElement navigationLabelNode)
+ {
+ EpubNavigationLabel result = new EpubNavigationLabel();
+ XElement navigationLabelTextNode = navigationLabelNode.Element(navigationLabelNode.Name.Namespace + "text");
+ if (navigationLabelTextNode == null)
+ {
+ throw new Exception("Incorrect EPUB navigation label: label text element is missing.");
+ }
+ result.Text = navigationLabelTextNode.Value;
+ return result;
+ }
+
+ private static EpubNavigationContent ReadNavigationContent(XElement navigationContentNode)
+ {
+ EpubNavigationContent result = new EpubNavigationContent();
+ foreach (XAttribute navigationContentNodeAttribute in navigationContentNode.Attributes())
+ {
+ string attributeValue = navigationContentNodeAttribute.Value;
+ switch (navigationContentNodeAttribute.Name.LocalName.ToLowerInvariant())
+ {
+ case "id":
+ result.Id = attributeValue;
+ break;
+ case "src":
+ result.Source = attributeValue;
+ break;
+ }
+ }
+ if (String.IsNullOrWhiteSpace(result.Source))
+ {
+ throw new Exception("Incorrect EPUB navigation content: content source is missing.");
+ }
+ return result;
+ }
+
+ private static EpubNavigationPageList ReadNavigationPageList(XElement navigationPageListNode)
+ {
+ EpubNavigationPageList result = new EpubNavigationPageList();
+ foreach (XElement pageTargetNode in navigationPageListNode.Elements())
+ {
+ if (String.Compare(pageTargetNode.Name.LocalName, "pageTarget", StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ EpubNavigationPageTarget pageTarget = ReadNavigationPageTarget(pageTargetNode);
+ result.Add(pageTarget);
+ }
+ }
+ return result;
+ }
+
+ private static EpubNavigationPageTarget ReadNavigationPageTarget(XElement navigationPageTargetNode)
+ {
+ EpubNavigationPageTarget result = new EpubNavigationPageTarget();
+ foreach (XAttribute navigationPageTargetNodeAttribute in navigationPageTargetNode.Attributes())
+ {
+ string attributeValue = navigationPageTargetNodeAttribute.Value;
+ switch (navigationPageTargetNodeAttribute.Name.LocalName.ToLowerInvariant())
+ {
+ case "id":
+ result.Id = attributeValue;
+ break;
+ case "value":
+ result.Value = attributeValue;
+ break;
+ case "type":
+ EpubNavigationPageTargetType type;
+ if (!Enum.TryParse(attributeValue, out type))
+ {
+ throw new Exception(String.Format("Incorrect EPUB navigation page target: {0} is incorrect value for page target type.", attributeValue));
+ }
+ result.Type = type;
+ break;
+ case "class":
+ result.Class = attributeValue;
+ break;
+ case "playOrder":
+ result.PlayOrder = attributeValue;
+ break;
+ }
+ }
+ if (result.Type == default(EpubNavigationPageTargetType))
+ {
+ throw new Exception("Incorrect EPUB navigation page target: page target type is missing.");
+ }
+ foreach (XElement navigationPageTargetChildNode in navigationPageTargetNode.Elements())
+ switch (navigationPageTargetChildNode.Name.LocalName.ToLowerInvariant())
+ {
+ case "navlabel":
+ EpubNavigationLabel navigationLabel = ReadNavigationLabel(navigationPageTargetChildNode);
+ result.NavigationLabels.Add(navigationLabel);
+ break;
+ case "content":
+ EpubNavigationContent content = ReadNavigationContent(navigationPageTargetChildNode);
+ result.Content = content;
+ break;
+ }
+ if (!result.NavigationLabels.Any())
+ {
+ throw new Exception("Incorrect EPUB navigation page target: at least one navLabel element is required.");
+ }
+ return result;
+ }
+
+ private static EpubNavigationList ReadNavigationList(XElement navigationListNode)
+ {
+ EpubNavigationList result = new EpubNavigationList();
+ foreach (XAttribute navigationListNodeAttribute in navigationListNode.Attributes())
+ {
+ string attributeValue = navigationListNodeAttribute.Value;
+ switch (navigationListNodeAttribute.Name.LocalName.ToLowerInvariant())
+ {
+ case "id":
+ result.Id = attributeValue;
+ break;
+ case "class":
+ result.Class = attributeValue;
+ break;
+ }
+ }
+ foreach (XElement navigationListChildNode in navigationListNode.Elements())
+ {
+ switch (navigationListChildNode.Name.LocalName.ToLowerInvariant())
+ {
+ case "navlabel":
+ EpubNavigationLabel navigationLabel = ReadNavigationLabel(navigationListChildNode);
+ result.NavigationLabels.Add(navigationLabel);
+ break;
+ case "navTarget":
+ EpubNavigationTarget navigationTarget = ReadNavigationTarget(navigationListChildNode);
+ result.NavigationTargets.Add(navigationTarget);
+ break;
+ }
+ }
+ if (!result.NavigationLabels.Any())
+ {
+ throw new Exception("Incorrect EPUB navigation page target: at least one navLabel element is required.");
+ }
+ return result;
+ }
+
+ private static EpubNavigationTarget ReadNavigationTarget(XElement navigationTargetNode)
+ {
+ EpubNavigationTarget result = new EpubNavigationTarget();
+ foreach (XAttribute navigationPageTargetNodeAttribute in navigationTargetNode.Attributes())
+ {
+ string attributeValue = navigationPageTargetNodeAttribute.Value;
+ switch (navigationPageTargetNodeAttribute.Name.LocalName.ToLowerInvariant())
+ {
+ case "id":
+ result.Id = attributeValue;
+ break;
+ case "value":
+ result.Value = attributeValue;
+ break;
+ case "class":
+ result.Class = attributeValue;
+ break;
+ case "playOrder":
+ result.PlayOrder = attributeValue;
+ break;
+ }
+ }
+ if (String.IsNullOrWhiteSpace(result.Id))
+ {
+ throw new Exception("Incorrect EPUB navigation target: navigation target ID is missing.");
+ }
+ foreach (XElement navigationTargetChildNode in navigationTargetNode.Elements())
+ {
+ switch (navigationTargetChildNode.Name.LocalName.ToLowerInvariant())
+ {
+ case "navlabel":
+ EpubNavigationLabel navigationLabel = ReadNavigationLabel(navigationTargetChildNode);
+ result.NavigationLabels.Add(navigationLabel);
+ break;
+ case "content":
+ EpubNavigationContent content = ReadNavigationContent(navigationTargetChildNode);
+ result.Content = content;
+ break;
+ }
+ }
+ if (!result.NavigationLabels.Any())
+ {
+ throw new Exception("Incorrect EPUB navigation target: at least one navLabel element is required.");
+ }
+ return result;
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Readers/PackageReader.cs b/QuickLook.Plugin/VersOne.Epub/Readers/PackageReader.cs
new file mode 100644
index 0000000..230d9fb
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Readers/PackageReader.cs
@@ -0,0 +1,399 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using VersOne.Epub.Schema;
+
+namespace VersOne.Epub.Internal
+{
+ internal static class PackageReader
+ {
+ public static async Task ReadPackageAsync(ZipArchive epubArchive, string rootFilePath)
+ {
+ ZipArchiveEntry rootFileEntry = epubArchive.GetEntry(rootFilePath);
+ if (rootFileEntry == null)
+ {
+ throw new Exception("EPUB parsing error: root file not found in archive.");
+ }
+ XDocument containerDocument;
+ using (Stream containerStream = rootFileEntry.Open())
+ {
+ containerDocument = await XmlUtils.LoadDocumentAsync(containerStream).ConfigureAwait(false);
+ }
+ XNamespace opfNamespace = "http://www.idpf.org/2007/opf";
+ XElement packageNode = containerDocument.Element(opfNamespace + "package");
+ EpubPackage result = new EpubPackage();
+ string epubVersionValue = packageNode.Attribute("version").Value;
+ if (epubVersionValue == "2.0")
+ {
+ result.EpubVersion = EpubVersion.EPUB_2;
+ }
+ else if (epubVersionValue == "3.0")
+ {
+ result.EpubVersion = EpubVersion.EPUB_3;
+ }
+ else
+ {
+ throw new Exception(String.Format("Unsupported EPUB version: {0}.", epubVersionValue));
+ }
+ XElement metadataNode = packageNode.Element(opfNamespace + "metadata");
+ if (metadataNode == null)
+ {
+ throw new Exception("EPUB parsing error: metadata not found in the package.");
+ }
+ EpubMetadata metadata = ReadMetadata(metadataNode, result.EpubVersion);
+ result.Metadata = metadata;
+ XElement manifestNode = packageNode.Element(opfNamespace + "manifest");
+ if (manifestNode == null)
+ {
+ throw new Exception("EPUB parsing error: manifest not found in the package.");
+ }
+ EpubManifest manifest = ReadManifest(manifestNode);
+ result.Manifest = manifest;
+ XElement spineNode = packageNode.Element(opfNamespace + "spine");
+ if (spineNode == null)
+ {
+ throw new Exception("EPUB parsing error: spine not found in the package.");
+ }
+ EpubSpine spine = ReadSpine(spineNode);
+ result.Spine = spine;
+ XElement guideNode = packageNode.Element(opfNamespace + "guide");
+ if (guideNode != null)
+ {
+ EpubGuide guide = ReadGuide(guideNode);
+ result.Guide = guide;
+ }
+ return result;
+ }
+
+ private static EpubMetadata ReadMetadata(XElement metadataNode, EpubVersion epubVersion)
+ {
+ EpubMetadata result = new EpubMetadata
+ {
+ Titles = new List(),
+ Creators = new List(),
+ Subjects = new List(),
+ Publishers = new List(),
+ Contributors = new List(),
+ Dates = new List(),
+ Types = new List(),
+ Formats = new List(),
+ Identifiers = new List(),
+ Sources = new List(),
+ Languages = new List(),
+ Relations = new List(),
+ Coverages = new List(),
+ Rights = new List(),
+ MetaItems = new List()
+ };
+ foreach (XElement metadataItemNode in metadataNode.Elements())
+ {
+ string innerText = metadataItemNode.Value;
+ switch (metadataItemNode.Name.LocalName.ToLowerInvariant())
+ {
+ case "title":
+ result.Titles.Add(innerText);
+ break;
+ case "creator":
+ EpubMetadataCreator creator = ReadMetadataCreator(metadataItemNode);
+ result.Creators.Add(creator);
+ break;
+ case "subject":
+ result.Subjects.Add(innerText);
+ break;
+ case "description":
+ result.Description = innerText;
+ break;
+ case "publisher":
+ result.Publishers.Add(innerText);
+ break;
+ case "contributor":
+ EpubMetadataContributor contributor = ReadMetadataContributor(metadataItemNode);
+ result.Contributors.Add(contributor);
+ break;
+ case "date":
+ EpubMetadataDate date = ReadMetadataDate(metadataItemNode);
+ result.Dates.Add(date);
+ break;
+ case "type":
+ result.Types.Add(innerText);
+ break;
+ case "format":
+ result.Formats.Add(innerText);
+ break;
+ case "identifier":
+ EpubMetadataIdentifier identifier = ReadMetadataIdentifier(metadataItemNode);
+ result.Identifiers.Add(identifier);
+ break;
+ case "source":
+ result.Sources.Add(innerText);
+ break;
+ case "language":
+ result.Languages.Add(innerText);
+ break;
+ case "relation":
+ result.Relations.Add(innerText);
+ break;
+ case "coverage":
+ result.Coverages.Add(innerText);
+ break;
+ case "rights":
+ result.Rights.Add(innerText);
+ break;
+ case "meta":
+ if (epubVersion == EpubVersion.EPUB_2)
+ {
+ EpubMetadataMeta meta = ReadMetadataMetaVersion2(metadataItemNode);
+ result.MetaItems.Add(meta);
+ }
+ else if (epubVersion == EpubVersion.EPUB_3)
+ {
+ EpubMetadataMeta meta = ReadMetadataMetaVersion3(metadataItemNode);
+ result.MetaItems.Add(meta);
+ }
+ break;
+ }
+ }
+ return result;
+ }
+
+ private static EpubMetadataCreator ReadMetadataCreator(XElement metadataCreatorNode)
+ {
+ EpubMetadataCreator result = new EpubMetadataCreator();
+ foreach (XAttribute metadataCreatorNodeAttribute in metadataCreatorNode.Attributes())
+ {
+ string attributeValue = metadataCreatorNodeAttribute.Value;
+ switch (metadataCreatorNodeAttribute.Name.LocalName.ToLowerInvariant())
+ {
+ case "role":
+ result.Role = attributeValue;
+ break;
+ case "file-as":
+ result.FileAs = attributeValue;
+ break;
+ }
+ }
+ result.Creator = metadataCreatorNode.Value;
+ return result;
+ }
+
+ private static EpubMetadataContributor ReadMetadataContributor(XElement metadataContributorNode)
+ {
+ EpubMetadataContributor result = new EpubMetadataContributor();
+ foreach (XAttribute metadataContributorNodeAttribute in metadataContributorNode.Attributes())
+ {
+ string attributeValue = metadataContributorNodeAttribute.Value;
+ switch (metadataContributorNodeAttribute.Name.LocalName.ToLowerInvariant())
+ {
+ case "role":
+ result.Role = attributeValue;
+ break;
+ case "file-as":
+ result.FileAs = attributeValue;
+ break;
+ }
+ }
+ result.Contributor = metadataContributorNode.Value;
+ return result;
+ }
+
+ private static EpubMetadataDate ReadMetadataDate(XElement metadataDateNode)
+ {
+ EpubMetadataDate result = new EpubMetadataDate();
+ XAttribute eventAttribute = metadataDateNode.Attribute(metadataDateNode.Name.Namespace + "event");
+ if (eventAttribute != null)
+ {
+ result.Event = eventAttribute.Value;
+ }
+ result.Date = metadataDateNode.Value;
+ return result;
+ }
+
+ private static EpubMetadataIdentifier ReadMetadataIdentifier(XElement metadataIdentifierNode)
+ {
+ EpubMetadataIdentifier result = new EpubMetadataIdentifier();
+ foreach (XAttribute metadataIdentifierNodeAttribute in metadataIdentifierNode.Attributes())
+ {
+ string attributeValue = metadataIdentifierNodeAttribute.Value;
+ switch (metadataIdentifierNodeAttribute.Name.LocalName.ToLowerInvariant())
+ {
+ case "id":
+ result.Id = attributeValue;
+ break;
+ case "opf:scheme":
+ result.Scheme = attributeValue;
+ break;
+ }
+ }
+ result.Identifier = metadataIdentifierNode.Value;
+ return result;
+ }
+
+ private static EpubMetadataMeta ReadMetadataMetaVersion2(XElement metadataMetaNode)
+ {
+ EpubMetadataMeta result = new EpubMetadataMeta();
+ foreach (XAttribute metadataMetaNodeAttribute in metadataMetaNode.Attributes())
+ {
+ string attributeValue = metadataMetaNodeAttribute.Value;
+ switch (metadataMetaNodeAttribute.Name.LocalName.ToLowerInvariant())
+ {
+ case "name":
+ result.Name = attributeValue;
+ break;
+ case "content":
+ result.Content = attributeValue;
+ break;
+ }
+ }
+ return result;
+ }
+
+ private static EpubMetadataMeta ReadMetadataMetaVersion3(XElement metadataMetaNode)
+ {
+ EpubMetadataMeta result = new EpubMetadataMeta();
+ foreach (XAttribute metadataMetaNodeAttribute in metadataMetaNode.Attributes())
+ {
+ string attributeValue = metadataMetaNodeAttribute.Value;
+ switch (metadataMetaNodeAttribute.Name.LocalName.ToLowerInvariant())
+ {
+ case "id":
+ result.Id = attributeValue;
+ break;
+ case "refines":
+ result.Refines = attributeValue;
+ break;
+ case "property":
+ result.Property = attributeValue;
+ break;
+ case "scheme":
+ result.Scheme = attributeValue;
+ break;
+ }
+ }
+ result.Content = metadataMetaNode.Value;
+ return result;
+ }
+
+ private static EpubManifest ReadManifest(XElement manifestNode)
+ {
+ EpubManifest result = new EpubManifest();
+ foreach (XElement manifestItemNode in manifestNode.Elements())
+ {
+ if (String.Compare(manifestItemNode.Name.LocalName, "item", StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ EpubManifestItem manifestItem = new EpubManifestItem();
+ foreach (XAttribute manifestItemNodeAttribute in manifestItemNode.Attributes())
+ {
+ string attributeValue = manifestItemNodeAttribute.Value;
+ switch (manifestItemNodeAttribute.Name.LocalName.ToLowerInvariant())
+ {
+ case "id":
+ manifestItem.Id = attributeValue;
+ break;
+ case "href":
+ manifestItem.Href = Uri.UnescapeDataString(attributeValue);
+ break;
+ case "media-type":
+ manifestItem.MediaType = attributeValue;
+ break;
+ case "required-namespace":
+ manifestItem.RequiredNamespace = attributeValue;
+ break;
+ case "required-modules":
+ manifestItem.RequiredModules = attributeValue;
+ break;
+ case "fallback":
+ manifestItem.Fallback = attributeValue;
+ break;
+ case "fallback-style":
+ manifestItem.FallbackStyle = attributeValue;
+ break;
+ }
+ }
+ if (String.IsNullOrWhiteSpace(manifestItem.Id))
+ {
+ throw new Exception("Incorrect EPUB manifest: item ID is missing");
+ }
+ if (String.IsNullOrWhiteSpace(manifestItem.Href))
+ {
+ throw new Exception("Incorrect EPUB manifest: item href is missing");
+ }
+ if (String.IsNullOrWhiteSpace(manifestItem.MediaType))
+ {
+ throw new Exception("Incorrect EPUB manifest: item media type is missing");
+ }
+ result.Add(manifestItem);
+ }
+ }
+ return result;
+ }
+
+ private static EpubSpine ReadSpine(XElement spineNode)
+ {
+ EpubSpine result = new EpubSpine();
+ XAttribute tocAttribute = spineNode.Attribute("toc");
+ if (tocAttribute == null || String.IsNullOrWhiteSpace(tocAttribute.Value))
+ {
+ throw new Exception("Incorrect EPUB spine: TOC is missing");
+ }
+ result.Toc = tocAttribute.Value;
+ foreach (XElement spineItemNode in spineNode.Elements())
+ {
+ if (String.Compare(spineItemNode.Name.LocalName, "itemref", StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ EpubSpineItemRef spineItemRef = new EpubSpineItemRef();
+ XAttribute idRefAttribute = spineItemNode.Attribute("idref");
+ if (idRefAttribute == null || String.IsNullOrWhiteSpace(idRefAttribute.Value))
+ {
+ throw new Exception("Incorrect EPUB spine: item ID ref is missing");
+ }
+ spineItemRef.IdRef = idRefAttribute.Value;
+ XAttribute linearAttribute = spineItemNode.Attribute("linear");
+ spineItemRef.IsLinear = linearAttribute == null || String.Compare(linearAttribute.Value, "no", StringComparison.OrdinalIgnoreCase) != 0;
+ result.Add(spineItemRef);
+ }
+ }
+ return result;
+ }
+
+ private static EpubGuide ReadGuide(XElement guideNode)
+ {
+ EpubGuide result = new EpubGuide();
+ foreach (XElement guideReferenceNode in guideNode.Elements())
+ {
+ if (String.Compare(guideReferenceNode.Name.LocalName, "reference", StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ EpubGuideReference guideReference = new EpubGuideReference();
+ foreach (XAttribute guideReferenceNodeAttribute in guideReferenceNode.Attributes())
+ {
+ string attributeValue = guideReferenceNodeAttribute.Value;
+ switch (guideReferenceNodeAttribute.Name.LocalName.ToLowerInvariant())
+ {
+ case "type":
+ guideReference.Type = attributeValue;
+ break;
+ case "title":
+ guideReference.Title = attributeValue;
+ break;
+ case "href":
+ guideReference.Href = Uri.UnescapeDataString(attributeValue);
+ break;
+ }
+ }
+ if (String.IsNullOrWhiteSpace(guideReference.Type))
+ {
+ throw new Exception("Incorrect EPUB guide: item type is missing");
+ }
+ if (String.IsNullOrWhiteSpace(guideReference.Href))
+ {
+ throw new Exception("Incorrect EPUB guide: item href is missing");
+ }
+ result.Add(guideReference);
+ }
+ }
+ return result;
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Readers/RootFilePathReader.cs b/QuickLook.Plugin/VersOne.Epub/Readers/RootFilePathReader.cs
new file mode 100644
index 0000000..63c3487
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Readers/RootFilePathReader.cs
@@ -0,0 +1,33 @@
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+
+namespace VersOne.Epub.Internal
+{
+ internal static class RootFilePathReader
+ {
+ public static async Task GetRootFilePathAsync(ZipArchive epubArchive)
+ {
+ const string EPUB_CONTAINER_FILE_PATH = "META-INF/container.xml";
+ ZipArchiveEntry containerFileEntry = epubArchive.GetEntry(EPUB_CONTAINER_FILE_PATH);
+ if (containerFileEntry == null)
+ {
+ throw new Exception(String.Format("EPUB parsing error: {0} file not found in archive.", EPUB_CONTAINER_FILE_PATH));
+ }
+ XDocument containerDocument;
+ using (Stream containerStream = containerFileEntry.Open())
+ {
+ containerDocument = await XmlUtils.LoadDocumentAsync(containerStream).ConfigureAwait(false);
+ }
+ XNamespace cnsNamespace = "urn:oasis:names:tc:opendocument:xmlns:container";
+ XAttribute fullPathAttribute = containerDocument.Element(cnsNamespace + "container")?.Element(cnsNamespace + "rootfiles")?.Element(cnsNamespace + "rootfile")?.Attribute("full-path");
+ if (fullPathAttribute == null)
+ {
+ throw new Exception("EPUB parsing error: root file path not found in the EPUB container.");
+ }
+ return fullPathAttribute.Value;
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Readers/SchemaReader.cs b/QuickLook.Plugin/VersOne.Epub/Readers/SchemaReader.cs
new file mode 100644
index 0000000..a4a00e9
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Readers/SchemaReader.cs
@@ -0,0 +1,22 @@
+using System.IO.Compression;
+using System.Threading.Tasks;
+using VersOne.Epub.Schema;
+
+namespace VersOne.Epub.Internal
+{
+ internal static class SchemaReader
+ {
+ public static async Task ReadSchemaAsync(ZipArchive epubArchive)
+ {
+ EpubSchema result = new EpubSchema();
+ string rootFilePath = await RootFilePathReader.GetRootFilePathAsync(epubArchive).ConfigureAwait(false);
+ string contentDirectoryPath = ZipPathUtils.GetDirectoryPath(rootFilePath);
+ result.ContentDirectoryPath = contentDirectoryPath;
+ EpubPackage package = await PackageReader.ReadPackageAsync(epubArchive, rootFilePath).ConfigureAwait(false);
+ result.Package = package;
+ EpubNavigation navigation = await NavigationReader.ReadNavigationAsync(epubArchive, contentDirectoryPath, package).ConfigureAwait(false);
+ result.Navigation = navigation;
+ return result;
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubBookRef.cs b/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubBookRef.cs
new file mode 100644
index 0000000..95a6d88
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubBookRef.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.IO.Compression;
+using System.Threading.Tasks;
+using VersOne.Epub.Internal;
+
+namespace VersOne.Epub
+{
+ public class EpubBookRef : IDisposable
+ {
+ private bool isDisposed;
+
+ public EpubBookRef(ZipArchive epubArchive)
+ {
+ EpubArchive = epubArchive;
+ isDisposed = false;
+ }
+
+ ~EpubBookRef()
+ {
+ Dispose(false);
+ }
+
+ public string FilePath { get; set; }
+ public string Title { get; set; }
+ public string Author { get; set; }
+ public List AuthorList { get; set; }
+ public EpubSchema Schema { get; set; }
+ public EpubContentRef Content { get; set; }
+
+ internal ZipArchive EpubArchive { get; private set; }
+
+ public byte[] ReadCover()
+ {
+ return ReadCoverAsync().Result;
+ }
+
+ public async Task ReadCoverAsync()
+ {
+ return await BookCoverReader.ReadBookCoverAsync(this).ConfigureAwait(false);
+ }
+
+ public List GetChapters()
+ {
+ return GetChaptersAsync().Result;
+ }
+
+ public async Task> GetChaptersAsync()
+ {
+ return await Task.Run(() => ChapterReader.GetChapters(this)).ConfigureAwait(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!isDisposed)
+ {
+ if (disposing)
+ {
+ EpubArchive?.Dispose();
+ }
+ isDisposed = true;
+ }
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubByteContentFileRef.cs b/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubByteContentFileRef.cs
new file mode 100644
index 0000000..ed13eb4
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubByteContentFileRef.cs
@@ -0,0 +1,22 @@
+using System.Threading.Tasks;
+
+namespace VersOne.Epub
+{
+ public class EpubByteContentFileRef : EpubContentFileRef
+ {
+ public EpubByteContentFileRef(EpubBookRef epubBookRef)
+ : base(epubBookRef)
+ {
+ }
+
+ public byte[] ReadContent()
+ {
+ return ReadContentAsBytes();
+ }
+
+ public Task ReadContentAsync()
+ {
+ return ReadContentAsBytesAsync();
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubChapterRef.cs b/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubChapterRef.cs
new file mode 100644
index 0000000..1d06295
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubChapterRef.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace VersOne.Epub
+{
+ public class EpubChapterRef
+ {
+ private readonly EpubTextContentFileRef epubTextContentFileRef;
+
+ public EpubChapterRef(EpubTextContentFileRef epubTextContentFileRef)
+ {
+ this.epubTextContentFileRef = epubTextContentFileRef;
+ }
+
+ public string Title { get; set; }
+ public string ContentFileName { get; set; }
+ public string Anchor { get; set; }
+ public List SubChapters { get; set; }
+ public EpubChapterRef Parent { get; set; }
+
+ public string ReadHtmlContent()
+ {
+ return ReadHtmlContentAsync().Result;
+ }
+
+ public Task ReadHtmlContentAsync()
+ {
+ return epubTextContentFileRef.ReadContentAsTextAsync();
+ }
+
+ public override string ToString()
+ {
+ return String.Format("Title: {0}, Subchapter count: {1}", Title, SubChapters.Count);
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubContentFileRef.cs b/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubContentFileRef.cs
new file mode 100644
index 0000000..1e26637
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubContentFileRef.cs
@@ -0,0 +1,83 @@
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Threading.Tasks;
+using VersOne.Epub.Internal;
+
+namespace VersOne.Epub
+{
+ public abstract class EpubContentFileRef
+ {
+ private readonly EpubBookRef epubBookRef;
+
+ public EpubContentFileRef(EpubBookRef epubBookRef)
+ {
+ this.epubBookRef = epubBookRef;
+ }
+
+ public string FileName { get; set; }
+ public EpubContentType ContentType { get; set; }
+ public string ContentMimeType { get; set; }
+
+ public byte[] ReadContentAsBytes()
+ {
+ return ReadContentAsBytesAsync().Result;
+ }
+
+ public async Task ReadContentAsBytesAsync()
+ {
+ ZipArchiveEntry contentFileEntry = GetContentFileEntry();
+ byte[] content = new byte[(int)contentFileEntry.Length];
+ using (Stream contentStream = OpenContentStream(contentFileEntry))
+ using (MemoryStream memoryStream = new MemoryStream(content))
+ {
+ await contentStream.CopyToAsync(memoryStream).ConfigureAwait(false);
+ }
+ return content;
+ }
+
+ public string ReadContentAsText()
+ {
+ return ReadContentAsTextAsync().Result;
+ }
+
+ public async Task ReadContentAsTextAsync()
+ {
+ using (Stream contentStream = GetContentStream())
+ using (StreamReader streamReader = new StreamReader(contentStream))
+ {
+ return await streamReader.ReadToEndAsync().ConfigureAwait(false);
+ }
+ }
+
+ public Stream GetContentStream()
+ {
+ return OpenContentStream(GetContentFileEntry());
+ }
+
+ private ZipArchiveEntry GetContentFileEntry()
+ {
+ string contentFilePath = ZipPathUtils.Combine(epubBookRef.Schema.ContentDirectoryPath, FileName);
+ ZipArchiveEntry contentFileEntry = epubBookRef.EpubArchive.GetEntry(contentFilePath);
+ if (contentFileEntry == null)
+ {
+ throw new Exception(String.Format("EPUB parsing error: file {0} not found in archive.", contentFilePath));
+ }
+ if (contentFileEntry.Length > Int32.MaxValue)
+ {
+ throw new Exception(String.Format("EPUB parsing error: file {0} is bigger than 2 Gb.", contentFilePath));
+ }
+ return contentFileEntry;
+ }
+
+ private Stream OpenContentStream(ZipArchiveEntry contentFileEntry)
+ {
+ Stream contentStream = contentFileEntry.Open();
+ if (contentStream == null)
+ {
+ throw new Exception(String.Format("Incorrect EPUB file: content file \"{0}\" specified in manifest is not found.", FileName));
+ }
+ return contentStream;
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubContentRef.cs b/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubContentRef.cs
new file mode 100644
index 0000000..0ee82a1
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubContentRef.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+
+namespace VersOne.Epub
+{
+ public class EpubContentRef
+ {
+ public Dictionary Html { get; set; }
+ public Dictionary Css { get; set; }
+ public Dictionary Images { get; set; }
+ public Dictionary Fonts { get; set; }
+ public Dictionary AllFiles { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubTextContentFileRef.cs b/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubTextContentFileRef.cs
new file mode 100644
index 0000000..4f6f269
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/RefEntities/EpubTextContentFileRef.cs
@@ -0,0 +1,22 @@
+using System.Threading.Tasks;
+
+namespace VersOne.Epub
+{
+ public class EpubTextContentFileRef : EpubContentFileRef
+ {
+ public EpubTextContentFileRef(EpubBookRef epubBookRef)
+ : base(epubBookRef)
+ {
+ }
+
+ public string ReadContent()
+ {
+ return ReadContentAsText();
+ }
+
+ public Task ReadContentAsync()
+ {
+ return ReadContentAsTextAsync();
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigation.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigation.cs
new file mode 100644
index 0000000..cd72147
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigation.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubNavigation
+ {
+ public EpubNavigationHead Head { get; set; }
+ public EpubNavigationDocTitle DocTitle { get; set; }
+ public List DocAuthors { get; set; }
+ public EpubNavigationMap NavMap { get; set; }
+ public EpubNavigationPageList PageList { get; set; }
+ public List NavLists { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationContent.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationContent.cs
new file mode 100644
index 0000000..d1fab6f
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationContent.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubNavigationContent
+ {
+ public string Id { get; set; }
+ public string Source { get; set; }
+
+ public override string ToString()
+ {
+ return String.Concat("Source: " + Source);
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationDocAuthor.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationDocAuthor.cs
new file mode 100644
index 0000000..21b1fb4
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationDocAuthor.cs
@@ -0,0 +1,8 @@
+using System.Collections.Generic;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubNavigationDocAuthor : List
+ {
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationDocTitle.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationDocTitle.cs
new file mode 100644
index 0000000..8a48fa0
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationDocTitle.cs
@@ -0,0 +1,8 @@
+using System.Collections.Generic;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubNavigationDocTitle : List
+ {
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationHead.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationHead.cs
new file mode 100644
index 0000000..fb00035
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationHead.cs
@@ -0,0 +1,8 @@
+using System.Collections.Generic;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubNavigationHead : List
+ {
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationHeadMeta.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationHeadMeta.cs
new file mode 100644
index 0000000..d6637b4
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationHeadMeta.cs
@@ -0,0 +1,9 @@
+namespace VersOne.Epub.Schema
+{
+ public class EpubNavigationHeadMeta
+ {
+ public string Name { get; set; }
+ public string Content { get; set; }
+ public string Scheme { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationLabel.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationLabel.cs
new file mode 100644
index 0000000..97e5802
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationLabel.cs
@@ -0,0 +1,12 @@
+namespace VersOne.Epub.Schema
+{
+ public class EpubNavigationLabel
+ {
+ public string Text { get; set; }
+
+ public override string ToString()
+ {
+ return Text;
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationList.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationList.cs
new file mode 100644
index 0000000..325ce50
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationList.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubNavigationList
+ {
+ public string Id { get; set; }
+ public string Class { get; set; }
+ public List NavigationLabels { get; set; }
+ public List NavigationTargets { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationMap.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationMap.cs
new file mode 100644
index 0000000..dc01371
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationMap.cs
@@ -0,0 +1,8 @@
+using System.Collections.Generic;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubNavigationMap : List
+ {
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationPageList.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationPageList.cs
new file mode 100644
index 0000000..8aae30e
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationPageList.cs
@@ -0,0 +1,8 @@
+using System.Collections.Generic;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubNavigationPageList : List
+ {
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationPageTarget.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationPageTarget.cs
new file mode 100644
index 0000000..bb0b395
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationPageTarget.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubNavigationPageTarget
+ {
+ public string Id { get; set; }
+ public string Value { get; set; }
+ public EpubNavigationPageTargetType Type { get; set; }
+ public string Class { get; set; }
+ public string PlayOrder { get; set; }
+ public List NavigationLabels { get; set; }
+ public EpubNavigationContent Content { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationPageTargetType.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationPageTargetType.cs
new file mode 100644
index 0000000..a930f71
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationPageTargetType.cs
@@ -0,0 +1,9 @@
+namespace VersOne.Epub.Schema
+{
+ public enum EpubNavigationPageTargetType
+ {
+ FRONT = 1,
+ NORMAL,
+ SPECIAL
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationPoint.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationPoint.cs
new file mode 100644
index 0000000..084e8f6
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationPoint.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubNavigationPoint
+ {
+ public string Id { get; set; }
+ public string Class { get; set; }
+ public string PlayOrder { get; set; }
+ public List NavigationLabels { get; set; }
+ public EpubNavigationContent Content { get; set; }
+ public List ChildNavigationPoints { get; set; }
+
+ public override string ToString()
+ {
+ return String.Format("Id: {0}, Content.Source: {1}", Id, Content.Source);
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationTarget.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationTarget.cs
new file mode 100644
index 0000000..ea5ac2a
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Navigation/EpubNavigationTarget.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubNavigationTarget
+ {
+ public string Id { get; set; }
+ public string Class { get; set; }
+ public string Value { get; set; }
+ public string PlayOrder { get; set; }
+ public List NavigationLabels { get; set; }
+ public EpubNavigationContent Content { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubGuide.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubGuide.cs
new file mode 100644
index 0000000..8bb0f6e
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubGuide.cs
@@ -0,0 +1,8 @@
+using System.Collections.Generic;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubGuide : List
+ {
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubGuideReference.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubGuideReference.cs
new file mode 100644
index 0000000..69f488b
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubGuideReference.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubGuideReference
+ {
+ public string Type { get; set; }
+ public string Title { get; set; }
+ public string Href { get; set; }
+
+ public override string ToString()
+ {
+ return String.Format("Type: {0}, Href: {1}", Type, Href);
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubManifest.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubManifest.cs
new file mode 100644
index 0000000..d1464aa
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubManifest.cs
@@ -0,0 +1,8 @@
+using System.Collections.Generic;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubManifest : List
+ {
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubManifestItem.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubManifestItem.cs
new file mode 100644
index 0000000..4181589
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubManifestItem.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubManifestItem
+ {
+ public string Id { get; set; }
+ public string Href { get; set; }
+ public string MediaType { get; set; }
+ public string RequiredNamespace { get; set; }
+ public string RequiredModules { get; set; }
+ public string Fallback { get; set; }
+ public string FallbackStyle { get; set; }
+
+ public override string ToString()
+ {
+ return String.Format("Id: {0}, Href = {1}, MediaType = {2}", Id, Href, MediaType);
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadata.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadata.cs
new file mode 100644
index 0000000..e27f77a
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadata.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubMetadata
+ {
+ public List Titles { get; set; }
+ public List Creators { get; set; }
+ public List Subjects { get; set; }
+ public string Description { get; set; }
+ public List Publishers { get; set; }
+ public List Contributors { get; set; }
+ public List Dates { get; set; }
+ public List Types { get; set; }
+ public List Formats { get; set; }
+ public List Identifiers { get; set; }
+ public List Sources { get; set; }
+ public List Languages { get; set; }
+ public List Relations { get; set; }
+ public List Coverages { get; set; }
+ public List Rights { get; set; }
+ public List MetaItems { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadataContributor.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadataContributor.cs
new file mode 100644
index 0000000..8a89819
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadataContributor.cs
@@ -0,0 +1,9 @@
+namespace VersOne.Epub.Schema
+{
+ public class EpubMetadataContributor
+ {
+ public string Contributor { get; set; }
+ public string FileAs { get; set; }
+ public string Role { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadataCreator.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadataCreator.cs
new file mode 100644
index 0000000..015b840
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadataCreator.cs
@@ -0,0 +1,9 @@
+namespace VersOne.Epub.Schema
+{
+ public class EpubMetadataCreator
+ {
+ public string Creator { get; set; }
+ public string FileAs { get; set; }
+ public string Role { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadataDate.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadataDate.cs
new file mode 100644
index 0000000..7e882ec
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadataDate.cs
@@ -0,0 +1,8 @@
+namespace VersOne.Epub.Schema
+{
+ public class EpubMetadataDate
+ {
+ public string Date { get; set; }
+ public string Event { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadataIdentifier.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadataIdentifier.cs
new file mode 100644
index 0000000..61d3c2f
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadataIdentifier.cs
@@ -0,0 +1,9 @@
+namespace VersOne.Epub.Schema
+{
+ public class EpubMetadataIdentifier
+ {
+ public string Id { get; set; }
+ public string Scheme { get; set; }
+ public string Identifier { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadataMeta.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadataMeta.cs
new file mode 100644
index 0000000..133199d
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadataMeta.cs
@@ -0,0 +1,12 @@
+namespace VersOne.Epub.Schema
+{
+ public class EpubMetadataMeta
+ {
+ public string Name { get; set; }
+ public string Content { get; set; }
+ public string Id { get; set; }
+ public string Refines { get; set; }
+ public string Property { get; set; }
+ public string Scheme { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubPackage.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubPackage.cs
new file mode 100644
index 0000000..91efedd
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubPackage.cs
@@ -0,0 +1,11 @@
+namespace VersOne.Epub.Schema
+{
+ public class EpubPackage
+ {
+ public EpubVersion EpubVersion { get; set; }
+ public EpubMetadata Metadata { get; set; }
+ public EpubManifest Manifest { get; set; }
+ public EpubSpine Spine { get; set; }
+ public EpubGuide Guide { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubSpine.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubSpine.cs
new file mode 100644
index 0000000..022db95
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubSpine.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubSpine : List
+ {
+ public string Toc { get; set; }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubSpineItemRef.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubSpineItemRef.cs
new file mode 100644
index 0000000..c239f95
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubSpineItemRef.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace VersOne.Epub.Schema
+{
+ public class EpubSpineItemRef
+ {
+ public string IdRef { get; set; }
+ public bool IsLinear { get; set; }
+
+ public override string ToString()
+ {
+ return String.Concat("IdRef: ", IdRef);
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubVersion.cs b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubVersion.cs
new file mode 100644
index 0000000..b208e06
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubVersion.cs
@@ -0,0 +1,8 @@
+namespace VersOne.Epub.Schema
+{
+ public enum EpubVersion
+ {
+ EPUB_2 = 2,
+ EPUB_3
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Utils/XmlUtils.cs b/QuickLook.Plugin/VersOne.Epub/Utils/XmlUtils.cs
new file mode 100644
index 0000000..95b8bcd
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Utils/XmlUtils.cs
@@ -0,0 +1,28 @@
+using System.IO;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+
+namespace VersOne.Epub.Internal
+{
+ internal static class XmlUtils
+ {
+ public static async Task LoadDocumentAsync(Stream stream)
+ {
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
+ memoryStream.Position = 0;
+ XmlReaderSettings xmlReaderSettings = new XmlReaderSettings
+ {
+ DtdProcessing = DtdProcessing.Ignore,
+ Async = true
+ };
+ using (XmlReader xmlReader = XmlReader.Create(memoryStream, xmlReaderSettings))
+ {
+ return await Task.Run(() => XDocument.Load(memoryStream)).ConfigureAwait(false);
+ }
+ }
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/Utils/ZipPathUtils.cs b/QuickLook.Plugin/VersOne.Epub/Utils/ZipPathUtils.cs
new file mode 100644
index 0000000..e295dbb
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/Utils/ZipPathUtils.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace VersOne.Epub.Internal
+{
+ internal static class ZipPathUtils
+ {
+ public static string GetDirectoryPath(string filePath)
+ {
+ int lastSlashIndex = filePath.LastIndexOf('/');
+ if (lastSlashIndex == -1)
+ {
+ return String.Empty;
+ }
+ else
+ {
+ return filePath.Substring(0, lastSlashIndex);
+ }
+ }
+
+ public static string Combine(string directory, string fileName)
+ {
+ if (String.IsNullOrEmpty(directory))
+ {
+ return fileName;
+ }
+ else
+ {
+ return String.Concat(directory, "/", fileName);
+ }
+ }
+ }
+}
diff --git a/QuickLook.Plugin/VersOne.Epub/VersOne.Epub.csproj b/QuickLook.Plugin/VersOne.Epub/VersOne.Epub.csproj
new file mode 100644
index 0000000..63aecd5
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/VersOne.Epub.csproj
@@ -0,0 +1,15 @@
+
+
+ net45;netcoreapp1.0;netstandard1.3
+ vers
+
+
+
+ vers, 2015-2018
+ 2.0.4
+ false
+
+
+
+
+
diff --git a/QuickLook.Plugin/VersOne.Epub/VersOne.Epub.nuspec b/QuickLook.Plugin/VersOne.Epub/VersOne.Epub.nuspec
new file mode 100644
index 0000000..4f84ddc
--- /dev/null
+++ b/QuickLook.Plugin/VersOne.Epub/VersOne.Epub.nuspec
@@ -0,0 +1,31 @@
+
+
+
+ VersOne.Epub
+ 2.0.4
+ EPUB reader
+ .NET library for reading EPUB files
+ vers
+ false
+ https://github.com/vers-one/EpubReader/blob/master/LICENSE
+ https://github.com/vers-one/EpubReader
+ .NET Standard 1.3 support.
+ Unlicense <http://unlicense.org>
+ epub
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/QuickLook.sln b/QuickLook.sln
index 1aad176..1f2852f 100644
--- a/QuickLook.sln
+++ b/QuickLook.sln
@@ -72,6 +72,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickLook.Common", "QuickLo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickLook.Plugin.CameraRawViewer", "QuickLook.Plugin\QuickLook.Plugin.CameraRawViewer\QuickLook.Plugin.CameraRawViewer.csproj", "{BD58F3FC-7601-47BA-AAA1-D8A9D54A33DA}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickLook.Plugin.EpubViewer", "QuickLook.Plugin\QuickLook.Plugin.EpubViewer\QuickLook.Plugin.EpubViewer.csproj", "{260C9E70-0582-471F-BFB5-022CFE7984C8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VersOne.Epub", "QuickLook.Plugin\VersOne.Epub\VersOne.Epub.csproj", "{8BFC120E-7E59-437C-9196-595AB00025E1}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -210,6 +214,22 @@ Global
{BD58F3FC-7601-47BA-AAA1-D8A9D54A33DA}.Release|Any CPU.Build.0 = Release|Any CPU
{BD58F3FC-7601-47BA-AAA1-D8A9D54A33DA}.Release|x86.ActiveCfg = Release|x86
{BD58F3FC-7601-47BA-AAA1-D8A9D54A33DA}.Release|x86.Build.0 = Release|x86
+ {260C9E70-0582-471F-BFB5-022CFE7984C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {260C9E70-0582-471F-BFB5-022CFE7984C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {260C9E70-0582-471F-BFB5-022CFE7984C8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {260C9E70-0582-471F-BFB5-022CFE7984C8}.Debug|x86.Build.0 = Debug|Any CPU
+ {260C9E70-0582-471F-BFB5-022CFE7984C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {260C9E70-0582-471F-BFB5-022CFE7984C8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {260C9E70-0582-471F-BFB5-022CFE7984C8}.Release|x86.ActiveCfg = Release|Any CPU
+ {260C9E70-0582-471F-BFB5-022CFE7984C8}.Release|x86.Build.0 = Release|Any CPU
+ {8BFC120E-7E59-437C-9196-595AB00025E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8BFC120E-7E59-437C-9196-595AB00025E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8BFC120E-7E59-437C-9196-595AB00025E1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8BFC120E-7E59-437C-9196-595AB00025E1}.Debug|x86.Build.0 = Debug|Any CPU
+ {8BFC120E-7E59-437C-9196-595AB00025E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8BFC120E-7E59-437C-9196-595AB00025E1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8BFC120E-7E59-437C-9196-595AB00025E1}.Release|x86.ActiveCfg = Release|Any CPU
+ {8BFC120E-7E59-437C-9196-595AB00025E1}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -229,6 +249,8 @@ Global
{863ECAAC-18D9-4256-A27D-0F308089FB47} = {06EFDBE0-6408-4B37-BCF2-0CF9EBEA2E93}
{45E94893-3076-4A8E-8969-6955B6340739} = {06EFDBE0-6408-4B37-BCF2-0CF9EBEA2E93}
{BD58F3FC-7601-47BA-AAA1-D8A9D54A33DA} = {06EFDBE0-6408-4B37-BCF2-0CF9EBEA2E93}
+ {260C9E70-0582-471F-BFB5-022CFE7984C8} = {06EFDBE0-6408-4B37-BCF2-0CF9EBEA2E93}
+ {8BFC120E-7E59-437C-9196-595AB00025E1} = {06EFDBE0-6408-4B37-BCF2-0CF9EBEA2E93}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D3761C32-8C5F-498A-892B-3B0882994B62}