Please see the template page instead.



-- Categorizes content and display them.
-- Improvements are welcomed.

-- requires

-- forward declarations
local index;
local process_key_value;
local build_keys;
local build_values;
local as_key_content_function;
local as_value_content_function;
local as_fold_function;
local escape_replacement;
local unstrip_and_strip_nowiki_tags;

-- exposed: categorizes content and display them
-- 'folder', 'table_*', 'content'
index = function(frame)
	local args = build_args(frame)
	
	local folder = as_fold_function(args.folder)
	local keys, key_order = build_keys(args)
	local values = build_values(args.content)
	
	local result = ""
	-- ordered first
	for _, categories in ipairs(key_order) do
		for _, category in ipairs(categories) do
			local processed = process_key_value(keys, values, category)
			if processed then
				result = folder(result, processed)
				-- remove key and value to mark as processed
				keys[category] = nil
				values[category] = nil
			end
		end
	end
	-- unordered last
	for category, _ in pairs(keys) do
		local processed = process_key_value(keys, values, category)
		if processed then
			result = folder(result, processed)
		end
	end
	
	return frame:getParent():preprocess(result) -- intended to be used with a template
end

-- process key and value for a given category
process_key_value = function(keys, values, category)
	local key = keys[category]
	if key then
		local value = values[category]
		if not value then
			value = function(category)
				return ""
			end
		end
		local result = key(value(category))
		return result
	end
	return nil
end

-- builds arguments
-- reads parent frame
build_args = function(frame)
	local result = {}
	
	for key, value in pairs(frame.args) do
		result[mw.text.trim(key)] = unstrip_and_strip_nowiki_tags(value)
	end
	local pframe = frame:getParent()
	for key, value in pairs(pframe.args) do
		result[mw.text.trim(key)] = unstrip_and_strip_nowiki_tags(value)
	end
	
	return result
end

-- builds key mapping, key order mapping
-- reads 'table_*'
build_keys = function(args)
	local keys = {}
	local key_order = {}
	
	for arg_k, arg_v in pairs(args) do
		if arg_k:match("^table_") then
			local key = arg_k:gsub("table_", "", 1)
			local order = key:match("^(%d+)_")
			if order then
				key = key:gsub(order .. "_", "", 1)
				local order_list = key_order[order]
				if order_list then
					table.insert(order_list, key)
				else
					key_order[order] = {key}
				end
			end
			keys[key] = as_key_content_function(arg_v)
		end
	end
	
	-- sort key order
	local key_order_list = {}
	for order, category in pairs(key_order) do
		table.insert(key_order_list, {key=tonumber(order), value=category})
	end
	table.sort(key_order_list, function(a, b) return a.key < b.key end)
	key_order = {}
	for _, value in ipairs(key_order_list) do
		table.insert(key_order, value.value)
	end
	
	return keys, key_order
end

-- builds values mapping
-- magic words: '__M_INDEX__'
-- syntax: '__M_INDEX__ [category1] [category2] ... [categoryN]'
build_values = function(text)
	-- need cleanup
	local result = {}
	local context = {}
	local content = ""
	
	for _, line in ipairs(mw.text.split(text, "\n", true)) do -- is \n sufficient
		if line:match("^__M_INDEX__") then
			-- finish result
			for _, category in ipairs(context) do
				local sub_result = result[category]
				if sub_result == nil then
 					result[category] = content
 				else
 					result[category] = sub_result .. content
 				end
			end
			content = ""
			
			-- change context
			context = {}
			for category in line:gmatch("%[([^%]]*)%]") do
				table.insert(context, category)
			end
		else
			-- append content
			content = content .. line .. "\n"
		end
	end
	
	-- one more round of finishing result
	for _, category in ipairs(context) do
		local sub_result = result[category]
		if sub_result == nil then
 			result[category] = content
 		else
 			result[category] = sub_result .. content
 		end
	end
	
	-- turn into content function
	for key, value in pairs(result) do
		result[key] = as_value_content_function(value)
	end
	
	return result
end

-- converts text to key content function
-- magic words: '__M_CONTENT__'
as_key_content_function = function(text)
	local function func(content)
		local result = text:gsub("__M_CONTENT__", escape_replacement(content)) -- need this variable to only return 1 result
		return result
	end
	return func
end

-- converts text to value content function
-- magic words: '__M_CATEGORY__'
as_value_content_function = function(text)
	local function func(category)
		local result = text:gsub("__M_CATEGORY__", escape_replacement(category)) -- need this variable to only return 1 result
		return result
	end
	return func
end

-- converts text to folding function
-- magic words: '__M_LEFT__', '__M_RIGHT__'
as_fold_function = function(text)
	local function func(left, right)
		-- some improvements could be made here
		local result = text:gsub("__M_LEFT__", escape_replacement(left)):gsub("__M_RIGHT__", escape_replacement(right)) -- need this variable to only return 1 result
		return result
	end
	return func
end

-- escapes replacement, replace '%' with '%%'
-- ah, multiple return results cost us headache, apparently multiple return results expand into multiple arguments
escape_replacement = function(text)
	local result = text:gsub("%%", "%%%%") -- need this variable to only return 1 result
	return result
end

unstrip_and_strip_nowiki_tags = function(text)
	local result = mw.text.trim(mw.text.unstripNoWiki(text)) -- need this variable to only return 1 result
			:gsub("<nowiki>", "")
			:gsub("</nowiki>", "")
			:gsub("&lt;", "<") -- needs to be escaped in the source
			:gsub("&gt;", ">")
			:gsub("&quot;", '"')
	return result
end

return {index = index}