-- Version: 2021-02-06
-- Module to implement use of a blacklist and whitelist for infobox fields
-- Can take a named parameter |qid which is the Wikidata ID for the article
-- if not supplied, it will use the Wikidata ID associated with the current page.
-- Fields in blacklist are never to be displayed, i.e. module must return nil in all circumstances
-- Fields in whitelist return local value if it exists or the Wikidata value otherwise
-- The name of the field that this function is called from is passed in named parameter |name
-- The name is compulsory when blacklist or whitelist is used,
-- so the module returns nil if it is not supplied.
-- blacklist is passed in named parameter |suppressfields (or |spf)
-- whitelist is passed in named parameter |fetchwikidata (or |fwd)  
local p = {}  
local cdate -- initialise as nil and only load _complex_date function if needed
-- Module:Complex date is loaded lazily and has the following dependencies:
-- Module:Calendar
-- Module:ISOdate
-- Module:DateI18n
-- Module:No globals
-- Module:I18n/complex date
-- Module:Ordinal
-- Module:I18n/ordinal
-- Module:Yesno
-- Module:Formatnum
-- Module:Linguistic
---
-- The following, taken from https://www.mediawiki.org/wiki/Wikibase/DataModel#Dates_and_times,
-- is needed to use Module:Complex date which seemingly requires date precision as a string.
-- It would work better if only the authors of the mediawiki page could spell 'millennium'.
local dp = {
[6] = "millennium",
[7] = "century",
[8] = "decade",
[9] = "year",
[10] = "month",
[11] = "day",
}  
local i18n =
{
["errors"] =
{
["property-not-found"] = "Property not found.",
["No property supplied"] = "No property supplied",
["entity-not-found"] = "Wikidata entity not found.",
["unknown-claim-type"] = "Unknown claim type.",
["unknown-entity-type"] = "Unknown entity type.",
["qualifier-not-found"] = "Qualifier not found.",
["site-not-found"] = "Wikimedia project not found.",
["labels-not-found"] = "No labels found.",
["descriptions-not-found"] = "No descriptions found.",
["aliases-not-found"] = "No aliases found.",
["unknown-datetime-format"] = "Unknown datetime format.",
["local-article-not-found"] = "Article is available on Wikidata, but not on Wikipedia",
["dab-page"] = " (dab)",
},
["months"] =
{
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
},
["century"] = "century",
["BC"] = "BC",
["BCE"] = "BCE",
["ordinal"] =
{
[1] = "st",
[2] = "nd",
[3] = "rd",
["default"] = "th"
},
["filespace"] = "File",
["Unknown"] = "Unknown",
["NaN"] = "Not a number",
-- set the following to the name of a tracking category,
-- e.g. "", or "" to disable:
["missinginfocat"] = "",
["editonwikidata"] = "Edit this on Wikidata",
["latestdatequalifier"] = function (date) return "before " .. date end,
-- some languages, e.g. Bosnian use a period as a suffix after each number in a date
["datenumbersuffix"] = "",
["list separator"] = ", ",
["multipliers"] = {
[0] = "",
[3] = " thousand",
[6] = " million",
[9] = " billion",
[12] = " trillion",
}
}
-- This allows an internationisation module to override the above table
if 'en' ~= mw.getContentLanguage:getCode then
require("Module:i18n").loadI18n("Module:WikidataIB/i18n", i18n)
end  
-- This piece of html implements a collapsible container. Check the classes exist on your wiki.
local collapsediv = '<div data-expandtext="" data-collapsetext="">'  
-- Some items should not be linked.
-- Each wiki can create a list of those in Module:WikidataIB/nolinks
-- It should return a table called itemsindex, containing true for each item not to be linked
local donotlink = {}
local nolinks_exists, nolinks = pcall(mw.loadData, "Module:WikidataIB/nolinks")
if nolinks_exists then
donotlink = nolinks.itemsindex
end  
-- To satisfy Wikipedia:Manual of Style/Titles, certain types of items are italicised, and others are quoted.
-- The submodule Module:WikidataIB/titleformats lists the entity-ids used in 'instance of' (P31),
-- which allows this module to identify the values that should be formatted.
-- WikidataIB/titleformats exports a table p.formats, which is indexed by entity-id, and contains the value " or ''
local formats = {}
local titleformats_exists, titleformats = pcall(mw.loadData, "Module:WikidataIB/titleformats")
if titleformats_exists then
formats = titleformats.formats
end  
---
-- Private functions
---
---
---
-- makeOrdinal needs to be internationalised along with the above:
-- takes cardinal number as a numeric and returns the ordinal as a string
-- we need three exceptions in English for 1st, 2nd, 3rd, 21st, .. 31st, etc.
---
-- Dependencies:
---
local makeOrdinal = function(cardinal)
local ordsuffix = i18n.ordinal.default
if cardinal % 10 == 1 then
ordsuffix = i18n.ordinal[1]
elseif cardinal % 10 == 2 then
ordsuffix = i18n.ordinal[2]
elseif cardinal % 10 == 3 then
ordsuffix = i18n.ordinal[3]
end
-- In English, 1, 21, 31, etc. use 'st', but 11, 111, etc. use 'th'
-- similarly for 12 and 13, etc.
if (cardinal % 100 == 11) or (cardinal % 100 == 12) or (cardinal % 100 == 13) then
ordsuffix = i18n.ordinal.default
end
return tostring(cardinal) .. ordsuffix
end  
---
-- findLang takes a "langcode" parameter if supplied and valid
-- otherwise it tries to create it from the user's set language
-- failing that it uses the wiki's content language.
-- It returns a language object
---
-- Dependencies:
---
local findLang = function(langcode)
local langobj
langcode = mw.text.trim(langcode or "")
if mw.language.isKnownLanguageTag(langcode) then
langobj = mw.language.new( langcode )
else
langcode = mw.getCurrentFrame:preprocess( '' )
if mw.language.isKnownLanguageTag(langcode) then
langobj = mw.language.new( langcode )
else
langobj = mw.language.getContentLanguage
end
end
return langobj
end  
---
-- _getItemLangCode takes a qid parameter (using the current page's qid if blank)
-- If the item for that qid has property country (P17) it looks at the first preferred value
-- If the country has an official language (P37), it looks at the first preferred value
-- If that official language has a language code (P424), it returns the first preferred value
-- Otherwise it returns nothing.
---
-- Dependencies:
---
local _getItemLangCode = function(qid)
qid = mw.text.trim(qid or ""):upper
if qid == "" then qid = mw.wikibase.getEntityIdForCurrentPage end
if not qid then return end
local prop17 = mw.wikibase.getBestStatements(qid, "P17")[1]
if not prop17 or prop17.mainsnak.snaktype ~= "value" then return end
local qid17 = prop17.mainsnak.datavalue.value.id
local prop37 = mw.wikibase.getBestStatements(qid17, "P37")[1]
if not prop37 or prop37.mainsnak.snaktype ~= "value" then return end
local qid37 = prop37.mainsnak.datavalue.value.id
local prop424 = mw.wikibase.getBestStatements(qid37, "P424")[1]
if not prop424 or prop424.mainsnak.snaktype ~= "value" then return end
return prop424.mainsnak.datavalue.value
end  
---
-- roundto takes a number (x)
-- and returns it rounded to (sf) significant figures
---
-- Dependencies:
---
local roundto = function(x, sf)
if x == 0 then return 0 end
local s = 1
if x < 0 then
x = -x
s = -1
end
if sf < 1 then sf = 1 end
local p = 10 ^ (math.floor(math.log10(x)) - sf + 1)
x = math.floor(x / p + 0.5) * p * s
-- if it's integral, cast to an integer:
if x == math.floor(x) then x = math.floor(x) end
return x
end  
---
-- decimalToDMS takes a decimal degrees (x) with precision (p)
-- and returns degrees/minutes/seconds according to the precision
---
-- Dependencies:
---
local decimalToDMS = function(x, p)
-- if p is not supplied, use a precision around 0.1 seconds
if not tonumber(p) then p = 1e-4 end
local d = math.floor(x)
local ms = (x - d) * 60
if p > 0.5 then -- precision is > 1/2 a degree
if ms > 30 then d = d + 1 end
ms = 0
end
local m = math.floor(ms)
local s = (ms - m) * 60
if p > 0.008 then -- precision is > 1/2 a minute
if s > 30 then m = m +1 end
s = 0
elseif p > 0.00014 then -- precision is > 1/2 a second