3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Excel VBAAdvent Calendar 2017

Day 22

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

Last updated at Posted at 2017-12-21

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

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

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

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

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

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

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

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

ToTraditional関数を選択し、「OK」を押します。
「関数の引数」画面がでてきました。引数名が ソースコードのまま、「S」とでてて、ダサいですが、いったんはこれで良しとします。 Sの値として、A1セルを参照して、「OK」を押してみます。
image.png
意図したとおり、引数がそのまま結果となるワークシート関数ができました。
image.png

では、ここから実際に簡体字を繁体字(台湾・香港などの字体。おおよそ日本の戦前の旧字体に類似)に変換する関数を作っていきます。
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版を使うときに限定される話です。)

では、実行してみます。
image.png

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

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

3
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?