Module:Sandbox/User:Simanelix/Cross table
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​\;{{only|je|short=1}}}}) "Endless" | "Featherfall" ; "komorebi" ; "Puzzlebox" ; "Watcher" ; "yakusoku" ~ Eob | "Deeper" ; "Featherfall" ; "Eld Unknown" ; "Endless" ; "pokopoko" ; "komorebi" ; "Puzzlebox" ; "Watcher" ; "yakusoku" ~ Ms }}
Name | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | Every other biome | Menu screens |
---|---|---|---|---|---|---|---|---|---|---|---|---|
"Deeper" | Yes | Yes | Yes | No | No | No | No | No | No | No | No | Yes |
"Featherfall" | No | No | No | ~yes~ | ~yes~ | ~yes~ | No | No | No | No | Yes | Yes |
"Eld Unknown" | Yes | No | No | No | No | No | Yes | Yes | Yes | No | No | Yes |
"Endless" | Yes | No | No | No | No | No | Yes | Yes | No | Yes[JE only] | No | Yes |
"pokopoko" | Yes | No | No | No | No | No | Yes | Yes | No | Yes | No | Yes |
"komorebi" | No | No | No | No | No | No | No | No | No | No | Yes | Yes |
"Puzzlebox" | No | No | No | No | No | No | No | No | No | No | Yes | Yes |
"Watcher" | No | No | No | No | No | No | No | No | No | No | Yes | Yes |
"yakusoku" | No | No | No | No | No | No | No | No | No | No | Yes | Yes |
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 ofyes
where their column and row intersect
- assosciated items will default to a
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.
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