2010年7月14日 星期三

[PHP] 替已壓縮 Javascript Code 稍稍排版

有時後會需要看別人寫的 Javascript Code,很多情況該程式碼已經被壓縮或最佳化,然後程式碼就被擠成一列而已,然而,那一些展開後可能是數百數千列的程式碼。


為此,就簡單寫隻 PHP 程式碼,把 Javascript Code 稍稍地排版,例如碰到 { 和 } 要縮排等,我記得以前用 Visual C++ 時,可全選程式碼後按 shtif + F8 就可以排版好了,這已經是七年前的印象,不過手邊沒這種環境,目前只愛用 vim 啦,就簡單寫一下 PHP 來處理。在自行更改 input 跟 output 吧。


程式碼(僅適合觀看,不適合轉完又拿來用喔,因為有些像 regular expression 應該因為此排版而出錯):


<?php
        $raw = file_get_contents( 'bundle.js' );

        $level = 0;
        for( $i=0 ; $i<strlen( $raw ) ; ++$i )
        {   
                if( $level > 0 && $raw[$i] == "\n" )
                {   
                        $sp = '';
                        for( $j=0 ; $j<$level; ++$j )
                                $sp .= "\t";

                        $prev_part = substr( $raw , 0 , $i );
                        $next_part = substr( $raw , $i + 1 );
                        $raw = $prev_part . $raw[ $i ] . $sp . $next_part ;

                        $i += $level;
                }   
                else if( $raw[ $i ] == ';' )
                {   
                        $prev_part = substr( $raw , 0 , $i );
                        $next_part = substr( $raw , $i + 1 );
                        $raw = $prev_part . $raw[ $i ] . "\n" . $next_part ;
                }   
                else if( $raw[ $i ] == '{' )
                {   
                        $prev_part = substr( $raw , 0 , $i );
                        $next_part = substr( $raw , $i + 1 );
                        $raw = $prev_part . $raw[ $i ] . "\n" . $next_part ;

                        ++$level;
                }   
                else if( $level > 0 && $raw[ $i ] == '}' )
                {   
                        --$level;
                        $sp = '';
                        for( $j=0 ; $j<$level; ++$j )
                                $sp .= "\t";

                        $prev_part = substr( $raw , 0 , $i );
                        $next_part = substr( $raw , $i + 1 );
                        $raw = $prev_part . "\n" . $sp . $raw[ $i ] . "\n" . $next_part ;

                        $i += $level + 1 ;      // 1 for first newline
                }   
                echo "$level\n";
        }
        file_put_contents( 'bundle.js.new' , $raw );
?>



2010年7月12日 星期一

使用 Django 架設 Bookworm @ Ubuntu 10.04

Bookworm 是一套採用 New BSD License 的 Open Source,細節請參考 http://code.google.com/p/threepress/ ,也可以直接使用線上版:



紀錄一下在從無到有的安裝過程:



  1. 安裝 Virutalbox 

  2. 安裝 Ubuntu 10.04 32-bit

  3. 基本環境安裝

    • $ sudo apt-get install vim subversion



  4. 取得 Django 並安裝

    • $ wget http://www.djangoproject.com/download/1.0.4/tarball/
      $ tar xzvf Django-1.0.4.tar.gz
      $ cd Django-1.0.4
      $ sudo python setup.py install



  5. 取得 bookworm 程式碼

    • svn checkout http://threepress.googlecode.com/svn/trunk/ threepress-read-only



  6. 安裝相關環境

    • $ sudo apt-get install patch mysql-server mysql-query-browser python-mysqldb python-lxml python-cssutils python-beautifulsoup python-twill python-openid

    • $ cd threepress-read-only/bookworm/gdata ; sudo python setup.py install;

    • 讓 mysql 使用 utf8 (此部份是從網路上隨意找到的資料,不保證正確性)

      • $ sudo vim /etc/mysql/my.cnf

        • [client]

          • default-character-set = utf8



        • [mysqld]

          • character-set-server = utf8
            collation-server = utf8_unicode_ci
            init_connect = 'set collation_connection = utf8_unicode_ci;'





      • 重新開機( 我用 $ sudo /etc/init.d/mysql restart 好像沒啥用,最後重開機才解決)





  7. 建立 bookworm 資料庫、threepress 帳號,記得要設定好權限,避免錯誤可以先把 bookworm 的所有權限都打開

  8. 初始化與執行

    • $ cd threepress-read-only/bookworm/ ;
      $ mkdir log ; touch log/bookworm.log ;  chmod -R 777 log/ library/storage
      $ python manage.py syncdb
      $ mysql -u threepress -p
      mysql> use bookworm;
      mysql> create
      fulltext index epubtext on library_htmlfile (words);
      mysql> quit;
      $ python manage.py runserver




啟動後,連上 http://localhost:8000 就可以看到登入介面囉!只是一堆 css 似乎都不存在(請查看 urls.py ),所以版面有點亂。後來我把一本三國演義的 EPUB 電子書丟進去給他處理,看來沒啥問題。


2010年7月8日 星期四

UITableViewController 之 didSelectRowAtIndexPath 無反應的問題

想說寫個簡單的 UITableViewController 來了解一些流程細節,結果每當我按下某個項目時,卻遲遲沒有動作?確認後,發現 didSelectRowAtIndexPath 函式完全沒進入。找了一些文章,也看不出個所以然,畢竟完完全全是最簡單的 UITableViewController ,新增後只修改 numberOfSectionsInTableView 和 numberOfRowsInSection 回傳的數字而已。


經過幾番確認,最後發現是我自己呼叫的流程問題:


錯誤的程式碼:


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

    UINavigationController *n = [[UINavigationController alloc] init];
    
    MyUITableViewController *t = [[MyUITableViewController alloc] initWithStyle:UITableViewStylePlain];
    [n pushViewController:t animated:YES];
    t.title = @"MyTable";

    [window addSubview:n.view];
    [t release];
    [n release];
    // Override point for customization after application launch.
    
    [window makeKeyAndVisible];
    
    return YES;
}


錯誤的地方就只是把 UINavigationController 給 release 掉,然後他又會 release 掉 MyUITableViewController,因此關於 MyUITableViewController 物件就等於被回收掉。需留意 [window addSubview:n.view] 也只是會對那個 view 做 retain 而已,所以整個 table 還是看的到,但 controller 的事件卻無反應。


在此僅小小測試用的解法,就是不要去做 [n release] 。若是真正要跑的程式,應該是擺在 dealloc 函式中去處理,而 UINavigationController 要拉到 Interface 裡頭宣告。果真一個禮拜沒寫 iPhone 就會快忘光了!這個簡單的東西花了十來分鐘才發現。


而回到原本的測試是單純想要了解,從一個 UITableViewController 初始化、被切換到另一個 View 時,再被切回來時會呼叫哪些函數。測試的程式就只是在 MyUITableViewController 的 didSelectRowAtIndexPath 裡頭,宣告一個新的 MyUITableViewController 並 push 到 self.navigationController 裡頭,接著再 back 回來。從一開始的執行到結束所印出的訊息:


tabletest[1706:207] viewDidLoad:First
tabletest[1706:207] viewWillAppear:First
tabletest[1706:207] viewDidAppearFirst
tabletest[1706:207] >>>>In didSelectRowAtIndexPath<<<<
tabletest[1706:207] viewDidLoad:Second
tabletest[1706:207] viewWillDisappear:First
tabletest[1706:207] viewWillAppear:Second
tabletest[1706:207] viewDidDisappear:First
tabletest[1706:207] viewDidAppearSecond
tabletest[1706:207] viewWillDisappear:Second
tabletest[1706:207] viewWillAppear:First
tabletest[1706:207] viewDidDisappear:Second
tabletest[1706:207] viewDidAppearFirst


2010年7月3日 星期六

iOS 開發教學 - 使用 UIScrollView 顯示照片清單

UIScrollView

筆記之前透過 UIScorllView 呈現跟 iPhone 內建的照片瀏覽程式的類似效果

關鍵程式碼:

-
(void)viewWillAppear:(BOOL)animated

{
   [super viewWillAppear:animated];
   CGRect currFrame =
[[UIScreen mainScreen] bounds];

   
   [scroll setDelegate:self];
   [scroll
setBackgroundColor:[UIColor blackColor]];

   [scroll
setScrollEnabled:YES];

   [scroll setPagingEnabled:YES];

   CGSize photoView =
CGSizeMake(100, 100);

   
   int photo_cnt = 10;
   
   int col_cnt =
currFrame.size.width / photoView.width;

   int row_cnt =
photo_cnt / col_cnt + ( ( photo_cnt % col_cnt ) ? 1 : 0 );

   float pandding =
(currFrame.size.width - photoView.width * col_cnt ) / ( col_cnt + 1 );

   
   [scroll
setContentOffset:CGPointMake(0, 0)];

   [scroll
setContentSize:CGSizeMake(currFrame.size.width, (photoView.height +
pandding )* row_cnt + pandding )];


   [scroll setFrame:currFrame];
   [[self view]
addSubview:scroll];



   NSLog(@"row,col,pandding:(%d,%d,%f)", row_cnt ,col_cnt,pandding);

   
   for( int i=0 ;
i<photo_cnt ; i++ )

   {
       int x = i % col_cnt;
       int y = i /
col_cnt;


       


       UIImage *thumbnail = [UIImage imageNamed:[NSString
stringWithFormat: ( i+1 < 10 ? @"0%d.jpg" : @"%d.jpg" ), i+1]];


       UIButton *b =
[[UIButton alloc] init];

       [b setBackgroundImage:thumbnail
forState:UIControlStateNormal];

       [thumbnail release];
       
       [b
setShowsTouchWhenHighlighted:YES];

       [b
setUserInteractionEnabled:YES];

       [b setFrame:CGRectMake( x * (
photoView.width + pandding ) + pandding , y * ( photoView.height +
pandding ) + pandding , photoView.width , photoView.height )];

       [scroll
addSubview:b];

       [b release];
   }
}

使用上宣告一個 UIViewController 並且使用 UIScrollViewDelegate protocol,並且修改其 viewWillAppear 函式,除此之外,請自備 10 張圖,依序為 01.jpg, 02.jpg, ..., 10.jpg。程式碼中的 scroll 為 UIScrollView 物件。

程式流程:


  • 取得當前設備的解析度

  • 設定 scroll 物件的控制與回應、背景設成黑色等

  • 假設一張圖將使用 75x75 的大小,計算一列可擺幾張圖,以及圖與圖之間要留多少空間,以及最後有幾列,透過這些資訊決定 scroll 物件的內容大小

  • 接著使用 UIButton 來呈現圖片,並且依照圖片的順序決定顯示的位置

  • 收工!

使用 UIScrollView 算是可以很輕鬆地達到 iPhone 內建軟體的照片呈現效果,但有個很致命的缺點,那就是上述的過程是直接用原圖呈現的,所以圖片看起來會有失真,並且因為是使用原圖大小,將導致記憶體使用吃緊,並隨著圖片很多張時,問題會越來越嚴重!其他細節,可以參考 UITableViewCotrller ,其架構不錯,特別是在 Cell 的重複使用上,巧妙地控制好記憶體的使用。

因此,其實也是可以用 UITableViewController 來這個效果,只需要把 Cell 做一些手腳處理就行,如 [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; 和 [self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone]; 等。在此感謝同事的提醒,不然我還沒留意 UITableViewController 是繼承 UIScrollViewController 的!

此篇主要記錄照片座標等計算的部分,雖然使用 UITableViewController 後座標計算又不一樣並且更加簡單,但還是留念一下囉。至於縮圖方面,可以參考此篇文章:UIImage: Resize, then Crop

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'