Module:Catnav

-- please report issues to the discussion page -- if you will not fix them yourself

local p = { } local qp = "%f[%w][Qq]%d+"

function exists(w) local t = mw.title.new(w, "") return t and t.exists end

function isRedirect(w) local t = mw.title.new(w, "") return t:getContent:find(".ategory ?.edirect") or t.isRedirect end

-- mw.getCurrentFrame:callParserFunction("PAGENAME", w) function pagename(w) local t = mw.title.new(w, "") return t and t.text end

-- mw.title.new(w, ""):inNamespace(mw.site.namespaces.File.id) function imgexists(w) return w:find("File:", 1, true) == 1 or w:find("Image:", 1, true) == 1 end

-- flatten table entries of t to a string function flatten(t) local r = tbl for u, v in ipairs(t) do u = { } for i = 1, 10 -- v may not be a sequential table do if v[i] and #v[i] > 0 then r.ins(u, v[i]) end end if #u > 0 then u = mw.text.split(r.cat(u, " "):gsub("&#x8;", "\8"), "&#x7f;") for i = #u-1, 1, -1 do u[i] = u[i] .. u[i+1]:sub(2) end u = mw.text.split(u[1], "\8") for i = 2, #u do u[i] = u[i-1]:sub(1, -2) .. u[i] end r:app(u[#u]) end end return r end

function tbl(t) t = t or { } t.trc = function (_, x)		while not t:rm:find(x, 1, true) do end return 1 end t.app = function (...) for i = 2, arg.n do t[#t + 1] = arg[i] end end t.cat = table.concat t.ins = table.insert t.rm = table.remove return t end

function top(title, i, pfx, sfx, tpn) local r = tbl{ '', }	if def(i.img) then if imgexists(i.img) then local wl = def(flatten[1], '') i.lnk = exists(wl) and wl or exists(i.lnk) and i.lnk or '' r:app(				'',				'', i.wth, '|link=', i.lnk, ' '			) else r:app(i.img) end end if def(title) then local pn = pagename(flatten[1]) if pn		then r:app("", pn, "&#x202F;:&#32;") end end return r:cat("") end

function bottom return ' ' end

function row(disp, link, pref, suff, ticl, note, icon,				aln, wth, pfx, sfx, all, css, sep) local r = tbl if disp or link then local spA = ' ' local spZ = ' ' local c, _l = 0 r.rwd = function(t, l)			if c > 1 and _l and isRedirect(_l) then c = c - t:trc(spA) end _l = not all and l		end r:app("", "", "") -- maybe seq, seqdata, sep if link then local wl = {} for _p in mw.text.gsplit(pref or "", "|") do for _s in mw.text.gsplit(suff or "", "|") do if ticl then wl[#wl+1] = { pfx, ticl, _p, link, _s, sfx } end wl[#wl+1] = { pfx, _p, link, _s, sfx } end end for _, l in ipairs(flatten(wl)) do if all or exists(l) then c = c + 1 r:rwd(l) r:app(spA, c > 1 and "&#32;≈&#32;" .. wth .. "|link=" .. l)					end					if disp					then						r:aln(icon and " " .. disp)					end					r:app("", note or "", spZ)				end			end			r:rwd		else			if disp			then				r:app(spA, disp, note or "", spZ)				c = c + 1			end		end		local h, t = sep:seq_bounds		local s = c > 0 and sep:get		if h and h < 0 then r[1] = _seq("seq", css) end		if h then r[2] = _seq("seqdata", css, h) end		if s then r[3] = s end		if t then r:app(_seq, _seq) end	else		if note		then			if sep:seq_ahead			then r:app(_seq("seq", css), _seq("seqlabel", css, sep:seq_ahead),				css.__indent and (note:gsub("^ *<[Bb][Rr] */?>", "")) or note,				_seq)			else r:app(note)			end		end	end	return r:cat("") end

function _seq(class, css, c)	return not class and ' ' or tbl{ ' 0 and css['_' .. class], ''),		'">', }:cat('') end

function _sep(sep, compact, omit) local seqlabeled local seq local c = 0 return function (ld) return { get = function (_, r)				r = not omit and sep omit = nil return r			end, seq_ahead = function seqlabeled = ld				return seqlabeled and c			end, seq_bounds = function local h, t				if not seq then h = seqlabeled and c or (-1 - c)					seq = true omit = not compact or c == 0 end if seq and not ld				then t = c					seqlabeled = nil seq = nil c = c + 1 end return h, t			end, }	end end

function _tpn(f) f = f:getParent f = f and f:getTitle return def(f and f:find("Template:", 1, true) == 1 and f) end

-- maintenance / tracker categories function maintcat(tpn, r, t)	for k, v in pairs(t) do if v and tpn then r:app("") end end end

-- def(x) == nil if x is empty function def(x, y)	return x and #x > 0 and x or y end

function key(x) local f = mw.ustring.gsub return x and (f(f(x, "<(%w+)%f[%W][^>]*>(.*)", "%2"), "%W", "")) end

function rvd(x) return x:find("right", 1, true) and "left" or "right" end

function use(x) return x and #x > 0 and ("only once true yes 1 2"):find(x, 1, true) or nil end

function number_suffixed_named_args(f, a, qc, tpn) local r = tbl local o = use(a.compact) local s = _sep(a.__sep, o)	local s0 = s local s1 = s(1) local icons = use(a.icons) local fuse_icons = not (icons == 1) or nil local srt = fuse_icons and use(def(a.sort, "1")) local srt_once = srt == 6 and o	local ttr = tpn and tpn .. "/i18n" local tr = def if ttr and exists(ttr) then tr = function (x) x = x and f:expandTemplate{ title = ttr, args = { x } } return def(x) end local k = tr("__sort__") or "" srt = (srt_once or #k == 8 or use(k)) and srt end local Q = function (x, ex) local f = function (m) return ex and ex:find(m, 1, true) and m or mw.wikibase.getLabel(m) end return x and (x:gsub("i18n{([^}]+)}", tr):gsub(qp, f)) end local t = not srt and r or tbl{ srt = function (t, x)			if (not srt_once or srt_once and x) and #t > 0 then table.sort(t, function (x, y) return y.key < x.key end) while #t > 0 do r:ins(t:rm) end end end, }	local u = 1 + (qc or def(a.maxnum, 99)) for c = 0, u	do local i = icons and def(a["icon"..c]) local d = def(a["display"..c]) local l = def(a["link"..c]) local dl = d or l		if d and imgexists(d) then i, d = d, nil else d = (fuse_icons or not i or nil) and Q(d or tr(l), not qc and l)		end if r.n and (not o and dl or not dl) then r:ins{ note = r.n, sep = s1, } end r.n = Q(def(a["note"..c])) if dl		then t:ins{ disp = d, icon = i, link = l, key = srt and key(d) or c,				note = r.n, sep = s1, ticl = def(a["article"..c], a.__art), pref = a["prefix"..c], suff = a["suffix"..c], } r.n = nil else if srt then t:srt(c == u) end if r[#r] then r[#r].sep = s0 end end end return r end

function named_args(_f) local a = _f.args local f = _f:getParent or _f if pairs(a)(a) == nil -- if invoked without args then a = f.args      -- take parent args, else take unset args only: else if _f ~= f then for k, v in pairs(f.args) do a[k] = a[k] or v end end end a.__art = def(a.article) a.__art = not a.__art and tonumber(a.all) == 2 and "the" or a.__art a.__rtl = mw.getLanguage(f:callParserFunction("int", "lang")):isRTL a.__sep = def(a.sep) or " ·&#32;" a.img = { img = a.img or a.image, aln = def(a.imgalign or a.imagealign, "right"), lnk = a.imglink or a.imagelink or a.title or "", stl = def(a.imgstyle or a.imagestyle), wth = def(a.imgwidth or a.imagewidth, "30px"), }	a.iconsalign = def(a.iconsalign, rvd(a.img.aln)) a.iconswidth = def(a.iconswidth, "15px") a.img.aln = a.__rtl and rvd(a.img.aln) or a.img.aln f = { __indent = not use(a.compact) and use(a.indent), inline = "display:inline;", }	a.__css = f	if f.__indent then f.seq      = "display:table-row;" f.seqlabel = a.__rtl and "left" or "right" f.seqlabel = "display:table-cell;white-space:nowrap;vertical-align:top" ..";text-align:"..f.seqlabel..";padding-"..f.seqlabel..":.4em;" f.seqdata  = "display:table-cell;" f._seqlabel = "padding-top:.4em;" f._seqdata = f._seqlabel else f.seq      = f.inline f.seqlabel = f.inline f.seqdata  = f.inline end return a end

function p.main(frame, qc) local a = named_args(frame) local r = tbl local aln = a.iconsalign:find("right", 1, true) local wth = a.iconswidth local pfx = a.prefix local sfx = a.suffix local all = use(a.all or a.redlinks) local css = a.__css local tpn = _tpn(frame) r:app(a.__rtl and ' ' or '') r:app(top(a.title, a.img, pfx, sfx, tpn)) for _, v in ipairs(number_suffixed_named_args(frame, a, qc, tpn)) do r:app(row(v.disp, v.link, v.pref, v.suff, v.ticl, v.note, v.icon, aln, wth, pfx, sfx, all, css, v.sep)) end r:app(bottom) r:app(a.__rtl and ' ' or '') maintcat(tpn, r, {		redlink = all,		--indent = a.__css.__indent,	}) return r:cat("") end

function p.Q(frame) local a = frame.args local c = 1 local ip = def(a.icons) and (a.icons:match("^[0no]+[ly]*") or "1") or "" local ps = function (m, p)		for _, v in ipairs(mw.wikibase.getBestStatements(m, p)) do v = v.mainsnak and v.mainsnak.datavalue v = v and v.value if v then return v end end end a.icons, ip = ip, use(ip) and a.icons:match("P%d+$") a.prefix = a.prefix or ":Category:" for i, q in ipairs(a) do c = c + 1 if def(q) then for m in q:gmatch(qp) do	-- commons cat a["link"..i], m = ps(m, "P373"), ip and ps(m, ip) a["icon"..i] = m and "File:" .. m				break end if a["link"..i]			then a["display"..i] = q -- q may contain wikitext else a["note"..i] = q -- no wikidata id or no P373 end end end return p.main(frame, c) end

return p