Edge Rewrite
// HTMLRewriter · presentation

This page was redesigned at the edge.

Cloudflare fetched the original article and streamed it through HTMLRewriter to apply an entirely new visual system without rebuilding the source page.

// request.cf · coarse context

A page that knows where it met you.

Only coarse request metadata is shown. This demo does not display or persist visitor IP addresses.

Country
US
Cloudflare location
CMH
Connection
HTTP/2
Language
Not provided

Ray ID: a22f6eb7afb7d685

Jump to content

Module:Settlement Wikidata

Permanently protected module
From Wikipedia, the free encyclopedia

require('strict')

local p = {}

-- 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",
	P585 = "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

-- In some situations (e.g., France census) we want to link to a specific data page instead of
-- a generic census landing page. urlMap is a list of tuples:
-- (url to substitute, property to use in the substitution, url to use in the substitution)
-- the url to use in the substitution has ***** where it wants to swap in the property
local urlMap = {
{'https://www.insee.fr/fr/statistiques/8681011','P374','https://www.insee.fr/fr/statistiques/8643952?geo=COM-*****'},
{'https://www.insee.fr/fr/statistiques/8290669','P374','https://www.insee.fr/fr/statistiques/8288323?geo=COM-*****'}
}

local function mapURL(qid,origURL)
    if not qid or not origURL then
        return origURL
    end
    for _, mapper in ipairs(urlMap) do
        if origURL == mapper[1] then
            local statements = mw.wikibase.getBestStatements(qid,mapper[2])
            for _, statement in ipairs(statements) do
                local prop = fetchNested(statement,{'mainsnak','datavalue','value'})
                if prop then
                    return mw.ustring.gsub(mapper[3],'*****',prop)
                end
            end
        end
    end
    return origURL
end

-- function to assemble wikicode to create citation, given reference property
local function citeSource(qid,snaks)
    local args = {}
    args.mode = "cs1"
    scanSnaks(args, snaks)
    args.url = mapURL(qid,args.url)
    -- 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

local rankToNumeric = {truth = 3, preferred = 2, normal = 1, deprecated = -1}

local function numericRank(rank)
   if not rank then
       return 0
   end
   return rankToNumeric[rank] or 0
end

local function compareRanks(rank1,rank2)
   local numeric1 = numericRank(rank1)
   local numeric2 = numericRank(rank2)
   if numeric1 > numeric2 then
       return 1
   elseif numeric1 < numeric2 then
       return -1
   else
       return 0
   end
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)
-- 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)
    local results = nil
    local storedDate = nil
    local storedRank = nil
    local statements = mw.wikibase.getAllStatements(qid, pid) 
    for _, statement in pairs(statements) do
        local amount
        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
        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
        end
        local rank = fetchNested(statement,{'rank'})
        -- 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 ipairs(refs) do
                local renderedRef = ref.snaks and mw.wikibase.renderSnaks(ref.snaks)
                if renderedRef and not mw.ustring.find(renderedRef,'Wiki') then
					local citation = citeSource(qid,ref.snaks)
					if citation then
				        source = (source or '')..'<ref>'..citation..'</ref>'
					end
                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
			local betterRank = compareRanks(rank,storedRank)
			-- ranking logic:
			-- 1. Higher ranked statements are better than lower ranked
			-- 2. At the same rank, dated statements are better than undated
			-- 3. Between dated statements at the same rank, choose the latest one
			-- 4. All else being equal, choose the first statement
			-- 5. Ignore deprecated statements
		    if betterRank > 0 or (betterRank == 0 and date and (not storedDate or date > storedDate)) then
                results = theseResults
                storedDate = date
                storedRank = rank
			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)
    return p._getValueAndRef(qid,"P1082",nil)
end

-- for a qid, find the area of the entity in square kilometers
function p._area(qid)
    return p._getValueAndRef(qid,"P2046","Q712226") -- 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)
    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(results.source)
    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