Included epub library in plugin project

This commit is contained in:
Marco Gavelli
2018-07-16 10:06:17 +02:00
parent a78428c698
commit a82cacd126
57 changed files with 57 additions and 61 deletions

View 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; }
}
}

View File

@@ -0,0 +1,7 @@
namespace VersOne.Epub
{
public class EpubByteContentFile : EpubContentFile
{
public byte[] Content { get; set; }
}
}

View 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);
}
}
}

View 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; }
}
}

View File

@@ -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; }
}
}

View 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
}
}

View 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; }
}
}

View File

@@ -0,0 +1,7 @@
namespace VersOne.Epub
{
public class EpubTextContentFile : EpubContentFile
{
public string Content { get; set; }
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}
}

View File

@@ -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();
}
}
}

View 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);
}
}
}

View File

@@ -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;
}
}
}

View 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; }
}
}

View File

@@ -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();
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace VersOne.Epub.Schema
{
public class EpubNavigationDocAuthor : List<string>
{
}
}

View File

@@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace VersOne.Epub.Schema
{
public class EpubNavigationDocTitle : List<string>
{
}
}

View File

@@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace VersOne.Epub.Schema
{
public class EpubNavigationHead : List<EpubNavigationHeadMeta>
{
}
}

View File

@@ -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; }
}
}

View File

@@ -0,0 +1,12 @@
namespace VersOne.Epub.Schema
{
public class EpubNavigationLabel
{
public string Text { get; set; }
public override string ToString()
{
return Text;
}
}
}

View File

@@ -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; }
}
}

View File

@@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace VersOne.Epub.Schema
{
public class EpubNavigationMap : List<EpubNavigationPoint>
{
}
}

View File

@@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace VersOne.Epub.Schema
{
public class EpubNavigationPageList : List<EpubNavigationPageTarget>
{
}
}

View File

@@ -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; }
}
}

View File

@@ -0,0 +1,9 @@
namespace VersOne.Epub.Schema
{
public enum EpubNavigationPageTargetType
{
FRONT = 1,
NORMAL,
SPECIAL
}
}

View File

@@ -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);
}
}
}

View File

@@ -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; }
}
}

View File

@@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace VersOne.Epub.Schema
{
public class EpubGuide : List<EpubGuideReference>
{
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace VersOne.Epub.Schema
{
public class EpubManifest : List<EpubManifestItem>
{
}
}

View 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);
}
}
}

View 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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -0,0 +1,8 @@
namespace VersOne.Epub.Schema
{
public class EpubMetadataDate
{
public string Date { get; set; }
public string Event { get; set; }
}
}

View File

@@ -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; }
}
}

View 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; }
}
}

View 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; }
}
}

View File

@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace VersOne.Epub.Schema
{
public class EpubSpine : List<EpubSpineItemRef>
{
public string Toc { get; set; }
}
}

View 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);
}
}
}

View File

@@ -0,0 +1,8 @@
namespace VersOne.Epub.Schema
{
public enum EpubVersion
{
EPUB_2 = 2,
EPUB_3
}
}

View 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);
}
}
}
}
}

View 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);
}
}
}
}

View File

@@ -42,6 +42,10 @@
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.IO.Compression, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll</HintPath>
</Reference>
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xaml" />
<Reference Include="System.Xml.Linq" />
@@ -56,6 +60,58 @@
<Compile Include="..\..\GitVersion.cs">
<Link>Properties\GitVersion.cs</Link>
</Compile>
<Compile Include="EpubReader\Entities\EpubBook.cs" />
<Compile Include="EpubReader\Entities\EpubByteContentFile.cs" />
<Compile Include="EpubReader\Entities\EpubChapter.cs" />
<Compile Include="EpubReader\Entities\EpubContent.cs" />
<Compile Include="EpubReader\Entities\EpubContentFile.cs" />
<Compile Include="EpubReader\Entities\EpubContentType.cs" />
<Compile Include="EpubReader\Entities\EpubSchema.cs" />
<Compile Include="EpubReader\Entities\EpubTextContentFile.cs" />
<Compile Include="EpubReader\EpubReader.cs" />
<Compile Include="EpubReader\Readers\BookCoverReader.cs" />
<Compile Include="EpubReader\Readers\ChapterReader.cs" />
<Compile Include="EpubReader\Readers\ContentReader.cs" />
<Compile Include="EpubReader\Readers\NavigationReader.cs" />
<Compile Include="EpubReader\Readers\PackageReader.cs" />
<Compile Include="EpubReader\Readers\RootFilePathReader.cs" />
<Compile Include="EpubReader\Readers\SchemaReader.cs" />
<Compile Include="EpubReader\RefEntities\EpubBookRef.cs" />
<Compile Include="EpubReader\RefEntities\EpubByteContentFileRef.cs" />
<Compile Include="EpubReader\RefEntities\EpubChapterRef.cs" />
<Compile Include="EpubReader\RefEntities\EpubContentFileRef.cs" />
<Compile Include="EpubReader\RefEntities\EpubContentRef.cs" />
<Compile Include="EpubReader\RefEntities\EpubTextContentFileRef.cs" />
<Compile Include="EpubReader\Schema\Navigation\EpubNavigation.cs" />
<Compile Include="EpubReader\Schema\Navigation\EpubNavigationContent.cs" />
<Compile Include="EpubReader\Schema\Navigation\EpubNavigationDocAuthor.cs" />
<Compile Include="EpubReader\Schema\Navigation\EpubNavigationDocTitle.cs" />
<Compile Include="EpubReader\Schema\Navigation\EpubNavigationHead.cs" />
<Compile Include="EpubReader\Schema\Navigation\EpubNavigationHeadMeta.cs" />
<Compile Include="EpubReader\Schema\Navigation\EpubNavigationLabel.cs" />
<Compile Include="EpubReader\Schema\Navigation\EpubNavigationList.cs" />
<Compile Include="EpubReader\Schema\Navigation\EpubNavigationMap.cs" />
<Compile Include="EpubReader\Schema\Navigation\EpubNavigationPageList.cs" />
<Compile Include="EpubReader\Schema\Navigation\EpubNavigationPageTarget.cs" />
<Compile Include="EpubReader\Schema\Navigation\EpubNavigationPageTargetType.cs" />
<Compile Include="EpubReader\Schema\Navigation\EpubNavigationPoint.cs" />
<Compile Include="EpubReader\Schema\Navigation\EpubNavigationTarget.cs" />
<Compile Include="EpubReader\Schema\Opf\EpubGuide.cs" />
<Compile Include="EpubReader\Schema\Opf\EpubGuideReference.cs" />
<Compile Include="EpubReader\Schema\Opf\EpubManifest.cs" />
<Compile Include="EpubReader\Schema\Opf\EpubManifestItem.cs" />
<Compile Include="EpubReader\Schema\Opf\EpubMetadata.cs" />
<Compile Include="EpubReader\Schema\Opf\EpubMetadataContributor.cs" />
<Compile Include="EpubReader\Schema\Opf\EpubMetadataCreator.cs" />
<Compile Include="EpubReader\Schema\Opf\EpubMetadataDate.cs" />
<Compile Include="EpubReader\Schema\Opf\EpubMetadataIdentifier.cs" />
<Compile Include="EpubReader\Schema\Opf\EpubMetadataMeta.cs" />
<Compile Include="EpubReader\Schema\Opf\EpubPackage.cs" />
<Compile Include="EpubReader\Schema\Opf\EpubSpine.cs" />
<Compile Include="EpubReader\Schema\Opf\EpubSpineItemRef.cs" />
<Compile Include="EpubReader\Schema\Opf\EpubVersion.cs" />
<Compile Include="EpubReader\Utils\XmlUtils.cs" />
<Compile Include="EpubReader\Utils\ZipPathUtils.cs" />
<Compile Include="EpubViewerControl.xaml.cs">
<DependentUpon>EpubViewerControl.xaml</DependentUpon>
</Compile>
@@ -71,10 +127,6 @@
<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">

View File

@@ -2,4 +2,5 @@
<packages>
<package id="HtmlRenderer.Core" version="1.5.0.6" targetFramework="net461" />
<package id="HtmlRenderer.WPF" version="1.5.0.6" targetFramework="net461" />
<package id="System.IO.Compression" version="4.3.0" targetFramework="net462" />
</packages>