Lua

CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules

0 The module uses frequently the template {{=}} for title = "=" 0

This module contains the functions for different templates, solving their iteration problem

The MediaWiki template coding does not support the repetitive processing of a parameter set, which leads often to a very primitive and long chain of parameter checks in the kind of:

{{#if:{{{1|}}} | perform an action with parameter 1 }}
{{#if:{{{2|}}} | perform an action with parameter 2 }}
{{#if:{{{3|}}} | perform an action with parameter 3 }}
{{#if:{{{4|}}} | perform an action with parameter 4 }}
•••
•••
{{#if:{{{33|}}} perform an action with parameter 33 }}

with the disadvantage that such a construct will fail as soon as there comes a 34nd parm. I always thought that with Lua and its for loops this iterations should be possible, somehow. After long searching without any success and without an idea how to perform it, I asked at several forums and got finally from User:Trappist the monk the helping hint to solve it with a control structure like:

	local out = {}
	for i, v in ipairs (args) do
		table.insert (out, frame:expandTemplate{ title = title, args = v })
	end
	return table.concat (out)

The functions contained therein are from very simple, just one parameter to pass, to more complicated ones, where e.g. a pair of parameters needs kind of a flip-flop switch when always a tuple of e.g. an item and its correlated text needs to be passed.

When there are tuples, triples etc., the table.insert occurs, when the last element is processed; this performs problems when the last group is incomplete – it should be finished with empty values to trigger the table.insert. The check with table.maxn seems not to work correctly?

This problem is solved differently, for the function attribs with a primitive workaround.

Namespace lists edit

There are special lists for some namespaces:

  • Filelist - for items of the namespace 6
  • Userlist - for items of the namespace 2
  • Linklist - for items of the namespace 000 (not yet)
  • Templist - for items of the namespace 100 (not yet)
  • Cat-list - for items of the namespace 140 (not yet)
  • Mod-list - for items of the namespace 828 (not yet)

Filelist edit

File list templates edit

The three templates {{Ifim}}, {{Ifim1}} and {{Ifimt}} avoid to display the file itself as an |other version=.

There are currently five templates creating file lists with the function:filelist

  1. {{Other versions}} a vertical list (without initial)
  2. {{Filelist}} a horizontal list (without initial)
  3. {{Derivative versions}} a vertical list
  4. {{Derived from}} a vertical list
  5. {{File}} a horizontal list

All these templates are autoreferencing, and the support also the |gallery= option.

Other file lists edit

The function:svglang creates a list of just file_names and language_codes, for:

  1. {{Svg lang}}
  2. {{Lang gallery}}
  3. {{Multilingual SVG diagrams}}

Much more is created by the function:ownbased for the template:

  1. {{Own based}}

which will handle in the majority of cases just one file, but can serve an unlimited number.

This list can be controlled by the user to be displayed either horizontally or vertically.

  1. {{Attribs}}

The template works, whether there is a user assigned for a file, or one filename follows another.

  1. {{Emoji}}

The template displays a line of SVG emojis; Unicode characters/emojis can be displayed with the function emodis or emotab.

Variable iterations edit

A very simple iteration, usable for everything, is performed by function:iterate

used by e.g. {{Image extracted}}, {{Image translated}},

More usable is the function:iteration for transclusions of a template with:

  • a number of positional parameters which can change at each transclusion, and
  • a number of positional parameters which are the same for all transclusions.

See template {{Iterate}}. The function:loop is used by {{Repeat}}.

samefile function edit

When the name of one of the files is the same as the first one, it is enough to reference it with its extension after a dot, e.g. just ".jpeg". The most standard extensions can be abbreviated, e.g. ".j" will be sufficient for ".jpg" (only for lower case!).

The function incrhx increments a hexadecimal number (parm 1) by a decimal number (parm 2).

Valid numbers are assumed, no checking occurs!

When parameter 2 is preceded by a minus, the first number is decremented by the second one.

Userlist edit

  • Userlist: {{Userlist}} with many formatting parameters

Linklist edit

Templist edit

Other iterations edit

  • Langlist:
  • Colorbox: {{ColorString}} and other color box templates
  • Legends : {{Legtab}} combines single {{Legend}}s
  • Tleparms: the metatemplate {{Tle}} displays the parameter usage of a template
  • Navigate: {{Navcat}} navigates through color categories
  • Showname: {{Showname}} helper template, for parameter tests
  • Emodis 0: {{Emoji}} displays (one or many) Unicode characters
The function emodis displays Unicode characters / emojis; it can care for newlines, while
The function emotab displays a table of files / emojis.

Code

-- This module contains the functions for different templates, solving their iteration problems.

local max = math.max
local NL     = string.char(10)
local len    = string.len
local sub    = string.sub
local find   = string.find
local format = string.format
local insert = table.insert
local concat = table.concat
local trim  = mw.text.trim
local split = mw.text.split
local titleNew = mw.title.new
local lower = mw.ustring.lower
local upper = mw.ustring.upper
local gsub  = mw.ustring.gsub
local Usub  = mw.ustring.sub
local function Ucfirst(text)
	text = trim(text)
	return upper(Usub(text, 1, 1)) .. Usub(text, 2)
end

----------------------------------------------------------------------------------------------
-- Template expansion helpers for performance:
-- Saves CPU and time resources on large iterations by minimizing repeated recursive calls to
-- the parser, eliminating many table constructions/deletion/lookups in memory. Also allows
-- reporting CPU/time usage for expansions of costly templates inside recursive calls
-- (otherwise all CPU/time is counted inside Lua, those templates are not counted).

local currentTitle = mw.title.getCurrentTitle()
local currentNs    = tonumber(currentTitle.namespace)
local currentPage  = currentTitle.text

local frameCurrent = mw.getCurrentFrame()
local expandTemplateCurrent     = frameCurrent.expandTemplate
local callParserFunctionCurrent = frameCurrent.callParserFunction

local contentLanguage = mw.language.getContentLanguage()
local langDefault = contentLanguage:getCode()
local langUser = lower(trim(callParserFunctionCurrent(frameCurrent, 'Int', 'Lang')))

local transclude = {}
local function expand(template, args)
	transclude.title = template
    transclude.args = args or {}
	return expandTemplateCurrent(frameCurrent, transclude)
end
local argsOne = {}
local function expand1(title, arg1)
	argsOne[1] = arg1
	return expand(title, argsOne)
end
local argsLangUser = { lang = langUser }
local argsLangDefault = { lang = langDefault }
local argsLangOther = {}
local function expandWithLang(template, lang)
	if type(lang) == 'string' then
		lang = trim(lang)
		if lang == '' then
			lang = nil
		else
			lang = lower(lang)
		end
	else
		lang = nil
	end
	if not lang or lang == langUser then
		return expand(template, argsLangUser)
	elseif lang == langDefault then
		return expand(template, argsLangDefault)
	else
		argsLangOther.lang = lang
		return expand(template, argsLangOther)
	end
end
local Comma, ConjAnd
local function commaAnd(isLast)
	if isLast then
		if not ConjAnd then
			ConjAnd = expand('Conj-and', argsLangUser)
		end
		return ConjAnd
	else
		if not Comma then
			Comma = expand('Comma', argsLangUser)
		end
		return Comma
	end
end

----------------------------------------------------------------------------------------------

local p = {}

-- For Template:Ifim.
local argsIfim1 = { -- Parameters for expanding 'Template:Ifim1'.
	'', -- [1] = v
	'', -- [2] = trim(ppar.p1 or ''),
	'', -- [3] = trim(ppar.p2 or ''),
	'', -- [4] = trim(ppar.p3 or ''),
	'', -- [5] = trim(ppar.p4 or ''),
	'', -- [6] = trim(ppar.p5 or ''),
	n = '' -- ['n'] = trim(ppar.fn or ''),
}
function p.ifim1(frame)
	local ppar = frameCurrent:getParent().args
	argsIfim1[2] = trim(ppar.p1 or '')
	argsIfim1[3] = trim(ppar.p2 or '')
	argsIfim1[4] = trim(ppar.p3 or '')
	argsIfim1[5] = trim(ppar.p4 or '')
	argsIfim1[6] = trim(ppar.p5 or '')
	argsIfim1.n = trim(ppar.fn or '')
	local results = {}
	for _, v in ipairs(ppar) do
		argsIfim1[1] = v
		insert(results, expand('Ifim1', argsIfim1))
	end
	return concat(results)
end -- function ifim

-- For Template:Ifimc.
local argsIfim2 = { -- Parameters for expanding 'Template:Ifim2'.
	'', -- [1] = v1
	'', -- [2] = trim(ppar.p1 or ''),
	'', -- [3] = trim(ppar.p2 or ''),
	'', -- [4] = trim(ppar.p3 or ''),
	'', -- [5] = trim(ppar.p4 or ''),
	'', -- [6] = v
}
function p.ifim2(frame)
	local ppar = frameCurrent:getParent().args
	argsIfim2[2] = trim(ppar.p1 or '')
	argsIfim2[3] = trim(ppar.p2 or '')
	argsIfim2[4] = trim(ppar.p3 or '')
	argsIfim2[5] = trim(ppar.p4 or '')
	local results = {}
	for _, v in ipairs(ppar) do
		argsIfim2[1] = v
		argsIfim2[6] = v
		insert(results, expand('Ifim2', argsIfim2))
	end
	return concat(results)
end -- function ifim2

-- For Template:Ifimt (param pairs).
function p.ifimt(frame)
	local ppar = frameCurrent:getParent().args
	argsIfim2[2] = trim(ppar.p1 or '')
	argsIfim2[3] = trim(ppar.p2 or '')
	argsIfim2[4] = trim(ppar.p3 or '')
	argsIfim2[5] = trim(ppar.p4 or '')
	local v1 = ''
	local results = {}
	for _, v in ipairs(ppar) do
		if v1 == '' then
			v1 = v
		else
			argsIfim2[1] = v1
			argsIfim2[6] = v
			insert(results, expand('Ifim2', argsIfim2))
			v1 = ''
		end
	end
	if v1 ~= '' then
		argsIfim2[6] = ''
		insert(results, expand('Ifim2', argsIfim2)) -- Last item.
	end
	return concat(results)
end -- function ifimt

-------------------------------------------------------
-- Helper function for: ownbased, and filelist
local function samefile(filename, num, first)
	local filename = trim(filename or '')
	if sub(filename, 1, 1) == '.' then
		local part = 'Example' -- default filename
		if currentNs == 6 or currentNs == 7 then
			part = split(currentPage , '.', true)
			part = concat(part, '.', 1, math.max(#part - 1, 1))
		end
		-- Supported short aliases:
		if     filename == '.'  then filename = part .. '.png'
		elseif filename == '.p' then filename = part .. '.png'
		elseif filename == '.g' then filename = part .. '.gif'
		elseif filename == '.j' then filename = part .. '.jpg'
		elseif filename == '.s' then filename = part .. '.svg'
		-- Exotics:
		elseif filename == '.m' then filename = part .. '.mid'
		elseif filename == '.o' then filename = part .. '.ogg'
		elseif filename == '.t' then filename = part .. '.tif'
		elseif filename == '.v' then filename = part .. '.wav'
		elseif filename == '.x' then filename = part .. '.xcf'
		-- Note: no "short alias" for '.pdf' ('.p' already used for '.png')
		-- Note: short aliases for a few longer extensions:
		elseif filename == '.d' then filename = part .. '.djvu'
		elseif filename == '.w' then filename = part .. '.webp'
		else                         filename = part ..  filename
		end
	elseif filename == '' or filename == '*' then
		if tonumber(num) == 1 then
			filename = 'Example.svg'
			if currentNs == 6 or currentNs == 7 then
				filename = currentPage
			end
		else
			filename = trim(first)
			if filename == '' or filename == '*' or sub(filename, 1, 1) == '.' then
				filename = 'Example.svg'
				if currentNs == 6 or currentNs == 7 then
					filename = currentPage
				end
			end
		end
	end
	return filename
end -- function samefile

---++++++++++++++++++++++++++++++++++++++++++++++++++++

-- Simple iterations - without many params.
function p.iterate(frame)
	local gpar = frame.args -- global parms
	local template = gpar[1] or '' -- template name
	local args = { -- Parameters for expanding the given template.
		'', -- [1] = v
		gpar[2] or '',
		gpar[3] or '',
	}
	local ppar = frameCurrent:getParent().args
	local results = {}
	for _, v in ipairs(ppar) do
		args[1] = v
		insert(results, expand(template, args))
	end
	return concat(results)
end -- function iterate

-- More iterations - for params, param pairs, or n-tuples.
function p.iteration(frame)
	local gpar = frame.args -- Global parms.
	local vmax = tonumber(gpar.n) or 2 -- Tuple number (default = 2).
	local vnum = vmax + 1 -- Additional not-changing params.
	local template = '' -- Template name.
	local args = {} -- Parameters for expanding the given template.
	for i, p in ipairs(gpar) do
		p = trim(p)
		if i == 1 then
			template = p
		else
			args[vnum] = p
			vnum = vnum + 1
		end
	end
	vnum = 1
	local ppar = frameCurrent:getParent().args
	local results = {} -- output
	for _, v in ipairs(ppar) do
		args[vnum] = trim(v)
		vnum = vnum + 1
		if vnum > vmax then
			insert(results, expand(template, args))
			vnum = 1
		end
	end
	return concat(results)
end -- function iteration

-- For different templates, for general use, e.g. Emoji.
function p.parlst(frame)
	local gpar = frame.args -- Global parameters.
	local template = trim(gpar.temp or '')
	local results = {}
	if template ~= '' then
		local ff = trim(gpar.ff or '') -- Inbetween?
		if ff ~= '' then
			ff = expand1(ff, trim(gpar.pf or ''))
		else
			ff = nil
		end
		local nocat = trim(gpar.nocat or '')
		local args = { -- Parameters for expanding the given template.
			nocat = nocat ~= '' and nocat or nil
		}
		for k, v in pairs(gpar) do
			v = trim(v)
			if v ~= '' then args[k] = v end
		end
		local count = tonumber(args[1] or '')
		for i = 1, (count or 1) do -- default: go 1× for nil
			if ff then insert(results, ff) end
			if count then args[1] = i - 1 end
			insert(results, expand(template, args))
		end
	end
	return concat(results)
end -- function parlst

-- Global function for one filename.
function p.filename(frame)
	local gpar = frame.args -- Global parameters.
	return samefile(trim(gpar[1] or ''), 1)
end -- function filename

-- For Template:Own based (one filename which is '.').
function p.ownbasby(frame)
	local gpar = frame.args -- Global parameters.
	return expand('F', {
		samefile(gpar[1] or '.', 1),
		by = trim(gpar[2] or '')
	})
end -- function ownbasby

-- For Template:Own based (horizontal - but vertical when "b1=").
function p.ownbased(frame)
	local ppar = frameCurrent:getParent().args
	local by0 = trim(ppar.b or ppar.by or ppar.u or ppar.user or '')
	local dis = trim(ppar.d or ppar.dis or ppar.display or '')
	local hil = trim(ppar.h or ppar.hilite or '')
	local lng = trim(ppar.i or ppar.lang or '')
	local wik = trim(ppar.l or ppar.w or ppar.wiki or '')
	local nam = trim(ppar.n or ppar.name or '')
	local opt = trim(ppar.o or ppar.opt or ppar.option or '')
	local mod = trim(ppar.m or ppar.mod or '')
	local pr4 = trim(ppar.par4 or ppar.qpar or '')
	local pr5 = trim(ppar.par5 or ppar.rpar or '')
	local btab = {}
	local dtab = {}
	local htab = {}
	local itab = {}
	local ltab = {}
	local ntab = {}
	local otab = {}
	local qtab = {}
	local rtab = {}
	local ttab = {}
	local utab = {}
	local x = 0 -- running index, can be ~= i
	local fst = 0 -- first occurrence
	local cor = 0
	local max = 0
	local plus = ''
	for i, v in ipairs(ppar) do
		if v == '+' then
			plus = '+'
		elseif v == '-' then
			if plus == '' then plus = '-' end
		else
			x = x + 1
			if fst == 0 then fst = x end
			local z = tostring(x)
			btab[x] = trim(ppar['b' .. z] or ppar['by' .. z] or ppar['u' .. z] or '-')
			dtab[x] = trim(ppar['d' .. z] or '-')
			htab[x] = trim(ppar['h' .. z] or '-')
			itab[x] = trim(ppar['i' .. z] or '-')
			ltab[x] = trim(ppar['l' .. z] or ppar['w' .. z] or '-')
			ntab[x] = trim(ppar['n' .. z] or '-')
			otab[x] = trim(ppar['o' .. z] or '-')
			qtab[x] = trim(ppar['q' .. z] or '-')
			rtab[x] = trim(ppar['r' .. z] or '-')
			ttab[x] = trim(ppar['t' .. z] or '-')
			utab[x] = trim(ppar['m' .. z] or '-')
		end
		max = x
		if v == 'x' or v == 'X' or v == '×' then
			cor = cor + 1
		end
	end -- for
	x = 0
	if fst > 0 then
		local mnm = trim(ppar[fst])
	end
	local hls = {
		'<',
		'', -- [2] = hl
		'>',
		'', -- [4] = (nm ~= '') and nm or vv
		'</',
		'', -- [6] = hl
		'>',
	}
	local args = { -- Parameters for expanding 'Template:F'.
		'', -- [1] = vv,
		'', -- [2] = nm
		'', -- [3] = ds
		'', -- [4] = op
		'', -- [5] = p4
		'', -- [6] = p5
		plus,
		l = '', -- ['l'] = il
		lang = '', -- ['lang'] = lg
		p = '', -- ['p'] = px
		by = '', -- ['by'] = by
		u = '', -- ['u'] = um
	}
	local results = {}
	for _, v in ipairs(ppar) do
		if v ~= '+' and v ~= '-' then
			x = x + 1
			local by = btab[x] ~= '-' and btab[x] or by0
			local ds = dtab[x] ~= '-' and dtab[x] or dis
			local hl = htab[x] ~= '-' and htab[x] or hil
			local il = ltab[x] ~= '-' and ltab[x] or wik
			local lg = itab[x] ~= '-' and itab[x] or lng
			local op = otab[x] ~= '-' and otab[x] or opt
			local p4 = qtab[x] ~= '-' and qtab[x] or pr4
			local p5 = rtab[x] ~= '-' and rtab[x] or pr5
			local um = utab[x] ~= '-' and utab[x] or mod
			local nm = ntab[x] ~= '-' and ntab[x] or ''
			local tx = ttab[x] ~= '-' and ttab[x] or ''
			local vv = trim(v)
			if find(vv, '/') == nil then
				vv = samefile(vv, x, mnm)
				if x == 1 then mnm = vv end
			end
			if vv ~= '' and vv ~= '×' then
				local px = ''
				if ppar.b1 == nil then -- parameter missing
					if x == 1 then
						px = '&#32;'
					else
						insert(results, commaAnd(x == max))
					end
				else --	ppar.b1 is defined (with value, or empty)
					px = '<br /> <span style=color:#69F>✦ </span>' -- "list" item
				end
				if sub(ds, -2) == 'px' then
					ds = sub(ds, 1, -3)
				end
				if nm == '' and x == 1 then
					nm = nam
				end
				if hl ~= '' then
					hls[2] = hl
					hls[4] = (nm ~= '') and nm or vv
					hls[6] = hl
					nm = concat(hlparts)
				end
				-- if il ~= '' then ds = '' end -- ? (discrepancy)
				if vv == 'x' then ds = '' end -- this should be the last "file"
				if by == ''	then -- check for abbreviating '/'
					local sby = find(vv, '/')
					if sby ~= nil then
						by = sub(vv, sby + 1)
						vv = sub(vv, 1, sby - 1)
						vv = samefile(vv, x, mnm)
						if x == 1 then mnm = vv end
					end
				end
				args[1] = vv
				args[2] = nm
				args[3] = ds
				args[4] = op
				args[5] = p4
				args[6] = p5
				args.l = il
				args.lang = lg
				args.p = px
				args.by = by
				args.u = um
				insert(results, expand('F', args))
				if tx ~= '' then
					insert(results, expand1('=', tx))
				end
			end
		end
	end -- for
	if max - cor > 9 then
		insert(results, expand1('Igen/cat', 'Own-based with more than 9 files|' .. max))
	end
 	return concat(results)
end -- function ownbased

-- Horizontal file list for: Template:SVG lang, Template:Lang gallery, and others.
function p.svglang(frame)
	local gpar = frame.args -- Global parameters.
	local template = gpar[1] or 'Source thumb' -- 'SVG lang', 'Lang gallery/thumb'
	local ppar = frameCurrent:getParent().args
	args = { -- Parameters to expand the given template.
		trim(ppar.file or ''),
		'', -- [2] = lang
		p = trim(ppar.p or ''),
	}
	local results = {}
	for _, v in ipairs(ppar) do
		local lang = trim(v)
		if lang > ' ' then
			args[2] = lang
			insert(results, trim(expand(template, args)))
			insert(results, NL)
		end
	end
	return concat(results)
end -- function svglang

-- Elements count for: Template:SVG lang, Template:Lang gallery, and others.
function p.elemct (frame)
	local ppar = frameCurrent:getParent().args
	local count = 0
	for _, v in ipairs(ppar) do
		if trim(v) ~= '' then
			count = count + 1
		end
	end
	return count
end -- function elemct

-- Horizontal file list for: Template:Filelist, Template:File.
-- Vertical file list for: Template:Other versions, Template:Derived from, Template:Derivative versions.
function p.filelist(frame)
	local gpar = frame.args -- Global parameters.
	local ppar = frameCurrent:getParent().args
	local spa = trim(ppar.spa or ppar.s or '-')
	local pfx = trim(ppar.x or ppar.pfx or ppar.prefix or '') -- "List" item {{Comma}}
	local dir = gpar[1] or 'none'
	if dir == 'vert' and pfx == '' then
		pfx = '\n* ' -- "list" item
	end
	local nam = trim(ppar.n or ppar.name or '')
	local dis = trim(ppar.d or ppar.z or ppar.dis or ppar.display or '')
	if sub(dis, -2) == 'px' then dis = sub(dis, 1, -3) end
	local opt = trim(ppar.o or ppar.opt or ppar.option or '')
	local pr4 = trim(ppar.par4 or ppar.qpar or '')
	local pr5 = trim(ppar.par5 or ppar.rpar or '')
	local pr6 = trim(ppar.par6 or ppar.vpar or '')
	local wik = trim(ppar.w or ppar.k or ppar.wiki or ppar.sisterproject or '')
	local int = trim(ppar.i or ppar.int or ppar.ind or ppar.inter or '')
	local pre = trim(ppar.p or ppar.pre or ppar.pretext or '')
	local by0 = trim(ppar.user or ppar.by or '')
	local mod = trim(ppar.m or ppar.mod or '')
	local lnk = trim(ppar.l or ppar.lnk or ppar.link or '')
	local con = trim(ppar.conj or ppar.con or ppar.c or '')
	con = sub(con, 1, 1)
	if ppar.z then
		if con == '' then con = 'n' end
		if opt == '' then opt = 'Z' end
		if pre == '' then pre = '&#32;' end
	end
	local vary = trim(ppar.vary or ppar.v or '') -- Variable pattern
	local var1 = trim(ppar.var1 or ppar.v1 or '') -- Variable filling space before
	local var2 = trim(ppar.var2 or ppar.v2 or '') -- Variable filling space after
	local replacement = {
		(var1 == 'space') and ' ' or var1,
		'', -- [2] = (sp == '+' and '&#32;' or '')
		'', -- [3] = vv
		(var2 == 'space') and ' ' or var2,
	}
	local stab = {}
	local xtab = {}
	local ntab = {}
	local dtab = {}
	local otab = {}
	local qtab = {}
	local rtab = {}
	local vtab = {}
	local ktab = {}
	local itab = {}
	local ptab = {}
	local btab = {}
	local utab = {}
	local ltab = {}
	local ttab = {}
	local loop = {}
	local mnum = 0 -- maximum index reached in the previous tables of indexed parameters
	for i, v in ipairs(ppar) do
		local z = tostring(i)
		stab[i] = trim(ppar['s' .. z] or '~')
		xtab[i] = trim(ppar['x' .. z] or '-')
		ntab[i] = trim(ppar['n' .. z] or ppar['l' .. z] or '-')
		dtab[i] = trim(ppar['d' .. z] or '-')
		otab[i] = trim(ppar['o' .. z] or '-')
		qtab[i] = trim(ppar['q' .. z] or '-')
		rtab[i] = trim(ppar['r' .. z] or '-')
		vtab[i] = trim(ppar['v' .. z] or '-')
		ktab[i] = trim(ppar['k' .. z] or '-')
		itab[i] = trim(ppar['i' .. z] or '-')
		ptab[i] = trim(ppar['p' .. z] or '-')
		btab[i] = trim(ppar['b' .. z] or ppar['by' .. z] or '-')
		utab[i] = trim(ppar['m' .. z] or '-')
		ltab[i] = trim(ppar['l' .. z] or '-')
		ttab[i] = trim(ppar['t' .. z] or '-')
		mnum = i
	end
	local args = { -- Parameters for expanding 'Template:F'.
		'', -- [1] = vv
		'', -- [2] = nm
		'', -- [3] = ds
		'', -- [4] = op
		'', -- [5] = p4
		'', -- [6] = p5
		'', -- [7] = p6
		l = '', -- ['l'] = pk
		lang = '', -- ['lang'] = pi
		p = '', -- ['p'] = pr
		by = '', -- ['by'] = by
		u = '', -- ['u'] = um
		link = '', -- ['link'] = (ln ~= '' and ln or nil)
	}
	local results = {}
	local lcnt = 0 -- Loop count.
	local i = 1 -- While index.
	while ppar[i] ~= nil do --	for i, v in ipairs(ppar) do
		local j = 1
		local vv = trim(ppar[i])
		if vary ~= '' and sub(vv, 1, 1) == '#' and sub(vv, -1) == '#' then -- Loop processing?
			loop = split(vv, '#', true--[[plain]])
			local llow = tonumber(loop[2]) or -1
			local lupp = tonumber(loop[3]) or -1
			if llow >= 0 and lupp >= llow then
				llow = llow + lcnt
				vv = tostring(llow)
				lcnt = lcnt + 1
				if llow < lupp then
					j = 0 -- Iterate this index.
				else
					lcnt = 0 --	Loop ended.
				end
			end
	 	end -- End loop processing.
		if sub(vv, 1, 1) == '"' and sub(vv, -1) == '"' then -- Text processing?
			insert(results, expand1('=', sub(vv, 2, -2)))
		elseif vary == '' or i > 1 then
			local sp = stab[i] ~= '~' and stab[i] or spa
			local px = xtab[i] ~= '-' and xtab[i] or pfx
			local nm = ntab[i] ~= '-' and ntab[i] or nam
			local ds = dtab[i] ~= '-' and dtab[i] or dis
			local op = otab[i] ~= '-' and otab[i] or opt
			local p4 = qtab[i] ~= '-' and qtab[i] or pr4
			local p5 = rtab[i] ~= '-' and rtab[i] or pr5
			local p6 = vtab[i] ~= '-' and vtab[i] or pr6
			local pk = ktab[i] ~= '-' and ktab[i] or wik
			local pi = itab[i] ~= '-' and itab[i] or int
			local pr = ptab[i] ~= '-' and ptab[i] or pre
			local by = btab[i] ~= '-' and btab[i] or by0
			local um = utab[i] ~= '-' and utab[i] or mod
			local ln = ltab[i] ~= '-' and ltab[i] or lnk
			local tx = ttab[i] ~= '-' and ttab[i] or ''
			if vary ~= '' then
				replacement[2] = (sp == '+' and '&#32;' or '')
				replacement[3] = vv
				vv = gsub(ppar[1], vary, concat(replacement))
			else
				vv = samefile(vv, i, ppar[1])
			end
			if vv ~= '' and vv ~= '×' then
				if dir == 'hori' and con ~= 'n' then -- Horizontal list.
					px = (i == 1) and '&#32;' or commaAnd(i == mnum)
				else --	elseif dir == 'vert' then -- Vertical list.
					px = (i == 1) and '&#32;' or px
				end
				if px ~= '' then insert(results, px) end
				if by == '' then -- Check for abbreviating '/'.
					local sby = find(vv, '/')
					if sby ~= nil then
						by = sub(vv, sby + 1)
						vv = sub(vv, 1, sby - 1)
					end
				end
				args[1] = vv
				args[2] = nm
				args[3] = ds
				args[4] = op
				args[5] = p4
				args[6] = p5
				args[7] = p6
				args.l = pk
				args.lang = pi
				args.p = pr
				args.by = by
				args.u = um
				args.link = (ln ~= '' and ln or nil)
				insert(results, expand('F', args))
				if tx ~= '' then insert(results, expand1('=', tx)) end
			end -- if vary
		end -- if vv
		i = i + j -- next in do loop
	end -- while
 	return concat(results)
end -- function filelist

-- For Template:Attribs (param pairs; but also for single params).
function p.attribs(frame)
	local ppar = frameCurrent:getParent().args
	local un = trim(ppar.by or ppar.U or ppar.u or '')
	local md = trim(ppar.m or ppar.mod or '')
	local tt = trim(ppar.t or ppar.to or '') -- "to" topic.
	local f = trim(ppar.f or ppar.from or tt) -- "from".
	local p = trim(ppar.p or ppar.part or '')
	local args = { -- Parameters for expandnig 'Template:Attrib'.
		'', -- [1] = vx
		'', -- [2] = ux
		'-',
		trim(ppar.type or 'SVG'), -- Needs check.
		'',
		ux, -- [6] = ux
		tt,
		'', -- [8] = (ftab[hnum] ~= '.') and ftab[hnum] or f
		'', -- [9] = (ptab[hnum] ~= '.') and ptab[hnum] or p
		m = '', -- ['m'] = (mtab[hnum] ~= '.') and mtab[hnum] or md
		s = trim(ppar.s or ppar.style or 's'), -- default
	}
	local ftab = {} -- "from" topic.
	local ptab = {} -- Parts.
	local mtab = {} -- Modification.
	local rtab = {} -- Working table.
	local fnum = 0
	local rnum = 0
	local hnum = 0
	for i, v in ipairs(ppar) do
		fnum = fnum + 1 -- Input parm number.
		if fnum % 2 == 0 then -- Even: should be a username.
			local enam = trim(v)
			-- A rough check: is_extension?
			local snam = lower(sub(enam, -4))
			if snam == '.png'
			or snam == '.gif'
			or snam == '.jpg'
			or snam == '.svg'
			-- Exotics:
			or snam == '.mid'
			or snam == '.ogg'
			or snam == '.tif'
			or snam == '.wav'
			or snam == '.xcf'
			or snam == '.pdf' -- Note: no "short alias" in samefile()
			-- Note: Longer extensions still not supported (need check):
			-- snam == '.djvu'
			-- snam == '.webp'
			then -- No - it's the next filename.
				insert(rtab, '') -- Empty username inbetween.
				rnum = rnum + 1
				fnum = fnum + 1 -- Make it odd.
			end
		end
		if fnum % 2 == 1 then -- Odd (now): is a filename.
			hnum = (fnum + 1) / 2
			local z = tostring(hnum)
			ftab[hnum] = ppar['f' .. z] or '.'
			ptab[hnum] = ppar['p' .. z] or '.'
			mtab[hnum] = ppar['m' .. z] or '.'
		end
		-- table.maxnum(ppar) does not work; therefore the "rtab" workaround.
		insert(rtab, ppar[i]) -- = enam
		rnum = rnum + 1
	end
	if rnum % 2 == 1 then -- Plus one item when odd number.
 		insert(rtab, '') -- Empty user name, to get a pair.
	end
	local results = {} -- Output.
	local vx = ''
	for i, v in ipairs(rtab) do
		if i % 2 == 1 then -- odd: 1st value in pair is a filename
			vx = trim(v) -- this 1st 'v' should not be empty
		else -- even: 2nd value in pair is a user name (possibly empty)
			local ux = trim(v) -- this 2nd 'v' can be empty
			if ux == '' then ux = un end -- Does not work otherwise?
			hnum = i / 2
			args[1] = vx -- a filename
			args[2] = ux -- a username
			args[6] = ux -- a username
			args[8] = (ftab[hnum] ~= '.') and ftab[hnum] or f
			args[9] = (ptab[hnum] ~= '.') and ptab[hnum] or p
			args.m = (mtab[hnum] ~= '.') and mtab[hnum] or md
			insert(results, expand('Attrib', args))
			vx = '' -- reset the filename for the next pairs
		end
	end
	return concat(results)
end -- function attribs

-- Get the user id: the (last) parameter which is prefixed by '/'.
function p.byuser(frame)
	local ppar = frameCurrent:getParent().args
	local user = ''
	for _, value in pairs(ppar) do
		if value ~= nil and user == '' and sub(value, 1, 1) == '/' then
			user = sub(value, 2) -- remove the '/'
		end
		-- TODO: Test whether userID exists?
	end
	return user
end -- function byuser, for template:F

-- for Template:userlist (horizontal - but vertical when dir=I/O/U/D/V).
function p.userlist(frame)
	local gpar = frame.args -- global parms (par/P, dir/V)
	local ppar = frameCurrent:getParent().args

	local mod = ppar.m or ppar.mod or ppar.user or ppar.u or ppar.page or ppar.p or ''
	local hil = ppar.h or ppar.hilite or ''
	local nam = ppar.n or ppar.name or ''
	local wik = ppar.w or ppar.wiki or ppar.lang or ''
	local opt = ppar.o or ppar.opt or ppar.option or ''
	local lnk = ppar.l or ppar.link or ''
--@	local pr4 = ppar.q or ppar.qpar or ppar.par4 or ''
--@	local pr5 = ppar.r or ppar.rpar or ppar.par5 or ''
	local cas = ppar.c or ppar.case or ''
	local trl = ppar.t or ppar.i18n or ppar.translate or ''

	-- If ever template expansion of prim, prfx, sufx, or pend is needed,
	-- this must be done once here (out of the loop below).
	local prim, prfx, sufx, pend = '', '', '', ''
	if gpar.dir == 'O' then -- Ordered/numbered vertical list.
		prim, prfx, sufx, pend = '<ol>', '<li>', '</li>', '</ol>'
	elseif gpar.dir == 'U' then -- Unordered/bulleted vertical list.
		prim, prfx, sufx, pend = '<ul>', '<li>', '</li>', '</ul>'
	elseif gpar.dir == 'D' then -- Simple definition/indented vertical list.
		prim, prfx, sufx, pend = '<dl>', '<dt>', '</dt>', '</dl>'
	elseif gpar.dir == 'V' then -- Custom vertical list of users.
		prim, prfx, sufx, pend = '<dl style="margin:.3em 0">', '<dt><span style="color:#69F">✦ </span>', '</dt>', '</dl>'
	else --'I': Inline horizontal list.
		-- prim, prfx, sufx, pend: not used; replaced by separators, expanded by commaAnd()
	end

	local mtab = {} -- u_mod c/t/w/wt (aka utab, mtab, ptab)
	local htab = {} -- hilite
	local ntab = {} -- 2 display name
	local wtab = {} -- 3 interwiki
	local otab = {} -- 4 +/- option
	local ltab = {} -- link option
--@	local qtab = {}
--@	local rtab = {}
	local ctab = {} -- case
	local ttab = {} -- translate i18n
	local xtab = {} -- postfix text

	local x = 0 -- running index, can be ~= i
	local y = 0 -- running index, can be ~= i
	local plus = '' -- opt
	local xmax = 0
	for _, v in ipairs(ppar) do
		v = mw.text.trim ( v )
		if v == '+' then
			plus = '+'
		elseif v == '-' then
			if plus == '' then
				plus = '-'
			end
		else
			y = y + 1
			if gpar.par == 'P' --[[pairs of (user, name)]] and y % 2 == 0 --[[1=user, 0=name]] then
				x = x
			else
				x = x + 1
				local z = tostring(x)
				mtab[x] = ppar['m' .. z] or ppar['u' .. z] or ppar['p' .. z] or '-'
				htab[x] = ppar['h' .. z] or '-'
				ntab[x] = ppar['n' .. z] or '-'
				wtab[x] = ppar['w' .. z] or '-'
				otab[x] = ppar['o' .. z] or '/'
				ltab[x] = ppar['l' .. z] or '-'
--@				qtab[x] = ppar['q' .. z] or '-'
--@				rtab[x] = ppar['r' .. z] or '-'
				ctab[x] = ppar['c' .. z] or '-'
				ttab[x] = ppar['t' .. z] or '-'
				xtab[x] = ppar['x' .. z] or '-'
			end
		end
		xmax = x
	end -- for
	if plus ~= '' and opt == '' then
		opt = plus --[[discrepancy? opt has priority]]
	end
	local odd = y % 2 --[[1 when last one not paired]]
	local p2s = {
		'~',
		'', -- [2] = (wk ~= '') and wk or 'commons'
		'wiki',
	}
	local hls = {
		'<',
		'', -- [2] = hl
		'>',
		'', -- [4] = (nm ~= '') and nm or vv
		'</',
		'', -- [6] = hl
		'>',
	}
	local args = { -- Parameters for expanding 'Tempplate:U/main'.
		'', -- [1] = vv,
		'', -- [2] = nm,
		'', -- [3] = wk,
		'', -- [4] = op,
		link = '', -- ['link'] = lk
		par1 = '', -- ['par1'] = p1
		par2 = '', -- ['par2'] = p2
--@		par4 = '', -- ['par4'] = p4
--@		par5 = '', -- ['par5'] = p5
		case = '', -- ['case'] = cs
		i18n = '', -- ['i18n'] = tr
	}
	local results = {}
	local out = 0
	local vv = ''
	x = 0
	y = 0
	for _, v in ipairs(ppar) do
		v = trim(v)
		if v ~= '+' and v ~= '-' then
			y = y + 1
			if gpar.par == 'P' --[[pairs of (user, name)]] and y % 2 == 1 --[[1=user, 0=name]] and odd == 0 then -- last one paired
				vv = v -- userid
			else
				x = x + 1
				local md = (mtab[x] ~= '-') and mtab[x] or mod
				local hl = (htab[x] ~= '-') and htab[x] or hil
				local nm = (ntab[x] ~= '-') and ntab[x] or nam
				local wk = (wtab[x] ~= '-') and wtab[x] or wik
				local op = (otab[x] ~= '/') and otab[x] or opt
				local lk = (ltab[x] ~= '-') and ltab[x] or lnk
--@				local p4 = (qtab[x] ~= '-') and qtab[x] or pr4
--@				local p5 = (rtab[x] ~= '-') and rtab[x] or pr5
				local cs = (ctab[x] ~= '-') and ctab[x] or cas
				local tr = (ttab[x] ~= '-') and ttab[x] or trl
				local tx = (xtab[x] ~= '-') and xtab[x] or '' 
				if gpar.par == 'P'	then --	y%2 = 0 (name)
					if odd == 1	then --[[no: last user:]]
						vv = v --[[user]]
					else
						nm = v --[[name]]
					end
				else
					vv = v
				end
				if hl ~= '' then
					hls[2] = hl
					hls[4] = (nm ~= '') and nm or vv
					hls[6] = hl
					nm = concat(hls)
				end
				if md == 'n' or md == 'no' then
					lk = md -- no link
				end
				local p1 = '' -- prefix
				if md == 't' or md == 'wt' or md == 'tw' then
					p1 = '&#32;talk'
				end
				local p2 = '' -- postfix
				if md == 'w' or md == 'wt' or md == 'tw' then
					p2s[2] = (wk ~= '') and wk or 'commons'
					p2 = concat(p2s)
				end
				if vv ~= '' and vv ~= '×' then
					if md == 'c' then vv = 'Special:Contributions/' .. vv end
					if gpar.dir == 'I' or gpar.dir == 'O' or gpar.dir == 'U' or gpar.dir == 'V' then
						-- Vertical list (uses: prim, prfx, sufx, pend).
						if out == 0 then
							insert(results, prim) -- expansion of prim not needed
						end
						insert(results, prfx) -- expansion of prefx not needed
						out = out + 1
					else -- Horizontal inline list.
						if x ~= 1 then
							insert(results, commaAnd(x == xmax))
						end
					end
					if sub(vv, 1, 2) == '{{' or sub(vv, 1, 2) == '[[' then
						insert(results, expand1('=', vv))
					else
						args[1] = vv
						args[2] = nm
						args[3] = wk
						args[4] = op
						args.link = lk
						args.par1 = p1
						args.par2 = p2
--@						args.par4 = p4
--@						args.par5 = p5
						args.case = cs
						args.i18n = tr
						insert(results, expand('U/main', args))
					end
					if tx ~= '' then
						insert(results, expand1('=', tx))
					end
					if sufx ~= '' then
						insert(results, sufx) -- expansion of sufx not needed
					end
				end
			end
		end
	end -- for
	if out > 0 and pend ~= '' then
		insert(results, pend) -- expansion of pend not needed
	end
	return concat(results)
end -- function userlist

-- Table for templates: Legend; Legend-line, Legend2, Legend-small... (param pairs)
function p.legendt (frame)
	local ppar = frameCurrent:getParent().args
	local ttip = trim(ppar.tt or ppar.ttip or ppar.tooltipping or '')
	local frmp = frame.args
	local template = trim(frmp[1] or 'Legend')
	local args = { -- Parameters for expanding the given template.
		'', -- [1] = v1
		'', -- [2] = v
		lang = trim(ppar.lang or ''),
		p = trim(frmp[2] or ''),
		size = trim(frmp[3] or ''),
		css = '', -- ['css'] = '" title="' .. upper(v1)
	}
	local results = {}
	local v1 = ''
	for _, v in ipairs(ppar) do
		if v1 == '' then
			v1 = v
			if ttip == 'yes' and v1 ~= '' then -- Tooltip the colors.
				-- HACK: terminates the style="..." attribute and opens another attribute
				args.css = '" title="' .. Ucfirst(v1)
			end
		else
			args[1] = v1
			args[2] = v
			insert(results, expand(template, args))
			v1 = ''
			args.css = ''
		end
	end
	if v1 ~= '' then -- Last item: problem when not a last pair
		insert(results, expand(template, args))
	end
	return concat(results)
end -- function legendt

-- For Template:ColorString (Igen/cbox, param pairs).
local argsColorbox = { -- Parameters for expanding 'Template:Igen/cbox'.
	'', -- [1] -- (striped; border)
	'', -- [2] -- color value
	'', -- [3] -- either code-2 ( BCDLMST-), or color value
	0, -- [4] -- count of spaces
	'', -- [5] -- ?
	'', -- [6] -- 'char' or 'file'
	'', -- ['lang'] = ppar.lang or '{{PAGELANGUAGE}}',
}
function p.colorbox(frame)
	local ppar = frameCurrent:getParent().args
	argsColorbox.lang = ppar.lang or '{{PAGELANGUAGE}}'
	local results = {}
	local v1 = ''
	for i, v in ipairs(ppar) do
		if i <= 2 then
			argsColorbox[3] = v -- 	either code-2 ( BCDLMST-), or color value
		elseif v1 == '' then
			v1 = v -- (striped; border)
			local ct = 0 -- count of spaces
			local bp = 0 -- position of first space
			for c = 1, len(v1) do
				if sub(v1, c, c) == ' ' then
					ct = ct + 1 -- space count
					if bp == 0 then bp = c end
				end
			end
			local bc = ''
			if ct >= 3 then -- 3 = border, 4 = line
				bc = sub(v1, bp + 1)
				v1 = sub(v1, 1, bp - 1)
			end
			local cf = ''
			if ct == 1 then -- 'char' or 'file'
				bc = sub(v1, bp + 1)
				v1 = sub(v1, 1, bp - 1)
				cf = (sub(bc, -4, -4 ) == '.') and 'file' or 'char'
			end
			argsColorbox[4] = ct -- count of spaces
			argsColorbox[5] = bc
			argsColorbox[6] = cf -- 'char' or 'file'
		else
			argsColorbox[1] = v1
			argsColorbox[2] = v
			insert(results, expand('Igen/cbox', argsColorbox))
			v1 = ''
		end
	end
	if v1 ~= '' then
		argsColorbox[1] = v1
		argsColorbox[2] = ''
		insert(results, expand('Igen/cbox', argsColorbox)) -- last item
	end
	return concat(results)
end -- function colorbox

--------------------------------------------------------------------------------
-- Returns a parameter list: (replacement of «#» or «~» by «=»: not necessary).
function p.plist(frame)
	local ppar = frameCurrent:getParent().args
	return concat(ppar, '|') -- return gsub(concat(ppar, '|'), '#', '='), not necessary
end -- function plist

-- Repeats a text string.
function p.loop (frame)
	local ppar = frameCurrent:getParent().args
	return string.rep(ppar[2] or '&nbsp', tonumber(ppar[1] or 1))
end -- function loop

-- Increment a hex number by a ±decimal value
function p.incrhx(frame)
	local gpar = frame.args -- global parms
	return format('%X', tonumber(gpar[1], 16) + gpar[2])
end -- function incrhx

-- Display inline a range of characters with their Unicode code point in tool tip, such as emojis for Template:Emoji, with optional
-- line breaks to limit lines to at most 16 characters (breaks after every code point which is the last one in a UCS column):
-- {{#invoke:Iteration|parlst |temp=Emoji |1=(nb of characters) |2=(4- to 6-digits uppercase hexadecimal code point)
-- |3=(border or 0) |4=(padding or 0) |5=(font size in px or -)
-- |6=(styles or -) |(nocat=1)
-- }}
local argsEmodis = {
	-- WARNING: partial HTML (see the template using this function)
	-- This function can only be used after an UNTERMINATED opening HTML tag,
	-- whose LAST attribute MUST BE ' title=' not followed by quotes.
	-- This returned value closes the opening HTML tag of the template
	-- The calling template will then append itself the closing HTML tag.
	'"U+',
	'', -- [2] = ucod
	'">&#x',
	'', -- [4] = ucod
	';',
	nil -- [6] = optional line break
}
function p.emodis(frame)
	local gpar = frame.args -- global parms
	local code = tonumber(gpar[1], 16) + gpar[2]
	local ucod = format('%04X', code)
	argsEmodis[2] = ucod
	argsEmodis[4] = ucod
	argsEmodis[6] = gpar[3] == '16' and (code % 16 == 15 and '<br />') or nil
	return concat(argsEmodis)
end -- function emodis

-- For Template:Emoji table: {{#invoke:Iteration|emotab|prefix=1F250|par1=... | ... }}
local argsEmotab = { -- Parameters for expanding 'Template:F'.
	'', -- [1] = file
	'', -- [2] = 'Z'
	'', -- [3] = '49'
	'', -- [4] = 'b'
	'', -- [5] = 'l'
	'', -- [6] = ''
	link = '*',
}
function p.emotab(frame)
	local gpar = frame.args -- global parms
	local codp
	codp = trim(gpar.par1 or '') --[['Z' ]] argsEmotab[2] = (codp ~= '') and codp or nil
	codp = trim(gpar.par2 or '') --[['49']] argsEmotab[3] = (codp ~= '') and codp or nil
	codp = trim(gpar.par3 or '') --[['b' ]] argsEmotab[4] = (codp ~= '') and codp or nil
	codp = trim(gpar.par4 or '') --[['l' ]] argsEmotab[5] = (codp ~= '') and codp or nil
	codp = trim(gpar.par5 or '') --[[''  ]] argsEmotab[6] = (codp ~= '') and codp or nil
	codp = trim(gpar.codp or '')
	local parts = {
		'', -- [1] = v
		'', -- [2] = ((file == 'Emojio' or file == 'Fxemoj' or file == 'OpenMo') and upper or lower)(codp)
		'.svg',
	}
	local results = {}
	for _, v in ipairs(gpar) do
		local file = sub(v, 1, 6)
		parts[1] = v
		parts[2] = ((file == 'Emojio' or file == 'Fxemoj' or file == 'OpenMo') and upper or lower)(codp)
		file = concat(parts)
 		if titleNew(file, 6).exists then
			argsEmotab[1] = file
			insert(results, expand('F', argsEmotab))
		end
	end
	return concat(results)
end -- function emotab

-- For template: Navcat (via Navcattab).
function p.navcat(frame)
	local gpar = frame.args -- global parms
	local cats = { -- Categories to generate.
		trim(gpar.c1 or ''),
		'', -- [2] = ctxt
		trim(gpar.c2 or ''),
	}
	local args = { -- Parameters for expanding 'Template:Navcat'.
		'', -- [1] = (ccod = vtab[2] --[[opt.]])
		'', -- [2] = concat(cats)
		'', -- [3] = (dtxt = Ucfirst(ctxt))
		'', -- [4] = (cnam = vtab[3] or ccod or ctxt)
		span = trim(gpar.span or ''),
	}
	local ppar = frameCurrent:getParent().args
	local results = {}
	for _, v in ipairs(ppar) do
		v = trim(v)
		if v == '' or v == 'nl' then
			insert(results, '<br /> ')
		else
			local vtab = split(v, '/')
			local ctxt = vtab[1] or '&nbsp;' --[[requ.]]
			local ccod = vtab[2] --[[opt.]]
			cats[2] = ctxt
			args[1] = ccod
			args[2] = concat(cats)
			args[3] = Ucfirst(ctxt)
			args[4] = vtab[3] or ccod or ctxt
			insert(results, expand('Navcat', args))
		end
	end
 	return concat(results)
end -- function navcat

-- For Template:Tle.
function p.tleparm(frame)
	local ppar = frameCurrent:getParent().args
	local parnumber = trim(ppar.number or '')
	local parkbd = trim(ppar.kbd or '')
	if parkbd ~= '' then
		parkbd = {
			'<kbd>',
			'', -- [2] = par1
			'</kbd>',
		}
	else
		parkbd = nil
	end
	local args = { -- Parameters for expanding 'Template:Tle/parm'.
		'', -- [1] = par1
		'', -- [2] = v
		trim(ppar.f or ''), -- form feed
		trim(ppar.style or ''), -- style for parameter value
		trim(ppar.style2 or ''), -- style for parameter name
	}
	local results = {}
	for i, v in ipairs(ppar) do
		local z = tostring(i)
		local par1 = trim(ppar['p' .. z] or '')
		if par1 ~= '' and parkbd then
			parkbd[2] = par1
			par1 = concat(parkbd)
		end
		if par1 == '' and parnumber ~= '' then
			par1 = z
		end
		if par1 ~= '' then -- keep parameter value as is if positional (and not parnumberd explicitly)
			v = trim(v)
			if v == '' then v = '&thinsp;' --[[Note: '&thinsp;' would change the semantics.]] end
		end
		args[1] = par1
		args[2] = v
		insert(results, expand('Tle/parm', args))
	end
 	return concat(results)
end -- function tleparm

-- for Template:Variate.
function p.variation(frame)
	local ppar = frameCurrent:getParent().args
	local sept = ppar.sept or '|' -- separator before
	local sample = trim(ppar[1] or '') -- sample string
	local pattern = ppar.pttn or '?' -- pattern
	local var1 = ppar.var1 or '' -- filling space before
	local var2 = ppar.var2 or '' -- filling space after
	local sep2 = ppar.sep2 or '' -- separator after
	local parts = { -- Replacement parts for gsub().
		(var1 == 'space') and ' ' or var1,
		'', -- [2] = trim(v)
		(var2 == 'space') and ' ' or var2,
	}
	local results = {}
	for i, v in ipairs(ppar) do
		if i > 1 then
			insert(results, sept)
			parts[2] = trim(v)
			insert(results, gsub(sample, pattern, concat(parts)))
			insert(results, sep2)
		end
	end
	return concat(results)
end

return p