Modul:Wikidata
Erscheinungsbild
| Vorlagenprogrammierung | Diskussionen | Lua | Test | Unterseiten | ||
| Modul | Deutsch
|
Modul: | Dokumentation | |||
Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus
-- module local variables
local wiki =
{
langcode = mw.language.getContentLanguage().code
}
-- internationalisation
local 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"] =
{
-- $1 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"
}
local numberIsParentCalls = 0 -- global value to count calls of expensive function isParent, including recursive calls
--important properties
local propertyId =
{
["starttime"] = "P580",
["endtime"] = "P582",
["pointoftime"] = "P585"
}
local formatchar =
{
[10] = {"n","m","M","F","xg"}, --precision: month
[11] = {"W","j","d","z","D","l","N","w"}, --precision: day
[12] = {"a","A","g","h","G","H"}, --precision: hour
[13] = {"i"}, --precision: minute
[14] = {"s","U"} --precision: second
}
--[=[
==============================================================================
GENERAL HELPERS
==============================================================================
]=]
--[=[
print an error with the given error code
parameters:
- code (string): error code
- info (opt, string): additional information
returns: (string) error html
]=]
local function printError( code, info )
local text
text = i18n.errors[ code ] or code or ""
if info then
text = text .. ": " .. info
end
return mw.ustring.format( i18n.errortext, text )
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
]=]
local function orderedPairs( array, order )
if not order then
return pairs( array )
end
-- return iterator function
local i = 0
return function()
i = i + 1
if order[ i ] then
return order[ i ], array[ order[ i ] ]
end
end
end
--[=[
return the size of the given table
parameters:
- tbl (table): table to get size of
returns: (number) number of pairs in table
]=]
local function tableSize( tbl )
if type( tbl ) ~= "table" then
return 0
end
local count = 0
for _ in pairs( tbl ) do
count = count + 1
end
return count
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
]=]
local function isValidEntityIdTypesafe( 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
]=]
local function entityExistsTypesafe( id )
return ( 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
]=]
local function getConnectedEntityIdIfInvalid( id )
if 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
]=]
local function getConnectedEntityIdIfInexisting( id )
if 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
]=]
local function extractEntityId( 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
]=]
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
--[=[
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
]=]
function formatDate( 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 = 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( i18n.datetime[ precision ], tostring( y2 ) )
if year < 0 then
relative = mw.ustring.format( i18n.datetime.beforenow, relative )
else
relative = mw.ustring.format( i18n.datetime.afternow, relative )
end
return relative
end
-- precision is decades, centuries and millenia
local era
if precision == 6 then
era = mw.ustring.format( i18n.datetime[ 6 ], tostring( math.floor( ( math.abs( year ) - 1 ) / 1000 ) + 1 ) )
end
if precision == 7 then
era = mw.ustring.format( i18n.datetime[ 7 ], tostring( math.floor( ( math.abs( year ) - 1 ) / 100 ) + 1 ) )
end
if precision == 8 then
era = mw.ustring.format( 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( i18n.datetime.bc, '"', "" ), era )
elseif year > 0 then
era = mw.ustring.format( mw.ustring.gsub( 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( formatchar[ i ] ) do
if formatstr:find( ch ) then
formatstr = i18n.datetime[ precision ]
end
end
end
else
formatstr = i18n.datetime[ precision ]
end
if year == 0 then
formatstr = mw.ustring.gsub( formatstr, i18n.datetime[ 9 ], "" )
elseif year < 0 then
-- MediaWiki's formatDate doesn't support negative years
date = mw.ustring.sub( date, 2 )
formatstr = mw.ustring.gsub( formatstr, i18n.datetime[ 9 ], mw.ustring.format( i18n.datetime.bc, i18n.datetime[ 9 ] ) )
elseif year > 0 and i18n.datetime.ad ~= "%s" then
formatstr = mw.ustring.gsub( formatstr, i18n.datetime[ 9 ], mw.ustring.format( i18n.datetime.ad, i18n.datetime[ 9 ] ) )
end
return mw.language.new( 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
]=]
local function datavalueTimeToDateObject( 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 datavalueTimeToDateObject()
returns: (number) datetime representation
]=]
function julianDay( dateObject )
local year = dateObject.year
local month = dateObject.month or 0
local day = dateObject.day or 0
if month == 0 then
month = 1
end
if day == 0 then
day = 1
end
if month <= 2 then
year = year - 1
month = month + 12
end
local time = ( ( ( ( dateObject.sec or 0) / 60 + ( dateObject.min or 0 ) + ( dateObject.timezone or 0 ) ) / 60 ) + ( dateObject.hour or 0 ) ) / 24
local b
if dateObject.julian then
b = 0
else
local century = math.floor( year / 100 )
b = 2 - century + math.floor( century / 4 )
end
return math.floor( 365.25 * ( year + 4716 ) ) + math.floor( 30.6001 * ( month + 1 ) ) + day + time + b - 1524.5
end
--[=[
==============================================================================
SNAK RENDERERS
==============================================================================
]=]
--[=[
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
]=]
local function printDatavalueCoordinate( data, parameter )
if parameter then
if parameter == "globe" then
-- extract entity id from the globe URI
data.globe = 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
]=]
local function printDatavalueQuantity( data, parameter )
if not parameter or parameter == "amount" then
return tonumber( data.amount )
elseif parameter == "unit" then
return 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
]=]
local function printDatavalueTime( 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 = extractEntityId( data.calendarmodel )
elseif para and para == "time" then
-- output formatted time string
return formatDate( data.time, data.precision, data.timezone, formatstr )
elseif parameter == "time" then
-- output standard date format
data.time = normalizeDate( data.time )
end
return data[ parameter ]
else
-- return full date
return 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
]=]
local function printDatavalueEntity( 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 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
]=]
local function printDatavalueMonolingualText( data, parameter )
if parameter then
return data[ parameter ]
else
return mw.ustring.format( i18n.monolingualtext, data[ "language" ], data[ "text" ] )
end
end
--[=[
==============================================================================
DATA ACCESS
==============================================================================
]=]
--[=[
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
]=]
function getSnakValue( snak, parameter )
if snak.snaktype == "value" then
-- call the respective snak renderer
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
-- novalue, somevalue
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
]=]
function getQualifierSnak( claim, qualifierId )
-- return nil if claim is invalid
-- TODO: add error message for invalid claim data
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, 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
]=]
function getQualifierSortValue( claim, qualifierId )
local snak = getQualifierSnak( claim, qualifierId )
if snak and snak.snaktype == "value" then
if snak.datavalue.type == "time" then
-- use datetime representation vor time datavalue
return julianDay( datavalueTimeToDateObject( snak.datavalue.value ) )
else
-- use snak value for other datavalue types
return 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
]=]
function getClaimValue( claim, qualifierId, parameter )
local error
local snak
snak, error = getQualifierSnak( claim, qualifierId )
if snak then
return getSnakValue( snak, parameter )
end
return nil, error
end
--[=[
convert reference object to string representation
parameters:
- ref (table): ref data
returns: (string|nil) reference text or nil if error
]=]
function formatReference( 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, 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
]=]
function getReferences( frame, claim )
local result = ""
-- loop through all references
for ref in pairs( claim.references or {} ) do
-- put snaks to table for later formatting with formatReference()
local refTable = {}
for snakkey, snakval in orderedPairs( claim.references[ ref ].snaks or {}, claim.references[ ref ][ "snaks-order" ] ) do
if #snakval == 1 then
-- only one value in snak
refTable[ snakkey ] = getSnakValue( snakval[ 1 ] )
else
-- multiple values in snak
local multival = {}
for snakidx = 1, #snakval do
table.insert( multival, getSnakValue( snakval[ snakidx ] ) )
end
refTable[ snakkey ] = multival
end
end
-- format reference with data in refTable
local formattedRef, f = 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 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
]=]
local function hasqualifier( 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 existance 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
]=]
local function hassource( 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
]=]
function atdate( 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 = datavalueTimeToDateObject( { time = mw.language.getContentLanguage():formatDate( "+Y-m-d\\TH:i:s\\Z", checkdate ) } )
checkdateyear = 0
end
end
local refjd = julianDay( refdate )
-- get date qualifiers for claim
local exactdate = getQualifierSortValue( claim, propertyId[ "pointoftime" ] )
local mindate = getQualifierSortValue( claim, propertyId[ "starttime" ] )
local maxdate = getQualifierSortValue( claim, 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 = julianDay( { 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
]=]
local function notdeprecated( claim )
return claim.rank ~= "deprecated"
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
]=]
local function isParent( item, parent, exitItem, maxDepth, depth )
-- increment call number
numberIsParentCalls = 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 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] = getSnakValue( v.mainsnak, "numeric-id" )
end
end
if claims279 and #claims279 > 0 then
for i, v in ipairs( claims279 ) do
parentIds[ #parentIds + 1 ] = 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 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 = 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
--returns a table of claims excluding claims not passed the filters
function filterClaims(frame, claims)
local function filter(condition, filterfunction)
if not frame.args[condition] then
return
end
local newclaims = {}
for i, claim in pairs(claims) do
if filterfunction(claim, frame.args[condition]) then
table.insert(newclaims, claim)
end
end
claims = newclaims
end
filter("hasqualifier", hasqualifier)
filter("hassource", hassource)
filter("atdate", atdate)
if not frame.args.includedeprecated then
frame.args.notdeprecated = true
filter("notdeprecated", notdeprecated)
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]
local negated = string.sub(values, 1, 1) == "!"
if negated then values = string.sub(values, 2) end
for i, claim in pairs(claims) do
local hasvalue = false
for val in mw.text.gsplit(values, ",") do
if hasqualifier(claim, frame.args[key - 1], val) then
hasvalue = true
break
end
end
if hasvalue ~= negated then table.insert(newclaims, claim) end
end
claims = newclaims
end
end
return claims
end
--[=[
==============================================================================
TEMPLATE EXPORT
==============================================================================
]=]
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
]=]
function p.isSubclass( frame )
local result
-- check for validity of start and parent item
if entityExistsTypesafe( frame.args[ "id" ] ) and 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 = isParent( frame.args[ "id" ], frame.args[ "parent" ], frame.args[ "exitItem" ], maxDepth)
-- mw.log(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
function p.claim(frame)
local property = frame.args[1] or ""
local id = frame.args["id"]
local qualifierId = frame.args["qualifier"]
local parameter = frame.args["parameter"]
local language = frame.args["language"]
local 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"]
if default then showerrors = nil end
-- get wikidata entity
if id then
if not mw.wikibase.isValidEntityId(id) then
if showerrors then
return printError("entity-not-valid")
else
local temp = mw.title.new(i18n["maintenance-pages"]["entity-not-valid"], "Modul").exists
return default
end
elseif not mw.wikibase.entityExists(id) then
if showerrors then
return printError("entity-not-found")
else
local temp = mw.title.new(i18n["maintenance-pages"]["entity-not-found"], "Modul").exists
return default
end
end
end
local entity = mw.wikibase.getEntity(id)
if not entity then
if showerrors then return printError("entity-not-found") else return default end
end
-- check if property exists
local realProp = mw.wikibase.resolvePropertyId(property)
if not realProp then
local temp = mw.title.new(i18n["maintenance-pages"]["property-not-existing"], "Modul").exists
end
-- fetch the first claim of 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 printError("property-not-found")
else return default end
end
--filter claims
claims = 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
local comparator
if sort then
comparator = function(a, b) --comparator function for sorting statements based on qualifier value
-- load qualifier values
local QualifierSortValueA = getQualifierSortValue(claims[a], sort)
local QualifierSortValueB = 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 QualifierSortValueB then
if not QualifierSortValueA then
-- if neither of the two statements has this qualifier, arbitrarily but consistently return a < b
return a < b
elseif sortEmptiesFirst then
return false
else
return true
end
elseif not QualifierSortValueA then
if sortEmptiesFirst then return true else return false end
end
if type(QualifierSortValueA) ~= type(QualifierSortValueB) and not (tonumber(QualifierSortValueA) and tonumber(QualifierSortValueB)) then
if tonumber(QualifierSortValueA) then return true
elseif tonumber(QualifierSortValueB) then return false
elseif tostring(QualifierSortValueA) and tostring(QualifierSortValueB) then
if inverse then return tostring(QualifierSortValueA) > tostring(QualifierSortValueB) else return tostring(QualifierSortValueA) < tostring(QualifierSortValueB) end
else return false end -- different types, neither numbers nor strings, no chance to compare => random result to avoid script error
elseif tonumber(QualifierSortValueA) and tonumber(QualifierSortValueB) then
QualifierSortValueA = tonumber(QualifierSortValueA)
QualifierSortValueB = tonumber(QualifierSortValueB)
end
if inverse then
return QualifierSortValueA > QualifierSortValueB
else
return QualifierSortValueA < QualifierSortValueB
end
end
elseif sortInItem then
-- fill table sortkeys
local sortkeys = {}
local snakSingle
local sortkeyValueId
local claimContainingValue
for idx, claim in pairs(claims) do
snakSingle = getQualifierSnak(claim)
sortkeyValueId = "Q" .. getSnakValue(snakSingle, "numeric-id")
claimContainingValue = mw.wikibase.getEntity(sortkeyValueId).claims[mw.wikibase.resolvePropertyId(sortInItem)]
if claimContainingValue then
sortkeys[#sortkeys + 1] = getClaimValue(claimContainingValue[1])
else
sortkeys[#sortkeys + 1] = ""
end
end
comparator = function(a, b)
if inverse then
return sortkeys[a] > sortkeys [b]
else
return sortkeys[a] < sortkeys [b]
end
end
else
-- sort by claim rank
comparator = function(a, b)
local rankmap = { deprecated = 2, normal = 1, preferred = 0 }
local ranka = rankmap[claims[a].rank or "normal"] .. string.format("%08d", a)
local rankb = rankmap[claims[b].rank or "normal"] .. string.format("%08d", b)
return ranka < rankb
end
end
table.sort(sortindices, comparator)
local result
local error
if countValues then
local count = 0
for _ in pairs(claims) do count = count + 1 end
result = count
elseif list then
list = string.gsub(list, "\\n", "\n") -- if a newline is provided (whose backslash will be escaped) unescape it
local value
-- iterate over all elements and return their value (if existing)
result = {}
for idx in pairs(claims) do
local claim = claims[sortindices[idx]]
value, error = getClaimValue(claim, qualifierId, parameter)
if not value and value ~= 0 and showerrors then value = error end
if not value and value ~= 0 and includeempty then value = "" end
if value and references then value = value .. getReferences(frame, claim) end
result[#result + 1] = value
end
if listMaxItems and listMaxItems > 0 then
result = table.concat(result, list, 1, math.min(#result, listMaxItems))
else
result = table.concat(result, list)
end
else
-- return first element
local claim = claims[sortindices[1]]
if language == "Q" then
result, error = "Q" .. getSnakValue(getQualifierSnak(claim), "numeric-id")
elseif 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 = getClaimValue(claim, qualifierId, parameter)
break
end
end
else
result, error = getClaimValue(claim, qualifierId, parameter)
end
if references == "only" then
result = getReferences(frame, claim)
elseif result and references then
result = result .. getReferences(frame, claim)
end
end
if result then return result else
if showerrors then return error else return default end
end
end
function p.getValue(frame)
local param = frame.args[2]
if param == "FETCH_WIKIDATA" or param == i18n["FETCH_WIKIDATA"] then return p.claim(frame) else return param end
end
--[=[
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
]=]
function p.pageId( 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
]=]
function p.labelIn( 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 = getConnectedEntityIdIfInexisting( id )
if not id then
return ""
end
return mw.wikibase.getLabelByLang( id, langcode or 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
]=]
function p.labelOf( 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 = getConnectedEntityIdIfInexisting( id )
if not id then
return 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
]=]
function p.descriptionIn( 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 = getConnectedEntityIdIfInexisting( id )
if not id then
return ""
end
return mw.wikibase.getDescriptionByLang( id, langcode or 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
]=]
function p.sitelinkOf( frame )
local id = frame.args[ 1 ]
local site = frame.args[ 2 ]
-- mw.wikibase.getSitelink() doesn't support omitting the id parameter, so
-- get the current page's entity id manually and fail if not found.
id = getConnectedEntityIdIfInexisting( id )
if not id then
return printError( "entity-not-found" )
end
-- if site is nil, the sitelink to the current wiki will be returned
return mw.wikibase.getSitelink( id, site ) or ""
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
]=]
function p.sitelinkCount( 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 = tableSize( 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
]=]
function p.badges( frame )
local site = frame.args[ 1 ]
local id = frame.args[ 2 ]
-- entity is required for mw.wikibase.getBadges()
id = getConnectedEntityIdIfInexisting( id )
if not id then
return 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>|...}}
function p.debug(frame)
local func = frame.args[1]
if func then
-- create new parameter set, where the first parameter with the function name is removed
local newargs = {}
for key, val in pairs(frame.args) do
if type(key) == "number" then
if key > 1 then newargs[key - 1] = val end
else
newargs[key] = val
end
end
frame.args = newargs
local status, result = pcall(p[func], frame)
-- if status then return tostring(result) or "" else return '<span class="error">' .. result .. '</span>' end -- revert
if status then return result else return '<span class="error">' .. result .. '</span>' end
else
return printError("invalid-parameters")
end
end
function p.printEntity(frame)
local id = frame.args[1]
local entity = mw.wikibase.getEntity(id)
if entity then return "<pre>" .. mw.text.jsonEncode(entity, mw.text.JSON_PRETTY) .. "</pre>" end
end
--[=[
==============================================================================
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
function p.ffCoordinate(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, language = "Q" }}
t = t and t:gsub("Q.*", {
Q8502 = "mountain",
Q54050 = "landmark"
})
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, language = "Q" }}
r = r and p.claim{ args = { "P297", r, id = r }}
if not r
then 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
function p.ffCoordinateAndLatLonMaplink(frame)
frame.args.maplink = 1
--frame.args.mlname = nil
return p.ffCoordinate(frame)
end
function p.ffCoordinateAndMaplink(frame)
frame.args.maplink = 1
frame.args.mlname = 1
return p.ffCoordinate(frame)
end
return p