259
edits
(Update package: OSW Core) |
(Update package: OSW Core) |
||
(18 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
-- mw.logObject(p.processJsondata({jsondata=p.loadJson({title="Item:OSW7d7193567ea14e4e89b74de88983b718", slot="jsondata"}).json, debug=true, mode="header"})) | |||
local lustache = require("Module:Lustache") | local lustache = require("Module:Lustache") | ||
Line 5: | Line 7: | ||
p.keys = { --jsonschema / json-ld keys | p.keys = { --jsonschema / json-ld keys | ||
category='type', | category='type', | ||
category_pseudoproperty='Category', -- Property:Category | |||
subcategory='subclass_of', | subcategory='subclass_of', | ||
schema_type='schema_type', | schema_type='schema_type', | ||
Line 20: | Line 23: | ||
} | } | ||
p.slots = { --slot names | p.slots = { --slot names | ||
main='main', | |||
jsondata='jsondata', | jsondata='jsondata', | ||
jsonschema='jsonschema', | jsonschema='jsonschema', | ||
Line 31: | Line 35: | ||
query='query' | query='query' | ||
} | } | ||
p.cache = {} | |||
--loads json from a wiki page | --loads json from a wiki page | ||
Line 37: | Line 43: | ||
function p.loadJson(args) | function p.loadJson(args) | ||
local page_title = p.defaultArg(args.title, "JsonSchema:Entity") --for testing | local page_title = p.defaultArg(args.title, "JsonSchema:Entity") --for testing | ||
local slot = p.defaultArg(args.slot, | local slot = p.defaultArg(args.slot, 'main') | ||
local debug = p.defaultArg(args.debug, nil) | local debug = p.defaultArg(args.debug, nil) | ||
local msg = "" | local msg = "" | ||
Line 43: | Line 49: | ||
local json = {} | local json = {} | ||
if (slot == | if p.cache[page_title] ~= nil then | ||
if p.cache[page_title][slot] ~= nil then | |||
if (debug) then msg = msg .. "Fetch slot " .. p.slots.jsondata .. " of page " .. page_title .. " from cache <br>" end | |||
json = p.cache[page_title][slot] | |||
return {json=json, debug_msg=msg} | |||
end | |||
else p.cache[page_title] = {} | |||
end | |||
if (slot == 'main') then | |||
--json = mw.loadJsonData( "JsonSchema:Entity" ) --requires MediaWiki 1.39 | --json = mw.loadJsonData( "JsonSchema:Entity" ) --requires MediaWiki 1.39 | ||
local page = mw.title.makeTitle(p.splitString(page_title, ':')[1], p.splitString(page_title, ':')[2]) | local page = mw.title.makeTitle(p.splitString(page_title, ':')[1], p.splitString(page_title, ':')[2]) | ||
Line 49: | Line 64: | ||
if (text ~= nil) then json = mw.text.jsonDecode(text) end | if (text ~= nil) then json = mw.text.jsonDecode(text) end | ||
else | else | ||
if (debug) then msg = msg .. "Fetch slot " .. p.slots.jsondata .. " | if (debug) then msg = msg .. "Fetch slot " .. p.slots.jsondata .. " of page " .. page_title .. "<br>" end | ||
local text = mw.slots.slotContent( slot , page_title ) | local text = mw.slots.slotContent( slot , page_title ) | ||
if (text ~= nil) then json = mw.text.jsonDecode(text) end | if (text ~= nil) then json = mw.text.jsonDecode(text) end | ||
Line 55: | Line 70: | ||
--mw.logObject(json) | --mw.logObject(json) | ||
p.cache[page_title][slot] = json | |||
return {json=json, debug_msg=msg} | return {json=json, debug_msg=msg} | ||
end | end | ||
-- test: mw.logObject(p.walkJsonSchema({jsonschema=p.loadJson({title="Category:Hardware", slot="jsonschema"}).json, debug=true}).jsonschema) | -- test: mw.logObject(p.walkJsonSchema({jsonschema=p.loadJson({title="Category:Hardware", slot="jsonschema"}).json, debug=true}).jsonschema) | ||
Line 67: | Line 84: | ||
local mode = p.defaultArg(args.mode, p.mode.header) | local mode = p.defaultArg(args.mode, p.mode.header) | ||
--local merged_jsonschema = p.defaultArg(args.merged_jsonschema, {}) | --local merged_jsonschema = p.defaultArg(args.merged_jsonschema, {}) | ||
local template = p.defaultArg(args.template, nil) | |||
local templates = p.defaultArg(args.templates, {}) | local templates = p.defaultArg(args.templates, {}) | ||
local recursive = p.defaultArg(args.recursive, true) | local recursive = p.defaultArg(args.recursive, true) | ||
Line 78: | Line 96: | ||
if (mode == p.mode.header) then category_template_slot = p.slots.header_template end | if (mode == p.mode.header) then category_template_slot = p.slots.header_template end | ||
if (categories == nil) then categories = p.getCategories({jsonschema=jsonschema, includeNamespace=true}).categories end | if (categories == nil) then categories = p.getCategories({jsonschema=jsonschema, includeNamespace=true, includeSchemas=true}).categories end | ||
if (type(categories) ~= 'table') then categories = {categories} end | if (type(categories) ~= 'table') then categories = {categories} end | ||
if (debug) then msg = msg .. "Supercategories: " .. mw.dumpObject(categories) .. "\n<br>" end | if (debug) then msg = msg .. "Supercategories: " .. mw.dumpObject(categories) .. "\n<br>" end | ||
Line 85: | Line 103: | ||
--mw.logObject("Visit " .. category) | --mw.logObject("Visit " .. category) | ||
if (debug) then msg = msg .. "Fetch slot " .. p.slots.jsonschema .. " from page " .. category .. "\n<br>" end | if (debug) then msg = msg .. "Fetch slot " .. p.slots.jsonschema .. " from page " .. category .. "\n<br>" end | ||
local | local super_jsonschema = nil | ||
if ( | if p.splitString(category, ':')[1] == "JsonSchema" then super_jsonschema = p.loadJson({title=category, slot=p.slots.main}).json | ||
else super_jsonschema = p.loadJson({title=category, slot=p.slots.jsonschema}).json end | |||
if (super_jsonschema ~= nil) then | |||
if (recursive) then | if (recursive) then | ||
local res = p.walkJsonSchema({jsonschema=super_jsonschema, jsonschemas=jsonschemas, templates=templates, mode=mode, visited=visited, root=false}) | local res = p.walkJsonSchema({jsonschema=super_jsonschema, jsonschemas=jsonschemas, templates=templates, mode=mode, visited=visited, root=false}) | ||
Line 96: | Line 115: | ||
--mw.logObject("Store " .. category) | --mw.logObject("Store " .. category) | ||
table.insert(visited, category) | table.insert(visited, category) | ||
jsonschemas[category] = | |||
jsonschemas[category] = p.copy( super_jsonschema ) --keep a copy of the schema, super_jsonschema passed by references gets modified | |||
--jsonschema = p.tableMerge(jsonschema, super_jsonschema) --merge superschema is done by the caller | --jsonschema = p.tableMerge(jsonschema, super_jsonschema) --merge superschema is done by the caller | ||
end | end | ||
Line 105: | Line 125: | ||
end | end | ||
if (root) then | if (root) then | ||
for i, category in ipairs(visited) do | for i, category in ipairs(visited) do | ||
jsonschema = p.tableMerge(jsonschema, jsonschemas[category]) | --merge all schemas. we need to make a copy here, otherwise jsonschemas["Category:Entity"] contains the merged schema | ||
jsonschema = p.copy(p.tableMerge(jsonschema, jsonschemas[category])) | |||
end | end | ||
end | end | ||
Line 134: | Line 154: | ||
local msg = "" | local msg = "" | ||
local res = p.defaultArg(args.jsondata, "") | local res = p.defaultArg(args.jsondata, "") | ||
local root = p.defaultArg(args.root, true) -- first entry into recursion | |||
for k,v in pairs(jsondata) do | for k,v in pairs(jsondata) do | ||
Line 149: | Line 170: | ||
-- mustache can handle objects and array to we can parse it directly | -- mustache can handle objects and array to we can parse it directly | ||
-- todo: handle nested templates | -- todo: handle nested templates | ||
if (debug) then msg = msg .. "Parse mustache template " .. eval_template.value .. " with params " .. mw.dumpObject( | local template_param = {[k]=v} | ||
jsondata[k] = lustache:render(eval_template.value, { | if (eval_template.root_key == false) then template_param = v end | ||
if (debug) then msg = msg .. "Parse mustache template " .. eval_template.value .. " with params " .. mw.dumpObject( template_param ) .. "\n<br>" end | |||
jsondata[k] = lustache:render(eval_template.value, template_param, p.tableMerge({self=eval_template.value}, eval_template.partials)) -- render with self as registered partial for recursion | |||
if (eval_template.type == "mustache-wikitext") then | if (eval_template.type == "mustache-wikitext") then | ||
jsondata[k] = frame:preprocess( jsondata[k] ) | jsondata[k] = frame:preprocess( jsondata[k] ) | ||
end | end | ||
elseif type(v) == 'table' then | elseif type(v) == 'table' then | ||
if (v[1] == nil) then --key value array = object/dict | if (p.tableLength(v) > 0 and v[1] == nil) then --key value array = object/dict | ||
local sub_res = p.expandEmbeddedTemplates({frame=frame, jsondata=v, jsonschema=p.defaultArgPath(jsonschema, {"properties", k}, {}), template=eval_template, mode=mode, stringify_arrays=stringify_arrays}) | local sub_res = p.expandEmbeddedTemplates({frame=frame, jsondata=v, jsonschema=p.defaultArgPath(jsonschema, {"properties", k}, {}), template=eval_template, mode=mode, stringify_arrays=stringify_arrays, root=false}) | ||
msg = msg .. sub_res.debug_msg | msg = msg .. sub_res.debug_msg | ||
jsondata[k] = sub_res.res | jsondata[k] = sub_res.res | ||
Line 163: | Line 186: | ||
local string_list = "" | local string_list = "" | ||
for i,e in pairs(v) do | for i,e in pairs(v) do | ||
local eval_template = nil | |||
local eval_templates = p.defaultArgPath(jsonschema, {"properties", k, "items", p.keys.template}, {}) | |||
if (eval_templates[1] == nil) then eval_templates = {eval_templates} end --ensure list of objects | |||
for i, t in pairs(eval_templates) do | |||
if (t[p.keys.mode] ~= nil and t[p.keys.mode] == mode) then eval_template = t --use only render templates in render mode and store templates in store mode | |||
elseif (t[p.keys.mode] == nil) then eval_template = t --default | |||
elseif (debug) then msg = msg .. "Ignore eval_template" .. mw.dumpObject( t ) .. "\n<br>" | |||
end | |||
end | |||
if type(e) == 'table' then | if type(e) == 'table' then | ||
local sub_res = p.expandEmbeddedTemplates({frame=frame, jsondata=e, jsonschema=p.defaultArgPath(jsonschema, {"properties", k, "items"}, {}), template=eval_template, mode=mode, stringify_arrays=stringify_arrays, root=false}) | |||
local sub_res = p.expandEmbeddedTemplates({frame=frame, jsondata=e, jsonschema=p.defaultArgPath(jsonschema, {"properties", k, "items"}, {}), template=eval_template, mode=mode, stringify_arrays=stringify_arrays}) | |||
msg = msg .. sub_res.debug_msg | msg = msg .. sub_res.debug_msg | ||
if (type(sub_res.res) == 'table') then | if (type(sub_res.res) == 'table') then | ||
Line 182: | Line 208: | ||
end | end | ||
else | else | ||
if (eval_template ~= nil and eval_template.value ~= nil) then | |||
--evaluate single array item string as json {"self": "<value>", ".": "<value>"} => does not work since jsondata is an object | |||
--e = p.expandEmbeddedTemplates({frame=frame, jsondata={["self"]=e,["."]=e}, jsonschema=p.defaultArgPath(jsonschema, {"properties", k, "items"}, {}), template=eval_template, mode=mode, stringify_arrays=stringify_arrays, root=false}) | |||
if (eval_template.type == "mustache" or eval_template.type == "mustache-wikitext") then | |||
if (debug) then msg = msg .. "Parse mustache template " .. eval_template.value .. " with params " .. mw.dumpObject( e ) .. "\n<br>" end | |||
-- {{.}} in the template will be the value of e | |||
e = lustache:render(eval_template.value, e, p.tableMerge({self=eval_template.value}, eval_template.partials)) -- render with self as registered partial for recursion | |||
end | |||
if (eval_template.type == "mustache-wikitext") then --or eval_template.type == "wikitext") then | |||
if (debug) then msg = msg .. "Parse wikitext template " .. e .. " with params " .. mw.dumpObject( e ) .. "\n<br>" end | |||
e = frame:preprocess( e ) | |||
end | |||
v[i] = e -- update array | |||
end | |||
if (stringify_arrays) then string_list = string_list .. e .. ";" end | if (stringify_arrays) then string_list = string_list .. e .. ";" end | ||
end | end | ||
Line 191: | Line 234: | ||
if (template == nil) then | if (template == nil and root == false) then -- don't stringify root json objects | ||
local templates = jsondata[p.keys.template] | local templates = jsondata[p.keys.template] | ||
if (templates == nil) then templates = p.defaultArg(jsonschema[p.keys.template], {}) end | if (templates == nil) then templates = p.defaultArg(jsonschema[p.keys.template], {}) end | ||
Line 203: | Line 246: | ||
end | end | ||
if template ~= nil then | if (template ~= nil and root == false) then -- don't stringify root json objects | ||
if (template.type == "wikitext") then | if (template.type == "wikitext") then | ||
for k,v in pairs(jsondata) do | for k,v in pairs(jsondata) do | ||
Line 248: | Line 291: | ||
--jsonschema = p.defaultArg(jsonschema, {}) | --jsonschema = p.defaultArg(jsonschema, {}) | ||
--jsondata = p.defaultArg(jsondata, {}) | --jsondata = p.defaultArg(jsondata, {}) | ||
if (categories == nil) then categories = jsondata[p.keys.category] end | --if (categories == nil) then categories = jsondata[p.keys.category] end -- let function param overwrite json property | ||
if (not p.nilOrEmpty(jsondata[p.keys.category])) then categories = jsondata[p.keys.category] end -- let json property overwrite function param | |||
local schema_res = p.walkJsonSchema({jsonschema=jsonschema, categories=categories, mode=mode, recursive=recursive, debug=debug}) | local schema_res = p.walkJsonSchema({jsonschema=jsonschema, categories=categories, mode=mode, recursive=recursive, debug=debug}) | ||
local expand_res = p.expandJsonRef({json=schema_res.jsonschema, debug=debug}) | |||
--mw. | jsonschema = expand_res.json | ||
--mw.log(mw.text.jsonEncode(jsonschema)) | |||
local jsonld = p.copy(jsondata) | local jsonld = p.copy(jsondata) | ||
local json_data_store = p.copy(jsondata) | local json_data_store = p.copy(jsondata) | ||
Line 269: | Line 310: | ||
local smw_res = nil | local smw_res = nil | ||
if (mode == p.mode.header) then | if (mode == p.mode.header) then | ||
-- get the semantic properties by looking up the json keys in the json-ld context | |||
smw_res = p.getSemanticProperties({jsonschema=jsonschema, jsondata=json_res_store.res, store=false, debug=debug}) | smw_res = p.getSemanticProperties({jsonschema=jsonschema, jsondata=json_res_store.res, store=false, debug=debug}) | ||
-- store metadata where properties were defined / overridden | |||
for i, category in ipairs(schema_res.visited) do | |||
for k, v in pairs(p.defaultArgPath(schema_res.jsonschemas, {category, 'properties'}, {})) do --property section may not exisit | |||
if smw_res.definitions[k] == nil then smw_res.definitions[k] = {} end | |||
if smw_res.definitions[k]['defined_in'] == nil then smw_res.definitions[k]['defined_in'] = {} end | |||
table.insert(smw_res.definitions[k]['defined_in'], category) | |||
end | |||
end | |||
-- embed json-ld in resulting html for search engine discovery | |||
jsonld["@context"] = smw_res.context | jsonld["@context"] = smw_res.context | ||
jsonld["@type"] = p.tableMerge(p.tablefy(jsonschema.schema_type), p.tablefy(jsonld["@type"])) -- | jsonld["@type"] = p.tableMerge(p.tablefy(jsonschema.schema_type), p.tablefy(jsonld["@type"])) -- | ||
Line 280: | Line 334: | ||
end | end | ||
end | end | ||
wikitext = wikitext .. "<div class='jsonld-header' style='display:none' data-jsonld='" .. mw.text.jsonEncode( jsonld ) .. "'></div>" | wikitext = wikitext .. "<div class='jsonld-header' style='display:none' data-jsonld='" .. mw.text.jsonEncode( jsonld ):gsub("'","`") .. "'></div>" | ||
end | end | ||
Line 288: | Line 342: | ||
--mw.log("JSONDATA RENDER") | --mw.log("JSONDATA RENDER") | ||
--mw.logObject(jsondata) | --mw.logObject(jsondata) | ||
local max_index = p.tableLength(schema_res.visited) | |||
for i, category in ipairs(schema_res.visited) do | for i, category in ipairs(schema_res.visited) do | ||
local | if (mode == p.mode.footer) then category = schema_res.visited[max_index - i +1] end --reverse order for footer templates | ||
local super_jsonschema = schema_res.jsonschemas[category] | |||
local template = schema_res.templates[category] | local template = schema_res.templates[category] | ||
if (template ~= nil) then | if (template ~= nil) then | ||
Line 300: | Line 355: | ||
end | end | ||
local child = frame:newChild{args=stripped_jsondata} | local child = frame:newChild{args=stripped_jsondata} | ||
if ( template:sub(1, #"=") == "=" ) then template = "\n" .. template end -- add line break if template starts with heading (otherwise not rendered by mw parser) | |||
wikitext = wikitext .. child:preprocess( template ) | wikitext = wikitext .. child:preprocess( template ) | ||
elseif (mode == p.mode.header) then | elseif (mode == p.mode.header) then | ||
local infobox_res = p.renderInfoBox({jsonschema=jsonschema, jsondata=jsondata}) | local ignore_properties = {[p.keys.category]=true} -- don't render type/category on every subclass | ||
for j, subcategory in ipairs(schema_res.visited) do | |||
if j > i then | |||
local subjsonschema = schema_res.jsonschemas[subcategory] | |||
for k, v in pairs(p.defaultArg(subjsonschema['properties'], {})) do | |||
-- skip properties that are overwritten in subschemas, render them only once at the most specific position | |||
ignore_properties[k] = true | |||
end | |||
end | |||
end | |||
-- render the infobox for the schema itself and every super_schema using always the global json-ld context (merged within walkJsonSchema()) | |||
-- context needs to be preprocessed with buildContext() since the generic json/table merge of the @context atttribute produces a list of strings (remote context) and context objects | |||
-- context is already build in p.getSemanticProperties. schema_allOfMerged is used to provide the full schema for overridden properties | |||
local infobox_res = p.renderInfoBox({jsonschema=super_jsonschema, schema_allOfMerged=jsonschema, context=smw_res.context, property_definitions=smw_res.definitions, jsondata=jsondata, ignore_properties=ignore_properties}) | |||
wikitext = wikitext .. frame:preprocess( infobox_res.wikitext ) | wikitext = wikitext .. frame:preprocess( infobox_res.wikitext ) | ||
end | end | ||
end | end | ||
local set_categories_in_wikitext = {} | |||
p.tableMerge(set_categories_in_wikitext, json_res_store.res[p.keys.subcategory]) --classes/categories, nil for items | |||
if (title.nsText ~= "Category") then | if (title.nsText ~= "Category") then --items | ||
p.tableMerge(set_categories_in_wikitext, json_res_store.res[p.keys.category]) -- categories from schema type | |||
end | |||
-- Todo: Consider moving the category and this block to p.getSemanticProperties with store=true. However, settings categories with @category is only possible for subobjects | |||
if (smw_res ~= nil) then | if (smw_res ~= nil) then | ||
local display_label = p.getDisplayLabel(json_res_store.res, smw_res.properties) | |||
if title.nsText == "Property" then display_label = p.defaultArgPath(json_res_store.res, {p.keys.name}, display_label) end | |||
if (debug) then msg = msg .. "Store page properties" end | if (debug) then msg = msg .. "Store page properties" end | ||
smw_res.properties['Display title of'] = display_label --set special property display title | -- category handling | ||
p.tableMerge(set_categories_in_wikitext, smw_res.properties[p.keys.category_pseudoproperty]) | |||
smw_res.properties[p.keys.category_pseudoproperty] = nil -- delete pseudo property | |||
smw_res.properties['HasOswId'] = mw.title.getCurrentTitle().fullText --set special property OswId to own title | |||
-- label and display title handling | |||
if display_label ~= nil then | |||
smw_res.properties['Display title of'] = display_label --set special property display title | |||
smw_res.properties['Display title of lowercase'] = display_label:lower() --store lowercase for case insensitive query | |||
smw_res.properties['Display title of normalized'] = display_label:lower():gsub('[^%w]+','') --store with all non-alphanumeric chars removed for normalized query | |||
end | |||
p.setNormalizedLabel(smw_res.properties) --build normalized multilang label | |||
mw.ext.displaytitle.set(display_label) | mw.ext.displaytitle.set(display_label) | ||
--smw_res.properties['@category'] = jsondata[p.keys.category] | --smw_res.properties['@category'] = jsondata[p.keys.category] | ||
Line 327: | Line 413: | ||
--wikitext = mw.dumpObject(smw_res.properties) .. wikitext | --wikitext = mw.dumpObject(smw_res.properties) .. wikitext | ||
end | end | ||
wikitext = wikitext .. "\n" .. p.setCategories({categories=set_categories_in_wikitext, sortkey=display_label}).wikitext | |||
if (debug) then mw.logObject(res) end | if (debug) then mw.logObject(res) end | ||
Line 336: | Line 423: | ||
-- test: mw.logObject(p.renderInfoBox({jsonschema=p.loadJson({title="JsonSchema:Entity"}).json, jsondata={uuid="123123"}})) | -- test: mw.logObject(p.renderInfoBox({jsonschema=p.loadJson({title="JsonSchema:Entity"}).json, jsondata={uuid="123123"}})) | ||
function p.renderInfoBox(args) | function p.renderInfoBox(args) | ||
local debug = p.defaultArg(args.debug, false) | |||
local jsondata = p.defaultArg(args.jsondata, {}) | local jsondata = p.defaultArg(args.jsondata, {}) | ||
local schema = p.defaultArg(args.jsonschema, nil) | local schema = p.defaultArg(args.jsonschema, nil) -- local schema from the perspective of the current category | ||
local | local schema_allOfMerged = p.defaultArg(args.schema_allOfMerged, schema) -- global schema with allOfs merged | ||
local property_definitions = p.defaultArg(args.property_definitions, {}) -- dict schema_key: {property: <smw_property>, ...} | |||
local res = "" | local res = "" | ||
if schema == nil then return res end | if schema == nil then return res end | ||
local | |||
local context = p.defaultArg(args.context, p.buildContext({jsonschema=schema}).context) | |||
local ignore_properties = p.defaultArg(args.ignore_properties, {}) | |||
local schema_label = p.renderMultilangValue({jsonschema=schema}) | |||
-- see also: https://help.fandom.com/wiki/Extension:Scribunto/HTML_Library_usage_notes | -- see also: https://help.fandom.com/wiki/Extension:Scribunto/HTML_Library_usage_notes | ||
Line 349: | Line 441: | ||
:attr( 'class', 'info_box' ) | :attr( 'class', 'info_box' ) | ||
:tag( 'tr' ) | :tag( 'tr' ) | ||
:tag( ' | :tag( 'th' ) | ||
:attr( 'class', ' | :attr( 'class', 'heading' ) | ||
:attr( 'colspan', '2' ) | :attr( 'colspan', '2' ) | ||
:wikitext( schema_label ) | :wikitext( schema_label ) | ||
for k,v in pairs(jsondata) do | for k,v in pairs(jsondata) do | ||
if (schema['properties'] ~= nil and schema['properties'][k] ~= nil and (type(v) ~= 'table' or v[1] ~= nil)) then --literal or literal array | if (not ignore_properties[k]) then | ||
if (schema['properties'] ~= nil and schema['properties'][k] ~= nil and (type(v) ~= 'table' or v[1] ~= nil)) then --literal or literal array | |||
local def = schema_allOfMerged['properties'][k] | |||
--mw.logObject(def) | |||
local label = p.renderMultilangValue({jsonschema=def, default=k}) | |||
local description = p.renderMultilangValue({jsonschema=def, key="description"}) | |||
if (p.tableLength(p.defaultArgPath(property_definitions, {k, 'defined_in'}, {})) > 0) then description = description .. "<br>Definition: " end | |||
for i, c in pairs(p.defaultArgPath(property_definitions, {k, 'defined_in'}, {})) do | |||
if (i > 1) then description = description .. ", " end | |||
description = description .. "[[:" ..c .. "]]" | |||
end | |||
if (description ~= "") then description = "{{#info: " .. description .. "|note }}" end -- smw tooltip | |||
label = label .. description | |||
--res = res .. title ": " .. v | |||
local cell = tbl:tag( 'tr' ) | |||
:tag( 'th' ) | |||
:wikitext( label ) | |||
:done() | |||
:tag( 'td' ) | |||
if (type(v) == 'table') then | |||
for i,e in pairs(v) do | |||
if (type(e) ~= 'table') then | |||
local p_type = p.defaultArgPath(context, {k, '@type'}, '@value') | |||
if (p_type == '@id' and p.defaultArgPath(def, {'items', 'type'}, 'unknown') == 'string' and def['eval_template'] == nil) then | |||
-- auto-link (OSW-)IDs if no eval_template is present | |||
e = string.gsub(e, "Category:", ":Category:") -- make sure category links work | |||
e = string.gsub(e, "File:", ":File:") -- do not embedd images but link to them | |||
e = "[[" .. e .. "]]" | |||
elseif (p_type == 'xsd:date') then -- formate date with user preferences | |||
e = "{{#dateformat:" .. e .. "|ymd}}" | |||
elseif (p_type == 'xsd:dateTime') then -- formate time with user preferences | |||
local smw_property = p.defaultArgPath(property_definitions, {k, 'property'}) | |||
if (smw_property ~= nil) then e = "{{#ask: [[{{FULLPAGENAME}}]]|?" .. smw_property .. "#LOCL#TO= |format=plain |mainlabel=-}}" | |||
else | |||
local _, _, date, hours, minutes = string.find(e, "(%S+)[T ](%S+)[:](%S+)[:?]") | |||
e = "{{#dateformat:" .. date .. "|ymd}} " .. hours .. ":" .. minutes .. " (UTC)" | |||
end | |||
elseif (type(v) == 'boolean') then | |||
if (v) then v = "✅" else v = "❌" end -- green check mark or red cross | |||
elseif (def['eval_template'] == nil and (string.len(e) > 100) and (string.find(e, "{{") == nil) and (string.find(e, "</") == nil) and (string.find(e, "%[%[") == nil)) then -- no markup, no links | |||
e = string.sub(e, 1, 100) .. "..."; -- limit infobox plain text to max 100 chars | |||
elseif (debug) then | |||
mw.log("Unformated: " .. k .. " " .. p.defaultArgPath(def, {'items', 'type'}, 'unknown')) | |||
mw.logObject(def) | |||
end | |||
cell:wikitext("\n* " .. e .. "") | |||
end | |||
end | |||
else | |||
local p_type = p.defaultArgPath(context, {k, '@type'}, '@value') | |||
if (p_type == '@id' and p.defaultArgPath(def, {'type'}, 'unknown') == 'string' and def['eval_template'] == nil) then | |||
-- auto-link (OSW-)IDs if no eval_template is present | |||
v = string.gsub(v, "Category:", ":Category:") -- make sure category links work | |||
v = string.gsub(v, "File:", ":File:") -- do not embedd images but link to them | |||
v = "[[" .. v .. "]]" | |||
elseif (p_type == 'xsd:date') then -- formate date & time with user preferences | |||
v = "{{#dateformat:" .. v .. "|ymd}}" | |||
elseif (p_type == 'xsd:dateTime') then -- formate time with user preferences | |||
local smw_property = p.defaultArgPath(property_definitions, {k, 'property'}) | |||
if (smw_property ~= nil) then v = "{{#ask: [[{{FULLPAGENAME}}]]|?" .. smw_property .. "#LOCL#TO= |format=plain |mainlabel=-}}" | |||
else | |||
local _, _, date, hours, minutes = string.find(v, "(%S+)[T ](%S+)[:](%S+)[:?]") | |||
v = "{{#dateformat:" .. date .. "|ymd}} " .. hours .. ":" .. minutes .. " (UTC)" | |||
end | |||
elseif (type(v) == 'boolean') then | |||
if (v) then v = "✅" else v = "❌" end -- green check mark or red cross | |||
elseif (def['eval_template'] == nil and (string.len(v) > 100) and (string.find(v, "{{") == nil) and (string.find(v, "</") == nil) and (string.find(v, "%[%[") == nil)) then -- no markup, no links | |||
v = string.sub(v, 1, 100) .. "..."; -- limit infobox plain text to max 100 chars | |||
elseif (debug) then | |||
mw.log("Unformated: " .. k .. " " .. p.defaultArgPath(def, {'type'}, 'unknown')) | |||
mw.logObject(def) | |||
end | end | ||
cell:wikitext("\n" .. v .. "") | |||
end | end | ||
end | end | ||
end | end | ||
Line 392: | Line 538: | ||
local jsonschema = p.defaultArg(args.jsonschema, {}) | local jsonschema = p.defaultArg(args.jsonschema, {}) | ||
local includeNamespace = p.defaultArg(args.includeNamespace, false) | local includeNamespace = p.defaultArg(args.includeNamespace, false) | ||
local includeSchemas = p.defaultArg(args.includeSchemas, false) | |||
local categories = {} | local categories = {} | ||
Line 398: | Line 545: | ||
--properties['@category'] = {} | --properties['@category'] = {} | ||
for k, entry in pairs(allOf) do | for k, entry in pairs(allOf) do | ||
if type(entry) == 'table' then -- "allOf": [{"$ref": "/wiki/Category:Test?action=raw"}] | local refs = nil | ||
if type(entry) == 'table' then refs = entry -- "allOf": [{"$ref": "/wiki/Category:Test?action=raw"}] | |||
else refs = {entry} end-- "allOf": {"$ref": "/wiki/Category:Test?action=raw"} | |||
for p, v in pairs(entry) do | |||
if (p == '$ref') then | |||
table.insert(categories, | for category in v:gmatch("Category:([^?]+)") do -- e.g. "/wiki/Category:Test?action=raw" | ||
if (includeNamespace) then category = "Category:" .. category end | |||
table.insert(categories, category) | |||
end | |||
if includeSchemas then | |||
for schema in v:gmatch("JsonSchema:([^?]+)") do -- e.g. "/wiki/JsonSchema:Test?action=raw" | |||
if (includeNamespace) then schema = "JsonSchema:" .. schema end | |||
table.insert(categories, schema) | |||
end | end | ||
end | end | ||
end | end | ||
end | end | ||
end | end | ||
Line 439: | Line 586: | ||
jsonschema = p.expandJsonRef({json=p.loadJson({title=category, slot="jsonschema"}).json}).json | jsonschema = p.expandJsonRef({json=p.loadJson({title=category, slot="jsonschema"}).json}).json | ||
mw.logObject(p.buildContext({jsonschema=jsonschema, debug=true})) | mw.logObject(p.buildContext({jsonschema=jsonschema, debug=true})) | ||
mw.log(mw.text.jsonEncode(p.buildContext({jsonschema=jsonschema, debug=false}).context)) | |||
or | or | ||
jsonschema = { | jsonschema = { | ||
Line 461: | Line 609: | ||
--]] | --]] | ||
-- constructs a property specific local jsonld context | |||
function p.buildContext(args) | function p.buildContext(args) | ||
local schema = p.defaultArg(args.jsonschema, {}) | local schema = p.defaultArg(args.jsonschema, {}) | ||
Line 472: | Line 621: | ||
elseif (type(v) == 'table' and v[1] ~= nil) then --custom addtional mappings, e. g. "type*": ["Property:HasType"] | elseif (type(v) == 'table' and v[1] ~= nil) then --custom addtional mappings, e. g. "type*": ["Property:HasType"] | ||
result[k] = v | result[k] = v | ||
elseif (type(v) == 'table' and v['@id'] == nil) then --subcontext | elseif (type(v) == 'table' and v['@id'] == nil and v['@reverse'] == nil) then --subcontext | ||
p.tableMerge(result, p.buildContext({context=v}).context) | p.tableMerge(result, p.buildContext({context=v}).context) | ||
else | else | ||
Line 481: | Line 630: | ||
local properties = p.defaultArg(schema.properties, {}) | local properties = p.defaultArg(schema.properties, {}) | ||
-- build property context | |||
for k,v in pairs(properties) do | for k,v in pairs(properties) do | ||
local subcontext = nil | local subcontext = nil | ||
Line 487: | Line 637: | ||
subcontext = p.buildContext({jsonschema=properties[k]}).context | subcontext = p.buildContext({jsonschema=properties[k]}).context | ||
elseif (p.defaultArgPath(properties, {k, 'items', 'type'}) == 'object') then | elseif (p.defaultArgPath(properties, {k, 'items', 'type'}) == 'object') then | ||
mw.logObject(properties[k]['items']) | --mw.logObject(properties[k]['items']) | ||
subcontext = p.buildContext({jsonschema=properties[k]['items']}).context | subcontext = p.buildContext({jsonschema=properties[k]['items']}).context | ||
end | end | ||
Line 501: | Line 651: | ||
--maps jsondata values to semantic properties by using the @context attribute within the schema | --maps jsondata values to semantic properties by using the @context attribute within the schema | ||
--test: mw.logObject(p.getSemanticProperties({jsonschema={["@context"]={test="Property:TestProperty", myObjectProperty={["@id"]= "Property:MyObjectProperty", ["@type"]= "@id"}}}, jsondata={test="TestValue", myObjectProperty="123"}, debug=true})) | --test: mw.logObject(p.getSemanticProperties({jsonschema={["@context"]={test="Property:schema:TestProperty", myObjectProperty={["@id"]= "Property:MyObjectProperty", ["@type"]= "@id"}}}, jsondata={test="TestValue", myObjectProperty="123"}, debug=true})) | ||
--test: mw.logObject(p.getSemanticProperties({jsonschema={["@context"]={"some uri",{test="Property:TestProperty", myObjectProperty={["@id"]= "Property:MyObjectProperty", ["@type"]= "@id"}}}}, jsondata={test="TestValue", myObjectProperty="123"}, debug=true})) | --test: mw.logObject(p.getSemanticProperties({jsonschema={["@context"]={"some uri",{test="Property:TestProperty", myObjectProperty={["@id"]= "Property:MyObjectProperty", ["@type"]= "@id"}}}}, jsondata={test="TestValue", myObjectProperty="123"}, debug=true})) | ||
--[[ | --[[ | ||
Line 526: | Line 676: | ||
local schema = p.defaultArg(args.jsonschema, {}) | local schema = p.defaultArg(args.jsonschema, {}) | ||
local subschema = p.defaultArg(args.subschema, schema) | local subschema = p.defaultArg(args.subschema, schema) | ||
local parent_schema_property = p.defaultArg(args.parent_schema_property, {}) | local parent_schema_property = p.defaultArg(args.parent_schema_property, {}) -- ToDo: Not used except in getSemanticQuery => remove | ||
local store = p.defaultArg(args.store, false) | local store = p.defaultArg(args.store, false) | ||
local root = p.defaultArg(args.root, true) | local root = p.defaultArg(args.root, true) | ||
local properties = p.defaultArg(args.properties, {}) --semantic properties to store, dict key=property_name, value=array of string values | |||
local debug = p.defaultArg(args.debug, false) | local debug = p.defaultArg(args.debug, false) | ||
--if (debug) then mw.logObject("Call getSemanticProperties with args " .. mw.dumpObject( args ) .. "\n<br>") end | --if (debug) then mw.logObject("Call getSemanticProperties with args " .. mw.dumpObject( args ) .. "\n<br>") end | ||
local | local subjectId = mw.title.getCurrentTitle().fullText | ||
local subobjectId = nil | |||
if (root == false and jsondata['uuid'] ~= nil) then | |||
subobjectId = "OSW" .. string.gsub(jsondata['uuid'], "-", "") | |||
subjectId = subjectId .. '#' .. subobjectId | |||
end | |||
local property_data = {} | local property_data = {} | ||
local context = p.defaultArg(args.context, p.buildContext({jsonschema=schema}).context) | local context = p.defaultArg(args.context, p.buildContext({jsonschema=schema}).context) | ||
Line 548: | Line 705: | ||
for k,v in pairs(jsondata) do | for k,v in pairs(jsondata) do | ||
local property_names = {} | local property_names = {} | ||
local subobject_properties = {} -- reverse properties to store in the subobject | |||
local mapping_found = false | local mapping_found = false | ||
local property_definitions = {} | local property_definitions = {} -- list of objects {id=..., reverse=...} | ||
for term, def in pairs(context) do | for term, def in pairs(context) do | ||
local term_parts = p.splitString(term, "*") | local term_parts = p.splitString(term, "*") | ||
if ( | if (term_parts[1] == k) then --custom additional mapping term*(*...): "Property:..." | ||
table.insert(property_definitions, def) | if type(def) == 'table' then | ||
-- note: json-ld allows only @id OR @reverse | |||
if (def["@id"] ~= nil) then table.insert(property_definitions, {id=def["@id"], reverse=false}) end | |||
if (def["@reverse"] ~= nil) then table.insert(property_definitions, {id=def["@reverse"], reverse=true}) end | |||
else table.insert(property_definitions, {id=def}) end | |||
end | end | ||
end | end | ||
if (debug) then mw.logObject(property_definitions) end | if (debug) then mw.logObject(property_definitions) end | ||
for i,e in ipairs(property_definitions) do | for i,e in ipairs(property_definitions) do | ||
local property_definition = p.splitString( | local id = e["id"] | ||
local property_definition = p.splitString(id, ':') | |||
if property_definition[1] == p.keys.property_ns_prefix then | if property_definition[1] == p.keys.property_ns_prefix then | ||
mapping_found = true | mapping_found = true | ||
table.insert(property_names, | property_name = string.gsub(id, p.keys.property_ns_prefix .. ":", "") -- also allow prefix properties like: Property:schema:url | ||
if (e["reverse"]) then -- reverse properties are handled in the respective subobject | |||
if (subobject_properties[property_name] == nil) then subobject_properties[property_name] = {} end --initialize empty list | |||
table.insert(subobject_properties[property_name], subjectId) -- add triple subobject -property-> subject | |||
else table.insert(property_names, property_name) end | |||
local schema_property = p.defaultArg(schema_properties[k], {}) | local schema_property = p.defaultArg(schema_properties[k], {}) | ||
local schema_type = p.defaultArg(schema_property.type, nil) --todo: also load smw property type on demand | local schema_type = p.defaultArg(schema_property.type, nil) --todo: also load smw property type on demand | ||
property_data[k] = {schema_type=schema_type, schema_data=schema_property, property= | property_data[k] = {schema_type=schema_type, schema_data=schema_property, property=property_name, value=v, reverse=e["reverse"]} | ||
end | end | ||
end | end | ||
Line 580: | Line 744: | ||
context = p.tableMerge(context, subcontext) -- pull up nested context | context = p.tableMerge(context, subcontext) -- pull up nested context | ||
local values = {} | local values = {} | ||
if (v[1] == nil) then --key value array = object/dict | if (p.tableLength(v) > 0 and v[1] == nil) then --key value array = object/dict => subobject | ||
local subproperties_res = p.getSemanticProperties({jsonschema=schema, jsondata=v, store=true, root=false, debug=debug, context=context, subschema=schema_properties[k], parent_schema_property=property_data[k]}) | local subproperties_res = p.getSemanticProperties({jsonschema=schema, jsondata=v, properties=p.copy(subobject_properties), store=true, root=false, debug=debug, context=context, subschema=schema_properties[k], parent_schema_property=property_data[k]}) | ||
local id = subproperties_res.id --subobject_id | local id = subproperties_res.id --subobject_id | ||
if (id ~= nil) then | if (id ~= nil) then | ||
Line 591: | Line 755: | ||
for i, e in pairs(v) do | for i, e in pairs(v) do | ||
if (type(e) == 'table') then | if (type(e) == 'table') then | ||
local subproperties_res = p.getSemanticProperties({jsonschema=schema, jsondata=e, store=true, root=false, debug=debug, context=context, subschema=schema_properties[k], parent_schema_property=property_data[k]}) | local subproperties_res = p.getSemanticProperties({jsonschema=schema, jsondata=e, properties=p.copy(subobject_properties), store=true, root=false, debug=debug, context=context, subschema=schema_properties[k], parent_schema_property=property_data[k]}) | ||
local id = subproperties_res.id --subobject_id | local id = subproperties_res.id --subobject_id | ||
if (id ~= nil) then | if (id ~= nil) then | ||
Line 620: | Line 784: | ||
end | end | ||
local store_res = nil | local store_res = nil | ||
if (store) then | if (store) then | ||
properties['HasOswId'] = subjectId | |||
if (root) then | if (root) then | ||
if (debug) then mw.logObject("Store page properties") end | if (debug) then mw.logObject("Store page properties") end | ||
store_res = mw.smw.set( properties ) --store as semantic properties | store_res = mw.smw.set( properties ) --store as semantic properties | ||
else | else | ||
properties['@category'] = {} | |||
p.tableMerge(properties['@category'], jsondata[p.keys.category]) -- from json property 'type' | |||
p.tableMerge(properties['@category'], properties[p.keys.category_pseudoproperty]) -- from json-ld context 'Property:Category' | |||
properties[p.keys.category_pseudoproperty] = nil -- delete pseudo property | |||
local display_label = p.getDisplayLabel(jsondata, properties) | |||
properties[' | if properties['Display title of'] == nil and properties['Display_title_of'] == nil then | ||
if (display_label ~= nil and display_label ~= "") then properties['Display title of'] = display_label | |||
else properties['Display title of'] = p.defaultArg(subschema['title'], "") end -- fall back to property name in schema | |||
end | |||
p.setNormalizedLabel(properties) --build normalized multilang label | |||
if (p.tableLength(properties) > 0) then | if (p.tableLength(properties) > 0) then | ||
store_res = mw.smw.subobject( properties, subobjectId ) --store as subobject | store_res = mw.smw.subobject( properties, subobjectId ) --store as subobject | ||
Line 655: | Line 824: | ||
if (statement["HasSubject"] == nil or statement["HasSubject"][1] == nil or statement["HasSubject"][1] == "") then --implicit subject | if (statement["HasSubject"] == nil or statement["HasSubject"][1] == nil or statement["HasSubject"][1] == "") then --implicit subject | ||
if (statement["HasProperty"] ~= nil and statement["HasProperty"][1] ~= nil and statement["HasProperty"][1] ~= "" and statement["HasObject"] ~= nil) then | if (statement["HasProperty"] ~= nil and statement["HasProperty"][1] ~= nil and statement["HasProperty"][1] ~= "" and statement["HasObject"] ~= nil) then | ||
local property = | local property = string.gsub(statement["HasProperty"][1], p.keys.property_ns_prefix .. ":", "") -- also allow prefix properties like: Property:schema:url | ||
if (debug) then | if (debug) then | ||
mw.log("Set property " .. property .. " from statement to ") | mw.log("Set property " .. property .. " from statement to ") | ||
Line 748: | Line 917: | ||
if type(v) == "table" then | if type(v) == "table" then | ||
json[k] = p.expandJsonRef({json=v}).json | json[k] = p.expandJsonRef({json=v}).json | ||
end | |||
end | |||
local result = p.copy(json) | |||
for k,v in pairs(json) do | |||
if (k == "allOf") then | |||
if (type(v) == "table" and v[1] == nil) then v = {v} end -- ensure array | |||
for i,s in pairs(v) do | |||
result = p.tableMerge(s, result) | |||
if (debug) then mw.log("merge allOf with title " .. s["title"]) end | |||
end | |||
result[k] = nil | |||
end | end | ||
end | end | ||
return {json= | return {json=result} | ||
end | end | ||
function p.defaultArg(arg, default) | function p.defaultArg(arg, default) | ||
Line 831: | Line 1,013: | ||
--test: mw.logObject(p.tableMerge({"string", test1="test1", subtable1={"test"}}, {"string2", test1="test2", test3="test4"})) | --test: mw.logObject(p.tableMerge({"string", test1="test1", subtable1={"test"}}, {"string2", test1="test2", test3="test4"})) | ||
function p.tableMerge(t1, t2) | function p.tableMerge(t1, t2) | ||
if (t1 == nil) then t1 = {} elseif (type(t1) ~= 'table') then t1 = {t1} end | |||
if (t2 == nil) then t2 = {} elseif (type(t2) ~= 'table') then t2 = {t2} end | |||
for k,v in pairs(t2) do | for k,v in pairs(t2) do | ||
if type(v) == "table" then | if type(v) == "table" then | ||
Line 856: | Line 1,040: | ||
for k, v in pairs(obj) do res[p.copy(k, s)] = p.copy(v, s) end | for k, v in pairs(obj) do res[p.copy(k, s)] = p.copy(v, s) end | ||
return res | return res | ||
end | |||
-- get normalized label | |||
function p.getDisplayLabel(jsondata, properties) | |||
local display_label = nil | |||
-- check if label properties are mapped | |||
if (properties["HasLabel"] ~= nil and properties["HasLabel"][1] ~= nil) then display_label = p.splitString(properties["HasLabel"][1], '@')[1] | |||
elseif (properties["HasName"] ~= nil) then | |||
if type(properties["HasName"]) == 'table' then display_label = properties["HasName"][1] | |||
else display_label = properties["HasName"] end | |||
-- fall back to unmapped keywords | |||
elseif (jsondata[p.keys.label] ~= nil and jsondata[p.keys.label][1] ~= nil) then | |||
if type(jsondata[p.keys.label][1]) ~= 'table' then display_label = p.splitString(jsondata[p.keys.label][1], '@')[1] | |||
else display_label = jsondata[p.keys.label][1][p.keys.text] end -- no eval_template applied | |||
elseif (jsondata[p.keys.name] ~= nil) then display_label = jsondata[p.keys.name] | |||
end | |||
return display_label | |||
end | |||
-- build normalized multilang label | |||
function p.setNormalizedLabel(properties, use_fallbacks) | |||
if (use_fallbacks == nil) then use_fallbacks = true end | |||
if (properties['HasLabel'] ~= nil) then | |||
labels = properties['HasLabel'] | |||
if(type(labels) ~= 'table') then labels = {labels} end | |||
properties['HasNormalizedLabel'] = {} | |||
for i, label in ipairs(labels) do | |||
label_norm = p.splitString(label, '@')[1]:lower():gsub('[^%w]+','') | |||
label_lang = "en" | |||
if (p.splitString(label, '@')[2] ~= nil) then label_lang = p.splitString(label, '@')[2] end | |||
table.insert(properties['HasNormalizedLabel'], label_norm .. "@" .. label_lang) | |||
end | |||
elseif (use_fallbacks and properties['HasName'] ~= nil) then -- fallback, assume English lang | |||
labels = properties['HasName'] | |||
if(type(labels) ~= 'table') then labels = {labels} end | |||
properties['HasNormalizedLabel'] = {} | |||
for i, label in ipairs(labels) do | |||
label_norm = label:lower():gsub('[^%w]+','') | |||
table.insert(properties['HasNormalizedLabel'], label_norm .. "@en") | |||
end | |||
elseif (use_fallbacks and properties['Display title of'] ~= nil) then -- fallback, assume English lang | |||
labels = properties['Display title of'] | |||
if(type(labels) ~= 'table') then labels = {labels} end | |||
properties['HasNormalizedLabel'] = {} | |||
for i, label in ipairs(labels) do | |||
label_norm = label:lower():gsub('[^%w]+','') | |||
table.insert(properties['HasNormalizedLabel'], label_norm .. "@en") | |||
end | |||
end | |||
end | |||
function p.renderMultilangValue(args) | |||
local jsondata = p.defaultArg(args.jsondata, {}) | |||
local jsonschema = p.defaultArg(args.jsonschema, {}) | |||
local key = p.defaultArg(args.key, "title") | |||
local result = p.defaultArg(args.default, "") | |||
local default = p.defaultArg(args.default, nil) | |||
-- "title*": {"de": ...} | |||
if jsonschema[key] ~= nil then result = jsonschema[key] end | |||
if jsonschema[key .. '*'] ~= nil then -- multilang label with switch | |||
result = "{{#switch:{{USERLANGUAGECODE}} |#default=" .. result | |||
for k,v in pairs(jsonschema[key .. '*']) do | |||
if k == en then default = v | |||
else result = result .. " |" .. k .. "=" .. v end | |||
end | |||
if default ~= nil then result = result .. " |#default=" .. default end | |||
result = result .. " }}" | |||
end | |||
-- "some_property": [{"lang": "de", "text": ...}] | |||
if jsondata[key] ~= nil then -- multilang label with switch | |||
result = "{{#switch:{{USERLANGUAGECODE}}" | |||
for k,v in pairs(jsondata[key]) do | |||
if v["lang"] ~= nil and v["text"] ~= nil then | |||
if v["lang"] == "en" then default = v["text"] | |||
else result = result .. " |" .. v["lang"] .. "=" .. v["text"] end | |||
end | |||
end | |||
if default ~= nil then result = result .. " |#default=" .. default end | |||
result = result .. " }}" | |||
end | |||
return result | |||
end | end | ||
return p | return p |