2010年6月28日 星期一

[PHP] 使用 cURL + HTTP REFERER + Cookie + File:自製 my_wget 下載資料存到檔案

對 wget 這個 tool 不熟,平常使用 wget 下載一些資料時,可以輕易地使用 --referer 來偽造 HTTP Header 資料,因此能夠通過對方 Server 檢查


wget --referer="REFERER_URL" "TARGET_URL"


然而,上述的 REFERER_URL 和 TARGET_URL 都是固定的位置,如果是會根據 session / cookie 的而改變的話,不曉得還有沒有辦法?對我而言,寫 PHP 比去看 manpage 來得快 XD 所以我就寫成 PHP 囉!或許 wget 也有更方便的下法吧,改天再努力看 manpage


程式碼:


<?php
$output_file = 'result.file';  // 儲存結果
$cookie_file = 'cookie.tmp';  // cookie file
$source_url = 'SOURCE_URL';  // 之後會變成 REFERER_URL
$pattern = '/class="download" href="(.*?)"/';  // 此為一個範例, 用來撈 TARGET_URL

$ch = curl_init();
curl_setopt( $ch , CURLOPT_URL, $source_url );
curl_setopt( $ch , CURLOPT_COOKIEFILE , $cookie_file );
curl_setopt( $ch , CURLOPT_COOKIEJAR , $cookie_file );
curl_setopt( $ch , CURLOPT_RETURNTRANSFER , true );

$result = curl_exec( $ch );

if( preg_match_all( $pattern , $result , $match ) )
{
        if( isset( $match[1][1] ) )
        {  
                $target_url = $match[1][1];  // 請依 pattern 決定
                $referer_url = $source_url;

                curl_setopt( $ch , CURLOPT_URL, $target_url );
                curl_setopt( $ch , CURLOPT_REFERER , $referer_url );
                curl_setopt( $ch , CURLOPT_COOKIEFILE , $cookie_file );
                curl_setopt( $ch , CURLOPT_COOKIEJAR , $cookie_file );
                //curl_setopt( $ch , CURLOPT_RETURNTRANSFER , true );

                $fp = fopen ( $output_file, 'wb' );

                curl_setopt( $ch , CURLOPT_FILE , $fp );

                echo "GO...\n";
                curl_exec( $ch );
                echo "Finish..\n";

                fclose( $fp );
        }   
}

curl_close( $ch );
?>


以上是要從 SOURCE_URL 上頭, 找到下載位置(target_url), 然而, 那個位置卻每次都不一樣, 最重要的是跟 session 有關係並且下載 target_url 時還必須奉上 cookie 資訊, 所以, 先收集一下 cookie 囉!(上述程式並不謹慎, 例如儲存結果的檔案有可能開檔失敗)


後記,無聊又改寫成 tool mode:


<?php

$shortopt = array();

$shortopt['h'] =  array(
    'value' => '' ,
    'text' => '-h, help' );
$shortopt['c:'] =  array(
    'value'    => '' ,
    'text'    => "-c '/tmp/cookie_file' , tmp file for cookie" );
$shortopt['o:'] = array(
    'value'    => '' ,
    'text'    => "-o '/tmp/output_file' , path for result file. default use stdout" );
$shortopt['u:'] = array(
    'value'    => NULL ,
    'text'    => "-u 'http://www.google.com' , source url" );
$shortopt['e:'] = array(
    'value'    => NULL ,
    'text'    => "-e '/class=\"normal-down\" href=\"(.*?)\"/is' , regexp pattern for extract the target url" );
$shortopt['m:'] = array(
    'value'    => '' ,
    'text'    => "-m '1,1' , choose the result matched to be used. e.g. use the match[5][2] is '5,2'" );
$shortopt['d'] = array(
    'value'    => 'true' ,
    'text'    => "-d , disable test mode for showing the target matched by regexp pattern" );

// check function
if( !function_exists( 'getopt' ) )
{
    echo "'getopt' is not supported in current PHP version.\n";
    exit;
}

// help menu
$shortopt_list = '';
$shottopt_help = '';
foreach( $shortopt as $k => $v )
{
    $shortopt_list .= $k;
    $shottopt_help .= "\t".$v['text']."\n";
}

// start to parse...
$parse_arg = getopt( $shortopt_list );

// show help
if( isset( $parse_arg['h'] ) )
{
    echo "Usage> php ".$argv[0]." -h\n";
    echo $shottopt_help;
    exit;
}

// set the value
foreach( $parse_arg as $k => $v )
{
    if( isset( $shortopt[$k] ) )
        $shortopt[$k]['value'] = !strcasecmp( $shortopt[$k]['value'] , 'false' ) ? true : false ;
    else if( isset( $shortopt[$k.':'] ) )
        $shortopt[$k.':']['value'] = $v;
}

// check value (cannot be NULL)
$check_out = '';
foreach( $shortopt as $k => $v )
    if( !isset( $v['value'] ) )
        $check_out .= "\t".$v['text']."\n";
if( !empty( $check_out ) )
{
    echo "Usage> php ".$argv[0]." -h\n";
    echo "Must Set:\n$check_out\n";
    exit;
}

$cookie_file = !empty( $shortopt['c:']['value'] ) ? $shortopt['c:']['value'] : NULL ;
$source_url = $shortopt['u:']['value'];
$output_file = !empty( $shortopt['o:']['value'] ) ? $shortopt['o:']['value'] : NULL ;
$regexp_pattern = $shortopt['e:']['value'];

if( !empty( $shortopt['m:']['value'] ) )
    $shortopt['m:']['value'] = trim( $shortopt['m:']['value'] );
$choose_match = !empty( $shortopt['m:']['value'] ) ? explode( ',' , $shortopt['m:']['value'] ) : NULL;
$test_mode = empty( $choose_match ) || $shortopt['d']['value'];

$ch = curl_init();
curl_setopt( $ch , CURLOPT_URL, $source_url );

if( !empty( $cookie_file ) )
{
    curl_setopt( $ch , CURLOPT_COOKIEFILE , $cookie_file );
    curl_setopt( $ch , CURLOPT_COOKIEJAR , $cookie_file );
}
curl_setopt( $ch , CURLOPT_RETURNTRANSFER , true );

$result = curl_exec( $ch );

if( preg_match_all( $regexp_pattern , $result , $matches ) )
{
    $target_url = getTargetURL( $matches , $choose_match );
    if( $test_mode || empty( $target_url ) )
    {
        echo "Matched Target URL: \n";
        print_r( $matches );
        echo "Choose option(Cannot be empty):".$shortopt['m:']['value']."\n";
        echo "Target(Cannot be empty):$target_url\n";
    }
    else
    {
        curl_setopt( $ch , CURLOPT_URL, $target_url );
        curl_setopt( $ch , CURLOPT_REFERER , $source_url );

        if( !empty( $cookie_file ) )
        {
            curl_setopt( $ch , CURLOPT_COOKIEFILE , $cookie_file );
            curl_setopt( $ch , CURLOPT_COOKIEJAR , $cookie_file );
        }

        if( !empty( $output_file ) )
        {
            echo "Target URL:$target_url\n";
            echo "Referer URL:$source_url\n";

            if( ( $fp = fopen ( $output_file , 'wb' ) ) == NULL )
            {
                echo "ERROR: Cannot open the output file to write:$output_file\n";
                exit;
            }
            curl_setopt( $ch , CURLOPT_FILE , $fp );

            echo "Begin...\n";
            curl_exec( $ch );
            echo "...Finish\n";
            fclose( $fp );
        }
        else
        {
            curl_exec( $ch );
        }
    }
}
curl_close( $ch );
exit;

function getTargetURL( $matches , $choose )
{
    if( !isset( $matches ) )
        return NULL;
    if( is_array( $matches ) && is_array( $choose ) && count( $choose ) > 0 )
    {
        $index = array_shift( $choose );
        if( isset( $matches[ $index ] ) )
            return getTargetURL( $matches[ $index ] , $choose );
        return NULL;
    }

    if( !is_array( $matches ) )
        return $matches;
    else if( isset( $matches[ $choose ] ) )
        return $matches[ $choose ];
    return NULL;
}
?>


用法:


單純以抓 Yahoo! New 為例


尚未指定 -m
# php my_wget.php -u 'http://tw.yahoo.com' -e '/<h3><a href="([^"]+)" title="([^"]+)"/is'


Matched Target URL:
Array
(
    [0] => Array
        (
            [0] => <h3><a href="news/a/h1/t/*http://tw.news.yahoo.com/article/url/d/a/100628/5/289yr.html" title="莫拉克風災 學者:無關暖化"
            [1] => <h3><a href="news/a/h2/t/*http://tw.news.yahoo.com/article/url/d/a/100628/69/289tr.html" title="立院藏七寶 總價數億元"
        )

    [1] => Array
        (
            [0] => news/a/h1/t/*http://tw.news.yahoo.com/article/url/d/a/100628/5/289yr.html
            [1] => news/a/h2/t/*http://tw.news.yahoo.com/article/url/d/a/100628/69/289tr.html
        )

    [2] => Array
        (
            [0] => 莫拉克風災 學者:無關暖化
            [1] => 立院藏七寶 總價數億元
        )

)
Choose option(Cannot be empty):
Target(Cannot be empty):


指定 -m '1,1'
# php my_wget.php -u 'http://tw.yahoo.com' -e '/<h3><a
href="([^"]+)" title="([^"]+)"/is' -m '1,1'


Matched Target URL:
Array
(
    [0] => Array
        (
            [0] => <h3><a href="news/a/h1/t/*http://tw.news.yahoo.com/article/url/d/a/100628/5/289yr.html" title="莫拉克風災 學者:無關暖化"
            [1] => <h3><a href="news/a/h2/t/*http://tw.news.yahoo.com/article/url/d/a/100628/69/289tr.html" title="立院藏七寶 總價數億元"
        )

    [1] => Array
        (
            [0] => news/a/h1/t/*http://tw.news.yahoo.com/article/url/d/a/100628/5/289yr.html
            [1] => news/a/h2/t/*http://tw.news.yahoo.com/article/url/d/a/100628/69/289tr.html
        )

    [2] => Array
        (
            [0] => 莫拉克風災 學者:無關暖化
            [1] => 立院藏七寶 總價數億元
        )

)
Choose option(Cannot be empty):1,1
Target(Cannot be empty):news/a/h2/t/*http://tw.news.yahoo.com/article/url/d/a/100628/69/289tr.html


正式要下載請記得加 -d (disable test) , 但此例不適用, 因為抓出來的 url 並不完整, 開頭只是 "news/a/h2/t/*....."
# php my_wget.php -u 'http://tw.yahoo.com' -e '/<h3><a
href="([^"]+)" title="([^"]+)"/is' -m '1,1' -d


輸出到檔案
# php my_wget.php -u
'http://tw.yahoo.com' -e '/<h3><a
href="([^"]+)" title="([^"]+)"/is' -m '1,1' -d -o '/tmp/output'


需要 cookie

# php my_wget.php -u
'http://tw.yahoo.com' -e '/<h3><a
href="([^"]+)" title="([^"]+)"/is' -m '1,1' -d -o '/tmp/output' -c '/tmp/cookie'


UIImageWriteToSavedPhotosAlbum Domain=ALAssetsLibraryErrorDomain Code=-3301 "寫入忙碌中"

前陣子在 iPhone 模擬器上寫之儲存照片的小程式,使用連續儲存的方式,發現可以很順利,但是移到實體機子上頭,卻發現儲存的照片張數不對,假設有 10 張照片要儲存,但真正存進去的只有 5 張不到,而且現象是有時張數多有時張數少,但一定沒有達到 10 張。


後來透過 error report 並在實體機上跑時才看到錯誤訊息:


Error Domain=ALAssetsLibraryErrorDomain Code=-3301 "寫入忙碌中" UserInfo=0xXXXXXX {NSLocalizedFailureReason=, NSLocalizedRecoverySuggestion=再試著寫入一次, NSLocalizedDescription=寫入忙碌中}


偶爾也還有看到 sqlite3 等訊息(但它有容錯處理)


sqlite error 5 [database is locked]
sqlite prepare statement retry was successful.  Continuing.


而真正的問題還是儲存照片問題,最後則是想到用 sleep 的方式解決,也就是避開短時間儲存,改成每存完一張 sleep 1.5 秒


- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error
contextInfo:(void *)contextInfo
{
        if( error != nil )
               
NSLog( @"SaveError(%f,%f) Message:%@", image.size.width,
image.size.height, error);
}

- (void)saveToPhotoLibrary
{
    for( id raw_data in [images allValues] )
    {
        UIImage *image = [[UIImage alloc] initWithData:raw_data];
        UIImageWriteToSavedPhotosAlbum( image, self, @selector(image:didFin    ishSavingWithError:contextInfo:), nil );
        [image release];
        [NSThread sleepForTimeInterval:1.5];
    }
}


修正後,還是偶爾會出現其他的訊息,並且隨著 Photos Library 內的圖片增加,導致訊息顯示會越來越頻繁甚至不正常當掉(當掉可能比較跟記憶體控制有關)


slow transaction: time was 0.xxxxxx seconds
Received memory warning. Level=1
Received memory warning. Level=2


暫時還沒想到恰當的解法(可以考慮依 Photos Library 的照片張數調整 sleep 秒數),未來設計上可能還是要避開把資料塞進 Photos Library 吧!


2010年6月27日 星期日

iOS 開發教學 - 讓 App 支援多國語言、依語言顯示 App 名稱

LocalizableString

iTunes App Store 可在於多個國家販售、散佈分享所寫的 App,因此,客製化不同語言的操作介面是很基本的功夫,概念上很簡單,凡是在 App 上使用 NSString 印出來的字串,都可以透過查表的方式,依照設定的語言,更改成想要的。

如:

UILabel *show = [[UILabel alloc] init];
show.textLabel.text = @"Hello Moto";

希望可以依照語言改變時,那就改用 NSLocalizedString 處理:

UILabel *show = [[UILabel alloc] init];

show.textLabel.text = NSLocalizedString( @"Hello Moto" , @"note" );

其中後面的 @"note" 也可以空白,那類似註解、用來回憶的,但如果要顯示的文字已經很有記憶性跟意義性,那其實後面就乾脆留白就好囉。透過上述的使用,如果沒有建立甚麼查表,那其實 show.textLabel.text = NSLocalizedString( @"Hello Moto" ,
@"note" ); 等同於
show.textLabel.text = @"Hello Moto"; 這樣的效果,也就是可以把一堆常用輸出的部分改成 NSLocalizedString 也不會怎樣。

接著建立查表,在此僅提供簡單的 source code 轉換的部分,關於使用 Interface Builder 也可以透過 Xcode 介面產出,只是我沒再用啦,所以也忘了。

$ cd YourProject ;
$ mkdir English.lproj ;
$ genstrings -o English.lproj/ iPhone/*.m iPhone/*.h iPad/*.m iPad/*.h *.m *.h

透過上述三步,就將 YourProject 裡頭所有的 *.m 和 *.h 裡頭用到的 NSLocalizedString 部份,拉出來見出可以查詢的表格,並儲存在 YourProject/English.lproj/ 中,其內文:

/* No comment provided by engineer. */
"Hello Moto" = "Hello Moto";

這等同於 Key-Value Pair 的模式,因此,如果你希望在繁體中文顯是為"你好機車",那對應則改成:

/* No comment provided by engineer. */

"Hello Moto" = "你好機車";

以上是簡短的範例。真正的使用的過程,一開始是使用 genstrings 產生出 Localizable.strings 檔案,接著把他拖拉進去 Project 中,這時彈跳的視窗記得要選成 UTF-16 ,而當你想要新增另一個語言時,則是對 Localizable.strings 點選右鍵挑選 Get Info,彈跳的視窗下面則可以直接按 Add Localization ,接著可以打入語言,如 zh_TW 、 zh_CN 等

utf16

Add Localization

如此一來,在 Localizable.strings 下則會自動幫你複製一份 zh_TW 出來,而 Project 目錄下也會自動產生 zh_TW.lproj 目錄。接著一樣的用法,只是變成在 zh_TW 語言時,想要顯示的字是甚麼。而 App 預設的語言是 English ,可以在 Project-Info.plist 中 Localization native development region 項目裡看到這個設定值。

經過上述的設定,你的程式就可以依照設備的語言,顯示內容囉,然而,還有一種設定,那就應用程式在設備上顯示的名稱,此部分則是另外在建立一個檔案 InfoPlist.strings 並且拉到 Project 裡頭,一樣挑選 UTF-16 ,其內文:

CFBundleName = "HelloMoto";  
CFBundleDisplayName = "HelloMoto";

一樣可以透過對 InfoPlist.strings 進行 Get Info 和 Add Localization 來建立其他語言的顯示。

木偶人動畫電影版"BBS鄉民的正義"預告片





 


第一次是看鐵拳無敵孫中山,這次預告片拍得很讚!看到這些影片,都讓人想起 BBS 對台灣人的影響,有的人覺得那邊只不過是個純文字的瀏覽介面,再不然頂多加個 ASCII 圖片,或更進階成一秒秒切換成的動畫等等,除此之外,還有人設計相關的程式互動,讓你玩遊戲甚至在上頭寫寫程式等等的。但重要的並不是這些,因介面的簡單,少了許多像網站的廣告;因 telnet 協定,讓切換頁面用的資源更少更快;最重要的,裡頭的鄉民、文章、板規、站規不就等同於一個小型社會嗎?人才是最珍貴的價值,以 PTT 來說,技術上已經允許同時上線人數突破 15 萬關卡了!像最近的世足賽,其 WorldCup 可以同時超過一萬人的板友互動哩,當然,還有記者最愛挖新聞的八卦板,幾乎每次看都一定有上千人在裡頭。


前陣子高中的 BBS 站復站了,回憶起 1999 年的時光,過了十年後,到底還需不需要 BBS 呢?我想,這早就已是另一種台灣的文化了。有人再探討需要或去除哪些文化嗎?一切就讓他靜靜地翻騰吧


更多資訊請上 木偶人動畫專區


 






























99交通大學畢業 MV








很讚的 MV ,就算畢業了三年,很多場景就像昨天一般,瞬地轉入了眼簾。


2010年6月23日 星期三

[C] strtok , strtok_r 和 strtok_s

我好像還沒有真正用過 strtok,一開始還有點慌,擔心東擔心西的,用了才知道還滿順的,順便筆記一下,另外,若是要在 vistual studio 系列上,可以把 strtok_r 直接 define 成 strtok_s 就可以,細節請參考 msdn


程式碼:


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

int main( int argc, char *argv[])
{
        char  *getTag, *tokenBrk, *data;
        char *tag[100];
        int cnt=0;

        data = NULL;

        if( argc > 1 )
                data = argv[1];
        else
                data = NULL;

        for ( getTag = strtok_r( data , ", " , &tokenBrk ) ; getTag ; getTag = strtok_r( NULL , ", " , &tokenBrk ) )
        {
                tag[cnt++] = getTag;
                //printf("[%s]\n" , getTag );
        }
        printf("[Src:%s]\n" , data );

        while( cnt-- > 0 )
                printf("[%s]\n" , tag[cnt] );

        return 0;
}


結果:


> ./a.out " 1 2 , 3,45 ,678 , 90 , ,"
[Src: 1]
[90]
[678]
[45]
[3]
[2]
[1]


原先一直很好奇為啥 getTag 取出來的字串有 '\0' 結尾,後來就把原字串印出來就知道了,因為那些 ',' 或 ' ' 都被取代掉囉!


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 )