Module:SemanticAnnotations: Difference between revisions

// via Wikitext Extension for VSCode
// via Wikitext Extension for VSCode
 
(31 intermediate revisions by the same user not shown)
Line 1: Line 1:
-- Module:SemanticAnnotations
--[[
-- Generates semantic annotations for templates in MediaWiki
* Name: SemanticAnnotations
-- REVIEW: In the face of the new Helpers
* Author: Mark W. Datysgeld
* Description: Primary semantic integration module for generating semantic properties with transformation support and property limits
* Notes: Implements batching, deduplication and property limits (200 total, 25 per property); supports simple, object, complex, and subobject mappings; falls back to parser functions when mw.smw unavailable; includes pruning to prevent server crashes
]]


local p = {}
local p = {}
local TemplateHelpers = require('Module:TemplateHelpers')
local NormalizationText = require('Module:NormalizationText')


-- Trim whitespace helper
-- Limits to prevent server crashes
local function trim(s) return (s:gsub("^%s+", ""):gsub("%s+$", "")) end
local TOTAL_LIMIT = 200  -- per page render, tune later
local VALUE_LIMIT = 25    -- per individual property


--[[ Generates semantic annotations using #set parser function
--[[ Fallback for mw.smw.set using the #set parser function.
   @param args - Template parameters
   @param args - Template parameters
   @param mappings - Property mappings: {["Property"] = "param"} or complex format
   @param mappings - Property mappings: {["Property"] = "param"} or complex format
Line 42: Line 48:
         if type(param) == "string" then
         if type(param) == "string" then
             local fullPropertyName = prefix .. property
             local fullPropertyName = prefix .. property
             local value = args[param]
             local _, value = TemplateHelpers.getFieldValue(args, { key = param })
              
              
             -- Apply transform if needed
             -- Apply transform if needed
Line 61: Line 67:
     for property, condition in pairs(conditional) do
     for property, condition in pairs(conditional) do
         local fullPropertyName = prefix .. property
         local fullPropertyName = prefix .. property
         if args[condition.param] and args[condition.param] == condition.value then
         local _, condValue = TemplateHelpers.getFieldValue(args, { key = condition.param })
        if condValue == condition.value then
             table.insert(result, string.format('    |%s=%s', fullPropertyName, condition.target or "true"))
             table.insert(result, string.format('    |%s=%s', fullPropertyName, condition.target or "true"))
             propertyCount = propertyCount + 1
             propertyCount = propertyCount + 1
Line 75: Line 82:
end
end


-- Renders a table using TemplateStructure and adds annotations
-- Prune properties to prevent server crashes
function p.renderWithSemantics(args, config, semanticMappings, semanticOptions)
local function prune(properties)
     local TemplateStructure = require('Module:TemplateStructure')
     local total = 0
    local renderedTable = TemplateStructure.render(args, config)
    local TemplateHelpers = require('Module:TemplateHelpers')
    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
     for prop,val in pairs(properties) do
    local mappings = {}
        if type(val)=='table' then
    local i = 1
            -- dedup array using centralized removeDuplicates function
    while args["property" .. i] and args["param" .. i] do
            properties[prop] = TemplateHelpers.removeDuplicates(val)
        mappings[args["property" .. i]] = args["param" .. i]
        end
         i = i + 1
        -- per-property cap
        if type(properties[prop])=='table' and #properties[prop] > VALUE_LIMIT then
            properties[prop] = { unpack(properties[prop],1,VALUE_LIMIT) }
        end
         -- global counter
        total = total + (type(properties[prop])=='table' and #properties[prop] or 1)
     end
     end
      
     if total > TOTAL_LIMIT then return nil, "SMW limit hit: "..total end
    -- Extract options and generate annotations
     return properties
    local options = {
        visible = args.visible == "true",
        prefix = args.prefix or ""
    }
     return p.generateAnnotations(parentArgs, mappings, options)
end
end


Line 131: Line 129:
         local param = mappingEntry.param
         local param = mappingEntry.param
         local metadata = mappingEntry.metadata or {}
         local metadata = mappingEntry.metadata or {}
         local value = args[param]
        -- Case-insensitive lookup: try exact match first, then lowercase
         local value = args[param] or args[param:lower()]
          
          
         if value and value ~= "" then
         if value and value ~= "" then
Line 175: Line 174:
end
end


-- Enhanced version supporting complex mappings (fallback when mw.smw unavailable)
-- Enhanced fallback for mw.smw.set with complex mapping support.
function p.generateEnhancedAnnotations(args, mappings, options)
function p.generateEnhancedAnnotations(args, mappings, options)
     args = args or {}
     args = args or {}
Line 199: Line 198:
          
          
         if type(mapping) == "string" then
         if type(mapping) == "string" then
             -- Simple string mapping
             -- Simple string mapping with case-insensitive lookup
            local value = args[mapping] or args[mapping:lower()]
             propertyCount = propertyCount + addSimplePropertyToResult(result,  
             propertyCount = propertyCount + addSimplePropertyToResult(result,  
                 fullPropertyName, args[mapping], transform[property], default[property])
                 fullPropertyName, value, 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
                 -- Object with param structure with case-insensitive lookup
                local value = args[mapping.param] or args[mapping.param:lower()]
                 propertyCount = propertyCount + addSimplePropertyToResult(result,  
                 propertyCount = propertyCount + addSimplePropertyToResult(result,  
                     fullPropertyName, args[mapping.param], transform[property], default[property])
                     fullPropertyName, value, transform[property], default[property])
             elseif mapping.mappings then
             elseif mapping.mappings then
                 -- Complex mapping with multiple parameters
                 -- Complex mapping with multiple parameters
Line 212: Line 213:
                     local param = mappingEntry.param
                     local param = mappingEntry.param
                     local metadata = mappingEntry.metadata or {}
                     local metadata = mappingEntry.metadata or {}
                     local value = args[param]
                    -- Case-insensitive lookup
                     local value = args[param] or args[param:lower()]
                      
                      
                     if value and value ~= "" then
                     if value and value ~= "" then
Line 255: Line 257:
end
end


--[[ Sets semantic properties with native API or fallback
--[[ Sets semantic properties using the native mw.smw API.
  This is the primary function for setting semantic data.
   @param args - Template parameters
   @param args - Template parameters
   @param mappings - Property mappings in formats:
   @param mappings - Property mappings in formats:
Line 281: Line 284:
          
          
         if type(mapping) == "string" then
         if type(mapping) == "string" then
             -- Simple string mapping
             -- Simple string mapping with case-insensitive lookup
             processSimpleMapping(properties, fullPropertyName, args[mapping], transform[property], default[property])
            -- Try exact match first, then lowercase
            local value = args[mapping] or args[mapping:lower()]
             processSimpleMapping(properties, fullPropertyName, value, transform[property], default[property])
         elseif type(mapping) == "table" then
         elseif type(mapping) == "table" then
             if mapping.is_subobject then
             if mapping.is_subobject then
Line 339: Line 344:
                 end
                 end
             elseif mapping.param then
             elseif mapping.param then
                 -- Single mapping with object structure
                 -- Single mapping with object structure with case-insensitive lookup
                 processSimpleMapping(properties, fullPropertyName, args[mapping.param], transform[property], default[property])
                local value = args[mapping.param] or args[mapping.param:lower()]
                 processSimpleMapping(properties, fullPropertyName, value, transform[property], default[property])
             elseif mapping.mappings then
             elseif mapping.mappings then
                 -- Complex mapping with multiple parameters
                 -- Complex mapping with multiple parameters
                 processComplexMapping(properties, fullPropertyName, args, mapping.mappings, transform[property])
                 processComplexMapping(properties, fullPropertyName, args, mapping.mappings, transform[property])
            else
                -- Direct array of values: add each value as a property
                for _, value in ipairs(mapping) do
                    processSimpleMapping(properties, fullPropertyName, value, transform[property], default[property])
                end
             end
             end
         end
         end
    end
   
    -- Before setting properties, prune them
    local pruned, err = prune(properties)
    if not pruned then
        -- Add HTML comment with error message
        semanticOutput = semanticOutput .. "\n<!-- " .. err .. " -->"
        return semanticOutput
     end
     end
      
      
     -- Set properties if any exist
     -- Set properties if any exist
     if next(properties) then
     if next(pruned) then
         local success, result = pcall(function() return mw.smw.set(properties) end)
         local success, result = pcall(function() return mw.smw.set(pruned) end)
         if success then return semanticOutput
         if success then return semanticOutput
         else return p.generateEnhancedAnnotations(args, mappings, options) .. semanticOutput
         else return p.generateEnhancedAnnotations(args, mappings, options) .. semanticOutput
Line 373: Line 392:
     return result
     return result
end
end
-- For backward compatibility
generateSmwSubobjectFragment = p.generateSmwSubobjectFragment


return p
return p