Big5 網頁難字


如果有個 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"));
    }
}

那你會看到:

Big5 網頁難字

「犇」變亂碼了?不對,並不是亂碼。

在〈HTML 實體〉中規範了實體名稱(Entity)與實體編號(Entity number),用以表達網頁上無法直接表現的字元,實體名稱的格式是 &entity_name;,以 <> 為例,因為 <> 在HTML原始碼中,用來作為標籤之用,若要在網頁上呈現 <>,在 HTML 原始碼中必須撰寫為 &lt;&gt;,實體編號的格式為 &#entity_number;,若要用實體編碼來表示 <>,必須寫為 &#60;&#62;

如果知道一個字元的 Unicode 碼點,要得到它的實體編號,就只要將十六進位表示換為十進位表示就可以了,以犇為例,其 Unicode 碼點為 U+7287,7287 為十六進位表示,換為十進位表示就是 29319。

有一些程式庫可以直接作轉換,例如 Java 可以使用 Commons LangStringEscapeUtilsescapeHTML()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"))
    );
    }
}

重新發送「王大犇」,結果就可以看到正確的中文了:

Big5 網頁難字

網頁表單通常不允許使用者輸入 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 會呈現以下的結果:

Big5 網頁難字

加上 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>

則會呈現以下的結果:

Big5 網頁難字

觀看網頁原始碼,可以發現實體名稱的存在:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
    留言:&lt;a href=&#039;http://openhome.cc&#039;&gt;打廣告&lt;/a&gt;
</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,無法直接顯示「犇」,會出現以下的畫面:

Big5 網頁難字

如果要解決這個問題,方法之一,就是設定請求範圍屬性前,先用 StringEscapeUtilsescapeHTML() 替換為實體編號:

request.setAttribute("name", StringEscapeUtils.escapeHtml(name));
request.getRequestDispatcher("test.jsp").forward(request, response);

但這麼作之後,反而出現以下畫面:

Big5 網頁難字

這是當然的,由於已經替換為實體編號了,就不需要再使用 <c:out> 了,否則 HTML會是:

<!DOCTYPE html>
<html>
<head>
<meta charset="Big5">
</head>
<body>
    留言:&amp;#29579;&amp;#22823;&amp;&#29319;
</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> 

畫面如下:

Big5 網頁難字

因為HTML原始碼現在是:

<!DOCTYPE html>
<html>
<head>
<meta charset="Big5">
</head>
<body>
    留言:&#29579;&#22823;&#29319;
</body>
</html> 

看到這邊,你會覺得,為何要這麼麻煩?現在不是鼓勵全部改用 UTF-8 嗎?為何要用 Big5 網頁自找麻煩?別忘了,有許多維護為主的公司,也許因為系統的歷史包袱,也許因為公司的組織分工,也許是其他的人事問題,舊系統不是說改就改,即使是改個文字編碼也會困難重重。

有許多人常簡單地問,為什麼我的網頁出現亂碼?為什麼我的資料庫出現亂碼?為什麼我的 XXX 出現亂碼,老實說,很難回答這個問題,唯有了解系統中對於文字編碼的關鍵部份處理,才能解決問題,而這又有賴於對編碼的了解,與所使用技術的熟悉度。