2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
9 local setmetatable
= setmetatable
;
10 local ipairs
= ipairs
;
11 local type, next = type, next;
12 local tonumber = tonumber;
13 local t_concat
= table.concat
;
14 local st
= require
"util.stanza";
15 local jid_prep
= require
"util.jid".prep
;
20 local xmlns_forms
= 'jabber:x:data';
21 local xmlns_validate
= 'http://jabber.org/protocol/xdata-validate';
24 local form_mt
= { __index
= form_t
};
26 local function new(layout
)
27 return setmetatable(layout
, form_mt
);
30 function form_t
.form(layout
, data
, formtype
)
31 if not formtype
then formtype
= "form" end
32 local form
= st
.stanza("x", { xmlns
= xmlns_forms
, type = formtype
});
33 if formtype
== "cancel" then
36 if formtype
~= "submit" then
38 form
:tag("title"):text(layout
.title
):up();
40 if layout
.instructions
then
41 form
:tag("instructions"):text(layout
.instructions
):up();
44 for _
, field
in ipairs(layout
) do
45 local field_type
= field
.type or "text-single";
47 form
:tag("field", { type = field_type
, var
= field
.var
or field
.name
, label
= formtype
~= "submit" and field
.label
or nil });
49 if formtype
~= "submit" then
51 form
:text_tag("desc", field
.desc
);
55 if formtype
== "form" and field
.datatype
then
56 form
:tag("validate", { xmlns
= xmlns_validate
, datatype
= field
.datatype
});
62 local value
= field
.value
;
63 local options
= field
.options
;
65 if data
and data
[field
.name
] ~= nil then
66 value
= data
[field
.name
];
68 if formtype
== "form" and type(value
) == "table"
69 and (field_type
== "list-single" or field_type
== "list-multi") then
70 -- Allow passing dynamically generated options as values
71 options
, value
= value
, nil;
75 if formtype
== "form" and options
then
77 for _
, val
in ipairs(options
) do
78 if type(val
) == "table" then
79 form
:tag("option", { label
= val
.label
}):tag("value"):text(val
.value
):up():up();
81 defaults
[#defaults
+1] = val
.value
;
84 form
:tag("option", { label
= val
}):tag("value"):text(val
):up():up();
88 if field_type
== "list-single" then
90 elseif field_type
== "list-multi" then
97 if type(value
) == "number" then
98 -- TODO validate that this is ok somehow, eg check field.datatype
99 value
= ("%g"):format(value
);
101 -- Add value, depending on type
102 if field_type
== "hidden" then
103 if type(value
) == "table" then
104 -- Assume an XML snippet
109 form
:tag("value"):text(value
):up();
111 elseif field_type
== "boolean" then
112 form
:tag("value"):text((value
and "1") or "0"):up();
113 elseif field_type
== "fixed" then
114 form
:tag("value"):text(value
):up();
115 elseif field_type
== "jid-multi" then
116 for _
, jid
in ipairs(value
) do
117 form
:tag("value"):text(jid
):up();
119 elseif field_type
== "jid-single" then
120 form
:tag("value"):text(value
):up();
121 elseif field_type
== "text-single" or field_type
== "text-private" then
122 form
:tag("value"):text(value
):up();
123 elseif field_type
== "text-multi" then
124 -- Split into multiple <value> tags, one for each line
125 for line
in value
:gmatch("([^\r\n]+)\r?\n*") do
126 form
:tag("value"):text(line
):up();
128 elseif field_type
== "list-single" then
129 form
:tag("value"):text(value
):up();
130 elseif field_type
== "list-multi" then
131 for _
, val
in ipairs(value
) do
132 form
:tag("value"):text(val
):up();
137 local media
= field
.media
;
139 form
:tag("media", { xmlns
= "urn:xmpp:media-element", height
= media
.height
, width
= media
.width
});
140 for _
, val
in ipairs(media
) do
141 form
:tag("uri", { type = val
.type }):text(val
.uri
):up()
146 if formtype
== "form" and field
.required
then
147 form
:tag("required"):up();
150 -- Jump back up to list of fields
156 local field_readers
= {};
157 local data_validators
= {};
159 function form_t
.data(layout
, stanza
, current
)
164 for _
, field
in ipairs(layout
) do
166 for field_tag
in stanza
:childtags("field") do
167 if (field
.var
or field
.name
) == field_tag
.attr
.var
then
174 if current
and current
[field
.name
] ~= nil then
175 data
[field
.name
] = current
[field
.name
];
176 elseif field
.required
then
177 errors
[field
.name
] = "Required value missing";
179 elseif field
.name
then
180 present
[field
.name
] = true;
181 local reader
= field_readers
[field
.type];
183 local value
, err
= reader(tag, field
.required
);
184 local validator
= field
.datatype
and data_validators
[field
.datatype
];
185 if value
~= nil and validator
then
186 local valid
, ret
= validator(value
, field
);
190 value
, err
= nil, ret
or ("Invalid value for data of type " .. field
.datatype
);
193 data
[field
.name
], errors
[field
.name
] = value
, err
;
198 return data
, errors
, present
;
200 return data
, nil, present
;
203 local function simple_text(field_tag
, required
)
204 local data
= field_tag
:get_child_text("value");
205 -- XEP-0004 does not say if an empty string is acceptable for a required value
206 -- so we will follow HTML5 which says that empty string means missing
207 if required
and (data
== nil or data
== "") then
208 return nil, "Required value missing";
210 return data
; -- Return whatever get_child_text returned, even if empty string
213 field_readers
["text-single"] = simple_text
;
215 field_readers
["text-private"] = simple_text
;
217 field_readers
["jid-single"] =
218 function (field_tag
, required
)
219 local raw_data
, err
= simple_text(field_tag
, required
);
220 if not raw_data
then return raw_data
, err
; end
221 local data
= jid_prep(raw_data
);
223 return nil, "Invalid JID: " .. raw_data
;
228 field_readers
["jid-multi"] =
229 function (field_tag
, required
)
232 for value_tag
in field_tag
:childtags("value") do
233 local raw_value
= value_tag
:get_text();
234 local value
= jid_prep(raw_value
);
235 result
[#result
+1] = value
;
236 if raw_value
and not value
then
237 err
[#err
+1] = ("Invalid JID: " .. raw_value
);
241 return result
, (#err
> 0 and t_concat(err
, "\n") or nil);
243 return nil, "Required value missing";
247 field_readers
["list-multi"] =
248 function (field_tag
, required
)
250 for value
in field_tag
:childtags("value") do
251 result
[#result
+1] = value
:get_text();
256 return nil, "Required value missing";
260 field_readers
["text-multi"] =
261 function (field_tag
, required
)
262 local data
, err
= field_readers
["list-multi"](field_tag
, required
);
264 data
= t_concat(data
, "\n");
269 field_readers
["list-single"] = simple_text
;
271 local boolean_values
= {
272 ["1"] = true, ["true"] = true,
273 ["0"] = false, ["false"] = false,
276 field_readers
["boolean"] =
277 function (field_tag
, required
)
278 local raw_value
, err
= simple_text(field_tag
, required
);
279 if not raw_value
then return raw_value
, err
; end
280 local value
= boolean_values
[raw_value
];
282 return nil, "Invalid boolean representation:" .. raw_value
;
287 field_readers
["hidden"] =
289 return field_tag
:get_child_text("value");
292 data_validators
["xs:integer"] =
294 local n
= tonumber(data
);
296 return false, "not a number";
297 elseif n
% 1 ~= 0 then
298 return false, "not an integer";
304 local function get_form_type(form
)
305 if not st
.is_stanza(form
) then
306 return nil, "not a stanza object";
307 elseif form
.attr
.xmlns
~= "jabber:x:data" or form
.name
~= "x" then
308 return nil, "not a dataform element";
310 for field
in form
:childtags("field") do
311 if field
.attr
.var
== "FORM_TYPE" then
312 return field
:get_child_text("value");
320 get_type
= get_form_type
;
329 title
= "MUC Configuration",
330 instructions
= [[Use this form to configure options for this MUC room.]],
332 { name
= "FORM_TYPE", type = "hidden", required
= true };
333 { name
= "field-name", type = "field-type", required
= false };