./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()], validate => validate()}.
20 -type option() :: binary() | {binary(), binary()}.
21 -type validate() :: #{method => atom(), datatype => binary()}.
22
23 -type parsed_form() :: #{type => binary(), ns => binary(), kvs := kv_map()}.
24 -type kv_map() :: #{binary() => [binary()]}.
25
26 -export_type([form/0, field/0, option/0, validate/0, kv_map/0]).
27
28 -ignore_xref([is_form/1]). % exported for consistency, might be used later
29
30 %% Form processing
31
32 %% @doc Find a form in subelements, and then parse its fields
33 -spec find_and_parse_form(exml:element()) -> parsed_form() | {error, binary()}.
34 find_and_parse_form(Parent) ->
35 383 case find_form(Parent) of
36 undefined ->
37 14 {error, <<"Form not found">>};
38 Form ->
39 369 parse_form_fields(Form)
40 end.
41
42 -spec find_form(exml:element()) -> exml:element() | undefined.
43 find_form(Parent) ->
44 3052 exml_query:subelement_with_name_and_ns(Parent, <<"x">>, ?NS_XDATA).
45
46 -spec find_form(exml:element(), Default) -> exml:element() | Default.
47 find_form(Parent, Default) ->
48 7 exml_query:subelement_with_name_and_ns(Parent, <<"x">>, ?NS_XDATA, Default).
49
50 %% @doc Check if the element is a form, and then parse its fields
51 -spec parse_form(exml:element()) -> parsed_form() | {error, binary()}.
52 parse_form(Elem) ->
53 7224 case is_form(Elem) of
54 true ->
55 217 parse_form_fields(Elem);
56 false ->
57 7007 {error, <<"Invalid form element">>}
58 end.
59
60 %% @doc Parse the form fields without checking that it is a form element
61 -spec parse_form_fields(exml:element()) -> parsed_form().
62 parse_form_fields(Elem) ->
63 2723 M = case form_type(Elem) of
64 12 undefined -> #{};
65 2711 Type -> #{type => Type}
66 end,
67 2723 KVs = form_fields_to_kvs(Elem#xmlel.children),
68 2723 case maps:take(<<"FORM_TYPE">>, KVs) of
69 {[NS], FKVs} ->
70 1802 M#{ns => NS, kvs => FKVs};
71 _ ->
72 % Either zero or more than one value of FORM_TYPE.
73 % According to XEP-0004 the form is still valid.
74 921 M#{kvs => KVs}
75 end.
76
77 -spec is_form(exml:element()) -> boolean().
78 is_form(#xmlel{name = Name} = Elem) ->
79 7262 Name =:= <<"x">> andalso exml_query:attr(Elem, <<"xmlns">>) =:= ?NS_XDATA.
80
81 -spec is_form(exml:element(), [binary()]) -> boolean().
82 is_form(Elem, Types) ->
83 38 is_form(Elem) andalso lists:member(form_type(Elem), Types).
84
85 -spec form_type(exml:element()) -> binary() | undefined.
86 form_type(Form) ->
87 2761 exml_query:attr(Form, <<"type">>).
88
89 -spec form_fields_to_kvs([exml:element()]) -> kv_map().
90 form_fields_to_kvs(Fields) ->
91 2723 maps:from_list(lists:flatmap(fun form_field_to_kv/1, Fields)).
92
93 form_field_to_kv(FieldEl = #xmlel{name = <<"field">>}) ->
94 5011 case exml_query:attr(FieldEl, <<"var">>) of
95 3 undefined -> [];
96 5008 Var -> [{Var, exml_query:paths(FieldEl, [{element, <<"value">>}, cdata])}]
97 end;
98 form_field_to_kv(_) ->
99 2 [].
100
101 %% Form construction
102
103 -spec form(form()) -> exml:element().
104 form(Spec) ->
105 543 #xmlel{name = <<"x">>,
106 attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, maps:get(type, Spec, <<"form">>)}],
107 3258 children = lists:flatmap(fun(Item) -> form_children(Item, Spec) end,
108 [title, instructions, ns, fields, reported, items])
109 }.
110
111 form_children(title, #{title := Title}) ->
112 80 [form_title(Title)];
113 form_children(instructions, #{instructions := Instructions}) ->
114 39 [form_instructions(Instructions)];
115 form_children(ns, #{ns := NS}) ->
116 478 [form_type_field(NS)];
117 form_children(fields, #{fields := Fields}) ->
118 504 [form_field(Field) || Field <- Fields];
119 form_children(reported, #{reported := ReportedFields}) ->
120 21 [reported_element([form_field(Field) || Field <- ReportedFields])];
121 form_children(items, #{items := Items}) ->
122 21 [item_element([form_field(Field) || Field <- ItemFields]) || ItemFields <- Items];
123 form_children(_, #{}) ->
124 2115 [].
125
126 -spec form_type_field(binary()) -> exml:element().
127 form_type_field(NS) when is_binary(NS) ->
128 478 form_field(#{var => <<"FORM_TYPE">>, type => <<"hidden">>, values => [NS]}).
129
130 -spec form_field(field()) -> exml:element().
131 form_field(M) when is_map(M) ->
132 2147 Validate = form_field_validate(maps:get(validate, M, [])),
133 2147 Values = [form_field_value(Value) || Value <- maps:get(values, M, [])],
134 2147 Options = [form_field_option(Option) || Option <- maps:get(options, M, [])],
135 2147 Attrs = [{atom_to_binary(K), V}
136 2147 || {K, V} <- maps:to_list(M), K =/= values, K =/= options, K =/= validate],
137 2147 #xmlel{name = <<"field">>, attrs = Attrs, children = Values ++ Options ++ Validate}.
138
139 -spec form_title(binary()) -> exml:element().
140 form_title(Title) ->
141 80 #xmlel{name = <<"title">>, attrs = [], children = [{xmlcdata, Title}]}.
142
143 -spec form_instructions(binary()) -> exml:element().
144 form_instructions(Instructions) ->
145 39 #xmlel{name = <<"instructions">>, attrs = [], children = [{xmlcdata, Instructions}]}.
146
147 -spec reported_element([exml:element()]) -> exml:element().
148 reported_element(Fields) ->
149 21 #xmlel{name = <<"reported">>, attrs = [], children = Fields}.
150
151 -spec item_element([exml:element()]) -> exml:element().
152 item_element(Fields) ->
153 14 #xmlel{name = <<"item">>, attrs = [], children = Fields}.
154
155 -spec form_field_option(option()) -> exml:element().
156 form_field_option({Label, Value}) ->
157 373 #xmlel{name = <<"option">>,
158 attrs = [{<<"label">>, Label}],
159 children = [form_field_value(Value)]};
160 form_field_option(Option) ->
161 242 form_field_option({Option, Option}).
162
163 -spec form_field_value(binary()) -> exml:element().
164 form_field_value(Value) ->
165 2057 #xmlel{name = <<"value">>, children = [#xmlcdata{content = Value}]}.
166
167 -spec form_field_validate(validate()) -> [exml:element()].
168 form_field_validate(#{method := Method, datatype := Datatype}) ->
169 7 [#xmlel{name = <<"validate">>,
170 attrs = [{<<"xmlns">>, ?NS_DATA_VALIDATE}, {<<"datatype">>, Datatype}],
171 children = form_field_validation_method(Method)}];
172 2140 form_field_validate(_) -> [].
173
174 -spec form_field_validation_method(atom()) -> [exml:element()].
175 form_field_validation_method(open) ->
176 7 [#xmlel{name = <<"open">>}].
Line Hits Source