Current section

Files

Jump to
basehangul lib basehangul.ex
Raw

lib/basehangul.ex

defmodule BaseHangul do
@moduledoc ~S"""
This module provides BaseHangul encoding/decoding functionalities.
Visit the [GitHub Page](https://github.com/dalgona/basehangul) for more information.
"""
use Bitwise
@padchr [0xc8, 0xe5]
@doc ~S"""
Encodes `input` to a BaseHangul string. `input` can be a string or a binary.
Returns a BaseHangul-encoded string on success.
## Examples
iex> BaseHangul.encode("Hello")
"낏뗐맸굉"
iex> BaseHangul.encode(<<1, 2, 3, 4, 5>>)
"갈걍꺅갉"
"""
def encode(input) when is_binary(input) do
{:ok, sio} = StringIO.open input
Stream.map(IO.binstream(sio, 5), &(encunit &1)) |> Enum.join("")
end
@doc ~S"""
Decodes BaseHangul-encoded string `input`.
Returns the decoded result as a string or a binary.
## Examples
iex> BaseHangul.decode("낏뗐맸굉")
"Hello"
iex> BaseHangul.decode("갈걍꺅갉")
<<1, 2, 3, 4, 5>>
## Notes
This function expects a UTF-8 encoded text.
Passing an invalid BaseHangul-encoded string as an input will result in an
undefined behavior.
"""
def decode(input) when is_binary(input) do
{:ok, sio} = StringIO.open input
Stream.map(IO.binstream(sio, 12), &(decunit &1)) |> Enum.join("")
end
@doc ~S"""
Takes data from `indev` IO device and writes BaseHangul-encoded string
to `outdev` IO device.
Returns `:ok` on success.
## Examples
iex> {:ok, file} = File.open("some_file", [:read])
{:ok, #PID<0.XXX.0>}
iex> BaseHangul.encode(file, :stdio)
넥라넒굔덥램꼬먈눠멂불뮐궂뚠붉걍닐묩묻굡궂릅는놨궂몹듕낍
# ... more ...
놨궂몹듕낍닥듈럇낍뉴름댜둥:ok
"""
def encode(indev, outdev \\ Process.group_leader()),
do: Enum.each IO.binstream(indev, 5), &(IO.write(outdev, encunit &1))
@doc ~S"""
Takes BaseHangul-encoded data from `indev` IO device and writes decoded data
to `outdev` IO device.
Returns `:ok` on success.
## Examples
iex> {:ok, file} = File.open("some_file", [:read])
{:ok, #PID<0.XXX.0>}
iex> BaseHangul.decode(file, :stdio)
This is an encoded string.:ok
## Notes
This function expects a UTF-8 encoded text.
Passing an invalid BaseHangul-encoded string as an input will result in an
undefined behavior.
"""
def decode(indev, outdev \\ Process.group_leader()),
do: Enum.each IO.binstream(indev, 12), &(IO.write(outdev, decunit &1))
defp encunit(x), do: :iconv.convert("euc-kr", "utf-8", x |> repack_8to10 |> to_euclist([]))
defp decunit(x), do: :iconv.convert("utf-8", "euc-kr", x) |> to_ordlist([]) |> repack_10to8
defp repack_8to10(bin) when byte_size(bin) <= 5 do
sz = byte_size bin
zpad = 8 * (5 - sz)
<<bignum::40>> = bin <> <<0::size(zpad)>>
{(bignum |> repack_8to10_rev([]) |> Enum.reverse), sz}
end
defp to_euclist({list, sz}, out) when length(list) > 0 do
[h|t] = list
case length(list) do
4 -> to_euclist({t, sz}, out ++ get_euc(h))
1 ->
out ++ (if h == 0 and sz < 4 do @padchr
else (if sz == 4, do: get_euc(bor(h >>> 8, 1024)), else: get_euc(h)) end)
_ ->
to_euclist({t, sz}, out ++ (
if h == 0 and sz <= (4 - length(list)) do @padchr else get_euc(h) end
))
end
end
defp to_ordlist(<<>>, out), do: out
defp to_ordlist(eucstr, out) do
<<n1, n2>> <> rest = eucstr
to_ordlist(rest, out ++ (if [n1, n2] == @padchr do [false] else [get_ord(n1, n2)] end))
end
defp repack_10to8(ordlist) do
tmp = Enum.map ordlist, &(if &1 == false, do: 0, else: &1)
sz = Enum.find_index ordlist, &(&1==false)
last = List.last tmp
case sz do
x when is_number(x) or (is_nil(x) and last < 1024) ->
sz = sz || 5
_ ->
sz = 4
tmp = List.update_at tmp, 3, &((&1 - 1024) <<< 8)
end
bigint = Enum.reduce(tmp, 0, fn(x, a) -> (a <<< 10) + x end)
binary_part <<bigint::40>>, 0, sz
end
defp repack_8to10_rev(0, list) when length(list) == 4, do: list
defp repack_8to10_rev(n, list), do: repack_8to10_rev(n >>> 10, list ++ [n &&& 0x3FF])
defp get_euc(ord), do: [0xb0 + div(ord, 94), 0xa1 + rem(ord, 94)]
defp get_ord(n1, n2), do: (n1 - 0xb0) * 94 + (n2 - 0xa1)
end