Module:AVDevice: Difference between revisions
Fix semantic query parameter names to use short labels (count, signal, connector, location) (via update-page on MediaWiki MCP Server) |
Group inputs and outputs by location with prominent labels (via update-page on MediaWiki MCP Server) |
||
| Line 79: | Line 79: | ||
return tostring(ul) | return tostring(ul) | ||
end | end | ||
end | |||
-- Helper to query inputs/outputs for a specific location | |||
local function queryIOByLocation(frame, fullPageName, ioType, location) | |||
local locationFilter = '' | |||
if location then | |||
locationFilter = ' [[Has ' .. (ioType == 'input' and 'input' or 'output') .. ' location::' .. location .. ']]\n' | |||
end | |||
local signalProp = ioType == 'input' and 'Has video signal format' or 'Has output signal format' | |||
local countProp = ioType == 'input' and 'Has input count' or 'Has output count' | |||
local connectorProp = ioType == 'input' and 'Has connector type' or 'Has output connector' | |||
local templateName = ioType == 'input' and 'AV device input row' or 'AV device output row' | |||
local queryWikitext = | |||
'{{#ask:\n' .. | |||
' [[Has AV device::' .. fullPageName .. ']]\n' .. | |||
' [[' .. signalProp .. '::+]]\n' .. | |||
locationFilter .. | |||
' |?' .. countProp .. '=count\n' .. | |||
' |?' .. signalProp .. '=signal\n' .. | |||
' |?' .. connectorProp .. '=connector\n' .. | |||
' |format=template\n' .. | |||
' |template=' .. templateName .. '\n' .. | |||
' |named args=yes\n' .. | |||
' |link=none\n' .. | |||
'}}' | |||
local result = frame:preprocess(queryWikitext) | |||
return trim(result) | |||
end | |||
-- Helper to count inputs/outputs for a specific location | |||
local function countIOByLocation(frame, fullPageName, ioType, location) | |||
local locationFilter = '' | |||
if location then | |||
locationFilter = ' [[Has ' .. (ioType == 'input' and 'input' or 'output') .. ' location::' .. location .. ']]\n' | |||
end | |||
local signalProp = ioType == 'input' and 'Has video signal format' or 'Has output signal format' | |||
local countWikitext = | |||
'{{#ask:\n' .. | |||
' [[Has AV device::' .. fullPageName .. ']]\n' .. | |||
' [[' .. signalProp .. '::+]]\n' .. | |||
locationFilter .. | |||
' |format=count\n' .. | |||
'}}' | |||
local result = frame:preprocess(countWikitext) | |||
local count = tonumber(trim(result)) or 0 | |||
return count | |||
end | end | ||
| Line 208: | Line 260: | ||
end | end | ||
-- Connectivity section - query structured inputs and outputs | -- Connectivity section - query structured inputs and outputs grouped by location | ||
local | local locations = {'front', 'rear', 'side'} | ||
local locationLabels = {front = 'Front', rear = 'Rear', side = 'Side'} | |||
-- Count total inputs and outputs | |||
local totalInputs = 0 | |||
local totalOutputs = 0 | |||
for _, loc in ipairs(locations) do | |||
local | totalInputs = totalInputs + countIOByLocation(frame, fullPageName, 'input', loc) | ||
totalOutputs = totalOutputs + countIOByLocation(frame, fullPageName, 'output', loc) | |||
local | end | ||
local hasInputs = totalInputs > 0 | |||
' | local hasOutputs = totalOutputs > 0 | ||
local | |||
local hasOutputs = | |||
if hasInputs or hasOutputs then | if hasInputs or hasOutputs then | ||
addSectionRow(container, 'Connectivity') | addSectionRow(container, 'Connectivity') | ||
-- Inputs grouped by location | |||
if hasInputs then | if hasInputs then | ||
local | local inputsContent = '' | ||
for _, loc in ipairs(locations) do | |||
local count = countIOByLocation(frame, fullPageName, 'input', loc) | |||
if count > 0 then | |||
local locInputs = queryIOByLocation(frame, fullPageName, 'input', loc) | |||
if locInputs and locInputs ~= '' then | |||
inputsContent = inputsContent .. "<div style='margin-bottom:0.3em;'><strong>" .. locationLabels[loc] .. "</strong><br/>" .. locInputs .. "</div>" | |||
end | |||
end | |||
end | |||
if inputsContent ~= '' then | |||
addRow(container, 'Inputs', inputsContent, true) | |||
if | |||
addRow(container, 'Inputs', | |||
end | end | ||
end | end | ||
-- Outputs grouped by location | |||
if hasOutputs then | if hasOutputs then | ||
local | local outputsContent = '' | ||
for _, loc in ipairs(locations) do | |||
local count = countIOByLocation(frame, fullPageName, 'output', loc) | |||
' | if count > 0 then | ||
local locOutputs = queryIOByLocation(frame, fullPageName, 'output', loc) | |||
if locOutputs and locOutputs ~= '' then | |||
outputsContent = outputsContent .. "<div style='margin-bottom:0.3em;'><strong>" .. locationLabels[loc] .. "</strong><br/>" .. locOutputs .. "</div>" | |||
end | |||
end | |||
end | |||
if outputsContent ~= '' then | |||
addRow(container, 'Outputs', outputsContent, true) | |||
if | |||
addRow(container, 'Outputs', | |||
end | end | ||
end | end | ||
Revision as of 18:42, 4 February 2026
Documentation for this module may be created at Module:AVDevice/doc
-- Module:AVDevice
-- Builds the AV device infobox as strict HTML to avoid whitespace issues
-- Uses frame:preprocess() so parser functions like #ask and #formredlink evaluate.
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
local function css(node, cssText)
if cssText and cssText ~= '' then
node:cssText(cssText)
end
return node
end
local function addSectionRow(container, title)
local section = container:tag('div')
section:addClass('infobox-section-header')
css(section, 'grid-column: 1 / -1; text-align:center; padding:0.5em; font-weight:bold;')
section:wikitext(title)
end
local function addRow(container, label, valueHtmlOrWikitext, isRawHtml)
local labelDiv = container:tag('div')
labelDiv:addClass('infobox-label')
css(labelDiv, 'padding:0.2em 0.4em; font-weight:bold; text-align:left;')
labelDiv:wikitext(label)
local valueDiv = container:tag('div')
valueDiv:addClass('infobox-value')
css(valueDiv, 'padding:0.2em 0.4em;')
if isRawHtml then
valueDiv:node(mw.html.create('span'):wikitext(valueHtmlOrWikitext))
else
valueDiv:wikitext(valueHtmlOrWikitext)
end
end
local function addFullRow(container, valueHtmlOrWikitext, cssText, isRawHtml, extraClass)
local fullDiv = container:tag('div')
css(fullDiv, 'grid-column: 1 / -1; ' .. (cssText or ''))
if extraClass then
fullDiv:addClass(extraClass)
end
if isRawHtml then
fullDiv:wikitext(valueHtmlOrWikitext)
else
fullDiv:wikitext(valueHtmlOrWikitext)
end
end
-- Helper function to format comma-separated values as a bulleted list or inline
local function formatList(value, inline)
if not value then return nil end
local items = {}
for item in value:gmatch('[^,]+') do
local trimmed = trim(item)
if trimmed then
table.insert(items, trimmed)
end
end
if #items == 0 then return nil end
if inline or #items <= 3 then
return table.concat(items, ', ')
else
local ul = mw.html.create('ul')
ul:css('margin', '0'):css('padding-left', '1.2em')
for _, item in ipairs(items) do
ul:tag('li'):wikitext(item)
end
return tostring(ul)
end
end
-- Helper to query inputs/outputs for a specific location
local function queryIOByLocation(frame, fullPageName, ioType, location)
local locationFilter = ''
if location then
locationFilter = ' [[Has ' .. (ioType == 'input' and 'input' or 'output') .. ' location::' .. location .. ']]\n'
end
local signalProp = ioType == 'input' and 'Has video signal format' or 'Has output signal format'
local countProp = ioType == 'input' and 'Has input count' or 'Has output count'
local connectorProp = ioType == 'input' and 'Has connector type' or 'Has output connector'
local templateName = ioType == 'input' and 'AV device input row' or 'AV device output row'
local queryWikitext =
'{{#ask:\n' ..
' [[Has AV device::' .. fullPageName .. ']]\n' ..
' [[' .. signalProp .. '::+]]\n' ..
locationFilter ..
' |?' .. countProp .. '=count\n' ..
' |?' .. signalProp .. '=signal\n' ..
' |?' .. connectorProp .. '=connector\n' ..
' |format=template\n' ..
' |template=' .. templateName .. '\n' ..
' |named args=yes\n' ..
' |link=none\n' ..
'}}'
local result = frame:preprocess(queryWikitext)
return trim(result)
end
-- Helper to count inputs/outputs for a specific location
local function countIOByLocation(frame, fullPageName, ioType, location)
local locationFilter = ''
if location then
locationFilter = ' [[Has ' .. (ioType == 'input' and 'input' or 'output') .. ' location::' .. location .. ']]\n'
end
local signalProp = ioType == 'input' and 'Has video signal format' or 'Has output signal format'
local countWikitext =
'{{#ask:\n' ..
' [[Has AV device::' .. fullPageName .. ']]\n' ..
' [[' .. signalProp .. '::+]]\n' ..
locationFilter ..
' |format=count\n' ..
'}}'
local result = frame:preprocess(countWikitext)
local count = tonumber(trim(result)) or 0
return count
end
function p.infobox(frame)
local parent = frame:getParent()
local args = parent and parent.args or frame.args
local title = mw.title.getCurrentTitle()
local pageName = title.text
local fullPageName = title.fullText
local pageId = title.id or 0
-- Extract parameters
local brand = trim(args.brand)
local model_code = trim(args.model_code)
local device_type = trim(args.device_type)
local format_family = trim(args.format_family)
local supported_formats = trim(args.supported_formats)
local recording_formats = trim(args.recording_formats)
local image_main = trim(args.image_main)
local year_released = trim(args.year_released)
local year_discontinued = trim(args.year_discontinued)
local av_system = trim(args.av_system)
local tuner_system = trim(args.tuner_system)
local frequency = trim(args.frequency)
local power_avg = trim(args.power_avg)
local power_max = trim(args.power_max)
local features = trim(args.features)
local height = trim(args.height)
local width = trim(args.width)
local depth = trim(args.depth)
local weight = trim(args.weight)
-- Use frame:extensionTag to create a proper <style> tag
local styleTag = frame:extensionTag('templatestyles', '', {src = 'Template:Infobox styles.css'})
local container = mw.html.create('div')
container:addClass('infobox')
container:addClass('infobox-av-device')
css(container, 'float:right; clear:right; margin:0 0 1em 1em; width:22em; font-size:88%; line-height:1.5em; display:grid; grid-template-columns:auto 1fr; gap:3px; padding:3px;')
-- Title row
addFullRow(
container,
(brand or '') .. ' ' .. (model_code or ''),
'text-align:center; font-size:125%; font-weight:bold; padding:0.5em;',
false,
'infobox-title'
)
if image_main then
local file = image_main:gsub('^File:', '')
addFullRow(
container,
string.format('[[File:%s|frameless|300px]]', file),
'text-align:center; padding:0;',
false,
'infobox-image'
)
end
addSectionRow(container, 'Overview')
-- Brand row using #formredlink
if brand then
local brandWikitext = string.format(
'{{#formredlink: target=%s |form=Manufacturer |existing page link text=%s }}',
brand, brand
)
local brandHtml = frame:preprocess(brandWikitext)
addRow(container, 'Brand', brandHtml, true)
end
if model_code then
addRow(container, 'Model', model_code, false)
end
if device_type then
addRow(container, 'Type', '[[' .. device_type .. ']]', true)
end
-- Format family with link to category
if format_family then
addRow(container, 'Format family', '[[:Category:' .. format_family .. ' devices|' .. format_family .. ']]', true)
end
-- Year info
if year_released or year_discontinued then
local yearStr = ''
if year_released and year_discontinued then
yearStr = year_released .. ' – ' .. year_discontinued
elseif year_released then
yearStr = year_released .. ' –'
elseif year_discontinued then
yearStr = '– ' .. year_discontinued
end
addRow(container, 'Years', yearStr, false)
end
-- AV System support
if av_system then
local formattedAV = formatList(av_system, true)
if formattedAV then
addRow(container, 'AV system', formattedAV, false)
end
end
-- ECID row
local ecidWikitext = string.format('[{{fullurl:%s|curid=%d}} EC%d]', pageName, pageId, pageId)
addRow(container, 'ECID', frame:preprocess(ecidWikitext), true)
-- Format compatibility section (only show if tape-based device)
if supported_formats or recording_formats then
addSectionRow(container, 'Format Compatibility')
if supported_formats then
local formattedSupported = formatList(supported_formats, true)
if formattedSupported then
addRow(container, 'Plays', formattedSupported, false)
end
end
if recording_formats then
local formattedRecording = formatList(recording_formats, true)
if formattedRecording then
addRow(container, 'Records', formattedRecording, false)
end
end
end
-- Connectivity section - query structured inputs and outputs grouped by location
local locations = {'front', 'rear', 'side'}
local locationLabels = {front = 'Front', rear = 'Rear', side = 'Side'}
-- Count total inputs and outputs
local totalInputs = 0
local totalOutputs = 0
for _, loc in ipairs(locations) do
totalInputs = totalInputs + countIOByLocation(frame, fullPageName, 'input', loc)
totalOutputs = totalOutputs + countIOByLocation(frame, fullPageName, 'output', loc)
end
local hasInputs = totalInputs > 0
local hasOutputs = totalOutputs > 0
if hasInputs or hasOutputs then
addSectionRow(container, 'Connectivity')
-- Inputs grouped by location
if hasInputs then
local inputsContent = ''
for _, loc in ipairs(locations) do
local count = countIOByLocation(frame, fullPageName, 'input', loc)
if count > 0 then
local locInputs = queryIOByLocation(frame, fullPageName, 'input', loc)
if locInputs and locInputs ~= '' then
inputsContent = inputsContent .. "<div style='margin-bottom:0.3em;'><strong>" .. locationLabels[loc] .. "</strong><br/>" .. locInputs .. "</div>"
end
end
end
if inputsContent ~= '' then
addRow(container, 'Inputs', inputsContent, true)
end
end
-- Outputs grouped by location
if hasOutputs then
local outputsContent = ''
for _, loc in ipairs(locations) do
local count = countIOByLocation(frame, fullPageName, 'output', loc)
if count > 0 then
local locOutputs = queryIOByLocation(frame, fullPageName, 'output', loc)
if locOutputs and locOutputs ~= '' then
outputsContent = outputsContent .. "<div style='margin-bottom:0.3em;'><strong>" .. locationLabels[loc] .. "</strong><br/>" .. locOutputs .. "</div>"
end
end
end
if outputsContent ~= '' then
addRow(container, 'Outputs', outputsContent, true)
end
end
end
-- Features section
if features then
addSectionRow(container, 'Features')
addFullRow(container, features, 'padding:0.2em 0.4em;', false)
end
-- Dimensions section
if height or width or depth or weight then
addSectionRow(container, 'Dimensions')
if height or width or depth then
local h = height or '–'
local w = width or '–'
local d = depth or '–'
addRow(container, 'H × W × D', string.format('%s × %s × %s', h, w, d), false)
end
if weight then
local weightWikitext = '{{#show: ' .. fullPageName .. ' |?Weight # lb }} / {{#show: ' .. fullPageName .. ' |?Weight # kg }}'
addRow(container, 'Weight', frame:preprocess(weightWikitext), true)
end
end
-- Power section - query voltage ranges and display power specs
local voltageQueryWikitext =
'{{#ask:\n' ..
' [[Has AV device::' .. pageName .. ']]\n' ..
' [[Voltage min::+]]\n' ..
' |?Voltage min=vmin\n' ..
' |?Voltage max=vmax\n' ..
' |format=template\n' ..
' |template=AV device voltage row\n' ..
' |named args=yes\n' ..
' |link=none\n' ..
'}}'
local voltageResult = frame:preprocess(voltageQueryWikitext)
local hasVoltage = voltageResult and trim(voltageResult) and trim(voltageResult) ~= ''
if hasVoltage or frequency or power_avg or power_max then
addSectionRow(container, 'Power')
if hasVoltage then
addRow(container, 'Voltage', voltageResult, true)
end
if frequency then
local formattedFreq = formatList(frequency, true)
if formattedFreq then
addRow(container, 'AC frequency', formattedFreq, false)
end
end
if power_avg or power_max then
local powerStr = ''
if power_avg and power_max then
powerStr = power_avg .. 'W (avg) / ' .. power_max .. 'W (max)'
elseif power_avg then
powerStr = power_avg .. 'W'
elseif power_max then
powerStr = power_max .. 'W (max)'
end
addRow(container, 'Power', powerStr, false)
end
end
-- Archival Links section - query AV link subobjects
local linksCountWikitext =
'{{#ask:\n' ..
' [[AV device::' .. fullPageName .. ']]\n' ..
' [[Model link URL::+]]\n' ..
' |format=count\n' ..
'}}'
local linkCount = frame:preprocess(linksCountWikitext)
local hasLinks = linkCount and tonumber(trim(linkCount)) and tonumber(trim(linkCount)) > 0
if hasLinks then
addSectionRow(container, 'Links')
local linksListWikitext =
'{{#ask:\n' ..
' [[AV device::' .. fullPageName .. ']]\n' ..
' [[Model link URL::+]]\n' ..
' |?Model link type=type\n' ..
' |?Model link source=source\n' ..
' |?Model link URL=url\n' ..
' |format=template\n' ..
' |template=AV device link row\n' ..
' |named args=yes\n' ..
' |link=none\n' ..
'}}'
local linksResult = frame:preprocess(linksListWikitext)
local linksDiv = container:tag('div')
css(linksDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
linksDiv:wikitext(linksResult)
end
-- Documents section
addSectionRow(container, 'Documents')
local timestamp = os.time()
math.randomseed(timestamp + pageId)
local random = math.random(1000, 9999)
local target = "Document:" .. timestamp .. "-" .. random
local uploadUrl = mw.uri.fullUrl(
"Special:FormEdit",
{
form = "Document",
target = target,
["Document[av_devices]"] = pageName
}
)
-- Query for document count
local documentsCountWikitext =
'{{#ask:\n' ..
' [[Category:Documents]]\n' ..
' [[Has related AV device::' .. fullPageName .. ']]\n' ..
' |format=count\n' ..
'}}'
local documentCount = frame:preprocess(documentsCountWikitext)
local hasDocuments = documentCount and tonumber(trim(documentCount)) and tonumber(trim(documentCount)) > 0
local docsDiv = container:tag('div')
css(docsDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em;')
if hasDocuments then
local docsQueryWikitext =
'{{#ask:\n' ..
' [[Category:Documents]]\n' ..
' [[Has related AV device::' .. fullPageName .. ']]\n' ..
' |?Has document type\n' ..
' |?Has document URL\n' ..
' |?Has document file\n' ..
' |?Has document title\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
'}}'
local docsResult = frame:preprocess(docsQueryWikitext)
local ul = mw.html.create('ul')
ul:css('margin', '0'):css('padding-left', '1.5em')
if docsResult then
for rowContent in docsResult:gmatch('<tr[^>]*>(.-)</tr>') do
local cells = {}
for cellContent in rowContent:gmatch('<td[^>]*>(.-)</td>') do
local cleanContent = cellContent:gsub('<[^>]+>', '')
table.insert(cells, cleanContent)
end
if #cells >= 2 then
local docType = trim(cells[2])
local docUrl = trim(cells[3])
local docFile = trim(cells[4])
local docTitle = trim(cells[5])
local docPageName = trim(cells[1])
if docPageName then
local displayText = 'Document'
if docTitle then
displayText = docTitle
elseif docType then
displayText = docType
end
local li = ul:tag('li')
li:wikitext('[[' .. docPageName .. '|' .. displayText .. ']]')
end
end
end
end
docsDiv:node(ul)
local uploadDiv = mw.html.create('div')
uploadDiv:css('margin-top', '0.5em'):css('text-align', 'center')
uploadDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span class="infobox-button infobox-button-secondary">Add a document</span>]')
docsDiv:node(uploadDiv)
else
local uploadDiv = mw.html.create('div')
uploadDiv:css('text-align', 'center')
uploadDiv:wikitext('[' .. tostring(uploadUrl) .. ' <span class="infobox-button infobox-button-secondary">Add a document</span>]')
docsDiv:node(uploadDiv)
end
-- Contribute section
addSectionRow(container, 'Contribute')
local editUrl = mw.uri.fullUrl(
"Special:FormStart",
{
form = "AV device",
page_name = pageName
}
)
local editDiv = container:tag('div')
css(editDiv, 'grid-column: 1 / -1; padding:0.2em 0.4em; text-align:center;')
editDiv:wikitext('[' .. tostring(editUrl) .. ' <span class="infobox-button infobox-button-primary">Edit this data</span>]')
return styleTag .. tostring(container)
end
return p