Module:Catnav
CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules
For calling in template code use either
{{#invoke:Catnav|main|<named_argument_list>}}
or{{#invoke:Catnav|Q|<positional_argument_list>|<named_arguments_without_number_suffix>}}
(putting named arguments before positional ones is fine too if your layout benefits)
The following doc is extensive. Usually things should just work. If you want to use this module for a template, start by looking at the examples. This module is intended to save template authors from typing and repeating the same phrases over and over again. If you find a bug, keep calm and if you will not fix it yourself, report it to the discussion page.
First case | Second case | |||
---|---|---|---|---|
| ||||
named_argument_list is compatible with that of the legacy Template:Catnav, which served as an inspiration for this module. With default values given in braces and with X being replacable by a number in the range 0 to maxnum , the possible arguments are:
For a given number To determine the link target of an entry, the value of If Thereafter
For instance, with
|
All named arguments that end in X , as described left, should not be passed in this mode as they may be overwritten internally by positional argument processing. Other named arguments may be used. The default value for prefix is :Category: in this mode.
If no commons category (P373) could be found, then Qarg is used to set All positional arguments may be encased in simple wikitext formatting modifiers, i.e. The property identifier in
| |||
Note that a space is generated by default on both sides of the enumerated name in the target of generated wikilinks, i.e. after the
The sorting feature can be disabled explicitly using In |
Code
-- please report issues to the discussion page
-- if you will not fix them yourself
require('strict')
local titlenew = mw.title.new
local split = mw.text.split
local concat = table.concat
local insert = table.insert
local remove = table.remove
local function exists(w)
local t = titlenew(w, '')
return t and t.exists
end
local function isRedirect(w)
local t = titlenew(w, '')
return t:getContent():find('[Cc]ategory ?[Rr]edirect') or t.isRedirect
end
-- mw.getCurrentFrame():callParserFunction('PAGENAME', w)
local function pagename(w)
local t = titlenew(w, '')
return t and t.text
end
-- mw.title.new(w, ''):inNamespace(mw.site.namespaces.File.id)
local function imgexists(w)
return w:find('File:', 1, true) == 1
or w:find('Image:', 1, true) == 1
end
-- parser called once and cached
local isRTL = mw.getLanguage(mw.getCurrentFrame():callParserFunction('int', 'Lang')):isRTL()
-- def(x) == nil if x is empty
local function def(x, y)
return x and #x > 0 and x or y
end
local function tbl(t)
t = t or {}
t.trc = function(t, x)
repeat until remove(t):find(x, 1, true)
return 1
end
t.app = function(t, ...)
for i = 1, arg.n do
insert(t, arg[i])
end
end
return t
end
-- flatten table entries of t to a string
local function flatten(t)
local r = tbl()
for u, v in ipairs(t) do
u = {}
for i = 1, 32 do -- v may not be a sequential table
if v[i] and #v[i] > 0 then
insert(u, v[i])
end
end
if #u > 0 then
u = split(concat(u, ' '):gsub('', '\8'), '')
for i = #u - 1, 1, -1 do
u[i] = u[i] .. u[i + 1]:sub(2)
end
u = 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
local function rvd(x)
return x:find('right', 1, true) and 'left' or 'right'
end
local function top(title, i, pfx, sfx, tpn)
local r = tbl{
'<div dir="', isRTL and 'rtl' or 'ltr',
'" class="catlinks catnav catnav_', def(tpn, ''):sub(10):gsub('[^%-%w\128-\255]+', '_'),
'" style="clear:none;display:table;font-size:88%;line-height:normal;margin:2px 0;padding:2px"><div style="display:table-cell;min-width:36em">',
}
if def(i.img) then
if imgexists(i.img) then
local wl = def(flatten{{ pfx, i.lnk, sfx }}[1], '')
i.lnk = exists(wl) and wl or exists(i.lnk) and i.lnk or ''
r:app(
'<div style="float:', i.aln, ';margin-', rvd(i.aln), ':2px;', i.stl,
'">[[', i.img, '|', i.wth, '|border|link=', i.lnk, ']]</div>'
)
else
r:app(i.img)
end
end
if def(title) then
local pn = pagename(flatten{{ pfx, title, sfx }}[1])
if pn then
r:app('<em>', pn, ' :</em> ')
end
end
return concat(r, '')
end
local function bottom()
return '<div style="clear:both"></div></div></div>'
end
local spA = '<span style="white-space:nowrap">'
local spZ = '</span>'
local function _seq(class, css, c)
return not class and '</div>' or concat({
'<div class="catnav_', class, '" style="', css[class],
def(c and c > 0 and ';' .. def(css['_' .. class], ''), ''),
'">'
}, '')
end
local function row(aln, wth, pfx, sfx, all, css,
sep, disp, link, pref, suff, ticl, note, icon)
local r = tbl()
if disp or link then
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 ' ≈ [[' or '[[')
aln = aln and #r + 1
r.aln = function(t, v)
insert(t, aln or #t + 1, v)
end
if icon then
r:aln(icon .. '|' .. wth .. '|link=' .. l)
end
if disp then
r:aln(icon and ']] [[' or '')
r:aln(l .. '|' .. disp)
end
r:app(']]', note or '', spZ)
end
end
r:rwd()
elseif disp then
r:app(spA, disp, note or '', spZ)
c = c + 1
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
elseif 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
return concat(r, '')
end
local 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
local function use(x)
return x and #x > 0 and ('only once true yes 1 2'):find(x, 1, true) or nil
end
local function named_args(_f)
local a = _f.args
local f = _f:getParent() or _f
if pairs(a)(a) == nil then -- if invoked without args
a = f.args -- take parent args, else take unset args only:
elseif _f ~= f then
for k, v in pairs(f.args) do
a[k] = a[k] or v
end
end
a.__art = def(a.article)
a.__art = not a.__art and tonumber(a.all) == 2 and 'the' or a.__art
a.__sep = def(a.sep) or ' <b>·</b> '
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 = isRTL and rvd(a.img.aln) or a.img.aln
f = {
__indent = not use(a.compact) and use(a.indent),
}
a.__css = f
if f.__indent then
local side = isRTL and 'left' or 'right'
f.seq = 'display:table-row;vertical-align:top'
f.seqlabel = 'display:table-cell;text-align:' .. side .. ';padding-' .. side .. ':.4em;white-space:nowrap'
f._seqlabel = 'padding-top:.2em'
f.seqdata = 'display:table-cell'
f._seqdata = 'padding-top:.2em'
else
f.seq = 'display:inline'
f.seqlabel = 'display:inline'
f.seqdata = 'display:inline'
end
return a
end
local QIDpattern = '%f[%w][Qq]%d+'
local getLabel = mw.wikibase.getLabel
local function key(k)
return type(k) == 'string' and k
:gsub('<!%-%-(.-)%-%->', '') -- strip HTML/XML comments
:gsub('</?%s*([%a_][%-%.:%w_]*)[^>]*>', '') -- strip HTML/XML element tags (preserve text elements)
:gsub('^%s*(.-)%s*$', '%1') -- trim leading/trailing whitespaces
:gsub('%s+', ' ') -- pack remaining whitespaces
end
local 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 function Q(x, ex)
return x and x
:gsub('i18n{([^}]+)}', tr)
:gsub(QIDpattern,
function(m)
return ex and ex:find(m, 1, true) and m
or getLabel(m)
end)
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
insert(r, remove(t))
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
insert(r, {
note = r.n,
sep = s1,
})
end
r.n = Q(def(a['note' .. c]))
if dl then
insert(t, {
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
-- maintenance / tracker categories
local function maintcat(tpn, r, t)
for k, v in pairs(t) do
if v and tpn then
r:app('[[Category:', tpn, ' maintenance/', k, ' users]]')
end
end
end
local function _tpn(frame)
frame = frame:getParent()
local tpn = frame and frame:getTitle()
return def(tpn and tpn:find('Template:', 1, true) == 1 and tpn)
end
local function main(frame, qc)
local tpn = _tpn(frame)
local a = named_args(frame)
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 r = tbl()
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(aln, wth, pfx, sfx, all, css,
v.sep, v.disp, v.link, v.pref, v.suff, v.ticl, v.note, v.icon
)
)
end
r:app(bottom())
maintcat(tpn, r, {
redlink = all,
--indent = a.__css.__indent,
})
return concat(r, '')
end
local function 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(QIDpattern) 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 main(frame, c)
end
-- exported functions
return {
main = main,
Q = Q,
}