Zelda Wiki

Want to contribute to this wiki?
Sign up for an account, and get started!

Come join the Zelda Wiki community Discord server!

READ MORE

Zelda Wiki
mNo edit summary
No edit summary
(8 intermediate revisions by the same user not shown)
Line 4: Line 4:
 
local utilsFunction = require("Module:UtilsFunction")
 
local utilsFunction = require("Module:UtilsFunction")
 
local utilsLayout = require("Module:UtilsLayout/Tabs")
 
local utilsLayout = require("Module:UtilsLayout/Tabs")
  +
local utilsNumber = require("Module:UtilsNumber")
 
local utilsString = require("Module:UtilsString")
 
local utilsString = require("Module:UtilsString")
 
local utilsTable = require("Module:UtilsTable")
 
local utilsTable = require("Module:UtilsTable")
Line 85: Line 86:
   
 
function h.resolveShorthand(data)
 
function h.resolveShorthand(data)
  +
data = mw.clone(data)
local fullData = {
 
  +
if data.headers then
hideEmptyColumns = data.hideEmptyColumns,
 
 
table.insert(data.rows, 1, {
hideEmptyRows = data.hideEmptyRows,
 
sortable = data.sortable,
+
header = true,
styles = data.styles,
+
cells = data.headers
}
+
})
  +
end
local rows = data.rows or utilsTable.ivalues(data)
 
 
for i, row in ipairs(data.rows) do
fullData.rows = {}
 
 
row.cells = row.cells or utilsTable.ivalues(row)
for i, row in ipairs(rows) do
 
 
for j, cell in ipairs(row.cells) do
fullData.rows[i] = {
 
header = row.header,
 
footer = row.footer,
 
styles = row.styles,
 
cells = {}
 
}
 
local cells = row.cells or utilsTable.ivalues(row)
 
for j, cell in ipairs(cells) do
 
 
local cell = h.resolveShorthandCell(cell)
 
local cell = h.resolveShorthandCell(cell)
fullData.rows[i].cells[j] = cell
+
data.rows[i].cells[j] = cell
 
if cell then
 
if cell then
 
cell.styles = utilsTable.merge(row.styles or {}, cell.styles or {})
 
cell.styles = utilsTable.merge(row.styles or {}, cell.styles or {})
 
end
 
end
 
end
 
end
if utilsTable.isEmpty(fullData.rows[i]) then
+
if utilsTable.isEmpty(data.rows[i]) then
fullData.rows[i] = nil
+
data.rows[i] = nil
 
end
 
end
 
end
 
end
return fullData
+
return data
 
end
 
end
 
function h.resolveShorthandCell(cell)
 
function h.resolveShorthandCell(cell)
Line 155: Line 149:
 
function h.hideEmptyColumns(data)
 
function h.hideEmptyColumns(data)
 
local totalColumns = h.countTotalColumns(data.rows)
 
local totalColumns = h.countTotalColumns(data.rows)
local emptyCellCounts = {}
+
local emptyCellsPerColumn = {}
for _, row in ipairs(data.rows) do
+
for i, row in ipairs(data.rows) do
for i, cell in ipairs(row.cells) do
+
for j, cell in ipairs(row.cells) do
  +
emptyCellsPerColumn[j] = emptyCellsPerColumn[j] or 0
 
if (not cell.header and not cell.footer) and h.isCellEmpty(cell) then
 
if (not cell.header and not cell.footer) and h.isCellEmpty(cell) then
emptyCellCounts[i] = (emptyCellCounts[i] or 0) + 1
+
emptyCellsPerColumn[j] = emptyCellsPerColumn[j] + 1
 
end
 
end
 
end
 
end
 
for i in utilsFunction.range(#row.cells + 1, totalColumns) do
 
for i in utilsFunction.range(#row.cells + 1, totalColumns) do
emptyCellCounts[i] = (emptyCellCounts[i] or 0) + 1
+
emptyCellsPerColumn[i] = emptyCellsPerColumn[i] + 1
 
end
 
end
 
end
 
end
data = mw.clone(data)
+
local data = mw.clone(data)
local removedColumns = 0
 
 
local headerRows = utilsTable.filter(data.rows, "header")
 
local headerRows = utilsTable.filter(data.rows, "header")
 
local footerRows = utilsTable.filter(data.rows, "footer")
 
local footerRows = utilsTable.filter(data.rows, "footer")
Line 173: Line 167:
 
for i, row in ipairs(data.rows) do
 
for i, row in ipairs(data.rows) do
 
for j, cell in ipairs(row.cells) do
 
for j, cell in ipairs(row.cells) do
if emptyCellCounts[j] == totalRows then
+
if emptyCellsPerColumn[j] == totalRows then
  +
row.cells[j] = nil
table.remove(data.rows[i].cells, j)
 
 
end
 
end
 
end
 
end
 
row.cells = utilsTable.compact(row.cells)
 
end
 
end
 
return data
 
return data
Line 284: Line 279:
 
),
 
),
 
utilsTable.zip,
 
utilsTable.zip,
  +
utilsTable._map(utilsTable._padNils(utilsNumber.MIN)),
 
utilsTable._map(utilsTable.max),
 
utilsTable._map(utilsTable.max),
 
}
 
}
Line 304: Line 300:
 
data = {
 
data = {
 
definitions = {
 
definitions = {
options = {
 
type = "record",
 
properties = {
 
{
 
name = "sortable",
 
type = "boolean",
 
desc = "If <code>true</code> renders a table that can be sorted by any of its columns."
 
},
 
{
 
name = "hideEmptyColumns",
 
type = "boolean",
 
desc = "If <code>true</code>, columns that are completely empty (except for header and footer rows) are omitted.",
 
},
 
{
 
name = "hideEmptyRows",
 
type = "boolean",
 
desc = "If <code>true</code>, rows that are completely empty (except for header cells) are omitted.",
 
},
 
{
 
name = "styles",
 
type = "map",
 
keys = { type = "string" },
 
values = { type = "string" },
 
desc = "Key-value pairs of CSS properties to apply to the <code><nowiki><table></nowiki></code> element.",
 
},
 
},
 
},
 
 
rowOptions = {
 
rowOptions = {
 
type = "record",
 
type = "record",
Line 354: Line 323:
 
},
 
},
 
rows = {
 
rows = {
oneOf = {
+
type = "array",
 
items = { _ref = "#/definitions/row" },
 
},
 
row = {
 
allOf = {
 
{ _ref = "#/definitions/rowOptions" },
 
{
 
{
 
type = "record",
 
type = "record",
 
properties = {
 
properties = {
 
{
 
{
name = "rows",
+
name = "cells",
required = true,
 
 
type = "array",
 
type = "array",
items = { _ref = "#/definitions/row" },
+
items = { _ref = "#/definitions/cell" }
}
+
},
}
+
},
},
+
}
{
 
type = "array",
 
items = { _ref = "#/definitions/row" },
 
},
 
}
 
},
 
row = {
 
allOf = {
 
{ _ref = "#/definitions/rowOptions" },
 
{ _ref = "#/definitions/cells" },
 
 
}
 
}
 
},
 
},
Line 422: Line 385:
 
},
 
},
 
{
 
{
 
type = "array",
 
_ref = "#/definitions/rows",
 
_ref = "#/definitions/rows",
 
desc = "Subvidisions for the cell. Think of it as a table within a table."
 
desc = "Subvidisions for the cell. Think of it as a table within a table."
Line 433: Line 397:
 
},
 
},
 
 
 
type = "record",
 
required = true,
 
required = true,
 
desc = "A Lua table representing the desired wikitable.",
 
desc = "A Lua table representing the desired wikitable.",
allOf = {
+
properties = {
 
{
{ _ref = "#/definitions/options" },
 
{ _ref = "#/definitions/rows" },
+
name = "sortable",
 
type = "boolean",
}
 
 
desc = "If <code>true</code> renders a table that can be sorted by any of its columns."
 
},
 
{
 
name = "hideEmptyColumns",
 
type = "boolean",
 
desc = "If <code>true</code>, columns that are completely empty (except for header and footer rows) are omitted.",
 
},
 
{
 
name = "hideEmptyRows",
 
type = "boolean",
 
desc = "If <code>true</code>, rows that are completely empty (except for header cells) are omitted.",
 
},
 
{
 
name = "styles",
 
type = "map",
 
keys = { type = "string" },
 
values = { type = "string" },
 
desc = "Key-value pairs of CSS properties to apply to the <code><nowiki><table></nowiki></code> element.",
 
},
  +
{
 
name = "headers",
 
type = "array",
  +
items = { type = "string" },
  +
desc = "An array of strings to serve as a header row.",
 
},
  +
{
 
name = "rows",
 
required = true,
  +
type = "array",
 
items = {
 
allOf = {
 
{ _ref = "#/definitions/rowOptions" },
 
{ _ref = "#/definitions/cells" },
 
}
 
},
 
},
 
},
 
}
 
}
 
},
 
},
Line 521: Line 523:
 
args = {
 
args = {
 
{
 
{
 
rows = {
{'column1', 'column2', 'column3', header = true},
 
{'cell1', 'cell2', 'cell3'},
+
{'cell1', 'cell2', 'cell3'}
}
+
},
 
headers = {'column1', 'column2', 'column3'},
}
 
 
},
  +
},
 
},
 
},
 
{
 
{
Line 530: Line 534:
 
args = {
 
args = {
 
{
 
{
 
rows = {
{"cell | 1", "cell |} 2", "cell {| 3"},
+
{"cell | 1", "cell |} 2", "cell {| 3"},
},
 
 
},
 
}
 
},
 
},
 
},
 
},
Line 539: Line 545:
 
{
 
{
 
rows = {
 
rows = {
{
+
{
 
{
cells = {"col1", "col2", "col3", "col4", "col5"}
 
},
+
colspan = 2,
 
content = "spans 2 columns",
{
 
cells = {
+
},
{
+
{
colspan = 2,
+
colspan = 3,
content = "spans 2 columns",
+
content = "spans 3 columns",
},
 
{
 
colspan = 3,
 
content = "spans 3 columns",
 
},
 
 
},
 
},
 
},
 
},
 
{
 
{
cells = {
+
{
{
+
colspan = -1,
colspan = -1,
+
content = "spans all columns",
content = "spans all columns",
 
},
 
 
},
 
},
 
},
 
},
 
},
 
},
 
headers = {"col1", "col2", "col3", "col4", "col5"}
 
}
 
}
 
},
 
},
Line 573: Line 573:
 
hideEmptyColumns = true,
 
hideEmptyColumns = true,
 
rows = {
 
rows = {
{ "not empty", "empty", header = true},
+
{"row1", "", "row1"},
{ "row1", "" },
+
{"row2", "", "row2"},
{ "row2", nil },
+
},
  +
headers = {"not empty", "empty", "not empty"},
}
 
 
}
 
}
 
}
 
}

Revision as of 16:16, 15 August 2020

This module exports the following functions.

table

table(data)

Parameters

  • data
    A Lua table representing the desired wikitable.
    [sortable]
    If true renders a table that can be sorted by any of its columns.
    [hideEmptyColumns]
    If true, columns that are completely empty (except for header and footer rows) are omitted.
    [hideEmptyRows]
    If true, rows that are completely empty (except for header cells) are omitted.
    [styles]
    Key-value pairs of CSS properties to apply to the <table> element.
    [headers]
    An array of strings to serve as a header row.
    rows
    [header]
    If true renders all cells in row as <th> elements.
    [footer]
    If true renders all cells in row as <th> elements.
    [styles]
    Key-value pairs of CSS properties to apply to each cell in the row.
    [cells]
    [header]
    If true, renders the cell as a <th> element.
    [colspan]
    [rowspan]
    [styles]
    Key-value pairs of CSS properties to apply to the cell. Overrides any conflicting row styles.
    content
    stringrows
    Wikitext content for the cell.
    Subvidisions for the cell. Think of it as a table within a table.
    [header]
    If true renders all cells in row as <th> elements.
    [footer]
    If true renders all cells in row as <th> elements.
    [styles]
    Key-value pairs of CSS properties to apply to each cell in the row.
    [cells]

Returns

Examples

#InputResult
Basic table with header and footer
1
table({
  rows = {
    {
      header = true,
      cells = {"column1", "column2", "column3"},
    },
    {
      cells = {"cell1", "cell2", "cell3"},
    },
    {
      cells = {"cell4", "cell5", "cell6"},
    },
    {
      footer = true,
      cells = {"foot1", "foot2", "foot2"},
    },
  },
})
column1column2column3
cell1cell2cell3
cell4cell5cell6
foot1foot2foot2
Shorthand syntax
2
table({
  headers = {"column1", "column2", "column3"},
  rows = {
    {"cell1", "cell2", "cell3"},
  },
})
column1column2column3
cell1cell2cell3
Works with pipe characters
3
table({
  rows = {
    {"cell | 1", "cell |} 2", "cell {| 3"},
  },
})
cell | 1cell |} 2cell {| 3
Cell spanning multiple columns
4
table({
  headers = {"col1", "col2", "col3", "col4", "col5"},
  rows = {
    {
      {
        colspan = 2,
        content = "spans 2 columns",
      },
      {
        colspan = 3,
        content = "spans 3 columns",
      },
    },
    {
      {
        colspan = -1,
        content = "spans all columns",
      },
    },
  },
})
col1col2col3col4col5
spans 2 columnsspans 3 columns
spans all columns
Option to hide columns when they're completely empty except for headers/footers
5
table({
  hideEmptyRows = true,
  hideEmptyColumns = true,
  rows = {
    {"row1", "", "row1"},
    {"row2", "", "row2"},
  },
  headers = {"not empty", "empty", "not empty"},
})
not emptynot empty
row1row1
row2row2
Option to hide rows when they're completely empty except for headers
6
table({
  hideEmptyRows = true,
  rows = {
    {
      {
        header = true,
        content = "Header1",
      },
      "not empty",
    },
    {
      {
        header = true,
        content = "Header2",
      },
      "",
    },
    {},
  },
})
Header1not empty
Table styles are applied at the table level. Cell styles can be specified individually, or once for the entire row.
7
table({
  rows = {
    {"centered", "centered"},
    {
      cells = {
        { content = "left-aligned" },
        {
          content = "right-aligned",
          styles = { ["text-align"] = "right" },
        },
      },
      styles = { ["text-align"] = "left" },
    },
  },
  styles = {
    ["text-align"] = "center",
    width = "20em",
  },
})
centeredcentered
left-alignedright-aligned
Individual cells subdivided into multiple columns and rows
8
table({
  rows = {
    {
      header = true,
      cells = {"Column1", "Column2", "Column3"},
    },
    {
      "A",
      {
        {"B1"},
        {"B2"},
      },
      "C",
    },
    {
      {
        {"D1", "D2"},
      },
      {
        {"E1"},
        {
          {
            content = "E2",
            header = true,
          },
          "E3",
        },
      },
      {
        {"F1"},
        {"F2"},
        {"F3"},
      },
    },
    {
      {
        {"G1", "G2", "G3"},
      },
      "H",
      {
        {},
      },
    },
  },
  styles = { ["text-align"] = "center" },
})
Column1Column2Column3
AB1C
B2
D1D2E1F1
E2E3F2
F3
G1G2G3H

tabbedTable

tabbedTable(data)

Parameters

Returns

  • A series of wikitables displayed using tabs. Useful for representing data with three dimensions, or data with too many columns.

Examples

#InputResult
Tabbed table with shared headers and footers
9
tabbedTable({
  headerRows = {
    {"col1", "col2", "col3"},
  },
  tabs = {
    {
      label = "tab1",
      rows = {
        {"cell1", "cell2", "cell3"},
        {"cell4", "cell5", "cell6"},
      },
    },
    {
      label = "tab2",
      rows = {
        {"cell7", "cell8", "cell9"},
        {"cell10", "cell11", "cell12"},
      },
    },
  },
  footerRows = {
    {"foo12", "foot2", "foot3"},
  },
})
tab1tab2
col1col2col3
cell1cell2cell3
cell4cell5cell6
foo12foot2foot3
col1col2col3
cell7cell8cell9
cell10cell11cell12
foo12foot2foot3

local p = {}
local h = {}

local utilsFunction = require("Module:UtilsFunction")
local utilsLayout = require("Module:UtilsLayout/Tabs")
local utilsNumber = require("Module:UtilsNumber")
local utilsString = require("Module:UtilsString")
local utilsTable = require("Module:UtilsTable")

function p.table(data)
	data = h.resolveShorthand(data)
	if data.hideEmptyColumns then
		data = h.hideEmptyColumns(data)
	end
	if data.hideEmptyRows then
		data = h.hideEmptyRows(data)
	end
	data = h.splitCells(data)
	
	return h.createTable(data)
end
		
function p.tabbedTable(data)
	local tabs = {}
	local headerRows = data.headerRows or {}
	local footerRows = data.footerRows or {}
	for i, headerRow in ipairs(headerRows) do
		headerRow.header = true
	end
	for i, footerRow in ipairs(footerRows) do
		footerRow.footer = true
	end
	for i, tabData in ipairs(data.tabs) do
		local tabRows = utilsTable.concat(headerRows, tabData.rows, footerRows)
		table.insert(tabs, {
			label = tabData.label,
			content = p.table({ rows = tabRows })
		})
	end
	return utilsLayout.tabs(tabs)
end

function h.createTable(data)
	local html = mw.html.create("table"):addClass("wikitable")
	for _, row in ipairs(data.rows) do
		html:node(h.createRow(row))
	end
	
	local margins
	if data.align == "center" then
		margins = {
			margin = "0 auto"
		}
	elseif data.align == "right" then
		margins = {
			["margin-left"] = "auto"
		}
	end
	html:css(margins or {})
	html:css(data.styles or {})
	html:addClass(data.sortable and "sortable" or nil)
	
	return tostring(html)
end
function h.createRow(row)
	local html = mw.html.create("tr")
	for _, cell in ipairs(row.cells) do 
		local cellTag = (row.header or row.footer or cell.header or cell.footer) and "th" or "td"
		local colspan = cell.colspan
		local rowspan = cell.rowspan
		if colspan and colspan < 0 then
			colspan = 1000 
		end
		if rowspan and rowspan < 0 then
			rowspan = 1000
		end
		html:tag(cellTag)
			:attr("colspan", colspan)
			:attr("rowspan", rowspan)
			:css(cell.styles or {})
			:wikitext(mw.getCurrentFrame():preprocess(cell.content))
			:done()
	end
	return tostring(html)
end

function h.resolveShorthand(data)
	data = mw.clone(data)
	if data.headers then
		table.insert(data.rows, 1, {
			header = true,
			cells = data.headers
		})
	end
	for i, row in ipairs(data.rows) do
		row.cells = row.cells or utilsTable.ivalues(row)
		for j, cell in ipairs(row.cells) do
			local cell = h.resolveShorthandCell(cell)
			data.rows[i].cells[j] = cell
			if cell then
				cell.styles = utilsTable.merge(row.styles or {}, cell.styles or {})
			end
		end
		if utilsTable.isEmpty(data.rows[i]) then
			data.rows[i] = nil
		end
	end
	return data
end
function h.resolveShorthandCell(cell)
	if type(cell) == "string" or type(cell) == "number" or type(cell) == "boolean" then
		return {
			content = cell
		}
	end
	if type(cell) == "table" and utilsTable.isEmpty(cell) then
		return nil
	end
	if type(cell) == "table" and cell.content and not utilsTable.isArray(cell.content) then
		return cell
	end
	if not cell.content then
		cell = {
			content = utilsTable.ivalues(cell)
		}
	end
	if utilsTable.isArray(cell.content) then
		for i, subrow in ipairs(cell.content) do
			table.remove(cell, i)
			cell.content[i] = utilsTable.map(subrow, h.resolveShorthandCell)
		end
	end
	return cell
end

function h.hideEmptyRows(data)
	for i, row in ipairs(data.rows) do
		local isNonEmptyCell = function(cell) 
			return not cell.header and not h.isCellEmpty(cell)
		end
		local nonEmptyCells = utilsTable.filter(row.cells, isNonEmptyCell)
		if #nonEmptyCells == 0 then
			table.remove(data.rows, i)
		end
	end
	return data
end

function h.hideEmptyColumns(data)
	local totalColumns = h.countTotalColumns(data.rows)
	local emptyCellsPerColumn = {}
	for i, row in ipairs(data.rows) do
		for j, cell in ipairs(row.cells) do
			emptyCellsPerColumn[j] = emptyCellsPerColumn[j] or 0
			if (not cell.header and not cell.footer) and h.isCellEmpty(cell) then
				emptyCellsPerColumn[j] = emptyCellsPerColumn[j] + 1
			end
		end
		for i in utilsFunction.range(#row.cells + 1, totalColumns) do
			emptyCellsPerColumn[i] = emptyCellsPerColumn[i] + 1
		end
	end
	local data = mw.clone(data)
	local headerRows = utilsTable.filter(data.rows, "header")
	local footerRows = utilsTable.filter(data.rows, "footer")
	local totalRows = #data.rows - (math.max(#headerRows, #footerRows))
	for i, row in ipairs(data.rows) do
		for j, cell in ipairs(row.cells) do
			if emptyCellsPerColumn[j] == totalRows then
				row.cells[j] = nil
			end
		end
		row.cells = utilsTable.compact(row.cells)
	end
	return data
end
-- Cell is empty if:
-- its content is nil or the empty string
-- it has subdivisons where all the rows are empty
function h.isCellEmpty(cell)
	if type(cell.content) == "string" and utilsString.isBlank(cell.content) then
		return true
	end
	if type(cell.content) == "table" then
		for _, subRow in ipairs(cell.content) do
			for _, subCell in ipairs(subRow) do
				if not utilsString.isBlank(cell.subCell) then
					return true
				end
			end
		end
	end
	return false
end
function h.countTotalColumns(rows)
	local maxColumns = 0
	for _, row in ipairs(rows) do
		maxColumns = math.max(maxColumns, #row.cells)
	end
	return maxColumns
end

function h.splitCells(data)
	data = h.normalize(data)
	data = h.splitRows(data)
	data = h.applyColspans(data)
	data = h.flattenCellGroups(data)
	return data
end

function h.normalize(data)
	for i, row in ipairs(data.rows) do
		for j, cell in ipairs(row.cells) do
			data.rows[i].cells[j] = h.normalizeCell(cell)
		end
	end
	return data
end
function h.normalizeCell(cell)
	if utilsTable.isArray(cell.content) then
		return cell.content
	end
	return {{cell}}
end

function h.splitRows(data)
	data.rows = utilsTable.flatMap(data.rows, h.splitRow)
	return data
end
function h.splitRow(row)
	local splitRows = utilsTable.zip(row.cells, {})
	local cellSubrows = utilsTable.zip(splitRows)
	for i, subrows in ipairs(cellSubrows) do
		local isNotEmpty = utilsFunction.negate(utilsTable.isEmpty)
		local lastSubrow, lastSubrowIndex = utilsTable.findLast(subrows, isNotEmpty)
		if lastSubrow then
			local rowspan = #subrows - lastSubrowIndex + 1
			h.applyRowspan(lastSubrow, rowspan)
		end
	end
	local rows = {}
	for i, splitRow in ipairs(splitRows) do
		rows[i] = {
			header = row.header,
			footer = row.footer,
			row = row.styles,
			cells = splitRow,
		}
	end
	return rows
end
function h.applyRowspan(cellGroup, rowspan)
	if rowspan <= 1 or not cellGroup then
		return
	end
	for i, cell in ipairs(cellGroup) do
		if cell.rowspan and cell.rowspan > 1 then
			cell.rowspan = rowspan + cell.rowspan
		else
			cell.rowspan = rowspan
		end
	end
end

function h.applyColspans(data)
	local colspansPerColumn = h.getColspansForEachColumn(data)
	for i, row in ipairs(data.rows) do
		for j, cellGroup in ipairs(row.cells) do
			h.applyColspan(cellGroup, colspansPerColumn[j])
		end
	end
	return data
end
function h.getColspansForEachColumn(data)
	return utilsFunction.pipe(data.rows) { 
		utilsTable._map("cells"),
		utilsTable._map(
			utilsTable._map(utilsTable.size)
		),
		utilsTable.zip,
		utilsTable._map(utilsTable._padNils(utilsNumber.MIN)),
		utilsTable._map(utilsTable.max),
	}
end
function h.applyColspan(cellGroup, colspan)
	if #cellGroup > 0 and #cellGroup < colspan then
		cellGroup[#cellGroup].colspan = colspan - #cellGroup + 1
	end
end

function h.flattenCellGroups(data)
	for i, row in ipairs(data.rows) do
		row.cells = utilsTable.flatten(row.cells)
	end
	return data
end

p.Schemas = {
	table = {
		data = {
			definitions = {
				rowOptions = {
					type = "record",
					properties = {
						{
							name = "header",
							type = "boolean",
							desc = "If <code>true</code> renders all cells in row as <code><nowiki><th></nowiki></code> elements.",
						},
						{
							name = "footer",
							type = "boolean",
							desc = "If <code>true</code> renders all cells in row as <code><nowiki><th></nowiki></code> elements.",
						},
						{
							name = "styles",
							type = "map",
							keys = { type = "string" },
							values = { type = "string" },
							desc = "Key-value pairs of CSS properties to apply to each cell in the row.",
						},
					},
				},
				rows = {
					type = "array",
					items = { _ref = "#/definitions/row" },
				},
				row = {
					allOf = {
						{ _ref = "#/definitions/rowOptions" },
						{
							type = "record",
							properties = {
								{
									name = "cells",
									type = "array",
									items = { _ref = "#/definitions/cell" }
								},
							},
						}
					}
				},
				cells = {
					type = "record",
					properties = {
						{
							name = "cells",
							type = "array",
							items = { _ref = "#/definitions/cell" }
						},
					},
				},
				cell = {
					oneOf = {
						{
							type = "record",
							properties = {
								{
									name = "header",
									type = "boolean",
									desc = "If <code>true</code>, renders the cell as a <code><nowiki><th></nowiki></code> element.",
								},
								{
									name = "colspan",
									type = "number",
								},
								{
									name = "rowspan",
									type = "number",
								},
								{
									name = "styles",
									type = "map",
									keys = { type = "string" },
									values = { type = "string" },
									desc = "Key-value pairs of CSS properties to apply to the cell. Overrides any conflicting row styles.",
								},
								{
									name = "content",
									required = true,
									oneOf = {
										{
											type = "string",
											desc = "Wikitext content for the cell.",
										},
										{
											type = "array",
											_ref = "#/definitions/rows",
											desc = "Subvidisions for the cell. Think of it as a table within a table."
										}
									}
								}
							},
						}
					}
				},
			},
			
			type = "record",
			required = true,
			desc = "A Lua table representing the desired wikitable.",
			properties = {
				{
					name = "sortable",
					type = "boolean",
					desc = "If <code>true</code> renders a table that can be sorted by any of its columns."
				},
				{
					name = "hideEmptyColumns",
					type = "boolean",
					desc = "If <code>true</code>, columns that are completely empty (except for header and footer rows) are omitted.",
				},
				{
					name = "hideEmptyRows",
					type = "boolean",
					desc = "If <code>true</code>, rows that are completely empty (except for header cells) are omitted.",
				},
				{
					name = "styles",
					type = "map",
					keys = { type = "string" },
					values = { type = "string" },
					desc = "Key-value pairs of CSS properties to apply to the <code><nowiki><table></nowiki></code> element.",
				},
				{
					name = "headers",
					type = "array",
					items = { type = "string" },
					desc = "An array of strings to serve as a header row.",
				},
				{
					name = "rows",
					required = true,
					type = "array",
					items = {
						allOf = {
							{ _ref = "#/definitions/rowOptions" },
							{ _ref = "#/definitions/cells" },
						}
					},
				},
			},
		}
	},
	tabbedTable = {
		data = {
			type = "record",
			required = true,
			properties = {
				{
					name = "headerRows",
					type = "any",
					typeLabel = "rows",
					desc = "Header rows common to every tab.",
				},
				{
					name = "headerRows",
					type = "any",
					typeLabel = "rows",
					desc = "Footer rows common to every tab.",
				},
				{
					name = "tabs",
					required = true,
					allOf = {
						{
							type = "record",
							properties = {
								{
									name = "label",
									type = "string",
									required = true,
									desc = "Tab label",
								},
								{
									name = "rows",
									type = "any",
									required = true,
									typeLabel = "rows",
									desc = "Table rows for the tab. See {{Sect|table}} for format.",
								}
							},
						},
						
					}
				}
			}
		},
	},
}

p.Documentation = {
	table = {
		params = {"data"},
		returns = "Wikitext for the table using {{mediawiki|Help:Table#Other table syntax|XHTML syntax}}.",
		cases = {
			resultOnly = true,
			{
				desc = "Basic table with header and footer",
				args = {
					{
						rows = {
							{
								header = true,
								cells = { 'column1', 'column2', 'column3'},
							},
							{
								cells = {'cell1', 'cell2', 'cell3'},
							},
							{
								cells = {'cell4', 'cell5', 'cell6'},
							},
							{
								footer = true,
								cells = {'foot1', 'foot2', 'foot2'},
							},
						},
					},
				},
			},
			{
				desc = "Shorthand syntax",
				args = {
					{
						rows = {
							{'cell1', 'cell2', 'cell3'}
						},
						headers = {'column1', 'column2', 'column3'},
					},
				},
			},
			{
				desc = "Works with pipe characters",
				args = {
					{
						rows = {
							{"cell | 1", "cell |} 2", "cell {| 3"},
						},
					}
				},
			},
			{
				desc = "Cell spanning multiple columns",
				args = {
					{
						rows = {
							{ 
								{
									colspan = 2,
									content = "spans 2 columns",
								},
								{
									colspan = 3,
									content = "spans 3 columns",
								},
							},
							{
								{
									colspan = -1,
									content = "spans all columns",
								},
							},
						},
						headers = {"col1", "col2", "col3", "col4", "col5"}
					}
				},
			},
			{
				desc = "Option to hide columns when they're completely empty except for headers/footers",
				args = {
					{
						hideEmptyRows = true,
						hideEmptyColumns = true,
						rows = {
							{"row1", "", "row1"},
							{"row2", "", "row2"},
						},
						headers = {"not empty", "empty", "not empty"},
					}
				}
			},
			{
				desc = "Option to hide rows when they're completely empty except for headers",
				args = {
					{
						hideEmptyRows = true,
						rows = {
							{ { header = true, content = "Header1"}, "not empty"},
							{ { header = true, content = "Header2" }, "" },
							{ },
						}
					}
				}
			},
			{
				desc = "Table styles are applied at the table level. Cell styles can be specified individually, or once for the entire row.",
				args = {
					{
						styles = {
							["width"] = "20em",
							["text-align"] = "center",
						},
						rows = {
							{ "centered", "centered" },
							{
								styles = {
									["text-align"] = "left"
								},
								cells = {
									{ content = "left-aligned" },
									{
										content = "right-aligned",
										styles = {
											["text-align"] = "right",
										}
									},
								},
							},
						},
					},
				},
			},
			{
				desc = "Individual cells subdivided into multiple columns and rows",
				args = {
					{
						styles = { ["text-align"] = "center" },
						rows = {
							{ 
								header = true,
								cells = {"Column1", "Column2", "Column3"},
							},
							{
								"A",
								{ 
									{"B1"},
									{"B2"},
								},
								"C"
							},
							{
								{
									{"D1", "D2"}
								},
								{
									{"E1"},
									{{ content = "E2", header = true }, "E3"},
								},
								{
									{"F1"},
									{"F2"},
									{"F3"},
								}
							},
							{
								{
									{ "G1", "G2", "G3"}
								},
								"H",
								{{}},
							}
						},
					},
				},
			}
		}
	},
	tabbedTable = {
		params = {"data"},
		returns = "A series of wikitables displayed using tabs. Useful for representing data with three dimensions, or data with too many columns.",
		cases = {
			resultOnly = true,
			{
				desc = "Tabbed table with shared headers and footers",
				args = {
					{
						headerRows = {
							{"col1", "col2", "col3"}
						},
						footerRows = {
							{"foo12", "foot2", "foot3"}
						},
						tabs = {
							{
								label = "tab1",
								rows = {
									{'cell1', 'cell2', 'cell3'},
									{'cell4', 'cell5', 'cell6'},
								}
							},
							{
								label = "tab2",
								rows = {
									{'cell7', 'cell8', 'cell9'},
									{'cell10', 'cell11', 'cell12'},
								},
							},
						},
					},
				},
			},
		},
	},
}
		
return p