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 原始碼
No comments:
Post a Comment