Module:Roadtable
Jump to navigation
Jump to search
Code
local p = {}
local SANDBOX = false -- Change to false when deploying
-- Library aliases
local concat = table.concat
local format = mw.ustring.format
local insert = table.insert
local loadData = mw.loadData
local sort = table.sort
local split = mw.text.split
local strFormat = string.format
local trim = mw.text.trim
-- Data modules
local globalI18n = loadData('Module:Roadtable/i18n')
local modelsI18n = loadData('Module:Roadtable/models/i18n')
local sectionsI18n = loadData('Module:Roadtable/sections/i18n')
local modelData
if SANDBOX then
modelData = loadData('Module:Roadtable/models/sandbox')
else
modelData = loadData('Module:Roadtable/models')
end
-- Constants
local headerColor = '#eaecf0'
-- Global data
local fullModel = modelData.models.full
local globalData = {}
local sectionDatasets = {}
local importanceDatasets = {{}, {}, {}, {}}
local importanceData = {}
local perSectionData = {}
local root = ''
local fullPage
-- Translation code
local int_lang, subpage_lang
local function translate(str)
return str[subpage_lang] or str[int_lang] or str.en
end
local importanceNumToStr, summaryStr
-- Utility functions
local function listParser(list)
list = trim(list)
local entries = split(list, ";")
local fullEntries = {}
for _,v in ipairs(entries) do
local newEntry = split(v, '_')
if #newEntry > 1 then
insert(fullEntries, newEntry)
else
insert(fullEntries, v)
end
end
return fullEntries
end
local function listToTruth(list)
truth = {}
for _,v in ipairs(list) do
truth[v] = true
end
return truth
end
local function propTableToList(data)
local properties = {}
for _,property in ipairs(fullModel) do
if not data.usedProperties[property] then
insert(properties, -1)
else
insert(properties, data.properties[property])
end
end
return properties
end
-- Header and footer
local function top(model)
local headers = {'{| class=wikitable'}
local itemHeader = translate(globalI18n.itemHeader)
insert(headers, '!' .. itemHeader)
local props
if type(model) == 'table' then
props = model
else
props = modelData.models[model]
end
local images = modelData.images
local altStrs = modelsI18n.alt
local langImage = images.langs
local langAlt = translate(altStrs.langs)
insert(headers, format('![[File:%s|25px|link=|%s]]', langImage, langAlt))
for i,v in ipairs(props) do
local image = images[v]
if not image then
insert(headers, strFormat('!%s', tostring(i)))
else
local alt = translate(altStrs[v])
insert(headers, format('![[File:%s|25px|link=|%s]]', image, alt))
end
end
if model ~= 'full' and SANDBOX then
local classStr = translate(globalI18n.classes.header)
insert(headers, '!' .. classStr)
end
return concat(headers, '\n')
end
local function bottom(model)
local props
if type(model) == 'table' then
props = model
else
props = modelData.models[model]
end
local footers = {'|-'}
local images = modelData.images
local altStrs = modelsI18n.alt
local langImage = images.langs
local langAlt = translate(altStrs.langs)
local langStr = translate(globalI18n.labelDesc)
insert(footers, format('*[[File:%s|25px|link=|%s]] %s', langImage, langAlt, langStr))
for i,v in ipairs(props) do
local image = images[v]
local entry
if not image then
entry = strFormat("*'''%s''' ", i)
else
local alt = translate(altStrs[v])
entry = format('*[[File:%s|25px|link=|%s]] ', image, alt)
end
entry = entry .. mw.getCurrentFrame():preprocess(strFormat('{{P|%s}}', v))
local additional = fullModel[v]
if additional and type(additional) == 'table' then
for _,v in ipairs(additional) do
entry = entry .. mw.getCurrentFrame():preprocess(strFormat(', {{P|%s}}', v))
end
end
insert(footers, entry)
end
local colspan = SANDBOX and (#footers + 1) or #footers
insert(footers, 2, '| colspan="' .. tostring(colspan) .. '" class="hlist" style="text-align: center; background-color: ' .. headerColor .. '; font-size: 90%;"|')
insert(footers, '|-\n|}')
return concat(footers, '\n')
end
-- Languages
local function excludeLang(data, currLangs)
local excludedLangs = {}
for k,_ in pairs(data.languages) do
if not currLangs[k] then
excludedLangs[k] = true
end
end
for k,_ in pairs(excludedLangs) do
perSectionData.languages[k] = nil
globalData.languages[k] = nil
importanceData.languages[k] = nil
end
end
local function languageList(langs)
if not perSectionData.languages then
perSectionData.languages = listToTruth(langs)
if not globalData.languages then
globalData.languages = listToTruth(langs)
else
if not importanceData.languages then
importanceData.languages = listToTruth(langs)
else
excludeLang(importanceData, perSectionData.languages)
end
excludeLang(globalData, perSectionData.languages)
end
if not importanceData.languages then
importanceData.languages = listToTruth(langs)
else
excludeLang(importanceData, perSectionData.languages)
end
else
local currLangs = listToTruth(langs)
excludeLang(perSectionData, currLangs)
end
return concat(langs, ' ')
end
local function autoLanguageList(item)
local labels = item.labels or {}
local descriptions = item.descriptions or {}
local langs = {}
for lang,_ in pairs(labels) do
if descriptions[lang] then
insert(langs, lang)
end
end
sort(langs)
return languageList(langs)
end
local function manualLanguageList(args)
local langStr = args.langs or ''
local langs = split(langStr, ',')
sort(langs)
return languageList(langs)
end
-- Properties
local fullModelProps = listToTruth(fullModel)
local dataProps = {__index=fullModelProps}
-- Class requirement parser
local classParser = {}
classParser['and'] = function (cells, requirements)
for _,req in ipairs(requirements) do
if type(req) == 'table' then
if not classParser.parse(cells, req) then
return false
end
else
if not cells[req] then
return false
end
end
end
return true
end
classParser['or'] = function (cells, requirements)
for _,req in ipairs(requirements) do
if type(req) == 'table' then
if classParser.parse(cells, req) then
return true
end
else
if cells[req] then
return true
end
end
end
return false
end
classParser['some'] = function (cells, requirements)
local count = 0
local needed = requirements.count
for _,req in ipairs(requirements) do
if type(req) == 'table' then
if classParser.parse(cells, req) then
count = count + 1
end
else
if cells[req] then
count = count + 1
end
end
end
return count >= needed
end
classParser.parse = function (cells, requirements)
local operator = requirements.operator or 'and'
return classParser[operator](cells, requirements)
end
local function modelPropertyRow(modelName, item)
local models = modelData.models
local model = models[modelName] or models.route
if not perSectionData.properties then
perSectionData.properties = {}
setmetatable(perSectionData.properties, dataProps)
perSectionData.usedProperties = {}
end
if not globalData.properties then
globalData.properties = {}
setmetatable(globalData.properties, dataProps)
globalData.usedProperties = {}
end
if not importanceData.properties then
importanceData.properties = {}
setmetatable(importanceData.properties, dataProps)
importanceData.usedProperties = {}
end
local modelUsed = listToTruth(model)
local propertyList = item:getProperties()
local properties = listToTruth(propertyList)
local cells = {}
local function presenceTester(value, property)
local test
if value ~= nil then
if type(value) == 'table' then
for _,v in ipairs(value) do
if type(v) == 'number' then -- check multiple allowed values
test = test or presenceTester(v, property)
elseif string.sub(v,1,1) == 'P' then -- check multiple allowed properties
test = test or presenceTester(value[v], v)
end
end
elseif type(value) == 'number' then
local claims = item.claims or {}
local claimsForProp = claims[property]
if not claimsForProp then
test = false
else
local itemId
for _,claim in ipairs(claimsForProp) do
itemId = claim.mainsnak.datavalue.value['numeric-id']
test = (itemId == value)
if test then
break
end
end
end
elseif type(value) == 'boolean' then
test = value
end
else
test = (properties[property] == true)
end
return test
end
local function insertCell(property)
if not modelUsed[property] then
cells[property] = -1
else
perSectionData.usedProperties[property] = true
globalData.usedProperties[property] = true
importanceData.usedProperties[property] = true
local value = model[property]
local test = presenceTester(value, property)
if not test then
perSectionData.properties[property] = false
end
cells[property] = test
end
end
for _,v in ipairs(fullModel) do
insertCell(v)
end
if SANDBOX then
local classes = modelData.classes[modelName]
if classes then
local letters = {'D', 'C', 'B', 'A'}
local class = 'F'
for _,v in ipairs(letters) do
local requirements = classes[v]
local met = classParser.parse(cells, requirements)
if met then
class = v
else
break
end
end
cells.class = translate(globalI18n.classes[class])
else
cells.class = ''
end
end
return cells
end
local function manualPropertyRow(args)
if not perSectionData.properties then
perSectionData.properties = {}
setmetatable(perSectionData.properties, dataProps)
perSectionData.usedProperties = {}
end
if not globalData.properties then
globalData.properties = {}
setmetatable(globalData.properties, dataProps)
globalData.usedProperties = {}
end
if not importanceData.properties then
importanceData.properties = {}
setmetatable(importanceData.properties, dataProps)
importanceData.usedProperties = {}
end
local propStrs = split(args.properties, ':')
local properties = {}
for _,v in pairs(propStrs) do
local prop = split(v, '-')
properties[prop[1]] = prop[2]
end
local cells = {class = ''}
local function insertCell(property)
local cases = {yes = true, no = false, na = -1}
local test = cases[properties[property] or 'no']
if test == -1 then
cells[property] = -1
else
perSectionData.usedProperties[property] = true
globalData.usedProperties[property] = true
importanceData.usedProperties[property] = true
if not test then
perSectionData.properties[property] = false
end
cells[property] = test
end
end
for _,v in ipairs(fullModel) do
insertCell(v)
end
return cells
end
-- Row generation
local function manualHeader(args)
local target, display
-- Target
local subpage = args.subpage or ''
if subpage ~= '' then
local rootPage = args.root or root
local thisPage = "Wikidata:WikiProject Roads" .. rootPage
target = format("Special:MyLanguage/%s/%s", thisPage, subpage)
else
target = args.link or ''
end
-- Display
local q = args.targetq or ''
local section = args.targetsection or ''
if q ~= '' then -- Target item (use localized label for link display)
local itemName = 'Q' .. q
display = mw.wikibase.getLabelByLang(itemName, subpage_lang) or mw.wikibase.label(itemName)
elseif section ~= '' then
local temp = split(section, '-')
if #temp > 1 then
section = concat(temp, '_')
end
local translations = sectionsI18n[section] or {en = 'Section ' .. section}
display = translate(translations)
else
display = args.target or ''
end
local link = format("[[%s|%s]]", target, display)
return link
end
local function getData(args)
local q = args.q or ''
local model = args.model or ''
local item, langs, properties, header
item = mw.wikibase.getEntityObject('Q' .. q)
if not item then
return {noitem='Q' .. q}
end
langs = autoLanguageList(item)
properties = modelPropertyRow(model, item)
local label = item:getLabel(subpage_lang) or item:getLabel(int_lang) or item:getLabel('en')
if not label then
header = format("[[Q%s]]", q)
else
header = format("[[Q%s|%s <small>(Q%s)</small>]]", q, label, q)
end
return {header=header, langs=langs, props=properties}
end
local function rowGen(args, summary)
local row = {}
if not summary then
local data = getData(args)
if data.noitem then
return data
end
row.def = '|-'
row.header = data.header
row.langs = data.langs
row.properties = data.props
elseif summary == -1 then
row.def = '|-'
row.header = manualHeader(args)
row.langs = manualLanguageList(args)
row.properties = manualPropertyRow(args)
else
row.def = '|- style="background-color: ' .. headerColor .. '; border-top: 2px solid black !important;" | '
row.header = summaryStr
local languages = {}
for k,_ in pairs(perSectionData.languages) do
insert(languages, k)
end
sort(languages)
row.langs = concat(languages, ' ')
local properties = {}
for _,property in ipairs(fullModel) do
if not perSectionData.usedProperties[property] then
properties[property] = -1
else
properties[property] = perSectionData.properties[property]
end
end
properties.class = ''
row.properties = properties
end
return row
end
local function overviewRowGen(data, summary)
local row
local langs, properties
if not summary then
local importanceData = importanceDatasets[data.importance]
row = {'|-'}
insert(row, format('! [[#%s|%s]]', data.name, data.label))
if not data.placeholder then
local languages = {}
for k,_ in pairs(data.languages) do
insert(languages, k)
end
sort(languages)
langs = concat(languages, ' ')
properties = propTableToList(data)
for _,property in ipairs(fullModel) do
if not data.properties[property] then
globalData.properties[property] = false
importanceData.properties[property] = false
end
end
else
insert(row, "|style=\"text-align: center;\" colspan=15|''No data''")
return concat(row, '\n')
end
elseif type(summary) == 'number' then
if not data.languages then return nil end
row = {'|- style="background-color: ' .. headerColor .. ';" | '}
insert(row, format('! %s', importanceNumToStr[summary]))
local languages = {}
for k,_ in pairs(data.languages) do
insert(languages, k)
end
sort(languages)
langs = concat(languages, ' ')
properties = propTableToList(data)
else
row = {'|- style="background-color: ' .. headerColor .. '; border-top: 2px solid black !important;" | '}
insert(row, '! ' .. summaryStr)
local languages = {}
for k,_ in pairs(globalData.languages) do
insert(languages, k)
end
sort(languages)
langs = concat(languages, ' ')
properties = propTableToList(globalData)
end
insert(row, '|' .. langs)
for _,prop in ipairs(properties) do
if prop == -1 then
insert(row, '| style="text-align: center; background-color: #d3d3d3;" | —')
elseif prop then
insert(row, '| style="text-align: center; background-color: #ddffdd;" | ✓')
else
insert(row, '| style="text-align: center; background-color: #ffdddd;" | ✗')
end
end
return concat(row, '\n')
end
-- Tables
local function body(args, section, level)
local sectionDataset
if SANDBOX then
sectionDataset = loadData('Module:Roadtable/sections/sandbox')
else
sectionDataset = loadData('Module:Roadtable/sections')
end
local sectionData = sectionDataset[section]
local model = args[section .. '_model'] or ''
local importance = tonumber(args[section .. '_importance'])
if sectionData then
model = model == '' and sectionData.model or model
importance = importance or sectionData.importance
end
importanceData = importanceDatasets[importance]
perSectionData = {name = section, importance = importance}
local rowData = {}
local rows = {}
local used = {}
local entryList = args[section] or ''
if entryList ~= '' then
local entries = listParser(entryList)
for i,v in ipairs(entries) do
local rowArgs = {}
local q
if type(v) == 'table' then
q = v[1]
else
q = v
end
if q == '' or q == 'noitem' then -- No item (use string for name)
local noitem = v[2]
if not noitem then
error(strFormat('No title provided for noitem at position %s', i), 0)
else
local noitemTitle = split(noitem, '=')[2]
insert(rowData, {noitem=noitemTitle})
end
elseif q == 'manual' then
for idx,arg in ipairs(v) do
if idx > 1 then
local param = split(arg, '=')
rowArgs[param[1]] = param[2]
end
end
insert(rowData, rowGen(rowArgs, -1))
else
rowArgs.q = trim(q)
if type(v) == 'table' then
rowArgs.model = split(v[2], '=')[2]
else
rowArgs.model = model
end
insert(rowData, rowGen(rowArgs))
end
end
insert(rowData, rowGen({}, true))
for _,v in ipairs(fullModel) do
if perSectionData.usedProperties[v] then
insert(used, v)
end
end
insert(rows, top(used))
for _,row in ipairs(rowData) do
if row.noitem then
insert(rows, format("|-\n!%s\n|colspan=15 style=\"text-align: center;\"|''Item not created''", row.noitem))
else
local rowStrs = {row.def, '! ' .. row.header, '| ' .. row.langs}
props = row.properties
for _,prop in ipairs(used) do
if props[prop] == -1 then
insert(rowStrs, '| style="text-align: center; background-color: #d3d3d3;" | —')
elseif props[prop] then
insert(rowStrs, '| style="text-align: center; background-color: #ddffdd;" | ✓')
else
insert(rowStrs, '| style="text-align: center; background-color: #ffdddd;" | ✗')
end
end
if SANDBOX then insert(rowStrs, '| style="text-align: center;" | ' .. row.properties.class) end
insert(rows, concat(rowStrs, '\n'))
end
end
else
perSectionData.placeholder = true
end
local anchor = strFormat('<span id="%s"></span>', section)
local header
if sectionData then
local translations = sectionsI18n[section] or {en = 'Section ' .. section}
header = translate(translations)
else
local title = args[section .. '_title'] or tonumber(args[section .. '_titleq'] or '') or ''
if title == '' then
header = 'Section ' .. section
elseif type(title) == 'number' then
local itemName = 'Q' .. title
header = mw.wikibase.getLabelByLang(itemName, subpage_lang) or mw.wikibase.label(itemName)
else
header = title
end
end
if level == 2 then
local headerWikitext
if fullPage then
headerWikitext = format('===%s %s===', anchor, header)
else
headerWikitext = format('====%s %s====', anchor, header)
end
insert(rows, 1, headerWikitext)
perSectionData.label = '— ' .. header
else
local headerWikitext
if fullPage then
headerWikitext = format('==%s %s==', anchor, header)
else
headerWikitext = format('===%s %s===', anchor, header)
end
insert(rows, 1, headerWikitext)
perSectionData.label = header
end
if entryList ~= '' then
insert(rows, bottom(used))
end
insert(sectionDatasets, perSectionData)
return concat(rows, '\n')
end
local function overview()
local rows = {}
for i,v in ipairs(sectionDatasets) do
insert(rows, overviewRowGen(v))
end
insert(rows, overviewRowGen({}, true))
for i = 4, 1, -1 do
insert(rows, overviewRowGen(importanceDatasets[i], i))
end
insert(rows, 1, top('full'))
insert(rows, bottom('full'))
return concat(rows, '\n')
end
-- Pages
function p._page(args)
local sections = {}
local sectionList = args.sections
root = args.root or ''
fullPage = (args.fullpage ~= 'no')
local sectionsTable = listParser(sectionList)
for _,section in ipairs(sectionsTable) do
if type(section) == 'table' then
for _,subsection in ipairs(section) do
if subsection == section[1] then
insert(sections, body(args, subsection, 1))
else
insert(sections, body(args, section[1] .. '_' .. subsection, 2))
end
end
else
insert(sections, body(args, section))
end
end
insert(sections, 1, '__NOTOC__')
insert(sections, 1, overview())
return concat(sections, '\n\n')
end
function p.page(frame)
local pframe = frame:getParent()
local config = frame.args -- the arguments passed BY the template, in the wikitext of the template itself
local args = pframe.args -- the arguments passed TO the template, in the wikitext that transcludes the template
--int_lang = frame:preprocess('{{int:Lang}}')
int_lang = config.lang
subpage_lang = config.lang
importanceNumToStr = translate(globalI18n.importance)
summaryStr = translate(globalI18n.summary)
return p._page(args)
end
return p