Module:Sandbox/Dipsacus fullonum/elections
Documentation for this module may be created at Module:Sandbox/Dipsacus fullonum/elections/doc
Code
local p = {}
require('strict')
--[[ These variables are set by the initialize function and should not be changed later ]]
local messages -- Table for localized messages
local config -- Table for local configuration variables
local args -- Table for template or #invoke arguments
local lang -- Language object for the local language
local decimalSeparator -- Decimal separator in the local language
--[[ Get calling arguments and local configuration. This function sets the
variables messages, config and args. ]]
local function initialize(frame)
-- If the parent has arguments, use these. Else use the frame's arguments
local parent = frame:getParent()
if parent and #parent.args > 0 then
args = parent.args
else
args = frame.args
end
config = mw.loadData("Module:Sandbox/Dipsacus fullonum/elections/configuration")
messages = config.messages
lang = config.language_code and mw.getLanguage(config.language_code) or mw.getContentLanguage()
decimalSeparator = string.sub(lang:formatNum(1.1), 2, 2)
end
--[[ Format a percentage number to a certain number of decimals and format it for the used language ]]
local function formatPercentage(percentage, decimals)
local formattedNumber = string.format("%." .. decimals .. "f", percentage)
if decimalSeparator ~= '.' then
formattedNumber = string.gsub(formattedNumber, "%.", decimalSeparator)
end
return string.gsub(config.percentage_format, '$1', { ['$1'] = formattedNumber } )
end
--[[ The "typefunc"s - A series of functions that extract the wanted information
from a statement value depending on the value type. ]]
local function getAmount(value) return tonumber(value.amount) end
local function getString(value) return value end
local function getItem(value) return value.id end
local function getYear(value) return string.sub(value.time, 2, 5) end
--[[ Get a best ranking statement value from "item", trying the properties
in the table "properties" in order.
The value is returned through the function "typefunc". ]]
local function getStatementValueWithProptable(item, properties, typefunc)
for _, property in ipairs(properties) do
local statements = mw.wikibase.getBestStatements(item, property)
if statements[1] and statements[1].mainsnak.snaktype == 'value' then
return typefunc(statements[1].mainsnak.datavalue.value)
end
end
end
--[[ Get a best ranking statement value from "item" with the property "property".
The value is returned through the function "typefunc". ]]
local function getStatementValue(item, property, typefunc)
local statements = mw.wikibase.getBestStatements(item, property)
if statements[1] and statements[1].mainsnak.snaktype == 'value' then
return typefunc(statements[1].mainsnak.datavalue.value)
end
end
--[[ Get a qualifier for a best ranking statement.
The qualifier value is returned thorugh the function "typefunc". ]]
local function getQualifierValue(item, property, qualifier, typefunc)
local statements = mw.wikibase.getBestStatements(item, property)
if statements[1] and statements[1].qualifiers and statements[1].qualifiers[qualifier]
and statements[1].qualifiers[qualifier][1].snaktype == 'value' then
return typefunc(statements[1]["qualifiers"][qualifier][1].datavalue.value)
end
end
--[[ Get a qualifier for a best ranking statement. "qualifiers" is table with
with qualifiers which are tried in turn until a value is found.
The qualifier value is returned thorugh the function "typefunc". ]]
local function getQualifierValueWithQualtable(item, property, qualifiers, typefunc)
local statements = mw.wikibase.getBestStatements(item, property)
for _, qualifier in ipairs(qualifiers) do
if statements[1] and statements[1].qualifiers and statements[1].qualifiers[qualifier]
and statements[1].qualifiers[qualifier][1].snaktype == 'value' then
return typefunc(statements[1]["qualifiers"][qualifier][1].datavalue.value)
end
end
end
local function getItemTableWithQuantityQualifier(item, property, qualifier, ignore_zero)
local statements = mw.wikibase.getBestStatements(item, property)
local items = {}
local highest = 0
local sum = 0
for key, statement in pairs(statements) do
if statement.mainsnak.snaktype == 'value' then
local item = statement.mainsnak.datavalue.value.id
if statement.qualifiers and statement.qualifiers[qualifier]
and statement.qualifiers[qualifier][1].snaktype == 'value' then
local quantity = tonumber(statement.qualifiers[qualifier][1].datavalue.value.amount)
if quantity ~= 0 or not ignore_zero then
items[item] = quantity
sum = sum + quantity
if quantity > highest then
highest = quantity
end
end
end
end
end
return items, highest, sum
end
local function makeGetMonolingualValue(property, wanted_lang, tooltip)
return function(item)
local statements = mw.wikibase.getBestStatements(item, property)
for key, statement in pairs(statements) do
if statement.mainsnak.snaktype == 'value' then
if wanted_lang == statement.mainsnak.datavalue.value.language then
local text = statement.mainsnak.datavalue.value.text
local formatted_text
if tooltip == 'label' then
local label = mw.wikibase.getLabelByLang(item, wanted_lang)
if label then
formatted_text = '<abbr title="' .. label .. '">' .. text .. '</abbr>'
end
end
return text, formatted_text
end
end
end
end
end
local function makeGetLabel(wanted_lang)
return function(item)
return mw.wikibase.getLabelByLang(item, wanted_lang)
end
end
local function makeGetLink(wiki)
return function(item)
local sitelink = mw.wikibase.getSitelink(item, wiki)
if wiki and sitelink then
-- For interwikilinks add globalSiteId without 'wiki' as prefix
sitelink = ':' .. string.sub(wiki, 1, -5) .. ':' .. sitelink
end
return sitelink
end
end
local function makePartyFunctions()
local getName = {}
for _, value in ipairs(config.party_text) do
if value.method == 'P1813' then
getName[#getName + 1] = makeGetMonolingualValue('P1813', value.lang, value.tooltip)
elseif value.method == 'label' then
getName[#getName + 1] = makeGetLabel(value.lang)
elseif value.method == 'qid' then
getName[#getName + 1] = function(item) return item end
end
end
local getLink = makeGetLink(config.party_link)
return getName, getLink
end
--[[ Create a sequence table with info the all found parties.
The values are tables with party information. ]]
local function getAllParties(electionData)
-- First make a table with the found party items as keys
local partyItems = {}
for _, election in pairs(electionData) do
for party, amount in pairs(election.elected) do
if partyItems[party] then
partyItems[party].elected = partyItems[party].elected + amount
partyItems[party].elections = partyItems[party].elections + 1
else
partyItems[party] = { elected = amount, elections = 1, party = party }
end
end
end
-- Create and populate the party table
local parties = {}
local getPartyName, getPartyLink = makePartyFunctions()
for party, partyInfo in pairs(partyItems) do
local name, formatted_name
for _, func in ipairs(getPartyName) do
name, formatted_name = func(party)
if name then break end
end
partyInfo.name = name
partyInfo.formatted_name = formatted_name
partyInfo.sitelink = getPartyLink(party)
parties[#parties + 1] = partyInfo
end
return parties
end
local function writeTableHeader(parties)
local table = mw.html.create('table')
local use_color_row = config.party_color_bar and config.party_color_bar > 0
local header_rowspan = use_color_row and "3" or "2"
table:attr('class', 'wikitable sortable')
:tag('tr')
:tag('th'):attr('rowspan', header_rowspan):wikitext(messages['Year']):done()
:tag('th'):attr('rowspan', header_rowspan):wikitext(messages['Electorate']):done()
:tag('th'):attr('rowspan', header_rowspan):wikitext(messages['Valid votes']):done()
:tag('th'):attr('rowspan', header_rowspan):wikitext(messages['Participation']):done()
:tag('th'):attr('colspan', #parties):wikitext(messages['Parties']):done()
:tag('th'):attr('rowspan', header_rowspan):wikitext(messages['Total seats']):done()
:tag('th'):attr('rowspan', header_rowspan)
:wikitext('[[File:Wikidata-logo.svg|20px|link=d:|' .. messages['Wikidata logo'] .. ']]')
local row = mw.html.create('tr')
for _, partyInfo in ipairs(parties) do
local name = partyInfo.formatted_name or partyInfo.name
if partyInfo.sitelink then
name = '[[' .. partyInfo.sitelink .. '|' .. name .. ']]'
end
row:tag('th'):wikitext(name)
end
table:node(row)
if use_color_row then
local color_row = mw.html.create('tr'):css('height', config.party_color_bar .. 'px')
for _, partyInfo in ipairs(parties) do
local color_cell = color_row:tag('th')
local color = getStatementValue(partyInfo.party, 'P465', getString)
if color then
color_cell:css('background', '#' .. color)
end
end
table:node(color_row)
end
return table
end
local function getElectionData(electionItem)
local data = {}
data["electorate"] = getStatementValueWithProptable(electionItem, { "P1867", "P1831" }, getAmount)
data["valid votes"] = getStatementValue(electionItem, "P1697", getAmount)
data["total votes"] = getStatementValue(electionItem, "P1868", getAmount)
data["mandates"] = getQualifierValueWithQualtable(electionItem, "P541", { "P1410", "P1114" }, getAmount)
data["elected"], data["highest elected value"], data["elected sum"] =
getItemTableWithQuantityQualifier(electionItem, "P991", "P1410", config.ignore_zero_elected)
data["electionItem"] = electionItem
return data
end
local function partySortFunc(a, b)
for _, sortkey in ipairs(config.party_column_order) do
if sortkey == 'most_elected' then
if a.elected > b.elected then return true end
if a.elected < b.elected then return false end
elseif sortkey == 'most_elections' then
if a.elections > b.elections then return true end
if a.elections < b.elections then return false end
elseif sortkey == 'name' then
if a.name < b.name then return true end
if a.name > b.name then return false end
else
return false
end
end
end
--[[ elections: Make an overview table for each of a series of elections with the number
of elected for each party for each election
arguments:
start = item for first election in table. From that follow the chain of next qualifiers
end = item for last election in table.
]]
function p.elections(frame)
initialize(frame)
local electionItem = args.start
local electionData = {}
while electionItem do
local year = getStatementValue(electionItem, "P585", getYear)
if args.last_year and year > args.last_year then break end
if not args.first_year or year >= args.first_year then
local data = getElectionData(electionItem)
data["year"] = year
electionData[#electionData + 1] = data
end
if electionItem == args["end"] then break end
electionItem = getQualifierValue(electionItem, "P31", "P156", getItem)
end
local parties = getAllParties(electionData)
table.sort(parties, partySortFunc)
local table = writeTableHeader(parties)
local getYearLink = makeGetLink(config.year_link)
for _, election in pairs(electionData) do
local yearLink = getYearLink(election["electionItem"])
if not yearLink then
-- no article for this specific election, try for what it is part of
local part_of = getStatementValue(election["electionItem"], 'P361', getItem)
if part_of then
yearLink = getYearLink(part_of)
end
end
local yearText = yearLink and ('[[' .. yearLink .. '|' .. election["year"] .. ']]')
or election["year"]
local row = mw.html.create('tr'):css('text-align', 'right')
:tag('td'):wikitext(yearText):done()
:tag('td'):wikitext(lang:formatNum(election["electorate"])):done()
:tag('td'):wikitext(lang:formatNum(election["valid votes"])):done()
local participation
if election["total votes"] and election["electorate"] then
participation = formatPercentage(election["total votes"] / election["electorate"] * 100,
config.percentage_decimals)
end
row:tag('td'):wikitext(participation)
local highest = election["highest elected value"]
for _, partyInfo in ipairs(parties) do
local elected = election.elected[partyInfo.party]
if elected == highest then
row:tag('td'):css('font-weight', 'bold'):wikitext(elected)
else
row:tag('td'):wikitext(elected)
end
end
row:tag('td'):wikitext(election["mandates"])
row:tag('td'):wikitext("[[File:Blue pencil.svg|8px|link=d:" .. election["electionItem"]
.. "|" .. messages["Wikidata pencil"] .. "]]")
table:node(row)
end
return table
end
return p