Module:Countries

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Lua

CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules

This module implements templates that output a "Countries of" navigation box.

Module:Countries is the main module. Data for each supported region is defined in a submodule. It may also be used to create navigation box for other types of regions (not just lists of countries in a continent), this main module could be used as well to render a list of administrative or electoral subdivisions in any country or region, or for a list of teams in a sport competition, or for a list of named elements in a chemical classification).

Example of module for the list of countries of Africa

For example, Module:Countries/Africa specifies the all text displayed and the countries listed for Africa.

That module is then used to render the navigation box requested by template {{Countries of Africa}}, which displays its results with:

{{#invoke:Countries|main|Africa}}

The first unnamed parameter (Africa) identifies the submodule defining the countries in Africa. The parameter is case sensitive, it can be the full name of the data module, but if there's no colon indicated in this value, the full page name of the data module will be based on a common prefix prepended to this parameter.

Pages using the module with an invalid parameter are added to the tracking Category:Countries template with invalid parameters. The tracking category is also added if the list of countries selected by the user's language has a country code that is not defined in the infos table (or countries table, but not both). For testing, the parameter |nocat=yes can be used in the template to suppress the category.

Codes used in lists of items can be arbitrarily chosen, but should remain short (they may include digits, and their lettercase is significant, but they must not contain any space). For administrative units these codes should preferably be formed starting by a capitalized ISO 3166 country or territory code (if possible); for international sports teams/delegations, these codes should be based on standard IOC codes instead; for chemical elements these codes could be standardized codes as well like C or H for carbon or hydrogen. If needed, additional letters, digits or hyphens may be used to specify a precision or disambiguation.

Usually, these codes will not be longer than 8 ASCII characters (including extensions) and should be language-neutral, and independant of possible changes of official or translated names. Items in the data module just list the default name or optional aliases used in Wikimedia Commons categories, frequently but not alwyas in English; actual names and translations displayed in the generated navigation box (when templates are not used with |all=1) are taken from Wikidata items associated to each data item with their qid='Qnnn', property.

The translated names from Wikidata may be automatically sorted (however the automatic sort is language-neutral and may be very crude for some languages, it is based on the NFKD normalization of the Unicode-encoded names from Wikidata, where combining characters and format controls have then been removed from the text used for the primary and secondary key; then primary characters (forced to lowercase) are sorted in binary order. This works for all common alphabets, abjads, abugidas, or even for syllabaries and the Hangul alphabet used in Korean (thanks to the supported NFKD decomposition). However, this does not work with a few scripts whose primary lowercase characters have codepoints that could not be fully assigned by Unicode in ascending binary order (e.g. for the sinographic script used in Chinese, but also for some alphabets whose base lowercase letters were encoded with additional blocks, out of the binary sequential order, and for which the automatic sort key generator cannot replace them by other suitable primary letters in the main alphabetic sequence). For such case, or if a language sorts differently than the default UCA order, the sort order can be customized for specific languages and the automatic sort order (normally used by default for all languages whose sort order is not customized) will not be used. Supporting an automatic sort for Chinese is challenging (it would require the support by Mediawiki, accessible from Scribunto, to generate UCA collation keys for each language from the ICU library, which for now only provides support for case transforms and standard normalizations; fully implementing UCA for sorting in pure Lua would require to maintain large datatables that would slow down the module each time it's used, even for just the language-neutral order using the standard DUCET).

Codes for data items are normally used only internally by this module, but may be displayed (using a builtin default presentation) using a |showcode=1 parameter in templates calling the modules. If these codes are not appropriate and one wans to display other codes, items in each list can specify a note='[CUSTOM-CODE]', property in the data module: this custom note will be shown just after each displayed wikilink (see an example in Module:Most populous cities of the world, where codes used in the main list of cities are not used but custimized by truncating them to only their leading country code).

Data items for each code can also associate a mark='MARK', specifying a very brief footnote call, displayed in superscript immediately after each generated wikilink (but before the code or note); the footnote will be generated by an additional formatted section (that contains no list of codes). The datamodule specify how to format these sections and can apply conditional displays (only when one of the items actually generate a visible wikilink in a specified section, whose name appear in the condition encoded after each formating string).

Changes

Changes should be performed in the appropriate sandbox module and tested using the corresponding sandbox template. For example, a change to the text displayed or the countries listed for Africa would be made in Module:Countries/Africa/sandbox.

{{Countries of Africa/sandbox}} displays its results with:

{{#invoke:Countries/sandbox|main|Africa}}

Use this sandbox template to test changes to the sandbox module for Africa.

Currently supported specializations for list of countries, territories or subdivisions of an area

The specific modules and their sandboxes defined for list of countries, territories or subdivisions of an area are implemented as the following subpages of the main module, and their associated templates (see Module:Convert/tester).

The sandbox versions of modules should be identical, except temporarily for testing changes (rendered with the sandbox template, in their comparative test cases)

Modules in Commons

Templates in Commons using these modules

The sandbox versions of these templates should be different as they use the sandbox version of the modules.

Summary information about each item

See Module talk:Countries/show for a summary of all information about specific items in lists, including links to the item at Wikidata. If there appears to be an error in the name for a item, find the item in this list, then use the link to the item to check what changes may have occurred at Wikidata.

History

  • Report at phab:T171392 July 2017 (Lua out-of-memory problems resulting in "Error: 503, Service Unavailable").
  • Problem discussed at en:WP:VPT August 2017.
  • Module started September 2017 based on data and logic copied from Template:Countries of Europe to implement that template.
  • Use Module:Redirect to parse soft category redirects in their content. July 2020.

Code

--[==[
This module implements templates that output a countries navbox.
First usage was at [[Template:Countries of Europe]].
--]==]

-- Locally used by langSwitch().
local _langSwitch -- Cache for loading 'Module:Fallback' lazily.

-- Locally used by getList():getCodes() and _main().
local function langSwitch(translations, lang)
	if translations[lang] then
		return translations[lang]
	end
	--[==[ Note: the sandbox version handles BCP 47 rules more strictly for
	fallbacks. In addition it is a bit faster, uses less temporary memory than
	the current non-sandbox version of Module:Fallback.
	--]==]
	_langSwitch = _langSwitch or require('Module:Fallback/sandbox')._langSwitch
	return _langSwitch(translations, lang) or ''
end

-- Locally used by getList():getTitle() and getGroupData().
--[==[
If text is a non-empty string (not just containing spaces), return its trimmed
content. Otherwise, return nil (text is an empty string or is not a string).
--]==]
local function stripToNil(text)
	if type(text) == 'string' then
		text = text:match('^%s*(.-)%s*$') -- trim starting and trailing spaces
		if text ~= '' then -- not nil and not empty
			return text:gsub('%s+', ' ') -- compress and normalize inner spaces
		end
	end
	return nil
end

-- Locally used by getTargetFromCatRedirect().
local _getTargetFromCatRedirect -- Cache for loading 'Module:Redirect' lazily.

--[==[
Detect soft redirect in wiki page content of 'Category:' pages using template
{{Category redirect|target}} or one of its known aliases on Commons.
Loaded lazily from 'Module:Redirect'.
--]==]
local function getTargetFromCatRedirect(...)
	if not _getTargetFromCatRedirect then
		_getTargetFromCatRedirect =
			require('Module:Redirect').getTargetFromCatRedirect
	end
	getTargetFromCatRedirect = _getTargetFromCatRedirect
	return getTargetFromCatRedirect(...)
end

-- Locally used by getList().
local _makeSortKey -- Cache for loading 'Module:MakeSortKey' lazily.

-- Locally used by _main().
local function getList(lists, exclude, options, infos)

	--[==[ Return:
	* title of redirect target (prefixed by ':' if it's a category), if the
	  specified page is a redirect and target exists;
	* false if specified page is not a suitable target, try next page if any;
	* nil if specified page should be used as the target
	--]==]
	local function getRedirectTarget(titleObj)
		if titleObj.isRedirect then
			-- target is false if target page with that title does not exist
			local target = titleObj.redirectTarget.prefixedText
			return type(target) == 'string'
				and target:match('^%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:')
				and (':' .. target) or target
		end
		local content = titleObj:getContent()
		if content then
			local target = getTargetFromCatRedirect(content)
			if target then
				return ':' .. target
			end
			if content:match('{{%s*[Dd]isambig%s*}}')
				or content:match('{{%s*[Dd]ab%s*}}') then
				return false
			end
		end
		return nil
	end

	--[==[
	Avoid overhead of checking target for a country with no alternative name.
	LATER: It appears the extra overhead may be low; perhaps remove this?
	--]==]
	local function getNilTarget(titleObj)
		return nil
	end

	-- Locally used by getTitle() and makeItem() and set as info = codes[code].
	local info

	--[==[
	Return the title of a link to an existing page (if not 'all'),
	or nil if there's none.
	Uses (info, options) from getList().
	--]==]
	local function getTitle(tryThe)
		local pfx = (stripToNil(options.prefix) or '') .. options.presep
		--[==[ Enforce a single leading ':' for links to special namespaces
		(namespace names are not case-significant).
		--]==]
		local ns, name = pfx:match('^:*%s-(%w+)%s*:%s*(.-)$')
		if ns then
			--[==[ TODO: should we recognize interwiki prefixes here
			(e.g. "fr:" or "de:" which are also special namespaces)
			to enforce an inline link to the other wiki, instead of generating
			an interwiki on the side bar?
			This is for not needed on international wikis like Commons, but may
			be needed on Wikipedia.
			--]==]
			for _, special in ipairs({'Category', 'File', 'Special'}) do
				if special:lower() == ns:lower() then
					pfx = ':' .. special .. ':' .. name
				end
			end
		end
		local sfx = stripToNil(options.suffix) or ''
		--[==[ Replace final parts found in (option.suffix), according to the
		optional (subst) table defined in the (info) taken from the entry
		defined for the country code in (lists)
		--]==]
		sfx = options.sufsep .. (type(info.subst) == 'table' and
			sfx:gsub('%S.+$', info.subst) or sfx)
		local getTarget = #info > 1 and getRedirectTarget or getNilTarget
		for i, name in ipairs(info) do
			if tryThe then
				if i > 1 then
					return nil
				end
				name = 'the ' .. name
			end
			--[==[ Compress whitespaces in excess possibly introduced by
			(option.presep, option.sufsep) in (pfx) or (sfx), or left after
			applying (info.subst) to the (option.suffix)
			--]==]
			local title = stripToNil(pfx .. name .. sfx) or ''
			if options.all then
				return title
			end
			local titleObj = mw.title.new(title)
			if titleObj and titleObj.exists then
				local t = getTarget(titleObj)
				if t ~= false then
					return t or title
				end
			end
		end
		return nil
	end

	local itemPattern = '[[{title}|<bdi>{label}</bdi>]]{post}{bullet}'
	local head, trail = '<span style="white-space:nowrap">', '</span>'
	--[==[ These bullets are used by makeItem(), but also after processing
	all items to remove the last bullet==stdBullet.
	--]==]
	local altBullet, stdBullet = ' ≈', " '''·'''"
	--[==[ trail is used here, but also after processing all items to remove
	the last bullet==stdBullet.
	--]==]
	itemPattern = head .. itemPattern .. trail
	local post

	-- Locally used recursively.
	local function makeItem(tryThe)
		local pretitle = ''
		local itemLabel
		local bullet = stdBullet
		local second = ''
		if tryThe then
			--[==[ LATER:
			This assumes 'the' applies to the first name, and only the first.
			--]==]
			if not info.the then
				return nil
			end
			if options.all then
				pretitle = 'the '
				itemLabel, second = makeItem()
				bullet = altBullet
				second = ' ' .. second
			end
		end
		local title = getTitle(tryThe)
		if not title then
			return nil
		end
		itemLabel =
			not options.all and mw.wikibase.getLabelByLang(info.qid, options.lang) or
			(pretitle .. info[1])
		return itemLabel,
			(itemPattern:gsub('{(%a+)}', {
				title = title,
				label = itemLabel,
				post = post,
				bullet = bullet,
				})
			) .. second
	end

	--[==[ Language selection.
	The following works with a list that defines results for various languages.
	It also handles the special entries illustrated in the following.
	If the provided list does not contain an entry for the provided lang,
	langSwitch() will look for fallbacks defined in that list: assume 'LX' and
	'LY' are language codes that are not defined in the list, and 'LX' falls
	back to 'en', while 'LY' falls back to 'default'. Currently, the latter
	cannot occur, but it conceivably could.
		list = {
			automatic = 'AB CD EF GH',    -- country codes
			english = 'automatic',        -- uselang=en → 'AB CD EF GH' without sort
			default = 'automatic sorted', -- uselang=LY → 'AB CD EF GH' after sorting
			en = 'automatic sorted',      -- uselang=LX → 'AB CD EF GH' after sorting
		},
	* We'll use codes = the space-separated codes which are the most
	  appropriate order for lang.
	* We'll use getSortKey = nil or a function to make a sort key, if the
	  entry was 'automatic sorted'.
	* The two entries 'automatic' and 'english' override what langSwitch alone
	  would return. But a specific language may be set to use.
	As an optimization, lang=='en' uses the 'english' entry setting, if defined:
	* when english=='automatic', the result is the automatic setting, which
	  may or may not use the overhead of sorting.
	Sorting applies to country names obtained from Wikidata in the user's
	language:
	* The automatic sorting is crude (language neutral) and will often be
	  unhelpful for specific languages (notably for the ideographic script
	  which remains sorted in binary order and still requires manual sorting).
	* It is based on sort keys computed in Module:MakeSortKey.
	--]==]
	local codes
	if options.lang == 'en' and lists.english then
		codes = lists.english
	else
		codes = langSwitch(lists, options.lang)
	end

	local getSortKey
	if codes == 'automatic' or codes == 'automatic sorted' then
		if codes == 'automatic sorted' then
			if not _makeSortKey then
				_makeSortKey = require('Module:MakeSortKey').makeSortKey
			end
			getSortKey = _makeSortKey
		end
		codes = lists.automatic or error('Codes list uses "'
			.. codes .. '" but "automatic" is not defined')
	end

	--[==[
	Split the space-separated list of codes, find and process their info to
	build the unsorted items to display. Items will be actually sorted, if
	wanted, once they are complete.
	--]==]
	local items, wantSort = {}, getSortKey and not options.all
	for code in codes:gmatch('%S+') do
		if not exclude[code] then
			info = infos[code]
			if info then
				post =
					(options.showcode and
						(' <bdi>[<kbd>' .. code .. '</kbd>]</bdi>')
						or '') ..
					(type(info.mark) == 'string' and
						('<sup><bdi>' .. info.mark .. '</bdi></sup>')
						or '') ..
					(type(info.note) == 'string' and
						(' <bdi>' .. info.note .. '</bdi>')
						or '')
				post = post ~= '' and '<small style="font-size:88%">'
					.. post .. '</small>' or ''
				local itemLabel, result = makeItem(true)
				if not result then
					itemLabel, result = makeItem()
				end
				if result then
					table.insert(items,
						wantSort and {
							getSortKey(itemLabel, options.lang),
							result
						} or result)
				end
			else
				options.message = options.message or ('No info about code "'
					.. code .. '"')
			end
		end
	end

	--[==[
	Sort the items, if wanted, using the sort key (precomputed above)
	with which they were associated.
	--]==]
	if wantSort then
		table.sort(items, function (a, b) return a[1] < b[1] end)
		for i, v in ipairs(items) do
			items[i] = v[2]
		end
	end

	--[==[
	Pack the items into a single string, and remove the last
	bullet==stdBullet just before trail.
	--]==]
	local result = table.concat(items, ' ')
	local stdBulletTrail = stdBullet .. trail
	local stdBulletTrailLen = #stdBulletTrail
	if result:sub(-stdBulletTrailLen) == stdBulletTrail then
		-- Omit trailing bullet from last item
		result = result:sub(1, -stdBulletTrailLen - 1) .. trail
	end
	return result
end

-- Locally used by _main() and main().
local function isnonempty(text)
	return text and text ~= ''
end

-- Locally used by main().
--[==[
Exported by this module, for usage in Lua.
--]==]
local function _main(options, data)
	-- Caller must provide a valid language code in options.lang.
	local lang = options.lang
	local langObj = mw.language.new(lang)

	--[==[
	Generate the table of variable names supported in placeholders
	(inside patterns or subpatterns), or in simple conditions.
	--]==]
	local var = {
		-- These supported variables are independant of the sections.
		lang = lang,
		dir = langObj:getDir(), -- value = 'ltr' or 'rtl'
		colon = options.colon or ': ',
	}
	local wantSimple = options.simple and data.simple
	local exceptions = data.simple or {}
	-- Each section adds two variables for its title and its list of items.
	local sections = exceptions.sections or {}
	for section, titles in pairs(data.titles) do
		-- Add support for the variable named like '{section}title'.
		if not wantSimple or sections[section] then
			var[section .. 'title'] = '<bdi>' .. langSwitch(titles, lang)
				.. '</bdi>'
		end
	end
	for section, lists in pairs(data.lists) do
		-- Add support for the variable named like '{section}list'.
		if not wantSimple or sections[section] then
			local exclude = wantSimple and {} or sections[section] or {}
			var[section .. 'list']
				= getList(lists, exclude, options, data.infos)
		end
	end

	--[==[
	The pattern may contain '{variablename}' placeholders to be replaced by
	the value of `var['variablename']`. See above for supported variables.
	--]==]
	local pattern = wantSimple and exceptions.pattern or data.pattern
	local usedpattern
	if type(pattern) == 'string' then
		-- This is an unconditional pattern, represented as a simple string
		usedpattern = pattern
	elseif type(pattern) == 'table' then
		--[==[ pattern is an array containing an ordered array of conditional
		patterns, added in the same order to the result (non-integer keys in
		this table are ignored).
		--]==]
		usedpattern = ''
		-- Note: keys not in the numbered sequence are ignored.
		for _, condpattern in ipairs(pattern) do
			--[=[
			Each conditional pattern is represented either:
			- as a simple string when there's no condition (this unconditional
			  pattern will be always added to the result), or
			- as an ordered table, whose first element is a subpattern string
			  and the other elements represent an union of several
			  (non-exclusive) conditions.
			(If any one of the conditions evaluates to true (OR), the conditional
			subpattern will be used.)
			Each condition may itself be represented either:
			- as a simple string for simple conditions, or
			- as an ordered table of subconditions, i.e. a conjunction of several
			  (non-exclusive) simple conditions.
			(If any one of the subconditions evaluates to false (AND), the
			conditional subpattern will NOT be used. Simple conditions are
			represented as strings, used to evaluate tests based on names of
			variables (usable in placeholders of patterns or subpatterns.)
			The variable names used in simple conditions don't need to be
			present within the pattern or subpattern strings.
			A simple condition can currently take one the following forms:
			- 'variablename': the simple condition is true if the variable
			  with that name is non-empty;
			- '!variablename': the simple condition is true if the variable
			  with that name is empty.
			--]=]
			local subpattern, condition
			if type(condpattern) == 'string' then -- Unconditional subpattern.
				subpattern, condition = condpattern, true
			elseif type(condpattern) == 'table' then -- Conditional subpattern.
				subpattern, condition = '', false
				-- Note: keys not in the numbered sequence are ignored.
				for i, v in ipairs(condpattern) do
					--[==[ The first element is the subpattern, other numbered
					elements are its conditions (forming an union).
					--]==]
					if i == 1 then
						subpattern = v
					-- Handle conditions that are simple strings.
					elseif type(v) == 'string' then
						--[==[ Evaluate the condition string which is for now a
						simple variable name:
						* the condition '!variablename' is true if this variable
						  has an empty string value;
						* the condition 'variablename' is true if this variable
						  has a non-empty string value;
						* the supported variable names are defined above (e.g.
						  {section}..'title' and {section}..'list').
						--]==]
						if v:sub(1, 1) == '!' and not isnonempty(var[v:sub(2)])
						or v:sub(1, 1) ~= '!' and isnonempty(var[v]) then
							condition = true -- Subpattern EFFECTIVELY used.
							break -- Don't need to evaluate other conditions.
						--else
							--[==[
							Other string values of v are ignored. This
							condition evaluates as false, other possible
							conditions must be evaluated.
							--]==]
						end
					--[==[ Handle conditions that are arrays of subconditions
					(AND). If any subcondition evaluates to false, the
					condition also evaluates to false, and other conditions
					must be checked to evaluate them as OR).
					--]==]
					elseif type(v) == 'table' then
						--[==[ The subpattern will then be used unless a
						subcondition evaluates to false.
						--]==]
						condition = true
						-- Note: keys not in the numbered sequence are ignored.
						for _, w in ipairs(v) do
							-- Handle subconditions that are simple strings.
							if type(w) == 'string' then
								--[==[ Evaluate the subcondition string which
								is for now a simple variable name (like above).
								--]==]
								if w:sub(1, 1) == '!'
									and isnonempty(var[w:sub(2)])
								or w:sub(1, 1) ~= '!'
									and not isnonempty(var[w])
								then
									condition = false
									--[==[ This subcondition is evaluated as
									false. The subpattern may still be used,
									but need to evaluate other conditions.
									]==]
									break
								--else
									--[==[
									Other string values of v are ignored. This
									subcondition evaluates as false, other
									possible subconditions must be evaluated.
									--]==]
								end
							else
								--[==[ Don't know what to do with this type of
								subcondition. And because this is part of a
								conjonction (AND), the subcondition evaluates
								as false. The subpattern may still be used, but
								we need to evaluate other conditions.
								--]==]
								condition = false
								break
							end
						end
						if condition then
							--[==[ If the array of subconditions (AND) is still
							true, the main condition (OR) instantly evaluates
							as true. The subpattern will EFFECTIVELY be used.
							Don't need to evaluate other conditions.
							--]==]
							break
						end
					--else
						--[==[ Don't know what to do with this type of
						condition. And because this is part of an union (OR),
						it is ignored, but we can evaluate other conditions.
						--]==]
					end
				end
			end
			if condition then
				usedpattern = usedpattern .. subpattern
			end
		end
	--else
		--[==[ Don't know what to do with this type of pattern (not used in
		the final pattern)
		--]==]
	end
	return usedpattern:gsub('{(%a+)}', var)
end

-- Locally used by getGroupData() and show().
--[==[
If there's no ':' in the id, it is assumed to be in a subpage of
Module:Countries; otherwise it can be the full page name of any other module
stored anywhere. Also autodetect the '/sandbox' version according to the
parent page name.
--]==]
local function getDataModuleName(frame, id)
	return	(id:find(':', 1, true) and id or 'Module:Countries/' .. id) ..
		(frame and frame:getTitle():find('sandbox', 1, true)
			and '/sandbox' or '')
end

-- Locally used by main().
local function getGroupData(frame)
	--[==[ Return table of data defining a group of items for the first
	template parameter. The data will rarely be used more than once on a
	page, so mw.loadData is not useful.
	--]==]
	local data
	-- Id of the group (case sensitive), e.g. 'Europe' or 'Module:CustomData'.
	local id = stripToNil(frame.args[1])
	if id then
		local module, status = getDataModuleName(frame, id), nil
		status, data = pcall(require, module)
		if not status then
			error('Data could not be loaded from [[:' .. module ..']]')
		end
	end
	if type(data) ~= 'table' then
		error('First template parameter must specify a defined data module')
	end
	-- Compatiblity note: `.infos` is preferred, `.countries` is legacy.
	-- We do not allow to define both (would be ambiguous).
	if data.infos == nil and data.countries ~= nil then
		data.infos = data.countries
		data.countries = nil
	end
	if type(data.titles) ~= 'table' or
		type(data.lists) ~= 'table' or
		type(data.infos) ~= 'table' or type(data.countries) ~= 'nil' or
		type(data.pattern) ~= 'string' and type(data.pattern) ~= 'table' or
		type(data.simple) ~= 'nil' and type(data.simple) ~= 'table' or
		type(data.simple) == 'table' and (
			type(data.simple.pattern) ~= 'string'
				and type(data.simple.pattern) ~= 'table' or
			type(data.simple.sections) ~= 'table'
		) then
		error('The specified data module ('
			.. id .. ') is not validly defined')
	end
	if frame.args.mode == 'fop' then
		--[==[
		For Module:TNTExpandByCountries which generates
		"Commons:Freedom of panorama" pages:
		* Use any given fop exception name. For example, the exception
		  fop = 'Luxembourg' means that [[Luxembourg|...]] should be the
		  returned link so TNTExpandByCountries uses Template:Luxembourg
		  which correctly contains {{Label|Q32|...}}.
		* Do not follow redirects. That occurs if only one name for a country
		  code is given.
		--]==]
		for code, spec in pairs(data.infos) do
			if spec.fop then
				if spec.fop == 'EXCLUDE' then
					data.infos[code] = nil
				else
					data.infos[code] = {
						spec.fop, qid = spec.qid
						-- other fields in `spec` are not needed.
					}
				end
			end
		end
	end
	return data
end

--[==[
Exported by this module, for usage in wiki templates.
--]==]
local function main(frame)
	local args = frame.args
	local options = {
		--[==[
		Use default args set by "{{#invoke:}}" used in any page or in the
		main template.
		--]==]
		prefix  = args.prefix or '',
		presep = args.presep or args.sep or ' ',
		sufsep = args.sufsep or args.sep or ' ',
		suffix = args.suffix or '',
		simple = args.simple,
		showcode = args.showcode,
		all = args.all,
		nocat = args.nocat,
		lang = args.lang or frame:callParserFunction('Int', 'Lang'),
	}
	local lang = options.lang

	--[==[
	Override args with those passed to the main template (and check them
	verbosely).
	--]==]
	args = frame:getParent().args
	if args then
		options.prefix = args.prefix or options.prefix
		options.presep = args.presep or args.sep or options.presep
		options.sufsep = args.sufsep or args.sep or options.presep
		options.suffix = args.suffix or options.suffix
		options.simple = args.simple or options.simple
		options.showcode = args.showcode or options.showcode
		options.all = args.all or options.all
		options.nocat = args.nocat or options.nocat
		local goodArgs, badArgs = {
			prefix = true,
			presep = true,
			sufsep = true,
			suffix = true,
			sep = true,
			simple = true,
			showcode = true,
			all = true,
			nocat = true,
		}, {}
		for k, v in pairs(args) do
			if not goodArgs[k] then
				table.insert(badArgs, k .. '=' .. v)
			end
		end
		if #badArgs > 0 then
			options.message = 'invalid parameter "|'
				.. mw.text.nowiki(table.concat(badArgs, '|')) .. '"'
		end
	end
	options.colon = frame:expandTemplate({
			title = 'colon',
			args = {
				lang = lang,
			},
		})
	options.simple = isnonempty(options.simple)
	options.showcode = isnonempty(options.showcode)
	options.all = isnonempty(options.all)
	options.nocat = isnonempty(options.nocat)

	local result = _main(options, getGroupData(frame))
	if options.message then
		--[==[
		Check if a warning should be displayed for invalid input.
		--]==]
		local success, revid = pcall(function ()
			return frame:preprocess('{{REVISIONID}}')
		end)
		if success and revid == '' then
			result = result .. '<strong class="error">Error: '
				.. options.message .. '</strong>'
		end
		if not options.nocat then
			result = result
				.. '[[Category:Countries template with invalid parameters]]'
		end
	end
	return result
end

--[==[
Exported by this module, for usage by {{#invoke:thisModule|show}} in wiki.
For documentation, return wikitext listing data from the country modules.
See [[Module talk:Countries/show]] for results.
--]==]
local function show(frame)
	local templateids = {
		--[==[
		Format for each entry: { '<Template name>', '<Data module id>' },

	    Note: without any embedded ':', the '<Data module id>' indicates
		loading 'Module:Countries/<Data module id>' by default. Data modules
		do not necessarily have to be subpages of 'Module:Countries', but must
		then specify their full page name (starting by 'Module:').

		Templates and data modules don't need to be listed all, they are only
		here to be used on the report shown on [[Module talk:Countries/show]],
		which performs extensive check of all features of this module.
		--]==]
		{ 'Countries of Africa', 'Africa' },
		{ 'Countries of the Americas', 'Americas' },
		{ 'Countries of the Arab world', 'Arab world' },
		{ 'Countries of Asia', 'Asia' },
		{ 'Countries of the Caribbean', 'Caribbean' },
		{ 'Countries of Central America', 'Central America' },
		{ 'Countries of Europe', 'Europe' },
		{ 'Countries of the European Union', 'European Union' },
		{ 'Countries of North America', 'North America' },
		{ 'Countries of North America (subcontinent)', 'North America (subcontinent)' },
		{ 'Countries of Oceania', 'Oceania' },
		{ 'Countries of South America', 'South America' },
		{ 'Countries in the United Kingdom', 'United Kingdom' },
		{ 'Copyright rules by territory', 'CRT other' },
		{ 'Olympic teams', 'Olympic teams' },
		{ 'Most populous cities of the world', 'Module:Most populous cities of the world' },
		{ 'Regions of France', 'Module:Regions of France' },
		{ 'Departments of France', 'Module:Departments of France' },
	}
	local lines = {}
	local function output(line)
		lines[#lines + 1] = line
	end
	for _, templateid in ipairs(templateids) do
		local template, id = templateid[1], templateid[2]
		local module = getDataModuleName(frame, id)
		local data = require(module)
		 -- Compatibility note: `.infos` is prefered, `.countries` is legacy.
		local infos = data.infos or data.countries
		if infos then
			local codes, maxnames, usethe = {}, 0, false
			for code, info in pairs(infos) do
				codes[#codes + 1] = '' .. code
				maxnames = math.max(#info, maxnames)
				usethe = usethe or info.the
			end
			table.sort(codes)
			output('== ' .. template .. ' ==')
			output('* [[Template:' .. template .. ']]')
			output('* [[' .. module .. ']]')
			output('{|class="wikitable sortable"')
			output('|-')
			output('!scope="col"| Code')
			output('!scope="col"| Wikidata')
			if usethe then
				output('!scope="col"| the')
			end
			output('!scope="col" colspan="' .. (maxnames < 2 and maxnames or 2)
				.. '"| Name' .. (maxnames <= 1 and ''
					or maxnames == 2 and ' and alias'
					or ' and aliases')
				.. ' on Commons')
			for _, code in ipairs(codes) do
				local info = infos[code]
				output('|-')
				output('!scope="row" style="text-align:left"| <kbd style="font-size:smaller">'
					.. code .. '</kbd>')
				output('| <small>' .. (info.qid and ('[[d:' .. info.qid .. '|'
					.. info.qid .. ']]') or '') .. '</small>')
				if usethe then
					output(info.the
						and ('| [[The ' .. info[1]
							.. '|the]] <small>([[:Category:The ' .. info[1]
							.. '|cat]])</small>')
						or '|style="background:#CCC"| &nbsp;')
				end
				output(info[1]
					and ('| [[' .. info[1] .. ']] <small>([[:Category:'
						.. info[1] .. '|cat]])</small>')
					or '|style="background:#CCC"| &nbsp;')
				if maxnames >= 2 then
					local aliases = {}
					for i = 2, #info do
						aliases[#aliases + 1] = '[[' .. info[i]
							.. ']] <small>([[:Category:' .. info[i]
							.. '|cat]])</small>'
					end
					output(#aliases > 0 and ('| '
							.. table.concat(aliases, ', '))
						or '|style="background:#CCC"| &nbsp;')
				end
			end
			output('|}')
			output('')
		end
	end
	return table.concat(lines, '\n')
end

--[==[
Exports.
--]==]
return {
	main = main, -- For use in a MediaWiki template.
	_main = _main, -- For use in Lua only.
	show = show, -- Only for the documentation of this module.
}