Module:Infobox: Difference between revisions
OtherXAdmin (talk | contribs) No edit summary Tag: Reverted |
OtherXAdmin (talk | contribs) No edit summary |
||
(10 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
-- Module:Infobox | --[=[ | ||
-- For documentation, see [[Module:Infobox/doc]] | |||
--]=] | |||
-- <nowiki> | |||
local Infobox = {} | local Infobox = {} | ||
Infobox.__index = Infobox | Infobox.__index = Infobox | ||
Line 7: | Line 10: | ||
-- Edit button for unknown params | -- Edit button for unknown params | ||
local editbutton = require('Module:Edit button') | local editbutton = require('Module:Edit button') | ||
local var = mw.ext.VariablesLua | |||
local edit = editbutton("'''?''' (edit)") | local edit = editbutton("'''?''' (edit)") | ||
-- Page title | |||
local pagename = mw.title.getCurrentTitle().fullText | |||
-- map of flags to html tags used by Infobox.addRow() | |||
-- let's only define it once, since :addRow() is used multiple times per module | |||
local tagmap = { | |||
tr = 'tr', | |||
th = 'th', | |||
td = 'td', | |||
argh = 'th', | |||
argd = 'td' | |||
} | |||
--[=[ | |||
-- Standardised functions | |||
-- called as string with defineParams | |||
--]=] | |||
-- Standardised "has content" function | -- Standardised "has content" function | ||
function hasContent(arg, default) | function hasContent(arg, default) | ||
-- Return arg if any non-whitespace character is found | |||
return string.match(arg or '','%S') and arg or default | |||
end | end | ||
-- | -- Standardised "name" function | ||
function | function subjectName(arg) | ||
return string.match(arg or '','%S') and arg or nil | |||
end | end | ||
-- | -- Create a standardised release function, since so many pages use it | ||
-- | -- Turns release and update into a single parameter | ||
function releaseUpdate(release, update) | function releaseUpdate(release, update) | ||
if not Infobox.isDefined(release) then | |||
return nil | |||
end | |||
if string.lower(release) == 'no' then | |||
return 'N/A' | |||
end | |||
if not Infobox.isDefined(update) then | |||
return string.format('%s (Update unknown)',release) | |||
end | |||
if string.lower(update) == 'no' then | |||
return release | |||
end | |||
return string.format('%s ([[Update:%s|Update]])', release, update) | |||
end | end | ||
-- Standardised | -- Standardised image function | ||
function image(img) | function image(img) | ||
if img and img:find('%S') then | |||
return img | |||
else | |||
return nil | |||
end | |||
end | end | ||
-- Standardised | -- Standardised numbers | ||
function numbers(num) | function numbers(num) | ||
-- | num = string.gsub(num or '',',','') | ||
return tonumber(num) | |||
end | |||
-- Wrap content with line breaks if it contains list-like wiki syntax | |||
function wrapContent(content) | |||
if type(content) == "string" then | |||
local firstListItem = math.min(content:find('^[*#]') or math.huge, content:find('\n[*#]') or math.huge) | |||
if firstListItem ~= math.huge then | |||
local suffix = content:find('\n[*#][^\n]+$') and '\n' or '' | |||
content = content:sub(1, firstListItem - 1) .. '\n' .. content:sub(firstListItem) .. suffix | |||
end | |||
end | |||
return content | |||
end | |||
-- map of names to pre-defined functions, used by Infobox:defineParams | |||
local func_map = { | |||
name = subjectName, | |||
release = { name = releaseUpdate, params = { 'release', 'update' }, flag = 'p' }, | |||
removal = { name = releaseUpdate, params = { 'removal', 'removalupdate' }, flag = 'p' }, | |||
has_content = hasContent, | |||
hasContent = hasContent, | |||
image = image, | |||
numbers = numbers, | |||
} | |||
-- used to fill nil params in switching sections | |||
-- this message isn't kidding | |||
-- If you see this message anywhere outside of this code | |||
-- (including inside switchfo box data) | |||
-- report it | |||
local nil_param = 'UH OH YOU SHOULDN\'T SEE THIS!' | |||
-- In case the nil_param is needed outside of this module | |||
-- give it an easy way to be accessed | |||
function Infobox.nilParam() | |||
return nil_param | |||
end | |||
-- switch infobox globals | |||
local LINE_WIDTH = 300 | |||
local MAX_LINES = 2 | |||
local DEFAULT_MAX_BUTTONS = 6 | |||
-- calculate with width of a switch infobox button | |||
-- potential @TODO: rework to use actual character widths | |||
function button_width(label) | |||
local PX_PER_CHAR = 6 | |||
local PX_PAD_MAR = 24 | |||
return string.len(label) * PX_PER_CHAR + PX_PAD_MAR | |||
end | |||
Infobox.splitpoint = '&&SPLITPOINT&&' | |||
-- quick test to see if a value is considered nil | |||
function Infobox.isDefined(arg) | |||
if arg == nil then | |||
return false | |||
end | |||
if type(arg) == 'string' then | |||
if arg == nil_param then | |||
return false | |||
elseif arg:find('%S') then | |||
if arg:find('action=edit') then | |||
return false | |||
else | |||
return true | |||
end | |||
else | |||
return false | |||
end | |||
end | |||
return true | |||
end | |||
--[[ | |||
Infobox class | |||
-- args : parameters from frame to pass through | |||
-- Sets a meta table and creates a <div> tag wrapper | |||
-- other fields are initialised in other functions | |||
--]] | |||
function Infobox.new(args) | |||
local obj = setmetatable({ | |||
args = args, -- parameters (uncleaned) | |||
rargs = {}, -- parameters (cleaned) | |||
params = {}, -- parameters mapped to functions | |||
paramnames = {}, -- parameter names | |||
dupeable = {}, -- parameters that are allowed to have duplicated switch data | |||
addrswibclass = true, | |||
switchfo = false, -- switch infobox? or not? | |||
switchfoattr = {}, -- switch data class changes | |||
maxbuttons = DEFAULT_MAX_BUTTONS, -- maximum number of buttons before switching becomes a menu | |||
switch_tag = '', -- switchfo data | |||
switch_buttons_tag = '', -- switchfo buttons | |||
custom_buttons = false, | |||
smw_error_tag = '', | |||
rtable = nil, -- returned infobox table | |||
labels = nil, -- returned labels | |||
_smw = {}, -- semantic mediawiki data | |||
_smwOne = {}, -- semantic mediawiki data part 2 | |||
_smwSubobject = {}, -- semantic mediawiki data part 3 | |||
_smwSubobjectName = nil, -- semantic mediawiki data part 3.5 | |||
_smwElement = {}, -- semantic mediawiki data part 4 | |||
set_default_version_smw = false, -- whether to set [[Property:Default version]] | |||
setSMWElement = true, | |||
suppressAllSMW = false, | |||
suppressVersionSMW = {}, | |||
versions = -1, -- number of switch versions (-1 is uncalculated) | |||
infoboxname = nil, -- template name | |||
appendStrs = {}, | |||
bottomlinks = { -- template bottom links | |||
links = { | |||
{ 'Template talk:%s', 'talk' }, | |||
{ 'Template:%s', 'view' } | |||
}, | |||
colspan = 2 | |||
}, | |||
catdata = {}, -- meta category data | |||
catlist = {}, -- defined table of category names (strings) | |||
__finished = false, -- infobox status | |||
}, | |||
Infobox) | |||
return obj | |||
end | |||
--[[ | |||
Toggles the addition of infobox class | |||
use before :create() | |||
noop if not a boolean | |||
--]] | |||
function Infobox:setAddRSWInfoboxClass(bool) | |||
if type(bool) == 'boolean' then | |||
self.addrswibclass = bool | |||
end | |||
end | |||
--[[ | |||
Creates an infobox | |||
-- If Infobox:maxVersions() has not been run, it will be run here | |||
-- If the infobox should be a switch infobox, all labels will be added | |||
-- Creates a wikitable that will be the infobox | |||
THIS SHOULD BE DONE AFTER ADDING AND CLEANING PARAMETERS | |||
--]] | |||
function Infobox:create() | |||
-- Run to find if this is a switch infobox and if so, how many boxes | |||
if self.versions == -1 then | |||
self:maxVersion() | |||
end | |||
-- Run if switch infobox | |||
if self.switchfo then | |||
-- Buttons wrapper | |||
-- Hidden by default, unhidden by javascript | |||
self.switch_buttons_tag = mw.html.create('div') | |||
:addClass('infobox-buttons') | |||
-- default version to immediately switch to via js | |||
local defv = tonumber(self.args.defver) | |||
if defv and defv <= self.versions then -- you troll, don't try to show something that isn't there | |||
self.switch_buttons_tag:attr('data-default-version',defv) | |||
end | |||
local numlines = 1 | |||
local width_working = 0 | |||
local total_width = 0 | |||
local buttons = {} | |||
-- Add individual buttons to the wrapper | |||
for i=1,self.versions do | |||
local wid = button_width(self.labels[i] or i) | |||
width_working = width_working + wid | |||
total_width = total_width + wid | |||
if width_working > LINE_WIDTH then | |||
numlines = numlines + 1 | |||
width_working = wid | |||
end | |||
local b = mw.html.create('span') | |||
:attr('data-switch-index',tostring(i)) | |||
-- space to underscore | |||
:attr('data-switch-anchor','#'..string.gsub(self.labels[i] or i,' ','_')) | |||
:addClass('button') | |||
:wikitext(self.labels[i] or i) | |||
table.insert(buttons, {b, wid}) | |||
end | |||
local best = {-1, 100000} | |||
if (numlines > 1) and (numlines <= MAX_LINES) then | |||
-- attempt to balance line widths | |||
local w_s, w_e = 0,total_width | |||
for i = 1,#buttons-1 do | |||
w_s = w_s + buttons[i][2] | |||
w_e = w_e - buttons[i][2] | |||
if w_s > LINE_WIDTH then | |||
-- w_s only increases, so we're done once it exceeds the width | |||
break | |||
end | |||
if w_e <= LINE_WIDTH then | |||
-- w_e only decreases, so just continue if it exceeds line | |||
local diff = math.abs(w_s - w_e) | |||
if diff < best[2] then | |||
best = { i, diff } | |||
end | |||
end | |||
end | |||
if best[1] == -1 then | |||
best = { math.floor(#buttons/2), 100000 } | |||
end | |||
end | |||
for i,v in ipairs(buttons) do | |||
self.switch_buttons_tag:node(v[1]) | |||
if i == best[1] then | |||
self.switch_buttons_tag:tag('span'):addClass('line-break') | |||
end | |||
end | |||
-- Used by JavaScript to turn the buttons into a menu list if too many variants | |||
if self.versions > self.maxbuttons or numlines > MAX_LINES then | |||
self.switch_buttons_tag:addClass('infobox-buttons-select') | |||
end | |||
self.switch_buttons_tag:done() | |||
end | |||
-- Create infobox table | |||
self.rtable = mw.html.create('table') | |||
if self.addrswibclass then | |||
self.rtable:addClass('infobox') | |||
end | |||
-- Add necessary class if switch infobox | |||
if self.switchfo then | |||
self.rtable:addClass('infobox-switch') | |||
end | |||
end | |||
-- Defines an infobox name ({{Template:arg}}) | |||
-- Used to create a link at the bottom of pages | |||
function Infobox:defineName(arg) | |||
self.infoboxname = arg | |||
end | |||
-- Defines the bottom links of the infobox | |||
-- pass a table whose elements are tables that define a link and a label | |||
-- { | |||
-- { 'link', 'label }, | |||
-- ... | |||
-- } | |||
-- The template name can be substituted into the tables using '%s' | |||
-- If we wanted Template:InFooBar to link to it's /doc page with a "doc" label: | |||
-- { ... | |||
-- { 'Template:%s/doc', 'doc' }, | |||
-- ... } | |||
-- The template's name can only be called 5 times | |||
function Infobox:defineLinks(arg) | |||
if type(arg) == 'table' then | |||
if arg.colspan then | |||
self.bottomlinks.colspan = arg.colspan | |||
end | |||
if arg.links then | |||
if type(arg.links) == 'table' then | |||
self.bottomlinks.links = arg.links | |||
end | |||
end | |||
if arg.hide then | |||
self.bottomlinks.hide = arg.hide | |||
end | |||
end | |||
end | |||
-- Change max number of buttons before switching to menu | |||
-- defaults to 5 | |||
-- MUST BE RUN BEFORE :create() | |||
function Infobox:setMaxButtons(arg) | |||
-- if not a number, just go back to default | |||
self.maxbuttons = tonumber(arg) or DEFAULT_MAX_BUTTONS | |||
end | |||
--[[ | |||
Add parameters functions | |||
All parameters should be tables | |||
The first parameter defines the type of cell to create | |||
-- th : <th> | |||
-- td : <td> | |||
-- argh : <th> | |||
-- argd : <td> | |||
The second parameter defines what is inside the tag | |||
-- th | th : text passed | |||
-- argh | argd : parameter with the name passed | |||
Additional named parameters can be used to add any styling or attributes | |||
-- attr : mw.html:attr({ arg1 = '1', ... }) | |||
-- css : mw.html:css({ arg1 = '1', ...) | |||
-- class : mw.html:addClass('arg') | |||
---- class also supports a table of values, even though mw.html:addClass() does not | |||
-- rowspan : mw.html:attr('rowspan',arg) | |||
-- colspan : mw.html:attr('colspan',arg) | |||
-- title : mw.html:attr('title',arg) | |||
Example: | |||
ipsobox:addRow( { 'th' , 'Header', title = 'Title' }, | |||
{ 'argh', 'arg1', class = 'parameter' } }) | |||
produces: | |||
<tr><th title="Title">Header</th><th class="parameter">args.arg1</th></tr> | |||
adding it to the infobox table of ipsobox | |||
Cells defined as 'argh' and 'argd' will automatically have data-attr-param="" added, and defined as the passed argument if the infobox in creation is defined as a switch infobox | |||
The row itself may be modified with metadata using the named index at "meta" | |||
-- meta.addClass : mw.html:addClass('arg') | |||
-- this function currently only supports a single string | |||
--]] | |||
function Infobox.addRow(box, ...) | |||
-- New row to add | |||
local args = ... | |||
local _row = box.rtable:tag('tr') | |||
-- For each member of tags | |||
for i, v in ipairs(args) do | |||
-- map tag name to appropriate tag, default to <td> | |||
local _cell = _row:tag(tagmap[v.tag] or 'td') | |||
-- mw.html:attr() and mw.html:css() both accept table input | |||
-- colspan, rowspan, title will be quick ways to access attr | |||
-- these functions also do all the necessary work | |||
if v.attr then | |||
_cell:attr(v.attr) | |||
end | |||
if v.colspan then | |||
_cell:attr('colspan',v.colspan) | |||
end | |||
if v.rowspan then | |||
_cell:attr('rowspan',v.rowspan) | |||
end | |||
if v.title then | |||
_cell:attr('title',v.title) | |||
end | |||
if v.css then | |||
_cell:css(v.css) | |||
end | |||
-- if class is a string, it can be added directly | |||
-- if a table, add every value | |||
-- mw.html:addClass() doesn't function with tables | |||
-- so iterate over the class names here and add them individually | |||
if v.class then | |||
if type(v.class) == 'string' then | |||
_cell:addClass(v.class) | |||
elseif type(v.class) == 'table' then | |||
for _, w in ipairs(v.class) do | |||
_cell:addClass(w) | |||
end | |||
end | |||
end | |||
-- if the cell is a normal th or td, add the exact argument passed | |||
if v.tag == 'th' or v.tag == 'td' then | |||
_cell:wikitext(wrapContent(v.content)) | |||
-- if defined with "arg", add the argument with name passed | |||
elseif v.tag == 'argh' or v.tag == 'argd' then | |||
local content = box.rargs[v.content] | |||
-- if the requested parameter doesn't exist whatsoever, just return a blank string | |||
if not content then | |||
content = '' | |||
-- If switches exist, first attempt to use the version1 values | |||
elseif content.switches then | |||
if content.switches[1] ~= nil_param then | |||
content = content.switches[1] or '' | |||
else | |||
content = content.d or '' | |||
end | |||
-- fallback to default value | |||
else | |||
content = content.d or '' | |||
end | |||
_cell:wikitext(wrapContent(content)) | |||
-- add necessary attribute for switch infoboxes | |||
if box.switchfo then | |||
_cell:attr('data-attr-param',v.content) | |||
end | |||
end | |||
end | |||
-- not that meta | |||
-- allow classes to be defined on the whole row | |||
-- okay, sort of meta | |||
if args.meta then | |||
if args.meta.addClass then | |||
_row:addClass(args.meta.addClass) | |||
end | |||
end | |||
return box | |||
end | |||
function Infobox.customButtonPlacement(box,arg) | |||
box.custom_buttons = arg | |||
return box | |||
end | |||
-- Choose whether to set [[Property:Default version]] | |||
-- Defaults to false. | |||
function Infobox.setDefaultVersionSMW(box, arg) | |||
box.set_default_version_smw = arg | |||
return box | |||
end | |||
function Infobox.addButtonsRow(box, args) | |||
if box.switchfo then | |||
box.custom_buttons = true | |||
local _row = box.rtable:tag('tr') | |||
:addClass('infobox-switch-buttons-row') | |||
:tag('td') | |||
:addClass('infobox-switch-buttons') | |||
:attr('colspan', args.colspan) | |||
:node(box.switch_buttons_tag) | |||
end | |||
return box | |||
end | |||
function Infobox.addButtonsCaption(box) | |||
if box.switchfo then | |||
box.custom_buttons = true | |||
local _row = box.rtable:tag('caption') | |||
:addClass('infobox-switch-buttons-caption') | |||
:node(box.switch_buttons_tag) | |||
end | |||
return box | |||
end | |||
--[[ | |||
-- adds a blank row of padding spanning the given number of columns | |||
--]] | |||
function Infobox.pad(box, colspan, class) | |||
local tr = box:tag('tr') | |||
:tag('td'):attr('colspan', colspan or 1):addClass('infobox-padding') | |||
:done() | |||
if class then | |||
tr:addClass(class) | |||
end | |||
tr:done() | |||
return box | |||
end | |||
--[[ | |||
-- functions the same as mw.html:wikitext() on the wrapper | |||
-- Should only be used for categories really | |||
--]] | |||
function Infobox.wikitext(box, arg) | |||
box.rtable:wikitext(arg) | |||
return box | |||
end | |||
--[[ | |||
-- Adds the specified item(s) to the end of the infobox, outside of the table | |||
-- items are concatenated together with an empty space | |||
--]] | |||
function Infobox.append(box, ...) | |||
for i,v in ipairs({...}) do | |||
table.insert(box.appendStrs, v) | |||
end | |||
return box | |||
end | |||
--[[ | |||
-- Adds a caption to the infobox | |||
-- defaults to the pagename | |||
-- or the default argument if defined | |||
--]] | |||
function Infobox.caption(box) | |||
-- default to the article's name | |||
local name = pagename | |||
-- first see if the name parameter exists | |||
if box.rargs.name then | |||
-- then try the default | |||
if box.rargs.name.d then | |||
name = box.rargs.name.d | |||
-- then look for swithes | |||
elseif box.rargs.name.switches then | |||
-- then look at version 1 | |||
if box.rargs.name.switches[1] ~= nil_param then | |||
name = box.rargs.name.switches[1] | |||
end | |||
end | |||
end | |||
local caption = box.rtable:tag('caption') | |||
:wikitext(name) | |||
-- add necessary attribute for switch infoboxes | |||
if box.switchfo then | |||
caption:attr('data-attr-param','name') | |||
end | |||
return box | |||
end | |||
--[[ | |||
-- Functions for styling the infobox | |||
-- works the same as the respective mw.html functions | |||
--]] | |||
-- attr | |||
function Infobox.attr(box, arg) | |||
box.rtable:attr(arg) | |||
return box | |||
end | |||
-- css | |||
function Infobox.float(box,float) | |||
box.rtable:css('float',float) | |||
return box | |||
end | |||
function Infobox.css(box, ...) | |||
box.rtable:css(...) | |||
return box | |||
end | |||
-- addClass | |||
function Infobox.addClass(box, arg) | |||
box.rtable:addClass(arg) | |||
return box | |||
end | |||
-- Much like Infobox.addClass, but adds multiple classes | |||
function Infobox.addClasses(box, ...) | |||
for _, v in ipairs(...) do | |||
box.rtable:addClass(box) | |||
end | |||
return box | |||
end | |||
--[[ | |||
Add tags directly to the infobox table | |||
Use sparingly | |||
Returns the tag created rather than the entire box | |||
Which is an mw.html object | |||
Further uses of :tag() will be mw.html.tag, rather than Infobox.tag | |||
As such, Infobox:addRow() cannot be used afterwards without restating the infobox as the object | |||
--]] | |||
function Infobox.tag(box, arg) | |||
return box.rtable:tag(arg) | |||
end | |||
--[[ | |||
Allows the infobox to use Semantic Media Wiki and give parameters properties | |||
Pass a table to this function to map parameter names to properties | |||
Calling syntax: | |||
-- {{#show:page|?property}}: | |||
-- "<property>" - unqualified and without a number will display the default value | |||
-- "<property#>" - with a number will show the switch data from that index | |||
-- "all <property>" - adding all will display every unique value in a comma separated list | |||
Properties initiated in Infobox:finish() | |||
--]] | |||
function Infobox:useSMW(arg) | |||
if type(arg) == 'table' then | |||
for w, v in pairs(arg) do | |||
self._smw[w] = v | |||
end | |||
end | |||
end | |||
--[[ | |||
As above, but only assigns to "<property>", which will act like "all <property>" - "<property>#" not present | |||
Properties initiated in Infobox:finish() | |||
--]] | |||
function Infobox:useSMWOne(arg) | |||
if type(arg) == 'table' then | |||
for w, v in pairs(arg) do | |||
self._smwOne[w] = v | |||
end | |||
end | |||
end | |||
--[[ | |||
Set up the infobox to set properties in a SMW subobject. This will create a subobject for each version | |||
- if there is only one version, it will put the properties directly on to the page, like useSMWOne | |||
Properties initiated in Infobox:finish() | |||
--]] | |||
function Infobox:useSMWSubobject(arg) | |||
if type(arg) == 'table' then | |||
for w, v in pairs(arg) do | |||
self._smwSubobject[w] = v | |||
end | |||
end | |||
end | |||
function Infobox:useSMWElement(arg) | |||
if type(arg) == 'table' then | |||
for w, v in pairs(arg) do | |||
self._smwElement[w] = v | |||
end | |||
self.setSMWElement = true | |||
end | |||
end | |||
--[[ | |||
Finishing function | |||
-- Finishes the return, adding necessary final tags | |||
--]] | |||
function Infobox:finish() | |||
local currentNamespace = mw.title.getCurrentTitle().namespace | |||
-- 0 = mainspace, 4 = RuneScape | |||
local onContentNamespace = currentNamespace == 0 or currentNamespace == 4 | |||
-- Don't finish twice | |||
if self.__finished then | |||
return | |||
end | |||
-- Add switch infobox resources | |||
if self.switchfo then | |||
self.rtable:attr('data-resource-class', '.infobox-resources-'..string.gsub(tostring(self.infoboxname), ' ', '_')) | |||
-- Wrapper tag, hidden | |||
self.switch_tag = mw.html.create('div') | |||
:addClass('infobox-switch-resources') | |||
:addClass('infobox-resources-'..string.gsub(tostring(self.infoboxname), ' ', '_')) | |||
:addClass('hidden') | |||
for _, v in ipairs(self.paramnames) do | |||
local param = self.rargs[v] | |||
local default_value = param.d or edit | |||
-- Parameters may not have any switches data, those are ignored | |||
local switchattr = self.switchfoattr[v] | |||
-- Parameter data wrapper | |||
local res_span = self.switch_tag:tag('span') | |||
:attr('data-attr-param',v) | |||
-- Child for default value | |||
local def = res_span:tag('span') | |||
:attr('data-attr-index',0) | |||
:wikitext(tostring(default_value)) | |||
-- Switch classes | |||
if switchattr then | |||
def:attr('data-addclass',switchattr.d) | |||
end | |||
def:done() | |||
if param.switches then | |||
-- Add all switches, ignore those defined as nil | |||
for i, w in ipairs(param.switches) do | |||
if w ~= nil_param and w ~= nil and w ~= default_value then | |||
local _w = res_span:tag('span') | |||
:attr('data-attr-index',i) | |||
:wikitext(tostring(w)) | |||
-- Switch classes | |||
if switchattr and switchattr.switches then | |||
_w:attr('data-addclass',switchattr.switches[i]) | |||
elseif switchattr then | |||
mw.logObject({string.format("Expected switches for `%s` but got none:", v), switchattr}) | |||
end | |||
_w:done() | |||
end | |||
end | |||
res_span:done() | |||
end | |||
end | |||
-- Add a tracking category for pages that have more than 1 version | |||
if onContentNamespace and self.versions > 1 then | |||
-- version count data | |||
self.switch_tag:wikitext('[[Category:Pages that contain switch infobox data]]') | |||
if not self.suppressAllSMW then | |||
self.switch_tag:tag('span') | |||
:wikitext(string.format('Versions: [[Version count::%s]]',self.versions)) | |||
:done() | |||
-- set default version smw | |||
local defver = tonumber(self.args.defver) or 1 | |||
local default_subobject_value = self.args['smwname'..defver] or self.args['version'..defver] | |||
if default_subobject_value and self.set_default_version_smw then | |||
-- Only take first subobject if multiple are defined using "¦" | |||
local default_subobject_name = default_subobject_value:match("[^¦]+") | |||
self.switch_tag:tag('span') | |||
:wikitext(string.format('Default version: [[Default version::%s]]',default_subobject_name)) | |||
end | |||
end | |||
end | |||
self.switch_tag:done() | |||
end | |||
-- smw data | |||
if onContentNamespace and not self.suppressAllSMW then | |||
-- members smw display, yes --> true; no --> false; other --> unknown | |||
local function smwMembers(smw_arg) | |||
local smw_argv = string.lower(smw_arg or '') | |||
if smw_argv == 'yes' then | |||
return 'true' | |||
elseif smw_argv == 'no' then | |||
return 'false' | |||
else | |||
return 'unknown' | |||
end | |||
end | |||
-- release date smw display | |||
local function smwRelease(smw_arg) | |||
local _d,_m,_y = string.match(smw_arg or '', '%[%[(%d%d?) (%a+)%]%] %[%[(%d%d%d%d)%]%]') | |||
if _d == nil then | |||
return nil | |||
end | |||
return table.concat({_d,_m,_y},' ') | |||
end | |||
-- default, just return the text | |||
local function smwDefault(smw_arg) | |||
if smw_arg ~= nil_param and smw_arg ~= edit then | |||
return smw_arg | |||
else | |||
return 'unknown' | |||
end | |||
end | |||
local smw_to_func = { | |||
members = smwMembers, | |||
release = smwRelease, | |||
removal = smwRelease, | |||
default = smwDefault | |||
} | |||
local smw_data_arr = {} | |||
-- custom properties | |||
for w, v in pairs(self._smw) do | |||
-- only needed to give special formatting to release | |||
-- and to make members true/false | |||
local smwfunc = smw_to_func[w] or smw_to_func.default | |||
local curarg = self.rargs[w] | |||
if curarg then | |||
local _arg = curarg.d | |||
local argdefault = _arg | |||
if _arg == edit then | |||
argdefault = 'unknown' | |||
else | |||
local _x = mw.text.split(tostring(_arg), Infobox.splitpoint, true) | |||
if not smw_data_arr[v] then | |||
smw_data_arr[v] = {} | |||
end | |||
if not smw_data_arr['All '..v] then | |||
smw_data_arr['All '..v] = {} | |||
end | |||
for _,y in ipairs(_x) do | |||
local temp_smw_data = smwfunc(y) | |||
table.insert(smw_data_arr[v], temp_smw_data) | |||
table.insert(smw_data_arr['All '..v], temp_smw_data) | |||
end | |||
end | |||
if curarg.switches then | |||
local _args = {} | |||
for x_i, x in ipairs(curarg.switches) do | |||
if not self.suppressVersionSMW[x_i] then | |||
if x ~= nil_param then | |||
table.insert(_args,x) | |||
else | |||
table.insert(_args,argdefault or nil_param) | |||
end | |||
end | |||
end | |||
for i, x in ipairs(_args) do | |||
local _x = mw.text.split(tostring(x), Infobox.splitpoint, true) | |||
if not smw_data_arr[v..i] then | |||
smw_data_arr[v..i] = {} | |||
end | |||
if not smw_data_arr['All '..v] then | |||
smw_data_arr['All '..v] = {} | |||
end | |||
for _,y in ipairs(_x) do | |||
local temp_smw_data = smwfunc(y) | |||
table.insert(smw_data_arr[v..i], temp_smw_data) | |||
table.insert(smw_data_arr['All '..v], temp_smw_data) | |||
end | |||
end | |||
end | |||
end | |||
end | |||
-- if one version, put smwSubobject into smwOne and just do that | |||
if self.versions < 2 and not self._smwSubobjectName then | |||
for w,v in pairs(self._smwSubobject) do | |||
if not self._smwOne[w] then | |||
self._smwOne[w] = v | |||
elseif type(self._smwOne[w]) == 'table' then | |||
table.insert(self._smwOne[w], v) | |||
else | |||
self._smwOne[w] = { self._smwOne[w], v } | |||
end | |||
end | |||
end | |||
for w, _v in pairs(self._smwOne) do | |||
-- only needed to give special formatting to release | |||
-- and to make members true/false | |||
local smwfunc = smw_to_func[w] or smw_to_func.default | |||
local curarg = self.rargs[w] | |||
if curarg then | |||
local _arg = curarg.d | |||
local argdefault = _arg | |||
if _arg == edit then | |||
argdefault = 'unknown' | |||
end | |||
if curarg.switches then | |||
local _args = {} | |||
for x_i, x in ipairs(curarg.switches) do | |||
if not self.suppressVersionSMW[x_i] then | |||
if x ~= nil_param then | |||
table.insert(_args,x) | |||
else | |||
table.insert(_args,argdefault or nil_param) | |||
end | |||
end | |||
end | |||
for i, x in ipairs(_args) do | |||
local _x = mw.text.split(tostring(x), Infobox.splitpoint, true) | |||
for _,y in ipairs(_x) do | |||
local temp_smw_data = smwfunc(y) | |||
for _,v in ipairs(type(_v) == 'table' and _v or {_v}) do | |||
if not smw_data_arr[v] then | |||
smw_data_arr[v] = {} | |||
end | |||
table.insert(smw_data_arr[v], temp_smw_data) | |||
end | |||
end | |||
end | |||
else | |||
if Infobox.isDefined(_arg) then | |||
local _targ = mw.text.split(tostring(_arg), Infobox.splitpoint, true) | |||
for _,y in ipairs(_targ) do | |||
local temp_smw_data = smwfunc(y) | |||
for _,v in ipairs(type(_v) == 'table' and _v or {_v}) do | |||
if not smw_data_arr[v] then | |||
smw_data_arr[v] = {} | |||
end | |||
table.insert(smw_data_arr[v], temp_smw_data) | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end | |||
local smw_data_arr_elem = {} | |||
for w, v in pairs(self._smwElement) do | |||
-- only needed to give special formatting to release | |||
-- and to make members true/false | |||
local smwfunc = smw_to_func[w] or smw_to_func.default | |||
local curarg = self.rargs[w] | |||
if curarg then | |||
local _arg = curarg.d | |||
local argdefault = _arg | |||
if _arg == edit then | |||
argdefault = 'unknown' | |||
end | |||
if curarg.switches then | |||
local _args = {} | |||
for x_i, x in ipairs(curarg.switches) do | |||
if not self.suppressVersionSMW[x_i] then | |||
if x ~= nil_param then | |||
table.insert(_args,x) | |||
else | |||
table.insert(_args,argdefault or nil_param) | |||
end | |||
end | |||
end | |||
for i, x in ipairs(_args) do | |||
if Infobox.isDefined(x) then | |||
local _x = mw.text.split(tostring(x), Infobox.splitpoint, true) | |||
for _,y in ipairs(_x) do | |||
local temp_smw_data = smwfunc(y) | |||
if not smw_data_arr_elem[v] then | |||
smw_data_arr_elem[v] = {} | |||
end | |||
table.insert(smw_data_arr_elem[v], temp_smw_data) | |||
end | |||
end | |||
end | |||
else | |||
if Infobox.isDefined(_arg) then | |||
local _targ = mw.text.split(tostring(_arg), Infobox.splitpoint, true) | |||
for _,y in ipairs(_targ) do | |||
local temp_smw_data = smwfunc(y) | |||
if not smw_data_arr_elem[v] then | |||
smw_data_arr_elem[v] = {} | |||
end | |||
table.insert(smw_data_arr_elem[v], temp_smw_data) | |||
end | |||
end | |||
end | |||
end | |||
end | |||
-- if is a switchfo, setup for subobjects | |||
local smw_data_arr_subobj = {} | |||
if self._smwSubobjectName then | |||
for i,k in ipairs(self._smwSubobjectName) do | |||
if not self.suppressVersionSMW[i] then | |||
local subobj_data = { | |||
['Is variant of'] = {pagename}, | |||
} | |||
for w,v in pairs(self._smwSubobject) do | |||
local smwfunc = smw_to_func[w] or smw_to_func.default | |||
local curarg = self.rargs[w] | |||
if curarg then | |||
local argval = curarg.d | |||
if curarg.switches then | |||
argval = curarg.switches[i] | |||
if not Infobox.isDefined(argval) then | |||
argval = curarg.d | |||
end | |||
end | |||
if Infobox.isDefined(argval) then | |||
local _x = mw.text.split(tostring(argval), Infobox.splitpoint, true) | |||
for _, _x1 in ipairs(_x) do | |||
_x1 = smwfunc(_x1) | |||
if _x1 ~= 'unknown' then | |||
if not subobj_data[v] then | |||
subobj_data[v] = {} | |||
end | |||
table.insert(subobj_data[v], _x1) | |||
end | |||
end | |||
end | |||
end | |||
end | |||
for w,v in ipairs(k) do | |||
local subobj_name = v | |||
-- can't have a . in the first 5 characters of a subobject identifier | |||
if mw.ustring.find(mw.ustring.sub(subobj_name,0,5), '%.') then | |||
self:wikitext('[[Category:Pages with an invalid subobject name]]') | |||
subobj_name = mw.ustring.gsub(subobj_name, '%.', '') | |||
end | |||
if subobj_name == '0' or subobj_name == '' then | |||
self:wikitext('[[Category:Pages with an invalid subobject name]]') | |||
subobj_name = 'v_'..subobj_name | |||
end | |||
smw_data_arr_subobj[subobj_name] = subobj_data | |||
end | |||
end | |||
end | |||
end | |||
local res = mw.smw.set(smw_data_arr) | |||
local res_subobj = true | |||
for w,v in pairs(smw_data_arr_subobj) do | |||
res_subobj = mw.smw.subobject(v, w) | |||
if not res_subobj == true then | |||
break | |||
end | |||
end | |||
if not (res == true and res_subobj == true) then | |||
self.smw_error_tag = mw.html.create('div'):css('display','none'):addClass('infobox-smw-data') | |||
if not res == true then | |||
self.smw_error_tag:tag('div'):wikitext('Error setting SMW properties: '+res.error):done() | |||
end | |||
if not res_subobj == true then | |||
self.smw_error_tag:tag('div'):wikitext('Error setting SMW subobject properties: '+res_subobj.error):done() | |||
end | |||
end | |||
if self.setSMWElement then | |||
if self.smw_error_tag == '' then | |||
self.smw_error_tag = mw.html.create('div'):css('display','none'):addClass('infobox-smw-data') | |||
end | |||
for i,v in pairs(smw_data_arr_elem) do | |||
for j,k in ipairs(v) do | |||
if k ~= 'unknown' then | |||
self.smw_error_tag:tag('span'):wikitext(mw.ustring.format('%s: [[%s::%s]]', i,i,k )) | |||
end | |||
end | |||
end | |||
end | |||
if self._smwSubobjectName then | |||
if self.smw_error_tag == '' then | |||
self.smw_error_tag = mw.html.create('div'):css('display','none'):addClass('infobox-smw-data') | |||
end | |||
for w,v in pairs(smw_data_arr_subobj) do | |||
local subobjdiv = self.smw_error_tag:tag('div') | |||
subobjdiv:tag('span'):wikitext('SMW Subobject for '..w) | |||
for j,k in pairs(v) do | |||
subobjdiv:tag('span'):wikitext(mw.ustring.format('%s: %s', j, table.concat(k, ', '))) | |||
end | |||
end | |||
end | |||
end | |||
-- Add view and talk links to infobox | |||
-- Only done if a name is defined | |||
if self.infoboxname and not self.bottomlinks.hide then | |||
local bottom_links = {} | |||
for _, v in ipairs(self.bottomlinks.links) do | |||
table.insert(bottom_links, | |||
string.format( | |||
table.concat({'[[', | |||
v[1], | |||
'|', | |||
v[2], | |||
']]'}), | |||
self.infoboxname, | |||
self.infoboxname, | |||
self.infoboxname, | |||
self.infoboxname, | |||
self.infoboxname) | |||
) | |||
end | |||
bottom_links = table.concat(bottom_links,' • ') | |||
self.rtable:tag('tr'):tag('td') | |||
:addClass('infobox-template-links') | |||
:attr('colspan', self.bottomlinks.colspan) | |||
:wikitext(bottom_links) | |||
:done() | |||
end | |||
-- Define as finished | |||
self.__finished = true | |||
end | |||
--[[ | |||
Function for defining parameters | |||
-- name : parameter name | |||
-- func : function to define param, defaults to looking at blanks | |||
DO NOT DEFINE VERSION HERE | |||
USE :maxVersion() | |||
Can be used any number of times for efficient definition | |||
--]] | |||
function Infobox:defineParams(...) | |||
for _, v in ipairs(...) do | |||
-- For every parameter, store its corresponding function to self.params | |||
if v.name then | |||
-- If the value is a function or a table (which should define a function) | |||
if type(v.func) == 'function' or type(v.func) == 'table' then | |||
self.params[v.name] = v.func | |||
-- If the value is a string, use the predefined Infobox function of that name | |||
elseif type(v.func) == 'string' then | |||
self.params[v.name] = func_map[v.func] or hasContent | |||
-- Everything else just looks for blanks | |||
else | |||
self.params[v.name] = hasContent() | |||
end | |||
-- Create a list of all param names | |||
table.insert(self.paramnames,v.name) | |||
-- function to allow duplicated values | |||
if v.dupes then | |||
self.dupeable[v.name] = true | |||
end | |||
end | |||
end | |||
end | |||
--[[ | |||
-- Forces an infobox to only use 1 variant | |||
-- Mainly used by lite infoboxes | |||
-- This should be run before creation | |||
--]] | |||
function Infobox:noSwitch() | |||
self.versions = 1 | |||
self.switchfo = false | |||
end | |||
--[[ | |||
-- Calculates the max version | |||
-- Adds labels | |||
-- Sees if this needs to be a switch infobox | |||
-- Returns extra version count (even if already run) | |||
--]] | |||
function Infobox.maxVersion(box) | |||
-- Only allowed to run once | |||
if box.versions ~= -1 then | |||
return box.versions | |||
end | |||
box.labels = {} | |||
box.versions = 0 | |||
local smwname = {} | |||
if string.lower(box.args['smw'] or '') == 'no' then | |||
box.suppressAllSMW = true | |||
end | |||
-- Look for up to 125 variants, defined in order | |||
for i=1, 125 do | |||
-- If variant# exists | |||
if box.args['version'..i] then | |||
-- Increase version count | |||
box.versions = box.versions + 1 | |||
-- Add its label | |||
local label = box.args['version'..i] or ('Version '..i) | |||
table.insert(box.labels,label) | |||
-- add to smwname | |||
if box.args['smwname'..i] then | |||
table.insert(smwname, mw.text.split(box.args['smwname'..i], '¦')) | |||
else | |||
table.insert(smwname, {label}) | |||
end | |||
box.suppressVersionSMW[i] = (box.args['smw'..i] and string.lower(box.args['smw'..i] or '') == 'no') | |||
else | |||
-- Stop if it doesn't exist | |||
break | |||
end | |||
end | |||
-- Define self as a switch infobox if at least 1 other version is found | |||
if box.versions > 0 then | |||
box.switchfo = true | |||
box._smwSubobjectName = smwname | |||
else | |||
-- single version, check for smwname | |||
if box.args['smwname'] then | |||
box._smwSubobjectName = {mw.text.split(box.args['smwname'], '¦')} | |||
end | |||
end | |||
-- versions calculated | |||
return box.versions | |||
end | |||
--[[ | |||
-- Cleans parameters as defined by the above function | |||
-- SHOULD NOT BE RUN UNTIL ALL THINGS ARE DEFINED | |||
-- Handles switches as well | |||
-- adds table _add to rargs, a cleaned up version of arguments | |||
-- d : default value | |||
-- switches : table of switches (in numerical order) | |||
-- Functions can be defined with tables | |||
---- name : name of function | |||
---- params : table of args to pass to functions | |||
---- flag : flags for input | |||
d | #default : use the cleaned parameter first, otherwise passed | |||
p : use the passed value of parameters | |||
r | l : use raw (literal) text, rather than values | |||
-- Defining a single flag will use that flag on all parameters | |||
-- Defining a table of flags will use the respective flag by position | |||
--]] | |||
function Infobox:cleanParams() | |||
-- map of flags to functionality | |||
local flagmap = { | |||
r = 'r', | |||
l = 'r', | |||
d = 'd', | |||
p = 'p' | |||
} | |||
-- For all parameters named | |||
for _, v in ipairs(self.paramnames) do | |||
-- Parameter to add | |||
local _add = {} | |||
local catdata = { all_defined = true, one_defined = false } | |||
-- If the value of params is a function | |||
if type(self.params[v]) == 'function' then | |||
-- Perform that function with the parameter | |||
_add.d = self.params[v](self.args[v]) | |||
-- If it's a table, parse it into a function | |||
elseif type(self.params[v]) == 'table' then | |||
-- Find the functions name | |||
local func = self.params[v].name | |||
if type(func) == 'string' then | |||
func = func_map[func] | |||
end | |||
-- catch all | |||
if type(func) ~= 'function' then | |||
func = has_content | |||
end | |||
-- Recreate table of args and flags | |||
local func_args = {} | |||
local flag = {} | |||
-- If the flags are NOT a table, turn them into a table | |||
-- Same size as the parameter table | |||
-- Every flag will be the same | |||
if type(self.params[v].flag) ~= 'table' then | |||
-- Map flags, if unmapped, use default | |||
local _flag = flagmap[self.params[v].flag] or 'd' | |||
-- recreate table | |||
for x=1,#self.params[v].params do | |||
table.insert(flag,_flag) | |||
end | |||
-- If flags are already a table, recreate them in new table | |||
elseif type(self.params[v].flag) == 'table' then | |||
local _flag = self.params[v].flag | |||
-- recreate table | |||
for x=1,#self.params[v].params do | |||
-- Map flags, if unmapped, use default | |||
table.insert(flag,flagmap[_flag[x]] or 'd') | |||
end | |||
end | |||
-- Recreate param table, parsing flags as instructions | |||
for x, w in ipairs(self.params[v].params) do | |||
local xarg | |||
-- By default or defined as 'd' | |||
-- looks for the cleaned value of the named parameter first | |||
-- if it doesn't exist, look at the passed value next | |||
-- if that doesn't exist, use blank | |||
if flag[x] == 'd' then | |||
xarg = self.rargs[w] and self.rargs[w].d | |||
-- compare to nil explicitly because false is a valid value | |||
if xarg == nil then | |||
xarg = self.args[w] or '' | |||
end | |||
-- Look only at the passed value of the named parameter | |||
-- if that doesn't exist, use blank | |||
elseif flag[x] == 'p' then | |||
xarg = self.args[w] or '' | |||
-- Don't interpret value as a parameter name, and paste the as is | |||
elseif flag[x] == 'r' then | |||
xarg = w | |||
end | |||
-- Add parsed argument to table | |||
table.insert(func_args,xarg) | |||
end | |||
-- Run function | |||
_add.d = func(unpack(func_args)) | |||
end | |||
if _add.d == nil or _add.d == nil_param then | |||
-- have to do pagename defaults here to prevent weird behaviour with switch values | |||
if v == 'name' then | |||
_add.d = pagename | |||
else | |||
_add.d = edit | |||
end | |||
catdata.all_defined = false | |||
else | |||
--_add.d is not nil | |||
catdata.one_defined = true | |||
end | |||
if self.switchfo then | |||
-- Table of switches values and count of them | |||
local _add_switch = {} | |||
local switches = 0 | |||
-- Look for up to the maximum number | |||
for i=1, self.versions do | |||
local _addarg | |||
-- see if this param is allowed to have switch | |||
if v ~= 'image' and v ~= 'examine' and string.find(self.args[v..i] or '','%$%d') then | |||
local refi = string.match(self.args[v..i],'%$(%d+)') | |||
_addarg = _add_switch[tonumber(refi)] or nil_param | |||
else | |||
-- If the value of params is a function | |||
if type(self.params[v]) == 'function' then | |||
-- Perform that function with the parameter at that index | |||
_addarg = self.params[v](self.args[v..i]) | |||
-- If it's a table, parse it into a function | |||
elseif type(self.params[v]) == 'table' then | |||
-- Find the functions name | |||
local func = self.params[v].name | |||
if type(func) == 'string' then | |||
func = func_map[func] | |||
end | |||
-- catch all | |||
if type(func) ~= 'function' then | |||
func = has_content | |||
end | |||
-- Recreate table of args and flags | |||
local func_args = {} | |||
local flag = {} | |||
-- If the flags are NOT a table, turn them into a table | |||
-- Same size as the parameter table | |||
-- Every flag will be the same | |||
if type(self.params[v].flag) ~= 'table' then | |||
-- Map flags, if unmapped, use default | |||
local _flag = flagmap[self.params[v].flag] or 'd' | |||
-- recreate table | |||
for x=1,#self.params[v].params do | |||
table.insert(flag,_flag) | |||
end | |||
-- If flags are already a table, recreate them in new table | |||
elseif type(self.params[v].flag) == 'table' then | |||
local _flag = self.params[v].flag | |||
-- recreate table | |||
for x=1,#self.params[v].params do | |||
-- Map flags, if unmapped, use default | |||
table.insert(flag,flagmap[_flag[x]] or 'd') | |||
end | |||
end | |||
-- Recreate param table, parsing flags as instructions | |||
for x, w in ipairs(self.params[v].params) do | |||
local xarg | |||
-- By default or defined as 'd' | |||
-- looks for the cleaned value of the named parameter first | |||
-- if it doesn't exist, look at the passed value next | |||
-- if that doesn't exist, look at the default | |||
-- if that doesn't exist, use blank | |||
if flag[x] == 'd' then | |||
if self.rargs[w] then | |||
if self.rargs[w].switches then | |||
xarg = self.rargs[w].switches[i] | |||
else | |||
xarg = self.args[w..i] | |||
end | |||
if xarg == nil or xarg == nil_param then | |||
xarg = self.rargs[w].d | |||
end | |||
end | |||
-- multiple catches in a row just to cover everything | |||
if xarg == nil or xarg == nil_param then | |||
xarg = self.args[w..i] | |||
end | |||
if xarg == nil or xarg == edit or xarg == nil_param then | |||
xarg = self.args[w] | |||
end | |||
if xarg == nil or xarg == edit or xarg == nil_param then | |||
xarg = '' | |||
end | |||
-- Look only at the passed value of the named parameter | |||
-- if that doesn't exist, use unnumbered parameter | |||
-- if that doesn't exist, use blank | |||
elseif flag[x] == 'p' then | |||
xarg = self.args[w..i] or self.args[w] or '' | |||
-- Don't interpret value as a parameter name, and paste the as is | |||
elseif flag[x] == 'r' then | |||
xarg = w | |||
end | |||
-- Add parsed argument to table | |||
table.insert(func_args,xarg) | |||
end | |||
-- Run function | |||
_addarg = func(unpack(func_args)) | |||
end | |||
end | |||
-- If not defined, add the nil_param value | |||
-- An actual nil would cause errors in placement | |||
-- So it needs to be filled with an actual value | |||
-- "nil_param" is understood as nil in other functions | |||
-- Include table in case parameter isn't defined by template | |||
if _addarg == nil or _addarg == nil_param then | |||
table.insert(_add_switch, nil_param) | |||
else | |||
switches = switches + 1 | |||
table.insert(_add_switch, _addarg) | |||
catdata.one_defined = true | |||
end | |||
end | |||
-- If there are actually other values to switch to | |||
-- Define a switches subtable, otherwise ignore it | |||
if switches > 0 then | |||
_add.switches = _add_switch | |||
end | |||
end | |||
-- Quick fix for names (which defaults to pagename) | |||
if v == 'name' then | |||
if _add.d == pagename then | |||
if _add.switches and _add.switches[1] ~= pagename and _add.switches[1] ~= nil_param then | |||
_add.d = _add.switches[1] | |||
end | |||
end | |||
end | |||
-- Parameter cleaning finished, add to table of cleaned args | |||
self.rargs[v] = _add | |||
-- Category metadata | |||
-- If every param except default is defined, all_defined = true | |||
if catdata.all_defined == false then | |||
if _add.d == edit then | |||
if _add.switches then | |||
catdata.all_defined = true | |||
for _, v in ipairs(_add.switches) do | |||
if v == nil_param then | |||
catdata.all_defined = false | |||
break | |||
end | |||
end | |||
end | |||
end | |||
end | |||
self.catdata[v] = catdata | |||
end | |||
-- mass dupe removal | |||
-- this needs to be done at the end to keep dependent parameters working | |||
-- also removes incompatible data types | |||
for _, v in ipairs(self.paramnames) do | |||
-- not removed from dupe enabled params parameters | |||
if not self.dupeable[v] and self.rargs[v] and self.rargs[v].switches then | |||
-- tells us whether or not we'll need to remove the switch data | |||
-- switched to false if a switch values does not match the default | |||
local rmvswitch = true | |||
for q, z in ipairs(self.rargs[v].switches) do | |||
-- remove types that don't turn into strings properly | |||
if type(z) == 'table' or type(z) == 'function' then | |||
self.rargs[v].switches[q] = nil_param | |||
-- if it isn't nil or an edit button | |||
-- change variable to keep the switch data | |||
elseif z ~= nil_param and z ~= edit then | |||
rmvswitch = false | |||
end | |||
end | |||
-- remove switch data if everything was a dupe | |||
if rmvswitch then | |||
self.rargs[v].switches = nil | |||
end | |||
end | |||
end | |||
-- Title parentheses (has to be done here, sadly) | |||
local _name | |||
if self.rargs.name then | |||
_name = self.rargs.name.d | |||
-- replace html entities to their actual character | |||
_name = mw.text.decode(_name) | |||
-- if the page name matches the item name, don't add parentheses | |||
if _name == mw.title.getCurrentTitle().fullText then | |||
self.rtable:addClass('no-parenthesis-style') | |||
end | |||
end | |||
end | |||
--[[ | |||
Function to link internal use parameters with JS class attribution | |||
-- self:linkParams{ { arg1, arg2 }, ... { arg1a, arg2a } } | |||
-- arg1: parameter name being linked | |||
-- arg2: parameter name that holds the classes | |||
-- THIS FUNCTION SHOULD BE RUN AFTER :cleanParams() | |||
-- THIS FUNCTION SHOULD BE RUN BEFORE :finish() | |||
-- The second argument's data should always contain a value (a CSS class name) at every index | |||
-- This function is cancelled for non switch boxes | |||
--]] | |||
function Infobox:linkParams(...) | |||
if not self.switchfo then | |||
return | |||
end | |||
for _, v in ipairs(...) do | |||
self.switchfoattr[v[1]] = self.rargs[v[2]] | |||
end | |||
end | |||
--[==========================================[ | |||
-- Functions for accessing parameters easily | |||
--]==========================================] | |||
--[[ | |||
Access the param | |||
-- arg : param name | |||
-- retp : return type | |||
d | #default : self.rargs[arg].d -- Default value | |||
f | full : self.rargs[arg] -- Entire table | |||
s | switches : self.rargs[arg].switches -- Entire switch table | |||
s# : self.rargs[arg].switches[#] -- Single switch value at index # | |||
r : switches[1] or d | |||
--]] | |||
function Infobox:param(arg, retp) | |||
-- All parameters | |||
if arg == 'all' then | |||
return self.rargs | |||
end | |||
-- case-insensitive flagging | |||
retp = tostring(retp):lower() | |||
local fmap = { | |||
d = 'd', default = 'd', s0 = 'd', -- let s-naught count as default (since that's what it is) | |||
f = 'f', full = 'f', | |||
s = 's', switch = 's', switches = 's', | |||
r = 'r' | |||
} | |||
local ret_func | |||
-- quickly see if the parameter is a value greater than 0 | |||
if retp:match('s[1-9]') then | |||
ret_func = 's2' | |||
else | |||
-- Otherwise map it to the correct flag, or the default | |||
ret_func = fmap[retp] or fmap.d | |||
end | |||
-- Fetch parameter | |||
local param = self.rargs[arg] | |||
-- Return nil if no table found | |||
if not param then return nil end | |||
-- Return default | |||
if ret_func == 'd' then | |||
return param.d | |||
end | |||
-- Return full table | |||
if ret_func == 'f' then | |||
return param | |||
end | |||
-- Return switch table | |||
if ret_func == 's' then | |||
return param.switches | |||
end | |||
-- Return the first switch, otherwise the default | |||
if ret_func == 'r' then | |||
if not param.switches then | |||
return param.d | |||
elseif param.switches[1] == nil_param then | |||
return param.d | |||
else | |||
return param.switches[1] | |||
end | |||
end | |||
-- If s2, reread the param | |||
if ret_func == 's2' then | |||
-- no switches | |||
if not param.switches then | |||
return nil | |||
end | |||
-- Parse index by removing the s | |||
local index = retp:match('s(%d+)') | |||
-- nil_param | |||
if param.switches[index] == nil_param then | |||
return nil | |||
else | |||
return param.switches[index] | |||
end | |||
end | |||
end | |||
--[[ | |||
Checks if a parameter is defined and not blank | |||
-- arg : parameter to look at | |||
-- index : index of switches to look at (defaults to default param) | |||
-- defining 'all' will look at every possible value for the parameter | |||
--]] | |||
function Infobox:paramDefined(arg,index) | |||
-- Can use cleaned params for switches | |||
-- but need the passed to identify blanks in the template | |||
local param = self.rargs[arg] | |||
local _arg = self.args[arg] | |||
if string.find(_arg or '','%?action=edit') then | |||
_arg = '' | |||
end | |||
index = index or 0 | |||
local ret | |||
-- create a long strong of every value to test for things if 'all' | |||
if string.lower(index) == 'all' then | |||
return self.catdata[arg] and (self.catdata[arg].one_defined or self.catdata[arg].all_defined) | |||
-- index to number otherwise | |||
else | |||
index = tonumber(index) or 0 | |||
if index == 0 then | |||
if param.switches then | |||
if Infobox.isDefined(param.switches[1]) then | |||
ret = param.switches[1] | |||
else | |||
ret = _arg | |||
end | |||
else | |||
ret = _arg | |||
end | |||
else | |||
if not param.switches then | |||
return nil | |||
end | |||
if param.switches[index] == nil_param then | |||
return nil | |||
end | |||
ret = param.switches[index] | |||
end | |||
end | |||
return tostring(ret or ''):find('%S') | |||
end | |||
--[[ | |||
Function to perform a search on all parameters of a defined name | |||
-- param: param name | |||
-- val: a value or function | |||
-- functions passed must return either true or false | |||
-- with true being counted as a match | |||
--]] | |||
function Infobox:paramGrep(param,val) | |||
local arg = self.rargs[param] | |||
-- if no parameters, return nil | |||
if not arg then | |||
return nil | |||
end | |||
local ret | |||
local valtype = type(val) | |||
-- start with the default value | |||
-- if it's a function, run it | |||
if valtype == 'function' then | |||
ret = val(arg.d) | |||
-- true means it matched | |||
if ret == true then | |||
return ret | |||
end | |||
-- switches up here for functions | |||
if arg.switches then | |||
for _, v in ipairs(arg.switches) do | |||
ret = val(v) | |||
if ret == true then | |||
return true | |||
end | |||
end | |||
end | |||
-- if it's just a value, compare the two | |||
-- only run if types match to avoid errors | |||
-- compare switches later | |||
elseif valtype == type(arg.d) then | |||
-- if a string, make case insensitive | |||
if valtype == 'string' then | |||
if string.lower(val) == string.lower(arg.d) then | |||
return true | |||
end | |||
-- everything else just test directly | |||
elseif val == arg.d then | |||
return true | |||
end | |||
end | |||
-- switch cases | |||
-- more organised putting them here | |||
if arg.switches then | |||
for _, v in ipairs(arg.switches) do | |||
if valtype == type(v) then | |||
if valtype == 'string' then | |||
if val:lower() == v:lower() then | |||
return true | |||
end | |||
elseif val == v then | |||
return true | |||
end | |||
end | |||
end | |||
end | |||
-- return false in every other case | |||
return false | |||
end | |||
------ | |||
function Infobox.paramRead(arg,val) | |||
-- if no parameters, return nil | |||
if not arg then | |||
return nil | |||
end | |||
local ret | |||
local valtype = type(val) | |||
-- start with the default value | |||
-- if it's a function, run it | |||
if valtype == 'function' then | |||
ret = val(arg.d) | |||
-- true means it matched | |||
if ret == true then | |||
return ret | |||
end | |||
-- switches up here for functions | |||
if arg.switches then | |||
for _, v in ipairs(arg.switches) do | |||
ret = val(v) | |||
if ret == true then | |||
return true | |||
end | |||
end | |||
end | |||
-- if it's just a value, compare the two | |||
-- only run if types match to avoid errors | |||
-- compare switches later | |||
elseif valtype == type(arg.d) then | |||
-- if a string, make case insensitive | |||
if valtype == 'string' then | |||
if string.lower(val) == string.lower(arg.d) then | |||
return true | |||
end | |||
-- everything else just test directly | |||
elseif val == arg.d then | |||
return true | |||
end | |||
end | |||
-- switch cases | |||
-- more organised putting them here | |||
if arg.switches then | |||
for _, v in ipairs(arg.switches) do | |||
if valtype == type(v) then | |||
if valtype == 'string' then | |||
if val:lower() == v:lower() then | |||
return true | |||
end | |||
elseif val == v then | |||
return true | |||
end | |||
end | |||
end | |||
end | |||
-- return false in every other case | |||
return false | |||
end | |||
---- | |||
-- Return collected category data | |||
function Infobox:categoryData() | |||
return self.catdata | |||
end | |||
-- Infobox:addDropLevelVars("thieving", "skilllvl1") | |||
function Infobox:addDropLevelVars(key, paramName) | |||
local levelParams = self:param(paramName, 'f') | |||
local dropParams = self:param('dropversion', 'f') | |||
if levelParams == nil then | |||
return | |||
end | |||
if dropParams == nil or not self:paramDefined("dropversion", "all") then | |||
dropParams = {d = 'DEFAULT'} | |||
end | |||
if dropParams.switches == nil then | |||
dropParams.switches = {} | |||
end | |||
local levels = levelParams.switches or {levelParams.d} | |||
local dropVersions = {} | |||
for i=1,#levels do | |||
local dropVersionFromInfobox = dropParams.switches[i] or dropParams.d | |||
if dropVersionFromInfobox == nil_param then | |||
dropVersionFromInfobox = 'DEFAULT' | |||
else | |||
dropVersionFromInfobox = dropVersionFromInfobox .. ",DEFAULT" | |||
end | |||
for dropVersion in string.gmatch(dropVersionFromInfobox, ' *([^,]+) *') do | |||
if dropVersions[dropVersion] == nil then | |||
dropVersions[dropVersion] = {} | |||
end | |||
dropVersions[dropVersion][levels[i]] = true | |||
end | |||
end | |||
-- This part is to append levels from previous Infobox invocations | |||
for dropVersion, dropLevels in pairs(dropVersions) do | |||
-- example variable: DropLevel_combat_High_level | |||
local var_name = string.format("DropLevel_%s_%s", key, dropVersion) | |||
local previousVar = var.var(var_name) | |||
if previousVar ~= "" then | |||
for v in string.gmatch(previousVar, ' *([^,]+) *') do | |||
dropVersions[dropVersion][v] = true | |||
end | |||
end | |||
local ordered = {} | |||
for k, v in pairs(dropVersions[dropVersion]) do | |||
local n = tonumber(k) | |||
if n ~= nil then | |||
table.insert(ordered, n) | |||
end | |||
end | |||
table.sort(ordered) | |||
var.vardefine(var_name, table.concat(ordered, ',')) | |||
end | |||
end | |||
-- Override tostring | |||
function Infobox.tostring(box) | |||
-- If not finished, finish | |||
if not box.__finished then | |||
box:finish() | |||
end | |||
-- Make entire html wrapper a string and return it | |||
local btns = box.switch_buttons_tag | |||
if box.custom_buttons then | |||
btns = '' | |||
end | |||
if box.args.__dump__ then | |||
return '<' .. 'pre>'..mw.dumpObject(box) .. '</' .. 'pre>[[Category:Dumping infoboxes]]' | |||
end | |||
return tostring(btns) .. tostring(box.rtable) .. table.concat(box.appendStrs, '') .. tostring(box.switch_tag) .. tostring(box.smw_error_tag) | |||
end | end | ||
return Infobox | return Infobox | ||
-- </nowiki> |
Latest revision as of 21:54, 21 January 2024
Creating a template step-by-step
Import Module:Infobox
<syntaxhighlight lang="lua"> local infobox = require('Module:Infobox') </syntaxhighlight>
Unpack the frame arguments
<syntaxhighlight lang="lua"> function p.main(frame) local args = frame:getParent().args ... </syntaxhighlight>
Define an Infobox object
<syntaxhighlight lang="lua"> local ret = infobox.new(args) </syntaxhighlight>
Map your arguments to functions
<syntaxhighlight lang="lua"> ret:defineParams { ... } </syntaxhighlight>
Parse your arguments
<syntaxhighlight lang="lua"> ret:cleanParams() </syntaxhighlight>
Initialise your HTML
<syntaxhighlight lang="lua"> ret:create() </syntaxhighlight>
Name your infobox
<syntaxhighlight lang="lua"> -- note: we don't use the "Template:" namespace prefix ret:defineName('Infobox FooBar') </syntaxhighlight>
Give your infobox custom behaviour
<syntaxhighlight lang="lua"> ret:addClass(...) ret:useSMW({ ... }) ret:defineLinks({ ... }) </syntaxhighlight>
<syntaxhighlight lang="lua"> ret:caption() </syntaxhighlight>
Format your table
<syntaxhighlight lang="lua"> ret:addRow{ ... } :addRow{ ... } </syntaxhighlight>
Finalise your template
<syntaxhighlight lang="lua"> ret:finish() </syntaxhighlight>
Functions
Public functions
For the example snippets below, ret
is an arbitrary infobox initiated as:
<syntaxhighlight lang="lua">
local ret = Infobox.new(args)
</syntaxhighlight>
Priority
Certain functions rely on information created by other functions, and thus must be run in a particular order:
0 |
|
---|---|
1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
nilParam
<syntaxhighlight lang="lua">
Infobox.nilParam()
</syntaxhighlight>
Returns the value of the placeholder value Infobox.nil_param
. This value is generally not seen, but it is used to fill switchbox data as nil
causes unwanted behaviour.
isDefined
<syntaxhighlight lang="lua">
Infobox.isDefined(arg)
</syntaxhighlight>
Checks whether the value arg
can be considered as defined.
new
<syntaxhighlight lang="lua">
Infobox.new(args)
</syntaxhighlight>
Creates a new infobox object with args
as a table
of values passed from the template invocation.
This function also creates the top-level wrapper and sets a metatable for the infobox.
create
<syntaxhighlight lang="lua">
ret:create()
</syntaxhighlight>
Creates the HTML tags used for the infobox itself. Will run maxVersion()
if it has not been run already.
defineName
<syntaxhighlight lang="lua"> ret:defineName(arg) </syntaxhighlight> Defines the name of the infobox. This should be the base pagename; i.e. no "Template:" namespace prefix.
defineLinks
<syntaxhighlight lang="lua">
ret:defineLinks(tbl)
</syntaxhighlight>
Defines any number of infobox bottom links in a table
whose elements are table
variables formatted as such:
<syntaxhighlight lang="lua"> { { link, label }, ... } </syntaxhighlight>
link
and label
should be string
variables that will, in a basic sense, define [[Link|label]]
.
In a more technical sense, link
and label
help form a string
for string.format()
, with the module using the equivalent of (but not exactly):
<syntaxhighlight lang="lua">
string.format('['..label..']',...)
</syntaxhighlight>
The template name itself can be called up to 5 times by using "%s", in either link
or label
.
setMaxButtons
<syntaxhighlight lang="lua"> ret:setMaxButtons(n) </syntaxhighlight> Changes the behaviour of buttons by defining the maximum number of buttons that are allowed to appear. If the version count exceeds the button count, their buttons will be replaced with a drop down menu. If not run, the maximum button count will be 5.
cleanParams
<syntaxhighlight lang="lua"> ret:cleanParams() </syntaxhighlight> Parses the parameters with their mapped functions.
defineParams
<syntaxhighlight lang="lua">
ret:defineParams{
...
}
</syntaxhighlight>
Maps parameters to functions as defined by a table
formatted as such:
<syntaxhighlight lang="lua"> { name = <param>, func = <func>, dupes = true }, </syntaxhighlight>
param
should be a string
that names the parameter as it will be in references for use in other functions.
func
should be a function
or instructions on how to find a function.
dupes
is a meta configuration that allows the parameter to be duplicated in switch data. Normally, these duplicate values are not needed, and they will be deleted if they are the same as the default values after all parameters have been parsed. Some parameters require duplication to function properly, such as display altering parameters defined with linkParams()
.
If duplicates are not needed, this parameter should be deleted.
- Predefined functions
If func
is a string
, then the module will attempt to use a predefined function.
Function | Use |
---|---|
name | Uses the parameter named "name". If "name" is blank or undefined, it will use the page name. |
release | Uses the parameters named "release" and "update" and formats it as such:
|
removal | Uses the parameters named "removal" and "removalupdate" and formats it the same as release
|
hasContent | If the parameter is defined and not blank, it will be used. Otherwise, ? (edit) will be used.
|
image | Currently functions the same as hasContent
|
numbers | Removes commas from the parameter and attempts to cast it to a number . If it works, the number is used, otherwise ? (edit)
|
- User-defined functions
If func
is a function
, then that function will be used to parse the parameter. User-defined functions allow for more robust formatting and guidelines.
As a general practice, user-defined functions should be located under the main
functions in the module. It is best to document these functions so that they may be changed by other editors if necessary.
In user-defined functions, circumstances where the edit button should be shown should return nil
, which is automatically handled by the module.
- Simple and complex definitions
Parameters may be mapped to functions in a straightforward manner by simply definining a name of or reference to a function, such as: <syntaxhighlight lang="lua"> ret:defineParams{ { name = 'foo', func = 'hasContent' }, { name = 'bar', func = barFunc } } </syntaxhighlight>
Simple definitions only pass the parameter from the master list named <name>. Some parameters need more interaction with other parameters. To do this, we require a complex definition. Complex definitions point to a user-defined function and map what parameters to pass. Complex definitions can also pass literal values or the uncleaned value with the use of flags.
Complex functions are defined as table
values formatted as such:
<syntaxhighlight lang="lua">
func = { name = <funcname>, params = { <paramname>, ... }, flag = <flags> }
</syntaxhighlight>
A basic example for complex functions is: <syntaxhighlight lang="lua"> ret:defineParams{ { name = 'foo', func = { name = fooFunc, params = { 'bar', 'baz' } }, } -- ... -- ... function fooFunc(arg1,arg2) return arg1..arg2 end </syntaxhighlight>
In this example, we have a parameter named "foo", but we use the parameters passed to the template named "bar" and "baz". Parameters are passed in the order listed, so in the above, the parameter bar
is used for arg1
, and baz
is used for arg2
.
Flags define the behaviour of the parameters named. The following flag behaviours exist:
d
- Looks for the cleaned and parsed version of the parameter. If not cleaned, it will use the value passed to the template. If neither exist, it will usenil
. This is the default behaviour if no flag is defined.p
- Looks only at the value passed to the template. If it does not exist, it will usenil
.r
orl
- Uses the literal (or raw) value.
If flag
is a string
, then the behaviour defined by it will apply to every parameter passed. If flag
is a table
, then each flag will only define the behaviour of the respective parameter.
For example: <syntaxhighlight lang="lua"> ret:defineParams{ { name = 'foo', func = { name = fooFunc, params = { 'foo', 'bar', 'baz' }, flag = { 'd', 'p', 'l' }, }, } </syntaxhighlight>
In the above snippet, foo
will use the default behaviour and bar
will only look for the value passed to the template. The third parameter will use the string literal 'baz'
.
- Definition order
Parameters are defined in the order that they are coded. If a parameter relies on the cleaned value of another parameter, then the parameter dependent on the other will need to be defined after in the definition table.
addRow
<syntaxhighlight lang="lua">
ret:addRow(tbl)
</syntaxhighlight>
Adds a new row to the template with columns and behaviour defined by tbl
, which should be a table
that holds cells defined as table
variables, formatted as such:
<syntaxhighlight lang="lua"> { { celltype, label, <attr1> = <value1>, <attr2> = <value2> ... }, ... } </syntaxhighlight>
celltype
and label
are string
values that define the fundamental display of the cell.
celltype | Output |
---|---|
th | Creates a <th> tag where the content is the value of label
|
td | Creates a <td> tag where the content is the value of label
|
argh | Creates a <th> tag where the content is the value of the parameter named label
|
argd | Creates a <td> tag where the content is the value of the parameter named label
|
The attributes are any of the available attributes defined inside the function. All functions that are ultimately run are documented in the Lua reference manual and are run on the tag for the specific table cell they are called on.
attr | Type | Use |
---|---|---|
attr | table | Passes the value to mw.html.attr() |
css | table | Passes the value to mw.html.css() |
colspan | number | Uses the value to run mw.html.attr('colspan',#) |
rowspan | number | Uses the value to run mw.html.attr('rowspan',#) |
title | string | Uses the value to run mw.html.attr('title',text) |
class | string | Passes the value to mw.html.addClass() |
class | table | Passes every value in the table to mw.html.addClass() |
If the template is a switch infobox, then data-attr-param="<paramname>"
will be added to the table cell.
This function will return the template, allowing further self functions to be performed.
wikitext
<syntaxhighlight lang="lua"> ret:wikitext(txt) </syntaxhighlight> Appends wikitext to the top-level wrapper. Templates, etc. passed directly from Lua code will need explicit preprocessing prior to display properly.
This function will return the template, allowing further self functions to be performed.
<syntaxhighlight lang="lua"> ret:caption() </syntaxhighlight> Adds a caption to the infobox based off the subject name, using the following priority for the text:
name
parametername1
parameter (if switch infobox){{PAGENAME}}
If the template is a switch infobox, this will also add data-attr-param="name"
to the caption
.
This function will return the template, allowing further self functions to be performed.
attr
<syntaxhighlight lang="lua">
ret:attr(arg)
</syntaxhighlight>
Passes arg
to mw.html.attr()
.
This function will return the template, allowing further self functions to be performed.
float
<syntaxhighlight lang="lua">
ret:float(dir)
</syntaxhighlight>
Changes the direction of the CSS style float
for the top level wrapper. By default, all infoboxes float right
.
This function will return the template, allowing further self functions to be performed.
css
<syntaxhighlight lang="lua">
ret:css(...)
</syntaxhighlight>
Passes the arguments to mw.html.css()
.
This function will return the template, allowing further self functions to be performed.
addClass
<syntaxhighlight lang="lua">
ret:addClass(arg)
</syntaxhighlight>
Passes arg
to mw.html.addClass()
.
This function will return the template, allowing further self functions to be performed.
addClasses
<syntaxhighlight lang="lua">
ret:attr(...)
</syntaxhighlight>
Passes every argument to mw.html.addClass()
individually.
This function will return the template, allowing further self functions to be performed.
tag
<syntaxhighlight lang="lua">
ret:tag(arg)
</syntaxhighlight>
Passes arg
to mw.html.tag()
.
This function will return the tag (not the template), allowing further self functions to be performed.
useSMW
<syntaxhighlight lang="lua">
ret:useSMW(tbl)
</syntaxhighlight>
Tells the module to create properties for parameters, as well as defining those mappings with tbl
, a table
whose elements form an associated array formatted as such:
<syntaxhighlight lang="lua"> { parameter = property, ... } </syntaxhighlight>
When defined, properties will be defined for two separate properties: both "Property:<name>" and "Property:All <name>". If the template holds switch infobox data, then properties will be defined for "Property:<name><X>" for all applicable X. The "All <name>" properties exist to create storage for all applicable values. Keep this in mind, as "Property:<name>" will only ever store the default value.
By default, the module will define Property:Release date and Property:Is members only.
noSwitch
<syntaxhighlight lang="lua"> ret:noSwitch() </syntaxhighlight> Forces the template to use only a single version, the default.
maxVersion
<syntaxhighlight lang="lua"> ret:maxVersion() </syntaxhighlight> Returns the number of versions used by the infobox. When run the first time, this function will check and define the version count first. Subsequent calls will simply return the count.
linkParams
<syntaxhighlight lang="lua">
ret:linkParams{
paramName = linkedParam,
...
}
</syntaxhighlight>
Links two parameters where one parameter (paramName
) is the parameter name of a cell's contents, and linkedParam
is the name of the parameter that contains the classes for the specified cell. It will only have an effect on switch infoboxes. Both parameters will need to be defined in the infobox with definedParams
. They should be functions of the parameter they operator on and include dupes = true
in their definitions.
This function must be called only after calling ret:cleanParams()
(and before ret:finish()
).
Example: grand exchange graphs do not make sense to display for untradeable items within switch infoboxes (e.g. ahrim's staff); one could use a linkParam to add a class that will display:none table row when switching to untradeable items.
In the source code of the infobox, these will be added as such:
<syntaxhighlight lang="html5">
</syntaxhighlight>
From this, the switch infobox javascript will add the contents of data-addclass
to class attribute of the row of the table when the infobox is switched. You will also need to define the classes you are using in global CSS.
If the parameter is a th or td element, the class is added to the parent tr. Otherwise, it is added to the element directly (e.g. caption element).
finish
Finalises the template and adds remaining required HTML.
- Creates hidden tag for switch infobox data (if needed)
- Adds Category:Pages that contain switch infobox data if in the mainspace
- Defines Property:Version count if in the mainspace
- Adds Semantic MediaWiki Data in a hidden tag (if needed)
- This data can be viewed on the page itself by using inspect element, but it is not normally visible
- Adds infobox bottom links
param
<syntaxhighlight lang="lua">
ret:param(arg, flag)
</syntaxhighlight>
Returns the value of the parameter named arg
. The exact format and values are determined by the flag
:
d
or empty - default value of the parameterf
orfull
- all values of the parameter as a tables
orswitches
- table of all switch values (or nil if no switches are present)s#
- switch value at index#
r
- if defined, switch value 1, otherwise the default value
Note that this function returns the actual table used by the template. If a switch value is equal to Infobox.nil_param
for flag s#
, then nil
will be returned instead; however, when calling specific indices from the tables returned by f
or r
, the value of Infobox.nil_param
will be returned without extra parsing.
paramDefined
<syntaxhighlight lang="lua">
ret:paramDefined(arg, flag)
</syntaxhighlight>
Looks to see if the parameter named arg
is defined at any index, based on instructions from flag
:
0
or empty - default value of parameter#
- value of switch at index#
all
- true if the parameter is defined with a default or at any switch value
paramGrep
<syntaxhighlight lang="lua">
ret:paramGrep(arg, query)
</syntaxhighlight>
Performs a function or search on every possible value of the parameter named arg
. query
can be either a function
, a string
, or a number
. If a match is found or the function query returns true, paramGrep
will end its search.
If query
is a function, that function must pass a single argument (the parameter), and return either true
or false
.
If query
is a string, then that string will be compared to each value of the parameter. If they match exactly, disregarding casing, then true
will be returned. To perform a pattern match on strings, you will need to define query
as a function that returns a boolean value based on string.find
or mw.ustring.find
.
If query
is a number (or any other type), then it will be compared to each value for a match.
Matches are only checked if the type of query
matches the value of the parameter; e.g., it is not valid to search for a number (ex: 4
) inside a string (ex: '455'
).
paramRead
<syntaxhighlight lang="lua">
Infobox.paramRead(arg, query)
</syntaxhighlight>
Performs the same function as paramGrep
; however, instead of arg
being a name of a parameter to look for, it is the table itself. Useful for functions where only the table of parameters is passed, but not the whole infobox.
categoryData
<syntaxhighlight lang="lua">
ret:categoryData()
</syntaxhighlight>
Returns fundamental category data collected during Infobox:cleanParams()
.
Fields in the category data include:
one_defined
-true
if at least one possible value of the parameter is definedall_defined
-false
if at least one possible value of the parameter is not defined
addDropLevelVars
<syntaxhighlight lang="lua">
Infobox:addDropLevelVars(key, paramName)
</syntaxhighlight>
Used to link infobox versions to specific drops table versions. Sets a var DropLevel_{key}_{version_name}
, where the value is pulled from the value of the given param paramName
at each version. If multiple values are set with the same key, the result will be a comma separated string.
For example, if an infobox has |version1 = A|level1 = 10
, then ret:addDropLevelVars('combat', 'level')
will set the variable DropLevel_combat_A = 10
.
Rules
Defined parameters
Parameters are to be considered "defined" if they meet all of the following criteria:
- Is not
nil
- Contains at least one non-whitespace character
string.find(param,'%S')
- Is not equal to
Infobox.nil_param
- Does not contain the string
'action=edit'
--[=[ -- For documentation, see [[Module:Infobox/doc]] --]=] -- <nowiki> local Infobox = {} Infobox.__index = Infobox Infobox.__tostring = Infobox.tostring -- Edit button for unknown params local editbutton = require('Module:Edit button') local var = mw.ext.VariablesLua local edit = editbutton("'''?''' (edit)") -- Page title local pagename = mw.title.getCurrentTitle().fullText -- map of flags to html tags used by Infobox.addRow() -- let's only define it once, since :addRow() is used multiple times per module local tagmap = { tr = 'tr', th = 'th', td = 'td', argh = 'th', argd = 'td' } --[=[ -- Standardised functions -- called as string with defineParams --]=] -- Standardised "has content" function function hasContent(arg, default) -- Return arg if any non-whitespace character is found return string.match(arg or '','%S') and arg or default end -- Standardised "name" function function subjectName(arg) return string.match(arg or '','%S') and arg or nil end -- Create a standardised release function, since so many pages use it -- Turns release and update into a single parameter function releaseUpdate(release, update) if not Infobox.isDefined(release) then return nil end if string.lower(release) == 'no' then return 'N/A' end if not Infobox.isDefined(update) then return string.format('%s (Update unknown)',release) end if string.lower(update) == 'no' then return release end return string.format('%s ([[Update:%s|Update]])', release, update) end -- Standardised image function function image(img) if img and img:find('%S') then return img else return nil end end -- Standardised numbers function numbers(num) num = string.gsub(num or '',',','') return tonumber(num) end -- Wrap content with line breaks if it contains list-like wiki syntax function wrapContent(content) if type(content) == "string" then local firstListItem = math.min(content:find('^[*#]') or math.huge, content:find('\n[*#]') or math.huge) if firstListItem ~= math.huge then local suffix = content:find('\n[*#][^\n]+$') and '\n' or '' content = content:sub(1, firstListItem - 1) .. '\n' .. content:sub(firstListItem) .. suffix end end return content end -- map of names to pre-defined functions, used by Infobox:defineParams local func_map = { name = subjectName, release = { name = releaseUpdate, params = { 'release', 'update' }, flag = 'p' }, removal = { name = releaseUpdate, params = { 'removal', 'removalupdate' }, flag = 'p' }, has_content = hasContent, hasContent = hasContent, image = image, numbers = numbers, } -- used to fill nil params in switching sections -- this message isn't kidding -- If you see this message anywhere outside of this code -- (including inside switchfo box data) -- report it local nil_param = 'UH OH YOU SHOULDN\'T SEE THIS!' -- In case the nil_param is needed outside of this module -- give it an easy way to be accessed function Infobox.nilParam() return nil_param end -- switch infobox globals local LINE_WIDTH = 300 local MAX_LINES = 2 local DEFAULT_MAX_BUTTONS = 6 -- calculate with width of a switch infobox button -- potential @TODO: rework to use actual character widths function button_width(label) local PX_PER_CHAR = 6 local PX_PAD_MAR = 24 return string.len(label) * PX_PER_CHAR + PX_PAD_MAR end Infobox.splitpoint = '&&SPLITPOINT&&' -- quick test to see if a value is considered nil function Infobox.isDefined(arg) if arg == nil then return false end if type(arg) == 'string' then if arg == nil_param then return false elseif arg:find('%S') then if arg:find('action=edit') then return false else return true end else return false end end return true end --[[ Infobox class -- args : parameters from frame to pass through -- Sets a meta table and creates a <div> tag wrapper -- other fields are initialised in other functions --]] function Infobox.new(args) local obj = setmetatable({ args = args, -- parameters (uncleaned) rargs = {}, -- parameters (cleaned) params = {}, -- parameters mapped to functions paramnames = {}, -- parameter names dupeable = {}, -- parameters that are allowed to have duplicated switch data addrswibclass = true, switchfo = false, -- switch infobox? or not? switchfoattr = {}, -- switch data class changes maxbuttons = DEFAULT_MAX_BUTTONS, -- maximum number of buttons before switching becomes a menu switch_tag = '', -- switchfo data switch_buttons_tag = '', -- switchfo buttons custom_buttons = false, smw_error_tag = '', rtable = nil, -- returned infobox table labels = nil, -- returned labels _smw = {}, -- semantic mediawiki data _smwOne = {}, -- semantic mediawiki data part 2 _smwSubobject = {}, -- semantic mediawiki data part 3 _smwSubobjectName = nil, -- semantic mediawiki data part 3.5 _smwElement = {}, -- semantic mediawiki data part 4 set_default_version_smw = false, -- whether to set [[Property:Default version]] setSMWElement = true, suppressAllSMW = false, suppressVersionSMW = {}, versions = -1, -- number of switch versions (-1 is uncalculated) infoboxname = nil, -- template name appendStrs = {}, bottomlinks = { -- template bottom links links = { { 'Template talk:%s', 'talk' }, { 'Template:%s', 'view' } }, colspan = 2 }, catdata = {}, -- meta category data catlist = {}, -- defined table of category names (strings) __finished = false, -- infobox status }, Infobox) return obj end --[[ Toggles the addition of infobox class use before :create() noop if not a boolean --]] function Infobox:setAddRSWInfoboxClass(bool) if type(bool) == 'boolean' then self.addrswibclass = bool end end --[[ Creates an infobox -- If Infobox:maxVersions() has not been run, it will be run here -- If the infobox should be a switch infobox, all labels will be added -- Creates a wikitable that will be the infobox THIS SHOULD BE DONE AFTER ADDING AND CLEANING PARAMETERS --]] function Infobox:create() -- Run to find if this is a switch infobox and if so, how many boxes if self.versions == -1 then self:maxVersion() end -- Run if switch infobox if self.switchfo then -- Buttons wrapper -- Hidden by default, unhidden by javascript self.switch_buttons_tag = mw.html.create('div') :addClass('infobox-buttons') -- default version to immediately switch to via js local defv = tonumber(self.args.defver) if defv and defv <= self.versions then -- you troll, don't try to show something that isn't there self.switch_buttons_tag:attr('data-default-version',defv) end local numlines = 1 local width_working = 0 local total_width = 0 local buttons = {} -- Add individual buttons to the wrapper for i=1,self.versions do local wid = button_width(self.labels[i] or i) width_working = width_working + wid total_width = total_width + wid if width_working > LINE_WIDTH then numlines = numlines + 1 width_working = wid end local b = mw.html.create('span') :attr('data-switch-index',tostring(i)) -- space to underscore :attr('data-switch-anchor','#'..string.gsub(self.labels[i] or i,' ','_')) :addClass('button') :wikitext(self.labels[i] or i) table.insert(buttons, {b, wid}) end local best = {-1, 100000} if (numlines > 1) and (numlines <= MAX_LINES) then -- attempt to balance line widths local w_s, w_e = 0,total_width for i = 1,#buttons-1 do w_s = w_s + buttons[i][2] w_e = w_e - buttons[i][2] if w_s > LINE_WIDTH then -- w_s only increases, so we're done once it exceeds the width break end if w_e <= LINE_WIDTH then -- w_e only decreases, so just continue if it exceeds line local diff = math.abs(w_s - w_e) if diff < best[2] then best = { i, diff } end end end if best[1] == -1 then best = { math.floor(#buttons/2), 100000 } end end for i,v in ipairs(buttons) do self.switch_buttons_tag:node(v[1]) if i == best[1] then self.switch_buttons_tag:tag('span'):addClass('line-break') end end -- Used by JavaScript to turn the buttons into a menu list if too many variants if self.versions > self.maxbuttons or numlines > MAX_LINES then self.switch_buttons_tag:addClass('infobox-buttons-select') end self.switch_buttons_tag:done() end -- Create infobox table self.rtable = mw.html.create('table') if self.addrswibclass then self.rtable:addClass('infobox') end -- Add necessary class if switch infobox if self.switchfo then self.rtable:addClass('infobox-switch') end end -- Defines an infobox name ({{Template:arg}}) -- Used to create a link at the bottom of pages function Infobox:defineName(arg) self.infoboxname = arg end -- Defines the bottom links of the infobox -- pass a table whose elements are tables that define a link and a label -- { -- { 'link', 'label }, -- ... -- } -- The template name can be substituted into the tables using '%s' -- If we wanted Template:InFooBar to link to it's /doc page with a "doc" label: -- { ... -- { 'Template:%s/doc', 'doc' }, -- ... } -- The template's name can only be called 5 times function Infobox:defineLinks(arg) if type(arg) == 'table' then if arg.colspan then self.bottomlinks.colspan = arg.colspan end if arg.links then if type(arg.links) == 'table' then self.bottomlinks.links = arg.links end end if arg.hide then self.bottomlinks.hide = arg.hide end end end -- Change max number of buttons before switching to menu -- defaults to 5 -- MUST BE RUN BEFORE :create() function Infobox:setMaxButtons(arg) -- if not a number, just go back to default self.maxbuttons = tonumber(arg) or DEFAULT_MAX_BUTTONS end --[[ Add parameters functions All parameters should be tables The first parameter defines the type of cell to create -- th : <th> -- td : <td> -- argh : <th> -- argd : <td> The second parameter defines what is inside the tag -- th | th : text passed -- argh | argd : parameter with the name passed Additional named parameters can be used to add any styling or attributes -- attr : mw.html:attr({ arg1 = '1', ... }) -- css : mw.html:css({ arg1 = '1', ...) -- class : mw.html:addClass('arg') ---- class also supports a table of values, even though mw.html:addClass() does not -- rowspan : mw.html:attr('rowspan',arg) -- colspan : mw.html:attr('colspan',arg) -- title : mw.html:attr('title',arg) Example: ipsobox:addRow( { 'th' , 'Header', title = 'Title' }, { 'argh', 'arg1', class = 'parameter' } }) produces: <tr><th title="Title">Header</th><th class="parameter">args.arg1</th></tr> adding it to the infobox table of ipsobox Cells defined as 'argh' and 'argd' will automatically have data-attr-param="" added, and defined as the passed argument if the infobox in creation is defined as a switch infobox The row itself may be modified with metadata using the named index at "meta" -- meta.addClass : mw.html:addClass('arg') -- this function currently only supports a single string --]] function Infobox.addRow(box, ...) -- New row to add local args = ... local _row = box.rtable:tag('tr') -- For each member of tags for i, v in ipairs(args) do -- map tag name to appropriate tag, default to <td> local _cell = _row:tag(tagmap[v.tag] or 'td') -- mw.html:attr() and mw.html:css() both accept table input -- colspan, rowspan, title will be quick ways to access attr -- these functions also do all the necessary work if v.attr then _cell:attr(v.attr) end if v.colspan then _cell:attr('colspan',v.colspan) end if v.rowspan then _cell:attr('rowspan',v.rowspan) end if v.title then _cell:attr('title',v.title) end if v.css then _cell:css(v.css) end -- if class is a string, it can be added directly -- if a table, add every value -- mw.html:addClass() doesn't function with tables -- so iterate over the class names here and add them individually if v.class then if type(v.class) == 'string' then _cell:addClass(v.class) elseif type(v.class) == 'table' then for _, w in ipairs(v.class) do _cell:addClass(w) end end end -- if the cell is a normal th or td, add the exact argument passed if v.tag == 'th' or v.tag == 'td' then _cell:wikitext(wrapContent(v.content)) -- if defined with "arg", add the argument with name passed elseif v.tag == 'argh' or v.tag == 'argd' then local content = box.rargs[v.content] -- if the requested parameter doesn't exist whatsoever, just return a blank string if not content then content = '' -- If switches exist, first attempt to use the version1 values elseif content.switches then if content.switches[1] ~= nil_param then content = content.switches[1] or '' else content = content.d or '' end -- fallback to default value else content = content.d or '' end _cell:wikitext(wrapContent(content)) -- add necessary attribute for switch infoboxes if box.switchfo then _cell:attr('data-attr-param',v.content) end end end -- not that meta -- allow classes to be defined on the whole row -- okay, sort of meta if args.meta then if args.meta.addClass then _row:addClass(args.meta.addClass) end end return box end function Infobox.customButtonPlacement(box,arg) box.custom_buttons = arg return box end -- Choose whether to set [[Property:Default version]] -- Defaults to false. function Infobox.setDefaultVersionSMW(box, arg) box.set_default_version_smw = arg return box end function Infobox.addButtonsRow(box, args) if box.switchfo then box.custom_buttons = true local _row = box.rtable:tag('tr') :addClass('infobox-switch-buttons-row') :tag('td') :addClass('infobox-switch-buttons') :attr('colspan', args.colspan) :node(box.switch_buttons_tag) end return box end function Infobox.addButtonsCaption(box) if box.switchfo then box.custom_buttons = true local _row = box.rtable:tag('caption') :addClass('infobox-switch-buttons-caption') :node(box.switch_buttons_tag) end return box end --[[ -- adds a blank row of padding spanning the given number of columns --]] function Infobox.pad(box, colspan, class) local tr = box:tag('tr') :tag('td'):attr('colspan', colspan or 1):addClass('infobox-padding') :done() if class then tr:addClass(class) end tr:done() return box end --[[ -- functions the same as mw.html:wikitext() on the wrapper -- Should only be used for categories really --]] function Infobox.wikitext(box, arg) box.rtable:wikitext(arg) return box end --[[ -- Adds the specified item(s) to the end of the infobox, outside of the table -- items are concatenated together with an empty space --]] function Infobox.append(box, ...) for i,v in ipairs({...}) do table.insert(box.appendStrs, v) end return box end --[[ -- Adds a caption to the infobox -- defaults to the pagename -- or the default argument if defined --]] function Infobox.caption(box) -- default to the article's name local name = pagename -- first see if the name parameter exists if box.rargs.name then -- then try the default if box.rargs.name.d then name = box.rargs.name.d -- then look for swithes elseif box.rargs.name.switches then -- then look at version 1 if box.rargs.name.switches[1] ~= nil_param then name = box.rargs.name.switches[1] end end end local caption = box.rtable:tag('caption') :wikitext(name) -- add necessary attribute for switch infoboxes if box.switchfo then caption:attr('data-attr-param','name') end return box end --[[ -- Functions for styling the infobox -- works the same as the respective mw.html functions --]] -- attr function Infobox.attr(box, arg) box.rtable:attr(arg) return box end -- css function Infobox.float(box,float) box.rtable:css('float',float) return box end function Infobox.css(box, ...) box.rtable:css(...) return box end -- addClass function Infobox.addClass(box, arg) box.rtable:addClass(arg) return box end -- Much like Infobox.addClass, but adds multiple classes function Infobox.addClasses(box, ...) for _, v in ipairs(...) do box.rtable:addClass(box) end return box end --[[ Add tags directly to the infobox table Use sparingly Returns the tag created rather than the entire box Which is an mw.html object Further uses of :tag() will be mw.html.tag, rather than Infobox.tag As such, Infobox:addRow() cannot be used afterwards without restating the infobox as the object --]] function Infobox.tag(box, arg) return box.rtable:tag(arg) end --[[ Allows the infobox to use Semantic Media Wiki and give parameters properties Pass a table to this function to map parameter names to properties Calling syntax: -- {{#show:page|?property}}: -- "<property>" - unqualified and without a number will display the default value -- "<property#>" - with a number will show the switch data from that index -- "all <property>" - adding all will display every unique value in a comma separated list Properties initiated in Infobox:finish() --]] function Infobox:useSMW(arg) if type(arg) == 'table' then for w, v in pairs(arg) do self._smw[w] = v end end end --[[ As above, but only assigns to "<property>", which will act like "all <property>" - "<property>#" not present Properties initiated in Infobox:finish() --]] function Infobox:useSMWOne(arg) if type(arg) == 'table' then for w, v in pairs(arg) do self._smwOne[w] = v end end end --[[ Set up the infobox to set properties in a SMW subobject. This will create a subobject for each version - if there is only one version, it will put the properties directly on to the page, like useSMWOne Properties initiated in Infobox:finish() --]] function Infobox:useSMWSubobject(arg) if type(arg) == 'table' then for w, v in pairs(arg) do self._smwSubobject[w] = v end end end function Infobox:useSMWElement(arg) if type(arg) == 'table' then for w, v in pairs(arg) do self._smwElement[w] = v end self.setSMWElement = true end end --[[ Finishing function -- Finishes the return, adding necessary final tags --]] function Infobox:finish() local currentNamespace = mw.title.getCurrentTitle().namespace -- 0 = mainspace, 4 = RuneScape local onContentNamespace = currentNamespace == 0 or currentNamespace == 4 -- Don't finish twice if self.__finished then return end -- Add switch infobox resources if self.switchfo then self.rtable:attr('data-resource-class', '.infobox-resources-'..string.gsub(tostring(self.infoboxname), ' ', '_')) -- Wrapper tag, hidden self.switch_tag = mw.html.create('div') :addClass('infobox-switch-resources') :addClass('infobox-resources-'..string.gsub(tostring(self.infoboxname), ' ', '_')) :addClass('hidden') for _, v in ipairs(self.paramnames) do local param = self.rargs[v] local default_value = param.d or edit -- Parameters may not have any switches data, those are ignored local switchattr = self.switchfoattr[v] -- Parameter data wrapper local res_span = self.switch_tag:tag('span') :attr('data-attr-param',v) -- Child for default value local def = res_span:tag('span') :attr('data-attr-index',0) :wikitext(tostring(default_value)) -- Switch classes if switchattr then def:attr('data-addclass',switchattr.d) end def:done() if param.switches then -- Add all switches, ignore those defined as nil for i, w in ipairs(param.switches) do if w ~= nil_param and w ~= nil and w ~= default_value then local _w = res_span:tag('span') :attr('data-attr-index',i) :wikitext(tostring(w)) -- Switch classes if switchattr and switchattr.switches then _w:attr('data-addclass',switchattr.switches[i]) elseif switchattr then mw.logObject({string.format("Expected switches for `%s` but got none:", v), switchattr}) end _w:done() end end res_span:done() end end -- Add a tracking category for pages that have more than 1 version if onContentNamespace and self.versions > 1 then -- version count data self.switch_tag:wikitext('[[Category:Pages that contain switch infobox data]]') if not self.suppressAllSMW then self.switch_tag:tag('span') :wikitext(string.format('Versions: [[Version count::%s]]',self.versions)) :done() -- set default version smw local defver = tonumber(self.args.defver) or 1 local default_subobject_value = self.args['smwname'..defver] or self.args['version'..defver] if default_subobject_value and self.set_default_version_smw then -- Only take first subobject if multiple are defined using "¦" local default_subobject_name = default_subobject_value:match("[^¦]+") self.switch_tag:tag('span') :wikitext(string.format('Default version: [[Default version::%s]]',default_subobject_name)) end end end self.switch_tag:done() end -- smw data if onContentNamespace and not self.suppressAllSMW then -- members smw display, yes --> true; no --> false; other --> unknown local function smwMembers(smw_arg) local smw_argv = string.lower(smw_arg or '') if smw_argv == 'yes' then return 'true' elseif smw_argv == 'no' then return 'false' else return 'unknown' end end -- release date smw display local function smwRelease(smw_arg) local _d,_m,_y = string.match(smw_arg or '', '%[%[(%d%d?) (%a+)%]%] %[%[(%d%d%d%d)%]%]') if _d == nil then return nil end return table.concat({_d,_m,_y},' ') end -- default, just return the text local function smwDefault(smw_arg) if smw_arg ~= nil_param and smw_arg ~= edit then return smw_arg else return 'unknown' end end local smw_to_func = { members = smwMembers, release = smwRelease, removal = smwRelease, default = smwDefault } local smw_data_arr = {} -- custom properties for w, v in pairs(self._smw) do -- only needed to give special formatting to release -- and to make members true/false local smwfunc = smw_to_func[w] or smw_to_func.default local curarg = self.rargs[w] if curarg then local _arg = curarg.d local argdefault = _arg if _arg == edit then argdefault = 'unknown' else local _x = mw.text.split(tostring(_arg), Infobox.splitpoint, true) if not smw_data_arr[v] then smw_data_arr[v] = {} end if not smw_data_arr['All '..v] then smw_data_arr['All '..v] = {} end for _,y in ipairs(_x) do local temp_smw_data = smwfunc(y) table.insert(smw_data_arr[v], temp_smw_data) table.insert(smw_data_arr['All '..v], temp_smw_data) end end if curarg.switches then local _args = {} for x_i, x in ipairs(curarg.switches) do if not self.suppressVersionSMW[x_i] then if x ~= nil_param then table.insert(_args,x) else table.insert(_args,argdefault or nil_param) end end end for i, x in ipairs(_args) do local _x = mw.text.split(tostring(x), Infobox.splitpoint, true) if not smw_data_arr[v..i] then smw_data_arr[v..i] = {} end if not smw_data_arr['All '..v] then smw_data_arr['All '..v] = {} end for _,y in ipairs(_x) do local temp_smw_data = smwfunc(y) table.insert(smw_data_arr[v..i], temp_smw_data) table.insert(smw_data_arr['All '..v], temp_smw_data) end end end end end -- if one version, put smwSubobject into smwOne and just do that if self.versions < 2 and not self._smwSubobjectName then for w,v in pairs(self._smwSubobject) do if not self._smwOne[w] then self._smwOne[w] = v elseif type(self._smwOne[w]) == 'table' then table.insert(self._smwOne[w], v) else self._smwOne[w] = { self._smwOne[w], v } end end end for w, _v in pairs(self._smwOne) do -- only needed to give special formatting to release -- and to make members true/false local smwfunc = smw_to_func[w] or smw_to_func.default local curarg = self.rargs[w] if curarg then local _arg = curarg.d local argdefault = _arg if _arg == edit then argdefault = 'unknown' end if curarg.switches then local _args = {} for x_i, x in ipairs(curarg.switches) do if not self.suppressVersionSMW[x_i] then if x ~= nil_param then table.insert(_args,x) else table.insert(_args,argdefault or nil_param) end end end for i, x in ipairs(_args) do local _x = mw.text.split(tostring(x), Infobox.splitpoint, true) for _,y in ipairs(_x) do local temp_smw_data = smwfunc(y) for _,v in ipairs(type(_v) == 'table' and _v or {_v}) do if not smw_data_arr[v] then smw_data_arr[v] = {} end table.insert(smw_data_arr[v], temp_smw_data) end end end else if Infobox.isDefined(_arg) then local _targ = mw.text.split(tostring(_arg), Infobox.splitpoint, true) for _,y in ipairs(_targ) do local temp_smw_data = smwfunc(y) for _,v in ipairs(type(_v) == 'table' and _v or {_v}) do if not smw_data_arr[v] then smw_data_arr[v] = {} end table.insert(smw_data_arr[v], temp_smw_data) end end end end end end local smw_data_arr_elem = {} for w, v in pairs(self._smwElement) do -- only needed to give special formatting to release -- and to make members true/false local smwfunc = smw_to_func[w] or smw_to_func.default local curarg = self.rargs[w] if curarg then local _arg = curarg.d local argdefault = _arg if _arg == edit then argdefault = 'unknown' end if curarg.switches then local _args = {} for x_i, x in ipairs(curarg.switches) do if not self.suppressVersionSMW[x_i] then if x ~= nil_param then table.insert(_args,x) else table.insert(_args,argdefault or nil_param) end end end for i, x in ipairs(_args) do if Infobox.isDefined(x) then local _x = mw.text.split(tostring(x), Infobox.splitpoint, true) for _,y in ipairs(_x) do local temp_smw_data = smwfunc(y) if not smw_data_arr_elem[v] then smw_data_arr_elem[v] = {} end table.insert(smw_data_arr_elem[v], temp_smw_data) end end end else if Infobox.isDefined(_arg) then local _targ = mw.text.split(tostring(_arg), Infobox.splitpoint, true) for _,y in ipairs(_targ) do local temp_smw_data = smwfunc(y) if not smw_data_arr_elem[v] then smw_data_arr_elem[v] = {} end table.insert(smw_data_arr_elem[v], temp_smw_data) end end end end end -- if is a switchfo, setup for subobjects local smw_data_arr_subobj = {} if self._smwSubobjectName then for i,k in ipairs(self._smwSubobjectName) do if not self.suppressVersionSMW[i] then local subobj_data = { ['Is variant of'] = {pagename}, } for w,v in pairs(self._smwSubobject) do local smwfunc = smw_to_func[w] or smw_to_func.default local curarg = self.rargs[w] if curarg then local argval = curarg.d if curarg.switches then argval = curarg.switches[i] if not Infobox.isDefined(argval) then argval = curarg.d end end if Infobox.isDefined(argval) then local _x = mw.text.split(tostring(argval), Infobox.splitpoint, true) for _, _x1 in ipairs(_x) do _x1 = smwfunc(_x1) if _x1 ~= 'unknown' then if not subobj_data[v] then subobj_data[v] = {} end table.insert(subobj_data[v], _x1) end end end end end for w,v in ipairs(k) do local subobj_name = v -- can't have a . in the first 5 characters of a subobject identifier if mw.ustring.find(mw.ustring.sub(subobj_name,0,5), '%.') then self:wikitext('[[Category:Pages with an invalid subobject name]]') subobj_name = mw.ustring.gsub(subobj_name, '%.', '') end if subobj_name == '0' or subobj_name == '' then self:wikitext('[[Category:Pages with an invalid subobject name]]') subobj_name = 'v_'..subobj_name end smw_data_arr_subobj[subobj_name] = subobj_data end end end end local res = mw.smw.set(smw_data_arr) local res_subobj = true for w,v in pairs(smw_data_arr_subobj) do res_subobj = mw.smw.subobject(v, w) if not res_subobj == true then break end end if not (res == true and res_subobj == true) then self.smw_error_tag = mw.html.create('div'):css('display','none'):addClass('infobox-smw-data') if not res == true then self.smw_error_tag:tag('div'):wikitext('Error setting SMW properties: '+res.error):done() end if not res_subobj == true then self.smw_error_tag:tag('div'):wikitext('Error setting SMW subobject properties: '+res_subobj.error):done() end end if self.setSMWElement then if self.smw_error_tag == '' then self.smw_error_tag = mw.html.create('div'):css('display','none'):addClass('infobox-smw-data') end for i,v in pairs(smw_data_arr_elem) do for j,k in ipairs(v) do if k ~= 'unknown' then self.smw_error_tag:tag('span'):wikitext(mw.ustring.format('%s: [[%s::%s]]', i,i,k )) end end end end if self._smwSubobjectName then if self.smw_error_tag == '' then self.smw_error_tag = mw.html.create('div'):css('display','none'):addClass('infobox-smw-data') end for w,v in pairs(smw_data_arr_subobj) do local subobjdiv = self.smw_error_tag:tag('div') subobjdiv:tag('span'):wikitext('SMW Subobject for '..w) for j,k in pairs(v) do subobjdiv:tag('span'):wikitext(mw.ustring.format('%s: %s', j, table.concat(k, ', '))) end end end end -- Add view and talk links to infobox -- Only done if a name is defined if self.infoboxname and not self.bottomlinks.hide then local bottom_links = {} for _, v in ipairs(self.bottomlinks.links) do table.insert(bottom_links, string.format( table.concat({'[[', v[1], '|', v[2], ']]'}), self.infoboxname, self.infoboxname, self.infoboxname, self.infoboxname, self.infoboxname) ) end bottom_links = table.concat(bottom_links,' • ') self.rtable:tag('tr'):tag('td') :addClass('infobox-template-links') :attr('colspan', self.bottomlinks.colspan) :wikitext(bottom_links) :done() end -- Define as finished self.__finished = true end --[[ Function for defining parameters -- name : parameter name -- func : function to define param, defaults to looking at blanks DO NOT DEFINE VERSION HERE USE :maxVersion() Can be used any number of times for efficient definition --]] function Infobox:defineParams(...) for _, v in ipairs(...) do -- For every parameter, store its corresponding function to self.params if v.name then -- If the value is a function or a table (which should define a function) if type(v.func) == 'function' or type(v.func) == 'table' then self.params[v.name] = v.func -- If the value is a string, use the predefined Infobox function of that name elseif type(v.func) == 'string' then self.params[v.name] = func_map[v.func] or hasContent -- Everything else just looks for blanks else self.params[v.name] = hasContent() end -- Create a list of all param names table.insert(self.paramnames,v.name) -- function to allow duplicated values if v.dupes then self.dupeable[v.name] = true end end end end --[[ -- Forces an infobox to only use 1 variant -- Mainly used by lite infoboxes -- This should be run before creation --]] function Infobox:noSwitch() self.versions = 1 self.switchfo = false end --[[ -- Calculates the max version -- Adds labels -- Sees if this needs to be a switch infobox -- Returns extra version count (even if already run) --]] function Infobox.maxVersion(box) -- Only allowed to run once if box.versions ~= -1 then return box.versions end box.labels = {} box.versions = 0 local smwname = {} if string.lower(box.args['smw'] or '') == 'no' then box.suppressAllSMW = true end -- Look for up to 125 variants, defined in order for i=1, 125 do -- If variant# exists if box.args['version'..i] then -- Increase version count box.versions = box.versions + 1 -- Add its label local label = box.args['version'..i] or ('Version '..i) table.insert(box.labels,label) -- add to smwname if box.args['smwname'..i] then table.insert(smwname, mw.text.split(box.args['smwname'..i], '¦')) else table.insert(smwname, {label}) end box.suppressVersionSMW[i] = (box.args['smw'..i] and string.lower(box.args['smw'..i] or '') == 'no') else -- Stop if it doesn't exist break end end -- Define self as a switch infobox if at least 1 other version is found if box.versions > 0 then box.switchfo = true box._smwSubobjectName = smwname else -- single version, check for smwname if box.args['smwname'] then box._smwSubobjectName = {mw.text.split(box.args['smwname'], '¦')} end end -- versions calculated return box.versions end --[[ -- Cleans parameters as defined by the above function -- SHOULD NOT BE RUN UNTIL ALL THINGS ARE DEFINED -- Handles switches as well -- adds table _add to rargs, a cleaned up version of arguments -- d : default value -- switches : table of switches (in numerical order) -- Functions can be defined with tables ---- name : name of function ---- params : table of args to pass to functions ---- flag : flags for input d | #default : use the cleaned parameter first, otherwise passed p : use the passed value of parameters r | l : use raw (literal) text, rather than values -- Defining a single flag will use that flag on all parameters -- Defining a table of flags will use the respective flag by position --]] function Infobox:cleanParams() -- map of flags to functionality local flagmap = { r = 'r', l = 'r', d = 'd', p = 'p' } -- For all parameters named for _, v in ipairs(self.paramnames) do -- Parameter to add local _add = {} local catdata = { all_defined = true, one_defined = false } -- If the value of params is a function if type(self.params[v]) == 'function' then -- Perform that function with the parameter _add.d = self.params[v](self.args[v]) -- If it's a table, parse it into a function elseif type(self.params[v]) == 'table' then -- Find the functions name local func = self.params[v].name if type(func) == 'string' then func = func_map[func] end -- catch all if type(func) ~= 'function' then func = has_content end -- Recreate table of args and flags local func_args = {} local flag = {} -- If the flags are NOT a table, turn them into a table -- Same size as the parameter table -- Every flag will be the same if type(self.params[v].flag) ~= 'table' then -- Map flags, if unmapped, use default local _flag = flagmap[self.params[v].flag] or 'd' -- recreate table for x=1,#self.params[v].params do table.insert(flag,_flag) end -- If flags are already a table, recreate them in new table elseif type(self.params[v].flag) == 'table' then local _flag = self.params[v].flag -- recreate table for x=1,#self.params[v].params do -- Map flags, if unmapped, use default table.insert(flag,flagmap[_flag[x]] or 'd') end end -- Recreate param table, parsing flags as instructions for x, w in ipairs(self.params[v].params) do local xarg -- By default or defined as 'd' -- looks for the cleaned value of the named parameter first -- if it doesn't exist, look at the passed value next -- if that doesn't exist, use blank if flag[x] == 'd' then xarg = self.rargs[w] and self.rargs[w].d -- compare to nil explicitly because false is a valid value if xarg == nil then xarg = self.args[w] or '' end -- Look only at the passed value of the named parameter -- if that doesn't exist, use blank elseif flag[x] == 'p' then xarg = self.args[w] or '' -- Don't interpret value as a parameter name, and paste the as is elseif flag[x] == 'r' then xarg = w end -- Add parsed argument to table table.insert(func_args,xarg) end -- Run function _add.d = func(unpack(func_args)) end if _add.d == nil or _add.d == nil_param then -- have to do pagename defaults here to prevent weird behaviour with switch values if v == 'name' then _add.d = pagename else _add.d = edit end catdata.all_defined = false else --_add.d is not nil catdata.one_defined = true end if self.switchfo then -- Table of switches values and count of them local _add_switch = {} local switches = 0 -- Look for up to the maximum number for i=1, self.versions do local _addarg -- see if this param is allowed to have switch if v ~= 'image' and v ~= 'examine' and string.find(self.args[v..i] or '','%$%d') then local refi = string.match(self.args[v..i],'%$(%d+)') _addarg = _add_switch[tonumber(refi)] or nil_param else -- If the value of params is a function if type(self.params[v]) == 'function' then -- Perform that function with the parameter at that index _addarg = self.params[v](self.args[v..i]) -- If it's a table, parse it into a function elseif type(self.params[v]) == 'table' then -- Find the functions name local func = self.params[v].name if type(func) == 'string' then func = func_map[func] end -- catch all if type(func) ~= 'function' then func = has_content end -- Recreate table of args and flags local func_args = {} local flag = {} -- If the flags are NOT a table, turn them into a table -- Same size as the parameter table -- Every flag will be the same if type(self.params[v].flag) ~= 'table' then -- Map flags, if unmapped, use default local _flag = flagmap[self.params[v].flag] or 'd' -- recreate table for x=1,#self.params[v].params do table.insert(flag,_flag) end -- If flags are already a table, recreate them in new table elseif type(self.params[v].flag) == 'table' then local _flag = self.params[v].flag -- recreate table for x=1,#self.params[v].params do -- Map flags, if unmapped, use default table.insert(flag,flagmap[_flag[x]] or 'd') end end -- Recreate param table, parsing flags as instructions for x, w in ipairs(self.params[v].params) do local xarg -- By default or defined as 'd' -- looks for the cleaned value of the named parameter first -- if it doesn't exist, look at the passed value next -- if that doesn't exist, look at the default -- if that doesn't exist, use blank if flag[x] == 'd' then if self.rargs[w] then if self.rargs[w].switches then xarg = self.rargs[w].switches[i] else xarg = self.args[w..i] end if xarg == nil or xarg == nil_param then xarg = self.rargs[w].d end end -- multiple catches in a row just to cover everything if xarg == nil or xarg == nil_param then xarg = self.args[w..i] end if xarg == nil or xarg == edit or xarg == nil_param then xarg = self.args[w] end if xarg == nil or xarg == edit or xarg == nil_param then xarg = '' end -- Look only at the passed value of the named parameter -- if that doesn't exist, use unnumbered parameter -- if that doesn't exist, use blank elseif flag[x] == 'p' then xarg = self.args[w..i] or self.args[w] or '' -- Don't interpret value as a parameter name, and paste the as is elseif flag[x] == 'r' then xarg = w end -- Add parsed argument to table table.insert(func_args,xarg) end -- Run function _addarg = func(unpack(func_args)) end end -- If not defined, add the nil_param value -- An actual nil would cause errors in placement -- So it needs to be filled with an actual value -- "nil_param" is understood as nil in other functions -- Include table in case parameter isn't defined by template if _addarg == nil or _addarg == nil_param then table.insert(_add_switch, nil_param) else switches = switches + 1 table.insert(_add_switch, _addarg) catdata.one_defined = true end end -- If there are actually other values to switch to -- Define a switches subtable, otherwise ignore it if switches > 0 then _add.switches = _add_switch end end -- Quick fix for names (which defaults to pagename) if v == 'name' then if _add.d == pagename then if _add.switches and _add.switches[1] ~= pagename and _add.switches[1] ~= nil_param then _add.d = _add.switches[1] end end end -- Parameter cleaning finished, add to table of cleaned args self.rargs[v] = _add -- Category metadata -- If every param except default is defined, all_defined = true if catdata.all_defined == false then if _add.d == edit then if _add.switches then catdata.all_defined = true for _, v in ipairs(_add.switches) do if v == nil_param then catdata.all_defined = false break end end end end end self.catdata[v] = catdata end -- mass dupe removal -- this needs to be done at the end to keep dependent parameters working -- also removes incompatible data types for _, v in ipairs(self.paramnames) do -- not removed from dupe enabled params parameters if not self.dupeable[v] and self.rargs[v] and self.rargs[v].switches then -- tells us whether or not we'll need to remove the switch data -- switched to false if a switch values does not match the default local rmvswitch = true for q, z in ipairs(self.rargs[v].switches) do -- remove types that don't turn into strings properly if type(z) == 'table' or type(z) == 'function' then self.rargs[v].switches[q] = nil_param -- if it isn't nil or an edit button -- change variable to keep the switch data elseif z ~= nil_param and z ~= edit then rmvswitch = false end end -- remove switch data if everything was a dupe if rmvswitch then self.rargs[v].switches = nil end end end -- Title parentheses (has to be done here, sadly) local _name if self.rargs.name then _name = self.rargs.name.d -- replace html entities to their actual character _name = mw.text.decode(_name) -- if the page name matches the item name, don't add parentheses if _name == mw.title.getCurrentTitle().fullText then self.rtable:addClass('no-parenthesis-style') end end end --[[ Function to link internal use parameters with JS class attribution -- self:linkParams{ { arg1, arg2 }, ... { arg1a, arg2a } } -- arg1: parameter name being linked -- arg2: parameter name that holds the classes -- THIS FUNCTION SHOULD BE RUN AFTER :cleanParams() -- THIS FUNCTION SHOULD BE RUN BEFORE :finish() -- The second argument's data should always contain a value (a CSS class name) at every index -- This function is cancelled for non switch boxes --]] function Infobox:linkParams(...) if not self.switchfo then return end for _, v in ipairs(...) do self.switchfoattr[v[1]] = self.rargs[v[2]] end end --[==========================================[ -- Functions for accessing parameters easily --]==========================================] --[[ Access the param -- arg : param name -- retp : return type d | #default : self.rargs[arg].d -- Default value f | full : self.rargs[arg] -- Entire table s | switches : self.rargs[arg].switches -- Entire switch table s# : self.rargs[arg].switches[#] -- Single switch value at index # r : switches[1] or d --]] function Infobox:param(arg, retp) -- All parameters if arg == 'all' then return self.rargs end -- case-insensitive flagging retp = tostring(retp):lower() local fmap = { d = 'd', default = 'd', s0 = 'd', -- let s-naught count as default (since that's what it is) f = 'f', full = 'f', s = 's', switch = 's', switches = 's', r = 'r' } local ret_func -- quickly see if the parameter is a value greater than 0 if retp:match('s[1-9]') then ret_func = 's2' else -- Otherwise map it to the correct flag, or the default ret_func = fmap[retp] or fmap.d end -- Fetch parameter local param = self.rargs[arg] -- Return nil if no table found if not param then return nil end -- Return default if ret_func == 'd' then return param.d end -- Return full table if ret_func == 'f' then return param end -- Return switch table if ret_func == 's' then return param.switches end -- Return the first switch, otherwise the default if ret_func == 'r' then if not param.switches then return param.d elseif param.switches[1] == nil_param then return param.d else return param.switches[1] end end -- If s2, reread the param if ret_func == 's2' then -- no switches if not param.switches then return nil end -- Parse index by removing the s local index = retp:match('s(%d+)') -- nil_param if param.switches[index] == nil_param then return nil else return param.switches[index] end end end --[[ Checks if a parameter is defined and not blank -- arg : parameter to look at -- index : index of switches to look at (defaults to default param) -- defining 'all' will look at every possible value for the parameter --]] function Infobox:paramDefined(arg,index) -- Can use cleaned params for switches -- but need the passed to identify blanks in the template local param = self.rargs[arg] local _arg = self.args[arg] if string.find(_arg or '','%?action=edit') then _arg = '' end index = index or 0 local ret -- create a long strong of every value to test for things if 'all' if string.lower(index) == 'all' then return self.catdata[arg] and (self.catdata[arg].one_defined or self.catdata[arg].all_defined) -- index to number otherwise else index = tonumber(index) or 0 if index == 0 then if param.switches then if Infobox.isDefined(param.switches[1]) then ret = param.switches[1] else ret = _arg end else ret = _arg end else if not param.switches then return nil end if param.switches[index] == nil_param then return nil end ret = param.switches[index] end end return tostring(ret or ''):find('%S') end --[[ Function to perform a search on all parameters of a defined name -- param: param name -- val: a value or function -- functions passed must return either true or false -- with true being counted as a match --]] function Infobox:paramGrep(param,val) local arg = self.rargs[param] -- if no parameters, return nil if not arg then return nil end local ret local valtype = type(val) -- start with the default value -- if it's a function, run it if valtype == 'function' then ret = val(arg.d) -- true means it matched if ret == true then return ret end -- switches up here for functions if arg.switches then for _, v in ipairs(arg.switches) do ret = val(v) if ret == true then return true end end end -- if it's just a value, compare the two -- only run if types match to avoid errors -- compare switches later elseif valtype == type(arg.d) then -- if a string, make case insensitive if valtype == 'string' then if string.lower(val) == string.lower(arg.d) then return true end -- everything else just test directly elseif val == arg.d then return true end end -- switch cases -- more organised putting them here if arg.switches then for _, v in ipairs(arg.switches) do if valtype == type(v) then if valtype == 'string' then if val:lower() == v:lower() then return true end elseif val == v then return true end end end end -- return false in every other case return false end ------ function Infobox.paramRead(arg,val) -- if no parameters, return nil if not arg then return nil end local ret local valtype = type(val) -- start with the default value -- if it's a function, run it if valtype == 'function' then ret = val(arg.d) -- true means it matched if ret == true then return ret end -- switches up here for functions if arg.switches then for _, v in ipairs(arg.switches) do ret = val(v) if ret == true then return true end end end -- if it's just a value, compare the two -- only run if types match to avoid errors -- compare switches later elseif valtype == type(arg.d) then -- if a string, make case insensitive if valtype == 'string' then if string.lower(val) == string.lower(arg.d) then return true end -- everything else just test directly elseif val == arg.d then return true end end -- switch cases -- more organised putting them here if arg.switches then for _, v in ipairs(arg.switches) do if valtype == type(v) then if valtype == 'string' then if val:lower() == v:lower() then return true end elseif val == v then return true end end end end -- return false in every other case return false end ---- -- Return collected category data function Infobox:categoryData() return self.catdata end -- Infobox:addDropLevelVars("thieving", "skilllvl1") function Infobox:addDropLevelVars(key, paramName) local levelParams = self:param(paramName, 'f') local dropParams = self:param('dropversion', 'f') if levelParams == nil then return end if dropParams == nil or not self:paramDefined("dropversion", "all") then dropParams = {d = 'DEFAULT'} end if dropParams.switches == nil then dropParams.switches = {} end local levels = levelParams.switches or {levelParams.d} local dropVersions = {} for i=1,#levels do local dropVersionFromInfobox = dropParams.switches[i] or dropParams.d if dropVersionFromInfobox == nil_param then dropVersionFromInfobox = 'DEFAULT' else dropVersionFromInfobox = dropVersionFromInfobox .. ",DEFAULT" end for dropVersion in string.gmatch(dropVersionFromInfobox, ' *([^,]+) *') do if dropVersions[dropVersion] == nil then dropVersions[dropVersion] = {} end dropVersions[dropVersion][levels[i]] = true end end -- This part is to append levels from previous Infobox invocations for dropVersion, dropLevels in pairs(dropVersions) do -- example variable: DropLevel_combat_High_level local var_name = string.format("DropLevel_%s_%s", key, dropVersion) local previousVar = var.var(var_name) if previousVar ~= "" then for v in string.gmatch(previousVar, ' *([^,]+) *') do dropVersions[dropVersion][v] = true end end local ordered = {} for k, v in pairs(dropVersions[dropVersion]) do local n = tonumber(k) if n ~= nil then table.insert(ordered, n) end end table.sort(ordered) var.vardefine(var_name, table.concat(ordered, ',')) end end -- Override tostring function Infobox.tostring(box) -- If not finished, finish if not box.__finished then box:finish() end -- Make entire html wrapper a string and return it local btns = box.switch_buttons_tag if box.custom_buttons then btns = '' end if box.args.__dump__ then return '<' .. 'pre>'..mw.dumpObject(box) .. '</' .. 'pre>[[Category:Dumping infoboxes]]' end return tostring(btns) .. tostring(box.rtable) .. table.concat(box.appendStrs, '') .. tostring(box.switch_tag) .. tostring(box.smw_error_tag) end return Infobox -- </nowiki>