%%% 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 %%% . %%% %%% Author contact: w.a.de.jong@gmail.com %%% ==================================================================== %%% Writes record definitions, to be used with Erlsom. %%% ==================================================================== %%% Writes record definitions, 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('#ANY') -> "any()"; 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).