Zum Inhalt springen

Modul:Wikidata

aus Wikipedia, der freien Enzyklopädie
Vorlagenprogrammierung Diskussionen Lua Test Unterseiten
Modul Deutsch

Modul: Dokumentation

Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus


local Wd = {
	-- project configuration
	wiki = {
		langcode = mw.language.getContentLanguage().code
	},

	-- internationalisation
	i18n = {
		["errors"] = {
			["property-not-found"] = "Eigenschaft nicht gefunden.",
			["entity-not-found"] = "Wikidata-Eintrag nicht gefunden.",
			["entity-not-valid"] = "Die an die Wikidata-Schnittstelle übergebene Item-ID ist nicht gültig.",
			["unknown-claim-type"] = "Unbekannter Aussagentyp.",
			["unknown-entity-type"] = "Unbekannter Entity-Typ.",
			["qualifier-not-found"] = "Qualifikator nicht gefunden.",
			["site-not-found"] = "Wikimedia-Projekt nicht gefunden.",
			["invalid-parameters"] = "Ungültige Parameter.",
			["module-not-loaded"] = "Loading of additional module failed."
		},
		["maintenance-pages"] = {
			["entity-not-found"] = "Wikidata/Wartung/Fehlendes Datenobjekt",
			["entity-not-valid"] = "Wikidata/Wartung/Ungültige Datenobjekt-Identifikationsnummer",
			["property-not-existing"] = "Wikidata/Wartung/Eigenschaft existiert nicht"
		},
		["datetime"] = {
			-- $s is a placeholder for the actual number
			[0] = "%s Mrd. Jahren",		-- precision: billion years
			[1] = "%s00 Mio. Jahren",	-- precision: hundred million years
			[2] = "%s0 Mio. Jahren",	-- precision: ten million years
			[3] = "%s Mio. Jahren",		-- precision: million years
			[4] = "%s00.000 Jahren",	-- precision: hundred thousand years
			[5] = "%s0.000 Jahren",		-- precision: ten thousand years
			[6] = "%s. Jahrtausend", 	-- precision: millenium
			[7] = "%s. Jahrhundert",	-- precision: century
			[8] = "%ser",				-- precision: decade
			-- the following use the format of #time parser function
			[9]  = "Y",					-- precision: year,
			[10] = "F Y",				-- precision: month
			[11] = "j. F Y",			-- precision: day
			[12] = 'j. F Y, G "Uhr"',	-- precision: hour
			[13] = "j. F Y G:i",		-- precision: minute
			[14] = "j. F Y G:i:s",		-- precision: second
			["beforenow"] = "vor %s",	-- how to format negative numbers for precisions 0 to 5
			["afternow"] = "in %s",		-- how to format positive numbers for precisions 0 to 5
			["bc"] = '%s "v.Chr."',		-- how print negative years
			["ad"] = "%s"				-- how print positive years
		},
		["monolingualtext"] = '<span lang="%s">%s</span>',
		["errortext"] = '<span class="error">%s</span>',
		["FETCH_WIKIDATA"] = "ABFRAGE_WIKIDATA"
	},
	
	-- global value to count calls of expensive function isParent, including recursive calls
	numberIsParentCalls = 0,

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

	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
	}
}

--[=[
  ==============================================================================
  GENERAL HELPERS
  ==============================================================================
]=]

--[=[
  print an error with the given error code
  
  parameters:
  - code (string): error code
  - info (opt, string): additional information

  returns: (string) error html
]=]
Wd.printError = function( code, info )
	local text
	
	text = Wd.i18n.errors[ code ] or code or ""
	if info then
		text = text .. ": " .. info
	end
	
	return mw.ustring.format( Wd.i18n.errortext, text )
end

--[=[
  create a new error object
  
  parameters:
  - code (string): error code
  - info (opt, string): additional information

  returns: (table) error table
]=]
Wd.newError = function( code, info )
	return { 
		code = code,
		info = tostring( info )
	}
end

--[=[
  parse the given error object
  
  parameters:
  - error (table): error object created with Wd.newError

  returns:
  - (string): error code
  - (string|nil): additional information if available
]=]
Wd.parseError = function( error )
	return error.code, error.info
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.
  
  parameters:
  - array (table): table to get items from
  - order (opt, table): order table
  
  returns: (function) iterator function
]=]
Wd.orderedPairs = function( 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

--[=[
  return the size of the given table
  
  parameters:
  - tbl (table): table to get size of
  
  returns: (number) number of pairs in table
]=]
Wd.getTableSize = function( tbl )
	if type( tbl ) ~= "table" then
		return 0
	end
	
	local count = 0
	for _ in pairs( tbl ) do
		count = count + 1
	end
	
	return count
end

--[=[
  create a silent maintenance link
  
  parameters:
  - key (string): key to maintenance link title
  
  returns: (boolean) link title exists or not (to record usage)
]=]
Wd.createMaintenanceLink = function( key )
	if type( key ) == "string" and Wd.i18n[ "maintenance-pages" ][ key ] then
		return mw.title.new( Wd.i18n[ "maintenance-pages" ][ key ], "Modul" ).exists
	end
end

--[=[
  get an argument of the invocation frame with options
  
  parameters:
  - frame (table): invocation frame
  - key (string|number) argument key
  - allowEmpty (opt, boolean): if true, empty strings count as valid arguments
  - default (opt, any): default value to return if argument is invalid
  
  returns: (boolean) link title exists or not (to record usage)
]=]
Wd.getFrameArgument = function( frame, key, allowEmpty, default )
	-- default allowEmpty to false
	if not allowEmpty then
		allowEmpty = false
	end
	
	-- get frame argument
	local arg = frame.args[ key ]
	if arg and ( allowEmpty == true or ( allowEmpty == false and arg ~= "") ) then
		return arg
	else
		return default
	end
end

--[=[
  ==============================================================================
  WIKIBASE CLIENT LUA API HELPERS
  ==============================================================================
]=]

--[=[
  check wheter the given identifier is a valid entity id, typesafe
  
  parameters:
  - id (any): entity id
  
  returns: (bool) entity id valid or invalid
]=]
Wd.isValidEntityIdTypesafe = function( id )
	return ( type( id ) == "string" and mw.wikibase.isValidEntityId( id ) )
end

--[=[
  check wheter the entity with the given id exists, typesafe
  
  parameters:
  - id (any): entity id
  
  returns: (bool) entity id exists or not
]=]
Wd.entityExistsTypesafe = function( id )
	return ( Wd.isValidEntityIdTypesafe( id ) and mw.wikibase.entityExists( id ) )
end

--[=[
  return the entity id of the current page if given id is invalid
  
  parameters:
  - id (string|nil): entity id
  
  returns: (string|nil) entity id of current page or nil if page is not connected
]=]
Wd.getConnectedEntityIdIfInvalid = function( id )
	if Wd.isValidEntityIdTypesafe( id ) then
		return id
	else
		return mw.wikibase.getEntityIdForCurrentPage()
	end
end

--[=[
  return the entity id of the current page if given id does not exist
  
  parameters:
  - id (string|nil): entity id
  
  returns: (string|nil) entity id of current page or nil if page is not connected
]=]
Wd.getConnectedEntityIdIfInexisting = function( id )
	if Wd.entityExistsTypesafe( id ) then
		return id
	else
		return mw.wikibase.getEntityIdForCurrentPage()
	end
end

--[=[
  extract the first entity id from the given string
  
  parameters:
  - text (string): input string
  - etype (opt, string): entity type, "Q" for items and "P" for properties
  
  returns: (string|nil) entity id or nil if no qid found
]=]
Wd.extractEntityId = function( text, etype )
	if not etype then
		etype = "Q"
	end
	return mw.ustring.match( text, etype .. "%d+" )
end

--[=[
  ==============================================================================
  DATETIME HELPERS
  ==============================================================================
]=]

--[=[
  transform date to normalized form
  
  parameters:
  - date (string): date
  
  returns: (string) normalized date
           (int) year
]=]
Wd.normalizeDate = function( 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

--[=[
  create date string from date
  
  parameters:
  - date (string): date string
  - precision (number): date precision
  - timezone (string): date timezone
  - formatstr (string): formatter string for MediaWiki's formatDate function
  
  returns: (string) date string
]=]
Wd.formatDate = function( date, precision, timezone, formatstr )
	
	-- precisions: 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
	-- standard precision is days
	precision = precision or 11
	
	-- get normalized date and year
	date, year = Wd.normalizeDate( date )
	
	-- set to first day
	date = string.gsub( date, "-00%f[%D]", "-01" )
	
	-- min year precision required, but date has no year given
	if precision <= 9 and year == 0 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.format( Wd.i18n.datetime[ precision ], tostring( y2 ) )
		if year < 0 then
			relative = mw.ustring.format( Wd.i18n.datetime.beforenow, relative )
		else
			relative = mw.ustring.format( Wd.i18n.datetime.afternow, relative )
		end
		return relative
	end

	-- precision is decades, centuries and millenia
	local era
	if precision == 6 then
		era = mw.ustring.format( Wd.i18n.datetime[ 6 ], tostring( math.floor( ( math.abs( year ) - 1 ) / 1000 ) + 1 ) )
	end
	if precision == 7 then
		era = mw.ustring.format( Wd.i18n.datetime[ 7 ], tostring( math.floor( ( math.abs( year ) - 1 ) / 100 ) + 1 ) )
	end
	if precision == 8 then
		era = mw.ustring.format( Wd.i18n.datetime[ 8 ], tostring( math.floor( math.abs( year ) / 10 ) * 10 ) )
	end
	if era then
		if year < 0 then
			era = mw.ustring.format( mw.ustring.gsub( Wd.i18n.datetime.bc, '"', "" ), era )
		elseif year > 0 then
			era = mw.ustring.format( mw.ustring.gsub( Wd.i18n.datetime.ad, '"', "" ), era )
		end
		return era
	end

	-- precision is years or less
	if precision >= 9 then
		if formatstr then
			for i = ( precision + 1 ), 14 do
				for _, ch in pairs( Wd.formatchar[ i ] ) do
					if formatstr:find( ch ) then
						formatstr = Wd.i18n.datetime[ precision ]
					end
				end
			end
		else
			formatstr = Wd.i18n.datetime[ precision ]
		end
		if year == 0 then
			formatstr = mw.ustring.gsub( formatstr, Wd.i18n.datetime[ 9 ], "" )
		elseif year < 0 then
			-- MediaWiki's formatDate doesn't support negative years
			date = mw.ustring.sub( date, 2 )
			formatstr = mw.ustring.gsub( formatstr, Wd.i18n.datetime[ 9 ], mw.ustring.format( Wd.i18n.datetime.bc, Wd.i18n.datetime[ 9 ] ) )
		elseif year > 0 and Wd.i18n.datetime.ad ~= "%s" then
			formatstr = mw.ustring.gsub( formatstr, Wd.i18n.datetime[ 9 ], mw.ustring.format( Wd.i18n.datetime.ad, Wd.i18n.datetime[ 9 ] ) )
		end
		
		return mw.language.new( Wd.wiki.langcode ):formatDate( formatstr, date )
	end
	
	-- return empty string by default
	return ""
end

--[=[
  split time datavalue to separated date object
  
  parameters:
  - data (table): time datavalue object
  
  returns: (table) date object
]=]
Wd.datavalueTimeToDateObject = function( data )
	-- parse timestamp
	local sign, year, month, day, hour, minute, second = string.match( data.time, "(.)(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)Z" )
	
	-- fill date object
	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$" )
	}
	
	-- handle ac/bc
	if sign == "-" then
		result.year = -result.year
	end
	
	return result
end

--[=[
  handle julian calendar
  
  parameters:
  - dateObject (table): date object, e.g. retrieved from Wd.datavalueTimeToDateObject()
  
  returns: (number) datetime representation
]=]
Wd.handleJulianDay = function( 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

--[=[
  ==============================================================================
  SNAK RENDERERS
  ==============================================================================
]=]

--[=[
  print datavalue type string
  
  parameters:
  - data (table): value data
  - parameter (string): data parameter to return
  
  returns: (string) parameter value
]=]
Wd.printDatavalueString = function( data, parameter )
	return data
end

--[=[
  print datavalue type coordinate
  
  parameters:
  - data (table): value data
                  latitude [double], longitude [double], altitude [double], precision [double], globe [wikidata URI, usually http://www.wikidata.org/entity/Q2 [earth]]
  - parameter (string): data parameter to return
  
  returns: (string) parameter value
]=]
Wd.printDatavalueCoordinate = function( data, parameter )
	if parameter then
		if parameter == "globe" then
			-- extract entity id from the globe URI
			data.globe = Wd.extractEntityId( data.globe )
		end
		return data[ parameter ]
	else
		-- combine latitude and longitude, which can be decomposed using the #titleparts wiki function
		return data.latitude .. "/" .. data.longitude
	end
end

--[=[
  print datavalue type quantity
  
  parameters:
  - data (table): value data
                  amount [number], unit [string], upperBound [number], lowerBound [number]
  - parameter (string): data parameter to return
  
  returns: (string) parameter value
]=]
Wd.printDatavalueQuantity = function( data, parameter )
	if not parameter or parameter == "amount" then
		return tonumber( data.amount )
	elseif parameter == "unit" then
		return Wd.extractEntityId( data.unit )
	else
		return data[ parameter ]
	end
end

--[=[
  print datavalue type time
  
  parameters:
  - data (table): value data
                  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
  - parameter (string): data parameter to return
  
  returns: (string) parameter value
]=]
Wd.printDatavalueTime = function( data, parameter )
	if parameter then
		-- check if format is specified
		local para, formatstr = parameter:match( "([^:]+):([^:]+)" )
		
		if parameter == "calendarmodel" then
			-- extract entity id from the calendar model URI
			data.calendarmodel = Wd.extractEntityId( data.calendarmodel )
		elseif para and para == "time" then
			-- output formatted time string
			return Wd.formatDate( data.time, data.precision, data.timezone, formatstr )
		elseif parameter == "time" then
			-- output standard date format
			data.time = Wd.normalizeDate( data.time )
		end
		return data[ parameter ]
	else
		-- return full date
		return Wd.formatDate( data.time, data.precision, data.timezone )
	end
end

--[=[
  print datavalue type entity
  
  parameters:
  - data (table): value data
                  entity-type [string], numeric-id [int, Wikidata id]
  - parameter (string): data parameter to return
  
  returns: (string) parameter value
]=]
Wd.printDatavalueEntity = function( data, parameter )
	local id
	
	-- get entity type
	if data[ "entity-type" ] == "item" then
		id = "Q" .. data[ "numeric-id" ]
	elseif data[ "entity-type" ] == "property" then
		id = "P" .. data[ "numeric-id" ]
	else
		return Wd.printError( "unknown-entity-type" )
	end

	if parameter then
		if parameter == "link" then
			-- create link
			local linkTarget = mw.wikibase.getSitelink( id )
			local linkName   = mw.wikibase.getLabel( id )
			if linkTarget then
				-- if there is a local Wikipedia page linking to it, use the label or the page title
				local link = linkTarget
				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.getLabel( id ) or id
	end
end

--[=[
  print datavalue type monolingual text
  
  parameters:
  - data (table): value data
                  language [string], text [string]
  - parameter (string): data parameter to return
  
  returns: (string) parameter value
]=]
Wd.printDatavalueMonolingualText = function( data, parameter )
	if parameter then
		return data[ parameter ]
	else
		return mw.ustring.format( Wd.i18n.monolingualtext, data[ "language" ], data[ "text" ] )
	end
end

--[=[
  ==============================================================================
  CLAIMS
  ==============================================================================
]=]

--[=[
  get the value of a snak. snaks have three types: "novalue" for null/nil,
  "somevalue" for not null/not nil, or "value" for actual data
  
  parameters:
  - snak (table): snak data
  - parameter (string): data parameter to return
  
  returns: (string) rendered snak value
]=]
Wd.getSnakValue = function( snak, parameter )
	if snak.snaktype == "value" then
		-- define snak renderers
		local snakRendererList = {
			["string"] = Wd.printDatavalueString,
			["globecoordinate"] = Wd.printDatavalueCoordinate,
			["quantity"] = Wd.printDatavalueQuantity,
			["time"] = Wd.printDatavalueTime,
			["wikibase-entityid"] = Wd.printDatavalueEntity,
			["monolingualtext"] = Wd.printDatavalueMonolingualText
		}
		
		-- get the respective snak renderer and call it
		local snakRenderer = snakRendererList[ snak.datavalue.type ]
		if snakRenderer then
			return snakRenderer( snak.datavalue.value, parameter )
		end
	end
	
	-- any other snak type or datavalue type
	return mw.wikibase.renderSnak( snak )
end

--[=[
  get the snak of the given qualifier of the claim or its mainsnak.
  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
  
  parameters:
  - claim (table): claim data
  - qualifierId (opt, string): qualifier to get the snak from
  
  returns: (table|nil) snak data or nil if error
           (string) error message if needed
]=]
Wd.getQualifierSnak = function( claim, qualifierId )
	-- return nil if claim is invalid
	if not claim then
		return nil
	end
	
	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, Wd.printError( "qualifier-not-found" )
	else
		-- otherwise return the main snak
		return claim.mainsnak
	end
end

--[=[
  get the sort value of the given qualifier of the claim or its mainsnak.
  
  parameters:
  - claim (table): claim data
  - qualifierId (opt, string): qualifier to get the snak from
  
  returns: (string|nil) sort value or nil if error
]=]
Wd.getQualifierSortValue = function( claim, qualifierId )
	local snak = Wd.getQualifierSnak( claim, qualifierId )
	if snak and snak.snaktype == "value" then
		if snak.datavalue.type == "time" then
			-- use datetime representation vor time datavalue
			return Wd.handleJulianDay( Wd.datavalueTimeToDateObject( snak.datavalue.value ) )
		else
			-- use snak value for other datavalue types
			return Wd.getSnakValue( snak )
		end
	end
end

--[=[
  get the value of the mainsnak of the claim or the value of the given qualifier
  
  parameters:
  - claim (table): claim data
  - qualifierId (opt, string): qualifier to get the data from
  
  returns: (string|nil) value or nil if error
           (string) error message if needed
]=]
Wd.getClaimValue = function( claim, qualifierId, parameter )
	local error
	local snak
	snak, error = Wd.getQualifierSnak( claim, qualifierId )
	
	if snak then
		return Wd.getSnakValue( snak, parameter )
	end
	
	return nil, error
end

--[=[
  checks if a claim has the given qualifier, and optionally, with the given value
  
  parameters:
  - claim (table): claim data
  - property (string): qualifier property
  - value (opt, string): value of qualifier

  returns: (boolean) check result
]=]
Wd.claimHasQualifier = function( claim, property, value )
	local invert = false
	
	-- allow inversion of the qualifier check
	if string.sub( property, 1, 1 ) == "!" then
		invert = true
		property = string.sub( property, 2 )
	end

	-- check if claim has any qualifiers.
	-- if property is empty string or literal true, check for existence of any 
	-- qualifiers only, without inversion - invert is always false in this case
	-- so will return false in the first condition
	if not claim.qualifiers then
		return invert
	elseif property == "" or property == "true" then
		return true
	end
	
	-- check if qualifier with property exists
	if not claim.qualifiers[ property ] then
		return invert
	end
	
	-- if check is only for qualifier existence return true or false inverted
	if not value then
		return ( not invert )
	end
	
	-- check for existence of the given value
	local found = false
	for key, snak in pairs( claim.qualifiers[ property ] ) do
		if snak.snaktype == "value" then
			if snak.datavalue.type == "wikibase-entityid" then
				-- wikibase id
				if snak.datavalue.value.id == value then
					found = true
					break
				end
			--TODO: elseif other types
			end
		end
	end
	
	-- return true or false inverted
	return ( found ~= invert )
end

--[=[
  checks if a claim has the given source
  
  parameters:
  - claim (table): claim data
  - property (string): source property

  returns: (boolean) check result
]=]
Wd.claimHasSource = function( claim, property )
	
	-- check if claim has any references.
	-- if property is empty string or literal true, check for existence of any 
	-- references only
	if not claim.references then
		return false
	elseif property == "" or property == "true" then
		return true
	end
	
	-- allow inversion of the reference property check
	local invert = false
	if string.sub( property, 1, 1 ) == "!" then
		invert = true
		property = string.sub( property, 2 )
	end
	
	-- check for existence of the given source property
	for _, source in pairs( claim.references ) do
		if source.snaks[ property ] then
			return ( not invert )
		end
	end
	
	-- return true or false inverted
	return invert
end

--[=[
  checks if a claim is valid at the given date
  
  parameters:
  - claim (table): claim data
  - checkdate (opt, string): date to check, or current date

  returns: (boolean) check result
]=]
Wd.claimValidAtDate = function( claim, checkdate )
	local refdate, checkdateyear
	if not checkdate or checkdate == "" then
		-- date not given, use current date
		refdate = os.date( "!*t" )
		checkdateyear = 0
	else
		-- parse given date
		if string.match( checkdate, "^%d+$" ) then
			-- only year given
			refdate = { year = tonumber( checkdate ) }
			checkdateyear = tonumber( checkdate )
		else
			-- exact date given
			refdate = Wd.datavalueTimeToDateObject( { time = mw.language.getContentLanguage():formatDate( "+Y-m-d\\TH:i:s\\Z", checkdate ) } )
			checkdateyear = 0
		end
	end
	
	local refjd = Wd.handleJulianDay( refdate )
	
	-- get date qualifiers for claim
	local exactdate = Wd.getQualifierSortValue( claim, Wd.propertyId[ "pointoftime" ] )
	local mindate   = Wd.getQualifierSortValue( claim, Wd.propertyId[ "starttime" ] )
	local maxdate   = Wd.getQualifierSortValue( claim, Wd.propertyId[ "endtime" ] )
	
	if exactdate then
		-- if there is an exact date in the qualifier, and the atdate parameter
		-- is on year precision, mindate is the beginning of the year and maxdate
		-- is 31st Dec of that particular year
		local refmaxjd
		if checkdateyear > 0 then
			refmaxjd = Wd.handleJulianDay( { year = tonumber( checkdate ), month = 12, day = 31 } )
		else 
			refmaxjd = refjd
		end
		
		-- return false if exact date is out of bounds
		if exactdate < refjd or exactdate > refmaxjd then
			return false
		end
	else
		-- return false if date is before mindate or after maxdate
		if mindate and mindate > refjd then
			return false
		end
		if maxdate and maxdate < refjd then
			return false
		end
	end
	
	-- success, the claim was valid at "checkdate"
	return true
end

--[=[
  checks if a claim is not deprecated
  
  parameters:
  - claim (table): claim data

  returns: (boolean) check result
]=]
Wd.claimNotDeprecated = function( claim )
	return claim.rank ~= "deprecated"
end

--[=[
  return a table of claims excluding claims not passed the filters
  
  parameters:
  - frame (table): call arguments
  - claims (table): claims to filter

  returns: (table) filtered claim table
]=]
Wd.filterClaims = function( frame, claims )
	-- subfunction that calls the check functions and does the filtering
	-- parameters:
	-- - condition (string): call argument that contains the filter condition
	-- - filterfunction (function): check function for the condition
	local function filter( condition, filterfunction )
		-- if condition not found in call arguments, do nothing
		if not frame.args[ condition ] then
			return
		end
		
		-- filter claim table
		local newclaims = {}
		for i, claim in pairs( claims ) do
			-- call filter function and check for result
			if filterfunction( claim, frame.args[ condition ] ) then
				table.insert( newclaims, claim )
			end
		end
		claims = newclaims
	end
	
	-- call available filters
	filter( "hasqualifier", Wd.claimHasQualifier )
	filter( "hassource", Wd.claimHasSource )
	filter( "atdate", Wd.claimValidAtDate )
	if not frame.args.includedeprecated then
		frame.args.notdeprecated = true
    	filter( "notdeprecated", Wd.claimNotDeprecated )
    end
	
	-- use additional unnamed parameters as qualifier conditions (in pairs)
	for key in pairs( frame.args ) do
		if type( key ) == "number" and key > 2 and key % 2 == 1 then
			-- key = 3, 5, 7 and so on
			local newclaims = {}
			local values = frame.args[ key ]
			
			-- allow inversion of values
			local invert = string.sub( values, 1, 1 ) == "!"
			if invert then
				values = string.sub( values, 2 )
			end
			
			-- filter claims
			for i, claim in pairs( claims ) do
				local hasvalue = false
				for val in mw.text.gsplit( values, "," ) do
					-- check if qualifier has current value
					if Wd.claimHasQualifier( claim, frame.args[ key - 1 ], val ) then
						hasvalue = true
						break
					end
				end
				if hasvalue ~= invert then
					table.insert( newclaims, claim )
				end
			end
			
			-- replace claims table
			claims = newclaims
		end
	end
	
	-- return filtered claim table
	return claims
end

--[=[
  convert single reference to its string representation
  
  parameters:
  - ref (table): ref data

  returns: (string|nil) reference text or nil if error
]=]
Wd.formatReference = function( ref )
	-- "imported from"-references are useless, skip them:
	if ref[ "P143" ] or ref[ "P4656" ] then
		return nil
	end
	
	-- load [[Modul:Zitation]]
	local ZitationSuccess, r = pcall( require, "Modul:Zitation" )
	if type( r ) == "table" then
		Zitation = r.Zitation()
		-- clear Zitation state from previous invocations
		Zitation.o = nil
	end
	-- assert (ZitationSuccess, Wd.i18n["errors"]["module-not-loaded"])
	
	-- assignments of Wikidata properties to Zitation parameters
	local wdZmap = {
		P1433 = { "bas", "Werk" },
		P248  = { "bas", "Werk" },
		P1476 = { "bas", "Titel" },
		P1680 = { "bas", "TitelErg" },
		P407  = { "bas", "Sprache" },
		P364  = { "bas", "Sprache" },
		P2439 = { "bas", "Sprache" },
		P123  = { "bas", "Verlag" },
		P577  = { "bas", "Datum" },
		P98   = { "bas", "Hrsg" },
		P2093 = { "bas", "Autor" },
		P50   = { "bas", "Autor" },
		P1683 = { "bas", "Zitat" },
		P854  = { "www", "URL" },
		P813  = { "www", "Abruf" },
		P1065 = { "www", "ArchivURL" },
		P2960 = { "www", "ArchivDatum" },
		P2701 = { "www", "Format" },
		P393  = { "print", "Auflage" },
		P291  = { "print", "Ort" },
		P304  = { "fragment", "Seiten" },
		P792  = { "fragment", "Kapitel" },
		P629  = { "orig", "Titel" }
	}
	
	for prop, value in pairs( ref ) do
		if wdZmap[ prop ] then
			if type( value ) == "table" then
				-- more snaks with same property, we concatenate using a comma
				value = table.concat( value, ", " )
			end
			-- value should be string now, so we can call Zitation
			if type( value ) == "string" and string.len( value ) > 0 then
				Zitation.fill( wdZmap[ prop ][ 1 ], wdZmap[ prop ][ 2 ], value, prop )	
			end
		end
	end
	-- if no title on Wikidata, try to use the URL as title
	if ( not ref[ "P1476" ] ) and ref[ "P854" ] then
		local URLutil = Zitation.fetch( "URLutil" )
		Zitation.fill( "bas", "Titel", URLutil.getHost( ref[ "P854" ] ) )
	end
	return Zitation.format()
end

--[=[
  return references of the given claim as reference string
  
  parameters:
  - frame (table): invocation frame
  - claim (table): claim data

  returns: (string) reference text
]=]
Wd.getReferences = function( frame, claim )
	local result = ""
	
	-- loop through all references
	for ref in pairs( claim.references or {} ) do
		-- put snaks to table for later formatting with Wd.formatReference()
		local refTable = {}
		for snakkey, snakval in Wd.orderedPairs( claim.references[ ref ].snaks or {}, claim.references[ ref ][ "snaks-order" ] ) do
			if #snakval == 1 then
				-- only one value in snak
				refTable[ snakkey ] = Wd.getSnakValue( snakval[ 1 ] )
			else
				-- multiple values in snak
				local multival = {}
				for snakidx = 1, #snakval do
						table.insert( multival, Wd.getSnakValue( snakval[ snakidx ] ) )
				end
				refTable[ snakkey ] = multival
			end
		end
		
		-- format reference with data in refTable
		local formattedRef, f = Wd.formatReference( refTable )
		
		if formattedRef and formattedRef ~= "" then
			-- reference formatting successful, create hash from text to avoid double refs
			local hash = mw.hash.hashValue( "fnv164", formattedRef )
			
			-- output reference
			result = result .. frame:extensionTag( "ref", formattedRef, { name = "_" .. hash } )
		end
	end
	
	return result
end

--[=[
  checks whether a certain item is a parent of a given item. A parent is
  connected via P31 or P279. Attention: very intensive function, use carefully!
  
  parameters:
  - item (string|number): start item
  - parent (string): parent item
  - exitItem (string): item to exit check at
  - maxDepth (number): maximum search depth
  - depth (opt, number): current search depth

  returns: (boolean) check result
]=]
Wd.isParent = function( item, parent, exitItem, maxDepth, depth )
	
	-- increment call number
	Wd.numberIsParentCalls = Wd.numberIsParentCalls + 1
	
	-- initial depth is zero
	if not depth then
		depth = 0
	end
	
	-- cast start item number to string
	if type( item ) == "number" then
		item = "Q" .. item
	end
	
	-- check if start entity exists
	if not Wd.entityExistsTypesafe( item ) then
		return false
	end
	
	-- get entity
	local entity = mw.wikibase.getEntity( item )
	if not entity then
		return false
	end
	
	-- if entity has no claims, end check
	if not entity.claims then
		return false
	end
	
	-- resolve property ids
	local claims31  = mw.wikibase.resolvePropertyId( "P31" ) 
	local claims279 = mw.wikibase.resolvePropertyId( "P279" )
	if not claims31 or not claims279 then
		return false
	end
	
	-- get connector claims, if both don't exist end check
	claims31  = entity.claims[ claims31 ]
	claims279 = entity.claims[ claims279 ]
	if not claims31 and not claims279 then
		return false
	end
	
	-- retrieve parent entity ids
	local parentIds = {}
	if claims31 and #claims31 > 0 then
		for i, v in ipairs( claims31 ) do
			parentIds[ #parentIds + 1] = Wd.getSnakValue( v.mainsnak, "numeric-id" )
		end
	end
	if claims279 and #claims279 > 0 then
		for i, v in ipairs( claims279 ) do
			parentIds[ #parentIds + 1 ] = Wd.getSnakValue( v.mainsnak, "numeric-id" )
		end
	end

	-- if no parent ids found, end check
	if not parentIds[ 1 ] or #parentIds == 0 then
		return false
	end

	-- check if searched parent or exit item is reached or do recursive call
	local itemString = ""
	local result     = nil
	for i, v in ipairs( parentIds ) do
		-- create item string and check if it is valid
		itemString = "Q" .. v
		if not Wd.isValidEntityIdTypesafe( itemString ) then
			return false
		end

		if itemString == parent then
			-- parent item found
			return true
		elseif itemString == exitItem or itemString == "Q35120" then
			-- exit if either exit item or node item "entity" (Q35120) is reached
			return false
		else
			-- resursive call on parents if within maxDepth bounds
			if depth + 1 < maxDepth then
				result = Wd.isParent( itemString, parent, exitItem, maxDepth, depth + 1 )
			else
				return false
			end
			
			-- return true if recursive call returned true
			if result == true then
				return result
			end
		end
	end
	
	-- return false
	do return false end
end

--[=[
  ==============================================================================
  ENTITY PROPERTIES
  ==============================================================================
]=]

--[=[
  return the sitelink name of the given entity on the given site.
  
  parameters:
  - id (opt, string): entity id
  - title (opt, string): title of a local page that is connected to the entity
  - site (opt, string): site name, or current site
  - titleSite (opt, string): site name to look for title, or current site
  One of id and title have to be specified. id takes precedence over title.
  
  returns:
  - (boolean) success indicator
  - (string|table) sitelink if successful or error object
]=]
Wd.getSitelink = function( id, title, site, titleSite )
	
	-- get id if not specified
	if not id then
		if title then
			-- get id by title
			-- if titleSite is nil, the title will be taken from the current wiki
			id = mw.wikibase.getEntityIdForTitle( title, titleSite )
		else
			-- no id and no title given, invalid call
			return false, Wd.newError( "invalid-parameters", "Wd.getSitelink" )
		end
	end
	
	-- raise error if id doesn't exist
	if not Wd.entityExistsTypesafe( id ) then
		return false, Wd.newError( "entity-not-found", id )
	end
		
	-- if site is nil, the sitelink to the current wiki will be returned
	return true, mw.wikibase.getSitelink( id, site )
end

--[=[
  ==============================================================================
  TEMPLATE EXPORT
  ==============================================================================
]=]

-- export table
local p = {}

--[=[
  checks whether a certain item is a subclass of a given item.
  
  parameters (frame):
  - id (string): start item
  - parent (string): parent item
  - exitItem (string): item to exit check at
  - maxDepth (opt, string): maximum search depth
  - returnInt (opt, string): if set, return 1 or empty instead of true/false

  returns: (boolean|number) check result in
]=]
p.isSubclass = function( frame )
	local result
	
	-- check for validity of start and parent item
	if Wd.entityExistsTypesafe( frame.args[ "id" ] ) and Wd.entityExistsTypesafe( frame.args[ "parent" ] ) then
		-- check if id and parent are same
		if frame.args[ "id" ] ~= frame.args[ "parent" ] then
			-- set maximum depth
			local maxDepth
			maxDepth = tonumber( frame.args[ "maxDepth" ] ) or 5
			
			-- getParent() call
			result = Wd.isParent( frame.args[ "id" ], frame.args[ "parent" ], frame.args[ "exitItem" ], maxDepth)
			-- mw.log(Wd.numberIsParentCalls) --uncomment to load number of isParent() calls into log
		else
			result = true
		end
	else
		result = false
	end
	
	-- return result
	if frame.args[ "returnInt" ] then
		if result == true then
			return 1
		else
			return ""
		end
	else
		if result then
			return result
		else
			return false
		end
	end
end

--[=[
  get a claim
  
  parameters (frame):
  - 1 (string): property of claim
  - id (string): entity id
  - qualifier (string): qualifier to return
  - parameter (string): value parameter to return
  - language (string): value language to return
  - countValues (string): return number of results instead of returning the actual result
  - list (string): concatenate all results with the given separator
  - listMaxItems (string): maximum items to list
  - includeempty (string): also include empty values
  - references (string): return also references, if "only", return references only
  - sort (string): sort results
  - sortEmptiesFirst (string): sort empty values first
  - sortInItem (string): sort based on property value in given item
  - inverse (string): inverse sorting order
  - showerrors (string): show errors instead of returning empty string
  - default (string): default return value if empty or error

  returns: (string) claim or empty or error
]=]
p.claim = function( frame )
	-- (A) PARAMETER ASSIGNMENTS --
	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 countValues      = frame.args[ "countValues" ]
	local list             = frame.args[ "list" ]
	local includeempty     = frame.args[ "includeempty" ]
	local listMaxItems     = tonumber( frame.args[ "listMaxItems" ] ) or 0
	local references       = frame.args[ "references" ]
	local sort             = frame.args[ "sort" ]
	local sortEmptiesFirst = frame.args[ "sortEmptiesFirst" ]
	local sortInItem       = frame.args[ "sortInItem" ]
	local inverse          = frame.args[ "inverse" ]
	local showerrors       = frame.args[ "showerrors" ]
	local default          = frame.args[ "default" ]
	
	-- don't show errors if default value is set
	if default then
		showerrors = nil
	end
	
	-- (B) VALIDITY CHECKS --
	-- get wikidata entity
	if id then
		if not Wd.isValidEntityIdTypesafe( id ) then
			-- invalid entity id
			if showerrors then
				return Wd.printError( "entity-not-valid" ) 
			else
				Wd.createMaintenanceLink( "entity-not-valid" )
				return default
			end
		elseif not Wd.entityExistsTypesafe( id ) then
			-- entity id does not exist
			if showerrors then
				return Wd.printError( "entity-not-found" ) 
			else
				-- create maintenance link
				Wd.createMaintenanceLink( "entity-not-found" )
				return default 
			end
		end
	end
	
	-- get entity object
	local entity = mw.wikibase.getEntity( id )
	if not entity then
		if showerrors then
			return Wd.printError( "entity-not-found" )
		else
			return default
		end
	end

	-- check if property exists
	local realProp = mw.wikibase.resolvePropertyId( property )
	if not realProp then
		Wd.createMaintenanceLink( "property-not-existing" )
	end

	-- fetch the first claim satisfying the given property
	local claims
	if entity.claims then
		claims = entity.claims[ realProp ]
	end
	if not claims or not claims[ 1 ] then
		if countValues then
			return 0
		elseif showerrors then
			return Wd.printError( "property-not-found" ) 
		else
			return default
		end
	end
	
	-- (C) FILTERING AND SORTING --
	-- filter claims
	claims = Wd.filterClaims( frame, claims )
	if not claims[ 1 ] then
		if countValues then
			return 0
		else
			return default
		end
	end
	
	-- get initial sort indices
	local sortindices = {}
	for idx in pairs( claims ) do
		sortindices[ #sortindices + 1 ] = idx
	end
	
	-- get comparator for sorting
	local comparator
	if sort then
		-- comparator function for sorting statements based on qualifier value
		comparator = function( a, b )
			-- load qualifier values
			local qualSortValA = Wd.getQualifierSortValue( claims[ a ], sort )
			local qualSortValB = Wd.getQualifierSortValue( claims[ b ], sort )
			
			-- if either of the two statements does not have this qualifer: 
			---- if sortEmptiesFirst=true: sort it to the beginning 
			---- else: always sort it to the end
			if not qualSortValB then 
				if not qualSortValA then
					-- if neither of the two statements has this qualifier, arbitrarily but consistently return a < b
					return a < b
				else
					-- true if sortEmptiesFirst is false
					return ( not sortEmptiesFirst )
				end
			elseif not qualSortValA then
				-- true if sortEmptiesFirst is not false
				return ( not not sortEmptiesFirst )
			end
			
			if type( qualSortValA ) ~= type( qualSortValB ) and not ( tonumber( qualSortValA ) and tonumber( qualSortValB ) ) then
				if tonumber( qualSortValA ) then
					return true
				elseif tonumber( qualSortValB ) then
					return false
				elseif tostring( qualSortValA ) and tostring( qualSortValB ) then
					if inverse then
						return tostring( qualSortValA ) > tostring( qualSortValB )
					else
						return tostring( qualSortValA ) < tostring( qualSortValB )
					end
				else
					-- different types, neither numbers nor strings, no chance to compare => random result to avoid script error
					return false
				end
			elseif tonumber( qualSortValA ) and tonumber( qualSortValB ) then
				qualSortValA = tonumber( qualSortValA )
				qualSortValB = tonumber( qualSortValB )
			end
			if inverse then
				return qualSortValA > qualSortValB
			else
				return qualSortValA < qualSortValB
			end
		end
	elseif sortInItem then
		-- get sortkeys of claims in referenced entity and given property
		local sortkeys = {}
		local snakSingle
		local claimContainingValue
		local claimContainingProperty
		local claimContainingEntity
		
		-- check if given property is valid
		claimContainingProperty = mw.wikibase.resolvePropertyId( sortInItem )
		if not claimContainingProperty then
			if showerrors then
				return Wd.printError( "property-not-found" ) 
			else
				return default
			end
		end
		
		-- loop trough all claims and retrieve the sortkeys
		for idx, claim in pairs( claims ) do
			-- get referenced entity
			snakSingle = Wd.getQualifierSnak( claim )
			claimContainingEntity = "Q" .. Wd.getSnakValue( snakSingle, "numeric-id" )
			
			-- get value if entity exists and has the property
			if Wd.entityExistsTypesafe( claimContainingEntity ) then
				claimContainingEntity = mw.wikibase.getEntity( claimContainingEntity )
				if claimContainingEntity and claimContainingEntity.claims and claimContainingEntity.claims[ claimContainingProperty ] then
					claimContainingValue = claimContainingEntity.claims[ claimContainingProperty ]
				end
			end
			
			-- use value as sortkey, or empty string if no property value found
			if claimContainingValue then
				sortkeys[ #sortkeys + 1 ] = Wd.getClaimValue( claimContainingValue[ 1 ] )
			else
				sortkeys[ #sortkeys + 1 ] = ""
			end
		end
		-- comparator function based on retrieved sortkeys
		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
	
	-- use comparator to sort indices table
	table.sort( sortindices, comparator )

	-- (D) RETURN VALUE --
	local result
	local error
	if countValues then
		-- return claim count
		result = Wd.getTableSize( claims )
	elseif list then
		-- list claims
		
		-- if a newline is provided (whose backslash will be escaped) unescape it
		list = string.gsub( list, "\\n", "\n" )
		
		-- loop through all elements and return their value (if existing)
		local value
		result = {}
		for idx in pairs( claims ) do
			local claim = claims[ sortindices[ idx ] ]
			
			-- get claim value and handle result
			value, error = Wd.getClaimValue( claim, qualifierId, parameter )
			if not value then
				if showerrors then
					value = error
				end
				if includeempty then
					-- overwrite error to empty string
					value = ""
				end
			end
			
			-- add references
			if value and references then
				value = value .. Wd.getReferences( frame, claim )
			end
			
			-- add to result list
			result[ #result + 1 ] = value
		end
		
		-- concat result table to string
		if listMaxItems and listMaxItems > 0 then
			-- return until maximum results is reached
			result = table.concat( result, list, 1, math.min( #result, listMaxItems ) )
		else
			-- return everything
			result = table.concat( result, list )
		end
	else
		-- return first element only
		local claim = claims[ sortindices[ 1 ] ]
		
		-- get value with language support
		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 = Wd.getClaimValue( claim, qualifierId, parameter )
					break
				end
			end
		else
			-- no language given, get claim value
			result, error = Wd.getClaimValue( claim, qualifierId, parameter )
		end
		
		-- handle references
		if references == "only" then
			-- only return references
			result = Wd.getReferences( frame, claim )
		elseif result and references then
			-- add references
			result = result .. Wd.getReferences( frame, claim ) 
		end
	end
	
	-- return result
	if result then
		return result
	else
		if showerrors then
			return error
		else
			return default
		end
	end
end

--[=[
  returns the given value or runs claim() if keyword is found

  parameters (frame):
  - 2 (opt, string): value to return or keyword
  
  returns: (string) value or return of claim()
]=]
p.getValue = function( frame )
	local param = frame.args[ 2 ]
	if param == "FETCH_WIKIDATA" or param == Wd.i18n["FETCH_WIKIDATA"] then
		return p.claim( frame )
	else
		return param
	end
end

--[=[
  return the entity id of the current page, or check the given entity id
  for redirects and return the redirect target, if existing
  
  parameters (frame):
  - 1 (opt, string): entity id
  
  returns: (string) entity id or empty string
]=]
p.pageId = function( frame )
	local id = frame.args[ 1 ]
	
	-- if id not given, return the id of the current page.
	-- mw.wikibase.getEntityIdForCurrentPage() loads less data than getEntity(),
	-- so it's preferable to use it in this case.
	if not id then
		return mw.wikibase.getEntityIdForCurrentPage() or ""
	end
	
	-- get the given entity to check for redirects
	local entity = mw.wikibase.getEntity( id )
	if entity then
		return entity:getId() or ""
	end
	
	-- return empty string by default
	return ""
end

--[=[
  return label of a Wikidata entity in the given language or the default language of this Wikipedia site
  
  parameters (frame):
  - 1 (opt, string): language code
  - 2 (opt, string): entity id
  - noFallback (opt, string): suppress language fallbacks
  
  returns: (string) label text or empty string
]=]
p.labelIn = function( frame )
	local langcode   = frame.args[ 1 ]
	local id         = frame.args[ 2 ]
	local noFallback = frame.args[ "noFallback" ]
		
	if noFallback then
		-- get label in language without fallbacks.
		-- id is required mw.wikibase.getLabelByLang(), so get the connected entity
		-- if not given or invalid
		id = Wd.getConnectedEntityIdIfInexisting( id )
		if not id then
			return ""
		end
		return mw.wikibase.getLabelByLang( id, langcode or Wd.wiki.langcode ) or ""
	elseif not langcode then
		-- no langcode given, return label in wiki language with fallbacks
		return mw.wikibase.getLabel( id ) or ""
	else
		-- get label in language with fallbacks
		local entity = mw.wikibase.getEntity( id )
		if entity then
			return entity:getLabel( langcode ) or ""
		end
	end
	
	-- return empty string by default
	return ""
end

--[=[
  return the label of the given entity or property id

  parameters (frame):
  - 1 (opt, string): entity id, or entity id of current page
  
  returns: (string) label text, error text or empty string
]=]
p.labelOf = function( frame )
	local id = frame.args[ 1 ]
	
	-- mw.wikibase.getLabel() would support omitting the id, but we want
	-- to display an error message if the page isn't connected.
	id = Wd.getConnectedEntityIdIfInexisting( id )
	if not id then
		return Wd.printError( "entity-not-found" )
	end
	
	return mw.wikibase.getLabel( id ) or ""
end

--[=[
  return description of a Wikidata entity in the given language or the default language of this Wikipedia site
  
  parameters (frame):
  - 1 (opt, string): language code
  - 2 (opt, string): entity id
  - noFallback (opt, string): suppress language fallbacks
  
  returns: (string) description text or empty string
]=]
p.descriptionIn = function( frame )
	local langcode   = frame.args[ 1 ]
	local id         = frame.args[ 2 ]
	local noFallback = frame.args[ "noFallback" ]
	
	if noFallback then
		-- get description in language without fallbacks
		-- id is required mw.wikibase.getDescriptionByLang(), so get the connected entity
		-- if not given or invalid
		id = Wd.getConnectedEntityIdIfInexisting( id )
		if not id then
			return ""
		end
		return mw.wikibase.getDescriptionByLang( id, langcode or Wd.wiki.langcode ) or ""
	elseif not langcode then
		-- no langcode given, return description in wiki language with fallbacks
		return mw.wikibase.getDescription( id ) or ""
	else
		-- get description in language with fallbacks
		local entity = mw.wikibase.getEntity( id )
		if entity then
			return entity:getDescription( langcode ) or ""
		end
	end
	
	-- return empty string by default
	return ""
end

--[=[
  return the Wikipedia page name of the given entity on the given site.
  
  parameters (frame):
  - 1 (opt, string): entity id, or entity id of current page
  - 2 (opt, string): site name, or current site
  
  returns: (string) page name, error text or empty string
]=]
p.sitelinkOf = function( frame )
	local id   = Wd.getFrameArgument( frame, 1 )
	local site = Wd.getFrameArgument( frame, 2 )

	-- Wd.getSitelink() doesn't support omitting the id parameter, so
	-- get the current page's entity id manually and fail if not found.
	id = Wd.getConnectedEntityIdIfInexisting( id )
	if not id then
		return Wd.printError( "entity-not-found" )
	end
	
	-- get the sitelink
	local success, result = Wd.getSitelink( id, nil, site )
	if not success then
		return Wd.printError( Wd.parseError( result ) )
	else
		return result or ""
	end
end

--[=[
  return the number of sitelinks in the given entity
  
  parameters (frame):
  - 1 (opt, string): sitelink filter for projects
  - 2 (opt, string): entity id, or entity id of current page 
  
  returns: (number) sitelink count
]=]
p.sitelinkCount = function( frame )
	local filter = frame.args[ 1 ]
	local id     = frame.args[ 2 ]
	
	if filter then
		filter = "^.*" .. filter .. "$"
	else
		filter = false
	end
	
	local count  = 0
	local entity = mw.wikibase.getEntity( id )
	if entity and entity.sitelinks then
		if filter == false then
			count = Wd.getTableSize( entity.sitelinks )
		else
			for project, _ in pairs( entity.sitelinks ) do
				if string.find( project, filter ) then
					count = count + 1
				end
			end
		end
	end
	
	return count
end

--[=[
  return the badges of the given entity for the given site
  
  parameters (frame):
  - 1 (opt, string): site id, or current site
  - 2 (opt, string): entity id, or entity id of current page
  
  returns: (string) badges
]=]
p.badges = function( frame )
	local site = frame.args[ 1 ]
	local id   = frame.args[ 2 ]
	
	-- entity is required for mw.wikibase.getBadges()
	id = Wd.getConnectedEntityIdIfInexisting( id )
	if not id then
		return Wd.printError( "entity-not-found" )
	end
	
	-- get badges, site is allowed to be nil
	badges = mw.wikibase.getBadges( id, site )
	if type( badges ) == "table" then
		return table.concat( badges, "/" )
	end
	
	-- return empty string by default
	return ""
end

--[=[
  ==============================================================================
  DEBUGGING
  ==============================================================================
]=]

--[=[
  call this in cases of script errors within a function:
  instead of {{#invoke:Wikidata|<method>|...}} call {{#invoke:Wikidata|debug|<method>|...}}

  parameters (frame):
  - (...) call parameters of function to debug
  
  returns: (...) return value of function to debug
]=]
p.debug = function( frame )
	-- get function name
	local func = frame.args[ 1 ]
	if not func then
		return Wd.printError( "invalid-parameters" )
	end
	
	-- 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
			-- skip first unnamed param
			if key > 1 then
				newargs[ key - 1 ] = val
			end
		else
			newargs[ key ] = val
		end
	end
	
	-- replace frame args and call the given function
	frame.args = newargs
	local status, result = pcall( p[ func ], frame )
	
	-- output return value or state
	if status then
		return result
	else
		return mw.ustring.format( "<span class=\"error\">%s</span>", result )
	end
end

--[=[
  print the content of an entity

  parameters (frame):
  - 1 (opt, string): entity id, or entity id of current page
  
  returns: (string) entity json content
]=]
p.printEntity = function( frame )
	local id = Wd.getConnectedEntityIdIfInexisting( frame.args[ 1 ] );
	if id then
		-- get entity and dump it
		local entity = mw.wikibase.getEntity( id )
		if entity then
			return mw.ustring.format( "<pre>%s</pre>", mw.dumpObject( entity ) )
		end
	end
	
	-- return empty string by default
	return ""
end

--[=[
  ==============================================================================
  COORDINATE TEMPLATE HELPERS
  ==============================================================================
]=]

-- formfill Template:Coordinate (NS, EW, name from WikidataEntity) and expand it
-- füllt Vorlage:Coordinate (NS, EW, name mit Wikidata-Werten) + expandiert sie
--  1st frame.arg                            .. Q prefixed entity id (mandatory)
--  named frame.arg "type", "region", "text" .. see doc of 'Coordinate' template
p.ffCoordinate = function(frame)
	local f = frame
	local id = f.args[1] or f.args.Q
	local name = f.args.name or p.labelIn{ args = { nil, id, id = id }}
	
	local coord = mw.text.split(p.claim{ args = { "P625", id, id = id }}, "/")
	coord[1] = tonumber(coord[1])
	coord[2] = tonumber(coord[2])

	local t, r = f.args.type, f.args.region
	if not t
	then t = p.claim{ args = { "P31", id, id = id, parameter = "numeric-id", default = "" }}
		 if t and t ~= "" then
			t = "Q" .. t
			t = t:gsub("Q.*", {
				Q8502  = "mountain",
				Q54050 = "landmark"
			})
		 end
		 if not t or t and t:find("Q", 1, true)
		 then t="" -- no default, let Coordinate warn about unset type= param
		 end
	end
	if not r
	then r = p.claim{ args = { "P17", id, id = id, parameter = "numeric-id", default = "" }}
		 if r and r ~= "" then
		 	r = "Q" .. r
		 	r = p.claim{ args = { "P297", r, id = r }}
		 else
		 	r="" -- no default, let Coordinate warn about unset region= param
		 end
	end
	
	return ('<span data-sort-value="%010.6f"></span>'):format((f.args.sortkey
		or "EW"):find("EW", 1, true) and coord[2]+360.0 or coord[1]+180.0
	) .. f:expandTemplate{ title = "Coordinate", args = {
		NS = coord[1], EW = coord[2], type = t, region = r,
		text = f.args.text or (f.args.maplink and "ICON0" or "/"),
		name = name, simple = f.args.simple
	}} .. (not f.args.maplink and "" or (" " ..
		--f:callParserFunction{ name="#statements", args={ "P625", from = id } }
		f:callParserFunction{ name="#tag:maplink", args={ "",
			class = "no-icon", text = f.args.mlname and name,
			zoom = 12, latitude = coord[1], longitude = coord[2]
		}}
	))
end

p.ffCoordinateAndLatLonMaplink = function(frame)
	frame.args.maplink = 1
	--frame.args.mlname = nil
	return p.ffCoordinate(frame)
end

p.ffCoordinateAndMaplink = function(frame)
	frame.args.maplink = 1
	frame.args.mlname = 1
	return p.ffCoordinate(frame)
end

return p