Dokumentacja modułu [stwórz] [odśwież]
local navboxFramePrefixStart = '<table class="navbox '
local navboxFramePrefixEnd = '><tr><td>'
local navboxFrameSuffix = '</td></tr></table>'

local nameTemplateArg = "nazwa"
local classesTemplateArg = "klasa"
local widthTemplateArg = "szerokość"
local parityTemplateArg = "parzystość"
local listTemplateArg = "spis"
local groupTemplateArg = "opis"
local indexTemplateArg = "n"

local minimumIndexArgName = "min"
local maximumIndexArgName = "max"
local listOnlyTemplateArgName = "spis"
local listGroupTemplateArgName = "opis"

local genericCategory = "Kategoria:Szablony nawigacyjne"

local listArgFormat = "spis%d"
local groupArgFormat = "opis%d"

local classNavboxInner = "navbox-inner"
local classNavbox = "navbox"
local classNavboxSubGroup = "navbox-subgroup"
local classColumnsTable = "navbox-columns-table"
local classNavboxList = "navbox-list"
local classNavboxGroup = "navbox-group"
local classNavboxColumn = "navbox-column"
local classNavboxOdd = "navbox-odd"
local classNavboxEven = "navbox-even"
local classNavboxAboveBelow = "navbox-abovebelow"
local classFixedParity = "fixed-parity"
local classHList = "hlist"

local paritySwapped = "zamień"
local parityEven = "parzyste"
local parityOdd = "nieparzyste"
local parityOff = "brak"

local tableHeaderGroupArgName = "nagłówek opisu"
local tableHeaderGroupWidthArgName = "szerokość opisu"
local tableHeaderArgFormat = "nagłówek%d"
local tableContentArgName = "zawartość"
local tableContentGroupName = "opis"
local tableHeaderClassGroup = "opis"
local tableRowClassGroupFormat = "opis%d"
local tableHeaderClassListFormat = "spis%d"
local tableRowClassListFormat = "wpis-%d-%d"

local tableParityHorizontal = "poziomo"
local tableParityVertical = "pionowo"

local tableHeaderDefaultGroupWidth = "10em"

local validExtraClasses = {
	"ll-script",
	"ll-script-dl-hlist",
	"kz-linia",
	"nav-teams",
	"hnowrap-ul-ul"
}

function findNavboxClasses(text)
	local classes = {}
	local i = 1
	while true do
		local s, e, c = mw.ustring.find(text, "class%s*=%s*\"([^\"]-%f[%a]navbox%f[^%a][^\"]*)\"", i)
		if not c then
			break
		end
		
		local cc = mw.text.split(c,"%s+")
		for _, v in ipairs(cc) do
			if (v == classNavbox) or (v == classNavboxSubGroup) or (v == classColumnsTable) then
				table.insert(classes, { list=false })
				break
			elseif (v == classNavboxList) or (v == classNavboxColumn) then
				table.insert(classes, { list=true, c=cc, s=s, e=e })
				break
			end
		end
		
		i = e + 1
	end
	
	return classes
end

function adjustHList(classes)
	local lists = {}
	for i = 1, #classes do
		if classes[i].list then
			local needsParity = i >= #classes or classes[i + 1].list
			
			-- copy all classes except hlist
			local cc = {}
			local vlist = false
			for _, v in ipairs(classes[i].c) do
				if v == classNavboxColumn then
					vlist = true
					table.insert(cc, v)
				elseif (v ~= classHList) then
					table.insert(cc, v)
				end
			end
			
			if needsParity and not vlist then
				-- restore or update missing hlist
				table.insert(cc, classHList)
			end
			
			local class = 'class="'..table.concat(cc, ' ')..'"'
			table.insert(lists, { s = classes[i].s, e = classes[i].e, c = class })
		end
	end
	
	return lists
end

function adjustHListAndParity(classes, oddParity, navboxOdd, navboxEven)
	local lists = {}
	for i = 1, #classes do
		if classes[i].list then
			local needsParity = i >= #classes or classes[i + 1].list
			
			-- copy all classes except navbox parity markers
			local cc = {}
			local vlist = false
			local fixed = false
			for _, v in ipairs(classes[i].c) do
				if v == classFixedParity then
					fixed = true
					break
				end
			end
			
			for _, v in ipairs(classes[i].c) do
				if v == classHList then
					-- remove
				elseif v == classNavboxColumn then
					vlist = true
					table.insert(cc, v)
				elseif fixed or (v ~= classNavboxOdd) and (v ~= classNavboxEven) then
					table.insert(cc, v)
				end
			end
			
			if needsParity then
				if not vlist then
					table.insert(cc, classHList)
				end
				
				if not fixed then
					table.insert(cc, oddParity and navboxOdd or navboxEven)
					if navboxOdd == navboxEven then
						table.insert(cc, classFixedParity)
					end
					
					oddParity = not oddParity
				end
			end
			
			local class = 'class="'..table.concat(cc, ' ')..'"'
			table.insert(lists, { s = classes[i].s, e = classes[i].e, c = class })
		end
	end
	
	return lists
end

function apply(text, lists)
	local fragments = {}
	local start = 1
	for _, l in ipairs(lists) do
		table.insert(fragments, mw.ustring.sub(text, start, l.s-1))
		table.insert(fragments, l.c)
		start = l.e + 1
	end
	
	table.insert(fragments, mw.ustring.sub(text, start, #text))
	return table.concat(fragments, "")
end

function makeClasses(classes, name)
	local valid = {}
	for _, v in ipairs(validExtraClasses) do
		valid[v] = true
	end
	
	local added = {}
	local result = {
		"v2",
		"do-not-make-smaller"
	}

	for c in string.gmatch(classes, "%S+") do
		if valid[c] and not added[c] then
			table.insert(result, c)
			added[c] = true
		end
	end
	
	if #name > 0 then
		table.insert(result, mw.uri.anchorEncode("navbox-name-"..mw.getContentLanguage():lcfirst(name)))
	end
	
	return table.concat(result, " ")
end

function unwrap(text, makeChild)
	if not text or (#text < (#navboxFramePrefixStart + #navboxFramePrefixEnd + #navboxFrameSuffix)) then
		-- za krótki text
		return text
	end
	
	if string.sub(text, 1, #navboxFramePrefixStart) ~= navboxFramePrefixStart then
		-- brak początku prefiksu
		return text
	end
	
	local ps, pe = string.find(text, navboxFramePrefixEnd, #navboxFramePrefixStart, true)
	if not pe then
		-- brak końca prefiksu
		return text
	end
	
	local gs, ge = string.find(text, classNavboxInner, pe, true)
	if not gs then
		-- brak klasy wnętrza navboksu
		return text
	end

	local ss, se = string.find(text, navboxFrameSuffix, ge, true)
	if not se then
		-- brak sufiksu
		return text
	end
		
	while true do
		local s, e = string.find(text, navboxFrameSuffix, se, true)
		if not s then
			-- brak następnego sufixu
			break
		end
		
		-- znalazłem kolejny sufix
		ss = s
		se = e
	end
	
	local result = {}
	if makeChild then
		table.insert(result, string.sub(text, pe+1, gs-1))
		table.insert(result, classNavboxSubGroup)
		table.insert(result, string.sub(text, ge+1, ss-1))
	else
		table.insert(result, string.sub(text, pe+1, ss-1))
	end
	
	table.insert(result, string.sub(text, se+1, #text))
	local innerText = table.concat(result, "")
	if makeChild then
		-- trim generic category
		innerText = mw.ustring.gsub(innerText, "%[%[ *"..genericCategory.." *|.-%]%]", "")
		innerText = mw.ustring.gsub(innerText, "%[%[ *"..genericCategory.." *%]%]", "")
	end
	
	return innerText
end

function wrap(text, classes, name, width)
	if not text then
		return text
	end

	local result = {}
	table.insert(result, navboxFramePrefixStart)
	table.insert(result, makeClasses(classes, name))
	table.insert(result, '"')
	if #width > 0 then
		table.insert(result, ' style="width: ')
		table.insert(result, width)
		table.insert(result, ';"')
	end
	
	table.insert(result, navboxFramePrefixEnd)
	table.insert(result, text)
	table.insert(result, navboxFrameSuffix)
	return table.concat(result, "")
end

return {
	
Finish = function(frame)
	local parity = frame:getParent().args[parityTemplateArg]
	local text = wrap(
		unwrap(frame.args and frame.args[1] or frame[1], false),
		frame:getParent().args[classesTemplateArg] or "",
		frame:getParent().args[nameTemplateArg] or "",
		frame:getParent().args[widthTemplateArg] or ""
	)
	if not text then
		return text
	end
	
	local classes = findNavboxClasses(text)
	if #classes <= 0 then
		return text
	end
	
	local oddParity = parity ~= paritySwapped
	local navboxOdd = parity == parityEven and classNavboxEven or classNavboxOdd
	local navboxEven = parity == parityOdd and classNavboxOdd or classNavboxEven

	local lists = parity == parityOff
		and adjustHList(classes)
		or adjustHListAndParity(classes, oddParity, navboxOdd, navboxEven)
	if #lists <= 0 then
		return text
	end
	
	return apply(text, lists)
end,

Iterate = function(frame)
	local minimumIndex = tonumber(frame.args[minimumIndexArgName]) or 1
	local maximumIndex = tonumber(frame.args[maximumIndexArgName]) or 30
	local listOnlyTemplate = frame.args[listOnlyTemplateArgName]
	local listGroupTemplate = frame.args[listGroupTemplateArgName] or listOnlyTemplate
	local result = {}
	for i = minimumIndex, maximumIndex do
		local listArgName = string.format(listArgFormat, i)
		local groupArgName = string.format(groupArgFormat, i)
		local listArg = frame:getParent().args[listArgName] or ""
		if #listArg > 0 then
			local groupArg = frame:getParent().args[groupArgName] or ""
			local template = #groupArg > 0 and listGroupTemplate or listOnlyTemplate
			local repl = {
				[listTemplateArg] = unwrap(listArg, true),
				[groupTemplateArg] = groupArg,
				[indexTemplateArg] = tostring(i),
			}
			local text, _ = mw.ustring.gsub(template, "{{{(%w+)}}}", repl)
			table.insert(result, text)
		end
	end
	
	return table.concat(result, "")
end,

ColSpan = function(frame)
	local colspan = tonumber(frame.args[1]) or 0
	local minimumIndex = tonumber(frame.args[minimumIndexArgName]) or 1
	local maximumIndex = tonumber(frame.args[maximumIndexArgName]) or 30
	local result = {}
	for i = minimumIndex, maximumIndex do
		local listArgName = string.format(listArgFormat, i)
		local groupArgName = string.format(groupArgFormat, i)
		local listArg = frame:getParent().args[listArgName] or ""
		if #listArg > 0 then
			local groupArg = frame:getParent().args[groupArgName] or ""
			if #groupArg > 0 then
				colspan = colspan + 1
				break
			end
		end
	end
	
	return colspan > 1 and string.format('colspan="%d"', colspan) or ""
end,

TableRow = function(frame)
	local args = frame:getParent().args
	return mw.text.jsonEncode({
		[tableContentGroupName] = args[tableContentGroupName],
		[1] = args[1],
		[2] = args[2],
		[3] = args[3],
		[4] = args[4],
		[5] = args[5],
		[6] = args[6],
		[7] = args[7],
		[8] = args[8],
		[9] = args[9],
	})
end,

Table = function(frame)
	
	local args = frame:getParent().args
	
	function getNonEmpty(name)
		local result = args[name]
		return (result and (#result > 0)) and result or false
	end
	
	local header = {
		h = getNonEmpty(tableHeaderGroupArgName),
		s = getNonEmpty(tableHeaderGroupWidthArgName) or tableHeaderDefaultGroupWidth,
	}
	
	function fakeParity(row, col)
		return nil
	end
	
	function horizontalParity(row, col)
		return (row % 2) == 0 and classNavboxEven or classNavboxOdd
	end
	
	function verticalParity(row, col)
		return (col % 2) == 0 and classNavboxEven or classNavboxOdd
	end
	
	local parity = getNonEmpty(parityTemplateArg)
	local selectParity
	if parity == tableParityHorizontal then
		selectParity = horizontalParity
	elseif parity == tableParityVertical then
		selectParity = verticalParity
	else
		selectParity = fakeParity
	end
	
	for i = 1, 9 do
		local name = string.format(tableHeaderArgFormat, i)
		local columnTitle = getNonEmpty(name)
		if not columnTitle and (i == 1) then
			return nil
		elseif not columnTitle then
			break
		end
		
		header[i] = columnTitle
	end
	
	local content = getNonEmpty(tableContentArgName)
	if not content then
		return nil
	end
	
	local data = mw.text.jsonDecode("["..content.."false]")
	if not data or not data[1] then
		return nil
	end
	
	local buffer = mw.html.create("table")
		:addClass(classColumnsTable)
		:addClass('v2') -- to jest tylko tymczasowo
	local headerRow = buffer:tag("tr")
	if header.h then
		headerRow:tag("th")
			:addClass(classNavboxAboveBelow)
			:addClass(tableHeaderClassGroup)
			:attr('scope','col')
			:css('width',header.s):wikitext(header.h)
	end
	for i, columnTitle in ipairs(header) do
		headerRow:tag("th")
			:addClass(classNavboxAboveBelow)
			:addClass(string.format(tableHeaderClassListFormat, i))
			:attr('scope','col')
			:wikitext(columnTitle)
	end
	for i, values in ipairs(data) do
		if not values then
			break
		end
		
		local dataRow = buffer:tag("tr")
		if header.h then
			dataRow:tag("th")
				:addClass(classNavboxGroup)
				:addClass(string.format(tableRowClassGroupFormat, i))
				:attr('scope','row')
				:wikitext(values[tableContentGroupName] or string.format("%d. {{{%s}}}", i, tableContentGroupName))
		end
		
		for j, columnTitle in ipairs(header) do
			local cell = dataRow:tag("td")
				:addClass(classNavboxColumn)
				:addClass(string.format(tableRowClassListFormat, i, j))
				:wikitext(values[j] or string.format("%d. {{{%d}}}", i, j))
			local parity = selectParity(i, j)
			if parity then
				cell
					:addClass(classFixedParity)
					:addClass(parity)
			end
		end
	end
	
	return tostring(buffer)
end,

}