summaryrefslogtreecommitdiff
path: root/lib/epub.js/src/packaging.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/epub.js/src/packaging.js')
-rw-r--r--lib/epub.js/src/packaging.js372
1 files changed, 372 insertions, 0 deletions
diff --git a/lib/epub.js/src/packaging.js b/lib/epub.js/src/packaging.js
new file mode 100644
index 0000000..396b5d4
--- /dev/null
+++ b/lib/epub.js/src/packaging.js
@@ -0,0 +1,372 @@
+import {qs, qsa, qsp, indexOfElementNode} from "./utils/core";
+
+/**
+ * Open Packaging Format Parser
+ * @class
+ * @param {document} packageDocument OPF XML
+ */
+class Packaging {
+ constructor(packageDocument) {
+ this.manifest = {};
+ this.navPath = '';
+ this.ncxPath = '';
+ this.coverPath = '';
+ this.spineNodeIndex = 0;
+ this.spine = [];
+ this.metadata = {};
+
+ if (packageDocument) {
+ this.parse(packageDocument);
+ }
+ }
+
+ /**
+ * Parse OPF XML
+ * @param {document} packageDocument OPF XML
+ * @return {object} parsed package parts
+ */
+ parse(packageDocument){
+ var metadataNode, manifestNode, spineNode;
+
+ if(!packageDocument) {
+ throw new Error("Package File Not Found");
+ }
+
+ metadataNode = qs(packageDocument, "metadata");
+ if(!metadataNode) {
+ throw new Error("No Metadata Found");
+ }
+
+ manifestNode = qs(packageDocument, "manifest");
+ if(!manifestNode) {
+ throw new Error("No Manifest Found");
+ }
+
+ spineNode = qs(packageDocument, "spine");
+ if(!spineNode) {
+ throw new Error("No Spine Found");
+ }
+
+ this.manifest = this.parseManifest(manifestNode);
+ this.navPath = this.findNavPath(manifestNode);
+ this.ncxPath = this.findNcxPath(manifestNode, spineNode);
+ this.coverPath = this.findCoverPath(packageDocument);
+
+ this.spineNodeIndex = indexOfElementNode(spineNode);
+
+ this.spine = this.parseSpine(spineNode, this.manifest);
+
+ this.uniqueIdentifier = this.findUniqueIdentifier(packageDocument);
+ this.metadata = this.parseMetadata(metadataNode);
+
+ this.metadata.direction = spineNode.getAttribute("page-progression-direction");
+
+ return {
+ "metadata" : this.metadata,
+ "spine" : this.spine,
+ "manifest" : this.manifest,
+ "navPath" : this.navPath,
+ "ncxPath" : this.ncxPath,
+ "coverPath": this.coverPath,
+ "spineNodeIndex" : this.spineNodeIndex
+ };
+ }
+
+ /**
+ * Parse Metadata
+ * @private
+ * @param {node} xml
+ * @return {object} metadata
+ */
+ parseMetadata(xml){
+ var metadata = {};
+
+ metadata.title = this.getElementText(xml, "title");
+ metadata.creator = this.getElementText(xml, "creator");
+ metadata.description = this.getElementText(xml, "description");
+
+ metadata.pubdate = this.getElementText(xml, "date");
+
+ metadata.publisher = this.getElementText(xml, "publisher");
+
+ metadata.identifier = this.getElementText(xml, "identifier");
+ metadata.language = this.getElementText(xml, "language");
+ metadata.rights = this.getElementText(xml, "rights");
+
+ metadata.modified_date = this.getPropertyText(xml, "dcterms:modified");
+
+ metadata.layout = this.getPropertyText(xml, "rendition:layout");
+ metadata.orientation = this.getPropertyText(xml, "rendition:orientation");
+ metadata.flow = this.getPropertyText(xml, "rendition:flow");
+ metadata.viewport = this.getPropertyText(xml, "rendition:viewport");
+ metadata.media_active_class = this.getPropertyText(xml, "media:active-class");
+ metadata.spread = this.getPropertyText(xml, "rendition:spread");
+ // metadata.page_prog_dir = packageXml.querySelector("spine").getAttribute("page-progression-direction");
+
+ return metadata;
+ }
+
+ /**
+ * Parse Manifest
+ * @private
+ * @param {node} manifestXml
+ * @return {object} manifest
+ */
+ parseManifest(manifestXml){
+ var manifest = {};
+
+ //-- Turn items into an array
+ // var selected = manifestXml.querySelectorAll("item");
+ var selected = qsa(manifestXml, "item");
+ var items = Array.prototype.slice.call(selected);
+
+ //-- Create an object with the id as key
+ items.forEach(function(item){
+ var id = item.getAttribute("id"),
+ href = item.getAttribute("href") || "",
+ type = item.getAttribute("media-type") || "",
+ overlay = item.getAttribute("media-overlay") || "",
+ properties = item.getAttribute("properties") || "";
+
+ manifest[id] = {
+ "href" : href,
+ // "url" : href,
+ "type" : type,
+ "overlay" : overlay,
+ "properties" : properties.length ? properties.split(" ") : []
+ };
+
+ });
+
+ return manifest;
+
+ }
+
+ /**
+ * Parse Spine
+ * @private
+ * @param {node} spineXml
+ * @param {Packaging.manifest} manifest
+ * @return {object} spine
+ */
+ parseSpine(spineXml, manifest){
+ var spine = [];
+
+ var selected = qsa(spineXml, "itemref");
+ var items = Array.prototype.slice.call(selected);
+
+ // var epubcfi = new EpubCFI();
+
+ //-- Add to array to mantain ordering and cross reference with manifest
+ items.forEach(function(item, index){
+ var idref = item.getAttribute("idref");
+ // var cfiBase = epubcfi.generateChapterComponent(spineNodeIndex, index, Id);
+ var props = item.getAttribute("properties") || "";
+ var propArray = props.length ? props.split(" ") : [];
+ // var manifestProps = manifest[Id].properties;
+ // var manifestPropArray = manifestProps.length ? manifestProps.split(" ") : [];
+
+ var itemref = {
+ "idref" : idref,
+ "linear" : item.getAttribute("linear") || "yes",
+ "properties" : propArray,
+ // "href" : manifest[Id].href,
+ // "url" : manifest[Id].url,
+ "index" : index
+ // "cfiBase" : cfiBase
+ };
+ spine.push(itemref);
+ });
+
+ return spine;
+ }
+
+ /**
+ * Find Unique Identifier
+ * @private
+ * @param {node} packageXml
+ * @return {string} Unique Identifier text
+ */
+ findUniqueIdentifier(packageXml){
+ var uniqueIdentifierId = packageXml.documentElement.getAttribute("unique-identifier");
+ if (! uniqueIdentifierId) {
+ return "";
+ }
+ var identifier = packageXml.getElementById(uniqueIdentifierId);
+ if (! identifier) {
+ return "";
+ }
+
+ if (identifier.localName === "identifier" && identifier.namespaceURI === "http://purl.org/dc/elements/1.1/") {
+ return identifier.childNodes.length > 0 ? identifier.childNodes[0].nodeValue.trim() : "";
+ }
+
+ return "";
+ }
+
+ /**
+ * Find TOC NAV
+ * @private
+ * @param {element} manifestNode
+ * @return {string}
+ */
+ findNavPath(manifestNode){
+ // Find item with property "nav"
+ // Should catch nav irregardless of order
+ // var node = manifestNode.querySelector("item[properties$='nav'], item[properties^='nav '], item[properties*=' nav ']");
+ var node = qsp(manifestNode, "item", {"properties":"nav"});
+ return node ? node.getAttribute("href") : false;
+ }
+
+ /**
+ * Find TOC NCX
+ * media-type="application/x-dtbncx+xml" href="toc.ncx"
+ * @private
+ * @param {element} manifestNode
+ * @param {element} spineNode
+ * @return {string}
+ */
+ findNcxPath(manifestNode, spineNode){
+ // var node = manifestNode.querySelector("item[media-type='application/x-dtbncx+xml']");
+ var node = qsp(manifestNode, "item", {"media-type":"application/x-dtbncx+xml"});
+ var tocId;
+
+ // If we can't find the toc by media-type then try to look for id of the item in the spine attributes as
+ // according to http://www.idpf.org/epub/20/spec/OPF_2.0.1_draft.htm#Section2.4.1.2,
+ // "The item that describes the NCX must be referenced by the spine toc attribute."
+ if (!node) {
+ tocId = spineNode.getAttribute("toc");
+ if(tocId) {
+ // node = manifestNode.querySelector("item[id='" + tocId + "']");
+ node = manifestNode.querySelector(`#${tocId}`);
+ }
+ }
+
+ return node ? node.getAttribute("href") : false;
+ }
+
+ /**
+ * Find the Cover Path
+ * <item properties="cover-image" id="ci" href="cover.svg" media-type="image/svg+xml" />
+ * Fallback for Epub 2.0
+ * @private
+ * @param {node} packageXml
+ * @return {string} href
+ */
+ findCoverPath(packageXml){
+ var pkg = qs(packageXml, "package");
+ var epubVersion = pkg.getAttribute("version");
+
+ // Try parsing cover with epub 3.
+ // var node = packageXml.querySelector("item[properties='cover-image']");
+ var node = qsp(packageXml, "item", {"properties":"cover-image"});
+ if (node) return node.getAttribute("href");
+
+ // Fallback to epub 2.
+ var metaCover = qsp(packageXml, "meta", {"name":"cover"});
+
+ if (metaCover) {
+ var coverId = metaCover.getAttribute("content");
+ // var cover = packageXml.querySelector("item[id='" + coverId + "']");
+ var cover = packageXml.getElementById(coverId);
+ return cover ? cover.getAttribute("href") : "";
+ }
+ else {
+ return false;
+ }
+ }
+
+ /**
+ * Get text of a namespaced element
+ * @private
+ * @param {node} xml
+ * @param {string} tag
+ * @return {string} text
+ */
+ getElementText(xml, tag){
+ var found = xml.getElementsByTagNameNS("http://purl.org/dc/elements/1.1/", tag);
+ var el;
+
+ if(!found || found.length === 0) return "";
+
+ el = found[0];
+
+ if(el.childNodes.length){
+ return el.childNodes[0].nodeValue;
+ }
+
+ return "";
+
+ }
+
+ /**
+ * Get text by property
+ * @private
+ * @param {node} xml
+ * @param {string} property
+ * @return {string} text
+ */
+ getPropertyText(xml, property){
+ var el = qsp(xml, "meta", {"property":property});
+
+ if(el && el.childNodes.length){
+ return el.childNodes[0].nodeValue;
+ }
+
+ return "";
+ }
+
+ /**
+ * Load JSON Manifest
+ * @param {document} packageDocument OPF XML
+ * @return {object} parsed package parts
+ */
+ load(json) {
+ this.metadata = json.metadata;
+
+ let spine = json.readingOrder || json.spine;
+ this.spine = spine.map((item, index) =>{
+ item.index = index;
+ item.linear = item.linear || "yes";
+ return item;
+ });
+
+ json.resources.forEach((item, index) => {
+ this.manifest[index] = item;
+
+ if (item.rel && item.rel[0] === "cover") {
+ this.coverPath = item.href;
+ }
+ });
+
+ this.spineNodeIndex = 0;
+
+ this.toc = json.toc.map((item, index) =>{
+ item.label = item.title;
+ return item;
+ });
+
+ return {
+ "metadata" : this.metadata,
+ "spine" : this.spine,
+ "manifest" : this.manifest,
+ "navPath" : this.navPath,
+ "ncxPath" : this.ncxPath,
+ "coverPath": this.coverPath,
+ "spineNodeIndex" : this.spineNodeIndex,
+ "toc" : this.toc
+ };
+ }
+
+ destroy() {
+ this.manifest = undefined;
+ this.navPath = undefined;
+ this.ncxPath = undefined;
+ this.coverPath = undefined;
+ this.spineNodeIndex = undefined;
+ this.spine = undefined;
+ this.metadata = undefined;
+ }
+}
+
+export default Packaging;