1
0
Fork 0
mirror of https://github.com/futurepress/epub.js.git synced 2025-10-04 15:09:16 +02:00

Added resources, renamed unarchive -> archive, moved replacements to Book

This commit is contained in:
Fred Chasen 2016-11-08 16:05:47 +01:00
parent 47787678f7
commit b5cfc74e12
8 changed files with 252 additions and 260 deletions

View file

@ -1,15 +1,16 @@
var core = require('./core');
var request = require('./request');
var mime = require('../libs/mime/mime');
var Path = require('./core').Path;
function Unarchive() {
function Archive() {
this.checkRequirements();
this.urlCache = {};
}
Unarchive.prototype.checkRequirements = function(callback){
Archive.prototype.checkRequirements = function(callback){
try {
if (typeof JSZip === 'undefined') {
JSZip = require('jszip');
@ -20,25 +21,26 @@ Unarchive.prototype.checkRequirements = function(callback){
}
};
Unarchive.prototype.open = function(input, isBase64){
Archive.prototype.open = function(input, isBase64){
return this.zip.loadAsync(input, {"base64": isBase64});
};
Unarchive.prototype.openUrl = function(zipUrl, isBase64){
Archive.prototype.openUrl = function(zipUrl, isBase64){
return request(zipUrl, "binary")
.then(function(data){
return this.zip.loadAsync(data, {"base64": isBase64});
}.bind(this));
};
Unarchive.prototype.request = function(url, type){
Archive.prototype.request = function(url, type){
var deferred = new core.defer();
var response;
var r;
var path = new Path(url);
// If type isn't set, determine it from the file extension
if(!type) {
type = core.extension(url);
type = path.extension;
}
if(type == 'blob'){
@ -61,7 +63,7 @@ Unarchive.prototype.request = function(url, type){
return deferred.promise;
};
Unarchive.prototype.handleResponse = function(response, type){
Archive.prototype.handleResponse = function(response, type){
var r;
if(type == "json") {
@ -85,7 +87,7 @@ Unarchive.prototype.handleResponse = function(response, type){
return r;
};
Unarchive.prototype.getBlob = function(url, _mimeType){
Archive.prototype.getBlob = function(url, _mimeType){
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
var entry = this.zip.file(decodededUrl);
var mimeType;
@ -98,7 +100,7 @@ Unarchive.prototype.getBlob = function(url, _mimeType){
}
};
Unarchive.prototype.getText = function(url, encoding){
Archive.prototype.getText = function(url, encoding){
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
var entry = this.zip.file(decodededUrl);
@ -109,7 +111,7 @@ Unarchive.prototype.getText = function(url, encoding){
}
};
Unarchive.prototype.getBase64 = function(url, _mimeType){
Archive.prototype.getBase64 = function(url, _mimeType){
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
var entry = this.zip.file(decodededUrl);
var mimeType;
@ -122,7 +124,7 @@ Unarchive.prototype.getBase64 = function(url, _mimeType){
}
};
Unarchive.prototype.createUrl = function(url, options){
Archive.prototype.createUrl = function(url, options){
var deferred = new core.defer();
var _URL = window.URL || window.webkitURL || window.mozURL;
var tempUrl;
@ -175,10 +177,10 @@ Unarchive.prototype.createUrl = function(url, options){
return deferred.promise;
};
Unarchive.prototype.revokeUrl = function(url){
Archive.prototype.revokeUrl = function(url){
var _URL = window.URL || window.webkitURL || window.mozURL;
var fromCache = this.urlCache[url];
if(fromCache) _URL.revokeObjectURL(fromCache);
};
module.exports = Unarchive;
module.exports = Archive;

View file

@ -9,8 +9,9 @@ var Parser = require('./parser');
var Container = require('./container');
var Packaging = require('./packaging');
var Navigation = require('./navigation');
var Resources = require('./resources');
var Rendition = require('./rendition');
var Unarchive = require('./unarchive');
var Archive = require('./archive');
var request = require('./request');
var EpubCFI = require('./epubcfi');
@ -31,7 +32,8 @@ function Book(url, options){
this.settings = core.extend(this.settings || {}, {
requestMethod: this.requestMethod,
requestCredentials: undefined,
encoding: undefined // optional to pass 'binary' or base64' for archived Epubs
encoding: undefined, // optional to pass 'binary' or base64' for archived Epubs
base64: true
});
core.extend(this.settings, options);
@ -51,7 +53,8 @@ function Book(url, options){
metadata: new core.defer(),
cover: new core.defer(),
navigation: new core.defer(),
pageList: new core.defer()
pageList: new core.defer(),
resources: new core.defer()
};
this.loaded = {
@ -60,7 +63,8 @@ function Book(url, options){
metadata: this.loading.metadata.promise,
cover: this.loading.cover.promise,
navigation: this.loading.navigation.promise,
pageList: this.loading.pageList.promise
pageList: this.loading.pageList.promise,
resources: this.loading.resources.promise
};
// this.ready = RSVP.hash(this.loaded);
@ -72,7 +76,7 @@ function Book(url, options){
this.loaded.metadata,
this.loaded.cover,
this.loaded.navigation,
this.loaded.pageList ]);
this.loaded.resources ]);
// Queue for methods used before opening
@ -128,22 +132,20 @@ Book.prototype.open = function(input, what){
if (type === "binary") {
this.archived = true;
this.url = new Url("/", "");
opening = this.openEpub(input);
} else if (type === "epub") {
this.archived = true;
this.url = new Url("/", "");
opening = this.request(input, 'binary')
.then(function(epubData) {
return this.openEpub(epubData);
}.bind(this));
.then(this.openEpub.bind(this));
} else if(type == "opf") {
this.url = new Url(input);
opening = this.openPackaging(input);
} else {
this.url = new Url(input);
opening = this.openContainer(CONTAINER_PATH)
.then(function(packagePath) {
return this.openPackaging(packagePath);
}.bind(this))
.then(this.openPackaging.bind(this));
}
return opening;
@ -152,10 +154,10 @@ Book.prototype.open = function(input, what){
Book.prototype.openEpub = function(data, encoding){
return this.unarchive(data, encoding || this.settings.encoding)
.then(function() {
return this.openContainer("/" + CONTAINER_PATH);
return this.openContainer(CONTAINER_PATH);
}.bind(this))
.then(function(packagePath) {
return this.openPackaging("/" + packagePath);
return this.openPackaging(packagePath);
}.bind(this));
};
@ -163,7 +165,7 @@ Book.prototype.openContainer = function(url){
return this.load(url)
.then(function(xml) {
this.container = new Container(xml);
return this.container.packagePath;
return this.resolve(this.container.packagePath);
}.bind(this));
};
@ -180,9 +182,9 @@ Book.prototype.openPackaging = function(url){
Book.prototype.load = function (path) {
var resolved;
if(this.unarchived) {
if(this.archived) {
resolved = this.resolve(path);
return this.unarchived.request(resolved);
return this.archive.request(resolved);
} else {
resolved = this.resolve(path);
return this.request(resolved, null, this.requestCredentials, this.requestHeaders);
@ -244,6 +246,12 @@ Book.prototype.unpack = function(opf){
this.spine.unpack(this.package, this.resolve.bind(this));
this.resources = new Resources(this.package.manifest, {
archive: this.archive,
resolver: this.resolve.bind(this),
base64: this.settings.base64
});
this.loadNavigation(this.package).then(function(toc){
this.toc = toc;
this.loading.navigation.resolve(this.toc);
@ -256,11 +264,20 @@ Book.prototype.unpack = function(opf){
this.loading.metadata.resolve(this.package.metadata);
this.loading.spine.resolve(this.spine);
this.loading.cover.resolve(this.cover);
this.loading.resources.resolve(this.resources);
this.isOpen = true;
// Resolve book opened promise
this.opening.resolve(this);
if(this.archived) {
this.replacements().then(function() {
this.opening.resolve(this);
}.bind(this));
} else {
// Resolve book opened promise
this.opening.resolve(this);
}
};
Book.prototype.loadNavigation = function(opf){
@ -311,34 +328,8 @@ Book.prototype.setRequestHeaders = function(_headers) {
* Unarchive a zipped epub
*/
Book.prototype.unarchive = function(bookUrl, encoding){
this.unarchived = new Unarchive();
return this.unarchived.open(bookUrl, encoding);
};
/**
* Checks if url has a .epub or .zip extension, or is ArrayBuffer (of zip/epub)
*/
Book.prototype.isArchivedUrl = function(bookUrl){
var extension;
if (bookUrl instanceof ArrayBuffer) {
return true;
}
// Reuse parsed url or create a new uri object
// if(typeof(bookUrl) === "object") {
// uri = bookUrl;
// } else {
// uri = core.uri(bookUrl);
// }
// uri = URI(bookUrl);
extension = core.extension(bookUrl);
if(extension && (extension == "epub" || extension == "zip")){
return true;
}
return false;
this.archive = new Archive();
return this.archive.open(bookUrl, encoding);
};
/**
@ -347,8 +338,8 @@ Book.prototype.isArchivedUrl = function(bookUrl){
Book.prototype.coverUrl = function(){
var retrieved = this.loaded.cover.
then(function(url) {
if(this.unarchived) {
return this.unarchived.createUrl(this.cover);
if(this.archived) {
return this.archive.createUrl(this.cover);
}else{
return this.cover;
}
@ -359,6 +350,17 @@ Book.prototype.coverUrl = function(){
return retrieved;
};
Book.prototype.replacements = function(){
this.spine.hooks.serialize.register(function(output, section) {
section.output = this.resources.substitute(output, section.url);
}.bind(this));
return this.resources.replacements().
then(function() {
return this.resources.replaceCss();
}.bind(this));
};
/**
* Find a DOM Range for a given CFI Range
*/

View file

@ -12,6 +12,7 @@ var requestAnimationFrame = (typeof window != 'undefined') ? (window.requestAnim
*/
function Url(urlString, baseString) {
var absolute = (urlString.indexOf('://') > -1);
var pathname;
this.href = urlString;
this.protocol = "";
@ -20,7 +21,7 @@ function Url(urlString, baseString) {
this.search = "";
this.base = baseString || undefined;
if (!absolute && !baseString) {
if (!absolute && typeof(baseString) !== "string") {
this.base = window && window.location.href;
}
@ -32,12 +33,15 @@ function Url(urlString, baseString) {
this.origin = this.Url.origin;
this.fragment = this.Url.fragment;
this.search = this.Url.search;
pathname = this.Url.pathname;
} catch (e) {
// console.error(e);
this.Url = undefined;
pathname = urlString;
}
this.Path = new Path(this.Url.pathname);
this.Path = new Path(pathname);
this.directory = this.Path.directory;
this.filename = this.Path.filename;
this.extension = this.Path.extension;
@ -96,6 +100,10 @@ Path.prototype.parse = function (what) {
return path.parse(what);
};
Path.prototype.isAbsolute = function (what) {
return path.isAbsolute(what || this.path);
};
Path.prototype.isDirectory = function (what) {
return (what.charAt(what.length-1) === '/');
};
@ -122,41 +130,6 @@ function assertPath(path) {
}
};
function extension(_url) {
var url;
var pathname;
var ext;
try {
url = new Url(url);
pathname = url.pathname;
} catch (e) {
pathname = _url;
}
ext = path.extname(pathname);
if (ext) {
return ext.slice(1);
}
return '';
}
function directory(_url) {
var url;
var pathname;
var ext;
try {
url = new Url(url);
pathname = url.pathname;
} catch (e) {
pathname = _url;
}
return path.dirname(pathname);
}
function isElement(obj) {
return !!(obj && obj.nodeType == 1);
};
@ -630,8 +603,6 @@ function defer() {
module.exports = {
'extension' : extension,
'directory' : directory,
'isElement': isElement,
'uuid': uuid,
'values': values,

View file

@ -58,11 +58,6 @@ function Rendition(book, options) {
// this.starting = new core.defer();
// this.started = this.starting.promise;
this.q.enqueue(this.start);
if(this.book.archived) {
this.q.enqueue(this.replacements.bind(this));
}
};
Rendition.prototype.setManager = function(manager) {
@ -400,156 +395,6 @@ Rendition.prototype.triggerSelectedEvent = function(cfirange){
this.emit("selected", cfirange);
};
Rendition.prototype.replacements = function(){
// Wait for loading
// return this.q.enqueue(function () {
// Get thes books manifest
var manifest = this.book.package.manifest;
var manifestArray = Object.keys(manifest).
map(function (key){
return manifest[key];
});
// Exclude HTML
var items = manifestArray.
filter(function (item){
if (item.type != "application/xhtml+xml" &&
item.type != "text/html") {
return true;
}
});
// Only CSS
var css = items.
filter(function (item){
if (item.type === "text/css") {
return true;
}
});
// Css Urls
var cssUrls = css.map(function(item) {
return item.href;
});
// All Assets Urls
var urls = items.
map(function(item) {
return item.href;
}.bind(this));
// Create blob urls for all the assets
var processing = urls.
map(function(url) {
// var absolute = new URL(url, this.book.baseUrl).toString();
var absolute = this.book.resolve(url);
// Full url from archive base
return this.book.unarchived.createUrl(absolute, {"base64": this.settings.useBase64});
}.bind(this));
var replacementUrls;
// After all the urls are created
return Promise.all(processing)
.then(function(_replacementUrls) {
var replaced = [];
replacementUrls = _replacementUrls;
// Replace Asset Urls in the text of all css files
cssUrls.forEach(function(href) {
replaced.push(this.replaceCss(href, urls, replacementUrls));
}.bind(this));
return Promise.all(replaced);
}.bind(this))
.then(function () {
// Replace Asset Urls in chapters
// by registering a hook after the sections contents has been serialized
this.book.spine.hooks.serialize.register(function(output, section) {
this.replaceAssets(section, urls, replacementUrls);
}.bind(this));
}.bind(this))
.catch(function(reason){
console.error(reason);
});
// }.bind(this));
};
Rendition.prototype.replaceCss = function(href, urls, replacementUrls){
var newUrl;
var indexInUrls;
// Find the absolute url of the css file
// var fileUri = URI(href);
// var absolute = fileUri.absoluteTo(this.book.baseUrl).toString();
if (path.isAbsolute(href)) {
return new Promise(function(resolve, reject){
resolve(urls, replacementUrls);
});
}
var fileUri;
var absolute = this.book.resolve(href);
// Get the text of the css file from the archive
var textResponse = this.book.unarchived.getText(absolute);
// Get asset links relative to css file
var relUrls = urls.
map(function(assetHref) {
var resolved = this.book.resolve(assetHref);
var relative = new Path(absolute).relative(resolved);
return relative;
}.bind(this));
return textResponse.then(function (text) {
// Replacements in the css text
text = replace.substitute(text, relUrls, replacementUrls);
// Get the new url
if (this.settings.useBase64) {
newUrl = core.createBase64Url(text, 'text/css');
} else {
newUrl = core.createBlobUrl(text, 'text/css');
}
// switch the url in the replacementUrls
indexInUrls = urls.indexOf(href);
if (indexInUrls > -1) {
replacementUrls[indexInUrls] = newUrl;
}
return new Promise(function(resolve, reject){
resolve(urls, replacementUrls);
});
}.bind(this));
};
Rendition.prototype.replaceAssets = function(section, urls, replacementUrls){
// var fileUri = URI(section.url);
var fileUri;
var absolute = section.url;
// Get Urls relative to current sections
var relUrls = urls.
map(function(href) {
var resolved = this.book.resolve(href);
var relative = new Path(absolute).relative(resolved);
return relative;
}.bind(this));
section.output = replace.substitute(section.output, relUrls, replacementUrls);
};
Rendition.prototype.range = function(_cfi, ignoreClass){
var cfi = new EpubCFI(_cfi);
var found = this.visible().filter(function (view) {

View file

@ -1,4 +1,5 @@
var core = require('./core');
var Path = require('./core').Path;
function request(url, type, withCredentials, headers) {
var supportsURL = (typeof window != "undefined") ? window.URL : false; // TODO: fallback for url if window isn't defined
@ -40,9 +41,7 @@ function request(url, type, withCredentials, headers) {
// If type isn't set, determine it from the file extension
if(!type) {
// uri = new URI(url);
// type = uri.suffix();
type = core.extension(url);
type = new Path(url).extension;
}
if(type == 'blob'){

172
src/resources.js Normal file
View file

@ -0,0 +1,172 @@
var replace = require('./replacements');
var core = require('./core');
var Path = require('./core').Path;
var path = require('path');
function Resources(manifest, options) {
this.settings = {
base64: (options && options.base64) || true,
archive: (options && options.archive),
resolver: (options && options.resolver)
};
this.manifest = manifest;
this.resources = Object.keys(manifest).
map(function (key){
return manifest[key];
});
this.replacementUrls = undefined;
this.split();
this.urls();
}
Resources.prototype.split = function(){
// HTML
this.html = this.resources.
filter(function (item){
if (item.type === "application/xhtml+xml" ||
item.type === "text/html") {
return true;
}
});
// Exclude HTML
this.assets = this.resources.
filter(function (item){
if (item.type !== "application/xhtml+xml" &&
item.type !== "text/html") {
return true;
}
});
// Only CSS
this.css = this.resources.
filter(function (item){
if (item.type === "text/css") {
return true;
}
});
};
Resources.prototype.urls = function(){
// All Assets Urls
this.urls = this.assets.
map(function(item) {
return item.href;
}.bind(this));
// Css Urls
this.cssUrls = this.css.map(function(item) {
return item.href;
});
};
/**
* Create blob urls for all the assets
* @param {Archive} archive
* @param {resolver} resolver Url resolver
* @return {Promise} returns replacement urls
*/
Resources.prototype.replacements = function(archive, resolver){
archive = archive || this.settings.archive;
resolver = resolver || this.settings.resolver;
var replacements = this.urls.
map(function(url) {
var absolute = resolver(url);
return archive.createUrl(absolute, {"base64": this.settings.base64});
}.bind(this))
return Promise.all(replacements)
.then(function(replacementUrls) {
this.replacementUrls = replacementUrls;
return replacementUrls;
}.bind(this));
};
Resources.prototype.replaceCss = function(archive, resolver){
var replaced = [];
archive = archive || this.settings.archive;
resolver = resolver || this.settings.resolver;
this.cssUrls.forEach(function(href) {
var replacment = this.createCssFile(href, archive, resolver)
.then(function (replacementUrl) {
// switch the url in the replacementUrls
var indexInUrls = this.urls.indexOf(href);
if (indexInUrls > -1) {
this.replacementUrls[indexInUrls] = replacementUrl;
}
}.bind(this));
replaced.push(replacment);
}.bind(this));
return Promise.all(replaced);
};
Resources.prototype.createCssFile = function(href, archive, resolver){
var newUrl;
var indexInUrls;
archive = archive || this.settings.archive;
resolver = resolver || this.settings.resolver;
if (path.isAbsolute(href)) {
return new Promise(function(resolve, reject){
resolve(urls, replacementUrls);
});
}
var absolute = resolver(href);
// Get the text of the css file from the archive
var textResponse = archive.getText(absolute);
// Get asset links relative to css file
var relUrls = this.urls.map(function(assetHref) {
var resolved = resolver(assetHref);
var relative = new Path(absolute).relative(resolved);
return relative;
}.bind(this));
return textResponse.then(function (text) {
// Replacements in the css text
text = replace.substitute(text, relUrls, this.replacementUrls);
// Get the new url
if (this.settings.base64) {
newUrl = core.createBase64Url(text, 'text/css');
} else {
newUrl = core.createBlobUrl(text, 'text/css');
}
return newUrl;
}.bind(this));
};
Resources.prototype.relativeTo = function(absolute, resolver){
resolver = resolver || this.settings.resolver;
// Get Urls relative to current sections
return this.urls.
map(function(href) {
var resolved = resolver(href);
var relative = new Path(absolute).relative(resolved);
return relative;
}.bind(this));
};
Resources.prototype.substitute = function(content, url) {
var relUrls;
if (url) {
relUrls = this.relativeTo(url);
} else {
relUrls = this.urls;
}
return replace.substitute(content, relUrls, this.replacementUrls);
};
module.exports = Resources;

View file

@ -1,6 +1,7 @@
var core = require('./core');
var EpubCFI = require('./epubcfi');
var Hook = require('./hook');
var Url = require('./core').Url;
function Section(item, hooks){
this.idref = item.idref;
@ -36,7 +37,7 @@ Section.prototype.load = function(_request){
request(this.url)
.then(function(xml){
var base;
var directory = core.directory(this.url);
var directory = new Url(this.url).directory;
this.document = xml;
this.contents = xml.documentElement;

View file

@ -45,7 +45,7 @@ describe('ePub', function() {
return book.opened.then(function(){
assert.equal( book.isOpen, true, "book is opened" );
assert( book.unarchived, "book is unarchived" );
assert( book.archive, "book is unarchived" );
});
});