lib/mojiex.ex

defmodule Mojiex do
  @moduledoc """
  Japanese strings - Wide/Half "Kana" charactors Conversion Library for Elixir lang.
  """

  @doc """
  Japanese strings - Wide/Half "Kana" charactors Conversion Library for Elixir lang

  ## option combination

  |atom  |kind          |   |atom |kind          |
  |------|--------------|---|-----|--------------|
  |:zk   |全角カタカナ  | ↔ |:hk  | 半角カタカナ |
  |:ze   |全角英数      | ↔ |:he  | 半角英数     |
  |:he   |ひらがな      | ↔ |:kk  | カタカナ     |
  |:zs   |全角SPACE     | ↔ |:hs  | 半角SPACE    |

  ## Examples

      iex> Mojiex.convert("ABCD 01234あいうアイウABCD 01234アイウ", {:hk, :zk})
      "ABCD 01234あいうアイウABCD 01234アイウ"

  """

  @zenhan_list [
    # 全角英数
    ze: [start: 0xFF01, end: 0xFF5E],
    # 半角英数
    he: [start: 0x0021, end: 0x007E],
    # ひらがな
    hg: [start: 0x3041, end: 0x3096],
    # カタカナ
    kk: [start: 0x30A1, end: 0x30F6],
    zs: [{" ", " "}],
    zk: [
      {"。", "。"},
      {"「", "「"},
      {"」", "」"},
      {"、", "、"},
      {"・", "・"},
      {"ヲ", "ヲ"},
      {"ァ", "ァ"},
      {"ィ", "ィ"},
      {"ゥ", "ゥ"},
      {"ェ", "ェ"},
      {"ォ", "ォ"},
      {"ャ", "ャ"},
      {"ュ", "ュ"},
      {"ョ", "ョ"},
      {"ッ", "ッ"},
      {"ー", "ー"},
      {"ア", "ア"},
      {"イ", "イ"},
      {"ウ", "ウ"},
      {"エ", "エ"},
      {"オ", "オ"},
      {"カ", "カ"},
      {"キ", "キ"},
      {"ク", "ク"},
      {"ケ", "ケ"},
      {"コ", "コ"},
      {"サ", "サ"},
      {"シ", "シ"},
      {"ス", "ス"},
      {"セ", "セ"},
      {"ソ", "ソ"},
      {"タ", "タ"},
      {"チ", "チ"},
      {"ツ", "ツ"},
      {"テ", "テ"},
      {"ト", "ト"},
      {"ナ", "ナ"},
      {"ニ", "ニ"},
      {"ヌ", "ヌ"},
      {"ネ", "ネ"},
      {"ノ", "ノ"},
      {"ハ", "ハ"},
      {"ヒ", "ヒ"},
      {"フ", "フ"},
      {"ヘ", "ヘ"},
      {"ホ", "ホ"},
      {"マ", "マ"},
      {"ミ", "ミ"},
      {"ム", "ム"},
      {"メ", "メ"},
      {"モ", "モ"},
      {"ヤ", "ヤ"},
      {"ユ", "ユ"},
      {"ヨ", "ヨ"},
      {"ラ", "ラ"},
      {"リ", "リ"},
      {"ル", "ル"},
      {"レ", "レ"},
      {"ロ", "ロ"},
      {"ワ", "ワ"},
      {"ン", "ン"},
      {"゛", "゙"},
      {"゜", "゚"},
      {"ヺ", "ヺ"},
      {"ヴ", "ヴ"},
      {"ガ", "ガ"},
      {"ギ", "ギ"},
      {"グ", "グ"},
      {"ゲ", "ゲ"},
      {"ゴ", "ゴ"},
      {"ザ", "ザ"},
      {"ジ", "ジ"},
      {"ズ", "ズ"},
      {"ゼ", "ゼ"},
      {"ゾ", "ゾ"},
      {"ダ", "ダ"},
      {"ヂ", "ヂ"},
      {"ヅ", "ヅ"},
      {"デ", "デ"},
      {"ド", "ド"},
      {"バ", "バ"},
      {"パ", "パ"},
      {"ビ", "ビ"},
      {"ピ", "ピ"},
      {"ブ", "ブ"},
      {"プ", "プ"},
      {"ベ", "ベ"},
      {"ペ", "ペ"},
      {"ボ", "ボ"},
      {"ポ", "ポ"},
      {"ヷ", "ヷ"}
    ]
  ]
  @type zh_pair :: {atom(), atom()}

  @spec convert(binary, zh_pair()) :: binary
  def convert(str, {:zk, :hk}), do: zh_map(&ch_zk_hk/1, str)

  def convert(str, {:hk, :zk}), do: zh_map(&ch_hk_zk/1, str)

  def convert(str, {:hs, :zs}), do: zh_map(&ch_hs_zs/1, str)

  def convert(str, {:zs, :hs}), do: zh_map(&ch_zs_hs/1, str)

  def convert(str, {:ze, :he} = {from, to}), do: conv_range(str, from, to)

  def convert(str, {:he, :ze} = {from, to}), do: conv_range(str, from, to)

  def convert(str, {:hg, :kk} = {from, to}), do: conv_range(str, from, to)

  def convert(str, {:kk, :hg} = {from, to}), do: conv_range(str, from, to)

  # def convert(str, _), do: str

  @spec conv_range(binary, atom(), atom()) :: binary
  defp conv_range(str, from_kind, to_kind) do
    dist = @zenhan_list[from_kind][:start] - @zenhan_list[to_kind][:start]
    start_ch = @zenhan_list[from_kind][:start]

    to_charlist(str)
    |> Enum.map(fn c ->
      if c >= start_ch && c <= @zenhan_list[from_kind][:end] do
        c - dist
      else
        c
      end
    end)
    |> to_string
  end

  @spec zh_map(fun(), binary) :: binary
  defp zh_map(map_fn, str) do
    to_charlist(str)
    |> Enum.map(&map_fn.(<<&1::utf8>>))
    |> to_string
  end

  @spec ch_zk_hk(binary) :: binary
  defp ch_zk_hk(code) do
    List.keyfind(@zenhan_list[:zk], code, 0, {nil, code}) |> elem(1)
  end

  @spec ch_hk_zk(binary) :: binary
  defp ch_hk_zk(code) do
    List.keyfind(@zenhan_list[:zk], code, 1, {code, nil}) |> elem(0)
  end

  @spec ch_zs_hs(binary) :: binary
  def ch_zs_hs(code) do
    List.keyfind(@zenhan_list[:zs], code, 0, {nil, code}) |> elem(1)
  end

  @spec ch_hs_zs(binary) :: binary
  defp ch_hs_zs(code) do
    List.keyfind(@zenhan_list[:zs], code, 1, {code, nil}) |> elem(0)
  end

  @spec list :: list()
  def list do
    @zenhan_list
  end
end