misima Ajax & Servlet 版 Ver. 2.5

旧字・旧仮名遣い変換ソフトウェア misima Servlet 版を公開した。同時に,JIS 第一・第二水準範囲内での旧字変換の問題(単純変換などを同時に指定したとき範囲外の漢字が混入してしまう問題)をやっと訂正した。こちらから利用できる。

辞書を先読みすることで高速化を図った misimaserver Perl デーモンを,バックエンドで利用する。オプションを簡易化し,従来サーバで行っていた misima オプション引数の組立てをブラウザ側で実行する。Ajax を用いてページのリロードなしに変換結果を挿入する。従来のものと比べ,変換オプションが限られていてユーザ辞書も受け付けない欠点がある一方,高速に動作する。シンプルな画面で簡易に使いたいひとに向いている。処理可能なテキストサイズは 8,000 バイトの制限を設けた (Tomcat 5.5 server.xmlmaxPostSize パラメータによる)。

Ajax に基づくコード実装において,久しぶりに JavaScript と格闘した。先に紹介した O'Reilly の『Ajax & Java — Java プログラマのための Ajax プライマー』などオライリー書籍が役立った。サーバを Perl で書くか,Java Servlet にするか悩んだのだけれど,本書の影響もあって後者を選択。

Ajax は JavaScript の一テクノロジーに過ぎないが,Google の地図アプリで俄然脚光を浴びるようになった。非同期にサーバと通信を行い,ページの一部を書き換えることで軽快な Web アプリを実現する。misima Servlet 版 JavaScript のコアな部分をあげておく。

// -*- coding: utf-8; mode: javascript; -*-
// misima convert: Ajax Java Servlet invoking
var req;
// misima 変換要求
function misimaconvert() { 
    var it  = document.getElementById("obj").value;
    var url = "/misimaservlet/convert";
    // misima オプションパラメータの組立て
    var ss  = "<misima_param>" + parameter() + "</misima_param>" 
        + it.replace(/\r?\n/g, ":#;~");
    // HTTP リクエストの生成
    if (window.XMLHttpRequest) { 
        req = new XMLHttpRequest(); 
    } 
    else if (window.ActiveXObject) { 
        req = new ActiveXObject("Msxml2.XMLHTTP"); 
        if (!req) {
            req = new ActiveXObject("Microsoft.XMLHTTP"); 
        }
    }
    // 変換要求 (非同期実行のポイント)
    req.open("POST", url, true); 
    req.setRequestHeader("content-type",
                         "application/x-www-form-urlencoded; charset=UTF-8");
    req.onreadystatechange = callback; 
    req.send("obj=" + encodeURI(ss));
}
// misima パラメータ組立て
function parameter() {
    var params = "-q";
    switch (document.getElementById("bopt").selectedIndex) {
    case 0:  params = params + " -kyit -s"; break;
    case 1:  params = params + " -kyt -s"; break;
    case 2:  params = params + " -kt -s"; break;
    case 3:  params = params + " -t -s"; break;
    default: break;
    }
    if (document.getElementById("jopt").checked) {
        params = params + " j";
    } else {
        params = params + " c";
    }
    if (document.getElementById("topt").checked) {
        params = params + " -x kuit -g";
    }
    if (document.getElementById("nopt").checked) {
        params = params + " -n";
    }
    return params;
}
// Ajax コールバック: 非同期に受信した変換結果をページに挿入する。
function callback() { 
    if (req.readyState == 4) { 
        if (req.status == 200) { 
            var res = req.responseText.replace(/:#;~/g, "<br />"); 
            document.getElementById("out").innerHTML = res;
        } 
    }
}

この JavaScript を呼び出すフォーム HTML は以下の通り。

<!-- Ajax misima convert フォーム -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
  <head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
    <link type="text/css" rel="stylesheet" href="style.css" />
    <script type="text/javascript" src="misima-ajax.js"></script>
    <title>misima Ajax &amp; Java Servlet 版</title>
  </head>
  <body>
    オプション<br />
    <select id="bopt">
      <option value="0" selected>旧字/旧仮名/用語/繰返</option>
      <option value="1">旧字/旧仮名/用語</option>
      <option value="2">旧字/旧仮名</option>
      <option value="3">旧字</option>
    </select> 
    <input type="checkbox" id="topt" />LaTeX 
    <input type="checkbox" id="jopt" />第一・二水準内 
    <input type="checkbox" id="nopt" />仮名反転<br />
    テキスト入力<br />
    <input type="button" value="変 換"
           onClick="misimaconvert();" /><br />
    <textarea id="obj" cols="40" rows="6"></textarea>
    変換結果<br />
    <div id="out" />
  </body>
</html>

HTTP リクエストオブジェクト XMLHttpRequest() を生成し,misima パラメータ+対象テキストを POST リクエストで送信し,一方コールバックで非同期に結果を待ち受けるところがポイントである (req.open から req.send まで)。オプションパラメータの組立ての関数 (parameter()) において,getElementById() メソッドでないと select や checkbox のオブジェクトが参照できないのは何故? 「document.フォーム名.エレメント名」ではダメなんである。ここが悩んだところである。

このフォームから起動される Java Servlet misimaServlet コードは以下に掲げるとおりである。JavaScript からは convert として呼び出されているが,この対応はデプロイメントディスクリプタ web.xmlservlet-mapping で定義しておく。

<servlet-mapping>
  <servlet-name>misimaServlet</servlet-name>
  <url-pattern>/convert</url-pattern>
</servlet-mapping>

misimaServlet は,socket を使って misimaserver と通信する以外はまったく普通のサーブレットである。出力テキストは UTF-8 なので,setContentType メソッドの引数に charset=UTF-8 を入れておかないと,getWriter().write() が正しく動作しない。

// misima Java Servlet for misimaserver & Ajax client
// 2007 (c) isao yasuda, All Rights Reserved.
import java.io.*;
import java.net.*;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public class misimaServlet extends HttpServlet {
    private static final String HOST = "localhost";
    private static final int PORT = 34000;
  
    public void doPost(HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException
    {
        /** ユーザリクエストの取得 */
        String obj = (String) req.getParameter("obj"); // 変換対象テキスト
        StringBuffer rbuf = new StringBuffer();        // 編集バッファ
        String ct = "?";    // 変換後テキスト
        Socket sock = null; // misimaserver 接続用ソケット
 
        if (obj != null) {
            /** misimaserver ソケット接続 */
            try {
                sock = new Socket(HOST, PORT);
                /** 対象テキストを送信する */
                Writer send = new OutputStreamWriter(
                    sock.getOutputStream(), "UTF-8");
                send.write(obj + "\n");
                send.flush();
                /** 変換結果テキストを受信する */
                InputStreamReader recv = new InputStreamReader(
                    new BufferedInputStream(sock.getInputStream()), "UTF-8");
                int c;
                while ((c = recv.read()) != -1) {
                    rbuf.append((char)c);
                }
            }
            catch (Exception e) {
                rbuf.append("** Error occured in connection.\n" + obj);
            }
            finally {
                if (sock != null) sock.close();
                sock = null;
            }
            ct = rbuf.toString();
        }
        /** 結果出力 */
        res.setContentType("text/xml; charset=UTF-8");
        res.setHeader("Cache-Control", "no-cache");
        res.getWriter().write(ct);
    }
}