<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://themidnight.wiki/index.php?action=history&amp;feed=atom&amp;title=Module%3AParameter_validation</id>
	<title>Module:Parameter validation - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://themidnight.wiki/index.php?action=history&amp;feed=atom&amp;title=Module%3AParameter_validation"/>
	<link rel="alternate" type="text/html" href="https://themidnight.wiki/index.php?title=Module:Parameter_validation&amp;action=history"/>
	<updated>2026-04-03T19:13:29Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.41.1</generator>
	<entry>
		<id>https://themidnight.wiki/index.php?title=Module:Parameter_validation&amp;diff=6828&amp;oldid=prev</id>
		<title>Timothy: 1 revision imported</title>
		<link rel="alternate" type="text/html" href="https://themidnight.wiki/index.php?title=Module:Parameter_validation&amp;diff=6828&amp;oldid=prev"/>
		<updated>2023-06-04T03:55:12Z</updated>

		<summary type="html">&lt;p&gt;1 revision imported&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 23:55, 3 June 2023&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-notice&quot; lang=&quot;en&quot;&gt;&lt;div class=&quot;mw-diff-empty&quot;&gt;(No difference)&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</summary>
		<author><name>Timothy</name></author>
	</entry>
	<entry>
		<id>https://themidnight.wiki/index.php?title=Module:Parameter_validation&amp;diff=6827&amp;oldid=prev</id>
		<title>wikipedia&gt;ProcrastinatingReader: doc was moved into actual doc page</title>
		<link rel="alternate" type="text/html" href="https://themidnight.wiki/index.php?title=Module:Parameter_validation&amp;diff=6827&amp;oldid=prev"/>
		<updated>2021-05-08T15:21:49Z</updated>

		<summary type="html">&lt;p&gt;doc was moved into actual doc page&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;local util = {&lt;br /&gt;
	empty = function( s ) &lt;br /&gt;
		return s == nil  or type( s ) == &amp;#039;string&amp;#039; and mw.text.trim( s ) == &amp;#039;&amp;#039;   &lt;br /&gt;
	end&lt;br /&gt;
	, &lt;br /&gt;
	extract_options = function ( frame, optionsPrefix )&lt;br /&gt;
		optionsPrefix = optionsPrefix or &amp;#039;options&amp;#039; &lt;br /&gt;
&lt;br /&gt;
		local options, n, more = {}&lt;br /&gt;
		if frame.args[&amp;#039;module_options&amp;#039;] then&lt;br /&gt;
			local module_options = mw.loadData( frame.args[&amp;#039;module_options&amp;#039;] ) &lt;br /&gt;
			if type( module_options ) ~= &amp;#039;table&amp;#039; then return {} end&lt;br /&gt;
			local title = mw.title.getCurrentTitle()&lt;br /&gt;
			local local_ptions = module_options[ title.namespace ] or module_options[ title.nsText ] or {} &lt;br /&gt;
			for k, v in pairs( local_ptions ) do options[k] = v end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		repeat&lt;br /&gt;
			ok, more = pcall( mw.text.jsonDecode, frame.args[optionsPrefix .. ( n or &amp;#039;&amp;#039; )] )&lt;br /&gt;
			if ok and type( more ) == &amp;#039;table&amp;#039; then&lt;br /&gt;
				for k, v in pairs( more ) do options[k] = v end&lt;br /&gt;
			end&lt;br /&gt;
			n = ( n or 0 ) + 1&lt;br /&gt;
		until not ok&lt;br /&gt;
&lt;br /&gt;
		return options&lt;br /&gt;
	end&lt;br /&gt;
	, &lt;br /&gt;
	build_namelist = function ( template_name, sp )&lt;br /&gt;
		local res = { template_name }&lt;br /&gt;
		if sp then&lt;br /&gt;
			if type( sp ) == &amp;#039;string&amp;#039; then sp = { sp } end&lt;br /&gt;
			for _, p in ipairs( sp ) do table.insert( res, template_name .. &amp;#039;/&amp;#039; .. p ) end&lt;br /&gt;
		end&lt;br /&gt;
		return res&lt;br /&gt;
	end&lt;br /&gt;
	,&lt;br /&gt;
	table_empty = function( t ) -- normally, test if next(t) is nil, but for some perverse reason, non-empty tables returned by loadData return nil...&lt;br /&gt;
		if type( t ) ~= &amp;#039;table&amp;#039; then return true end&lt;br /&gt;
		for a, b in pairs( t ) do return false end&lt;br /&gt;
		return true&lt;br /&gt;
	end&lt;br /&gt;
	,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local function _readTemplateData( templateName ) &lt;br /&gt;
	local title = mw.title.makeTitle( 0, templateName )  &lt;br /&gt;
	local templateContent = title and title.exists and title:getContent() -- template&amp;#039;s raw content&lt;br /&gt;
	local capture =  templateContent and mw.ustring.match( templateContent, &amp;#039;&amp;lt;templatedata%s*&amp;gt;(.*)&amp;lt;/templatedata%s*&amp;gt;&amp;#039; ) -- templatedata as text&lt;br /&gt;
--	capture = capture and mw.ustring.gsub( capture, &amp;#039;&amp;quot;(%d+)&amp;quot;&amp;#039;, tonumber ) -- convert &amp;quot;1&amp;quot;: {} to 1: {}. frame.args uses numerical indexes for order-based params.&lt;br /&gt;
	local trailingComma = capture and mw.ustring.find( capture, &amp;#039;,%s*[%]%}]&amp;#039; ) -- look for ,] or ,} : jsonDecode allows it, but it&amp;#039;s verbotten in json&lt;br /&gt;
	if capture and not trailingComma then return pcall( mw.text.jsonDecode, capture ) end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function readTemplateData( templateName )&lt;br /&gt;
	if type( templateName ) == &amp;#039;string&amp;#039; then &lt;br /&gt;
		templateName = { templateName, templateName .. &amp;#039;/&amp;#039; .. docSubPage }&lt;br /&gt;
	end&lt;br /&gt;
	if type( templateName ) == &amp;quot;table&amp;quot; then&lt;br /&gt;
		for _, name in ipairs( templateName ) do&lt;br /&gt;
			local td, result = _readTemplateData( name ) &lt;br /&gt;
			if td then return result end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- this is the function to be called by other modules. it expects the frame, and then an optional list of subpages, e.g. { &amp;quot;Documentation&amp;quot; }.&lt;br /&gt;
-- if second parameter is nil, only tempalte page will be searched for templatedata.&lt;br /&gt;
function calculateViolations( frame, subpages )&lt;br /&gt;
-- used for parameter type validy test. keyed by TD &amp;#039;type&amp;#039; string. values are function(val) returning bool.&lt;br /&gt;
	local type_validators = { &lt;br /&gt;
		[&amp;#039;number&amp;#039;] = function( s ) return mw.language.getContentLanguage():parseFormattedNumber( s ) end&lt;br /&gt;
	}&lt;br /&gt;
	function compatible( typ, val )&lt;br /&gt;
		local func = type_validators[typ]&lt;br /&gt;
		return type( func ) ~= &amp;#039;function&amp;#039; or util.empty( val ) or func( val )&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local t_frame = frame:getParent()&lt;br /&gt;
	local t_args, template_name = t_frame.args, t_frame:getTitle()&lt;br /&gt;
	template_name = mw.ustring.gsub( template_name, &amp;#039;/sandbox&amp;#039;, &amp;#039;&amp;#039;, 1 )&lt;br /&gt;
	local td_source = util.build_namelist( template_name, subpages )&lt;br /&gt;
	if frame.args[&amp;#039;td_source&amp;#039;] then&lt;br /&gt;
		table.insert(td_source, frame.args[&amp;#039;td_source&amp;#039;])&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local templatedata = readTemplateData( td_source )&lt;br /&gt;
	local td_params = templatedata and templatedata.params&lt;br /&gt;
	local all_aliases, all_series = {}, {}&lt;br /&gt;
&lt;br /&gt;
	if not td_params then return { [&amp;#039;no-templatedata&amp;#039;] = { [&amp;#039;&amp;#039;] = &amp;#039;&amp;#039; } } end&lt;br /&gt;
	-- from this point on, we know templatedata is valid.&lt;br /&gt;
&lt;br /&gt;
	local res = {} -- before returning to caller, we&amp;#039;ll prune empty tables&lt;br /&gt;
&lt;br /&gt;
	-- allow for aliases&lt;br /&gt;
	for x, p in pairs( td_params ) do for y, alias in ipairs( p.aliases or {} ) do&lt;br /&gt;
		p[&amp;#039;primary&amp;#039;] = x&lt;br /&gt;
		td_params[x] = p&lt;br /&gt;
		all_aliases[alias] = p&lt;br /&gt;
		if tonumber(alias) then all_aliases[tonumber(alias)] = p end&lt;br /&gt;
	end end&lt;br /&gt;
&lt;br /&gt;
	-- handle undeclared and deprecated&lt;br /&gt;
	local already_seen = {}&lt;br /&gt;
	local series = frame.args[&amp;#039;series&amp;#039;]&lt;br /&gt;
	for p_name, value in pairs( t_args ) do&lt;br /&gt;
		local tp_param, noval, numeric, table_name = td_params[p_name] or all_aliases[p_name], util.empty( value ), tonumber( p_name )&lt;br /&gt;
		local hasval = not noval&lt;br /&gt;
&lt;br /&gt;
		if not tp_param and series then -- 2nd chance. check to see if series&lt;br /&gt;
			for s_name, p in pairs(td_params) do &lt;br /&gt;
				if mw.ustring.match( p_name, &amp;#039;^&amp;#039; .. s_name .. &amp;#039;%d+&amp;#039; .. &amp;#039;$&amp;#039;) then &lt;br /&gt;
					-- mw.log(&amp;#039;found p_name &amp;#039;.. p_name .. &amp;#039;  s_name:&amp;#039; .. s_name, &amp;#039; p is:&amp;#039;, p) debugging series support&lt;br /&gt;
					tp_param = p &lt;br /&gt;
				end -- don&amp;#039;t bother breaking. td always correct.&lt;br /&gt;
			end 				&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		if not tp_param then -- not in TD: this is called undeclared&lt;br /&gt;
			-- calculate the relevant table for this undeclared parameter, based on parameter and value types&lt;br /&gt;
			table_name = &lt;br /&gt;
				noval and numeric and &amp;#039;empty-undeclared-numeric&amp;#039; or&lt;br /&gt;
				noval and not numeric and &amp;#039;empty-undeclared&amp;#039; or&lt;br /&gt;
				hasval and numeric and &amp;#039;undeclared-numeric&amp;#039; or&lt;br /&gt;
				&amp;#039;undeclared&amp;#039; -- tzvototi nishar.&lt;br /&gt;
		else -- in td: test for deprecation and mistype. if deprecated, no further tests&lt;br /&gt;
			table_name = tp_param.deprecated and hasval and &amp;#039;deprecated&amp;#039; &lt;br /&gt;
				or tp_param.deprecated and noval and &amp;#039;empty-deprecated&amp;#039; &lt;br /&gt;
				or not compatible( tp_param.type, value ) and &amp;#039;incompatible&amp;#039;&lt;br /&gt;
				or not series and already_seen[tp_param] and hasval and &amp;#039;duplicate&amp;#039;&lt;br /&gt;
&lt;br /&gt;
			if hasval and table_name ~= &amp;#039;duplicate&amp;#039; then&lt;br /&gt;
				already_seen[tp_param] = p_name&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		-- report it.&lt;br /&gt;
		if table_name then&lt;br /&gt;
			res[table_name] = res[table_name] or {}&lt;br /&gt;
			if table_name == &amp;#039;duplicate&amp;#039; then&lt;br /&gt;
				local primary_param = tp_param[&amp;#039;primary&amp;#039;]&lt;br /&gt;
				local primaryData = res[table_name][primary_param]&lt;br /&gt;
				if not primaryData then&lt;br /&gt;
					primaryData = {}&lt;br /&gt;
					table.insert(primaryData, already_seen[tp_param])&lt;br /&gt;
				end&lt;br /&gt;
				table.insert(primaryData, p_name)&lt;br /&gt;
				res[table_name][primary_param] = primaryData&lt;br /&gt;
			else&lt;br /&gt;
				res[table_name][p_name] = value&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- check for empty/missing parameters declared &amp;quot;required&amp;quot; &lt;br /&gt;
	for p_name, param in pairs( td_params ) do &lt;br /&gt;
		if param.required and util.empty( t_args[p_name] ) then&lt;br /&gt;
			local is_alias&lt;br /&gt;
			for _, alias in ipairs( param.aliases or {} ) do is_alias = is_alias or not util.empty( t_args[alias] ) end&lt;br /&gt;
			if not is_alias then&lt;br /&gt;
				res[&amp;#039;empty-required&amp;#039;] = res[&amp;#039;empty-required&amp;#039;] or {} &lt;br /&gt;
				res[&amp;#039;empty-required&amp;#039;][p_name] = &amp;#039;&amp;#039; &lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	mw.logObject(res)&lt;br /&gt;
	&lt;br /&gt;
	return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- wraps report in hidden frame&lt;br /&gt;
function wrapReport(report, template_name, options)&lt;br /&gt;
	mw.logObject(report)&lt;br /&gt;
	if util.empty( report ) then return &amp;#039;&amp;#039; end&lt;br /&gt;
	local naked = mw.title.new( template_name )[&amp;#039;text&amp;#039;]&lt;br /&gt;
	naked = mw.ustring.gsub(naked, &amp;#039;Infobox&amp;#039;, &amp;#039;infobox&amp;#039;, 1)&lt;br /&gt;
	&lt;br /&gt;
	report = ( options[&amp;#039;wrapper-prefix&amp;#039;] or &amp;quot;&amp;lt;div class = &amp;#039;paramvalidator-wrapper&amp;#039;&amp;gt;&amp;lt;span class=&amp;#039;paramvalidator-error&amp;#039;&amp;gt;&amp;quot; )&lt;br /&gt;
			.. report&lt;br /&gt;
			.. ( options[&amp;#039;wrapper-suffix&amp;#039;] or &amp;quot;&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;quot; )&lt;br /&gt;
	&lt;br /&gt;
	report = mw.ustring.gsub( report, &amp;#039;tname_naked&amp;#039;, naked )&lt;br /&gt;
	report = mw.ustring.gsub( report, &amp;#039;templatename&amp;#039;, template_name )&lt;br /&gt;
&lt;br /&gt;
	return report&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- this is the &amp;quot;user&amp;quot; version, called with {{#invoke:}} returns a string, as defined by the options parameter&lt;br /&gt;
function validateParams( frame )&lt;br /&gt;
	local options, report, template_name = util.extract_options( frame ), &amp;#039;&amp;#039;, frame:getParent():getTitle()&lt;br /&gt;
&lt;br /&gt;
	local ignore = function( p_name )&lt;br /&gt;
		for _, pattern in ipairs( options[&amp;#039;ignore&amp;#039;] or {} ) do&lt;br /&gt;
			if mw.ustring.match( p_name, &amp;#039;^&amp;#039; .. pattern .. &amp;#039;$&amp;#039; ) then return true end&lt;br /&gt;
		end&lt;br /&gt;
		return false&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local replace_macros = function( error_type, s, param_names )&lt;br /&gt;
		function concat_and_escape( t , sep )&lt;br /&gt;
			sep = sep or &amp;#039;, &amp;#039;&lt;br /&gt;
			local s = table.concat( t, sep )&lt;br /&gt;
			return ( mw.ustring.gsub( s, &amp;#039;%%&amp;#039;, &amp;#039;%%%%&amp;#039; ) )&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if s and ( type( param_names ) == &amp;#039;table&amp;#039; ) then&lt;br /&gt;
			local k_ar, kv_ar = {}, {}&lt;br /&gt;
			for k, v in pairs( param_names ) do&lt;br /&gt;
				table.insert( k_ar, k )&lt;br /&gt;
				if type(v) == &amp;#039;table&amp;#039; then&lt;br /&gt;
					v = table.concat(v, &amp;#039;, &amp;#039;)&lt;br /&gt;
				end&lt;br /&gt;
					&lt;br /&gt;
				if error_type == &amp;#039;duplicate&amp;#039; then&lt;br /&gt;
					table.insert( kv_ar, v)&lt;br /&gt;
				else&lt;br /&gt;
					table.insert( kv_ar, k .. &amp;#039;: &amp;#039; .. v)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
			s = mw.ustring.gsub( s, &amp;#039;paramname&amp;#039;, concat_and_escape( k_ar ) )&lt;br /&gt;
			s = mw.ustring.gsub( s, &amp;#039;paramandvalue&amp;#039;, concat_and_escape( kv_ar, &amp;#039; AND &amp;#039; ) )&lt;br /&gt;
&lt;br /&gt;
			if mw.getCurrentFrame():preprocess( &amp;quot;{{REVISIONID}}&amp;quot; ) ~= &amp;quot;&amp;quot; then&lt;br /&gt;
				s = mw.ustring.gsub( s, &amp;quot;&amp;lt;div.*&amp;lt;%/div&amp;gt;&amp;quot;, &amp;quot;&amp;quot;, 1 )&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		return s&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local report_params = function( key, param_names )&lt;br /&gt;
		local res = replace_macros( key, options[key], param_names )&lt;br /&gt;
		res = frame:preprocess(res or &amp;#039;&amp;#039;)&lt;br /&gt;
		report = report ..  ( res or &amp;#039;&amp;#039; )&lt;br /&gt;
		return res&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- no option no work.&lt;br /&gt;
	if util.table_empty( options ) then return &amp;#039;&amp;#039; end&lt;br /&gt;
&lt;br /&gt;
	-- get the errors.&lt;br /&gt;
	local violations = calculateViolations( frame, options[&amp;#039;doc-subpage&amp;#039;] )&lt;br /&gt;
	-- special request of bora: use skip_empty_numeric&lt;br /&gt;
	if violations[&amp;#039;empty-undeclared-numeric&amp;#039;] then &lt;br /&gt;
		for i = 1, tonumber( options[&amp;#039;skip-empty-numeric&amp;#039;] ) or 0 do &lt;br /&gt;
			violations[&amp;#039;empty-undeclared-numeric&amp;#039;][i] = nil &lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- handle ignore list, and prune empty violations - in that order!&lt;br /&gt;
	local offenders = 0&lt;br /&gt;
	for name, tab in pairs( violations ) do &lt;br /&gt;
		-- remove ignored parameters from all violations&lt;br /&gt;
		for pname in pairs( tab ) do if ignore( pname ) then tab[pname] = nil end end&lt;br /&gt;
		-- prune empty violations&lt;br /&gt;
		if util.table_empty( tab ) then violations[name] = nil end&lt;br /&gt;
	-- WORK IS DONE. report the errors.&lt;br /&gt;
	-- if report then count it.&lt;br /&gt;
		if violations[name] and report_params( name, tab ) then offenders = offenders + 1 end &lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if offenders &amp;gt; 1 then report_params( &amp;#039;multiple&amp;#039; ) end&lt;br /&gt;
	if offenders ~= 0 then report_params( &amp;#039;any&amp;#039; ) end -- could have tested for empty( report ), but since we count them anyway...&lt;br /&gt;
	return wrapReport(report, template_name, options)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
	[&amp;#039;validateparams&amp;#039;] = validateParams,&lt;br /&gt;
	[&amp;#039;calculateViolations&amp;#039;] = calculateViolations,&lt;br /&gt;
	[&amp;#039;wrapReport&amp;#039;] = wrapReport&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>wikipedia&gt;ProcrastinatingReader</name></author>
	</entry>
</feed>