Module:Wiki output utilities
Appearance
Documentation for this module may be created at Module:Wiki output utilities/doc
--[[
Module:Wiki output utilities
Part of the Module:Utilities family.
All forward-facing HTML and wikitext renderers. Receives already-processed
data and turns it into output strings. No Cargo queries here.
All functions are re-exported through Module:Utilities for backward compatibility.
Functions:
warning_box Render a styled warning box
generate_wiki_table Generate a wiki table (legacy + enhanced modes)
generate_wiki_table_enhanced Generate a wiki table with collapsible row support
generate_wiki_table_simple Generate a plain wiki table with minimal options
build_bulleted_taxon_list Render a nested taxon hierarchy as a bulleted HTML list
display_field_in_bulleted_list Render a bulleted list from a Cargo results field
bulleted_list Render a sorted bulleted list of unique field values
--]]
local output = {}
-- ============================================================
-- Warning box
-- ============================================================
-- Renders a styled mw-message-box warning div with a bold title and body text.
function output.warning_box(title, msg)
local box = mw.html.create("div")
:addClass("mw-message-box mw-message-box-warning")
:cssText("margin:0.8em 0;")
box:tag("strong"):wikitext(title):done()
box:tag("div")
:cssText("margin-top:0.4em; white-space:pre-wrap;")
:wikitext(msg)
return tostring(box)
end
-- ============================================================
-- generate_wiki_table
-- ============================================================
--[[
Generates a formatted wiki table from Cargo query results.
Supports two modes:
Legacy / compat mode (default):
Accepts a flat array of pre-formatted rows and a simple display_fields list.
Outputs raw field values as-is.
Enhanced mode (compat_mode = false, field_schema provided):
Formats fields by declared Cargo type (page, file, list variants).
Supports column display name mapping and character cutoff.
Both modes support optional collapsible table behaviour via mw-collapsible.
--]]
function output.generate_wiki_table(rows, display_fields, collapse, character_cutoff, field_schema, column_label_map, compat_mode, file_display_format)
-- Detect legacy argument order: (rows, header, display_fields, collapse, ...)
if type(display_fields) == "string" and type(collapse) == "table" then
local header = display_fields
display_fields = collapse
collapse = character_cutoff
character_cutoff = field_schema
field_schema = nil
column_label_map = nil
compat_mode = true
file_display_format = nil
end
file_display_format = file_display_format or "[[File:%s|frameless|100px]]"
if compat_mode == nil then compat_mode = true end
-- ----------------------------------------
-- Legacy mode
-- ----------------------------------------
if compat_mode or type(field_schema) ~= "table" then
local html = mw.html.create("table")
:addClass("wikitable")
:addClass("cargo-table")
:addClass("sortable")
local thead = html:tag("tr")
for _, field in ipairs(display_fields) do
local label = column_label_map and column_label_map[field] or field
thead:tag("th"):wikitext(label)
end
for _, row in ipairs(rows) do
local tr = html:tag("tr")
for _, field in ipairs(display_fields) do
tr:tag("td"):wikitext(row[field] or "")
end
end
if collapse then
html:addClass("mw-collapsible")
html:addClass("mw-collapsed")
end
return tostring(html)
end
-- ----------------------------------------
-- Enhanced mode
-- ----------------------------------------
local html = mw.html.create("table")
:addClass("wikitable")
:addClass("cargo-table")
:addClass("sortable")
local thead = html:tag("tr")
for _, field in ipairs(display_fields) do
local display_name = (column_label_map and column_label_map[field]) or field
thead:tag("th"):wikitext(display_name)
end
for _, row in ipairs(rows) do
local tr = html:tag("tr")
for _, field in ipairs(display_fields) do
local value = row[field]
local field_info = field_schema[field]
local field_type = field_info and field_info.type or "string"
local content = ""
if type(value) == "table" then
local formatted_items = {}
for _, item in ipairs(value) do
if field_type == "list_of_page" then
table.insert(formatted_items, "[[" .. item .. "]]")
elseif field_type == "list_of_file" then
if item and item ~= "" then
table.insert(formatted_items, string.format(file_display_format, item))
end
else
table.insert(formatted_items, item)
end
end
content = table.concat(formatted_items, ", ")
elseif type(value) == "string" then
if field_type == "page" then
content = "[[" .. value .. "]]"
elseif field_type == "file" then
content = (value and value ~= "") and string.format(file_display_format, value) or ""
else
content = value
end
end
if character_cutoff
and field_type ~= "file"
and field_type ~= "list_of_file"
and mw.ustring.len(content) > character_cutoff then
content = mw.ustring.sub(content, 1, character_cutoff) .. "..."
end
tr:tag("td"):wikitext(content)
end
end
if collapse then
html:addClass("mw-collapsible")
html:addClass("mw-collapsed")
end
return tostring(html)
end
-- ============================================================
-- generate_wiki_table_enhanced
-- ============================================================
--[[
Generates a wiki table using wikitext string assembly rather than
mw.html. Supports a custom collapsible row cutoff via CSS classes.
--]]
function output.generate_wiki_table_enhanced(
rows, display_fields, collapse, character_cutoff,
field_schema, column_label_map, compat_mode,
file_display_format, row_cutoff, options
)
file_display_format = file_display_format or "[[File:%s|frameless|100px]]"
compat_mode = (compat_mode == nil) and true or compat_mode
local row_cutoff_value = row_cutoff or 5
options = options or {}
local classes = { "wikitable", "cargo-table", "sortable" }
if collapse then
table.insert(classes, "collapsible-cargo-table")
table.insert(classes, "row-cutoff-" .. row_cutoff_value)
end
local lines = {}
table.insert(lines, '{| class="' .. table.concat(classes, " ") .. '"')
table.insert(lines, "|-")
for _, field in ipairs(display_fields) do
local display_name = (column_label_map and column_label_map[field]) or field
table.insert(lines, "! " .. display_name)
end
for _, row in ipairs(rows) do
table.insert(lines, "|-")
for _, field in ipairs(display_fields) do
local value = row[field]
local field_info = field_schema and field_schema[field]
local field_type = field_info and field_info.type or "string"
local content = ""
if type(value) == "table" then
local formatted_items = {}
for _, item in ipairs(value) do
if field_type == "list_of_page" then
table.insert(formatted_items, "[[" .. item .. "]]")
elseif field_type == "list_of_file" then
if item and item ~= "" then
table.insert(formatted_items, string.format(file_display_format, item))
end
else
table.insert(formatted_items, item)
end
end
content = table.concat(formatted_items, ", ")
elseif type(value) == "string" then
if field_type == "page" then
content = "[[" .. value .. "]]"
elseif field_type == "file" then
content = (value and value ~= "") and string.format(file_display_format, value) or ""
else
content = value
end
end
if character_cutoff
and field_type ~= "file"
and field_type ~= "list_of_file"
and mw.ustring.len(content) > character_cutoff then
content = mw.ustring.sub(content, 1, character_cutoff) .. "..."
end
table.insert(lines, "| " .. (content or ""))
end
end
table.insert(lines, "|}")
return table.concat(lines, "\n")
end
-- ============================================================
-- generate_wiki_table_simple
-- ============================================================
--[[
Generates a plain wiki table with minimal options. No schema required.
Useful for simple pre-formatted result arrays.
--]]
function output.generate_wiki_table_simple(
rows,
display_fields,
column_label_map,
collapse,
character_cutoff,
row_cutoff,
extra_classes
)
character_cutoff = character_cutoff or nil
row_cutoff = row_cutoff or 5
extra_classes = extra_classes or {}
local classes = { "wikitable", "cargo-table", "sortable" }
if collapse then
table.insert(classes, "collapsible-cargo-table")
table.insert(classes, "row-cutoff-" .. row_cutoff)
end
for _, cls in ipairs(extra_classes) do
table.insert(classes, cls)
end
local lines = {}
table.insert(lines, '{| class="' .. table.concat(classes, " ") .. '"')
table.insert(lines, "|-")
for _, field in ipairs(display_fields) do
local label = (column_label_map and column_label_map[field]) or field
table.insert(lines, "! " .. label)
end
for _, row in ipairs(rows) do
table.insert(lines, "|-")
for _, field in ipairs(display_fields) do
local value = row[field]
local content = ""
if value ~= nil then
content = tostring(value)
if character_cutoff and mw.ustring.len(content) > character_cutoff then
content = mw.ustring.sub(content, 1, character_cutoff) .. "..."
end
end
table.insert(lines, "| " .. content)
end
end
table.insert(lines, "|}")
return table.concat(lines, "\n")
end
-- ============================================================
-- build_bulleted_taxon_list
-- ============================================================
--[[
Renders a nested taxon hierarchy (produced by tax.build_nested_hierarchy)
as a bulleted HTML unordered list. Only the children of the root node
are rendered -- the root itself is not shown.
--]]
function output.build_bulleted_taxon_list(taxon_hierarchy)
local function recurse(node)
local list = mw.html.create("ul")
for _, child in ipairs(node.children or {}) do
local li = list:tag("li")
if child.rank ~= "" then
li:wikitext(child.rank .. ": " .. child.name)
else
li:wikitext(child.name)
end
if #child.children > 0 then
li:node(recurse(child))
end
end
return list
end
return tostring(recurse(taxon_hierarchy))
end
-- ============================================================
-- display_field_in_bulleted_list
-- ============================================================
--[[
Generates a bulleted HTML list from a single field across all Cargo rows.
Handles multi-value fields by splitting on delimiter.
args:
cargo_results -- Cargo results array
field_name -- field to extract values from
sort_alphabetically -- boolean (default false)
page_links -- boolean, wrap items in wiki page links (default false)
delimiter -- split delimiter (default ",")
--]]
function output.display_field_in_bulleted_list(args)
local cargo_results = args.cargo_results
local field_name = args.field_name
local sort_alphabetically = args.sort_alphabetically or false
local page_links = args.page_links or false
local delimiter = args.delimiter or ","
local items = {}
for _, result in ipairs(cargo_results) do
local field_value = result[field_name] or ""
local item_list = mw.text.split(field_value, delimiter)
for _, item in ipairs(item_list) do
local trimmed = mw.text.trim(item)
if trimmed ~= "" then
if page_links then
trimmed = string.format("[[%s]]", trimmed)
end
table.insert(items, trimmed)
end
end
end
if sort_alphabetically then
table.sort(items, function(a, b)
local clean_a = mw.ustring.gsub(a, "^%[%[(.-)%]%]$", "%1")
local clean_b = mw.ustring.gsub(b, "^%[%[(.-)%]%]$", "%1")
return clean_a < clean_b
end)
end
local ul = mw.html.create("ul")
for _, item in ipairs(items) do
ul:tag("li"):wikitext(item)
end
return tostring(ul)
end
-- ============================================================
-- bulleted_list
-- ============================================================
--[[
Generates a sorted bulleted list of unique values for a specified field.
Handles both scalar string values and list-type (table) values.
Wraps "page" and "list_of_page" fields in wiki page links. Set
field_schema[field_name].italicize = true to also wrap entries in wiki
italics (e.g. for scientific names).
--]]
function output.bulleted_list(results, field_name, field_schema, column_label_map)
field_schema = field_schema or {}
local uniques = {}
for _, row in ipairs(results) do
local value = row[field_name]
if type(value) == "table" then
for _, v in ipairs(value) do
uniques[v] = true
end
elseif type(value) == "string" then
uniques[value] = true
end
end
local sorted = {}
for v, _ in pairs(uniques) do
table.insert(sorted, v)
end
table.sort(sorted)
local field_info = field_schema[field_name]
local field_type = field_info and field_info.type or "string"
local italicize = field_info and field_info.italicize
local list = {}
for _, item in ipairs(sorted) do
local display = item
if field_type == "page" or field_type == "list_of_page" then
display = "[[" .. display .. "]]"
end
if italicize then
display = "''" .. display .. "''"
end
table.insert(list, mw.html.create("li"):wikitext(display))
end
if #list > 0 then
local result_list = mw.html.create("ul")
for _, li in ipairs(list) do
result_list:node(li)
end
return tostring(result_list) .. "\n"
else
return nil
end
end
return output