0% found this document useful (0 votes)
38 views

Check

The document describes how to create an Epub archive from an EpubBook object by adding required files like the container, content, and metadata files. It also describes classes for reading an Epub archive and extracting metadata like the root file path, content directory path, and navigation information. Key files and elements are checked to ensure the archive is valid.

Uploaded by

duongnhgch18349
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
38 views

Check

The document describes how to create an Epub archive from an EpubBook object by adding required files like the container, content, and metadata files. It also describes classes for reading an Epub archive and extracting metadata like the root file path, content directory path, and navigation information. Key files and elements are checked to ensure the archive is valid.

Uploaded by

duongnhgch18349
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 37

class EpubWriter {

static const _container_file =


'<?xml version="1.0"?><container version="1.0"
xmlns="urn:oasis:names:tc:opendocument:xmlns:container"><rootfiles><rootfile full-path="OEBPS/content.opf"
media-type="application/oebps-package+xml"/></rootfiles></container>';

// Creates a Zip Archive of an EpubBook


static Archive _createArchive(EpubBook book) {
var arch = Archive();

// Add simple metadata


arch.addFile(ArchiveFile.noCompress(
"metadata", 20, convert.utf8.encode("application/epub+zip")));

// Add Container file


arch.addFile(ArchiveFile("META-INF/container.xml", _container_file.length,
convert.utf8.encode(_container_file)));

// Add all content to the archive


book.Content.AllFiles.forEach((name, file) {
List<int> content;

if (file is EpubByteContentFile) {
content = file.Content;
} else if (file is EpubTextContentFile) {
content = convert.utf8.encode(file.Content);
}

arch.addFile(ArchiveFile(
ZipPathUtils.combine(book.Schema.ContentDirectoryPath, name),
content.length,
content));
});

// Generate the content.opf file and add it to the Archive


var contentopf = EpubPackageWriter.writeContent(book.Schema.Package);
arch.addFile(ArchiveFile(
ZipPathUtils.combine(book.Schema.ContentDirectoryPath, "content.opf"),
contentopf.length,
convert.utf8.encode(contentopf)));

return arch;
}

// Serializes the EpubBook into a byte array


static List<int> writeBook(EpubBook book) {
var arch = _createArchive(book);

return ZipEncoder().encode(arch);
}
}

abstract class EpubContentFileRef {


EpubBookRef epubBookRef;

String FileName;

EpubContentType ContentType;
String ContentMimeType;
EpubContentFileRef(EpubBookRef epubBookRef) {
this.epubBookRef = epubBookRef;
}

@override
int get hashCode =>
hash3(FileName.hashCode, ContentMimeType.hashCode, ContentType.hashCode);

bool operator ==(other) {


return (other is EpubContentFileRef &&
other.FileName == FileName &&
other.ContentMimeType == ContentMimeType &&
other.ContentType == ContentType);
}

ArchiveFile getContentFileEntry() {
String contentFilePath =
ZipPathUtils.combine(epubBookRef.Schema.ContentDirectoryPath, FileName);
ArchiveFile contentFileEntry = epubBookRef.EpubArchive().files.firstWhere(
(ArchiveFile x) => x.name == contentFilePath,
orElse: () => null);
if (contentFileEntry == null) {
throw Exception(
"EPUB parsing error: file $contentFilePath not found in archive.");
}
return contentFileEntry;
}

List<int> getContentStream() {
return openContentStream(getContentFileEntry());
}

List<int> openContentStream(ArchiveFile contentFileEntry) {


List<int> contentStream = <int>[];
if (contentFileEntry.content == null) {
throw Exception(
'Incorrect EPUB file: content file \"$FileName\" specified in manifest is not found.');
}
contentStream.addAll(contentFileEntry.content);
return contentStream;
}

Future<List<int>> readContentAsBytes() async {


ArchiveFile contentFileEntry = getContentFileEntry();
var content = openContentStream(contentFileEntry);
return content;
}

Future<String> readContentAsText() async {


List<int> contentStream = getContentStream();
String result = convert.utf8.decode(contentStream);
return result;
}
}

// ignore_for_file: non_constant_identifier_names, test_types_in_equals

import 'package:quiver/collection.dart' as collections;


import 'package:quiver/core.dart';

import 'epub_byte_content_file_ref.dart';
import 'epub_content_file_ref.dart';
import 'epub_text_content_file_ref.dart';

class EpubContentRef {
Map<String, EpubTextContentFileRef> Html;
Map<String, EpubTextContentFileRef> Css;
Map<String, EpubByteContentFileRef> Images;
Map<String, EpubByteContentFileRef> Fonts;
Map<String, EpubContentFileRef> AllFiles;

EpubContentRef() {
Html = Map<String, EpubTextContentFileRef>();
Css = Map<String, EpubTextContentFileRef>();
Images = Map<String, EpubByteContentFileRef>();
Fonts = Map<String, EpubByteContentFileRef>();
AllFiles = Map<String, EpubContentFileRef>();
}

@override
int get hashCode {
var objects = []
..addAll(Html.keys.map((key) => key.hashCode))
..addAll(Html.values.map((value) => value.hashCode))
..addAll(Css.keys.map((key) => key.hashCode))
..addAll(Css.values.map((value) => value.hashCode))
..addAll(Images.keys.map((key) => key.hashCode))
..addAll(Images.values.map((value) => value.hashCode))
..addAll(Fonts.keys.map((key) => key.hashCode))
..addAll(Fonts.values.map((value) => value.hashCode))
..addAll(AllFiles.keys.map((key) => key.hashCode))
..addAll(AllFiles.values.map((value) => value.hashCode));

return hashObjects(objects);
}

bool operator ==(other) {


var otherAs = other as EpubContentRef;
if (otherAs == null) {
return false;
}

return collections.mapsEqual(Html, otherAs.Html) &&


collections.mapsEqual(Css, otherAs.Css) &&
collections.mapsEqual(Images, otherAs.Images) &&
collections.mapsEqual(Fonts, otherAs.Fonts) &&
collections.mapsEqual(AllFiles, otherAs.AllFiles);
}
}

// ignore_for_file: non_constant_identifier_names, test_types_in_equals

import 'dart:async';

import 'package:quiver/collection.dart' as collections;


import 'package:quiver/core.dart';

import 'epub_text_content_file_ref.dart';

class EpubChapterRef {
EpubTextContentFileRef epubTextContentFileRef;
String Title;
String ContentFileName;
String Anchor;
List<EpubChapterRef> SubChapters;

EpubChapterRef(EpubTextContentFileRef epubTextContentFileRef) {
this.epubTextContentFileRef = epubTextContentFileRef;
}

@override
int get hashCode {
var objects = []
..add(Title.hashCode)
..add(ContentFileName.hashCode)
..add(Anchor.hashCode)
..add(epubTextContentFileRef.hashCode)
..addAll(SubChapters?.map((subChapter) => subChapter.hashCode) ?? [0]);
return hashObjects(objects);
}

bool operator ==(other) {


var otherAs = other as EpubChapterRef;
if (otherAs == null) {
return false;
}
return Title == otherAs.Title &&
ContentFileName == otherAs.ContentFileName &&
Anchor == otherAs.Anchor &&
epubTextContentFileRef == otherAs.epubTextContentFileRef &&
collections.listsEqual(SubChapters, otherAs.SubChapters);
}

Future<String> readHtmlContent() async {


return epubTextContentFileRef.readContentAsText();
}
String toString() {
return "Title: $Title, Subchapter count: ${SubChapters.length}";
}
}

import 'dart:async';

import 'epub_book_ref.dart';
import 'epub_content_file_ref.dart';

class EpubTextContentFileRef extends EpubContentFileRef {


EpubTextContentFileRef(EpubBookRef epubBookRef) : super(epubBookRef);

Future<String> readContentAsync() async {


return readContentAsText();
}
}

class SchemaReader {
static Future<EpubSchema> readSchema(Archive epubArchive) async {
EpubSchema result = EpubSchema();

String rootFilePath = await RootFilePathReader.getRootFilePath(epubArchive);


String contentDirectoryPath = ZipPathUtils.getDirectoryPath(rootFilePath);
result.ContentDirectoryPath = contentDirectoryPath;

EpubPackage package =
await PackageReader.readPackage(epubArchive, rootFilePath);
result.Package = package;

EpubNavigation navigation = await NavigationReader.readNavigation(


epubArchive, contentDirectoryPath, package);
result.Navigation = navigation;

return result;
}
}

class RootFilePathReader {
static Future<String> getRootFilePath(Archive epubArchive) async {
const String EPUB_CONTAINER_FILE_PATH = "META-INF/container.xml";

ArchiveFile containerFileEntry = epubArchive.files.firstWhere(


(ArchiveFile file) => file.name == EPUB_CONTAINER_FILE_PATH,
orElse: () => null);
if (containerFileEntry == null) {
throw Exception(
"EPUB parsing error: $EPUB_CONTAINER_FILE_PATH file not found in archive.");
}

xml.XmlDocument containerDocument =
xml.parse(convert.utf8.decode(containerFileEntry.content));
xml.XmlElement packageElement = containerDocument
.findAllElements("container",
namespace: "urn:oasis:names:tc:opendocument:xmlns:container")
.firstWhere((xml.XmlElement elem) => elem != null, orElse: () => null);
if (packageElement == null) {
throw Exception("EPUB parsing error: Invalid epub container");
}

xml.XmlElement rootFileElement = packageElement.descendants.firstWhere(


(xml.XmlNode testElem) =>
(testElem is xml.XmlElement) && "rootfile" == testElem.name.local,
orElse: () => null);

return rootFileElement.getAttribute("full-path");
}
}

class PackageReader {
static EpubGuide readGuide(xml.XmlElement guideNode) {
EpubGuide result = EpubGuide();
result.Items = List<EpubGuideReference>();
guideNode.children
.whereType<xml.XmlElement>()
.forEach((xml.XmlElement guideReferenceNode) {
if (guideReferenceNode.name.local.toLowerCase() == "reference") {
EpubGuideReference guideReference = EpubGuideReference();
guideReferenceNode.attributes
.forEach((xml.XmlAttribute guideReferenceNodeAttribute) {
String attributeValue = guideReferenceNodeAttribute.value;
switch (guideReferenceNodeAttribute.name.local.toLowerCase()) {
case "type":
guideReference.Type = attributeValue;
break;
case "title":
guideReference.Title = attributeValue;
break;
case "href":
guideReference.Href = attributeValue;
break;
}
});
if (guideReference.Type == null || guideReference.Type.isEmpty) {
throw Exception("Incorrect EPUB guide: item type is missing");
}
if (guideReference.Href == null || guideReference.Href.isEmpty) {
throw Exception("Incorrect EPUB guide: item href is missing");
}
result.Items.add(guideReference);
}
});
return result;
}

static EpubManifest readManifest(xml.XmlElement manifestNode) {


EpubManifest result = EpubManifest();
result.Items = List<EpubManifestItem>();
manifestNode.children
.whereType<xml.XmlElement>()
.forEach((xml.XmlElement manifestItemNode) {
if (manifestItemNode.name.local.toLowerCase() == "item") {
EpubManifestItem manifestItem = EpubManifestItem();
manifestItemNode.attributes
.forEach((xml.XmlAttribute manifestItemNodeAttribute) {
String attributeValue = manifestItemNodeAttribute.value;
switch (manifestItemNodeAttribute.name.local.toLowerCase()) {
case "id":
manifestItem.Id = attributeValue;
break;
case "href":
manifestItem.Href = 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 (manifestItem.Id == null || manifestItem.Id.isEmpty) {


throw Exception("Incorrect EPUB manifest: item ID is missing");
}
if (manifestItem.Href == null || manifestItem.Href.isEmpty) {
throw Exception("Incorrect EPUB manifest: item href is missing");
}
if (manifestItem.MediaType == null || manifestItem.MediaType.isEmpty) {
throw Exception(
"Incorrect EPUB manifest: item media type is missing");
}
result.Items.add(manifestItem);
}
});
return result;
}

static EpubMetadata readMetadata(


xml.XmlElement metadataNode, EpubVersion epubVersion) {
EpubMetadata result = EpubMetadata();
result.Titles = List<String>();
result.Creators = List<EpubMetadataCreator>();
result.Subjects = List<String>();
result.Publishers = List<String>();
result.Contributors = List<EpubMetadataContributor>();
result.Dates = List<EpubMetadataDate>();
result.Types = List<String>();
result.Formats = List<String>();
result.Identifiers = List<EpubMetadataIdentifier>();
result.Sources = List<String>();
result.Languages = List<String>();
result.Relations = List<String>();
result.Coverages = List<String>();
result.Rights = List<String>();
result.MetaItems = List<EpubMetadataMeta>();
metadataNode.children
.whereType<xml.XmlElement>()
.forEach((xml.XmlElement metadataItemNode) {
String innerText = metadataItemNode.text;
switch (metadataItemNode.name.local.toLowerCase()) {
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.Epub2) {
EpubMetadataMeta meta = readMetadataMetaVersion2(metadataItemNode);
result.MetaItems.add(meta);
} else if (epubVersion == EpubVersion.Epub3) {
EpubMetadataMeta meta = readMetadataMetaVersion3(metadataItemNode);
result.MetaItems.add(meta);
}
break;
}
});
return result;
}

static EpubMetadataContributor readMetadataContributor(


xml.XmlElement metadataContributorNode) {
EpubMetadataContributor result = EpubMetadataContributor();
metadataContributorNode.attributes
.forEach((xml.XmlAttribute metadataContributorNodeAttribute) {
String attributeValue = metadataContributorNodeAttribute.value;
switch (metadataContributorNodeAttribute.name.local.toLowerCase()) {
case "role":
result.Role = attributeValue;
break;
case "file-as":
result.FileAs = attributeValue;
break;
}
});
result.Contributor = metadataContributorNode.text;
return result;
}

static EpubMetadataCreator readMetadataCreator(


xml.XmlElement metadataCreatorNode) {
EpubMetadataCreator result = EpubMetadataCreator();
metadataCreatorNode.attributes
.forEach((xml.XmlAttribute metadataCreatorNodeAttribute) {
String attributeValue = metadataCreatorNodeAttribute.value;
switch (metadataCreatorNodeAttribute.name.local.toLowerCase()) {
case "role":
result.Role = attributeValue;
break;
case "file-as":
result.FileAs = attributeValue;
break;
}
});
result.Creator = metadataCreatorNode.text;
return result;
}

static EpubMetadataDate readMetadataDate(xml.XmlElement metadataDateNode) {


EpubMetadataDate result = EpubMetadataDate();
String eventAttribute = metadataDateNode.getAttribute("event",
namespace: metadataDateNode.name.namespaceUri);
if (eventAttribute != null && eventAttribute.isNotEmpty) {
result.Event = eventAttribute;
}
result.Date = metadataDateNode.text;
return result;
}

static EpubMetadataIdentifier readMetadataIdentifier(


xml.XmlElement metadataIdentifierNode) {
EpubMetadataIdentifier result = EpubMetadataIdentifier();
metadataIdentifierNode.attributes
.forEach((xml.XmlAttribute metadataIdentifierNodeAttribute) {
String attributeValue = metadataIdentifierNodeAttribute.value;
switch (metadataIdentifierNodeAttribute.name.local.toLowerCase()) {
case "id":
result.Id = attributeValue;
break;
case "scheme":
result.Scheme = attributeValue;
break;
}
});
result.Identifier = metadataIdentifierNode.text;
return result;
}

static EpubMetadataMeta readMetadataMetaVersion2(


xml.XmlElement metadataMetaNode) {
EpubMetadataMeta result = EpubMetadataMeta();
metadataMetaNode.attributes
.forEach((xml.XmlAttribute metadataMetaNodeAttribute) {
String attributeValue = metadataMetaNodeAttribute.value;
switch (metadataMetaNodeAttribute.name.local.toLowerCase()) {
case "name":
result.Name = attributeValue;
break;
case "content":
result.Content = attributeValue;
break;
}
});
return result;
}

static EpubMetadataMeta readMetadataMetaVersion3(


xml.XmlElement metadataMetaNode) {
EpubMetadataMeta result = EpubMetadataMeta();
metadataMetaNode.attributes
.forEach((xml.XmlAttribute metadataMetaNodeAttribute) {
String attributeValue = metadataMetaNodeAttribute.value;
switch (metadataMetaNodeAttribute.name.local.toLowerCase()) {
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.text;
return result;
}

static Future<EpubPackage> readPackage(


Archive epubArchive, String rootFilePath) async {
ArchiveFile rootFileEntry = epubArchive.files.firstWhere(
(ArchiveFile testfile) => testfile.name == rootFilePath,
orElse: () => null);
if (rootFileEntry == null) {
throw Exception("EPUB parsing error: root file not found in archive.");
}
xml.XmlDocument containerDocument =
xml.parse(convert.utf8.decode(rootFileEntry.content));
String opfNamespace = "http://www.idpf.org/2007/opf";
xml.XmlElement packageNode = containerDocument
.findElements("package", namespace: opfNamespace)
.firstWhere((xml.XmlElement elem) => elem != null);
EpubPackage result = EpubPackage();
String epubVersionValue = packageNode.getAttribute("version");
if (epubVersionValue == "2.0") {
result.Version = EpubVersion.Epub2;
} else if (epubVersionValue == "3.0") {
result.Version = EpubVersion.Epub3;
} else {
throw Exception("Unsupported EPUB version: ${epubVersionValue}.");
}
xml.XmlElement metadataNode = packageNode
.findElements("metadata", namespace: opfNamespace)
.firstWhere((xml.XmlElement elem) => elem != null);
if (metadataNode == null) {
throw Exception("EPUB parsing error: metadata not found in the package.");
}
EpubMetadata metadata = readMetadata(metadataNode, result.Version);
result.Metadata = metadata;
xml.XmlElement manifestNode = packageNode
.findElements("manifest", namespace: opfNamespace)
.firstWhere((xml.XmlElement elem) => elem != null);
if (manifestNode == null) {
throw Exception("EPUB parsing error: manifest not found in the package.");
}
EpubManifest manifest = readManifest(manifestNode);
result.Manifest = manifest;

xml.XmlElement spineNode = packageNode


.findElements("spine", namespace: opfNamespace)
.firstWhere((xml.XmlElement elem) => elem != null);
if (spineNode == null) {
throw Exception("EPUB parsing error: spine not found in the package.");
}
EpubSpine spine = readSpine(spineNode);
result.Spine = spine;
xml.XmlElement guideNode = packageNode
.findElements("guide", namespace: opfNamespace)
.firstWhere((xml.XmlElement elem) => elem != null, orElse: () => null);
if (guideNode != null) {
EpubGuide guide = readGuide(guideNode);
result.Guide = guide;
}
return result;
}

static EpubSpine readSpine(xml.XmlElement spineNode) {


EpubSpine result = EpubSpine();
result.Items = List<EpubSpineItemRef>();
String tocAttribute = spineNode.getAttribute("toc");
result.TableOfContents = tocAttribute;
spineNode.children
.whereType<xml.XmlElement>()
.forEach((xml.XmlElement spineItemNode) {
if (spineItemNode.name.local.toLowerCase() == "itemref") {
EpubSpineItemRef spineItemRef = EpubSpineItemRef();
String idRefAttribute = spineItemNode.getAttribute("idref");
if (idRefAttribute == null || idRefAttribute.isEmpty) {
throw Exception("Incorrect EPUB spine: item ID ref is missing");
}
spineItemRef.IdRef = idRefAttribute;
String linearAttribute = spineItemNode.getAttribute("linear");
spineItemRef.IsLinear =
linearAttribute == null || (linearAttribute.toLowerCase() == "no");
result.Items.add(spineItemRef);
}
});
return result;
}
}

import 'dart:async';

import 'package:archive/archive.dart';
import 'package:dart2_constant/convert.dart' as convert;
import 'package:nxbxaydung/vhmt_epub_reader/epub/src/schema/opf/epub_version.dart';
import 'package:xml/xml.dart' as xml;

import '../schema/navigation/epub_metadata.dart';
import '../schema/navigation/epub_navigation.dart';
import '../schema/navigation/epub_navigation_doc_author.dart';
import '../schema/navigation/epub_navigation_doc_title.dart';
import '../schema/navigation/epub_navigation_head.dart';
import '../schema/navigation/epub_navigation_head_meta.dart';
import '../schema/navigation/epub_navigation_label.dart';
import '../schema/navigation/epub_navigation_list.dart';
import '../schema/navigation/epub_navigation_map.dart';
import '../schema/navigation/epub_navigation_page_list.dart';
import '../schema/navigation/epub_navigation_page_target.dart';
import '../schema/navigation/epub_navigation_page_target_type.dart';
import '../schema/navigation/epub_navigation_point.dart';
import '../schema/navigation/epub_navigation_target.dart';
import '../schema/opf/epub_manifest_item.dart';
import '../schema/opf/epub_package.dart';
import '../utils/enum_from_string.dart';
import '../utils/zip_path_utils.dart';

class NavigationReader {
static Future<EpubNavigation> readNavigation(Archive epubArchive,
String contentDirectoryPath, EpubPackage package) async {
EpubNavigation result = EpubNavigation();
String tocId = package.Spine.TableOfContents;
if (tocId == null || tocId.isEmpty) {
if (package.Version == EpubVersion.Epub2) {
throw Exception("EPUB parsing error: TOC ID is empty.");
}
return null;
}

EpubManifestItem tocManifestItem = package.Manifest.Items.firstWhere(


(EpubManifestItem item) => item.Id.toLowerCase() == tocId.toLowerCase(),
orElse: () => null);
if (tocManifestItem == null) {
throw Exception(
"EPUB parsing error: TOC item $tocId not found in EPUB manifest.");
}

String tocFileEntryPath =
ZipPathUtils.combine(contentDirectoryPath, tocManifestItem.Href);
ArchiveFile tocFileEntry = epubArchive.files.firstWhere(
(ArchiveFile file) =>
file.name.toLowerCase() == tocFileEntryPath.toLowerCase(),
orElse: () => null);
if (tocFileEntry == null) {
throw Exception(
"EPUB parsing error: TOC file $tocFileEntryPath not found in archive.");
}

xml.XmlDocument containerDocument =
xml.parse(convert.utf8.decode(tocFileEntry.content));

String ncxNamespace = "http://www.daisy.org/z3986/2005/ncx/";


xml.XmlElement ncxNode = containerDocument
.findAllElements("ncx", namespace: ncxNamespace)
.firstWhere((xml.XmlElement elem) => elem != null, orElse: () => null);
if (ncxNode == null) {
throw Exception(
"EPUB parsing error: TOC file does not contain ncx element.");
}

xml.XmlElement headNode = ncxNode


.findAllElements("head", namespace: ncxNamespace)
.firstWhere((xml.XmlElement elem) => elem != null, orElse: () => null);
if (headNode == null) {
throw Exception(
"EPUB parsing error: TOC file does not contain head element.");
}

EpubNavigationHead navigationHead = readNavigationHead(headNode);


result.Head = navigationHead;
xml.XmlElement docTitleNode = ncxNode
.findElements("docTitle", namespace: ncxNamespace)
.firstWhere((xml.XmlElement elem) => elem != null, orElse: () => null);
if (docTitleNode == null) {
throw Exception(
"EPUB parsing error: TOC file does not contain docTitle element.");
}

EpubNavigationDocTitle navigationDocTitle =
readNavigationDocTitle(docTitleNode);
result.DocTitle = navigationDocTitle;
result.DocAuthors = List<EpubNavigationDocAuthor>();
ncxNode
.findElements("docAuthor", namespace: ncxNamespace)
.forEach((xml.XmlElement docAuthorNode) {
EpubNavigationDocAuthor navigationDocAuthor =
readNavigationDocAuthor(docAuthorNode);
result.DocAuthors.add(navigationDocAuthor);
});

xml.XmlElement navMapNode = ncxNode


.findElements("navMap", namespace: ncxNamespace)
.firstWhere((xml.XmlElement elem) => elem != null, orElse: () => null);
if (navMapNode == null) {
throw Exception(
"EPUB parsing error: TOC file does not contain navMap element.");
}

EpubNavigationMap navMap = readNavigationMap(navMapNode);


result.NavMap = navMap;
xml.XmlElement pageListNode = ncxNode
.findElements("pageList", namespace: ncxNamespace)
.firstWhere((xml.XmlElement elem) => elem != null, orElse: () => null);
if (pageListNode != null) {
EpubNavigationPageList pageList = readNavigationPageList(pageListNode);
result.PageList = pageList;
}

result.NavLists = List<EpubNavigationList>();
ncxNode
.findElements("navList", namespace: ncxNamespace)
.forEach((xml.XmlElement navigationListNode) {
EpubNavigationList navigationList =
readNavigationList(navigationListNode);
result.NavLists.add(navigationList);
});

return result;
}

static EpubNavigationContent readNavigationContent(


xml.XmlElement navigationContentNode) {
EpubNavigationContent result = EpubNavigationContent();
navigationContentNode.attributes
.forEach((xml.XmlAttribute navigationContentNodeAttribute) {
String attributeValue = navigationContentNodeAttribute.value;
switch (navigationContentNodeAttribute.name.local.toLowerCase()) {
case "id":
result.Id = attributeValue;
break;
case "src":
result.Source = attributeValue;
break;
}
});
if (result.Source == null || result.Source.isEmpty) {
throw Exception(
"Incorrect EPUB navigation content: content source is missing.");
}

return result;
}
static EpubNavigationDocAuthor readNavigationDocAuthor(
xml.XmlElement docAuthorNode) {
EpubNavigationDocAuthor result = EpubNavigationDocAuthor();
result.Authors = List<String>();
docAuthorNode.children
.whereType<xml.XmlElement>()
.forEach((xml.XmlElement textNode) {
if (textNode.name.local.toLowerCase() == "text") {
result.Authors.add(textNode.text);
}
});
return result;
}

static EpubNavigationDocTitle readNavigationDocTitle(


xml.XmlElement docTitleNode) {
EpubNavigationDocTitle result = EpubNavigationDocTitle();
result.Titles = List<String>();
docTitleNode.children
.whereType<xml.XmlElement>()
.forEach((xml.XmlElement textNode) {
if (textNode.name.local.toLowerCase() == "text") {
result.Titles.add(textNode.text);
}
});
return result;
}

static EpubNavigationHead readNavigationHead(xml.XmlElement headNode) {


EpubNavigationHead result = EpubNavigationHead();
result.Metadata = List<EpubNavigationHeadMeta>();

headNode.children
.whereType<xml.XmlElement>()
.forEach((xml.XmlElement metaNode) {
if (metaNode.name.local.toLowerCase() == "meta") {
EpubNavigationHeadMeta meta = EpubNavigationHeadMeta();
metaNode.attributes.forEach((xml.XmlAttribute metaNodeAttribute) {
String attributeValue = metaNodeAttribute.value;
switch (metaNodeAttribute.name.local.toLowerCase()) {
case "name":
meta.Name = attributeValue;
break;
case "content":
meta.Content = attributeValue;
break;
case "scheme":
meta.Scheme = attributeValue;
break;
}
});

if (meta.Name == null || meta.Name.isEmpty) {


throw Exception(
"Incorrect EPUB navigation meta: meta name is missing.");
}
if (meta.Content == null) {
throw Exception(
"Incorrect EPUB navigation meta: meta content is missing.");
}

result.Metadata.add(meta);
}
});
return result;
}

static EpubNavigationLabel readNavigationLabel(


xml.XmlElement navigationLabelNode) {
EpubNavigationLabel result = EpubNavigationLabel();

xml.XmlElement navigationLabelTextNode = navigationLabelNode


.findElements("text", namespace: navigationLabelNode.name.namespaceUri)
.firstWhere((xml.XmlElement elem) => elem != null, orElse: () => null);
if (navigationLabelTextNode == null) {
throw Exception(
"Incorrect EPUB navigation label: label text element is missing.");
}

result.Text = navigationLabelTextNode.text;

return result;
}

static EpubNavigationList readNavigationList(


xml.XmlElement navigationListNode) {
EpubNavigationList result = EpubNavigationList();
navigationListNode.attributes
.forEach((xml.XmlAttribute navigationListNodeAttribute) {
String attributeValue = navigationListNodeAttribute.value;
switch (navigationListNodeAttribute.name.local.toLowerCase()) {
case "id":
result.Id = attributeValue;
break;
case "class":
result.Class = attributeValue;
break;
}
});
navigationListNode.children
.whereType<xml.XmlElement>()
.forEach((xml.XmlElement navigationListChildNode) {
switch (navigationListChildNode.name.local.toLowerCase()) {
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.isEmpty) {
throw Exception(
"Incorrect EPUB navigation page target: at least one navLabel element is required.");
}
return result;
}

static EpubNavigationMap readNavigationMap(xml.XmlElement navigationMapNode) {


EpubNavigationMap result = EpubNavigationMap();
result.Points = List<EpubNavigationPoint>();
navigationMapNode.children
.whereType<xml.XmlElement>()
.forEach((xml.XmlElement navigationPointNode) {
if (navigationPointNode.name.local.toLowerCase() == "navpoint") {
EpubNavigationPoint navigationPoint =
readNavigationPoint(navigationPointNode);
result.Points.add(navigationPoint);
}
});
return result;
}

static EpubNavigationPageList readNavigationPageList(


xml.XmlElement navigationPageListNode) {
EpubNavigationPageList result = EpubNavigationPageList();
result.Targets = List<EpubNavigationPageTarget>();
navigationPageListNode.children
.whereType<xml.XmlElement>()
.forEach((xml.XmlElement pageTargetNode) {
if (pageTargetNode.name.local == "pageTarget") {
EpubNavigationPageTarget pageTarget =
readNavigationPageTarget(pageTargetNode);
result.Targets.add(pageTarget);
}
});

return result;
}

static EpubNavigationPageTarget readNavigationPageTarget(


xml.XmlElement navigationPageTargetNode) {
EpubNavigationPageTarget result = EpubNavigationPageTarget();
result.NavigationLabels = List<EpubNavigationLabel>();
navigationPageTargetNode.attributes
.forEach((xml.XmlAttribute navigationPageTargetNodeAttribute) {
String attributeValue = navigationPageTargetNodeAttribute.value;
switch (navigationPageTargetNodeAttribute.name.local.toLowerCase()) {
case "id":
result.Id = attributeValue;
break;
case "value":
result.Value = attributeValue;
break;
case "type":
var converter = EnumFromString<EpubNavigationPageTargetType>(
EpubNavigationPageTargetType.values);
EpubNavigationPageTargetType type = converter.get(attributeValue);
result.Type = type;
break;
case "class":
result.Class = attributeValue;
break;
case "playorder":
result.PlayOrder = attributeValue;
break;
}
});
if (result.Type == EpubNavigationPageTargetType.UNDEFINED) {
throw Exception(
"Incorrect EPUB navigation page target: page target type is missing.");
}

navigationPageTargetNode.children
.whereType<xml.XmlElement>()
.forEach((xml.XmlElement navigationPageTargetChildNode) {
switch (navigationPageTargetChildNode.name.local.toLowerCase()) {
case "navlabel":
EpubNavigationLabel navigationLabel =
readNavigationLabel(navigationPageTargetChildNode);
result.NavigationLabels.add(navigationLabel);
break;
case "content":
EpubNavigationContent content =
readNavigationContent(navigationPageTargetChildNode);
result.Content = content;
break;
}
});
if (result.NavigationLabels.isEmpty) {
throw Exception(
"Incorrect EPUB navigation page target: at least one navLabel element is required.");
}

return result;
}

static EpubNavigationPoint readNavigationPoint(


xml.XmlElement navigationPointNode) {
EpubNavigationPoint result = EpubNavigationPoint();
navigationPointNode.attributes
.forEach((xml.XmlAttribute navigationPointNodeAttribute) {
String attributeValue = navigationPointNodeAttribute.value;
switch (navigationPointNodeAttribute.name.local.toLowerCase()) {
case "id":
result.Id = attributeValue;
break;
case "class":
result.Class = attributeValue;
break;
case "playorder":
result.PlayOrder = attributeValue;
break;
}
});
if (result.Id == null || result.Id.isEmpty) {
throw Exception("Incorrect EPUB navigation point: point ID is missing.");
}

result.NavigationLabels = List<EpubNavigationLabel>();
result.ChildNavigationPoints = List<EpubNavigationPoint>();
navigationPointNode.children
.whereType<xml.XmlElement>()
.forEach((xml.XmlElement navigationPointChildNode) {
switch (navigationPointChildNode.name.local.toLowerCase()) {
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.isEmpty) {
throw Exception(
"EPUB parsing error: navigation point ${result.Id} should contain at least one navigation label.");
}
if (result.Content == null) {
throw Exception(
"EPUB parsing error: navigation point ${result.Id} should contain content.");
}

return result;
}

static EpubNavigationTarget readNavigationTarget(


xml.XmlElement navigationTargetNode) {
EpubNavigationTarget result = EpubNavigationTarget();
navigationTargetNode.attributes
.forEach((xml.XmlAttribute navigationPageTargetNodeAttribute) {
String attributeValue = navigationPageTargetNodeAttribute.value;
switch (navigationPageTargetNodeAttribute.name.local.toLowerCase()) {
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 (result.Id == null || result.Id.isEmpty) {
throw Exception(
"Incorrect EPUB navigation target: navigation target ID is missing.");
}

navigationTargetNode.children
.whereType<xml.XmlElement>()
.forEach((xml.XmlElement navigationTargetChildNode) {
switch (navigationTargetChildNode.name.local.toLowerCase()) {
case "navlabel":
EpubNavigationLabel navigationLabel =
readNavigationLabel(navigationTargetChildNode);
result.NavigationLabels.add(navigationLabel);
break;
case "content":
EpubNavigationContent content =
readNavigationContent(navigationTargetChildNode);
result.Content = content;
break;
}
});
if (result.NavigationLabels.isEmpty) {
throw Exception(
"Incorrect EPUB navigation target: at least one navLabel element is required.");
}

return result;
}
}

class ContentReader {
static EpubContentRef parseContentMap(EpubBookRef bookRef) {
EpubContentRef result = EpubContentRef();
result.Html = Map<String, EpubTextContentFileRef>();
result.Css = Map<String, EpubTextContentFileRef>();
result.Images = Map<String, EpubByteContentFileRef>();
result.Fonts = Map<String, EpubByteContentFileRef>();
result.AllFiles = Map<String, EpubContentFileRef>();

bookRef.Schema.Package.Manifest.Items
.forEach((EpubManifestItem manifestItem) {
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 =
EpubTextContentFileRef(bookRef);
{
epubTextContentFile.FileName = Uri.decodeFull(fileName);
epubTextContentFile.ContentMimeType = contentMimeType;
epubTextContentFile.ContentType = contentType;
}
switch (contentType) {
case EpubContentType.XHTML_1_1:
result.Html[fileName] = epubTextContentFile;
break;
case EpubContentType.CSS:
result.Css[fileName] = epubTextContentFile;
break;
case EpubContentType.DTBOOK:
case EpubContentType.DTBOOK_NCX:
case EpubContentType.OEB1_DOCUMENT:
case EpubContentType.XML:
case EpubContentType.OEB1_CSS:
case EpubContentType.IMAGE_GIF:
case EpubContentType.IMAGE_JPEG:
case EpubContentType.IMAGE_PNG:
case EpubContentType.IMAGE_SVG:
case EpubContentType.FONT_TRUETYPE:
case EpubContentType.FONT_OPENTYPE:
case EpubContentType.OTHER:
break;
}
result.AllFiles[fileName] = epubTextContentFile;
break;
default:
EpubByteContentFileRef epubByteContentFile =
EpubByteContentFileRef(bookRef);
{
epubByteContentFile.FileName = Uri.decodeFull(fileName);
epubByteContentFile.ContentMimeType = contentMimeType;
epubByteContentFile.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;
case EpubContentType.CSS:
case EpubContentType.XHTML_1_1:
case EpubContentType.DTBOOK:
case EpubContentType.DTBOOK_NCX:
case EpubContentType.OEB1_DOCUMENT:
case EpubContentType.XML:
case EpubContentType.OEB1_CSS:
case EpubContentType.OTHER:
break;
}
result.AllFiles[fileName] = epubByteContentFile;
break;
}
});
return result;
}

static EpubContentType getContentTypeByContentMimeType(


String contentMimeType) {
switch (contentMimeType.toLowerCase()) {
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;
}
}
}

// ignore_for_file: deprecated_member_use

import '../ref_entities/epub_book_ref.dart';
import '../ref_entities/epub_chapter_ref.dart';
import '../ref_entities/epub_text_content_file_ref.dart';
import '../schema/navigation/epub_navigation_point.dart';

class ChapterReader {
static List<EpubChapterRef> getChapters(EpubBookRef bookRef) {
if (bookRef.Schema.Navigation == null) {
return List<EpubChapterRef>();
}
return getChaptersImpl(bookRef, bookRef.Schema.Navigation.NavMap.Points);
}

static List<EpubChapterRef> getChaptersImpl(


EpubBookRef bookRef, List<EpubNavigationPoint> navigationPoints) {
List<EpubChapterRef> result = 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);
}
contentFileName = Uri.decodeFull(contentFileName);
EpubTextContentFileRef htmlContentFileRef;
if (!bookRef.Content.Html.containsKey(contentFileName)) {
throw Exception(
"Incorrect EPUB manifest: item with href = \"$contentFileName\" is missing.");
}

htmlContentFileRef = bookRef.Content.Html[contentFileName];
EpubChapterRef chapterRef = 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;
}
}

import 'dart:async';

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

import '../ref_entities/epub_book_ref.dart';
import '../ref_entities/epub_byte_content_file_ref.dart';
import '../schema/opf/epub_manifest_item.dart';
import '../schema/opf/epub_metadata_meta.dart';

class BookCoverReader {
static Future<images.Image> readBookCover(EpubBookRef bookRef) async {
List<EpubMetadataMeta> metaItems =
bookRef.Schema.Package.Metadata.MetaItems;
if (metaItems == null || metaItems.isEmpty) return null;

EpubMetadataMeta coverMetaItem = metaItems.firstWhere(


(EpubMetadataMeta metaItem) =>
metaItem.Name != null && metaItem.Name.toLowerCase() == "cover",
orElse: () => null);
if (coverMetaItem == null) return null;
if (coverMetaItem.Content == null || coverMetaItem.Content.isEmpty) {
throw 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 Exception(
"Incorrect EPUB manifest: item with ID = \"${coverMetaItem.Content}\" is missing.");
}

EpubByteContentFileRef coverImageContentFileRef;
if (!bookRef.Content.Images.containsKey(coverManifestItem.Href)) {
throw Exception(
"Incorrect EPUB manifest: item with href = \"${coverManifestItem.Href}\" is missing.");
}

coverImageContentFileRef = bookRef.Content.Images[coverManifestItem.Href];
List<int> coverImageContent =
await coverImageContentFileRef.readContentAsBytes();
images.Image retval = images.decodeImage(coverImageContent);
return retval;
}
}

You might also like