Module:SMWUtils

Revision as of 07:03, 15 January 2026 by Arclight-MCP-Bot (talk | contribs) (Rewrite to use mw.smw.ask() now that Semantic Scribunto is installed (via update-page on MediaWiki MCP Server))

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

-- Module:SMWUtils
-- Utility functions for Semantic MediaWiki queries
-- Requires Semantic Scribunto extension for mw.smw.ask()

local p = {}

local function trim(s)
    if s == nil then return nil end
    s = tostring(s)
    s = s:gsub('^%s+', ''):gsub('%s+$', '')
    if s == '' then return nil end
    return s
end

--- Find all results where a property equals its minimum value across the result set
-- @param frame Frame object with args:
--   conditions: SMW query conditions (e.g., "[[Has video signal format::Component (YPbPr)]]")
--   minProperty: Property to find minimum of (e.g., "Has CRT model.Has screen size inches")
--   printouts: Comma-separated list of properties to display
--   format: Output format (table, ul, ol) - default "table"
--   headers: Comma-separated header names for table format
--   intro: Optional intro text (supports wikitext)
-- @return Wikitext output
function p.findAllWithMin(frame)
    local args = frame.args
    local conditions = trim(args.conditions) or ''
    local minProperty = trim(args.minProperty)
    local printouts = trim(args.printouts) or ''
    local format = trim(args.format) or 'table'
    local headers = trim(args.headers) or ''
    local intro = trim(args.intro)
    
    if not minProperty then
        return '<span class="error">Error: minProperty is required</span>'
    end
    
    -- Build the printout list for the query
    local printoutList = {}
    for prop in printouts:gmatch('[^,]+') do
        table.insert(printoutList, '?' .. trim(prop))
    end
    -- Always include the minProperty for comparison
    table.insert(printoutList, '?' .. minProperty)
    
    -- Build and execute the query
    local queryString = conditions .. '\n' .. table.concat(printoutList, '\n') .. '\n|limit=500'
    local results = mw.smw.ask(queryString)
    
    if not results or #results == 0 then
        return "No results found."
    end
    
    -- Find the minimum value
    local minValue = nil
    for _, row in ipairs(results) do
        local val = row[minProperty]
        if val then
            local numVal = tonumber(val)
            if numVal then
                if minValue == nil or numVal < minValue then
                    minValue = numVal
                end
            end
        end
    end
    
    if minValue == nil then
        return "Could not determine minimum value."
    end
    
    -- Filter results to only those matching the minimum
    local filtered = {}
    for _, row in ipairs(results) do
        local val = row[minProperty]
        if val and tonumber(val) == minValue then
            table.insert(filtered, row)
        end
    end
    
    -- Build output based on format
    local headerList = {}
    for h in headers:gmatch('[^,]+') do
        table.insert(headerList, trim(h))
    end
    
    local printoutProps = {}
    for prop in printouts:gmatch('[^,]+') do
        table.insert(printoutProps, trim(prop))
    end
    
    local introText = intro or ("Smallest screen size with this input: '''" .. minValue .. "\"'''")
    
    if format == 'table' then
        return p._formatTable(filtered, printoutProps, headerList, introText)
    elseif format == 'ul' or format == 'ol' then
        return p._formatList(filtered, printoutProps, format, introText)
    else
        return p._formatTable(filtered, printoutProps, headerList, introText)
    end
end

--- Find all results where a property equals its maximum value
function p.findAllWithMax(frame)
    local args = frame.args
    local conditions = trim(args.conditions) or ''
    local maxProperty = trim(args.maxProperty)
    local printouts = trim(args.printouts) or ''
    local format = trim(args.format) or 'table'
    local headers = trim(args.headers) or ''
    local intro = trim(args.intro)
    
    if not maxProperty then
        return '<span class="error">Error: maxProperty is required</span>'
    end
    
    -- Build the printout list
    local printoutList = {}
    for prop in printouts:gmatch('[^,]+') do
        table.insert(printoutList, '?' .. trim(prop))
    end
    table.insert(printoutList, '?' .. maxProperty)
    
    -- Build and execute the query
    local queryString = conditions .. '\n' .. table.concat(printoutList, '\n') .. '\n|limit=500'
    local results = mw.smw.ask(queryString)
    
    if not results or #results == 0 then
        return "No results found."
    end
    
    -- Find the maximum value
    local maxValue = nil
    for _, row in ipairs(results) do
        local val = row[maxProperty]
        if val then
            local numVal = tonumber(val)
            if numVal then
                if maxValue == nil or numVal > maxValue then
                    maxValue = numVal
                end
            end
        end
    end
    
    if maxValue == nil then
        return "Could not determine maximum value."
    end
    
    -- Filter results to only those matching the maximum
    local filtered = {}
    for _, row in ipairs(results) do
        local val = row[maxProperty]
        if val and tonumber(val) == maxValue then
            table.insert(filtered, row)
        end
    end
    
    -- Build output
    local headerList = {}
    for h in headers:gmatch('[^,]+') do
        table.insert(headerList, trim(h))
    end
    
    local printoutProps = {}
    for prop in printouts:gmatch('[^,]+') do
        table.insert(printoutProps, trim(prop))
    end
    
    local introText = intro or ("Largest screen size with this input: '''" .. maxValue .. "\"'''")
    
    if format == 'table' then
        return p._formatTable(filtered, printoutProps, headerList, introText)
    elseif format == 'ul' or format == 'ol' then
        return p._formatList(filtered, printoutProps, format, introText)
    else
        return p._formatTable(filtered, printoutProps, headerList, introText)
    end
end

-- Internal: Format results as a wikitable
function p._formatTable(results, printouts, headers, introText)
    local output = {}
    
    table.insert(output, introText .. "\n\n")
    table.insert(output, '{| class="wikitable sortable"\n')
    
    -- Header row
    table.insert(output, '|-\n! #')
    for i, prop in ipairs(printouts) do
        local headerText = headers[i] or prop:gsub('.*%.', ''):gsub('Has ', '')
        table.insert(output, ' !! ' .. headerText)
    end
    table.insert(output, '\n')
    
    -- Data rows
    for i, row in ipairs(results) do
        local tr = '|-\n| ' .. tostring(i)
        for _, prop in ipairs(printouts) do
            local val = row[prop]
            local displayVal = ''
            if val then
                if type(val) == 'table' then
                    -- Handle linked values (pages)
                    if val.fulltext then
                        displayVal = '[[' .. val.fulltext .. ']]'
                    elseif #val > 0 then
                        -- Array of values
                        local parts = {}
                        for _, v in ipairs(val) do
                            if type(v) == 'table' and v.fulltext then
                                table.insert(parts, '[[' .. v.fulltext .. ']]')
                            else
                                table.insert(parts, tostring(v))
                            end
                        end
                        displayVal = table.concat(parts, ', ')
                    end
                else
                    displayVal = tostring(val)
                end
            end
            tr = tr .. ' || ' .. displayVal
        end
        table.insert(output, tr .. '\n')
    end
    
    table.insert(output, '|}')
    
    return table.concat(output)
end

-- Internal: Format results as a list
function p._formatList(results, printouts, listType, introText)
    local output = {}
    local bullet = listType == 'ol' and '#' or '*'
    
    table.insert(output, introText .. "\n\n")
    
    for _, row in ipairs(results) do
        local parts = {}
        for _, prop in ipairs(printouts) do
            local val = row[prop]
            if val then
                if type(val) == 'table' and val.fulltext then
                    table.insert(parts, '[[' .. val.fulltext .. ']]')
                elseif type(val) ~= 'table' then
                    table.insert(parts, tostring(val))
                end
            end
        end
        table.insert(output, bullet .. ' ' .. table.concat(parts, ' – ') .. '\n')
    end
    
    return table.concat(output)
end

return p
MediaWiki Appliance - Powered by TurnKey Linux