Skip to content

Commit

Permalink
Initial Code Commit
Browse files Browse the repository at this point in the history
This is a code complete initial pass.
Tests need to be added.
  • Loading branch information
orthros committed May 11, 2017
1 parent 25eeebd commit c123feb
Show file tree
Hide file tree
Showing 57 changed files with 1,681 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
language: dart
sudo: false
script: ./tool/travis.sh
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# dartpub
Epub Reader for Dart
Epub Reader for Dart inspired by [this fantastic C# Epub Reader](https://github.com/versfx/EpubReader)
1 change: 1 addition & 0 deletions lib/dartpub.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
library dartpub;
16 changes: 16 additions & 0 deletions lib/src/entities/epubBook.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'epubChapter.dart';
import 'epubContent.dart';
import 'epubSchema.dart';

import 'package:image/image.dart';

class EpubBook {
String FilePath;
String Title;
String Author;
List<String> AuthorList;
EpubSchema Schema;
EpubContent Content;
Image CoverImage;
List<EpubChapter> Chapters;
}
5 changes: 5 additions & 0 deletions lib/src/entities/epubByteContentFile.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import 'epubContentFile.dart';

class EpubByteContentFile extends EpubContentFile {
List<int> Content;
}
11 changes: 11 additions & 0 deletions lib/src/entities/epubChapter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class EpubChapter {
String Title;
String ContentFileName;
String Anchor;
String HtmlContent;
List<EpubChapter> SubChapters;

String toString() {
return "Title: ${Title}, Subchapter count: ${SubChapters.length}";
}
}
11 changes: 11 additions & 0 deletions lib/src/entities/epubContent.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'epubByteContentFile.dart';
import 'epubContentFile.dart';
import 'epubTextContentFile.dart';

class EpubContent{
Map<String, EpubTextContentFile> Html;
Map<String, EpubTextContentFile> Css;
Map<String, EpubByteContentFile> Images;
Map<String, EpubByteContentFile> Fonts;
Map<String, EpubContentFile> AllFiles;
}
8 changes: 8 additions & 0 deletions lib/src/entities/epubContentFile.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'epubContentType.dart';

abstract class EpubContentFile
{
String FileName;
EpubContentType ContentType;
String ContentMimeType;
}
16 changes: 16 additions & 0 deletions lib/src/entities/epubContentType.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
enum EpubContentType {
XHTML_1_1,
DTBOOK,
DTBOOK_NCX,
OEB1_DOCUMENT,
XML,
CSS,
OEB1_CSS,
IMAGE_GIF,
IMAGE_JPEG,
IMAGE_PNG,
IMAGE_SVG,
FONT_TRUETYPE,
FONT_OPENTYPE,
OTHER
}
8 changes: 8 additions & 0 deletions lib/src/entities/epubSchema.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import '../schema/navigation/epubNavigation.dart';
import '../schema/opf/epubPackage.dart';

class EpubSchema {
EpubPackage Package;
EpubNavigation Navigation;
String ContentDirectoryPath;
}
5 changes: 5 additions & 0 deletions lib/src/entities/epubTextContentFile.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import 'epubContentFile.dart';

class EpubTextContentFile extends EpubContentFile {
String Content;
}
165 changes: 165 additions & 0 deletions lib/src/epubReader.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import 'dart:async';
import 'dart:io' as IO;

import 'entities/epubBook.dart';
import 'entities/epubByteContentFile.dart';
import 'entities/epubChapter.dart';
import 'entities/epubContent.dart';
import 'entities/epubContentFile.dart';
import 'entities/epubTextContentFile.dart';
import 'readers/contentReader.dart';
import 'readers/schemaReader.dart';
import 'refEntities/epubBookRef.dart';
import 'refEntities/epubByteContentFileRef.dart';
import 'refEntities/epubChapterRef.dart';
import 'refEntities/epubContentFileRef.dart';
import 'refEntities/epubContentRef.dart';
import 'refEntities/epubTextContentFileRef.dart';
import 'schema/opf/epubMetadataCreator.dart';

import 'package:archive/archive.dart';

class EpubReader {

/// Opens the book synchronously without reading its content. Holds the handle to the EPUB file.
static EpubBookRef OpenBook(String filePath) {
EpubBookRef res;
Future.wait([OpenBookAsync(filePath)])
.then((List<EpubBookRef> results) => res = results.first);
return res;
}

/// Opens the book asynchronously without reading its content. Holds the handle to the EPUB file.
static Future<EpubBookRef> OpenBookAsync(String filePath) async {
var targetFile = new IO.File(filePath);
if (!(await targetFile.exists()))
throw new Exception("Specified epub file not found: ${filePath}");

List<int> bytes = await targetFile.readAsBytes();
Archive epubArchive = new ZipDecoder().decodeBytes(bytes);

EpubBookRef bookRef = new EpubBookRef(epubArchive);
bookRef.FilePath = filePath;
bookRef.Schema = await SchemaReader.ReadSchemaAsync(epubArchive);
bookRef.Title = bookRef.Schema
.Package
.Metadata
.Titles
.firstWhere((String name) => true, orElse: () => "");
bookRef.AuthorList = bookRef.Schema
.Package
.Metadata
.Creators
.map((EpubMetadataCreator creator) => creator.Creator).toList();
bookRef.Author = bookRef.AuthorList.join(", ");
bookRef.Content = await ContentReader.ParseContentMap(bookRef);
return bookRef;
}

/// Opens the book synchronously and reads all of its content into the memory. Does not hold the handle to the EPUB file.
static EpubBook ReadBook(String filePath) {
EpubBook res;
Future.wait([ReadBookAsync(filePath)])
.then((List<EpubBook> result) => res = result.first);
return res;
}

/// Opens the book asynchronously and reads all of its content into the memory. Does not hold the handle to the EPUB file.
static Future<EpubBook> ReadBookAsync(String filePath) async {
EpubBook result = new EpubBook();

EpubBookRef epubBookRef = await OpenBookAsync(filePath);
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);
result.CoverImage = await epubBookRef.ReadCoverAsync();
List<EpubChapterRef> chapterRefs = await epubBookRef.GetChaptersAsync();
result.Chapters = await readChapters(chapterRefs);

return result;
}

static Future<EpubContent> ReadContent(EpubContentRef contentRef) async {
EpubContent result = new EpubContent();
result.Html = await readTextContentFiles(contentRef.Html);
result.Css = await readTextContentFiles(contentRef.Css);
result.Images = await readByteContentFiles(contentRef.Images);
result.Fonts = await readByteContentFiles(contentRef.Fonts);
result.AllFiles = new Map<String, EpubContentFile>();

result.Html.forEach((String key, EpubTextContentFile value){
result.AllFiles[key] = value;
});
result.Css.forEach((String key, EpubTextContentFile value){
result.AllFiles[key] = value;
});

result.Images.forEach((String key, EpubByteContentFile value){
result.AllFiles[key] = value;
});
result.Fonts.forEach((String key, EpubByteContentFile value){
result.AllFiles[key] = value;
});

await contentRef.AllFiles.forEach((String key, EpubContentFileRef value) async {
if(!result.AllFiles.containsKey(key)) {
result.AllFiles[key] = await readByteContentFile(value);
}
});

return result;
}

static Future<Map<String, EpubTextContentFile>> readTextContentFiles(Map<String, EpubTextContentFileRef> textContentFileRefs) async {
Map<String, EpubTextContentFile> result = new Map<String, EpubTextContentFile>();
await textContentFileRefs.forEach((String key, EpubContentFileRef value) async {
EpubTextContentFile textContentFile = new EpubTextContentFile();
{
textContentFile.FileName = value.FileName;
textContentFile.ContentType = value.ContentType;
textContentFile.ContentMimeType = value.ContentMimeType;
};
textContentFile.Content = await value.ReadContentAsTextAsync();
result[key] = textContentFile;
});
return result;
}

static Future<Map<String, EpubByteContentFile>> readByteContentFiles(Map<String, EpubByteContentFileRef> byteContentFileRefs) async {
Map<String, EpubByteContentFile> result = new Map<String, EpubByteContentFile>();
byteContentFileRefs.forEach((String key, EpubByteContentFileRef value) async {
result[key] = await readByteContentFile(value);
});
return result;
}

static Future<EpubByteContentFile> readByteContentFile(EpubContentFileRef contentFileRef) async {
EpubByteContentFile result = new EpubByteContentFile();
{
result.FileName = contentFileRef.FileName;
result.ContentType = contentFileRef.ContentType;
result.ContentMimeType = contentFileRef.ContentMimeType;
};
result.Content = await contentFileRef.ReadContentAsBytesAsync();
return result;
}

static Future<List<EpubChapter>> readChapters(List<EpubChapterRef> chapterRefs) async {
List<EpubChapter> result = new List<EpubChapter>();
await chapterRefs.forEach((EpubChapterRef chapterRef) async {
EpubChapter chapter = new EpubChapter();
{
chapter.Title = chapterRef.Title;
chapter.ContentFileName = chapterRef.ContentFileName;
chapter.Anchor = chapterRef.Anchor;
};
chapter.HtmlContent = await chapterRef.ReadHtmlContentAsync();
chapter.SubChapters = await readChapters(chapterRef.SubChapters);
result.add(chapter);
});
return result;
}
}
35 changes: 35 additions & 0 deletions lib/src/readers/bookCoverReader.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'dart:async';

import '../refEntities/epubBookRef.dart';
import '../refEntities/epubByteContentFileRef.dart';
import '../schema/opf/epubManifestItem.dart';
import '../schema/opf/epubMetadataMeta.dart';

import 'package:image/image.dart' as images;

class BookCoverReader {
static Future<images.Image> ReadBookCoverAsync(EpubBookRef bookRef) async {
List<EpubMetadataMeta> metaItems = bookRef.Schema.Package.Metadata.MetaItems;
if (metaItems == null || metaItems.length == 0)
return null;
EpubMetadataMeta coverMetaItem = metaItems.firstWhere((EpubMetadataMeta metaItem) => metaItem.Name.toLowerCase() == "cover", orElse: () => null);
if (coverMetaItem == null)
return null;
if (coverMetaItem.Content == null || coverMetaItem.Content.isEmpty)
throw new Exception("Incorrect EPUB metadata: cover item content is missing.");
EpubManifestItem coverManifestItem = bookRef.Schema
.Package
.Manifest
.Items
.firstWhere((EpubManifestItem manifestItem) => manifestItem.Id.toLowerCase() == coverMetaItem.Content.toLowerCase(), orElse: () => null));
if (coverManifestItem == null)
throw new Exception("Incorrect EPUB manifest: item with ID = \"${coverMetaItem.Content}\" is missing.");
EpubByteContentFileRef coverImageContentFileRef;
if (!bookRef.Content.Images.containsKey(coverManifestItem.Href))
throw new Exception("Incorrect EPUB manifest: item with href = \"${coverManifestItem.Href}\" is missing.");
coverImageContentFileRef = bookRef.Content.Images[coverManifestItem.Href];
List<int> coverImageContent = await coverImageContentFileRef.ReadContentAsBytesAsync();
images.Image retval = images.decodeImage(coverImageContent);
return retval;
}
}
41 changes: 41 additions & 0 deletions lib/src/readers/chapterReader.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import '../refEntities/epubBookRef.dart';
import '../refEntities/epubChapterRef.dart';
import '../refEntities/epubTextContentFileRef.dart';
import '../schema/navigation/epubNavigationPoint.dart';

class ChapterReader {
static List<EpubChapterRef> GetChapters(EpubBookRef bookRef) {
return GetChaptersImpl(bookRef, bookRef.Schema.Navigation.NavMap.Points);
}

static List<EpubChapterRef> GetChaptersImpl(EpubBookRef bookRef, List<EpubNavigationPoint> navigationPoints) {
List<EpubChapterRef> result = new List<EpubChapterRef>();
navigationPoints.forEach((EpubNavigationPoint navigationPoint) {
String contentFileName;
String anchor;
int contentSourceAnchorCharIndex = navigationPoint.Content.Source.indexOf('#');
if (contentSourceAnchorCharIndex == -1)
{
contentFileName = navigationPoint.Content.Source;
anchor = null;
}
else
{
contentFileName = navigationPoint.Content.Source.substring(0, contentSourceAnchorCharIndex);
anchor = navigationPoint.Content.Source.substring(contentSourceAnchorCharIndex + 1);
}
EpubTextContentFileRef htmlContentFileRef;
if (!bookRef.Content.Html.containsKey(contentFileName)) {
throw new Exception("Incorrect EPUB manifest: item with href = \"${contentFileName}\" is missing.");
}
htmlContentFileRef = bookRef.Content.Html[contentFileName];
EpubChapterRef chapterRef = new EpubChapterRef(htmlContentFileRef);
chapterRef.ContentFileName = contentFileName;
chapterRef.Anchor = anchor;
chapterRef.Title = navigationPoint.NavigationLabels.first.Text;
chapterRef.SubChapters = GetChaptersImpl(bookRef, navigationPoint.ChildNavigationPoints);
result.add(chapterRef);
});
return result;
}
}
Loading

0 comments on commit c123feb

Please sign in to comment.