local p = {} local lang = mw.getContentLanguage() local navbar = require("Module:Navbar")  local messages = { 	["true"] = "Yes", 	["false"] = "No", 	null = "N/A", }  local bgColors = { 	["true"] = "#9f9", 	["false"] = "#f99", 	null = "#ececec", }  local colors = { 	null = "#2c2c2c", }  function p._cell(args) 	local data = args.data or mw.ext.data.get(args[1]) 	local rowIdx = tonumber(args.output_row) 	local outputFormat = args.output_format 	 	local outputColumnNames = { 		args.output_column1 or args.output_column, 	} 	while args["output_column" .. #outputColumnNames + 1] do 		table.insert(outputColumnNames, args["output_column" .. #outputColumnNames + 1]) 	end 	 	local outputColumnIdxs = {} 	local numOutputColumnIdxs = 0 	for i, field in ipairs(data.schema.fields) do 		for j, outputColumnName in ipairs(outputColumnNames) do 			if field.name == outputColumnName then 				outputColumnIdxs[outputColumnName] = i 				numOutputColumnIdxs = numOutputColumnIdxs + 1 			end 		end 		if numOutputColumnIdxs == #outputColumnNames then 			break 		end 	end 	if numOutputColumnIdxs < #outputColumnNames then 		for i, outputColumnName in ipairs(outputColumnNames) do 			assert(outputColumnIdxs[outputColumnName], 				mw.ustring.format("Output column “%s” not found.", outputColumnName)) 		end 	end 	 	if rowIdx > 0 then 		rowIdx = (rowIdx - 1) % #data.data + 1 	elseif rowIdx < 0 then 		rowIdx = rowIdx % #data.data + 1 	else 		error("0 is not a valid row index.") 	end 	 	local record = data.data[rowIdx] 	if record ~= nil then 		if outputFormat or numOutputColumnIdxs > 1 then 			local values = {} 			for i, columnName in ipairs(outputColumnNames) do 				local columnIdx = outputColumnIdxs[columnName] 				table.insert(values, record[columnIdx]) 			end 			if outputFormat then 				return mw.ustring.format(outputFormat, unpack(values)) 			else 				return mw.text.listToText(values) 			end 		else 			local columnIdx = outputColumnIdxs[outputColumnNames[1]] 			return record[columnIdx] 		end 	end end  --- Returns the value of the cell at the given row index and column name. --- A row index of 1 refers to the first row in the table. A row index of -1 --- refers to the last row in the table. It is an error to specify a row index --- of 0. --- Usage:  function p.cell(frame) 	return p._cell(frame.args) end  function p._lookup(args) 	local data = args.data or mw.ext.data.get(args[1]) 	local searchValue = args.search_value 	local searchPattern = args.search_pattern 	local searchColumnName = args.search_column 	 	local searchColumnIdx 	for i, field in ipairs(data.schema.fields) do 		if field.name == searchColumnName then 			searchColumnIdx = i 		end 		if searchColumnIdx then 			break 		end 	end 	assert(searchColumnIdx, mw.ustring.format("Search column “%s” not found.", searchColumnName)) 	 	local occurrence = tonumber(args.occurrence) or 1 	 	local numMatchingRecords = 0 	for i = (occurrence < 0 and #data.data or 1), 		(occurrence < 0 and 1 or #data.data), 		(occurrence < 0 and -1 or 1) do 		local record = data.data[i] 		if (searchValue and record[searchColumnIdx] == searchValue) or 			(searchPattern and mw.ustring.match(tostring(record[searchColumnIdx]), searchPattern)) then 			numMatchingRecords = numMatchingRecords + 1 			if numMatchingRecords == math.abs(occurrence) then 				local args = mw.clone(args) 				args.data = data 				args.output_row = i 				return p._cell(args) 			end 		end 	end end  --- Returns the value of the cell(s) in the given output column(s) of the row --- matching the search key and column. --- Reminiscent of LOOKUP() macros in popular spreadsheet applications, except --- that the search key must match exactly. (On the other hand, this means the --- table does not need to be sorted.) --- Usage:  function p.lookup(frame) 	return p._lookup(frame.args) end  function p._wikitable(args) 	local pageName = args[1] 	local data = mw.ext.data.get(pageName) 	 	local datatypes = {} 	 	local htmlTable = mw.html.create("table") 		:addClass("wikitable sortable") 	htmlTable 		:tag("caption") 		:wikitext(navbar.navbar({ 			template = ":c:Data:" .. pageName, 			mini = "y", 			style = "float: right;", 			"view", "edit", 		})) 		:wikitext(data.description) 	 	local headerRow = htmlTable 		:tag("tr") 	for i, field in ipairs(data.schema.fields) do 		headerRow 			:tag("th") 			:attr("scope", "col") 			:attr("data-sort-type", datatypes[j] == "text" and "string" or datatypes[j]) 			:wikitext(field.title) 		datatypes[i] = field.type 	end 	 	for i, record in ipairs(data.data) do 		local row = htmlTable:tag("tr") 		for j = 1, #data.schema.fields do 			local cell = row:tag("td") 			if record[j] then 				local formattedData = record[j] 				if datatypes[j] == "number" then 					formattedData = lang:formatNum(formattedData) 					cell:attr("align", "right") 				elseif datatypes[j] == "boolean" then 					cell 						:addClass(record[j] and "table-yes" or "table-no") 						:css({ 							background = record[j] and bgColors["true"] or bgColors["false"], 							color = record[j] and colors["true"] or colors["false"], 							["vertical-align"] = "middle", 							["text-align"] = "center", 						}) 						:wikitext(record[j] and messages["true"] or messages["false"]) 				end 				cell:wikitext(formattedData) 			else 				cell 					:addClass("mw-tabular-value-null") 					:addClass("table-na") 					:css({ 						background = bgColors.null, 						color = colors.null, 						["vertical-align"] = "middle", 						["text-align"] = "center", 					}) 					:wikitext(messages.null) 			end 		end 	end 	 	local footer = htmlTable 		:tag("tr") 		:tag("td") 		:addClass("sortbottom") 		:attr("colspan", #data.schema.fields) 	footer:wikitext(data.sources) 	footer:tag("br") 	 	local licenseText = mw.message.new("Jsonconfig-license", 		mw.ustring.format("[%s %s]", data.license.url, data.license.text)) 	footer 		:tag("i") 		:wikitext(tostring(licenseText)) 	 	return htmlTable end  --- Returns a tabular data page as a wikitext table. --- Usage:  function p.wikitable(frame) 	return p._wikitable(frame.args) end  return p