util/datamapper.lua
changeset 12137 11060c8919b6
parent 11485 b2f9782497dd
child 12584 a9dbf657c894
equal deleted inserted replaced
12136:4ff0d33dfb2b 12137:11060c8919b6
     1 local st = require("util.stanza");
     1 local st = require("util.stanza");
       
     2 local pointer = require("util.jsonpointer");
     2 
     3 
     3 local schema_t = {}
     4 local schema_t = {}
     4 
     5 
     5 local function toboolean(s)
     6 local function toboolean(s)
     6 	if s == "true" or s == "1" then
     7 	if s == "true" or s == "1" then
    24 		return tonumber(s)
    25 		return tonumber(s)
    25 	end
    26 	end
    26 end
    27 end
    27 
    28 
    28 local value_goes = {}
    29 local value_goes = {}
       
    30 
       
    31 local function resolve_schema(schema, root)
       
    32 	if type(schema) == "table" and schema["$ref"] and schema["$ref"]:sub(1, 1) == "#" then
       
    33 		local referenced = pointer.resolve(root, schema["$ref"]:sub(2));
       
    34 		if referenced ~= nil then
       
    35 			return referenced
       
    36 		end
       
    37 	end
       
    38 	return schema
       
    39 end
    29 
    40 
    30 local function unpack_propschema(propschema, propname, current_ns)
    41 local function unpack_propschema(propschema, propname, current_ns)
    31 
    42 
    32 	local proptype = "string"
    43 	local proptype = "string"
    33 	local value_where = propname and "in_text_tag" or "in_text"
    44 	local value_where = propname and "in_text_tag" or "in_text"
   122 	elseif value_where == "in_text_tag" then
   133 	elseif value_where == "in_text_tag" then
   123 		return s:get_child_text(name, namespace)
   134 		return s:get_child_text(name, namespace)
   124 	end
   135 	end
   125 end
   136 end
   126 
   137 
   127 function parse_object(schema, s)
   138 function parse_object(schema, s, root)
   128 	local out = {}
   139 	local out = {}
       
   140 	schema = resolve_schema(schema, root)
   129 	if type(schema) == "table" and schema.properties then
   141 	if type(schema) == "table" and schema.properties then
   130 		for prop, propschema in pairs(schema.properties) do
   142 		for prop, propschema in pairs(schema.properties) do
       
   143 			propschema = resolve_schema(propschema, root)
   131 
   144 
   132 			local proptype, value_where, name, namespace, prefix, single_attribute, enums = unpack_propschema(propschema, prop, s.attr.xmlns)
   145 			local proptype, value_where, name, namespace, prefix, single_attribute, enums = unpack_propschema(propschema, prop, s.attr.xmlns)
   133 
   146 
   134 			if value_where == "in_children" and type(propschema) == "table" then
   147 			if value_where == "in_children" and type(propschema) == "table" then
   135 				if proptype == "object" then
   148 				if proptype == "object" then
   136 					local c = s:get_child(name, namespace)
   149 					local c = s:get_child(name, namespace)
   137 					if c then
   150 					if c then
   138 						out[prop] = parse_object(propschema, c);
   151 						out[prop] = parse_object(propschema, c, root);
   139 					end
   152 					end
   140 				elseif proptype == "array" then
   153 				elseif proptype == "array" then
   141 					local a = parse_array(propschema, s);
   154 					local a = parse_array(propschema, s, root);
   142 					if a and a[1] ~= nil then
   155 					if a and a[1] ~= nil then
   143 						out[prop] = a;
   156 						out[prop] = a;
   144 					end
   157 					end
   145 				else
   158 				else
   146 					error("unreachable")
   159 					error("unreachable")
   147 				end
   160 				end
   148 			elseif value_where == "in_wrapper" and type(propschema) == "table" and proptype == "array" then
   161 			elseif value_where == "in_wrapper" and type(propschema) == "table" and proptype == "array" then
   149 				local wrapper = s:get_child(name, namespace);
   162 				local wrapper = s:get_child(name, namespace);
   150 				if wrapper then
   163 				if wrapper then
   151 					out[prop] = parse_array(propschema, wrapper);
   164 					out[prop] = parse_array(propschema, wrapper, root);
   152 				end
   165 				end
   153 			else
   166 			else
   154 				local value = extract_value(s, value_where, proptype, name, namespace, prefix, single_attribute, enums)
   167 				local value = extract_value(s, value_where, proptype, name, namespace, prefix, single_attribute, enums)
   155 
   168 
   156 				out[prop] = totype(proptype, value)
   169 				out[prop] = totype(proptype, value)
   159 	end
   172 	end
   160 
   173 
   161 	return out
   174 	return out
   162 end
   175 end
   163 
   176 
   164 function parse_array(schema, s)
   177 function parse_array(schema, s, root)
   165 	local itemschema = schema.items;
   178 	local itemschema = resolve_schema(schema.items, root);
   166 	local proptype, value_where, child_name, namespace, prefix, single_attribute, enums = unpack_propschema(itemschema, nil, s.attr.xmlns)
   179 	local proptype, value_where, child_name, namespace, prefix, single_attribute, enums = unpack_propschema(itemschema, nil, s.attr.xmlns)
   167 	local attr_name
   180 	local attr_name
   168 	if value_where == "in_single_attribute" then
   181 	if value_where == "in_single_attribute" then
   169 		value_where = "in_attribute";
   182 		value_where = "in_attribute";
   170 		attr_name = single_attribute;
   183 		attr_name = single_attribute;
   172 	local out = {}
   185 	local out = {}
   173 
   186 
   174 	if proptype == "object" then
   187 	if proptype == "object" then
   175 		if type(itemschema) == "table" then
   188 		if type(itemschema) == "table" then
   176 			for c in s:childtags(child_name, namespace) do
   189 			for c in s:childtags(child_name, namespace) do
   177 				table.insert(out, parse_object(itemschema, c));
   190 				table.insert(out, parse_object(itemschema, c, root));
   178 			end
   191 			end
   179 		else
   192 		else
   180 			error("array items must be schema object")
   193 			error("array items must be schema object")
   181 		end
   194 		end
   182 	elseif proptype == "array" then
   195 	elseif proptype == "array" then
   183 		if type(itemschema) == "table" then
   196 		if type(itemschema) == "table" then
   184 			for c in s:childtags(child_name, namespace) do
   197 			for c in s:childtags(child_name, namespace) do
   185 				table.insert(out, parse_array(itemschema, c));
   198 				table.insert(out, parse_array(itemschema, c, root));
   186 			end
   199 			end
   187 		end
   200 		end
   188 	else
   201 	else
   189 		for c in s:childtags(child_name, namespace) do
   202 		for c in s:childtags(child_name, namespace) do
   190 			local value = extract_value(c, value_where, proptype, attr_name or child_name, namespace, prefix, single_attribute, enums)
   203 			local value = extract_value(c, value_where, proptype, attr_name or child_name, namespace, prefix, single_attribute, enums)
   195 	return out
   208 	return out
   196 end
   209 end
   197 
   210 
   198 local function parse(schema, s)
   211 local function parse(schema, s)
   199 	if schema.type == "object" then
   212 	if schema.type == "object" then
   200 		return parse_object(schema, s)
   213 		return parse_object(schema, s, schema)
   201 	elseif schema.type == "array" then
   214 	elseif schema.type == "array" then
   202 		return parse_array(schema, s)
   215 		return parse_array(schema, s, schema)
   203 	else
   216 	else
   204 		error("top-level scalars unsupported")
   217 		error("top-level scalars unsupported")
   205 	end
   218 	end
   206 end
   219 end
   207 
   220 
   217 	end
   230 	end
   218 end
   231 end
   219 
   232 
   220 local unparse
   233 local unparse
   221 
   234 
   222 local function unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, single_attribute)
   235 local function unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix,
       
   236 	single_attribute, root)
       
   237 
   223 	if value_where == "in_attribute" then
   238 	if value_where == "in_attribute" then
   224 		local attr = name
   239 		local attr = name
   225 		if prefix then
   240 		if prefix then
   226 			attr = prefix .. ":" .. name
   241 			attr = prefix .. ":" .. name
   227 		elseif namespace and namespace ~= current_ns then
   242 		elseif namespace and namespace ~= current_ns then
   252 				out:tag(v, propattr):up();
   267 				out:tag(v, propattr):up();
   253 			elseif proptype == "boolean" and v == true then
   268 			elseif proptype == "boolean" and v == true then
   254 				out:tag(name, propattr):up();
   269 				out:tag(name, propattr):up();
   255 			end
   270 			end
   256 		elseif proptype == "object" and type(propschema) == "table" and type(v) == "table" then
   271 		elseif proptype == "object" and type(propschema) == "table" and type(v) == "table" then
   257 			local c = unparse(propschema, v, name, namespace);
   272 			local c = unparse(propschema, v, name, namespace, nil, root);
   258 			if c then
   273 			if c then
   259 				out:add_direct_child(c);
   274 				out:add_direct_child(c);
   260 			end
   275 			end
   261 		elseif proptype == "array" and type(propschema) == "table" and type(v) == "table" then
   276 		elseif proptype == "array" and type(propschema) == "table" and type(v) == "table" then
   262 			if value_where == "in_wrapper" then
   277 			if value_where == "in_wrapper" then
   263 				local c = unparse(propschema, v, name, namespace);
   278 				local c = unparse(propschema, v, name, namespace, nil, root);
   264 				if c then
   279 				if c then
   265 					out:add_direct_child(c);
   280 					out:add_direct_child(c);
   266 				end
   281 				end
   267 			else
   282 			else
   268 				unparse(propschema, v, name, namespace, out);
   283 				unparse(propschema, v, name, namespace, out, root);
   269 			end
   284 			end
   270 		else
   285 		else
   271 			out:text_tag(name, toxmlstring(proptype, v), propattr)
   286 			out:text_tag(name, toxmlstring(proptype, v), propattr)
   272 		end
   287 		end
   273 	end
   288 	end
   274 end
   289 end
   275 
   290 
   276 function unparse(schema, t, current_name, current_ns, ctx)
   291 function unparse(schema, t, current_name, current_ns, ctx, root)
       
   292 
       
   293 	if root == nil then
       
   294 		root = schema
       
   295 	end
   277 
   296 
   278 	if schema.xml then
   297 	if schema.xml then
   279 		if schema.xml.name then
   298 		if schema.xml.name then
   280 			current_name = schema.xml.name
   299 			current_name = schema.xml.name
   281 		end
   300 		end
   288 	local out = ctx or st.stanza(current_name, {xmlns = current_ns})
   307 	local out = ctx or st.stanza(current_name, {xmlns = current_ns})
   289 
   308 
   290 	if schema.type == "object" then
   309 	if schema.type == "object" then
   291 
   310 
   292 		for prop, propschema in pairs(schema.properties) do
   311 		for prop, propschema in pairs(schema.properties) do
       
   312 			propschema = resolve_schema(propschema, root)
   293 			local v = t[prop]
   313 			local v = t[prop]
   294 
   314 
   295 			if v ~= nil then
   315 			if v ~= nil then
   296 				local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(propschema, prop, current_ns)
   316 				local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(propschema, prop, current_ns)
   297 				unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, single_attribute)
   317 				unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, single_attribute, root)
   298 			end
   318 			end
   299 		end
   319 		end
   300 		return out
   320 		return out
   301 
   321 
   302 	elseif schema.type == "array" then
   322 	elseif schema.type == "array" then
   303 		local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(schema.items, current_name, current_ns)
   323 		local itemschema = resolve_schema(schema.items, root)
       
   324 		local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(itemschema, current_name, current_ns)
   304 		for _, item in ipairs(t) do
   325 		for _, item in ipairs(t) do
   305 			unparse_property(out, item, proptype, schema.items, value_where, name, namespace, current_ns, prefix, single_attribute)
   326 			unparse_property(out, item, proptype, itemschema, value_where, name, namespace, current_ns, prefix, single_attribute, root)
   306 		end
   327 		end
   307 		return out
   328 		return out
   308 	end
   329 	end
   309 end
   330 end
   310 
   331