Module:SemanticAnnotations
Appearance
Documentation for this module may be created at Module:SemanticAnnotations/doc
-- Module:SemanticAnnotations
-- Generates semantic annotations for templates in MediaWiki
-- Docs: https://github.com/SemanticMediaWiki/SemanticScribunto/tree/master/docs
local p = {}
-- Trim whitespace helper
local function trim(s) return (s:gsub("^%s+", ""):gsub("%s+$", "")) end
--[[ Generates semantic annotations using #set parser function
@param args - Template parameters
@param mappings - Property mappings: {["Property"] = "param"} or complex format
@param options - Config options (visible, prefix, transform, default, conditional)
@return Wikitext with semantic annotations
]]
function p.generateAnnotations(args, mappings, options)
-- If complex mappings found, delegate to enhanced function
if mappings and type(mappings) == "table" then
for _, mapping in pairs(mappings) do
if type(mapping) == "table" then return p.generateEnhancedAnnotations(args, mappings, options) end
end
end
-- Set defaults
args = args or {}
mappings = mappings or {}
options = options or {}
local visible = options.visible or false
local prefix = options.prefix or ""
local transform = options.transform or {}
local default = options.default or {}
local conditional = options.conditional or {}
-- Build annotation block
local result = {}
if not visible then table.insert(result, '<div style="display:none;">') end
table.insert(result, ' {{#set:')
local propertyCount = 0
-- Process string mappings
for property, param in pairs(mappings) do
if type(param) == "string" then
local fullPropertyName = prefix .. property
local value = args[param]
-- Apply transform if needed
if value and transform[property] then value = transform[property](value) end
-- Add property if value exists or default provided
if value and value ~= "" then
table.insert(result, string.format(' |%s=%s', fullPropertyName, value))
propertyCount = propertyCount + 1
elseif default[property] then
table.insert(result, string.format(' |%s=%s', fullPropertyName, default[property]))
propertyCount = propertyCount + 1
end
end
end
-- Process conditional properties
for property, condition in pairs(conditional) do
local fullPropertyName = prefix .. property
if args[condition.param] and args[condition.param] == condition.value then
table.insert(result, string.format(' |%s=%s', fullPropertyName, condition.target or "true"))
propertyCount = propertyCount + 1
end
end
-- Close the parser function and wrapper
table.insert(result, ' }}')
if not visible then table.insert(result, '</div>') end
-- Return result or empty string
return propertyCount > 0 and table.concat(result, "\n") or ""
end
-- Renders a table using TemplateStructure and adds annotations
function p.renderWithSemantics(args, config, semanticMappings, semanticOptions)
local TemplateStructure = require('Module:TemplateStructure')
local renderedTable = TemplateStructure.render(args, config)
local annotations = p.generateAnnotations(args, semanticMappings, semanticOptions)
return renderedTable .. "\n" .. annotations
end
-- Allows templates to append annotations directly via transclusion
function p.appendToTemplate(frame)
local args = frame.args
local parent = frame:getParent()
local parentArgs = parent and parent.args or {}
-- Build mappings from numbered parameters
local mappings = {}
local i = 1
while args["property" .. i] and args["param" .. i] do
mappings[args["property" .. i]] = args["param" .. i]
i = i + 1
end
-- Extract options and generate annotations
local options = {
visible = args.visible == "true",
prefix = args.prefix or ""
}
return p.generateAnnotations(parentArgs, mappings, options)
end
-- Process simple property mapping
local function processSimpleMapping(properties, propertyName, value, transformFunc, defaultValue)
-- Apply transform if applicable
if value and value ~= "" and transformFunc then value = transformFunc(value) end
-- Handle value setting with array conversion if needed
if value and value ~= "" then
if properties[propertyName] then
-- Convert to array if first duplicate
if type(properties[propertyName]) ~= "table" then
properties[propertyName] = {properties[propertyName]}
end
table.insert(properties[propertyName], value)
else
properties[propertyName] = value
end
elseif defaultValue then
properties[propertyName] = defaultValue
end
end
-- Process complex property mapping with metadata
local function processComplexMapping(properties, propertyName, args, mappings, transformFunc)
for _, mappingEntry in ipairs(mappings) do
local param = mappingEntry.param
local metadata = mappingEntry.metadata or {}
local value = args[param]
if value and value ~= "" then
-- Apply transform
if transformFunc then value = transformFunc(value) end
-- Handle metadata qualifiers
local qualifiedProperty = propertyName
if next(metadata) then
local qualifiers = {}
for metaKey, metaValue in pairs(metadata) do
table.insert(qualifiers, metaKey .. "=" .. metaValue)
end
table.sort(qualifiers)
qualifiedProperty = propertyName .. "#" .. table.concat(qualifiers, ";")
end
-- Set property with array handling
if properties[qualifiedProperty] then
if type(properties[qualifiedProperty]) ~= "table" then
properties[qualifiedProperty] = {properties[qualifiedProperty]}
end
table.insert(properties[qualifiedProperty], value)
else
properties[qualifiedProperty] = value
end
end
end
end
-- Add simple property to parser function result
local function addSimplePropertyToResult(result, propertyName, value, transformFunc, defaultValue)
if value and value ~= "" and transformFunc then value = transformFunc(value) end
if value and value ~= "" then
table.insert(result, string.format(' |%s=%s', propertyName, value))
return 1
elseif defaultValue then
table.insert(result, string.format(' |%s=%s', propertyName, defaultValue))
return 1
end
return 0
end
-- Enhanced version supporting complex mappings (fallback when mw.smw unavailable)
function p.generateEnhancedAnnotations(args, mappings, options)
args = args or {}
mappings = mappings or {}
options = options or {}
-- Initialize with defaults
local visible = options.visible or false
local prefix = options.prefix or ""
local transform = options.transform or {}
local default = options.default or {}
local conditional = options.conditional or {}
-- Build annotation block
local result = {}
if not visible then table.insert(result, '<div style="display:none;">') end
table.insert(result, ' {{#set:')
local propertyCount = 0
-- Process all property types
for property, mapping in pairs(mappings) do
local fullPropertyName = prefix .. property
if type(mapping) == "string" then
-- Simple string mapping
propertyCount = propertyCount + addSimplePropertyToResult(result,
fullPropertyName, args[mapping], transform[property], default[property])
elseif type(mapping) == "table" then
if mapping.param then
-- Object with param structure
propertyCount = propertyCount + addSimplePropertyToResult(result,
fullPropertyName, args[mapping.param], transform[property], default[property])
elseif mapping.mappings then
-- Complex mapping with multiple parameters
for _, mappingEntry in ipairs(mapping.mappings) do
local param = mappingEntry.param
local metadata = mappingEntry.metadata or {}
local value = args[param]
if value and value ~= "" then
-- Apply transform
if transform[property] then value = transform[property](value) end
-- Add metadata qualifiers
local qualifiedProperty = fullPropertyName
if next(metadata) then
local qualifiers = {}
for metaKey, metaValue in pairs(metadata) do
table.insert(qualifiers, metaKey .. "=" .. metaValue)
end
table.sort(qualifiers)
qualifiedProperty = fullPropertyName .. "#" .. table.concat(qualifiers, ";")
end
-- Add property to result
table.insert(result, string.format(' |%s=%s', qualifiedProperty, value))
propertyCount = propertyCount + 1
end
end
end
end
end
-- Handle conditional properties
for property, condition in pairs(conditional) do
local fullPropertyName = prefix .. property
if args[condition.param] and args[condition.param] == condition.value then
table.insert(result, string.format(' |%s=%s', fullPropertyName, condition.target or "true"))
propertyCount = propertyCount + 1
end
end
-- Close parser function and wrapper
table.insert(result, ' }}')
if not visible then table.insert(result, '</div>') end
-- Return result or empty string
return propertyCount > 0 and table.concat(result, "\n") or ""
end
--[[ Sets semantic properties with native API or fallback
@param args - Template parameters
@param mappings - Property mappings in formats:
- Simple: {["Property"] = "param_name"}
- Object: {["Property"] = {param = "param_name"}}
- Complex: {["Property"] = {mappings = [{param="p1", metadata={...}}, ...]}}
- Subobject: {["Property"] = {is_subobject=true, properties={...}, id_prefix="..."}}
@param options - Configuration options
@return Generated markup or empty string if using native API
]]
function p.setSemanticProperties(args, mappings, options)
-- Fall back to parser functions if mw.smw unavailable
if not mw.smw then return p.generateEnhancedAnnotations(args, mappings, options) end
options = options or {}
local transform = options.transform or {}
local default = options.default or {}
local prefix = options.prefix or ""
local properties = {}
local semanticOutput = ""
-- Process all mapping types
for property, mapping in pairs(mappings) do
local fullPropertyName = prefix .. property
if type(mapping) == "string" then
-- Simple string mapping
processSimpleMapping(properties, fullPropertyName, args[mapping], transform[property], default[property])
elseif type(mapping) == "table" then
if mapping.is_subobject then
-- Subobject definition
local subobjectProperties = mapping.properties or {}
local actualProperties = {}
-- Process subobject properties
for subPropName, subPropValue in pairs(subobjectProperties) do
if type(subPropValue) == "table" and subPropValue.param then
-- Object with param reference
local paramName = subPropValue.param
if args[paramName] and args[paramName] ~= "" then
local value = args[paramName]
if subPropValue.transform and type(subPropValue.transform) == "function" then
value = subPropValue.transform(value)
end
actualProperties[subPropName] = value
end
elseif type(subPropValue) == "string" then
-- String mapping or static value
if args[subPropValue] and args[subPropValue] ~= "" then
actualProperties[subPropName] = args[subPropValue]
else
actualProperties[subPropName] = subPropValue
end
end
end
-- Create subobject if properties exist
if next(actualProperties) then
-- Generate ID
local idPrefix = mapping.id_prefix or "subobj"
local idValue = ""
local primaryProp = mapping.primary_property
if primaryProp and actualProperties[primaryProp] then
idValue = tostring(actualProperties[primaryProp]):gsub("[^%w]", "_")
else
idValue = tostring(os.time() % 10000) .. "_" .. math.random(1000, 9999)
end
local subobjectId = idPrefix .. "_" .. idValue
-- Create subobject
local subobjectResult = mw.smw.subobject({
id = subobjectId,
properties = actualProperties
})
-- Add error info if needed
if type(subobjectResult) == "table" and subobjectResult.error then
semanticOutput = semanticOutput .. "\n<!-- SMW Error: " ..
tostring(subobjectResult.error) .. " -->"
end
end
elseif mapping.param then
-- Single mapping with object structure
processSimpleMapping(properties, fullPropertyName, args[mapping.param], transform[property], default[property])
elseif mapping.mappings then
-- Complex mapping with multiple parameters
processComplexMapping(properties, fullPropertyName, args, mapping.mappings, transform[property])
end
end
end
-- Set properties if any exist
if next(properties) then
local success, result = pcall(function() return mw.smw.set(properties) end)
if success then return semanticOutput
else return p.generateEnhancedAnnotations(args, mappings, options) .. semanticOutput
end
end
return semanticOutput
end
-- Generate #subobject parser function with optional ID
function p.generateSmwSubobjectFragment(properties, id)
local result = '<div style="display:none;">\n {{#subobject:'
if id and id ~= "" then result = result .. "|@" .. id end
for propName, propValue in pairs(properties) do
if propValue and propValue ~= "" then
result = result .. "\n |" .. propName .. "=" .. propValue
end
end
result = result .. "\n }}\n</div>"
return result
end
-- For backward compatibility
generateSmwSubobjectFragment = p.generateSmwSubobjectFragment
return p