2010/07/30

[Other] ADO Stream 的轉碼說明

一般在使用 AJAX 時, 會遇到回傳資料的中文亂碼問題.
不過, 因為網頁瀏覽器的安全性設定問題, 所以使用 AJAX 呼叫的頁面都是在同網域頁面.
也就是說, 呼叫的頁面是在開發者能掌控的範圍 (包括可修改).
因此, 許多網頁對於 AJAX 所呼叫的頁面傳回亂碼的解答, 就會是建議開發者去修改被呼叫頁面的 charset.

例如以下三種語言的修改法:
  1. ASP / ASP.Net:
    Response.Charse="big5"
  2. PHP:
    header("Content-Type:text/html;charset=big5");
  3. JSP:
    response.setHeader("Charset","big5");
但若是只想寫一個 HTML Application, 或是 vbs / js 檔, 這類在本機端執行的小應用程式.
由於是在本機執行, 所以權限上可以到外站去讀取網頁資料.
在此情況下, 外站的編碼就不是開發者能去控制的, 僅能讀回來後自行轉碼.
以下將使用 Track Anywhere 這個網站的 IP/Domain 網域查詢, 說明 ADO Stream 的轉碼.
JavaScript 的程式片段如下:
<html>
<head>
<meta http-equiv="Content-Language" content="ZH-TW">
<meta http-equiv="Content-Type" content="text/html; charset=utf8">
<title>IP/Domain查詢程式</title>
<HTA:APPLICATION ID="oHTA" INNERBORDER="no" NAVIGABLE="yes"
   SCROLL="no" Border="Thin" MAXIMIZEBUTTON="no"/>    
<script type="text/javascript">
window.resizeTo(800, 600);
var objXmlHttp = null;
function DoQuery()
{
  //設定使用者輸入的IP或Domain Name
  var strInput = document.getElementById("txtInput").value;  
  if (window.XMLHttpRequest)
  {// code for IE7, Firefox, Opera, etc.
    objXmlHttp = new XMLHttpRequest();
  }
  else if (window.ActiveXObject)
  {// code for IE6, IE5
    objXmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
  }
  if (objXmlHttp==null)
  {
    alert("未支援XMLHTTP");
    return;
  }
  //設定此查詢的處理函式
  objXmlHttp.onreadystatechange = GetQueryResponse;
  //送出查詢
  objXmlHttp.open("POST", 
    "http://tracker.derekr.com//cgi-bin/cgiwrap/tracker/services/whois.cgi", true);
  //送出查詢的參數
  objXmlHttp.send("IP=" + strInput);
}
function GetQueryResponse()
{
  if (objXmlHttp.readyState == 4) 
  {
    var objStream = new ActiveXObject("ADODB.Stream");
    objStream.Mode = 3;//Read/write
    objStream.Type = 1;//Binary
    objStream.Open();//開啟Stream Object
    objStream.Position = 0;//設定起始位置
    //將查詢結果的Binary資料寫入至Stream Object
    objStream.Write(objXmlHttp.responseBody);
    objStream.Position = 0;//再設定一次起始位置
    objStream.Type = 2;//把Type調整至純文字
    objStream.Charset = "Big5";//設定輸出的charset是big5
    //移除掉換行符號(方便後續的字串處理)
    var strResp = objStream.ReadText().replace(/[\r\n]/g, "");
    objStream.Close();//關閉Stream Object
    //只取結果<center></center>中的資料
    var regResult = /<center[^>]*>.+<\/center>/gm;
    //顯示結果
    document.getElementById("divOutput").innerHTML = regResult.exec(strResp);
  }
}
</script>
</head>
<body style="text-align:center;width:100%">
IP/Domain:<input id="txtInput" value="192.168.0.1" />
<input type="button" value="查詢" onclick="DoQuery()" />
<hr/>
<div id="divOutput" >
</div>
</body>
</html>
以下是程式的說明 (如果上述的程式註解看不懂):
  1. Line 41: 先把 Stream 物件的 Mode 設定至 Read / Write. (可參考: ConnectModeEnum)
  2. Line 42: 將 Stream 物件的 Type 設定至 Binary 的資料類型. (可參考: StreamTypeEnum)
  3. Line 43: 開啟Stream物件,開始進行資料寫入的動作.
  4. Line 46: 為 step2 已經設定是 Binary 的資料類型, 所以這邊寫入的是 responseBody, 而不是 responseText.
    至於為什麼要用 responseBody, 而不用 responseText, 其實各位可以試試看.
    我的測試結果是: 用 responseBody 去轉碼至 Big5 可以成功, responseText 去轉碼還是亂碼. 所以建議用 Binary 的資料去轉.
  5. Line 47: 因為先前已經有寫入資料至 Stream 物件中, 所以要讀資料前, 先把 Position 再設定至 0, 也就是設定至資料的起始位置.
  6. Line 48: 將 Stream 物件的 Type 設定至 Text 的資料類型.
  7. Line 49: 將 Stream 物件的 Charset 設定至中文的 Big5.
  8. Line 51: 透過 ReadText() 函式, 轉碼後的文字讀出來. 這邊多做了一個移除換行符號的處理, 為的是方便後續 Regexp 處理查詢結果.
  9. Line 52: 資料讀完後, 記得將 Stream 物件關閉. (養成有開有關的好習慣)
  10. Line 54 & 56: 因為查詢結果中包括了整個 html 的程式碼, 而我只需要其中 <center> 中的資料. 所以就弄了個 Regexp 將我要的資料抽出來.
    如果有人想要查詢結果 <table> 中的資料, 把 center 改成 table 就可以了.
註:
實際上, 我是將上述的程式再改成可輸入多筆的 IP/Domain Name, 一樣透過 AJAX 的概念去 Track Anywhere 這個網站取得查詢結果.
不然介面上只允許一筆 IP/Domain Name 的輸入, 不就跟使用瀏覽器到網站上查詢一樣嗎? XD
既然要改成 HTML Application, 那就多加一點功能, 看起來也比較有價值.

沒有留言: