This repository has been archived by the owner on Sep 13, 2022. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 984eff7
Showing
9 changed files
with
302 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
server: http://localhost:4224 | ||
|
||
load: | ||
- mocks.js | ||
- ../js-epub.js | ||
- tests/*_test.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
JSUnzip = function () {}; | ||
JSInflate = {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"]); | ||
} | ||
}); |
Submodule js-inflate
added at
512c32
Submodule js-unzip
added at
4e6a2d