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 |
3006 |
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 |
7204 |
case is_form(Elem) of |
53 |
|
true -> |
54 |
217 |
parse_form_fields(Elem); |
55 |
|
false -> |
56 |
6987 |
{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 |
2669 |
M = case form_type(Elem) of |
63 |
12 |
undefined -> #{}; |
64 |
2657 |
Type -> #{type => Type} |
65 |
|
end, |
66 |
2669 |
KVs = form_fields_to_kvs(Elem#xmlel.children), |
67 |
2669 |
case maps:take(<<"FORM_TYPE">>, KVs) of |
68 |
|
{[NS], FKVs} -> |
69 |
1790 |
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 |
879 |
M#{kvs => KVs} |
74 |
|
end. |
75 |
|
|
76 |
|
-spec is_form(exml:element()) -> boolean(). |
77 |
|
is_form(#xmlel{name = Name} = Elem) -> |
78 |
7242 |
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 |
2707 |
exml_query:attr(Form, <<"type">>). |
87 |
|
|
88 |
|
-spec form_fields_to_kvs([exml:element()]) -> kv_map(). |
89 |
|
form_fields_to_kvs(Fields) -> |
90 |
2669 |
maps:from_list(lists:flatmap(fun form_field_to_kv/1, Fields)). |
91 |
|
|
92 |
|
form_field_to_kv(FieldEl = #xmlel{name = <<"field">>}) -> |
93 |
4933 |
case exml_query:attr(FieldEl, <<"var">>) of |
94 |
3 |
undefined -> []; |
95 |
4930 |
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 |
543 |
#xmlel{name = <<"x">>, |
105 |
|
attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, maps:get(type, Spec, <<"form">>)}], |
106 |
3258 |
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 |
478 |
[form_type_field(NS)]; |
116 |
|
form_children(fields, #{fields := Fields}) -> |
117 |
504 |
[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 |
2115 |
[]. |
124 |
|
|
125 |
|
-spec form_type_field(binary()) -> exml:element(). |
126 |
|
form_type_field(NS) when is_binary(NS) -> |
127 |
478 |
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 |
2140 |
Values = [form_field_value(Value) || Value <- maps:get(values, M, [])], |
132 |
2140 |
Options = [form_field_option(Option) || Option <- maps:get(options, M, [])], |
133 |
2140 |
Attrs = [{atom_to_binary(K), V} || {K, V} <- maps:to_list(M), K =/= values, K =/= options], |
134 |
2140 |
#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 |
2057 |
#xmlel{name = <<"value">>, children = [#xmlcdata{content = Value}]}. |