This module creates a list with automatically balanced columns. It should not be used directly in entries, but in templates such as {{col2}}
or {{col3}}
. List entries are given as parameters to the template.
{{#invoke:columns|display|sort=1|collapse=1|columns=3}} -> {{col3|en|z|y|x|w|v|u|t}}
{{#invoke:columns|display|sort=1|collapse=1|columns=2}} -> {{col2|nl|a|b|c|d|e|f|g}}
export.create_list {
column_count = number,
content = list, alphabetize = boolean,
background_color = string, collapse = boolean,
toggle_category = string,
class = string, lang = language_object,
}
content
{ "term1", "term2", "term3", ... }
.lang
collapse
column_count
sort
toggle_category
class
div
tag that contains the list. Supply columns-bg
to get the blue background normally seen on Wiktionary's columnar term lists.The old name for the main function. It is now just a wrapper for create_list
.
The template-invokable function.
function export.format_item(item, args, face)
This function lacks documentation. Please add a description of its usages, inputs and outputs, or its difference from similar functions, or make it local to remove it from the function list.
function export.construct_old_style_header(header, horiz)
This function lacks documentation. Please add a description of its usages, inputs and outputs, or its difference from similar functions, or make it local to remove it from the function list.
function export.make_sortkey(frame)
Bot-callable function to generate a number of sortkeys simultaneously. |1=
contains the langcode, and remaining numeric parameters contain "bot-URL-encoded" strings whose sort keys will be computed and returned as a JSON array. Here, "bot-URL-encoded" means that the six characters { | } = & %
should be converted to their URL-encoded representation (respectively %7B %7C %7D %3D %26 %25
), and will be decoded appropriately before computing the sortkey.
function export.create_list(args)
Format a list of items using HTML. args
is an object specifying the items to add and related properties, with the following fields:
content
: A list of the items to format. See below for the format of the items.lang
: The language object of the items to format, if the items in content
are strings.sc
: The script object of the items to format, if the items in content
are strings.raw
: If true, return the list raw, without any collapsing or columns.class
: The CSS class of the surrounding column_count
: Number of columns to format the list into.alphabetize
: If true, sort the items in the table.collapse
: If true, make the table partially collapsed by default, with a "Show more" button at the bottom.toggle_category
: Value of data-toggle-category
property grouping collapsible elements.header
: If specified, Wikicode to prepend to the output.title_new_style
: If true, the header is treated as a title and displayed in a new style. This is ignored if horiz
is non-nil.subitem_separator
: Separator used between subitems when multiple subitems occur on a line, if not specified in the subitem itself (using the separator
field). Defaults to ", "
.keepfirst
: If > 0, keep this many rows unsorted at the beginning of the top level.keeplast
: If > 0, keep this many rows unsorted at the end of the top level.horiz
: If non-nil, format the items horizontally. If the value is "bullet", put a center dot/bullet (·) between items. If the value is "comma", put a comma between items (but if there is an embedded comma in any item, put a semicolon between all items).Each item in content
is in one of the following formats:
terms
specifying the list of subitems. Each subitem is in the same format as for a single top-level item, except that it should also have a separator
field specifying the separator to display before each item (which will typically be a blank string before the first item).function export.create_table(...)
This function lacks documentation. Please add a description of its usages, inputs and outputs, or its difference from similar functions, or make it local to remove it from the function list.
function export.display_from(frame_args, parent_args, frame)
This function lacks documentation. Please add a description of its usages, inputs and outputs, or its difference from similar functions, or make it local to remove it from the function list.
function export.handle_display_from_or_topic_list(iargs, raw_item_args, topic_list_data)
Implement display_from()
as well as regular (column-oriented) topic lists, invoked through Module:topic list. iargs
are the invocation args of {{col}}
, and raw_item_args
are the arguments specifying the values of each row as well as other properties, corresponding to the user-specified template arguments of {{col}}
. Note that show()
in Module:topic list is normally invoked directly by a topic list template, whose invocation arguments are passed in using raw_item_args
and are similar to the template arguments of {{col}}
. iargs
for topic-list invocations is hard-coded, and template arguments to a topic-list template are processed in Module:topic list itself. Note that the handling of topic lists is currently implemented almost entirely through callbacks in topic_list_data
(which is nil if we're processing {{col}}
rather than a topic list) in an attempt to reduce the coupling and keep the topic-list-specific code in Module:topic list, but IMO the coupling is still too tight. Probably the control structure should be reversed and the following function split up into subfunctions, which are invoked as needed by {{col}}
and/or Module:topic list.
function export.display(frame)
This function lacks documentation. Please add a description of its usages, inputs and outputs, or its difference from similar functions, or make it local to remove it from the function list.
local export = {}
local collation_module = "Module:collation"
local debug_track_module = "Module:debug/track"
local headword_data_module = "Module:headword/data"
local JSON_module = "Module:JSON"
local languages_module = "Module:languages"
local links_module = "Module:links"
local pages_module = "Module:pages"
local parameter_utilities_module = "Module:parameter utilities"
local parameters_module = "Module:parameters"
local parse_utilities_module = "Module:parse utilities"
local pron_qualifier_module = "Module:pron qualifier"
local qualifier_module = "Module:qualifier"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local yesno_module = "Module:yesno"
local m_str_utils = require(string_utilities_module)
local concat = table.concat
local html = mw.html.create
local is_substing = mw.isSubsting
local insert = table.insert
local rmatch = m_str_utils.match
local remove = table.remove
local sub = string.sub
local trim = m_str_utils.trim
local u = m_str_utils.char
local dump = mw.dumpObject
local function track(page)
require(debug_track_module)("columns/" .. page)
return true
end
local function deepEquals(...)
deepEquals = require(table_module).deepEquals
return deepEquals(...)
end
local function term_already_linked(term)
return term == "?" or -- signals an unknown term
-- optimization to avoid unnecessarily loading ]
(term:find("") and require(parse_utilities_module).term_already_linked(term))
end
local function convert_delimiter_to_separator(item, itemind, args)
if itemind == 1 then
item.separator = nil
elseif item.delimiter == " " then
item.separator = args.space_delim
elseif item.delimiter == "~" then
item.separator = args.tilde_delim
else
item.separator = args.comma_delim
end
end
local function get_horizontal_separator(args_horiz, embedded_comma)
return args_horiz == "bullet" and " · " or embedded_comma and "; " or ", "
end
-- Suppress false positives in categories like ] so people won't
-- be tempted to "correct" them; terms like embedded ~ like ] or embedded comma not followed by a space
-- such as ] need to have a link around them to avoid the tilde or comma being interpreted as a
-- delimiter.
local function suppress_redundant_wikilink_cat(term, alt)
return term:find("~") or term:find(",%S")
end
local function full_link_and_track_self_links(item, face)
if item.term then
local pagename = mw.loadData(headword_data_module).pagename
local term_is_pagename = item.term == pagename
local term_contains_pagename = item.term:find("%]")
if term_is_pagename or term_contains_pagename then
local current_L2 = require(pages_module).get_current_L2()
if current_L2 then
local current_L2_lang = require(languages_module).getByCanonicalName(current_L2)
if current_L2_lang and current_L2_lang:getCode() == item.lang:getCode() then
if term_is_pagename then
track("term-is-pagename")
else
track("term-contains-pagename")
end
end
end
end
end
item.suppress_redundant_wikilink_cat = suppress_redundant_wikilink_cat
return require(links_module).full_link(item, face)
end
local function format_subitem(subitem, lang, face, compute_embedded_comma)
local embedded_comma = false
local text
if subitem.term and term_already_linked(subitem.term) then
text = subitem.term
if compute_embedded_comma then
embedded_comma = not not require(utilities_module).get_plaintext(text):find(",")
end
else
text = full_link_and_track_self_links(subitem, face)
if compute_embedded_comma then
-- We don't check qualifier, label or reference text for commas as it's inside parens or displayed
-- elsewhere.
local subitem_plaintext = subitem.alt or subitem.term
if subitem_plaintext then
embedded_comma = not not subitem_plaintext:find(",")
end
end
end
-- We could use the "show qualifiers" flag to full_link() but not when term_already_linked().
if subitem.q and subitem.q or subitem.qq and subitem.qq or subitem.l and subitem.l or
subitem.ll and subitem.ll or subitem.refs and subitem.refs then
text = require(pron_qualifier_module).format_qualifiers {
lang = subitem.lang or args.lang,
text = text,
q = subitem.q,
qq = subitem.qq,
l = subitem.l,
ll = subitem.ll,
refs = subitem.refs,
}
end
return text, embedded_comma
end
function export.format_item(item, args, face)
local compute_embedded_comma = args.horiz == "comma"
local embedded_comma = false
if type(item) == "table" then
if item.terms then
local parts = {}
local is_first = true
for _, subitem in ipairs(item.terms) do
if subitem == false then
-- omitted subitem; do nothing
else
local separator = subitem.separator or not is_first and (args.subitem_separator or ", ")
if separator then
if compute_embedded_comma then
embedded_comma = embedded_comma or not not separator:find(",")
end
insert(parts, separator)
end
local formatted, this_embedded_comma = format_subitem(subitem, args.lang, face,
compute_embedded_comma)
embedded_comma = embedded_comma or this_embedded_comma
insert(parts, formatted)
is_first = false
end
end
return concat(parts), embedded_comma
else
return format_subitem(item, args.lang, face, compute_embedded_comma)
end
else
if compute_embedded_comma then
embedded_comma = not not require(utilities_module).get_plaintext(item):find(",")
end
if args.lang and not term_already_linked(item) then
return full_link_and_track_self_links({lang = args.lang, term = item, sc = args.sc}, face), embedded_comma
else
return item, embedded_comma
end
end
end
function export.construct_old_style_header(header, horiz)
local old_style_header
local function ib_colon()
return tostring(html("span"):addClass("ib-colon"):addClass("ib-content"):wikitext(":"))
end
if horiz then
old_style_header = require(qualifier_module).format_qualifiers {
qualifiers = header,
open = false,
close = false,
} .. ib_colon() .. " "
else
old_style_header = require(qualifier_module).format_qualifiers {
qualifiers = header
} .. ib_colon()
old_style_header = tostring(html("div"):wikitext(old_style_header))
end
return old_style_header
end
-- Construct the sort base of a single term. As a hack, sort appendices after mainspace items.
local function term_sortbase(val)
if not val then
-- This should not normally happen.
return u(0x10FFFF)
elseif val:find("^%[*Appendix:") then
return u(0x10FFFE) .. val
else
return val
end
end
-- Construct the sort base of a single item, using the display form preferentially, otherwise the term itself.
-- As a hack, sort appendices after mainspace items.
local function item_sortbase(item)
return term_sortbase(item.alt or item.term)
end
local function make_sortbase(item)
if item == false then
return "*" -- doesn't matter, will be omitted in create_list()
elseif type(item) == "table" then
if item.terms then
-- Optimize for the common case of only a single term
if item.terms then
local parts = {}
-- multiple terms
local first = true
for _, subitem in ipairs(item.terms) do
if subitem ~= false then
if not first then
insert(parts, ", ")
end
insert(parts, item_sortbase(subitem))
first = false
end
end
if parts then
return concat(parts)
end
else
local subitem = item.terms
if subitem ~= false then
return item_sortbase(subitem)
end
end
return "*" -- doesn't matter, entire group will be omitted in create_list()
else
return item_sortbase(item)
end
else
return item
end
end
local function make_node_sortbase(node)
return make_sortbase(node.item)
end
-- Sort a sublist of `list` in place, keeping the first `keepfirst` and last `keeplast` items fixed.
-- `lang` is the language of the items and `make_sortbase` creates the appropriate sort base.
local function sort_sublist(list, lang, make_sortbase, keepfirst, keeplast)
if keepfirst == 0 and keeplast == 0 then
require(collation_module).sort(list, lang, make_sortbase)
else
local sublist = {}
for i = keepfirst + 1, #list - keeplast do
sublist = list
end
require(collation_module).sort(sublist, lang, make_sortbase)
for i = keepfirst + 1, #list - keeplast do
list = sublist
end
end
end
-- URL-encode only the characters that serve as template delimiters (left and right brace, vertical bar, equal sign
-- and percent sign since it's the escape character).
local function bot_url_encode(txt)
return (txt:gsub("",
{ = "%25", = "%7C", = "%7B", = "%7D", = "%3D", = "%26"}))
end
-- Reverse the action of bot_url_encode().
local function bot_url_decode(txt)
return (txt:gsub("%%7()", {B = "{", C = "|", D = "}"}):gsub("%%3D", "="):gsub("%%26", "&"):gsub("%%25", "%%"))
end
--[==[
Bot-callable function to generate a number of sortkeys simultaneously. {{para|1}} contains the langcode, and remaining
numeric parameters contain "bot-URL-encoded" strings whose sort keys will be computed and returned as a JSON array.
Here, "bot-URL-encoded" means that the six characters `{ | } = & %` should be converted to
their URL-encoded representation (respectively <code>%7B %7C %7D %3D %26 %25</code>), and will be decoded appropriately
before computing the sortkey.
]==]
function export.make_sortkey(frame)
local iparams = {
= {type = "language"},
= {list = true},
}
local iargs = require(parameters_module).process(frame.args, iparams)
local make_sortkey = require(collation_module).make_lang_sortkey_function(iargs, term_sortbase)
local retval = {}
for _, arg in ipairs(iargs) do
arg = bot_url_decode(arg)
insert(retval, make_sortkey(arg))
end
return require(JSON_module).toJSON(retval)
end
local large_text_scripts = {
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
= true,
}
--[==[
Format a list of items using HTML. `args` is an object specifying the items to add and related properties, with the
following fields:
* `content`: A list of the items to format. See below for the format of the items.
* `lang`: The language object of the items to format, if the items in `content` are strings.
* `sc`: The script object of the items to format, if the items in `content` are strings.
* `raw`: If true, return the list raw, without any collapsing or columns.
* `class`: The CSS class of the surrounding <div>.
* `column_count`: Number of columns to format the list into.
* `alphabetize`: If true, sort the items in the table.
* `collapse`: If true, make the table partially collapsed by default, with a "Show more" button at the bottom.
* `toggle_category`: Value of `data-toggle-category` property grouping collapsible elements.
* `header`: If specified, Wikicode to prepend to the output.
* `title_new_style`: If true, the header is treated as a title and displayed in a new style. This is ignored if `horiz`
is non-nil.
* `subitem_separator`: Separator used between subitems when multiple subitems occur on a line, if not specified in the
subitem itself (using the `separator` field). Defaults to {", "}.
* `keepfirst`: If > 0, keep this many rows unsorted at the beginning of the top level.
* `keeplast`: If > 0, keep this many rows unsorted at the end of the top level.
* `horiz`: If non-nil, format the items horizontally. If the value is "bullet", put a center dot/bullet (·) between
items. If the value is "comma", put a comma between items (but if there is an embedded comma in any item,
put a semicolon between all items).
Each item in `content` is in one of the following formats:
* A string. This is for compatibility and should not be used by new callers.
* An object describing an item to format, in the format expected by full_link() in ] but can also
have left or right qualifiers, left or right labels, or references.
* An object describing a list of subitems to format, displayed side-by-side, separated by a comma or other separator.
This format is identified by the presence of a key `terms` specifying the list of subitems. Each subitem is in
the same format as for a single top-level item, except that it should also have a `separator` field specifying the
separator to display before each item (which will typically be a blank string before the first item).
]==]
function export.create_list(args)
if type(args) ~= "table" then
error("expected table, got " .. type(args))
end
local column_count = args.column_count or 1
local toggle_category = args.toggle_category or "derived terms"
local keepfirst = args.keepfirst or 0
local keeplast = args.keeplast or 0
if keepfirst > 0 then
track("keepfirst")
end
if keeplast > 0 then
track("keeplast")
end
-- maybe construct old-style header
local old_style_header = nil
if args.header and (args.horiz or not args.title_new_style) then
old_style_header = export.construct_old_style_header(args.header, args.horiz)
end
if args.horiz then
old_style_header = "* " .. (old_style_header or "")
end
local list
local any_extra_indented_item = false
for _, item in ipairs(args.content) do
if item == false then
-- do nothing
elseif type(item) == "table" and item.extra_indent and item.extra_indent > 0 then
any_extra_indented_item = true
break
end
end
-- If any extra indented item, convert the items to a nested structure, which is necessary both for sorting and
-- for converting to HTML.
if any_extra_indented_item then
local function make_node(item)
return {
item = item
}
end
local root_node = make_node(nil)
local node_stack = {root_node}
local last_indent = 0
local function append_subnode(node, subnode)
if not node.subnodes then
node.subnodes = {}
end
insert(node.subnodes, subnode)
end
for i, item in ipairs(args.content) do
if item == false then
-- do nothing
else
local this_indent
if type(item) ~= "table" then
this_indent = 1
else
this_indent = (item.extra_indent or 0) + 1
end
local node = make_node(item)
if this_indent == last_indent then
append_subnode(node_stack, node)
elseif this_indent > last_indent + 1 then
error(("Element #%s (%s) has indent %s, which is more than one greater than the previous item with indent %s"):format(
i, make_sortbase(item), this_indent, last_indent))
elseif this_indent > last_indent then
-- Start a new sublist attached to the last item of the sublist one level up; but we need special
-- handling for the root node (last_indent == 0).
if last_indent > 0 then
local subnodes = node_stack.subnodes
if not subnodes then
error(("Internal error: Not first item and no subnodes at preceding level %s: %s"):format(
#node_stack, dump(node_stack)))
end
insert(node_stack, subnodes)
end
append_subnode(node_stack, node)
last_indent = this_indent
else
while last_indent > this_indent do
local finished_node = table.remove(node_stack)
if args.alphabetize then
require(collation_module).sort(finished_node.subnodes, args.lang, make_node_sortbase)
end
last_indent = last_indent - 1
end
append_subnode(node_stack, node)
end
end
end
if args.alphabetize then
while node_stack do
local finished_node = table.remove(node_stack)
if node_stack then
-- We're sorting something other than the root node.
require(collation_module).sort(finished_node.subnodes, args.lang, make_node_sortbase)
else
-- We're sorting the root node; honor `keepfirst` and `keeplast`.
sort_sublist(finished_node.subnodes, args.lang, make_node_sortbase, keepfirst, keeplast)
end
end
end
local function format_node(node, depth)
local sublist
local embedded_comma = false
if node.subnodes then
if args.horiz then
sublist = {}
else
sublist = html("ul")
end
local prevnode = nil
for _, subnode in ipairs(node.subnodes) do
local thisnode, this_embedded_comma = format_node(subnode, depth + 1)
embedded_comma = embedded_comma or this_embedded_comma
if not prevnode or not args.alphabetize or not deepEquals(prevnode, thisnode) then
if args.horiz then
table.insert(sublist, thisnode)
else
sublist = sublist:node(thisnode)
end
prevnode = thisnode
end
end
if args.horiz then
sublist = table.concat(sublist, get_horizontal_separator(args.horiz, embedded_comma))
end
end
if not node.item then
-- At the root.
return sublist, embedded_comma
end
local formatted, listitem
-- Ignore embedded commas in subitems inside of parens or square brackets.
formatted, embedded_comma = export.format_item(node.item, args)
if args.horiz then
listitem = formatted
if sublist then
-- Use parens for the first, third, fifth, etc. sublists and square brackets for the remainder.
if depth % 2 == 1 then
listitem = ("%s (%s)"):format(listitem, sublist)
else
listitem = ("%s "):format(listitem, sublist)
end
end
else
listitem = html("li"):wikitext(formatted)
if sublist then
listitem = listitem:node(sublist)
end
end
return listitem, embedded_comma
end
list = format_node(root_node, 0)
else
if args.alphabetize then
sort_sublist(args.content, args.lang, make_sortbase, keepfirst, keeplast)
end
if args.horiz then
list = {}
else
list = html("ul")
end
local previtem = nil
local embedded_comma = false
for _, item in ipairs(args.content) do
if item == false then
-- omitted item; do nothing
else
local thisitem, this_embedded_comma = export.format_item(item, args)
embedded_comma = embedded_comma or this_embedded_comma
if not previtem or not args.alphabetize or previtem ~= thisitem then
if args.horiz then
table.insert(list, thisitem)
else
list = list:node(html("li"):wikitext(thisitem))
end
previtem = thisitem
end
end
end
if args.horiz then
list = table.concat(list, get_horizontal_separator(args.horiz, embedded_comma))
end
end
local output
if args.horiz then
output = list
else
output = html("div"):addClass("term-list"):node(list)
if args.class then
output:addClass(args.class)
end
if not args.raw then
output:addClass("ul-column-count")
:attr("data-column-count", column_count)
if args.collapse then
output = html("div")
:node(output)
:addClass("list-switcher")
:attr("data-toggle-category", toggle_category)
-- identify commonly used scripts that use large text and
-- provide a special CSS class to make the template bigger
local sc = args.sc
if sc == nil then
local scripts = args.lang:getScripts()
if #scripts > 0 then
sc = scripts
end
end
if sc ~= nil then
local scriptcode = sc:getParentCode()
if scriptcode == "top" then
scriptcode = sc:getCode()
end
if large_text_scripts then
output:addClass("list-switcher-large-text")
end
end
end
end
if args.collapse or args.title_new_style then
-- wrap in wrapper to prevent interference from floating elements
local list_switcher_wrapper = html("div")
:addClass("list-switcher-wrapper")
if args.title_new_style then
list_switcher_wrapper
:node(
html("div")
:addClass("list-switcher-header")
:wikitext(args.header)
)
end
list_switcher_wrapper:node(output)
output = list_switcher_wrapper
end
output = tostring(output)
end
return (old_style_header or "") .. output
end
-- This function is for compatibility with earlier version of ]
-- (now found in ]).
function export.create_table(...)
-- Earlier arguments to create_table:
-- n_columns, content, alphabetize, bg, collapse, class, title, column_width, line_start, lang
local args = {}
args.column_count, args.content, args.alphabetize,
args.collapse, args.class, args.header, args.column_width,
args.line_start, args.lang = ...
return export.create_list(args)
end
function export.display_from(frame_args, parent_args, frame)
local boolean = {type = "boolean"}
local iparams = {
= true,
-- Default for auto-collapse. Overridable by template |collapse= param.
= boolean,
-- If specified, this specifies the number of columns, and no columns parameter is available on the template.
-- Otherwise, the columns parameter is named |n=.
= {type = "number"},
-- If specified, this specifies the default language code, which can be overridden using |lang= in the template.
-- Otherwise, the language-code parameter is required and normally found in |1=, but for compatibility can be
-- specified as |lang= (which leads to deprecation handling).
= {type = "language"},
-- Default for auto-sort. Overridable by template |sort= param.
= boolean,
= true,
-- Minimum number of rows required to format into a multicolumn list. If below this, the list is displayed "raw"
-- (no columns, no collapsbility).
= {type = "number", default = 5},
}
local iargs = require(parameters_module).process(frame_args, iparams)
local langcode_in_lang = iargs.lang or parent_args.lang
local lang_param = langcode_in_lang and "lang" or 1
local deprecated = not iargs.lang and langcode_in_lang
local ret = export.handle_display_from_or_topic_list(iargs, parent_args, nil)
return deprecated and frame:expandTemplate{title = "check deprecated lang param usage",
args = {ret, lang = args}} or ret
end
--[==[
Implement `display_from()` [the internal entry point for {{tl|col}} and variants, which enter originally through
`display()`] as well as regular (column-oriented) topic lists, invoked through ].
`iargs` are the invocation args of {{tl|col}}, and `raw_item_args` are the arguments specifying the values of
each row as well as other properties, corresponding to the user-specified template arguments of {{tl|col}}. Note that
`show()` in ] is normally invoked directly by a topic list template, whose invocation
arguments are passed in using `raw_item_args` and are similar to the template arguments of {{tl|col}}. `iargs` for
topic-list invocations is hard-coded, and template arguments to a topic-list template are processed in
] itself. Note that the handling of topic lists is currently implemented almost entirely
through callbacks in `topic_list_data` (which is nil if we're processing {{tl|col}} rather than a topic list) in an
attempt to reduce the coupling and keep the topic-list-specific code in ], but IMO the coupling
is still too tight. Probably the control structure should be reversed and the following function split up into
subfunctions, which are invoked as needed by {{tl|col}} and/or ].
]==]
function export.handle_display_from_or_topic_list(iargs, raw_item_args, topic_list_data)
local boolean = {type = "boolean"}
local langcode_in_lang = iargs.lang or raw_item_args.lang
local lang_param = langcode_in_lang and "lang" or 1
local first_content_param = langcode_in_lang and 1 or 2
local params = {
= {required = not iargs.lang, type = "language",
template_default = not iargs.lang and "und" or nil},
= not iargs.columns and {type = "number"} or nil,
= {list = true, allow_holes = true},
= {},
= boolean,
= boolean,
= {type = "script"},
-- used when calling from ] so the page displaying the synonyms/antonyms doesn't occur in the
-- list
= {list = true},
= {type = "number", default = 0},
= {type = "number", default = 0},
= {},
= boolean,
= boolean,
= {},
= {},
= {},
}
if topic_list_data then
topic_list_data.add_topic_list_params(params)
end
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{default = true, require_index = true},
{group = "link"}, -- sc has separate_no_index = true; that's the only one
-- It makes no sense to have overall l=, ll=, q= or qq= params for columnar display.
{group = {"ref", "l", "q"}, require_index = true},
}
m_param_utils.augment_params_with_modifiers(params, param_mods)
local processed_args = require(parameters_module).process(raw_item_args, params)
local horiz = processed_args.horiz
if horiz and horiz ~= "comma" and horiz ~= "bullet" then
horiz = require(yesno_module)(horiz)
if horiz == nil then
error(("Unrecognized value |horiz=%s; should be 'comma', 'bullet' or a recognized Boolean value such " ..
"as 'yes' or '1' (same as 'bullet') or 'no' or '0'"):format(processed_args.horiz))
end
if horiz == true then
horiz = "bullet"
end
processed_args.horiz = horiz
end
-- If default argument values specified, set them after parsing the caller-specified arguments in `raw_item_args`.
if topic_list_data then
topic_list_data.set_default_arguments(processed_args)
end
-- Now set defaults for the various delimiters, depending in some cases on whether horiz was set.
-- We can't set these defaults (even regardless of their dependency on horiz=) in `local params` above
-- because we want any defaults specified in `default_props` to override these.
if not processed_args.tilde_delim then
local tilde_with_abbr = '<abbr title="near equivalent">~</abbr>'
processed_args.tilde_delim = processed_args.horiz and tilde_with_abbr or " " .. tilde_with_abbr .. " "
end
if not processed_args.space_delim then
processed_args.space_delim = " "
end
if not processed_args.comma_delim then
processed_args.comma_delim = processed_args.horiz and "/" or ", "
end
-- Check for extra term indent. Do this before calling parse_list_with_inline_modifiers_and_separate_params()
-- because sometimes space is a delimiter and the space in the indent will confuse things and get interpreted as a
-- delimiter.
local extra_indent_by_termno = {}
local termargs = processed_args
for i = 1, termargs.maxindex do
local term = termargs
if term then
local extra_indent, actual_term = rmatch(term, "^(%*+)%s+(.-)$")
if extra_indent then
termargs = actual_term
extra_indent_by_termno = #extra_indent
end
end
end
local groups, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
param_mods = param_mods,
processed_args = processed_args,
termarg = first_content_param,
parse_lang_prefix = true,
allow_multiple_lang_prefixes = true,
disallow_custom_separators = true,
track_module = "columns",
lang = iargs.lang or lang_param,
sc = "sc.default",
splitchar = processed_args.allow_space_delim and "" or "",
}
local lang = iargs.lang or args
local langcode = lang:getCode()
local fulllangcode = lang:getFullCode()
local sc = args.sc.default
local sort = iargs.sort
if args.sort ~= nil then
if not args.sort then
track("nosort")
end
sort = args.sort
else
-- HACK! For Japanese-script languages (Japanese, Okinawan, Miyako, etc.), sorting doesn't yet work properly, so
-- disable it.
for _, langsc in ipairs(lang:getScriptCodes()) do
if langsc == "Jpan" then
sort = false
break
end
end
end
local collapse = iargs.collapse
if args.collapse ~= nil then
if not args.collapse then
track("nocollapse")
end
collapse = args.collapse
end
local title = args.title
local formatted_cats
if topic_list_data then
title, formatted_cats = topic_list_data.get_title_and_formatted_cats(args, lang, sc, topic_list_data)
end
local number_of_groups = 0
for i, group in ipairs(groups) do
local number_of_items = 0
group.extra_indent = extra_indent_by_termno
for j, item in ipairs(group.terms) do
convert_delimiter_to_separator(item, j, args)
if args.notr then
item.tr = "-"
end
-- If a separate language code was given for the term, display the language name as a right qualifier.
-- (Briefly we made them labels but this leads to non-obvious behavior e.g. "French" becoming "France" under
-- some circumstances.) Otherwise it may not be obvious that the term is in a separate language (e.g. if the
-- main language is 'zh' and the term language is a Chinese lect such as Min Nan). But don't do this for
-- Translingual terms, which are often added to the list of English and other-language terms.
if item.termlangs then
local qqs = {}
for _, termlang in ipairs(item.termlangs) do
local termlangcode = termlang:getCode()
if termlangcode ~= langcode and termlangcode ~= "mul" then
insert(qqs, termlang:getCanonicalName())
end
end
if item.qq then
for _, qq in ipairs(item.qq) do
insert(qqs, qq)
end
end
item.qq = qqs
end
local omitted = false
for _, omitted_item in ipairs(args.omit) do
if omitted_item == item.term then
omitted = true
break
end
end
if omitted then
-- signal create_list() to omit this item
group.terms = false
else
number_of_items = number_of_items + 1
end
end
if number_of_items == 0 then
-- omit the whole group
groups = false
else
number_of_groups = number_of_groups + 1
end
end
local column_count = iargs.columns or args.n
-- FIXME: This needs a total rewrite.
if column_count == nil then
column_count = number_of_groups <= 3 and 1 or
number_of_groups <= 9 and 2 or
number_of_groups <= 27 and 3 or
number_of_groups <= 81 and 4 or
5
end
local raw = number_of_groups < iargs.minrows
local horiz_edit_button
if topic_list_data and args.horiz then
-- append edit button to title
horiz_edit_button = topic_list_data.make_horiz_edit_button(topic_list_data.topic_list_template)
end
return export.create_list {
column_count = column_count,
raw = raw,
content = groups,
alphabetize = sort,
header = title,
title_new_style = (title ~= nil and title ~= ''),
collapse = collapse,
toggle_category = iargs.toggle_category,
-- columns-bg (in ]) provides the background color
class = (iargs.class and iargs.class .. " columns-bg" or "columns-bg"),
lang = lang,
sc = sc,
subitem_separator = ", ",
keepfirst = args.keepfirst,
keeplast = args.keeplast,
horiz = args.horiz,
} .. (horiz_edit_button or "") .. (formatted_cats or "")
end
function export.display(frame)
if not is_substing() then
return export.display_from(frame.args, frame:getParent().args, frame, false)
end
-- If substed, unsubst template with newlines between each term, redundant wikilinks removed, and remove duplicates + sort terms if sort is enabled.
local m_table = require("Module:table")
local m_template_parser = require("Module:template parser")
local parent = frame:getParent()
local elems = m_table.shallowCopy(parent.args)
local code = remove(elems, 1)
code = code and trim(code)
local lang = require("Module:languages").getByCode(code, 1)
local i = 1
while true do
local elem = elems
while elem do
elem = trim(elem, "%s")
if elem ~= "" then
break
end
remove(elems, i)
elem = elems
end
if not elem then
break
elseif not ( -- Strip redundant wikilinks.
not elem:match("^()%[%[") or
elem:find("[[", 3, true) or
elem:find("]]", 3, true) ~= #elem - 1 or
elem:find("|", 3, true)
) then
elem = sub(elem, 3, -3)
elem = trim(elem, "%s")
end
elems = elem .. "\n"
i = i + 1
end
-- If sort is enabled, remove duplicates then sort elements.
if require("Module:yesno")(frame.args.sort) then
elems = m_table.removeDuplicates(elems)
require("Module:collation").sort(elems, lang)
end
-- Readd the langcode.
insert(elems, 1, code .. "\n")
-- TODO: Place non-numbered parameters after 1 and before 2.
local template = m_template_parser.getTemplateInvocationName(mw.title.new(parent:getTitle()))
return "{{" .. concat(m_template_parser.buildTemplate(template, elems), "|") .. "}}"
end
return export