diff --git a/src/fxml_gen.erl b/src/fxml_gen.erl index b91000d..626b4bf 100644 --- a/src/fxml_gen.erl +++ b/src/fxml_gen.erl @@ -1716,12 +1716,20 @@ get_spec_by_label(Label, Elem) -> -spec group_refs([erl_types:erl_type()]) -> [{atom(), [erl_types:erl_type()]}]. group_refs(Refs) -> - dict:to_list( - lists:foldl( - fun(#ref{name = Name, label = Label} = Ref, G) -> - L = prepare_label(Label, Name), - dict:append_list(L, [Ref], G) - end, dict:new(), Refs)). + {LabelsRev, Groups} = + lists:foldl( + fun(#ref{name = Name, label = Label} = Ref, + {AccLabels, AccGroups}) -> + GroupLabel = prepare_label(Label, Name), + NewLabels = case dict:is_key(GroupLabel, AccGroups) of + true -> AccLabels; + false -> [GroupLabel | AccLabels] + end, + {NewLabels, + dict:append_list(GroupLabel, [Ref], AccGroups)} + end, {[], dict:new()}, Refs), + [{Label, dict:fetch(Label, Groups)} + || Label <- lists:reverse(LabelsRev)]. make_elem_dec_fun(#elem{name = Name, result = Result, refs = Refs, module = Mod, cdata = CData, attrs = Attrs, xmlns = XMLNS, @@ -2432,7 +2440,7 @@ make_elem_enc_fun(#elem{result = Result, attrs = Attrs, false -> ?AST([]) end, - RefsFun = lists:foldr( + RefsFun = lists:foldl( fun({Label, _}, Acc) -> Var = label_to_var(Label), make_function_call( diff --git a/src/fxmlrpc_codec.erl b/src/fxmlrpc_codec.erl index cf87383..0820bd1 100644 --- a/src/fxmlrpc_codec.erl +++ b/src/fxmlrpc_codec.erl @@ -460,30 +460,30 @@ encode_name_cdata(_val, _acc) -> decode_member(__TopXMLNS, __Opts, {xmlel, <<"member">>, _attrs, _els}) -> - {Value, Name} = decode_member_els(__TopXMLNS, + {Name, Value} = decode_member_els(__TopXMLNS, __Opts, _els, error, error), {Name, Value}. -decode_member_els(__TopXMLNS, __Opts, [], Value, - Name) -> - {case Value of +decode_member_els(__TopXMLNS, __Opts, [], Name, + Value) -> + {case Name of error -> erlang:error({fxmlrpc_codec, - {missing_tag, <<"value">>, __TopXMLNS}}); - {value, Value1} -> Value1 + {missing_tag, <<"name">>, __TopXMLNS}}); + {value, Name1} -> Name1 end, - case Name of + case Value of error -> erlang:error({fxmlrpc_codec, - {missing_tag, <<"name">>, __TopXMLNS}}); - {value, Name1} -> Name1 + {missing_tag, <<"value">>, __TopXMLNS}}); + {value, Value1} -> Value1 end}; decode_member_els(__TopXMLNS, __Opts, - [{xmlel, <<"name">>, _attrs, _} = _el | _els], Value, - Name) -> + [{xmlel, <<"name">>, _attrs, _} = _el | _els], Name, + Value) -> case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs, __TopXMLNS) @@ -492,14 +492,14 @@ decode_member_els(__TopXMLNS, __Opts, decode_member_els(__TopXMLNS, __Opts, _els, - Value, - {value, decode_name(<<"xmlrpc">>, __Opts, _el)}); + {value, decode_name(<<"xmlrpc">>, __Opts, _el)}, + Value); _ -> - decode_member_els(__TopXMLNS, __Opts, _els, Value, Name) + decode_member_els(__TopXMLNS, __Opts, _els, Name, Value) end; decode_member_els(__TopXMLNS, __Opts, - [{xmlel, <<"value">>, _attrs, _} = _el | _els], Value, - Name) -> + [{xmlel, <<"value">>, _attrs, _} = _el | _els], Name, + Value) -> case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs, __TopXMLNS) @@ -508,18 +508,18 @@ decode_member_els(__TopXMLNS, __Opts, decode_member_els(__TopXMLNS, __Opts, _els, - {value, decode_value(<<"xmlrpc">>, __Opts, _el)}, - Name); + Name, + {value, decode_value(<<"xmlrpc">>, __Opts, _el)}); _ -> - decode_member_els(__TopXMLNS, __Opts, _els, Value, Name) + decode_member_els(__TopXMLNS, __Opts, _els, Name, Value) end; -decode_member_els(__TopXMLNS, __Opts, [_ | _els], Value, - Name) -> +decode_member_els(__TopXMLNS, __Opts, [_ | _els], Name, + Value) -> decode_member_els(__TopXMLNS, __Opts, _els, - Value, - Name). + Name, + Value). encode_member({Name, Value}, __TopXMLNS) -> __NewTopXMLNS = @@ -535,12 +535,12 @@ encode_member({Name, Value}, __TopXMLNS) -> __TopXMLNS), {xmlel, <<"member">>, _attrs, _els}. -'encode_member_$value'(Value, __TopXMLNS, _acc) -> - [encode_value(Value, __TopXMLNS) | _acc]. - 'encode_member_$name'(Name, __TopXMLNS, _acc) -> [encode_name(Name, __TopXMLNS) | _acc]. +'encode_member_$value'(Value, __TopXMLNS, _acc) -> + [encode_value(Value, __TopXMLNS) | _acc]. + decode_struct(__TopXMLNS, __Opts, {xmlel, <<"struct">>, _attrs, _els}) -> Members = decode_struct_els(__TopXMLNS, @@ -1485,25 +1485,25 @@ encode_methodResponse({response, Payload}, decode_methodCall(__TopXMLNS, __Opts, {xmlel, <<"methodCall">>, _attrs, _els}) -> - {Params, Name} = decode_methodCall_els(__TopXMLNS, + {Name, Params} = decode_methodCall_els(__TopXMLNS, __Opts, _els, - [], - error), + error, + []), {call, Name, Params}. -decode_methodCall_els(__TopXMLNS, __Opts, [], Params, - Name) -> - {Params, - case Name of +decode_methodCall_els(__TopXMLNS, __Opts, [], Name, + Params) -> + {case Name of error -> erlang:error({fxmlrpc_codec, {missing_tag, <<"methodName">>, __TopXMLNS}}); {value, Name1} -> Name1 - end}; + end, + Params}; decode_methodCall_els(__TopXMLNS, __Opts, [{xmlel, <<"methodName">>, _attrs, _} = _el | _els], - Params, Name) -> + Name, Params) -> case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs, __TopXMLNS) @@ -1512,21 +1512,21 @@ decode_methodCall_els(__TopXMLNS, __Opts, decode_methodCall_els(__TopXMLNS, __Opts, _els, - Params, {value, decode_methodName(<<"xmlrpc">>, __Opts, - _el)}); + _el)}, + Params); _ -> decode_methodCall_els(__TopXMLNS, __Opts, _els, - Params, - Name) + Name, + Params) end; decode_methodCall_els(__TopXMLNS, __Opts, - [{xmlel, <<"params">>, _attrs, _} = _el | _els], Params, - Name) -> + [{xmlel, <<"params">>, _attrs, _} = _el | _els], Name, + Params) -> case fxmlrpc_codec:get_attr(<<"xmlns">>, _attrs, __TopXMLNS) @@ -1535,22 +1535,22 @@ decode_methodCall_els(__TopXMLNS, __Opts, decode_methodCall_els(__TopXMLNS, __Opts, _els, - decode_params(<<"xmlrpc">>, __Opts, _el), - Name); + Name, + decode_params(<<"xmlrpc">>, __Opts, _el)); _ -> decode_methodCall_els(__TopXMLNS, __Opts, _els, - Params, - Name) + Name, + Params) end; decode_methodCall_els(__TopXMLNS, __Opts, [_ | _els], - Params, Name) -> + Name, Params) -> decode_methodCall_els(__TopXMLNS, __Opts, _els, - Params, - Name). + Name, + Params). encode_methodCall({call, Name, Params}, __TopXMLNS) -> __NewTopXMLNS = @@ -1566,10 +1566,10 @@ encode_methodCall({call, Name, Params}, __TopXMLNS) -> __TopXMLNS), {xmlel, <<"methodCall">>, _attrs, _els}. +'encode_methodCall_$name'(Name, __TopXMLNS, _acc) -> + [encode_methodName(Name, __TopXMLNS) | _acc]. + 'encode_methodCall_$params'([], __TopXMLNS, _acc) -> _acc; 'encode_methodCall_$params'(Params, __TopXMLNS, _acc) -> [encode_params(Params, __TopXMLNS) | _acc]. - -'encode_methodCall_$name'(Name, __TopXMLNS, _acc) -> - [encode_methodName(Name, __TopXMLNS) | _acc]. diff --git a/test/fxml_gen_test.erl b/test/fxml_gen_test.erl new file mode 100644 index 0000000..076ed37 --- /dev/null +++ b/test/fxml_gen_test.erl @@ -0,0 +1,72 @@ +%%%---------------------------------------------------------------------- +%%% File : fxml_gen_test.erl +%%% Purpose : XML generator testing +%%% +%%% +%%% 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(fxml_gen_test). + +-include_lib("eunit/include/eunit.hrl"). + +reference_order_test() -> + TmpBase = case os:getenv("TMPDIR") of + false -> "/tmp"; + Dir -> Dir + end, + TmpDir = filename:join( + TmpBase, + "fast_xml_reference_order_" ++ os:getpid() ++ "_" ++ + integer_to_list(erlang:unique_integer([positive]))), + Modules = [reference_order_codec_external, reference_order_codec], + ok = file:make_dir(TmpDir), + try + ok = fxml_gen:compile( + "test/reference_order_codec.spec", + [{erl_dir, TmpDir}, {hrl_dir, TmpDir}]), + lists:foreach(fun(Mod) -> load_generated(Mod, TmpDir) end, Modules), + {xmlel, <<"ordered">>, _, Children} = + reference_order_codec:encode( + {ordered, + [{mechanism, <<"first">>}, {mechanism, <<"second">>}], + {inline}}), + Names = [Name || {xmlel, Name, _, _} <- Children], + ?assertEqual( + [<<"mechanism">>, <<"mechanism">>, <<"inline">>], + Names) + after + lists:foreach(fun unload/1, lists:reverse(Modules)), + remove_generated(TmpDir, Modules) + end. + +load_generated(Mod, Dir) -> + Source = filename:join(Dir, atom_to_list(Mod) ++ ".erl"), + {ok, Mod, Binary} = compile:file(Source, [binary]), + {module, Mod} = code:load_binary(Mod, Source, Binary), + ok. + +unload(Mod) -> + code:delete(Mod), + code:purge(Mod), + ok. + +remove_generated(Dir, Modules) -> + Files = [atom_to_list(Mod) ++ ".erl" || Mod <- Modules] ++ + ["reference_order_codec.hrl"], + lists:foreach(fun(File) -> + ok = file:delete(filename:join(Dir, File)) + end, Files), + ok = file:del_dir(Dir). diff --git a/test/reference_order_codec.spec b/test/reference_order_codec.spec new file mode 100644 index 0000000..ccce3d3 --- /dev/null +++ b/test/reference_order_codec.spec @@ -0,0 +1,20 @@ +-xml(ordered, + #elem{name = <<"ordered">>, + xmlns = <<"urn:test:reference-order">>, + result = {ordered, '$mechanisms', '$inline'}, + refs = [#ref{name = mechanism, + label = '$mechanisms'}, + #ref{name = inline, + label = '$inline', + min = 0, max = 1}]}). + +-xml(mechanism, + #elem{name = <<"mechanism">>, + xmlns = <<"urn:test:reference-order">>, + result = {mechanism, '$cdata'}, + cdata = #cdata{required = true}}). + +-xml(inline, + #elem{name = <<"inline">>, + xmlns = <<"urn:test:reference-order">>, + result = {inline}}).