./ct_report/coverage/mongoose_data_forms.COVER.html

1 -module(mongoose_data_forms).
2 -xep([{xep, 4}, {version, "2.13.1"}]).
3 -xep([{xep, 68}, {version, "1.3.0"}]).
4
5 %% Form processing
6 -export([find_and_parse_form/1, find_form/1, find_form/2,
7 parse_form/1, parse_form_fields/1,
8 is_form/1, is_form/2]).
9
10 %% Form construction
11 -export([form/1]).
12
13 -include_lib("exml/include/exml.hrl").
14 -include("mongoose_ns.hrl").
15
16 -type form() :: #{type => binary(), title => binary(), instructions => binary(), ns => binary(),
17 fields => [field()], reported => [field()], items => [[field()]]}.
18 -type field() :: #{var => binary(), type => binary(), label => binary(),
19 values => [binary()], options => [option()]}.
20 -type option() :: binary() | {binary(), binary()}.
21
22 -type parsed_form() :: #{type => binary(), ns => binary(), kvs := kv_map()}.
23 -type kv_map() :: #{binary() => [binary()]}.
24
25 -export_type([form/0, field/0, option/0, kv_map/0]).
26
27 -ignore_xref([is_form/1]). % exported for consistency, might be used later
28
29 %% Form processing
30
31 %% @doc Find a form in subelements, and then parse its fields
32 -spec find_and_parse_form(exml:element()) -> parsed_form() | {error, binary()}.
33 find_and_parse_form(Parent) ->
34 383 case find_form(Parent) of
35 undefined ->
36 14 {error, <<"Form not found">>};
37 Form ->
38 369 parse_form_fields(Form)
39 end.
40
41 -spec find_form(exml:element()) -> exml:element() | undefined.
42 find_form(Parent) ->
43 2882 exml_query:subelement_with_name_and_ns(Parent, <<"x">>, ?NS_XDATA).
44
45 -spec find_form(exml:element(), Default) -> exml:element() | Default.
46 find_form(Parent, Default) ->
47 7 exml_query:subelement_with_name_and_ns(Parent, <<"x">>, ?NS_XDATA, Default).
48
49 %% @doc Check if the element is a form, and then parse its fields
50 -spec parse_form(exml:element()) -> parsed_form() | {error, binary()}.
51 parse_form(Elem) ->
52 7352 case is_form(Elem) of
53 true ->
54 217 parse_form_fields(Elem);
55 false ->
56 7135 {error, <<"Invalid form element">>}
57 end.
58
59 %% @doc Parse the form fields without checking that it is a form element
60 -spec parse_form_fields(exml:element()) -> parsed_form().
61 parse_form_fields(Elem) ->
62 2580 M = case form_type(Elem) of
63 12 undefined -> #{};
64 2568 Type -> #{type => Type}
65 end,
66 2580 KVs = form_fields_to_kvs(Elem#xmlel.children),
67 2580 case maps:take(<<"FORM_TYPE">>, KVs) of
68 {[NS], FKVs} ->
69 1771 M#{ns => NS, kvs => FKVs};
70 _ ->
71 % Either zero or more than one value of FORM_TYPE.
72 % According to XEP-0004 the form is still valid.
73 809 M#{kvs => KVs}
74 end.
75
76 -spec is_form(exml:element()) -> boolean().
77 is_form(#xmlel{name = Name} = Elem) ->
78 7390 Name =:= <<"x">> andalso exml_query:attr(Elem, <<"xmlns">>) =:= ?NS_XDATA.
79
80 -spec is_form(exml:element(), [binary()]) -> boolean().
81 is_form(Elem, Types) ->
82 38 is_form(Elem) andalso lists:member(form_type(Elem), Types).
83
84 -spec form_type(exml:element()) -> binary() | undefined.
85 form_type(Form) ->
86 2618 exml_query:attr(Form, <<"type">>).
87
88 -spec form_fields_to_kvs([exml:element()]) -> kv_map().
89 form_fields_to_kvs(Fields) ->
90 2580 maps:from_list(lists:flatmap(fun form_field_to_kv/1, Fields)).
91
92 form_field_to_kv(FieldEl = #xmlel{name = <<"field">>}) ->
93 4815 case exml_query:attr(FieldEl, <<"var">>) of
94 3 undefined -> [];
95 4812 Var -> [{Var, exml_query:paths(FieldEl, [{element, <<"value">>}, cdata])}]
96 end;
97 form_field_to_kv(_) ->
98 2 [].
99
100 %% Form construction
101
102 -spec form(form()) -> exml:element().
103 form(Spec) ->
104 529 #xmlel{name = <<"x">>,
105 attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, maps:get(type, Spec, <<"form">>)}],
106 3174 children = lists:flatmap(fun(Item) -> form_children(Item, Spec) end,
107 [title, instructions, ns, fields, reported, items])
108 }.
109
110 form_children(title, #{title := Title}) ->
111 80 [form_title(Title)];
112 form_children(instructions, #{instructions := Instructions}) ->
113 39 [form_instructions(Instructions)];
114 form_children(ns, #{ns := NS}) ->
115 464 [form_type_field(NS)];
116 form_children(fields, #{fields := Fields}) ->
117 490 [form_field(Field) || Field <- Fields];
118 form_children(reported, #{reported := ReportedFields}) ->
119 21 [reported_element([form_field(Field) || Field <- ReportedFields])];
120 form_children(items, #{items := Items}) ->
121 21 [item_element([form_field(Field) || Field <- ItemFields]) || ItemFields <- Items];
122 form_children(_, #{}) ->
123 2059 [].
124
125 -spec form_type_field(binary()) -> exml:element().
126 form_type_field(NS) when is_binary(NS) ->
127 464 form_field(#{var => <<"FORM_TYPE">>, type => <<"hidden">>, values => [NS]}).
128
129 -spec form_field(field()) -> exml:element().
130 form_field(M) when is_map(M) ->
131 2077 Values = [form_field_value(Value) || Value <- maps:get(values, M, [])],
132 2077 Options = [form_field_option(Option) || Option <- maps:get(options, M, [])],
133 2077 Attrs = [{atom_to_binary(K), V} || {K, V} <- maps:to_list(M), K =/= values, K =/= options],
134 2077 #xmlel{name = <<"field">>, attrs = Attrs, children = Values ++ Options}.
135
136 -spec form_title(binary()) -> exml:element().
137 form_title(Title) ->
138 80 #xmlel{name = <<"title">>, attrs = [], children = [{xmlcdata, Title}]}.
139
140 -spec form_instructions(binary()) -> exml:element().
141 form_instructions(Instructions) ->
142 39 #xmlel{name = <<"instructions">>, attrs = [], children = [{xmlcdata, Instructions}]}.
143
144 -spec reported_element([exml:element()]) -> exml:element().
145 reported_element(Fields) ->
146 21 #xmlel{name = <<"reported">>, attrs = [], children = Fields}.
147
148 -spec item_element([exml:element()]) -> exml:element().
149 item_element(Fields) ->
150 14 #xmlel{name = <<"item">>, attrs = [], children = Fields}.
151
152 -spec form_field_option(option()) -> exml:element().
153 form_field_option({Label, Value}) ->
154 373 #xmlel{name = <<"option">>,
155 attrs = [{<<"label">>, Label}],
156 children = [form_field_value(Value)]};
157 form_field_option(Option) ->
158 242 form_field_option({Option, Option}).
159
160 -spec form_field_value(binary()) -> exml:element().
161 form_field_value(Value) ->
162 2043 #xmlel{name = <<"value">>, children = [#xmlcdata{content = Value}]}.
Line Hits Source