2010年8月31日 星期二

Javascript Unzip Testing

前陣子一直在調校 Javascript & Unzip 的事情,找到一個滿貼切工作的 -- Booktorious ,並且開始修改它。只是再怎樣地修改,在 Mobile Device 都不太適用,也被提醒會不會挑到的程式沒有實做很好,這部份我有留意它 unzip 的部份,的確存在不少可以精進的地方,當我準備要改得時候,我又看到了 rePublish 裡頭用的 zip 其實就已經接近我要改善的方式,因此筆記一下比較的過程,之後有空再慢慢增加其他對應的 library。


整個過程,看起來有點線性的成長,隨著檔案大小的增加,解壓縮的時間也會接近倍數成長。而 Booktorious 之 js-unzip 裡頭,有用到大量的資料複製,所以時間上花費會更多,相對於 rePublish 之 zip 的使用,對於 raw data 採用紀錄 offset 的方式,因此比 Booktorious 更加接近線性關係。


函式庫:



測資(純粹看 size 關係而非內容或檔案數目):



@ AMD X4 955, Ubuntu 10.04 i386, DDR3-1333 4GB, Google Chrome 6.0.495.0 dev


91KB


0.034s @ [js-zip & js-inflate]
0.026s @ [zip & inflate]


929KB


0.302s @ [js-zip & js-inflate]
0.139s @ [zip & inflate]


9.2MB


14.945s @ [js-zip & js-inflate]
1.654s @ [zip & inflate]


@ iPad, iOS 3.2.2


91KB


2.182s @ [js-zip & js-inflate]

1.214s @ [zip & inflate]


929KB


JavaScript execution exceeded timeout @ [js-zip & js-inflate]

7.177s @ [zip & inflate]


9.2MB


JavaScript execution exceeded timeout @ [js-zip & js-inflate]
JavaScript execution exceeded timeout @ [zip & inflate]


@ iPhone 3G, iOS 4.0.2


91KB


8.438s @ [js-zip & js-inflate]
5.397s @ [zip & inflate]


929KB


JavaScript execution exceeded timeout @ [js-zip & js-inflate]
JavaScript execution exceeded timeout @ [zip & inflate]


9.2MB


JavaScript execution exceeded timeout @ [js-zip & js-inflate]
JavaScript execution exceeded timeout @ [zip & inflate]


以下是實驗的 Source Code,而測試中如果瀏覽器已經等很久甚至產生 timeout 的訊息時,試著一次只測試一個 library 吧,並且在 iPad 或 iPhone 也有機會碰到直接跳出 Safari 的情況


@index.html


<!DOCTYPE html>
<html xml:lang="utf-8" lang="utf-8" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <script type="text/javascript" src="js-unzip.js"></script>
        <script type="text/javascript" src="js-inflate.js"></script>
        <script type="text/javascript" src="zip.js"></script>
        <script type="text/javascript" src="inflate.js"></script>
    </head>
    <body>
<script language="Javascript">
    function report( s, clear )
    {
        var report = document.getElementById( 'report' );
        if( clear )
            while ( report.hasChildNodes() && report.childNodes.length >= 1 )
                report.removeChild( report.firstChild );
        if( s )
        {
            report.appendChild( document.createTextNode( s ) );
            report.appendChild( document.createElement( 'br' ) );
        }
    }
    function doZip( epub , choose )
    {
        var path = epub || "epub/91kb.epub";
        var ajReq = new XMLHttpRequest();
 
        try{
            ajReq.open( 'GET' , path , false );
            ajReq.overrideMimeType( 'text/plain; charset=x-user-defined' );
            ajReq.send(null);
            ajReq.overrideMimeType( 'text/plain; charset=UTF-8' );
 
            var out = ajReq.responseText || '' ;
            var out_array = [];
            for( var i=0, len=out.length, scc=String.fromCharCode ; i<len ; ++i )
                out_array[i] = scc( out.charCodeAt(i) & 0xff );
            var out_binary = out_array.join( '' );
 
            if( out_binary !== '' )
            {
                var cost ;
                var unzipper;
 
                report( null, true );
 
                //
                // js-unzip & js-inflate
                //
                if( !choose || choose === 1 )
                {
                    unzipper = null;
                    cost = new Date();
                    unzipper = new JSUnzip( out_binary );
                    if( unzipper.isZipFile() )
                    {
                        unzipper.readEntries();
                        for (var i = 0 , len = unzipper.entries.length; i < len ; i++)
                        {
                            if ( unzipper.entries[i].compressionMethod === 0)
                                ; // unzipper.entries[i].data
                            else if ( unzipper.entries[i].compressionMethod === 8)
                                JSInflate.inflate( unzipper.entries[i].data );
                        }
                    }
                    cost = new Date() - cost ;
                    report( (cost / 1000.0) + 's' + ' @ [js-zip & js-inflate]' );
                }
 
                //
                // zip & inflate
                //
                if( !choose || choose === 2 )
                {
                    unzipper = null;
                    cost = new Date();
                    unzipper = Zip;
                    unzipper.Archive( out_binary );
                    for (var i = 0 , len = unzipper.entries.length; i < len ; i++)
                        unzipper.entries[i].content();
                    cost = new Date() - cost ;
                    report( (cost / 1000.0) + 's' + ' @ [zip & inflate]' );
                }
            }            
 
        }catch( err ){
 
            alert( 'Error:' + err );
 
        }
    }
</script>
        <dl>
            <dt>Do all</dt>
            <dd><button onclick="doZip( 'epub/91kb.epub' );">Unzip 91KB</button></dd>
            <dd><button onclick="doZip( 'epub/929kb.epub' );">Unzip 929kB</button></dd>
            <dd><button onclick="doZip( 'epub/9.2mb.epub' );">Unzip 9.2MB</button></dd>
        </dl>
        <dl>
            <dt>Use Booktorious</dt>
            <dd><button onclick="doZip( 'epub/91kb.epub' , 1 );">Unzip 91KB</button></dd>
            <dd><button onclick="doZip( 'epub/929kb.epub' , 1 );">Unzip 929kB</button></dd>
            <dd><button onclick="doZip( 'epub/9.2mb.epub' , 1 );">Unzip 9.2MB</button></dd>
        </dl>
                    
        <dl>
            <dt>Use rePublish</dt>
            <dd><button onclick="doZip( 'epub/91kb.epub' , 2 );">Unzip 91KB</button></dd>
            <dd><button onclick="doZip( 'epub/929kb.epub' , 2 );">Unzip 929kB</button></dd>
            <dd><button onclick="doZip( 'epub/9.2mb.epub' , 2 );">Unzip 9.2MB</button></dd>
        </dl>
        <p id="report"></p>
    </body>
</html>


2010年8月30日 星期一

追風








昨天睡前想了一會兒,在思考要如何過好生活。記得幾天前還跟同事閒聊,要不要拿進入 G社 當作目標?那種感覺並不是非要進去不可,是拿個目標要求自己,至於現在?完全找不到別人要聘我的理由。


除此之外,也想了一些可能可以列入考慮的事物:



  • 買一台冰箱

  • 買幾個鍋子

  • 買一台電鍋

  • 買一張氣墊床


理由是我的房門前就是宿舍的交誼廳,有電磁爐跟微波爐。過去三餐正常,導致我連煮東西的想法也都沒有,但現在仔細想想,不希望兩年這樣過了,我還是僅學會寫程式賺錢而已。


距離公司宿舍外頭約三分鐘的車程,有一間超商,好像叫五聯社,有點像全聯,印象中有蔬果類的,似乎很適合補貨;前陣子也買了公司健身房的年費,希望一星期可以運動三次啦。


以上是目前暫時想到的生活規劃


2010年8月29日 星期日

PassionRepublic Taiwan Trip








很讚的台灣之旅!這大概是我看過的旅遊影片中的佼佼者吧!從學長 Facebook 的分享來看,這是「馬來西亞動畫設計公司(Passion Republic)員工旅遊」之五天旅程,僅用Canon EOS 7D單眼攝影相機及GoPro Hero防水運動攝影機!而最近 PTT 也有一系列討論馬來西亞的文化等等的,台灣真的挺讚的!


很佩服這影片以及這行動!旅行的途中又可以將所學的應用上,我想這是最難的可貴的。記得大五那年我也想試著用所學的嘗試改善生活,例如使用簡單的物理常識,讓家裡更通風等等的,我覺得這真的比賺錢還實在。希望未來也能行之有餘,用所學的幫助別人。


2010年8月28日 星期六

iPad 出包記! Jailbreak 後無法正常啟動

週五下午接到主管的指令,要我把手上那台 iPad 給他 jailbreak 一下,小弟我當然就給他 try 一下。由於那台是 iOS 3.2.1 (最初是 iOS 3.2) ,所以就馬上試試 www.jailbreakme.com 這個網站的高招方式!它是利用 PDF 的漏洞(iOS 3.2.2 已修正),只要 iPad 透過 Safari 瀏覽器,就可以進行 jailbreak ,超方便的。


只是越獄後的 iPad ,還是少了主管要我做的相關資料,並且操作上有點怪怪的,主管就叫我重弄看看,我一時耍小聰明,直接用 General > Reset 的方式把資料清空,當下是因為沒有存 iPad iOS 3.2.1 ,直接用 iTunes 進行恢復大概也只能弄成 iOS 3.2.2 版,所以一時就想試看看 Reset 的方式,結果,試完重開機後,整個 iPad 就無法正常操作。


通常越獄的機器,開機後除了第一張最初的 iOS 的開機畫面,接著會顯示第二張圖片用來標記越獄成功,而我那台 iPad 就是一直停在第二張圖,直到天荒地老 XD 並且無法正常關機,而使用 Home + Power 也頂多是重開機而已,並且因為還沒正常啟動,因此 iTunes 不會辨認出它也無法進行回復。週五晚上六點多,大概覺得沒救了要送修,所以就寄了信回報,就把它擺在旁邊,過了一晚他還是卡在那邊 XD


後來,周六早上想起來,可以利用長壓 Home + Power 鍵讓他進入回復/開發/初始狀態?也就是會看到一條 USB 線與 iTunes 的那個圖。於是,先透過 Home + Power 鍵令他重開機,接著仍舊不放手,不久後就切換到 UBS 線與 iTunes 的圖啦,而 iTunes 就可以偵測到那台 iPad ,雖然顯示有點不正常,但至少可以做回復的動作了!我二話不說,馬上升到 iOS 3.2.2 啦,因為我也懶得去找 iOS 3.2.1 啦,能夠救回 iPad 就好,能不能 jailbreak 以後再說 XDD


就這樣,iPad 驚魂記終於落幕了。由於這台是美國買的吧?送修應該會很麻煩吧 orz 只能慶幸去年十月自己也有把一台 iPhone 3G 越獄過,那台是僅限 AT&T 的 sim 卡,所以我不得不把它越獄啦,那時的越獄方式複雜多了!沒想到那時的經驗還是有幫助到 :D 而這次 iPad 碰到的問題,我猜可能是因為 firmware 越獄後,啟動時可能還要從 disk 讀取一些資訊吧,然而資料卻被我清空,導致讀不到資料卡在那邊吧!


@ 2010/08/29 : 那個 USB + iTunes 圖是 DFU Mode ;而 iPad 部分,據說只要原先是 3.2 版,雖然更新到 3.2.2 但透過修改 hosts ,還是可以在用 iTunes 時可以按住 Shift + 回復,就可以使用 iPad iOS 3.2.1 囉,這部分我還沒測試。至於 ipsw 下載位置可以找尋 Google 囉!以下是找到的 Apple 官方(http://appldnld.apple.com/*) 下載位置:



2010年8月25日 星期三

[Python] BBS Crawler 筆記

遙想台灣 BBS 的興起,大概是 1997 前後,至少我是那時候開始接觸的?記得那時可還用著 33.6Kb 的數據機,後來又升級到 56 Kb ,當年好像是我姐說想要玩 BBS ,然後家裡就多牽了一條電話線,就這樣開啟玩網路的年代。除了 BBS 外,還有 ICQ 等聊天軟體,另外我則是跟隨同學的腳步,常常去"史萊姆的第一個家"找些新奇的軟體,當然,還有 MIDI 音樂等等的。但最後,還是跌入 BBS 裡頭,在那個青澀的年代裡。


前陣子想到撈一下 BBS 裡頭的資料,於是挑選了 Python 這個語言,它有 telnetlib 跟 expect 可以用,就等於解決撈 BBS 的最大困難之處。


範例一,使用帳號密碼連到指定的站台:


import telnetlib

tn = telnetlib.Telnet( SITE_IP )
q = tn.expect(['使用者帳號:'] , 10)
tn.write( USER_ID + '\r' )
tn.expect(['密碼:'] , 10 )
tn.write( PASSWORD + '\r' )


其中 "使用者帳號:" 跟 "密碼:" 這是兩個很重要的 pattern ,並且依各家 BBS 的情況都不一樣。例如 PTT 就是"以 new 註冊:" 和 "請輸入您的密碼:" 等。上述的程式碼依序是連到 SITE_IP 這個位置的 BBS ,並且收集封包,等碰到第一個 pattern 時,才將 USER_ID + '\r' 的資料丟出去,然後繼續收集資料直到第二個 pattern 出現,在做對應的動作,此處是丟出密碼。


透過上述例子,我想應該就十分清楚了!由於 telnet 等同不間斷地丟資料過來,因此在判斷執行下一個動作前,要去等待某個關鍵 pattern ,以 BBS 之 24X80 的頁面,大部分就是挑換頁完後最後一個 pattern ,如最右下角等。


其實,這個筆記就只有一個範例就收工了 XD 老狗變不出新把戲啦!說說其它的技巧


def skip():
        q = tn.expect( ['pattern1', 'pattern2', 'pattern3'] , 10 )
        if q[0] == 0:
                tn.write('Key1' + "\r")
                skip()
        elif q[0] == 1:
                tn.write('Key2' + "\r")

                skip()

        elif q[0] == 2:

                return True


這個就是直到看到 pattern3 才離開,並且看到 pattern1 或 pattern2 則分別丟出指定的關鍵字出去。這種用法有時會需要。而為啥 expect 後面有接一個數字,那是因為避免收集不到 pattern 而一直卡在那邊,也就是 timeout 的使用,我覺得可以用在特殊情況,像是碰到 Server 掛掉沒丟完資料,或是自己耍蠢沒弄好全部的 pattern ,導致整個連線一直卡在那邊等到天荒地老啦。


其他常用的就是 Regular Expression 吧


import re

raw = re.sub( re_pattern_replace_space , '' , rawData )

out = re.findall( re_pattern_get_items,  raw )
for sub_item in out:
        pass

check = re.search( re_pattern_check , raw )
if check <> None :
        raw_pattern = check.group(0)


另外,還有丟Key 給 BBS Server 的部分,要去找一下對應表,例如上下左右是對應哪個 ASCII code ,我後來偷懶都用英文而已,例如離開可以用左鍵也可以按 q 鍵,如此等等


最後,提醒一下,有時候 BBS 為了效能的關係,每一次並不是吐一整個完整 24X80 的頁面,也就是並非每次都把整個頁面更新,有時候只更新部分資料,例如這次的頁面跟上次的只差一點點,因此 Server 就可能只丟出要更新的資料並利用控制碼去更新,所以,要判斷頁面是否已收集完畢,不一定能靠最右下角那個 pattern 喔!這是我實作上碰到一個很關鍵的地方。如此一來,整個 telnet crawler 就能完工囉!


其他資料請參考 Python - telnetlibPython - re,別忘了留意使用的 Python 版本是否合用喔。


2010年8月24日 星期二

騰雲駕霧








不知不覺又過了一年了!今年實驗室的學弟妹很可惜沒有進入決賽,不能享受到免費的投籃機、泡麵、飲料還有其他吃喝拉撒的活動!


總覺得去年好像真的運氣很好,趨勢很費勁地邀請學生參加比賽,大手筆補助學生到台北上課,甚至車錢還住宿都有補助,當時初賽前好像在師大那邊聽了一場,不過很快就遺忘,畢竟實驗室沒再用 open source,喜歡自己刻東西,因此,入決賽還滿意外的,因為絕大部分的團隊都早已對 Hadoop 熟的很,甚至實驗室早都在用了。


回想起來還滿搞笑的,初賽是七月一號,那天對我們 team 來說是正式開始使用 Hadoop 的第一天,把趨勢附的 CentOS VM 環境弄一弄,開始試試傳說中的 word count!然後自己想要複習一下 Java ,想要設計一個非常 General 的 sorting 還 couting,可以吃任何格式,結果就這樣過了幾天,過了一個週末後,真正有用的產出是零,最後在星期一時下定決心,還是回歸到 C 語言!果然是多 C 多健康。而其實 C 語言就是以字串來處理 data type ,換句話說也達到我想玩的東西。


隨後開始正式去討論要做甚麼,就突發奇想設計一個不錯的 indexing + sorting 機制,只要跑兩次 MapReduce 就可以建立一個 sorted data + indexed meta data,於是跟同隊的越聊越黑皮,搞得好像很厲害似的,接著開始刻一個簡單的 Web 整合介面,用 PHP 語言呼叫 MapReduce Job ,並且提供 Web 觀看執行的過程跟成果,用表格輸出結果。最後,則是 paper work ,隊長帶領著大家把報告生出來並進入決賽啦!


至始至終都只用到 C 跟 Hadoop,天殺地認為用 C 最快啦。但直到工作後我才知道,那個 HBase 才是真正可以做 Real time query 的服務,HBase 會把常用的資料存在記憶體中,不像 Hadoop 每次執行都是從 Disk 讀出來處理!難怪當初決賽時,別人用 HBase 三兩下就跑完測資,我弄的部分要跑超久,連 QA 都不耐煩了 :P 雖然最後沒拿到預聘書,但大家的出路也都還不錯囉,忘了一提,那年是碩二下參賽,七月底還要口試!咱們是七月初弄完比賽就趕著寫論文,接著口試完過沒多久就公布決賽名單。好險那時還沒接著參加通訊大賽,不然一定兩頭空啊。


這幾天跟同事閒聊一些程式效率,頗有一點點以前實驗室大家一起寫程式的快感。隨著工作環境,現在都一股腦兒地使用 Open Source ,雖然時間很珍貴,但我覺得真正的樂趣卻是建立那些 framework 才會體會到的啦。


2010年8月22日 星期日

碎碎念

經過幾周的起起伏伏,心裡回顧起來好像沒有踏實感?看起來在追求什麼,其實只是被數字把玩著。


前幾天看到了這篇文章 Ph.D. 圖解 (The illustrated guide to a Ph.D.) ,當下的第一個觀感是--等價交換。 Dr. 是某個領域的尖端,但換個角度來說,卻也可能失去很多很多,或者該說,富有到只剩那個領域?不禁想起國中時,常常聽到 xx 博士不會用電鍋,把電鍋放在瓦斯爐的玩笑。現實生活中應該不會那麼糟,但同樣也有取捨,像是成天追求技術的人,會不會連最簡單的生活也不會享受呢?成天把時間抓得緊緊的人,又怎能知道在樹蔭下午睡的樂趣呢?


傍晚看到這則 - 非關命運:媽!我不是妳的小木偶(4/5) 20100818 ( http://www.youtube.com/watch?v=2pKMtcZeb9A )


童話故事寓意 - 父母喜歡用數字來判斷兒女朋友的優劣

因為大人們喜歡數目字
當你跟大人談起一位新朋友的時候
他們從來不會問你主要的事情
他們從來不會問你
你這朋友聲音怎樣
你這朋友喜歡什麼遊戲
他有收集蝴蝶嗎
大人只會問你

他幾歲了
他有幾個兄弟
他體重多少
他父親有多少收入
他爸爸在做甚麼
他媽媽在做甚麼

他們才相信你認識他

假如你告訴那些大人說
我看見一間用玫瑰色紅磚蓋成的房子
裡面有天竺葵  屋頂上有鴿子
他們無法想像得出這間房子
你應該告訴他們說

地段在哪裡  房子價值多少
然後他們才會說
真是一個豪宅  多麼美麗啊


有點諷刺,我想連我自己因為環境漸漸地也有點落入這種情境,看到這笑了一下,不禁也嘆了口氣。我的父母並沒有這樣對我,反而是自己漸漸地待人有點像上述那樣,凡事好像在追求效率,每一句話都要得到最佳的結果而鋪呈。趁這個機會提醒一下自己,除此之外,也要警告自己快點找到人生目標吧~


2010年8月13日 星期五

[Javascript] 處理 binary 的編碼問題

之前 把玩 Booktorious 時,碰到編碼的問題。苦思良久,今天發現,原來 Booktorious 的片段程式碼早就解決了!只是他只用在 XHTML 檔案而已。


整個流成是將一個 EPUB/ZIP 檔案,以 binary 型態讀進 Javascript 變數中,接著使用 js-unzip 和其相關套件,將變數所儲存的資料進行解壓縮,該 EPUB 檔案主要是一些 XHTML 或 XML 組成,緊接著分批處理那些 XHTML 跟 XML 檔案,在 Booktorious 已經很完善地解決完 XHTML 部份,然而,有些文件是 XML 部份則有編碼的問題。


EPUB TEST


過去我一直認為,因為 XML 檔案描述不夠完整,導致瀏覽器無法顯示正確的編碼,如上圖左邊書目,此資料來源是一個 XML 檔案。認為資料一開始以 binary 的方式儲存在 Javascript 變數中,因為過程是從 zip 檔案解壓縮產生並未透過瀏覽器而無法處理編碼(把該 XML 檔解壓縮出來,直接用瀏覽器看則會顯示正確編碼),加上自己對 Javascript 不熟深深,覺得這大概是個瓶頸了!


在仔細確認一次 Booktorious 處理 XHTML 的部份,驚覺過程中用到了一個關鍵的步驟:


var data = decodeURIComponent(escape( raw_data ) );


其中 raw_data 就是從 EPUB 檔案透過 js-unzip 解壓縮出來的一個 XML 檔案內容,若編碼非英文,單純用 console.log( raw_data ); 則會看到亂碼。如今透過上述兩個函式的處理,則 data 就是按照內文中的編碼呈現。


最後的成果,就是正確書目囉


EPUB


2010年8月11日 星期三

[Javascript] 把玩 Booktorious

EPUB


Booktorious 是一套純 Javascript 的 EPUB reader,單純接收一個 *.epub 檔案,接著用 Javascript 進行 unzip ,接著再依照 EPUB 格式進行 parsing ,最終則把 EPUB 的內容用多個 iframe 依序成列出來。這是一套 open source 的 framework,這陣子花了一點時間把玩,此預設一次把 EPUB 全部 parsing 完,而我想更改成挑選章節的解壓縮方式。


粗略筆記原先的過程:



  1. 透過 HTML5 的 FileReader 物件,將 user 端的檔案讀進 Javascript 裡處理

  2. 使用 JSEPUB 物件進行 EPUB 檔案的處理

  3. JSEPUB 物件接著會將整個 EPUB 進行初步 unzip 的處理,如果壓縮檔跟作者所撰寫的 ZIP 或 EPUB 格式不符,則會停下來

  4. 分析 EPUB 的 META-INF/container.xml 檔案,從中得知 content.opf 位置

  5. 分析 content.opf 資訊,得知 EPUB 內所有的檔案清單與格式

  6. 依照檔案格式,如 text/css 、application/xhtml+xml 進行處理,像 XHTML 部份,處理裡頭的連結位置,甚至用到的圖片資訊會從連結改用成內嵌的方式呈現 ( src="data,image/jpeg,....." ),並透過 new DOMParser().parseFromString() 轉成 DOM 物件

  7. 最後將各個 XHTML 之 DOM 模式,再透過 new XMLSerializer().serializeToString() 寫到 iframe 裡頭呈現於網頁


我想嘗試的,則是第三步起,不對整個 EPUB 檔案作解壓縮,只依序對 META-INF/container.xml、content.opf、toc.ncx 以及對該 EPUB 的第一章或封面進行解壓縮處理。


嘗試的成果,碰到問題



  • content.opf 和 toc.ncx 亂碼問題(如上圖左邊的書目)

  • toc.ncx 其中 href 使用 test.html#123 這種用 '#tag' 方式的連結問題


前者部份,由於是 xml 格式,雖然有標明 UTF-8 編碼等等,但我單純從 Javascript 讀 EPUB、解壓縮、拿到該 xml 資料、進行 dom parsing 等等動作,都是在 Javascript 以 binary 處理,處理完仍是亂碼。若這些檔案單純用 browser 去瀏覽,則可以顯示正確的編碼。不曉得是不是獨缺給 browser 碰一下的過程,因此無法顯示正確的編碼,但如果是以 xhtml 檔案,則都可以正確呈現;對於 toc.ncx 裡頭,有些章節並不是指到一檔案而已,而是某個 XHTML 檔案的某個段落,因此就要處理如何呈現到正確位置。請教米蟲大神後,得知既然是用 JS 產生 HTML ,那則可以對該物件進行 location 的操作,如 data.location = '#tag'; 的方式。只是我測試的結果,僅能對 webkit 成功,像 Firefox 的就無法正確處理。


上述中連結解法,片段程式:


var obj_div = document.getElementById( 'show' );  // <div id="show"></div>
var data = xhtml_dom_structure;                           // 一個 xhtml 檔案,已透過 DOMParser 處理
var iframe = document.createElement("iframe");    // 動態產生 iframe
var target = '#tag';                                                // 打算指到 test.xhtml#tag 位置

var doc = iframe.contentDocument ? iframe.contentDocument : iframe.contentWindow ? iframe.contentWindow : iframe.document ? iframe.document : null;

if(doc == null)
    throw "Document not initialized";

doc.open();
doc.write( new XMLSerializer().serializeToString(data) );
doc.close();

if( target && doc.defaultView && doc.defaultView.location )
    doc.defaultView.location = target;


因此也不算是解掉,僅能在 Google Chrome 或 Safari 使用。


原先只是單純想把 EPUB 解壓縮從全部解壓縮改成部份解壓縮,以此加速呈現,經過幾番測試,速度的確可以拉到 1 秒內完成,但這是在 AMD X4 945 的主機,並搭配 Google Chrome 的成果。以一個 1MB 的檔案,預設全解花 6.5 秒左右(使用Firefox約 13 秒),改成只解開必要的以及第一章約 0.2 秒就可以完工了!但是,移到 iPad 上測試卻要花 8 秒處理!更何況是 iPhone 3G 呢(事實上iPhone 3G + iOS4並未正常執行該程式,因此無法測試所需時間)。


最後,如果 EPUB 的壓縮格式有問題,可以試著解壓縮後重新壓縮,其指令如下:


$ unzip test.epub -d test
$ cd test
$ zip -0Xq ../new.epub mimetype
$ zip -Xr9D ../new.epub *


另外,也可以確認一下 mimetype 內容是否為 "application/epub+zip"


簡單的 bash


#!/bin/sh
if [ -z $1 ]; then
        echo ''
        echo 'This is a tool to zip current work dir in to an EPUB file'
        echo ''
        echo 'Usage>' $0 'output_path.epub'
        echo ' e.g. ' $0 '/tmp/out.epub'
        return
fi

if [ ! -e 'mimetype' ]; then
        echo '[Error] No mimetype'
        return
fi

if [ ! -e 'META-INF/container.xml' ]; then
        echo '[Error] No META-INF/container.xml'
        return
fi

zip -0Xq $1 mimetype
zip -Xr9D $1 *


2010年8月2日 星期一

[Javascript] 讀取 Binary 資料存入變數中

將檔案資料讀取到 Javascript 變數中,大概可以分成兩個方向,一種是將遠端的檔案透過 URL 讀取進來,另一種則是從本地端透過使用者選取的檔案讀取進來。在此後者使用 HTML5 的 File API 進行處理。


先提一下遠端檔案, Ajax query 雖然已經很常見了,但對於要將讀取的資料以 binary 姿態儲存在變數之中,還是需要額外處理:


var raw_data = null;


var path = "test_file.zip";
var ajReq = new XMLHttpRequest();

try
{
        ajReq.open( 'GET' , path , false );
        ajReq.overrideMimeType( 'text/plain; charset=x-user-defined' );
        ajReq.send(null);

        var out = ajReq.responseText || '' ;

        var out_array = [];

        for( var i=0, len=out.length, scc=String.fromCharCode ; i<len ; ++i )

                out_array[i] = scc( out.charCodeAt(i) & 0xff );

        raw_data = out_array.join( '' );

}
catch( err )
{
        alert( 'Error:' + err );
}


而從本地端讀取使用者所選取的檔案,則使用 HTML5 - File API , FileReader


function getRawData( raw_data )
{

}

var reader = new FileReader();
reader.onload = function(e){ getRawData( e.target.result ); };
if( document.getElementById('file').files[0] )
{
        reader.readAsBinaryString( document.getElementById('file').files[0] );
}


其中在 HTML 裡,有個 <input id="file" type="file" />,而 FileReader 似乎滿好玩的,還有 readAsDataURL 可以玩玩


此例可在 Firefox 3.6 或 Google Chrome 6 等支援 HTML5 的瀏覽器中測試


2010年8月1日 星期日

[PHP] 從 5.2.x 升到 5.3.x 的問題處理 @ FreeBSD

最近查看了以前的測試機器,發現原先用的 PHP 5.2.x 系列已經被更新成 5.3.x 系列,並且連執行個 php -v 會出現 Segmentation fault,上網隨意找到一些資訊,大概是 5.3.x 系列已經包括一些常用的 lib 進來,再次請教一下學弟,發現他已處理過這方面的問題,僅需在 php5-extension 去移除那些套件,另外則是 extensions.ini 須去除或註解一些套件,如此一來便可以處理完畢。雖然我修到 php -v 不會出現問題,但還是有些 lib 不能正常運行。甚至從編譯 php5-extension 仍會出錯,主要卡在 PCRE 的部分。


參考資料


php5-extensions-1.4 Upgrade Failure, php5-filter-5.3.2, php5-imap-5.3.2 and pcre issues: Stop in /usr/ports/security/php5-filter


最後解法


查看目前安裝的 PHP 相關套件


# pkg_info | grep php


使用 pkg_delete 把他們都移除掉

# pkg_info | grep php5 | awk '{system("pkg_delete " $1)}'
# pkgdb -Fu


移除備份舊 php5 資料


# mv /usr/local/etc/php /path/backup/php5/etc
# mv /usr/local/lib/php /path/backup/php5/lib
# mv /usr/local/include/php /path/backup/php5/include


編寫 /etc/make.conf


# for PHP 5.3.x
WITH_BUNDLED_PCRE="YES"


重新編譯 php5


# cd /usr/ports/lang/php5 && make deinstall clean && make rmconfig && make install


重新編譯 php5-extension


順便加上常用的套件
+CURL        CURL support
+GD          GD library support
+IMAP        IMAP support
+MBSTRING    multibyte string support
+MCRYPT      Encryption support
+OPENSSL     OpenSSL support
+PCNTL       pcntl support (CLI only)
+PSPELL      pspell support
+SOCKETS     sockets support


# cd /usr/ports/lang/php5-extensions && make deinstall clean && make rmconfig && make config && make install