Module:Infobox: Difference between revisions
No edit summary Tag: Reverted |
No edit summary Tag: Reverted |
||
| Line 4: | Line 4: | ||
local origArgs = {} | local origArgs = {} | ||
local root | local root | ||
local empty_row_categories = {} | local empty_row_categories = {} | ||
local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' | local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' | ||
local has_rows = false | local has_rows = false | ||
local | -- ============================================================ | ||
-- SAFE yesno loader (handles all Module:Yesno variants) | |||
-- ============================================================ | |||
local _yesno_module = require("Module:Yesno") | |||
local yesno | |||
if type(_yesno_module) == "function" then | |||
yesno = _yesno_module | |||
elseif type(_yesno_module._main) == "function" then | |||
yesno = _yesno_module._main | |||
elseif type(_yesno_module.yesno) == "function" then | |||
yesno = _yesno_module.yesno | |||
else | |||
yesno = function(val) | |||
if not val then return false end | |||
val = mw.ustring.lower(tostring(val)) | |||
return val == 'yes' or val == 'true' or val == '1' or val == 'on' | |||
end | |||
end | |||
-- ============================================================ | |||
local lists = { | 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) | 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 or {}) do | |||
for _, pattern in ipairs(list.patterns) do | |||
if arg and mw.ustring.find(arg, pattern) then | |||
list.found = true | |||
break | |||
end | |||
end | |||
if list.found then break end | |||
end | |||
end | |||
end | |||
end | end | ||
local function fixChildBoxes(sval, tt) | 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 | |||
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 | |||
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 | |||
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 | end | ||
local function cleanInfobox() | local function cleanInfobox() | ||
root = tostring(root) | |||
if has_rows == false then | |||
root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') | |||
end | |||
end | end | ||
local function union(t1, t2) | local function union(t1, t2) | ||
local vals = {} | |||
local ret = {} | |||
for _, v in pairs(t1 or {}) do vals[v] = true end | |||
for _, v in pairs(t2 or {}) do vals[v] = true end | |||
for k in pairs(vals) do table.insert(ret, k) end | |||
return ret | |||
end | |||
local function getArgNums(prefix) | |||
local nums = {} | |||
for k 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 | |||
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) | |||
:cssText(args.headerstyle) | |||
:cssText(rowArgs.rowcellstyle) | |||
:wikitext(fixChildBoxes(rowArgs.header, 'th')) | |||
if rowArgs.data and not yesno(args.decat) 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') | |||
: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) | |||
: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) | |||
:cssText(args.titlestyle) | |||
:wikitext(args.title) | |||
end | end | ||
local function renderAboveRow() | |||
local function | 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) | |||
: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) | |||
:cssText(args.belowstyle) | |||
:wikitext(fixChildBoxes(args.below, 'td')) | |||
end | end | ||
local function addSubheaderRow(subheaderArgs) | |||
local function | 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) | |||
row:tag('td') | |||
: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 _, num in ipairs(subheadernums) do | |||
addSubheaderRow({ | |||
data = args['subheader' .. num], | |||
datastyle = args.subheaderstyle, | |||
rowcellstyle = args['subheaderstyle' .. num], | |||
class = args.subheaderclass, | |||
rowclass = args['subheaderrowclass' .. 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) | |||
row:tag('td') | |||
: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 | end | ||
local function | 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 _, num in ipairs(imagenums) do | |||
local caption = args['caption' .. num] | |||
local data = mw.html.create():wikitext(args['image' .. num]) | |||
if caption then | |||
data:tag('div') | |||
:addClass('infobox-caption') | |||
:cssText(args.captionstyle) | |||
:wikitext(caption) | |||
end | |||
addImageRow({ | |||
data = tostring(data), | |||
datastyle = args.imagestyle, | |||
class = args.imageclass, | |||
rowclass = args['imagerowclass' .. num] | |||
}) | |||
end | |||
end | |||
local function preprocessRows() | |||
if not args.autoheaders then return end | |||
local rownums = union(getArgNums('header'), getArgNums('data')) | |||
table.sort(rownums) | |||
local lastheader | |||
for _, num in ipairs(rownums) do | |||
if args['header' .. num] then | |||
if lastheader then args['header' .. lastheader] = nil end | |||
lastheader = num | |||
elseif args['data' .. num] and | |||
args['data' .. num]:gsub(category_in_empty_row_pattern, ''):match('^%S') then | |||
lastheader = nil | |||
end | |||
end | |||
if lastheader then args['header' .. lastheader] = nil end | |||
end | end | ||
local function renderRows() | local function renderRows() | ||
local rownums = union(getArgNums('header'), getArgNums('data')) | |||
table.sort(rownums) | |||
for _, num in ipairs(rownums) do | |||
addRow({ | |||
header = args['header' .. num], | |||
label = args['label' .. num], | |||
data = args['data' .. num], | |||
datastyle = args.datastyle, | |||
class = args['class' .. num], | |||
rowclass = args['rowclass' .. num], | |||
rowstyle = args['rowstyle' .. num], | |||
rowcellstyle = args['rowcellstyle' .. 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 | |||
local function renderEmptyRowCategories() | |||
for _, s in ipairs(empty_row_categories) do | |||
root:wikitext(s) | |||
end | |||
end | |||
-- ============================================================ | |||
-- renderTrackingCategories — uses the safe yesno from top | |||
-- ============================================================ | |||
local function renderTrackingCategories() | |||
if yesno(args.decat) 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 | |||
local function loadTemplateStyles() | |||
local frame = mw.getCurrentFrame() | |||
local hlist_ts = '' | |||
if lists.hlist_t.found then | |||
hlist_ts = frame:extensionTag{ name = 'templatestyles', args = { src = lists.hlist_t.styles } } | |||
end | |||
local plainlist_ts = '' | |||
if lists.plainlist_t.found then | |||
plainlist_ts = frame:extensionTag{ name = 'templatestyles', args = { src = lists.plainlist_t.styles } } | |||
end | |||
local base_ts = frame:extensionTag{ | |||
name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' } | |||
} | |||
local ts = '' | |||
if args['templatestyles'] then | |||
ts = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } | |||
end | |||
local child_ts = '' | |||
if args['child templatestyles'] then | |||
child_ts = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } | |||
end | |||
local grandchild_ts = '' | |||
if args['grandchild templatestyles'] then | |||
grandchild_ts = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } | |||
end | |||
return table.concat({ hlist_ts, plainlist_ts, base_ts, ts, child_ts, grandchild_ts }) | |||
end | end | ||
local function | local function structure_infobox_common() | ||
renderSubheaders() | |||
renderImages() | |||
preprocessRows() | |||
renderRows() | |||
renderBelowRow() | |||
renderNavBar() | |||
renderItalicTitle() | |||
renderEmptyRowCategories() | |||
renderTrackingCategories() | |||
cleanInfobox() | |||
end | end | ||
local function _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) | |||
: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() .. tostring(root) | |||
end | end | ||
local function preprocessSingleArg( | local function preprocessSingleArg(argName) | ||
if origArgs[argName] and origArgs[argName] ~= '' then | |||
args[argName] = origArgs[argName] | |||
end | |||
end | |||
local function preprocessArgs(prefixTable, step) | |||
if type(prefixTable) ~= 'table' then error("Non-table prefix table", 2) end | |||
if type(step) ~= 'number' then error("Invalid step", 2) end | |||
for _, v in ipairs(prefixTable) do | |||
preprocessSingleArg(v.prefix) | |||
if args[v.prefix] and v.depend then | |||
for _, dep in ipairs(v.depend) do | |||
preprocessSingleArg(dep) | |||
end | |||
end | |||
end | |||
local a = 1 | |||
local moreExist = true | |||
while moreExist do | |||
moreExist = false | |||
for i = a, a + step - 1 do | |||
for _, v in ipairs(prefixTable) do | |||
local pname = v.prefix .. i | |||
if origArgs[pname] then | |||
moreExist = true | |||
preprocessSingleArg(pname) | |||
end | |||
if v.depend and (args[pname] or (i == 1 and args[v.prefix])) then | |||
for _, dep in ipairs(v.depend) do | |||
preprocessSingleArg(dep .. i) | |||
end | |||
end | |||
end | |||
end | |||
a = a + step | |||
end | |||
end | end | ||
local function parseDataParameters() | 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') | |||
args['italic title'] = origArgs['italic title'] | |||
preprocessSingleArg('decat') | |||
preprocessSingleArg('templatestyles') | |||
preprocessSingleArg('child templatestyles') | |||
preprocessSingleArg('grandchild templatestyles') | |||
end | end | ||
function p.infobox(frame) | function p.infobox(frame) | ||
if frame == mw.getCurrentFrame() then | |||
origArgs = frame:getParent().args | |||
else | |||
origArgs = frame | |||
end | |||
parseDataParameters() | |||
return _infobox() | |||
end | |||
function p.infoboxTemplate(frame) | |||
origArgs = {} | |||
for k, v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end | |||
parseDataParameters() | |||
return _infobox() | |||
end | end | ||
return p | return p | ||