Module:SMWUtils: Difference between revisions
Rewrite to use mw.smw.ask() now that Semantic Scribunto is installed (via update-page on MediaWiki MCP Server) |
Add debug function and improve property chain handling (via update-page on MediaWiki MCP Server) |
||
| Line 11: | Line 11: | ||
if s == '' then return nil end | if s == '' then return nil end | ||
return s | return s | ||
end | |||
-- Helper to get a value from a row, trying multiple key formats for property chains | |||
local function getRowValue(row, prop) | |||
-- Try the exact property name first | |||
if row[prop] ~= nil then | |||
return row[prop] | |||
end | |||
-- For property chains like "Has CRT model.Has brand", also try just "Has brand" | |||
local lastPart = prop:match('%.([^%.]+)$') | |||
if lastPart and row[lastPart] ~= nil then | |||
return row[lastPart] | |||
end | |||
return nil | |||
end | |||
-- Helper to format a value for display | |||
local function formatValue(val) | |||
if val == nil then | |||
return '' | |||
end | |||
if type(val) == 'table' then | |||
-- Handle linked values (pages) | |||
if val.fulltext then | |||
return '[[' .. 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 | |||
return table.concat(parts, ', ') | |||
end | |||
return '' | |||
else | |||
return tostring(val) | |||
end | |||
end | |||
--- Debug function to show what keys mw.smw.ask() returns | |||
-- Usage: {{#invoke:SMWUtils|debug|query=[[Has video signal format::Component (YPbPr)]]|printouts=Has CRT model, Has CRT model.Has brand}} | |||
function p.debug(frame) | |||
local args = frame.args | |||
local query = trim(args.query) or '' | |||
local printouts = trim(args.printouts) or '' | |||
-- Build printout list | |||
local printoutList = {} | |||
for prop in printouts:gmatch('[^,]+') do | |||
table.insert(printoutList, '?' .. trim(prop)) | |||
end | |||
local queryString = query .. '\n' .. table.concat(printoutList, '\n') .. '\n|limit=3' | |||
local results = mw.smw.ask(queryString) | |||
if not results or #results == 0 then | |||
return "No results found for query: <code>" .. mw.text.nowiki(queryString) .. "</code>" | |||
end | |||
local output = {} | |||
table.insert(output, "'''Query:''' <code>" .. mw.text.nowiki(queryString) .. "</code>\n\n") | |||
table.insert(output, "'''Results (" .. #results .. " shown):'''\n\n") | |||
for i, row in ipairs(results) do | |||
table.insert(output, "'''Row " .. i .. " keys:'''\n") | |||
for k, v in pairs(row) do | |||
local valStr | |||
if type(v) == 'table' then | |||
if v.fulltext then | |||
valStr = "table{fulltext=\"" .. v.fulltext .. "\"}" | |||
else | |||
valStr = "table[" .. #v .. "]" | |||
end | |||
else | |||
valStr = tostring(v) | |||
end | |||
table.insert(output, "* <code>" .. tostring(k) .. "</code> = " .. valStr .. "\n") | |||
end | |||
table.insert(output, "\n") | |||
end | |||
return table.concat(output) | |||
end | end | ||
| Line 54: | Line 143: | ||
local minValue = nil | local minValue = nil | ||
for _, row in ipairs(results) do | for _, row in ipairs(results) do | ||
local val = row | local val = getRowValue(row, minProperty) | ||
if val then | if val then | ||
local numVal = tonumber(val) | local numVal = tonumber(val) | ||
| Line 72: | Line 161: | ||
local filtered = {} | local filtered = {} | ||
for _, row in ipairs(results) do | for _, row in ipairs(results) do | ||
local val = row | local val = getRowValue(row, minProperty) | ||
if val and tonumber(val) == minValue then | if val and tonumber(val) == minValue then | ||
table.insert(filtered, row) | table.insert(filtered, row) | ||
| Line 132: | Line 221: | ||
local maxValue = nil | local maxValue = nil | ||
for _, row in ipairs(results) do | for _, row in ipairs(results) do | ||
local val = row | local val = getRowValue(row, maxProperty) | ||
if val then | if val then | ||
local numVal = tonumber(val) | local numVal = tonumber(val) | ||
| Line 150: | Line 239: | ||
local filtered = {} | local filtered = {} | ||
for _, row in ipairs(results) do | for _, row in ipairs(results) do | ||
local val = row | local val = getRowValue(row, maxProperty) | ||
if val and tonumber(val) == maxValue then | if val and tonumber(val) == maxValue then | ||
table.insert(filtered, row) | table.insert(filtered, row) | ||
| Line 197: | Line 286: | ||
local tr = '|-\n| ' .. tostring(i) | local tr = '|-\n| ' .. tostring(i) | ||
for _, prop in ipairs(printouts) do | for _, prop in ipairs(printouts) do | ||
local val = row | local val = getRowValue(row, prop) | ||
tr = tr .. ' || ' .. formatValue(val) | |||
tr = tr .. ' || ' .. | |||
end | end | ||
table.insert(output, tr .. '\n') | table.insert(output, tr .. '\n') | ||
| Line 240: | Line 307: | ||
local parts = {} | local parts = {} | ||
for _, prop in ipairs(printouts) do | for _, prop in ipairs(printouts) do | ||
local val = row | local val = getRowValue(row, prop) | ||
local formatted = formatValue(val) | |||
if formatted ~= '' then | |||
table.insert(parts, formatted) | |||
end | end | ||
end | end | ||
Revision as of 07:05, 15 January 2026
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
-- Helper to get a value from a row, trying multiple key formats for property chains
local function getRowValue(row, prop)
-- Try the exact property name first
if row[prop] ~= nil then
return row[prop]
end
-- For property chains like "Has CRT model.Has brand", also try just "Has brand"
local lastPart = prop:match('%.([^%.]+)$')
if lastPart and row[lastPart] ~= nil then
return row[lastPart]
end
return nil
end
-- Helper to format a value for display
local function formatValue(val)
if val == nil then
return ''
end
if type(val) == 'table' then
-- Handle linked values (pages)
if val.fulltext then
return '[[' .. 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
return table.concat(parts, ', ')
end
return ''
else
return tostring(val)
end
end
--- Debug function to show what keys mw.smw.ask() returns
-- Usage: {{#invoke:SMWUtils|debug|query=[[Has video signal format::Component (YPbPr)]]|printouts=Has CRT model, Has CRT model.Has brand}}
function p.debug(frame)
local args = frame.args
local query = trim(args.query) or ''
local printouts = trim(args.printouts) or ''
-- Build printout list
local printoutList = {}
for prop in printouts:gmatch('[^,]+') do
table.insert(printoutList, '?' .. trim(prop))
end
local queryString = query .. '\n' .. table.concat(printoutList, '\n') .. '\n|limit=3'
local results = mw.smw.ask(queryString)
if not results or #results == 0 then
return "No results found for query: <code>" .. mw.text.nowiki(queryString) .. "</code>"
end
local output = {}
table.insert(output, "'''Query:''' <code>" .. mw.text.nowiki(queryString) .. "</code>\n\n")
table.insert(output, "'''Results (" .. #results .. " shown):'''\n\n")
for i, row in ipairs(results) do
table.insert(output, "'''Row " .. i .. " keys:'''\n")
for k, v in pairs(row) do
local valStr
if type(v) == 'table' then
if v.fulltext then
valStr = "table{fulltext=\"" .. v.fulltext .. "\"}"
else
valStr = "table[" .. #v .. "]"
end
else
valStr = tostring(v)
end
table.insert(output, "* <code>" .. tostring(k) .. "</code> = " .. valStr .. "\n")
end
table.insert(output, "\n")
end
return table.concat(output)
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 = getRowValue(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 = getRowValue(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 = getRowValue(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 = getRowValue(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 = getRowValue(row, prop)
tr = tr .. ' || ' .. formatValue(val)
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 = getRowValue(row, prop)
local formatted = formatValue(val)
if formatted ~= '' then
table.insert(parts, formatted)
end
end
table.insert(output, bullet .. ' ' .. table.concat(parts, ' – ') .. '\n')
end
return table.concat(output)
end
return p