Search

Andoird - 啟動檔案瀏覽器選擇檔案

2015-12-31 11:16 AM

App 開發真的比 Web 還來得辛苦啊...

抱怨結束

在開發時常常需要有選擇圖片、檔案等等的功能

這時就需要啟動外部 App (啟動檔案瀏覽器) 來獲得選取檔案後的結果

例如點擊按鈕後啟動檔案管理員,選擇檔案後取得檔案路徑

再使用檔案路徑完成上傳檔案啦~ 圖片編輯等等的後續處理

搜尋 Intent file browser 可以得到很多資訊喔

接下來就開始介紹如何實現這項功能

4.4 版本以上由於 Android 修改了檔案的存取方式

請搭配這篇教學的方法使用 KitKat 以上版本從 URI 取得檔案實體路徑

程式碼範例
// 外部 App 回傳結果的類型判斷碼
private static final int FILE_SELECT_CODE = 0;

// 在按鈕上監聽點擊事件
settingsTypefaceButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        fileBrowserIntent();
    }
});

/**
 * 啟動外部 App 的檔案管理員
 */
private void fileBrowserIntent(){
    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    // 設定 MIME Type 但這裡是沒用的 加個心安而已
    intent.setType("application/font-sfnt");
    intent.addCategory(Intent.CATEGORY_OPENABLE);

    try {
        startActivityForResult( Intent.createChooser(intent, "選擇字型"), FILE_SELECT_CODE );
    } catch (android.content.ActivityNotFoundException ex) {
        // 若使用者沒有安裝檔案瀏覽器的 App 則顯示提示訊息
        Toast.makeText(this, "沒有檔案瀏覽器 是沒辦法選擇字型的", Toast.LENGTH_SHORT).show();
    }
}

/**
 * 使用者選擇的結果將會作為參數傳遞至此
 * @param requestCode   發出要求的代碼, 用以確定是取得何種結果
 * @param resultCode    回傳結果的代碼, 用已確定是否請求成功
 * @param data          回傳的資料
 */
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        // 檔案選擇代碼
        case FILE_SELECT_CODE:
            // 請求確認返回
            if (resultCode == RESULT_OK) {
                // 取得檔案路徑 Uri
                Uri uri = data.getData();
                // 取得路徑
                String path = null;
                try {
                    path = FileUtils.getPath(this, uri);
                } catch (URISyntaxException e) {
                    Toast.makeText(this, "檔案不對勁!", Toast.LENGTH_SHORT).show();
                    logger.error("Get file path failed.", e);
                    return;
                }

                // 檢查檔案類型
                String filename = FileUtils.typefaceChecker(path);

                if( filename.isEmpty() ){
                    Toast.makeText(this, "檔案不對勁!", Toast.LENGTH_SHORT).show();
                    return;
                }

                // Do something here...
            }
            break;
    }
    super.onActivityResult(requestCode, resultCode, data);
}

眼尖的人就注意到了,FileUtils 是啥? 為什麼我沒有!!!

為什麼網路教學老是缺東缺西的啊!!! 崩潰啊啊啊啊啊啊啊啊!!!

我說寧王先別急著發飆啊

因為那是需要自己建立的 Class

原始碼如下

import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import cmc.toolkit.FileKit;

import java.io.File;
import java.net.URISyntaxException;

public class FileUtils {

    /**
     * 由外部 Activity 回傳的資料取得檔案路徑
     * @param context   Activity
     * @param uri       Uri
     * @return
     *  檔案路徑
     * @throws URISyntaxException
     */
    public static String getPath(Context context, Uri uri) throws URISyntaxException {
        if ("content".equalsIgnoreCase(uri.getScheme())) {
            String[] projection = { "_data" };
            Cursor cursor = null;

            try {
                cursor = context.getContentResolver().query(uri, projection, null, null, null);
                int column_index = cursor.getColumnIndexOrThrow("_data");
                if (cursor.moveToFirst()) {
                    return cursor.getString(column_index);
                }
            } catch (Exception e) {
                // Do nothing
            }
            finally {
                if( cursor != null ) cursor.close();
            }
        }
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }

        return null;
    }

    /**
     * 檢查取回的檔案副檔名
     * @param path  檔案路徑
     * @return
     *  若檔案類型正確則回傳檔案名稱, 反之則回傳空字串
     */
    public static String typefaceChecker(String path){
        if( path == null || path.isEmpty() ) return "";

        File file = new File(path);

        if( !file.exists() || !file.isFile() ) {
            return "";
        }

        String filename = file.getName();
        String ext      = FileKit.getFileExt(filename);

        if( !ext.equalsIgnoreCase(".ttf") ){
            return "";
        }

        return filename;
    }

}



// 一併附上取得副檔名的程式碼, FileKit 是我剛學 Java 時自己寫的小工具

public static String getFileExt(String fileName) {
    int dotIndex = fileName.lastIndexOf(".");
    return dotIndex == -1 ? "" : fileName.substring(dotIndex);
}
各項資料連結
Stackoverflow - Android file chooser

Java - PTT 文章代碼(AID)轉換為 URL

2015-12-26 5:38 PM

因為開發經驗第二集要花比較多時間撰寫

剛好最近有用到 AID(文章代碼) 轉換成 URL 網址的方法

寫了一個轉換工具 AidConverter

可以轉換 AID 為 URL 或是 檔案名稱

也可以將 URL 或 檔案名稱 轉換為 AID

就決定先寫這篇教學文章了

PTT 的原始碼是用 C 語言寫的 而且檔名很可怕!!!

但我只碰過 C++ 而且那也已經是N年前的事了(遙想當年...)

我懶得複習也懶得去看那幾段程式碼到底在做什麼

就用硬轉的方式把那幾段轉成 Java 語言

所以這段教學不會有什麼說明 打算放放原始碼就了事...

雖然測試回傳結果都是正確的 但只測過十幾篇所以記得自己加一些錯誤處理

如果有大師願意修正為更好的版本也請不要客氣囉~

AidConverter
import com.google.common.base.Splitter;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.ihad.ptt.model.bean.AidBean;
import org.apache.commons.lang3.StringUtils;

import java.util.List;

/**
 * 文章編號與檔案名稱轉換工具<br />
 * 將符合此格式的 [a-zA-Z-_]{8} 文章編號轉換為檔案名稱 或是 URL<br />
 * 範例:<br />
 * 1MVYyFDv -> M.1451110159.A.379 -> https://www.ptt.cc/bbs/Gossiping/M.1451110159.A.379.html<br /><br />
 * 或是將符合 [M|G].[unsigned_integer].A.[HEX{3}] 格式的檔案名稱轉換為文章編號<br />
 * 若為 URL 則將轉換為 {@link AidBean}, 其中包含看板名與文章編號<br />
 * 範例:<br />
 * https://www.ptt.cc/bbs/Gossiping/M.1451110159.A.379.html -> M.1451110159.A.379 -> 1MVYyFDv
 */
public class AidConverter {

    private static final String DOMAIN_URL = "https://www.ptt.cc/bbs/";
    private static final String FILE_EXT = ".html";

    private static String aidTable = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_";
    private static BiMap<Character, Long> table = tableInitializer();

    /**
     * 建立文章編號字元 Map, 方便取得對應數值
     * @return
     *  文章編號字元 Map
     */
    private static BiMap<Character, Long> tableInitializer(){
        BiMap<Character, Long> table = HashBiMap.create();

        long index = 0;
        int size = aidTable.length();

        for( int i = 0; i < size; i++ ){
            table.forcePut( aidTable.charAt(i), index );
            index ++;
        }

        return table;
    }

    /**
     * 將檔案名稱轉換為˙數字型態的文章序號
     * @param fn    檔案名稱
     * @return
     *  數字型態的文章序號, 若檔案名稱格式不符將回傳 0<br />
     *  轉換後的文章編號將符合 [M|G].[unsigned_integer].A.[HEX{3}]<br />
     *  範例: M.1451100858.A.71E
     */
    private static long fn2aidu(String fn){
        long aidu = 0;
        long type = 0;
        long v1 = 0;
        long v2 = 0;

        if( fn == null ) return 0;

        List<String> fnList = Splitter.on(".").omitEmptyStrings().trimResults().splitToList(fn);

        if( fnList.size() != 4 ) return 0;

        String typeString = fnList.get(0);
        String v1String = fnList.get(1);
        String v2String = fnList.get(3);

        if( !fnList.get(2).equals("A") ) return 0;
        if( !StringUtils.isNumeric(v1String) || v1String.length() != 10 ) return 0;

        switch (typeString){
            case "M":
                type = 0;
                break;
            case "G":
                type = 1;
                break;
            default:
                return 0;
        }

        v1 = Long.parseLong( v1String );
        v2 = Long.parseLong( v2String, 16 );

        aidu = ((type & 0xf) << 44) | ((v1 & 0xffffffffL) << 12) | (v2 & 0xfff);

        return aidu;
    }

    /**
     * 將數字型態的文章序號轉換為字串型態的文章編號
     * @param aidu  數字型態之文章序號
     * @return
     *  轉換後的文章編號將符合 [a-zA-Z-_]{8}<br />
     *  範例: 1MVWgwSU
     */
    private static String aidu2aidc(long aidu){
        int size = table.size();
        BiMap<Long, Character> inverseTable = table.inverse();

        StringBuffer stringBuffer = new StringBuffer();

        while( stringBuffer.length() < 8 ){
            long v = aidu % size;
            if( !inverseTable.containsKey( v ) ) return null;

            stringBuffer.insert( 0, inverseTable.get( v ) );
            aidu = aidu / size;
        }

        return stringBuffer.toString();
    }

    /**
     * 將文章編號轉換為數字型態的文章序號
     * @param aid   文章編號
     * @return
     *  數字型態的文章序號
     */
    private static long aidc2aidu(String aid){
        char[] aidChars = aid.toCharArray();
        long aidu = 0;

        for( char aidChar : aidChars ){
            if( aidChar == '@' ) break;
            if( !table.containsKey(aidChar) ) return 0;

            long v = table.get(aidChar);

            aidu = aidu << 6;
            aidu = aidu | (v & 0x3f);
        }

        return aidu;
    }

    /**
     * 將文章序號(數字型態)轉換為檔案名稱
     * @param aidu  文章序號(數字型態)
     * @return
     *  轉換後的檔案名稱, 格式將符合 [M|G].[unsigned_integer].A.[HEX{3}]<br />
     *  最後的16進位表示法若未滿3個字將以0從左邊開始補齊<br />
     *  範例: M.1451100858.A.71E
     */
    private static String aidu2fn(long aidu){
        long type = ((aidu >> 44) & 0xf);
        long v1 = ((aidu >> 12) & 0xffffffffL);
        long v2 = (aidu & 0xfff);

        // casting to unsigned
//        v1 = v1 & 0xffffffffL;
        String hex = Long.toHexString(v2).toUpperCase();

        return ((type == 0) ? "M" : "G") + "." + v1 + ".A." + StringUtils.leftPad(hex, 3, "0");
    }

    /**
     * 將文章編號轉換為檔案名稱
     * @param aid   文章編號
     * @return
     *  轉換後的檔案名稱, 格式將符合 [M|G].[unsigned_integer].A.[HEX{3}]<br />
     *  最後的16進位表示法若未滿3個字將以0從左邊開始補齊<br />
     *  範例: M.1451100858.A.71E
     */
    public static String aidToFileName(String aid){
        return aidu2fn( aidc2aidu(aid) );
    }

    /**
     * 將文章編號轉換為 WEB 版 URL
     * @param boardTitle    文章所屬看板名稱
     * @param aid           文章編號
     * @return
     *  WEB 版的完整 URL
     */
    public static String aidToUrl(String boardTitle, String aid){
        if( boardTitle.isEmpty() || aid.isEmpty() ) return "";

        return DOMAIN_URL + boardTitle + "/" + aidToFileName(aid) + FILE_EXT;
    }

    /**
     * 將檔案名稱(也就是 URL 的最後一段 不包含副檔名)轉換為文章編號
     * @param fileName  檔案名稱
     * @return
     *  轉換後的文章編號, 若檔案名稱格式不符則將回傳 null
     */
    public static String fileNameToAid(String fileName){
        return aidu2aidc( fn2aidu(fileName) );
    }

    /**
     * 將 URL 轉換為 AID 物件
     * @param url   PTT WEB 版的 URL
     * @return
     *  物件內包含 文章編號 與 看板名
     * @see AidBean
     */
    public static AidBean urlToAid(String url){
        List<String> urlList = Splitter.on("/").omitEmptyStrings().trimResults().splitToList(url);

        if( urlList.size() < 4 ) return null;

        String boardTitle   = urlList.get(3);
        String fileName     = urlList.get(4).replaceAll("\\.(html|htm)", "");
        String aid          = fileNameToAid( fileName );

        return AidBean.Builder.anAidBean()
                .withBoardTitle( boardTitle )
                .withAid( aid )
                .build();
    }

}

AidBean
public class AidBean{

    // 看板名稱
    private String boardTitle;

    // 文章編號
    private String aid;

    public String getBoardTitle() {
        return boardTitle;
    }

    public void setBoardTitle(String boardTitle) {
        this.boardTitle = boardTitle;
    }

    public String getAid() {
        return aid;
    }

    public void setAid(String aid) {
        this.aid = aid;
    }

    public static class Builder {
        private String boardTitle;
        private String aid;

        private Builder() {
        }

        public static Builder anAidBean() {
            return new Builder();
        }

        public Builder withBoardTitle(String boardTitle) {
            this.boardTitle = boardTitle;
            return this;
        }

        public Builder withAid(String aid) {
            this.aid = aid;
            return this;
        }

        public Builder but() {
            return anAidBean().withBoardTitle(boardTitle).withAid(aid);
        }

        public AidBean build() {
            AidBean aidBean = new AidBean();
            aidBean.setBoardTitle(boardTitle);
            aidBean.setAid(aid);
            return aidBean;
        }
    }
}
各項資料連結
Java - PTT 批踢踢實業坊 Client 軟體開發經驗(1) - 前置準備
PTT 原始碼

Java - PTT Client 軟體開發經驗(1) - 前置準備

2015-12-16 11:41 AM

警告! 開發 PTT Client 非常之危險!

無所不在的時間黑洞以及各種無法預料的狀況會將你的精神折磨至崩潰!!!

哈哈哈哈哈哈哈哈哈哈(就像這樣)

--

以下都是廢話加閒談,趕時間的可以按這裡跳到重點

可喜可賀!

經過了漫長的開發時間終於成功上架了

目前似乎也運氣很好的穩定運行了一段時間

有興趣的朋友可以自己在部落格內尋找 App 連結,我就不多打廣告了

自信滿滿,人稱程式小Bug王的我原本想說這種小東西大概一兩個月就可以完成了吧!

結果花了我快半年...

其中一個原因是第一次開發 Android App

另一個就是氣死人不償命的 VT100 終端機訊息傳遞機制,這個留待往後慢慢說明

而且網路上的資源可以說是少之又少

很多東西都是自己Google來東拼西湊才知道的

所以很多東西看看就好不一定是對的,自己找重點記(超不負責任)

尤其很多都是英文的資料,對於 PTT 這中文的論壇所需的技術來說實在是很不友善

於是我就想說寫這篇教學文章來好好闡述一下這血與淚的開發史

希望可以求得一些歷史定位(!?)

呃...是造福其他開發者啦

最主要的目的是告訴其他有興趣的開發者趕快放棄開發的計劃,因為實在太難做了

如果再讓我選一次我絕對不會選擇開發這個 App!!!

心理準備

首先我們來彈彈 PTT 的額頭,要很用力!(到底有多恨...)

是來談談 PTT 使用的 ANSI Escape Sequence 以及相關的知識

如果以為這只是單純的文字處理那就大錯特錯了!

當初天真爛漫的我其實就是這麼想的,殊不知背後龐大的利益鳩葛與情仇交纏...

PTT 是使用 VT100 這種類型的終端機定義來傳遞訊息

而使用 ANSI Escape Sequence 則可以將訊息格式化

諸如文字顏色、背景顏色、閃爍、文字出現位置以及清除畫面等等

所以你用 PC MAN 或任何終端機連上後可以看得見如此精美的畫面


但是你得到的資料其實是...

 [H [1;37;44m【主功能表】                      [33m批踢踢實業坊 [0;1;37;44m                                  
 [0;47m        [30m│ [2;6H                                                      [0;47m﹎ [30m﹏︷~一︸一~︷﹏﹎ [0;47m  
     [30;43m╱ [3;3H     [31;47m◢ [3;5H [41m      [30m﹏     ╱ [3;15H [47m           [1m▄ [3;23H    ▄ [3;25H    ▄ [3;27H    ▄ [3;29H    ▄ [3;31H    ▄ [3;33H    ▄ [3;35H    ▄ [3;37H    ▄ [3;39H    ▄ [3;41H                       [0;30;47m∕ [3;61H明天要出去玩,  ﹨  
 [0;43m   [41m  [30m#          ╱ [4;13H   [47m          [0;47m█ [4;22H [0;1;30m  [34m/  [0;1;37m.       [5;34m∕ [4;30H     [0;30m█ [4;32H [1;36m/   [5;34m/ [0;5;34m      [0;1;30m║ [4;40H    █ [4;42H [0;47m           ▁ [4;51H     [30m▂ [4;53H    ▁ [4;55H [0;47m     [30m﹨   雨還沒停嗎...    ∕ [4;79H  
     [0;43m◢ [5;3H [33;41m     [30m〞    ╱ [5;11H _   [47m          [0;47m█ [5;22H [0;1;30m   [5;36m/ [0;5;36m  [0;1;5;37m.      [0;1;36m∕ [5;31H     [30m█ [5;33H [0;30m  [0;1;37m.     ● [5;38H [30m人    █ [5;42H [0;47m          [30m◢ [5;49H [0;30m           [47m◣ [5;57H [0;47m  ﹊ [30m`"︸一~︷~一︸"`﹊ [0;47m  
     [43m◤ [6;3H [33;41m      [30m氵 [6;6H     ╱ [6;9H     ╱ [6;12H    ║ [6;14H  [47m          [0;47m█ [6;22H [0;1;30m      [34m∕ [6;25H     [36m′ [6;27H [0;36m       [30m▃ [6;31H     [1m█ [6;33H     [5;34m′ [6;35H [36m/      [0;1;37m▲ [6;39H      [30m█ [6;42H [0;47m          [30m◤ [6;49H     [43m◤ [6;51H    ◤ [6;53H [0;30m        [47m▏ [6;58H      ∞ [6;62H [0;47m         [36m▃ [6;68H    ▄ [6;70H    ▃ [6;72H [0;47m         
     [33;41m◤ [7;3H       [30m╱ [7;7H    ▏ [7;9H/      ║ [7;14H  [47m          [0;47m█ [7;22H [0;1;30m     [5;34m/ [0;1;36m/     [0;30m◤ [7;31H     [1m█ [7;33H [0;30m  [1;34m/     ′ [7;38H [0;34m  [1;5m/     [0;1;30m█ [7;42H [0;47m       [43m      [30m˙ [7;51H      [33m◤ [7;54H     [30m◥ [7;56H     [47m▎ [7;58H。           [36;46m◤ [7;68H [1;33;47m/|\          
 [0;30;41m`     ╱ [8;5H      [31m▋ [8;8H     [30m║ [8;10H      ║ [8;14H  [47m          [0;47m█ [8;22H [0;1;30m      [36m′ [8;25H [0;36m     [1;34m/     [30m█ [8;33H [0;30m   [1;36m/ [0;36m  [0;1;5;37m.      [0;1;30m█ [8;42H [0;47m       [43m          [33m◢ [8;55H [0;30m      [47m▏ [8;58H         [46m  [47m              
     [41m╱ [9;3H _      [31m▋ [9;8H     [30m║ [9;10H      ║ [9;14H  [47m          [0;47m█ [9;22H     [0;1;5;36m∕ [9;24H     [0;1;34m′ [9;26H [5;36m/ [0;5;36m  [0;1;37m.      [30m█ [9;33H     [34m∕ [9;35H     [5;36m′ [9;37H [0;5;36m      [0;1;36m∕ [9;40H     [30m█ [9;42H [0;47m         ◥ [9;49H [31;43mㄟ [33m      ▲ [9;55H     [30;47m◤ [9;57H [0;47m          [46m  [47m                [1;30mξ [9;80H 
 [0;41m      [30m╱ [10;4H    ║ [10;6H     [31m▋ [10;8H     [30m║ [10;10H      ║ [10;14H  [47m          [0;47m█ [10;22H     [0;1;30m▄ [10;24H    ▄ [10;26H    ▄ [10;28H    ▄ [10;30H    ▄ [10;32H    ▄ [10;34H    ▄ [10;36H    ▄ [10;38H    ▄ [10;40H    █ [10;42H [0;47m              [30;42m╭ [10;53H    ╮ [10;55H [47m             [33m◢ [10;65H     [36;43m▃ [10;67H    ▃ [10;69H [33m       [31m▁ [10;73H    ▁ [10;75H       [33m▲ [10;79H     [44m▲ [10;81H
 [30;41m/      ║ [11;6H     [31m▋ [11;8H     [30m║ [11;10H      ║ [11;14H      [1;47mψ [11;17Hrod24574575                            [0;30;42m│ [11;53H    │ [11;55H [47m           [33m◢ [11;63H [43m           [31m▕ [11;71H     [30;47m≡ [11;73H     [1m▏ [11;75H     [0;31;43m▏ [11;77H  [34m ̄ 
 [m [K
    ─ [13;3H    ─ [13;5H    ─ [13;7H    ─ [13;9H    ─ [13;11H    ─ [13;13H    ─ [13;15H    ─ [13;17H    ─ [13;19H 上方為使用者心情點播留言區,不代表本站立場     ─ [13;65H    ─ [13;67H    ─ [13;69H    ─ [13;71H    ─ [13;73H    ─ [13;75H    ─ [13;77H    ─ [13;79H [14;10H             ( [1;36mA [m)nnounce     【 精華公佈欄 】 [K [15;10H             ( [1;36mF [m)avorite     【 我 的 最愛 】 [K [16;10H             ( [1;36mC [m)lass        【 分組討論區 】 [K [17;10H             ( [1;36mM [m)ail         【 私人信件區 】 [K [18;10H             ( [1;36mT [m)alk         【 休閒聊天區 】 [K [19;10H             ( [1;36mU [m)ser [19;38H【 個人設定區 】 [K [20;10H             ( [1;36mX [m)yz          【 系統資訊區 】 [K [21;10H             ( [1;36mP [m)lay         【 娛樂與休閒 】 [K [22;10H             ( [1;36mN [m)amelist     【 編特別名單 】 [K [23;10H           > ( [1;36mG [m)oodbye         離開,再見    … [23;53H [K
 [34;46m[12/16 星期三 12:04] [1;33;45m [ 天秤時 ]    [24;36H [30;47m線上 [31m108017 [30m人, 我是 [31mnoartsarc [30m    [呼叫器] [31m打開  [m [23;21H

有沒有感覺到一種被欺騙了的感覺

...

...

...

那麼接下來我們就進入正式的介紹了(現在才開始!? 那前面打那麼多字幹嘛啦)

ANSI Escape Sequence

首先我希望各位將以下幾個網頁加入最愛或是直接儲存整個網頁

對於往後的開發來說會是非常重要的資源

控制字元對照表 - ASCII code caracters

ANSI 控制碼對照表 - ANSI Codes

CTRL 複合按鍵對照表 - Key Pressed with CTRL key down

第一個跟第三個連結暫時還用不上,我們先打開 ANSI Codes 的連結

然後看到下方表格的 color and text formatting 這欄


這邊主要是在說明所有可以用的文字格式設定控制碼

 /**
  * ESC 就是代表鍵盤上的 ESC 字元, 如何輸入留待後面再提
  * [#(;#) 中間的 # 就是表格內的數字組合, 可以放很多組格式設定
  * 但重複的設定會被後面的覆蓋掉
  * 例如我想要有紅色的字體, 綠色的背景再讓他閃爍就會使用以下控制碼
  * ESC[1;31;42m紅字綠背景ESC[m
  * 而如果重複使用了前景或背景設定
  * ESC[1;36;35;34;33;32;43;42m紅字綠背景ESC[m
  * 他則會以最後出現的設定來顯示(以上範例為32;42兩組設定)
  * 所以以上兩個範例顯示的結果會是相同的
  * 最後面的 ESC[m 則是還原用控制碼
  * 將所有文字格式設定還原
  * 而控制碼的結尾必須要以 m 告知處理器格式設定已經完成了
  * 這邊的觀念非常重要, 因為我們必須親自寫出一個上面提到的處理器
  * 也就是 ANSI Escape Sequence Parsing Engine
  */
 ESC[#(;#)m

再來就是另一個比較複雜的重頭戲了

請看到網頁中表格的 cursor controls 以及 erase functions


這邊主要是在處理文字的排版

也是上面那一大串文字的最主要原因

PTT 回傳的資料有很多種類型, 有時會給你完整的字串用空白補齊位子

有時會不給你空白, 而是使用 ESC[12;30H 的方式告訴你接下來的字是放在哪個位置

 /**
  * ESC 就是代表鍵盤上的 ESC 字元, 如何輸入留待後面再提
  * [#;#H 中間的 # 表示要將游標移動到哪個位置
  * 而這個游標位置又影響了接下來的字串會在哪個位置開始插入
  * 可以想像成一個 24 * 80 的矩陣會比較容易了解
  * 例如PTT回傳了要在 第 2 行的第 20 個的位置開始插入字串就會使用
  * ESC[2;20H隨便字串
  * 以此類推, 而若是使用
  * ESC[H
  * 則表示將游標位置歸零
  */
 ESC[#;#H

 // 清空所有資料
 ESC[2J
 // 清空目前所在行數的資料
 ESC[K

 // 將游標往後退一格 Backspace
 \b

 // 將游標回到同一列的第一個位置
 \r

 // 將游標往下移動一個 row
 \n

其他的部分因為 PTT 不會回傳那些控制碼所以可以暫時不用看

下一章就會開始講解 ANSI Escape Sequence Parsing Engine 該如何撰寫

用一長串英文有沒有感覺很專業?

其實就是將 PTT 回傳的資料一個蘿蔔一個坑的放入剛剛提到的 24 * 80 陣列的方法

算是整個 PTT Client 的第一個最重要的核心

也是當初撰寫時遇到的第一個難關

那麼就容我稍微拖個稿,請待下回分解!!!

各項資料連結
Java - PTT 文章代碼(AID)轉換為 URL
VT100
ASCII code caracters
ANSI Codes
Key Pressed with CTRL key down
PTT 完全操作手冊