Current section
Files
Jump to
Current section
Files
src/erlsom_example_value.erl
-module(erlsom_example_value).
%% output code that makes an example value for a type, using an erlsom model as input.
%%
%% example:
%% #'p:Transaction'{
%% 'TransactionID' = "?",
%% 'SessionID' = "?",
%% 'CurrencyID' = "?",
%% 'Value' = "?"}
%%
%% In order to be able to embed the result it must be possible to specificy
%% indentation.
-include("erlsom_parse.hrl").
-export([from_model/2]).
-export([from_model/3]).
-export([test/1]).
-type options() :: [option()].
-type option() :: {indent, integer()} | {indent_level, integer()}.
-record(e_state, {
indent :: integer(),
level :: integer(),
choice_depth = 0 :: integer()
}).
test(File) ->
Options = [{include_any_attribs, false}],
% generate hrl file, store in test_example.hrl
erlsom:write_xsd_hrl_file(File, "test_example.hrl", Options),
{ok, Model} = erlsom:compile_xsd_file(File, Options),
% Pick a type from the model
#model{tps = [#type{nm = '_document', els=Elements} | _]} = Model,
[#el{alts = [#alt{tp = Type} | _]} | _] = Elements,
% generate an example value,
Example_value = from_model(Type, Model),
file:write_file("test_example.erl",
[test_header(), Example_value, ".\n"]),
%% See if it compiles
compile:file("test_example.erl").
test_header() ->
"-module(test_example).\n"
"-export([go/0]).\n"
"-include(\"test_example.hrl\").\n"
"go() -> \n".
-spec from_model(Type::atom(), erlsom:model()) -> string().
from_model(Type, Model) ->
from_model(Type, Model, []).
-spec from_model(Type::atom(), erlsom:model(), options()) -> string().
from_model(Type, Model, Options) ->
State = #e_state{indent = proplists:get_value(indent, Options, 4),
level = proplists:get_value(indent_level, Options, 0)},
from_type(Type, Model, State).
from_type(Type, #model{tps = Types} = Model, State) ->
case lists:keyfind(Type, #type.nm, Types) of
false ->
throw({error, "Type not found", Type});
Value ->
from_type2(Value, Model, State)
end.
from_type2(#type{nm = Name, els = Elements, atts = Attributes},
#model{any_attribs = AnyAtts} = Model, State) ->
Attribute_result = [from_attribute(A, Model, State) || A <- Attributes],
Element_result = from_elements(Elements, Model, State),
Fields =
case AnyAtts of
true ->
Any_attribs = [[comment(State), indent(State), " anyAttribs = []"]],
["{\n", add_commas(Any_attribs ++ Attribute_result ++ Element_result), $}];
false ->
["{\n", add_commas(Attribute_result ++ Element_result), $}]
end,
[comment(State), indent(State), $#, atom_list(Name), Fields].
from_elements(Elements, Model, State) ->
from_elements(Elements, Model, State, 0, []).
from_elements([], _Model, _State, _ChoiceCount, Acc) ->
lists:reverse(Acc);
from_elements([E | T], Model, State, ChoiceCount, Acc) ->
{Result, NewCount} =
from_element(E, Model, State, ChoiceCount),
from_elements(T, Model, State, NewCount, [Result | Acc]).
indent(#e_state{indent = Indent, level = Level}) ->
lists:duplicate(Indent * Level, 32). % 32 = space.
bump_level(State) ->
bump_level(State, 2).
bump_level(#e_state{level = Level} = State, Nr) ->
State#e_state{level = Level + Nr}.
%% This is used between the alternatives - no comma's
%% because only one of them should be used, the others are commented
%% out.
add_breaks(List) ->
separate(List, "\n").
add_commas(List) ->
separate(List, ",\n").
separate([], _) ->
[];
separate([H | T], Separator) ->
separate(T, [H], Separator).
separate([], Acc, _) ->
lists:reverse(Acc);
separate([H | T], Acc, Separator) ->
separate(T, [H, Separator | Acc], Separator).
from_attribute(#att{nm = Name, opt = Optional, tp = Type}, Model,
State) ->
Comment = case Optional of
true ->
[comment(State), indent(State), " % Optional:\n"];
false ->
""
end,
Value = default_value(Type, Model, State),
[Comment, comment(State), indent(State), io_lib:format(" ~p = ~s", [Name, Value])].
from_element(#el{alts = Alternatives, mn = Min, mx = Max}, Model, State, Nr_choices) when
length(Alternatives) == 1 ->
Min_Max_comment = min_max_comment(Min, Max, State),
Values = [from_alternative(A, Max, Model, State) || A <- Alternatives],
{[Min_Max_comment, Values], Nr_choices};
from_element(#el{alts = Alternatives, mn = Min, mx = Max}, Model, State, Nr_choices) ->
Unique_alternatives = lists:ukeysort(#alt.tp, Alternatives),
Choice_comment = choice_comment(length(Unique_alternatives), State),
Min_Max_comment = min_max_comment(Min, Max, State),
%% If there are several tags that lead to 1 alternative, there may be more than 1
%% #alt{} record for the same type.
Alts = from_alternatives(Unique_alternatives, Model, State),
Label = choice_label(Nr_choices),
Result =
case (Max > 1) of %% unbound > 1
true ->
[Min_Max_comment, comment(State), indent(State), " ", Label, " = [\n",
Choice_comment, add_breaks(Alts), $]];
false ->
[Min_Max_comment, comment(State), indent(State), " ", Label, " = \n",
Choice_comment, add_breaks(Alts)]
end,
{Result, Nr_choices + 1}.
choice_label(0) ->
"choice";
choice_label(N) ->
["choice", integer_to_list(N)].
%% only used for alternatives of a choice
from_alternatives(Alternatives, Model, State) ->
from_alternatives(Alternatives, Model, State, 1, []).
from_alternatives([], _, _, _, Acc) ->
lists:reverse(Acc);
from_alternatives([H|T], Model, #e_state{choice_depth= Depth} = State,
Count, Acc) ->
%% All alternatives are commented out, exacpt for the last one
%% (the last one, because otherwise there are problems with commas, closing braces
%% etc.).
New_depth =
case T of
[] -> % no more alternatives, so this is the last one
Depth;
_ ->
Depth + 1
end,
from_alternatives(T, Model, State, Count + 1,
[from_alternative2(H, Model,
State#e_state{choice_depth = New_depth}) | Acc]).
from_alternative(#alt{tag = Tag, tp = Type, rl = Real, mn = _Min2, mx = _Max2},
Max, Model, State) ->
Field_name = name(Tag, Type, Real),
%% add a newline if the type is a record
Newline = newline(Type),
%% add a relevant comment if the type is 'any':
Any_comment = any_comment(Type, State),
Value = default_value(Type, Model, State),
Field = case (Max > 1) of %% unbound > 1
true ->
case Type of
_ when Type == any; Type == '#ANY' ->
%% Note: this is not correct if MinOccurs > 0,
%% but that is rare, and it would be difficult
%% to figure out what to put in such a case.
io_lib:format(" ~p = ~s[]",
[Field_name, Newline]);
_ ->
io_lib:format(" ~p = [~s~s]",
[Field_name, Newline, Value])
end;
false ->
io_lib:format(" ~p = ~s~s", [Field_name, Newline, Value])
end,
[Any_comment, comment(State), indent(State), Field].
any_comment(any, State) ->
[comment(State), indent(State), " % Any value:\n"];
any_comment(_, _State) ->
"".
%% inside a choice
from_alternative2(#alt{tp = Type, mn = Min, mx = Max}, Model, State) ->
Value = default_value(Type, Model, State),
Min_Max_comment = min_max_comment(Min, Max, bump_level(State, 1)),
Field = case (Max > 1) of %% unbound > 1
true ->
Value2 = put_brace(Value),
io_lib:format("~s]", [Value2]);
false ->
io_lib:format("~s", [Value])
end,
[Min_Max_comment, Field].
name(Tag, Type, Real) ->
With_prefix =
case Real of
false ->
case Type of
{_,_} ->
Tag;
_ ->
Type
end;
_ ->
Tag
end,
base_name(With_prefix).
%% the names of the fields should not have the prefix
base_name(Atom) when is_atom(Atom) ->
String = atom_to_list(Atom),
String_no_prefix = case string:tokens(String, ":") of
[_Prefix, Name] ->
Name;
_ ->
String
end,
list_to_atom(String_no_prefix).
comment(#e_state{choice_depth = D}) ->
lists:duplicate(D, $%).
put_brace(String) ->
Flat = lists:flatten(String),
put_brace(Flat, []).
%% String = n * space + Something, must become:
%% n * space + [ + Something
put_brace([32 | T], Acc) ->
put_brace(T, [32 | Acc]);
put_brace([$% | T], Acc) ->
put_brace(T, [$% | Acc]);
put_brace([_ | _T] = Rest, Acc) ->
[lists:reverse(Acc), $[, Rest].
min_max_comment(1, 1, _) ->
"";
min_max_comment(0, 1, State) ->
[comment(State), indent(State), " % Optional:\n"];
min_max_comment(0, M, State) ->
[comment(State), indent(State),
io_lib:format(" % List with zero ~s elements:~n", [max_as_string(M)])];
min_max_comment(N, M, State) ->
[comment(State), indent(State),
io_lib:format(" % List with ~p ~s elements:~n", [N, max_as_string(M)])].
max_as_string(unbound) ->
"or more";
max_as_string(N) ->
"to " ++ integer_to_list(N).
choice_comment(1, _State) ->
"";
choice_comment(N, State) ->
[comment(State), indent(State),
io_lib:format(" % Select one from the following ~p elements:~n", [N])].
newline({_, _}) ->
% simple type
[];
newline(any) ->
[];
newline(_) ->
"\n".
default_value({'#PCDATA', Type}, Model, State) ->
default_value(Type, Model, State);
default_value(bool, _, _) ->
"true";
default_value(any, _, _) ->
"undefined";
default_value('#ANY', _, _) ->
"\"could be anything\"";
default_value(qname, _, _) ->
"qname";
default_value(integer, _, _) ->
"42";
default_value({integer, long}, _, _) ->
"42000";
default_value({integer, int}, _, _) ->
"4200";
default_value({integer, short}, _, _) ->
"420";
default_value({integer, byte}, _, _) ->
"42";
default_value({integer, unsignedLong}, _, _) ->
"43000";
default_value({integer, unsignedInt}, _, _) ->
"4300";
default_value({integer, unsignedShort}, _, _) ->
"430";
default_value({integer, unsignedByte}, _, _) ->
"43";
default_value({integer, nonPositiveInteger}, _, _) ->
"0";
default_value({integer, positiveInteger}, _, _) ->
"42";
default_value({integer, negativeInteger}, _, _) ->
"-42";
default_value({integer, nonNegativeInteger}, _, _) ->
"0";
default_value(float, _, _) ->
"3.1415927";
default_value(char, _, _) ->
"\"?\"";
default_value(Type, Model, State) ->
from_type(Type, Model, bump_level(State)).
atom_list(Atom) ->
io_lib:format("~p", [Atom]).