模块:Iteration

来自有兽档案馆
文档图示 模块文档[创建] [跳转到代码]

本模块还没有文档页面。

您可以创建文档以让编者更好地理解本模块的用途。
编者可以在本模块的沙盒创建 | 镜像和测试样例创建页面进行实验。
请将模块自身所属的分类添加在文档中。本模块的子页面
-- This module contains the functions for different templates, solving their iteration problems.

local max = math.max
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 char = mw.ustring.char
local NL     = char(10)
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 -- Slow expansion (e.g. for template = 'Emoji', will recurse into iterations calling p.emodis)
		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 '') or 0
		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
	else -- Template expansion not needed (much faster, without recursed iterations calling p.emodis from that template).
		local count = tonumber(gpar[1] or '') or 0
		local code = tonumber(gpar[2] or '', 16) or 0
		local args = {
			'<span title="U+',
			'', -- [2] = format('%04X', code + i - 1)
			'">',
			'', -- [4] = char(code + i - 1)
			'</span>',
		}
		for i = 1, count do
			args[2] = format('%04X', code + i - 1)
			args[4] = char(code + i - 1)
			insert(results, concat(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] = format('%04X', code)
	'">',
	'', -- [4] = char(code)
	nil -- [5] = optional line break
}
function p.emodis(frame)
	local gpar = frame.args -- global parms
	local code = tonumber(gpar[1], 16) + gpar[2]
	argsEmodis[2] = format('%04X', code)
	argsEmodis[4] = char(code)
	argsEmodis[5] = 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