This module implements all of the infoboxes. Individual infoboxes are defined on subpages of this page, and MUST be marked translatable, or you will get an error when trying to use them, as the module reads the translations exclusively.
Functions
{{#invoke:Infobox|infobox|<boxname>|...}}
Expands to the infobox defined by the subpage boxName
. When creating the template wrappers, supply fromParent=1
instead of the parameters directly. This will cause the infobox to take its parameters from the ones supplied to the wrapper.
{{#invoke:Infobox|documentationPage|<boxname>}}
Expands to a documentation page for boxname.
{{#invoke:Infobox|parameterHelp|<boxname>}}
List the parameters available in a suitable form for template documentation.
{{#invoke:Infobox|emptyBox|<boxname>}}
Produce an infobox with all the parameter names shown.
Format of infobox definitions
Please note that any translatable text MUST use multiline string syntax with at least one = in it. That is, rather than "string"
, do [=[string]=]
. This is because the translate extension inserts linebreaks, which are not compatible with quotes, and the [[string]]
syntax breaks if it contains any wikitext links.
Fields listed as wikitext are strings, but ones in which it is reasonable to put wikitext. Templates should work in most of them.
Each definition is a Lua module, so it has to return a table. Typically this is done by:
local box = {} -- box definition is here return box
The contents of this table are as following:
Key | Type | Description |
---|---|---|
docLead | Wikitext | Lead section of the documentation (the hatnote and "This template is used to create an infobox..." text are inserted for you). |
parameterLead | Wikitext | Text immediately preceding the parameter list (the "an empty infobox..." text is also inserted automatically) |
parameterTrail | Wikitext | Text immediately following the parameter list. |
format | Format table | Details of the infobox (see the next section) |
examples | Table of examples | Examples to place in the documentation. Each example is a table containing an optional description in the text key, and some wikitext to use as an example in the first indexed entry. For example:
{ text="Demonstration of hatnote", ":''This article is about the wiki concept. For sticking notes on actual hats, see [[Hat#Annotating]]''" } |
Format of infobox details
This is also a table, containing a list of sections; a section is the part of the infobox that is headed by a blue header. Each section is a table, with the following contents. "Argument" denotes a field that can be either an argument table (see below), or wikitext.
Key | Type | Description |
---|---|---|
name | String | Name that appears as a header in the documentation. Consecutive sections with identical names will be merged into one in the documentation. |
title | Argument | Mandatory. The heading for a section (ie, the bit that appears in the blue bar). |
desc | Wikitext | Optional. Explanatory text which will appear in the documentation for this section. |
noDoc | Boolean | Optional. If present, don't show this section in the documentation at all. |
docOnly | Boolean | Optional. If present, this section is not treated as part of the infobox, but documentation is still shown for it. |
Indexed | Subsection table | The subsections to display. You need at least one of these. |
Subsections can contain desc
noDoc
and docOnly
keys, and contain a list of rows (again, you need at least one). Rows can also contain those three keys, and contain one or two Arguments as indexed entries. If one, then it will be centered across the width of the infobox, using the colspan parameter in the generated cell.
Format of Arguments
An argument which is not a string is a table referring to a template parameter supplied to an invocation of infobox
, the value of this parameter being used as if it had been given in place of the table.
Key | Type | Description |
---|---|---|
arg | String or table of strings | The name of the template parameter. If you give a table here, its indexed parameters will be used as synonyms, with the first being the canonical name. |
type | One of:
"text" "image" "switch" "link" "templateCall" | How the argument will be interpreted. text is just wikitext. image will treat it as the name of an image. switch will look it up in the allowedValues parameter. switch will also add to if an invalid value is given for the parameter (but not none at all). link will treat it as the URL of an external link. templateCall will treat it as the lone argument to the template given in the template member. |
style | String | CSS to insert in the table cell's style attribute. |
maxSize | Number | Maximum size of an image in pixels. Omit the px , it will be added for you. |
imageParameters | String | Additional parameters to add to the image. Use pipes to separate them, as you would in wikitext (but you don't need to start or end with one). |
allowedValues | Table of strings | Permitted values for a switch . Mandatory for switches, pointless otherwise. The value given as a template parameter will be used as a key into this table, and the value will be displayed. A listing of keys and values will be entered into the documentation. |
desc | Wikitext (no templates) | Description to displayed in the listing of parameters in the documentation. If omitted, then no entry will be displayed at all. |
noDoc | Boolean | If present, the argument will not be mentioned in the documentation. Overrides the presence of desc . |
linkText | String | If the type is link use this as the link text. |
prefix | String | Prepend this text, followed by a space, to the argument's value. |
units | String | Append a space and then this text, to the argument's value. |
template | String | Name of template to expand. |
local p = {}
local util = require("Module:Utility_functions")
local ipairs, pairs, rawget, type = ipairs, pairs, rawget, type
Accumulator = {}
p.a = Accumulator
Accumulator.new = function()
local nextidx = 1
this = {}
local n = {
out = function(str)
if not str then return end
this[nextidx] = str
nextidx = nextidx + 1
end,
outv = function(...)
for i,t in ipairs({...}) do
this[nextidx] = t
nextidx = nextidx + 1
end
end,
outt = function(tab)
for i,t in ipairs(tab) do
this[nextidx] = t
nextidx = nextidx + 1
end
end
}
setmetatable(n, {__tostring = function()
return table.concat(this)
end})
return n
end
-- returns (true, module) or (false, message)
function loadTranslatedModule(name, forceUntranslated)
local moduleName = name
if not forceUntranslated then
moduleName = moduleName .. util.pageSuffix()
end
return pcall(function()
return require(moduleName)
end)
end
local stringsLoaded, strings = loadTranslatedModule("Module:Infobox/strings")
if not stringsLoaded then
strings = {}
end
setmetatable(strings, {__index = function(tab, key)
if (key == "_fellbackStrings") then
tab._fellbackStrings = {}
return tab._fellbackStrings
end
if (key == "_missingStrings") then
tab._missingStrings = {}
return tab._missingStrings
end
if key == "_ModuleStringTableProblem" then
if rawget(tab,"_stringTableEntirelyMissing") then
return "!!! [[Module:Infobox/strings]] is gone or not a valid Lua module !!!"
end
if (#tab._fellbackStrings > 0) or (#tab._missingStrings > 0) then
return "!!! Problem with [[Module:Infobox/strings]] or [[Module:Infobox/strings".. util.pageSuffix() .."|a translation thereof]]. Fallbacks used for: {" .. table.concat(tab._fellbackStrings, ", ") .. "}. Missing strings: {" .. table.concat(tab._missingStrings, ", ") .. "}. Please inform a translation admin as the translation markings probably need updating. !!!"
else
return nil
end
end
if not rawget(tab,"_fallback") then
pcall(function()
tab._fallback = require("Module:Infobox/strings")
end)
if not rawget(tab,"_fallback") then
tab._stringTableEntirelyMissing = true
tab._fallback = { }
end
end
local val = tab._fallback[key]
if val then
tab._fellbackStrings[#(tab._fellbackStrings)+1] = key
else
tab._missingStrings[#(tab._missingStrings)+1] = key
val = "noString!"..key
end
val = string.gsub(val, "</?translate>","")
tab[key] = val -- inhibit repeats.
return val
end})
local function makePreprocess(frame)
return function(str)
if str then
return frame:preprocess(tostring(str)) or "wat"
else
return ""
end
end
end
-- load an infobox
local function loadInfobox(name, forceUntranslated)
return loadTranslatedModule("Module:Infobox/" .. util.trim(name), forceUntranslated)
end
-- this is exactly as lua-users wiki defined it. Never mind the odd gsub argument.
local function interp(s, tab)
return (s:gsub('($%b{})', function(w) return tab[w:sub(3, -2)] or w end))
end
-- return the canonical, ie first, name given in an argdata.
local function argCName(argdata)
if type(argdata.arg) == "string" then
return argdata.arg
else
return argdata.arg[1]
end
end
-- wrap an argument value in the necessary surround
local function wrapArg(argdata, value, expansionFrame)
if argdata.type == "image" then
local sizeRequested = tonumber(argdata.maxSize or "0")
if sizeRequested == 0 then
sizeRequested = tonumber(strings.maxImageSize)
end
local parameters = (argdata.imageParameters or strings.imageParameters)
if parameters == "" then
parameters = nil
end
mw.log({'File:'.. value, sizeRequested .. 'px', parameters})
-- force frameless for now, because that makes size do the right thing
local stuff = util.compact({'File:'.. value, sizeRequested .. 'px', parameters , "frameless"})
return '[['.. table.concat(stuff,"|")..']]'
elseif argdata.type == "link" then
local linktext = (argdata.linkText or strings.linkText)
return table.concat({'[', value, ' ', linktext, ']'})
elseif argdata.type == "templateCall" then
if expansionFrame == "noTransclude" then
return table.concat({'<nowiki>{{</nowiki>', argdata.template, '|', value, '<nowiki>}}</nowiki>'})
else
return expansionFrame:expandTemplate{title = argdata.template, args = { value }}
end
else
return value
end
end
-- resolve argument data for a given arg table
local function argtableResolver(argdata, frame, errordata)
local argtable = frame.args
if not argdata then
return nil
end
if type(argdata) == "string" then
return argdata
end
local text = nil
if type(argdata.arg) == "string" then
text = argtable[argdata.arg]
elseif (type(argdata.arg) == "table") and (#(argdata.arg) > 0) then
for i,arg in ipairs(argdata.arg) do
local candidate = util.trim(argtable[arg] or "")
if not (candidate == "") then
text = candidate
end
end
else
error(strings.errArgMissingArgName .. " " .. interp(position.formatString, errordata))
end
if not text then
return nil
end
if argdata.type == "switch" then
if (not argdata.allowedValues) or (type(argdata.allowedValues) ~= "table") then
error(strings.errArgMissingSwitchValues .. " " .. interp(position.formatString, errordata))
elseif not argdata.allowedValues[text] then
text = strings.unknownType .. ' [[Category:Articles with bad parameter values]]'
else
text = argdata.allowedValues[text]
end
end
local wrappedText = wrapArg(argdata, text, frame)
if argdata.prefix then
wrappedText = argdata.prefix .. " " .. wrappedText
end
if argdata.units then
wrappedText = wrappedText .. " " .. argdata.units
end
if argdata.style then
return {style=argdata.style, wrappedText}
else
return wrappedText
end
end
local function makeResolver(frame)
sourceframe = frame
if frame.args.fromParent then
sourceframe = frame:getParent()
end
return function(argdata, errordata)
return argtableResolver(argdata, sourceframe, errordata)
end
end
-- resolve argument data for an empty infobox
-- parameters
-- argdata: argdata|string
-- returns
-- string|{style=cellstyle, string}
local function emptyBoxResolver(argdata)
if not argdata then
return "wat"
end
if type(argdata) == "string" then
return argdata
elseif type(argdata) == "table" then
local text = "{{{"..argCName(argdata).."}}}"
if argdata.style then
return {style=argdata.style, wrapArg(argdata, text, "noTransclude")}
else
return wrapArg(argdata, text, "noTransclude")
end
end
end
-- resolve the arguments of an infobox definition according to the given resolver,
-- and return a structure with all the arguments resolved and any unnecessary sections
-- etc omitted (sections with an argument title are never omitted).
-- parameters
-- args.box: infobox definition
-- args.resolver: function(argdata|string) -> string.
local function calculateEffectiveInfobox(args)
local effectiveBox = {type="box"}
if (not args.box) or (type(args.box) ~= "table") then
error(strings.errNoFormat)
end
local sectioncount = 0
for i, section in ipairs(args.box) do
if type(section) ~= "table" then
error(interp(strings.errBadSection, {section = i}))
end
if not section.docOnly then
sectioncount = sectioncount + 1
if not section.title then
error(interp(strings.errSectionNoTitle, {section = i}))
end
local newSection = { title = args.resolver(section.title, {formatString = strings.errSectionLocation, section = i}), type="section" }
for j, subsection in ipairs(section) do
if type(subsection) ~= "table" then
error(interp(strings.errBadSubsection, {section = i, subsection = j}))
end
if not subsection.docOnly then
local newSubsection = {type="subsection"}
for k, row in ipairs(subsection) do
if type(row) ~= "table" then
error(interp(strings.errBadRow, {section = i, subsection = j, row = k}))
end
if not row.docOnly then
local newRow = { type="row"}
for m, cell in ipairs(row) do
newRow[#newRow + 1] = args.resolver(cell, {formatString = strings.errCellLocation, section = i, subsection = j, row = k, cell = m})
end
if (#row == #newRow) then
newSubsection[#newSubsection+1] = newRow
end
end
end
if (#newSubsection > 0) then
newSection[#newSection + 1] = newSubsection
end
end
end
if (#newSection > 0) or (type(section.title) == "table") then
effectiveBox[#effectiveBox + 1] = newSection
end
end
end
if sectioncount == 0 then
error(strings.errEmptyInfoboxFormat)
end
return effectiveBox
end
-- convert an infobox definition that has had all the arguments resolved, to a string.
-- box like
-- { class=tableclass, { title=sectionheader, { { cell, cell }, {cell, cell } } } }
-- cell like string or { style=cellstyle, content }
local function renderBox(box, wtprocessor)
result = ""
local out = function(thing)
result = result .. thing
end
out('<table class="infobox')
if box.class then
out(' '..box.class)
end
out('">')
local count = 0
for i,section in ipairs(box) do
if i == 1 then
out('<tr><th class="infoboxFirstHeader" colspan="2">')
else
out('<tr class="infoboxSectionHeader"><th class="infoboxSectionHeader" colspan="2">')
end
local title = wtprocessor(section.title)
out(title)
out('</th></tr>')
if title and (title ~= "") then
count = count + 1
end
for j,subsection in ipairs(section) do
if j > 1 then
out('<tr class="infoboxSubsectionBreak"><td></td><td></td></tr>')
end
for k, row in ipairs(subsection) do
out('<tr>')
for m,cell in ipairs(row) do
if m < 3 then
out('<td')
if #row == 1 then
out(' colspan="2"')
end
if type(cell)=="table" and cell.style then
out(' style="'..cell.style..'"')
end
out('>')
else
if type(cell)=="table" and cell.style then
out('<span style="'..cell.style..'">')
end
end
if type(cell)=="string" then
out(wtprocessor(cell))
elseif type(cell)=="table" then
out(wtprocessor(cell[1]))
end
count = count + 1
if type(cell)=="table" and cell.style and m >= 3 then
out('</span>')
end
if m == 1 or (m == #row) then
out('</td>')
end
end
out('</tr>')
end
end
end
if count == 0 then
out('<tr><td colspan="2">' .. strings.errNoOutput .. '</td></tr>')
end
out('</table>')
return result
end
local function renderEmptyBox(frame, box)
local presentedBox = calculateEffectiveInfobox{box=box.format, resolver = emptyBoxResolver}
presentedBox.class="infoboxNoCollapse"
return renderBox(presentedBox, function(str)
return frame:preprocess(str or "")
end)
end
--- returns list of { name = string, text = string, argdata... }
function p.calculateParameterListing(box, nondocmode)
local parameters = {}
for i,section in ipairs(box) do
if (nondocmode and (not section.docOnly)) or (not section.noDoc) then
local sectiondata = { name = section.name}
if (not parameters[#parameters]) or (not (sectiondata.name == parameters[#parameters].name)) then
parameters[#parameters+1] = sectiondata
else
sectiondata = parameters[#parameters]
end
if type(section.title) == "table" then
sectiondata[#sectiondata+1] = section.title
end
if section.desc then
sectiondata[#sectiondata+1] = { text = section.desc }
end
for j,subsection in ipairs(section) do
if (nondocmode and (not subsection.docOnly)) or (not subsection.noDoc) then
if subsection.desc then
sectiondata[#sectiondata+1] = { text = subsection.desc }
end
for k,row in ipairs(subsection) do
if (nondocmode and (not row.docOnly)) or (not row.noDoc) then
if row.desc then
sectiondata[#sectiondata+1] = { text = row.desc }
end
for m,cell in ipairs(row) do
if (type(cell) == "table") and ((nondocmode and (not cell.docOnly)) or (cell.desc and (not cell.noDoc))) then
sectiondata[#sectiondata+1] = cell
end
end
end
end
end
end
end
end
return parameters
end
local function renderParameterListing(box, a)
local parameters = p.calculateParameterListing(box)
local collapseByValue = function(tab)
local iv={}
for k,v in pairs(tab) do
if not iv[v] then
iv[v]={k}
else
iv[v][#(iv[v]) + 1] = k
end
end
local s={}
for k,v in pairs(iv) do
if not s[v] then
s[v]=k
end
end
return s
end
local out = a.out
local outt = a.outt
for i,section in ipairs(parameters) do
if section.name then
outt{"===",section.name,"===\n"}
end
for j,parameter in ipairs(section) do
if parameter.text then
outt{parameter.text,'\n'}
elseif parameter.desc then
local namelist = parameter.arg
if type(parameter.arg) == "table" then
namelist = table.concat(parameter.arg, "''' '' " .. strings.nameorname .. " '' '''")
end
outt{"* '''",namelist,"''': ",parameter.desc}
if parameter.type == "switch" then
outt{" ",strings.switchdoc,"\n"}
for key,value in util.orderedPairs(collapseByValue(parameter.allowedValues)) do
outt{"** ''",table.concat(key, " '' " .. strings.nameorname .. " '' "),"'': ",value,"\n"}
end
elseif parameter.type == "image" then
outt{" ",strings.imagedoc ,"\n"}
else
out("\n")
end
end
end
end
return result
end
local function makeInvokeWithBox(func)
return function(frame)
local out = ""
local success, box = loadInfobox(frame.args[1], frame.args.forceUntranslated)
if not success then
out = "!!! " .. strings.loadFailure .. " [[Module:Infobox/" .. frame.args[1] .. util.pageSuffix() .. "]] " .. strings.informTranslationAdmin .. " \n\n"
out = out .. box .. " !!!\n\n"
else
local success = nil
local result = nil
if frame.args.stackTrace then
success, result = xpcall(function()
func(frame, box)
end, debug.traceback)
else
success, result = pcall(func, frame, box)
end
if not success then
out = "!!! " .. result .. " " .. strings.informTranslationAdmin .. " !!!\n\n"
else
out = result
end
end
if strings._ModuleStringTableProblem then
out = strings._ModuleStringTableProblem .. "\n\n" .. out
end
return out
end
end
-- emit an infobox.
-- parameters:
-- 1: Infobox definition to use
-- everything else: whatever the infobox definition says
p.infobox = makeInvokeWithBox(function(frame, box)
local presentedBox = calculateEffectiveInfobox{box=box.format, resolver = makeResolver(frame)}
local out = renderBox(presentedBox, function(str)
return frame:preprocess(str or "")
end)
local paramList = p.calculateParameterListing(box.format, true)
local params = {}
for i,section in ipairs(paramList) do
for j, param in ipairs(section) do
if type(param.arg) == "table" then
for j,a in ipairs(param.arg) do
params[a] = true
end
elseif param.arg then
params[param.arg] = true
end
end
end
local hasBadArgs = false
for name, value in pairs((frame:getParent() or frame).args) do
if not params[name] then
hasBadArgs = true
out = out .. "\n\n<div style=\"display:none;\">" .. strings.badParameterName .. " " .. name .. "</div>"
end
end
if hasBadArgs then
out = out .. "[[Category:Articles with bad template parameters]]"
end
return out
end)
-- emit an infobox with the parameters displayed
-- parameters:
-- 1: Infobox definition to use
p.emptyBox = makeInvokeWithBox(function(frame, box)
return renderEmptyBox(frame, box)
end)
-- emit the parameter list for documentation
-- parameters
-- 1: Infobox definition to use
p.parameterHelp = makeInvokeWithBox(function(frame, box)
return tostring(renderParameterListing(box.format, Accumulator.new()))
end)
-- emit the entire documentation page
-- parameters
-- 1: infobox definition to use
-- in the infobox definition
-- intro is the text at the top
-- postParameterText is displayed after the parameter listing
-- examples is an array of examples: { { optional text = wikitext string, template-supporting wikitext}, ... }
p.documentationPage = makeInvokeWithBox(function(frame, box)
local fp = makePreprocess(frame)
local a = Accumulator.new()
local out = a.out
local outv = a.outv
local outt = a.outt
local function hatnote(str)
return ":''" .. str .. "''"
end
outv("{{Doc/Start{{L}}}}\n{{Lua{{L}}|Infobox|data=Infobox/", frame.args[1], "}}\n")
--outv(hatnote(noteText), '\n')
-- ^^ noteText is never defined, leaving "Text?" at the start of template documentation
outv(strings.docLead, ' ')
outv(box.docLead or '', '\n\n')
outv("==", strings.parameters, "==\n")
outv(strings.parameterLead, " ")
out(box.parameterLead)
outv(renderEmptyBox(frame, box), '\n')
renderParameterListing(box.format, a)
outt{'\n', box.parameterTrail}
out('<div style="clear:both;"></div>')
outv("\n==", strings.examples, "==\n")
if box.examples then
for i, example in ipairs(box.examples) do
outv(example.text or "", '\n')
outv('<table class="wikitable">\n<tr><th>', strings.exampleCode, '</th><th>', strings.exampleResult, '</th></tr>\n')
outv('<tr>\n<td><pre>\n', example[1], '\n</pre></td>\n')
outv('<td>\n', example[1], '\n</td></tr>\n')
out('</table>\n\n')
end
else
out(hatnote(fp(strings.noExamples)))
end
out("{{Doc/End{{L}}}}")
return frame:preprocess(tostring(a))
end)
return p