Module:Navbox with collapsible groups/sandbox

-- This module implements {{Navbox with collapsible groups}}
-- Eventually this should be merged with [[Module:Navbox]] and replaced with
--[[
local Navbox = require('Module:Navbox/sandbox')
local p = {
	_navbox = function (frame) return Navbox._withCollapsibleGroups(frame) end,
	navbox = function (frame) return Navbox['with collapsible groups'](frame) end
}
return p
]]--

local q = {}

local Navbox = require('Module:Navbox')
local inArray = require("Module:TableTools").inArray

local parentCfg = mw.loadData('Module:Navbox/configuration')
local thisCfg = mw.loadData('Module:Navbox with collapsible groups/configuration')
local cfg = {}
for k, v in pairs(thisCfg) do
	if type(v) == 'table' then
		cfg[k] = {}
		if type(parentCfg[k]) == 'table' then
			for kk, vv in pairs(parentCfg[k]) do cfg[k][kk] = vv end
		end
		for kk, vv in pairs(v) do cfg[k][kk] = vv end
	end
end

-- helper functions
local function concatstrings(s)
	local r = table.concat(s, '')
	if r:match('^%s*$') then r = nil end
	return r
end

local function concatstyles(s)
	local r = table.concat(s, ';')
	while r:match(';%s*;') do
		r = mw.ustring.gsub(r, ';%s*;', ';')
	end
	if r:match('^%s*;%s*$') then r = nil end
	return r
end

local function andnum(s, n)
	return string.format(cfg.arg[s..'_and_num'], n)
end


function q._navbox(pargs)
	local function getChild(listnum, listText)
		local childArgs = {
			[cfg.arg.border] = cfg.keyword.border_subgroup,
			[cfg.arg.navbar] = cfg.keyword.navbar_plain
		}
		local hasChildArgs = false
		for k, v in pairs(pargs) do
			k = tostring(k)
			for _, w in ipairs(cfg.keyword.subgroups) do
				w = w .. listnum .. "_"
				if (#k > #w) and (k:sub(1, #w) == w) then
					childArgs[k:sub(#w + 1)] = v
					hasChildArgs = true
				end
			end
		end
		return hasChildArgs and Navbox._navbox(childArgs) or listText
	end
	
	-- table for args passed to navbox
	local targs = {}

	-- process args
	local passthrough = {
		[cfg.arg.name]=true,[cfg.arg.navbar]=true,[cfg.arg.state]=true,
		[cfg.arg.border]=true,[cfg.arg.bodyclass]=true,[cfg.arg.groupclass]=true,
		[cfg.arg.listclass]=true,[cfg.arg.style]=true,[cfg.arg.bodystyle]=true,
		[cfg.arg.basestyle]=true,[cfg.arg.title]=true,[cfg.arg.titleclass]=true,
		[cfg.arg.titlestyle]=true,[cfg.arg.above]=true,[cfg.arg.aboveclass]=true,
		[cfg.arg.abovestyle]=true,[cfg.arg.below]=true,[cfg.arg.belowclass]=true,
		[cfg.arg.belowstyle]=true,[cfg.arg.image]=true,[cfg.arg.imageclass]=true,
		[cfg.arg.imagestyle]=true,[cfg.arg.imageleft]=true,[cfg.arg.imageleftstyle]=true
	}
	for k,v in pairs(pargs) do
		if k and type(k) == 'string' then
			if passthrough[k] then
				targs[k] = v
			elseif (k:match(cfg.pattern.num)) then
				local n = mw.ustring.gsub(k, cfg.pattern.num, '%1')
				local list_and_num = andnum('list', n)
				if ((k:match(cfg.pattern.listnum) or k:match(cfg.pattern.contentnum))
						and targs[list_and_num] == nil
						and pargs[andnum('group', n)] == nil
						and pargs[andnum('sect', n)] == nil
						and pargs[andnum('section', n)] == nil) then
					targs[list_and_num] = concatstrings({
						pargs[list_and_num] or '',
						pargs[andnum('content', n)] or ''
					})
					if (targs[list_and_num] and inArray(cfg.keyword.subgroups, targs[list_and_num])) then
						targs[list_and_num] = getChild(n, targs[list_and_num])
					end
				elseif ((k:match(cfg.pattern.groupnum) or k:match(cfg.pattern.sectnum) or k:match(cfg.pattern.sectionnum))
						and targs[list_and_num] == nil) then
					local titlestyle = concatstyles({
						pargs[cfg.arg.groupstyle] or '',
						pargs[cfg.arg.secttitlestyle] or '', 
						pargs[andnum('groupstyle', n)] or '', 
						pargs[andnum('sectiontitlestyle', n)] or ''
					})
					local liststyle = concatstyles({
						pargs[cfg.arg.liststyle] or '',
						pargs[cfg.arg.contentstyle] or '', 
						pargs[andnum('liststyle', n)] or '', 
						pargs[andnum('contentstyle', n)] or ''
					})
					local title = concatstrings({
						pargs[andnum('group', n)] or '',
						pargs[andnum('sect', n)] or '',
						pargs[andnum('section', n)] or ''
					})
					local list = concatstrings({
						pargs[list_and_num] or '', 
						pargs[andnum('content', n)] or ''
					})
					if list and inArray(cfg.keyword.subgroups, list) then
						list = getChild(n, list)
					end
					local abbr_and_num = andnum('abbr', n)
					local state = (pargs[abbr_and_num] and pargs[abbr_and_num] == pargs[cfg.arg.selected]) 
						and cfg.keyword.state_uncollapsed
						or (pargs[andnum('state', n)] or cfg.keyword.state_collapsed)
					
					targs[list_and_num] = Navbox._navbox({
						cfg.keyword.border_child,
						[cfg.arg.navbar] = cfg.keyword.navbar_plain,
						[cfg.arg.state] = state,
						[cfg.arg.basestyle] = pargs[cfg.arg.basestyle],
						[cfg.arg.title] = title,
						[cfg.arg.titlestyle] = titlestyle,
						[andnum('list', 1)] = list,
						[cfg.arg.liststyle] = liststyle,
						[cfg.arg.listclass] = pargs[andnum('listclass', n)],
						[cfg.arg.image] = pargs[andnum('image', n)],
						[cfg.arg.imageleft] = pargs[andnum('imageleft', n)],
						[cfg.arg.listpadding] = pargs[cfg.arg.listpadding]
					})
				end
			end
		end
	end
	-- ordering of style and bodystyle
	targs[cfg.arg.style] = concatstyles({targs[cfg.arg.style] or '', targs[cfg.arg.bodystyle] or ''})
	targs[cfg.arg.bodystyle] = nil
	
	-- child or subgroup
	if targs[cfg.arg.border] == nil then targs[cfg.arg.border] = pargs[1] end

	return Navbox._navbox(targs)
end

function q.navbox(frame)
	local function readArgs(args, prefix)
		-- Read the arguments in the order they'll be output in, to make references
		-- number in the right order.
		local _
		_ = args[prefix .. cfg.arg.title]
		_ = args[prefix .. cfg.arg.above]
		-- Limit this to 20 as covering 'most' cases (that's a SWAG) and because
		-- iterator approach won't work here
		for i = 1, 20 do
			_ = args[prefix .. andnum('group', i)]
			if inArray(cfg.keyword.subgroups, args[prefix .. andnum('list', i)]) then
				for _, v in ipairs(cfg.keyword.subgroups) do
					readArgs(args, prefix .. v .. i .. "_")
				end
			end
		end
		_ = args[prefix .. cfg.arg.below]
	end
	
	local pargs = require('Module:Arguments').getArgs(frame, {wrappers = {cfg.pattern.templatename}})
	readArgs(pargs, "")
	return q._navbox(pargs)
end

return q