Module:SemanticAnnotations: Difference between revisions
// via Wikitext Extension for VSCode |
// via Wikitext Extension for VSCode |
||
| Line 1: | Line 1: | ||
-- Module:SemanticAnnotations | -- Module:SemanticAnnotations | ||
-- Generates semantic annotations for templates | -- Generates semantic annotations for templates in MediaWiki | ||
-- Docs: https://github.com/SemanticMediaWiki/SemanticScribunto/tree/master/docs | -- Docs: https://github.com/SemanticMediaWiki/SemanticScribunto/tree/master/docs | ||
local p = {} | local p = {} | ||
-- | -- Trim whitespace helper | ||
local function trim(s) | local function trim(s) return (s:gsub("^%s+", ""):gsub("%s+$", "")) end | ||
end | |||
--[[ | --[[ Generates semantic annotations using #set parser function | ||
@param args - Template parameters table | |||
@param mappings - Property mappings: {["Property"] = "param"} or complex format | |||
@param options - Config options (visible, prefix, transform, default, conditional) | |||
@return Wikitext string containing semantic annotations | |||
]] | ]] | ||
function p.generateAnnotations(args, mappings, options) | function p.generateAnnotations(args, mappings, options) | ||
-- | -- If complex mappings found, delegate to enhanced function | ||
if mappings and type(mappings) == "table" then | if mappings and type(mappings) == "table" then | ||
for _, mapping in pairs(mappings) do | for _, mapping in pairs(mappings) do | ||
if type(mapping) == "table" then | if type(mapping) == "table" then return p.generateEnhancedAnnotations(args, mappings, options) end | ||
end | end | ||
end | end | ||
-- | -- Initialize with defaults | ||
args = args or {} | args = args or {} | ||
mappings = mappings or {} | mappings = mappings or {} | ||
options = options or {} | options = options or {} | ||
local visible = options.visible or false | local visible = options.visible or false | ||
local prefix = options.prefix or "" | local prefix = options.prefix or "" | ||
| Line 54: | Line 32: | ||
local conditional = options.conditional or {} | local conditional = options.conditional or {} | ||
-- | -- Build annotation block | ||
local result = {} | local result = {} | ||
if not visible then table.insert(result, '<div style="display:none;">') end | |||
if not visible then | |||
table.insert(result, ' {{#set:') | table.insert(result, ' {{#set:') | ||
local propertyCount = 0 | local propertyCount = 0 | ||
-- | -- Process string mappings | ||
for property, param in pairs(mappings) do | for property, param in pairs(mappings) do | ||
if type(param) == "string" then | if type(param) == "string" then | ||
local fullPropertyName = prefix .. property | local fullPropertyName = prefix .. property | ||
local value = args[param] | local value = args[param] | ||
-- Apply transform if | -- Apply transform if exists | ||
if value and transform[property] then | if value and transform[property] then value = transform[property](value) end | ||
-- | -- Add property if value exists or default provided | ||
if value and value ~= "" then | if value and value ~= "" then | ||
table.insert(result, string.format(' |%s=%s', fullPropertyName, value)) | table.insert(result, string.format(' |%s=%s', fullPropertyName, value)) | ||
| Line 91: | Line 58: | ||
end | end | ||
-- | -- Process conditional properties | ||
for property, condition in pairs(conditional) do | for property, condition in pairs(conditional) do | ||
local fullPropertyName = prefix .. property | local fullPropertyName = prefix .. property | ||
| Line 100: | Line 67: | ||
end | end | ||
-- Close the | -- Close the parser function and wrapper | ||
table.insert(result, ' }}') | 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 | end | ||
-- | -- Renders a table using TemplateStructure and adds annotations | ||
function p.renderWithSemantics(args, config, semanticMappings, semanticOptions) | function p.renderWithSemantics(args, config, semanticMappings, semanticOptions) | ||
local TemplateStructure = require('Module:TemplateStructure') | local TemplateStructure = require('Module:TemplateStructure') | ||
local renderedTable = TemplateStructure.render(args, config) | local renderedTable = TemplateStructure.render(args, config) | ||
local annotations = p.generateAnnotations(args, semanticMappings, semanticOptions) | local annotations = p.generateAnnotations(args, semanticMappings, semanticOptions) | ||
return renderedTable .. "\n" .. annotations | return renderedTable .. "\n" .. annotations | ||
end | end | ||
-- Allows templates to append | -- Allows templates to append annotations directly via transclusion | ||
function p.appendToTemplate(frame) | function p.appendToTemplate(frame) | ||
local args = frame.args | local args = frame.args | ||
| Line 146: | Line 89: | ||
local parentArgs = parent and parent.args or {} | local parentArgs = parent and parent.args or {} | ||
-- | -- Build mappings from numbered parameters | ||
local mappings = {} | local mappings = {} | ||
local i = 1 | local i = 1 | ||
while args["property" .. i] and args["param" .. i] do | while args["property" .. i] and args["param" .. i] do | ||
mappings[args["property" .. i]] = args["param" .. i] | mappings[args["property" .. i]] = args["param" .. i] | ||
| Line 155: | Line 97: | ||
end | end | ||
-- Extract options | -- Extract options and generate annotations | ||
local options = { | local options = { | ||
visible = args.visible == "true", | visible = args.visible == "true", | ||
prefix = args.prefix or "" | prefix = args.prefix or "" | ||
} | } | ||
return p.generateAnnotations(parentArgs, mappings, options) | return p.generateAnnotations(parentArgs, mappings, options) | ||
end | end | ||
-- | -- Process simple property mapping | ||
local function processSimpleMapping(properties, propertyName, value, transformFunc, defaultValue) | local function processSimpleMapping(properties, propertyName, value, transformFunc, defaultValue) | ||
-- Apply transform if | -- Apply transform if applicable | ||
if value and value ~= "" and transformFunc then | 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 value and value ~= "" then | ||
if properties[propertyName] then | if properties[propertyName] then | ||
-- Convert to array if | -- Convert to array if first duplicate | ||
if type(properties[propertyName]) ~= "table" then | if type(properties[propertyName]) ~= "table" then | ||
properties[propertyName] = {properties[propertyName]} | properties[propertyName] = {properties[propertyName]} | ||
end | end | ||
table.insert(properties[propertyName], value) | table.insert(properties[propertyName], value) | ||
else | else | ||
properties[propertyName] = value | properties[propertyName] = value | ||
end | end | ||
| Line 191: | Line 126: | ||
end | end | ||
-- | -- Process complex property mapping with metadata | ||
local function processComplexMapping(properties, propertyName, args, mappings, transformFunc) | local function processComplexMapping(properties, propertyName, args, mappings, transformFunc) | ||
for _, mappingEntry in ipairs(mappings) do | for _, mappingEntry in ipairs(mappings) do | ||
| Line 198: | Line 133: | ||
local value = args[param] | local value = args[param] | ||
if value and value ~= "" then | if value and value ~= "" then | ||
-- Apply transform | -- Apply transform | ||
if transformFunc then | if transformFunc then value = transformFunc(value) end | ||
-- | -- Handle metadata qualifiers | ||
local qualifiedProperty = propertyName | local qualifiedProperty = propertyName | ||
if next(metadata) then | if next(metadata) then | ||
| Line 213: | Line 144: | ||
table.insert(qualifiers, metaKey .. "=" .. metaValue) | table.insert(qualifiers, metaKey .. "=" .. metaValue) | ||
end | end | ||
table.sort(qualifiers) | table.sort(qualifiers) | ||
qualifiedProperty = propertyName .. "#" .. table.concat(qualifiers, ";") | qualifiedProperty = propertyName .. "#" .. table.concat(qualifiers, ";") | ||
end | end | ||
-- Set | -- Set property with array handling | ||
if properties[qualifiedProperty] then | if properties[qualifiedProperty] then | ||
if type(properties[qualifiedProperty]) ~= "table" then | if type(properties[qualifiedProperty]) ~= "table" then | ||
properties[qualifiedProperty] = {properties[qualifiedProperty]} | properties[qualifiedProperty] = {properties[qualifiedProperty]} | ||
end | end | ||
table.insert(properties[qualifiedProperty], value) | table.insert(properties[qualifiedProperty], value) | ||
else | else | ||
properties[qualifiedProperty] = value | properties[qualifiedProperty] = value | ||
end | end | ||
end | end | ||
end | end | ||
end | end | ||
-- | -- Add simple property to parser function result | ||
local function addSimplePropertyToResult(result, propertyName, value, transformFunc, defaultValue) | local function addSimplePropertyToResult(result, propertyName, value, transformFunc, defaultValue) | ||
if value and value ~= "" and transformFunc then value = transformFunc(value) end | |||
if value and value ~= "" and transformFunc then | |||
if value and value ~= "" then | if value and value ~= "" then | ||
table.insert(result, string.format(' |%s=%s', propertyName, value)) | table.insert(result, string.format(' |%s=%s', propertyName, value)) | ||
| Line 250: | Line 172: | ||
return 1 | return 1 | ||
end | end | ||
return 0 | return 0 | ||
end | end | ||
-- | -- Enhanced version supporting complex mappings (fallback when mw.smw unavailable) | ||
function p.generateEnhancedAnnotations(args, mappings, options) | function p.generateEnhancedAnnotations(args, mappings, options) | ||
args = args or {} | args = args or {} | ||
| Line 263: | Line 181: | ||
options = options or {} | options = options or {} | ||
-- | -- Initialize with defaults | ||
local visible = options.visible or false | local visible = options.visible or false | ||
local prefix = options.prefix or "" | local prefix = options.prefix or "" | ||
| Line 270: | Line 188: | ||
local conditional = options.conditional or {} | local conditional = options.conditional or {} | ||
-- | -- Build annotation block | ||
local result = {} | local result = {} | ||
if not visible then table.insert(result, '<div style="display:none;">') end | |||
if not visible then | |||
table.insert(result, ' {{#set:') | table.insert(result, ' {{#set:') | ||
local propertyCount = 0 | local propertyCount = 0 | ||
-- | -- Process all property types | ||
for property, mapping in pairs(mappings) do | for property, mapping in pairs(mappings) do | ||
local fullPropertyName = prefix .. property | local fullPropertyName = prefix .. property | ||
if type(mapping) == "string" then | if type(mapping) == "string" then | ||
-- | -- Simple string mapping | ||
propertyCount = propertyCount + addSimplePropertyToResult(result, | propertyCount = propertyCount + addSimplePropertyToResult(result, | ||
fullPropertyName, args[mapping], transform[property], default[property]) | fullPropertyName, args[mapping], transform[property], default[property]) | ||
elseif type(mapping) == "table" then | elseif type(mapping) == "table" then | ||
if mapping.param then | if mapping.param then | ||
-- | -- Object with param structure | ||
propertyCount = propertyCount + addSimplePropertyToResult(result, | propertyCount = propertyCount + addSimplePropertyToResult(result, | ||
fullPropertyName, args[mapping.param], transform[property], default[property]) | fullPropertyName, args[mapping.param], transform[property], default[property]) | ||
| Line 305: | Line 214: | ||
local value = args[param] | local value = args[param] | ||
if value and value ~= "" then | if value and value ~= "" then | ||
-- Apply transform | -- Apply transform | ||
if transform[property] then | if transform[property] then value = transform[property](value) end | ||
-- Add metadata qualifiers | -- Add metadata qualifiers | ||
local qualifiedProperty = fullPropertyName | local qualifiedProperty = fullPropertyName | ||
if next(metadata) then | if next(metadata) then | ||
| Line 319: | Line 225: | ||
table.insert(qualifiers, metaKey .. "=" .. metaValue) | table.insert(qualifiers, metaKey .. "=" .. metaValue) | ||
end | end | ||
table.sort(qualifiers) | table.sort(qualifiers) | ||
qualifiedProperty = fullPropertyName .. "#" .. table.concat(qualifiers, ";") | qualifiedProperty = fullPropertyName .. "#" .. table.concat(qualifiers, ";") | ||
end | end | ||
-- Add | -- Add property to result | ||
table.insert(result, string.format(' |%s=%s', qualifiedProperty, value)) | table.insert(result, string.format(' |%s=%s', qualifiedProperty, value)) | ||
propertyCount = propertyCount + 1 | propertyCount = propertyCount + 1 | ||
| Line 333: | Line 238: | ||
end | end | ||
-- Handle conditional properties | -- Handle conditional properties | ||
for property, condition in pairs(conditional) do | for property, condition in pairs(conditional) do | ||
local fullPropertyName = prefix .. property | local fullPropertyName = prefix .. property | ||
| Line 342: | Line 247: | ||
end | end | ||
-- Close | -- Close parser function and wrapper | ||
table.insert(result, ' }}') | 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 | end | ||
--[[ | --[[ Sets semantic properties with native API or fallback | ||
@param args - Template parameters | |||
@param mappings - Property mappings in various 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) | function p.setSemanticProperties(args, mappings, options) | ||
-- | -- Fall back to parser functions if mw.smw unavailable | ||
if not mw.smw then | if not mw.smw then return p.generateEnhancedAnnotations(args, mappings, options) end | ||
options = options or {} | options = options or {} | ||
| Line 384: | Line 273: | ||
local default = options.default or {} | local default = options.default or {} | ||
local prefix = options.prefix or "" | local prefix = options.prefix or "" | ||
local properties = {} | local properties = {} | ||
local semanticOutput = "" | local semanticOutput = "" | ||
-- Process all | -- Process all mapping types | ||
for property, mapping in pairs(mappings) do | for property, mapping in pairs(mappings) do | ||
local fullPropertyName = prefix .. property | local fullPropertyName = prefix .. property | ||
if type(mapping) == "string" then | if type(mapping) == "string" then | ||
-- | -- Simple string mapping | ||
processSimpleMapping(properties, fullPropertyName, args[mapping], transform[property], default[property]) | processSimpleMapping(properties, fullPropertyName, args[mapping], transform[property], default[property]) | ||
elseif type(mapping) == "table" then | elseif type(mapping) == "table" then | ||
if mapping.is_subobject then | if mapping.is_subobject then | ||
-- | -- Subobject definition | ||
local subobjectProperties = mapping.properties or {} | local subobjectProperties = mapping.properties or {} | ||
local actualProperties = {} | local actualProperties = {} | ||
-- Process | -- Process subobject properties | ||
for subPropName, subPropValue in pairs(subobjectProperties) do | for subPropName, subPropValue in pairs(subobjectProperties) do | ||
if type(subPropValue) == "table" and subPropValue.param then | if type(subPropValue) == "table" and subPropValue.param then | ||
-- Object with param reference | -- Object with param reference | ||
local paramName = subPropValue.param | local paramName = subPropValue.param | ||
if args[paramName] and args[paramName] ~= "" then | if args[paramName] and args[paramName] ~= "" then | ||
local value = args[paramName] | local value = args[paramName] | ||
if subPropValue.transform and type(subPropValue.transform) == "function" then | if subPropValue.transform and type(subPropValue.transform) == "function" then | ||
value = subPropValue.transform(value) | value = subPropValue.transform(value) | ||
end | end | ||
actualProperties[subPropName] = value | actualProperties[subPropName] = value | ||
end | end | ||
elseif type(subPropValue) == "string" then | elseif type(subPropValue) == "string" then | ||
-- | -- String mapping or static value | ||
if args[subPropValue] and args[subPropValue] ~= "" then | if args[subPropValue] and args[subPropValue] ~= "" then | ||
actualProperties[subPropName] = args[subPropValue] | actualProperties[subPropName] = args[subPropValue] | ||
else | else | ||
actualProperties[subPropName] = subPropValue | actualProperties[subPropName] = subPropValue | ||
end | end | ||
| Line 437: | Line 311: | ||
end | end | ||
-- | -- Create subobject if properties exist | ||
if next(actualProperties) then | if next(actualProperties) then | ||
-- Generate | -- Generate ID | ||
local idPrefix = mapping.id_prefix or "subobj" | local idPrefix = mapping.id_prefix or "subobj" | ||
local idValue = "" | local idValue = "" | ||
local primaryProp = mapping.primary_property | |||
if primaryProp and actualProperties[primaryProp] then | if primaryProp and actualProperties[primaryProp] then | ||
idValue = tostring(actualProperties[primaryProp]):gsub("[^%w]", "_") | idValue = tostring(actualProperties[primaryProp]):gsub("[^%w]", "_") | ||
else | else | ||
idValue = tostring(os.time() % 10000) .. "_" .. math.random(1000, 9999) | idValue = tostring(os.time() % 10000) .. "_" .. math.random(1000, 9999) | ||
end | end | ||
local subobjectId = idPrefix .. "_" .. idValue | local subobjectId = idPrefix .. "_" .. idValue | ||
-- Create | -- 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 | ||
end | end | ||
| Line 485: | Line 348: | ||
end | end | ||
-- | -- Set properties if any exist | ||
if next(properties) then | if next(properties) then | ||
local success, result = pcall(function() return mw.smw.set(properties) end) | |||
local success, result = pcall(function() | if success then return semanticOutput | ||
else return p.generateEnhancedAnnotations(args, mappings, options) .. semanticOutput | |||
if success then | |||
else | |||
end | end | ||
end | end | ||
| Line 504: | Line 359: | ||
end | end | ||
-- | -- Generate #subobject parser function with optional ID | ||
function p.generateSmwSubobjectFragment(properties, id) | function p.generateSmwSubobjectFragment(properties, id) | ||
local result = '<div style="display:none;">\n {{#subobject:' | local result = '<div style="display:none;">\n {{#subobject:' | ||
if id and id ~= "" then result = result .. "|@" .. id end | |||
if id and id ~= "" then | |||
for propName, propValue in pairs(properties) do | for propName, propValue in pairs(properties) do | ||
| Line 523: | Line 374: | ||
end | end | ||
-- For backward compatibility | -- For backward compatibility | ||
generateSmwSubobjectFragment = p.generateSmwSubobjectFragment | generateSmwSubobjectFragment = p.generateSmwSubobjectFragment | ||
return p | return p | ||