LoginSignup
8
10

【C#】漢字をひらがな、カタカナに変換する

Last updated at Posted at 2023-11-06

はじめに

C# で漢字を読み仮名に変換する方法を調べて実装まで漕ぎつけたので、メモとして残しておこうと思います。ほとんど参照先の丸パクリだけど、漢字から読み仮名(カタカナ)変換は自分なりに試行錯誤したところなので許して頂ければと・・・。

環境

  • Windows 10
  • Visual Studio 2022
  • .Net Framework 4.7.2

まずは漢字<>ひらがなの相互変換

勉強不足で細かい処理は分かっていませんが、下記のようにCOM経由でIMEの言語処理サービス(IFELanguage)を利用することで漢字<>ひらがなの相互変換を実現します。GetPhoneticで漢字→ひらがな、GetConversionでひらがな→漢字に変換可能です。MainにSTAThread属性を付与するのを忘れずに。

using System;
using System.Runtime.InteropServices;

[ComImport]
[Guid("019F7152-E6DB-11d0-83C3-00C04FDDB82E")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IFELanguage {
  int Open();
  int Close();
  int GetJMorphResult(uint dwRequest, uint dwCMode, int cwchInput, [MarshalAs(UnmanagedType.LPWStr)] string pwchInput, IntPtr pfCInfo, out IntPtr ppResult);
  int GetConversionModeCaps(ref uint pdwCaps);
  int GetPhonetic([MarshalAs(UnmanagedType.BStr)] string @string, int start, int length, [MarshalAs(UnmanagedType.BStr)] out string result);
  int GetConversion([MarshalAs(UnmanagedType.BStr)] string @string, int start, int length, [MarshalAs(UnmanagedType.BStr)] out string result);
}

internal class Program {
  [STAThread]
  static void Main(string[] args) {
    string kanzi = "漢字";
    IFELanguage ife = null;
    try {
      ife = Activator.CreateInstance(Type.GetTypeFromProgID("MSIME.Japan")) as IFELanguage;
      ife.Open();
      ife.GetPhonetic(kanzi, 1, -1, out string hiragana);
      System.Console.WriteLine($"{kanzi} => {hiragana}"); // 漢字 => かんじ
      ife.GetConversion(hiragana, 1, -1, out kanzi);
      System.Console.WriteLine($"{hiragana} => {kanzi}"); // かんじ => 漢字
    } finally {
      ife?.Close();
    }
    System.Console.ReadKey();
  }
}

次は漢字→カタカナの変換

結論から言えば、GetJMorphResultに下記の引数を渡すことで変換が可能です。


// iterface 省略

internal class Program {
  [STAThread]
  static void Main(string[] args) {
    string kanzi = "漢字";
    IFELanguage ife = null;
    try {
      ife = Activator.CreateInstance(Type.GetTypeFromProgID("MSIME.Japan")) as IFELanguage;
      ife.Open();
      ife.GetJMorphResult(0x00030000, 0x00000008, kanzi.Length, kanzi, IntPtr.Zero, out IntPtr result);
      string katakana = Marshal.PtrToStringUni(Marshal.ReadIntPtr(result, 4), Marshal.ReadInt16(result, 8));
      System.Console.WriteLine($"{kanzi} => {katakana}"); // 漢字 => カンジ
    } finally {
      ife?.Close();
    }
    System.Console.ReadKey();
  }
}

以降で試行錯誤して得た GetJMorphResultの各引数について説明します。

GetMorphResult の引数について

このGetMorphResultの引数ですが下記の定義となっています。

int GetJMorphResult(uint dwRequest, uint dwCMode, int cwchInput, [MarshalAs(UnmanagedType.LPWStr)] string pwchInput, IntPtr pfCInfo, out IntPtr ppResult);
  • dwRequest:変換リクエスト
  • dwCMode:変換オプション
  • cwchInput:変換元の文字列の長さ
  • pwchInput:変換元文字列
  • pfcInfo:?(各列の情報?、調べたけどよくわかりませんでした)
  • ppResult:戻り値ポインタ

先頭の2つで変換処理が決まるので、何が設定出来るのかそれぞれ詳しく見ていきます。

dwRequest

こちらに設定出来るのは下記の3つの値です。

定数名 説明
FELANG_REQ_CONV 0x00010000 変換(ひらがなを漢字に変換)
FELANG_REQ_RECONV 0x00020000 再変換(カタカナや半カナも漢字に変換)
FELANG_REQ_REV 0x00030000 逆変換(読み仮名に変換)

FELANG_REQ_CONVFELANG_REQ_RECONVの違いは、前者はひらがなを漢字にするだけですが、後者はカタカナや半角カナや全角ローマ字も(可能であれば)漢字に変換します。

dwCMode

こちらは下記のフラグをORで組み合わせて設定します。もちろん1つでも構いません。

定数名 説明
FELANG_CMODE_MONORUBY 0x00000002 ?(漢字の文字の上のフリガナ、paMonoBubyPosに返って来る?)
FELANG_CMODE_NOPRUNING 0x00000004 No pruning.
FELANG_CMODE_KATAKANAOUT 0x00000008 カタカナ出力
FELANG_CMODE_HIRAGANAOUT 0x00000000 ひらがな出力(デフォルト)
FELANG_CMODE_HALFWIDTHOUT 0x00000010 半角出力
FELANG_CMODE_FULLWIDTHOUT 0x00000020 全角出力
FELANG_CMODE_BOPOMOFO 0x00000040 台湾発音?
FELANG_CMODE_HANGUL 0x00000080 ハングル発音?
FELANG_CMODE_PINYIN 0x00000100 中国発音?
FELANG_CMODE_PRECONV 0x00000200 以下の変換を実施
・ローマ字を仮名
・変換前にオートコレクト
・句読点と括弧の変換
FELANG_CMODE_RADICAL 0x00000400 ?
FELANG_CMODE_UNKNOWNREADING 0x00000800 ?
FELANG_CMODE_MERGECAND 0x00001000 Merge display with the same candidate.
FELANG_CMODE_ROMAN 0x00002000 ?
FELANG_CMODE_BESTFIRST 0x00004000 Make only the first best.
FELANG_CMODE_USENOREVWORDS 0x00008000 Use invalid revword on REV/RECONV.
FELANG_CMODE_NONE 0x01000000 IME_SMODE_NONE
FELANG_CMODE_PLAURALCLAUSE 0x02000000 IME は複数文節情報を利用
FELANG_CMODE_SINGLECONVERT 0x04000000 IME は単文字モード
FELANG_CMODE_AUTOMATIC 0x08000000 IME は自動モード
FELANG_CMODE_PHRASEPREDICT 0x10000000 IME は語句情報を使用して次の文字を予測
FELANG_CMODE_CONVERSATION 0x20000000 IME は会話モード
FELANG_CMODE_NAME 0x10000000 Name mode (MSKKIME). FELANG_CMODE_PHRASEPREDICTと同じ
FELANG_CMODE_NOINVISIBLECHAR 0x40000000 Remove invisible chars (for example, the tone mark).

この中でもFELANG_CMODE_PRECONVは、FELANG_REQ_RECONVを指定した時に加えて、半角ローマ字も漢字に変換してくれる強力な補正をしてくれます。

あとFELANG_CMODE_NOINVISIBLECHARは感嘆符や顔文字等の記号を削除してくれそうに思えますが、指定の有無に関わらず出力内容に変化がないので、もしかすると値や使用方法が間違っているのかもしれません。

検証内容

dwRequestdwCModeを変えると出力結果にどのような影響があるのか確認した結果は以下の通りです。実際はもっと色んなパターンを試していますが、あまり変わり映えなかったので、意味のありそうな出力結果だけ取り上げています。

FELANG_REQ_CONV

HIRA KATA HIRA
PRE
NOINV
HIRA
ROMAN
PRE
NOINV
rhythm rhythm rhythm rhythm rhythm
kanzi kanzi kanzi 漢字 漢字
kanji 漢字 漢字 漢字 漢字
かんじ 漢字 漢字 漢字 漢字
カンジ 漢字 漢字 漢字 漢字
カンジ 漢字 漢字 漢字 漢字
漢字 漢字 漢字 漢字 漢字
長い文章で 長い文章で 長イ文章デ 長い文章で 長い文章で
!💛ー? !💛―? !💛―? !💛-? !💛-?
あ い う 亜 い ウ 亜 イ ウ 亜 い ウ 亜 い ウ
123 123 123 123 123

FELANG_REQ_RECONV

HIRA KATA HIRA
PRE
NOINV
HIRA
ROMAN
PRE
NOINV
rhythm rhythm rhythm rhythm rhythm
kanzi kanzi kanzi 漢字 漢字
kanji 漢字 漢字 漢字 漢字
かんじ 漢字 漢字 漢字 漢字
カンジ 漢字 漢字 漢字 漢字
カンジ 漢字 漢字 漢字 漢字
漢字 漢字 漢字 漢字 漢字
長い文章で 長い文章で 長イ文章デ 長い文章で 長い文章で
!💛ー? !💛―? !💛―? !💛-? !💛-?
あ い う 亜 い ウ 亜 イ ウ 亜 い ウ 亜 い ウ
123 123 123 123 123

FELANG_REQ_REV

HIRA KATA HIRA
PRE
NOINV
HIRA
ROMAN
PRE
NOINV
rhythm rhythm rhythm rhythm rhythm
kanzi kanzi kanzi かんじ かんじ
kanji かんじ カンジ かんじ かんじ
かんじ かんじ カンジ かんじ かんじ
カンジ かんじ カンジ かんじ かんじ
カンジ かんじ カンジ かんじ かんじ
漢字 かんじ カンジ かんじ かんじ
長い文章で ながいぶんしょうで ナガイブンショウデ ながいぶんしょうで ながいぶんしょうで
!💛ー? !💛ー? !💛ー? !💛-? !💛-?
あ い う あ い う ア イ ウ あ い う あ い う
123 123 123 123 123

※ HIRA:FELANG_CMODE_HIRAGANAOUT
※ KATA:FELANG_CMODE_KATAKANAOUT
※ PRE:FELANG_CMODE_PRECONV
※ ROMAN:FELANG_CMODE_ROMAN
※ NOINV:FELANG_CMODE_NOINVISIBLECHAR

さいごに

EXCELの関数に読み仮名変換があったと記憶しているので簡単に出来ると思っていたのですが、想像以上に難解で参考先の情報がなければ絶対に解決出来なかったと思います。特にCOM周りはお恥ずかしながら初めて知った機能でした。調べてみるとプロパティの取得など痒い所に手が届く機能だったので、少しずつ勉強していきたいと思います。そして肝心の読み仮名変換についても、ひとまず自分が欲してる変換は出来たものの、仕様の完璧な把握には程遠い状態にありますので、こちらも何かの折に合わせて調査進められればなぁと思います。

参考

ソース

using System;
using System.Runtime.InteropServices;

[ComImport]
[Guid("019F7152-E6DB-11d0-83C3-00C04FDDB82E")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IFELanguage {
  /// <summary>
  /// 初期化
  /// </summary>
  int Open();
  /// <summary>
  /// 終了
  /// </summary>
  int Close();
  /// <summary>
  /// 形態解析結果を取得
  /// </summary>
  /// <param name="dwRequest">変換リクエスト、FELANG_REQ_XXX いずれかの値</param>
  /// <param name="dwCMode">変換出力文字と変換オプション、FELANG_CMODE_XXX 1つ以上の組み合わせ(OR)</param>
  /// <param name="cwchInput">pwchInputの文字数</param>
  /// <param name="pwchInput">形態素エンジンによって変換される入力文字(UNICODE)
  ///                         このパラメータをNULLに設定すると前に入力した文字列の次のランクの次のエントリが取得される
  ///                         次のエントリが返される順序は実装によって定義される</param>
  /// <param name="pfCInfo">各列の情報。各pfCInfo[x]はpwchInput[x]に対応、各DWORDはFELANG_CLMN_XXXの組み合わせ</param>
  /// <param name="ppResult">モルフォロジー結果データを受け取るMORRSLT構造体のアドレス</param>
  /// <returns>HRESULT</returns>
  int GetJMorphResult(uint dwRequest, uint dwCMode, int cwchInput, [MarshalAs(UnmanagedType.LPWStr)] string pwchInput, IntPtr pfCInfo, out IntPtr ppResult);
  /// <summary>
  /// IFELanguage オブジェクトの変換モード機能を取得
  /// </summary>
  /// <param name="pdwCaps">FELANG_CMODE_XXX 1つ以上の組み合わせとして表される機能</param>
  /// <returns>HRESULT</returns>
  int GetConversionModeCaps(ref uint pdwCaps);
  /// <summary>
  /// 指定した文字列の日本語のふりがなを取得
  /// </summary>
  /// <param name="string">変換する文字列</param>
  /// <param name="start">変換を開始する開始文字(最初の文字は、0ではなく1)</param>
  /// <param name="length">変換する文字数(-1 の場合、先頭から残りのすべての文字が変換)</param>
  /// <param name="result">変換された文字列</param>
  /// <returns>HRESULT</returns>
  int GetPhonetic([MarshalAs(UnmanagedType.BStr)] string @string, int start, int length, [MarshalAs(UnmanagedType.BStr)] out string result);
  /// <summary>
  /// 入力文字列 (通常はひらがな文字を含む) を変換された文字列に変換
  /// </summary>
  /// <param name="string">変換するふりがなの文字列</param>
  /// <param name="start">変換を開始する開始文字(最初の文字は、0ではなく1)</param>
  /// <param name="length">変換する文字数(-1 の場合、先頭から残りのすべての文字が変換)</param>
  /// <param name="result">変換された文字列</param>
  /// <returns>HRESULT</returns>
  int GetConversion([MarshalAs(UnmanagedType.BStr)] string @string, int start, int length, [MarshalAs(UnmanagedType.BStr)] out string result);
}

public class FELanguage : IDisposable {

  #region
  public static readonly uint FELANG_REQ_CONV = 0x00010000;
  public static readonly uint FELANG_REQ_RECONV = 0x00020000;
  public static readonly uint FELANG_REQ_REV = 0x00030000;
  public static readonly uint FELANG_CMODE_MONORUBY = 0x00000002; // 漢字の文字の上のフリガナ、paMonoBubyPosに返って来る
  public static readonly uint FELANG_CMODE_NOPRUNING = 0x00000004; // No pruning.
  public static readonly uint FELANG_CMODE_KATAKANAOUT = 0x00000008; // カタカナ出力
  public static readonly uint FELANG_CMODE_HIRAGANAOUT = 0x00000000; // ひらがな出力(デフォルト)
  public static readonly uint FELANG_CMODE_HALFWIDTHOUT = 0x00000010; // 半角出力
  public static readonly uint FELANG_CMODE_FULLWIDTHOUT = 0x00000020; // 全角出力
  public static readonly uint FELANG_CMODE_BOPOMOFO = 0x00000040; // 台湾発音?
  public static readonly uint FELANG_CMODE_HANGUL = 0x00000080; // ハングル発音?
  public static readonly uint FELANG_CMODE_PINYIN = 0x00000100; // 中国発音?
  public static readonly uint FELANG_CMODE_PRECONV = 0x00000200; // 以下の変換を実施: ローマ字を仮名、変換前にオートコレクト、句読点と括弧の変換
  public static readonly uint FELANG_CMODE_RADICAL = 0x00000400; // ?
  public static readonly uint FELANG_CMODE_UNKNOWNREADING = 0x00000800; // ?
  public static readonly uint FELANG_CMODE_MERGECAND = 0x00001000; // Merge display with the same candidate.
  public static readonly uint FELANG_CMODE_ROMAN = 0x00002000; // ?
  public static readonly uint FELANG_CMODE_BESTFIRST = 0x00004000; // Make only the first best.
  public static readonly uint FELANG_CMODE_USENOREVWORDS = 0x00008000; // Use invalid revword on REV/RECONV.
  public static readonly uint FELANG_CMODE_NONE = 0x01000000; // IME_SMODE_NONE
  public static readonly uint FELANG_CMODE_PLAURALCLAUSE = 0x02000000; // IME は複数文節情報を利用
  public static readonly uint FELANG_CMODE_SINGLECONVERT = 0x04000000; // IME は単文字モード
  public static readonly uint FELANG_CMODE_AUTOMATIC = 0x08000000; // IME は自動モード
  public static readonly uint FELANG_CMODE_PHRASEPREDICT = 0x10000000; // IME は語句情報を使用して次の文字を予測
  public static readonly uint FELANG_CMODE_CONVERSATION = 0x20000000; // IME は会話モード
  public static readonly uint FELANG_CMODE_NAME = 0x10000000; // Name mode (MSKKIME). FELANG_CMODE_PHRASEPREDICTと同じ
  public static readonly uint FELANG_CMODE_NOINVISIBLECHAR = 0x40000000; // Remove invisible chars (for example, the tone mark).
  public static readonly uint FELANG_CLMN_WBREAK = 0x01;
  public static readonly uint FELANG_CLMN_NOWBREAK = 0x02;
  public static readonly uint FELANG_CLMN_PBREAK = 0x04;
  public static readonly uint FELANG_CLMN_NOPBREAK = 0x08;
  public static readonly uint FELANG_CLMN_FIXR = 0x10;
  public static readonly uint FELANG_CLMN_FIXD = 0x20; // fix display of word
  #endregion

  private IFELanguage _ifelang;

  public FELanguage() {
    Type type = Type.GetTypeFromProgID("MSIME.Japan");
    this._ifelang = Activator.CreateInstance(type) as IFELanguage;
    int hr = this._ifelang.Open();
    if (hr != 0) {
      throw Marshal.GetExceptionForHR(hr) ?? throw new Exception($"{hr} is not error");
    }
  }

  public void Dispose() {
    this._ifelang?.Close();
  }

  public string GetJMorphResult(string str, uint dwRequest, uint dwCMode) {
    int hr = this._ifelang.GetJMorphResult(dwRequest, dwCMode, str.Length, str, IntPtr.Zero, out IntPtr result);
    if (hr != 0) {
      throw Marshal.GetExceptionForHR(hr) ?? throw new Exception($"{hr} is not error");
    }
    return Marshal.PtrToStringUni(Marshal.ReadIntPtr(result, 4), Marshal.ReadInt16(result, 8));
  }

  public string GetConversion(string str) {
    int hr = this._ifelang.GetConversion(str, 1, -1, out string kanzi);
    if (hr != 0) {
      throw Marshal.GetExceptionForHR(hr) ?? throw new Exception($"{hr} is not error");
    }
    return kanzi;
  }

  public string GetPhonetic(string str) {
    int hr = this._ifelang.GetPhonetic(str, 1, -1, out string yomigana);
    if (hr != 0) {
      throw Marshal.GetExceptionForHR(hr) ?? throw new Exception($"{hr} is not error");
    }
    return yomigana;
  }

  public string GetKatakana(string str) {
    return this.GetJMorphResult(str, FELANG_REQ_REV, FELANG_CMODE_KATAKANAOUT | FELANG_CMODE_PRECONV);
  }

  public string GetKanzi(string str) {
    return this.GetJMorphResult(str, FELANG_REQ_CONV, FELANG_CMODE_PRECONV);
  }

  public string GetHiragana(string str) {
    return this.GetJMorphResult(str, FELANG_REQ_REV, FELANG_CMODE_PRECONV);
  }

}

internal class Program {
  [STAThread]
  static void Main(string[] args) {
    string kanzi = "漢字";
    IFELanguage ife = null;
    try {
      // Raw IFE Ver
      ife = Activator.CreateInstance(Type.GetTypeFromProgID("MSIME.Japan")) as IFELanguage;
      ife.Open();
      ife.GetJMorphResult(0x00030000, 0x00000008, kanzi.Length, kanzi, IntPtr.Zero, out IntPtr result);
      string katakana = Marshal.PtrToStringUni(Marshal.ReadIntPtr(result, 4), Marshal.ReadInt16(result, 8));
      System.Console.WriteLine($"{kanzi} => {katakana}");
      // FELanguage Ver
      FELanguage fe = new FELanguage();
      katakana = fe.GetKatakana(kanzi);
      System.Console.WriteLine($"{kanzi} => {katakana}");
    } finally {
      ife?.Close();
    }
    System.Console.ReadKey();
  }
}
8
10
0

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
8
10