2010年6月22日 星期二

[Javascript] EPUB Reader?

原先想在寫一個 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 瀏覽器上呈現結果:


EPUB Browser EPUB Browser EPUB Browser


2010年6月20日 星期日

初夏

2010/06/18 新竹 2010/06/18 新竹太魯閣號


沿著熟悉半年的街道返家,經過新竹女中時,看到裙擺才想起已經過了端午。差不多習慣工作的環境,成天都待在冷氣房裡,有時分不出到底是晴天還雨天、冬天還夏天,畢竟冷氣房裡永遠都是冷冷清清。


這次回家帶著一台可拍照的傢伙,順手拍拍眼前看到的一切,忘了搭過幾次,來來回回也過了六年多吧?


搭車的好處莫過於可以靜靜地待在一個空間,偶爾偷聽附近的對談聲,也是種另類的享受吧。這次聽到隔壁的打電話跟其他人詢問工作的機會,我也開始比較習慣這種話題,過去會有那種要一直在某間公司吃苦磨練的耐心,彷彿撐過去就一定可以爬得更高似的,然而,現實生活絕對不是這樣,越優渥的環境,越可以看到醜陋的吃相。只是生活不該只有這些,從中攫取出真正的價值,那就是人生智慧了。


2010/06/18 斗六火車站 2010/06/18 斗六火車站


返家時,順手拍拍,但我完全沒進去過。


經過計程車行時,看到熟悉的背影玩著復古的遊戲機,原本已經走過其身旁,還是繞回來偷拍了一張,喜歡這種很古意的認真。


2010/06/18 斗六計程車行


2010年6月18日 星期五

iOS 開發教學 - 呈列出 App 之 Document 目錄裡的所有檔案

有時偷懶設計,把一堆資料儲存在 App 的專屬 Document 目錄裡頭,等到要用時才要去找尋他,這時候就會需要像 Unix 的 ls 指令來呈列出檔名,細節請參考contentsOfDirectoryAtPath:error:

程式碼:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {  

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsPath = [paths objectAtIndex:0];

    NSFileManager *manager = [NSFileManager defaultManager];
    NSArray *fileList = [manager contentsOfDirectoryAtPath:documentsPath error:nil];
  
    NSLog(@"%@" , paths);
    NSLog(@"%@", fileList);

    // Override point for customization after application launch
  
    [window makeKeyAndVisible];
  
    return YES;
}

另外,也可以直接切換到 iPhone 模擬器上進行一些檔案的建立與測試:

$ cd ~/Library/Application\ Support/iPhone\ Simulator/ ; ls
3.0    3.1    3.1.2    3.1.3    3.2

$ cd 3.1 ; ls
Applications    Library        Media        Root        tmp

$ cd Applications
$ cd XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX ; ls
Documents    Library        YourApp.app    tmp

$ cd Documents

接著就可以建立相關檔案以及使用上述的程式碼來呈列東西囉!(在這之前我竟然曾想過 jb 模擬器然後 ssh 登入進去 XD)

2010年6月16日 星期三

[C] 不定長引數串列 Variable-length Argument Lists

習慣在 Unix 環境上使用 fprintf 來進行 report ,然而,移到 Windows 時,這樣的做法並不適用,因此,有學長建議我使用Macro Definition 的方式來處理,如:


#ifdef  WIN32_REPORT

#define err_report ...

#else

#define err_report fprintf

#endif


然而,我想到我使用 fprintf 時接的參數並不固定,導致轉移並不輕鬆,所以,我就來試試不定長引數串列啦!自己定義一個函數:


#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

void do_err_report( FILE * out , ... )
{
        va_list ap;
        char *format , *string_value;
        char report_buffer[2048+1];
        int i , report_buffer_length = 2048;

        report_buffer[report_buffer_length] = '\0';

        va_start( ap , out );

        for( i=0 , format = va_arg( ap , char *); *format && i < report_buffer_length ; format++ )
        {
                if( *format != '%' )
                {
                        report_buffer[i++] = *format;
                        continue;
                }
                switch( *++format )
                {
                        case 'd':
                                i += snprintf( report_buffer + i , report_buffer_length - i , "%d" , va_arg( ap , int ) );
                                break;
                        case 'f':
                                i += snprintf( report_buffer + i , report_buffer_length - i , "%f" , va_arg( ap , double ) );
                                break;
                        case 'c':
                                i += snprintf( report_buffer + i , report_buffer_length - i , "%c" , va_arg( ap , int ) );
                                break;
                        case 's':
                                i += snprintf( report_buffer + i , report_buffer_length - i , "%s" , va_arg( ap , char * ) );
                                break;
                }
        }
        fprintf( out , "%s" , report_buffer );
        va_end( ap );
}

int main()
{
        do_err_report( stderr , "Hellow World %d @ %s \n" , 1 , "2010/06/16" );
        return 0;
}


這樣下來,我就可以在自行處理啦!主要動作是先把要印的東西存在一個變數中,到時候看是要用 AfxMessageBox 或是輸出到 log 檔也 ok 囉


2010年6月1日 星期二

[Python] XMLRPC - 使用 blogger.getUsersBlogs 與 metaWeblog.newPost

利用 XMLRPC 進行發文至 Blog 上頭,原先已玩過 PHP 了,這次換換口味,嘗試 Python,簡短的程式筆記


程式碼:


#!/usr/bin/env python
import xmlrpclib

server = xmlrpclib.ServerProxy( xmlrpc_api_url )
#print server.system.listMethods()
getRawBlogInfo = server.blogger.getUsersBlogs( '0123456789ABCDEF' , id , password )
if len(getRawBlogInfo) > 0 and 'blogid' in getRawBlogInfo[0] :
    content = {}
    content['title'] = 'title'
    content['description'] = 'data'
    #content['categories'] =
    #content['dateCreated'] =
    server.metaWeblog.newPost( getRawBlogInfo[0]['blogid'] , id , password , content, True )