Module:UtilsLayout/Table

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 defaultClass = "wikitable" if data.sortable then defaultClass = "wikitable sortable" end local html = mw.html.create("table"):addClass(data.class or defaultClass) if data.caption then html:tag("caption"):wikitext(data.caption) end 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 {}) 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 local cellElement = html:tag(cellTag) :attr("colspan", colspan) :attr("rowspan", rowspan) :css(cell.styles or {}) :wikitext(mw.getCurrentFrame:preprocess(cell.content)) if cell.class then cellElement:addClass(cell.class) end if cell.sortValue then cellElement:attr("data-sort-value", cell.sortValue) end 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.isEmpty(cell.content) then return true end if type(cell.content) == "table" then if #utilsTable.keys(cell.content) == 0 then return true end for _, subRow in ipairs(cell.content) do			for _, subCell in ipairs(subRow) do				if not utilsString.isEmpty(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 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 return p