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