Skip to content

Commit e0fca27

Browse files
committed
Preserve reference order in generated XML encoders
1 parent 2fc94e5 commit e0fca27

4 files changed

Lines changed: 158 additions & 58 deletions

File tree

src/fxml_gen.erl

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1716,12 +1716,20 @@ get_spec_by_label(Label, Elem) ->
17161716
-spec group_refs([erl_types:erl_type()]) -> [{atom(), [erl_types:erl_type()]}].
17171717

17181718
group_refs(Refs) ->
1719-
dict:to_list(
1720-
lists:foldl(
1721-
fun(#ref{name = Name, label = Label} = Ref, G) ->
1722-
L = prepare_label(Label, Name),
1723-
dict:append_list(L, [Ref], G)
1724-
end, dict:new(), Refs)).
1719+
{LabelsRev, Groups} =
1720+
lists:foldl(
1721+
fun(#ref{name = Name, label = Label} = Ref,
1722+
{AccLabels, AccGroups}) ->
1723+
GroupLabel = prepare_label(Label, Name),
1724+
NewLabels = case dict:is_key(GroupLabel, AccGroups) of
1725+
true -> AccLabels;
1726+
false -> [GroupLabel | AccLabels]
1727+
end,
1728+
{NewLabels,
1729+
dict:append_list(GroupLabel, [Ref], AccGroups)}
1730+
end, {[], dict:new()}, Refs),
1731+
[{Label, dict:fetch(Label, Groups)}
1732+
|| Label <- lists:reverse(LabelsRev)].
17251733

17261734
make_elem_dec_fun(#elem{name = Name, result = Result, refs = Refs, module = Mod,
17271735
cdata = CData, attrs = Attrs, xmlns = XMLNS,
@@ -2432,7 +2440,7 @@ make_elem_enc_fun(#elem{result = Result, attrs = Attrs,
24322440
false ->
24332441
?AST([])
24342442
end,
2435-
RefsFun = lists:foldr(
2443+
RefsFun = lists:foldl(
24362444
fun({Label, _}, Acc) ->
24372445
Var = label_to_var(Label),
24382446
make_function_call(

src/fxmlrpc_codec.erl

Lines changed: 51 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -460,30 +460,30 @@ encode_name_cdata(_val, _acc) ->
460460

461461
decode_member(__TopXMLNS, __Opts,
462462
{xmlel, <<"member">>, _attrs, _els}) ->
463-
{Value, Name} = decode_member_els(__TopXMLNS,
463+
{Name, Value} = decode_member_els(__TopXMLNS,
464464
__Opts,
465465
_els,
466466
error,
467467
error),
468468
{Name, Value}.
469469

470-
decode_member_els(__TopXMLNS, __Opts, [], Value,
471-
Name) ->
472-
{case Value of
470+
decode_member_els(__TopXMLNS, __Opts, [], Name,
471+
Value) ->
472+
{case Name of
473473
error ->
474474
erlang:error({fxmlrpc_codec,
475-
{missing_tag, <<"value">>, __TopXMLNS}});
476-
{value, Value1} -> Value1
475+
{missing_tag, <<"name">>, __TopXMLNS}});
476+
{value, Name1} -> Name1
477477
end,
478-
case Name of
478+
case Value of
479479
error ->
480480
erlang:error({fxmlrpc_codec,
481-
{missing_tag, <<"name">>, __TopXMLNS}});
482-
{value, Name1} -> Name1
481+
{missing_tag, <<"value">>, __TopXMLNS}});
482+
{value, Value1} -> Value1
483483
end};
484484
decode_member_els(__TopXMLNS, __Opts,
485-
[{xmlel, <<"name">>, _attrs, _} = _el | _els], Value,
486-
Name) ->
485+
[{xmlel, <<"name">>, _attrs, _} = _el | _els], Name,
486+
Value) ->
487487
case fxmlrpc_codec:get_attr(<<"xmlns">>,
488488
_attrs,
489489
__TopXMLNS)
@@ -492,14 +492,14 @@ decode_member_els(__TopXMLNS, __Opts,
492492
decode_member_els(__TopXMLNS,
493493
__Opts,
494494
_els,
495-
Value,
496-
{value, decode_name(<<"xmlrpc">>, __Opts, _el)});
495+
{value, decode_name(<<"xmlrpc">>, __Opts, _el)},
496+
Value);
497497
_ ->
498-
decode_member_els(__TopXMLNS, __Opts, _els, Value, Name)
498+
decode_member_els(__TopXMLNS, __Opts, _els, Name, Value)
499499
end;
500500
decode_member_els(__TopXMLNS, __Opts,
501-
[{xmlel, <<"value">>, _attrs, _} = _el | _els], Value,
502-
Name) ->
501+
[{xmlel, <<"value">>, _attrs, _} = _el | _els], Name,
502+
Value) ->
503503
case fxmlrpc_codec:get_attr(<<"xmlns">>,
504504
_attrs,
505505
__TopXMLNS)
@@ -508,18 +508,18 @@ decode_member_els(__TopXMLNS, __Opts,
508508
decode_member_els(__TopXMLNS,
509509
__Opts,
510510
_els,
511-
{value, decode_value(<<"xmlrpc">>, __Opts, _el)},
512-
Name);
511+
Name,
512+
{value, decode_value(<<"xmlrpc">>, __Opts, _el)});
513513
_ ->
514-
decode_member_els(__TopXMLNS, __Opts, _els, Value, Name)
514+
decode_member_els(__TopXMLNS, __Opts, _els, Name, Value)
515515
end;
516-
decode_member_els(__TopXMLNS, __Opts, [_ | _els], Value,
517-
Name) ->
516+
decode_member_els(__TopXMLNS, __Opts, [_ | _els], Name,
517+
Value) ->
518518
decode_member_els(__TopXMLNS,
519519
__Opts,
520520
_els,
521-
Value,
522-
Name).
521+
Name,
522+
Value).
523523

524524
encode_member({Name, Value}, __TopXMLNS) ->
525525
__NewTopXMLNS =
@@ -535,12 +535,12 @@ encode_member({Name, Value}, __TopXMLNS) ->
535535
__TopXMLNS),
536536
{xmlel, <<"member">>, _attrs, _els}.
537537

538-
'encode_member_$value'(Value, __TopXMLNS, _acc) ->
539-
[encode_value(Value, __TopXMLNS) | _acc].
540-
541538
'encode_member_$name'(Name, __TopXMLNS, _acc) ->
542539
[encode_name(Name, __TopXMLNS) | _acc].
543540

541+
'encode_member_$value'(Value, __TopXMLNS, _acc) ->
542+
[encode_value(Value, __TopXMLNS) | _acc].
543+
544544
decode_struct(__TopXMLNS, __Opts,
545545
{xmlel, <<"struct">>, _attrs, _els}) ->
546546
Members = decode_struct_els(__TopXMLNS,
@@ -1485,25 +1485,25 @@ encode_methodResponse({response, Payload},
14851485

14861486
decode_methodCall(__TopXMLNS, __Opts,
14871487
{xmlel, <<"methodCall">>, _attrs, _els}) ->
1488-
{Params, Name} = decode_methodCall_els(__TopXMLNS,
1488+
{Name, Params} = decode_methodCall_els(__TopXMLNS,
14891489
__Opts,
14901490
_els,
1491-
[],
1492-
error),
1491+
error,
1492+
[]),
14931493
{call, Name, Params}.
14941494

1495-
decode_methodCall_els(__TopXMLNS, __Opts, [], Params,
1496-
Name) ->
1497-
{Params,
1498-
case Name of
1495+
decode_methodCall_els(__TopXMLNS, __Opts, [], Name,
1496+
Params) ->
1497+
{case Name of
14991498
error ->
15001499
erlang:error({fxmlrpc_codec,
15011500
{missing_tag, <<"methodName">>, __TopXMLNS}});
15021501
{value, Name1} -> Name1
1503-
end};
1502+
end,
1503+
Params};
15041504
decode_methodCall_els(__TopXMLNS, __Opts,
15051505
[{xmlel, <<"methodName">>, _attrs, _} = _el | _els],
1506-
Params, Name) ->
1506+
Name, Params) ->
15071507
case fxmlrpc_codec:get_attr(<<"xmlns">>,
15081508
_attrs,
15091509
__TopXMLNS)
@@ -1512,21 +1512,21 @@ decode_methodCall_els(__TopXMLNS, __Opts,
15121512
decode_methodCall_els(__TopXMLNS,
15131513
__Opts,
15141514
_els,
1515-
Params,
15161515
{value,
15171516
decode_methodName(<<"xmlrpc">>,
15181517
__Opts,
1519-
_el)});
1518+
_el)},
1519+
Params);
15201520
_ ->
15211521
decode_methodCall_els(__TopXMLNS,
15221522
__Opts,
15231523
_els,
1524-
Params,
1525-
Name)
1524+
Name,
1525+
Params)
15261526
end;
15271527
decode_methodCall_els(__TopXMLNS, __Opts,
1528-
[{xmlel, <<"params">>, _attrs, _} = _el | _els], Params,
1529-
Name) ->
1528+
[{xmlel, <<"params">>, _attrs, _} = _el | _els], Name,
1529+
Params) ->
15301530
case fxmlrpc_codec:get_attr(<<"xmlns">>,
15311531
_attrs,
15321532
__TopXMLNS)
@@ -1535,22 +1535,22 @@ decode_methodCall_els(__TopXMLNS, __Opts,
15351535
decode_methodCall_els(__TopXMLNS,
15361536
__Opts,
15371537
_els,
1538-
decode_params(<<"xmlrpc">>, __Opts, _el),
1539-
Name);
1538+
Name,
1539+
decode_params(<<"xmlrpc">>, __Opts, _el));
15401540
_ ->
15411541
decode_methodCall_els(__TopXMLNS,
15421542
__Opts,
15431543
_els,
1544-
Params,
1545-
Name)
1544+
Name,
1545+
Params)
15461546
end;
15471547
decode_methodCall_els(__TopXMLNS, __Opts, [_ | _els],
1548-
Params, Name) ->
1548+
Name, Params) ->
15491549
decode_methodCall_els(__TopXMLNS,
15501550
__Opts,
15511551
_els,
1552-
Params,
1553-
Name).
1552+
Name,
1553+
Params).
15541554

15551555
encode_methodCall({call, Name, Params}, __TopXMLNS) ->
15561556
__NewTopXMLNS =
@@ -1566,10 +1566,10 @@ encode_methodCall({call, Name, Params}, __TopXMLNS) ->
15661566
__TopXMLNS),
15671567
{xmlel, <<"methodCall">>, _attrs, _els}.
15681568

1569+
'encode_methodCall_$name'(Name, __TopXMLNS, _acc) ->
1570+
[encode_methodName(Name, __TopXMLNS) | _acc].
1571+
15691572
'encode_methodCall_$params'([], __TopXMLNS, _acc) ->
15701573
_acc;
15711574
'encode_methodCall_$params'(Params, __TopXMLNS, _acc) ->
15721575
[encode_params(Params, __TopXMLNS) | _acc].
1573-
1574-
'encode_methodCall_$name'(Name, __TopXMLNS, _acc) ->
1575-
[encode_methodName(Name, __TopXMLNS) | _acc].

test/fxml_gen_test.erl

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
%%%----------------------------------------------------------------------
2+
%%% File : fxml_gen_test.erl
3+
%%% Purpose : XML generator testing
4+
%%%
5+
%%%
6+
%%% Copyright (C) 2002-2026 ProcessOne, SARL. All Rights Reserved.
7+
%%%
8+
%%% Licensed under the Apache License, Version 2.0 (the "License");
9+
%%% you may not use this file except in compliance with the License.
10+
%%% You may obtain a copy of the License at
11+
%%%
12+
%%% http://www.apache.org/licenses/LICENSE-2.0
13+
%%%
14+
%%% Unless required by applicable law or agreed to in writing, software
15+
%%% distributed under the License is distributed on an "AS IS" BASIS,
16+
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
%%% See the License for the specific language governing permissions and
18+
%%% limitations under the License.
19+
%%%
20+
%%%----------------------------------------------------------------------
21+
-module(fxml_gen_test).
22+
23+
-include_lib("eunit/include/eunit.hrl").
24+
25+
reference_order_test() ->
26+
TmpBase = case os:getenv("TMPDIR") of
27+
false -> "/tmp";
28+
Dir -> Dir
29+
end,
30+
TmpDir = filename:join(
31+
TmpBase,
32+
"fast_xml_reference_order_" ++ os:getpid() ++ "_" ++
33+
integer_to_list(erlang:unique_integer([positive]))),
34+
Modules = [reference_order_codec_external, reference_order_codec],
35+
ok = file:make_dir(TmpDir),
36+
try
37+
ok = fxml_gen:compile(
38+
"test/reference_order_codec.spec",
39+
[{erl_dir, TmpDir}, {hrl_dir, TmpDir}]),
40+
lists:foreach(fun(Mod) -> load_generated(Mod, TmpDir) end, Modules),
41+
{xmlel, <<"ordered">>, _, Children} =
42+
reference_order_codec:encode(
43+
{ordered,
44+
[{mechanism, <<"first">>}, {mechanism, <<"second">>}],
45+
{inline}}),
46+
Names = [Name || {xmlel, Name, _, _} <- Children],
47+
?assertEqual(
48+
[<<"mechanism">>, <<"mechanism">>, <<"inline">>],
49+
Names)
50+
after
51+
lists:foreach(fun unload/1, lists:reverse(Modules)),
52+
remove_generated(TmpDir, Modules)
53+
end.
54+
55+
load_generated(Mod, Dir) ->
56+
Source = filename:join(Dir, atom_to_list(Mod) ++ ".erl"),
57+
{ok, Mod, Binary} = compile:file(Source, [binary]),
58+
{module, Mod} = code:load_binary(Mod, Source, Binary),
59+
ok.
60+
61+
unload(Mod) ->
62+
code:delete(Mod),
63+
code:purge(Mod),
64+
ok.
65+
66+
remove_generated(Dir, Modules) ->
67+
Files = [atom_to_list(Mod) ++ ".erl" || Mod <- Modules] ++
68+
["reference_order_codec.hrl"],
69+
lists:foreach(fun(File) ->
70+
ok = file:delete(filename:join(Dir, File))
71+
end, Files),
72+
ok = file:del_dir(Dir).

test/reference_order_codec.spec

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-xml(ordered,
2+
#elem{name = <<"ordered">>,
3+
xmlns = <<"urn:test:reference-order">>,
4+
result = {ordered, '$mechanisms', '$inline'},
5+
refs = [#ref{name = mechanism,
6+
label = '$mechanisms'},
7+
#ref{name = inline,
8+
label = '$inline',
9+
min = 0, max = 1}]}).
10+
11+
-xml(mechanism,
12+
#elem{name = <<"mechanism">>,
13+
xmlns = <<"urn:test:reference-order">>,
14+
result = {mechanism, '$cdata'},
15+
cdata = #cdata{required = true}}).
16+
17+
-xml(inline,
18+
#elem{name = <<"inline">>,
19+
xmlns = <<"urn:test:reference-order">>,
20+
result = {inline}}).

0 commit comments

Comments
 (0)