Module:Infobox

-- 中文的此模組對比英文維基多解析了 overimage, overcaption,

-- overimagerowclass 以及 header/labal/data*style 引數。

local p =

local args =

local origArgs =

local root

local emptyrowcategories =

local categoryinemptyrowpattern = '%%% sCcAaTtEeGgOoRrYy% 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 haslistclass (argstocheck)

	for _, list in pairs (lists) do

		if not list.found then

			for , arg in pairs (argsto_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 =

		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, '(% s)(%%% sCcAaTtEeGgOoRrYy% s:^%%)', '%2%1')

			s = mw.ustring.gsub (s, '(% s)(\127^\127UNIQ%-%-templatestyles%-% x+%-QINU^\127*\127)', '%2%1')

		end

		-- end moving templatestyles and categories inside of table rows

		s = mw.ustring.gsub (s, '', '%1' .. marker)

		if s:match (marker) then

			s = mw.ustring.gsub (s, marker .. '% s*' .. marker,)

			s = mw.ustring.gsub (s, '(\r\n

			s = mw.ustring.gsub (s, marker .. '% s*(\r\n

			s = mw.ustring.gsub (s, '(% s*)' .. marker, '%1')

			s = mw.ustring.gsub (s, '(>% s)' .. marker, '%1')

			s = mw.ustring.gsub (s, '^(%

			s = mw.ustring.gsub (s, '(\r\n%

			s = mw.ustring.gsub (s, marker .. '(% 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 .. subcellsk ..

				elseif k == #subcells then

					local rowstyle = ' style="display:none"'

					if notempty (subcellsk) then rowstyle = 	end

					s = s .. '\n' ..

						subcellsk

				elseif notempty (subcellsk) then

					if (k % 2) == 0 then

						s = s .. subcellsk

					else

						s = s .. '\n' ..

							subcellsk ..

					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, '>% s', )

	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

		valsv = true

	end

	for k, v in pairs (t2) do

		valsv = 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

		haslistclass ({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- .infobox-header

					-- for individual headerstyle: target .infobox- .infobox--row > .infobox-header

					:cssText (rowArgs.headerstyle)

					:cssText (rowArgs.rowcellstyle)

					:wikitext (fixChildBoxes (rowArgs.header, 'th'))

		if rowArgs.data then

			root:wikitext (	)

		end

	elseif rowArgs.data and rowArgs.data:gsub (categoryinemptyrowpattern,):match ('^% S') then

		has_rows = true

		haslistclass ({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- .infobox-label

					-- for individual labelstyle: target .infobox- .infobox--row > .infobox-label

					:cssText (rowArgs.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- .infobox (-full)-data

			-- for individual datastyle: target .infobox- .infobox--row > .infobox (-full)-data

			:cssText (rowArgs.datastyle)

			:cssText (rowArgs.rowcellstyle)

			:wikitext (fixChildBoxes (rowArgs.data, 'td'))

	else

		table.insert (emptyrowcategories, rowArgs.data or)

	end

end

local function renderTitle

	if not args.title then return end

	has_rows = true

	haslistclass ({args.titleclass})

	root

		:tag ('caption')

			:addClass ('infobox-title')

			:addClass (args.titleclass)

			-- @deprecated next; target .infobox- .infobox-title

			:cssText (args.titlestyle)

			:wikitext (args.title)

end

local function renderAboveRow

	if not args.above then return end

	has_rows = true

	haslistclass ({args.aboveclass})

	root

		:tag ('tr')

			:tag ('th')

				:attr ('colspan', '2')

				:addClass ('infobox-above')

				:addClass (args.aboveclass)

				-- @deprecated next; target .infobox- .infobox-above

				:cssText (args.abovestyle)

				:wikitext (fixChildBoxes (args.above,'th'))

end

local function renderBelowRow

	if not args.below then return end

	has_rows = true

	haslistclass ({args.belowclass})

	root

		:tag ('tr')

			:tag ('td')

				:attr ('colspan', '2')

				:addClass ('infobox-below')

				:addClass (args.belowclass)

				-- @deprecated next; target .infobox- .infobox-below

				:cssText (args.belowstyle)

				:wikitext (fixChildBoxes (args.below,'td'))

end

local function addSubheaderRow (subheaderArgs)

	if subheaderArgs.data and

		subheaderArgs.data:gsub (categoryinemptyrowpattern,):match ('^% S') then

		has_rows = true

		haslistclass ({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 (emptyrowcategories, 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- .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 (categoryinemptyrowpattern,):match ('^% S') then

		has_rows = true

		haslistclass ({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 (emptyrowcategories, imageArgs.data or)

	end

end

local function renderOverImage

	local caption = args.overcaption

	local data = mw.html.create :wikitext (args.overimage)

	if caption then

		data

			:tag ('div')

				:addClass ('infobox-caption')

				-- @deprecated next; target .infobox- .infobox-caption

				:cssText (args.captionstyle)

				:wikitext (caption)

	end

	addImageRow ({data = tostring (data),

		-- @deprecated next; target .infobox- .infobox-image

		datastyle = args.imagestyle,

		class = args.imageclass,

		rowclass = args.overimagerowclass

	})

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- .infobox-caption

					:cssText (args.captionstyle)

					:wikitext (caption)

		end

		addImageRow ({data = tostring (data),

			-- @deprecated next; target .infobox- .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 (categoryinemptyrowpattern,):match ('^% S') then

			local data = args'data' .. tostring (num)

			if data:gsub (categoryinemptyrowpattern,):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),

			headerstyle = (args.headerstyle or) .. (args.headerstyle and ';' or) .. (args'header' .. tostring (num) .. 'style' or ),

			label = args'label' .. tostring (num),

			labelstyle = (args.labelstyle or) .. (args.labelstyle and ';' or) .. (args'label' .. tostring (num) .. 'style' or ),

			data = args'data' .. tostring (num),

			datastyle = (args.datastyle or) .. (args.datastyle and ';' or) .. (args'data' .. tostring (num) .. 'style' or ),

			class = args'class' .. tostring (num),

			rowclass = args'rowclass' .. tostring (num),

			-- @deprecated next; target .infobox- 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 emptyrowcategories.

-- 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 (emptyrow_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 (	)

		end

	elseif #(getArgNums ('data')) == 0 and mw.title.getCurrentTitle .namespace == 0 then

		root:wikitext

	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 Mediawikitalk:Common.css/todo#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

		hlisttemplatestyles = frame:extensionTag {name = 'templatestyles', args = { src = lists.hlistt.styles}

		}

	end

	local plainlist_templatestyles =

	if lists.plainlist_t.found then

		plainlisttemplatestyles = frame:extensionTag {name = 'templatestyles', args = { src = lists.plainlistt.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 structureinfoboxcommon

	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-

			:cssText (args.bodystyle)

		haslistclass ({args.bodyclass})

		renderTitle

		renderOverImage

		renderAboveRow

	else

		root = mw.html.create

		root

			:wikitext (args.title)

	end

	structureinfoboxcommon

	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 origArgsargName and origArgsargName ~= then

		argsargName = origArgsargName

	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 argsv.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 origArgsprefixArgName 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 (argsprefixArgName or (i == 1 and argsv.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

-- Assign the parameters *style to the args table

local function preprocessSpecificStyle (styleTable, step)

	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 (styleTable) do

				local styleArgName = v.arg .. tostring (i) .. 'style'

				if origArgsstyleArgName then

					-- Do another loop if any arguments are found, even blank ones.

					moreArgumentsExist = true

					preprocessSingleArg (styleArgName)

				end

			end

		end

		a = a + step

	end

end

-- Parse the data parameters in the same order that the old 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 ('overimage')

	preprocessSingleArg ('overcaption')

	preprocessSingleArg ('overimagerowclass')

	preprocessSingleArg ('title')

	preprocessSingleArg ('titleclass')

	preprocessSingleArg ('titlestyle')

	preprocessSingleArg ('above')

	preprocessSingleArg ('aboveclass')

	preprocessSingleArg ('abovestyle')

	preprocessArgs (

	}, 10)

	preprocessSingleArg ('subheaderstyle')

	preprocessSingleArg ('subheaderclass')

	preprocessArgs (

	}, 10)

	preprocessSingleArg ('captionstyle')

	preprocessSingleArg ('imagestyle')

	preprocessSingleArg ('imageclass')

	preprocessArgs (,

		{prefix = 'rowclass'},

		{prefix = 'rowstyle'},

		{prefix = 'rowcellstyle'},

		{prefix = 'class'}

	}, 80)

	preprocessSpecificStyle ({{arg = 'header'},

		{arg = 'label'},

		{arg = 'data'}

	}, 80)

	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 origArgsk = mw.text.trim (v) end

	parseDataParameters

	return _infobox

end

return p