中国語簡体字を中国語繁体字に変換するユーザー定義関数を作ってみた


中国本土の字って、さっぱり読めません。
たとえば、「请给我电话」。日本人的には、「清給我申活」に見えます。さっぱり意味がわかりません。
でも、これが実は「請給我電話」の字体違いだとわかると、もう少し何か意味がみえてくる気がします。

ま、最初から翻訳サイトにかけちゃえば一発なんですが、せめて、字の変換だけでも簡単にできないかなと、ExcelVBAでワークシート関数を作ってみたいと思います。

ちなみに、この投稿で訴えたい技術面でのポイントは、以下のことです。

  1. VBAでワークシートで使える関数が作れる。
  2. VBAでWindowsAPIを使える。
  3. VBAでもUnicodeを使える。

まず、1点目、VBAでワークシートで使える関数が作れる点。
標準モジュールに、Public Functionを作ると、そのFunctionは、ワークシートから実行できるようになります。
まずは、最初の一歩として、与えられた引数をそのまま返す関数として、作ってみます。
Visual Basic Editorのプロジェクトツリーで 右クリック→「挿入」→「標準モジュール」として、標準モジュールを追加します。

そして、この追加してできた 標準モジュール Module1 に以下のソースを記載します。

Public Function ToTraditional(ByVal s As String)
    ToTraditional = s
End Function

では、この関数がワークシート関数として動作することを確認してみます。
準備として、A1セルに適当な文字列を入れておきます。
「数式」→「関数の挿入」として表示される「関数の挿入」画面で「関数の分類」を「ユーザー定義」に切り替えると、見事に先に作ったToTraditional関数ができています。

ToTraditional関数を選択し、「OK」を押します。
「関数の引数」画面がでてきました。引数名が ソースコードのまま、「S」とでてて、ダサいですが、いったんはこれで良しとします。 Sの値として、A1セルを参照して、「OK」を押してみます。

意図したとおり、引数がそのまま結果となるワークシート関数ができました。

では、ここから実際に簡体字を繁体字(台湾・香港などの字体。おおよそ日本の戦前の旧字体に類似)に変換する関数を作っていきます。
Windowsは簡体字と繁体字の変換機能をもっています。
残念ながら簡体字と日本語字体との変換機能はもっていません。それでも繁体字の方が簡体字よりは、まだわかりやすいので、繁体字への変換で我慢することにします。
WindowsがOSとして提供する機能をVBAからも利用することができます。こうした行為は一般的にAPI呼び出しとよばれたりします。

ちなみに、API呼び出しができるのが、私がExcelVBAが好きな理由だったりします。会社に存在するほとんどすべてのPCでAPI呼び出しを使ったプログラムを作ることができるという言語はExcelVBAをおいてほかにありません。他の言語は開発言語のインストールが必要です。VBAならExcelが入っていればいいので、多くのPCで追加インストールの必要がありません。

ではこの変換機能の呼び出しを実装していきます。
Windowsのこの変換機能は LCMapString APIによって呼び出すことができます。
LCMapStringでググるとでてくるMicrosoft社の解説ページによると、LCMapStringは次のように説明されています。

int LCMapString(
LCID Locale, // ロケール識別子
DWORD dwMapFlags, // マップ変換の種類
LPCTSTR lpSrcStr, // マップ元文字列のアドレス
int cchSrc, // マップ元文字列の文字数
LPTSTR lpDestStr, // マップ先バッファのアドレス
int cchDest // マップ先バッファのサイズ
);

VBAでこれを使うには、declare文というものを書く必要があります。
declare文は上のAPI解説の内容をVBAに翻訳して、VBAに伝えるものとなります。

MicrosoftのAPIの解説は、以下の構造を持っています。

戻り値のC言語での型 API関数名(
引数1のC言語での型 引数1の名前のサンプル, // 引数1の意味
引数2のC言語での型 引数2の名前のサンプル, // 引数2の意味
....
);

ざっくりいうと、引数の型名がLPで始まるものは VBAでいうところのLongPtr型、そうでないものはたいていVBAでいうところのLong型です。
ほんとは、そんなに簡単ではなくて、山のような対応規則がありますが、その説明はまた別の機会に譲りたいと思います。

説明を一気にはしょると、上のAPIの解説を Declare文にすると、こうなります。

Declare PtrSafe Function LCMapStringW Lib "kernel32" _
(ByVal Locale As Long, ByVal dwMapFlags As Long, _
ByVal lpSrcStr As LongPtr, ByVal cchSrc As Long, _
ByVal lpDesStr As LongPtr, ByVal cchDest As Long) As Long

LCMapStringWはLCMapStringに由来しますが、これは上記の解説ページに「Unicode:Windows NT/2000 は Unicode 版と ANSI 版を実装」とあることに起因します。
この記述があるAPIは Wで終わるUnicode版と Aで終わるANSI版という2バージョンがあります。中国語など日本語文字以外を扱う場合はUnicode版を使う必要があり、API名の後ろにWをつけます。
そして、「Lib "kernel32"」は、これまた上記の解説ページの
「インポートライブラリ:kernel32.lib を使用」に起因します。
どのdllファイルにそのAPIを存在するのかをExcelに伝えるために、declare文では利用API名のあとに、「lib [dllファイル名]」 と記載しますが、この部分にインポートライブラリの.libを取り払った文字列を記載します。

そして、このLCMapString APIは上記の解説ページによると、dwMapFlagsの使い方で、変換処理の内容が決まります。

LCMAP_SIMPLIFIED_CHINESE    中国語の簡体字を繁体字にマップします。
LCMAP_TRADITIONAL_CHINESE   中国語の繁体字を簡体字にマップします。

となってますが、たぶん誤訳で、本当は

LCMAP_SIMPLIFIED_CHINESE    中国語の簡体字に繁体字をマップします。
LCMAP_TRADITIONAL_CHINESE   中国語の繁体字に簡体字をマップします。

が正解で、我々が使うべきは、LCMAP_TRADITIONAL_CHINESE です。
このLCMAP_TRADITIONAL_CHINESEがなにかいうと別途定義されている定数なのですが、解説ページには定義内容がないので、こういうときは
「const [定数名]」でググります。
「const LCMAP_TRADITIONAL_CHINESE」でググると、0x04000000 という値がみつかると思います。 0x は VBAでの &h に相当するC言語の表現なので &H4000000 と考えます。

そして、また、いっきに説明をはしょると、できあがりのソースはこうなります。
先ほど、標準モジュール Module1 に書いたダミーのソースは一度全部消して、以下のソースを貼り付けてみてください。


Declare PtrSafe Function LCMapStringW Lib "kernel32" _
(ByVal Locale As Long, ByVal dwMapFlags As Long, _
 ByVal lpSrcStr As LongPtr, ByVal cchSrc As Long, _
 ByVal lpDesStr As LongPtr, ByVal cchDest As Long) As Long

Const LCMAP_TRADITIONAL_CHINESE = &H4000000

Public Function ToTraditional(ByVal Simplified As String)
    Dim nLengthToNeed As Long

    'まずは変換結果の格納に必要な文字数をLCMapStringWで調べる
    nLengthToNeed = LCMapStringW(0, LCMAP_TRADITIONAL_CHINESE, _
                    StrPtr(Simplified), Len(Simplified), _
                    0, 0)

    Dim sTemp As String
    '必要な文字数分のメモリを変数に確保する
    sTemp = String(nLengthToNeed, vbNullChar)

    '確保した変数sTempの内容を変換結果で上書きするようにLCMapStringWに指示
    LCMapStringW 0, LCMAP_TRADITIONAL_CHINESE, _
                 StrPtr(Simplified), Len(Simplified), _
                 StrPtr(sTemp), nLengthToNeed

    'sTempの内容を関数の戻り値とする
    ToTraditional = sTemp

End Function

ここでのポイントは、上記解説ページで「~のアドレス」と書いてある引数には 文字列変数の場合、StrPtrを使って、変数に紐づく文字列バッファのアドレスを渡すということです。(これは Unicode版を使うときに限定される話です。)

では、実行してみます。

みごと、请给我电话 が 請給我電話 に変換できました。

どうでしょうか? この投稿でVBAからのAPI利用に興味を持たれて、始められる方がいたら、うれしいです。