summaryrefslogtreecommitdiff
path: root/lib/epub.js/src/navigation.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/epub.js/src/navigation.js')
-rw-r--r--lib/epub.js/src/navigation.js356
1 files changed, 356 insertions, 0 deletions
diff --git a/lib/epub.js/src/navigation.js b/lib/epub.js/src/navigation.js
new file mode 100644
index 0000000..8d593a8
--- /dev/null
+++ b/lib/epub.js/src/navigation.js
@@ -0,0 +1,356 @@
+import {qs, qsa, querySelectorByType, filterChildren, getParentByTagName} from "./utils/core";
+
+/**
+ * Navigation Parser
+ * @param {document} xml navigation html / xhtml / ncx
+ */
+class Navigation {
+ constructor(xml) {
+ this.toc = [];
+ this.tocByHref = {};
+ this.tocById = {};
+
+ this.landmarks = [];
+ this.landmarksByType = {};
+
+ this.length = 0;
+ if (xml) {
+ this.parse(xml);
+ }
+ }
+
+ /**
+ * Parse out the navigation items
+ * @param {document} xml navigation html / xhtml / ncx
+ */
+ parse(xml) {
+ let isXml = xml.nodeType;
+ let html;
+ let ncx;
+
+ if (isXml) {
+ html = qs(xml, "html");
+ ncx = qs(xml, "ncx");
+ }
+
+ if (!isXml) {
+ this.toc = this.load(xml);
+ } else if(html) {
+ this.toc = this.parseNav(xml);
+ this.landmarks = this.parseLandmarks(xml);
+ } else if(ncx){
+ this.toc = this.parseNcx(xml);
+ }
+
+ this.length = 0;
+
+ this.unpack(this.toc);
+ }
+
+ /**
+ * Unpack navigation items
+ * @private
+ * @param {array} toc
+ */
+ unpack(toc) {
+ var item;
+
+ for (var i = 0; i < toc.length; i++) {
+ item = toc[i];
+
+ if (item.href) {
+ this.tocByHref[item.href] = i;
+ }
+
+ if (item.id) {
+ this.tocById[item.id] = i;
+ }
+
+ this.length++;
+
+ if (item.subitems.length) {
+ this.unpack(item.subitems);
+ }
+ }
+
+ }
+
+ /**
+ * Get an item from the navigation
+ * @param {string} target
+ * @return {object} navItem
+ */
+ get(target) {
+ var index;
+
+ if(!target) {
+ return this.toc;
+ }
+
+ if(target.indexOf("#") === 0) {
+ index = this.tocById[target.substring(1)];
+ } else if(target in this.tocByHref){
+ index = this.tocByHref[target];
+ }
+
+ return this.getByIndex(target, index, this.toc);
+ }
+
+ /**
+ * Get an item from navigation subitems recursively by index
+ * @param {string} target
+ * @param {number} index
+ * @param {array} navItems
+ * @return {object} navItem
+ */
+ getByIndex(target, index, navItems) {
+ if (navItems.length === 0) {
+ return;
+ }
+
+ const item = navItems[index];
+ if (item && (target === item.id || target === item.href)) {
+ return item;
+ } else {
+ let result;
+ for (let i = 0; i < navItems.length; ++i) {
+ result = this.getByIndex(target, index, navItems[i].subitems);
+ if (result) {
+ break;
+ }
+ }
+ return result;
+ }
+ }
+
+ /**
+ * Get a landmark by type
+ * List of types: https://idpf.github.io/epub-vocabs/structure/
+ * @param {string} type
+ * @return {object} landmarkItem
+ */
+ landmark(type) {
+ var index;
+
+ if(!type) {
+ return this.landmarks;
+ }
+
+ index = this.landmarksByType[type];
+
+ return this.landmarks[index];
+ }
+
+ /**
+ * Parse toc from a Epub > 3.0 Nav
+ * @private
+ * @param {document} navHtml
+ * @return {array} navigation list
+ */
+ parseNav(navHtml){
+ var navElement = querySelectorByType(navHtml, "nav", "toc");
+ var list = [];
+
+ if (!navElement) return list;
+
+ let navList = filterChildren(navElement, "ol", true);
+ if (!navList) return list;
+
+ list = this.parseNavList(navList);
+ return list;
+ }
+
+ /**
+ * Parses lists in the toc
+ * @param {document} navListHtml
+ * @param {string} parent id
+ * @return {array} navigation list
+ */
+ parseNavList(navListHtml, parent) {
+ const result = [];
+
+ if (!navListHtml) return result;
+ if (!navListHtml.children) return result;
+
+ for (let i = 0; i < navListHtml.children.length; i++) {
+ const item = this.navItem(navListHtml.children[i], parent);
+
+ if (item) {
+ result.push(item);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Create a navItem
+ * @private
+ * @param {element} item
+ * @return {object} navItem
+ */
+ navItem(item, parent) {
+ let id = item.getAttribute("id") || undefined;
+ let content = filterChildren(item, "a", true);
+
+ if (!content) {
+ return;
+ }
+
+ let src = content.getAttribute("href") || "";
+
+ if (!id) {
+ id = src;
+ }
+ let text = content.textContent || "";
+
+ let subitems = [];
+ let nested = filterChildren(item, "ol", true);
+ if (nested) {
+ subitems = this.parseNavList(nested, id);
+ }
+
+ return {
+ "id": id,
+ "href": src,
+ "label": text,
+ "subitems" : subitems,
+ "parent" : parent
+ };
+ }
+
+ /**
+ * Parse landmarks from a Epub > 3.0 Nav
+ * @private
+ * @param {document} navHtml
+ * @return {array} landmarks list
+ */
+ parseLandmarks(navHtml){
+ var navElement = querySelectorByType(navHtml, "nav", "landmarks");
+ var navItems = navElement ? qsa(navElement, "li") : [];
+ var length = navItems.length;
+ var i;
+ var list = [];
+ var item;
+
+ if(!navItems || length === 0) return list;
+
+ for (i = 0; i < length; ++i) {
+ item = this.landmarkItem(navItems[i]);
+ if (item) {
+ list.push(item);
+ this.landmarksByType[item.type] = i;
+ }
+ }
+
+ return list;
+ }
+
+ /**
+ * Create a landmarkItem
+ * @private
+ * @param {element} item
+ * @return {object} landmarkItem
+ */
+ landmarkItem(item){
+ let content = filterChildren(item, "a", true);
+
+ if (!content) {
+ return;
+ }
+
+ let type = content.getAttributeNS("http://www.idpf.org/2007/ops", "type") || undefined;
+ let href = content.getAttribute("href") || "";
+ let text = content.textContent || "";
+
+ return {
+ "href": href,
+ "label": text,
+ "type" : type
+ };
+ }
+
+ /**
+ * Parse from a Epub > 3.0 NC
+ * @private
+ * @param {document} navHtml
+ * @return {array} navigation list
+ */
+ parseNcx(tocXml){
+ var navPoints = qsa(tocXml, "navPoint");
+ var length = navPoints.length;
+ var i;
+ var toc = {};
+ var list = [];
+ var item, parent;
+
+ if(!navPoints || length === 0) return list;
+
+ for (i = 0; i < length; ++i) {
+ item = this.ncxItem(navPoints[i]);
+ toc[item.id] = item;
+ if(!item.parent) {
+ list.push(item);
+ } else {
+ parent = toc[item.parent];
+ parent.subitems.push(item);
+ }
+ }
+
+ return list;
+ }
+
+ /**
+ * Create a ncxItem
+ * @private
+ * @param {element} item
+ * @return {object} ncxItem
+ */
+ ncxItem(item){
+ var id = item.getAttribute("id") || false,
+ content = qs(item, "content"),
+ src = content.getAttribute("src"),
+ navLabel = qs(item, "navLabel"),
+ text = navLabel.textContent ? navLabel.textContent : "",
+ subitems = [],
+ parentNode = item.parentNode,
+ parent;
+
+ if(parentNode && (parentNode.nodeName === "navPoint" || parentNode.nodeName.split(':').slice(-1)[0] === "navPoint")) {
+ parent = parentNode.getAttribute("id");
+ }
+
+
+ return {
+ "id": id,
+ "href": src,
+ "label": text,
+ "subitems" : subitems,
+ "parent" : parent
+ };
+ }
+
+ /**
+ * Load Spine Items
+ * @param {object} json the items to be loaded
+ * @return {Array} navItems
+ */
+ load(json) {
+ return json.map(item => {
+ item.label = item.title;
+ item.subitems = item.children ? this.load(item.children) : [];
+ return item;
+ });
+ }
+
+ /**
+ * forEach pass through
+ * @param {Function} fn function to run on each item
+ * @return {method} forEach loop
+ */
+ forEach(fn) {
+ return this.toc.forEach(fn);
+ }
+}
+
+export default Navigation;