程式碼高𠅙

2009/03/13

搜尋引擎設定也能帶著走

Foxmarks/Xmarks 這類線上書籤同步工具的出現,讓我們在不同的電腦與瀏覽器之間同步書籤。我習慣叫這種應用叫 "書籤帶著走"。但是,不曉得有沒有人和我一樣,想把瀏覽器上的其他設定也帶著走呢?

對我來講,除了書籤之外,"搜尋引擎設定" 也是我想帶著走的。這裡的搜尋引擎,提的是「搜尋工具列」或是「Smart Search」裡面的搜尋引擎。

「搜尋工具列」大家比較清楚,就是網址列旁的搜尋框。而 Smart Search 知道的人或愛用的人可能比較少,有人叫它「智能搜尋」,或叫 Quick Searches,這裡有篇文章介紹;基本上是讓你把書籤當成搜尋引擎使用的一種方式。

過去我一直覺得奇怪,Firefox 為何要有兩套不同的搜尋機制,也不了解這兩者間如何選擇。後來因為有了 ConQuery 及 Context Search 這類套件,讓我可以在右鍵選單上直接使用搜尋功能,這種方便性讓我一直以「搜尋工具列」裡的搜尋引擎作為我的主要搜尋工具。

但是,搜尋工具列裡的搜尋引擎在使用上卻有一個缺點,那就是,他們無法像書籤一樣,可以帶者走… 因此,在 Firefox 2.x 版的年代,我便透過一套叫 FireUploader 的工具,手動將搜尋引擎設定上傳到某個網站,然後再於別台電腦將它們下載回來,但這終究不是長久之計啊!

後來找到一個就叫做 SmartSearch 的套件,這個套件做的事與 ConQuery 或 Context Search 類似,就是讓你可以在右鍵選單上,選擇你要使用的搜尋引擎進行查詢,但不同的地方在於,它的搜尋引擎選單是來自「智能搜尋」的 Quick Searches Bookmarks,這代表著,你的搜尋引擎清單就是書籤連結,是可以「帶著走」的--只要你同時安裝 SmartSearch 及 Foxmarks/Xmarks 即可。

相關連結:

Technorati : ,

2009/03/04

SQL 設計小技巧--用 ISNULL 或 NVL 達到選擇性條件的下法

透過表單介面查詢資料庫時,常會遇到一種情況,即表單介面上有很多查詢選項,但不一定都要設定。若是不設定,代表 "忽略" 那條查詢選項。

舉個例子,假設畫面上有個欄位是要依照 "學歷" 及 "性別" 取出會員資料,因此,查詢資料庫的 SQL 可能如下:

   select * from member where education = @education and gender = @gender

若此時使用者未設定學歷條件,則查詢語法變成:

   select * from member where education = @education

當條件變動,還有另外兩種可能的查詢語法:

   select * from member where gender = @gender
   select * from member where education

如果你是用 PHP,由前端先組好查詢法語,再傳入後端資料庫處理,這沒有多大問題,只要透過一些 if 的判斷即可。但想像一下,若有 10 個查詢選項,那組合 SQL 語法的過程,不會讓你感到高興吧。

如果你是透過 ORMapping,雖然可以避免自行組合 SQL 查詢語法,卻也免不了要判斷該參數是否已設定,有設定的參數,才需加入 query criteria 中。

透過程式語言組裝查詢條件,再送入資料庫查詢,除了設計上的不便外,也不是所有的情況下都可使用。例如,假設你使用微軟的 Reporting Services,報表資料集的來源,直接就對應到 "靜態的" SQL 語法,你無法在執行期才去組裝它,這時就要使用不同的作法。

一個好用的技巧,是善用 ISNULL (MS SQL/T-SQL) 或 NVL (Oracle/PLSQL) 函式,它們的語法近似:

  • ISNULL(A, B)
  • NVL(A, B)

上面兩個式子,都代表著,若是 A 不等於 NULL,則傳回 A,否則傳回 B。

這個東西的有用之處,讓我們直接透過例子來解釋,把上面的查詢語句改為如下:

   --MS SQL
   select * from member where education = ISNULL(@education, education) and gender = ISNULL(@gender, gander)

   --Oracle
   select * from member where education = NVL(:education, education) and gender = NVL(:gender, gander)

類似這樣的式子,就可以解決我們所有的查詢組合情況。我們取出其中一段做分解說明:

   gender = ISNULL(@gender, gander)

@gender 代表查詢參數,當 @gender 未設定查詢條件時,我們讓他預設值為 NULL,結果 gender = ISNULL(NULL, gander) 就變成 gender = gender,而這個條件總是成立,所以就等於沒下這個條件。反之,或 gender 設定為 'F' 時,gender = ISNULL('F', gander) 就變成 gender = 'F',如此一來,就只會查詢出性別為 F 的會員資料。

基於這一原理,即使你的查詢條件有 20 個、30 個,你也不用再費心思索如何組裝查詢條件了。

2009/02/24

如何取得 Web 應用程式的根目錄 Context URL

我們常常在開發 Web 應用程式時,會有需要取得 Context URL。例如,為了透過這個 URL 取得某些資源。Context URL,也就是 Web 應用程式根目錄的 URL。例如,給定一個 URL 如下:

    http://test.com:8080/AppContext/path/to/your/page

則 Context URL 指的是:

    http://test.com:8080/AppContext   

如果我們都是以相對路徑 (相對於根目錄或或目前路徑) 的方式來存取資源,那直接使用 Context URL 的必要性就比較低了。不過有時候並無法使用相對路徑,例如:

  • 因為配合 API 的呼叫,你不曉得這個 API 會在虛擬路徑的第幾層呼叫,因此就無法提供相對路徑。
  • 因為配合某種 code gen 機制,你要傳入一個 URL。
  • 因為採用了某種 URL dispatch 的機制,導致你網頁相對路徑發生偏差。

當然還有其他原因。而面對這個問題的解法,傳統上是在 web.config 或 web.xml 裡面去定義一個類似 HOST_URL 的參數來解決。但缺點是,每次 deploy 時,就得變更 HOST_URL 的設定,除了顯得不便之外,因會增加 API 與環境變數的相依性,實在不能算是很好的做法。因此透過程式自動抓取 ContextURL 的作法,便有其必要。

現在,我們假設我們的 ContextURL 就是以上的形式,而不是 WebServer 的根目錄 (例如 http://test.com:8080/),那麼取得 Context URL 的作法,在一般情況下並不難。首先看看 ASP.NET/C# 版本:

   int idx = Request.Url.AbsoluteUri.IndexOf(Request.ApplicationPath);
   string serviceUri = Request.Url.AbsoluteUri.Substring(0, idx); // 伺服器 URL
   string applicationUri = serviceUri + Request.ApplicationPath; // 應用程式 Context URL

JSP/Java 版本如下:

   int idx = request.getRequestURL().toString().indexOf(request.getContextPath());
   String serviceUri = request.getRequestURL().substring(0, idx); // 伺服器 URL
   String applicationUri = serviceUri + request.getContextPath(); // 應用程式 Context URL

不過有些情況比較特殊,例果透過某種 URL dispatch 的機制來存取網頁,你會發現用以上方式取得的 URL,與 Client 端發送的 URL 並不相同。我們以 IBM Tivoli Access Manager 為例,它的 URL 組成格式如下:

      http://sso.com/JUNCTION/AppContext/path/to/your/page
  • sso.com: 是一個用來作 singal sign-on 的網站
  • JUNCTION: 用來試別要將 client 端 request dispatch 到後端哪個實發處理的網站
  • /AppContext/path/to/your/page: 後端處理要求的網頁或程式,實際位址可能是 http://192.168.1.111:8080 /AppContext/path/to/your/page 之類的。

也就是說,當 Client 端發送 http://sso.com/JUNCTION/AppContext/path/to/your/page 這個請求時,經過 Tivoli Access Manager 轉送後,Server 端實際取得的服務位址是 http://192.168.1.111:8080/AppContext/path/to/your/page。

為了解決這個問題,我們需要動用到 HTTP Header 裡面的一個參數 -- referer。referer 是指目前存取這個網頁,是透過哪個網頁連結過來的。有了這層認識,我們可以將上面的程式碼改成如下。 ASP.NET/C# 版本:

   if(IsPostBack) {
      idx = Request.UrlReferrer.AbsoluteUri.IndexOf(Request.ApplicationPath);
      serviceUri = Request.UrlReferrer.AbsoluteUri.Substring(0, idx); // 伺服器 URL
      applicationUri = serviceUri + Request.ApplicationPath; // 應用程式 Context URL
   }

JSP/Java 版本如下:

   if(request.getParameter("IsPostBack").equals("true")){
      idx = request.getHeader("referer").indexOf(request.getContextPath());
      serviceUri = request.getHeader("referer").substring(0, idx); // 伺服器 URL
      applicationUri = serviceUri + request.getContextPath(); // 應用程式 Context URL
   }

如果你是直接在瀏覽器的網址列輸入 URL,或是點選書籤/我的最愛直接連結,那 referer 的值會是 null。因此,我們可以透過檢查這個網頁是否是 post back 網頁,來確保 referer 有值。注意一下,JSP 裡面並沒有預設的 IsPostBack 檢查機制,你要自己做。

另外,有些防火牆或防毒軟體也可能會改寫 Client 端發出的 referer,使得 Server 上取得的值為 null。若是這種情況,可以試著透過 client 端的 Javascript,取得 document.location.href 的值,將它指定給某個隱藏表單欄位,在 post 時傳給 Server 端來加以解決。

2009/01/23

Java SE 6 Update 10 裡面的 Nimbus Look and Feel

Java 視窗應用程式在過去給部分人留下著刻版印象,總是認為那是跑得慢,看來醜的應用程式。不過,這真的只是刻版印象。個人在過去曾經以 Swing 套件,開發了 4 年以上的 Java Client 端應用程式,無論是在啟動速度、執行效能及視覺美觀上,Swing 介面均能達到令人滿意的程度。

而事實上不論是 Sun 或是 Java 社群也一直在 Look and Feel (就相當於佈景主題--Theme 的概念) 下了相當心血。在 Java SE 6 Update 10 裡面,就包括了一個 Nimbus Look and Feel,這是一個簡潔、洗練、具現代感的 Look and Feel。我們先來看看 SwingSet3 在 Windows XP 中的呈現樣式:

SwingSet3Nimbus

是不是還不賴呢? 我們展現一下 Metal Look and Feel 比較一下:

SwingSet3Metal

除了視覺上的輕巧之外,Nimbus Look and Feel 在實質上也是相當輕巧的,因為你在畫面上所看到的各個元件,包括按鈕、捲軸、下拉選單及標題的漸層及陰影等效果,全部是用 Java 2D 向量方式 (而不是透過 bitmap 圖檔) 畫出來的,因此才占用了 56 KB 的空間。

設定 Swing 應用程式使用 Nimbus Look and Feel

這樣漂亮的 Look and Feel,並沒有隨著 Java SE 6 Update 10的到來,而成為 Swing 預設的 Look and Feel。開發人員必須加以設定,才能在應用程式中以它作為佈景主題。隨著需求的不同,設定方式至少有三種。

在應用程式中進行設定--透過程式碼指定,你所要使用的 Look and Feel。這種方式只會將主題套用到應用程式本身,這只需要一行程式碼:

UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");

或者,也可以在啟動 Java 程式時,加入以下的 java 啟動參數:

-Dswing.defaultlaf=com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel

如果你想將 Nimbus Look and Feel 設定成整台電腦中,所有 Swing 程式的預設主題,則可以在 /lib/swing.properties 這個檔案中,加入底下這行:

swing.defaultlaf=com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel

如果上述的設定檔不存在,你可以自行新增一個。

在 jEdit 中設定 Nimbus Look and Feel

除了以上方式之外,有些應用程式可以透過 UI 介面,讓你設定應用程式的 Look and Feel。例如 jEdit 的這個以 Java 開發而成的編輯器,就可以在 Utilities/Global Options... 主選單所開啟的 Options 視窗中,選擇 Appearance 設定 Look and Feel。以下是套用 Nimbus 之後的外觀:

jEditNimbus

附上一張預設樣式 (Metal) 作比較:

jEditDefault

設定 NetBeans 使用 Nimbus Look and Feel

一直覺得 Sun 是一家有著偏執傾向的公司,他們似乎有著一種,不管別人如何批評,想要做的,便會全力以赴做到好的企業精神。因此這幾年來在 NetBeans IDE 上的進展是有目共睹的 (雖然,我還是 Eclipse 的愛用者啊! ),而我也因此相當看好 JavaFX 未來的發展…。OK,回歸正題,我想社群裡面 NetBeans 的愛好者應該也不少,要設定 NetBeans 使用 Nimbus Look and Feel 也很簡單,只要找到 NetBeans 的安裝目錄,在 etc 子目錄下的 netbeans.conf 設定檔中,找到 netbeans_default_options 設定,在設定中加上 --laf Nimbus 即可。例如,我的設定是這樣子的:

netbeans_default_options="-J-Dorg.glassfish.v3.installRoot=\"C:\java\glassfish-v3-prelude-b15b\" -J-Dcom.sun.aas.installRoot=\"C:\java\glassfish-v2ur2\" -J-client -J-Xverify:none -J-Xss2m -J-Xms32m -J-XX:PermSize=32m -J-XX:MaxPermSize=200m -J-Dnetbeans.logger.console=true -J-ea -J-Dapple.laf.useScreenMenuBar=true -J-Dsun.java2d.noddraw=true --laf Nimbus"

以下是套用 Nimbus 之後的外觀:

NetBeansNimbus

再附上預設外觀看看:

NetBeansDefault

其實,隨著應用的不同,你很難找到一套一體適用於所有應用情境的 Look and Feel,不過,這又是另一個主題了,我就此打住。

2008/12/10

小朋友的邏輯測驗

小二的國語參考書上看到一個選擇題,題目是:
「媽媽說,哥哥和我是她的一對寶貝 (1) 兒女 (2) 女兒」
唸小二的大兒子,兒女、女兒亂猜一通…
再問念小一的小兒子,他也不能正確答題。
於是我問他們,說這話的是誰,大兒子就說是哥哥,小兒子也不能知道是誰說的。
這時候老婆很熱心的打 pass:題目都說是媽媽說,所以就是媽媽說的。
為了讓我自己不要昏死在旁邊,我只好給提示:
「哥哥、姐姐、弟弟、妹妹」其中一個說的,你們選誰。
這時候老婆知道我在問什麼了。
二個小朋友還是一臉茫然…
以前唸過皮亞傑談到識知發展的書,簡要的內容就是人的各種認知、邏輯能力,
是隨著年齡而成長的。所以一些成人們認為理所當然的事,
對小朋友而言,並不總是理所當然。
今天這道題目,總算讓我有了另一次體驗。

2008/11/11

Google Translate 非官方翻譯 API

之前為了將系統的多語化,其中一個議題就是要產生各種語系的訊息資源檔。繁簡中文的對譯,只要透過 Word 就行了,要是英日語,就得人工作業。靈機一動,想說何不借助既有的翻譯服務,為 resource 檔提供預設的簡中、英、日翻譯。

就這樣,找到了一些應用 Google Translate API 的討論。試用了之後,多少都有語系編碼的問題。我最後實作了一個版本,可以在輸入正體中文時,正常產出簡中、英、日語系的翻譯。

一開始我是採用 C# 語言測試,程式碼如下:

public static string TranslateText(string fromLang, string toLang, string msg)
{
  string target = "http://www.google.com/translate_t?ie=UTF-8&oe=UTF-8&text={2}&langpair={0}|{1}";
  string url = String.Format(target, fromLang, toLang, msg);
  WebClient webClient = new WebClient();
  webClient.Encoding = System.Text.Encoding.UTF8;
  string result = webClient.DownloadString(url);
  string sign = "<div id=result_box dir=\"ltr\">";
  result = result.Substring(result.IndexOf(sign) + sign.Length);
  result = result.Substring(0, result.IndexOf("</div"));
  return result.Replace("<br>", "\n");;
}

後來又做了一個 Java 的版本,程式碼如下:

public static String translateText(String fromLang, String toLang, String msg) throws IOException{
  String target = "http://www.google.com/translate_t?ie=UTF-8&oe=UTF-8&langpair=%s|%s&text=%s";
  String url = String.format(target, fromLang, toLang, URLEncoder.encode(msg, "utf-8"));
  String result = getUrlContent(url);
  String sign = "<div id=result_box dir=\"ltr\">";
  result = result.substring(result.indexOf(sign) + sign.length());
  result = result.substring(0, result.indexOf("</div"));
  return result.replaceAll("<br>", "\n");
}

public static String getUrlContent(String url) throws IOException {
  URL u = new URL(url);   
  HttpURLConnection conn = (HttpURLConnection)u.openConnection();   
  conn.setRequestProperty("User-agent","Mozilla/4.0");
  conn.setRequestProperty("Content-Language","UTF-8" );
  conn.connect();
  return slurp(conn.getInputStream());            
}

public static String slurp (InputStream in) throws IOException {
  StringBuffer out = new StringBuffer();
  byte[] b = new byte[4096];
  for (int n; (n = in.read(b)) != -1;) {
    out.append(new String(b, 0, n, "UTF-8"));
  }
  return out.toString();
}

測試程式長得像這樣:

public static void main(String []argv) throws IOException{
  System.out.println(GoogleTranslate.translateText("zh-TW", "zh-CN", "書不同文、車不同軌、世界不大同!"));
  System.out.println(GoogleTranslate.translateText("zh-TW", "en", "書不同文、車不同軌、世界不大同!"));
  System.out.println(GoogleTranslate.translateText("zh-TW", "ja", "書不同文、車不同軌、世界不大同!"));          
}

稍微說明一下我的測試心得。

首先,就是使用 Google Translate 時,最好是明確指定你所用的輸入編碼及輸出編碼,即上面 URL 連結中的 ie=UTF-8&oe=UTF-8。若不明確指定,Google 可是會依據你的輸入語言,而決定輸出編碼。例如,你輸入的語言是 "zh-CN",它預設會採用 gb2312 編碼回應。這會造成處理上的困擾。

其次,由於 .NET 與 Java 平台內建皆採用 UTF-8 編碼,因此在我們指定輸入編碼及輸出編碼時,當然是優先考量 UTF-8。

最後,如果你用 Eclipse 做測試,你可能會發現 console 裡有部分亂碼。請依照下圖,在 Run/Run Configurations... 裡面做設定,將 Console Encoding 指定為 UTF-8:

eclipse-console-utf8

最後就能產出正常的顯示了。

eclipse-console-utf8-2

2008/11/07

正妹牆螢幕保護程式

foxsaver-beauty1

辦公室前面擺了一個 40 幾吋的電漿螢幕,放置不用殊為可惜。與同事閒聊之際,突然靈機一動,何不拿來展示正妹牆,24 下時輪播,慰勞整天辛勞的宅男工程師。

有了這個想法,首先想到了就是要找個能播放 media rss 的螢幕保護程式。試了幾個,包括:

效果都出不來。決定拿 FoxSaver 一試 -- 嘿,別看太久,年輕人會流鼻血的。

FoxSaver 是 Firefox 的一個 Extension,可以把 Firefox 變成螢幕保護程式。從它的選項來看,圖片來源可以來自本機目錄,RSS 或一般網頁。由於這個 Extension 還在實驗中,在 addons.mozilla.org 網站上需要登入才能安裝。我在安裝之後,使用上是沒有大問題。重點來了,怎麼設定 FoxSaver 讀取正妹牆 RSS 呢。一圖勝千言,請見右圖 。

如此之後,在 Firefox 狀態列上的 FoxSaver 按鈕上點右鍵,選擇「FoxSaver 開始」就行了。

當然,也有人推薦用 PicLens/Cooliris 來觀正妹牆,除了可以自己操刀,將正妹們拉遠拉近觀看外(是不是第一次感受到那種呼之即來,揮之即去的操作感 >_<|||),要透過 Cooliris 來達到類似螢幕保護程式的效果,記得按一下三角型的播放鈕,這 -- 不就變成美女自動送上門的桌上版了嗎。