Module:Category description

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Lua

CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules


The module Category description implements the logic for the {{Category description}} template family, a flexible and universal system for building category pages.

Usage

[edit]
{{#invoke:Category description|categoryDescription}}

Parameters

[edit]

See {{Category description/core}}.

Code

-- =============================================================================
-- Routines for the automatic generation of complete category description pages
-- with navigation, using Wikidata
-- =============================================================================

require("strict")

local arguments    = require "Module:Arguments"
local autocat      = require "Module:Autocat"
local navbox       = require "Module:Navbox"
local wdLabel      = require "Module:Wikidata label"
local wdStatements = require "Module:Wikidata statements"
local specialRules = require "Module:Navigation by Wikidata/special rules"

local p = {}

-- -----------------------------------------------------------------------------
-- Escape magic characters for a regular expression
-- -----------------------------------------------------------------------------

local function regExEscape(pattern)
	return string.gsub(pattern, "([%(%)%.%%%+%-%*%?%[%^%$])", "%%%1")
end

-- -----------------------------------------------------------------------------
-- Convert a pattern into a Lua-style regular expression
-- -----------------------------------------------------------------------------

local function makeRegEx(pattern, variable)
	-- Change underlines into spaces
	local regex = string.gsub(pattern, "_", " ")
	-- Escape any magic characters in the pattern
	regex = regExEscape(regex)
	-- Change <<...>> into regex placeholders
	-- We use non-greedy matching ("..-" instead of ".*"), see for example
	-- the category "Seasons in Ebenthal in Kärnten" with two "in"
	regex = string.gsub(regex, "<<" .. variable .. ">>", "(..-)")
	regex = string.gsub(regex, "<<[^>]+>>", "..-")
	-- Add beginning and end marks
	regex = "^" .. regex .. "$"
	return regex
end

-- -----------------------------------------------------------------------------
-- Find out the pattern variables and their content for the current page
-- -----------------------------------------------------------------------------

local function getVariableList(pagename, pattern)
	local variableList = {} 	-- sorted
	local variableTable = {}	-- unsorted but indexed by variable name
	for placeholder in string.gmatch(pattern, "<<([^>]+)>>") do
		local variable = {}
		-- Split the placeholder at each ":" to separate modifiers
		for part in string.gmatch(placeholder, "[^:]+") do
			if not variable.name then
				variable.name = part
			elseif part == "hyphen" then
				variable.hyphen = true
			elseif part == "the" then
				variable.the = true
			end
		end
		-- Determine variable content in current page name and assigned Wikidata item id
		variable.content = assert(
			string.match(pagename, makeRegEx(pattern, placeholder)),
			"“" .. pagename .. "” does not match “" .. pattern .. "”"
		)
		-- Remove hyphens if necessary
		if variable.hyphen then
			variable.content = string.gsub(variable.content, "-", " ")
		end
		-- Optionally remove the "the" prefix from the category name, but only
		-- if the unmodified category name does not exist; this keeps names like
		-- "The Bahamas" unchanged
		variable.item = mw.wikibase.getEntityIdForTitle('Category:' .. variable.content)
		if not variable.item and variable.the and string.find(variable.content, "the ") == 1 then
			variable.content = string.sub(variable.content, 5)
			variable.item = mw.wikibase.getEntityIdForTitle('Category:' .. variable.content)
		end
		-- Find out which wikidata entity matches the variable content
		if not variable.item then
			error("Page “Category:" .. variable.content .. "” not found")
		end
		variable.item = wdStatements.getOneItemId(variable.item, "P301") or variable.item
		table.insert(variableList, variable)
		variableTable[variable.name] = variable
	end
	return variableList, variableTable
end
-- -----------------------------------------------------------------------------
-- Fill in variable contents in a pattern, potentially keeping one placeholder
-- -----------------------------------------------------------------------------

local function fillVariables(pattern, variableTable, keep, replacement)
	local result = {pattern = pattern}
	for placeholder in string.gmatch(pattern, "<<([^>]+)>>") do
		local variable
		local modifiers = {the="no"} -- necessary because some "Navigation by" templates initialize "the" with "yes"
		-- Split the placeholder at each ":" to separate modifiers
		for part in string.gmatch(placeholder, "[^:]+") do
			if not variable then
				variable = assert(variableTable[part],
					"Unknown placeholder “" .. placeholder .. "”")
			elseif part == "label" then
				modifiers.label = "yes"
			elseif part == "hyphen" then
				modifiers.hyphen = "yes"
			elseif part == "the" then
				modifiers.the = "yes"
			end
		end
		if variable.name == keep then
			-- This becomes the complete argument list for Navigation_by/...
			result.item = variable.item
			-- Remove modifiers from placeholder
			result.pattern = string.gsub(result.pattern, placeholder, replacement)
			result.hyphen = modifiers.hyphen
			result.the = modifiers.the
			result.style = "block"
		else
			-- Fill in the actual value for placeholder
			local value, lang
			if modifiers.label then
				value, lang = mw.wikibase.getLabelWithLang(variable.item)
				if not value then
					value = variable.item
					lang = "en"
				end
			else
				value = variable.content
				lang = "en"
			end
			if not modifiers.label or lang == "en" then
				if modifiers.hyphen then
					value = value:gsub(" ", "-")
				end
				if modifiers.the == "yes" and specialRules[variable.item] and specialRules[variable.item].the then
					value = "the " .. value
				end
			end
			result.pattern = string.gsub(result.pattern, "<<" .. placeholder .. ">>", value)
		end
	end
	return result
end

-- -----------------------------------------------------------------------------
-- Determine navigation data for a page and a pattern
-- -----------------------------------------------------------------------------

local function getTitleAndNavigationBlocks(args, variableList, variableTable)
	local frame = mw.getCurrentFrame()
	local title = args.title or ""
	local blocks = ""
	for index, variable in ipairs(variableList) do
		-- Compile title line
		if args[variable.name .. ":title"] ~= "no" then
			if title ~= "" then
				title = title .. " - "
			end
			title = title .. wdLabel._getLabel(variable.item, nil, "wikipedia", "ucfirst")
		end
		-- Build navigation blocks
		local navigationArgs = fillVariables(args.pattern, variableTable, variable.name, args[variable.name] or variable.name)
		-- Level "siblings"
		navigationArgs.level = "siblings"
		if args[variable.name .. ":parent:pattern"] then
			local parentArgs = fillVariables(args[variable.name .. ":parent:pattern"], variableTable, variable.name, variable.name)
			navigationArgs["title:pattern"] = parentArgs.pattern
			navigationArgs["title:hyphen"] = parentArgs.hyphen
			navigationArgs["title:the"] = parentArgs.the
		end
		navigationArgs.redlinks = args[variable.name .. ":redlinks"]
		if args[variable.name .. ":autocat"] then
			navigationArgs["autocat"] = args[variable.name .. ":autocat"]
			navigationArgs["autocat:parent"] = args[variable.name .. ":autocat:parent"]
			if args[variable.name .. ":autocat:candidates"] then
				navigationArgs["autocat:candidates"] = fillVariables(args[variable.name .. ":autocat:candidates"], variableTable).pattern
			end
		end
		local block = frame:expandTemplate{
			title = "Navigation by/" .. variable.name,
			args = navigationArgs
		}
		blocks = blocks .. block .. "\n"
		navigationArgs["title:pattern"] = nil
		navigationArgs["title:hyphen"] = nil
		navigationArgs["title:the"] = nil
		navigationArgs["autocat"] = nil
		navigationArgs["autocat:parent"] = nil
		navigationArgs["autocat:candidates"] = nil
		-- Level "children"
		if args[variable.name .. ":children"] ~= "no" then
			navigationArgs.level = "children"
			if args[variable.name .. ":children:pattern"] then
				local childrenArgs = fillVariables(args[variable.name .. ":children:pattern"], variableTable, variable.name, variable.name)
				navigationArgs["title:pattern"] = navigationArgs.pattern
				navigationArgs["title:hyphen"] = navigationArgs.hyphen
				navigationArgs["title:the"] = navigationArgs.the
				navigationArgs.pattern = childrenArgs.pattern
				navigationArgs.hyphen = childrenArgs.hyphen
				navigationArgs.the = childrenArgs.the
			end
			navigationArgs.redlinks = args[variable.name .. ":children:redlinks"] or args[variable.name .. ":redlinks"]
			local block = frame:expandTemplate{
				title = "Navigation by/" .. variable.name,
				args = navigationArgs
			}
			blocks = blocks .. block .. "\n"
		end
	end
	return title, blocks
end

-- -----------------------------------------------------------------------------
-- Build a box with description and navigation blocks
-- Arguments: name, title, description, remarks, pattern, pagename
-- -----------------------------------------------------------------------------

function p._categoryDescription(args)
	if not args.pattern then
		error("Missing “pattern” parameter")
	end
	local categories = ""
	local boxargs = {
		name = args.name,
		title = args.title,
		titlestyle = "font-size: 114%;",
		above = args.description,
		abovestyle = "font-size: 114%;",
		listclass = "hlist",
		below = args.remarks,
		belowstyle = "font-size: 114%;"
	}
	if args.lang then
		boxargs.lang = args.lang
		boxargs.dir = mw.language.new(args.lang):getDir()
	end
	if args.pagename then
		local variableList, variableTable = getVariableList(args.pagename, args.pattern)
		if args.autocat and mw.title.getCurrentTitle().text == args.pagename then
			categories = autocat.autoCat(fillVariables(args.autocat, variableTable).pattern)
		end
		if boxargs.above then
			boxargs.above = fillVariables(boxargs.above, variableTable).pattern
		end
		if boxargs.below then
			boxargs.below = fillVariables(boxargs.below, variableTable).pattern
		end
		local blocks
		boxargs.title, blocks = getTitleAndNavigationBlocks(args, variableList, variableTable)
		local count = 0
		for line in string.gmatch(blocks, "[^\n]+") do
			if string.sub(line, 1, 1) == ";" then
				line = string.gsub(line, "^; *", "")
				count = count + 1
				boxargs["group" .. tostring(count)] = line
				boxargs["list" .. tostring(count)] = ""
			else
				if count == 0 then
					count = count + 1
					boxargs["list" .. tostring(count)] = ""
				end
				boxargs["list" .. tostring(count)] = (
					boxargs["list" .. tostring(count)] .. line .. "\n"
				)
			end
		end
	end
	return categories .. navbox._navbox(boxargs)
end

-- -----------------------------------------------------------------------------
-- Function wrapper for usage with #invoke
-- -----------------------------------------------------------------------------

function p.categoryDescription(frame)
	return p._categoryDescription(arguments.getArgs(frame))
end

-- =============================================================================

return p