util.dataforms: Allow separation of options from values in list fields
[prosody.git] / util / dataforms.lua
index b2988ae7e5a99df56d6c86d80bcf024086f31cbd..685f3aaf298b2f1f5975f5f85bc0f935320ec263 100644 (file)
@@ -7,20 +7,20 @@
 --
 
 local setmetatable = setmetatable;
-local pairs, ipairs = pairs, ipairs;
+local ipairs = ipairs;
 local tostring, type, next = tostring, type, next;
 local t_concat = table.concat;
 local st = require "util.stanza";
 local jid_prep = require "util.jid".prep;
 
-module "dataforms"
+local _ENV = nil;
 
 local xmlns_forms = 'jabber:x:data';
 
 local form_t = {};
 local form_mt = { __index = form_t };
 
-function new(layout)
+local function new(layout)
        return setmetatable(layout, form_mt);
 end
 
@@ -32,7 +32,7 @@ function form_t.form(layout, data, formtype)
        if layout.instructions then
                form:tag("instructions"):text(layout.instructions):up();
        end
-       for n, field in ipairs(layout) do
+       for _, field in ipairs(layout) do
                local field_type = field.type or "text-single";
                -- Add field tag
                form:tag("field", { type = field_type, var = field.name, label = field.label });
@@ -69,10 +69,10 @@ function form_t.form(layout, data, formtype)
                                end
                        elseif field_type == "list-single" then
                                local has_default = false;
-                               for _, val in ipairs(value) do
+                               for _, val in ipairs(field.options or value) do
                                        if type(val) == "table" then
                                                form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up();
-                                               if val.default and (not has_default) then
+                                               if value == val.value or field.options and val.default and (not has_default) then
                                                        form:tag("value"):text(val.value):up();
                                                        has_default = true;
                                                end
@@ -80,17 +80,25 @@ function form_t.form(layout, data, formtype)
                                                form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up();
                                        end
                                end
+                               if field.options and value then
+                                       form:tag("value"):text(value):up();
+                               end
                        elseif field_type == "list-multi" then
-                               for _, val in ipairs(value) do
+                               for _, val in ipairs(field.options or value) do
                                        if type(val) == "table" then
                                                form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up();
-                                               if val.default then
+                                               if not field.options and val.default then
                                                        form:tag("value"):text(val.value):up();
                                                end
                                        else
                                                form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up();
                                        end
                                end
+                               if field.options and value then
+                                       for _, val in ipairs(value) do
+                                               form:tag("value"):text(val):up();
+                                       end
+                               end
                        end
                end
 
@@ -118,6 +126,7 @@ local field_readers = {};
 function form_t.data(layout, stanza)
        local data = {};
        local errors = {};
+       local present = {};
 
        for _, field in ipairs(layout) do
                local tag;
@@ -133,6 +142,7 @@ function form_t.data(layout, stanza)
                                errors[field.name] = "Required value missing";
                        end
                else
+                       present[field.name] = true;
                        local reader = field_readers[field.type];
                        if reader then
                                data[field.name], errors[field.name] = reader(tag, field.required);
@@ -140,35 +150,34 @@ function form_t.data(layout, stanza)
                end
        end
        if next(errors) then
-               return data, errors;
+               return data, errors, present;
        end
-       return data;
+       return data, nil, present;
 end
 
-field_readers["text-single"] =
-       function (field_tag, required)
-               local data = field_tag:get_child_text("value");
-               if data and #data > 0 then
-                       return data
-               elseif required then
-                       return nil, "Required value missing";
-               end
+local function simple_text(field_tag, required)
+       local data = field_tag:get_child_text("value");
+       -- XEP-0004 does not say if an empty string is acceptable for a required value
+       -- so we will follow HTML5 which says that empty string means missing
+       if required and (data == nil or data == "") then
+               return nil, "Required value missing";
        end
+       return data; -- Return whatever get_child_text returned, even if empty string
+end
+
+field_readers["text-single"] = simple_text;
 
-field_readers["text-private"] =
-       field_readers["text-single"];
+field_readers["text-private"] = simple_text;
 
 field_readers["jid-single"] =
        function (field_tag, required)
-               local raw_data = field_tag:get_child_text("value")
+               local raw_data, err = simple_text(field_tag, required);
+               if not raw_data then return raw_data, err; end
                local data = jid_prep(raw_data);
-               if data and #data > 0 then
-                       return data
-               elseif raw_data then
+               if not data then
                        return nil, "Invalid JID: " .. raw_data;
-               elseif required then
-                       return nil, "Required value missing";
                end
+               return data;
        end
 
 field_readers["jid-multi"] =
@@ -212,8 +221,7 @@ field_readers["text-multi"] =
                return data, err;
        end
 
-field_readers["list-single"] =
-       field_readers["text-single"];
+field_readers["list-single"] = simple_text;
 
 local boolean_values = {
        ["1"] = true, ["true"] = true,
@@ -222,15 +230,13 @@ local boolean_values = {
 
 field_readers["boolean"] =
        function (field_tag, required)
-               local raw_value = field_tag:get_child_text("value");
-               local value = boolean_values[raw_value ~= nil and raw_value];
-               if value ~= nil then
-                       return value;
-               elseif raw_value then
-                       return nil, "Invalid boolean representation";
-               elseif required then
-                       return nil, "Required value missing";
+               local raw_value, err = simple_text(field_tag, required);
+               if not raw_value then return raw_value, err; end
+               local value = boolean_values[raw_value];
+               if value == nil then
+                       return nil, "Invalid boolean representation:" .. raw_value;
                end
+               return value;
        end
 
 field_readers["hidden"] =
@@ -238,7 +244,9 @@ field_readers["hidden"] =
                return field_tag:get_child_text("value");
        end
 
-return _M;
+return {
+       new = new;
+};
 
 
 --[=[