Arclight changed the content model of the page Module:SMWUtils from "wikitext" to "Scribunto module"
Rewrite to use frame:preprocess() instead of mw.smw.ask() (Semantic Scribunto not installed) (via update-page on MediaWiki MCP Server)
Line 1: Line 1:
-- Module:SMWUtils
-- Module:SMWUtils
-- Utility functions for Semantic MediaWiki queries
-- Utility functions for Semantic MediaWiki queries
-- Includes helpers for finding all results matching min/max values
-- Uses frame:preprocess() since Semantic Scribunto is not installed


local p = {}
local p = {}
Line 13: Line 13:
end
end


--- Find all results where a property equals its minimum value across the result set
--- Find all results where a chained property equals its minimum value
-- Uses a two-pass approach: first find min, then query for all matching
-- @param frame Frame object with args:
-- @param frame Frame object with args:
--  conditions: SMW query conditions (e.g., "[[Has video signal format::Component (YPbPr)]]")
--  conditions: SMW query conditions (e.g., "[[Has video signal format::Component (YPbPr)]]")
Line 20: Line 21:
--  format: Output format (table, ul, ol) - default "table"
--  format: Output format (table, ul, ol) - default "table"
--  headers: Comma-separated header names for table format
--  headers: Comma-separated header names for table format
--  intro: Optional intro text
-- @return Wikitext output
-- @return Wikitext output
function p.findAllWithMin(frame)
function p.findAllWithMin(frame)
Line 28: Line 30:
     local format = trim(args.format) or 'table'
     local format = trim(args.format) or 'table'
     local headers = trim(args.headers) or ''
     local headers = trim(args.headers) or ''
    local intro = trim(args.intro)
      
      
     if not minProperty then
     if not minProperty then
Line 33: Line 36:
     end
     end
      
      
     -- Build the printout list
     -- Step 1: Find the minimum value using a sorted query with limit=1
     local printoutList = {}
     local minQueryWikitext = '{{#ask:' .. conditions .. '\n' ..
    for prop in printouts:gmatch('[^,]+') do
         '|?' .. minProperty .. '\n' ..
         table.insert(printoutList, '?' .. trim(prop))
        '|sort=' .. minProperty .. '\n' ..
    end
        '|order=asc\n' ..
    -- Always include the minProperty for comparison
        '|limit=1\n' ..
    table.insert(printoutList, '?' .. minProperty)
        '|format=broadtable\n' ..
   
        '|headers=hide\n' ..
    -- Build and execute the query
        '|link=none\n' ..
    local queryString = conditions .. '\n' .. table.concat(printoutList, '\n') .. '\n|limit=500'
        '}}'
    local results = mw.smw.ask(queryString)
      
      
     if not results or #results == 0 then
     local minResult = frame:preprocess(minQueryWikitext)
        return "No results found."
    end
      
      
     -- Find the minimum value
     -- Parse the minimum value from the table result
     local minValue = nil
     local minValue = nil
     for _, row in ipairs(results) do
     if minResult then
        local val = row[minProperty]
        -- Extract from last <td> in the row (the property value)
        if val then
        for cellContent in minResult:gmatch('<td[^>]*>(.-)</td>') do
             local numVal = tonumber(val)
            local cleanContent = cellContent:gsub('<[^>]+>', '')
             local numVal = tonumber(trim(cleanContent))
             if numVal then
             if numVal then
                 if minValue == nil or numVal < minValue then
                 minValue = numVal
                    minValue = numVal
                end
             end
             end
         end
         end
Line 64: Line 63:
      
      
     if minValue == nil then
     if minValue == nil then
         return "Could not determine minimum value."
         return "No results found or could not determine minimum value."
    end
   
    -- Step 2: Query for all results matching this minimum value
    local printoutList = {}
    for prop in printouts:gmatch('[^,]+') do
        table.insert(printoutList, '|?' .. trim(prop))
     end
     end
      
      
    -- Filter results to only those matching the minimum
     local fullQueryWikitext = '{{#ask:' .. conditions .. '\n' ..
     local filtered = {}
        '[[' .. minProperty .. '::' .. minValue .. ']]\n' ..
    for _, row in ipairs(results) do
        table.concat(printoutList, '\n') .. '\n' ..
         local val = row[minProperty]
         '|sort=' .. minProperty .. '\n' ..
         if val and tonumber(val) == minValue then
        '|limit=100\n' ..
            table.insert(filtered, row)
        '|format=broadtable\n' ..
         end
        '|headers=hide\n' ..
        '|link=all\n' ..
         '}}'
   
    local fullResult = frame:preprocess(fullQueryWikitext)
   
    -- Parse results from HTML table
    local results = p._parseTableResults(fullResult, printouts)
   
    if #results == 0 then
         return "No results found matching minimum value " .. minValue .. "."
     end
     end
      
      
     -- Build output based on format
     -- Build output
     local headerList = {}
     local headerList = {}
     for h in headers:gmatch('[^,]+') do
     for h in headers:gmatch('[^,]+') do
Line 86: Line 101:
         table.insert(printoutProps, trim(prop))
         table.insert(printoutProps, trim(prop))
     end
     end
   
    local introText = intro or ("Smallest screen size with this input: '''" .. minValue .. "\"'''")
      
      
     if format == 'table' then
     if format == 'table' then
         return p._formatTable(filtered, printoutProps, headerList, minProperty, minValue)
         return p._formatTableFromParsed(results, printoutProps, headerList, introText)
     elseif format == 'ul' or format == 'ol' then
     elseif format == 'ul' or format == 'ol' then
         return p._formatList(filtered, printoutProps, format, minProperty, minValue)
         return p._formatListFromParsed(results, format, introText)
     else
     else
         return p._formatTable(filtered, printoutProps, headerList, minProperty, minValue)
         return p._formatTableFromParsed(results, printoutProps, headerList, introText)
     end
     end
end
end
Line 104: Line 121:
     local format = trim(args.format) or 'table'
     local format = trim(args.format) or 'table'
     local headers = trim(args.headers) or ''
     local headers = trim(args.headers) or ''
    local intro = trim(args.intro)
      
      
     if not maxProperty then
     if not maxProperty then
Line 109: Line 127:
     end
     end
      
      
     -- Build the printout list
     -- Step 1: Find the maximum value using a sorted query with limit=1
     local printoutList = {}
     local maxQueryWikitext = '{{#ask:' .. conditions .. '\n' ..
    for prop in printouts:gmatch('[^,]+') do
         '|?' .. maxProperty .. '\n' ..
         table.insert(printoutList, '?' .. trim(prop))
        '|sort=' .. maxProperty .. '\n' ..
    end
        '|order=desc\n' ..
    table.insert(printoutList, '?' .. maxProperty)
        '|limit=1\n' ..
   
        '|format=broadtable\n' ..
    -- Build and execute the query
        '|headers=hide\n' ..
    local queryString = conditions .. '\n' .. table.concat(printoutList, '\n') .. '\n|limit=500'
        '|link=none\n' ..
    local results = mw.smw.ask(queryString)
        '}}'
      
      
     if not results or #results == 0 then
     local maxResult = frame:preprocess(maxQueryWikitext)
        return "No results found."
    end
      
      
     -- Find the maximum value
     -- Parse the maximum value from the table result
     local maxValue = nil
     local maxValue = nil
     for _, row in ipairs(results) do
     if maxResult then
        local val = row[maxProperty]
        for cellContent in maxResult:gmatch('<td[^>]*>(.-)</td>') do
        if val then
            local cleanContent = cellContent:gsub('<[^>]+>', '')
             local numVal = tonumber(val)
             local numVal = tonumber(trim(cleanContent))
             if numVal then
             if numVal then
                 if maxValue == nil or numVal > maxValue then
                 maxValue = numVal
                    maxValue = numVal
                end
             end
             end
         end
         end
Line 139: Line 153:
      
      
     if maxValue == nil then
     if maxValue == nil then
         return "Could not determine maximum value."
         return "No results found or could not determine maximum value."
     end
     end
      
      
     -- Filter results to only those matching the maximum
     -- Step 2: Query for all results matching this maximum value
     local filtered = {}
     local printoutList = {}
     for _, row in ipairs(results) do
     for prop in printouts:gmatch('[^,]+') do
         local val = row[maxProperty]
         table.insert(printoutList, '|?' .. trim(prop))
         if val and tonumber(val) == maxValue then
    end
            table.insert(filtered, row)
   
         end
    local fullQueryWikitext = '{{#ask:' .. conditions .. '\n' ..
        '[[' .. maxProperty .. '::' .. maxValue .. ']]\n' ..
         table.concat(printoutList, '\n') .. '\n' ..
        '|sort=' .. maxProperty .. '\n' ..
        '|limit=100\n' ..
        '|format=broadtable\n' ..
        '|headers=hide\n' ..
        '|link=all\n' ..
        '}}'
   
    local fullResult = frame:preprocess(fullQueryWikitext)
   
    -- Parse results from HTML table
    local results = p._parseTableResults(fullResult, printouts)
   
    if #results == 0 then
         return "No results found matching maximum value " .. maxValue .. "."
     end
     end
      
      
Line 161: Line 191:
         table.insert(printoutProps, trim(prop))
         table.insert(printoutProps, trim(prop))
     end
     end
   
    local introText = intro or ("Largest screen size with this input: '''" .. maxValue .. "\"'''")
      
      
     if format == 'table' then
     if format == 'table' then
         return p._formatTable(filtered, printoutProps, headerList, maxProperty, maxValue)
         return p._formatTableFromParsed(results, printoutProps, headerList, introText)
     elseif format == 'ul' or format == 'ol' then
     elseif format == 'ul' or format == 'ol' then
         return p._formatList(filtered, printoutProps, format, maxProperty, maxValue)
         return p._formatListFromParsed(results, format, introText)
     else
     else
         return p._formatTable(filtered, printoutProps, headerList, maxProperty, maxValue)
         return p._formatTableFromParsed(results, printoutProps, headerList, introText)
     end
     end
end
end


-- Internal: Format results as a wikitable
-- Internal: Parse HTML table results into a Lua table
function p._formatTable(results, printouts, headers, valueProperty, value)
function p._parseTableResults(htmlResult, printouts)
     local html = mw.html.create('div')
     local results = {}
    html:wikitext("''Showing all results where " .. valueProperty .. " = " .. tostring(value) .. ":''\n\n")
      
      
     local tbl = html:tag('table')
    if not htmlResult then
     tbl:addClass('wikitable'):addClass('sortable')
        return results
    end
   
    -- Count expected columns from printouts
     local numPrintouts = 0
    for _ in printouts:gmatch('[^,]+') do
        numPrintouts = numPrintouts + 1
    end
   
    -- Match each table row
     for rowContent in htmlResult:gmatch('<tr[^>]*>(.-)</tr>') do
        local cells = {}
        -- Extract content from each <td>, preserving HTML for links
        for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
            table.insert(cells, cellContent)
        end
       
        -- First cell is usually the page link, rest are printout values
        if #cells >= 1 then
            table.insert(results, cells)
        end
    end
   
    return results
end
 
-- Internal: Format parsed results as a wikitable
function p._formatTableFromParsed(results, printouts, headers, introText)
    local output = {}
   
    table.insert(output, introText .. "\n\n")
    table.insert(output, '{| class="wikitable sortable"\n')
      
      
     -- Header row
     -- Header row
     local headerRow = tbl:tag('tr')
     table.insert(output, '|-\n! #')
    headerRow:tag('th'):wikitext('#')
     for i, prop in ipairs(printouts) do
     for i, prop in ipairs(printouts) do
         local headerText = headers[i] or prop:gsub('.*%.', ''):gsub('Has ', '')
         local headerText = headers[i] or prop:gsub('.*%.', ''):gsub('Has ', '')
         headerRow:tag('th'):wikitext(headerText)
         table.insert(output, ' !! ' .. headerText)
     end
     end
    table.insert(output, '\n')
      
      
     -- Data rows
     -- Data rows
     for i, row in ipairs(results) do
     for i, row in ipairs(results) do
         local tr = tbl:tag('tr')
         table.insert(output, '|-\n| ' .. tostring(i))
        tr:tag('td'):wikitext(tostring(i))
         for j = 1, #printouts do
         for _, prop in ipairs(printouts) do
             local cellVal = row[j] or ''
             local val = row[prop]
             table.insert(output, ' || ' .. cellVal)
            local displayVal = ''
             if val then
                if type(val) == 'table' then
                    -- Handle linked values (pages)
                    if val.fulltext then
                        displayVal = '[[' .. val.fulltext .. ']]'
                    else
                        displayVal = table.concat(val, ', ')
                    end
                else
                    displayVal = tostring(val)
                end
            end
            tr:tag('td'):wikitext(displayVal)
         end
         end
        table.insert(output, '\n')
     end
     end
      
      
     return tostring(html)
    table.insert(output, '|}')
   
     return table.concat(output)
end
end


-- Internal: Format results as a list
-- Internal: Format parsed results as a list
function p._formatList(results, printouts, listType, valueProperty, value)
function p._formatListFromParsed(results, listType, introText)
     local tag = listType == 'ol' and 'ol' or 'ul'
     local output = {}
   
    local bullet = listType == 'ol' and '#' or '*'
    local html = mw.html.create('div')
    html:wikitext("''Showing all results where " .. valueProperty .. " = " .. tostring(value) .. ":''\n\n")
      
      
     local list = html:tag(tag)
     table.insert(output, introText .. "\n\n")
      
      
     for _, row in ipairs(results) do
     for _, row in ipairs(results) do
        local li = list:tag('li')
         local parts = {}
         local parts = {}
         for _, prop in ipairs(printouts) do
         for _, cell in ipairs(row) do
            local val = row[prop]
             if cell and cell ~= '' then
             if val then
                 table.insert(parts, cell)
                if type(val) == 'table' and val.fulltext then
                    table.insert(parts, '[[' .. val.fulltext .. ']]')
                 else
                    table.insert(parts, tostring(val))
                end
             end
             end
         end
         end
         li:wikitext(table.concat(parts, ' - '))
         table.insert(output, bullet .. ' ' .. table.concat(parts, ' ') .. '\n')
     end
     end
      
      
     return tostring(html)
     return table.concat(output)
end
end


return p
return p

Revision as of 07:01, 15 January 2026

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

-- Module:SMWUtils
-- Utility functions for Semantic MediaWiki queries
-- Uses frame:preprocess() since Semantic Scribunto is not installed

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 chained property equals its minimum value
-- Uses a two-pass approach: first find min, then query for all matching
-- @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
-- @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
    
    -- Step 1: Find the minimum value using a sorted query with limit=1
    local minQueryWikitext = '{{#ask:' .. conditions .. '\n' ..
        '|?' .. minProperty .. '\n' ..
        '|sort=' .. minProperty .. '\n' ..
        '|order=asc\n' ..
        '|limit=1\n' ..
        '|format=broadtable\n' ..
        '|headers=hide\n' ..
        '|link=none\n' ..
        '}}'
    
    local minResult = frame:preprocess(minQueryWikitext)
    
    -- Parse the minimum value from the table result
    local minValue = nil
    if minResult then
        -- Extract from last <td> in the row (the property value)
        for cellContent in minResult:gmatch('<td[^>]*>(.-)</td>') do
            local cleanContent = cellContent:gsub('<[^>]+>', '')
            local numVal = tonumber(trim(cleanContent))
            if numVal then
                minValue = numVal
            end
        end
    end
    
    if minValue == nil then
        return "No results found or could not determine minimum value."
    end
    
    -- Step 2: Query for all results matching this minimum value
    local printoutList = {}
    for prop in printouts:gmatch('[^,]+') do
        table.insert(printoutList, '|?' .. trim(prop))
    end
    
    local fullQueryWikitext = '{{#ask:' .. conditions .. '\n' ..
        '[[' .. minProperty .. '::' .. minValue .. ']]\n' ..
        table.concat(printoutList, '\n') .. '\n' ..
        '|sort=' .. minProperty .. '\n' ..
        '|limit=100\n' ..
        '|format=broadtable\n' ..
        '|headers=hide\n' ..
        '|link=all\n' ..
        '}}'
    
    local fullResult = frame:preprocess(fullQueryWikitext)
    
    -- Parse results from HTML table
    local results = p._parseTableResults(fullResult, printouts)
    
    if #results == 0 then
        return "No results found matching minimum value " .. minValue .. "."
    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 ("Smallest screen size with this input: '''" .. minValue .. "\"'''")
    
    if format == 'table' then
        return p._formatTableFromParsed(results, printoutProps, headerList, introText)
    elseif format == 'ul' or format == 'ol' then
        return p._formatListFromParsed(results, format, introText)
    else
        return p._formatTableFromParsed(results, 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
    
    -- Step 1: Find the maximum value using a sorted query with limit=1
    local maxQueryWikitext = '{{#ask:' .. conditions .. '\n' ..
        '|?' .. maxProperty .. '\n' ..
        '|sort=' .. maxProperty .. '\n' ..
        '|order=desc\n' ..
        '|limit=1\n' ..
        '|format=broadtable\n' ..
        '|headers=hide\n' ..
        '|link=none\n' ..
        '}}'
    
    local maxResult = frame:preprocess(maxQueryWikitext)
    
    -- Parse the maximum value from the table result
    local maxValue = nil
    if maxResult then
        for cellContent in maxResult:gmatch('<td[^>]*>(.-)</td>') do
            local cleanContent = cellContent:gsub('<[^>]+>', '')
            local numVal = tonumber(trim(cleanContent))
            if numVal then
                maxValue = numVal
            end
        end
    end
    
    if maxValue == nil then
        return "No results found or could not determine maximum value."
    end
    
    -- Step 2: Query for all results matching this maximum value
    local printoutList = {}
    for prop in printouts:gmatch('[^,]+') do
        table.insert(printoutList, '|?' .. trim(prop))
    end
    
    local fullQueryWikitext = '{{#ask:' .. conditions .. '\n' ..
        '[[' .. maxProperty .. '::' .. maxValue .. ']]\n' ..
        table.concat(printoutList, '\n') .. '\n' ..
        '|sort=' .. maxProperty .. '\n' ..
        '|limit=100\n' ..
        '|format=broadtable\n' ..
        '|headers=hide\n' ..
        '|link=all\n' ..
        '}}'
    
    local fullResult = frame:preprocess(fullQueryWikitext)
    
    -- Parse results from HTML table
    local results = p._parseTableResults(fullResult, printouts)
    
    if #results == 0 then
        return "No results found matching maximum value " .. maxValue .. "."
    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._formatTableFromParsed(results, printoutProps, headerList, introText)
    elseif format == 'ul' or format == 'ol' then
        return p._formatListFromParsed(results, format, introText)
    else
        return p._formatTableFromParsed(results, printoutProps, headerList, introText)
    end
end

-- Internal: Parse HTML table results into a Lua table
function p._parseTableResults(htmlResult, printouts)
    local results = {}
    
    if not htmlResult then
        return results
    end
    
    -- Count expected columns from printouts
    local numPrintouts = 0
    for _ in printouts:gmatch('[^,]+') do
        numPrintouts = numPrintouts + 1
    end
    
    -- Match each table row
    for rowContent in htmlResult:gmatch('<tr[^>]*>(.-)</tr>') do
        local cells = {}
        -- Extract content from each <td>, preserving HTML for links
        for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
            table.insert(cells, cellContent)
        end
        
        -- First cell is usually the page link, rest are printout values
        if #cells >= 1 then
            table.insert(results, cells)
        end
    end
    
    return results
end

-- Internal: Format parsed results as a wikitable
function p._formatTableFromParsed(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
        table.insert(output, '|-\n| ' .. tostring(i))
        for j = 1, #printouts do
            local cellVal = row[j] or ''
            table.insert(output, ' || ' .. cellVal)
        end
        table.insert(output, '\n')
    end
    
    table.insert(output, '|}')
    
    return table.concat(output)
end

-- Internal: Format parsed results as a list
function p._formatListFromParsed(results, 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 _, cell in ipairs(row) do
            if cell and cell ~= '' then
                table.insert(parts, cell)
            end
        end
        table.insert(output, bullet .. ' ' .. table.concat(parts, ' – ') .. '\n')
    end
    
    return table.concat(output)
end

return p
MediaWiki Appliance - Powered by TurnKey Linux