Module:Sandbox/User:Simanelix/Cross table

Jump to navigation Jump to search

Usage[edit source]

This module is an advanced method of creating a table with repetitive data. You should only use this in special circumstances.

Example[edit source]

{{User:Simanelix/Sandbox/Cross table
|caption = Where each music track plays
|name = Track
|C B  = {{BiomeLink|Badlands|text=B}}
|C CG = {{BiomeLink|Cherry Grove|text=CG}}
|C DC = {{BiomeLink|Dripstone Caves|text=DC}}
|C DD = {{BiomeLink|Deep Dark|text=DD}}
|C FF = {{BiomeLink|Flower Forest|text=FF}}
|C G  = {{BiomeLink|Grove|text=G}}
|C JP = {{BiomeLink|Jagged Peaks|text=JP}}
|C LC = {{BiomeLink|Lush Caves|text=LC}}
|C SP = {{BiomeLink|Stony Peaks|text=SP}}
|C SS = {{BiomeLink|Snowy Slopes|text=SS}}
|C Eob = Every other biome
|C Ms  = [[Menu screen]]s
|
"Deeper"      ~ DC; DD; FF
|
"Featherfall" ~ (\~yes\~) B ; CG; LC
|
"Eld Unknown" ;
"Endless"     ;
"pokopoko"    ~ DC; G ; JP
|
"Eld Unknown" ~ SP
|
"pokopoko"    ~ SS ~ (R {{tc|yes|Yes&#x200B\;{{only|je|short=1}}}}) "Endless"
|
"Featherfall" ;
"komorebi"    ;
"Puzzlebox"   ;
"Watcher"     ;
"yakusoku"    ~ Eob
|
"Deeper"      ;
"Featherfall" ;
"Eld Unknown" ;
"Endless"     ;
"pokopoko"    ;
"komorebi"    ;
"Puzzlebox"   ;
"Watcher"     ;
"yakusoku"    ~ Ms
}}
NameDCDDFFBCGLCGJPSPSSEvery other biomeMenu screens
"Deeper"YesYesYesNoNoNoNoNoNoNoNoYes
"Featherfall"NoNoNo~yes~~yes~~yes~NoNoNoNoYesYes
"Eld Unknown"YesNoNoNoNoNoYesYesYesNoNoYes
"Endless"YesNoNoNoNoNoYesYesNoYes​‌[JE only]NoYes
"pokopoko"YesNoNoNoNoNoYesYesNoYesNoYes
"komorebi"NoNoNoNoNoNoNoNoNoNoYesYes
"Puzzlebox"NoNoNoNoNoNoNoNoNoNoYesYes
"Watcher"NoNoNoNoNoNoNoNoNoNoYesYes
"yakusoku"NoNoNoNoNoNoNoNoNoNoYesYes

Parameters[edit source]

Every parameter is optional, but you will probably need to use the name or caption parameters if you want your table to make any sense.

Un-named parameters

The un-named parameter are assignment chains. Each tilde (~) does 3 things at once:

  • assigns all of the items to its left as row names
  • assigns all of the items to its right as column names
  • assosciates each of the items on the left with each of the items on the right
    • assosciated items will default to a {{tc}}-value of yes where their column and row intersect

In essence, tilder places a tc-value in the rows and columns named on its left and right. Row names and column names can be separated using ;.

  • A ; B ~ C ; D places tc-values in the cells where the columns A and B intersect with the columns C and D. This ends up placing 4 tc-values.
  • A ; B; C ~ D; E; F places tc-values in the cells where the columns A, B, and C intersect with the columns D, E, and F. This ends up placing 9 tc-values.

Additionally, a modifier can be placed after a tilde. The format of a modifier is: (<Flag> <Table content>).

  • <Flag> can be either:
    • L
    • R
    • B
  • <Table content> can be any text that does not contain parenthesis.
    • This is used as the content for all of the cells where the rows and columns of the tidle intersect.

The L does nothing except declare that the rest of the modifier is the <Table content>.

The R reverses the directionality of the tilde. So, items to the left of the tilde are assigned as columns, and items to the right of the tilde are assigned as rows.

The B makes the tilde assign columns and rows in both directions. So, items to the left of the tilde are assigned as columns and rows, and items to the right are assigned as columns and rows too.

One modifier cannot contain multiple flags, because there is no reason you would use multiple flags.

Each un-named parameter can contain a sequence of values with tilder between. Each tilder will assign columns and rows independently. So, | A ~ B ~ C ~ D is effectively a shorthand for | A ~ B | B ~ C | C ~ D.

Tilde modifiers also work independently. So, modifiers on any one tilde are not applied to any other tilde. Therefore, | A ~ B ~(R) C ~ D ~(R) E is effectively a shorthand for | A ~ B | C ~ B | C ~ D | E ~ D.

Named parameters

name is the content of the top-left cell of the table (but for some reason it doesn't work).

caption is the content of the table's caption.

sign is the symbol for the tildes in the un-named parameters. You can use this to rename tilde to whatever you want, but be careful. This "symbol" is used in Lua patterns in the module's code, and if you put anything weird in there, you might have to escape it with %.

sep is the symbol used to separate the row and column names in the un-named parameters. i.e. this symbol replaces ;. You can use this to rename tilde to whatever you want, but be careful. This "symbol" is used in Lua patterns in the module.

tc_sep is the symbol for separating the content of a table cell from the HTML attribute list for table cell's HTML element. You can use this to rename pipe to whatever you want, but be careful. This "symbol" is also used in Lua patterns in the module.

Column-name parameters

Any named parameter starting with C <space> (<space> means 1 or more required spaces) is interpreted as a column-name. Everything after C <space> (in the parameter's name) is defined as a shorthand for the column. The value of the parameter is the header of the column, which will be printed in the final table (if the column is ever assigned). Any tilde that uses the column's shorthand will place content in that column.

There are no row-name parameters, but maybe I will add those in the future.

[view] [edit] [history] [refresh]The above documentation is transcluded from Module:Sandbox/User:Simanelix/Cross table/doc.
local p = {}

function bool_str(b)
	if(b) then return "true" end
	return "false"
end
function str_bool(b)
	if(b == nil) then return false end
	if(type(b) == "string") then return ((#b) > 0) end
	return (not (not b))
end

function trim(a_str)
	return string.gsub(string.gsub(a_str, "^%s*", ""), "%s*$", "")
end
function ternary(a_cond, a_true, a_false)
	if(a_cond) then
	return a_true end
	return a_false
end
function str_split(a_str, a_char)
	return string.gfind(a_str, "[^"..a_char.."]+")
end
-- inverts an 'invertible' map
function map_to_inv(a_map)
	local res = {}
	for i,arg in pairs(a_map) do
		res[arg] = i
	end
	return res
end

-- allows you to use the tc template and standard wikitext table cell formatting tricks
function tc_parse(tc, sep)
	local sS, sE, tc_t, new_tc = string.find(tc, "([^"..sep.."]*)"..sep.."(.*)")
	if (new_tc ~= nil) then
		-- trim step is separate
		tc_t = trim(tc_t)
		tc = trim(new_tc)
	end
	return ("<td"
		.. ((tc_t and (" " .. tc_t)) or "")
		.. ">"
		.. tc
		.. "</td>"
	)
end

local escape_char_percent = "%"
local escape_char_backslash = "\\"
function escape(a_str, special_chars, escape_char)
	local res = ""
	for c in a_str:gmatch(".") do
		if (special_chars[c]) then
			res = res .. escape_char .. c
		else
			res = res .. c
		end
	end
	return res
end
function escapelate(a_str, c_map_inv, escape_char)
	local res = ""
	local escaped = false
	for c in a_str:gmatch(".") do
		if (escaped) then
			res = res .. escape_char .. (c_map_inv[c] or c)
			escaped = false
		else
			if (c == escape_char) then
				escaped = true
			else
				res = res .. c
			end
		end
	end
	return res
end
function unescape(a_str, c_map, escape_char)
	local res = ""
	local escaped = false
	for c in a_str:gmatch(".") do
		if (escaped) then
			res = res .. (c_map[c] or c)
			escaped = false
		else
			if (c == escape_char) then
				escaped = true
			else
				res = res .. c
			end
		end
	end
	return res
end

function p.output(f)
	local args = f
	if f == mw.getCurrentFrame() then 
		args = require('Module:ProcessArgs').merge(true)
	else
		f = mw.getCurrentFrame()
	end
	
	
	local eq_sign = args["sign"  ] or "%~"
	local eq_sep  = args["sep"   ] or "%;"
	local tc_sep  = args["tc_sep"] or "%|"
	local tc_n_default = args["empty"  ] or "class=\"tc-no\" " ..tc_sep.." No"
	local tc_y_default = args["default"] or "class=\"tc-yes\" "..tc_sep.." Yes"
	local tcp_n_default = tc_parse(tc_n_default, tc_sep)
	local tcp_y_default = tc_parse(tc_y_default, tc_sep)
	
	local tc_map = {}
	tc_map[tc_n_default] = tcp_n_default
	tc_map[tc_y_default] = tcp_y_default
	
	local top_left  = args["name"   ] or "Name"
	local p_caption = args["caption"] or ""
	local p_plain   = args["plain"  ] or ""
	local p_class   = args["class"  ] or ""
	
	-- escape setup
	local special_chars_map = {
		["q"] = unescape(eq_sign, {}, escape_char_percent),
		["e"] = unescape(eq_sep , {}, escape_char_percent),
		["c"] = unescape(tc_sep , {}, escape_char_percent),
	}
	local special_chars_map_inv = map_to_inv(special_chars_map)
	
	local res = ""
	
	-- assosciation table
	local stuff = {}
	
	-- the number of rows in the table
	local row_count = 0
	-- maps row names to numbers
	local row_indices = {}
	-- maps numbers to row names
	local row_indices_inv = {}
	-- the number of columns in the table
	local col_count = 0
	-- maps column names to numbers
	local col_indices = {}
	-- maps numbers to column names
	local col_indices_inv = {}
	
	for i,arg in ipairs(args) do
		-- res = res .. "argument detected: " .. arg .. "<br>"
		
		-- mmm look at that crazy code!
		local prev_lhs, prev_flag, prev_tc = "", "", ""
		local      lhs,      flag,      tc = "", "", ""
		local sStart, sEnd, new_arg
		while arg do
			arg = escapelate(arg, special_chars_map_inv, escape_char_backslash)
			
			prev_lhs  = lhs
			prev_flag = flag
			prev_tc   = tc
			
			-- check for flag AND tc
			sStart, sEnd, lhs, flag, tc, new_arg = string.find(arg, "^([^"..eq_sign.."]*)"..eq_sign.."%s*%(%s*([LRB])%s+([^%(%s%)][^%(%)]*)%)%s*(.*)$")
			
			-- check for only flag
			if(new_arg == nil) then
				sStart, sEnd, lhs, flag, new_arg = string.find(arg, "^([^"..eq_sign.."]*)"..eq_sign.."%s*%(%s*([LRB])%s*%)%s*(.*)$")
				tc = tc_y_default
			end
			-- check for only tc
			if(new_arg == nil) then
				sStart, sEnd, lhs, tc, new_arg = string.find(arg, "^([^"..eq_sign.."]*)"..eq_sign.."%s*%(%s*([^%(%)]-)%s*%)%s*(.*)$")
				flag = ""
			end
			-- check for no flag or tc
			if(new_arg == nil) then
				sStart, sEnd, lhs, new_arg = string.find(arg, "^([^"..eq_sign.."]*)"..eq_sign.."%s*%(?%s*%)?%s*(.*)$")
				tc = tc_y_default
				flag = ""
			end
			-- check for final value
			if(new_arg == nil) then
				lhs = arg
			end
			
			-- trim everything
			if(new_arg) then new_arg = trim(new_arg) end
			-- and assign arg so we can use it on next step
			arg  = new_arg
			tc   = trim(tc)
			flag = trim(flag)
			
			
			--[[
			res = res .. "Loop step<br>"
			res = res .. "* sStart = " .. (sStart or "") .. "<br>"
			res = res .. "* sEnd = " .. (sEnd or "") .. "<br>"
			res = res .. "* curr:<br>"
			res = res .. "** lhs = " .. (lhs or "") .. "<br>"
			res = res .. "** flag = " .. (flag or "") .. "<br>"
			res = res .. "** tc = " .. (tc or "") .. "<br>"
			res = res .. "* prev:<br>"
			res = res .. "** lhs = " .. (prev_lhs or "") .. "<br>"
			res = res .. "** flag = " .. (prev_flag or "") .. "<br>"
			res = res .. "** tc = " .. (prev_tc or "") .. "<br>"
			res = res .. "* arg = " .. (arg or "") .. "<br>"
			]]
			
			-- make sure prev_lhs exists
			if str_bool(prev_lhs) then
			
			-- assosciate data
			if (prev_flag == "B" or prev_flag == "R") then
				for a_o_l in str_split(lhs, eq_sep) do
				local a_l = unescape(trim(a_o_l), special_chars_map, escape_char_backslash)
				if(stuff[a_l] == nil) then
					row_count = row_count + 1
					row_indices[a_l] = row_count
					row_indices_inv[row_count] = a_l
					
					stuff[a_l] = {}
				end
				for a_o_r in str_split(prev_lhs, eq_sep) do
				local a_r = unescape(trim(a_o_r), special_chars_map, escape_char_backslash)
				if(col_indices[a_r] == nil) then
					col_count = col_count + 1
					col_indices[a_r] = col_count
					col_indices_inv[col_count] = a_r
				end
				
				-- res = res .. "* reverse!<br>"
				stuff[a_l][a_r] = unescape(prev_tc, special_chars_map, escape_char_backslash)
				end
				end
			end
			if (prev_flag == "B" or prev_flag == "L" or prev_flag == "") then
				for a_o_l in str_split(prev_lhs, eq_sep) do
				local a_l = unescape(trim(a_o_l), special_chars_map, escape_char_backslash)
				if(stuff[a_l] == nil) then
					row_count = row_count + 1
					row_indices[a_l] = row_count
					row_indices_inv[row_count] = a_l
					
					stuff[a_l] = {}
				end
				for a_o_r in str_split(lhs, eq_sep) do
				local a_r = unescape(trim(a_o_r), special_chars_map, escape_char_backslash)
				if(col_indices[a_r] == nil) then
					col_count = col_count + 1
					col_indices[a_r] = col_count
					col_indices_inv[col_count] = a_r
				end
				
				-- res = res .. "* forward!<br>"
				stuff[a_l][a_r] = unescape(prev_tc, special_chars_map, escape_char_backslash)
				end
				end
			end
			
			-- prev_lhs existed, so we assosciated it with lhs
			end
			
			if (arg == nil) then break end
		end
	end
	
	local my_table = {}
	for iy = 1, row_count do
		my_table[iy] = {}
		rowname = row_indices_inv[iy]
		row = stuff[rowname]
		for colname, cell in pairs(row) do
			my_table[iy][col_indices[colname]] = cell
		end
		-- fill empty rows too
		for ix = 1, col_count do
			if(my_table[iy][ix] == nil) then
				my_table[iy][ix] = tc_n_default
			end
		end
	end
	
	
	--[==[
<table>
  <tr>
    <th>Company</th>
    <th>Contact</th>
    <th>Country</th>
  </tr>
  <tr>
    <td>Alfreds Futterkiste</td>
    <td>Maria Anders</td>
    <td>Germany</td>
  </tr>
  <tr>
    <td>Centro comercial Moctezuma</td>
    <td>Francisco Chang</td>
    <td>Mexico</td>
  </tr>
</table>
	]==]
	
	-- fancy stuff: you can use named arguments to shorten content
	local col_args = {}
	for i_n,arg in pairs(args) do
		if (type(i_n) == "string") then
			local sS, sE, repl = string.find(trim(i_n), "^C%s+(.*)$")
			repl = trim(repl)
			if (repl) then
				col_args[repl] = arg
			end
		end
	end
	
	p_class = ternary(#p_plain > 0, p_class, p_class .. ternary(#p_class > 0, " ", "") .. "wikitable")
	
	res = res .. "<table" .. (p_class and (" class=\"" .. p_class .. "\"")) .. ">"
	
	res = (res
		.. (p_caption and (
			"<caption>"
			.. p_caption
			.. "</caption>"
		))
		.. "<tr>"
		.. "<th>"
		.. top_left
		.. "</th>"
	)
	for i = 1, col_count do
		res = (res
			.. "<th>"
			.. (col_args[col_indices_inv[i]] or col_indices_inv[i])
			.. "</th>"
		)
	end
	res = res .. "</tr>"
	
	for iy,row in ipairs(my_table) do
		res = (res
			.."<tr>"
			.."<td>"
			.. row_indices_inv[iy]
			.."</td>"
		)
		for ix,cell in ipairs(row) do
			res = res .. (
				tc_map[cell] or tc_parse(cell, tc_sep)
			)
		end
		res = res.."</tr>"
	end
	
	res = res .. "</table>"
	
	return res
end

return p