Skip to content
This repository has been archived by the owner on Sep 13, 2022. It is now read-only.

Commit

Permalink
Showoff
Browse files Browse the repository at this point in the history
  • Loading branch information
augustl committed Feb 4, 2010
0 parents commit 984eff7
Show file tree
Hide file tree
Showing 9 changed files with 302 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitmodules
@@ -0,0 +1,6 @@
[submodule "vendor/js-unzip"]
path = vendor/js-unzip
url = git://github.com/augustl/js-unzip.git
[submodule "vendor/js-inflate"]
path = vendor/js-inflate
url = git://github.com/augustl/js-inflate.git
23 changes: 23 additions & 0 deletions README.textile
@@ -0,0 +1,23 @@
h2. Work in progress

Very experimental, can't actually do anything useful yet.

h2. JSEpub

Reads and handles the ebook format EPUB.

<pre><code>var ePubFile = ...; // Use HTML5 files, or download it via XHR.
var epub = new JSEpub(epubFile);
</code></pre>

h2. Installing and using

JSEpub has the following dependencies:

* "JSUnzip":http://github.com/augustl/js-unzip. EPUB files are packaged as a Zip
archive.
* "JSInflate":http://github.com/augustl/js-inflate. Used to decompress the
compressed files in the Zip archive.

Simply include these javascripts on your page.

11 changes: 11 additions & 0 deletions Rakefile
@@ -0,0 +1,11 @@
namespace :jstestdriver do
desc "Starts a local server"
task :server do
exec("jstestdriver --port 4224")
end

desc "Runs the tests"
task :run do
exec("jstestdriver --tests all --config test/jsTestDriver.conf --reset")
end
end
135 changes: 135 additions & 0 deletions js-epub.js
@@ -0,0 +1,135 @@
(function (GLOBAL) {
var JSEpub = function (blob) {
this.blob = blob;
}

GLOBAL.JSEpub = JSEpub;

JSEpub.prototype = {
// For mockability
unzipperConstructor: JSUnzip,
inflater: JSInflate,

unzipBlob: function () {
var unzipper = new this.unzipperConstructor(this.blob);
if (!unzipper.isZipFile()) {
throw new Error("Provided file was not a zip file.");
}

unzipper.readEntries();
this.entries = unzipper.entries;
},

readEntries: function () {
this.files = {};

for (var i = 0, il = this.entries.length; i < il; i++) {
var entry = this.entries[i];
var data;

if (entry.compressionMethod === 0) {
data = entry.data;
} else if (entry.compressionMethod === 8) {
data = this.inflater.inflate(entry.data);
} else {
throw new Error("Unknown compression method "
+ entry.compressionMethod
+ " encountered.");
}

if (entry.fileName === "META-INF/container.xml") {
this.container = data;
} else if (entry.fileName === "mimetype") {
this.mimetype = data;
} else {
this.files[entry.fileName] = entry.data;
}
}
},

getOpfPathFromContainer: function () {
var doc = this.xmlDocument(this.container);
return doc
.getElementsByTagName("rootfile")[0]
.getAttribute("full-path");
},

readOpf: function (xml) {
var doc = this.xmlDocument(xml);

try {
var opf = {
metadata: {},
manifest: {},
spine: []
};

var metadataNodes = doc
.getElementsByTagName("metadata")[0]
.childNodes;

for (var i = 0, il = metadataNodes.length; i < il; i++) {
var node = metadataNodes[i];
var key = node.nodeName.toLowerCase();
if (key === "#text") { continue }
var attrs = {};
for (var i2 = 0, il2 = node.attributes.length; i2 < il2; i2++) {
var attr = node.attributes[i2];
attrs[attr.name] = attr.value;
}
attrs._text = node.textContent;
opf.metadata[key] = attrs;
}

var manifestEntries = doc
.getElementsByTagName("manifest")[0]
.getElementsByTagName("item");

for (var i = 0, il = manifestEntries.length; i < il; i++) {
var node = manifestEntries[i];
var attrs = {};
for (var i2 = 0, il2 = node.attributes.length; i2 < il2; i2++) {
var attr = node.attributes[i2];
if (attr.name === "id") { continue }
attrs[attr.name] = attr.value;
}
opf.manifest[node.getAttribute("id")] = attrs;
}

var spineEntries = doc
.getElementsByTagName("spine")[0]
.getElementsByTagName("itemref");

for (var i = 0, il = spineEntries.length; i < il; i++) {
var node = spineEntries[i];
opf.spine.push(node.getAttribute("idref"));
}

return opf;
} catch(e) {
// The DOMParser will not throw an error if the XML is invalid.
// It will return an XML error document, and it will be in
// here:
// doc.childNodes[1].childNodes[0].nodeValue
}
},

validate: function () {
if (this.container === undefined) {
throw new Error("META-INF/container.xml file not found.");
}

if (this.mimetype === undefined) {
throw new Error("Mimetype file not found.");
}

if (this.mimetype !== "application/epub+zip") {
throw new Error("Incorrect mimetype " + this.mimetype);
}
},

xmlDocument: function (xml) {
return new DOMParser().parseFromString(xml, "text/xml");
}
}
}(this));
6 changes: 6 additions & 0 deletions test/jsTestDriver.conf
@@ -0,0 +1,6 @@
server: http://localhost:4224

load:
- mocks.js
- ../js-epub.js
- tests/*_test.js
2 changes: 2 additions & 0 deletions test/mocks.js
@@ -0,0 +1,2 @@
JSUnzip = function () {};
JSInflate = {};
117 changes: 117 additions & 0 deletions test/tests/js_epub_test.js
@@ -0,0 +1,117 @@
TestCase("JsEpubTest", {
setUp: function () {
},

tearDown: function () {
},

"test unzipping blob": function () {
var expectedBlob = "arf";
var actualBlob;
var entries = "any object here";
JSEpub.prototype.unzipperConstructor = function (blob) {
actualBlob = blob;
}
JSEpub.prototype.unzipperConstructor.prototype = {
isZipFile: function () { return true },
readEntries: function () {},
entries: entries
}

var e = new JSEpub(expectedBlob);
e.unzipBlob();
assertEquals(expectedBlob, actualBlob);
assertEquals(entries, e.entries);
},

"test read": function () {
var e = new JSEpub();
var timesInflated = 0;

JSEpub.prototype.inflater = {inflate: function () {
timesInflated++;
}};
// Mocked JSUnzip output.
e.entries = [
{
fileName: "mimetype",
data: "application/epub+zip",
compressionMethod: 0
},
{
fileName: "META-INF/container.xml",
data: "",
compressionMethod: 8
},
{
fileName: "anything.nxc",
data: "",
compressionMethod: 8
},
{
fileName: "content/a_page.html",
data: "",
compressionMethod: 8
}
]

e.readEntries();

assertEquals(3, timesInflated);
assertEquals("application/epub+zip", e.mimetype);
},

"test invalid compression method": function () {
},

"test valitate": function () {
},

"test reading container": function () {
var xml = ""
+ '<?xml version="1.0" encoding="UTF-8"?>\n'
+ '<container version="1.0">\n'
+ ' <rootfiles>\n'
+ ' <rootfile full-path="foo.opf" />\n'
+ ' </rootfiles>\n'
+ '</container>\n';
var e = new JSEpub();
e.container = xml;
assertEquals("foo.opf", e.getOpfPathFromContainer());
},

"test reading opf": function () {
var xml = ""
+ '<?xml version="1.0" encoding="UTF-8"?>\n'
+ '<package>\n'
+ ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n'
+ ' <dc:language>en</dc:language>\n'
+ ' <dc:title>My Book</dc:title>\n'
+ ' <dc:creator opf:role="aut">Santa Claus</dc:creator>\n'
+ ' <dc:creator opf:role="aut">Rudolf</dc:creator>\n'
+ ' <dc:publisher>North Pole</dc:publisher>\n'
+ ' <dc:identifier opf:scheme="ISBN">1-123456-78-9</dc:identifier>\n'
+ ' </metadata>\n'
+ ' <manifest>\n'
+ ' <item id="book-css" href="css/book.css" media-type="text/css"/>\n'
+ ' <item id="chap1" href="chap1.html" media-type="application/xhtml+xml"/>\n'
+ ' <item id="chap2" href="chap2.html" media-type="application/xhtml+xml"/>\n'
+ ' <item id="nxc" href="toc.ncx" media-type="application/x-dtbncx+xml"/>\n'
+ ' </manifest>\n'
+ ' <spine toc="ncx">\n'
+ ' <itemref idref="chap1"/>\n'
+ ' <itemref idref="chap2"/>\n'
+ ' </spine>\n'
+ '</package>\n';
var e = new JSEpub();
var opf = e.readOpf(xml);
assertEquals(opf.metadata["dc:language"]._text, "en");
assertEquals(opf.metadata["dc:creator"]["opf:role"], "aut");
assertEquals(opf.metadata["dc:identifier"]["opf:scheme"], "ISBN");
assertEquals(opf.metadata["dc:identifier"]._text, "1-123456-78-9");
assertEquals(opf.manifest["book-css"]["href"], "css/book.css");
assertEquals(opf.manifest["book-css"]["media-type"], "text/css");
assertEquals(opf.manifest["chap1"]["href"], "chap1.html");
assertEquals(opf.spine, ["chap1", "chap2"]);
}
});
1 change: 1 addition & 0 deletions vendor/js-inflate
Submodule js-inflate added at 512c32
1 change: 1 addition & 0 deletions vendor/js-unzip
Submodule js-unzip added at 4e6a2d

0 comments on commit 984eff7

Please sign in to comment.