Module:SMWUtils: Difference between revisions
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 | ||
-- | -- 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 | --- 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 | ||
-- | -- Step 1: Find the minimum value using a sorted query with limit=1 | ||
local | 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 | local minValue = nil | ||
for | if minResult then | ||
-- Extract from last <td> in the row (the property value) | |||
for cellContent in minResult:gmatch('<td[^>]*>(.-)</td>') do | |||
local numVal = tonumber( | local cleanContent = cellContent:gsub('<[^>]+>', '') | ||
local numVal = tonumber(trim(cleanContent)) | |||
if numVal then | if numVal then | ||
minValue = numVal | |||
end | end | ||
end | end | ||
| Line 64: | Line 63: | ||
if minValue == nil then | if minValue == nil then | ||
return " | 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 | ||
local fullQueryWikitext = '{{#ask:' .. conditions .. '\n' .. | |||
local | '[[' .. 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 | end | ||
-- Build output | -- 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. | return p._formatTableFromParsed(results, printoutProps, headerList, introText) | ||
elseif format == 'ul' or format == 'ol' then | elseif format == 'ul' or format == 'ol' then | ||
return p. | return p._formatListFromParsed(results, format, introText) | ||
else | else | ||
return p. | 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 | ||
-- | -- Step 1: Find the maximum value using a sorted query with limit=1 | ||
local | 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 | local maxValue = nil | ||
for | if maxResult then | ||
for cellContent in maxResult:gmatch('<td[^>]*>(.-)</td>') do | |||
local cleanContent = cellContent:gsub('<[^>]+>', '') | |||
local numVal = tonumber( | local numVal = tonumber(trim(cleanContent)) | ||
if numVal then | if numVal then | ||
maxValue = numVal | |||
end | end | ||
end | end | ||
| Line 139: | Line 153: | ||
if maxValue == nil then | if maxValue == nil then | ||
return " | return "No results found or could not determine maximum value." | ||
end | end | ||
-- | -- Step 2: Query for all results matching this maximum value | ||
local | local printoutList = {} | ||
for | for prop in printouts:gmatch('[^,]+') do | ||
local | 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 | 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. | return p._formatTableFromParsed(results, printoutProps, headerList, introText) | ||
elseif format == 'ul' or format == 'ol' then | elseif format == 'ul' or format == 'ol' then | ||
return p. | return p._formatListFromParsed(results, format, introText) | ||
else | else | ||
return p. | return p._formatTableFromParsed(results, printoutProps, headerList, introText) | ||
end | end | ||
end | end | ||
-- Internal: | -- Internal: Parse HTML table results into a Lua table | ||
function p. | function p._parseTableResults(htmlResult, printouts) | ||
local | local results = {} | ||
local | 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 | -- Header row | ||
table.insert(output, '|-\n! #') | |||
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 ', '') | ||
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 | ||
table.insert(output, '|-\n| ' .. tostring(i)) | |||
for j = 1, #printouts do | |||
for | local cellVal = row[j] or '' | ||
local | table.insert(output, ' || ' .. cellVal) | ||
end | end | ||
table.insert(output, '\n') | |||
end | end | ||
return | table.insert(output, '|}') | ||
return table.concat(output) | |||
end | end | ||
-- Internal: Format results as a list | -- Internal: Format parsed results as a list | ||
function p. | function p._formatListFromParsed(results, listType, introText) | ||
local | local output = {} | ||
local bullet = listType == 'ol' and '#' or '*' | |||
table.insert(output, introText .. "\n\n") | |||
for _, row in ipairs(results) do | for _, row in ipairs(results) do | ||
local parts = {} | local parts = {} | ||
for _, | for _, cell in ipairs(row) do | ||
if cell and cell ~= '' then | |||
if | table.insert(parts, cell) | ||
end | end | ||
end | end | ||
table.insert(output, bullet .. ' ' .. table.concat(parts, ' – ') .. '\n') | |||
end | end | ||
return | 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