Difference between revisions of "Module:Infobox"

From Jeyropedia
(Created page with "-- Inputs: -- image - Can either be a bare filename (with or without the File:/Image: prefix) or a fully formatted image link -- page - page to display for multipage ima...")
m (1 revision imported)
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
-- Inputs:
+
--
--   image - Can either be a bare filename (with or without the File:/Image: prefix) or a fully formatted image link
+
-- This module implements {{Infobox}}
--    page - page to display for multipage images (DjVu)
+
--
--    size - size to display the image
 
--    maxsize - maximum size for image
 
--    sizedefault - default size to display the image if size param is blank
 
--    alt - alt text for image
 
--    title - title text for image
 
--    border - set to yes if border
 
--    center - set to yes, if the image has to be centered
 
--    upright - upright image param
 
--    suppressplaceholder - if yes then checks to see if image is a placeholder and suppresses it
 
--    link - page to visit when clicking on image
 
-- Outputs:
 
--    Formatted image.
 
-- More details available at the "Module:InfoboxImage/doc" page
 
  
local i = {};
+
local p = {}
  
local placeholder_image = {
+
local navbar = require('Module:Navbar')._navbar
    "Blue - Replace this image female.svg",
 
    "Blue - Replace this image male.svg",
 
    "Female no free image yet.png",
 
    "Flag of None (square).svg",
 
    "Flag of None.svg",
 
    "Flag of.svg",
 
    "Green - Replace this image female.svg",
 
    "Green - Replace this image male.svg",
 
    "Image is needed female.svg",
 
    "Image is needed male.svg",
 
    "Location map of None.svg",
 
    "Male no free image yet.png",
 
    "Missing flag.png",
 
    "No flag.svg",
 
    "No free portrait.svg",
 
    "No portrait (female).svg",
 
    "No portrait (male).svg",
 
    "Red - Replace this image female.svg",
 
    "Red - Replace this image male.svg",
 
    "Replace this image female (blue).svg",
 
    "Replace this image female.svg",
 
    "Replace this image male (blue).svg",
 
    "Replace this image male.svg",
 
    "Silver - Replace this image female.svg",
 
    "Silver - Replace this image male.svg",
 
    "Replace this image.svg",
 
"Cricket no pic.png",
 
"CarersLogo.gif",
 
"Diagram Needed.svg",
 
"Example.jpg",
 
"Image placeholder.png",
 
"No male portrait.svg",
 
"Nocover-upload.png",
 
"NoDVDcover copy.png",
 
"Noribbon.svg",
 
"No portrait-BFD-test.svg",
 
"Placeholder barnstar ribbon.png",
 
"Project Trains no image.png",
 
"Image-request.png",
 
"Sin bandera.svg",
 
"Sin escudo.svg",
 
"Replace this image - temple.png",
 
"Replace this image butterfly.png",
 
"Replace this image.svg",
 
"Replace this image1.svg",
 
"Resolution angle.png",
 
"Image-No portrait-text-BFD-test.svg",
 
"Insert image here.svg",
 
"No image available.png",
 
"NO IMAGE YET square.png",
 
"NO IMAGE YET.png",
 
"No Photo Available.svg",
 
"No Screenshot.svg",
 
"No-image-available.jpg",
 
"Null.png",
 
"PictureNeeded.gif",
 
"Place holder.jpg",
 
"Unbenannt.JPG",
 
"UploadACopyrightFreeImage.svg",
 
"UploadAnImage.gif",
 
"UploadAnImage.svg",
 
"UploadAnImageShort.svg",
 
"CarersLogo.gif",
 
"Diagram Needed.svg",
 
"No male portrait.svg",
 
"NoDVDcover copy.png",
 
"Placeholder barnstar ribbon.png",
 
"Project Trains no image.png",
 
"Image-request.png",
 
"Noimage.gif",
 
}
 
  
function i.IsPlaceholder(image)
+
local args = {}
    -- change underscores to spaces
+
local origArgs = {}
    image = mw.ustring.gsub(image, "_", " ");
+
local root
    assert(image ~= nil, 'mw.ustring.gsub(image, "_", " ") must not return nil')
 
    -- if image starts with [[ then remove that and anything after |
 
    if mw.ustring.sub(image,1,2) == "[[" then
 
        image = mw.ustring.sub(image,3);
 
        image = mw.ustring.gsub(image, "([^|]*)|.*", "%1");
 
        assert(image ~= nil, 'mw.ustring.gsub(image, "([^|]*)|.*", "%1") must not return nil')
 
    end
 
    -- Trim spaces
 
    image = mw.ustring.gsub(image, '^[ ]*(.-)[ ]*$', '%1');
 
    assert(image ~= nil, "mw.ustring.gsub(image, '^[ ]*(.-)[ ]*$', '%1') must not return nil")
 
    -- remove prefix if exists
 
    local allNames = mw.site.namespaces[6].aliases
 
    allNames[#allNames + 1] = mw.site.namespaces[6].name
 
    allNames[#allNames + 1] = mw.site.namespaces[6].canonicalName
 
    for i, name in ipairs(allNames) do
 
        if mw.ustring.lower(mw.ustring.sub(image, 1, mw.ustring.len(name) + 1)) == mw.ustring.lower(name .. ":") then
 
            image = mw.ustring.sub(image, mw.ustring.len(name) + 2);
 
            break
 
        end
 
    end
 
    -- Trim spaces
 
    image = mw.ustring.gsub(image, '^[ ]*(.-)[ ]*$', '%1');
 
    -- capitalise first letter
 
    image = mw.ustring.upper(mw.ustring.sub(image,1,1)) .. mw.ustring.sub(image,2);
 
  
    for i,j in pairs(placeholder_image) do
+
local function notempty( s ) return s and s:match( '%S' ) end
        if image == j then
+
 
            return true
+
local function fixChildBoxes(sval, tt)
        end
+
if notempty(sval) then
    end
+
local marker = '<span class=special_infobox_marker>'
    return false
+
local s = sval
 +
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
 +
-- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=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
 +
 
 +
local function union(t1, t2)
 +
-- Returns the union of the values of two tables, as a sequence.
 +
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
 +
 
 +
local function getArgNums(prefix)
 +
-- 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 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
 +
 
 +
local function addRow(rowArgs)
 +
-- Adds a row to the infobox, with either a header cell
 +
-- or a label/data cell combination.
 +
if rowArgs.header and rowArgs.header ~= '_BLANK_' then
 +
root
 +
:tag('tr')
 +
:addClass(rowArgs.rowclass)
 +
:cssText(rowArgs.rowstyle)
 +
:attr('id', rowArgs.rowid)
 +
:tag('th')
 +
:attr('colspan', 2)
 +
:attr('id', rowArgs.headerid)
 +
:addClass(rowArgs.class)
 +
:addClass(args.headerclass)
 +
:css('text-align', 'center')
 +
:cssText(args.headerstyle)
 +
:cssText(rowArgs.rowcellstyle)
 +
:wikitext(fixChildBoxes(rowArgs.header, 'th'))
 +
if rowArgs.data then
 +
root:wikitext('[[Category:Pages which use infobox templates with ignored data cells]]')
 +
end
 +
elseif rowArgs.data then
 +
if not rowArgs.data:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('^%S') then
 +
rowArgs.rowstyle = 'display:none'
 +
end
 +
local row = root:tag('tr')
 +
row:addClass(rowArgs.rowclass)
 +
row:cssText(rowArgs.rowstyle)
 +
row:attr('id', rowArgs.rowid)
 +
if rowArgs.label then
 +
row
 +
:tag('th')
 +
:attr('scope', 'row')
 +
:attr('id', rowArgs.labelid)
 +
:cssText(args.labelstyle)
 +
:cssText(rowArgs.rowcellstyle)
 +
:wikitext(rowArgs.label)
 +
:done()
 +
end
 +
 
 +
local dataCell = row:tag('td')
 +
if not rowArgs.label then
 +
dataCell
 +
:attr('colspan', 2)
 +
:css('text-align', 'center')
 +
end
 +
dataCell
 +
:attr('id', rowArgs.dataid)
 +
:addClass(rowArgs.class)
 +
:cssText(rowArgs.datastyle)
 +
:cssText(rowArgs.rowcellstyle)
 +
:wikitext(fixChildBoxes(rowArgs.data, 'td'))
 +
end
 +
end
 +
 
 +
local function renderTitle()
 +
if not args.title then return end
 +
 
 +
root
 +
:tag('caption')
 +
:addClass(args.titleclass)
 +
:cssText(args.titlestyle)
 +
:wikitext(args.title)
 +
end
 +
 
 +
local function renderAboveRow()
 +
if not args.above then return end
 +
 
 +
root
 +
:tag('tr')
 +
:tag('th')
 +
:attr('colspan', 2)
 +
:addClass(args.aboveclass)
 +
:css('text-align', 'center')
 +
:css('font-size', '125%')
 +
:css('font-weight', 'bold')
 +
:cssText(args.abovestyle)
 +
:wikitext(fixChildBoxes(args.above,'th'))
 +
end
 +
 
 +
local function renderBelowRow()
 +
if not args.below then return end
 +
 
 +
root
 +
:tag('tr')
 +
:tag('td')
 +
:attr('colspan', '2')
 +
:addClass(args.belowclass)
 +
:css('text-align', 'center')
 +
:cssText(args.belowstyle)
 +
:wikitext(fixChildBoxes(args.below,'td'))
 +
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
 +
addRow({
 +
data = args['subheader' .. tostring(num)],
 +
datastyle = args.subheaderstyle,
 +
rowcellstyle = args['subheaderstyle' .. tostring(num)],
 +
class = args.subheaderclass,
 +
rowclass = args['subheaderrowclass' .. tostring(num)]
 +
})
 +
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')
 +
:cssText(args.captionstyle)
 +
:wikitext(caption)
 +
end
 +
addRow({
 +
data = tostring(data),
 +
datastyle = args.imagestyle,
 +
class = args.imageclass,
 +
rowclass = args['imagerowclass' .. tostring(num)]
 +
})
 +
end
 
end
 
end
  
function i.InfoboxImage(frame)
+
local function preprocessRows()
    local image = frame.args["image"];
+
-- Gets the union of the header and data argument numbers,
   
+
-- and renders them all in order using addRow.
    if image == "" or image == nil then
+
local rownums = union(getArgNums('header'), getArgNums('data'))
        return "";
+
table.sort(rownums)
    end
+
local lastheader
    if image == "&nbsp;" then
+
for k, num in ipairs(rownums) do
        return image;
+
if args['header' .. tostring(num)] then
    end
+
if lastheader then
    if frame.args["suppressplaceholder"] ~= "no" then
+
args['header' .. tostring(lastheader)] = nil
        if i.IsPlaceholder(image) == true then
+
end
            return "";
+
lastheader = num
        end
+
elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('^%S') then
    end
+
local data = args['data' .. tostring(num)]
 +
if data:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('%S') then
 +
lastheader = nil
 +
end
 +
end
 +
end
 +
if lastheader then
 +
args['header' .. tostring(lastheader)] = nil
 +
end
 +
end
  
    if mw.ustring.lower(mw.ustring.sub(image,1,5)) == "http:" then
+
local function renderRows()
        return "";
+
-- Gets the union of the header and data argument numbers,
    end
+
-- and renders them all in order using addRow.
    if mw.ustring.lower(mw.ustring.sub(image,1,6)) == "[http:" then
+
local rownums = union(getArgNums('header'), getArgNums('data'))
        return "";
+
table.sort(rownums)
    end
+
for k, num in ipairs(rownums) do
    if mw.ustring.lower(mw.ustring.sub(image,1,7)) == "[[http:" then
+
addRow({
        return "";
+
header = args['header' .. tostring(num)],
    end
+
label = args['label' .. tostring(num)],
    if mw.ustring.lower(mw.ustring.sub(image,1,6)) == "https:" then
+
data = args['data' .. tostring(num)],
        return "";
+
datastyle = args.datastyle,
    end
+
class = args['class' .. tostring(num)],
    if mw.ustring.lower(mw.ustring.sub(image,1,7)) == "[https:" then
+
rowclass = args['rowclass' .. tostring(num)],
        return "";
+
rowstyle = args['rowstyle' .. tostring(num)],
    end
+
rowcellstyle = args['rowcellstyle' .. tostring(num)],
    if mw.ustring.lower(mw.ustring.sub(image,1,8)) == "[[https:" then
+
dataid = args['dataid' .. tostring(num)],
        return "";
+
labelid = args['labelid' .. tostring(num)],
    end
+
headerid = args['headerid' .. tostring(num)],
 +
rowid = args['rowid' .. tostring(num)]
 +
})
 +
end
 +
end
  
    if mw.ustring.sub(image,1,2) == "[[" then
+
local function renderNavBar()
        -- search for thumbnail images and add to tracking cat if found
+
if not args.name then return end
        local cat = "";
+
 
        if mw.title.getCurrentTitle().namespace == 0 and (mw.ustring.find(image, "|%s*thumb%s*[|%]]") or mw.ustring.find(image, "|%s*thumbnail%s*[|%]]")) then
+
root
            cat = "[[Category:Pages using infoboxes with thumbnail images]]";
+
:tag('tr')
        end
+
:tag('td')
        return image .. cat;
+
:attr('colspan', '2')
    elseif mw.ustring.sub(image,1,2) == "{{" and mw.ustring.sub(image,1,3) ~= "{{{" then
+
:css('text-align', 'right')
        return image;
+
:wikitext(navbar{
    elseif mw.ustring.sub(image,1,1) == "<" then
+
args.name,
        return image;
+
mini = 1,
    elseif mw.ustring.sub(image,1,5) == mw.ustring.char(127).."UNIQ" then
+
})
        -- Found strip marker at begining, so pass don't process at all
 
        return image;
 
    elseif mw.ustring.sub(image,4,9) == "`UNIQ-" then
 
        -- Found strip marker at begining, so pass don't process at all
 
        return image;
 
    else
 
        local result = "";
 
        local page = frame.args["page"];
 
        local size = frame.args["size"];
 
        local maxsize = frame.args["maxsize"];
 
        local sizedefault = frame.args["sizedefault"];
 
        local alt = frame.args["alt"];
 
        local link = frame.args["link"];
 
        local title = frame.args["title"];
 
        local border = frame.args["border"];
 
        local upright = frame.args["upright"] or "";
 
        local thumbtime = frame.args["thumbtime"] or "";
 
        local center= frame.args["center"];
 
       
 
        -- remove prefix if exists
 
        local allNames = mw.site.namespaces[6].aliases
 
        allNames[#allNames + 1] = mw.site.namespaces[6].name
 
        allNames[#allNames + 1] = mw.site.namespaces[6].canonicalName
 
        for i, name in ipairs(allNames) do
 
            if mw.ustring.lower(mw.ustring.sub(image, 1, mw.ustring.len(name) + 1)) == mw.ustring.lower(name .. ":") then
 
                image = mw.ustring.sub(image, mw.ustring.len(name) + 2);
 
                break
 
            end
 
        end
 
       
 
        if maxsize ~= "" and maxsize ~= nil then
 
            -- if no sizedefault then set to maxsize
 
            if sizedefault == "" or sizedefault == nil then
 
                sizedefault = maxsize
 
            end
 
            -- check to see if size bigger than maxsize
 
            if size ~= "" and size ~= nil then
 
                local sizenumber = tonumber(mw.ustring.match(size,"%d*")) or 0;
 
                local maxsizenumber = tonumber(mw.ustring.match(maxsize,"%d*")) or 0;
 
                if sizenumber>maxsizenumber and maxsizenumber>0 then
 
                    size = maxsize;
 
                end
 
            end
 
        end
 
        -- add px to size if just a number
 
        if (tonumber(size) or 0) > 0 then
 
            size = size .. "px";
 
        end
 
        -- add px to sizedefault if just a number
 
        if (tonumber(sizedefault) or 0) > 0 then
 
            sizedefault = sizedefault .. "px";
 
        end
 
       
 
        result = "[[File:" .. image;
 
        if page ~= "" and page ~= nil then
 
            result = result .. "|page=" .. page;
 
        end
 
        if size ~= "" and size ~= nil then
 
            result = result .. "|" .. size;
 
        elseif sizedefault ~= "" and sizedefault ~= nil then
 
            result = result .. "|" .. sizedefault;
 
        else
 
            result = result .. "|frameless";
 
        end
 
        if center == "yes" then
 
            result = result .. "|center"
 
        end
 
        if alt ~= "" and alt ~= nil then
 
            result = result .. "|alt=" .. alt;
 
        end
 
        if link ~= "" and link ~= nil then
 
            result = result .. "|link=" .. link;
 
        end
 
        if border == "yes" then
 
            result = result .. "|border";
 
        end
 
        if upright == "yes" then
 
            result = result .. "|upright";
 
        elseif upright ~= "" then
 
            result = result .. "|upright=" .. upright;
 
        end
 
        if thumbtime ~= "" then
 
            result = result .. "|thumbtime=" .. thumbtime;
 
        end
 
        if title ~= "" and title ~= nil then
 
            result = result .. "|" .. title;
 
        elseif alt ~= "" and alt ~= nil then
 
            result = result .. "|" .. alt;
 
        end
 
        result = result .. "]]";
 
       
 
        return result;
 
    end
 
 
end
 
end
  
return i;
+
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(mw.getCurrentFrame():expandTemplate({title = 'italic title'}))
 +
end
 +
end
 +
 
 +
local function renderTrackingCategories()
 +
if args.decat ~= 'yes' then
 +
if args.child == 'yes' then
 +
if args.title then
 +
root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]')
 +
end
 +
elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then
 +
root:wikitext('[[Category:Articles which use infobox templates with no data rows]]')
 +
end
 +
end
 +
end
 +
 
 +
local function _infobox()
 +
-- Specify the overall layout of the infobox, with special settings
 +
-- if the infobox is used as a 'child' inside another infobox.
 +
if args.child ~= 'yes' then
 +
root = mw.html.create('table')
 +
 
 +
root
 +
:addClass((args.subbox ~= 'yes') and 'infobox' or nil)
 +
:addClass(args.bodyclass)
 +
 
 +
if args.subbox == 'yes' then
 +
root
 +
:css('padding', '0')
 +
:css('border', 'none')
 +
:css('margin', '-3px')
 +
:css('width', 'auto')
 +
:css('min-width', '100%')
 +
:css('font-size', '100%')
 +
:css('clear', 'none')
 +
:css('float', 'none')
 +
:css('background-color', 'transparent')
 +
else
 +
root
 +
:css('width', '22em')
 +
end
 +
root
 +
:cssText(args.bodystyle)
 +
 
 +
renderTitle()
 +
renderAboveRow()
 +
else
 +
root = mw.html.create()
 +
 
 +
root
 +
:wikitext(args.title)
 +
end
 +
 
 +
renderSubheaders()
 +
renderImages()
 +
if args.autoheaders then
 +
preprocessRows()
 +
end
 +
renderRows()
 +
renderBelowRow()
 +
renderNavBar()
 +
renderItalicTitle()
 +
renderTrackingCategories()
 +
 
 +
return tostring(root)
 +
end
 +
 
 +
local function preprocessSingleArg(argName)
 +
-- 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.
 +
if origArgs[argName] and origArgs[argName] ~= '' then
 +
args[argName] = origArgs[argName]
 +
end
 +
end
 +
 
 +
local function preprocessArgs(prefixTable, step)
 +
-- 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.
 +
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
 +
moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones.
 +
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
 +
 
 +
local function parseDataParameters()
 +
-- 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.
 +
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'},
 +
{prefix = 'dataid'},
 +
{prefix = 'labelid'},
 +
{prefix = 'headerid'},
 +
{prefix = 'rowid'}
 +
}, 50)
 +
preprocessSingleArg('headerclass')
 +
preprocessSingleArg('headerstyle')
 +
preprocessSingleArg('labelstyle')
 +
preprocessSingleArg('datastyle')
 +
preprocessSingleArg('below')
 +
preprocessSingleArg('belowclass')
 +
preprocessSingleArg('belowstyle')
 +
preprocessSingleArg('name')
 +
args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent
 +
preprocessSingleArg('decat')
 +
end
 +
 
 +
function p.infobox(frame)
 +
-- If called via #invoke, use the args passed into the invoking template.
 +
-- Otherwise, for testing purposes, assume args are being passed directly in.
 +
if frame == mw.getCurrentFrame() then
 +
origArgs = frame:getParent().args
 +
else
 +
origArgs = frame
 +
end
 +
 +
parseDataParameters()
 +
 +
return _infobox()
 +
end
 +
 
 +
function p.infoboxTemplate(frame)
 +
-- For calling via #invoke within a template
 +
origArgs = {}
 +
for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end
 +
 +
parseDataParameters()
 +
 +
return _infobox()
 +
end
 +
return p

Latest revision as of 12:33, 17 July 2026

Documentation for this module may be created at Module:Infobox/doc

--
-- This module implements {{Infobox}}
--

local p = {}

local navbar = require('Module:Navbar')._navbar

local args = {}
local origArgs = {}
local root

local function notempty( s ) return s and s:match( '%S' ) end

local function fixChildBoxes(sval, tt)
	if notempty(sval) then
		local marker = '<span class=special_infobox_marker>'
		local s = sval
		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
		-- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=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

local function union(t1, t2)
	-- Returns the union of the values of two tables, as a sequence.
	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

local function getArgNums(prefix)
	-- 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 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

local function addRow(rowArgs)
	-- Adds a row to the infobox, with either a header cell
	-- or a label/data cell combination.
	if rowArgs.header and rowArgs.header ~= '_BLANK_' then
		root
			:tag('tr')
				:addClass(rowArgs.rowclass)
				:cssText(rowArgs.rowstyle)
				:attr('id', rowArgs.rowid)
				:tag('th')
					:attr('colspan', 2)
					:attr('id', rowArgs.headerid)
					:addClass(rowArgs.class)
					:addClass(args.headerclass)
					:css('text-align', 'center')
					:cssText(args.headerstyle)
					:cssText(rowArgs.rowcellstyle)
					:wikitext(fixChildBoxes(rowArgs.header, 'th'))
		if rowArgs.data then
			root:wikitext('[[Category:Pages which use infobox templates with ignored data cells]]')
		end
	elseif rowArgs.data then
		if not rowArgs.data:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('^%S') then
			rowArgs.rowstyle = 'display:none'
		end
		local row = root:tag('tr')
		row:addClass(rowArgs.rowclass)
		row:cssText(rowArgs.rowstyle)
		row:attr('id', rowArgs.rowid)
		if rowArgs.label then
			row
				:tag('th')
					:attr('scope', 'row')
					:attr('id', rowArgs.labelid)
					:cssText(args.labelstyle)
					:cssText(rowArgs.rowcellstyle)
					:wikitext(rowArgs.label)
					:done()
		end

		local dataCell = row:tag('td')
		if not rowArgs.label then
			dataCell
				:attr('colspan', 2)
				:css('text-align', 'center')
		end
		dataCell
			:attr('id', rowArgs.dataid)
			:addClass(rowArgs.class)
			:cssText(rowArgs.datastyle)
			:cssText(rowArgs.rowcellstyle)
			:wikitext(fixChildBoxes(rowArgs.data, 'td'))
	end
end

local function renderTitle()
	if not args.title then return end

	root
		:tag('caption')
			:addClass(args.titleclass)
			:cssText(args.titlestyle)
			:wikitext(args.title)
end

local function renderAboveRow()
	if not args.above then return end

	root
		:tag('tr')
			:tag('th')
				:attr('colspan', 2)
				:addClass(args.aboveclass)
				:css('text-align', 'center')
				:css('font-size', '125%')
				:css('font-weight', 'bold')
				:cssText(args.abovestyle)
				:wikitext(fixChildBoxes(args.above,'th'))
end

local function renderBelowRow()
	if not args.below then return end

	root
		:tag('tr')
			:tag('td')
				:attr('colspan', '2')
				:addClass(args.belowclass)
				:css('text-align', 'center')
				:cssText(args.belowstyle)
				:wikitext(fixChildBoxes(args.below,'td'))
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
		addRow({
			data = args['subheader' .. tostring(num)],
			datastyle = args.subheaderstyle,
			rowcellstyle = args['subheaderstyle' .. tostring(num)],
			class = args.subheaderclass,
			rowclass = args['subheaderrowclass' .. tostring(num)]
		})
	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')
					:cssText(args.captionstyle)
					:wikitext(caption)
		end
		addRow({
			data = tostring(data),
			datastyle = args.imagestyle,
			class = args.imageclass,
			rowclass = args['imagerowclass' .. tostring(num)]
		})
	end
end

local function preprocessRows()
	-- Gets the union of the header and data argument numbers,
	-- and renders them all in order using addRow.
	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('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('^%S') then
			local data = args['data' .. tostring(num)]
			if data:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('%S') then
				lastheader = nil
			end
		end
	end
	if lastheader then
		args['header' .. tostring(lastheader)] = nil
	end
end

local function renderRows()
	-- Gets the union of the header and data argument numbers,
	-- and renders them all in order using addRow.
	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)],
			rowstyle = args['rowstyle' .. tostring(num)],
			rowcellstyle = args['rowcellstyle' .. tostring(num)],
			dataid = args['dataid' .. tostring(num)],
			labelid = args['labelid' .. tostring(num)],
			headerid = args['headerid' .. tostring(num)],
			rowid = args['rowid' .. tostring(num)]
		})
	end
end

local function renderNavBar()
	if not args.name then return end

	root
		:tag('tr')
			:tag('td')
				:attr('colspan', '2')
				:css('text-align', 'right')
				:wikitext(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(mw.getCurrentFrame():expandTemplate({title = 'italic title'}))
	end
end

local function renderTrackingCategories()
	if args.decat ~= 'yes' then
		if args.child == 'yes' then
			if args.title then
				root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]')
			end
		elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then
			root:wikitext('[[Category:Articles which use infobox templates with no data rows]]')
		end
	end
end

local function _infobox()
	-- Specify the overall layout of the infobox, with special settings
	-- if the infobox is used as a 'child' inside another infobox.
	if args.child ~= 'yes' then
		root = mw.html.create('table')

		root
			:addClass((args.subbox ~= 'yes') and 'infobox' or nil)
			:addClass(args.bodyclass)

			if args.subbox == 'yes' then
				root
					:css('padding', '0')
					:css('border', 'none')
					:css('margin', '-3px')
					:css('width', 'auto')
					:css('min-width', '100%')
					:css('font-size', '100%')
					:css('clear', 'none')
					:css('float', 'none')
					:css('background-color', 'transparent')
			else
				root
					:css('width', '22em')
			end
		root
			:cssText(args.bodystyle)

		renderTitle()
		renderAboveRow()
	else
		root = mw.html.create()

		root
			:wikitext(args.title)
	end

	renderSubheaders()
	renderImages()
	if args.autoheaders then
		preprocessRows()
	end
	renderRows()
	renderBelowRow()
	renderNavBar()
	renderItalicTitle()
	renderTrackingCategories()

	return tostring(root)
end

local function preprocessSingleArg(argName)
	-- 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.
	if origArgs[argName] and origArgs[argName] ~= '' then
		args[argName] = origArgs[argName]
	end
end

local function preprocessArgs(prefixTable, step)
	-- 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.
	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
					moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones.
					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

local function parseDataParameters()
	-- 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.
	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'},
		{prefix = 'dataid'},
		{prefix = 'labelid'},
		{prefix = 'headerid'},
		{prefix = 'rowid'}
	}, 50)
	preprocessSingleArg('headerclass')
	preprocessSingleArg('headerstyle')
	preprocessSingleArg('labelstyle')
	preprocessSingleArg('datastyle')
	preprocessSingleArg('below')
	preprocessSingleArg('belowclass')
	preprocessSingleArg('belowstyle')
	preprocessSingleArg('name')
	args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent
	preprocessSingleArg('decat')
end

function p.infobox(frame)
	-- If called via #invoke, use the args passed into the invoking template.
	-- Otherwise, for testing purposes, assume args are being passed directly in.
	if frame == mw.getCurrentFrame() then
		origArgs = frame:getParent().args
	else
		origArgs = frame
	end
	
	parseDataParameters()
	
	return _infobox()
end

function p.infoboxTemplate(frame)
	-- For calling via #invoke within a template
	origArgs = {}
	for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end
	
	parseDataParameters()
	
	return _infobox()
end
return p