util.datamapper: Add 'unparse' for turning tables into XML
authorKim Alvefur <zash@zash.se>
Sun, 07 Mar 2021 00:57:36 +0100
changeset 11440 5df9ffc25bb4
parent 11439 a1fa6202fa13
child 11441 87a684df4b65
util.datamapper: Add 'unparse' for turning tables into XML
spec/util_datamapper_spec.lua
teal-src/util/datamapper.tl
util/datamapper.lua
--- a/spec/util_datamapper_spec.lua	Sun Mar 07 00:57:36 2021 +0100
+++ b/spec/util_datamapper_spec.lua	Sun Mar 07 00:57:36 2021 +0100
@@ -53,4 +53,16 @@
 			assert.same(d, map.parse(s, x));
 		end);
 	end);
+
+	describe("unparse", function()
+		it("works", function()
+			local u = map.unparse(s, d);
+			assert.equal("message", u.name);
+			assert.same(x.attr, u.attr);
+			assert.equal(#x.tags, #u.tags)
+			assert.equal(x:get_child_text("body"), u:get_child_text("body"));
+			assert.equal(x:get_child_text("delay", "urn:xmpp:delay"), u:get_child_text("delay", "urn:xmpp:delay"));
+			assert.same(x:get_child("delay", "urn:xmpp:delay").attr, u:get_child("delay", "urn:xmpp:delay").attr);
+		end);
+	end);
 end)
--- a/teal-src/util/datamapper.tl	Sun Mar 07 00:57:36 2021 +0100
+++ b/teal-src/util/datamapper.tl	Sun Mar 07 00:57:36 2021 +0100
@@ -94,7 +94,108 @@
 	end
 end
 
+local function unparse ( schema : js.schema_t, t : table, current_name : string, current_ns : string ) : st.stanza_t
+	if schema.type == "object" then
+
+		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
+			-- TODO prefix?
+		end
+
+		local out = st.stanza(current_name, { xmlns = current_ns })
+
+		for prop, propschema in pairs(schema.properties) do
+			local v = t[prop]
+
+			if v ~= nil then
+				local proptype : js.schema_t.type_e
+				if propschema is js.schema_t then
+					proptype = propschema.type
+				elseif propschema is js.schema_t.type_e then
+					proptype = propschema
+				end
+
+				local name = prop
+				local namespace = current_ns
+				local prefix : string = nil
+				local is_attribute = false
+				local is_text = false
+
+				if propschema is js.schema_t and propschema.xml then
+
+					if propschema.xml.name then
+						name = propschema.xml.name
+					end
+					if propschema.xml.namespace then
+						namespace = propschema.xml.namespace
+					end
+
+					if propschema.xml.prefix then
+						prefix = propschema.xml.prefix
+					end
+
+					if propschema.xml.attribute then
+						is_attribute = true
+					elseif propschema.xml.text then
+						is_text = true
+					end
+				end
+
+				if is_attribute then
+					local attr = name
+					if prefix then
+						attr = prefix .. ':' .. name
+					elseif namespace ~= current_ns then
+						attr = namespace .. "\1" .. name
+					end
+
+					if proptype == "string" and v is string then
+						out.attr[attr] = v
+					elseif proptype == "number" and v is number then
+						out.attr[attr] = string.format("%g", v)
+					elseif proptype == "integer" and v is number then
+						out.attr[attr] = string.format("%d", v)
+					elseif proptype == "boolean" then
+						out.attr[attr] = v and "1" or "0"
+					end
+				elseif is_text then
+					if v is string then
+						out:text(v)
+					end
+				else
+					local propattr : { string : string }
+					if namespace ~= current_ns then
+						propattr = { xmlns = namespace }
+					end
+					if proptype == "string" and v is string then
+						out:text_tag(name, v, propattr)
+					elseif proptype == "number" and v is number then
+						out:text_tag(name, string.format("%g", v), propattr)
+					elseif proptype == "integer" and v is number then
+						out:text_tag(name, string.format("%d", v), propattr)
+					elseif proptype == "boolean" and v is boolean then
+						out:text_tag(name, v and "1" or "0", propattr)
+					elseif proptype == "object" and propschema is js.schema_t and v is table then
+						local c = unparse(propschema, v, name, namespace);
+						if c then
+							out:add_direct_child(c);
+						end
+					-- else TODO
+					end
+				end
+			end
+		end
+		return out;
+
+	end
+end
+
 return {
 	parse = parse,
-	-- unparse = unparse, -- TODO
+	unparse = unparse,
 }
--- a/util/datamapper.lua	Sun Mar 07 00:57:36 2021 +0100
+++ b/util/datamapper.lua	Sun Mar 07 00:57:36 2021 +0100
@@ -93,4 +93,105 @@
 	end
 end
 
-return {parse = parse}
+local function unparse(schema, t, current_name, current_ns)
+	if schema.type == "object" then
+
+		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 = st.stanza(current_name, {xmlns = current_ns})
+
+		for prop, propschema in pairs(schema.properties) do
+			local v = t[prop]
+
+			if v ~= nil then
+				local proptype
+				if type(propschema) == "table" then
+					proptype = propschema.type
+				elseif type(propschema) == "string" then
+					proptype = propschema
+				end
+
+				local name = prop
+				local namespace = current_ns
+				local prefix = nil
+				local is_attribute = false
+				local is_text = false
+
+				if type(propschema) == "table" and propschema.xml then
+
+					if propschema.xml.name then
+						name = propschema.xml.name
+					end
+					if propschema.xml.namespace then
+						namespace = propschema.xml.namespace
+					end
+
+					if propschema.xml.prefix then
+						prefix = propschema.xml.prefix
+					end
+
+					if propschema.xml.attribute then
+						is_attribute = true
+					elseif propschema.xml.text then
+						is_text = true
+					end
+				end
+
+				if is_attribute then
+					local attr = name
+					if prefix then
+						attr = prefix .. ":" .. name
+					elseif namespace ~= current_ns then
+						attr = namespace .. "\1" .. name
+					end
+
+					if proptype == "string" and type(v) == "string" then
+						out.attr[attr] = v
+					elseif proptype == "number" and type(v) == "number" then
+						out.attr[attr] = string.format("%g", v)
+					elseif proptype == "integer" and type(v) == "number" then
+						out.attr[attr] = string.format("%d", v)
+					elseif proptype == "boolean" then
+						out.attr[attr] = v and "1" or "0"
+					end
+				elseif is_text then
+					if type(v) == "string" then
+						out:text(v)
+					end
+				else
+					local propattr
+					if namespace ~= current_ns then
+						propattr = {xmlns = namespace}
+					end
+					if proptype == "string" and type(v) == "string" then
+						out:text_tag(name, v, propattr)
+					elseif proptype == "number" and type(v) == "number" then
+						out:text_tag(name, string.format("%g", v), propattr)
+					elseif proptype == "integer" and type(v) == "number" then
+						out:text_tag(name, string.format("%d", v), propattr)
+					elseif proptype == "boolean" and type(v) == "boolean" then
+						out:text_tag(name, v and "1" or "0", propattr)
+					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
+
+					end
+				end
+			end
+		end
+		return out
+
+	end
+end
+
+return {parse = parse; unparse = unparse}