Documentation for this module may be created at ಮಾಡ್ಯೂಲ್:Infobox/sandbox/doc

-- This module implements {{Infobox}}

local checkType = require('libraryUtil').checkType
local CONFIG_MODULE = 'Module:Infobox/config'

--------------------------------------------------------------------------------
-- Helper functions
--------------------------------------------------------------------------------

local function nilFunc ()
	-- To avoid using a different anonymous function every time an argument
	-- uses the Infobox.rowArgDefaults metatable.
	return nil
end

local function trueFunc ()
	-- To avoid using a different anonymous function for every data row.
	return true
end

local function makeCategory(cat)
	if cat then
		return string.format('[[ವರ್ಗ:%s]]', cat)
	end
end

--------------------------------------------------------------------------------
-- Infobox class
--------------------------------------------------------------------------------

local Infobox = {}
Infobox.__index = Infobox
Infobox.rowArgDefaults = {__index = function (t, key)
	t[key] = nilFunc
	return nilFunc
end}

function Infobox.new(args, cfg, title)
	local self = setmetatable({}, Infobox)
	self.cfg = cfg
	self.title = title
	self.root = mw.html.create('table')

	-- Set the args. We pass the args around as functions, so that we can delay
	-- argument expansion until the last minute. This enables us to expand the
	-- arguments in the correct order, and only when necessary. This is
	-- important for <ref>...</ref> tags, as otherwise:
	-- 1. A <references /> tag at the bottom of an infobox might get expanded
	--    before <ref>...</ref> tags higher up in an infobox, and the reference
	--    doesn't appear where it should; or
	-- 2. <ref>...</ref> tags in a label cell can be expanded, but not
	--    displayed in the infobox due to the lack of a corresponding data
	--    cell argument. This causes a "phantom reference" where the citation
	--    is visible in the <references /> tag output, but there is no link to
	--    it anywhere in the article.
	self.args = setmetatable({}, {__index = function (t, key)
		local val
		local function expandThisArg ()
			if val == false then
				return nil
			elseif val then
				return val
			else
				val = args[key]
				if val == '' then
					val = false
					return nil
				elseif val == false then
					return nil
				else
					return val
				end
			end
		end
		t[key] = expandThisArg
		return expandThisArg
	end})

	-- Special case for italic title, which can be different depending on
	-- whether it is blank or absent.
	self.args['italic title'] = function () return args['italic title'] end
	
	self.hasDataCell = false -- Track data cell usage.

	return self
end

function Infobox:renderTitle()
	local args = self.args
	if not args.title() then return end

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

function Infobox:renderAboveRow()
	local args = self.args
	if not args.above() then return end
	
	self.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(args.above())
end

function Infobox:renderBelowRow()
	local args = self.args
	if not args.below() then return end
	
	self.root
		:tag('tr')
			:tag('td')
				:attr('colspan', '2')
				:addClass(args.belowclass())
				:css('text-align', 'center')
				:cssText(args.belowstyle())
				:newline()
				:wikitext(args.below())
end

function Infobox:addRow(rowArgs)
	-- Adds a row to the infobox, with either a header cell
	-- or a label/data cell combination.
	setmetatable(rowArgs, Infobox.rowArgDefaults) -- Set a default function
	local hasContent = false -- Tracks whether we have rendered anything.
	local args = self.args
	local header = rowArgs.header()
	if header then
		hasContent = true
		self.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())
					:wikitext(header)
	else
		local data = rowArgs.data()
		if data then
			hasContent = true

			-- Track data cells, but not images or subheaders, for use in
			-- generating tracking categories.
			if rowArgs.isDataCellCheck() then
				self.hasDataCell = true
			end

			local row = self.root:tag('tr')
			row:addClass(rowArgs.rowclass())
			row:cssText(rowArgs.rowstyle())
			row:attr('id', rowArgs.rowid())
			local label = rowArgs.label()
			if label then
				row
					:tag('th')
						:attr('scope', 'row')
						:attr('id', rowArgs.labelid())
						:css('text-align', 'left')
						:cssText(args.labelstyle())
						:wikitext(label)
						:done()
			end
			
			local dataCell = row:tag('td')
			if not label then 
				dataCell
					:attr('colspan', 2)
					:css('text-align', 'center') 
			end
			dataCell
				:attr('id', rowArgs.dataid())
				:addClass(rowArgs.class())
				:cssText(rowArgs.datastyle())
				:newline()
				:wikitext(data)
		end
	end
	return hasContent
end

function Infobox:renderNumberedRows(rowArgMaker, gap)
	-- Renders numbered rows using Infobox:addRow. rowArgMaker is a function
	-- that makes the table of rowArgs to be used by addRow. It takes the row
	-- number as input. This method will continue to try and add rows until
	-- the number of sequential blank rows is greater than the number specified
	-- in gap.
	local iRow, iGap = 0, 0
	while iGap < gap do
		iRow = iRow + 1
		iGap = iGap + 1
		if self:addRow(rowArgMaker(iRow)) then
			iGap = 0
		end
	end
end

function Infobox:renderSubheaders()
	local args = self.args
	if args.subheader() then
		args.subheader1 = args.subheader
	end
	if args.subheaderrowclass() then
		args.subheaderrowclass1 = args.subheaderrowclass
	end
	return self:renderNumberedRows(
		function (i)
			i = tostring(i)
			return {
				data = args['subheader' .. i],
				datastyle = function ()
					return args.subheaderstyle() or args['subheaderstyle' .. i]()
				end,
				class = args.subheaderclass,
				rowclass = args['subheaderrowclass' .. i]
			}
		end,
		2
	)
end

function Infobox:renderImages()
	local args = self.args
	if args.image() then
		args.image1 = args.image
	end
	if args.caption() then
		args.caption1 = args.caption
	end
	return self:renderNumberedRows(
		function (i)
			i = tostring(i)
			return {
				data = function ()
					local image = args['image' .. i]()
					if image then
						local data = mw.html.create():wikitext(image)
						local caption = args['caption' .. i]()
						if caption then
							data
								:tag('div')
									:cssText(args.captionstyle())
									:wikitext(caption)
						end
						return tostring(data)
					end
				end,
				datastyle = args.imagestyle,
				class = args.imageclass,
				rowclass = args['imagerowclass' .. i]
			}
		end,
		3
	)
end

function Infobox:renderContentRows()
	local args = self.args
	return self:renderNumberedRows(
		function (i)
			i = tostring(i)
			return {
				-- Tell addRow to set self.hasDataCells to true if it finds a
				-- data cell argument.
				isDataCellCheck = trueFunc,

				header = args['header' .. i],
				label = args['label' .. i],
				data = args['data' .. i],
				datastyle = args.datastyle,
				class = args['class' .. i],
				rowclass = args['rowclass' .. i],
				rowstyle = args['rowstyle' .. i],
				dataid = args['dataid' .. i],
				labelid = args['labelid' .. i],
				headerid = args['headerid' .. i],
				rowid = args['rowid' .. i]
			}
		end,
		10,
		'hasContentRows'
	)
end

function Infobox:renderNavBar()
	local args = self.args
	if not args.name() then return end
	
	self.root
		:tag('tr')
			:tag('td')
				:attr('colspan', '2')
				:css('text-align', 'right')
				:wikitext(mw.getCurrentFrame():expandTemplate({ 
					title = 'navbar', 
					args = { args.name(), mini = 1 }
				}))
end

function Infobox:renderItalicTitle()
	local args = self.args
	local italicTitle = args['italic title']() and mw.ustring.lower(args['italic title']())
	if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then
		self.root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'}))
	end
end

function Infobox:renderTrackingCategories()
	local args = self.args
	local cfg = self.cfg
	if args.decat() ~= 'yes' then
		if not self.hasContentRows and self.title.namespace == 0 then
			self.root:wikitext(makeCategory(cfg['category-no-data-row']))
		end
		if args.child() == 'yes' and args.title() then
			self.root:wikitext(makeCategory(cfg['category-embedded-title']))
		end
	end
end

function Infobox:__tostring()
	-- Specify the overall layout of the infobox, with special settings
	-- if the infobox is used as a 'child' inside another infobox.
	local args = self.args
	if args.child() ~= 'yes' then
		self.root
			:addClass('infobox')
			:addClass(args.bodyclass())
			
			if args.subbox() == 'yes' then
				self.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
				self.root
					:css('width', '22em')
			end
		self.root
			:cssText(args.bodystyle())
	
		self:renderTitle()
		self:renderAboveRow()
	else
		self.root = mw.html.create()
		
		self.root
			:wikitext(args.title())
	end

	self:renderSubheaders()
	self:renderImages() 
	self:renderContentRows() 
	self:renderBelowRow()  
	self:renderNavBar()
	self:renderItalicTitle()
	self:renderTrackingCategories()
	
	return tostring(self.root)
end

--------------------------------------------------------------------------------
-- Exports
--------------------------------------------------------------------------------

local p = {}

function p._infobox(args, cfg, title)
	checkType('_infobox', 1, args, 'table')
	checkType('_infobox', 2, cfg, 'table', true)
	cfg = cfg or mw.loadData(CONFIG_MODULE)
	title = title or mw.title.getCurrentTitle()
	return tostring(Infobox.new(args, cfg, title))
end
 
function p.infobox(frame, cfg)
	cfg = cfg or mw.loadData(CONFIG_MODULE)
	local args
	if cfg.wrapper then
		local parent = frame:getParent()
		local parentTitle = parent:getTitle()
		local wrapper = mw.title.new(cfg.wrapper).prefixedText
		if parentTitle == wrapper
			or parentTitle == wrapper .. cfg['wrapper-sandbox-suffix']
		then
			args = parent.args
		end
	end
	args = args or frame.args
	return p._infobox(args, cfg)
end
 
return p