Permalink
Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
328 lines (288 sloc) 10.7 KB
%%% Copyright (C) 2006 - 2008 Willem de Jong
%%%
%%% This file is part of Erlsom.
%%%
%%% Erlsom is free software: you can redistribute it and/or modify
%%% it under the terms of the GNU Lesser General Public License as
%%% published by the Free Software Foundation, either version 3 of
%%% the License, or (at your option) any later version.
%%%
%%% Erlsom is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%%% GNU Lesser General Public License for more details.
%%%
%%% You should have received a copy of the GNU Lesser General Public
%%% License along with Erlsom. If not, see
%%% <http://www.gnu.org/licenses/>.
%%%
%%% Author contact: w.a.de.jong@gmail.com
%%% ====================================================================
%%% Writes record definitions, to be used with Erlsom.
%%% ====================================================================
%%% Writes record defintions, taking a 'model' (from erlsom_compile) as
%%% input.
-module(erlsom_writeHrl).
-export([writeHrl/1, writeHrl/2]).
-export([write_hrl/1, write_hrl/2]).
-export([writeHrlFile/3]).
-export([writeXsdHrlFile/2]).
-include("erlsom_parse.hrl").
-include("erlsom.hrl").
-type hrl_header() :: iolist(). %% only explanatory text (comment)
-type hrl_types() :: iolist(). %% the actual record and type declarations
%% debug(Text) -> io:format("writeHrl: ~p~n", [Text]).
%% debug(Text1, Text2) ->
%% io:format("~p ~p~n", [Text1, Text2]).
-spec write_hrl(Model::erlsom:model()) -> {hrl_header(), hrl_types()}.
write_hrl(Model) ->
write_hrl(Model, []).
-spec write_hrl(Model::erlsom:model(), Options :: list()) -> {hrl_header(), hrl_types()}.
write_hrl(#model{tps = Types, th = TypeHierarchy, any_attribs = AnyAtts}, Options) ->
erlang:put(erlsom_attribute_hrl_prefix, proplists:get_value(attribute_hrl_prefix, Options, "")),
{header(), writeTypes(Types, TypeHierarchy, AnyAtts)}.
writeHrl(Model) ->
writeHrl(Model, []).
writeHrl(#model{} = Model, Options) ->
{Header, Types} = write_hrl(Model, Options),
[Header, Types].
writeHrlFile(Xsd, Prefix, Namespaces) ->
%% compile file
Result = erlsom:compile(Xsd, Prefix, Namespaces),
case Result of
{ok, Model} ->
writeHrl(Model);
{error, Error} ->
io:format("Error while compiling file: ~p~n", [Error])
end.
writeXsdHrlFile(Xsd, Options) ->
%% compile file
Result = erlsom:compile_xsd(Xsd, Options),
case Result of
{ok, Model} ->
writeHrl(Model, Options);
{error, Error} ->
throw({error, Error})
end.
header() ->
"%% HRL file generated by ERLSOM\n"
"%%\n"
"%% It is possible (and in some cases necessary) to change the name of\n"
"%% the record fields.\n"
"%%\n"
"%% It is possible to add default values, but be aware that these will\n"
"%% only be used when *writing* an xml document.\n\n"
"\n".
standard_types(AnyAtts) ->
case AnyAtts of
true ->
"-ifndef(ERLSOM_ANY_ATTRIB_TYPES).\n"
"-define(ERLSOM_ANY_ATTRIB_TYPES, true).\n"
"-type anyAttrib() :: {{string(), %% name of the attribute\n"
" string()}, %% namespace\n"
" string()}. %% value\n"
"\n"
"-type anyAttribs() :: [anyAttrib()] | undefined.\n"
"-endif.\n"
"\n";
_ ->
""
end ++
"-ifndef(ERLSOM_QNAME_TYPES).\n"
"-define(ERLSOM_QNAME_TYPES, true).\n"
"%% xsd:QName values are translated to #qname{} records.\n"
"-record(qname, {uri :: string(),\n"
" localPart :: string(),\n"
" prefix :: string(),\n"
" mappedPrefix :: string()}).\n"
"-endif.\n".
writeTypes(Types, TypeHierarchy, AnyAtts) ->
[standard_types(AnyAtts), [writeType(T, TypeHierarchy, AnyAtts) || T <- Types]].
writeType(#type{nm = '_document'}, _, _) ->
[];
writeType(#type{nm = Name, els = Elements, atts = Attributes, mxd = Mixed},
Hierarchy, AnyAtts) ->
Format = "~3n-record(~p, {~s})." ++
"~2n-type ~s :: ~s.",
Fields = [case AnyAtts of
true ->
"anyAttribs :: anyAttribs()";
_ ->
[]
end,
writeAttributes(Attributes),
writeElements(Elements, Mixed, Hierarchy)],
Args = [Name, add_commas(Fields),
formatType(Name), formatRecord(Name)],
lists:flatten(io_lib:format(Format, Args)).
add_commas(Parts) ->
string:join(lists:filter(fun(S) -> S /= "" end, Parts), ",").
%% writeElements(Elements, Mixed, Hierarchy) ->
%% writeElements(Elements, Mixed, Hierarchy, 0).
%% writeElements([], _Mixed, _Hierarchy, _) ->
%% [];
%% writeElements([Element | Tail], Mixed, Hierarchy, CountChoices) ->
%% {Elem, CountChoices2} = writeElement(Element, Mixed, Hierarchy, CountChoices),
%% NextElems = writeElements(Tail, Mixed, Hierarchy, CountChoices2),
%% [",\n\t", Elem, NextElems].
writeElements(Elements, Mixed, Hierarchy) ->
WriteFun = fun(Elem, AccIn) ->
{Acc, CountChoices} = AccIn,
{ElString, CountChoices2} = writeElement(Elem, Mixed, Hierarchy, CountChoices),
{[ElString | Acc], CountChoices2}
end,
{Result, _} = lists:foldl(WriteFun, {[], 0}, Elements),
string:join(lists:reverse(Result), ",").
writeElement(#el{alts = Alternatives, mn = Min, mx = Max, nillable = Nillable}, Mixed, Hierarchy, CountChoices) ->
{Label, Types, Count2} = case Mixed of
true ->
writeAlternatives(Alternatives, 1, 1, false, Hierarchy, CountChoices);
_ ->
writeAlternatives(Alternatives, Min, Max, Nillable, Hierarchy, CountChoices)
end,
TypeSpec = case Mixed of
true ->
["\n\t", Label, "[", Types, " | string()]"];
_ ->
["\n\t", Label, Types]
end,
{lists:flatten(TypeSpec), Count2}.
%% returns {Label (including " :: "), Type, CountChoices}
%%
%% more than 1 alternative: a choice
writeAlternatives(Alts, Min, Max, _N, Hierarchy, CountChoices) when length(Alts) > 1 ->
Label = case CountChoices of
0 ->
"choice :: ";
_ ->
["choice", integer_to_list(CountChoices), " :: "]
end,
Alternatives = case lists:keyfind('#any', #alt.tag, Alts) of
false ->
[writeAlternative(A, 1, 1, false, Hierarchy) || A <- Alts];
Alt ->
%% it makes no sense to have a choice between many things if
%% one of them is "any()" - in that case the any() suffices.
[writeAlternative(Alt, 1, 1, false, Hierarchy)]
end,
Type = lists:flatten([minMaxType(string:join(Alternatives, " | "),
Min, Max, 1, false, simple)]),
{Label, Type, CountChoices + 1};
%% 1 alternative (not a choice)
writeAlternatives([#alt{tag = Tag, tp = Tp, rl=Rl} = Alt], Min, Max, Nillable, Hierarchy, CountChoices) ->
LabelAtom = case Rl of
true ->
%% erlsom_lib:nameWithoutPrefix(atom_to_list(Tag));
baseName(Tag);
_ when Rl == false; Rl == simple ->
case Tp of
{'#PCDATA', _} ->
Tag;
_ ->
Tp
end
end,
Label = io_lib:format("~p :: ", [LabelAtom]),
Type = writeAlternative(Alt, Min, Max, Nillable, Hierarchy),
{Label, Type, CountChoices}.
%% alternative (not a choice), 'real' element (not a group)
writeAlternative(#alt{tag = '#any'}, _, _, _, _) ->
"any()";
writeAlternative(#alt{rl = true, tp = {Tp1, Tp2}, mx = Max2}, Min, Max, Nillable, _H) ->
formatSimpleType(Tp1, Tp2, Min, Max, Max2, Nillable);
writeAlternative(#alt{rl = Rl, tp = Type, mx = Max2}, Min, Max, Nillable, Hierarchy)
when Rl == true; Rl == simple ->
%% The type could be abstract, in that case put the 'leaves' of the type hierarchy
case erlsom_lib:getDescendants(Type, Hierarchy) of
[] ->
formatListType(Type, Min, Max, Max2, Nillable);
Leaves ->
minMaxType(string:join([formatType(L) || L <- [Type | Leaves]], " | "),
Min, Max, Max2, Nillable, false)
end;
%% simpleContent type
writeAlternative(#alt{rl = false, tp = {Tp1, Tp2}, mx=Max2}, Min, Max, Nillable, _H) ->
formatSimpleType(Tp1, Tp2, Min, Max, Max2, Nillable);
%% group type
writeAlternative(#alt{rl = false, tp=Tp, mx=Max2}, Min, Max, Nillable, _H) ->
formatListType(Tp, Min, Max, Max2, Nillable).
formatRecord(Type) ->
io_lib:format("#~p{}", [Type]).
formatType(Type) ->
io_lib:format("~p()", [Type]).
%% TODO: delete the flatten call.
writeAttributes(Attributes) ->
string:join(lists:map(fun writeAttribute/1, Attributes), ",").
-spec writeAttribute(#att{}) -> Acc when Acc :: list().
writeAttribute(#att{nm = Name, opt = Optional, tp = Type}) ->
OptOrReq = if Optional -> " | undefined"; true -> "" end,
AttrPrefix = erlang:get(erlsom_attribute_hrl_prefix),
AttrName = list_to_atom(AttrPrefix ++ atom_to_list(baseName(Name))),
Format = "~n\t~p :: ~s~s",
lists:flatten(io_lib:format(Format, [AttrName, makeType(Type), OptOrReq])).
%% the names of the fields should not have the prefix
baseName(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).
formatSimpleType(Tp1, Tp2, Min, Max, Max2, Nullable) ->
Type = simpleType(Tp1, Tp2),
minMaxType(Type, Min, Max, Max2, Nullable, true).
minMaxType(Type, Min, Max, Max2, Nullable, Simple) ->
Optional = if
Min == 0 ->
" | undefined";
true -> ""
end,
{Bracket1, Bracket2} = if
Max == 1 ->
{"", ""};
true ->
{"[", "]"}
end,
{BracketA, BracketB} = if
Max2 == 1 ->
{"", ""};
true ->
{"[", "]"}
end,
NullAlternative = case {Nullable, Simple} of
{true, true} ->
" | nil";
{true, false} ->
[" | {nil, ", Type, "}"];
_ ->
""
end,
lists:flatten([ Bracket1
, BracketA
, Type
, NullAlternative
, BracketB
, Bracket2
, Optional
]).
simpleType(_, Type) -> makeType(Type).
makeType(char) -> "string()";
makeType(integer) -> "integer()";
makeType({integer, negativeInteger}) -> "neg_integer()";
makeType({integer, positiveInteger}) -> "pos_integer()";
makeType({integer, nonPositiveInteger}) -> "neg_integer() | 0";
makeType({integer, Non_neg})
when Non_neg == nonNegativeInteger;
Non_neg == unsignedLong;
Non_neg == unsignedInt;
Non_neg == unsignedShort;
Non_neg == unsignedByte -> "non_neg_integer()";
makeType({integer, _}) -> "integer()";
makeType(bool) -> "boolean()";
makeType(float) -> "float() | 'NaN' | 'INF' | '-INF'";
makeType(qname) -> "#qname{}".
formatListType(Type, Min, Max, Max2, Nullable) ->
TypeAsString = formatType(Type),
minMaxType(TypeAsString, Min, Max, Max2, Nullable, false).