Module:Template parameter value

From wikiNonStop
Module documentation

Implements {{Template parameter value }} and {{HasTemplate }}, and can be used from other modules.

Module functions

getParameter

getParameter takes 4 arguments: The page name (string), the template/s (string or table of strings), the parameter (string), and an optional options table. It will return either true and the contents of the requested parameter or false and a reason for failure.

The following options are available:

  • template_index: Which occurance of the template to look for the parameter in (default: 1)
  • parameter_index: Which occurance of the parameter to look for (default: 1; only applies when ignore_subtemplates is false)
  • ignore_subtemplates: If parameters should only be searched for in the top-level template, ignoring the parameters in subtemplates (default: false)
  • only_subtemplates: If parameters should only be searched for in subtemplates of the top-level template (default: false)
  • ignore_blank: Whether or not blank values should count towards parameter_index (default: false)
  • treat_as_regex: Whether or not the template string(s) should be treated as a lua regex (default: false)

getTemplate

getTemplate takes 3 arguments: The page name (string), the template/s (string or table of strings), and an optional options table. It will return either true and the text of the requested template or false and a reason for failure.

getTemplate supports the options template_index and treat_as_regex options from getParameter.

Helper functions

The module exposes some of the helper functions used (matchAllTemplates, getParameters, and getAllParameters) for convenience. Each function has some comments above it in the code explaining its rough purpose.

Template functions

main implements {{Template parameter value }} and acts as a template wrapper for getParameter.

hasTemplate implements {{HasTemplate }} and somewhat acts as a wrapper for getTemplate (it only provides if the template was found, not the template text itself).

Testcases

Testcases are available at Module talk:Template parameter value/testcases

local p = {}
local PrepareText = require("Module:Wikitext Parsing").PrepareText

local function getTitle(title)
	local success, titleObj = pcall(mw.title.new, title)
	if success then return titleObj
	else return nil end
end

--string.gmatch will check the largest block it can without re-scanning whats inside, but we need whats inside
local function matchAllTemplates(str)
	local matches = {}
	for template in string.gmatch(str, "{%b{}}") do
		table.insert(matches, template)
		local innerContent = string.sub(template, 3, -3)
		for _,subtemplate in next,matchAllTemplates(innerContent) do
			table.insert(matches, subtemplate)
		end
	end
	return matches
end

--Forked version of getParameters from [[Module:Transcluder]] with extra features removed
local function escapeString(str)
	return string.gsub(str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0')
end
local function getParameters(template)
	local parameters, parameterOrder = {}, {}
	local params = string.match(template, '{{[^|}]-|(.*)}}')
	if params then
		local count = 0
		-- Temporarily replace pipes in subtemplates and wikilinks to avoid chaos
		for subtemplate in string.gmatch(params, '{%b{}}') do
			params = string.gsub(params, escapeString(subtemplate), string.gsub(subtemplate, ".", {["%"]="%%", ["|"]="@@:@@", ["="]="@@_@@"}) )
		end
		for wikilink in string.gmatch(params, '%[%b[]%]') do
			params = string.gsub(params, escapeString(wikilink), string.gsub(wikilink, ".", {["%"]="%%", ["|"]="@@:@@", ["="]="@@_@@"}) )
		end
		for parameter in mw.text.gsplit(params, '|') do
			local parts = mw.text.split(parameter, '=')
			local key = mw.text.trim(parts[1])
			local value
			if #parts == 1 then
				value = key
				count = count + 1
				key = tostring(count)
			else
				value = mw.text.trim(table.concat(parts, '=', 2))
			end
			value = string.gsub(string.gsub(value, '@@:@@', '|'), '@@_@@', '=')
			key = string.gsub(string.gsub(key, '@@:@@', '|'), '@@_@@', '=')
			table.insert(parameterOrder, key)
			parameters[key] = value
		end
	end
	return parameters, parameterOrder
end

-- Returns a table containing parameters and a table with the order in which each of their values were found.
-- Since this considers all subtemplates, a single parameter is expected to have multiple values.
-- E.g. {{ABC|X={{DEF|X=Value|Y=Other value}}{{ABC|X=Yes}}|Y=P}}
-- Would return {X={"{{DEF|X=Value|Y=Other value}}", "Value", "Yes"}, Y={"Other value", "P"}}
local function getAllParameters(template, ignore_blank, only_subtemplates)
	local parameterTree = setmetatable({}, {
		__index = function(self,key)
			rawset(self,key,{})
			return rawget(self,key)
		end
	})
	local params, paramOrder = getParameters(template)
	for _,key in ipairs(paramOrder) do
		local value = params[key]
		if not ignore_blank or value ~= "" then
			if not only_subtemplates then
				table.insert(parameterTree[key], value) --Insert the initial value into the tree
			end
			for subtemplate in string.gmatch(value, "{%b{}}") do --And now check for subvalues
				local subparams = getAllParameters(subtemplate, ignore_blank)
				for subkey,subset in next,subparams do
					for _,subvalue in ipairs(subset) do
						table.insert(parameterTree[subkey], subvalue) --And add any we find to our tree
					end
				end
			end
		end
	end
	return parameterTree
end

--Module entry point. Returns a success boolean and either the target template or why it failed
function p.getTemplate(page, templates, options)
	if not templates then --Required parameters
		return false, "Missing required parameter 'templates'"
	end
	options = options or {}
	
	local template_index = tonumber(options.template_index) or 1
	local treat_as_regex = options.treat_as_regex or false
	if type(templates) == "string" then
		-- TODO: Find a good way to allow specifying multiple templates via template invocation
		-- (Modules can just provide a table so no concerns there)
		-- Comma splitting is a bad idea (lots of templates have a comma in their name)
		templates = {templates}
	end
	
	local title = getTitle(page)
	if title == nil then
		return false, "Requested title doesn't exist"
	end
	local content = PrepareText(title:getContent() or "")
	
	local foundTemplates = 0
	for _,template in next,matchAllTemplates(content) do
		for _,wantedTemplate in pairs(templates) do
			if not treat_as_regex then
				wantedTemplate = escapeString(wantedTemplate)
			end
			local firstLetter = string.sub(wantedTemplate, 1, 1)
			local firstUpper, firstLower = firstLetter:upper(), firstLetter:lower()
			if firstUpper ~= firstLower then
				wantedTemplate = "[" .. firstUpper .. firstLower .. "]" .. string.sub(wantedTemplate, 2)
			end
			if string.match(template, "^{{%s*"..wantedTemplate.."%s*[|}]") then
				foundTemplates = foundTemplates + 1
				if foundTemplates == template_index then --Found our wanted template
					return true, template
				end
			end
		end
	end
	return false, "No valid template found"
end

--Module entry point. Returns a success boolean and either the target parameter's value or why it failed
function p.getParameter(page, templates, parameter, options)
	if not (templates and parameter) then --Required parameters
		return false, "Missing required parameters 'templates' and 'parameter'"
	end
	parameter = tostring(parameter) --Force consistency
	options = options or {}
	
	local success, text = p.getTemplate(page, templates, options)
	if not success then
		return success, text
	else
		local parameter_index = tonumber(options.parameter_index) or 1
		local ignore_subtemplates = options.ignore_subtemplates or false
		local only_subtemplates = options.only_subtemplates or false
		local ignore_blank = options.ignore_blank or false

		local value
		if ignore_subtemplates then
			value = getParameters(text)[parameter] or ""
		else
			local params = getAllParameters(text, ignore_blank, only_subtemplates)
			value = params[parameter][parameter_index] or ""
		end

		value = string.gsub(value, "</?%a*include%a*>", "")
		value = mw.text.trim(value) --technically wrong in some cases but not a big issue
		return true, mw.text.decode(value) --decode due to PrepareText
	end
end

--Template entry point. Returns either "yes" or nothing depending on if the wanted template is found
--Will return error text if no template is provided
function p.hasTemplate(frame)
	local args = require('Module:Arguments').getArgs(frame)
	local yesno = require("Module:Yesno")
	local page = args[1] or args.page
	local template = args[2] or args.template
	local template_index = tonumber(args[3] or args.N) or 1
	if not template or template == "" then
		return '<span class="error">No template provided for hasTemplate</span>'
	end
	local follow = yesno(args.follow) or false
	if follow then
		page = require("Module:Redirect").luaMain(page)
	end
	local options = {
		template_index = template_index,
		treat_as_regex = yesno(args.treat_as_regex) or false,
	}

	local success, _ = p.getTemplate(page, template, options)
	return success and "yes" or ""
end

--Template entry point for getParameter. Returns an empty string upon failure
function p.main(frame)
	local args = require('Module:Arguments').getArgs(frame, {
		wrappers = 'Template:Template parameter value'
	})
	local yesno = require("Module:Yesno")
	local options = {
		template_index = args[3] or args.template_index,
		parameter_index = args[5] or args.parameter_index,
		ignore_subtemplates = yesno(args.ignore_subtemplates or args.ist) or false,
		only_subtemplates = yesno(args.only_subtemplates) or false,
		ignore_blank = yesno(args.ignore_blank) or false,
		treat_as_regex = yesno(args.treat_as_regex) or false,
	}
	local page = args[1] or args.page
	local template = args[2] or args.template
	local parameter = args[4] or args.parameter
	local success, result = p.getParameter(page, template, parameter, options)
	if not success then
		return ""
	else
		if args.dontprocess then
			return result
		else
			return frame:preprocess(result)
		end
	end
end

--Backwards compatability
p.getValue = p.getParameter
--Potentially useful module entry points
p.matchAllTemplates = matchAllTemplates
p.getParameters = getParameters
p.getAllParameters = getAllParameters

return p