EPUBJS.Chapter = function(spineObject, store){ this.href = spineObject.href; this.absolute = spineObject.url; this.id = spineObject.id; this.spinePos = spineObject.index; this.cfiBase = spineObject.cfiBase; this.properties = spineObject.properties; this.manifestProperties = spineObject.manifestProperties; this.linear = spineObject.linear; this.pages = 1; this.store = store; this.epubcfi = new EPUBJS.EpubCFI(); this.deferred = new RSVP.defer(); this.loaded = this.deferred.promise; EPUBJS.Hooks.mixin(this); //-- Get pre-registered hooks for events this.getHooks("beforeChapterRender"); // Cached for replacement urls from storage this.caches = {}; }; EPUBJS.Chapter.prototype.load = function(_store){ var store = _store || this.store; var promise; // if(this.store && (!this.book.online || this.book.contained)) if(store){ promise = store.getXml(this.absolute); }else{ promise = EPUBJS.core.request(this.absolute, 'xml'); } promise.then(function(xml){ this.setDocument(xml); this.deferred.resolve(this); }.bind(this)); return promise; }; EPUBJS.Chapter.prototype.render = function(_store){ return this.load().then(function(doc){ var head = doc.querySelector('head'); var base = doc.createElement("base"); base.setAttribute("href", this.absolute); head.insertBefore(base, head.firstChild); this.contents = doc; return new RSVP.Promise(function (resolve, reject) { this.triggerHooks("beforeChapterRender", function () { resolve(doc); }.bind(this), this); }.bind(this)); }.bind(this)) .then(function(doc) { var serializer = new XMLSerializer(); var contents = serializer.serializeToString(doc); return contents; }.bind(this)); }; EPUBJS.Chapter.prototype.url = function(_store){ var deferred = new RSVP.defer(); var store = _store || this.store; var loaded; var chapter = this; var url; if(store){ if(!this.tempUrl) { store.getUrl(this.absolute).then(function(url){ chapter.tempUrl = url; deferred.resolve(url); }); } else { url = this.tempUrl; deferred.resolve(url); } }else{ url = this.absolute; deferred.resolve(url); } /* loaded = EPUBJS.core.request(url, 'xml', false); loaded.then(function(contents){ chapter.contents = contents; deferred.resolve(chapter.absolute); }, function(error){ deferred.reject(error); }); */ return deferred.promise; }; EPUBJS.Chapter.prototype.setPages = function(num){ this.pages = num; }; EPUBJS.Chapter.prototype.getPages = function(num){ return this.pages; }; EPUBJS.Chapter.prototype.getID = function(){ return this.ID; }; EPUBJS.Chapter.prototype.unload = function(store){ this.document = null; if(this.tempUrl && store) { store.revokeUrl(this.tempUrl); this.tempUrl = false; } }; EPUBJS.Chapter.prototype.setDocument = function(_document){ var uri = _document.namespaceURI; var doctype = _document.doctype; // Creates an empty document this.document = _document.implementation.createDocument( uri, null, null ); this.contents = this.document.importNode( _document.documentElement, //node to import true //clone its descendants ); this.document.appendChild(this.contents); // Fix to apply wgxpath to new document in IE if(!this.document.evaluate && document.evaluate) { this.document.evaluate = document.evaluate; } // this.deferred.resolve(this.contents); }; EPUBJS.Chapter.prototype.cfiFromRange = function(_range) { var range; var startXpath, endXpath; var startContainer, endContainer; var cleanTextContent, cleanEndTextContent; // Check for Contents if(!this.document) return; if(typeof document.evaluate != 'undefined') { startXpath = EPUBJS.core.getElementXPath(_range.startContainer); // console.log(startContainer) endXpath = EPUBJS.core.getElementXPath(_range.endContainer); startContainer = this.document.evaluate(startXpath, this.document, EPUBJS.core.nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if(!_range.collapsed) { endContainer = this.document.evaluate(endXpath, this.document, EPUBJS.core.nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; } range = this.document.createRange(); // Find Exact Range in original document if(startContainer) { try { range.setStart(startContainer, _range.startOffset); if(!_range.collapsed && endContainer) { range.setEnd(endContainer, _range.endOffset); } } catch (e) { console.log("missed"); startContainer = false; } } // Fuzzy Match if(!startContainer) { console.log("not found, try fuzzy match"); cleanStartTextContent = EPUBJS.core.cleanStringForXpath(_range.startContainer.textContent); startXpath = "//text()[contains(.," + cleanStartTextContent + ")]"; startContainer = this.document.evaluate(startXpath, this.document, EPUBJS.core.nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if(startContainer){ // console.log("Found with Fuzzy"); range.setStart(startContainer, _range.startOffset); if(!_range.collapsed) { cleanEndTextContent = EPUBJS.core.cleanStringForXpath(_range.endContainer.textContent); endXpath = "//text()[contains(.," + cleanEndTextContent + ")]"; endContainer = this.document.evaluate(endXpath, this.document, EPUBJS.core.nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if(endContainer) { range.setEnd(endContainer, _range.endOffset); } } } } } else { range = _range; // Just evaluate the current documents range } // Generate the Cfi return this.epubcfi.generateCfiFromRange(range, this.cfiBase); }; EPUBJS.Chapter.prototype.find = function(_query){ var chapter = this; var matches = []; var query = _query.toLowerCase(); //var xpath = this.document.evaluate(".//text()[contains(translate(., '"+query.toUpperCase()+"', '"+query+"'),'"+query+"')]", this.document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); var find = function(node){ // Search String var text = node.textContent.toLowerCase(); var range = chapter.document.createRange(); var cfi; var pos; var last = -1; var excerpt; var limit = 150; while (pos != -1) { pos = text.indexOf(query, last + 1); if(pos != -1) { // If Found, Create Range range = chapter.document.createRange(); range.setStart(node, pos); range.setEnd(node, pos + query.length); //Generate CFI cfi = chapter.cfiFromRange(range); // Generate Excerpt if(node.textContent.length < limit) { excerpt = node.textContent; } else { excerpt = node.textContent.substring(pos-limit/2,pos+limit/2); excerpt = "..." + excerpt + "..."; } //Add CFI to list matches.push({ cfi: cfi, excerpt: excerpt }); } last = pos; } }; // Grab text nodes /* for ( var i=0 ; i < xpath.snapshotLength; i++ ) { find(xpath.snapshotItem(i)); } */ this.textSprint(this.document, function(node){ find(node); }); // Return List of CFIs return matches; }; EPUBJS.Chapter.prototype.textSprint = function(root, func) { var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { acceptNode: function (node) { if (node.data && ! /^\s*$/.test(node.data) ) { return NodeFilter.FILTER_ACCEPT; } else { return NodeFilter.FILTER_REJECT; } } }, false); var node; while ((node = treeWalker.nextNode())) { func(node); } }; EPUBJS.Chapter.prototype.replace = function(query, func, finished, progress){ var items = this.contents.querySelectorAll(query), resources = Array.prototype.slice.call(items), count = resources.length; if(count === 0) { finished(false); return; } resources.forEach(function(item){ var called = false; var after = function(result, full){ if(called === false) { count--; if(progress) progress(result, full, count); if(count <= 0 && finished) finished(true); called = true; } }; func(item, after); }.bind(this)); }; EPUBJS.Chapter.prototype.replaceWithStored = function(query, attr, func, callback) { var _oldUrls, _newUrls = {}, _store = this.store, _cache = this.caches[query], _uri = EPUBJS.core.uri(this.absolute), _chapterBase = _uri.base, _attr = attr, _wait = 5, progress = function(url, full, count) { _newUrls[full] = url; }, finished = function(notempty) { if(callback) callback(); EPUBJS.core.values(_oldUrls).forEach(function(url){ _store.revokeUrl(url); }); _cache = _newUrls; }; if(!_store) return; if(!_cache) _cache = {}; _oldUrls = EPUBJS.core.clone(_cache); this.replace(query, function(link, done){ var src = link.getAttribute(_attr), full = EPUBJS.core.resolveUrl(_chapterBase, src); var replaceUrl = function(url) { var timeout; link.onload = function(){ clearTimeout(timeout); done(url, full); }; link.onerror = function(e){ clearTimeout(timeout); done(url, full); console.error(e); }; if(query == "image") { //-- SVG needs this to trigger a load event link.setAttribute("externalResourcesRequired", "true"); } if(query == "link[href]" && link.getAttribute("rel") !== "stylesheet") { //-- Only Stylesheet links seem to have a load events, just continue others done(url, full); } else { timeout = setTimeout(function(){ done(url, full); }, _wait); } link.setAttribute(_attr, url); }; if(full in _oldUrls){ replaceUrl(_oldUrls[full]); _newUrls[full] = _oldUrls[full]; delete _oldUrls[full]; }else{ func(_store, full, replaceUrl, link); } }, finished, progress); };