diff options
Diffstat (limited to 'util/datamapper.lua')
-rw-r--r-- | util/datamapper.lua | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/util/datamapper.lua b/util/datamapper.lua new file mode 100644 index 00000000..00c75a25 --- /dev/null +++ b/util/datamapper.lua @@ -0,0 +1,311 @@ +local st = require("util.stanza"); + +local schema_t = {} + +local function toboolean(s) + if s == "true" or s == "1" then + return true + elseif s == "false" or s == "0" then + return false + elseif s then + return true + end +end + +local function totype(t, s) + if not s then + return nil + end + if t == "string" then + return s + elseif t == "boolean" then + return toboolean(s) + elseif t == "number" or t == "integer" then + return tonumber(s) + end +end + +local value_goes = {} + +local function unpack_propschema(propschema, propname, current_ns) + + local proptype = "string" + local value_where = propname and "in_text_tag" or "in_text" + local name = propname + local namespace + local prefix + local single_attribute + local enums + + if type(propschema) == "table" then + proptype = propschema.type + elseif type(propschema) == "string" then + proptype = propschema + end + + if proptype == "object" or proptype == "array" then + value_where = "in_children" + end + + if type(propschema) == "table" then + local xml = propschema.xml + if xml then + if xml.name then + name = xml.name + end + if xml.namespace and xml.namespace ~= current_ns then + namespace = xml.namespace + end + if xml.prefix then + prefix = xml.prefix + end + if proptype == "array" and xml.wrapped then + value_where = "in_wrapper" + elseif xml.attribute then + value_where = "in_attribute" + elseif xml.text then + value_where = "in_text" + elseif xml.x_name_is_value then + value_where = "in_tag_name" + elseif xml.x_single_attribute then + single_attribute = xml.x_single_attribute + value_where = "in_single_attribute" + end + end + if propschema["const"] then + enums = {propschema["const"]} + elseif propschema["enum"] then + enums = propschema["enum"] + end + end + + return proptype, value_where, name, namespace, prefix, single_attribute, enums +end + +local parse_object +local parse_array + +local function extract_value(s, value_where, proptype, name, namespace, prefix, single_attribute, enums) + if value_where == "in_tag_name" then + local c + if proptype == "boolean" then + c = s:get_child(name, namespace); + elseif enums and proptype == "string" then + + for i = 1, #enums do + c = s:get_child(enums[i], namespace); + if c then + break + end + end + else + c = s:get_child(nil, namespace); + end + if c then + return c.name + end + elseif value_where == "in_attribute" then + local attr = name + if prefix then + attr = prefix .. ":" .. name + elseif namespace and namespace ~= s.attr.xmlns then + attr = namespace .. "\1" .. name + end + return s.attr[attr] + + elseif value_where == "in_text" then + return s:get_text() + + elseif value_where == "in_single_attribute" then + local c = s:get_child(name, namespace) + return c and c.attr[single_attribute] + elseif value_where == "in_text_tag" then + return s:get_child_text(name, namespace) + end +end + +function parse_object(schema, s) + local out = {} + if type(schema) == "table" and schema.properties then + for prop, propschema in pairs(schema.properties) do + + local proptype, value_where, name, namespace, prefix, single_attribute, enums = unpack_propschema(propschema, prop, s.attr.xmlns) + + if value_where == "in_children" and type(propschema) == "table" then + if proptype == "object" then + local c = s:get_child(name, namespace) + if c then + out[prop] = parse_object(propschema, c); + end + elseif proptype == "array" then + local a = parse_array(propschema, s); + if a and a[1] ~= nil then + out[prop] = a; + end + else + error("unreachable") + end + elseif value_where == "in_wrapper" and type(propschema) == "table" and proptype == "array" then + local wrapper = s:get_child(name, namespace); + if wrapper then + out[prop] = parse_array(propschema, wrapper); + end + else + local value = extract_value(s, value_where, proptype, name, namespace, prefix, single_attribute, enums) + + out[prop] = totype(proptype, value) + end + end + end + + return out +end + +function parse_array(schema, s) + local itemschema = schema.items; + local proptype, value_where, child_name, namespace, prefix, single_attribute, enums = unpack_propschema(itemschema, nil, s.attr.xmlns) + local attr_name + if value_where == "in_single_attribute" then + value_where = "in_attribute"; + attr_name = single_attribute; + end + local out = {} + + if proptype == "object" then + if type(itemschema) == "table" then + for c in s:childtags(child_name, namespace) do + table.insert(out, parse_object(itemschema, c)); + end + else + error("array items must be schema object") + end + elseif proptype == "array" then + if type(itemschema) == "table" then + for c in s:childtags(child_name, namespace) do + table.insert(out, parse_array(itemschema, c)); + end + end + else + for c in s:childtags(child_name, namespace) do + local value = extract_value(c, value_where, proptype, attr_name or child_name, namespace, prefix, single_attribute, enums) + + table.insert(out, totype(proptype, value)); + end + end + return out +end + +local function parse(schema, s) + if schema.type == "object" then + return parse_object(schema, s) + elseif schema.type == "array" then + return parse_array(schema, s) + else + error("top-level scalars unsupported") + end +end + +local function toxmlstring(proptype, v) + if proptype == "string" and type(v) == "string" then + return v + elseif proptype == "number" and type(v) == "number" then + return string.format("%g", v) + elseif proptype == "integer" and type(v) == "number" then + return string.format("%d", v) + elseif proptype == "boolean" then + return v and "1" or "0" + end +end + +local unparse + +local function unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, single_attribute) + if value_where == "in_attribute" then + local attr = name + if prefix then + attr = prefix .. ":" .. name + elseif namespace and namespace ~= current_ns then + attr = namespace .. "\1" .. name + end + + out.attr[attr] = toxmlstring(proptype, v) + elseif value_where == "in_text" then + out:text(toxmlstring(proptype, v)) + elseif value_where == "in_single_attribute" then + assert(single_attribute) + local propattr = {} + + if namespace and namespace ~= current_ns then + propattr.xmlns = namespace + end + + propattr[single_attribute] = toxmlstring(proptype, v) + out:tag(name, propattr):up(); + + else + local propattr + if namespace ~= current_ns then + propattr = {xmlns = namespace} + end + if value_where == "in_tag_name" then + if proptype == "string" and type(v) == "string" then + out:tag(v, propattr):up(); + elseif proptype == "boolean" and v == true then + out:tag(name, propattr):up(); + end + elseif proptype == "object" and type(propschema) == "table" and type(v) == "table" then + local c = unparse(propschema, v, name, namespace); + if c then + out:add_direct_child(c); + end + elseif proptype == "array" and type(propschema) == "table" and type(v) == "table" then + if value_where == "in_wrapper" then + local c = unparse(propschema, v, name, namespace); + if c then + out:add_direct_child(c); + end + else + unparse(propschema, v, name, namespace, out); + end + else + out:text_tag(name, toxmlstring(proptype, v), propattr) + end + end +end + +function unparse(schema, t, current_name, current_ns, ctx) + + if schema.xml then + if schema.xml.name then + current_name = schema.xml.name + end + if schema.xml.namespace then + current_ns = schema.xml.namespace + end + + end + + local out = ctx or st.stanza(current_name, {xmlns = current_ns}) + + if schema.type == "object" then + + for prop, propschema in pairs(schema.properties) do + local v = t[prop] + + if v ~= nil then + local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(propschema, prop, current_ns) + unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, single_attribute) + end + end + return out + + elseif schema.type == "array" then + local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(schema.items, current_name, current_ns) + for _, item in ipairs(t) do + unparse_property(out, item, proptype, schema.items, value_where, name, namespace, current_ns, prefix, single_attribute) + end + return out + end +end + +return {parse = parse; unparse = unparse} |