--- Example usage: ---  --- =p._externalData({datapage="COVID-19 cases in Santa Clara County, California.tab",datarecoveries="hospitalized",datacases="totalConfirmedCases"})  local p = {} local lang = mw.getContentLanguage() local english = mw.getLanguage("en")  local function round(x) 	return (math.modf(x + (x < 0 and -0.5 or 0.5))) end  local function formatChange(previous, current) 	if not previous or previous == 0 then 		return 	end 	if previous == current then 		return "=" 	end 	 	local change = current / previous * 100 - 100 	local sign = change < 0 and "−" or "+" 	return mw.ustring.format("%s%s%%", sign, lang:formatNum(round(math.abs(change)))) end  function p._externalData(args) 	local data = mw.ext.data.get(args.datapage) 	 	local dateIndex 	local deathsIndex 	local recoveriesIndex 	local casesIndex 	local class4Index 	local class5Index 	for i, field in ipairs(data.schema.fields) do 		if field.name == "date" or field.name == args.datadate then 			dateIndex = i 		elseif field.name == "deaths" or field.name == args.datadeaths then 			deathsIndex = i 		elseif field.name == "recoveries" or field.name == args.datarecoveries then 			recoveriesIndex = i 		elseif field.name == "cases" or field.name == args.datacases then 			casesIndex = i 		elseif field.name == "class4" or field.name == args.dataclass4 then 			class4Index = i 		elseif field.name == "class5" or field.name == args.dataclass5 then 			class5Index = i 		end 	end 	assert(dateIndex, "Date field not found.") 	assert(deathsIndex or not args.datadeaths, "Deaths field not found.") 	assert(recoveriesIndex or not args.datarecoveries, "Recoveries field not found.") 	assert(casesIndex or not args.datacases, "Cases field not found.") 	assert(class4Index or not args.dataclass4, "Class 4 field not found.") 	assert(class5Index or not args.dataclass5, "Class 5 field not found.") 	 	-- Restructure the data as tables with keys. 	local records = {} 	for i, row in ipairs(data.data) do 		local record = { 			date = row[dateIndex], 			deaths = deathsIndex and row[deathsIndex], 			recoveries = recoveriesIndex and row[recoveriesIndex], 			cases = casesIndex and row[casesIndex], 			class4 = class4Index and row[class4Index], 			class5 = class5Index and row[class5Index], 			options = {}, 			streak = 1, 		} 		local prevRecord = records[#records] or {} 		if casesIndex and not prevRecord.cases and record.cases and record.cases > 0 then 			record.options.firstright1 = "y" 		end 		if deathsIndex and prevRecord.deaths == 0 and record.deaths and record.deaths > 0 then 			record.options.firstright2 = "y" 		end 		if deathsIndex and (prevRecord.deaths or prevRecord.assumedDeaths) and not record.deaths then 			record.assumedDeaths = prevRecord.deaths or prevRecord.assumedDeaths 		end 		if casesIndex and (prevRecord.cases or prevRecord.assumedCases) and not record.cases then 			record.assumedCases = prevRecord.cases or prevRecord.assumedCases 		end 		if record.deaths == prevRecord.deaths 			and record.recoveries == prevRecord.recoveries 			and record.cases == prevRecord.cases 			and record.class4 == prevRecord.class4 			and record.class5 == prevRecord.class5 then 			record.streak = prevRecord.streak + 1 		end 		table.insert(records, record) 	end 	 	-- Collapse streaks of identical data. 	for i = #records, 1, -1 do 		local record = records[i] 		if record.streak > 3 then 			for j = i, i - record.streak + 3, -1 do 				table.remove(records, j) 			end 			i = i - record.streak + 2 			record = records[i] 			record.options.collapse = "y" 			record.options.id = english:formatDate("M", record.date):lower() 			record.date = nil 		end 	end 	 	-- Stringify the data. 	local rows = {} 	for i, record in ipairs(records) do 		local prevRecord = records[i - 1] or {} 		local row = { 			record.date or "", 			tostring(record.deaths or record.assumedDeaths or ""), 			tostring(record.recoveries or ""), 			tostring(record.cases or record.assumedCases or ""), 			tostring(record.class4 or ""), 			tostring(record.class5 or ""), 			record.cases and lang:formatNum(record.cases) or "", 			record.cases and formatChange(prevRecord.cases or prevRecord.assumedCases, record.cases) or "", 			record.deaths and lang:formatNum(record.deaths) or "", 			record.deaths and formatChange(prevRecord.deaths or prevRecord.assumedDeaths, record.deaths) or "", 		} 		for k, v in pairs(record.options) do 			table.insert(row, string.format("%s=%s", k, v)) 		end 		table.insert(rows, table.concat(row, ";")) 	end 	return table.concat(rows, "\n") end  function p.externalData(frame) 	local args = {} 	for k,v in pairs(frame.args) do 		if (v or ) ~=  then 			args['data'..k] = v 		end 	end 	return p._externalData(args) end return p