Ava peamenüü

Moodul:Kasutaja:WikedKentaur/WikidataDev

Selle mooduli dokumentatsiooni saab kirjutada asukohta Moodul:Kasutaja:WikedKentaur/WikidataDev/doc.

-- testimisjärgus Wikidata funktsioonid
-- muudatused
-- 27.03.2018  parameetrinimed põhimooduliga sarnaseks: sort, inverse, id

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

-- internationalisation
local i18n = {
    ["errors"] = {
        ["property-not-found"] = "Omadust ei leitud.",
        ["entity-not-found"] = "Wikidata andmeüksust ei leitud.",
        ["unknown-claim-type"] = "Unknown claim type.",
        ["unknown-entity-type"] = "Unknown entity type.",
        ["qualifier-not-found"] = "Qualifier not found.",
        ["site-not-found"] = "Wikimedia site not found.",        
    },
    ["datetime"] =
	{
		-- $1 is a placeholder for the actual number
		[0] = "$1 mld aastat",		-- precision: billion years
		[1] = "$100 mln aastat",	-- precision: hundred million years
		[2] = "$10 mln aastat",	-- precision: ten million years
		[3] = "$1 mln aastat",		-- precision: million years
		[4] = "$100.000 aastat",	-- precision: hundred thousand years
		[5] = "$10.000 aastat",		-- precision: ten thousand years
		[6] = "$1. aastatuhat", 	-- precision: millenium
		[7] = "$1. sajand",	-- precision: century
		[8] = "$1er",			-- precision: decade
		-- the following use the format of #time parser function
		[9]  = "Y",			-- precision: year, 
		[10] = "F Y",			-- precision: month
		[11] = "j.m.Y",		-- precision: day
		[12] = 'j.m.Y, "kell" G',	-- precision: hour
		[13] = "j.m.Y G:i",		-- precision: minute
		[14] = "j.m.Y G:i:s",		-- precision: second
		["beforenow"] = "vor $1",	-- how to format negative numbers for precisions 0 to 5
		["afternow"] = "in $1",		-- how to format positive numbers for precisions 0 to 5
		["bc"] = '$1 "eKr."',		-- 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 not parameter or parameter == "amount" then
		return tonumber(data.amount)
	elseif parameter == "unit" then	
		return mw.ustring.match(data.unit, "Q%d+")
	else
		return data[parameter]
	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 printDatavalueQuantity(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 nil, 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 = ""
	local prefcache = i18n.datetime[11]
	i18n.datetime[11] = "j.m.Y"
 
	-- traverse through all references
	for ref in pairs(claim.references or {}) do
		local refparts = "" 
		local outtext = ""
		local desc = ""
		local accessed = ""
		local url = ""
		-- traverse through all parts of the current reference
		for snakkey, snakval in orderedpairs(claim.references[ref].snaks or {}, claim.references[ref]["snaks-order"]) do
			local currentref = ""
			if #refparts > 0 then refparts = refparts .. ", " end
			-- 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 currentref = currentref .. ", " end
				if getSnakValue(snakval[snakidx]) then currentref = currentref .. getSnakValue(snakval[snakidx]) end
			end
			refparts = refparts .. currentref
			if snakkey == 'P248' then
				desc = currentref
			elseif snakkey == 'P813' then
				accessed = currentref
			elseif snakkey == 'P854' then 
				url = currentref
			end
		end
		if #url > 0 then
			if #desc > 0 then
				outtext = "[" .. url .. " " .. desc .. "]"
			else
				outtext = url
			end
			if #accessed > 0 then
				outtext = outtext .. ", vaadatud " .. accessed
			end
			if outtext then result = result .. frame:extensionTag("ref", outtext)
			end
		elseif refparts then result = result .. frame:extensionTag("ref", refparts) 
		end
	end
	i18n.datetime[11] = prefcache
	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 = mw.wikibase.getEntity(id)
	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 references == "only" then
			result = getReferences(frame, claim)
		else
			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
		
	end
	
	if result then return result else
		if showerrors then return error else return default end
	end 
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]
	local entity = mw.wikibase.getEntity(id)
	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

return p