Packages

Erlang/Elixir XMPP parsing and serialization library

Current section

Files

Jump to
contus_xmpp src xdata_codec.erl
Raw

src/xdata_codec.erl

%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @doc
%%%
%%% @end
%%%
%%%
%%% Copyright (C) 2002-2026 ProcessOne, SARL. All Rights Reserved.
%%%
%%% Licensed under the Apache License, Version 2.0 (the "License");
%%% you may not use this file except in compliance with the License.
%%% You may obtain a copy of the License at
%%%
%%% http://www.apache.org/licenses/LICENSE-2.0
%%%
%%% Unless required by applicable law or agreed to in writing, software
%%% distributed under the License is distributed on an "AS IS" BASIS,
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%%% See the License for the specific language governing permissions and
%%% limitations under the License.
%%%
%%%-------------------------------------------------------------------
-module(xdata_codec).
%% API
-export([compile/1, compile/2]).
-export([dec_int/1, dec_int/3, dec_enum/2, dec_bool/1, not_empty/1,
dec_enum_int/2, dec_enum_int/4, enc_int/1, enc_enum/1,
enc_bool/1, enc_enum_int/1, dec_ip/1, enc_ip/1,
format_error/1, io_format_error/1]).
-include("xmpp.hrl").
-type mfargs() :: {atom(), atom(), list()} | {atom(), list()}.
-record(state, {mod_name :: atom(),
file_name :: string(),
erl = "" :: string(),
hrl = "" :: string(),
dir = "" :: string(),
xmlns = [] :: [string()],
doc = <<>> :: binary(),
erl_dir = "" :: string(),
hrl_dir = "" :: string(),
prefix = [] :: [binary()],
dec_mfas = [] :: [{binary(), mfargs()}],
enc_mfas = [] :: [{binary(), mfargs()}],
specs = [] :: [{binary(), string()}],
ignore_unknown = false :: boolean(),
required = [] :: [{binary(), boolean()} | binary()],
defaults = [] :: [{binary(), any()}]}).
-define(is_multi_type(T),
((T == 'list-multi') or (T == 'jid-multi') or (T == 'text-multi'))).
-define(is_list_type(T),
((T == 'list-single') or (T == 'list-multi'))).
%%%===================================================================
%%% API
%%%===================================================================
compile(Path) ->
compile(Path, []).
compile(Path, Opts) ->
case filelib:is_dir(Path) of
true ->
filelib:fold_files(
Path, ".*.xdata", false,
fun(File, ok) ->
compile_file(File, Opts);
(_, Err) ->
Err
end, ok);
false ->
compile_file(Path, Opts)
end.
compile_file(Path, Opts) ->
try
{ok, _} = application:ensure_all_started(fast_xml),
DirName = filename:dirname(Path),
FileName = filename:basename(Path),
RootName = filename:rootname(FileName),
ConfigPath = filename:join(DirName, RootName) ++ ".cfg",
ModName = list_to_atom(RootName),
{ok, Data} = file:read_file(Path),
Config = case file:consult(ConfigPath) of
{ok, Terms} -> lists:flatten(Terms);
{error, enoent} -> []
end,
State = #state{mod_name = ModName,
file_name = FileName,
erl = filename:rootname(FileName) ++ ".erl",
hrl = filename:rootname(FileName) ++ ".hrl",
dir = DirName,
prefix = proplists:get_all_values(prefix, Config),
erl_dir = proplists:get_value(erl_dir, Opts, DirName),
hrl_dir = proplists:get_value(hrl_dir, Opts, DirName),
dec_mfas = proplists:get_value(decode, Config, []),
enc_mfas = proplists:get_value(encode, Config, []),
specs = proplists:get_value(specs, Config, []),
ignore_unknown = proplists:get_bool(ignore_unknown, Config),
required = proplists:get_value(required, Config, []),
defaults = proplists:get_value(defaults, Config, [])},
#xmlel{} = El = fxml_stream:parse_element(Data),
ok = compile_element(normalize(El), State),
io:format("Compiled ~s~n", [Path])
catch _:{badmatch, Err} ->
io:format(standard_error, "Failed to compile ~s: ~p~n",
[Path, Err]),
Err
end.
emit_raw(Str) ->
put(outbuf, get(outbuf) ++ Str).
emit(Format) ->
emit(Format, []).
emit(Format, Args) ->
put(outbuf, get(outbuf) ++ io_lib:format(Format, Args)).
dec_int(Val) ->
dec_int(Val, infinity, infinity).
dec_int(Val, Min, Max) ->
case erlang:binary_to_integer(Val) of
Int when Int =< Max, Min == infinity ->
Int;
Int when Int =< Max, Int >= Min ->
Int
end.
enc_int(Int) ->
integer_to_binary(Int).
dec_enum(Val, Enums) ->
AtomVal = erlang:binary_to_existing_atom(Val, utf8),
case lists:member(AtomVal, Enums) of
true ->
AtomVal
end.
enc_enum(Atom) ->
erlang:atom_to_binary(Atom, utf8).
dec_enum_int(Val, Enums) ->
try dec_int(Val)
catch _:_ -> dec_enum(Val, Enums)
end.
dec_enum_int(Val, Enums, Min, Max) ->
try dec_int(Val, Min, Max)
catch _:_ -> dec_enum(Val, Enums)
end.
enc_enum_int(Int) when is_integer(Int) ->
enc_int(Int);
enc_enum_int(Atom) ->
enc_enum(Atom).
dec_bool(<<"1">>) -> true;
dec_bool(<<"0">>) -> false;
dec_bool(<<"true">>) -> true;
dec_bool(<<"false">>) -> false.
enc_bool(true) -> <<"1">>;
enc_bool(false) -> <<"0">>.
not_empty(<<_, _/binary>> = Val) ->
Val.
dec_ip(Val) ->
{ok, Addr} = inet_parse:address(binary_to_list(Val)),
Addr.
enc_ip({0,0,0,0,0,16#ffff,A,B}) ->
enc_ip({(A bsr 8) band 16#ff, A band 16#ff,
(B bsr 8) band 16#ff, B band 16#ff});
enc_ip(Addr) ->
list_to_binary(inet_parse:ntoa(Addr)).
format_error({form_type_mismatch, Type}) ->
<<"FORM_TYPE doesn't match '", Type/binary, "'">>;
format_error({bad_var_value, Var, Type}) ->
<<"Bad value of field '", Var/binary, "' of type '", Type/binary, "'">>;
format_error({missing_value, Var, Type}) ->
<<"Missing value of field '", Var/binary, "' of type '", Type/binary, "'">>;
format_error({too_many_values, Var, Type}) ->
<<"Too many values for field '", Var/binary, "' of type '", Type/binary, "'">>;
format_error({unknown_var, Var, Type}) ->
<<"Unknown field '", Var/binary, "' of type '", Type/binary, "'">>;
format_error({missing_required_var, Var, Type}) ->
<<"Missing required field '", Var/binary, "' of type '", Type/binary, "'">>.
io_format_error({form_type_mismatch, Type}) ->
{<<"FORM_TYPE doesn't match '~s'">>, [Type]};
io_format_error({bad_var_value, Var, Type}) ->
{<<"Bad value of field '~s' of type '~s'">>, [Var, Type]};
io_format_error({missing_value, Var, Type}) ->
{<<"Missing value of field '~s' of type '~s'">>, [Var, Type]};
io_format_error({too_many_values, Var, Type}) ->
{<<"Too many values for field '~s' of type '~s'">>, [Var, Type]};
io_format_error({unknown_var, Var, Type}) ->
{<<"Unknown field '~s' of type '~s'">>, [Var, Type]};
io_format_error({missing_required_var, Var, Type}) ->
{<<"Missing required field '~s' of type '~s'">>, [Var, Type]}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
compile_element(#xmlel{name = <<"form_type">>, children = Els} = Form,
#state{erl = OutErl, erl_dir = ErlDir,
hrl = OutHrl, hrl_dir = HrlDir} = State0) ->
try
Names = lists:flatmap(
fun(#xmlel{name = <<"name">>} = Tag) ->
case fxml:get_tag_cdata(Tag) of
<<"">> -> [];
N -> [binary_to_list(N)]
end;
(_) -> []
end, Els),
Doc = fxml:get_subtag_cdata(Form, <<"doc">>),
X = #xmlel{name = <<"x">>,
attrs = [{<<"type">>, <<"form">>},
{<<"xmlns">>, <<"jabber:x:data">>}],
children = Els},
State = State0#state{xmlns = Names, doc = Doc},
#xdata{fields = Fs} = xmpp_codec:decode(X),
put(outbuf, []),
mk_header(State),
mk_specs(Fs, State),
mk_aux_funs(),
mk_top_decoder(Fs, State),
mk_top_encoder(Fs, State),
mk_decoder(Fs, State),
mk_encoders(Fs, State),
ErlData = get(outbuf),
ok = file:write_file(filename:join(ErlDir, OutErl), ErlData),
ok = erl_tidy:file(filename:join(ErlDir, OutErl),
[{backups, false}, {keep_unused, true}]),
put(outbuf, []),
mk_type_definitions(Fs, State),
HrlData = get(outbuf),
ok = file:write_file(filename:join(HrlDir, OutHrl), HrlData)
catch _:{badmatch, Err} ->
Err
end.
mk_aux_funs() ->
case get_abstract_code_from_myself() of
{ok, AbsCode} ->
AST = lists:filter(
fun(T) ->
try erl_syntax_lib:analyze_function(T) of
{format_error, 1} -> true;
{io_format_error, 1} -> true;
FA -> lists:member(FA, codec_funs())
catch _:_ ->
false
end
end, AbsCode),
emit_raw(erl_prettypr:format(erl_syntax:form_list(AST)) ++ io_lib:nl());
error ->
erlang:error({no_abstract_code_found, ?MODULE})
end.
get_abstract_code_from_myself() ->
{file, File} = code:is_loaded(?MODULE),
case beam_lib:chunks(File, [abstract_code]) of
{ok, {_, List}} ->
case lists:keyfind(abstract_code, 1, List) of
{abstract_code, {raw_abstract_v1, Abstr}} ->
{ok, Abstr};
_ ->
error
end;
_ ->
error
end.
mk_comment_header(#state{file_name = Source, xmlns = NS, doc = Doc}) ->
emit("%% Created automatically by xdata generator (xdata_codec.erl)~n"
"%% Source: ~s~n"
"%% Form type: ~s~n",
[Source, string:join(NS, ", ")]),
if Doc /= <<>> -> emit("%% Document: ~s~n~n", [Doc]);
true -> emit("~n")
end.
mk_header(#state{mod_name = Mod, hrl = Include, xmlns = NS} = State) ->
mk_comment_header(State),
emit("~n-module(~s).~n", [Mod]),
emit("-compile({nowarn_unused_function, ~p}).~n", [codec_funs()]),
emit("-compile(nowarn_unused_vars).~n"),
emit("-dialyzer({nowarn_function, dec_int/3}).~n"),
case NS of
[_] -> emit("-export([encode/1, encode/2, encode/3]).~n");
_ -> emit("-export([encode/2, encode/3, encode/4]).~n")
end,
emit("-export([decode/1, decode/2, decode/3, format_error/1, io_format_error/1]).~n"),
emit("-include(\"xmpp_codec.hrl\").~n"),
emit("-include(\"~s\").~n", [Include]),
emit("-export_type([property/0, result/0, form/0, error_reason/0]).~n"),
emit("-define(T(S), <<S>>).~n").
mk_specs(Fs, State) ->
emit("-spec format_error(error_reason()) -> binary().~n"),
emit("-spec io_format_error(error_reason()) -> {binary(), [binary()]}.~n"),
emit("-spec decode([xdata_field()]) -> result().~n"),
emit("-spec decode([xdata_field()], [binary(), ...]) -> result().~n"),
emit("-spec decode([xdata_field()], [binary(), ...], [binary()]) -> result().~n"),
emit("-spec decode([xdata_field()], [binary(), ...], [binary()], result()) -> result().~n"),
emit("-spec do_decode([xdata_field()], binary(), [binary()], result()) -> result().~n"),
BinType = case State#state.xmlns of
[_] -> "";
_ -> ", binary()"
end,
Required = [io_lib:format("'~s'", [var_to_rec_field(Var, State)])
|| #xdata_field{var = Var} <- Fs],
emit("-spec encode(form()~s) -> [xdata_field()].~n", [BinType]),
emit("-spec encode(form()~s, binary()) -> [xdata_field()].~n", [BinType]),
emit("-spec encode(form()~s, binary(), [~s]) -> [xdata_field()].~n",
[BinType, string:join(Required, " | ")]).
mk_type_definitions(Fs, State) ->
mk_comment_header(State),
lists:foreach(
fun(#xdata_field{var = Var} = F) ->
Spec = get_typespec(F, State),
case is_complex_type(Spec) of
true ->
emit("-type '~s'() :: ~s.~n",
[var_to_rec_field(Var, State), Spec]);
false ->
ok
end
end, Fs),
emit("~n-type property() :: "),
Fields = lists:map(
fun(#xdata_field{var = Var} = F) ->
RecField = var_to_rec_field(Var, State),
[io_lib:format("{'~s', ~s}",
[RecField, mk_typespec(F, State)])]
end, Fs),
emit(string:join(Fields, " |~n ") ++ ".~n"),
emit("-type result() :: [property()].~n~n"),
Options = lists:flatmap(
fun(#xdata_field{type = T, var = Var} = F) ->
RecName = var_to_rec_field(Var, State),
Spec1 = mk_typespec(F, State),
Spec2 = case default(F, State) of
undefined -> Spec1 ++ " | undefined";
_ -> Spec1
end,
[io_lib:format("{'~s', ~s}", [RecName, Spec2])|
if ?is_list_type(T) ->
Spec3 = get_typespec(F, State),
Spec4 = case is_complex_type(Spec3) of
true ->
io_lib:format("'~s'()", [RecName]);
false ->
Spec3
end,
[io_lib:format(
"{'~s', ~s, options(~s)}",
[RecName, Spec2, Spec4])];
true ->
[]
end]
end, Fs),
case lists:any(fun(#xdata_field{type = T}) -> ?is_list_type(T) end, Fs) of
true -> emit("-type options(T) :: [{binary(), T}].~n");
false -> ok
end,
emit("-type form_property() ::~n "),
emit(string:join(Options, " |~n ") ++ ".~n"),
emit("-type form() :: [form_property() | xdata_field()].~n~n"),
emit("-type error_reason() :: {form_type_mismatch, binary()} |~n"
" {bad_var_value, binary(), binary()} |~n"
" {missing_required_var, binary(), binary()} |~n"
" {missing_value, binary(), binary()} |~n"
" {too_many_values, binary(), binary()} |~n"
" {unknown_var, binary(), binary()}.~n").
mk_top_decoder(Fs, State) ->
Required = [Var || #xdata_field{var = Var} <- Fs, is_required(Var, State)],
NSList = "[" ++ string:join(["<<\"" ++ NS ++ "\">>" || NS <- State#state.xmlns], ", ") ++ "]",
emit("decode(Fs) -> decode(Fs, ~s, ~p, []).~n",
[NSList, Required]),
emit("decode(Fs, XMLNSList) -> decode(Fs, XMLNSList, ~p, []).~n",
[Required]),
emit("decode(Fs, XMLNSList, Required) -> decode(Fs, XMLNSList, Required, []).~n"),
emit("decode(Fs, [_|_] = XMLNSList, Required, Acc) ->"
" case lists:keyfind(<<\"FORM_TYPE\">>, #xdata_field.var, Fs) of"
" false ->"
" do_decode(Fs, hd(XMLNSList), Required, Acc);"
" #xdata_field{values = [XMLNS]} ->"
" case lists:member(XMLNS, XMLNSList) of"
" true -> do_decode(Fs, XMLNS, Required, Acc);"
" false -> erlang:error({?MODULE, {form_type_mismatch, XMLNS}})"
" end"
" end.~n").
mk_top_encoder(Fs, State) ->
Required = "[" ++ string:join(
[io_lib:format("'~s'", [var_to_rec_field(Var, State)])
|| #xdata_field{var = Var} <- Fs, is_required(Var, State)],
", ") ++ "]",
Clauses = string:join(
lists:map(
fun(#xdata_field{var = Var, type = T}) when ?is_list_type(T) ->
Field = var_to_rec_field(Var, State),
io_lib:format(
"{'~s', Val} ->"
" ['encode_~s'(Val, default, Lang, lists:member('~s', Required))];"
"{'~s', Val, Opts} ->"
" ['encode_~s'(Val, Opts, Lang, lists:member('~s', Required))]",
[Field, Field, Field, Field, Field, Field]);
(#xdata_field{var = Var}) ->
Field = var_to_rec_field(Var, State),
io_lib:format(
"{'~s', Val} -> ['encode_~s'(Val, Lang, lists:member('~s', Required))]",
[Field, Field, Field])
end, Fs) ++ ["#xdata_field{} -> [Opt]"],
";"),
case State#state.xmlns of
[_] ->
emit("encode(Cfg) -> encode(Cfg, <<\"en\">>, ~s).~n", [Required]),
emit("encode(Cfg, Lang) -> encode(Cfg, Lang, ~s).~n", [Required]),
emit("encode(List, Lang, Required) ->");
_ ->
emit("encode(Cfg, XMLNS) -> encode(Cfg, XMLNS, <<\"en\">>, ~s).~n", [Required]),
emit("encode(Cfg, XMLNS, Lang) -> encode(Cfg, XMLNS, Lang, ~s).~n", [Required]),
emit("encode(List, XMLNS, Lang, Required) ->")
end,
XMLNS = case State#state.xmlns of
[NS] -> io_lib:format("<<~p>>", [NS]);
_ -> "XMLNS"
end,
emit(" Fs = [case Opt of ~s end || Opt <- List],"
" FormType = #xdata_field{var = <<\"FORM_TYPE\">>, type = hidden,"
" values = [~s]},"
" [FormType|lists:flatten(Fs)].~n",
[Clauses, XMLNS]).
mk_decoder([#xdata_field{var = Var, type = Type} = F|Fs], State) ->
ValVar = if ?is_multi_type(Type) -> "Values";
true -> "[Value]"
end,
DecFun = if ?is_multi_type(Type) ->
["[", mk_decoding_fun(F, State), " || Value <- Values]"];
true ->
mk_decoding_fun(F, State)
end,
DelRequired = io_lib:format("lists:delete(~p, Required)", [Var]),
if Type == 'jid-multi' ->
%% Psi work-around
emit("do_decode([#xdata_field{var = ~p, values = [<<>>]} = F|Fs],"
" XMLNS, Required, Acc) ->~n"
" %% Psi work-around~n"
" do_decode([F#xdata_field{var = ~p, values = []}|Fs],"
" XMLNS, Required, Acc);",
[Var, Var]);
true ->
ok
end,
emit("do_decode([#xdata_field{var = ~p, values = ~s}|Fs], XMLNS, Required, Acc) ->"
" try ~s of"
" Result -> do_decode(Fs, XMLNS, ~s, [{'~s', Result}|Acc])"
" catch _:_ ->"
" erlang:error({?MODULE, {bad_var_value, ~p, XMLNS}})"
" end;",
[Var, ValVar, DecFun, DelRequired,
var_to_rec_field(Var, State), Var]),
if not ?is_multi_type(Type) ->
emit("do_decode([#xdata_field{var = ~p, values = []} = F|Fs],"
" XMLNS, Required, Acc) ->"
" do_decode([F#xdata_field{var = ~p, values = [<<>>]}|Fs],"
" XMLNS, Required, Acc);",
[Var, Var]),
emit("do_decode([#xdata_field{var = ~p}|_], XMLNS, _, _) ->"
" erlang:error({?MODULE, {too_many_values, ~p, XMLNS}});",
[Var, Var]);
true ->
ok
end,
mk_decoder(Fs, State);
mk_decoder([], State) ->
if State#state.ignore_unknown ->
emit("do_decode([_|Fs], XMLNS, Required, Acc) ->"
" do_decode(Fs, XMLNS, Required, Acc);");
true ->
emit("do_decode([#xdata_field{var = Var}|Fs], XMLNS, Required, Acc) ->"
" if Var /= <<\"FORM_TYPE\">> ->"
" erlang:error({?MODULE, {unknown_var, Var, XMLNS}});"
" true ->"
" do_decode(Fs, XMLNS, Required, Acc)"
" end;")
end,
emit("do_decode([], XMLNS, [Var|_], _) ->"
" erlang:error({?MODULE, {missing_required_var, Var, XMLNS}});~n"),
emit("do_decode([], _, [], Acc) -> Acc.~n").
mk_encoders(Fs, State) ->
lists:foreach(
fun(#xdata_field{var = Var, desc = Desc,
label = Label, type = Type} = F) ->
Spec0 = mk_typespec(F, State),
Spec1 = case default(F, State) of
undefined -> Spec0 ++ " | undefined";
_ -> Spec0
end,
EncVals = mk_encoded_values(F, State),
EncOpts = mk_encoded_options(F, State),
FieldName = var_to_rec_field(Var, State),
DescStr = if Desc == <<>> -> "<<>>";
true -> io_lib:format("xmpp_tr:tr(Lang, ?T(\"~s\"))", [Desc])
end,
LabelStr = if Label == <<>> -> "<<>>";
true -> io_lib:format("xmpp_tr:tr(Lang, ?T(\"~s\"))", [Label])
end,
if ?is_list_type(Type) ->
Spec2 = get_typespec(F, State),
Spec3 = case is_complex_type(Spec2) of
true -> io_lib:format("'~s'()", [FieldName]);
false -> Spec2
end,
emit("-spec 'encode_~s'(~s, default | options(~s), binary(), boolean()) -> xdata_field().~n"
"'encode_~s'(Value, Options, Lang, IsRequired) ->",
[FieldName, Spec1, Spec3, FieldName]);
true ->
emit("-spec 'encode_~s'(~s, binary(), boolean()) -> xdata_field().~n"
"'encode_~s'(Value, Lang, IsRequired) ->",
[FieldName, Spec1, FieldName])
end,
emit(" Values = ~s,"
" Opts = ~s,"
" #xdata_field{var = ~p,"
" values = Values,"
" required = IsRequired,"
" type = ~p,"
" options = Opts,"
" desc = ~s,"
" label = ~s}.~n",
[EncVals, EncOpts, Var, Type, DescStr, LabelStr])
end, Fs).
mk_encoded_values(#xdata_field{var = Var, type = Type,
options = Options} = F, State) ->
EncFun =
case get_enc_fun(Var, Type, Options, State) of
{M, Fun, Args} ->
Mod = if M == undefined -> "";
true -> io_lib:format("~s:", [M])
end,
FArgs = [io_lib:format(", ~p", [A]) || A <- Args],
if ?is_multi_type(Type) ->
"[" ++ io_lib:format("~s~s(V~s)", [Mod, Fun, FArgs]) ++
" || V <- Value]";
true ->
"[" ++ io_lib:format("~s~s(Value~s)", [Mod, Fun, FArgs])
++ "]"
end;
undefined when ?is_multi_type(Type) ->
"Value";
undefined ->
"[Value]"
end,
io_lib:format(
"case Value of"
" ~p -> [];~n"
" Value -> ~s~n"
"end",
[default(F, State), EncFun]).
mk_encoded_options(#xdata_field{var = Var, type = Type,
options = Options}, State) ->
EncFun = case get_enc_fun(Var, Type, Options, State) of
{M, Fun, Args} ->
Mod = if M == undefined -> "";
true -> io_lib:format("~s:", [M])
end,
FArgs = [io_lib:format(", ~p", [A]) || A <- Args],
io_lib:format("~s~s(V~s)", [Mod, Fun, FArgs]);
undefined ->
"V"
end,
EncOpts = string:join(
[case L of
<<>> ->
io_lib:format("#xdata_option{value = ~p}", [V]);
_ ->
io_lib:format(
"#xdata_option{label = xmpp_tr:tr(Lang, ?T(\"~s\")), value = ~p}",
[L, V])
end || #xdata_option{label = L, value = V} <- Options],
","),
if ?is_list_type(Type) ->
io_lib:format(
"if Options == default ->"
" [~s];"
"true ->"
" [#xdata_option{label = xmpp_tr:tr(Lang, L), value = ~s}"
" || {L, V} <- Options]"
"end",
[EncOpts, EncFun]);
true ->
"[]"
end.
mk_decoding_fun(#xdata_field{var = Var, type = Type,
options = Options}, State) ->
case get_dec_fun(Var, Type, Options, State) of
{M, Fun, Args} ->
Mod = if M == undefined -> "";
true -> io_lib:format("~s:", [M])
end,
FArgs = [io_lib:format(", ~p", [A]) || A <- Args],
io_lib:format("~s~s(Value~s)", [Mod, Fun, FArgs]);
undefined ->
"Value"
end.
var_to_rec_field(Var, #state{prefix = [Prefix|T]} = State) ->
Size = size(Prefix),
case Var of
<<(Prefix):Size/binary, Rest/binary>> ->
binary_to_atom(Rest, utf8);
_ ->
var_to_rec_field(Var, State#state{prefix = T})
end;
var_to_rec_field(Var, #state{prefix = []}) ->
Var.
get_dec_fun(Var, Type, Options, State) ->
case lists:keyfind(Var, 1, State#state.dec_mfas) of
false when Type == 'list-multi'; Type == 'list-single' ->
if Options /= [] ->
Variants = [binary_to_atom(V, utf8)
|| #xdata_option{value = V} <- Options],
{undefined, dec_enum, [Variants]};
true ->
undefined
end;
false when Type == 'jid-multi'; Type == 'jid-single' ->
{jid, decode, []};
false when Type == boolean ->
{undefined, dec_bool, []};
false ->
undefined;
{Var, {M, F, A}} ->
{M, F, A};
{Var, {dec_bool, []}} ->
{undefined, dec_bool, []};
{Var, {dec_ip, []}} ->
{undefined, dec_ip, []};
{Var, {not_empty, []}} ->
{undefined, not_empty, []};
{Var, {dec_enum, [Variants]}} ->
{undefined, dec_enum, [Variants]};
{Var, {dec_int, Args}} ->
{undefined, dec_int, Args};
{Var, {dec_enum_int, Args}} ->
{undefined, dec_enum_int, Args}
end.
get_enc_fun(Var, Type, Options, State) ->
case get_dec_fun(Var, Type, Options, State) of
{undefined, dec_enum, _} ->
{undefined, enc_enum, []};
{undefined, dec_bool, _} ->
{undefined, enc_bool, []};
{undefined, dec_int, _} ->
{undefined, enc_int, []};
{undefined, dec_enum_int, _} ->
{undefined, enc_enum_int, []};
{undefined, dec_ip, _} ->
{undefined, enc_ip, []};
{jid, decode, []} ->
{jid, encode, []};
_ ->
case lists:keyfind(Var, 1, State#state.enc_mfas) of
false ->
undefined;
{Var, {jid, decode, []}} ->
{jid, encode, []};
{Var, {M, F, A}} ->
{M, F, A};
{Var, {enc_bool, []}} ->
{undefined, enc_bool, []};
{Var, {dec_enum, _}} ->
{undefined, enc_enum, []};
{Var, {enc_int, _}} ->
{undefined, enc_int, []};
{Var, {dec_enum_int, _}} ->
{undefined, enc_enum_int, []};
{Var, {dec_ip, _}} ->
{undefined, enc_ip, []}
end
end.
mk_typespec(#xdata_field{type = Type, var = Var} = Field, State) ->
Spec0 = get_typespec(Field, State),
Spec1 = case is_complex_type(Spec0) of
true ->
io_lib:format("'~s'()", [var_to_rec_field(Var, State)]);
false ->
Spec0
end,
if ?is_multi_type(Type) -> "[" ++ Spec1 ++ "]";
true -> Spec1
end.
get_typespec(#xdata_field{var = Var, type = Type, options = Options}, State) ->
case lists:keyfind(Var, 1, State#state.specs) of
false ->
case get_dec_fun(Var, Type, Options, State) of
{undefined, dec_enum, Args} ->
enum_spec(Args);
{undefined, dec_bool, _} ->
"boolean()";
{undefined, dec_ip, _} ->
"inet:ip_address()";
{jid, decode, _} ->
"jid:jid()";
{undefined, dec_int, Args} ->
int_spec(Args);
{undefined, dec_enum_int, [Variants|T]} ->
enum_spec([Variants]) ++ " | " ++ int_spec(T);
_ ->
"binary()"
end;
{Var, Spec} ->
Spec
end.
-spec is_complex_type(string()) -> boolean().
is_complex_type(Spec) ->
string:chr(Spec, $|) /= 0.
int_spec([]) ->
"integer()";
int_spec([From, To]) ->
if From /= infinity, To /= infinity ->
io_lib:format("~p..~p", [From, To]);
From > 0 ->
"pos_integer()";
From == 0 ->
"non_neg_integer()";
true ->
"integer()"
end.
enum_spec([Variants]) ->
string:join([atom_to_list(V) || V <- Variants], " | ").
is_required(Var, State) ->
lists:member(Var, State#state.required) orelse
proplists:get_bool(Var, State#state.required).
normalize(#xmlel{name = Name, attrs = Attrs, children = Els}) ->
#xmlel{name = Name,
attrs = [normalize(Attr) || Attr <- Attrs],
children = [normalize(El) || El <- Els]};
normalize({Key, Data}) ->
{Key, normalize(Data)};
normalize(Txt) when is_binary(Txt) ->
case re:split(Txt, "[\\s\\r\\n\\t]+", [trim, {return, list}]) of
[""|T] ->
list_to_binary(string:join(T, " "));
T ->
list_to_binary(string:join(T, " "))
end.
default(#xdata_field{type = Type}, _) when ?is_multi_type(Type) ->
[];
default(#xdata_field{var = Var, type = Type,
options = Options}, State) ->
case get_dec_fun(Var, Type, Options, State) of
undefined -> <<>>;
_MFA -> undefined
end.
codec_funs() ->
[{dec_int, 3},
{dec_int, 1},
{dec_enum, 2},
{dec_enum_int, 2},
{dec_enum_int, 4},
{enc_int, 1},
{enc_enum, 1},
{enc_enum_int, 1},
{not_empty, 1},
{dec_bool, 1},
{enc_bool, 1},
{dec_ip, 1},
{enc_ip, 1}].