Permanently protected module
From Wikipedia, the free encyclopedia


-- vim: set noexpandtab ft=lua ts=4 sw=4:

require('strict')



local p = {}

local debug = false





------------------------------------------------------------------------------

-- module local variables and functions



local wiki =

{

	langcode = mw.language.getContentLanguage().code

}



-- internationalisation

local i18n =

{

	"errors" =

	{

		"property-not-found" = "Property not found.",

		"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.",

		"unknown-datetime-format" = "Unknown datetime format.",

		"local-article-not-found" = "Article is not yet available in this wiki."

	},

	"datetime" =

	{

		-- $1 is a placeholder for the actual number

		0 = "$1 billion years",	-- precision: billion years

		1 = "$100 million years",	-- precision: hundred million years

		2 = "$10 million years",	-- precision: ten million years

		3 = "$1 million years",	-- precision: million years

		4 = "$100,000 years",		-- precision: hundred thousand years

		5 = "$10,000 years",		-- precision: ten thousand years

		6 = "$1 millennium",		-- precision: millennium

		7 = "$1 century",			-- precision: century

		8 = "$1s",				-- precision: decade

		-- the following use the format of #time parser function

		9  = "Y",					-- precision: year,

		10 = "F Y",				-- precision: month

		11 = "F j, Y",			-- precision: day

		12 = "F j, Y ga",			-- precision: hour

		13 = "F j, Y g:ia",		-- precision: minute

		14 = "F j, Y g:i:sa",		-- precision: second

		"beforenow" = "$1 BCE",	-- how to format negative numbers for precisions 0 to 5

		"afternow" = "$1 CE",		-- how to format positive numbers for precisions 0 to 5

		"bc" = '$1 "BCE"',		-- how print negative years

		"ad" = "$1",				-- how print positive years

		-- the following are for function getDateValue() and getQualifierDateValue()

		"default-format" = "dmy", -- default value of the #3 (getDateValue) or

									-- #4 (getQualifierDateValue) argument

		"default-addon" = "BC",	-- default value of the #4 (getDateValue) or

									-- #5 (getQualifierDateValue) argument

		"prefix-addon" = false,	-- set to true for languages put "BC" in front of the

									-- datetime string; or the addon will be suffixed

		"addon-sep" = " ",		-- separator between datetime string and addon (or inverse)

		"format" =				-- options of the 3rd argument

		{

			"mdy" = "F j, Y",

			"my" = "F Y",

			"y" = "Y",

			"dmy" = "j F Y",

			"ymd" = "Y-m-d",

			"ym" = "Y-m"

		}

	},

	"monolingualtext" = '<span lang="%language">%text</span>',

	"warnDump" = "[[Category:Called function 'Dump' from module Wikidata]]",

	"ordinal" =

	{

		1 = "st",

		2 = "nd",

		3 = "rd",

		"default" = "th"

	}

}



if wiki.langcode ~= "en" then

	--require("Module:i18n").loadI18n("Module:Wikidata/i18n", i18n)

	-- got idea from [[:w:Module:Wd]]

	local module_title; if ... == nil then

		module_title = mw.getCurrentFrame():getTitle()

	else

		module_title = ...

	end

	require('Module:i18n').loadI18n(module_title..'/i18n', i18n)

end



-- this function needs to be internationalised along with the above:

-- takes cardinal numer as a numeric and returns the ordinal as a string

-- we need three exceptions in English for 1st, 2nd, 3rd, 21st, .. 31st, etc.

local function makeOrdinal (cardinal)

	local ordsuffix = i18n.ordinal.default

	if cardinal % 10 == 1 then

		ordsuffix = i18n.ordinal1

	elseif cardinal % 10 == 2 then

		ordsuffix = i18n.ordinal2

	elseif cardinal % 10 == 3 then

		ordsuffix = i18n.ordinal3

	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



local function printError(code)

	return '<span class="error">' .. (i18n.errorscode or code) .. '</span>'

end

local function parseDateFormat(f, timestamp, addon, prefix_addon, addon_sep) 

	local year_suffix

	local tstr = ""

	local lang_obj = mw.language.new(wiki.langcode)

	local f_parts = mw.text.split(f, 'Y', true)

	for idx, f_part in pairs(f_parts) do

		year_suffix = ''

		if string.match(f_part, "x[mijkot]$") then

			-- for non-Gregorian year

			f_part = f_part .. 'Y'

		elseif idx < #f_parts then

			-- supress leading zeros in year

			year_suffix = lang_obj:formatDate('Y', timestamp)

			year_suffix = string.gsub(year_suffix, '^0+', '', 1)

		end

		tstr = tstr .. lang_obj:formatDate(f_part, timestamp) .. year_suffix

	end

	if addon ~= "" and prefix_addon then

		return addon .. addon_sep .. tstr

	elseif addon ~= "" then

		return tstr .. addon_sep .. addon

	else

		return tstr

	end

end

local function parseDateValue(timestamp, date_format, date_addon)

	local prefix_addon = i18n"datetime"]["prefix-addon"

	local addon_sep = i18n"datetime"]["addon-sep"

	local addon = ""



	-- check for negative date

	if string.sub(timestamp, 1, 1) == '-' then

		timestamp = '+' .. string.sub(timestamp, 2)

		addon = date_addon

	end

	local _date_format = i18n"datetime"]["format"][date_format

	if _date_format ~= nil then

		return parseDateFormat(_date_format, timestamp, addon, prefix_addon, addon_sep)

	else

		return printError("unknown-datetime-format")

	end

end



-- This local function combines the year/month/day/BC/BCE handling of parseDateValue{}

-- with the millennium/century/decade handling of formatDate()

local function parseDateFull(timestamp, precision, date_format, date_addon)

	local prefix_addon = i18n"datetime"]["prefix-addon"

	local addon_sep = i18n"datetime"]["addon-sep"

	local addon = ""



	-- check for negative date

	if string.sub(timestamp, 1, 1) == '-' then

		timestamp = '+' .. string.sub(timestamp, 2)

		addon = date_addon

	end



	-- get the next four characters after the + (should be the year now in all cases)

	-- ok, so this is dirty, but let's get it working first

	local intyear = tonumber(string.sub(timestamp, 2, 5))

	if intyear == 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(intyear) / factor)

		local relative = mw.ustring.gsub(i18n.datetimeprecision], "$1", tostring(y2))

		if addon ~= "" then

			-- negative date

			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 (8), centuries (7) and millennia (6)

	local era, card

	if precision == 6 then

		card = math.floor((intyear - 1) / 1000) + 1

		era = mw.ustring.gsub(i18n.datetime6], "$1", makeOrdinal(card))

	end

	if precision == 7 then

		card = math.floor((intyear - 1) / 100) + 1

		era = mw.ustring.gsub(i18n.datetime7], "$1", makeOrdinal(card))

	end

	if precision == 8 then

		era = mw.ustring.gsub(i18n.datetime8], "$1", tostring(math.floor(math.abs(intyear) / 10) * 10))

	end

	if era then

		if addon ~= "" then

			era = mw.ustring.gsub(mw.ustring.gsub(i18n.datetime.bc, '"', ""), "$1", era)

		else

			era = mw.ustring.gsub(mw.ustring.gsub(i18n.datetime.ad, '"', ""), "$1", era)

		end

		return era

	end



	local _date_format = i18n"datetime"]["format"][date_format

	if _date_format ~= nil then

		-- check for precision is year and override supplied date_format

		if precision == 9 then

			_date_format = i18n"datetime"][9

		end

		return parseDateFormat(_date_format, timestamp, addon, prefix_addon, addon_sep)

	else

		return printError("unknown-datetime-format")

	end

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 orderi then

			return orderi], arrayorderi]]

		end

	end

end



-- precision: 0 - billion years, 1 - hundred million years, ..., 6 - millennia, 7 - century, 8 - decade, 9 - year, 10 - month, 11 - day, 12 - hour, 13 - minute, 14 - second

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



local function formatDate(date, precision, timezone)

	precision = precision or 11

	local date, year = normalizeDate(date)

	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.datetimeprecision], "$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 millennia

	local era

	if precision == 6 then era = mw.ustring.gsub(i18n.datetime6], "$1", tostring(math.floor((math.abs(year) - 1) / 1000) + 1)) end

	if precision == 7 then era = mw.ustring.gsub(i18n.datetime7], "$1", tostring(math.floor((math.abs(year) - 1) / 100) + 1)) end

	if precision == 8 then era = mw.ustring.gsub(i18n.datetime8], "$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 year

	if precision == 9 then

		return year

	end



	-- precision is less than years

	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

		]]--



		local formatstr = i18n.datetimeprecision

		if year == 0 then formatstr = mw.ustring.gsub(formatstr, i18n.datetime9], "")

		elseif year < 0 then

			-- Mediawiki formatDate doesn't support negative years

			date = mw.ustring.sub(date, 2)

			formatstr = mw.ustring.gsub(formatstr, i18n.datetime9], mw.ustring.gsub(i18n.datetime.bc, "$1", i18n.datetime9]))

		elseif year > 0 and i18n.datetime.ad ~= "$1" then

			formatstr = mw.ustring.gsub(formatstr, i18n.datetime9], mw.ustring.gsub(i18n.datetime.ad, "$1", i18n.datetime9]))

		end

		return mw.language.new(wiki.langcode):formatDate(formatstr, date)

	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.getSitelink(id)

			local linkName = mw.wikibase.getLabel(id)

			if linkTarget then

				-- if there is a local Wikipedia article link to it using the label or the article title

				return "[[" .. linkTarget .. "|" .. (linkName or linkTarget) .. "]]"

			else

				-- if there is no local Wikipedia article output the label or link to the Wikidata object to let the user input a proper label

				if linkName then return linkName else return "[[:d:" .. id .. "|" .. id .. "]]" end

			end

		else

			return dataparameter

		end

	else

		return mw.wikibase.getLabel(id) or id

	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 - millennia, 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

		if parameter == "calendarmodel" then data.calendarmodel = mw.ustring.match(data.calendarmodel, "Q%d+") -- extract entity id from the calendar model URI

		elseif parameter == "time" then data.time = normalizeDate(data.time) end

		return dataparameter

	else

		return formatDate(data.time, data.precision, data.timezone)

	end

end



local function printDatavalueMonolingualText(data, parameter)

	-- data fields: language [string], text [string]

	if parameter then

		return dataparameter

	else

		local result = mw.ustring.gsub(mw.ustring.gsub(i18n.monolingualtext, "%%language", data"language"]), "%%text", data"text"])

		return result

	end

end



local function findClaims(entity, property)

	if not property or not entity or not entity.claims then return end



	if mw.ustring.match(property, "^P%d+$") then

		-- if the property is given by an id (P..) access the claim list by this id

		return entity.claimsproperty

	else

		property = mw.wikibase.resolvePropertyId(property)

		if not property then return end



		return entity.claimsproperty

	end

end



local function getSnakValue(snak, parameter)

	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



local 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.qualifiersqualifierId

			if qualifier then return qualifier1 end

		end

		return nil, printError("qualifier-not-found")

	else

		-- otherwise return the main snak

		return claim.mainsnak

	end

end



local 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



local 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.referencesref].snaks or {}, claim.referencesref]["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.getLabel(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(snakvalsnakidx])

			end

		end

		if refparts then result = result .. frame:extensionTag("ref", refparts) end

	end

	return result

end



local function parseInput(frame)

	local qid = frame.args.qid

	if qid and (#qid == 0) then qid = nil end

	local propertyID = mw.text.trim(frame.args1 or "")

	local input_parm = mw.text.trim(frame.args2 or "")

	if input_parm ~= "FETCH_WIKIDATA" then

		return false, input_parm, nil, nil

	end

	local entity = mw.wikibase.getEntity(qid)

	local claims

	if entity and entity.claims then

		claims = entity.claimspropertyID

		if not claims then

			return false, "", nil, nil

		end

	else

		return false, "", nil, nil

	end

	return true, entity, claims, propertyID

end

local function isType(claims, type)

	return claims1 and claims1].mainsnak.snaktype == "value" and claims1].mainsnak.datavalue.type == type

end

local function getValue(entity, claims, propertyID, delim, labelHook) 

	if labelHook == nil then

		labelHook = function (qnumber)

			return nil;

		end

	end

	if isType(claims, "wikibase-entityid") then

		local out = {}

		for k, v in pairs(claims) do

			local qnumber = "Q" .. v.mainsnak.datavalue.value"numeric-id"

			local sitelink = mw.wikibase.getSitelink(qnumber)

			local label = labelHook(qnumber) or mw.wikibase.getLabel(qnumber) or qnumber

			if sitelink then

				out#out + 1 = "[[" .. sitelink .. "|" .. label .. "]]"

			else

				out#out + 1 = "[[:d:" .. qnumber .. "|" .. label .. "]]<abbr title='" .. i18n"errors"]["local-article-not-found" .. "'>[*]</abbr>"

			end

		end

		return table.concat(out, delim)

	else

		-- just return best values

		return entity:formatPropertyValues(propertyID).value

	end

end



------------------------------------------------------------------------------

-- module global functions



if debug then

	function p.inspectI18n(frame)

		local val = i18n

		for _, key in pairs(frame.args) do

			key = mw.text.trim(key)

			val = valkey

		end

		return val

	end

end



function p.descriptionIn(frame)

	local langcode = frame.args1

	local id = frame.args2

	-- return description of a Wikidata entity in the given language or the default language of this Wikipedia site

	return mw.wikibase.getEntity(id):getDescription(langcode or wiki.langcode)

end



function p.labelIn(frame)

	local langcode = frame.args1

	local id = frame.args2

	-- return label of a Wikidata entity in the given language or the default language of this Wikipedia site

	return mw.wikibase.getEntity(id):getLabel(langcode or wiki.langcode)

end



-- This is used to get a value, or a comma separated list of them if multiple values exist

p.getValue = function(frame)

	local delimdefault = ", " -- **internationalise later**

	local delim = frame.args.delimiter or ""

	delim = string.gsub(delim, '"', '')

	if #delim == 0 then

		delim = delimdefault

	end

	local go, errorOrentity, claims, propertyID = parseInput(frame)

	if not go then

		return errorOrentity

	end

	return getValue(errorOrentity, claims, propertyID, delim)

end



-- Same as above, but uses the short name property for label if available.

p.getValueShortName = function(frame)

	local go, errorOrentity, claims, propertyID = parseInput(frame)

	if not go then

		return errorOrentity

	end

	local entity = errorOrentity

	-- if wiki-linked value output as link if possible

	local function labelHook (qnumber)

		local label

		local claimEntity = mw.wikibase.getEntity(qnumber)

		if claimEntity ~= nil then

			if claimEntity.claims.P1813 then

				for k2, v2 in pairs(claimEntity.claims.P1813) do

					if v2.mainsnak.datavalue.value.language == "en" then

						label = v2.mainsnak.datavalue.value.text

					end

				end

			end

		end

		if label == nil or label == "" then return nil end

		return label

	end

	return getValue(errorOrentity, claims, propertyID, ", ", labelHook);

end



-- This is used to get a value, or a comma separated list of them if multiple values exist

-- from an arbitrary entry by using its QID.

-- Use : {{#invoke:Wikidata|getValueFromID|<ID>|<Property>|FETCH_WIKIDATA}}

-- E.g.: {{#invoke:Wikidata|getValueFromID|Q151973|P26|FETCH_WIKIDATA}} - to fetch value of 'spouse' (P26) from 'Richard Burton' (Q151973)

-- Please use sparingly - this is an *expensive call*.

p.getValueFromID = function(frame)

	local itemID = mw.text.trim(frame.args1 or "")

	local propertyID = mw.text.trim(frame.args2 or "")

	local input_parm = mw.text.trim(frame.args3 or "")

	if input_parm == "FETCH_WIKIDATA" then

		local entity = mw.wikibase.getEntity(itemID)

		local claims

		if entity and entity.claims then

			claims = entity.claimspropertyID

		end

		if claims then

			return getValue(entity, claims, propertyID, ", ")

		else

			return ""

		end

	else

		return input_parm

	end

end

local function getQualifier(frame, outputHook) 

	local propertyID = mw.text.trim(frame.args1 or "")

	local qualifierID = mw.text.trim(frame.args2 or "")

	local input_parm = mw.text.trim(frame.args3 or "")

	if input_parm == "FETCH_WIKIDATA" then

		local entity = mw.wikibase.getEntity()

		if entity.claimspropertyID ~= nil then

			local out = {}

			for k, v in pairs(entity.claimspropertyID]) do

				for k2, v2 in pairs(v.qualifiersqualifierID]) do

					if v2.snaktype == 'value' then

						out#out + 1 = outputHook(v2);

					end

				end

			end

			return table.concat(out, ", "), true

		else

			return "", false

		end

	else

		return input_parm, false

	end

end

p.getQualifierValue = function(frame)

	local function outputValue(value)

		local qnumber = "Q" .. value.datavalue.value"numeric-id"

		if (mw.wikibase.getSitelink(qnumber)) then

			return "[[" .. mw.wikibase.getSitelink(qnumber) .. "]]"

		else

			return "[[:d:" .. qnumber .. "|" ..qnumber .. "]]<abbr title='" .. i18n"errors"]["local-article-not-found" .. "'>[*]</abbr>"

		end

	end

	return (getQualifier(frame, outputValue))

end



-- This is used to get a value like 'male' (for property p21) which won't be linked and numbers without the thousand separators

p.getRawValue = function(frame)

	local go, errorOrentity, claims, propertyID = parseInput(frame)

	if not go then

		return errorOrentity

	end

	local entity = errorOrentity

	local result = entity:formatPropertyValues(propertyID, mw.wikibase.entity.claimRanks).value

	-- if number type: remove thousand separators, bounds and units

	if isType(claims, "quantity") then

		result = mw.ustring.gsub(result, "(%d),(%d)", "%1%2")

		result = mw.ustring.gsub(result, "(%d)±.*", "%1")

	end

	return result

end



-- This is used to get the unit name for the numeric value returned by getRawValue

p.getUnits = function(frame)

	local go, errorOrentity, claims, propertyID = parseInput(frame)

	if not go then

		return errorOrentity

	end

	local entity = errorOrentity

	local result = entity:formatPropertyValues(propertyID, mw.wikibase.entity.claimRanks).value

	if isType(claims, "quantity") then

		result = mw.ustring.sub(result, mw.ustring.find(result, " ")+1, -1)

	end

	return result

end



-- This is used to get the unit's QID to use with the numeric value returned by getRawValue

p.getUnitID = function(frame)

	local go, errorOrentity, claims = parseInput(frame)

	if not go then

		return errorOrentity

	end

	local entity = errorOrentity

	local result

	if isType(claims, "quantity") then

		-- get the url for the unit entry on Wikidata:

		result = claims1].mainsnak.datavalue.value.unit

		-- and just reurn the last bit from "Q" to the end (which is the QID):

		result = mw.ustring.sub(result, mw.ustring.find(result, "Q"), -1)

	end

	return result

end



p.getRawQualifierValue = function(frame)

	local function outputHook(value)

		if value.datavalue.value"numeric-id" then

			return mw.wikibase.getLabel("Q" .. value.datavalue.value"numeric-id"])

		else

			return value.datavalue.value

		end

	end

	local ret, gotData = getQualifier(frame, outputHook)

	if gotData then

		ret = string.upper(string.sub(ret, 1, 1)) .. string.sub(ret, 2)

	end

	return ret

end



-- This is used to get a date value for date_of_birth (P569), etc. which won't be linked

-- Dates and times are stored in ISO 8601 format (sort of).

-- At present the local formatDate(date, precision, timezone) function doesn't handle timezone

-- So I'll just supply "Z" in the call to formatDate below:

p.getDateValue = function(frame)

	local date_format = mw.text.trim(frame.args3 or i18n"datetime"]["default-format"])

	local date_addon = mw.text.trim(frame.args4 or i18n"datetime"]["default-addon"])

	local go, errorOrentity, claims = parseInput(frame)

	if not go then

		return errorOrentity

	end

	local entity = errorOrentity

	local out = {}

	for k, v in pairs(claims) do

		if v.mainsnak.datavalue.type == 'time' then

			local timestamp = v.mainsnak.datavalue.value.time

			local dateprecision = v.mainsnak.datavalue.value.precision

			-- A year can be stored like this: "+1872-00-00T00:00:00Z",

			-- which is processed here as if it were the day before "+1872-01-01T00:00:00Z",

			-- and that's the last day of 1871, so the year is wrong.

			-- So fix the month 0, day 0 timestamp to become 1 January instead:

			timestamp = timestamp:gsub("%-00%-00T", "-01-01T")

			out#out + 1 = parseDateFull(timestamp, dateprecision, date_format, date_addon)

		end

	end

	return table.concat(out, ", ")

end

p.getQualifierDateValue = function(frame)

	local date_format = mw.text.trim(frame.args4 or i18n"datetime"]["default-format"])

	local date_addon = mw.text.trim(frame.args5 or i18n"datetime"]["default-addon"])

	local function outputHook(value)

		local timestamp = value.datavalue.value.time

		return parseDateValue(timestamp, date_format, date_addon)

	end

	return (getQualifier(frame, outputHook))

end



-- This is used to fetch all of the images with a particular property, e.g. image (P18), Gene Atlas Image (P692), etc.

-- Parameters are | propertyID | value / FETCH_WIKIDATA / nil | separator (default=space) | size (default=frameless)

-- It will return a standard wiki-markup [[File:Filename | size]] for each image with a selectable size and separator (which may be html)

-- e.g. {{#invoke:Wikidata|getImages|P18|FETCH_WIKIDATA}}

-- e.g. {{#invoke:Wikidata|getImages|P18|FETCH_WIKIDATA|<br>|250px}}

-- If a property is chosen that is not of type "commonsMedia", it will return empty text.

p.getImages = function(frame)

	local sep = mw.text.trim(frame.args3 or " ")

	local imgsize = mw.text.trim(frame.args4 or "frameless")

	local go, errorOrentity, claims = parseInput(frame)

	if not go then

		return errorOrentity

	end

	local entity = errorOrentity

	if (claims1 and claims1].mainsnak.datatype == "commonsMedia") then

		local out = {}

		for k, v in pairs(claims) do

			local filename = v.mainsnak.datavalue.value

			out#out + 1 = "[[File:" .. filename .. "|" .. imgsize .. "]]"

		end

		return table.concat(out, sep)

	else

		return ""

	end

end



-- This is used to get the TA98 (Terminologia Anatomica first edition 1998) values like 'A01.1.00.005' (property P1323)

-- which are then linked to https://ifaa.unifr.ch/Public/EntryPage/TA98%20Tree/Entity%20TA98%20EN/01.1.00.005%20Entity%20TA98%20EN.htm

-- uses the newer mw.wikibase calls instead of directly using the snaks

-- formatPropertyValues returns a table with the P1323 values concatenated with ", " so we have to split them out into a table in order to construct the return string

p.getTAValue = function(frame)

	local ent = mw.wikibase.getEntity()

	local props = ent:formatPropertyValues('P1323')

	local out = {}

	local t = {}

	for k, v in pairs(props) do

		if k == 'value' then

			t = mw.text.split( v, ", ")

			for k2, v2 in pairs(t) do

				out#out + 1 = "[https://ifaa.unifr.ch/Public/EntryPage/TA98%20Tree/Entity%20TA98%20EN/" .. string.sub(v2, 2) .. "%20Entity%20TA98%20EN.htm " .. v2 .. "]"

			end

		end

	end

	local ret = table.concat(out, "<br> ")

	if #ret == 0 then

		ret = "Invalid TA"

	end

	return ret

end



--[[

This is used to return an image legend from Wikidata

image is property P18

image legend is property P2096



Call as {{#invoke:Wikidata |getImageLegend | <PARAMETER> | lang=<ISO-639code> |id=<QID>}}

Returns PARAMETER, unless it is equal to "FETCH_WIKIDATA", from Item QID (expensive call)

If QID is omitted or blank, the current article is used (not an expensive call)

If lang is omitted, it uses the local wiki language, otherwise it uses the provided ISO-639 language code

ISO-639: https://docs.oracle.com/cd/E13214_01/wli/docs92/xref/xqisocodes.html#wp1252447



Ranks are: 'preferred' > 'normal'

This returns the label from the first image with 'preferred' rank

Or the label from the first image with 'normal' rank if preferred returns nothing

Ranks: https://www.mediawiki.org/wiki/Extension:Wikibase_Client/Lua

]]



p.getImageLegend = function(frame)

	-- look for named parameter id; if it's blank make it nil

	local id = frame.args.id

	if id and (#id == 0) then

		id = nil

	end



	-- look for named parameter lang

	-- it should contain a two-character ISO-639 language code

	-- if it's blank fetch the language of the local wiki

	local lang = frame.args.lang

	if (not lang) or (#lang < 2) then

		lang = mw.language.getContentLanguage().code

	end



	-- first unnamed parameter is the local parameter, if supplied

	local input_parm = mw.text.trim(frame.args1 or "")

	if input_parm == "FETCH_WIKIDATA" then

		local ent = mw.wikibase.getEntity(id)

		local imgs

		if ent and ent.claims then

			imgs = ent.claims.P18

		end

		local imglbl

		if imgs then

			-- look for an image with 'preferred' rank

			for k1, v1 in pairs(imgs) do

				if v1.rank == "preferred" and v1.qualifiers and v1.qualifiers.P2096 then

					local imglbls = v1.qualifiers.P2096

					for k2, v2 in pairs(imglbls) do

						if v2.datavalue.value.language == lang then

							imglbl = v2.datavalue.value.text

							break

						end

					end

				end

			end

			-- if we don't find one, look for an image with 'normal' rank

			if (not imglbl) then

				for k1, v1 in pairs(imgs) do

					if v1.rank == "normal" and v1.qualifiers and v1.qualifiers.P2096 then

						local imglbls = v1.qualifiers.P2096

						for k2, v2 in pairs(imglbls) do

							if v2.datavalue.value.language == lang then

								imglbl = v2.datavalue.value.text

								break

							end

						end

					end

				end

			end

		end

		return imglbl

	else

		return input_parm

	end

end



-- This is used to get the QIDs of all of the values of a property, as a comma separated list if multiple values exist

-- Usage: {{#invoke:Wikidata |getPropertyIDs |<PropertyID> |FETCH_WIKIDATA}}

-- Usage: {{#invoke:Wikidata |getPropertyIDs |<PropertyID> |<InputParameter> |qid=<QID>}}



p.getPropertyIDs = function(frame)

	local go, errorOrentity, propclaims = parseInput(frame)

	if not go then

		return errorOrentity

	end

	local entity = errorOrentity

	-- if wiki-linked value collect the QID in a table

	if (propclaims1 and propclaims1].mainsnak.snaktype == "value" and propclaims1].mainsnak.datavalue.type == "wikibase-entityid") then

		local out = {}

		for k, v in pairs(propclaims) do

			out#out + 1 = "Q" .. v.mainsnak.datavalue.value"numeric-id"

		end

		return table.concat(out, ", ")

	else

		-- not a wikibase-entityid, so return empty

		return ""

	end

end



-- returns the page id (Q...) of the current page or nothing of the page is not connected to Wikidata

function p.pageId(frame)

	return mw.wikibase.getEntityIdForCurrentPage()

end



function p.claim(frame)

	local property = frame.args1 or ""

	local id = frame.args"id"

	local qualifierId = frame.args"qualifier"

	local parameter = frame.args"parameter"

	local list = frame.args"list"

	local references = frame.args"references"

	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 = findClaims(entity, property)

	if not claims or not claims1 then

		if showerrors then return printError("property-not-found") else return default end

	end



	-- get initial sort indices

	local sortindices = {}

	for idx in pairs(claims) do

		sortindices#sortindices + 1 = idx

	end

	-- sort by claim rank

	local comparator = function(a, b)

		local rankmap = { deprecated = 2, normal = 1, preferred = 0 }

		local ranka = rankmapclaimsa].rank or "normal" .. string.format("%08d", a)

		local rankb = rankmapclaimsb].rank or "normal" .. string.format("%08d", b)

		return ranka < rankb

	end

	table.sort(sortindices, comparator)



	local result

	local error

	if list then

		local value

		-- iterate over all elements and return their value (if existing)

		result = {}

		for idx in pairs(claims) do

			local claim = claimssortindicesidx]]

			value, error = getValueOfClaim(claim, qualifierId, parameter)

			if not value and showerrors then value = error 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 = claimssortindices1]]

		result, error = getValueOfClaim(claim, qualifierId, parameter)

		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



-- look into entity object

function p.ViewSomething(frame)

	local f = (frame.args1 or frame.args.id) and frame or frame:getParent()

	local id = f.args.id

	if id and (#id == 0) then

		id = nil

	end

	local data = mw.wikibase.getEntity(id)

	if not data then

		return nil

	end



	local i = 1

	while true do

		local index = f.argsi

		if not index then

			if type(data) == "table" then

				return mw.text.jsonEncode(data, mw.text.JSON_PRESERVE_KEYS + mw.text.JSON_PRETTY)

			else

				return tostring(data)

			end

		end



		data = dataindex or datatonumber(index)]

		if not data then

			return

		end



		i = i + 1

	end

end



-- getting sitelink of a given wiki

-- get sitelink of current item if qid not supplied

function p.getSiteLink(frame)

	local qid = frame.args.qid

	if qid == "" then qid = nil end

	local f = mw.text.trim( frame.args1 or "")

	local entity = mw.wikibase.getEntity(qid)

	if not entity then

		return

	end

	local link = entity:getSitelink( f )

	if not link then

		return

	end

	return link

end



function p.Dump(frame)

	local f = (frame.args1 or frame.args.id) and frame or frame:getParent()

	local data = mw.wikibase.getEntity(f.args.id)

	if not data then

		return i18n.warnDump

	end



	local i = 1

	while true do

		local index = f.argsi

		if not index then

			return "<pre>"..mw.dumpObject(data).."</pre>".. i18n.warnDump

		end



		data = dataindex or datatonumber(index)]

		if not data then

			return i18n.warnDump

		end



		i = i + 1

	end

end



return p