原先想在寫一個 EPUB Reader 的,後來被建議乾脆找純 Javascript 的來做做,畢竟 HTML5 也越來越熱囉!隨意地找尋,看到有兩套:
第一套似乎很威,可以直接從 EPUB 格式讀取資料出來,看他的 lib 裡的確也有 zip、base64 等相關字眼,只可惜初步測試好像有點問題,又很順手地找到 Monocle 這套!並且還是 MIT License !他類似只是一個 EBook 的套版,把資料餵給他吃就行了!
Monocle 挺厲害的,在網頁上直接地 show 一本電子書,馬上就可以體驗操作介面。上面那本書則是網站上的一個範例,整個就幾乎跟 iBooks 很像啦!因此就挑他上手吧!我的工作就只剩下把 EPUB 裡頭的東西抽出來組一個資料結構來餵給 Monocle 而已啦!(另外還要先對 EPUB 做 Unzip 囉)
以下是需用到的 Javascript 元件:
- DOMParser
- XMLHttpRequest
快速簡單的範例:
@index.html
<html>
<head>
<script type="text/javascript" src="src/monocle.js"></script>
<script type="text/javascript" src="src/book.js"></script>
<script type="text/javascript" src="src/compat.js"></script>
<script type="text/javascript" src="src/component.js"></script>
<script type="text/javascript" src="src/framer.js"></script>
<script type="text/javascript" src="src/place.js"></script>
<script type="text/javascript" src="src/reader.js"></script>
<script type="text/javascript" src="src/styles.js"></script>
<script type="text/javascript" src="src/controls/contents.js"></script>
<script type="text/javascript" src="src/controls/magnifier.js"></script>
<script type="text/javascript" src="src/controls/placesaver.js"></script>
<script type="text/javascript" src="src/controls/scrubber.js"></script>
<script type="text/javascript" src="src/controls/spinner.js"></script>
<script type="text/javascript" src="src/flippers/instant.js"></script>
<script type="text/javascript" src="src/flippers/legacy.js"></script>
<script type="text/javascript" src="src/flippers/scroller.js"></script>
<script type="text/javascript" src="src/flippers/slider.js"></script>
<script type="text/javascript" src="UnzipEPUBParser.js"></script>
</head>
<body>
<div id="reader" style="width: 300px; height: 400px"></div>
<script>
var test = new UnzipEPUBParser();
test.initWithPath( 'MyTestBook' );
var bookData = {
getComponents: function () {
return test.navList;
return [
'component1.xhtml',
'component2.xhtml',
'component3.xhtml',
'component4.xhtml'
];
},
getContents: function () {
return test.navPoint;
return [
{
title: "Chapter 1",
src: "component1.xhtml"
},
{
title: "Chapter 2",
src: "component3.xhtml#chapter-2"
}
];
},
getComponent: function (componentId) {
var raw = test.getFileContent( test.basePath + '/' + test.navMap[componentId]['src'] );
if( !raw.status )
return null;
return raw.data;
return {
'component1.xhtml':'<h1>Chapter 1</h1><p>Hello world</p>',
'component2.xhtml':'<p>Chapter 1 continued.</p>',
'component3.xhtml':'<p>Chapter 1 continued again.</p>' + '<h1 id="chapter-2">Chapter 2</h1>' +'<p>Hello from the second chapter.</p>',
'component4.xhtml':'<p>THE END.</p>'
}[componentId];
},
getMetaData: function(key) {
return {
title: "A book",
creator: "Inventive Labs"
}[key];
}
}
// Initialize the reader element.
var reader = Monocle.Reader('reader');
// Initialize a book object. (Of course we could have just passed it in
// as the second argument to the reader constructor, which would also
// obviate the need for the setBook call below. This is the scenic route.)
var book = Monocle.Book(bookData);
// Assign the book to the reader and go to the 3rd page.
reader.setBook(book);
//reader.moveTo({ page: 1 });
</script>
</body>
</html>
@UnzipEPUBParser.js
function UnzipEPUBParser() {}
UnzipEPUBParser.prototype.basePath = '';
UnzipEPUBParser.prototype.status = false;
UnzipEPUBParser.prototype.error = null;
UnzipEPUBParser.prototype.navPoint = new Array();
UnzipEPUBParser.prototype.navMap = new Array();
UnzipEPUBParser.prototype.navList = new Array();
UnzipEPUBParser.prototype.metadata = new Array();
UnzipEPUBParser.prototype.getFileContent = function( path ) {
var ajReq = new XMLHttpRequest();
try{
ajReq.open( "GET", path , false);
ajReq.send(null);
}catch(err){
return { 'status':false , 'data':err };
}
return { 'status':true , 'data':ajReq.responseText };
};
UnzipEPUBParser.prototype.initWithPath = function( path ) {
this.basePath = path;
var parser = new DOMParser();
var xmlDoc = null ;
var obj = null;
var META_INF_CONTAINER = this.getFileContent( path + '/META-INF/container.xml' );
if( !META_INF_CONTAINER.status )
{
this.status = false;
this.error = "Cannot open the file: "+path+"/META-INF/container.xml";
return this.status;
}
else if( META_INF_CONTAINER.data == null || META_INF_CONTAINER.data == '' )
{
this.status = false;
this.error = "empty file: "+path+"/META-INF/container.xml";
return this.status;
}
xmlDoc = parser.parseFromString( META_INF_CONTAINER.data , "text/xml" );
if( !( obj = xmlDoc.getElementsByTagName( 'rootfile' )[0] ) || !obj.getAttribute( 'full-path' ) ) {
this.status = false;
this.error = "Cannot find the <rootfile> or 'full-path'";
return this.status;
}
var CONTENT_OPF = this.getFileContent( path + '/' + obj.getAttribute( 'full-path' ) );
if( !CONTENT_OPF.status )
{
this.status = false;
this.error = "Cannot open the file: "+ path + '/' + obj.getAttribute( 'full-path' );
return this.status;
}
else if( CONTENT_OPF.data == null || CONTENT_OPF.data == '' )
{
this.status = false;
this.error = "empty file: "+path + '/' + obj.getAttribute( 'full-path' );
return this.status;
}
var get_base_path = -1;
if( ( get_base_path = obj.getAttribute( 'full-path' ).lastIndexOf( '/' ) ) > 0 )
{
path += '/' + obj.getAttribute( 'full-path' ).substring( 0 , get_base_path );
this.basePath = path;
}
xmlDoc = parser.parseFromString( CONTENT_OPF.data , "text/xml" );
if( !( obj = xmlDoc.getElementsByTagName( 'manifest' )[0] ) || !( obj = obj.getElementsByTagName( 'item' ) ) ){
this.status = false;
this.error = "Cannot find the <manifest> or <item>";
return this.status;
}
var ncx_path = null;
for( var i=0 , cnt=obj.length ; i<cnt ; ++i ) {
//console.log( obj[i].getAttribute('id') + "\n" );
if( obj[i].getAttribute('id') == 'ncx' )
ncx_path = obj[i].getAttribute('href');
}
if( !ncx_path ) {
this.status = false;
this.error = "Cannot find the ncx info";
return this.status;
}
var TOC_NCX = this.getFileContent( path + '/' + ncx_path );
if( !TOC_NCX.status )
{
this.status = false;
this.error = "Cannot open the file: "+ path + '/' + ncx_path ;
return this.status;
}
else if( TOC_NCX.data == null || TOC_NCX.data == '' )
{
this.status = false;
this.error = "empty file: "+path + '/' + ncx_path;
return this.status;
}
xmlDoc = parser.parseFromString( TOC_NCX.data , "text/xml" );
if( !( obj = xmlDoc.getElementsByTagName( 'navMap' )[0] ) || !( obj = obj.getElementsByTagName( 'navPoint' ) ) ){
this.status = false;
this.error = "Cannot find the <navMap> or <navPoint>";
return this.status;
}
for( var i=0 , cnt=obj.length ; i<cnt ; ++i ) {
//console.log( obj[i].getAttribute('id') + "\n" );
//console.log( obj[i].getElementsByTagName( 'text' )[0].childNodes[0].nodeValue );
if( obj[i].getElementsByTagName( 'content' )[0]
&& obj[i].getElementsByTagName( 'content' )[0].getAttribute( 'src' )
&& obj[i].getElementsByTagName( 'text' )[0]
&& obj[i].getElementsByTagName( 'text' )[0].childNodes[0].nodeValue
&& obj[i].getAttribute( 'id' )
&& obj[i].getAttribute( 'playOrder' )
)
{
this.navMap[ obj[i].getAttribute( 'id' ) ] = {
'id': obj[i].getAttribute( 'id' ) ,
'title': obj[i].getElementsByTagName( 'text' )[0].childNodes[0].nodeValue ,
'src': obj[i].getElementsByTagName( 'content' )[0].getAttribute( 'src' ) ,
'order': obj[i].getAttribute( 'playOrder' )
};
this.navPoint.push( this.navMap[ obj[i].getAttribute( 'id' ) ] );
this.navList.push( obj[i].getAttribute( 'id' ) );
}
}
//console.log( this.navPoint );
//console.log( this.navMap );
//console.log( obj );
//console.log( xmlDoc.getElementsByTagName( 'rootfile' ) );
this.status = true;
this.error = null;
return this.status;
//console.log( xmlDoc );
//alert( xmlDoc );
//alert( xmlDoc['childNodes'] );
//console.log( xmlDoc['childNodes'] );
};
如此一來只要擺定好東西就能搞定啦!目錄結構:
MyTestBook/
META-INF/
container.xml
...
index.html
UnzipEPUBParser.js
src/
book.js
compat.js
component.js
controls/
...
flippers/
...
framer.js
monocle.js
place.js
reader.js
styles.js
以 [教學] 保證學會製作iPad適用的電子書格式 裡頭的 三國演義ePub 作為範例,在 FireFox 瀏覽器上呈現結果:
沒有留言:
張貼留言