lib/kana_roman.ex

defmodule KanaRoman do
  @moduledoc """
  カタカナをローマ字表記に変換するモジュール。

  cf. https://support.microsoft.com/ja-jp/help/883232
  """

  @triple_katakana_map %{
    "キュウ" => "kyu",
    "キョウ" => "kyo",
    "シュウ" => "shu",
    "ショウ" => "sho",
    "チュウ" => "chu",
    "チョウ" => "cho",
    "ニュウ" => "nyu",
    "ニョウ" => "nyo",
    "ヒュウ" => "hyu",
    "ヒョウ" => "hyo",
    "ミュウ" => "myu",
    "ミョウ" => "myo",
    "リュウ" => "ryu",
    "リョウ" => "ryo"
  }

  @double_katakana_map %{
    "キャ" => "kya",
    "キィ" => "kyi",
    "キュ" => "kyu",
    "キェ" => "kye",
    "キョ" => "kyo",
    "クャ" => "qya",
    "クュ" => "qyu",
    "クョ" => "qyo",
    "クヮ" => "qwa",
    "クィ" => "qwi",
    "クゥ" => "qwu",
    "クェ" => "qwe",
    "クォ" => "qwo",
    "シャ" => "sha",
    "シィ" => "syi",
    "シュ" => "shu",
    "シェ" => "she",
    "ショ" => "sho",
    "スァ" => "swa",
    "スィ" => "swi",
    "スゥ" => "swu",
    "スェ" => "swe",
    "スォ" => "swo",
    "チャ" => "cha",
    "チィ" => "cyi",
    "チュ" => "chu",
    "チェ" => "che",
    "チョ" => "cho",
    "ツァ" => "tsa",
    "ツィ" => "tsi",
    "ツェ" => "tse",
    "ツォ" => "tso",
    "テャ" => "tha",
    "ティ" => "thi",
    "テュ" => "thu",
    "テェ" => "the",
    "テョ" => "tho",
    "トァ" => "twa",
    "トィ" => "twi",
    "トゥ" => "twu",
    "トェ" => "twe",
    "トォ" => "two",
    "ニャ" => "nya",
    "ニィ" => "nyi",
    "ニュ" => "nyu",
    "ニェ" => "nye",
    "ニョ" => "nyo",
    "ヒャ" => "hya",
    "ヒィ" => "hyi",
    "ヒュ" => "hyu",
    "ヒェ" => "hye",
    "ヒョ" => "hyo",
    "ファ" => "fa",
    "フィ" => "fi",
    "フェ" => "fe",
    "フォ" => "fo",
    "ミャ" => "mya",
    "ミィ" => "myi",
    "ミュ" => "myu",
    "ミェ" => "mye",
    "ミョ" => "myo",
    "リャ" => "rya",
    "リィ" => "ryi",
    "リュ" => "ryu",
    "リェ" => "rye",
    "リョ" => "ryo",
    "ギャ" => "gya",
    "ギィ" => "gyi",
    "ギュ" => "gyu",
    "ギェ" => "gye",
    "ギョ" => "gyo",
    "グァ" => "gwa",
    "グィ" => "gwi",
    "グゥ" => "gwu",
    "グェ" => "gwe",
    "グォ" => "gwo",
    "ジャ" => "ja",
    "ジィ" => "jyi",
    "ジュ" => "ju",
    "ジェ" => "je",
    "ジョ" => "jo",
    "ヂャ" => "dya",
    "ヂィ" => "dyi",
    "ヂュ" => "dyu",
    "ヂェ" => "dye",
    "ヂョ" => "dyo",
    "デャ" => "dha",
    "ディ" => "dhi",
    "デュ" => "dhu",
    "デェ" => "dhe",
    "デョ" => "dho",
    "ドァ" => "dwa",
    "ドィ" => "dwi",
    "ドゥ" => "dwu",
    "ドェ" => "dwe",
    "ドォ" => "dwo",
    "ビャ" => "bya",
    "ビィ" => "byi",
    "ビュ" => "byu",
    "ビェ" => "bye",
    "ビョ" => "byo",
    "ピャ" => "pya",
    "ピィ" => "pyi",
    "ピュ" => "pyu",
    "ピェ" => "pye",
    "ピョ" => "pyo",
    "オウ" => "o",
    "クウ" => "ku",
    "コウ" => "ko",
    "スウ" => "su",
    "ソウ" => "so",
    "ツウ" => "tsu",
    "トウ" => "to",
    "ヌウ" => "nu",
    "ノウ" => "no",
    "フウ" => "fu",
    "ホウ" => "ho",
    "ムウ" => "mu",
    "モウ" => "mo",
    "ユウ" => "yu",
    "ヨウ" => "yo",
    "ルウ" => "ru",
    "ロウ" => "ro",
    "グウ" => "gu",
    "ゴウ" => "go",
    "ズウ" => "zu",
    "ゾウ" => "zo",
    "ヅウ" => "du",
    "ドウ" => "do",
    "ブウ" => "bu",
    "ボウ" => "bo",
    "プウ" => "pu",
    "ポウ" => "po",
    "ウァ" => "uha",
    "ウィ" => "wi",
    "ウェ" => "we",
    "ウォ" => "who",
    "ヴァ" => "va",
    "ヴィ" => "vi",
    "ヴェ" => "ve",
    "ヴォ" => "vo"
  }

  @single_katakana_map %{
    "ア" => "a",
    "イ" => "i",
    "ウ" => "u",
    "エ" => "e",
    "オ" => "o",
    "カ" => "ka",
    "キ" => "ki",
    "ク" => "ku",
    "ケ" => "ke",
    "コ" => "ko",
    "サ" => "sa",
    "シ" => "shi",
    "ス" => "su",
    "セ" => "se",
    "ソ" => "so",
    "タ" => "ta",
    "チ" => "chi",
    "ツ" => "tsu",
    "テ" => "te",
    "ト" => "to",
    "ナ" => "na",
    "ニ" => "ni",
    "ヌ" => "nu",
    "ネ" => "ne",
    "ノ" => "no",
    "ハ" => "ha",
    "ヒ" => "hi",
    "フ" => "fu",
    "ヘ" => "he",
    "ホ" => "ho",
    "マ" => "ma",
    "ミ" => "mi",
    "ム" => "mu",
    "メ" => "me",
    "モ" => "mo",
    "ヤ" => "ya",
    "ユ" => "yu",
    "ヨ" => "yo",
    "ラ" => "ra",
    "リ" => "ri",
    "ル" => "ru",
    "レ" => "re",
    "ロ" => "ro",
    "ワ" => "wa",
    "ヲ" => "wo",
    "ン" => "n",
    "ガ" => "ga",
    "ギ" => "gi",
    "グ" => "gu",
    "ゲ" => "ge",
    "ゴ" => "go",
    "ザ" => "za",
    "ジ" => "ji",
    "ズ" => "zu",
    "ゼ" => "ze",
    "ゾ" => "zo",
    "ダ" => "da",
    "ヂ" => "di",
    "ヅ" => "zu",
    "デ" => "de",
    "ド" => "do",
    "バ" => "ba",
    "ビ" => "bi",
    "ブ" => "bu",
    "ベ" => "be",
    "ボ" => "bo",
    "パ" => "pa",
    "ピ" => "pi",
    "プ" => "pu",
    "ペ" => "pe",
    "ポ" => "po",
    "ー" => "",
    "ヱ" => "we",
    "ヰ" => "wi",
    "ヵ" => "xka",
    "ヶ" => "xke",
    "ヴ" => "vu",
    "ァ" => "xa",
    "ィ" => "xi",
    "ゥ" => "xu",
    "ェ" => "xe",
    "ォ" => "xo",
    "ャ" => "xya",
    "ュ" => "xyu",
    "ョ" => "xyo"
  }

  @doc """
  カタカナをローマ字表記に変換する。

  ## Examples

      iex> "アイウエオ" |> KanaRoman.to_roman()
      {:ok, "aiueo"}

  """
  # 促音は次のアルファベットを重ねる必要があるのでオプションを渡す
  def to_roman("ッ" <> rest, acc, false), do: to_roman(rest, acc, true)

  [@triple_katakana_map, @double_katakana_map, @single_katakana_map]
  |> Enum.each(fn kmap ->
    kmap
    |> Enum.each(fn {jstr, roman} ->
      def to_roman(unquote(jstr) <> rest, acc, sokuon) do
        if sokuon,
          do: to_roman(rest, acc <> String.at(unquote(roman), 0) <> unquote(roman), false),
          else: to_roman(rest, acc <> unquote(roman), false)
      end
    end)
  end)

  def to_roman(jstr), do: to_roman(jstr, "", false)
  def to_roman("", acc, _), do: {:ok, acc}

  def to_roman(unknown, acc, _),
    do:
      {:error, :undefined_kana_roman_map,
       "Undefined Katakana to Roman conversion: #{acc} _#{unknown}_"}
end