Documentation for this module may be created at မဝ်ဂျူ:get IDs/doc

local export = {}

local parameters_module = "Module:parameters"
local string_nowiki_module = "Module:string/nowiki"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local template_parser_module = "Module:template parser"

local anchor_encode = mw.uri.anchorEncode
local byte = string.byte
local concat = table.concat
local format = string.format
local get -- Defined below.
local gsub = string.gsub
local lower = string.lower
local new_title = mw.title.new
local normalize_anchor -- Defined below.
local pcall = pcall
local require = require

--[==[
Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]==]
	local function class_else_type(...)
		class_else_type = require(template_parser_module).class_else_type
		return class_else_type(...)
	end
	
	local function decode_entities(...)
		decode_entities = require(string_utilities_module).decode_entities
		return decode_entities(...)
	end
	
	local function nowiki(...)
		nowiki = require(string_nowiki_module)
		return nowiki(...)
	end
	
	local function parse(...)
		parse = require(template_parser_module).parse
		return parse(...)
	end
	
	local function process_params(...)
		process_params = require(parameters_module).process
		return process_params(...)
	end

--[==[
Loaders for objects, which load data (or some other object) into some variable, which can then be accessed as "foo or get_foo()", where the function get_foo sets the object to "foo" and then returns it. This ensures they are only loaded when needed, and avoids the need to check for the existence of the object each time, since once "foo" has been set, "get_foo" will not be called again.]==]
	local parameters_data
	local function get_parameters_data()
		parameters_data, get_parameters_data = mw.loadData("Module:parameters/data"), nil
		return parameters_data
	end
	
	local template_names
	local function get_template_names()
		template_names = require(table_module).listToSet{
			"anchor",
			"etymid",
			"etymon",
			"senseid",
			"trans-see",
			"trans-top",
			"trans-top-also"
		}
		get_template_names = nil
		return template_names
	end

function export.normalize_anchor(str)
	return decode_entities(anchor_encode(str))
end
normalize_anchor = export.normalize_anchor

local function handle_heading(name, seen, ids)
	-- Repeated IDs are disambiguated by appending _N, where N is an
	-- incremented integer. When determining this, ASCII characters are case-
	-- insensitive, but all other characters are case-sensitive.
	local check = lower(name)
	if seen[check] then
		local i, name_lower = 1, check
		repeat
			i = i + 1
			check = name_lower .. "_" .. i
		until not seen[check]
		name = name .. "_" .. i
	end
	seen[check] = true
	ids[#ids + 1] = name
end

local function pseudo_percent_encode(ch)
	return format(".%02X", byte(ch))
end

local function handle_template(name, node, ids)
	local args, success = node:get_arguments()
	success, args = pcall(process_params, args, (parameters_data or get_parameters_data())[name])
	if not success then
		return
	elseif name == "anchor" then
		args = args[1]
		for i = 1, #args do
			ids[#ids + 1] = normalize_anchor(args[i])
		end
		return
	end
	local id1, id2
	if name == "trans-top" or name == "trans-see" or name == "trans-top-also" then
		id1 = "Translations-"
		if name == "trans-top" then
			id2 = args.id or args[1]
		else
			id2 = args.id.default or args[1]
		end
	else
		id1 = args[1]:getCanonicalName() .. ": "
		id2 = args[name == "etymon" and "id" or 2]
	end
	if id2 then
		ids[#ids + 1] = normalize_anchor(id1 .. id2)
	end
end

function export.get(pagename, force_transclusion)
	local page, seen, ids = new_title(pagename), {}, {}
	
	for node in parse((page.redirectTarget or page):getContent() or error("Could not retrieve the page content of \"" .. pagename .. "\".")):iterate_nodes() do
		local node_class = class_else_type(node)
		if node_class == "heading" then
			local name = node:get_anchor()
			if name ~= nil then
				handle_heading(name, seen, ids)
				-- Headings get a fallback ID, which uses pseudo-percent-encoding
				-- where "%" is replaced by "." (e.g. "!" is ".21").
				local name2 = gsub(name, "[^%w%-.:_]", pseudo_percent_encode)
				if name2 ~= name then
					handle_heading(name2, seen, ids)
				end
			end
		elseif node_class == "template" then
			local name = node:get_name(nil, force_transclusion)
			if (template_names or get_template_names())[name] then
				handle_template(name, node, ids)
			end
		end
	end
	
	return ids
end
get = export.get

-- Used by [[MediaWiki:Gadget-OrangeLinks.js]].
function export.show(frame)
	local output = {}
	
	for i, pagename in ipairs(frame.args) do
		output[i] = nowiki(concat(get(pagename, true), " "))
	end
	
	return concat(output, "\n\n")
end

return export