JSP、HTML 與 JavaScript


你寫了一個 JSP 網頁,使用 Big5 編碼:

<%@ page contentType="text/html; charset=Big5" 
    pageEncoding="Big5"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="Big5">
<title>JSP 網頁</title>
</head>
<body>
    名稱:<input type="text" name="name" value="${name}">
</body>
</html>

其中 ${name} 會是資料庫中撈取出來的名稱,放在請求範圍屬性中再轉發至這個 JSP 頁面,如果實際上資料庫中的名稱是「王大犇」,在伺服端的 JVM 中撈取出來的名稱也確認是正確的顯示,但再經由這個 JSP 網頁,卻會是亂碼:

JSP、HTML 與 JavaScript

這是自然地,因為你的網頁採 Big5 編碼,對瀏覽器而言,看到的 HTML 是:

<!DOCTYPE html>
<html>
<head>
<meta charset="Big5">
<title>JSP 網頁</title>
</head>
<body>
    名稱:<input type="text" name="name" value="王大?">
</body>
</html>

在〈Big 5 網頁難字〉中介紹過,可以將特殊字元用實體編號表示,所以可以在伺服端 JVM 中使用 StringEscapeUtils.escapeHtml() 之類的程式庫,將「王大犇」處理為「&#29579;&#22823;&#29319;」:

request.setAttribute("name", StringEscapeUtils.escapeHtml("王大犇"));

JSP、HTML 與 JavaScript

這是因為瀏覽器看到:

名稱:<input type="text" name="name" value="&#29579;&#22823;&#29319;">

再將「&#29579;&#22823;&#29319;」轉為實際的「王大犇」。然而,如果這麼撰寫:

<%@ page contentType="text/html; charset=Big5" 
    pageEncoding="Big5"%>
<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript">
        window.onload = function() {
            document.getElementById('name').value = '${name}';
        };
    </script>
<meta charset="Big5">
<title>JSP 網頁</title>
</head>
<body>
    名稱:<input id="name" type="text" name="name">
</body>
</html>

如果 ${name} 是將「王大犇」處理為「&#29579;&#22823;&#29319;」的結果,那會看到:

JSP、HTML 與 JavaScript

這個問題在〈JavaScript 編碼基礎〉中略談過,瀏覽器對於收到的 HTML 會進行剖析,這是實體編號被剖析為對應字元的時間點,這也是為什麼:

名稱:<input type="text" name="name" value="&#29579;&#22823;&#29319;">

最後會看到「王大犇」,瀏覽器完成 HTML 剖析後,會建立 JavaScript 環境中對應的 DOM 物件,如果直接將字串指定給 DOM 的特性,那就會以實際的字串值設定給 DOM 特性,而不會再經由剖析,不過〈JavaScript 編碼基礎〉基礎提過,innerHTML 特性是個例外,指定給 innerHTML 的字串,會嘗試再進行 HTML 剖析,所以如果這麼撰寫:

<%@ page contentType="text/html; charset=Big5" 
    pageEncoding="Big5"%>
<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript">
        window.onload = function() {
            var span = document.createElement('span');
            span.innerHTML = '${name}';
            document.getElementById('name').value = span.innerHTML;
        };
    </script>
<meta charset="Big5">
<title>JSP 網頁</title>
</head>
<body>
    名稱:<input id="name" type="text" name="name">
</body>
</html>

那就會看到正確的「王大犇」。

你會想,誰會那麼無聊,使用 JSP 將字串產生為 JavaScript,再用 JavaScript 指定給輸入欄位呢?有的時候,尤其是維護舊系統為主的公司,有些部份是你可以修改的,但有些剖份是你不能修改的(例如也許是另一個部份負責的子系統),就會遇到類似的情況。

例如,也許另一個子系統傳回的字串一徑作實體編號,你的 JSP 中,使用 Ajax 進行查詢,像是某個查詢人名,另一個子系統直接將查得的人名作實體編號傳回以下結果:

&#29579;&#22823;&#29319;

如果你這麼撰寫:

request.onreadystatechange = function() {
    if(request.readyState === 4) {
        if(request.status === 200) {
            document.getElementById('name').value = request.responseText;
        }
    }
};

那文字欄位就會出現「&#29579;&#22823;&#29319;」,但如果你這麼撰寫:

<%@ page contentType="text/html; charset=Big5" 
    pageEncoding="Big5"%>
<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript">
    window.onload = function() {
        var xhr = window.XMLHttpRequest && 
              (window.location.protocol !== 'file:' 
                  || !window.ActiveXObject) ?
               function() {
                   return new XMLHttpRequest();
               } :
               function() {
                  try {
                     return new ActiveXObject('Microsoft.XMLHTTP');
                  } catch(e) {
                     throw new Error('XMLHttpRequest not supported');
                  }
               };
        var request = xhr();
        request.onreadystatechange = function() {
            if(request.readyState === 4) {
                if(request.status === 200) {
                    var span = document.createElement('span');
                    span.innerHTML = request.responseText;
                    document.getElementById('name').value = span.innerHTML;
                }
            }
        };
        request.open('GET', 'response');
        request.send(null);

    };
    </script>       
    <meta charset="Big5">
<title>JSP 網頁</title>
</head>
<body>
    名稱:<input id="name" type="text" name="name"><br>
</body>
</html>

那就可以得到正確的顯示結果。事實上,這些作法,只不過是〈Servlet 中文處理(Tomcat)〉、〈Big 5 網頁難字〉、〈JSP 的轉譯〉、〈JavaScript 編碼基礎〉等基礎觀念的綜合應用,許多程式設計人員在這些基礎不足下,自然就無法變化應用。

再看一個學員來信的例子:

問:若 db 的資料內容若有單引號或雙引號,撈出來會造成 html 顯示不正常,怎麼解決呢?

如果有個輸入欄位:

<input type="text" value="${data}">

如果資料庫中的資料有雙引號,例如 123"456 結果就會是:

<input type="text" value="123"456">

這個 HTML 當然有問題,也許你會想,那就這樣:

<input type="text" value='${data}'>

如果資料庫中的資料有雙引號,例如 123"456 結果就會是:

<input type="text" value='123"456'>

但現在的情況中,資料庫中的資料是雙引號或單引號都有,如果資料實際上是 123'456,你怎麼辦?

簡單的解決方式之一可以用 JSTL,例如:

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <h1>Hello World!</h1>
        <%
            String text1 = "test'test";
            String text2 = "test\"test";
        %>
        <input type="text" value="<c:out value="<%= text1 %>"/>"><br>
        <input type="text" value="<c:out value="<%= text2 %>"/>">
    </body>
</html>

<c:out> 預設 escapeXMLtrue,所以可解決問題,但這位學員又有問題了:

問:不過還真的有一好沒兩好,當我們想要 document.getElementById('xxx').value 給值的時候,卻會老老實實的顯示 html 內碼

ex.會顯示 123'456 或 123"456

我不太知道他要用 <c:out> 產生值給 JavaScript 的原因,但要同時滿足兩個需求也非不行:

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
        <%
            String text1 = "test'test";
            String text2 = "test\"test";
        %>
<html>
    <head>
        <meta charset="UTF-8">
        <title>JSP Page</title>
        <script type="text/javascript">
            window.onload = function() {
                document.getElementById('i3').value =
                           document.getElementById('i1').value;
                document.getElementById('i4').value =
                           document.getElementById('i2').value;

            };
        </script>
    </head>
    <body>
        <h1>Hello World!</h1>
        <input id="i1" type="hidden" value="<c:out value="<%= text1 %>"/>"><br>
        <input id="i2" type="hidden" value="<c:out value="<%= text2 %>"/>"><br>

        <input id="i3" type="text"><br>
        <input id="i4" type="text">
    </body>
</html>

使用 hidden 欄位也可以,或者也可以像先前用動態產生 DOM 元素的方式,這都是先前例子的再變化應用。