diff --git a/config.xml b/config.xml
index 0884ffc..50a3364 100644
--- a/config.xml
+++ b/config.xml
@@ -19,6 +19,7 @@
+
@@ -100,5 +101,6 @@
+
diff --git a/package-lock.json b/package-lock.json
index 4c85ace..00deb3f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1239,14 +1239,6 @@
"@types/cordova": "^0.0.34"
}
},
- "@ionic-native/diagnostic": {
- "version": "5.14.0",
- "resolved": "https://registry.npmjs.org/@ionic-native/diagnostic/-/diagnostic-5.14.0.tgz",
- "integrity": "sha512-hptAqkpfyORE3uJamItsBVBVdQwjU/4aO8JLtnueTadGSbPNQfYoXpYONZzz8/qOkrw+Uvqr6/e7Eu9Lsk0Iyg==",
- "requires": {
- "@types/cordova": "^0.0.34"
- }
- },
"@ionic-native/file": {
"version": "5.14.0",
"resolved": "https://registry.npmjs.org/@ionic-native/file/-/file-5.14.0.tgz",
@@ -1422,9 +1414,9 @@
"dev": true
},
"@types/node": {
- "version": "8.9.5",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-8.9.5.tgz",
- "integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ==",
+ "version": "7.10.7",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-7.10.7.tgz",
+ "integrity": "sha512-4I7+hXKyq7e1deuzX9udu0hPIYqSSkdKXtjow6fMnQ3OR9qkxIErGHbGY08YrfZJrCS1ajK8lOuzd0k3n2WM4A==",
"dev": true
},
"@types/q": {
@@ -3312,6 +3304,11 @@
"resolved": "https://registry.npmjs.org/cordova-plugin-device/-/cordova-plugin-device-2.0.2.tgz",
"integrity": "sha1-/Ajzci5n7ve2xnv8mag99q3Quro="
},
+ "cordova-plugin-file": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/cordova-plugin-file/-/cordova-plugin-file-6.0.2.tgz",
+ "integrity": "sha512-m7cughw327CjONN/qjzsTpSesLaeybksQh420/gRuSXJX5Zt9NfgsSbqqKDon6jnQ9Mm7h7imgyO2uJ34XMBtA=="
+ },
"cordova-plugin-ionic-keyboard": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cordova-plugin-ionic-keyboard/-/cordova-plugin-ionic-keyboard-2.2.0.tgz",
@@ -7433,6 +7430,19 @@
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"dev": true
},
+ "node": {
+ "version": "12.10.0",
+ "resolved": "https://registry.npmjs.org/node/-/node-12.10.0.tgz",
+ "integrity": "sha512-gzhu/wPSyupcu4TWzOoCsy9r1NONUZyzBQuItHeiRKWA5nov2pqHa+IO3CKNBHDu85nRw8yq13sw4I8S3aJxIQ==",
+ "requires": {
+ "node-bin-setup": "^1.0.0"
+ }
+ },
+ "node-bin-setup": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/node-bin-setup/-/node-bin-setup-1.0.6.tgz",
+ "integrity": "sha512-uPIxXNis1CRbv1DwqAxkgBk5NFV3s7cMN/Gf556jSw6jBvV7ca4F9lRL/8cALcZecRibeqU+5dFYqFFmzv5a0Q=="
+ },
"node-fetch-npm": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz",
diff --git a/package.json b/package.json
index 5e78ae0..1e82789 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,6 @@
"@angular/platform-browser-dynamic": "~8.1.2",
"@angular/router": "~8.1.2",
"@ionic-native/core": "^5.0.0",
- "@ionic-native/diagnostic": "^5.14.0",
"@ionic-native/file": "^5.14.0",
"@ionic-native/splash-screen": "^5.0.0",
"@ionic-native/status-bar": "^5.0.0",
@@ -29,6 +28,7 @@
"@ionic/angular": "^4.7.1",
"cordova-android": "7.1.4",
"cordova-plugin-device": "^2.0.2",
+ "cordova-plugin-file": "6.0.2",
"cordova-plugin-ionic-keyboard": "^2.2.0",
"cordova-plugin-ionic-webview": "^4.1.1",
"cordova-plugin-splashscreen": "^5.0.2",
@@ -37,6 +37,7 @@
"cordova-plugin-whitelist": "^1.3.3",
"core-js": "^2.5.4",
"epub": "^1.1.0",
+ "node": "^12.10.0",
"rxjs": "~6.5.1",
"tslib": "^1.9.0",
"zone.js": "~0.9.1"
@@ -53,7 +54,7 @@
"@ionic/angular-toolkit": "~2.0.0",
"@types/jasmine": "~3.3.8",
"@types/jasminewd2": "~2.0.3",
- "@types/node": "~8.9.4",
+ "@types/node": "^7.10.7",
"codelyzer": "^5.0.0",
"jasmine-core": "~3.4.0",
"jasmine-spec-reporter": "~4.2.1",
@@ -78,10 +79,11 @@
"cordova-plugin-ionic-webview": {
"ANDROID_SUPPORT_ANNOTATIONS_VERSION": "27.+"
},
- "cordova-plugin-ionic-keyboard": {}
+ "cordova-plugin-ionic-keyboard": {},
+ "cordova-plugin-file": {}
},
"platforms": [
"android"
]
}
-}
\ No newline at end of file
+}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index b9a2d65..4d3aaee 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -10,7 +10,7 @@ import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { FilesManager } from './services/files.service';
import { File } from '@ionic-native/file/ngx';
-import { Diagnostic } from '@ionic-native/diagnostic/ngx';
+//import { Diagnostic } from '@ionic-native/diagnostic/ngx';
@NgModule({
declarations: [AppComponent],
@@ -21,7 +21,6 @@ import { Diagnostic } from '@ionic-native/diagnostic/ngx';
SplashScreen,
FilesManager,
File,
- Diagnostic,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
],
bootstrap: [AppComponent]
diff --git a/src/app/home/home.page.html b/src/app/home/home.page.html
index d96444e..f00c5f1 100644
--- a/src/app/home/home.page.html
+++ b/src/app/home/home.page.html
@@ -7,7 +7,7 @@
-
+
SD Card
@@ -18,7 +18,11 @@
Internal
-
+
+ Recursive
+
+
+
+
+
+ {{file}}
+
+
diff --git a/src/app/home/home.page.ts b/src/app/home/home.page.ts
index 2333736..8dca30b 100644
--- a/src/app/home/home.page.ts
+++ b/src/app/home/home.page.ts
@@ -7,16 +7,18 @@ import { FilesManager } from '../services/files.service';
styleUrls: ['home.page.scss'],
})
export class HomePage {
- display: string
+ display: string;
+ which: number = 1;
+ recursive: boolean;
constructor(
private files: FilesManager
) {
- files.listFileSys(1);
+ files.loadFiles(this.which, this.recursive);
}
- onScan(ev: any) {
- console.log(ev);
- this.files.listFileSys(parseInt(ev.detail.value));
+ onScan() {
+ let which = typeof this.which == 'string' ? parseInt(this.which) : this.which;
+ this.files.loadFiles(which, this.recursive);
}
}
diff --git a/src/app/services/epub.service.ts b/src/app/services/epub.service.ts
new file mode 100644
index 0000000..d589a7e
--- /dev/null
+++ b/src/app/services/epub.service.ts
@@ -0,0 +1,768 @@
+import { ZipFile } from "zipfile";
+import { xml2js } from 'xml2js';
+import { EventEmitter } from 'events';
+
+export default class EPub extends EventEmitter {
+ version;
+
+ filename;
+ imageroot;
+ linkroot;
+ zip;
+ xml2jsOptions;
+
+ containerFile;
+ mimeFile;
+ rootFile: any;
+
+ metadata:any = {};
+ manifest = {};
+ guide = [];
+ spine: any = {toc: false, contents: []};
+ flow = [];
+ toc = [];
+
+ constructor(fname, imageroot = null, linkroot = null) {
+ super();
+ this.xml2jsOptions = xml2js.defaults['0.1'];
+ this.filename = fname;
+
+ this.imageroot = (imageroot || "/images/").trim();
+ this.linkroot = (linkroot || "/links/").trim();
+
+ if (this.imageroot.substr(-1) != "/") {
+ this.imageroot += "/";
+ }
+ if (this.linkroot.substr(-1) != "/") {
+ this.linkroot += "/";
+ }
+ }
+
+ /**
+ * EPub#parse() -> undefined
+ *
+ * Starts the parser, needs to be called by the script
+ **/
+ parse() {
+ this.containerFile = false;
+ this.mimeFile = false;
+ this.rootFile = false;
+
+ this.metadata = {};
+ this.manifest = {};
+ this.guide = [];
+ this.spine = {toc: false, contents: []};
+ this.flow = [];
+ this.toc = [];
+
+ this.open();
+ }
+
+
+ /**
+ * EPub#open() -> undefined
+ *
+ * Opens the epub file with Zip unpacker, retrieves file listing
+ * and runs mime type check
+ **/
+ open() {
+ try {
+ this.zip = new ZipFile(this.filename);
+ } catch (E) {
+ this.emit("error", new Error("Invalid/missing file"));
+ return;
+ }
+
+ if (!this.zip.names || !this.zip.names.length) {
+ this.emit("error", new Error("No files in archive"));
+ return;
+ }
+
+ this.checkMimeType();
+ };
+
+ /**
+ * EPub#checkMimeType() -> undefined
+ *
+ * Checks if there's a file called "mimetype" and that it's contents
+ * are "application/epub+zip". On success runs root file check.
+ **/
+ checkMimeType() {
+ var i, len;
+
+ for (i = 0, len = this.zip.names.length; i < len; i++) {
+ if (this.zip.names[i].toLowerCase() == "mimetype") {
+ this.mimeFile = this.zip.names[i];
+ break;
+ }
+ }
+ if (!this.mimeFile) {
+ this.emit("error", new Error("No mimetype file in archive"));
+ return;
+ }
+ this.zip.readFile(this.mimeFile, (function (err, data) {
+ if (err) {
+ this.emit("error", new Error("Reading archive failed"));
+ return;
+ }
+ var txt = data.toString("utf-8").toLowerCase().trim();
+
+ if (txt != "application/epub+zip") {
+ this.emit("error", new Error("Unsupported mime type"));
+ return;
+ }
+
+ this.getRootFiles();
+ }).bind(this));
+ };
+
+ /**
+ * EPub#getRootFiles() -> undefined
+ *
+ * Looks for a "meta-inf/container.xml" file and searches for a
+ * rootfile element with mime type "application/oebps-package+xml".
+ * On success calls the rootfile parser
+ **/
+ getRootFiles() {
+ var i, len;
+ for (i = 0, len = this.zip.names.length; i < len; i++) {
+ if (this.zip.names[i].toLowerCase() == "meta-inf/container.xml") {
+ this.containerFile = this.zip.names[i];
+ break;
+ }
+ }
+ if (!this.containerFile) {
+ this.emit("error", new Error("No container file in archive"));
+ return;
+ }
+
+ this.zip.readFile(this.containerFile, (function (err, data) {
+ if (err) {
+ this.emit("error", new Error("Reading archive failed"));
+ return;
+ }
+ var xml = data.toString("utf-8").toLowerCase().trim(),
+ xmlparser = new xml2js.Parser(this.xml2jsOptions);
+
+ xmlparser.on("end", (function (result) {
+
+ if (!result.rootfiles || !result.rootfiles.rootfile) {
+ this.emit("error", new Error("No rootfiles found"));
+ console.dir(result);
+ return;
+ }
+
+ var rootfile = result.rootfiles.rootfile,
+ filename = false, i, len;
+
+ if (Array.isArray(rootfile)) {
+
+ for (i = 0, len = rootfile.length; i < len; i++) {
+ if (rootfile[i]["@"]["media-type"] &&
+ rootfile[i]["@"]["media-type"] == "application/oebps-package+xml" &&
+ rootfile[i]["@"]["full-path"]) {
+ filename = rootfile[i]["@"]["full-path"].toLowerCase().trim();
+ break;
+ }
+ }
+
+ } else if (rootfile["@"]) {
+ if (rootfile["@"]["media-type"] != "application/oebps-package+xml" || !rootfile["@"]["full-path"]) {
+ this.emit("error", new Error("Rootfile in unknown format"));
+ return;
+ }
+ filename = rootfile["@"]["full-path"].toLowerCase().trim();
+ }
+
+ if (!filename) {
+ this.emit("error", new Error("Empty rootfile"));
+ return;
+ }
+
+
+ for (i = 0, len = this.zip.names.length; i < len; i++) {
+ if (this.zip.names[i].toLowerCase() == filename) {
+ this.rootFile = this.zip.names[i];
+ break;
+ }
+ }
+
+ if (!this.rootFile) {
+ this.emit("error", new Error("Rootfile not found from archive"));
+ return;
+ }
+
+ this.handleRootFile();
+
+ }).bind(this));
+
+ xmlparser.on("error", (function (err) {
+ this.emit("error", new Error("Parsing container XML failed"));
+ return;
+ }).bind(this));
+
+ xmlparser.parseString(xml);
+
+
+ }).bind(this));
+ };
+
+ /**
+ * EPub#handleRootFile() -> undefined
+ *
+ * Parses the rootfile XML and calls rootfile parser
+ **/
+ handleRootFile() {
+
+ this.zip.readFile(this.rootFile, (function (err, data) {
+ if (err) {
+ this.emit("error", new Error("Reading archive failed"));
+ return;
+ }
+ var xml = data.toString("utf-8"),
+ xmlparser = new xml2js.Parser(this.xml2jsOptions);
+
+ xmlparser.on("end", this.parseRootFile.bind(this));
+
+ xmlparser.on("error", (function (err) {
+ this.emit("error", new Error("Parsing container XML failed"));
+ return;
+ }).bind(this));
+
+ xmlparser.parseString(xml);
+
+ }).bind(this));
+ };
+
+ /**
+ * EPub#parseRootFile() -> undefined
+ *
+ * Parses elements "metadata," "manifest," "spine" and TOC.
+ * Emits "end" if no TOC
+ **/
+ parseRootFile(rootfile) {
+
+ this.version = rootfile['@'].version || '2.0';
+
+ var i, len, keys, keyparts, key;
+ keys = Object.keys(rootfile);
+ for (i = 0, len = keys.length; i < len; i++) {
+ keyparts = keys[i].split(":");
+ key = (keyparts.pop() || "").toLowerCase().trim();
+ switch (key) {
+ case "metadata":
+ this.parseMetadata(rootfile[keys[i]]);
+ break;
+ case "manifest":
+ this.parseManifest(rootfile[keys[i]]);
+ break;
+ case "spine":
+ this.parseSpine(rootfile[keys[i]]);
+ break;
+ case "guide":
+ this.parseGuide(rootfile[keys[i]]);
+ break;
+ }
+ }
+
+ if (this.spine.toc) {
+ this.parseTOC();
+ } else {
+ this.emit("end");
+ }
+ };
+
+ /**
+ * EPub#parseMetadata() -> undefined
+ *
+ * Parses "metadata" block (book metadata, title, author etc.)
+ **/
+ parseMetadata(metadata) {
+ var i, j, len, keys, keyparts, key;
+
+ keys = Object.keys(metadata);
+ for (i = 0, len = keys.length; i < len; i++) {
+ keyparts = keys[i].split(":");
+ key = (keyparts.pop() || "").toLowerCase().trim();
+ switch (key) {
+ case "publisher":
+ if (Array.isArray(metadata[keys[i]])) {
+ this.metadata.publisher = String(metadata[keys[i]][0] && metadata[keys[i]][0]["#"] || metadata[keys[i]][0] || "").trim();
+ } else {
+ this.metadata.publisher = String(metadata[keys[i]]["#"] || metadata[keys[i]] || "").trim();
+ }
+ break;
+ case "language":
+ if (Array.isArray(metadata[keys[i]])) {
+ this.metadata.language = String(metadata[keys[i]][0] && metadata[keys[i]][0]["#"] || metadata[keys[i]][0] || "").toLowerCase().trim();
+ } else {
+ this.metadata.language = String(metadata[keys[i]]["#"] || metadata[keys[i]] || "").toLowerCase().trim();
+ }
+ break;
+ case "title":
+ if (Array.isArray(metadata[keys[i]])) {
+ this.metadata.title = String(metadata[keys[i]][0] && metadata[keys[i]][0]["#"] || metadata[keys[i]][0] || "").trim();
+ } else {
+ this.metadata.title = String(metadata[keys[i]]["#"] || metadata[keys[i]] || "").trim();
+ }
+ break;
+ case "subject":
+ if (Array.isArray(metadata[keys[i]])) {
+ this.metadata.subject = String(metadata[keys[i]][0] && metadata[keys[i]][0]["#"] || metadata[keys[i]][0] || "").trim();
+ } else {
+ this.metadata.subject = String(metadata[keys[i]]["#"] || metadata[keys[i]] || "").trim();
+ }
+ break;
+ case "description":
+ if (Array.isArray(metadata[keys[i]])) {
+ this.metadata.description = String(metadata[keys[i]][0] && metadata[keys[i]][0]["#"] || metadata[keys[i]][0] || "").trim();
+ } else {
+ this.metadata.description = String(metadata[keys[i]]["#"] || metadata[keys[i]] || "").trim();
+ }
+ break;
+ case "creator":
+ if (Array.isArray(metadata[keys[i]])) {
+ this.metadata.creator = String(metadata[keys[i]][0] && metadata[keys[i]][0]["#"] || metadata[keys[i]][0] || "").trim();
+ this.metadata.creatorFileAs = String(metadata[keys[i]][0] && metadata[keys[i]][0]['@'] && metadata[keys[i]][0]['@']["opf:file-as"] || this.metadata.creator).trim();
+ } else {
+ this.metadata.creator = String(metadata[keys[i]]["#"] || metadata[keys[i]] || "").trim();
+ this.metadata.creatorFileAs = String(metadata[keys[i]]['@'] && metadata[keys[i]]['@']["opf:file-as"] || this.metadata.creator).trim();
+ }
+ break;
+ case "date":
+ if (Array.isArray(metadata[keys[i]])) {
+ this.metadata.date = String(metadata[keys[i]][0] && metadata[keys[i]][0]["#"] || metadata[keys[i]][0] || "").trim();
+ } else {
+ this.metadata.date = String(metadata[keys[i]]["#"] || metadata[keys[i]] || "").trim();
+ }
+ break;
+ case "identifier":
+ if (metadata[keys[i]]["@"] && metadata[keys[i]]["@"]["opf:scheme"] == "ISBN") {
+ this.metadata.ISBN = String(metadata[keys[i]]["#"] || "").trim();
+ } else if (metadata[keys[i]]["@"] && metadata[keys[i]]["@"].id && metadata[keys[i]]["@"].id.match(/uuid/i)) {
+ this.metadata.UUID = String(metadata[keys[i]]["#"] || "").replace('urn:uuid:', '').toUpperCase().trim();
+ } else if (Array.isArray(metadata[keys[i]])) {
+ for (j = 0; j < metadata[keys[i]].length; j++) {
+ if (metadata[keys[i]][j]["@"]) {
+ if (metadata[keys[i]][j]["@"]["opf:scheme"] == "ISBN") {
+ this.metadata.ISBN = String(metadata[keys[i]][j]["#"] || "").trim();
+ } else if (metadata[keys[i]][j]["@"].id && metadata[keys[i]][j]["@"].id.match(/uuid/i)) {
+ this.metadata.UUID = String(metadata[keys[i]][j]["#"] || "").replace('urn:uuid:', '').toUpperCase().trim();
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ var metas = metadata['meta'] || {};
+ Object.keys(metas).forEach(function(key) {
+ var meta = metas[key];
+ if (meta['@'] && meta['@'].name) {
+ var name = meta['@'].name;
+ this.metadata[name] = meta['@'].content;
+ }
+ if (meta['#'] && meta['@'].property) {
+ this.metadata[meta['@'].property] = meta['#'];
+ }
+
+ if(meta.name && meta.name =="cover"){
+ this.metadata[meta.name] = meta.content;
+ }
+ }, this);
+ };
+
+ /**
+ * EPub#parseManifest() -> undefined
+ *
+ * Parses "manifest" block (all items included, html files, images, styles)
+ **/
+ parseManifest(manifest) {
+ var i, len, path = this.rootFile.split("/"), element, path_str;
+ path.pop();
+ path_str = path.join("/");
+
+ if (manifest.item) {
+ for (i = 0, len = manifest.item.length; i < len; i++) {
+ if (manifest.item[i]['@']) {
+ element = manifest.item[i]['@'];
+
+ if (element.href && element.href.substr(0, path_str.length) != path_str) {
+ element.href = path.concat([element.href]).join("/");
+ }
+
+ this.manifest[manifest.item[i]['@'].id] = element;
+
+ }
+ }
+ }
+ };
+
+ /**
+ * EPub#parseGuide() -> undefined
+ *
+ * Parses "guide" block (locations of the fundamental structural components of the publication)
+ **/
+ parseGuide(guide) {
+ var i, len, path = this.rootFile.split("/"), element, path_str;
+ path.pop();
+ path_str = path.join("/");
+
+ if (guide.reference) {
+ if(!Array.isArray(guide.reference)){
+ guide.reference = [guide.reference];
+ }
+
+ for (i = 0, len = guide.reference.length; i < len; i++) {
+ if (guide.reference[i]['@']) {
+
+ element = guide.reference[i]['@'];
+
+ if (element.href && element.href.substr(0, path_str.length) != path_str) {
+ element.href = path.concat([element.href]).join("/");
+ }
+
+ this.guide.push(element);
+
+ }
+ }
+ }
+ };
+
+ /**
+ * EPub#parseSpine() -> undefined
+ *
+ * Parses "spine" block (all html elements that are shown to the reader)
+ **/
+ parseSpine(spine) {
+ var i, len, path = this.rootFile.split("/"), element;
+ path.pop();
+
+ if (spine['@'] && spine['@'].toc) {
+ this.spine.toc = this.manifest[spine['@'].toc] || false;
+ }
+
+ if (spine.itemref) {
+ if(!Array.isArray(spine.itemref)){
+ spine.itemref = [spine.itemref];
+ }
+ for (i = 0, len = spine.itemref.length; i < len; i++) {
+ if (spine.itemref[i]['@']) {
+ if (element = this.manifest[spine.itemref[i]['@'].idref]) {
+ this.spine.contents.push(element);
+ }
+ }
+ }
+ }
+ this.flow = this.spine.contents;
+ };
+
+ /**
+ * EPub#parseTOC() -> undefined
+ *
+ * Parses ncx file for table of contents (title, html file)
+ **/
+ parseTOC() {
+ var i, len, path = this.spine.toc.href.split("/"), id_list = {}, keys;
+ path.pop();
+
+ keys = Object.keys(this.manifest);
+ for (i = 0, len = keys.length; i < len; i++) {
+ id_list[this.manifest[keys[i]].href] = keys[i];
+ }
+
+ this.zip.readFile(this.spine.toc.href, (function (err, data) {
+ if (err) {
+ this.emit("error", new Error("Reading archive failed"));
+ return;
+ }
+ var xml = data.toString("utf-8"),
+ xmlparser = new xml2js.Parser(this.xml2jsOptions);
+
+ xmlparser.on("end", (function (result) {
+ if (result.navMap && result.navMap.navPoint) {
+ this.toc = this.walkNavMap(result.navMap.navPoint, path, id_list);
+ }
+
+ this.emit("end");
+ }).bind(this));
+
+ xmlparser.on("error", (function (err) {
+ this.emit("error", new Error("Parsing container XML failed"));
+ return;
+ }).bind(this));
+
+ xmlparser.parseString(xml);
+
+ }).bind(this));
+ };
+
+ /**
+ * EPub#walkNavMap(branch, path, id_list,[, level]) -> Array
+ * - branch (Array | Object): NCX NavPoint object
+ * - path (Array): Base path
+ * - id_list (Object): map of file paths and id values
+ * - level (Number): deepness
+ *
+ * Walks the NavMap object through all levels and finds elements
+ * for TOC
+ **/
+ walkNavMap(branch, path, id_list, level) {
+ level = level || 0;
+
+ // don't go too far
+ if (level > 7) {
+ return [];
+ }
+
+ var output = [];
+
+ if (!Array.isArray(branch)) {
+ branch = [branch];
+ }
+
+ for (var i = 0; i < branch.length; i++) {
+ if (branch[i].navLabel) {
+
+ var title = '';
+ if (branch[i].navLabel && typeof branch[i].navLabel.text == 'string') {
+ title = branch[i].navLabel && branch[i].navLabel.text || branch[i].navLabel===branch[i].navLabel ?
+ (branch[i].navLabel && branch[i].navLabel.text || branch[i].navLabel || "").trim() : '';
+ }
+ var order = Number(branch[i]["@"] && branch[i]["@"].playOrder || 0);
+ if (isNaN(order)) {
+ order = 0;
+ }
+ var href = '';
+ if (branch[i].content && branch[i].content["@"] && typeof branch[i].content["@"].src == 'string') {
+ href = branch[i].content["@"].src.trim();
+ }
+
+ var element:any = {
+ level: level,
+ order: order,
+ title: title
+ };
+
+ if (href) {
+ href = path.concat([href]).join("/");
+ element.href = href;
+
+ if (id_list[element.href]) {
+ // link existing object
+ element = this.manifest[id_list[element.href]];
+ element.title = title;
+ element.order = order;
+ element.level = level;
+ } else {
+ // use new one
+ element.href = href;
+ element.id = (branch[i]["@"] && branch[i]["@"].id || "").trim();
+ }
+
+ output.push(element);
+ }
+ }
+ if (branch[i].navPoint) {
+ output = output.concat(this.walkNavMap(branch[i].navPoint, path, id_list, level + 1));
+ }
+ }
+ return output;
+ };
+
+ /**
+ * EPub#getChapter(id, callback) -> undefined
+ * - id (String): Manifest id value for a chapter
+ * - callback (Function): callback function
+ *
+ * Finds a chapter text for an id. Replaces image and link URL's, removes
+ * etc. elements. Return only chapters with mime type application/xhtml+xml
+ **/
+ getChapter(id, callback) {
+ this.getChapterRaw(id, (function (err, str) {
+ if (err) {
+ callback(err);
+ return;
+ }
+
+ var i, len, path = this.rootFile.split("/"), keys = Object.keys(this.manifest);
+ path.pop();
+
+ // remove linebreaks (no multi line matches in JS regex!)
+ str = str.replace(/\r?\n/g, "\u0000");
+
+ // keep only contents
+ str.replace(/]*?>(.*)<\/body[^>]*?>/i, function (o, d) {
+ str = d.trim();
+ });
+
+ // remove