misima 秀丸マクロ for SOAP

日本オリンピック代表カタール戦を観ながら,misima 旧字・旧仮名遣い変換 SOAP Web Service 秀丸クライアント対応に取り組んだ。秀丸でテキストの選択範囲を,ネットワーク越しに私の misima SOAP サーバに転送し,サーバが旧字・旧仮名変換し返却して来た結果テキストで書き換える,というもの。ホームのカタールが勝負強さを発揮して,見事後半残すところ 13 分で追いつき,逆転。日本が信じられない負け方をした。信じられない勝ち方は見たことがないが。ロスタイムでなんとハンドで PK を献上するなんて,まあ優しい日本人らしいかわいいところか。こんなのでオリンピックに出場できたとしても,よいものだろうか。それはよいとして,秀丸用のマクロができた。秀丸バージョンは 7.03 である。

misima SOAP Web Service 秀丸クライアント方式

秀丸はマクロ実装において,テキスト選択範囲を外部プログラムの標準入力に渡すことができる。賢明な仕様だと感心した。でも,その文字コードがどうも Shift_JIS のようで,せっかく misima SOAP Java クライアント misimaSoapClient に食わせることができるのに,UTF-8 を前提としている misima の出力が化けてしまい目的に適わない。秀丸は定評ある優れたエディタではあるが,マクロレベルでのエンコーディングに対する配慮は,GNU Emacs (クリップボード,プロセス間通信などすべてのインタフェースで文字エンコーディングを指定できる) に比べるといまひとつ(というか,ダサダサ)という印象を受ける。

ならコード変換すればよい,という考え方は,UTF-8 から Shift_JIS に戻す段階で JIS X 0208 にない Unicode CJK 統合漢字が化けてしまい本末転倒である。なんとか Unicode で一貫しなければならないのである。

しようがないので,クリップボード経由で外部プログラムとテキストを連絡する方式にした。秀丸も UTF-8 を含むいくつかの文字コードをサポートしている。エンコードの異なる複数の秀丸文書間,あるいは他のアプリとの間でコピー&ペーストができないとなると,さすがに使い物にならない。よってもって,クリップボードのエンコーディングは Unicode で管理可能な作りになっていると想像できるからである。

という次第で,クリップボードからデータを読み取り,misima SOAP 変換をコールし,変換結果をクリップボードにストアする Java プログラム misimaSoapCbClient を書いた。Microsoft Word 用にもともと書きかけていたのですぐできた。これをドライブする簡単な秀丸マクロを書いたら,文字化けせずに変換が成功した。Word 用マクロは結局 Microsoft Office XP Web Services Toolkit により始末したが,クリップボード方式は無駄にならなかったわけである。秀丸マクロの仕事は,クリップボードとのやり取りと Java プログラムのドライブだけになる。

ここでもうひとつ障碍が発生。マクロから java プログラムを起動するとどうしても「コマンドプロンプト」の画面が変換中に立ち現れてちょっとみっともない。秀丸のマクロ Tips を検索したら "> nul" でこれを抑止できるような記事を見つけてこれを試してみたが,ダメであった。これまたしようがないので,引数に指定したプログラム (ここでは Java) を,コンソールプログラムとしてでなく,バックグラウンドプロセスとして起動する短いランチャプログラムを C で書いた (下記・秀丸マクロコード中の misimalaunch)。Borland C++ 5.5 の無償お試しコンパイラが役立った。全部 C で書けばよいのだけど,クリップボード,SOAP を操作する Windows アプリを C/C++ で書く気力はいまの私にはない。[ misimalaunch についてはその後『misima SOAP Web Service 2.4 公開』に記したので,そちらを参照。]

秀丸マクロ

misima SOAP Web Service クライアント秀丸マクロコードを以下に示す。このマクロそのものは,選択テキストをクリップボードに切り出し,runsync2 命令でもって外部プログラムをコールし,その結果によって書き換えられたクリップボードデータを再び編集エリアにペーストするだけのものである。

//
//  misima SOAP Web Service 秀丸クライアント
//  Copyright (c) 2007 isao yasuda, All Right Reserved.
//
 
// misima パラメータ: 旧字(UTF-8),旧仮名,用字・用語,単純変換指定
$param = "-kyit -s c ";
// 文字列が選択されていないならエラー終了
if (!selecting) {
    message "文字列を選択してください";
    endmacro;
}
// クリップボードに切り出し
cut;
// misima SOAP サーバコール
// - misimalaunch: コマンドプロンプト画面抑止ランチャ
// - misimaSoapCbClient: Java クリップボード SOAP クライアント
runsync2 "misimalaunch javaw misima.misimaSoapCbClient " +
    "-u http://yasuda.homeip.net/services/misimaSoapConnector " +
    $param;
// 異常終了の場合,対象テキストを戻して終了
if (!result) {
    message "実行に失敗しました";
    endmacro;
}
// クリップボードの misima 変換結果をペースト
paste; // 失敗時は cut した時で戻るはず

Java プログラム・コード

サーバに対する変換要求を実際に処理している SOAP クライアントプログラムは,秀丸マクロ runsync2 命令の引数にある Java クラス misimaSoapCbClient である。これは static main メソッドをもち,クリップボードとの入出力を担当する misimaClipboad クラス,misima SOAP Web Service サーバとやり取りをする misimaSoap クラス,misima 変換オプションを解析する misimaPameter クラスを使用している。

Java コードを掲載しておく。ただし,misimaParameter クラスは変換オプション解析処理であって,クリップボード処理,SOAP アクセス処理とは直接関係しないので,ここでは割愛。

misimaSoapCbClient クラスは,単体でコマンドラインから起動して,クリップボードデータを読み取って,misima SOAP 変換を行い,変換結果をクリップボードに書く。引数に -u サーバ・エンドポイントURI と misima サーバ側に与える変換オプションを取る。

よって,秀丸に限らず,クリップボードの Unicode 文字列をペーストできるアプリなら,クリップボードに文字列をコピーしたあと,以下のように misimaSoapCbClient を実行し,これが終了したあとにクリップボード内容をペーストすれば,旧字・旧仮名遣い変換結果を編集エリアに貼付けることができる。

(アプリで変換したいテキストをコピーないしカット)
C:¥Documents and Settings¥user> java misima.misimaSoapCbClient ¥
-u http://yasuda.homeip.net/services/misimaSoapConnector -kyit -s c
C:¥Documents and Settings¥user>
(アプリでペーストすると変換結果が得られる)

さらに言えば,秀丸 runsync2 と同じような,外部プログラムを起動できるマクロ機構を有するアプリならば,秀丸とまったく同じ方式で misima SOAP Web Service クライアントマクロを書くことができるはずである。

// -*- coding: utf-8; mode: java; -*-
package misima;
 
import java.io.*;
 
/**
 * <pre>
 *  misima SOAP WebService クライアント クリップボード版
 *  $Id: insomnia.txt 222 2014-03-27 11:23:12Z isao $
 *  Copyright (c) 2005-2007, isao yasuda, All Rights Reserved.
 *  ----
 *  旧仮名・旧字変換支援 misima SOAP WebService にアクセスする.
 * </pre>
 * @author 安田  功 (Isao YASUDA)
 * @version $Id: insomnia.txt 222 2014-03-27 11:23:12Z isao $
 */
public class misimaSoapCbClient
{
    /** テキスト最大文字数 */
    private static final int MAXSIZE = 8000; 
 
    /** misimaSoapCbClient を新たに作成する.*/
    public misimaSoapCbClient(){}
     
    /** main メソッド */
    public static void main(final String[] args)
    {
        /** misima 変換対象テキスト */
        String rt = null;
        /** misima 変換結果テキスト */
        String ct = null;
        /** 出力対象テキスト */
        String res = null;
        /** misima オプション解析 */
        misimaParameter mp = new misimaParameter(args);
 
        /** クリップボードから対象テキストを読み, 送信テキストにセットする. */
        misimaClipboard mcl = new misimaClipboard();
        rt = mcl.getmisimaClipboard();
 
        /** テキスト長最大値オーバーか0の時は入力を出力にセットする. */
        int rlen = rt.length();
        if (rlen > MAXSIZE || rlen == 0) {
            if (rlen > MAXSIZE) {
                System.err.println("入力が大き過ぎます(" + rlen + " 文字)");
            } else {
                System.err.println("入力がありません.");
            }
            res = rt;
        }
        /** misima SOAP Server に接続する. */
        else {
            misimaSoap ms = new misimaSoap(
                mp.getParameter(), rt, mp.getEndpoint());
            ct = ms.misimaSoapTransaction();
            /** 変換結果が null の時,入力を戻す. */
            if (ct == null) {
                System.err.println("出力が空でした.");
                res = rt;
            }
            /** 変換結果を出力バッファにセットする. */
            else {
                res = ct;
            }
        }
 
        /** 変換後テキストをクリップボードに書く. */
        mcl.setmisimaClipboard(res);
        System.exit(0);
    }
}

misimaClipboard クラスは AWT クラスライブラリのクリップボード操作クラスを使っている。中核をハイライト表示で示す。

// -*- coding: utf-8; mode: java; -*-
package misima;
 
import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;
 
/**
 * <pre>
 *  misima SOAP WebService クリップボードアクセス
 *  $Id: insomnia.txt 222 2014-03-27 11:23:12Z isao $
 *  Copyright(c) 2005-2007, isao yasuda, All Rights Reserved.
 *  ----
 *  クリップボードの読み書きを行う.
 * </pre>
 * @author 安田  功 (Isao YASUDA)
 * @version $Id: insomnia.txt 222 2014-03-27 11:23:12Z isao $
 */
public class misimaClipboard
{
    /** クリップボード */
    private Clipboard cb;
    /** 転送可能形式 */
    private Transferable st;
    /** セレクションデータ */
    private StringSelection ss;
    /** クリップボード取得文字列 */
    private String rt;
 
    /** クリップボードインスタンスを新たに生成する. */
    public misimaClipboard()
    {
        //クリップボードオブジェクトの取得
        cb = Toolkit.getDefaultToolkit().getSystemClipboard();
    }
   
    /**
     * クリップボードからテキストを取得し,返却する.
     * @return クリップボードから取得した文字列
     */
    public String getmisimaClipboard()
    {
        //クリップボードからトランスファに格納
        st = cb.getContents(this);
        try {
            rt = (String) st.getTransferData(DataFlavor.stringFlavor);
        }
        catch (Exception e) {
            System.err.println(e.toString());
        }
        return rt;
    }
 
    /**
     * テキストをクリップボードに出力する.
     * @param settext クリップボードにセットすべき文字列
     */
    public void setmisimaClipboard(String settext)
    {
        //取得テキストでセレクションを生成
        ss = new StringSelection(settext);
        //セレクションをクリップボードに書く
        cb.setContents(ss, ss);
    }
}

misimaSoap クラスは AXIS SOAP Web Service クラスライブラリを使用している。misima SOAP の WSDL で要求される二つの引数(misima オプション及び変換対象テキスト),misima SOAP Web Service サーバのエンドポイントを受けて,サーバへの要求送信,変換結果の受信,呼び出し元への返却を行う。

// -*- coding: utf-8; mode: java -*-
package misima;
 
import java.util.regex.*;
import javax.xml.rpc.*;
import javax.xml.rpc.encoding.TypeMappingRegistry;
import javax.xml.rpc.encoding.TypeMapping;
import javax.xml.rpc.encoding.XMLType;
import javax.xml.namespace.QName;
 
/**
 * <pre>
 *  misimaSoap SOAP 接続
 *  $Id: insomnia.txt 222 2014-03-27 11:23:12Z isao $
 *  Copyright(c) 2005-2007, isao yasuda, All Rights Reserved.
 *  ----
 *  旧仮名・旧字変換支援 misima SOAP サーバに接続する.
 * </pre>
 * @author 安田  功 (Isao YASUDA)
 * @version $Id: insomnia.txt 222 2014-03-27 11:23:12Z isao $
 */
public class misimaSoap
{
    /** エンドポイント */
    private String endpoint;
    /** 第一パラメータ(コマンドライン引数) */
    private String mReq1 = null;
    /** 第二パラメータ(変換対象テキスト) */
    private String mReq2 = null;
 
    /**     
     * Soap 接続を新たに作成する.
     * @param reqParam misimaコマンドライン引数
     * @param reqText 変換対象テキスト
     * @param ep エンドポイント
     */
    public misimaSoap(String reqParam, String reqText, String ep) 
    {
        endpoint = ep; 
        mReq1 = reqParam;
        mReq2 = reqText;
    }
     
    /**     
     * Soap 接続を新たに作成する.
     * @param reqParam misimaコマンドライン引数
     * @param reqText 変換対象テキスト
     */
    public misimaSoap(String reqParam, String reqText) 
    {
        endpoint = 
            "http://yasuda.homeip.net/axis/services/misimaSoapConnector"; 
        mReq1 = reqParam;
        mReq2 = reqText;
    }
     
    /**
     * misima SOAP 通信
     * @return 変換結果テキスト
     */
    public String misimaSoapTransaction()
    {
        /** SOAP 接続オブジェクト */
        Call call = null;
        /** SOAP サーバ変換結果文字列 */
        String mResult = null;
 
        try {
            /** Service オブジェクトを生成する. */
            ServiceFactory factory = ServiceFactory.newInstance();
            Service service = factory.createService(new QName("misima"));
            /** Call オブジェクトを生成する.    */
            call = (Call) service.createCall();
        } catch (Exception e) {
            System.err.println("SOAP オブジェクト生成に失敗しました: " + e);
            return null;
        }
     
        try {
            /** エンドポイントを設定する.       */
            call.setTargetEndpointAddress(endpoint);
            /** オペレーションを設定する. */
            call.setOperationName(new QName("misimaConvert"));
            /** パラメータを設定する. */
            call.addParameter(
                "op1", XMLType.XSD_STRING, ParameterMode.IN);
            call.addParameter(
                "op2", XMLType.XSD_STRING, ParameterMode.IN);
            call.setReturnType(XMLType.XSD_STRING);
            /** Web サービスを起動する. */
            mResult = (String) call.invoke(new Object[]{mReq1, mReq2});
        } 
        /** 例外処理. */
        catch (Exception e) {
            System.err.println("WebService 中にエラーが発生しました: " + e);
            return null;
        }
        return mResult;
    }
}

付記

まだ Java プログラムも C プログラムもプロトタイプなので,misima オプション指定などもう少しチューニングしてから公開するつもりでいる。Windows のエディタとして大きなシェアを持っている秀丸エディタでも,これで misima 変換がサポートできそうである。

次は Mac OS X で人気のある Jedit X 向けに misima SOAP 変換マクロ (AppleScript) に取り組む予定である。AppleScript は Apple Event Manager で SOAP や XML-RPC をサポートしているので,秀丸よりもっと簡単にサポートできると思う。

参考文献

秀丸マクロの実装にあたり,以下の書籍を参考にした。

2015.8.18 付記

最近の Web Service の主流は RESTful ではないかと思う。RESTful は SOAP と比べ,XML に依存せず簡便に実装できるメリットがあると思う。弊サイトの misima SOAP Web Service はセキュリティの課題(要するに,限定公開とするための認証機構を実装できていなかったこと)もあり,その後公開を停止した。現在,misima RESTful Web Service が稼働している。

misima RESTful Web Service は Web ブラウザ,GNU Emacs,Microsoft Word 2003 / 2007,秀丸エディタから直接 misima 旧字・旧仮名遣い変換をサポートしている。ただし,変換実行に際して usercode(友人にだけ配布した非公開キー)を指定する必要があり,限定ユーザしか利用できない。

misima RESTful Web Service のサーバ実装(Java Servlet),各種クライアント実装については,弊ブログの以下の記事を参照いただきたい。