如果有個 Big5 編碼撰寫的網頁,上頭的表單欄位,有人輸入了非 Big5 編碼容納的文字後送出,那會如何呢?
<!DOCTYPE html>
<html>
<head>
<meta charset="Big5">
<title>Big5 網頁</title>
</head>
<body>
<form action="form" method="post">
姓名:<input type="text" name="name">
<input type="submit" value="送出">
</form>
</body>
</html>
例如,若在上面這個範例網頁中輸入「王大犇」,發送至以下的 Servlet:
package cc.openhome;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/form")
public class Form extends HttpServlet {
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("Big5");
System.out.println(request.getParameter("name"));
}
}
那你會看到:
「犇」變亂碼了?不對,並不是亂碼。
在〈HTML 實體〉中規範了實體名稱(Entity)與實體編號(Entity number),用以表達網頁上無法直接表現的字元,實體名稱的格式是 &entity_name;
,以 <
與 >
為例,因為 <
與 >
在HTML原始碼中,用來作為標籤之用,若要在網頁上呈現 <
與 >
,在 HTML 原始碼中必須撰寫為 <
與 >
,實體編號的格式為 &#entity_number;
,若要用實體編碼來表示 <
與 >
,必須寫為 <
與 >
。
如果知道一個字元的 Unicode 碼點,要得到它的實體編號,就只要將十六進位表示換為十進位表示就可以了,以犇為例,其 Unicode 碼點為 U+7287,7287 為十六進位表示,換為十進位表示就是 29319。
有一些程式庫可以直接作轉換,例如 Java 可以使用 Commons Lang 中 StringEscapeUtils
的 escapeHTML()
與 unescapeHTML()
作轉換,以上面的 Servlet 為例,可以改為以下:
package cc.openhome;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringEscapeUtils;
@WebServlet("/form")
public class Form extends HttpServlet {
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("Big5");
System.out.println(
StringEscapeUtils.unescapeHtml(request.getParameter("name"))
);
}
}
重新發送「王大犇」,結果就可以看到正確的中文了:
網頁表單通常不允許使用者輸入 HTML,客戶端或伺服端通常會加以過濾,舉例來說,有個留言版,客戶端若輸入HTML,最基本的,你可能會過濾掉 <
與 >
,這在 Java 中可以用 Filter
來達到目的,在〈請求包裹器〉中有個例子,將 HTML 的 <
與 >
換為實體名稱。
如果事先沒有過濾 HTML,而這些留言進到了資料庫,你不想一個一個修正,或者想保留使用者原有的留言,那另一個方式,就是在傳送至使用者瀏覽器前, 將 <
與 >
等換為實體名稱或實體編號,最簡單的作法,就是使用 JSTL 核心標籤庫的 <c:out>
。例如:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
// 假設訊息來自資料庫
request.setAttribute("message",
"<a href='http://openhome.cc'>打廣告</a>");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
留言:${message}
</body>
</html>
這個 JSP 會呈現以下的結果:
加上 JSTL:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%
// 假設訊息來自資料庫
request.setAttribute("message",
"<a href='http://openhome.cc'>打廣告</a>");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
留言:<c:out value="${message}"/>
</body>
</html>
則會呈現以下的結果:
觀看網頁原始碼,可以發現實體名稱的存在:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
留言:<a href='http://openhome.cc'>打廣告</a>
</body>
</html>
這是因為 JSTL 的 <c:out>
,其 escapeXML
屬性預設為 true
,會替換特定的 XML 字元,不過它並不會替換像「犇」這類的字元,所以如果你的資料庫中撈出了「王大犇」,設定為請求範圍 name
屬性,並轉發至以下 JSP 網頁:
<%@ page contentType="text/html; charset=Big5" pageEncoding="Big5"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="Big5">
</head>
<body>
留言:<c:out value="${name}"/>
</body>
</html>
這個網頁是 Big5,無法直接顯示「犇」,會出現以下的畫面:
如果要解決這個問題,方法之一,就是設定請求範圍屬性前,先用 StringEscapeUtils
的 escapeHTML()
替換為實體編號:
request.setAttribute("name", StringEscapeUtils.escapeHtml(name));
request.getRequestDispatcher("test.jsp").forward(request, response);
但這麼作之後,反而出現以下畫面:
這是當然的,由於已經替換為實體編號了,就不需要再使用 <c:out>
了,否則 HTML會是:
<!DOCTYPE html>
<html>
<head>
<meta charset="Big5">
</head>
<body>
留言:&#29579;&#22823;&犇
</body>
</html>
將原本的 JSP 拿掉 <c:out>
就正常了:
<%@ page contentType="text/html; charset=Big5" pageEncoding="Big5"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="Big5">
</head>
<body>
留言:${name}
</body>
</html>
畫面如下:
因為HTML原始碼現在是:
<!DOCTYPE html>
<html>
<head>
<meta charset="Big5">
</head>
<body>
留言:王大犇
</body>
</html>
看到這邊,你會覺得,為何要這麼麻煩?現在不是鼓勵全部改用 UTF-8 嗎?為何要用 Big5 網頁自找麻煩?別忘了,有許多維護為主的公司,也許因為系統的歷史包袱,也許因為公司的組織分工,也許是其他的人事問題,舊系統不是說改就改,即使是改個文字編碼也會困難重重。
有許多人常簡單地問,為什麼我的網頁出現亂碼?為什麼我的資料庫出現亂碼?為什麼我的 XXX 出現亂碼,老實說,很難回答這個問題,唯有了解系統中對於文字編碼的關鍵部份處理,才能解決問題,而這又有賴於對編碼的了解,與所使用技術的熟悉度。