Difference between revisions of "Module:Election results"

From blackwiki
Jump to navigation Jump to search
blackwiki>Number 57
(Undid revision 969336758 by Number 57 (talk))
m (52 revisions imported)
 
(20 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 +
require('Module:No globals')
 
local p = {}
 
local p = {}
 +
 
function p.main(frame)
 
function p.main(frame)
 
local args = require('Module:Arguments').getArgs(frame)
 
local args = require('Module:Arguments').getArgs(frame)
local index, cols = {}, 3
+
local index, headings = {}, {}
local party, vp = false, false
+
local cols, rounds = 0, 1
local winner, winner_votes = 0, 0
+
local winner, winner_votes = {0, 0}, {0, 0}
local valid, invalid, electorate = 0, tonumber(args.invalid) or 0, tonumber(args.electorate) or 0
+
local valid = {0, 0}
for i = 1, 20 do
+
local invalid = {tonumber(args.invalid) or 0, tonumber(args.invalid2) or 0}
if args['cand' .. i] then
+
local electorate = {tonumber(args.electorate) or 0, tonumber(args.electorate2) or tonumber(args.electorate) or 0}
table.insert(index, i)
+
local seats, seatdiff = 0, 0
if not party and args['party' .. i] then
+
local row, secondrow
party = true
+
local tracking = ''
cols = cols + 2
+
local max_rows = 0
 +
 +
-- helper functions
 +
local lang = mw.getContentLanguage()
 +
local function fmt(n)
 +
return n and tonumber(n) and lang:formatNum(tonumber(n)) or nil
 +
end
 +
local function pct(n,d)
 +
n, d = tonumber(n), tonumber(d)
 +
if n and d and d > 0 then
 +
return string.format('%.2f', n / d * 100)
 +
end
 +
return '–'
 +
end
 +
local function tonumdash(s)
 +
if s then
 +
s = mw.ustring.gsub(s, '&[MmNn][Dd][Aa][Ss][Hh];', '-')
 +
s = mw.ustring.gsub(s, '&[Mm][Ii][Nn][Uu][Ss];', '-')
 +
s = mw.ustring.gsub(s, '[—–−]', '-')
 +
return tonumber(s) or 0
 +
end
 +
end
 +
local function unlink(s)
 +
if s then
 +
s = s:match("^[^%[]-%[%[([^%]]-)|[^%]]-%]%].*$") or s
 +
s = s:match("^[^%[]-%[%[([^%]]-)%]%].*$") or s
 +
end
 +
return s
 +
end
 +
 
 +
-- preprocess the input
 +
local stop_flag = false
 +
local i = 0
 +
while stop_flag == false do
 +
stop_flag = true
 +
for kk = 1, 20 do
 +
i = i + 1
 +
for k, key in ipairs({'cand', 'vp', 'party', 'sc', 'seats'}) do
 +
if args[key .. i] then
 +
headings[key] = true
 +
stop_flag = false
 +
max_rows = i > max_rows and i or max_rows
 +
end
 
end
 
end
if not vp and args['vp' .. i] then
+
if args['row' .. i] then
vp = true
+
stop_flag = false
cols = cols + 1
+
max_rows = i > max_rows and i or max_rows
 
end
 
end
if args['votes' .. i] then
+
 
args['votes' .. i] = tonumber(args['votes' .. i])
+
if args['row' .. i] or args['cand' .. i] or args['party' .. i] then
if args['votes' .. i] > winner_votes then
+
table.insert(index, i)
winner = i
+
if args['votes' .. i] then
winner_votes = args['votes' .. i]
+
local votesi = tonumber(args['votes' .. i]) or 0
 +
args['votes' .. i] = votesi
 +
if votesi > winner_votes[1] then
 +
winner[1] = i
 +
winner_votes[1] = votesi
 +
end
 +
valid[1] = valid[1] + votesi
 +
end
 +
if args['votes' .. i .. '_2'] then
 +
rounds = 2
 +
local votesi = tonumber(args['votes' .. i .. '_2']) or 0
 +
args['votes' .. i .. '_2'] = votesi
 +
if votesi > winner_votes[2] then
 +
winner[2] = i
 +
winner_votes[2] = votesi
 +
end
 +
valid[2] = valid[2] + votesi
 +
end
 +
if args['seats' .. i] then
 +
seats = seats + (tonumber(args['seats' .. i]) or 0)
 +
end
 +
if args['sc' .. i] then
 +
seatdiff = seatdiff + tonumdash(args['sc' .. i])
 
end
 
end
valid = valid + args['votes' .. i]
 
 
end
 
end
 
end
 
end
 
end
 
end
 +
 +
-- optional against all
 +
if args['against_all'] or args['against_all2'] then
 +
max_rows = max_rows + 1
 +
local i = max_rows
 +
table.insert(index, i)
 +
args['votes' .. i] = tonumber(args['against_all']) or 0
 +
args['votes' .. i .. '_2'] = tonumber(args['against_all_2']) or 0
 +
args['colour' .. i] = 'inherit'
 +
args['color' .. i] = 'inherit'
 +
args['row' .. i] = 'Against all'
 +
valid[1] = valid[1] + args['votes' .. i]
 +
valid[2] = valid[2] + args['votes' .. i .. '_2']
 +
tracking = tracking .. '[[Category:Pages using election results with against all]]'
 +
end
 +
 +
local ovalid = {valid[1], valid[2]}
 +
seats = ((args['total_seats'] or '') == 'TOTAL' and seats) or args['total_seats'] or seats
 +
seatdiff = (args['total_sc'] or '') == 'TOTAL' and seatdiff or args['total_sc']
 +
if seats or seatdiff or args['valid'] or ((rounds > 1) and args['valid2']) then
 +
max_rows = max_rows + 1
 +
local i = max_rows
 +
table.insert(index,i)
 +
args['votes' .. i] = valid[1]
 +
args['votes' .. i .. '_2'] = valid[2]
 +
args['colour' .. i] = 'inherit'
 +
args['color' .. i] = 'inherit'
 +
args['row' .. i] = 'Total'
 +
args['seats' .. i] = seats
 +
args['sc' .. i] = seatdiff
 +
args['font-weight' .. i] = 'bold'
 +
args['class' .. i] = 'sortbottom'
 +
ovalid[1] = tonumber(args['valid']) or valid[1]
 +
ovalid[2] = tonumber(args['valid2']) or valid[2]
 +
end
 +
 +
-- build the table
 
local root = mw.html.create('table')
 
local root = mw.html.create('table')
 
root
 
root
Line 32: Line 134:
 
:tag('caption')
 
:tag('caption')
 
:wikitext(args.caption)
 
:wikitext(args.caption)
local row = mw.html.create('tr')
+
if vp then
+
local topcell = nil
 +
if args['image'] then
 +
topcell = root
 +
:tag('tr')
 +
:tag('td')
 +
:wikitext(args['image'])
 +
:css('text-align', 'center')
 +
end
 +
 +
local rowspan = (rounds > 1) and 2 or nil
 +
row = root:tag('tr')
 +
if headings['cand'] then
 +
row
 +
:tag('th')
 +
:wikitext('Candidate')
 +
:attr('scope', 'col')
 +
:attr('colspan', 2)
 +
:attr('rowspan', rowspan)
 +
cols = cols + 2
 +
if headings['vp'] then
 +
row
 +
:tag('th')
 +
:wikitext('Running mate')
 +
:attr('scope', 'col')
 +
:attr('rowspan', rowspan)
 +
cols = cols + 1
 +
end
 +
if headings['party'] then
 +
row
 +
:tag('th')
 +
:wikitext('Party')
 +
:attr('scope', 'col')
 +
:attr('rowspan', rowspan)
 +
cols = cols + 1
 +
end
 +
else
 +
row
 +
:tag('th')
 +
:wikitext(headings['party'] and 'Party' or '')
 +
:attr('scope', 'col')
 +
:attr('colspan', 2)
 +
:attr('rowspan', rowspan)
 +
cols = cols + 2
 +
end
 +
if rounds > 1 then
 
row
 
row
 
:tag('th')
 
:tag('th')
:wikitext('President')
+
:wikitext('First round')
 +
:attr('scope', 'col')
 
:attr('colspan', 2)
 
:attr('colspan', 2)
 
:tag('th')
 
:tag('th')
:wikitext('Vice president')
+
:wikitext('Second round')
 +
:attr('scope', 'col')
 +
:attr('colspan', 2)
 +
secondrow = root:tag('tr')
 
else
 
else
 +
secondrow = row
 +
end
 +
for k=1,rounds do
 +
secondrow
 +
:tag('th')
 +
:wikitext('Votes')
 +
:attr('scope', 'col')
 +
:tag('th')
 +
:wikitext('%')
 +
:attr('scope', 'col')
 +
cols = cols + 2
 +
end
 +
if headings['seats'] then
 
row
 
row
 
:tag('th')
 
:tag('th')
:wikitext('Candidate')
+
:wikitext('Seats')
:attr('colspan', 2)
+
:attr('scope', 'col')
 +
:attr('rowspan', rowspan)
 +
cols = cols + 1
 
end
 
end
if party then
+
if headings['sc'] then
 
row
 
row
 
:tag('th')
 
:tag('th')
:wikitext('Party')
+
:wikitext(headings['seats'] and '+/–' or 'Seats±')
 +
:attr('scope', 'col')
 +
:attr('rowspan', rowspan)
 +
cols = cols + 1
 
end
 
end
row
+
 
:tag('th')
+
if topcell then
:wikitext('Votes')
+
topcell:attr('colspan', cols)
:tag('th')
 
:wikitext('%')
 
root:node(row)
 
local lang = mw.getContentLanguage()
 
local function fmt(n)
 
return lang:formatNum(n)
 
 
end
 
end
 +
 +
local cs = cols - 2*rounds
 +
- (headings['seats'] and 1 or 0)
 +
- (headings['sc'] and 1 or 0)
 +
local rsuff = (rounds > 1) and {'', '_2'} or {''}
 
for i, v in ipairs(index) do
 
for i, v in ipairs(index) do
local row = mw.html.create('tr')
+
row = root:tag('tr')
row
+
:addClass(args['class' .. v])
:tag('td')
+
:css('font-weight', args['font-weight' .. v])
:css('background-color', frame:expandTemplate{title = args['party' .. v] .. '/meta/color'})
+
 
if vp then
+
-- determine the color
row
+
local color = args['colour' .. v] or args['color' .. v] or nil
:tag('td')
+
if color == nil then
:wikitext(string.format('\[\[%s\]\]', args['cand' .. v]))
+
local party = unlink(args['party' .. v]) or ''
 +
if party ~= '' and mw.title.new('Template:' .. party .. '/meta/color').exists then
 +
color = frame:expandTemplate{title = party .. '/meta/color'}
 +
end
 +
end
 +
 
 +
if args['row' .. v] then
 
row
 
row
 
:tag('td')
 
:tag('td')
:wikitext(string.format('\[\[%s\]\]', args['vp' .. v]))
+
:wikitext(args['row' .. v])
 +
:attr('colspan', cs)
 
else
 
else
 +
-- create the empty color cell
 
row
 
row
 
:tag('td')
 
:tag('td')
:wikitext(string.format('\[\[%s\]\]', args['cand' .. v]))
+
:css('background-color', color)
 +
 
 +
-- add the rest of the row
 +
if headings['cand'] then
 +
row
 +
:tag('td')
 +
:wikitext(args['cand' .. v])
 +
if headings['vp'] then
 +
row
 +
:tag('td')
 +
:wikitext(args['vp' .. v])
 +
end
 +
end
 +
if headings['party'] then
 +
row
 +
:tag('td')
 +
:wikitext(args['party' .. v])
 +
end
 +
end
 +
for kk, suf in ipairs(rsuff) do
 +
if(args['votes' .. v .. suf]) then
 +
row:tag('td')
 +
:css('text-align', 'right')
 +
:wikitext(fmt(args['votes' .. v .. suf]))
 +
row:tag('td')
 +
:css('text-align', 'right')
 +
:wikitext(pct(args['votes' .. v .. suf], valid[kk]))
 +
else
 +
row:tag('td'):attr('colspan', 2)
 +
end
 
end
 
end
if args['party_name' .. v] then
+
if headings['seats'] then
 
row
 
row
 
:tag('td')
 
:tag('td')
:wikitext(string.format('\[\[%s\|%s\]\]', args['party' .. v], args['party_name' .. v]))
+
:css('text-align', 'right')
else
+
:wikitext(fmt(args['seats' .. v]))
 +
end
 +
if headings['sc'] then
 
row
 
row
 
:tag('td')
 
:tag('td')
:wikitext(string.format('\[\[%s\]\]', args['party' .. v]))
+
:css('text-align', 'right')
end
+
:wikitext(args['sc' .. v])
local cell = mw.html.create('td')
 
cell
 
:css('text-align', 'right')
 
if args['votes' .. v] then
 
cell
 
:wikitext(fmt(args['votes' .. v]))
 
end
 
if v == winner then
 
cell
 
:css('font-weight', 'bold')
 
end
 
row:node(cell)
 
local cell = mw.html.create('td')
 
cell
 
:css('text-align', 'right')
 
if args['votes' .. v] then
 
cell
 
:wikitext(string.format('%.2f%%', args['votes' .. v] / valid * 100))
 
end
 
if v == winner then
 
cell
 
:css('font-weight', 'bold')
 
 
end
 
end
row:node(cell)
 
root:node(row)
 
 
end
 
end
root
+
-- separating line
 +
row = root
 
:tag('tr')
 
:tag('tr')
 
:addClass('sortbottom')
 
:addClass('sortbottom')
 +
row
 +
:tag('td')
 
:css('background', '#eaecf0')
 
:css('background', '#eaecf0')
 +
:attr('colspan', cols)
 +
-- valid votes
 +
row = root
 +
:tag('tr')
 +
:addClass('sortbottom')
 
:css('text-align', 'right')
 
:css('text-align', 'right')
 +
row
 +
:tag('th')
 +
:wikitext('Valid votes')
 +
:attr('scope', 'row')
 +
:attr('colspan', cs)
 +
        :css('text-align', 'left')
 +
        :css('font-weight', 'normal')
 +
        :css('background', 'inherit')
 +
for k=1,rounds do
 +
row
 
:tag('td')
 
:tag('td')
:wikitext('Valid votes')
+
:wikitext(fmt(ovalid[k]))
:attr('colspan', cols - 2)
 
        :css('text-align', 'left')
 
 
:tag('td')
 
:tag('td')
:wikitext(fmt(valid))
+
:wikitext(pct(ovalid[k], ovalid[k] + invalid[k]))
 +
end
 +
if headings['seats'] or headings['sc'] then
 +
local cspan = (headings['seats'] and 1 or 0) + (headings['sc'] and 1 or 0)
 +
row
 
:tag('td')
 
:tag('td')
:wikitext(string.format('%.2f%%', valid / (valid + invalid) * 100))
+
:attr('colspan', cspan > 1 and cspan or nil)
:tag('tr')
+
end
 +
-- invalid votes
 +
row = root:tag('tr')
 
:addClass('sortbottom')
 
:addClass('sortbottom')
:css('background', '#eaecf0')
 
 
:css('text-align', 'right')
 
:css('text-align', 'right')
 +
row
 +
:tag('th')
 +
:wikitext('Invalid/blank votes')
 +
:attr('scope', 'row')
 +
:attr('colspan', cs)
 +
        :css('text-align', 'left')
 +
        :css('font-weight', 'normal')
 +
        :css('background', 'inherit')
 +
  for k=1,rounds do
 +
row
 
:tag('td')
 
:tag('td')
:wikitext('Invalid votes')
+
:wikitext(fmt(invalid[k]))
:attr('colspan', cols - 2)
 
        :css('text-align', 'left')
 
 
:tag('td')
 
:tag('td')
:wikitext(fmt(invalid))
+
:wikitext(pct(invalid[k], ovalid[k] + invalid[k]))
 +
  end
 +
if headings['seats'] or headings['sc'] then
 +
local cspan = (headings['seats'] and 1 or 0) + (headings['sc'] and 1 or 0)
 +
row
 
:tag('td')
 
:tag('td')
:wikitext(string.format('%.2f%%', invalid / (valid + invalid) * 100))
+
:attr('colspan', cspan > 1 and cspan or nil)
:tag('tr')
+
end
 +
-- total
 +
row = root:tag('tr')
 
:addClass('sortbottom')
 
:addClass('sortbottom')
 
:css('font-weight', 'bold')
 
:css('font-weight', 'bold')
:css('background', '#eaecf0')
 
 
:css('text-align', 'right')
 
:css('text-align', 'right')
 +
row
 +
:tag('th')
 +
:wikitext('Total votes')
 +
:attr('scope', 'row')
 +
:attr('colspan', cs)
 +
        :css('text-align', 'left')
 +
        :css('background', 'inherit')
 +
  for k=1,rounds do
 +
row
 
:tag('td')
 
:tag('td')
:wikitext('Total votes')
+
:wikitext(fmt(ovalid[k] + invalid[k]))
:attr('colspan', cols - 2)
 
        :css('text-align', 'left')
 
 
:tag('td')
 
:tag('td')
:wikitext(fmt(valid + invalid))
+
:wikitext(pct(1,1))
 +
  end
 +
  if headings['seats'] or headings['sc'] then
 +
local cspan = (headings['seats'] and 1 or 0) + (headings['sc'] and 1 or 0)
 +
row
 
:tag('td')
 
:tag('td')
:wikitext('100%')
+
:attr('colspan', cspan > 1 and cspan or nil)
:tag('tr')
+
  end
 +
-- registered
 +
row = root:tag('tr')
 
:addClass('sortbottom')
 
:addClass('sortbottom')
:css('background', '#eaecf0')
 
 
:css('text-align', 'right')
 
:css('text-align', 'right')
 +
row
 +
:tag('th')
 +
:wikitext('Registered voters/turnout')
 +
:attr('scope', 'row')
 +
:attr('colspan', cs)
 +
:css('text-align', 'left')
 +
        :css('font-weight', 'normal')
 +
        :css('background', 'inherit')
 +
  for k=1,rounds do
 +
row:tag('td')
 +
:wikitext(fmt(electorate[k]))
 +
row:tag('td')
 +
:wikitext(pct(ovalid[k] + invalid[k], electorate[k]))
 +
  end
 +
if headings['seats'] or headings['sc'] then
 +
local cspan = (headings['seats'] and 1 or 0) + (headings['sc'] and 1 or 0)
 +
  row
 
:tag('td')
 
:tag('td')
:wikitext('Registered voters/turnout')
+
:attr('colspan', cspan > 1 and cspan or nil)
:attr('colspan', cols - 2)
+
end
        :css('text-align', 'left')
+
 
:tag('td')
+
row = root:tag('tr')
:wikitext(fmt(electorate))
 
:tag('td')
 
:wikitext(string.format('%.2f%%', (valid + invalid) / electorate * 100))
 
:tag('tr')
 
 
:addClass('sortbottom')
 
:addClass('sortbottom')
:css('background', '#eaecf0')
 
 
:css('text-align', 'right')
 
:css('text-align', 'right')
:tag('td')
+
row:tag('td')
:wikitext('Source: ', args.source)
+
:wikitext('Source: ', args.source)
:attr('colspan', cols - 0)
+
:attr('colspan', cols)
        :css('text-align', 'left')
+
:css('text-align', 'left')
return tostring(root)
+
 +
return tostring(root) .. tracking
 
end
 
end
 +
 
return p
 
return p

Latest revision as of 16:27, 26 September 2020

Implements {{Election results}}


require('Module:No globals')
local p = {}

function p.main(frame)
	local args = require('Module:Arguments').getArgs(frame)
	local index, headings = {}, {}
	local cols, rounds = 0, 1
	local winner, winner_votes = {0, 0}, {0, 0}
	local valid = {0, 0}
	local invalid = {tonumber(args.invalid) or 0, tonumber(args.invalid2) or 0}
	local electorate = {tonumber(args.electorate) or 0, tonumber(args.electorate2) or tonumber(args.electorate) or 0}
	local seats, seatdiff = 0, 0
	local row, secondrow
	local tracking = ''
	local max_rows = 0
	
	-- helper functions
	local lang = mw.getContentLanguage()
	local function fmt(n)
		return n and tonumber(n) and lang:formatNum(tonumber(n)) or nil
	end
	local function pct(n,d)
		n, d = tonumber(n), tonumber(d)
		if n and d and d > 0 then
			return string.format('%.2f', n / d * 100)
		end
		return '–'
	end
	local function tonumdash(s)
		if s then
			s = mw.ustring.gsub(s, '&[MmNn][Dd][Aa][Ss][Hh];', '-')
			s = mw.ustring.gsub(s, '&[Mm][Ii][Nn][Uu][Ss];', '-')
			s = mw.ustring.gsub(s, '[—–−]', '-')
			return tonumber(s) or 0
		end
	end
	local function unlink(s)
		if s then
			s = s:match("^[^%[]-%[%[([^%]]-)|[^%]]-%]%].*$") or s
			s = s:match("^[^%[]-%[%[([^%]]-)%]%].*$") or s
		end
		return s
	end

	-- preprocess the input
	local stop_flag = false
	local i = 0
	while stop_flag == false do
		stop_flag = true
		for kk = 1, 20 do
			i = i + 1
			for k, key in ipairs({'cand', 'vp', 'party', 'sc', 'seats'}) do
				if args[key .. i] then
					headings[key] = true
					stop_flag = false
					max_rows = i > max_rows and i or max_rows
				end
			end
			if args['row' .. i] then
				stop_flag = false
				max_rows = i > max_rows and i or max_rows
			end

			if args['row' .. i] or args['cand' .. i] or args['party' .. i] then
				table.insert(index, i)
				if args['votes' .. i] then
					local votesi = tonumber(args['votes' .. i]) or 0
					args['votes' .. i] = votesi
					if votesi > winner_votes[1] then
						winner[1] = i
						winner_votes[1] = votesi
					end
					valid[1] = valid[1] + votesi
				end
				if args['votes' .. i .. '_2'] then
					rounds = 2
					local votesi = tonumber(args['votes' .. i .. '_2']) or 0
					args['votes' .. i .. '_2'] = votesi
					if votesi > winner_votes[2] then
						winner[2] = i
						winner_votes[2] = votesi
					end
					valid[2] = valid[2] + votesi
				end
				if args['seats' .. i] then
					seats = seats + (tonumber(args['seats' .. i]) or 0)
				end
				if args['sc' .. i] then
					seatdiff = seatdiff + tonumdash(args['sc' .. i])
				end
			end
		end
	end
	
	-- optional against all
	if args['against_all'] or args['against_all2'] then
		max_rows = max_rows + 1
		local i = max_rows
		table.insert(index, i)
		args['votes' .. i] = tonumber(args['against_all']) or 0
		args['votes' .. i .. '_2'] = tonumber(args['against_all_2']) or 0
		args['colour' .. i] = 'inherit'
		args['color' .. i] = 'inherit'
		args['row' .. i] = 'Against all'
		valid[1] = valid[1] + args['votes' .. i]
		valid[2] = valid[2] + args['votes' .. i .. '_2']
		tracking = tracking .. '[[Category:Pages using election results with against all]]'
	end

	local ovalid = {valid[1], valid[2]}
	seats = ((args['total_seats'] or '') == 'TOTAL' and seats) or args['total_seats'] or seats
	seatdiff = (args['total_sc'] or '') == 'TOTAL' and seatdiff or args['total_sc']
	if seats or seatdiff or args['valid'] or ((rounds > 1) and args['valid2']) then
		max_rows = max_rows + 1
		local i = max_rows
		table.insert(index,i)
		args['votes' .. i] = valid[1]
		args['votes' .. i .. '_2'] = valid[2]
		args['colour' .. i] = 'inherit'
		args['color' .. i] = 'inherit'
		args['row' .. i] = 'Total'
		args['seats' .. i] = seats
		args['sc' .. i] = seatdiff
		args['font-weight' .. i] = 'bold'
		args['class' .. i] = 'sortbottom'
		ovalid[1] = tonumber(args['valid']) or valid[1]
		ovalid[2] = tonumber(args['valid2']) or valid[2]
	end

	-- build the table
	local root = mw.html.create('table')
	root
		:addClass('wikitable sortable')
		:tag('caption')
			:wikitext(args.caption)
	
	local topcell = nil
	if args['image'] then
		topcell = root
			:tag('tr')
				:tag('td')
					:wikitext(args['image'])
					:css('text-align', 'center')
	end
		
	local rowspan = (rounds > 1) and 2 or nil
	row = root:tag('tr')
	if headings['cand'] then
		row
			:tag('th')
				:wikitext('Candidate')
				:attr('scope', 'col')
				:attr('colspan', 2)
				:attr('rowspan', rowspan)
		cols = cols + 2
		if headings['vp'] then
			row
				:tag('th')
						:wikitext('Running mate')
						:attr('scope', 'col')
						:attr('rowspan', rowspan)
			cols = cols + 1
		end
		if headings['party'] then
			row
				:tag('th')
				:wikitext('Party')
				:attr('scope', 'col')
				:attr('rowspan', rowspan)
			cols = cols + 1
		end
	else
		row
			:tag('th')
				:wikitext(headings['party'] and 'Party' or '')
				:attr('scope', 'col')
				:attr('colspan', 2)
				:attr('rowspan', rowspan)
		cols = cols + 2
	end
	if rounds > 1 then
		row
			:tag('th')
				:wikitext('First round')
				:attr('scope', 'col')
				:attr('colspan', 2)
			:tag('th')
				:wikitext('Second round')
				:attr('scope', 'col')
				:attr('colspan', 2)
		secondrow = root:tag('tr')
	else
		secondrow = row
	end
	for k=1,rounds do
		secondrow
			:tag('th')
				:wikitext('Votes')
				:attr('scope', 'col')
			:tag('th')
				:wikitext('%')
				:attr('scope', 'col')
		cols = cols + 2
	end
	if headings['seats'] then
		row
			:tag('th')
				:wikitext('Seats')
				:attr('scope', 'col')
				:attr('rowspan', rowspan)
		cols = cols + 1
	end
	if headings['sc'] then
		row
			:tag('th')
				:wikitext(headings['seats'] and '+/–' or 'Seats±')
				:attr('scope', 'col')
				:attr('rowspan', rowspan)
		cols = cols + 1
	end

	if topcell then
		topcell:attr('colspan', cols)
	end
	
	local cs = cols - 2*rounds 
			- (headings['seats'] and 1 or 0)
			- (headings['sc'] and 1 or 0)
	local rsuff = (rounds > 1) and {'', '_2'} or {''}
	for i, v in ipairs(index) do
		row = root:tag('tr')
			:addClass(args['class' .. v])
			:css('font-weight', args['font-weight' .. v])

		-- determine the color
		local color = args['colour' .. v] or args['color' .. v] or nil
		if color == nil then
			local party = unlink(args['party' .. v]) or ''
			if party ~= '' and mw.title.new('Template:' .. party .. '/meta/color').exists then
				color = frame:expandTemplate{title = party .. '/meta/color'}
			end
		end

		if args['row' .. v] then
			row
				:tag('td')
					:wikitext(args['row' .. v])
					:attr('colspan', cs)
		else
			-- create the empty color cell
			row
				:tag('td')
					:css('background-color', color)

			-- add the rest of the row
			if headings['cand'] then
				row
					:tag('td')
						:wikitext(args['cand' .. v])
				if headings['vp'] then
					row
						:tag('td')
							:wikitext(args['vp' .. v])
				end
			end
			if headings['party'] then
				row
					:tag('td')
						:wikitext(args['party' .. v])
			end
		end
		for kk, suf in ipairs(rsuff) do
			if(args['votes' .. v .. suf]) then
				row:tag('td')
					:css('text-align', 'right')
					:wikitext(fmt(args['votes' .. v .. suf]))
				row:tag('td')
					:css('text-align', 'right')
					:wikitext(pct(args['votes' .. v .. suf], valid[kk]))
			else
				row:tag('td'):attr('colspan', 2)
			end
		end
		if headings['seats'] then
			row
				:tag('td')
					:css('text-align', 'right')
					:wikitext(fmt(args['seats' .. v]))
		end
		if headings['sc'] then
			row
				:tag('td')
					:css('text-align', 'right')
					:wikitext(args['sc' .. v])
		end
	end
	-- separating line
	row = root
		:tag('tr')
			:addClass('sortbottom')
	row
		:tag('td')
			:css('background', '#eaecf0')
			:attr('colspan', cols)
	-- valid votes
	row = root
		:tag('tr')
			:addClass('sortbottom')
			:css('text-align', 'right')
	row
		:tag('th')
			:wikitext('Valid votes')
			:attr('scope', 'row')
			:attr('colspan', cs)
	        :css('text-align', 'left')
	        :css('font-weight', 'normal')
	        :css('background', 'inherit')
	for k=1,rounds do
		row
			:tag('td')
				:wikitext(fmt(ovalid[k]))
			:tag('td')
				:wikitext(pct(ovalid[k], ovalid[k] + invalid[k]))
	end
	if headings['seats'] or headings['sc'] then
		local cspan = (headings['seats'] and 1 or 0) + (headings['sc'] and 1 or 0)
		row
			:tag('td')
				:attr('colspan', cspan > 1 and cspan or nil)
	end
	-- invalid votes
	row = root:tag('tr')
			:addClass('sortbottom')
			:css('text-align', 'right')
	row
		:tag('th')
			:wikitext('Invalid/blank votes')
			:attr('scope', 'row')
			:attr('colspan', cs)
 	        :css('text-align', 'left')
	        :css('font-weight', 'normal')
	        :css('background', 'inherit')
   	for k=1,rounds do
		row
			:tag('td')
				:wikitext(fmt(invalid[k]))
			:tag('td')
				:wikitext(pct(invalid[k], ovalid[k] + invalid[k]))
   	end
	if headings['seats'] or headings['sc'] then
		local cspan = (headings['seats'] and 1 or 0) + (headings['sc'] and 1 or 0)
		row
			:tag('td')
				:attr('colspan', cspan > 1 and cspan or nil)
	end
	-- total	
	row = root:tag('tr')
			:addClass('sortbottom')
			:css('font-weight', 'bold')
			:css('text-align', 'right')
	row
		:tag('th')
			:wikitext('Total votes')
			:attr('scope', 'row')
			:attr('colspan', cs)
	        :css('text-align', 'left')
	        :css('background', 'inherit')
   	for k=1,rounds do
		row
			:tag('td')
				:wikitext(fmt(ovalid[k] + invalid[k]))
			:tag('td')
				:wikitext(pct(1,1))
   	end
   	if headings['seats'] or headings['sc'] then
		local cspan = (headings['seats'] and 1 or 0) + (headings['sc'] and 1 or 0)
		row
			:tag('td')
				:attr('colspan', cspan > 1 and cspan or nil)
   	end
	-- registered
	row = root:tag('tr')
			:addClass('sortbottom')
			:css('text-align', 'right')
	row
		:tag('th')
			:wikitext('Registered voters/turnout')
			:attr('scope', 'row')
			:attr('colspan', cs)
 			:css('text-align', 'left')
	        :css('font-weight', 'normal')
	        :css('background', 'inherit')
   	for k=1,rounds do
		row:tag('td')
			:wikitext(fmt(electorate[k]))
		row:tag('td')
			:wikitext(pct(ovalid[k] + invalid[k], electorate[k]))
   	end
	if headings['seats'] or headings['sc'] then
		local cspan = (headings['seats'] and 1 or 0) + (headings['sc'] and 1 or 0)
   		row
			:tag('td')
				:attr('colspan', cspan > 1 and cspan or nil)
	end

	row = root:tag('tr')
			:addClass('sortbottom')
			:css('text-align', 'right')
	row:tag('td')
		:wikitext('Source: ', args.source)
		:attr('colspan', cols)
 		:css('text-align', 'left')
 		
	return tostring(root) .. tracking
end

return p