2008年11月28日 星期五

Flash 3D 部落格文章標籤 for Blogger

原文出處: http://www.bloggerbuster.com/2008/08/blogumus-flash-animated-label-cloud-for.html

這個小套件可以將你Blog上的文章標籤以3D方式呈現, 跟隨滑鼠角度移動點選, 主要使用JavaScript and Flash animation。

你可以更改背景顏色, 字體顏色, 大小等參數, 如果觀看者不支援JavaScript與Flash, 雖然沒有動畫效果還是可以點擊。

安裝流程:

1. 先在版面配置HTML中展開小組件狀態下尋找這段程式碼。

<b:section class='sidebar' id='sidebar' preferred='yes'>

2. 在下一行貼上以下程式碼

<b:widget id='Label99' locked='false' title='Labels' type='Label'>  
<b:includable id='main'>  
<b:if cond='data:title'>  
<h2><data:title/></h2>  
</b:if>  
<div class='widget-content'>  
<script src='http://halotemplates.s3.amazonaws.com/wp-cumulus-example/swfobject.js' type='text/javascript'></script>  
<div id='flashcontent'>Blogumulus by <a href='http://www.roytanck.com/'>Roy Tanck</a> and <a href='http://www.bloggerbuster.com'>Amanda Fazani</a></div>  
<script type='text/javascript'>  
var so = new SWFObject(&quot;http://halotemplates.s3.amazonaws.com/wp-cumulus-example/tagcloud.swf&quot;, &quot;tagcloud&quot;, &quot;240&quot;, &quot;300&quot;, &quot;7&quot;, &quot;#ffffff&quot;);  
// uncomment next line to enable transparency  
//so.addParam(&quot;wmode&quot;, &quot;transparent&quot;);  
so.addVariable(&quot;tcolor&quot;, &quot;0x333333&quot;);  
so.addVariable(&quot;mode&quot;, &quot;tags&quot;);  
so.addVariable(&quot;distr&quot;, &quot;true&quot;);  
so.addVariable(&quot;tspeed&quot;, &quot;100&quot;);  
so.addVariable(&quot;tagcloud&quot;, &quot;<tags><b:loop values='data:labels' var='label'><a expr:href='data:label.url' style='12'><data:label.name/></a></b:loop></tags>&quot;);  
so.addParam(&quot;allowScriptAccess&quot;, &quot;always&quot;);  
so.write(&quot;flashcontent&quot;);  
</script>  
<b:include name='quickedit'/>  
</div>  
</b:includable>  
</b:widget>

這段程式碼中預設參數如下:

1. 寬度為 240px.
2. 高度為 300px.
3. 背景顏色為 #ffffff.
4. 文字顏色為 0x333333.
5. 文字大小為 12.

大家可以將程式碼中這些參數改成自己喜歡的樣式^^, 然後就大功告成...

2008年11月27日 星期四

信用卡校驗Java版


看了http://demo.tc/view.aspx?id=398這篇文章有感而發寫出Java版本, 以後應該有機會用到。

信用卡校驗規則為:

1.必須是十六個數字.
2.由左至右取出奇數位數字乘2, 如果大於9則減去9.
3.合併奇數位與偶數位每一個數字總合必須能夠被10整除.
4.開頭第一個字元為4則是Visa, 頭兩個字元大於50則為Master.

注意一下Java char轉int 不可以用(int)char強制轉, 否則會變成ascii code, 請使用Character的digit(char ch, int radix).我們要轉十進位數字所以radix填10.

The Character class wraps a value of the primitive type char in an object.
digit(char ch, int radix) Returns the numeric value of the character ch in the specified radix.

Sample Code:

public static String checkCreditCard(String cardNo)
{
    String result = "Wrong card number!!";
    try {
        // length validation must bigger than sixteen.
        int length = cardNo.length();
        if (length == 16) {

            String type = "";
            if (cardNo.substring(0, 2).compareTo("50") >= 0) {
                type = "Master";
            } else if (cardNo.substring(0, 1).equals("4") == true) {
                type = "Visa";
            }

            // separate the card number.
            char[] sepCard = cardNo.toCharArray();
            // create a new array.
            int[] newCard = new int[16];

            for (int i = 0; i < sepCard.length; i++) {
                if ((i + 1) % 2 == 0) {
                    newCard[i] = Character.digit(sepCard[i], 10);
                } else {
                    newCard[i] = Character.digit(sepCard[i], 10) * 2;
                    if (newCard[i] > 9)
                        newCard[i] -= 9;
                }
            }

            int sum = 0;
            for (int i = 0; i < newCard.length; i++) {
                sum += newCard[i];
            }

            if (sum % 10 == 0)
                result = type + " card.";
        }
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return result;
}

Java自製元件 -- HexTransfer


分享一下我平常在使用的Java自製元件, 這個元件功能為:

1. 轉換byte成16進制HexCode String與HexCode String還原成byte。
2. 整數與byte陣列互轉(用作Socket位元傳輸很方便)。

調用方法為: HexTransfer.byteToHexString(...);

HexTransfer:

public class HexTransfer
{

    // encode byte to hex code string.
    public static String byteToHexString(byte b)
    {
        String str = "";
        if ((b & 0xff) < 16) {
            str = "0";
        }
        str = str + Integer.toHexString(b & 0xff);
        return str;
    }

    // encode normal string to hex string.
    public static String stringToHexString(String str)
    {
        String hexString = "";
        for (int i = 0; i < str.length(); i++) {
            int ch = (int) str.charAt(i);
            String strHex = Integer.toHexString(ch);
            hexString = hexString + strHex;
        }
        return hexString;
    }

    // decode hex string to normal string.
    public static String hexToString(String str)
    {
        byte[] baKeyword = new byte[str.length() / 2];
        for (int i = 0; i < baKeyword.length; i++) {
            try {
                baKeyword[i] = (byte) (0xff & Integer.parseInt(str.substring(i * 2, i * 2 + 2), 16));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        try {
            str = new String(baKeyword, "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return str;
    }

    // decode hex string to byte.
    public static byte hexToByte(String str)
    {
        return (byte) Integer.parseInt(str, 16);
    }

    // transfer integer to byte array.
    public static byte[] integerToBytes(int intValue)
    {
        byte[] result = new byte[4];
        result[0] = (byte) ((intValue & 0xFF000000) >> 24);
        result[1] = (byte) ((intValue & 0x00FF0000) >> 16);
        result[2] = (byte) ((intValue & 0x0000FF00) >> 8);
        result[3] = (byte) ((intValue & 0x000000FF));
        return result;
    }

    // byte array to integer.
    public static int bytesToInteger(byte[] byteVal)
    {
        int result = 0;
        for (int i = 0; i < byteVal.length; i++) {
            int tmpVal = (byteVal[i] << (8 * (3 - i)));
            switch (i) {
                case 0:
                    tmpVal = tmpVal & 0xFF000000;
                    break;
                case 1:
                    tmpVal = tmpVal & 0x00FF0000;
                    break;
                case 2:
                    tmpVal = tmpVal & 0x0000FF00;
                    break;
                case 3:
                    tmpVal = tmpVal & 0x000000FF;
                    break;
            }
            result = result | tmpVal;
        }
        return result;
    }
}

2008年11月26日 星期三

TestDriven with JUnit


一般程式開發人員的習慣是撰寫完一段功能後再加以測試, 也就是說先由開發者角度去創建系統。但測試驅動開發(Test-Driven-Development)卻是從使用的角度開始, 反向讓開發人員回頭撰寫正確執行的程式。

----------------- 何謂測試驅動開發TDD -----------------

你可能會覺得連內容都沒有要怎麼測試呢?沒錯, 測試驅動的用意即是如此!
例如我希望有一個功能是將輸入的兩個數字相加。那測試端就必須事先模擬ex: a = 1, b = 2 。期望值為兩數相加將會等於3, 假設我的方法沒有將處理結果等於3即為錯誤。
一開始主程式的方法只是一個框架:

public int add(int a, int b) {  
     // 尚無任何內容  
     return 0;  
}

測試框架則是已經寫完使用情形:

public int testAdd(){  
     int a = 1;  
     int b = 2;  
     int expect = a + b;     // 期望值  
     if(expect == add(a, b)){  
          System.out.println("Testing success");  
     }else{  
          System.out.println("Testing failed");  
     }  
}

到目前為止測試框架的結果絕對都是Testing failed, 因為主程式add還沒有計算的過程。
現在讓我們回過頭來編寫 add 方法:

public int add(int a, int b){  
     return a + b;  
}

再去測試一次得到Testing success, 大功告成!!
所以測試驅動開發能讓開發者模擬任何情況, 例如最常見的輸入null值等等作相對應的處理, 並且強迫設計程式時,去考慮到物件的低耦合。
上述的範例只是告知各位測試驅動的原理, 事實上 JUnit 並不需要把測試環境寫在同一個類別中, 而是獨立出來的, 所以開發者可以放心使用, 不必擔心程式碼到後期很難管理。

----------------- JUnit簡介 -----------------

JUnit是一個Java語言的單元測試框架。它由Kent Beck和Erich Gamma建立並包括了以下的特性:

1. 對預期結果作斷言
2. 提供測試裝備的生成與銷毀
3. 易於組織與執行測試
4. 圖型與文字介面的測試器

來自JUnit的體驗對測試驅動開發是很重要的,所以一些 JUnit知識經常 和測試驅動開發的討論融合在一起。可以參考Kent Beck的 《Test-Driven Development: By Example》一書(有中文版)。

JUnit 的測試主要由 TestCase、TestSuit 與 TestRunner 三部份架構起來。

詳細請參閱良葛格學習筆記:
TestSuite: http://caterpillar.onlyfun.net/Gossip/JUnit/TestSuite.htm
TestCase: http://caterpillar.onlyfun.net/Gossip/JUnit/TestCase.htm
Failure、Error: http://caterpillar.onlyfun.net/Gossip/JUnit/FailureError.htm

----------------- 相關教學 -----------------

以下網址收錄了詳細的影音教學, 而Eclipse 3.1以後即預設支援JUnit test case, 所以使用Eclipse的朋友們可以事半功倍的來實現測試框架。

JUnit 官網: http://www.junit.org/
影片教學(with Eclipse & NetBeans): http://www.blogjava.net/beansoft/archive/2007/10/29/156650.html
文字教學: http://caterpillar.onlyfun.net/Gossip/JUnit/JUnitGossip.htm

設計原則大集合

OOAD中除了分析案例,需求清單,架構設計等概念性的東西外,最終還是必須將專注力放在程式碼撰寫上頭,這時候設計原則便將發揮作用。

設計原則是一群能夠被應用到設計或撰寫程式碼的工具或技術,讓程式更好維護,更具有彈性與擴展力。

1. 開閉原則 (OCP, Open Close Principle)

"類別應該允許擴展而開放, 禁止修改而關閉" --- 例如: 你寫出一個完美的類別,當同事想要使用時,你所建構的類別當然不允許直接修改其中內容而變成兩個不同類別(禁止修改),同時必須能夠以達成他人目的繼承覆寫(允許擴展)。 但是...OCP的重點並不在於繼承,而是"彈性",所以只要記住大方向(類似封裝與抽象的意涵),你也可以使用合成等方式達到此目的。

2. 自我不重複原則 (DRY, Don't Repeat Yourself Principle)

"抽取類別中重複的程式碼,置放於單一地方" --- 這個道理淺而易懂,假如這次專案中一堆類別裡都有copy-paste的程式碼,哪裡用到哪裡就放,半年後打開來修改應該會有一點當初到底在做甚麼的感覺...偉大的軟體其中一個重點就是好維護,就算工程師多厲害,寫出來難以維護的軟體就是垃圾,能夠輕易修改達成用戶需求才是王道,而且DRY原則如果做不好,表示架構流程設計有很大問題。

3. 單一責任原則 (SRP, Single Responsibility Principle)

"系統中每一個物件都應該聚焦在單一責任,同時也只具有單一責任" --- 每一個物件只能有一個理由改變,假若汽車類別中有駕駛方法不是很奇怪嗎?駕駛應該是操作者的責任才對。 偉大軟體條件之一: "高內聚低耦合",但有些沒經驗的工程師可能會把類別分成太細,細到甚至螺絲釘都成了類別,請注意:做軟體該做的事,別吹毛求疵。

4. 替代原則 (LSP, Liskov Substitution Principle)

"子型別(sub type)必須能替代其基礎型別(base type)" --- 這關乎著良好的繼承關係,假如有一天你發現子類別不能夠取代父類別,那表示繼承關係出錯...

這樣可能很難懂,舉個例子: 有一個平面地圖類別,其中有方法 get(x,y),現在要寫另一個3D地圖,你想說要繼承平面地圖,好的,當調用方法時取出x,y軸...z呢??

這種時候絕對不能使用繼承,不用擔心,還有其他的方式讓你完成你該做的事。

委派 --- 在3D地圖類別中直接使用平面地圖的方法get(x,y),不改變它的結構,讓他幫你取代x,y軸,3D地圖類別中再自己規劃z軸設計。

之外再提兩個方式:

(1) 合成 --- 能夠使用一組其他類別的行為,並在執行時期隨意切換。 (2) 聚合 --- 一個類別可以是另一個類別的一部份,但仍然能夠存在於該類別之外。

以上為設計原則,了解之後對於物件設計將會事半功倍!!

Java PDFParser


之前寫文件搜尋時候使用的PDF擷取器, 功能為將PDF內容抽出存成文字檔, 發布出來給大家參考。

首先要下載所需套件pdfBox: http://www.pdfbox.org/, 下載後將src/PDFBox-0.7.3.jarexternal/FontBox-0.1.0-dev.jar置放到classpath中。

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import org.pdfbox.pdmodel.PDDocument;
import org.pdfbox.util.PDFTextStripper;

public class PDFParser
{
    public void readPdf(String file) throws Exception
    {
        boolean sort = false;             // is sort.
        String pdfFile = file;            // pdf file name.
        String textFile = null;           // output file.
        String encoding = "UTF-8";        // encode type.
        int startPage = 1;                // parse start page no.
        int endPage = Integer.MAX_VALUE;  // parse end page no.
        Writer output = null;             // output writer.
        PDDocument document = null;       // PDF Document.
        try {
            URL url = new URL(pdfFile);   // create connection from source file.
            document = PDDocument.load(pdfFile);  // use PDDocument to load file.
            String fileName = url.getFile();  // get pdf's name.
            
            if (fileName.length() > 4) {  // name output file.
                File outputFile = new File(fileName.substring(0, fileName.length() - 4) + ".txt");
                textFile = outputFile.getName();
            }
            
            // create connection to output file.
            output = new OutputStreamWriter(new FileOutputStream(textFile), encoding);
            
            // use PDFTextStripper to get content.
            PDFTextStripper stripper = new PDFTextStripper();
            stripper.setSortByPosition(sort); // set sort.
            stripper.setStartPage(startPage); // set start page.
            stripper.setEndPage(endPage);     // set end page.
            
            // write to output file.
            stripper.writeText(document, output);

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } finally {
            // close all stream.
            if (output != null) {
                output.close();
            }
            if (document != null) {
                document.close();
            }
        }
    }

    public static void main(String[] args)
    {
        PDFParser pdfReader = new PDFParser();
        try {
            // read pdf content.
            pdfReader.readPdf("place your pdf location here");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

flashplayer 9.0.124.0 跨網域無法存取 XMLSocket 解決

這篇文章之前已經發佈過一次(我還沒被砍掉的一百多篇中...), 現在把它補回來!
flashplayer 9.0.124.0 由於SandBox存取規則變更, 所以會先從843port開始找, 如找不到在依actionscript中指定路徑或根目錄底下尋找crossdomain.xml。
但不知為何9.0.124.0版本如果沒有置放843port服務的話, 自己並不會去尋找crossdomain.xml, 這就等於逼著大家放一個驗證policy的server在伺服器上。
這裡給大家一個java寫的範本參考, 也是我目前在線上運作的版本。

Google News Parser

我們都知道Google新聞裡彙整了該地區所有媒體的新聞,所以不必再一家一家查訪, 直接去Google擷取就可以。
進入Google新聞頁面後點選類別, 在左下方有一塊 RSS | ATOM 資訊提供的連結, 點選RSS並複製出網址, 這個XML內容就是我們要分析的地方。
內容都包覆在item節點中, 詳細內容請各位自己判別, 這裡不多加贅述。
Sample Code:
import java.io.*;
import java.net.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;

public class GoogleNewsParser
{

    public GoogleNewsParser()
    {
        try {
            // create factory.
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

            // using factory to create xml parser.
            DocumentBuilder db = dbf.newDocumentBuilder();

            // create connection.
            String url = "http://news.google.com/news?ned=tw&topic=b&output=rss";
            URLConnection conn = new URL(url).openConnection();

            // Google always needs an identification of the client.
            conn.setRequestProperty("User-Agent", "RSSFeed");
            InputStream in = conn.getInputStream();

            // start parsing.
            Document doc = db.parse(in);
            NodeList nl = doc.getElementsByTagName("item");

            // parent node.
            StringBuffer data = new StringBuffer();
            data.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
            data.append("\r\n");
            data.append("<RSS>");
            data.append("\r\n");

            for (int i = 0; i < nl.getLength(); i++) {

                // child nodes.
                NodeList node = nl.item(i).getChildNodes();
                data.append("\t<news id='" + i + "'>");
                data.append("\r\n");
                for (int j = 0; j < node.getLength(); j++) {
                    data.append("\t\t<" + node.item(j).getNodeName() + ">");
                    if (node.item(j).getNodeName().equals("description")) {
                        if (node.item(j).getFirstChild().getNodeValue().indexOf("img src=") >= 0)
                            data.append(node.item(j).getFirstChild().getNodeValue().split("img src=")[1].split("&")[0]);
                    } else {
                        data.append(node.item(j).getFirstChild().getNodeValue());
                    }
                    data.append("</" + node.item(j).getNodeName() + ">");
                    data.append("\r\n");
                }
                data.append("\t</news>");
                data.append("\r\n");
            }
            data.append("</RSS>");
            data.append("\r\n");

            System.out.println(data.toString());

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public static void main(String[] args)
    {
        System.out.println("Parsing news, please wait...");
        new GoogleNewsParser();
    }
}

運用Apache HttpClient實作Get與Post動作

HttpClient 簡介:

HTTP 協定是現在 Internet 上使用得最多、最重要的協定,越來越多的 Java 應用程序需要直接通過 HTTP 協定來訪問網路資源。 雖然在 JDK 的 java.net 包中已經提供了訪問 HTTP 協定的基本功能,但是對於大部分應用程序來說,JDK 類別庫本身提供的功能還不夠豐富和靈活。 HttpClient 是 Apache Jakarta Common 下的子項目,用來提供高效能、最新、功能豐富的支持 HTTP 協定的client端開發工具,並且它支持 HTTP 協定最新的版本和建議。 HttpClient 已經應用在很多的項目中,比如 Apache Jakarta 上很著名的另外兩個開源項目 Cactus 和 HTMLUnit 都使用了 HttpClient,更多使用 HttpClient 的應用可以參見http://wiki.apache.org/jakarta-httpclient/HttpClientPowered。 HttpClient 項目非常活躍,使用的人還是非常多的。目前 HttpClient 正式版本是 3.1。

HttpClient 功能介紹

以下列出的是 HttpClient 提供的主要的功能,要知道更多詳細的功能可以參見 HttpClient 的主頁。

1. 實現了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)

2. 支持自動轉向

3. 支持 HTTPS 協議

4. 支持代理服務器

下面將逐一介紹怎樣使用這些功能。首先,我們必須安裝好 HttpClient。

HttpClient 可以在http://hc.apache.org/downloads.cgi下載

HttpClient用到了logging,你可以從這個地址http://commons.apache.org/downloads/download_logging.cgi下載到 common-logging,從下載後的壓縮包中取出 commons-logging.jar 加到 CLASSPATH 中

HttpClient用到了codec,你可以從這個地址http://commons.apache.org/downloads/download_codec.cgi 下載到最新的 common-codec,從下載後的壓縮包中取出 commons-codec-1.x.jar 加到 CLASSPATH 中

Get Method:


import java.io.IOException;

import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.params.HttpMethodParams;

public class HttpGet
{
    private static String url = "http://www.apache.org/"; // 目標網址.

    public static void main(String[] args)
    {
        // 建立HttpClient實體.
        HttpClient client = new HttpClient();

        // 建立GetMethod實體, 並指派網址, GetMethod會自動處理該網轉址動作, 如果不想自動轉址請呼叫 setFollowRedirects(false).
        GetMethod method = new GetMethod(url);

        // 這段代碼用意為連接不到時自動重新��試三次.
        method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(3, false));

        try {
            // 返回狀態值.
            int statusCode = client.executeMethod(method);
            if (statusCode != HttpStatus.SC_OK) {
                System.err.println("Method failed: " + method.getStatusLine());
            }

            // 取得回傳資訊.
            byte[] responseBody = method.getResponseBody();
            System.out.println(new String(responseBody));

        } catch (HttpException httpexc) {
            System.err.println("Fatal protocol violation: " + httpexc.getMessage());
            httpexc.printStackTrace();
        } catch (IOException ioexc) {
            System.err.println("Fatal transport error: " + ioexc.getMessage());
            ioexc.printStackTrace();
        } finally {

            // ** 無論如何都必須釋放連接.
            method.releaseConnection();
        }
    }
}

由於是執行在網路上的程序,運行executeMethod方法時,需要處理兩個異常,分別是HttpException和IOException。 第一種異常的原因主要可能是在建立GetMethod的時候輸入網址錯誤,比如不小心將"http"寫成"htp",或者遠端返回的資訊內容不正常等,並且該異常發生是不可恢復的。

第二種異常一般是由於網路原因引起的異常,對於這種異常 (IOException),HttpClient會根據你指定的恢復策略自動試著重新執行executeMethod方法。

HttpClient的恢復策略可以自定義(通過實現接口HttpMethodRetryHandler來實現)。通過httpClient的方法setParameter設置你實現的恢復策略,這裡使用的是系統提供的預設恢復方式,該方式在碰到第二類異常的時候將自動重試3次。

executeMethod返回值是一個整數,表示了執行該方法後服務器返回的狀態碼,該狀態碼能表示出該方法執行是否成功、需要認證或者頁面轉址(預設狀態下GetMethod是自動處理轉址)等。

POST Method:


import java.io.IOException;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;

public class HttpPost
{
    private static String url = "http://www.apache.org/"; // 目標網址.

    public static void main(String[] args)
    {
        // 建立HttpClient實體.
        HttpClient client = new HttpClient();

        // 建立PostMethod實體, 並指派網址
        PostMethod post = new PostMethod(url);

        // 建立NameValuePair陣列�儲欲傳送的資料, 對照為 (名稱, 內容)
        NameValuePair[] data = { new NameValuePair("Name", "Sam Wang"), new NameValuePair("Passwd", "Test1234") };

        // 將NameValuePair陣列設置到請求內容中
        post.setRequestBody(data);

        try {
            // 返回狀態值.
            int statusCode = client.executeMethod(post);
            if (statusCode != HttpStatus.SC_OK) {
                System.err.println("Method failed: " + post.getStatusLine());
            }

            // 取得回傳資訊.
            byte[] responseBody = post.getResponseBody();
            System.out.println(new String(responseBody));

        } catch (HttpException httpexc) {
            System.err.println("Fatal protocol violation: " + httpexc.getMessage());
            httpexc.printStackTrace();
        } catch (IOException ioexc) {
            System.err.println("Fatal transport error: " + ioexc.getMessage());
            ioexc.printStackTrace();
        } finally {

            // ** 無論如何都必須釋放連接.
            post.releaseConnection();
        }
    }
}

PostMethod運作重點大致上和GetMethod相同, 唯一不同的是傳送變數必須用NameValuePair[]陣列儲存, 再設置到requestBody中。

2008年11月25日 星期二

ActionScript 3.0 線上技術分析圖

http://twstock.age.com.tw/
Sample CandleChart screen


Xuite Blog圖片, 無名相簿圖等下載方法

*** 僅供技術參考, 不歡迎用作侵犯他人肖像權等用途 *** 有時候想去下載一些圖檔但發現每一次都要另存新檔實在是很麻煩...又不想使用外面的軟體, 所以乾脆自己來寫程式, 沒想到Xuite有cookie與表頭要求驗證, 無名也有表頭驗證, 到底要怎麼作呢?
public static void duplicateImage(String sourceURL, String imgSrc, String filename) throws Exception
{
    URL source = new URL(imgSrc);
    HttpURLConnection urlconn = (HttpURLConnection) source.openConnection();
    HttpURLConnection.setFollowRedirects(false);

    if (imgSrc.indexOf("xuite") >= 0) {
        String cookieVal = urlconn.getHeaderField("Set-Cookie"); // get server response.
        String sessionId = "";
        if (cookieVal != null) {
            sessionId = cookieVal.substring(0, cookieVal.indexOf(";"));
        }
        urlconn.disconnect();
        urlconn = (HttpURLConnection) source.openConnection(); // restart a new connection.
        if (sessionId != null) {
            urlconn.setRequestProperty("Cookie", sessionId);
        }
        urlconn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/0.3.154.9 Safari/525.19");
        urlconn.setRequestProperty("Accept-Language", "zh-TW,zh,en-US,en");
        urlconn.setRequestProperty("Accept-Charset", "Big5,*,utf-8");
        urlconn.setRequestProperty("Accept-Encoding", "gzip,deflate,bzip2");
        urlconn.setRequestProperty("Host", "7.blog.xuite.net");
        urlconn.setRequestProperty("Connection", "Keep-Alive");
    } else if (imgSrc.indexOf("wretch") >= 0) {
        urlconn.setRequestProperty("Referer", sourceURL);
    }

    BufferedInputStream input = new BufferedInputStream(urlconn.getInputStream());
    FileOutputStream output = new FileOutputStream("./img/" + filename);
    System.out.print("download file \"" + filename + "\" --> ");
    byte[] buff = new byte[1024]; // buffer byte array.
    int c = 0;
    double total = urlconn.getContentLength(); // file size.
    double now = 0;
    while ((c = input.read(buff)) != -1) {
        output.write(buff, 0, c);
        now += c;
        System.out.print((int) ((now / total) * 100) + "% > "); // print progress.
    }
    output.flush();
    input.close(); // close stream.
    output.close();
    System.out.print("complete.\n");
}

xuite: 解決方法就是先取得server的session, 再度於請求時發送, 並用表頭欺騙過去, 如果你不知道表頭怎麼設定的話, 可以使用SmartSniff等封包監聽工具先手動下載一次圖片探測出表頭檔再依樣畫葫蘆填回去就好。如果不填的話像 User-Agent 會變成你jdk的版本, 對方server會攔截掉...

無名: server會查詢上一次的來源, 所以如果你直接開啟圖片會被拒絕, 這時我們在表頭上加上referer為原始網頁, 到時候你解析出來的圖片就可以下載了歐!!

解析網頁中圖片有很多種方式, 當然你要先下載全HTML文字回來, 我的方式是使用Regular Expression:

String searchImgReg = "(?x)(src|SRC|background|BACKGROUND)=('|\")(http://([\\w-]+\\.)+[\\w-]+(:[0-9]+)*(/[\\w-]+)*(/[\\w-]+\\.(jpg|JPG|png|PNG|gif|GIF)))('|\")";  
Pattern pattern = Pattern.compile(searchImgReg); // use regular expression to parse image src.
Matcher matcher = pattern.matcher(html text);  
while (matcher.find()) // loop and get target string.
{  
   try {  
     String imgurl = matcher.group(3);  
     String filename = matcher.group(7).substring(1, matcher.group(7).length());  
      
     System.out.println("source: " + imgurl);  
     duplicateImage(imgurl, filename);  
   } catch (Exception e) {  
     e.printStackTrace();  
   }  
}