.NET C#でMigemoってみる(その2):記号検索時のエラーを回避する
前回の記事で.NET C#のアプリケーションにMigemoの機能を追加しましたが、一つ問題を見つけました。
特定の記号を含む文字列で検索すると、正規表現エラーになってしまうのです。
どうやら正規表現のメタキャラクターがエスケープされていないみたいです。
というわけで対策方法をまとめてみます。
原因
Migemo.dllのソースrxgen.c中にdefault_int2char関数で、正規表現内の(メタ文字を除く)文字列を内部表現(unsigned int)から文字列(unsigned char *)に変換していますが、エスケープ対象の文字種が'\\', '.', '*', '^'. '$', '/'だけとなっていて、.NET C#の正規表現仕様にマッチしていません。
対策
MigemoのAPI migemo_setproc_int2charで独自のコールバック関数を登録して、.NET C#の正規表現の仕様に合ったエスケープ処理を行います。
ついでに、MigemoのAPI 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();