From 4fd9b8f2b5a98bfcde57970b48fed2488a80f356 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Fri, 17 Sep 2021 21:53:37 +0300 Subject: add in master snapshot of epubjs --- lib/epub.js/src/navigation.js | 356 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 lib/epub.js/src/navigation.js (limited to 'lib/epub.js/src/navigation.js') 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; -- cgit v1.2.3