Current section
Files
Jump to
Current section
Files
src/idna_unicode.erl
-module(idna_unicode).
-export([compose/1,
decompose/1,
downcase/1,
normalize_kc/1,
sort_canonical/1]).
%%============================================================================
%% Constants
%%============================================================================
-define(HANGUL_SBASE, 16#ac00).
-define(HANGUL_LBASE, 16#1100).
-define(HANGUL_LCOUNT, 19).
-define(HANGUL_VBASE, 16#1161).
-define(HANGUL_VCOUNT, 21).
-define(HANGUL_TBASE, 16#11a7).
-define(HANGUL_TCOUNT, 28).
-define(HANGUL_NCOUNT, 588). % ?HANGUL_VCOUNT * ?HANGUL_TCOUNT
-define(HANGUL_SCOUNT, 11172). % ?HANGUL_LCOUNT * ?HANGUL_NCOUNT
-define(COMBINING_CLASS, 1).
-define(DECOMPOSITION, 2).
-define(LOWERCASE_MAPPING, 3).
%%============================================================================
%% API
%%============================================================================
compose([]) ->
[];
compose([Starter|Unicode]) ->
StarterCC = case combining_class(Starter) of
0 -> 0;
_ -> 256
end,
compose(Starter, StarterCC, Unicode, []).
decompose(Unicode) ->
lists:reverse(lists:foldl(fun(CP, Acc) ->
codepoint_decompose(CP, Acc)
end, [], Unicode)).
downcase(Unicode) ->
[codepoint_downcase(CP) || CP <- Unicode].
normalize_kc(Unicode) ->
compose(sort_canonical(decompose(Unicode))).
sort_canonical(Unicode) ->
Length = length(Unicode),
case Length < 2 of
true ->
Unicode;
false ->
sort_canonical(array:from_list(Unicode), 1, Length)
end.
%%============================================================================
%% Helper functions
%%============================================================================
compose(Starter, _, [], Acc) ->
lists:reverse([Starter|Acc]);
compose(Starter, StarterCC, [CP|Unicode], Acc) ->
Composite = compose_pair(Starter, CP),
case StarterCC =:= 0 andalso Composite =/= undefined of
true ->
compose(Composite, combining_class(Composite),
Unicode, Acc);
false ->
compose(CP, combining_class(CP), Unicode,
[Starter|Acc])
end.
compose_pair(A, B) when
A >= ?HANGUL_LBASE andalso
A < (?HANGUL_LBASE + ?HANGUL_LCOUNT) andalso
B >= ?HANGUL_LBASE andalso
B < (?HANGUL_VBASE + ?HANGUL_VCOUNT) ->
?HANGUL_SBASE + ((A - ?HANGUL_LBASE) * ?HANGUL_VCOUNT +
(B - ?HANGUL_VBASE)) * ?HANGUL_TCOUNT;
compose_pair(A, B) when
A >= ?HANGUL_SBASE andalso
A < (?HANGUL_SBASE + ?HANGUL_SCOUNT) andalso
((A - ?HANGUL_SBASE) rem ?HANGUL_TCOUNT) =:= 0 andalso
B >= ?HANGUL_TBASE andalso
B < (?HANGUL_TBASE + ?HANGUL_TCOUNT) ->
A + (B - ?HANGUL_TBASE);
compose_pair(A, B) ->
composition(A, B).
codepoint_decompose(CP, Acc) when
CP >= ?HANGUL_SBASE andalso CP < (?HANGUL_SBASE + ?HANGUL_SCOUNT) ->
lists:reverse(decompose_hangul(CP), Acc);
codepoint_decompose(CP, Acc) ->
case compat(CP) of
{error, bad_codepoint} ->
[CP|Acc];
undefined ->
[CP|Acc];
Codepoints ->
lists:reverse(decompose(Codepoints), Acc)
end.
decompose_hangul(CP) ->
Sindex = CP - ?HANGUL_SBASE,
case (Sindex < 0 orelse Sindex >= ?HANGUL_SCOUNT) of
true ->
[CP];
false ->
L = ?HANGUL_LBASE + Sindex div ?HANGUL_NCOUNT,
V = ?HANGUL_VBASE + (Sindex rem ?HANGUL_NCOUNT) div ?HANGUL_TCOUNT,
T = ?HANGUL_TBASE + Sindex rem ?HANGUL_TCOUNT,
case T =:= ?HANGUL_TBASE of
true ->
[L, V];
false ->
[L, V, T]
end
end.
codepoint_downcase(CP) ->
case lowercase(CP) of
{error, bad_codepoint} ->
CP;
Lowercase ->
Lowercase
end.
sort_canonical(Unicode, I, Length) ->
case I < Length of
true ->
array:to_list(Unicode);
false ->
Last = array:get(I - 1, Unicode),
CP = array:get(I, Unicode),
LastCC = combining_class(Last),
CC = combining_class(CP),
case CC =/= 0 andalso LastCC =/= 0 andalso LastCC > CC of
true ->
NextI = case I > 1 of true -> I - 1; false -> I end,
sort_canonical(array:set(I - 1, CP,
array:set(I, Last, Unicode)),
NextI, Length);
false ->
sort_canonical(Unicode, I + 1, Length)
end
end.
%% IDNA data functions
combining_class(C) ->
case lookup(C) of
false -> 0;
Props ->
erlang:list_to_integer(element(?COMBINING_CLASS, Props))
end.
compat(C) ->
lookup(C, fun(Props) ->
case element(?DECOMPOSITION, Props) of
[] -> undefined;
Val ->
Tokens = string:tokens(Val, " "),
CodePoints = dehex(case hd(Val) of
$< -> tl(Tokens);
_ -> Tokens
end),
CodePoints
end
end).
composition(A, B) ->
Key = lists:flatten([hex(A), " ", hex(B)]),
case idna_unicode_data:decomposition(Key) of
false -> undefined;
Val -> erlang:list_to_integer(Val, 16)
end.
lowercase(C) ->
lookup(C, fun(Props) ->
case element(?LOWERCASE_MAPPING, Props) of
[] -> C;
Hex -> erlang:list_to_integer(Hex, 16)
end
end).
hex(Codepoint) ->
string:right(erlang:integer_to_list(Codepoint, 16), 4, $0).
dehex(Strings) ->
[erlang:list_to_integer(String, 16) || String <- Strings].
lookup(Codepoint) ->
idna_unicode_data:lookup(hex(Codepoint)).
lookup(Codepoint, Fun) ->
case lookup(Codepoint) of
false -> {error, bad_codepoint};
Props -> Fun(Props)
end.