Module:Settlement Wikidata/sandbox
Appearance
| This is the module sandbox page for Module:Settlement Wikidata (diff). |
| This module is rated as ready for general use. It has reached a mature state, is considered relatively stable and bug-free, and may be used wherever appropriate. It can be mentioned on help pages and other Wikipedia resources as an option for new users. To minimise server load and avoid disruptive output, improvements should be developed through sandbox testing rather than repeated trial-and-error editing. |
| This Lua module is used on approximately 63,000 pages and changes may be widely noticed. Test changes in the module's /sandbox or /testcases subpages, or in your own module sandbox. Consider discussing changes on the talk page before implementing them. |
Uses Wikidata to look up area or population of settlement for use in {{Infobox settlement}} (or infoboxes that wrap it). Implements the following templates, see their test cases to test this module:
- {{Austria metadata Wikidata}} (test cases)
- {{France metadata Wikidata}} (test cases)
- {{Romania metadata Wikidata}} (test cases)
- {{Spain metadata Wikidata}} (test cases)
- {{Swiss metadata Wikidata}} (test cases)
Note that the module implements the community standard of returning values from Wikidata only if sourced to external sources.
Usage
[edit]{{#invoke:Settlement Wikidata|main}}
Parameters
[edit]- args[1]
- The name of the parameter in {{Infobox settlement}} whose value should be looked up in Wikidata, e.g.,
|area_total_km2=or|population_footnotes= - args[2]
- Optionally, the format of the returned date for "as of" look ups (e.g., "dmy" or "mdy")
- country
- the name of the country that contains the settlement
Returns
[edit]Depending on the parameter, either return a value, a date, or an auto-generated reference
require('strict')
local p = {}
-- List of preferred census dates, per country
local asOf = {
austria = '2018-01-01',
france = '2022-01-01',
romania = '2021-12-01',
spain = '2024',
switzerland = '2018-12-31',
}
-- Is the string a valid QID?
local function validQid(qid)
return qid and mw.ustring.find(qid,"^[Qq]%d+$")
end
-- lookup nestedIndex in outer table
-- Example: fetchNested(foo,{'bar','baz','quux'}) will return
-- foo.bar and foo.bar.baz and foo.bar.baz.quux
local function fetchNested(outer,nestedIndex)
if not outer or type(outer) ~= 'table' or not nestedIndex or type(nestedIndex) ~= 'table' then
return nil
end
local current = outer
for _, indx in ipairs(nestedIndex) do
current = current[indx]
if not current then
return current
end
end
return current
end
-- Mapping from wikidata properties (of a reference or "stated in" entity) to citation parameters
local pid2param = {
P50 = "author",
P123 = "publisher",
P407 = "language",
P577 = "date",
P813 = "access-date",
P854 = "url",
P856 = "url",
P953 = "url",
P1476 = "title",
P2699 = "url",
}
-- scan through snaks, loading citation args with correct values
local function scanSnaks(args, snaks)
for pid, data in pairs(snaks) do
data = fetchNested(data,{1,'mainsnak'}) or data[1] -- handle the case where the snak is buried in the claims
local param = pid2param[pid]
if pid == "P248" then --- "stated in": must look up different entity for data
local statedId = fetchNested(data,{'datavalue','value','id'})
local statedEntity = statedId and mw.wikibase.getEntity(statedId)
local statedClaims = statedEntity and statedEntity.claims
if statedClaims then
scanSnaks(args, statedClaims)
end
local statedLabel = fetchNested(statedEntity,{'labels','en','value'})
args.title = args.title or statedLabel
elseif param then
local renderedData = data and mw.wikibase.renderSnak(data)
if param == "title" then
-- set language from title if not otherwise specified
args.language = args.language or fetchNested(data,{'datavalue','value','language'})
end
if pid == "P856" then --- use official URL only if URL is missing
args[param] = args[param] or renderedData
else
args[param] = renderedData
end
end
end
end
-- function to assemble wikicode to create citation, given reference property
local function citeSource(snaks)
local args = {}
args.mode = "cs1"
scanSnaks(args, snaks)
-- citation templates are unhappy without a title, so return a wikilink
if not args.title then
return args.url and "["..args.url.."]"
end
if not args.url then
args["access-date"] = nil -- don't allow access date without url
end
local result = '{{'..(args.url and 'cite web' or 'citation')
for k,v in pairs(args) do
result = result..'|'..k..'='..v
end
result = result..'}}'
return result
end
-- Core function that looks up value, reference, and data for property
-- Arguments:
-- qid = entity id in Wikidata
-- pid = property id on that entity
-- unit = if non-nil, desired unit for value (expressed as a qid for that unit)
-- asOf = date or partial date string to prefer. If not given, find the latest version
-- Returns table only if Wikidata property is source externally
-- amount = value that is looked up (or nil)
-- source = wiki markup for reference
-- asOf = date that value is valid (if given)
function p._getValueAndRef(qid,pid,unit,asOf)
local results = nil
local latest = nil
local statements = mw.wikibase.getAllStatements(qid, pid )
for _, statement in pairs(statements) do
local amount
local wrongDate = true
local value = fetchNested(statement,{'mainsnak','datavalue','value'})
if value then
amount = value.amount
end
-- check if unit is correct, if given
if unit then
local storedUnit = value.unit
if not storedUnit or not mw.ustring.find(value.unit or '',unit,1,true) then
amount = nil
end
end
-- check if asOf matches stored date
-- if asOf not specified, remember stored date
local storedAsOf = fetchNested(statement,{'qualifiers','P585',1,'datavalue','value','time'})
local date
if storedAsOf then
date = mw.ustring.match(storedAsOf,'^%+(%d+%-%d+%-%d+)')
local justYear = mw.ustring.match(date,'^(%d+)%-0+%-0+$')
date = justYear or date
if asOf then
wrongDate = date and not mw.ustring.find(date,asOf,1,true)
end
end
-- check to see if this statement is adequately sourced (filter out "imported from" refs)
local source
local refs = statement['references']
if refs then
for _, ref in pairs(refs) do
local renderedRef = ref.snaks and mw.wikibase.renderSnaks(ref.snaks)
if renderedRef and not mw.ustring.find(renderedRef,'Wiki') then
source = citeSource(ref.snaks)
end
end
end
if amount and source then -- yes, it is adequately sourced,
local theseResults = {}
theseResults.amount = tonumber(amount)
theseResults.source = source
theseResults.asOf = date
-- if we haven't found the preferred date
if wrongDate then
if date and (not latest or date > latest) then -- store latest if dated
results = theseResults
latest = date
elseif not date then -- store first if undated
results = results or theseResults
end
-- here, we found the correct date, so return it
else
results = theseResults
break
end
end
end
return results
end
-- for a qid, find the entity's population
-- if asOf is specified, prefer that population value
function p._population(qid,asOf)
return p._getValueAndRef(qid,"P1082",nil,asOf)
end
-- for a qid, find the area of the entity in square kilometers
function p._area(qid)
return p._getValueAndRef(qid,"P2046","Q712226",nil) -- look for square kilometers only
end
-- main entry point
-- Arguments:
-- args[1] = name of infobox parameter to lookup in Wikidata (e.g., "area_total_km2" or "population_footnotes")
-- args[2] = date format for returned date
-- country = name of country which contains entity
-- qid = qid for entity (for testing, by default, use qid of current page)
-- Returns (depending on args[1]):
-- the value of the population or area, or
-- a reference for the value, or
-- the date the value is valid
function p._main(args)
local country = args.country
local param = args[1]
local qid = validQid(args.qid) and args.qid or mw.wikibase.getEntityIdForCurrentPage()
if not param or not qid then
return nil
end
qid = qid:upper()
country = country and string.lower(country)
param = string.lower(param)
--- France infobox is narrow, so display only year (unless otherwise specified)
args[2] = args[2] or country == "france" and 'y'
local results
-- currently supports area and population: check for substring in parameter
local wantArea = mw.ustring.find(param,"area",1,true)
local wantPop = mw.ustring.find(param,"population",1,true)
if wantArea then
results = p._area(qid)
elseif wantPop then
results = p._population(qid,asOf[country])
end
if not results then
return nil
elseif mw.ustring.find(param,"as_of",1,true) then
-- return date of validity
if not results.asOf then
return nil
end
--- if asOf has only year, return it
if mw.ustring.find(results.asOf,'^%d+$') then
return results.asOf
end
local lang = mw.getContentLanguage()
if args[2] == "ymd" then
return lang:formatDate("Y-m-d",results.asOf)
elseif args[2] == "dmy" then
return lang:formatDate("j F Y",results.asOf)
elseif args[2] == "mdy" then
return lang:formatDate("F j, Y",results.asOf)
elseif args[2] == "y" then
return lang:formatDate("Y",results.asOf)
else
return results.asOf
end
elseif mw.ustring.find(param,"footnotes",1,true) then
if not results.source then
return nil
end
-- run the pre-processor exactly once if returning a reference, because otherwise you get multiple (unused) reference
local frame = mw.getCurrentFrame()
return frame:preprocess("<ref>"..results.source.."</ref>")
elseif wantArea and mw.ustring.find(param,"km2",1,true) then
-- if parameter is for area in km2, return value
return results.amount
elseif wantPop then
return results.amount
end
-- either parameter unrecognized or something bad happened
return nil
end
-- template-facing entry point: do the usual argument processing
function p.main(frame)
local getArgs = require('Module:Arguments').getArgs
local args = getArgs(frame)
return p._main(args) or ""
end
return p