Jump to content

Module:Diagram

From Tensura Wiki

This module provides the functionality of the {{diagram}} template.

The module can be called from wikitext with the following functions:

  • {#invoke:Diagram| render }
Renders the diagram.

The module is only intended to be called from the template mentioned above. The parameters passed in the transclusions of that template (e.g. {diagram|A|-|B|A=Alice|B=Bob}) are recognized and handled by this module. See the template documentation for details about the parameters.


Template:Category/db-en[[Category:Template:Tr ]] Lua error in package.lua at line 80: module 'Module:Language_info/data' not found.


--------------------------------------------------------------------------------
--
-- =============================================================================
--
-- Module:Diagram
--
-- Rendering a family tree-like diagram
--
-- =============================================================================
--
-- Code annotations:
-- This module is documented according to LuaCATS (Lua Comment and Type System).
-- LuaCATS comments are prefixed with three dashes (---) and use Markdown syntax.
-- For a full list of annotations, see the following link:
-- https://luals.github.io/wiki/annotations/
--
--------------------------------------------------------------------------------


local trim = mw.text.trim

---Holds the arguments from the template call.
---@type table<string, string>
local inputArgs

---Return a trimmed version of the value of the template parameter with the specified `key`.
---Return `nil` if the parameter is empty or unset.
---@param key string|integer
---@return string?
local function getArg(key)
	local value = inputArgs[key]
	if not value then
		return nil
	end
	value = trim(value)
	if value == '' then
		return nil
	end
	return value
end

---Combine all the buffered cells during this row.
---@param row1 string Buffer from the iteration
---@param row2 string Buffer from the iteration
---@return string row HTML output for this row
local function processRow(row1, row2)
	return '<tr class="x"> ' .. row1 .. '</tr><tr class="y">' .. row2 .. '</tr>'
end

---Merge the common styles for all boxes (from the `boxstyle` argument) with the
---styles for this current box.
---@param commonBoxstyle string
---@param arg string Current box name
---@return string
local function mergeStyles(commonBoxstyle, arg)
	return table.concat({
		commonBoxstyle,
		getArg(arg .. '_boxstyle') or getArg(arg .. '_style') or getArg(arg .. '_css')
	}, ';')
end

---Table cells for connectors, i.e. horizontal and vertical lines, line corners,
---crossings, etc.
local connectorCells = {
	['_'] = {
		row1 = '<td class="l nr nb"></td><td class="l"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['-'] = {
		row1 = '<td colspan="2" class="l sb"></td>',
		row2 = '<td class="l nr"></td><td class="l"></td>',
	},
	['I'] = {
		row1 = '<td rowspan="2" class="l sr"></td><td class="l nb"></td>',
		row2 = '<td class="l"></td>',
	},
	[','] = {
		row1 = '<td class="l cf"></td><td class="l sb"></td>',
		row2 = '<td class="l sr"></td><td class="l"></td>',
	},
	['v'] = {
		row1 = '<td colspan="2" class="l sb"></td>',
		row2 = '<td class="l sr"></td><td class="l"></td>',
	},
	['.'] = {
		row1 = '<td class="l sb"></td><td class="l" rowspan="2"></td>',
		row2 = '<td class="l sr"></td>',
	},
	[')'] = {
		row1 = '<td rowspan="2" class="l sr"></td><td class="l sb"></td>',
		row2 = '<td class="l"></td>',
	},
	['+'] = {
		row1 = '<td class="l sr sb"></td><td class="l sb"></td>',
		row2 = '<td class="l sr"></td><td class="l"></td>',
	},
	['('] = {
		row1 = '<td class="l sb sr"></td><td class="l" rowspan="2"></td>',
		row2 = '<td class="l sr"></td>',
	},
	['`'] = {
		row1 = '<td class="l sr"></td><td class="l sb"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['^'] = {
		row1 = '<td class="l sr sb"></td><td class="l sb"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	["'"] = {
		row1 = '<td class="l sr sb"></td><td class="l"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['~'] = {
		row1 = '<td colspan="2" class="l db"></td>',
		row2 = '<td class="l nr"></td><td class="l"></td>',
	},
	[':'] = {
		row1 = '<td rowspan="2" class="l dr"></td><td class="l nb"></td>',
		row2 = '<td class="l"></td>',
	},
	['F'] = {
		row1 = '<td class="l cf"></td><td class="l db"></td>',
		row2 = '<td class="l dr"></td><td class="l"></td>',
	},
	['V'] = {
		row1 = '<td colspan="2" class="l db"></td>',
		row2 = '<td class="l dr"></td><td class="l"></td>',
	},
	['7'] = {
		row1 = '<td class="l db"></td><td class="l" rowspan="2"></td>',
		row2 = '<td class="l dr"></td>',
	},
	['D'] = {
		row1 = '<td rowspan="2" class="l dr"></td><td class="l db"></td>',
		row2 = '<td class="l"></td>',
	},
	['X'] = {
		row1 = '<td class="l dr db"></td><td class="l db"></td>',
		row2 = '<td class="l dr"></td><td class="l"></td>',
	},
	['C'] = {
		row1 = '<td class="l db dr"></td><td class="l" rowspan="2"></td>',
		row2 = '<td class="l dr"></td>',
	},
	['L'] = {
		row1 = '<td class="l dr"></td><td class="l db"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['A'] = {
		row1 = '<td class="l dr db"></td><td class="l db"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['J'] = {
		row1 = '<td class="l dr db"></td><td class="l"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['r'] = {
		row1 = '<td class="l cf"></td><td class="l db"></td>',
		row2 = '<td class="l sr"></td><td class="l"></td>',
	},
	['y'] = {
		row1 = '<td colspan="2" class="l db"></td>',
		row2 = '<td class="l sr"></td><td class="l"></td>',
	},
	['n'] = {
		row1 = '<td class="l db"></td><td class="l" rowspan="2"></td>',
		row2 = '<td class="l sr"></td>',
	},
	[']'] = {
		row1 = '<td rowspan="2" class="l sr"></td><td class="l db"></td>',
		row2 = '<td class="l"></td>',
	},
	['$'] = {
		row1 = '<td class="l sr db"></td><td class="l db"></td>',
		row2 = '<td class="l sr"></td><td class="l"></td>',
	},
	['['] = {
		row1 = '<td class="l db sr"></td><td class="l" rowspan="2"></td>',
		row2 = '<td class="l sr"></td>',
	},
	['c'] = {
		row1 = '<td class="l sr"></td><td class="l db"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['h'] = {
		row1 = '<td class="l sr db"></td><td class="l db"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['j'] = {
		row1 = '<td class="l sr db"></td><td class="l"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['p'] = {
		row1 = '<td class="l cf"></td><td class="l sb"></td>',
		row2 = '<td class="l dr"></td><td class="l"></td>',
	},
	['u'] = {
		row1 = '<td colspan="2" class="l sb"></td>',
		row2 = '<td class="l dr"></td><td class="l"></td>',
	},
	['q'] = {
		row1 = '<td class="l sb"></td><td class="l" rowspan="2"></td>',
		row2 = '<td class="l dr"></td>',
	},
	['E'] = {
		row1 = '<td rowspan="2" class="l dr"></td><td class="l sb"></td>',
		row2 = '<td class="l"></td>',
	},
	['x'] = {
		row1 = '<td class="l dr sb"></td><td class="l sb"></td>',
		row2 = '<td class="l dr"></td><td class="l"></td>',
	},
	['3'] = {
		row1 = '<td class="l sb dr"></td><td class="l" rowspan="2"></td>',
		row2 = '<td class="l dr"></td>',
	},
	['b'] = {
		row1 = '<td class="l dr"></td><td class="l sb"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['t'] = {
		row1 = '<td class="l dr sb"></td><td class="l sb"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['d'] = {
		row1 = '<td class="l dr sb"></td><td class="l"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['/'] = {
		row1 = '<td class="l dr db"></td><td class="l sb"></td>',
		row2 = '<td class="l sr"></td><td class="l"></td>',
	},
	['Y'] = {
		row1 = '<td class="l dr sb"></td><td class="l sb"></td>',
		row2 = '<td class="l sr"></td><td class="l"></td>',
	},
	['\\'] = {
		row1 = '<td class="l dr sb"></td><td class="l db"></td>',
		row2 = '<td class="l sr"></td><td class="l"></td>',
	},
	['B'] = {
		row1 = '<td class="l sr db"></td><td class="l sb"></td>',
		row2 = '<td class="l sr"></td><td class="l"></td>',
	},
	['G'] = {
		row1 = '<td class="l sr sb"></td><td class="l db"></td>',
		row2 = '<td class="l sr"></td><td class="l"></td>',
	},
	['R'] = {
		row1 = '<td class="l sr db"></td><td class="l sb"></td>',
		row2 = '<td class="l dr"></td><td class="l"></td>',
	},
	['U'] = {
		row1 = '<td class="l sr sb"></td><td class="l sb"></td>',
		row2 = '<td class="l dr"></td><td class="l"></td>',
	},
	['4'] = {
		row1 = '<td class="l sr sb"></td><td class="l db"></td>',
		row2 = '<td class="l dr"></td><td class="l"></td>',
	},
	['!'] = {
		row1 = '<td class="l sr nb"></td><td class="l" rowspan="2"></td>',
		row2 = '<td class="l dr"></td>',
	},
	['i'] = {
		row1 = '<td class="l dr nb"></td><td class="l" rowspan="2"></td>',
		row2 = '<td class="l sr"></td>',
	},
	['<'] = {
		row1 = '<td class="l sb nr"></td><td class="l db"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['>'] = {
		row1 = '<td class="l db nr"></td><td class="l sb"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['9'] = {
		row1 = '<td class="l sr nb"></td><td class="l" rowspan="2"></td>',
		row2 = '<td class="l"></td>',
	},
	['6'] = {
		row1 = '<td colspan="2" class="l nb"></td>',
		row2 = '<td class="l sr"></td><td class="l"></td>',
	},
	['&'] = {
		row1 = '<td class="l sb nr"></td><td class="l"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['?'] = {
		row1 = '<td class="l nr"></td><td class="l sb"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['"'] = {
		row1 = '<td class="l dr nb"></td><td class="l" rowspan="2"></td>',
		row2 = '<td class="l"></td>',
	},
	[';'] = {
		row1 = '<td colspan="2" class="l nb"></td>',
		row2 = '<td class="l dr"></td><td class="l"></td>',
	},
	['a'] = {
		row1 = '<td class="l db nr"></td><td class="l"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['e'] = {
		row1 = '<td class="l nr"></td><td class="l db"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['M'] = {
		row1 = '<td class="l dr db"></td><td class="l db"></td>',
		row2 = '<td class="l sr"></td><td class="l"></td>',
	},
	['W'] = {
		row1 = '<td class="l sr db"></td><td class="l db"></td>',
		row2 = '<td class="l dr"></td><td class="l"></td>',
	},
	['2'] = {
		row1 = '<td class="l dr sb"></td><td class="l db"></td>',
		row2 = '<td class="l dr"></td><td class="l"></td>',
	},
	['5'] = {
		row1 = '<td class="l dr db"></td><td class="l sb"></td>',
		row2 = '<td class="l dr"></td><td class="l"></td>',
	},
	['f'] = {
		row1 = '<td class="l sr nb"></td><td class="l db"></td>',
		row2 = '<td class="l dr"></td><td class="l"></td>',
	},
	['g'] = {
		row1 = '<td class="l sr db"></td><td class="l" rowspan="2"></td>',
		row2 = '<td class="l dr"></td>',
	},
	['k'] = {
		row1 = '<td class="l dr nb"></td><td class="l db"></td>',
		row2 = '<td class="l sr"></td><td class="l"></td>',
	},
	['s'] = {
		row1 = '<td class="l dr db"></td><td class="l" rowspan="2"></td>',
		row2 = '<td class="l sr"></td>',
	},
	['o'] = {
		row1 = '<td class="l nr sb"></td><td class="l db"></td>',
		row2 = '<td class="l dr"></td><td class="l"></td>',
	},
	['m'] = {
		row1 = '<td class="l nr db"></td><td class="l sb"></td>',
		row2 = '<td class="l dr"></td><td class="l"></td>',
	},
	['w'] = {
		row1 = '<td class="l dr sb"></td><td class="l db"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['z'] = {
		row1 = '<td class="l dr db"></td><td class="l sb"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['P'] = {
		row1 = '<td class="l dr nb"></td><td class="l sb"></td>',
		row2 = '<td class="l sr"></td><td class="l"></td>',
	},
	['H'] = {
		row1 = '<td class="l dr sb"></td><td class="l" rowspan="2"></td>',
		row2 = '<td class="l sr"></td>',
	},
	['K'] = {
		row1 = '<td class="l sr nb"></td><td class="l sb"></td>',
		row2 = '<td class="l dr"></td><td class="l"></td>',
	},
	['N'] = {
		row1 = '<td class="l sr sb"></td><td class="l" rowspan="2"></td>',
		row2 = '<td class="l dr"></td>',
	},
	['T'] = {
		row1 = '<td class="l nr db"></td><td class="l sb"></td>',
		row2 = '<td class="l sr"></td><td class="l"></td>',
	},
	['Z'] = {
		row1 = '<td class="l nr sb"></td><td class="l db"></td>',
		row2 = '<td class="l sr"></td><td class="l"></td>',
	},
	['S'] = {
		row1 = '<td class="l sr db"></td><td class="l sb"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['Q'] = {
		row1 = '<td class="l sr sb"></td><td class="l db"></td>',
		row2 = '<td class="l" colspan="2"></td>',
	},
	['←'] = {
		row1 = '<td class="l sb alt"></td><td class="l sb"></td>',
		row2 = '<td class="l alb" colspan="2"></td>',
	},
	['→'] = {
		row1 = '<td class="l sb"></td><td class="l sb art"></td>',
		row2 = '<td class="l arb" colspan="2"></td>',
	},
	['↑'] = {
		row1 = '<td class="l sr atl"></td><td class="l atr" rowspan="2"></td>',
		row2 = '<td class="l sr"></td>',
	},
	['↓'] = {
		row1 = '<td class="l sr"></td><td class="l abr" rowspan="2"></td>',
		row2 = '<td class="l sr abl"></td>',
	},
	['⇠'] = {
		row1 = '<td class="l db alt"></td><td class="l db"></td>',
		row2 = '<td class="l alb" colspan="2"></td>',
	},
	['⇢'] = {
		row1 = '<td class="l db"></td><td class="l db art"></td>',
		row2 = '<td class="l arb" colspan="2"></td>',
	},
	['⇡'] = {
		row1 = '<td class="l dr atl"></td><td class="l atr" rowspan="2"></td>',
		row2 = '<td class="l dr"></td>',
	},
	['⇣'] = {
		row1 = '<td class="l dr"></td><td class="l abr" rowspan="2"></td>',
		row2 = '<td class="l dr abl"></td>',
	},
	['*'] = {  -- unlinked crossing
		row1 = '<td class="l sb ct"></td><td class="l sb"></td>',
		row2 = '<td class="l sr cb"></td><td class="l"></td>',
	},
}
-- aliases
connectorCells['0'] = connectorCells['_']
connectorCells['O'] = connectorCells['_']

--------------------------------------------------------------------------------
---Main return object
local p = {}

---Process the arguments to render a HTML `<table>` element.
---@param frame table Interface to the parser (`mw.frame`)
---@return string
p.render = function(frame)
	inputArgs = frame:getParent().args  -- global input args cache

	local boxclass = getArg('boxclass')
	local boxstyle = getArg('boxstyle') or ''

	-- The unnamed arguments come in rows, e.g.:
	-- |A|_|B|#
	-- |C|^|D|#
	-- Unnamed args = { 'A', '_', 'B', '#', 'C', '^', 'D', '#' }
	-- We will iterate over these arguments in rows, which means: Create the table
	-- cell HTML for each argument and store it in a buffer. When we reach the
	-- end of a row, i.e. a '#' argument, then combine the buffered cells, add
	-- them to the processedRows, and clear the buffer for the next row.

	---HTML table rows
	local processedRows = {}
	---Argument index during iteration
	local argIndex = 1
	---Argument value during iteration
	local arg

	-- Each row of arguments will actually consist of *two* table rows in
	-- the HTML output (one `<tr class="x">`, one `<tr class="y">`). So the buffer
	-- consists of two variables, `row1` and `row2`.

	---Row buffer during iteration (children of `<tr class="x">`).
	local row1 = ''
	---Row buffer during iteration (children of `<tr class="y">`).
	local row2 = ''

	-- Start iterating
	while true do
		arg = inputArgs[argIndex]  -- current unnamed argument
		if not arg then
			break
		end
		argIndex = argIndex+1
		arg = trim(arg)

		-- `arg` is one of these:
		-- * a row terminator, then complete the current row and start a new one
		-- * a connector (line drawing) or box (content), then create its HTML
		--   and insert it into the buffer
		-- * a blank string (argument consisting only of whitespace), then ignore it

		if arg == '#' then
			-- row terminator
			processedRows[#processedRows + 1] = processRow(row1, row2)
			-- clear the buffer for the next row
			row1 = ''
			row2 = ''
		elseif connectorCells[arg] then
			-- connector
			row1 = row1 .. connectorCells[arg].row1
			row2 = row2 .. connectorCells[arg].row2
		elseif arg == '' then
			-- blank string, skipped
		else
			-- content box
			local td = mw.html.create('td')
				:attr({
					class = 'diagram-box',
					colspan = 2 * tonumber(getArg(arg .. '_cols') or 3),
					rowspan = 2 * tonumber(getArg(arg .. '_rows') or 1),
					id = getArg(arg .. '_id'),
				})
				:addClass(boxclass)
				:addClass(getArg(arg .. '_class'))
			local style = mergeStyles(boxstyle, arg)
			if style then
				td:attr('style', style)
			end
			td:wikitext(trim(inputArgs[arg] or ''))
			row1 = row1 .. tostring(td)
		end
	end

	-- We reached the end of the unnamed arguments. If the last argument was not
	-- a row terminator, then the buffer still contains the last row, so add that
	-- to the output.
	if row1 ~= '' then
		processedRows[#processedRows + 1] = processRow(row1, row2)
	end

	-- Insert all the table rows into an HTML `<table>`.
	local output = mw.html.create('table')
		:attr({
			class = 'diagram',
			id = getArg('id'),
			style = getArg('style') or getArg('css'),
			title = getArg('summary'),
		})
		:addClass(getArg('class'))
		:wikitext(table.concat(processedRows))  -- insert the rows
	return tostring(output)
end

return p