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 |
31 |
case find_form(Parent) of |
36 |
|
undefined -> |
37 |
6 |
{error, <<"Form not found">>}; |
38 |
|
Form -> |
39 |
25 |
parse_form_fields(Form) |
40 |
|
end. |
41 |
|
|
42 |
|
-spec find_form(exml:element()) -> exml:element() | undefined. |
43 |
|
find_form(Parent) -> |
44 |
2615 |
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 |
1 |
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 |
862 |
case is_form(Elem) of |
54 |
|
true -> |
55 |
107 |
parse_form_fields(Elem); |
56 |
|
false -> |
57 |
755 |
{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 |
2188 |
M = case form_type(Elem) of |
64 |
5 |
undefined -> #{}; |
65 |
2183 |
Type -> #{type => Type} |
66 |
|
end, |
67 |
2188 |
KVs = form_fields_to_kvs(Elem#xmlel.children), |
68 |
2188 |
case maps:take(<<"FORM_TYPE">>, KVs) of |
69 |
|
{[NS], FKVs} -> |
70 |
1272 |
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 |
916 |
M#{kvs => KVs} |
75 |
|
end. |
76 |
|
|
77 |
|
-spec is_form(exml:element()) -> boolean(). |
78 |
|
is_form(#xmlel{name = Name} = Elem) -> |
79 |
900 |
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 |
2226 |
exml_query:attr(Form, <<"type">>). |
88 |
|
|
89 |
|
-spec form_fields_to_kvs([exml:element()]) -> kv_map(). |
90 |
|
form_fields_to_kvs(Fields) -> |
91 |
2188 |
maps:from_list(lists:flatmap(fun form_field_to_kv/1, Fields)). |
92 |
|
|
93 |
|
form_field_to_kv(FieldEl = #xmlel{name = <<"field">>}) -> |
94 |
3446 |
case exml_query:attr(FieldEl, <<"var">>) of |
95 |
3 |
undefined -> []; |
96 |
3443 |
Var -> [{Var, exml_query:paths(FieldEl, [{element, <<"value">>}, cdata])}] |
97 |
|
end; |
98 |
|
form_field_to_kv(_) -> |
99 |
:-( |
[]. |
100 |
|
|
101 |
|
%% Form construction |
102 |
|
|
103 |
|
-spec form(form()) -> exml:element(). |
104 |
|
form(Spec) -> |
105 |
250 |
#xmlel{name = <<"x">>, |
106 |
|
attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, maps:get(type, Spec, <<"form">>)}], |
107 |
1500 |
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 |
56 |
[form_title(Title)]; |
113 |
|
form_children(instructions, #{instructions := Instructions}) -> |
114 |
15 |
[form_instructions(Instructions)]; |
115 |
|
form_children(ns, #{ns := NS}) -> |
116 |
198 |
[form_type_field(NS)]; |
117 |
|
form_children(fields, #{fields := Fields}) -> |
118 |
211 |
[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 |
978 |
[]. |
125 |
|
|
126 |
|
-spec form_type_field(binary()) -> exml:element(). |
127 |
|
form_type_field(NS) when is_binary(NS) -> |
128 |
198 |
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 |
1175 |
Validate = form_field_validate(maps:get(validate, M, [])), |
133 |
1175 |
Values = [form_field_value(Value) || Value <- maps:get(values, M, [])], |
134 |
1175 |
Options = [form_field_option(Option) || Option <- maps:get(options, M, [])], |
135 |
1175 |
Attrs = [{atom_to_binary(K), V} |
136 |
1175 |
|| {K, V} <- maps:to_list(M), K =/= values, K =/= options, K =/= validate], |
137 |
1175 |
#xmlel{name = <<"field">>, attrs = Attrs, children = Values ++ Options ++ Validate}. |
138 |
|
|
139 |
|
-spec form_title(binary()) -> exml:element(). |
140 |
|
form_title(Title) -> |
141 |
56 |
#xmlel{name = <<"title">>, attrs = [], children = [{xmlcdata, Title}]}. |
142 |
|
|
143 |
|
-spec form_instructions(binary()) -> exml:element(). |
144 |
|
form_instructions(Instructions) -> |
145 |
15 |
#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 |
156 |
#xmlel{name = <<"option">>, |
158 |
|
attrs = [{<<"label">>, Label}], |
159 |
|
children = [form_field_value(Value)]}; |
160 |
|
form_field_option(Option) -> |
161 |
97 |
form_field_option({Option, Option}). |
162 |
|
|
163 |
|
-spec form_field_value(binary()) -> exml:element(). |
164 |
|
form_field_value(Value) -> |
165 |
948 |
#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 |
1168 |
form_field_validate(_) -> []. |
173 |
|
|
174 |
|
-spec form_field_validation_method(atom()) -> [exml:element()]. |
175 |
|
form_field_validation_method(open) -> |
176 |
7 |
[#xmlel{name = <<"open">>}]. |