Jump to content

Module:SemanticAnnotations

Revision as of 01:23, 31 March 2025 by MarkWD (talk | contribs) (// via Wikitext Extension for VSCode)

Documentation for this module may be created at Module:SemanticAnnotations/doc

-- Module:SemanticAnnotations
-- Generates semantic annotations for templates
-- Compatible with Semantic MediaWiki, Semantic Scribunto, Semantic Drilldown, and DynamicPageList3
-- Docs: https://github.com/SemanticMediaWiki/SemanticScribunto/tree/master/docs

local p = {}

-- Private helper function to trim whitespace
local function trim(s)
    return (s:gsub("^%s+", ""):gsub("%s+$", ""))
end

--[[
    Generates semantic annotations using SMW's #set parser function.
    
    @param args     - Template parameters table
    @param mappings - Property to parameter mappings: {["Property"] = "param_name", ...}
    @param options  - Configuration options:
                     {
                       visible = false,   -- Show annotations (default: false)
                       prefix = "",       -- Property prefix
                       transform = {},    -- Transform functions: {["Property"] = function(val) ... end}
                       default = {},      -- Default values: {["Property"] = "default"}
                       conditional = {},  -- Conditional mappings: {["Property"] = {param="x", value="y"}}
                     }
    
    @return Wikitext string containing semantic annotations
]]
function p.generateAnnotations(args, mappings, options)
    args = args or {}
    mappings = mappings or {}
    options = options or {}
    
    -- Set defaults for options
    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 {}
    
    -- Start building the annotation block
    local result = {}
    
    -- Determine if we need the hidden div wrapper
    if not visible then
        table.insert(result, '<div style="display:none;">')
    end
    
    -- Start the #set parser function
    table.insert(result, '  {{#set:')
    
    -- Process all property mappings
    local propertyCount = 0
    
    -- Handle regular property mappings
    for property, param in pairs(mappings) do
        local fullPropertyName = prefix .. property
        local value = args[param]
        
        -- Apply transform if one exists for this property
        if value and transform[property] then
            value = transform[property](value)
        end
        
        -- Use the value if it exists, otherwise use default if 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
    
    -- 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 the #set parser function
    table.insert(result, '  }}')
    
    -- Close the hidden div if we're using it
    if not visible then
        table.insert(result, '</div>')
    end
    
    -- If no properties were set, return an empty string
    if propertyCount == 0 then
        return ""
    end
    
    -- Join all lines and return
    return table.concat(result, "\n")
end

--[[
    Renders a table using TemplateStructure and adds semantic annotations.
    
    @param args             - Template parameters
    @param config           - TemplateStructure configuration
    @param semanticMappings - Property to parameter mappings
    @param semanticOptions  - Annotation configuration options
    
    @return Rendered template with semantic annotations
]]
function p.renderWithSemantics(args, config, semanticMappings, semanticOptions)
    local TemplateStructure = require('Module:TemplateStructure')
    
    -- Render the table structure
    local renderedTable = TemplateStructure.render(args, config)
    
    -- Generate the semantic annotations
    local annotations = p.generateAnnotations(args, semanticMappings, semanticOptions)
    
    -- Combine and return
    return renderedTable .. "\n" .. annotations
end

-- Allows templates to append semantic annotations directly via transclusion
function p.appendToTemplate(frame)
    local args = frame.args
    local parent = frame:getParent()
    local parentArgs = parent and parent.args or {}
    
    -- Mapping is defined as pairs of properties and 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
    local options = {
        visible = args.visible == "true",
        prefix = args.prefix or ""
    }
    
    -- Generate and return the annotations
    return p.generateAnnotations(parentArgs, mappings, options)
end

--[[
    Sets semantic properties using mw.smw API when available, with parser function fallback.
    
    @param args     - Template parameters
    @param mappings - Property to parameter mappings
    @param options  - Configuration options (same as generateAnnotations)
    
    @return Empty string if set via mw.smw, or generated annotations string
]]
function p.setSemanticProperties(args, mappings, options)
    -- Check if mw.smw is available
    if not mw.smw then
        -- Fall back to the parser function approach if mw.smw is not available
        return p.generateAnnotations(args, mappings, options)
    end
    
    options = options or {}
    local transform = options.transform or {}
    local default = options.default or {}
    local prefix = options.prefix or ""
    
    -- Build the property table for mw.smw.set
    local properties = {}
    
    -- Process regular mappings
    for property, param in pairs(mappings) do
        local fullPropertyName = prefix .. property
        local value = args[param]
        
        -- Apply transform if one exists
        if value and transform[property] then
            value = transform[property](value)
        end
        
        -- Use value if it exists, otherwise use default
        if value and value ~= "" then
            properties[fullPropertyName] = value
        elseif default[property] then
            properties[fullPropertyName] = default[property]
        end
    end
    
    -- Only set properties if we have some
    if next(properties) then
        -- Set the properties using the native SMW interface
        local success, result = pcall(function()
            return mw.smw.set(properties)
        end)
        
        -- If successful, return empty string (properties are set behind the scenes)
        -- If failed, fall back to parser function approach
        if success then
            return ""
        else
            return p.generateAnnotations(args, mappings, options)
        end
    end
    
    return ""
end

return p