Modulo:Uzanto:Doesle/Wikidata

El Vikipedio, la libera enciklopedio

Dokumentado por ĉi tiu modulo povas esti kreata ĉe Modulo:Uzanto:Doesle/Wikidata/dokumentado

-- module local variables
local wiki =
{
    langcode = mw.language.getContentLanguage().code
}

-- internationalisation
local i18n = {
    ["errors"] = {
        ["property-not-found"] = "Trajto ne trovita.",
        ["entity-not-found"] = "Wikidata-ero ne trovita.",
        ["unknown-claim-type"] = "Nekonata tipo de deklaro.",
        ["unknown-entity-type"] = "Nekonata tipo de ento.",
        ["qualifier-not-found"] = "Kvalifikatoro ne trovita.",
        ["site-not-found"] = "Wikimedia-projekto ne trovita.",
    },
    ["datetime"] =
    {
        -- $1 is a placeholder for the actual number
        [0] = "$1 Mrd. da jaroj",    -- precision: billion years
        [1] = "$100 Mio. da jaroj",    -- precision: hundred million years
        [2] = "$10 Mio. da jaroj",    -- precision: ten million years
        [3] = "$1 Mio. da jaroj",    -- precision: million years
        [4] = "$100.000 jaroj",        -- precision: hundred thousand years
        [5] = "$10.000 jaroj",        -- precision: ten thousand years
        [6] = "$1-a jarmilo",         -- precision: millenium
        [7] = "$1-a jarcento",        -- precision: century
        [8] = "$1-aj",                -- precision: decade
        -- the following use the format of #time parser function
        [9]  = "Y",                    -- precision: year,
        [10] = "F Y",                -- precision: month
        [11] = 'j"-a de" F Y',            -- precision: day
        [12] = 'j"-a de" F Y, G "horo"',    -- precision: hour
        [13] = 'j"-a de" F Y G:i',        -- precision: minute
        [14] = 'j"-a de" F Y G:i:s',        -- precision: second
        ["beforenow"] = "antaŭ $1",    -- how to format negative numbers for precisions 0 to 5
        ["afternow"] = "en $1",        -- how to format positive numbers for precisions 0 to 5
        ["bc"] = '$1 "a.Kr."',        -- how print negative years
        ["ad"] = "$1"                -- how print positive years
    },
    ["monolingualtext"] = '<span lang="%language">%text</span>',
    ["FETCH_WIKIDATA"] = "ABFRAGE_WIKIDATA"
}

--important properties
local propertyId = {
    ["starttime"] = "P580",
    ["endtime"] = "P582"
}

local formatchar = {
    [10] = {"n","m","M","F","xg"},            --precision: month
    [11] = {"W","j","d","z","D","l","N","w"}, --precision: day
    [12] = {"a","A","g","h","G","H"},         --precision: hour
    [13] = {"i"},                             --precision: minute
    [14] = {"s","U"}                          --precision: second
}

local p = { }

local function printError(code)
    return '<span class="error">' .. (i18n.errors[code] or code) .. '</span>'
end

-- the "qualifiers" and "snaks" field have a respective "qualifiers-order" and "snaks-order" field
-- use these as the second parameter and this function instead of the built-in "pairs" function
-- to iterate over all qualifiers and snaks in the intended order.
local function orderedpairs(array, order)
    if not order then return pairs(array) end
    
    -- return iterator function
    local i = 0
    return function()
        i = i + 1
        if order[i] then
            return order[i], array[order[i]]
        end
    end    
end

function p.descriptionIn(frame)
    local langcode = frame.args[1]
    local id = frame.args[2]
    -- return description of a Wikidata entity in the given language or the default language of this Wikipedia site
    local entity = mw.wikibase.getEntity(id)
    if entity and entity.descriptions then
        local desc = entity.descriptions[langcode or wiki.langcode]
        if desc then return desc.value end
    end
end

function p.labelIn(frame)
    local langcode = frame.args[1]
    local id = frame.args[2]
    -- return label of a Wikidata entity in the given language or the default language of this Wikipedia site
    local entity = mw.wikibase.getEntity(id)
    if entity and entity.labels then
        local label = entity.labels[langcode or wiki.langcode]
        if label then return label.value end
    end
end

local function printDatavalueCoordinate(data, parameter)
    -- data fields: latitude [double], longitude [double], altitude [double], precision [double], globe [wikidata URI, usually http://www.wikidata.org/entity/Q2 [earth]]
    if parameter then
        if parameter == "globe" then data.globe = mw.ustring.match(data.globe, "Q%d+") end -- extract entity id from the globe URI
        return data[parameter]
    else
        return data.latitude .. "/" .. data.longitude -- combine latitude and longitude, which can be decomposed using the #titleparts wiki function
    end
end

local function printDatavalueQuantity(data, parameter)
    -- data fields: amount [number], unit [string], upperBound [number], lowerBound [number]
    if parameter then
        return data[paramater]
    else
        return tonumber(data.amount)
    end
end

local function printDatavalueQuantity2(data, parameter)
    -- data fields: amount [number], unit [string], upperBound [number], lowerBound [number]
    if not parameter or parameter == "amount" then
        return tonumber(data.amount)
    elseif parameter == "unit" then
        return mw.ustring.match(data.unit, "Q%d+")
    elseif parameter == "upperBound" then
        return data.upperBound
    elseif parameter == "lowerBound" then
        return data.lowerBound
    end
end

local function normalizeDate(date)
    date = mw.text.trim(date, "+")
    -- extract year
    local yearstr = mw.ustring.match(date, "^\-?%d+")
    local year = tonumber(yearstr)
    -- remove leading zeros of year
    return year .. mw.ustring.sub(date, #yearstr + 1), year
end

-- precision: 0 - billion years, 1 - hundred million years, ..., 6 - millenia, 7 - century, 8 - decade, 9 - year, 10 - month, 11 - day, 12 - hour, 13 - minute, 14 - second
function formatDate(date, precision, timezone, formatstr)
    precision = precision or 11
    date, year = normalizeDate(date)
    date = string.gsub(date, "-00%f[%D]", "-01")
    if year == 0 and precision <= 9 then return "" end
 
     -- precision is 10000 years or more
    if precision <= 5 then
        local factor = 10 ^ ((5 - precision) + 4)
        local y2 = math.ceil(math.abs(year) / factor)
        local relative = mw.ustring.gsub(i18n.datetime[precision], "$1", tostring(y2))
        if year < 0 then
            relative = mw.ustring.gsub(i18n.datetime.beforenow, "$1", relative)
        else
            relative = mw.ustring.gsub(i18n.datetime.afternow, "$1", relative)
        end            
        return relative
    end
 
     -- precision is decades, centuries and millenia
    local era
    if precision == 6 then era = mw.ustring.gsub(i18n.datetime[6], "$1", tostring(math.floor((math.abs(year) - 1) / 1000) + 1)) end
    if precision == 7 then era = mw.ustring.gsub(i18n.datetime[7], "$1", tostring(math.floor((math.abs(year) - 1) / 100) + 1)) end
    if precision == 8 then era = mw.ustring.gsub(i18n.datetime[8], "$1", tostring(math.floor(math.abs(year) / 10) * 10)) end
    if era then
        if year < 0 then era = mw.ustring.gsub(mw.ustring.gsub(i18n.datetime.bc, '"', ""), "$1", era)
        elseif year > 0 then era = mw.ustring.gsub(mw.ustring.gsub(i18n.datetime.ad, '"', ""), "$1", era) end
        return era
    end
    
    -- precision is years or less
    if precision >= 9 then
        --[[ the following code replaces the UTC suffix with the given negated timezone to convert the global time to the given local time
        timezone = tonumber(timezone)
        if timezone and timezone ~= 0 then
            timezone = -timezone
            timezone = string.format("%.2d%.2d", timezone / 60, timezone % 60)
            if timezone[1] ~= '-' then timezone = "+" .. timezone end
            date = mw.text.trim(date, "Z") .. " " .. timezone
        end
        ]]--
        if formatstr then
            for i=(precision+1), 14 do
                for _, ch in pairs(formatchar[i]) do
                    if formatstr:find(ch) then
                        formatstr = i18n.datetime[precision]
                    end
                end
            end    
        else
            formatstr = i18n.datetime[precision]
        end
        if year == 0 then formatstr = mw.ustring.gsub(formatstr, i18n.datetime[9], "")
        elseif year < 0 then
            -- Mediawiki formatDate doesn't support negative years
            date = mw.ustring.sub(date, 2)
            formatstr = mw.ustring.gsub(formatstr, i18n.datetime[9], mw.ustring.gsub(i18n.datetime.bc, "$1", i18n.datetime[9]))
        elseif year > 0 and i18n.datetime.ad ~= "$1" then
            formatstr = mw.ustring.gsub(formatstr, i18n.datetime[9], mw.ustring.gsub(i18n.datetime.ad, "$1", i18n.datetime[9]))
        end
        return mw.language.new(wiki.langcode):formatDate(formatstr, date)
    end
end

local function printDatavalueTime(data, parameter)
    -- data fields: time [ISO 8601 time], timezone [int in minutes], before [int], after [int], precision [int], calendarmodel [wikidata URI]
    --   precision: 0 - billion years, 1 - hundred million years, ..., 6 - millenia, 7 - century, 8 - decade, 9 - year, 10 - month, 11 - day, 12 - hour, 13 - minute, 14 - second
    --   calendarmodel: e.g. http://www.wikidata.org/entity/Q1985727 for the proleptic Gregorian calendar or http://www.wikidata.org/wiki/Q11184 for the Julian calendar]
    if parameter then
        para, formatstr = parameter:match("([^:]+):([^:]+)")
        if parameter == "calendarmodel" then
            data.calendarmodel = string.match(data.calendarmodel, "Q%d+") -- extract entity id from the calendar model URI
        elseif para and para == "time" then
            return formatDate(data.time, data.precision, data.timezone,formatstr)
        elseif parameter == "time" then
            data.time = normalizeDate(data.time)
        end
        return data[parameter]
    else
        return formatDate(data.time, data.precision, data.timezone)
    end
end

local function printDatavalueEntity(data, parameter)
    -- data fields: entity-type [string], numeric-id [int, Wikidata id]
    local id
    
    if data["entity-type"] == "item" then id = "Q" .. data["numeric-id"]
    elseif data["entity-type"] == "property" then id = "P" .. data["numeric-id"]
    else return printError("unknown-entity-type")
    end
    
    if parameter then
        if parameter == "link" then
            local linkTarget = mw.wikibase.sitelink(id)
            local linkName = mw.wikibase.label(id)
            if linkTarget then
                local link = linkTarget
                -- if there is a local Wikipedia article linking to it, use the label or the article title
                if linkName and (linkName ~= linkTarget) then link = link .. "|" .. linkName end
                return "[[" .. link .. "]]"
            else
                -- if there is no local Wikipedia article output the label or link to the Wikidata object to input a proper label
                if linkName then return linkName else return "[[:d:" .. id .. "|" .. id .. "]]" end
            end
        else
            return data[parameter]
        end
    else
        return mw.wikibase.label(id) or id
    end
end

local function printDatavalueMonolingualText(data, parameter)
    -- data fields: language [string], text [string]
    if parameter then
        return data[parameter]
    else
        local result = mw.ustring.gsub(mw.ustring.gsub(i18n.monolingualtext, "%%language", data["language"]), "%%text", data["text"])
        return result
    end
end

function getSnakValue(snak, parameter)
    -- snaks have three types: "novalue" for null/nil, "somevalue" for not null/not nil, or "value" for actual data
    if snak.snaktype == "value" then
        -- call the respective snak parser
        if snak.datavalue.type == "string" then return snak.datavalue.value
        elseif snak.datavalue.type == "globecoordinate" then return printDatavalueCoordinate(snak.datavalue.value, parameter)
        elseif snak.datavalue.type == "quantity" then return printDatavalueQuantity2(snak.datavalue.value, parameter)
        elseif snak.datavalue.type == "time" then return printDatavalueTime(snak.datavalue.value, parameter)
        elseif snak.datavalue.type == "wikibase-entityid" then return printDatavalueEntity(snak.datavalue.value, parameter)
        elseif snak.datavalue.type == "monolingualtext" then return printDatavalueMonolingualText(snak.datavalue.value, parameter)
        end
    end
    return mw.wikibase.renderSnak(snak)
end

function getQualifierSnak(claim, qualifierId)
    -- a "snak" is Wikidata terminology for a typed key/value pair
    -- a claim consists of a main snak holding the main information of this claim,
    -- as well as a list of attribute snaks and a list of references snaks
    if qualifierId then
        -- search the attribute snak with the given qualifier as key
        if claim.qualifiers then
            local qualifier = claim.qualifiers[qualifierId]
            if qualifier then return qualifier[1] end
        end
        return "getQualifierSnak error", printError("qualifier-not-found")
    else
        -- otherwise return the main snak
        return claim.mainsnak
    end
end

local function datavalueTimeToDateObject(data)
    local sign, year, month, day, hour, minute, second = string.match(data.time, "(.)(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)Z")
    local result =
    {
        year = tonumber(year),
        month = tonumber(month),
        day = tonumber(day),
        hour = tonumber(hour),
        min = tonumber(minute),
        sec = tonumber(second),
        timezone = data.timezone,
        julian = data.calendarmodel and string.match(data.calendarmodel, "Q11184$")
    }
    if sign == "-" then result.year = -result.year end
    return result
end

function julianDay(dateObject)
    local year = dateObject.year
    local month = dateObject.month or 0
    local day = dateObject.day or 0
    
    if month == 0 then month = 1 end
    if day == 0 then day = 1 end
    if month <= 2 then
        year = year - 1
        month = month + 12
    end
    
    local time = ((((dateObject.sec or 0) / 60 + (dateObject.min or 0) + (dateObject.timezone or 0)) / 60) + (dateObject.hour or 0)) / 24

    local b
    if dateObject.julian then b = 0 else
        local century = math.floor(year / 100)
        b = 2 - century + math.floor(century / 4)
    end
            
    return math.floor(365.25 * (year + 4716)) + math.floor(30.6001 * (month + 1)) + day + time + b - 1524.5
end

function getQualifierSortValue(claim, qualifierId)
    local snak = getQualifierSnak(claim, qualifierId)
    if snak and snak.snaktype == "value" then
        if snak.datavalue.type == "time" then
            return julianDay(datavalueTimeToDateObject(snak.datavalue.value))
        else
            return getSnakValue(snak)
        end
    end
end

function getValueOfClaim(claim, qualifierId, parameter)
    local error
    local snak
    snak, error = getQualifierSnak(claim, qualifierId)
    if snak then
        return getSnakValue(snak, parameter)
    else
        return nil, error
    end
end

function getReferences(frame, claim)
    local result = ""
    -- traverse through all references
    for ref in pairs(claim.references or {}) do
        local refparts
        -- traverse through all parts of the current reference
        for snakkey, snakval in orderedpairs(claim.references[ref].snaks or {}, claim.references[ref]["snaks-order"]) do
            if refparts then refparts = refparts .. ", " else refparts = "" end
            -- output the label of the property of the reference part, e.g. "imported from" for P143
            refparts = refparts .. tostring(mw.wikibase.label(snakkey)) .. ": "
            -- output all values of this reference part, e.g. "German Wikipedia" and "English Wikipedia" if the referenced claim was imported from both sites
            for snakidx = 1, #snakval do
                if snakidx > 1 then refparts = refparts .. ", " end
                refparts = refparts .. getSnakValue(snakval[snakidx])
            end
        end
        if refparts then result = result .. frame:extensionTag("ref", refparts) end
    end
    return result
end

local function hasqualifier(claim, qualifierproperty)
    local negotiate
    if string.sub(qualifierproperty, 1, 1) == "!" then negotiate = true else negotiate = false end
    if not claim.qualifiers and not negotiate then return false end
    if not claim.qualifiers and negotiate then return true end
    if qualifierproperty == '' then return true end
    if not negotiate and not claim.qualifiers[qualifierproperty] then return false end
    if negotiate and claim.qualifiers[string.sub(qualifierproperty, 2)] then return false end
    return true
 end

local function hassource(claim, sourceproperty)
    if not claim.references then return false end
    if sourceproperty == '' then return true end
    if string.sub(sourceproperty,1,1) ~= "!" then
        for _, source in pairs(claim.references) do
            if source.snaks[sourceproperty] then return true end
        end
        return false    
    else
        for _, source in pairs(claim.references) do
            for key in pairs(source.snaks) do
                if key ~= string.sub(sourceproperty,2) then return true end
            end
        end
        return false
    end
end

function atdate(claim, mydate)
    local refdate
    if not mydate or mydate == "" then
        refdate = os.date("!*t")
    else
        if string.match(mydate, "^%d+$") then
            refdate = { year = tonumber(mydate) }
        else
            refdate = datavalueTimeToDateObject({ time = mw.language.getContentLanguage():formatDate("+Y-m-d\\TH:i:s\\Z", mydate) })
        end
    end
    local refjd = julianDay(refdate)
    
    local mindate = getQualifierSortValue(claim, propertyId["starttime"])
    local maxdate = getQualifierSortValue(claim, propertyId["endtime"])
    
    if mindate and mindate > refjd then return false end
    if maxdate and maxdate < refjd then return false end
    
    return true
end

--returns a table of claims excluding claims not passed the filters
function filterClaims(frame, claims)
    local function filter(condition, filterfunction)
        if not frame.args[condition] then
            return
        end
        local newclaims = {}
        for i, claim in pairs(claims) do
            if filterfunction(claim, frame.args[condition]) then
                table.insert(newclaims, claim)
            end
        end
        claims = newclaims
    end

    filter('hasqualifier', hasqualifier)
    filter('hassource', hassource)
    filter('atdate', atdate)

    return claims
end

function p.claim(frame)
    local property = frame.args[1] or ""
    local id = frame.args["id"]
    local qualifierId = frame.args["qualifier"]
    local parameter = frame.args["parameter"]
    local language = frame.args["language"]
    local list = frame.args["list"]
    local includeempty = frame.args["includeempty"]
    local references = frame.args["references"]
    local sort = frame.args["sort"]
    local sortInItem = frame.args["sortInItem"]
    local inverse = frame.args["inverse"]
    local showerrors = frame.args["showerrors"]
    local default = frame.args["default"]
    if default then showerrors = nil end

    -- get wikidata entity
    local entity
    if id ~= nil and id ~= "" then
        entity = mw.wikibase.getEntity(id)
    else
        entity = mw.wikibase.getEntity()
    end
    if not entity then
        if showerrors then return printError("entity-not-found") else return default end
    end
    -- fetch the first claim of satisfying the given property
    local claims
    if entity.claims then claims = entity.claims[mw.wikibase.resolvePropertyId(property)] end
    if not claims or not claims[1] then
        if showerrors then return printError("property-not-found") else return default end
    end

    --filter claims
    claims = filterClaims(frame, claims)
    if not claims[1] then return default end

    -- get initial sort indices
    local sortindices = {}
    for idx in pairs(claims) do
        sortindices[#sortindices + 1] = idx
    end
    
    local comparator
    if sort then
        -- sort by time qualifier
        comparator = function(a, b)
            local timea = getQualifierSortValue(claims[a], sort) or ''
            local timeb = getQualifierSortValue(claims[b], sort) or ''
            if type(timea) ~= type(timeb) and not (tonumber(timea) and tonumber(timeb)) then
                if tonumber(timea) then return true
                elseif tonumber(timeb) then return false
                elseif tostring(timea) and tostring(timeb) then
                    if inverse then return tostring(timea) > tostring(timeb) else return tostring(timea) < tostring(timeb) end    
                else return false end -- different types, neither numbers nor strings, no chance to compare => random result to avoid script error
            elseif tonumber(timea) and tonumber(timeb) then
                timea = tonumber(timea)
                timeb = tonumber(timeb)
            end
            if inverse then
                return timea > timeb
            else
                return timea < timeb
            end
        end
    elseif sortInItem then
        -- fill table sortkeys
        local sortkeys = {}
        local snakSingle
        local sortkeyValueId
        local claimContainingValue
        for idx, claim in pairs(claims) do
            snakSingle = getQualifierSnak(claim)
            sortkeyValueId = "Q" .. getSnakValue(snakSingle, "numeric-id")
            claimContainingValue = mw.wikibase.getEntity(sortkeyValueId).claims[mw.wikibase.resolvePropertyId(sortInItem)]
            if claimContainingValue then
                sortkeys[#sortkeys + 1] = getValueOfClaim(claimContainingValue[1])
            else
                sortkeys[#sortkeys + 1] = ""
            end
        end
        comparator = function(a, b)
            if inverse then
                return sortkeys[a] > sortkeys [b]
            else
                return sortkeys[a] < sortkeys [b]
            end
        end
    else
        -- sort by claim rank
        comparator = function(a, b)
            local rankmap = { deprecated = 2, normal = 1, preferred = 0 }
            local ranka = rankmap[claims[a].rank or "normal"] ..  string.format("%08d", a)
            local rankb = rankmap[claims[b].rank or "normal"] ..  string.format("%08d", b)
            return ranka < rankb
        end
     end
    table.sort(sortindices, comparator)

    local result
    local error
    if list then
        list = string.gsub(list, "\\n", "\n") -- if a newline is provided (whose backslash will be escaped) unescape it
        local value
        -- iterate over all elements and return their value (if existing)
        result = {}
        for idx in pairs(claims) do
            local claim = claims[sortindices[idx]]
            value, error =  getValueOfClaim(claim, qualifierId, parameter)
            if not value and value ~= 0 and showerrors then value = error end
            if not value and value ~= 0 and includeempty then value = "" end
            if value and references then value = value .. getReferences(frame, claim) end
            result[#result + 1] = value
        end
        result = table.concat(result, list)
    else
        -- return first element    
        
        local claim = claims[sortindices[1]]
        if language and claim.mainsnak.datatype == "monolingualtext" then
            -- iterate over claims to find adequate language
            for idx, claim in pairs(claims) do
                if claim.mainsnak.datavalue.value.language == language then
                    result, error = getValueOfClaim(claim, qualifierId, parameter)
                    break
                end
            end
        else
            result, error = getValueOfClaim(claim, qualifierId, parameter)
        end
        if result and references then result = result .. getReferences(frame, claim) end
    end
    
    if result then return result else
        if showerrors then return error else return default end
    end
end

function p.faktoroKM2(frame)
    local unit = frame.args[1]
    local showerrors = frame.args["showerrors"]
    local default = frame.args["default"]
    
    local entity = mw.wikibase.getEntity(unit)
    if not entity then
        if showerrors then return printError("p.faktoroKM2: entity ne difinita") else return default end
    end
    local claims = entity.claims[mw.wikibase.resolvePropertyId("P2370")]
    claims = filterClaims(frame, claims)
    if not claims[1] then
        if showerrors then return printError("p.faktoroKM2: claims[1] ne difinita") else return default end  
    end
    local sortindices = {}
    for idx in pairs(claims) do
        sortindices[#sortindices + 1] = idx
    end
    local comparator = function(a, b)
        local rankmap = { deprecated = 2, normal = 1, preferred = 0 }
        local ranka = rankmap[claims[a].rank or "normal"] ..  string.format("%08d", a)
        local rankb = rankmap[claims[b].rank or "normal"] ..  string.format("%08d", b)
        return ranka < rankb
    end
    table.sort(sortindices, comparator)
    local claim = claims[sortindices[1]]
    local rezulto
    local eraro
    rezulto, eraro = getSnakValue(claim.mainsnak, "amount")
    if eraro ~= nil then
        return "faktoroKM2: " .. eraro
    end
    return 1e6 / rezulto
end

function p.areoKMHA(frame)
    local unuo = frame.args["unuo"] or frame.args[1] or "km2"
    local id = frame.args["id"]
    local showerrors = frame.args["showerrors"]
    local default = frame.args["default"]
    
    local entity
    if id ~= nil and id ~= "" then
        entity = mw.wikibase.getEntity(id)
    else
        entity = mw.wikibase.getEntity()
    end
    if not entity then
        if showerrors then return printError("p.areoKMHA (areo): entity ne difinita") else return default end
    end
    local claims = entity.claims[mw.wikibase.resolvePropertyId("P2046")]
    claims = filterClaims(frame, claims)
    if not claims[1] then
        if showerrors then return printError("p.areoKMHA: claims[1] ne difinita") else return default end
    end
    local comparator = function(a, b)
        local rankmap = { deprecated = 2, normal = 1, preferred = 0 }
        local ranka = rankmap[claims[a].rank or "normal"] ..  string.format("%08d", a)
        local rankb = rankmap[claims[b].rank or "normal"] ..  string.format("%08d", b)
        return ranka < rankb
    end
    local sortindices = {}
    
    for idx in pairs(claims) do
        sortindices[#sortindices + 1] = idx
    end
    
    table.sort(sortindices, comparator)
    local claim = claims[sortindices[1]]
    local areonombro
    local areoaunuo
    local eraro
    areonombro, eraro = getSnakValue(claim.mainsnak, "amount")
    if eraro ~= nil then
        if showerrors then return printError("p.areoKMHA (areonombro): " .. eraro) else return default end
    end
    areounuo, eraro = getSnakValue(claim.mainsnak, "unit")
    if eraro ~= nil then
        if showerrors then return printError("p.areoKMHA (areounuo): " .. eraro) else return default end
    end
    
    entity = mw.wikibase.getEntity(areounuo)
    if not entity then
        if showerrors then return printError("p.areoKMHA (areounuo): entity ne difinita") else return default end
    end
    claims = entity.claims[mw.wikibase.resolvePropertyId("P2370")]
    claims = filterClaims(frame, claims)
    if not claims[1] then
        if showerrors then return printError("p.areoKMHA (areounuo): claims[1] ne difinita") else return default end
    end
    sortindices = {}
    for idx in pairs(claims) do
        sortindices[#sortindices + 1] = idx
    end
    table.sort(sortindices, comparator)
    claim = claims[sortindices[1]]
    rezulto, eraro = getSnakValue(claim.mainsnak, "amount")
    if eraro ~= nil then
        if showerrors then return printError("p.areoKMHA (areounuo P2370): " .. eraro) else return default end
    end
    if unuo == "ha" then
        return areonombro / rezulto * 1e8
    else
        return areonombro / rezulto * 1e6
    end
end

function p.areoKM2(frame)
    frame.args["unuo"] = "km2"
    return p.areoKMHA(frame, "km2")
end

function p.areoHA(frame)
    frame.args["unuo"] = "ha"
    return p.areoKMHA(frame, "ha")
end

function p.getValue(frame)
    local param = frame.args[2]
    if param == "FETCH_WIKIDATA" or param == i18n["FETCH_WIKIDATA"] then return p.claim(frame) else return param end
end

function p.pageId(frame)
    local id = frame.args[1]
    return p._pageId(id)
end    

-- for use in modules only
function p._pageId(id)    
    local entity
    if id ~= nil and id ~= "" then
        entity = mw.wikibase.getEntity(id)
    else
        entity = mw.wikibase.getEntity()
    end
    if not entity then return nil else return entity.id end
end

function p.labelOf(frame)
    local id = frame.args[1]
    -- returns the label of the given entity/property id
    -- if no id is given, the one from the entity associated with the calling Wikipedia article is used
    if not id then
        local entity = mw.wikibase.getEntity()
        if not entity then return printError("entity-not-found") end
        id = entity.id
    end
    return mw.wikibase.label(id)
end

function p.sitelinkOf(frame)
    local id = frame.args[1]
    -- returns the Wikipedia article name of the given entity
    -- if no id is given, the one from the entity associated with the calling Wikipedia article is used
    if not id then
        local entity = mw.wikibase.getEntity()
        if not entity then return printError("entity-not-found") end
        id = entity.id
    end
    return mw.wikibase.sitelink(id)
end

function p.badges(frame)
    local site = frame.args[1]
    local id = frame.args[2]
    if not site then return printError("site-not-found") end
    local entity = mw.wikibase.getEntity(id)
    if not entity then return printError("entity-not-found") end
    local badges = entity.sitelinks[site].badges
    if badges then
        local result
        for idx = 1, #badges do
            if result then result = result .. "/" .. badges[idx] else result = badges[idx] end
        end
        return result
    end
end

function p.sitelinkCount(frame)
    local filter = "^.*" .. (frame.args[1] or "") .. "$"
    local id = frame.args[2]
    
    local entity = mw.wikibase.getEntity(id)
    local count = 0
    if entity and entity.sitelinks then
        for project, _ in pairs(entity.sitelinks) do
            if string.find(project, filter) then count = count + 1 end
        end
    end
    return count
end

-- call this in cases of script errors within a function instead of {{#invoke:Wikidata|<method>|...}} call {{#invoke:Wikidata|debug|<method>|...}}
function p.debug(frame)
    local func = frame.args[1]
    if func then
        -- create new parameter set, where the first parameter with the function name is removed
        local newargs = {}
        for key, val in pairs(frame.args) do
            if type(key) == "number" then
                if key > 1 then newargs[key - 1] = val end
            else
                newargs[key] = val
            end
        end
        frame.args = newargs
        local status, result = pcall(p[func], frame)
        if status then return result else return '<span class="error">' .. result .. '</span>' end
    else
        return '<span class="error">invalid parameters</span>'
    end
end

function p.printEntity(frame)
    local id = frame.args[1]
    local entity = mw.wikibase.getEntity(id)
    if entity then return "<pre>" .. mw.text.jsonEncode(entity, mw.text.JSON_PRETTY) .. "</pre>" end
end

-- from the english module
-- This is used to get a date value for date_of_birth (P569), etc. which won't
-- be linked -- consolidate by testing if entity.claims[propertyID].mainsnak.datavalue.type
-- is "time". Dates and times are stored in ISO 8601 format.
p.getDateValue = function(frame)
    local propertyID = mw.text.trim(frame.args[1] or "")
    local input_parm = mw.text.trim(frame.args[2] or "")
    local date_format = mw.text.trim(frame.args[3] or "dmy")
    local date_suffix = mw.text.trim(frame.args[4] or "BC")
    local id = frame.args["id"]
    local showerrors = frame.args["showerrors"]    
    local default = frame.args["default"]
    if input_parm == "FETCH_WIKIDATA" or input_parm == i18n["FETCH_WIKIDATA"] then
        --local entity = mw.wikibase.getEntityObject()
        local entity = mw.wikibase.getEntity(id)
        if not entity then
            if showerrors then return printError("entity-not-found") else return default end
        end
        
        if entity.claims[propertyID] ~= nil then
            local out = {}
            local dt = {}
            for k, v in pairs(entity.claims[propertyID]) do
                if v.mainsnak.snaktype == 'value' then
                    -- check for negative date
                    local suffix = ""
                    local timestamp = v.mainsnak.datavalue.value.time
                    if string.sub(timestamp, 1, 1) == '-' then
                        timestamp = '+' .. string.sub(timestamp, 2)
                        -- suffix = " " .. date_suffix
                        date_suffix = mw.ustring.lower(date_suffix)
                        if date_suffix == "bc" or date_suffix == "bce" then
                            date_suffix = "bc"
                        suffix = " " .. i18n.datetime.bc
                        suffix = mw.ustring.gsub(suffix,"$1 ","",1)
                        suffix = mw.ustring.gsub(suffix,'"',"")                        
                        end
                    end
                    local function d(f)
                        return mw.language.new(wiki.langcode):formatDate(f, timestamp) .. suffix
                    end
                    if date_format == "mdy" then
                        out[#out + 1] = d("F j, Y")
                    elseif date_format == "my" then
                        out[#out + 1] = d("F Y")
                    elseif date_format == "y" then
                        -- suppress leading zeros in year
                        local stryear = d("Y")
                        while string.sub(stryear, 1, 1) == '0' do
                            stryear = string.sub(stryear, 2)
                        end
                        out[#out + 1] = stryear
                    -- for a date with accusative    
                    elseif date_format == "dmya" then    
                        out[#out + 1] = d('j"-an de" F Y')
                    else
                        --out[#out + 1] = d("j F Y")
                        out[#out + 1] = d('j"-a de" F Y')
                    end
                end
            end
            return table.concat(out, ", ")
        else
            return ""
        end
    else
        return input_parm
    end
end

-- from the english module
p.getQualifierDateValue = function(frame)
    local propertyID = mw.text.trim(frame.args[1] or "")
    local qualifierID = mw.text.trim(frame.args[2] or "")
    local input_parm = mw.text.trim(frame.args[3] or "")
    local date_format = mw.text.trim(frame.args[4] or "dmy")
    local id = frame.args["id"]
    local showerrors = frame.args["showerrors"]    
    local default = frame.args["default"]    
    if input_parm == "FETCH_WIKIDATA" or input_parm == i18n["FETCH_WIKIDATA"] then
        -- local entity = mw.wikibase.getEntityObject()
        local entity = mw.wikibase.getEntity(id)

        if not entity then
            if showerrors then return printError("entity-not-found") else return default end
        end        
        
        if entity.claims[propertyID] ~= nil then
            local out = {}
            for k, v in pairs(entity.claims[propertyID]) do
                for k2, v2 in pairs(v.qualifiers[qualifierID]) do
                    if v2.snaktype == 'value' then
                        local function d(f)
                            return mw.language.new(wiki.langcode):formatDate(f, v2.datavalue.value.time)
                        end
                        if date_format == "mdy" then
                            out[#out + 1] = d("F j, Y")
                        elseif date_format == "my" then
                            out[#out + 1] = d("F Y")
                        elseif date_format == "y" then
                            out[#out + 1] = d("Y")
                        -- for a date with accusative    
                        elseif date_format == "dmya" then    
                            out[#out + 1] = d('j"-an de" F Y')                            
                        else
                            -- out[#out + 1] = d("j F Y")
                            out[#out + 1] = d('j"-a de" F Y')
                        end
                    end
                end
            end
            return table.concat(out, ", ")
        else
            return ""
        end
    else
        return input_parm
    end
end


return p