Module:InfoboxCharacterCategories

From Candypedia
Revision as of 02:25, 5 January 2026 by SuitCase (talk | contribs) (Tweaking how it outputs links in the infobox)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Documentation for this module may be created at Module:InfoboxCharacterCategories/doc

-- Module:InfoboxCharacterCategories
-- Purpose:
--   1) Auto-categorize character pages based on *infobox parameters* so editors
--      don't have to duplicate manual [[Category:...]] tags at the bottom.
--   2) Format selected infobox fields so the *displayed* values link to the
--      relevant category pages (not to nonexistent article pages).
--
-- Rules (per your requests):
--   - residence: ignored (no categories, no special formatting)
--   - affiliation: ONLY linked items become categories. Plain text is ignored.
--       affiliation = [[Cheer squad]], [[Senior friend group]]
--         -> page categories: [[Category:Cheer squad]][[Category:Senior friend group]]
--         -> infobox display: links point to [[:Category:Cheer squad]] etc.
--   - occupation: hardcoded mapping from occupation -> category (for pluralization).
--       Student -> Category:Students
--       Burger-Tron employee -> Category:Burger-Tron employees
--       Roseville cinema employee -> Category:Roseville cinema employees
--       School faculty -> Category:School faculty
--     Works whether the editor wrote Student bare or as [[Student]].
--
-- Optional:
--   - pass |nocat=yes in the infobox to suppress categorization (useful for sandbox/test pages)

local p = {}

local function trim(s)
	if s == nil then return nil end
	s = mw.text.trim(s)
	if s == "" then return nil end
	return s
end

-- Build a link to a category page without categorizing the current page.
-- Example: [[:Category:Students|Student]]
local function catlink(cat, label)
	cat = trim(cat)
	label = trim(label) or cat
	if not cat then return "" end
	return ("[[:Category:%s|%s]]"):format(cat, label)
end

-- Extract link targets like [[Cheer squad]] or [[Cheer squad|Squad]].
-- Returns just the link target ("Cheer squad").
local function extract_link_targets(wikitext)
	local targets = {}
	if not wikitext or wikitext == "" then return targets end

	for target in wikitext:gmatch("%[%[([^%]|#]+)") do
		target = trim(target)
		if target then
			targets[#targets + 1] = target
		end
	end
	return targets
end

-- Split a list like "Student, Burger-Tron employee"
-- Also handles someone typing a link; we strip links to their target for matching.
local function split_list(wikitext)
	local out = {}
	if not wikitext or wikitext == "" then return out end

	-- Convert any links to their target text for matching.
	-- Example: [[Student]] -> Student ; [[Foo|Bar]] -> Foo
	local s = wikitext:gsub("%[%[([^%]|#]+)[^%]]*%]%]", "%1")

	-- Normalize separators
	s = s:gsub("<br%s*/?>", ",")
	s = s:gsub("[;\n]", ",")

	for part in mw.text.gsplit(s, ",", true) do
		part = trim(part)
		if part then out[#out + 1] = part end
	end
	return out
end

-- Hardcoded occupation -> category mapping
local OCCUPATION_TO_CATEGORY = {
	["School faculty"] = "School faculty",
	["Student"] = "Students",
	["Burger-Tron employee"] = "Burger-Tron employees",
	["Roseville cinema employee"] = "Roseville cinema employees",
}

local function lookup_occupation_category(occupation)
	occupation = trim(occupation)
	if not occupation then return nil end

	-- Exact match first
	if OCCUPATION_TO_CATEGORY[occupation] then
		return OCCUPATION_TO_CATEGORY[occupation]
	end

	-- Case-insensitive match
	local lower = mw.ustring.lower(occupation)
	for k, v in pairs(OCCUPATION_TO_CATEGORY) do
		if mw.ustring.lower(k) == lower then
			return v
		end
	end
	return nil
end

-- Formatter used by the infobox display for occupation.
-- Always links recognized occupations to their CATEGORY pages.
-- Examples:
--   "Student" -> [[:Category:Students|Student]]
--   "[[Student]]" -> [[:Category:Students|Student]]
--   "Student, Burger-Tron employee" -> both linked to their categories
function p.formatOccupation(frame)
	local raw = frame.args[1] or frame.args.value
	raw = trim(raw)
	if not raw then return "" end

	local parts = split_list(raw)
	if #parts == 0 then return raw end

	for i, part in ipairs(parts) do
		local cat = lookup_occupation_category(part)
		if cat then
			-- Display singular label, link to plural category.
			parts[i] = catlink(cat, part)
		else
			parts[i] = part
		end
	end

	return table.concat(parts, ", ")
end

-- Formatter used by the infobox display for affiliation.
-- Converts any wikilinks into links to the corresponding CATEGORY page:
--   [[Cheer squad]] -> [[:Category:Cheer squad|Cheer squad]]
--   [[Cheer squad|Squad]] -> [[:Category:Cheer squad|Squad]]
-- Plain text remains unchanged.
function p.formatAffiliation(frame)
	local raw = frame.args[1] or frame.args.value
	raw = trim(raw)
	if not raw then return "" end

	-- Replace [[Target|Label]] first
	raw = raw:gsub("%[%[([^%]|#]+)%|([^%]]+)%]%]", function(target, label)
		target = trim(target)
		label = trim(label)
		if not target or not label then return "" end
		return catlink(target, label)
	end)

	-- Replace [[Target]]
	raw = raw:gsub("%[%[([^%]|#]+)%]%]", function(target)
		target = trim(target)
		if not target then return "" end
		return catlink(target, target)
	end)

	return raw
end

-- Category emission used at the bottom of the infobox template.
function p.categories(frame)
	local args = frame:getParent().args

	local nocat = trim(args.nocat)
	if nocat and (nocat == "yes" or nocat == "y" or nocat == "true" or nocat == "1") then
		return ""
	end

	local cats = {}

	-- Occupation categories (mapped)
	local occ = trim(args.occupation)
	if occ then
		for _, part in ipairs(split_list(occ)) do
			local cat = lookup_occupation_category(part)
			if cat then
				cats[#cats + 1] = ("[[Category:%s]]"):format(cat)
			end
		end
	end

	-- Affiliation categories (ONLY linked items)
	local aff = trim(args.affiliation)
	if aff then
		for _, target in ipairs(extract_link_targets(aff)) do
			cats[#cats + 1] = ("[[Category:%s]]"):format(target)
		end
	end

	-- De-dupe
	local seen, out = {}, {}
	for _, c in ipairs(cats) do
		if not seen[c] then
			seen[c] = true
			out[#out + 1] = c
		end
	end

	return table.concat(out)
end

return p