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 的瀏覽器中測試