Module:Infobox: Difference between revisions
From The Lands Of Liberos Project
Content added Content deleted
m (1 revision imported) |
m (1 revision imported) |
||
(3 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
-- <nowiki> |
|||
-------------------------------------------------------------------------------- |
|||
-- Infobox template module for [[w:c:dev]] documentation. |
|||
-- |
|||
-- @see [[:Category:Infobox templates]] |
|||
-- @usage {{#invoke:Infobox}} |
|||
-- @module infobox |
|||
-- @alias p |
|||
-- @version 1.1.2 |
|||
-- @author Speedit |
|||
-- @author DarthKitty |
|||
-- |
|||
-- @todo Fill holes in the documentation by replacing question marks. |
|||
-- @todo Use the already loaded data for sorting category data by name |
|||
-- instead of re-reading the page with mw.title.new in p.categoryDoc |
|||
-------------------------------------------------------------------------------- |
|||
local p = {} |
local p = {} |
||
local |
local args = {} |
||
local origArgs = {} |
|||
local getArgs = require('Dev:Arguments').getArgs |
|||
local root |
|||
local userError = require('Dev:User error') |
|||
local empty_row_categories = {} |
|||
local wdsButton = require('Dev:WDS Button') |
|||
local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' |
|||
local i18n = require('Dev:I18n').loadMessages( |
|||
local has_rows = false |
|||
'Infobox', |
|||
local lists = { |
|||
'Common', |
|||
plainlist_t = { |
|||
'Testharness' |
|||
patterns = { |
|||
) |
|||
'^plainlist$', |
|||
local entrypoint = require('Dev:Entrypoint') |
|||
'%splainlist$', |
|||
local data = mw.loadData('Dev:Infobox/data') |
|||
'^plainlist%s', |
|||
local title = mw.title.getCurrentTitle() |
|||
'%splainlist%s' |
|||
require('Dev:No interwiki access') |
|||
}, |
|||
found = false, |
|||
styles = 'Plainlist/styles.css' |
|||
}, |
|||
hlist_t = { |
|||
patterns = { |
|||
'^hlist$', |
|||
'%shlist$', |
|||
'^hlist%s', |
|||
'%shlist%s' |
|||
}, |
|||
found = false, |
|||
styles = 'Hlist/styles.css' |
|||
} |
|||
} |
|||
local function has_list_class(args_to_check) |
|||
-------------------------------------------------------------------------------- |
|||
for _, list in pairs(lists) do |
|||
-- Date formatter utility. |
|||
if not list.found then |
|||
-- |
|||
for _, arg in pairs(args_to_check) do |
|||
-- @see [[Template:FormatDate]] |
|||
for _, pattern in ipairs(list.patterns) do |
|||
-- |
|||
if mw.ustring.find(arg or '', pattern) then |
|||
-- @param {string} d |
|||
list.found = true |
|||
-- Unprocessed date. |
|||
break |
|||
-- @param {string} f |
|||
end |
|||
-- Date format to use. |
|||
end |
|||
-- @returns {string} |
|||
if list.found then break end |
|||
-- Formatted, localised date. |
|||
end |
|||
-------------------------------------------------------------------------------- |
|||
end |
|||
local function dtfm(d, f) |
|||
end |
|||
return mw.getCurrentFrame():expandTemplate{ |
|||
title = 'FormatDate', |
|||
args = { |
|||
[1] = d, |
|||
dateformat = f, |
|||
uselang = i18n:getLang() |
|||
} |
|||
} |
|||
end |
end |
||
local function fixChildBoxes(sval, tt) |
|||
-------------------------------------------------------------------------------- |
|||
local function notempty( s ) return s and s:match( '%S' ) end |
|||
-- Breadcrumb link generator. |
|||
-- |
|||
if notempty(sval) then |
|||
-- @param {string} t |
|||
local marker = '<span class=special_infobox_marker>' |
|||
-- Breadcrumb part. |
|||
local s = sval |
|||
-- @param {table} parts |
|||
-- start moving templatestyles and categories inside of table rows |
|||
-- Collection of title parts. |
|||
local slast = '' |
|||
-- @returns {string} |
|||
while slast ~= s do |
|||
-- Breadcrumb chunk. |
|||
slast = s |
|||
-------------------------------------------------------------------------------- |
|||
s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1') |
|||
local function crumbpart(parts) |
|||
s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1') |
|||
local d = #parts |
|||
end |
|||
-- end moving templatestyles and categories inside of table rows |
|||
s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') |
|||
s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) |
|||
if s:match(marker) then |
|||
s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') |
|||
s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') |
|||
s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') |
|||
s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') |
|||
s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') |
|||
s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') |
|||
s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') |
|||
s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') |
|||
s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') |
|||
end |
|||
if s:match(marker) then |
|||
local subcells = mw.text.split(s, marker) |
|||
s = '' |
|||
for k = 1, #subcells do |
|||
if k == 1 then |
|||
s = s .. subcells[k] .. '</' .. tt .. '></tr>' |
|||
elseif k == #subcells then |
|||
local rowstyle = ' style="display:none"' |
|||
if notempty(subcells[k]) then rowstyle = '' end |
|||
s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. |
|||
subcells[k] |
|||
elseif notempty(subcells[k]) then |
|||
if (k % 2) == 0 then |
|||
s = s .. subcells[k] |
|||
else |
|||
s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. |
|||
subcells[k] .. '</' .. tt .. '></tr>' |
|||
end |
|||
end |
|||
end |
|||
end |
|||
-- the next two lines add a newline at the end of lists for the PHP parser |
|||
-- [[Special:Diff/849054481]] |
|||
-- remove when [[:phab:T191516]] is fixed or OBE |
|||
s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') |
|||
s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') |
|||
s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') |
|||
s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') |
|||
return s |
|||
else |
|||
return sval |
|||
end |
|||
end |
|||
-- Cleans empty tables |
|||
return table.concat({ |
|||
local function cleanInfobox() |
|||
(d == 1 and '< ' or ' | '), |
|||
root = tostring(root) |
|||
'[[', |
|||
if has_rows == false then |
|||
table.concat(parts, '/'), |
|||
root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') |
|||
'|', |
|||
end |
|||
parts[d], |
|||
']]' |
|||
}) |
|||
end |
end |
||
-- Returns the union of the values of two tables, as a sequence. |
|||
-------------------------------------------------------------------------------- |
|||
local function union(t1, t2) |
|||
-- Infobox breadcrumb generator for mobile. |
|||
-- |
|||
-- @param {Frame} frame |
|||
-- Frame invocation object. |
|||
-- @returns {string} |
|||
-- Breadcrumb designed for mobile. |
|||
-------------------------------------------------------------------------------- |
|||
function p.breadcrumbs(frame) |
|||
local parts = {} |
|||
local ret = mw.html.create('center') |
|||
local vals = {} |
|||
for t in tostring(title.fullText):gmatch('[^/]+') do |
|||
for k, v in pairs(t1) do |
|||
table.insert(parts, t) |
|||
vals[v] = true |
|||
ret:wikitext(crumbpart(parts)) |
|||
end |
|||
for k, v in pairs(t2) do |
|||
vals[v] = true |
|||
end |
|||
local ret = {} |
|||
for k, v in pairs(vals) do |
|||
table.insert(ret, k) |
|||
end |
|||
return ret |
|||
end |
|||
-- Returns a table containing the numbers of the arguments that exist |
|||
return frame:preprocess(tostring(ret)) |
|||
-- for the specified prefix. For example, if the prefix was 'data', and |
|||
-- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. |
|||
local function getArgNums(prefix) |
|||
local nums = {} |
|||
for k, v in pairs(args) do |
|||
local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') |
|||
if num then table.insert(nums, tonumber(num)) end |
|||
end |
|||
table.sort(nums) |
|||
return nums |
|||
end |
end |
||
-- Adds a row to the infobox, with either a header cell |
|||
-------------------------------------------------------------------------------- |
|||
-- or a label/data cell combination. |
|||
-- Infobox data argument handler. Substitutes '$n' arguments with version |
|||
local function addRow(rowArgs) |
|||
-- numbers. |
|||
-- |
|||
if rowArgs.header and rowArgs.header ~= '_BLANK_' then |
|||
-- @usage {{#invoke:infobox|data|{{{Data}}}|ucfirst=1}} |
|||
has_rows = true |
|||
-- |
|||
has_list_class({ rowArgs.rowclass, rowArgs.class, args.headerclass }) |
|||
-- @param {Frame} frame |
|||
-- Frame invocation object. |
|||
root |
|||
-- @param {string} frame.args[1] |
|||
:tag('tr') |
|||
-- Infobox data input. |
|||
:addClass(rowArgs.rowclass) |
|||
-- @param {string} frame.args.ucfirst |
|||
:cssText(rowArgs.rowstyle) |
|||
-- Capitalization boolean. |
|||
:tag('th') |
|||
-- @throws {string} |
|||
:attr('colspan', '2') |
|||
-- 'missing argument from Module:Infobox in p.data' |
|||
:addClass('infobox-header') |
|||
-- @returns {string} |
|||
:addClass(rowArgs.class) |
|||
-- Argument-substituted infobox data. |
|||
:addClass(args.headerclass) |
|||
-------------------------------------------------------------------------------- |
|||
-- @deprecated next; target .infobox-<name> .infobox-header |
|||
function p.data(frame) |
|||
:cssText(args.headerstyle) |
|||
if not (frame.args or {})[1] then |
|||
:cssText(rowArgs.rowcellstyle) |
|||
error('missing argument from Module:Infobox in p.data') |
|||
:wikitext(fixChildBoxes(rowArgs.header, 'th')) |
|||
end |
|||
if rowArgs.data then |
|||
root:wikitext( |
|||
'[[Category:Pages using infobox templates with ignored data cells]]' |
|||
) |
|||
end |
|||
elseif rowArgs.data and rowArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then |
|||
has_rows = true |
|||
has_list_class({ rowArgs.rowclass, rowArgs.class }) |
|||
local row = root:tag('tr') |
|||
row:addClass(rowArgs.rowclass) |
|||
row:cssText(rowArgs.rowstyle) |
|||
if rowArgs.label then |
|||
row |
|||
:tag('th') |
|||
:attr('scope', 'row') |
|||
:addClass('infobox-label') |
|||
-- @deprecated next; target .infobox-<name> .infobox-label |
|||
:cssText(args.labelstyle) |
|||
:cssText(rowArgs.rowcellstyle) |
|||
:wikitext(rowArgs.label) |
|||
:done() |
|||
end |
|||
local dataCell = row:tag('td') |
|||
dataCell |
|||
local ret = frame.args[1] |
|||
:attr('colspan', not rowArgs.label and '2' or nil) |
|||
local uc1 = yesno(mw.text.trim(frame.args.ucfirst or '')) |
|||
:addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') |
|||
:addClass(rowArgs.class) |
|||
-- @deprecated next; target .infobox-<name> .infobox(-full)-data |
|||
:cssText(rowArgs.datastyle) |
|||
:cssText(rowArgs.rowcellstyle) |
|||
:wikitext(fixChildBoxes(rowArgs.data, 'td')) |
|||
else |
|||
table.insert(empty_row_categories, rowArgs.data or '') |
|||
end |
|||
end |
|||
local function renderTitle() |
|||
if not string.find(ret, '%$') then |
|||
if not args.title then return end |
|||
end |
|||
has_rows = true |
|||
-- Argument substitution. |
|||
has_list_class({args.titleclass}) |
|||
local function repl(d) |
|||
local rsub = d == '1' |
|||
root |
|||
and (tArgs.Submodule or i18n:msg('original')) |
|||
:tag('caption') |
|||
or (tArgs['Submodule' .. d] or i18n:msg('version', d)) |
|||
:addClass('infobox-title') |
|||
:addClass(args.titleclass) |
|||
-- @deprecated next; target .infobox-<name> .infobox-title |
|||
:cssText(args.titlestyle) |
|||
:wikitext(args.title) |
|||
end |
|||
local function renderAboveRow() |
|||
return uc1 |
|||
if not args.above then return end |
|||
and rsub:gsub('^%l', mw.ustring.upper) |
|||
or rsub |
|||
end |
|||
has_rows = true |
|||
ret = ret:gsub('%$(%d+)', repl) |
|||
has_list_class({ args.aboveclass }) |
|||
return ret |
|||
root |
|||
:tag('tr') |
|||
:tag('th') |
|||
:attr('colspan', '2') |
|||
:addClass('infobox-above') |
|||
:addClass(args.aboveclass) |
|||
-- @deprecated next; target .infobox-<name> .infobox-above |
|||
:cssText(args.abovestyle) |
|||
:wikitext(fixChildBoxes(args.above,'th')) |
|||
end |
end |
||
local function renderBelowRow() |
|||
-------------------------------------------------------------------------------- |
|||
if not args.below then return end |
|||
-- Infobox date list generator with version numbers. |
|||
-- |
|||
-- @usage {{#invoke:infobox|date}} |
|||
-- |
|||
-- @param {Frame} frame |
|||
-- Frame invocation object. |
|||
-- @param {string} frame.args |
|||
-- Invocation arguments. |
|||
-- @param {string} frame.args.ext |
|||
-- Code type. |
|||
-- @param {table} frame:getParent().args |
|||
-- Template arguments. |
|||
-- @returns {string} |
|||
-- Formatted multi-line date string. |
|||
-------------------------------------------------------------------------------- |
|||
function p.date(frame) |
|||
local tArgs = frame:getParent().args |
|||
local dateFmt = tArgs.dateformat or '' |
|||
local ret = '' |
|||
has_rows = true |
|||
if tArgs.Updated and #tArgs.Updated > 0 then |
|||
has_list_class({ args.belowclass }) |
|||
-- Generated first formatted date. |
|||
ret = dtfm(tArgs.Updated, dateFmt) |
|||
root |
|||
:tag('tr') |
|||
:tag('td') |
|||
:attr('colspan', '2') |
|||
:addClass('infobox-below') |
|||
:addClass(args.belowclass) |
|||
-- @deprecated next; target .infobox-<name> .infobox-below |
|||
:cssText(args.belowstyle) |
|||
:wikitext(fixChildBoxes(args.below,'td')) |
|||
end |
|||
local function addSubheaderRow(subheaderArgs) |
|||
if tArgs.Submodule or tArgs.Updated2 then |
|||
if subheaderArgs.data and |
|||
-- Append first version tag. |
|||
subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then |
|||
local sub1 = tArgs.Submodule or i18n:msg('original') |
|||
has_rows = true |
|||
has_list_class({ subheaderArgs.rowclass, subheaderArgs.class }) |
|||
local row = root:tag('tr') |
|||
row:addClass(subheaderArgs.rowclass) |
|||
local dataCell = row:tag('td') |
|||
if sub1 ~= '' then |
|||
dataCell |
|||
ret = ret .. ' (' .. sub1 .. ')' |
|||
:attr('colspan', '2') |
|||
end |
|||
:addClass('infobox-subheader') |
|||
end |
|||
:addClass(subheaderArgs.class) |
|||
:cssText(subheaderArgs.datastyle) |
|||
:cssText(subheaderArgs.rowcellstyle) |
|||
:wikitext(fixChildBoxes(subheaderArgs.data, 'td')) |
|||
else |
|||
table.insert(empty_row_categories, subheaderArgs.data or '') |
|||
end |
|||
end |
|||
local function renderSubheaders() |
|||
-- Handle further versions. |
|||
if args.subheader then |
|||
for d = 2, math.huge do |
|||
args.subheader1 = args.subheader |
|||
local p = tArgs['Updated' .. d] |
|||
end |
|||
if args.subheaderrowclass then |
|||
args.subheaderrowclass1 = args.subheaderrowclass |
|||
end |
|||
local subheadernums = getArgNums('subheader') |
|||
for k, num in ipairs(subheadernums) do |
|||
addSubheaderRow({ |
|||
data = args['subheader' .. tostring(num)], |
|||
-- @deprecated next; target .infobox-<name> .infobox-subheader |
|||
datastyle = args.subheaderstyle, |
|||
rowcellstyle = args['subheaderstyle' .. tostring(num)], |
|||
class = args.subheaderclass, |
|||
rowclass = args['subheaderrowclass' .. tostring(num)] |
|||
}) |
|||
end |
|||
end |
|||
local function addImageRow(imageArgs) |
|||
if not p then |
|||
break |
|||
end |
|||
if imageArgs.data and |
|||
local s = tArgs['Submodule' .. d] or i18n:msg('version', d) |
|||
imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then |
|||
has_rows = true |
|||
ret = (d == 2 and '* ' or '') .. ret |
|||
has_list_class({ imageArgs.rowclass, imageArgs.class }) |
|||
.. '\n* ' |
|||
.. dtfm(p, dateFmt) |
|||
local row = root:tag('tr') |
|||
.. ' (' .. s .. ')' |
|||
row:addClass(imageArgs.rowclass) |
|||
end |
|||
local dataCell = row:tag('td') |
|||
-- Default date field. |
|||
dataCell |
|||
elseif tArgs.Code then |
|||
:attr('colspan', '2') |
|||
-- @todo Use DPL template to extract main code page? |
|||
:addClass('infobox-image') |
|||
local ext = frame.args.ext or 'js' |
|||
:addClass(imageArgs.class) |
|||
local suffix = '.' .. ext |
|||
:cssText(imageArgs.datastyle) |
|||
:wikitext(fixChildBoxes(imageArgs.data, 'td')) |
|||
local u = frame:expandTemplate{ |
|||
else |
|||
title = 'Updated', |
|||
table.insert(empty_row_categories, imageArgs.data or '') |
|||
args = { |
|||
end |
|||
'MediaWiki:' .. title.baseText .. suffix |
|||
} |
|||
} |
|||
ret = dtfm(u, dateFmt) |
|||
end |
|||
return ret |
|||
end |
end |
||
local function renderImages() |
|||
-------------------------------------------------------------------------------- |
|||
if args.image then |
|||
-- Category formatter. |
|||
args.image1 = args.image |
|||
-- |
|||
end |
|||
-- @param {table} tbl |
|||
if args.caption then |
|||
-- Array of text items to be returned. |
|||
args.caption1 = args.caption |
|||
-- @param {string} cat |
|||
end |
|||
-- Category name. |
|||
local imagenums = getArgNums('image') |
|||
-- @param {string} sortkey |
|||
for k, num in ipairs(imagenums) do |
|||
-- Category sortkey. |
|||
local caption = args['caption' .. tostring(num)] |
|||
-------------------------------------------------------------------------------- |
|||
local data = mw.html.create():wikitext(args['image' .. tostring(num)]) |
|||
local function category(tbl, cat, sortkey) |
|||
if caption then |
|||
table.insert(tbl, '[[Category:') |
|||
data |
|||
table.insert(tbl, cat) |
|||
:tag('div') |
|||
:addClass('infobox-caption') |
|||
if sortkey and sortkey ~= '' then |
|||
-- @deprecated next; target .infobox-<name> .infobox-caption |
|||
table.insert(tbl, '|') |
|||
:cssText(args.captionstyle) |
|||
table.insert(tbl, sortkey) |
|||
:wikitext(caption) |
|||
end |
|||
end |
|||
addImageRow({ |
|||
table.insert(tbl, ']]') |
|||
data = tostring(data), |
|||
-- @deprecated next; target .infobox-<name> .infobox-image |
|||
datastyle = args.imagestyle, |
|||
class = args.imageclass, |
|||
rowclass = args['imagerowclass' .. tostring(num)] |
|||
}) |
|||
end |
|||
end |
end |
||
-- When autoheaders are turned on, preprocesses the rows |
|||
-------------------------------------------------------------------------------- |
|||
local function preprocessRows() |
|||
-- Returns error for missing description param in infobox. |
|||
if not args.autoheaders then return end |
|||
-- |
|||
-- @returns {string} |
|||
local rownums = union(getArgNums('header'), getArgNums('data')) |
|||
-- Error message and trancking category |
|||
table.sort(rownums) |
|||
-------------------------------------------------------------------------------- |
|||
local lastheader |
|||
function p.description() |
|||
for k, num in ipairs(rownums) do |
|||
return userError('Description missing', 'Content without description') |
|||
if args['header' .. tostring(num)] then |
|||
if lastheader then |
|||
args['header' .. tostring(lastheader)] = nil |
|||
end |
|||
lastheader = num |
|||
elseif args['data' .. tostring(num)] and |
|||
args['data' .. tostring(num)]:gsub( |
|||
category_in_empty_row_pattern, '' |
|||
):match('^%S') then |
|||
local data = args['data' .. tostring(num)] |
|||
if data:gsub(category_in_empty_row_pattern, ''):match('%S') then |
|||
lastheader = nil |
|||
end |
|||
end |
|||
end |
|||
if lastheader then |
|||
args['header' .. tostring(lastheader)] = nil |
|||
end |
|||
end |
end |
||
-- Gets the union of the header and data argument numbers, |
|||
-------------------------------------------------------------------------------- |
|||
-- and renders them all in order |
|||
-- Infobox category generator for type subcategorization. |
|||
local function renderRows() |
|||
-- |
|||
-- @param {Frame} frame |
|||
-- Frame invocation object. |
|||
-- @returns {string} |
|||
-- Type categories corresponding to `Type` infobox argument. |
|||
-------------------------------------------------------------------------------- |
|||
function p.categories(frame) |
|||
local ret = {} |
|||
local typ = frame.args[1] |
|||
local tArgs = p.getParent(frame).args |
|||
local catKeys = tArgs.Type |
|||
local rownums = union(getArgNums('header'), getArgNums('data')) |
|||
if |
|||
table.sort(rownums) |
|||
mw.ustring.lower(tArgs.Status or '') == 'archive' or |
|||
for k, num in ipairs(rownums) do |
|||
not data.categories[typ or ''] or |
|||
addRow({ |
|||
title.namespace ~= 0 |
|||
header = args['header' .. tostring(num)], |
|||
then |
|||
label = args['label' .. tostring(num)], |
|||
return '' |
|||
data = args['data' .. tostring(num)], |
|||
end |
|||
datastyle = args.datastyle, |
|||
class = args['class' .. tostring(num)], |
|||
local sortkey = tArgs.Title or title.prefixedText |
|||
rowclass = args['rowclass' .. tostring(num)], |
|||
-- @deprecated next; target .infobox-<name> rowclass |
|||
category(ret, typ, sortkey) |
|||
rowstyle = args['rowstyle' .. tostring(num)], |
|||
table.insert(ret, '[[:Category:') |
|||
rowcellstyle = args['rowcellstyle' .. tostring(num)] |
|||
table.insert(ret, typ) |
|||
}) |
|||
table.insert(ret, '|') |
|||
end |
|||
table.insert(ret, typ) |
|||
end |
|||
table.insert(ret, ']]') |
|||
-- Maintenance category |
|||
local REPORTCAT = 'Content without type categorization' |
|||
local TYPEDOC = ':Category:' .. REPORTCAT .. '#Documentation' |
|||
if catKeys then |
|||
for v in mw.text.gsplit(mw.ustring.lower(catKeys), '%s*,%s*') do |
|||
local cat = data.categories[typ][mw.text.trim(v)] |
|||
local function renderNavBar() |
|||
if cat then |
|||
if not args.name then return end |
|||
category(ret, cat, sortkey) |
|||
end |
|||
end |
|||
elseif typ == 'JavaScript' then |
|||
table.insert(ret, '<br />') |
|||
table.insert(ret, userError('Type categorization missing', REPORTCAT)) |
|||
table.insert(ret, ' ([[:' .. TYPEDOC .. '|') |
|||
table.insert( |
|||
ret, |
|||
mw.message.new('oasis-more'):useDatabase(false):plain() |
|||
) |
|||
table.insert(ret, ']])') |
|||
end |
|||
has_rows = true |
|||
return table.concat(ret) |
|||
root |
|||
:tag('tr') |
|||
:tag('td') |
|||
:attr('colspan', '2') |
|||
:addClass('infobox-navbar') |
|||
:wikitext(require('Module:Navbar')._navbar{ |
|||
args.name, |
|||
mini = 1, |
|||
}) |
|||
end |
end |
||
local function renderItalicTitle() |
|||
-------------------------------------------------------------------------------- |
|||
local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) |
|||
-- Category documentation generator. |
|||
if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then |
|||
-- |
|||
root:wikitext(require('Module:Italic title')._main({})) |
|||
-- @param {Frame} frame |
|||
end |
|||
-- Frame invocation object. |
|||
-- @param {table} frame.args |
|||
-- Frame argument table. |
|||
-- @param {string} frame.args[1] |
|||
-- Infobox type corresponding to [[Module:Infobox/data]]. |
|||
-- @returns {string} |
|||
-- Table of types against descriptions. |
|||
-------------------------------------------------------------------------------- |
|||
function p.categoryDoc(frame) |
|||
local typ = frame.args[1] or '' |
|||
local ret = mw.html.create('table'):attr { |
|||
['class'] = 'WikiaTable', |
|||
['border'] = '1', |
|||
['id'] = 'types' |
|||
} |
|||
ret:tag('tr') |
|||
:tag('th'):wikitext(i18n:msg('type')) |
|||
:done() |
|||
:tag('th'):wikitext(i18n:msg('description')) |
|||
:done() |
|||
if |
|||
not data.categories[typ] or |
|||
not data.descriptions[typ] |
|||
then |
|||
return ret |
|||
end |
|||
-- Extract categories from data. |
|||
local catData = {} |
|||
local catNames = {} |
|||
for k, n in pairs(data.categories[typ]) do |
|||
if not catData[n] then |
|||
catNames[#catNames + 1] = n |
|||
catData[n] = {k} |
|||
else |
|||
catData[n][#catData[n] + 1] = k |
|||
end |
|||
if |
|||
not catData[n].description and |
|||
data.descriptions[typ][k] |
|||
then |
|||
catData[n].description = data.descriptions[typ][k] |
|||
end |
|||
end |
|||
-- Sort category data by name. |
|||
local dataContent = mw.title.new('Module:Infobox/data'):getContent() |
|||
local function sortKey(a, b) |
|||
local i1, i2 = |
|||
dataContent:find('"' .. a .. '"'), |
|||
dataContent:find('"' .. b .. '"') |
|||
return i1 < i2 |
|||
end |
|||
table.sort(catNames, sortKey) |
|||
-- Render documentation table. |
|||
local cat, catRow, catKeys, desc |
|||
for i, n in ipairs(catNames) do |
|||
cat = catData[n] |
|||
catRow = ret:tag('tr') |
|||
catKeys = catRow:tag('td') |
|||
-- Handle multiple keys. |
|||
if #cat >= 2 then |
|||
catKeys = catKeys:tag('ul') |
|||
for i, k in ipairs(cat) do |
|||
catKeys |
|||
:tag('li'):tag('code') |
|||
:wikitext(k) |
|||
end |
|||
else |
|||
catKeys |
|||
:tag('code') |
|||
:wikitext(cat[1]) |
|||
end |
|||
-- Add description. |
|||
desc = cat.description:gsub('^%l', mw.ustring.upper) .. '.' |
|||
catRow:tag('td') |
|||
:wikitext('[[:Category:' .. n .. ']]') |
|||
:tag('p'):wikitext(desc) |
|||
end |
|||
return tostring(ret) |
|||
end |
end |
||
-- Categories in otherwise empty rows are collected in empty_row_categories. |
|||
-------------------------------------------------------------------------------- |
|||
-- This function adds them to the module output. It is not affected by |
|||
-- Category description generator. Used on category pages to describe pages. |
|||
-- args.decat because this module should not prevent module-external categories |
|||
-- |
|||
-- from rendering. |
|||
-- @param {Frame} frame |
|||
local function renderEmptyRowCategories() |
|||
-- Frame invocation object. |
|||
for _, s in ipairs(empty_row_categories) do |
|||
-- @returns {string} |
|||
root:wikitext(s) |
|||
-- Category description followed by parent category link. |
|||
end |
|||
-------------------------------------------------------------------------------- |
|||
function p.categoryDesc(frame) |
|||
local typ = frame.args[1] or '' |
|||
local key = frame.args[2] or '' |
|||
local ret = data.messages.description |
|||
local desc = (data.descriptions[typ] or {})[key] |
|||
if not desc then |
|||
error('misconfigured arguments in p.categoryDesc from Module:Install') |
|||
end |
|||
ret = ret:gsub('$1', desc) .. '[[Category:' .. typ .. '|{{SUBPAGENAME}}]]' |
|||
return frame:preprocess(ret) |
|||
end |
end |
||
-- Render tracking categories. args.decat == turns off tracking categories. |
|||
-------------------------------------------------------------------------------- |
|||
local function renderTrackingCategories() |
|||
-- Alias mapper for {{t|Infobox Lua}} `Type` argument. |
|||
if args.decat == 'yes' then return end |
|||
-- |
|||
if args.child == 'yes' then |
|||
-- @param {Frame} frame |
|||
if args.title then |
|||
-- Frame invocation object. |
|||
root:wikitext( |
|||
-- @returns {string} |
|||
'[[Category:Pages using embedded infobox templates with the title parameter]]' |
|||
-- Lua type (`'invocable'` or `'meta'`). |
|||
) |
|||
-------------------------------------------------------------------------------- |
|||
end |
|||
function p.luaType(frame) |
|||
elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then |
|||
local tArgs = frame:getParent().args |
|||
root:wikitext('[[Category:Articles using infobox templates with no data rows]]') |
|||
local typ = tArgs.Type or tArgs.type |
|||
end |
|||
return typ |
|||
and data.luaTypes[typ:lower()] |
|||
or '' |
|||
end |
end |
||
--[=[ |
|||
-------------------------------------------------------------------------------- |
|||
Loads the templatestyles for the infobox. |
|||
-- Test suite status. |
|||
-- |
|||
-- @param {Frame} frame |
|||
-- Frame invocation object. |
|||
-- @returns {string} |
|||
-- Test suite status badge. |
|||
-------------------------------------------------------------------------------- |
|||
function p.luaSuite(frame) |
|||
local page = |
|||
frame:getParent().Code or |
|||
mw.language.fetchLanguageName(title.subpageText) == '' |
|||
and title.subpageText |
|||
or title.baseText:match('/*([^/]+)$') |
|||
TODO: FINISH loading base templatestyles here rather than in |
|||
-- DPL query for categories |
|||
MediaWiki:Common.css. There are 4-5000 pages with 'raw' infobox tables. |
|||
local query = table.concat({ |
|||
See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). |
|||
'{{#dpl:', |
|||
When we do this we should clean up the inline CSS below too. |
|||
'| debug = 0', |
|||
Will have to do some bizarre conversion category like with sidebar. |
|||
'| mode = userformat', |
|||
'| allowcachedresults = 1', |
|||
'| category = Lua test suites', |
|||
'| titleregexp = ^' .. mw.uri.encode(page, 'WIKI') .. '/testcases$', |
|||
'| addcategories = true', |
|||
'| format = ,%CATNAMES%,,', |
|||
'}}' |
|||
}, '\n') |
|||
]=] |
|||
-- Fetch category data. |
|||
local function loadTemplateStyles() |
|||
local cats = mw.text.split(frame:preprocess(query), ',') |
|||
local frame = mw.getCurrentFrame() |
|||
--[=[ |
|||
-- @todo Fix this: |
|||
local hlist_templatestyles = '' |
|||
if cats[2] and data.luaStatus[cats[2]] |
|||
if lists.hlist_t.found then |
|||
and cats[1] == 'Lua test suites' |
|||
hlist_templatestyles = frame:extensionTag{ |
|||
and cats[2] ~= 'Lua test suites' |
|||
name = 'templatestyles', args = { src = lists.hlist_t.styles } |
|||
then |
|||
} |
|||
-- DPL sorts pages alphabetically, so if [[Module talk:PAGE/testcases]] |
|||
end |
|||
-- contains [[:Category:Lua test suites]], then [[:Category:Skipped Lua test suites]] |
|||
-- and [[:Category:Pages with script errors]] end up ingored, |
|||
local plainlist_templatestyles = '' |
|||
-- and the test suite is incorrectly reported as passing. |
|||
if lists.plainlist_t.found then |
|||
cats[1], cats[2] = cats[2], cats[1] |
|||
plainlist_templatestyles = frame:extensionTag{ |
|||
end |
|||
name = 'templatestyles', args = { src = lists.plainlist_t.styles } |
|||
]=] |
|||
} |
|||
local i18n_key = data.luaStatus[cats[1] or ''] or 'unknown' |
|||
end |
|||
-- See function description |
|||
local base_templatestyles = frame:extensionTag{ |
|||
name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' } |
|||
} |
|||
local templatestyles = '' |
|||
-- Build badge. |
|||
if args['templatestyles'] then |
|||
local badge = mw.html.create('div') |
|||
templatestyles = frame:extensionTag{ |
|||
:addClass('plainlinks') |
|||
name = 'templatestyles', args = { src = args['templatestyles'] } |
|||
:wikitext( |
|||
} |
|||
'[' .. |
|||
end |
|||
mw.site.server .. |
|||
mw.uri.fullUrl('Module talk:' .. page .. '/testcases').path .. |
|||
local child_templatestyles = '' |
|||
' ' .. |
|||
if args['child templatestyles'] then |
|||
wdsButton._badge(i18n:msg(i18n_key), i18n_key) .. |
|||
child_templatestyles = frame:extensionTag{ |
|||
']' |
|||
name = 'templatestyles', args = { src = args['child templatestyles'] } |
|||
) |
|||
} |
|||
end |
|||
local grandchild_templatestyles = '' |
|||
if args['grandchild templatestyles'] then |
|||
grandchild_templatestyles = frame:extensionTag{ |
|||
name = 'templatestyles', args = { src = args['grandchild templatestyles'] } |
|||
} |
|||
end |
|||
return table.concat({ |
|||
-- hlist -> plainlist -> base is best-effort to preserve old Common.css ordering. |
|||
-- this ordering is not a guarantee because the rows of interest invoking |
|||
-- each class may not be on a specific page |
|||
hlist_templatestyles, |
|||
plainlist_templatestyles, |
|||
base_templatestyles, |
|||
templatestyles, |
|||
child_templatestyles, |
|||
grandchild_templatestyles |
|||
}) |
|||
end |
|||
-- common functions between the child and non child cases |
|||
return tostring(badge) |
|||
local function structure_infobox_common() |
|||
renderSubheaders() |
|||
renderImages() |
|||
preprocessRows() |
|||
renderRows() |
|||
renderBelowRow() |
|||
renderNavBar() |
|||
renderItalicTitle() |
|||
renderEmptyRowCategories() |
|||
renderTrackingCategories() |
|||
cleanInfobox() |
|||
end |
end |
||
-- Specify the overall layout of the infobox, with special settings if the |
|||
--- Set of valid scope aliases |
|||
-- infobox is used as a 'child' inside another infobox. |
|||
local scopeAliases = { |
|||
local function _infobox() |
|||
p = "personal", |
|||
if args.child ~= 'yes' then |
|||
s = "site-wide", |
|||
root = mw.html.create('table') |
|||
mw = "vanilla mediawiki", |
|||
} |
|||
--- Set of valid scope names |
|||
local validScopes = { |
|||
["personal"] = true, |
|||
["site-wide"] = true, |
|||
["vanilla mediawiki"] = true, |
|||
} |
|||
--- Dictionary of "scope" → "i18n" key mapping |
|||
local scopeLangMap = { |
|||
["site-wide"] = "sitewide", |
|||
["vanilla mediawiki"] = "vanilla-mw" |
|||
} |
|||
--- Dictionary of "scope" → "category" subpage mapping |
|||
local scopeCategoryMap = { |
|||
["personal"] = "Personal use", |
|||
["site-wide"] = "Site-wide use", |
|||
["vanilla mediawiki"] = "Vanilla MediaWiki use" |
|||
} |
|||
--- The order in which scopes are displayed in the infobox |
|||
local scopeOrder = { |
|||
"personal", |
|||
"site-wide", |
|||
"vanilla mediawiki", |
|||
} |
|||
-------------------------------------------------------------------------------- |
|||
-- Function used by {{T|scope}}. |
|||
-- |
|||
-- @param {Frame|table} frame |
|||
-- Scribunto Frame object or table of arguments. |
|||
-- @returns {string} |
|||
-- The parsed scopes |
|||
-------------------------------------------------------------------------------- |
|||
function p.parseScope(frame) |
|||
local args = getArgs(frame) |
|||
root |
|||
local arg1 = mw.ustring.lower(args[1] or "") |
|||
:addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') |
|||
local type = args.type |
|||
:addClass(args.bodyclass) |
|||
local rec = yesno(args.pr) |
|||
-- @deprecated next; target .infobox-<name> |
|||
:cssText(args.bodystyle) |
|||
has_list_class({ args.bodyclass }) |
|||
renderTitle() |
|||
local usingLegacyAlias = false |
|||
renderAboveRow() |
|||
local hasUnknownScope = false |
|||
else |
|||
local scopes = {} |
|||
root = mw.html.create() |
|||
root |
|||
local function explodeScope(scopeName, callback) |
|||
:wikitext(args.title) |
|||
if not scopeName or #scopeName == 0 then return end |
|||
end |
|||
structure_infobox_common() |
|||
return loadTemplateStyles() .. root |
|||
end |
|||
-- If the argument exists and isn't blank, add it to the argument table. |
|||
scopeName = scopeAliases[scopeName] or scopeName |
|||
-- Blank arguments are treated as nil to match the behaviour of ParserFunctions. |
|||
if validScopes[scopeName] then |
|||
local function preprocessSingleArg(argName) |
|||
scopes[scopeName] = true |
|||
if origArgs[argName] and origArgs[argName] ~= '' then |
|||
elseif scopeName == "ps" then |
|||
args[argName] = origArgs[argName] |
|||
-- Legacy compat |
|||
scopes["personal"] = true |
|||
scopes["site-wide"] = true |
|||
usingLegacyAlias = true |
|||
elseif callback then |
|||
callback(scopeName) |
|||
else |
|||
hasUnknownScope = true |
|||
end |
|||
end |
end |
||
end |
|||
-- Assign the parameters with the given prefixes to the args table, in order, in |
|||
local function explodeScope2(splitSymbol) |
|||
-- batches of the step size specified. This is to prevent references etc. from |
|||
for splitWS in mw.text.gsplit(splitSymbol, "%s+") do |
|||
-- appearing in the wrong order. The prefixTable should be an array containing |
|||
explodeScope(splitWS) |
|||
-- tables, each of which has two possible fields, a "prefix" string and a |
|||
end |
|||
-- "depend" table. The function always parses parameters containing the "prefix" |
|||
-- string, but only parses parameters in the "depend" table if the prefix |
|||
-- parameter is present and non-blank. |
|||
local function preprocessArgs(prefixTable, step) |
|||
if type(prefixTable) ~= 'table' then |
|||
error("Non-table value detected for the prefix table", 2) |
|||
end |
|||
if type(step) ~= 'number' then |
|||
error("Invalid step value detected", 2) |
|||
end |
end |
||
-- Get arguments without a number suffix, and check for bad input. |
|||
for splitBr in mw.text.gsplit(arg1, "%s-<br%f[%s/>].-/?>%s*") do |
|||
for i,v in ipairs(prefixTable) do |
|||
if type(v) ~= 'table' or type(v.prefix) ~= "string" or |
|||
explodeScope(splitSymbol, explodeScope2) |
|||
(v.depend and type(v.depend) ~= 'table') then |
|||
error('Invalid input detected to preprocessArgs prefix table', 2) |
|||
end |
end |
||
preprocessSingleArg(v.prefix) |
|||
end |
|||
-- Only parse the depend parameter if the prefix parameter is present |
|||
-- and not blank. |
|||
local cats = {} |
|||
if args[v.prefix] and v.depend then |
|||
local result = {} |
|||
for j, dependValue in ipairs(v.depend) do |
|||
if type(dependValue) ~= 'string' then |
|||
for _, scopeName in ipairs(scopeOrder) do |
|||
error('Invalid "depend" parameter value detected in preprocessArgs') |
|||
if scopes[scopeName] then |
|||
end |
|||
if type then |
|||
preprocessSingleArg(dependValue) |
|||
table.insert(cats, "[[Category:" .. type .. "/" .. scopeCategoryMap[scopeName] .. "]]") |
|||
end |
|||
local scopeString = i18n:msg(scopeLangMap[scopeName] or scopeName) |
|||
if scopeName == "personal" and rec then |
|||
scopeString = scopeString .. " (" .. i18n:msg("recommended") .. ")" |
|||
end |
end |
||
table.insert(result, scopeString) |
|||
end |
end |
||
end |
end |
||
-- Get arguments with number suffixes. |
|||
if (#result == 0 or hasUnknownScope) and type then |
|||
local a = 1 -- Counter variable. |
|||
table.insert(cats, "[[Category:" .. type .. "/Unknown use]]") |
|||
local moreArgumentsExist = true |
|||
end |
|||
while moreArgumentsExist == true do |
|||
moreArgumentsExist = false |
|||
if usingLegacyAlias then |
|||
for i = a, a + step - 1 do |
|||
table.insert(cats, "[[Category:Pages using deprecated Template:Scope values]]") |
|||
for j,v in ipairs(prefixTable) do |
|||
end |
|||
local prefixArgName = v.prefix .. tostring(i) |
|||
if origArgs[prefixArgName] then |
|||
-- Do another loop if any arguments are found, even blank ones. |
|||
local ul = mw.html.create("ul") |
|||
moreArgumentsExist = true |
|||
for _, v in ipairs(result) do |
|||
preprocessSingleArg(prefixArgName) |
|||
ul:tag("li"):wikitext(v) |
|||
end |
|||
-- Process the depend table if the prefix argument is present |
|||
-- and not blank, or we are processing "prefix1" and "prefix" is |
|||
-- present and not blank, and if the depend table is present. |
|||
if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then |
|||
for j,dependValue in ipairs(v.depend) do |
|||
local dependArgName = dependValue .. tostring(i) |
|||
preprocessSingleArg(dependArgName) |
|||
end |
|||
end |
|||
end |
|||
end |
end |
||
a = a + step |
|||
return tostring(ul) .. table.concat(cats) |
|||
end |
end |
||
return (result[1] or i18n:msg("unknown")) .. table.concat(cats) |
|||
end |
end |
||
-- Parse the data parameters in the same order that the old {{infobox}} did, so |
|||
-------------------------------------------------------------------------------- |
|||
-- that references etc. will display in the expected places. Parameters that |
|||
-- Template wrapper for [[Template:Infobox]]. |
|||
-- depend on another parameter are only processed if that parameter is present, |
|||
-- |
|||
-- to avoid phantom references appearing in article reference lists. |
|||
-- @usage {{#invoke:infobox|main}} |
|||
local function parseDataParameters() |
|||
-- |
|||
-- @function p.main |
|||
-- @param {Frame} frame |
|||
-- Frame invocation object. |
|||
-- @returns {string|nil} |
|||
-- Package function output. |
|||
-------------------------------------------------------------------------------- |
|||
p.main = entrypoint(p) |
|||
preprocessSingleArg('autoheaders') |
|||
-------------------------------------------------------------------------------- |
|||
preprocessSingleArg('child') |
|||
-- Returns topmost parent frame. |
|||
preprocessSingleArg('bodyclass') |
|||
-- |
|||
preprocessSingleArg('subbox') |
|||
-- @param {Frame} frame |
|||
preprocessSingleArg('bodystyle') |
|||
-- Frame invocation object. |
|||
preprocessSingleArg('title') |
|||
-- @returns {table} |
|||
preprocessSingleArg('titleclass') |
|||
-- Highest parent frame. |
|||
preprocessSingleArg('titlestyle') |
|||
-------------------------------------------------------------------------------- |
|||
preprocessSingleArg('above') |
|||
function p.getParent(frame) |
|||
preprocessSingleArg('aboveclass') |
|||
local cf = frame |
|||
preprocessSingleArg('abovestyle') |
|||
local pf = frame:getParent() |
|||
preprocessArgs({ |
|||
{prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} |
|||
}, 10) |
|||
preprocessSingleArg('subheaderstyle') |
|||
preprocessSingleArg('subheaderclass') |
|||
preprocessArgs({ |
|||
{prefix = 'image', depend = {'caption', 'imagerowclass'}} |
|||
}, 10) |
|||
preprocessSingleArg('captionstyle') |
|||
preprocessSingleArg('imagestyle') |
|||
preprocessSingleArg('imageclass') |
|||
preprocessArgs({ |
|||
{prefix = 'header'}, |
|||
{prefix = 'data', depend = {'label'}}, |
|||
{prefix = 'rowclass'}, |
|||
{prefix = 'rowstyle'}, |
|||
{prefix = 'rowcellstyle'}, |
|||
{prefix = 'class'} |
|||
}, 50) |
|||
preprocessSingleArg('headerclass') |
|||
preprocessSingleArg('headerstyle') |
|||
preprocessSingleArg('labelstyle') |
|||
preprocessSingleArg('datastyle') |
|||
preprocessSingleArg('below') |
|||
preprocessSingleArg('belowclass') |
|||
preprocessSingleArg('belowstyle') |
|||
preprocessSingleArg('name') |
|||
-- different behaviour for italics if blank or absent |
|||
args['italic title'] = origArgs['italic title'] |
|||
preprocessSingleArg('decat') |
|||
preprocessSingleArg('templatestyles') |
|||
preprocessSingleArg('child templatestyles') |
|||
preprocessSingleArg('grandchild templatestyles') |
|||
end |
|||
-- If called via #invoke, use the args passed into the invoking template. |
|||
if pf then |
|||
-- Otherwise, for testing purposes, assume args are being passed directly in. |
|||
return p.getParent(pf) |
|||
function p.infobox(frame) |
|||
else |
|||
if frame == mw.getCurrentFrame() then |
|||
return cf |
|||
origArgs = frame:getParent().args |
|||
end |
|||
else |
|||
origArgs = frame |
|||
end |
|||
parseDataParameters() |
|||
return _infobox() |
|||
end |
end |
||
-- For calling via #invoke within a template |
|||
function p.infoboxTemplate(frame) |
|||
origArgs = {} |
|||
for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end |
|||
parseDataParameters() |
|||
return _infobox() |
|||
end |
|||
return p |
return p |
Latest revision as of 13:25, 24 January 2023
This Lua module is used on approximately 4,130,000 pages, or roughly 387793% of all pages. To avoid major disruption and server load, any changes should be tested in the module's /sandbox or /testcases subpages, or in your own module sandbox. The tested changes can be added to this page in a single edit. Consider discussing changes on the talk page before implementing them. |
This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
This module depends on the following other modules: |
This module uses TemplateStyles: |
Module:Infobox is a module that implements the {{Infobox}} template. Please see the template page for usage instructions.
Tracking categories
- Category:Pages which use infobox templates with ignored data cells (0)
- Category:Articles which use infobox templates with no data rows (0)
- Category:Pages which use embedded infobox templates with the title parameter (0)
local p = {}
local args = {}
local origArgs = {}
local root
local empty_row_categories = {}
local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]'
local has_rows = false
local lists = {
plainlist_t = {
patterns = {
'^plainlist$',
'%splainlist$',
'^plainlist%s',
'%splainlist%s'
},
found = false,
styles = 'Plainlist/styles.css'
},
hlist_t = {
patterns = {
'^hlist$',
'%shlist$',
'^hlist%s',
'%shlist%s'
},
found = false,
styles = 'Hlist/styles.css'
}
}
local function has_list_class(args_to_check)
for _, list in pairs(lists) do
if not list.found then
for _, arg in pairs(args_to_check) do
for _, pattern in ipairs(list.patterns) do
if mw.ustring.find(arg or '', pattern) then
list.found = true
break
end
end
if list.found then break end
end
end
end
end
local function fixChildBoxes(sval, tt)
local function notempty( s ) return s and s:match( '%S' ) end
if notempty(sval) then
local marker = '<span class=special_infobox_marker>'
local s = sval
-- start moving templatestyles and categories inside of table rows
local slast = ''
while slast ~= s do
slast = s
s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1')
s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1')
end
-- end moving templatestyles and categories inside of table rows
s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1')
s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker)
if s:match(marker) then
s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '')
s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1')
s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1')
s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1')
s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1')
s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1')
s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1')
s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1')
s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1')
end
if s:match(marker) then
local subcells = mw.text.split(s, marker)
s = ''
for k = 1, #subcells do
if k == 1 then
s = s .. subcells[k] .. '</' .. tt .. '></tr>'
elseif k == #subcells then
local rowstyle = ' style="display:none"'
if notempty(subcells[k]) then rowstyle = '' end
s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' ..
subcells[k]
elseif notempty(subcells[k]) then
if (k % 2) == 0 then
s = s .. subcells[k]
else
s = s .. '<tr><' .. tt .. ' colspan=2>\n' ..
subcells[k] .. '</' .. tt .. '></tr>'
end
end
end
end
-- the next two lines add a newline at the end of lists for the PHP parser
-- [[Special:Diff/849054481]]
-- remove when [[:phab:T191516]] is fixed or OBE
s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n')
s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n')
s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1')
s = mw.ustring.gsub(s, '^(%{%|)', '\n%1')
return s
else
return sval
end
end
-- Cleans empty tables
local function cleanInfobox()
root = tostring(root)
if has_rows == false then
root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '')
end
end
-- Returns the union of the values of two tables, as a sequence.
local function union(t1, t2)
local vals = {}
for k, v in pairs(t1) do
vals[v] = true
end
for k, v in pairs(t2) do
vals[v] = true
end
local ret = {}
for k, v in pairs(vals) do
table.insert(ret, k)
end
return ret
end
-- Returns a table containing the numbers of the arguments that exist
-- for the specified prefix. For example, if the prefix was 'data', and
-- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}.
local function getArgNums(prefix)
local nums = {}
for k, v in pairs(args) do
local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$')
if num then table.insert(nums, tonumber(num)) end
end
table.sort(nums)
return nums
end
-- Adds a row to the infobox, with either a header cell
-- or a label/data cell combination.
local function addRow(rowArgs)
if rowArgs.header and rowArgs.header ~= '_BLANK_' then
has_rows = true
has_list_class({ rowArgs.rowclass, rowArgs.class, args.headerclass })
root
:tag('tr')
:addClass(rowArgs.rowclass)
:cssText(rowArgs.rowstyle)
:tag('th')
:attr('colspan', '2')
:addClass('infobox-header')
:addClass(rowArgs.class)
:addClass(args.headerclass)
-- @deprecated next; target .infobox-<name> .infobox-header
:cssText(args.headerstyle)
:cssText(rowArgs.rowcellstyle)
:wikitext(fixChildBoxes(rowArgs.header, 'th'))
if rowArgs.data then
root:wikitext(
'[[Category:Pages using infobox templates with ignored data cells]]'
)
end
elseif rowArgs.data and rowArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then
has_rows = true
has_list_class({ rowArgs.rowclass, rowArgs.class })
local row = root:tag('tr')
row:addClass(rowArgs.rowclass)
row:cssText(rowArgs.rowstyle)
if rowArgs.label then
row
:tag('th')
:attr('scope', 'row')
:addClass('infobox-label')
-- @deprecated next; target .infobox-<name> .infobox-label
:cssText(args.labelstyle)
:cssText(rowArgs.rowcellstyle)
:wikitext(rowArgs.label)
:done()
end
local dataCell = row:tag('td')
dataCell
:attr('colspan', not rowArgs.label and '2' or nil)
:addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data')
:addClass(rowArgs.class)
-- @deprecated next; target .infobox-<name> .infobox(-full)-data
:cssText(rowArgs.datastyle)
:cssText(rowArgs.rowcellstyle)
:wikitext(fixChildBoxes(rowArgs.data, 'td'))
else
table.insert(empty_row_categories, rowArgs.data or '')
end
end
local function renderTitle()
if not args.title then return end
has_rows = true
has_list_class({args.titleclass})
root
:tag('caption')
:addClass('infobox-title')
:addClass(args.titleclass)
-- @deprecated next; target .infobox-<name> .infobox-title
:cssText(args.titlestyle)
:wikitext(args.title)
end
local function renderAboveRow()
if not args.above then return end
has_rows = true
has_list_class({ args.aboveclass })
root
:tag('tr')
:tag('th')
:attr('colspan', '2')
:addClass('infobox-above')
:addClass(args.aboveclass)
-- @deprecated next; target .infobox-<name> .infobox-above
:cssText(args.abovestyle)
:wikitext(fixChildBoxes(args.above,'th'))
end
local function renderBelowRow()
if not args.below then return end
has_rows = true
has_list_class({ args.belowclass })
root
:tag('tr')
:tag('td')
:attr('colspan', '2')
:addClass('infobox-below')
:addClass(args.belowclass)
-- @deprecated next; target .infobox-<name> .infobox-below
:cssText(args.belowstyle)
:wikitext(fixChildBoxes(args.below,'td'))
end
local function addSubheaderRow(subheaderArgs)
if subheaderArgs.data and
subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then
has_rows = true
has_list_class({ subheaderArgs.rowclass, subheaderArgs.class })
local row = root:tag('tr')
row:addClass(subheaderArgs.rowclass)
local dataCell = row:tag('td')
dataCell
:attr('colspan', '2')
:addClass('infobox-subheader')
:addClass(subheaderArgs.class)
:cssText(subheaderArgs.datastyle)
:cssText(subheaderArgs.rowcellstyle)
:wikitext(fixChildBoxes(subheaderArgs.data, 'td'))
else
table.insert(empty_row_categories, subheaderArgs.data or '')
end
end
local function renderSubheaders()
if args.subheader then
args.subheader1 = args.subheader
end
if args.subheaderrowclass then
args.subheaderrowclass1 = args.subheaderrowclass
end
local subheadernums = getArgNums('subheader')
for k, num in ipairs(subheadernums) do
addSubheaderRow({
data = args['subheader' .. tostring(num)],
-- @deprecated next; target .infobox-<name> .infobox-subheader
datastyle = args.subheaderstyle,
rowcellstyle = args['subheaderstyle' .. tostring(num)],
class = args.subheaderclass,
rowclass = args['subheaderrowclass' .. tostring(num)]
})
end
end
local function addImageRow(imageArgs)
if imageArgs.data and
imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then
has_rows = true
has_list_class({ imageArgs.rowclass, imageArgs.class })
local row = root:tag('tr')
row:addClass(imageArgs.rowclass)
local dataCell = row:tag('td')
dataCell
:attr('colspan', '2')
:addClass('infobox-image')
:addClass(imageArgs.class)
:cssText(imageArgs.datastyle)
:wikitext(fixChildBoxes(imageArgs.data, 'td'))
else
table.insert(empty_row_categories, imageArgs.data or '')
end
end
local function renderImages()
if args.image then
args.image1 = args.image
end
if args.caption then
args.caption1 = args.caption
end
local imagenums = getArgNums('image')
for k, num in ipairs(imagenums) do
local caption = args['caption' .. tostring(num)]
local data = mw.html.create():wikitext(args['image' .. tostring(num)])
if caption then
data
:tag('div')
:addClass('infobox-caption')
-- @deprecated next; target .infobox-<name> .infobox-caption
:cssText(args.captionstyle)
:wikitext(caption)
end
addImageRow({
data = tostring(data),
-- @deprecated next; target .infobox-<name> .infobox-image
datastyle = args.imagestyle,
class = args.imageclass,
rowclass = args['imagerowclass' .. tostring(num)]
})
end
end
-- When autoheaders are turned on, preprocesses the rows
local function preprocessRows()
if not args.autoheaders then return end
local rownums = union(getArgNums('header'), getArgNums('data'))
table.sort(rownums)
local lastheader
for k, num in ipairs(rownums) do
if args['header' .. tostring(num)] then
if lastheader then
args['header' .. tostring(lastheader)] = nil
end
lastheader = num
elseif args['data' .. tostring(num)] and
args['data' .. tostring(num)]:gsub(
category_in_empty_row_pattern, ''
):match('^%S') then
local data = args['data' .. tostring(num)]
if data:gsub(category_in_empty_row_pattern, ''):match('%S') then
lastheader = nil
end
end
end
if lastheader then
args['header' .. tostring(lastheader)] = nil
end
end
-- Gets the union of the header and data argument numbers,
-- and renders them all in order
local function renderRows()
local rownums = union(getArgNums('header'), getArgNums('data'))
table.sort(rownums)
for k, num in ipairs(rownums) do
addRow({
header = args['header' .. tostring(num)],
label = args['label' .. tostring(num)],
data = args['data' .. tostring(num)],
datastyle = args.datastyle,
class = args['class' .. tostring(num)],
rowclass = args['rowclass' .. tostring(num)],
-- @deprecated next; target .infobox-<name> rowclass
rowstyle = args['rowstyle' .. tostring(num)],
rowcellstyle = args['rowcellstyle' .. tostring(num)]
})
end
end
local function renderNavBar()
if not args.name then return end
has_rows = true
root
:tag('tr')
:tag('td')
:attr('colspan', '2')
:addClass('infobox-navbar')
:wikitext(require('Module:Navbar')._navbar{
args.name,
mini = 1,
})
end
local function renderItalicTitle()
local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title'])
if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then
root:wikitext(require('Module:Italic title')._main({}))
end
end
-- Categories in otherwise empty rows are collected in empty_row_categories.
-- This function adds them to the module output. It is not affected by
-- args.decat because this module should not prevent module-external categories
-- from rendering.
local function renderEmptyRowCategories()
for _, s in ipairs(empty_row_categories) do
root:wikitext(s)
end
end
-- Render tracking categories. args.decat == turns off tracking categories.
local function renderTrackingCategories()
if args.decat == 'yes' then return end
if args.child == 'yes' then
if args.title then
root:wikitext(
'[[Category:Pages using embedded infobox templates with the title parameter]]'
)
end
elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then
root:wikitext('[[Category:Articles using infobox templates with no data rows]]')
end
end
--[=[
Loads the templatestyles for the infobox.
TODO: FINISH loading base templatestyles here rather than in
MediaWiki:Common.css. There are 4-5000 pages with 'raw' infobox tables.
See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :).
When we do this we should clean up the inline CSS below too.
Will have to do some bizarre conversion category like with sidebar.
]=]
local function loadTemplateStyles()
local frame = mw.getCurrentFrame()
local hlist_templatestyles = ''
if lists.hlist_t.found then
hlist_templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = lists.hlist_t.styles }
}
end
local plainlist_templatestyles = ''
if lists.plainlist_t.found then
plainlist_templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = lists.plainlist_t.styles }
}
end
-- See function description
local base_templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' }
}
local templatestyles = ''
if args['templatestyles'] then
templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = args['templatestyles'] }
}
end
local child_templatestyles = ''
if args['child templatestyles'] then
child_templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = args['child templatestyles'] }
}
end
local grandchild_templatestyles = ''
if args['grandchild templatestyles'] then
grandchild_templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = args['grandchild templatestyles'] }
}
end
return table.concat({
-- hlist -> plainlist -> base is best-effort to preserve old Common.css ordering.
-- this ordering is not a guarantee because the rows of interest invoking
-- each class may not be on a specific page
hlist_templatestyles,
plainlist_templatestyles,
base_templatestyles,
templatestyles,
child_templatestyles,
grandchild_templatestyles
})
end
-- common functions between the child and non child cases
local function structure_infobox_common()
renderSubheaders()
renderImages()
preprocessRows()
renderRows()
renderBelowRow()
renderNavBar()
renderItalicTitle()
renderEmptyRowCategories()
renderTrackingCategories()
cleanInfobox()
end
-- Specify the overall layout of the infobox, with special settings if the
-- infobox is used as a 'child' inside another infobox.
local function _infobox()
if args.child ~= 'yes' then
root = mw.html.create('table')
root
:addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox')
:addClass(args.bodyclass)
-- @deprecated next; target .infobox-<name>
:cssText(args.bodystyle)
has_list_class({ args.bodyclass })
renderTitle()
renderAboveRow()
else
root = mw.html.create()
root
:wikitext(args.title)
end
structure_infobox_common()
return loadTemplateStyles() .. root
end
-- If the argument exists and isn't blank, add it to the argument table.
-- Blank arguments are treated as nil to match the behaviour of ParserFunctions.
local function preprocessSingleArg(argName)
if origArgs[argName] and origArgs[argName] ~= '' then
args[argName] = origArgs[argName]
end
end
-- Assign the parameters with the given prefixes to the args table, in order, in
-- batches of the step size specified. This is to prevent references etc. from
-- appearing in the wrong order. The prefixTable should be an array containing
-- tables, each of which has two possible fields, a "prefix" string and a
-- "depend" table. The function always parses parameters containing the "prefix"
-- string, but only parses parameters in the "depend" table if the prefix
-- parameter is present and non-blank.
local function preprocessArgs(prefixTable, step)
if type(prefixTable) ~= 'table' then
error("Non-table value detected for the prefix table", 2)
end
if type(step) ~= 'number' then
error("Invalid step value detected", 2)
end
-- Get arguments without a number suffix, and check for bad input.
for i,v in ipairs(prefixTable) do
if type(v) ~= 'table' or type(v.prefix) ~= "string" or
(v.depend and type(v.depend) ~= 'table') then
error('Invalid input detected to preprocessArgs prefix table', 2)
end
preprocessSingleArg(v.prefix)
-- Only parse the depend parameter if the prefix parameter is present
-- and not blank.
if args[v.prefix] and v.depend then
for j, dependValue in ipairs(v.depend) do
if type(dependValue) ~= 'string' then
error('Invalid "depend" parameter value detected in preprocessArgs')
end
preprocessSingleArg(dependValue)
end
end
end
-- Get arguments with number suffixes.
local a = 1 -- Counter variable.
local moreArgumentsExist = true
while moreArgumentsExist == true do
moreArgumentsExist = false
for i = a, a + step - 1 do
for j,v in ipairs(prefixTable) do
local prefixArgName = v.prefix .. tostring(i)
if origArgs[prefixArgName] then
-- Do another loop if any arguments are found, even blank ones.
moreArgumentsExist = true
preprocessSingleArg(prefixArgName)
end
-- Process the depend table if the prefix argument is present
-- and not blank, or we are processing "prefix1" and "prefix" is
-- present and not blank, and if the depend table is present.
if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then
for j,dependValue in ipairs(v.depend) do
local dependArgName = dependValue .. tostring(i)
preprocessSingleArg(dependArgName)
end
end
end
end
a = a + step
end
end
-- Parse the data parameters in the same order that the old {{infobox}} did, so
-- that references etc. will display in the expected places. Parameters that
-- depend on another parameter are only processed if that parameter is present,
-- to avoid phantom references appearing in article reference lists.
local function parseDataParameters()
preprocessSingleArg('autoheaders')
preprocessSingleArg('child')
preprocessSingleArg('bodyclass')
preprocessSingleArg('subbox')
preprocessSingleArg('bodystyle')
preprocessSingleArg('title')
preprocessSingleArg('titleclass')
preprocessSingleArg('titlestyle')
preprocessSingleArg('above')
preprocessSingleArg('aboveclass')
preprocessSingleArg('abovestyle')
preprocessArgs({
{prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}}
}, 10)
preprocessSingleArg('subheaderstyle')
preprocessSingleArg('subheaderclass')
preprocessArgs({
{prefix = 'image', depend = {'caption', 'imagerowclass'}}
}, 10)
preprocessSingleArg('captionstyle')
preprocessSingleArg('imagestyle')
preprocessSingleArg('imageclass')
preprocessArgs({
{prefix = 'header'},
{prefix = 'data', depend = {'label'}},
{prefix = 'rowclass'},
{prefix = 'rowstyle'},
{prefix = 'rowcellstyle'},
{prefix = 'class'}
}, 50)
preprocessSingleArg('headerclass')
preprocessSingleArg('headerstyle')
preprocessSingleArg('labelstyle')
preprocessSingleArg('datastyle')
preprocessSingleArg('below')
preprocessSingleArg('belowclass')
preprocessSingleArg('belowstyle')
preprocessSingleArg('name')
-- different behaviour for italics if blank or absent
args['italic title'] = origArgs['italic title']
preprocessSingleArg('decat')
preprocessSingleArg('templatestyles')
preprocessSingleArg('child templatestyles')
preprocessSingleArg('grandchild templatestyles')
end
-- If called via #invoke, use the args passed into the invoking template.
-- Otherwise, for testing purposes, assume args are being passed directly in.
function p.infobox(frame)
if frame == mw.getCurrentFrame() then
origArgs = frame:getParent().args
else
origArgs = frame
end
parseDataParameters()
return _infobox()
end
-- For calling via #invoke within a template
function p.infoboxTemplate(frame)
origArgs = {}
for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end
parseDataParameters()
return _infobox()
end
return p