mirror of
https://github.com/QL-Win/QuickLook.git
synced 2025-09-08 23:00:51 +00:00
Added EPUB plugin
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
<UserControl x:Class="QuickLook.Plugin.EpubViewer.EpubViewerControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:QuickLook.Plugin.EpubViewer"
|
||||
xmlns:htmlViewer="clr-namespace:TheArtOfDev.HtmlRenderer.WPF;assembly=HtmlRenderer.WPF"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="600" d:DesignWidth="800">
|
||||
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/QuickLook.Common;component/Styles/MainWindowStyles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid KeyDown="Grid_KeyDown">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0">
|
||||
<TextBlock FontSize="14"
|
||||
Text="{Binding Chapter, Mode=OneWay}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Padding="4"/>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button VerticalAlignment="Stretch"
|
||||
Margin="0,0,2,0"
|
||||
Content="<"
|
||||
Click="PrevButton_Click"
|
||||
Style="{DynamicResource CaptionTextButtonStyle}"/>
|
||||
<Button VerticalAlignment="Stretch"
|
||||
Content=">"
|
||||
Click="NextButton_Click"
|
||||
Style="{DynamicResource CaptionTextButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<htmlViewer:HtmlPanel x:Name="pagePanel"
|
||||
Grid.Row="1"/>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Logica di interazione per EpubViewerControl.xaml
|
||||
/// </summary>
|
||||
public partial class EpubViewerControl : UserControl, INotifyPropertyChanged
|
||||
{
|
||||
private EpubBookRef epubBook;
|
||||
private List<EpubChapterRef> 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<TheArtOfDev.HtmlRenderer.Core.Entities.HtmlImageLoadEventArgs> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get image by resource key.
|
||||
/// </summary>
|
||||
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<EpubChapterRef> Flatten(List<EpubChapterRef> 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 = "<div>Invalid chapter.</div>";
|
||||
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 = "<div>Invalid chapter.</div>";
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
67
QuickLook.Plugin/QuickLook.Plugin.EpubViewer/Plugin.cs
Normal file
67
QuickLook.Plugin/QuickLook.Plugin.EpubViewer/Plugin.cs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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.*")]
|
@@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{260C9E70-0582-471F-BFB5-022CFE7984C8}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>QuickLook.Plugin.EpubViewer</RootNamespace>
|
||||
<AssemblyName>QuickLook.Plugin.EpubViewer</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>..\..\Build\Debug\QuickLook.Plugin\QuickLook.Plugin.EpubViewer\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="HtmlRenderer, Version=1.5.0.6, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\HtmlRenderer.Core.1.5.0.6\lib\net45\HtmlRenderer.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="HtmlRenderer.WPF, Version=1.5.0.6, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\HtmlRenderer.WPF.1.5.0.6\lib\net45\HtmlRenderer.WPF.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xaml" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="WindowsBase" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\GitVersion.cs">
|
||||
<Link>Properties\GitVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="EpubViewerControl.xaml.cs">
|
||||
<DependentUpon>EpubViewerControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Plugin.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\QuickLook.Common\QuickLook.Common.csproj">
|
||||
<Project>{85fdd6ba-871d-46c8-bd64-f6bb0cb5ea95}</Project>
|
||||
<Name>QuickLook.Common</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\QuickLook.Plugin.HtmlViewer\QuickLook.Plugin.HtmlViewer.csproj">
|
||||
<Project>{ce22a1f3-7f2c-4ec8-bfde-b58d0eb625fc}</Project>
|
||||
<Name>QuickLook.Plugin.HtmlViewer</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\VersOne.Epub\VersOne.Epub.csproj">
|
||||
<Project>{8bfc120e-7e59-437c-9196-595ab00025e1}</Project>
|
||||
<Name>VersOne.Epub</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Include="EpubViewerControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="HtmlRenderer.Core" version="1.5.0.6" targetFramework="net461" />
|
||||
<package id="HtmlRenderer.WPF" version="1.5.0.6" targetFramework="net461" />
|
||||
</packages>
|
16
QuickLook.Plugin/VersOne.Epub/Entities/EpubBook.cs
Normal file
16
QuickLook.Plugin/VersOne.Epub/Entities/EpubBook.cs
Normal file
@@ -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<string> AuthorList { get; set; }
|
||||
public EpubSchema Schema { get; set; }
|
||||
public EpubContent Content { get; set; }
|
||||
public byte[] CoverImage { get; set; }
|
||||
public List<EpubChapter> Chapters { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
namespace VersOne.Epub
|
||||
{
|
||||
public class EpubByteContentFile : EpubContentFile
|
||||
{
|
||||
public byte[] Content { get; set; }
|
||||
}
|
||||
}
|
19
QuickLook.Plugin/VersOne.Epub/Entities/EpubChapter.cs
Normal file
19
QuickLook.Plugin/VersOne.Epub/Entities/EpubChapter.cs
Normal file
@@ -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<EpubChapter> SubChapters { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Format("Title: {0}, Subchapter count: {1}", Title, SubChapters.Count);
|
||||
}
|
||||
}
|
||||
}
|
13
QuickLook.Plugin/VersOne.Epub/Entities/EpubContent.cs
Normal file
13
QuickLook.Plugin/VersOne.Epub/Entities/EpubContent.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VersOne.Epub
|
||||
{
|
||||
public class EpubContent
|
||||
{
|
||||
public Dictionary<string, EpubTextContentFile> Html { get; set; }
|
||||
public Dictionary<string, EpubTextContentFile> Css { get; set; }
|
||||
public Dictionary<string, EpubByteContentFile> Images { get; set; }
|
||||
public Dictionary<string, EpubByteContentFile> Fonts { get; set; }
|
||||
public Dictionary<string, EpubContentFile> AllFiles { get; set; }
|
||||
}
|
||||
}
|
@@ -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; }
|
||||
}
|
||||
}
|
20
QuickLook.Plugin/VersOne.Epub/Entities/EpubContentType.cs
Normal file
20
QuickLook.Plugin/VersOne.Epub/Entities/EpubContentType.cs
Normal file
@@ -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
|
||||
}
|
||||
}
|
11
QuickLook.Plugin/VersOne.Epub/Entities/EpubSchema.cs
Normal file
11
QuickLook.Plugin/VersOne.Epub/Entities/EpubSchema.cs
Normal file
@@ -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; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
namespace VersOne.Epub
|
||||
{
|
||||
public class EpubTextContentFile : EpubContentFile
|
||||
{
|
||||
public string Content { get; set; }
|
||||
}
|
||||
}
|
231
QuickLook.Plugin/VersOne.Epub/EpubReader.cs
Normal file
231
QuickLook.Plugin/VersOne.Epub/EpubReader.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Opens the book synchronously without reading its whole content. Holds the handle to the EPUB file.
|
||||
/// </summary>
|
||||
/// <param name="filePath">path to the EPUB file</param>
|
||||
/// <returns></returns>
|
||||
public static EpubBookRef OpenBook(string filePath)
|
||||
{
|
||||
return OpenBookAsync(filePath).Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the book synchronously without reading its whole content.
|
||||
/// </summary>
|
||||
/// <param name="filePath">path to the EPUB file</param>
|
||||
/// <returns></returns>
|
||||
public static EpubBookRef OpenBook(Stream stream)
|
||||
{
|
||||
return OpenBookAsync(stream).Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the book asynchronously without reading its whole content. Holds the handle to the EPUB file.
|
||||
/// </summary>
|
||||
/// <param name="filePath">path to the EPUB file</param>
|
||||
/// <returns></returns>
|
||||
public static Task<EpubBookRef> OpenBookAsync(string filePath)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException("Specified epub file not found.", filePath);
|
||||
}
|
||||
return OpenBookAsync(GetZipArchive(filePath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the book asynchronously without reading its whole content.
|
||||
/// </summary>
|
||||
/// <param name="filePath">path to the EPUB file</param>
|
||||
/// <returns></returns>
|
||||
public static Task<EpubBookRef> OpenBookAsync(Stream stream)
|
||||
{
|
||||
return OpenBookAsync(GetZipArchive(stream));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the book synchronously and reads all of its content into the memory. Does not hold the handle to the EPUB file.
|
||||
/// </summary>
|
||||
/// <param name="filePath">path to the EPUB file</param>
|
||||
/// <returns></returns>
|
||||
public static EpubBook ReadBook(string filePath)
|
||||
{
|
||||
return ReadBookAsync(filePath).Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the book synchronously and reads all of its content into the memory. Does not hold the handle to the EPUB file.
|
||||
/// </summary>
|
||||
/// <param name="filePath">path to the EPUB file</param>
|
||||
/// <returns></returns>
|
||||
public static EpubBook ReadBook(Stream stream)
|
||||
{
|
||||
return ReadBookAsync(stream).Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the book asynchronously and reads all of its content into the memory. Does not hold the handle to the EPUB file.
|
||||
/// </summary>
|
||||
/// <param name="filePath">path to the EPUB file</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<EpubBook> ReadBookAsync(string filePath)
|
||||
{
|
||||
EpubBookRef epubBookRef = await OpenBookAsync(filePath).ConfigureAwait(false);
|
||||
return await ReadBookAsync(epubBookRef).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the book asynchronously and reads all of its content into the memory.
|
||||
/// </summary>
|
||||
/// <param name="filePath">path to the EPUB file</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<EpubBook> ReadBookAsync(Stream stream)
|
||||
{
|
||||
EpubBookRef epubBookRef = await OpenBookAsync(stream).ConfigureAwait(false);
|
||||
return await ReadBookAsync(epubBookRef).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task<EpubBookRef> 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<EpubBook> 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<EpubChapterRef> 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<EpubContent> 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<string, EpubContentFile>();
|
||||
foreach (KeyValuePair<string, EpubTextContentFile> textContentFile in result.Html.Concat(result.Css))
|
||||
{
|
||||
result.AllFiles.Add(textContentFile.Key, textContentFile.Value);
|
||||
}
|
||||
foreach (KeyValuePair<string, EpubByteContentFile> byteContentFile in result.Images.Concat(result.Fonts))
|
||||
{
|
||||
result.AllFiles.Add(byteContentFile.Key, byteContentFile.Value);
|
||||
}
|
||||
foreach (KeyValuePair<string, EpubContentFileRef> 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<Dictionary<string, EpubTextContentFile>> ReadTextContentFiles(Dictionary<string, EpubTextContentFileRef> textContentFileRefs)
|
||||
{
|
||||
Dictionary<string, EpubTextContentFile> result = new Dictionary<string, EpubTextContentFile>();
|
||||
foreach (KeyValuePair<string, EpubTextContentFileRef> 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<Dictionary<string, EpubByteContentFile>> ReadByteContentFiles(Dictionary<string, EpubByteContentFileRef> byteContentFileRefs)
|
||||
{
|
||||
Dictionary<string, EpubByteContentFile> result = new Dictionary<string, EpubByteContentFile>();
|
||||
foreach (KeyValuePair<string, EpubByteContentFileRef> byteContentFileRef in byteContentFileRefs)
|
||||
{
|
||||
result.Add(byteContentFileRef.Key, await ReadByteContentFile(byteContentFileRef.Value).ConfigureAwait(false));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static async Task<EpubByteContentFile> 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<List<EpubChapter>> ReadChapters(List<EpubChapterRef> chapterRefs)
|
||||
{
|
||||
List<EpubChapter> result = new List<EpubChapter>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
40
QuickLook.Plugin/VersOne.Epub/Readers/BookCoverReader.cs
Normal file
40
QuickLook.Plugin/VersOne.Epub/Readers/BookCoverReader.cs
Normal file
@@ -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<byte[]> ReadBookCoverAsync(EpubBookRef bookRef)
|
||||
{
|
||||
List<EpubMetadataMeta> 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;
|
||||
}
|
||||
}
|
||||
}
|
83
QuickLook.Plugin/VersOne.Epub/Readers/ChapterReader.cs
Normal file
83
QuickLook.Plugin/VersOne.Epub/Readers/ChapterReader.cs
Normal file
@@ -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<EpubChapterRef> GetChapters(EpubBookRef bookRef)
|
||||
{
|
||||
// return GetChapters(bookRef, bookRef.Schema.Navigation.NavMap);
|
||||
return GetChapters(bookRef, bookRef.Schema.Package.Spine, bookRef.Schema.Navigation.NavMap);
|
||||
}
|
||||
|
||||
public static List<EpubChapterRef> GetChapters(EpubBookRef bookRef, EpubSpine spine, List<EpubNavigationPoint> navigationPoints)
|
||||
{
|
||||
List<EpubChapterRef> result = new List<EpubChapterRef>();
|
||||
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<EpubChapterRef>();
|
||||
result.Add(chapterRef);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<EpubChapterRef> GetChapters(EpubBookRef bookRef, List<EpubNavigationPoint> navigationPoints, EpubChapterRef parentChapter = null)
|
||||
{
|
||||
List<EpubChapterRef> result = new List<EpubChapterRef>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
113
QuickLook.Plugin/VersOne.Epub/Readers/ContentReader.cs
Normal file
113
QuickLook.Plugin/VersOne.Epub/Readers/ContentReader.cs
Normal file
@@ -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<string, EpubTextContentFileRef>(),
|
||||
Css = new Dictionary<string, EpubTextContentFileRef>(),
|
||||
Images = new Dictionary<string, EpubByteContentFileRef>(),
|
||||
Fonts = new Dictionary<string, EpubByteContentFileRef>(),
|
||||
AllFiles = new Dictionary<string, EpubContentFileRef>()
|
||||
};
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
408
QuickLook.Plugin/VersOne.Epub/Readers/NavigationReader.cs
Normal file
408
QuickLook.Plugin/VersOne.Epub/Readers/NavigationReader.cs
Normal file
@@ -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<EpubNavigation> 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<EpubNavigationDocAuthor>();
|
||||
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<EpubNavigationList>();
|
||||
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<EpubNavigationLabel>();
|
||||
result.ChildNavigationPoints = new List<EpubNavigationPoint>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
399
QuickLook.Plugin/VersOne.Epub/Readers/PackageReader.cs
Normal file
399
QuickLook.Plugin/VersOne.Epub/Readers/PackageReader.cs
Normal file
@@ -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<EpubPackage> 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<string>(),
|
||||
Creators = new List<EpubMetadataCreator>(),
|
||||
Subjects = new List<string>(),
|
||||
Publishers = new List<string>(),
|
||||
Contributors = new List<EpubMetadataContributor>(),
|
||||
Dates = new List<EpubMetadataDate>(),
|
||||
Types = new List<string>(),
|
||||
Formats = new List<string>(),
|
||||
Identifiers = new List<EpubMetadataIdentifier>(),
|
||||
Sources = new List<string>(),
|
||||
Languages = new List<string>(),
|
||||
Relations = new List<string>(),
|
||||
Coverages = new List<string>(),
|
||||
Rights = new List<string>(),
|
||||
MetaItems = new List<EpubMetadataMeta>()
|
||||
};
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
33
QuickLook.Plugin/VersOne.Epub/Readers/RootFilePathReader.cs
Normal file
33
QuickLook.Plugin/VersOne.Epub/Readers/RootFilePathReader.cs
Normal file
@@ -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<string> 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;
|
||||
}
|
||||
}
|
||||
}
|
22
QuickLook.Plugin/VersOne.Epub/Readers/SchemaReader.cs
Normal file
22
QuickLook.Plugin/VersOne.Epub/Readers/SchemaReader.cs
Normal file
@@ -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<EpubSchema> 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;
|
||||
}
|
||||
}
|
||||
}
|
71
QuickLook.Plugin/VersOne.Epub/RefEntities/EpubBookRef.cs
Normal file
71
QuickLook.Plugin/VersOne.Epub/RefEntities/EpubBookRef.cs
Normal file
@@ -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<string> 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<byte[]> ReadCoverAsync()
|
||||
{
|
||||
return await BookCoverReader.ReadBookCoverAsync(this).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public List<EpubChapterRef> GetChapters()
|
||||
{
|
||||
return GetChaptersAsync().Result;
|
||||
}
|
||||
|
||||
public async Task<List<EpubChapterRef>> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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<byte[]> ReadContentAsync()
|
||||
{
|
||||
return ReadContentAsBytesAsync();
|
||||
}
|
||||
}
|
||||
}
|
37
QuickLook.Plugin/VersOne.Epub/RefEntities/EpubChapterRef.cs
Normal file
37
QuickLook.Plugin/VersOne.Epub/RefEntities/EpubChapterRef.cs
Normal file
@@ -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<EpubChapterRef> SubChapters { get; set; }
|
||||
public EpubChapterRef Parent { get; set; }
|
||||
|
||||
public string ReadHtmlContent()
|
||||
{
|
||||
return ReadHtmlContentAsync().Result;
|
||||
}
|
||||
|
||||
public Task<string> ReadHtmlContentAsync()
|
||||
{
|
||||
return epubTextContentFileRef.ReadContentAsTextAsync();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Format("Title: {0}, Subchapter count: {1}", Title, SubChapters.Count);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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<byte[]> 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<string> 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;
|
||||
}
|
||||
}
|
||||
}
|
13
QuickLook.Plugin/VersOne.Epub/RefEntities/EpubContentRef.cs
Normal file
13
QuickLook.Plugin/VersOne.Epub/RefEntities/EpubContentRef.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VersOne.Epub
|
||||
{
|
||||
public class EpubContentRef
|
||||
{
|
||||
public Dictionary<string, EpubTextContentFileRef> Html { get; set; }
|
||||
public Dictionary<string, EpubTextContentFileRef> Css { get; set; }
|
||||
public Dictionary<string, EpubByteContentFileRef> Images { get; set; }
|
||||
public Dictionary<string, EpubByteContentFileRef> Fonts { get; set; }
|
||||
public Dictionary<string, EpubContentFileRef> AllFiles { get; set; }
|
||||
}
|
||||
}
|
@@ -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<string> ReadContentAsync()
|
||||
{
|
||||
return ReadContentAsTextAsync();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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<EpubNavigationDocAuthor> DocAuthors { get; set; }
|
||||
public EpubNavigationMap NavMap { get; set; }
|
||||
public EpubNavigationPageList PageList { get; set; }
|
||||
public List<EpubNavigationList> NavLists { get; set; }
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VersOne.Epub.Schema
|
||||
{
|
||||
public class EpubNavigationDocAuthor : List<string>
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VersOne.Epub.Schema
|
||||
{
|
||||
public class EpubNavigationDocTitle : List<string>
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VersOne.Epub.Schema
|
||||
{
|
||||
public class EpubNavigationHead : List<EpubNavigationHeadMeta>
|
||||
{
|
||||
}
|
||||
}
|
@@ -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; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
namespace VersOne.Epub.Schema
|
||||
{
|
||||
public class EpubNavigationLabel
|
||||
{
|
||||
public string Text { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Text;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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<EpubNavigationLabel> NavigationLabels { get; set; }
|
||||
public List<EpubNavigationTarget> NavigationTargets { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VersOne.Epub.Schema
|
||||
{
|
||||
public class EpubNavigationMap : List<EpubNavigationPoint>
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VersOne.Epub.Schema
|
||||
{
|
||||
public class EpubNavigationPageList : List<EpubNavigationPageTarget>
|
||||
{
|
||||
}
|
||||
}
|
@@ -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<EpubNavigationLabel> NavigationLabels { get; set; }
|
||||
public EpubNavigationContent Content { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
namespace VersOne.Epub.Schema
|
||||
{
|
||||
public enum EpubNavigationPageTargetType
|
||||
{
|
||||
FRONT = 1,
|
||||
NORMAL,
|
||||
SPECIAL
|
||||
}
|
||||
}
|
@@ -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<EpubNavigationLabel> NavigationLabels { get; set; }
|
||||
public EpubNavigationContent Content { get; set; }
|
||||
public List<EpubNavigationPoint> ChildNavigationPoints { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Format("Id: {0}, Content.Source: {1}", Id, Content.Source);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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<EpubNavigationLabel> NavigationLabels { get; set; }
|
||||
public EpubNavigationContent Content { get; set; }
|
||||
}
|
||||
}
|
8
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubGuide.cs
Normal file
8
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubGuide.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VersOne.Epub.Schema
|
||||
{
|
||||
public class EpubGuide : List<EpubGuideReference>
|
||||
{
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
8
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubManifest.cs
Normal file
8
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubManifest.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VersOne.Epub.Schema
|
||||
{
|
||||
public class EpubManifest : List<EpubManifestItem>
|
||||
{
|
||||
}
|
||||
}
|
20
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubManifestItem.cs
Normal file
20
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubManifestItem.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
24
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadata.cs
Normal file
24
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadata.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VersOne.Epub.Schema
|
||||
{
|
||||
public class EpubMetadata
|
||||
{
|
||||
public List<string> Titles { get; set; }
|
||||
public List<EpubMetadataCreator> Creators { get; set; }
|
||||
public List<string> Subjects { get; set; }
|
||||
public string Description { get; set; }
|
||||
public List<string> Publishers { get; set; }
|
||||
public List<EpubMetadataContributor> Contributors { get; set; }
|
||||
public List<EpubMetadataDate> Dates { get; set; }
|
||||
public List<string> Types { get; set; }
|
||||
public List<string> Formats { get; set; }
|
||||
public List<EpubMetadataIdentifier> Identifiers { get; set; }
|
||||
public List<string> Sources { get; set; }
|
||||
public List<string> Languages { get; set; }
|
||||
public List<string> Relations { get; set; }
|
||||
public List<string> Coverages { get; set; }
|
||||
public List<string> Rights { get; set; }
|
||||
public List<EpubMetadataMeta> MetaItems { get; set; }
|
||||
}
|
||||
}
|
@@ -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; }
|
||||
}
|
||||
}
|
@@ -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; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
namespace VersOne.Epub.Schema
|
||||
{
|
||||
public class EpubMetadataDate
|
||||
{
|
||||
public string Date { get; set; }
|
||||
public string Event { get; set; }
|
||||
}
|
||||
}
|
@@ -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; }
|
||||
}
|
||||
}
|
12
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadataMeta.cs
Normal file
12
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubMetadataMeta.cs
Normal file
@@ -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; }
|
||||
}
|
||||
}
|
11
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubPackage.cs
Normal file
11
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubPackage.cs
Normal file
@@ -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; }
|
||||
}
|
||||
}
|
9
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubSpine.cs
Normal file
9
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubSpine.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VersOne.Epub.Schema
|
||||
{
|
||||
public class EpubSpine : List<EpubSpineItemRef>
|
||||
{
|
||||
public string Toc { get; set; }
|
||||
}
|
||||
}
|
15
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubSpineItemRef.cs
Normal file
15
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubSpineItemRef.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
8
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubVersion.cs
Normal file
8
QuickLook.Plugin/VersOne.Epub/Schema/Opf/EpubVersion.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace VersOne.Epub.Schema
|
||||
{
|
||||
public enum EpubVersion
|
||||
{
|
||||
EPUB_2 = 2,
|
||||
EPUB_3
|
||||
}
|
||||
}
|
28
QuickLook.Plugin/VersOne.Epub/Utils/XmlUtils.cs
Normal file
28
QuickLook.Plugin/VersOne.Epub/Utils/XmlUtils.cs
Normal file
@@ -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<XDocument> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
32
QuickLook.Plugin/VersOne.Epub/Utils/ZipPathUtils.cs
Normal file
32
QuickLook.Plugin/VersOne.Epub/Utils/ZipPathUtils.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
QuickLook.Plugin/VersOne.Epub/VersOne.Epub.csproj
Normal file
15
QuickLook.Plugin/VersOne.Epub/VersOne.Epub.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net45;netcoreapp1.0;netstandard1.3</TargetFrameworks>
|
||||
<Authors>vers</Authors>
|
||||
<Company />
|
||||
<Product />
|
||||
<Description />
|
||||
<Copyright>vers, 2015-2018</Copyright>
|
||||
<Version>2.0.4</Version>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.IO.Compression" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
31
QuickLook.Plugin/VersOne.Epub/VersOne.Epub.nuspec
Normal file
31
QuickLook.Plugin/VersOne.Epub/VersOne.Epub.nuspec
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>VersOne.Epub</id>
|
||||
<version>2.0.4</version>
|
||||
<title>EPUB reader</title>
|
||||
<description>.NET library for reading EPUB files</description>
|
||||
<authors>vers</authors>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<licenseUrl>https://github.com/vers-one/EpubReader/blob/master/LICENSE</licenseUrl>
|
||||
<projectUrl>https://github.com/vers-one/EpubReader</projectUrl>
|
||||
<releaseNotes>.NET Standard 1.3 support.</releaseNotes>
|
||||
<copyright>Unlicense <http://unlicense.org></copyright>
|
||||
<tags>epub</tags>
|
||||
<dependencies>
|
||||
<group targetFramework="net45">
|
||||
</group>
|
||||
<group targetFramework="netcoreapp1.0">
|
||||
<dependency id="System.IO.Compression" version="4.3.0" exclude="Build,Analyzers" />
|
||||
</group>
|
||||
<group targetFramework="netstandard1.3">
|
||||
<dependency id="System.IO.Compression" version="4.3.0" exclude="Build,Analyzers" />
|
||||
</group>
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="bin\Release\net45\VersOne.Epub.dll" target="lib\net45" />
|
||||
<file src="bin\Release\netcoreapp1.0\VersOne.Epub.dll" target="lib\netcoreapp1.0" />
|
||||
<file src="bin\Release\netstandard1.3\VersOne.Epub.dll" target="lib\netstandard1.3" />
|
||||
</files>
|
||||
</package>
|
@@ -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}
|
||||
|
Reference in New Issue
Block a user