Thinking Skeever

Skyrim/The Witcher 3 Modについてのあれこれ。FoModの作り方、Mod導入時のトラブル事例などのニッチな話を書いていきます。a.k.a. BowmoreLover@nexusmods

.NET C#でMigemoってみる(その2):記号検索時のエラーを回避する

前回の記事で.NET C#のアプリケーションにMigemoの機能を追加しましたが、一つ問題を見つけました。
特定の記号を含む文字列で検索すると、正規表現エラーになってしまうのです。

f:id:thinkingskeever:20180313215929p:plain

どうやら正規表現のメタキャラクターがエスケープされていないみたいです。
というわけで対策方法をまとめてみます。

原因

Migemo.dllのソースrxgen.c中にdefault_int2char関数で、正規表現内の(メタ文字を除く)文字列を内部表現(unsigned int)から文字列(unsigned char *)に変換していますが、エスケープ対象の文字種が'\\', '.', '*', '^'. '$', '/'だけとなっていて、.NET C#正規表現仕様にマッチしていません。

対策

MigemoAPI migemo_setproc_int2charで独自のコールバック関数を登録して、.NET C#正規表現の仕様に合ったエスケープ処理を行います。
ついでに、MigemoAPI migemo_setproc_char2intについても独自のコールバック関数を登録して、2バイト文字を意識した処理を行います(rxgen.c中にdefault_char2int関数が2バイト文字を意識していないため)。

コード例

Migemo.csに次のコードを追加します。

  • コールバック関数での引数のアクセスにunsafeコードを使っているので、プロジェクトオプションのビルドタブで[アンセーフコードの許可]をチェックしてください(引数のマーシャリング云々が分からずサボってしまいました)。
  • エスケープすべきメタ文字についてはMicrosoft Reference Sourceを参考にしました。
[DllImport("migemo.dll", CallingConvention = CallingConvention.StdCall)]
private static extern void migemo_setproc_char2int(IntPtr obj,
    [MarshalAs(UnmanagedType.FunctionPtr)]CharToIntCallbackDelegate callback);

[DllImport("migemo.dll", CallingConvention = CallingConvention.StdCall)]
private static extern void migemo_setproc_int2char(IntPtr obj,
    [MarshalAs(UnmanagedType.FunctionPtr)]IntToCharCallbackDelegate callback);

//コールバックのデリゲート
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private unsafe delegate int CharToIntCallbackDelegate(byte* input, uint* output);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private unsafe delegate int IntToCharCallbackDelegate(uint input, byte* output);

/ デリゲートがGCで消えないように保持しておく
private CharToIntCallbackDelegate DefaultCharToIntCallback;
private IntToCharCallbackDelegate DefaultIntToCharCallback;

// 独自のコールバックを登録する
// LoadDictionaryの延長で辞書の文字コードに合ったデフォルトのコールバックに戻されるので、
// LoadDictionaryの後に呼び出すこと。
public void SetDefaultProc()
{
    if (this.migemoObject != IntPtr.Zero)
    {
        unsafe
        {
            DefaultCharToIntCallback = DefaultCharToIntCallbackFunction;
            DefaultIntToCharCallback = DefaultIntToCharCallbackFunction;
            migemo_setproc_char2int(this.migemoObject, DefaultCharToIntCallback);
            migemo_setproc_int2char(this.migemoObject, DefaultIntToCharCallback);
        }
    }
}

// コールバック関数 CharToInt
private unsafe static int DefaultCharToIntCallbackFunction(byte* input, uint* output)
{
    if (((uint)((input[0]) ^ 0x20) - 0xa1 < 0x3c) &&                            //SJIS1バイト目の判定
        ((0x40 <= (input[1])) && ((input[1]) <= 0xfc) && ((input[1]) != 0x7f))) //SJIS2バイト目の判定
    {
        if (output != null)
            output[0] = (uint)((input[0] << 8) | input[1]);
        return 2;
    }
    if (output != null)
        output[0] = (uint)input[0];
    return 1;
}

// コールバック関数 IntToChar
private unsafe static int DefaultIntToCharCallbackFunction(uint input, byte* output)
{
    int len = 0;
    if (input >= 0x100)
    {
        if (output != null)
        {
            output[0] = (byte)((input >> 8) & 0xff);
            output[1] = (byte)(input & 0xff);
        }
        len = 2;
    }
    else
    {
        switch (input)
        {
            case ' ':
            case '#':
            case '$':
            case '(':
            case ')':
            case '*':
            case '+':
            case '-':
            case '.':
            case '?':
            case '[':
            case '\\':
            case ']':
            case '^':
            case '{':
            case '|':
            case '}':
                if (output != null)
                    output[len] = (byte)'\\';
                len++;
                break;
        }
        if (output != null)
            output[len] = (byte)(input & 0xff);
        len++;
    }
    return len;
}


アプリケーション側の処理に Migemo.SetDefaultProc メソッドの呼び出しを追加する。

Migemo migemo = new Migemo();
migemo.LoadDictionary(Migemo.DictionaryId.Migemo, "./dict/cp932/migemo-dict");
migemo.LoadDictionary(Migemo.DictionaryId.HanToZen, "./dict/cp932/han2zen.dat");
migemo.LoadDictionary(Migemo.DictionaryId.HiraToKata, "./dict/cp932/hira2kata.dat");
migemo.LoadDictionary(Migemo.DictionaryId.RomaToHira, "./dict/cp932/roma2hira.dat");
migemo.LoadDictionary(Migemo.DictionaryId.ZenToHan, "./dict/cp932/zen2han.dat");

//コールバックの登録。必ずLoadDictionaryの後に呼び出す
migemo.SetDefaultProc();

対策結果

無事、記号での検索ができるようになりました!

f:id:thinkingskeever:20180313215943p:plain

ちなみに、上記のコールバック関数でUTF-8との変換を行い、Migemo.csのQueryの文字コード変換処理を手直しすれば、utf-8の辞書が使えるようです。

以上です。



改訂履歴
2018/03/13 22:00 - 初回公開
2018/03/19 12:00 - コールバック関数 IntToChar のメタキャラ判定の訂正

Copyright (C) 2015,2017 ThinkingSkeever, All Rights Reserved.
ブログの記事内に記載されているメーカー名、製品名称等は、日本及びその他の国における各企業の商標または登録商標です。
リンクはご自由に。記事の転載はご遠慮ください。記事を引用する場合はトラックバックするか元のURLを明記してください。