Modul:Wikidata

Ez a lap műszaki védett
A Wikipédiából, a szabad enciklopédiából

Wikidata[mi ez?] • [dokumentáció: mutat, szerkeszt] • [tesztek: sikeres: 11, sikertelen: 0, kihagyva: 0 (részletek)]

Wikidata-adatok formázott megjelenítése, a {{Wikidata}} sablon implementációja.

Wikikódból hívható függvények

formatStatements

Paraméterek

property
A Wikidatában tárolt tulajdonság azonosítója. Megadható névvel is. Például p856.
value
A Wikidatában tárolt érték helyett megjelenítendő érték.
entityId
A cikkhez kapcsolt Wikidata-elem helyett a megadott Wikidata-elemből olvassa ki a tulajdonság értékét.
pageTitle
A Wikidatához kapcsolt lap címe. Nem adható entityId-vel együtt.
qualProp
Állítás minősítőjének tulajdonságazonosítója. Példa: P548.
qualValue
A szűrésnél alkalmazott érték. Több érték is adható, vesszővel elválasztva. Példa: Q3295609,Q51930650,Q2122918,Q21727724,Q1072356.
direction
Koordináták dimenziója. Értéke latitude, longitude vagy both lehet: az első kettő a további gépi feldolgozásra alkalmas számot adja vissza, a harmadik a Modul:Coordinate segítségével formázott eredményt ad.
link
Értéke nem, ha azt szeretnénk, hogy a függvény értéke ne link legyen, hanem csak a címke; csak, ha pedig azt szeretnénk, hogy csak a kapcsolt Wikipédia-szócikk.
externalId
Helyileg megadott külső azonosító formázása Wikidata helyett. Külső azonosítóknál ezt kell megadni második paraméter helyett!
externalIdLabel
Külső azonosítóhoz tartozó hivatkozás megjelenítési szövege.
lang
A címke nyelvének megadása, több nyelv esetén vesszővel elválasztva, szóköz nélkül, ekkor az első olyan nyelven jeleníti meg a címkét, amin létezik. Alapértelmezetten hu,en. Nyelvvel jelölt szövegek (monolingual text) esetén a nyelv szűrőfeltétele. all értékkel nincs szűrés.
first
Ha azt szeretnénk, hogy csak egy értéket adjon vissza, például képeknél.
showQualifiers
Összes minősítő megjelenítése, vagy megadott minősítők megjelenítése (ha a paraméterérték tulajdonságazonosítók listája, pl. P580, P582).
showReferences
Forráshivatkozások megjelenítése. Alapértelmezetten legfeljebb 5 forráshivatkozás jelenik meg; ha a paraméter értéke egy pozitív egész szám, akkor annyi a limit, ha 0, akkor pedig az összes (esetenként több tucat!) forráshivatkozás megjelenik.
rank
Értékei: preferred, normal, deprecated, all, valid. Ha nincs megadva, akkor a legmagasabb rangúakat adja vissza. A valid a preferred és a normal együttesen.
életkor
Értéke igen.
felsorolás
Az értékek felsorolásának típusa:
lista
pöttyözött lista
számozott lista
számozott lista
sorok
több érték esetén felsorolásjel nélküli lista, egy érték esetén sima szöveg (alapértelmezett)
szöveg
folyószöveg (értékek között vesszővel, utolsó érték előtt „és”-sel)
table
Lua-tömb
lenyíló
Hosszú listák megjelenítése nyitható listaként. Értéke egy szám. Ha a listának több eleme van, akkor nyitható listaként fog megjelenni. Lista, számozott lista és sorok felsorolásnál használható. Példa: Földközi-tenger.
format
raw
tárolt érték formázás nélkül
default
népesség formázása a {{Népesség}}(?) sablon alapmegjelenítése szerint
iso
dátum ISO 8601 formátumban
date-object
dátum a Modul:Time objektumával (csak modulból hívva van értelme)
percentage
számok megjelenítése százalékként
url
URL, például külső azonosító címe
sort
Rendezés több érték estén: üres vagy logikai igaz esetén címke szerint, Pxxx esetén a Pxxx minősítő szerint (a megadott minősítőket előre rendezve), egyébként a sort függvénnyel. sortDesc paraméter jelenléte esetén csökkenő sorrendben történik a rendezés.
sortDesc
Rendezés csökkenő sorrendben. A paraméter jelenléte esetén csökkenő sorrendben történik a rendezés, különben növekvő sorrendben.
dateformat
Dátumformátum a {{#time:}} értelmezőfüggvénynek (csak a népesség dátumánál működik, más helyzetben a változó pontosság – napra pontostól az évmilliárdig bármi lehet – és a függvény behatároltsága – csak 111-től 9999-ig értelmezi az éveket – miatt nincs értelme megadni egy fix formátumot).
punctuation
Központozás az utolsó érték és esetleges minősítői után, de a hozzá tartozó forráshivatkozás előtt. Általában felsorolás=lista esetén van értelme.

containsProperty

Wikidata-tulajdonság meglétének vizsgálata. Lásd {{Wikidata-f}}.

Luából hívható függvények

containsPropertyWithValue

Tulajdonág értékének vizsgálata.

withHighestRank

Tulajdonság értékeinek szűrése a legmagasabb fokozattal (rank) rendelkező értékekre.

formatStatement

Tulajdonság egy értékének formázott megjelenítése.

Használt modulok, sablonok

  • Modul:Arguments – paraméterek beolvasásához
  • Modul:Time – dátum kiírásához (formázottan)
  • {{plainlist}} – alapértelmezett kimenetnél a többelemű listák formázásához
require('strict')
local p = {}
local getArgs = require('Modul:Arguments').getArgs
local frame = mw.getCurrentFrame()
local formatStatements

local i18n = {
	['errors'] = {
		['property-param-not-provided'] = "Hiányzó ''property='' paraméter",
		['property-not-found'] = 'Érvénytelen tulajdonság: %s',
		['entity-not-found'] = 'Nem létező Wikidata-elem',
		['unknown-claim-type'] = 'Ismeretlen az állítás típusa',
		['unknown-snak-type'] = 'Ismeretlen a snak típusa',
		['unknown-datavalue-type'] = 'Ismeretlen az érték típusa',
		['unknown-entity-type'] = 'Ismeretlen a Wikidata-elem típusa',
		['unknown-value-module'] = "A ''value-module'' és ''value-function'' paramétert egyszerre kell beállítani",
		['value-module-not-found'] = "A ''value-module'' nem létező modulra mutat",
		['value-function-not-found'] = "A ''value-function'' nem létező funkcióra mutat",
		['globecoordinate-direction'] = "Az érték típusa ''globecoordinate:'' kell ''direction=latitude'', ''longitude'' vagy ''both''",
		['invalid-value'] = 'Érvénytelen érték',
		['unknown-unit'] = 'Ismeretlen mértékegység: %s',
		['excluding-arguments'] = 'Nem adható %s és %s egyidejűleg',
	},
	['somevalue'] = "''nem ismert''",
	['novalue'] = "''nincs''"
}

local function formatError(key, ...)
	error(i18n.errors[key]:format(...), 2)
end

function p.getUpperLevelOfType(property, typeId, entityId, item)
	local result, statements, visited = {}, {}, {[item and item.id or entityId] = true}
	local function getStatements(entityId, item)
		local wb_statements
		if item then
			wb_statements = item:getBestStatements(property)
		elseif entityId then
			wb_statements = mw.wikibase.getBestStatements(entityId, property)
		else
			return
		end
		for _, s in ipairs(wb_statements) do
			if s.mainsnak.snaktype == 'value' then
				local itemId = 'Q' .. s.mainsnak.datavalue.value['numeric-id']
				if not visited[itemId] then
					visited[itemId] = true
					local item = mw.wikibase.getEntity(itemId)
					if p.containsPropertyWithValue(item, 'P31', typeId) then
						statements[item.id] = s
					else
						getStatements(nil, item)
					end
				end
			end
		end
	end
	getStatements(entityId, item)
	for _, s in pairs(statements) do
		table.insert(result, s)
	end
	return result
end

local function firstValue(statements)
	for _, statement in ipairs(statements) do
		if statement.rank == 'preferred' then
			return {statement}
		end
	end
	for _, statement in ipairs(statements) do
		if statement.rank == 'normal' then
			return {statement}
		end
	end
	return {}
end

local function withRank(statements, ranks)
	local result = {}
	for _i, statement in ipairs(statements) do
		for _j, rank in ipairs(ranks) do
			if statement.rank == rank then
				table.insert(result, statement)
				break
			end
		end
	end
	return result
end

function p.withHighestRank(statements)
	local preferred, normal = {}, {}
	for _, statement in ipairs(statements) do
		if statement.rank == 'preferred' then
			table.insert(preferred, statement)
		elseif statement.rank == 'normal' then
			table.insert(normal, statement)
		end
	end
	return #preferred > 0 and preferred or normal
end

local function atDate(statements, date)
	local result = {}
	local Time = require('Modul:Time')
	local time = Time.newFromIso8601(date, true)
	if not time then
		return statements
	end
	local isQualified = false
	for _, s in ipairs(statements) do
		local startDate, endDate
		if s.qualifiers and s.qualifiers.P580 and #s.qualifiers.P580 == 1 and s.qualifiers.P580[1].snaktype == 'value' then
			startDate = Time.newFromWikidataValue(s.qualifiers.P580[1].datavalue.value)
		end
		if s.qualifiers and s.qualifiers.P582 and #s.qualifiers.P582 == 1 and s.qualifiers.P582[1].snaktype == 'value' then
			endDate = Time.newFromWikidataValue(s.qualifiers.P582[1].datavalue.value)
		end
		if startDate or endDate then
			isQualified = true
		end
		if not startDate and endDate and time <= endDate or
			startDate and not endDate and startDate <= time or
			startDate and endDate and startDate <= time and time <= endDate then
			table.insert(result, s)
		end
	end
	if isQualified then
		return result
	else
		return statements
	end
end

local function getEntityFromId(id)
	if id and id ~= '' then
		return mw.wikibase.getEntity(id)
	end
	return mw.wikibase.getEntity()
end

local function getEntityIdFromValue(value)
	local prefix
	if value['entity-type'] == 'item' then
		prefix = 'Q'
	elseif value['entity-type'] == 'property' then
		prefix = 'P'
	else
		formatError('unknown-entity-type')
	end
	return prefix .. value['numeric-id']
end

local function getChrDates(chrDate, entityId)
	if not chrDate then
		return {}
	end
	if chrDate:match('^P%d+$') then
		local dates = mw.wikibase.getEntity(entityId):getBestStatements(chrDate)
		local ret = {}
		for _, v in ipairs(dates) do
			if v.mainsnak.snaktype == 'value' then
				table.insert(ret, v.mainsnak.datavalue.value.time)
			end
		end
		return ret
	else
		return { chrDate }
	end
end

local function getChrQualifierDates(chrQualifierDate, statement)
	if not chrQualifierDate or not chrQualifierDate:match('^P%d+$') then
		return {}
	end
	if not statement.qualifiers or not statement.qualifiers[chrQualifierDate] then
		return {}
	end
	local ret = {}
	for _, v in ipairs(statement.qualifiers[chrQualifierDate]) do
		if v.snaktype == 'value' then
			table.insert(ret, v.datavalue.value.time)
		end
	end
	return ret
end

local function getDatedStatement(dates, options, dateFields, item)
	if not dateFields then
		dateFields = { 'atDate' }
	end
	local result, conflict = nil, false
	for _, v in ipairs(dates) do
		for _, w in ipairs(dateFields) do
			options[w] = v
		end
		local statement = formatStatements(options, item)
		if statement == '' then
			statement = nil
		end
		if statement and result == nil then
			result = statement
		elseif statement ~= result then
			conflict = true
		end
	end
	if not conflict then
		return result
	else
		return nil
	end
end

local function formatEntityId(entityId, options, statement)
	local link = mw.wikibase.sitelink(entityId)
	if options.link == 'csak' then
		return link
	end
	if link and options.link ~= 'nem' and mw.ustring.sub(link, 1, 10) == 'Kategória:' then
		return '[[' .. link .. ']]'
	end
	local label
	if options.lang then
		for lang in mw.text.gsplit(options.lang, ',') do
			label = mw.wikibase.getLabelByLang(entityId, lang)
			if label then
				break
			end
		end
	else
		label = mw.wikibase.label(entityId)
	end
	if options.labelProperty and options.labelProperty ~= '' then
		local options2 = {}
		for k, v in pairs(options) do
			if k ~= 'labelProperty' then
				options2[k] = v
			end
		end
		options2.property = options.labelProperty
		options2.entityId = entityId
		options2.rank = 'valid'
		options2.link = 'nem'
		options2.lang = options.lang or 'hu'
		options2.firstAfter = true
		options2['felsorolás'] = nil -- felsorolásjel nélkül
		local label2 = formatStatements(options2)
		if label2 and label2 ~= '' then
			label = label2
		end
	end
	if options.chrProperty then
		local dates
		if options.chrDate then
			dates = getChrDates(options.chrDate, options.entityId)
		elseif options.chrQualifierDate and statement then
			dates = getChrQualifierDates(options.chrQualifierDate, statement)
		else
			-- getDatedStatement with an empty date object is a no-op
			-- this should not happen if the module is used correctly
			dates = {}
		end
		local chrLabel = getDatedStatement(dates, {
			property = options.chrProperty,
			entityId = entityId,
			rank = 'valid',
			lang = options.lang or 'hu',
			firstAfter = true,
			['felsorolás'] = nil -- felsorolásjel nélkül
		})
		if chrLabel then
			label = chrLabel
		end
	end
	if label and options.format == 'ucfirst' then
		label = mw.language.getContentLanguage():ucfirst(label)
	end
	if link and options.link ~= 'nem' then
		if label then
			if mw.ustring.sub(label, 2) == mw.ustring.sub(link, 2) and 
				mw.ustring.lower(mw.ustring.sub(label, 1, 1)) == mw.ustring.lower(mw.ustring.sub(link, 1, 1)) then
				return '[[' .. label .. ']]'
			else
				return '[[' .. link .. '|' .. label .. ']]'
			end
		else
			return '[[' .. link .. ']]'
		end
	else
		return label or link  --TODO what if no links and label + fallback language?
	end
end

local function formatTimeValue(value, options)
	if options.format == 'raw' then
		return value.time
	else
		local time = require('Modul:Time').newFromWikidataValue(value)
		if time then
			if options.format == 'iso' then
				return tostring(time)
			elseif options.format == 'date-object' then
				return time
			end
			return time:formatDate(options)
		else
			formatError('invalid-value')
		end
	end
end

local function countryOf(itemId, options, noselflink, chrDate)
	local function getStatement(dates, new_options, item)
		return getDatedStatement(dates, new_options, { 'atDate', 'chrDate' }, item) or formatStatements(new_options, item)
	end
	if not itemId then
		return nil
	end
	local item = mw.wikibase.getEntity(itemId)
	if not item then
		return nil
	end
	local dates = getChrDates(chrDate, options.entityId)
	local dateFields = { 'atDate', 'chrDate' }
	local new_options = {
		property = 'P17',
		chrProperty = options.chrProperty,
		format = 'raw'
	}
	if noselflink and item.id == getStatement(dates, new_options, item) then
		return nil
	end
	new_options.format = nil
	return getStatement(dates, new_options, item)
end

local function formatNum(amount)
	if amount < 10000 and -10000 < amount then
		return tostring(amount):gsub('%.', ',')
	else
		return mw.getContentLanguage():formatNum(amount)
	end
end

local function formatDatavalue(datavalue, options, statement)
	--Use the customize handler if provided
	if options['value-module'] or options['value-function'] then
		if not options['value-module'] or not options['value-function'] then
			return formatError( 'unknown-value-module' )
		end
		local formatter = require ('Module:' .. options['value-module'])
		if formatter == nil then
			return formatError( 'value-module-not-found' )
		end
		local fun = formatter[options['value-function']]
		if fun == nil then
			return formatError( 'value-function-not-found' )
		end
		return fun( datavalue.value, options )
	end

	--Default formatters
	if datavalue.type == 'wikibase-entityid' then
		local itemId = getEntityIdFromValue(datavalue.value)
		if options.format == 'raw' then
			return itemId
		end
		local result = formatEntityId(itemId, options, statement)
		if not result then
			return nil
		end
		local country = options.format == 'with_country' and countryOf(itemId, options, true, options.chrDate)
		return result .. (country and ', ' .. country or '')
	elseif datavalue.type == 'string' then
		return datavalue.value --TODO ids + media
	elseif datavalue.type == 'time' then
		return formatTimeValue(datavalue.value, options)
	elseif datavalue.type == 'globecoordinate' then
		if options.format == 'geohack' then
			return '[[Fájl:OOjs UI icon mapPin-progressive.svg|16px|link=]] ' .. require('Modul:Coordinate').coord {
				datavalue.value.latitude,
				datavalue.value.longitude,
				options.meta,
				precision = 'wikidata',
				format = 'dms'
			}
		end
		if options.direction == 'latitude' then
			return datavalue.value.latitude
		elseif options.direction == 'longitude' then
			return datavalue.value.longitude
		else
			return formatError('globecoordinate-direction')
		end
	elseif datavalue.type == 'quantity' then
		if options.format == 'raw' then
			return datavalue.value.amount
		end
		local result
		local amount = tonumber(datavalue.value.amount)
		if datavalue.value.unit == '1' then
			if options.unit then
				return nil
			end
			if options.format == 'percentage' then
				result = formatNum(amount * 100) .. '%'
			else
				result = formatNum(amount)
			end
		else
			local unitId = datavalue.value.unit:match('Q%d+')
			local sourceUnit = mw.loadData('Modul:Wikidata/units').wikidata_item_ids[unitId]
			if not sourceUnit then
				if not options.unit and unitId then
					local sym_hu, sym_mul
					for _, st in ipairs(mw.wikibase.getBestStatements(unitId, 'P5061')) do  -- unit symbol
						sym_hu = p.formatStatement(st, { lang = 'hu' })
						if sym_hu then
							break
						end
						sym_mul = sym_mul or p.formatStatement(st, { lang = 'mul' })
					end
					sym_hu = sym_hu or sym_mul
					result = formatNum(amount) .. (unitId ~= 'Q28390' and ' ' or '') .. (sym_hu or mw.wikibase.getLabel(unitId) or unitId)
				else
					formatError('unknown-unit', unitId or 'nil')
				end
			else
				local targetUnit = options.unit or sourceUnit
				result = require('Modul:Convert')._convert{amount, sourceUnit, targetUnit, disp = options.showUnit and 'out' or 'number'}
			end
		end
		return result
	elseif datavalue.type == 'monolingualtext' then
		local langs = options.lang or 'hu'
		if langs == 'all' or langs == '~hu' and datavalue.value.language ~= 'hu' then
			return datavalue.value.text
		end
		for lang in mw.text.gsplit(langs, ',') do
			if lang:match('^%s*(.-)%s*$') == datavalue.value.language then
				return datavalue.value.text
			end
		end
		return nil
	else
		formatError('unknown-datavalue-type')
	end
end

local function formatExternalId(options, externalId)
	if options.format == 'raw' then
		return externalId
	end
	local formatterUrl = formatStatements {
		entityId = options.property,
		property = 'P1630',  -- formatter URL
		first = true
	}
	if not formatterUrl then
		return externalId
	end
	local url = formatterUrl:gsub('%$1', ((mw.uri.encode(externalId, 'PATH'):gsub('%%2F', '/')):gsub('%%', '%%%%')))
	if options.format == 'url' then
		return url
	end
	return string.format('[%s %s]', url, options.externalIdLabel ~= '' and options.externalIdLabel or externalId)
end

-- Format an arbitrary snak. Parameters:
--   snak: the snak to format (required)
--   options: a table of formatting options (optional)
--   statement: the statement of which this snak is the main snak of,
--     if it’s a main snak (optional)
local function formatSnak(snak, options, statement)
	local options = options or {}
	if snak.snaktype == 'somevalue' then
		return options.somevalue or (options.format == 'raw' and 'somevalue' or i18n['somevalue'])
	elseif snak.snaktype == 'novalue' then
		return options.format == 'raw' and 'novalue' or i18n['novalue']
	elseif snak.snaktype == 'value' then
		if options['value-module'] or options['value-function'] then
			return formatDatavalue(snak.datavalue, options, statement)
		end
		if snak.datatype == 'math' then
			return frame:extensionTag('math', snak.datavalue.value)
		elseif snak.datatype == 'external-id' then
			return formatExternalId(options, snak.datavalue.value)
		else
			return formatDatavalue(snak.datavalue, options, statement)
		end
	else
		formatError('unknown-snak-type')
	end
end

--[[
	Empty string result used in concatenations.
  ]]
local function formatSnaks(snaks, options)
	local formattedSnaks = {}
	for _, snak in ipairs(snaks) do
		table.insert(formattedSnaks, formatSnak(snak, options))
	end
	return mw.text.listToText(formattedSnaks, options.separator, options.conjunction)
end

local function formatReference(reference, options)
	
	local statedInItem
	
	local function formatProperty(prop, prop2, dashSeparated, extraOptions)
		local localOptions = extraOptions or {}
		if dashSeparated then
			localOptions['felsorolás'] = nil
			localOptions.separator = '&#8201;&#8211;&#8201;'
			localOptions.conjunction = '&#8201;&#8211;&#8201;'
		else
			localOptions['felsorolás'] = 'szöveg'
		end
		local formatted = reference.snaks[prop] and formatSnaks(reference.snaks[prop], localOptions)
		if formatted and formatted ~= '' then
			return formatted
		end
		if prop2 then
			formatted = reference.snaks[prop2] and formatSnaks(reference.snaks[prop2], localOptions)
			if formatted and formatted ~= '' then
				return formatted
			end
		end
		if statedInItem then
			localOptions.property = prop
			formatted = formatStatements(localOptions, statedInItem)
			if formatted and formatted ~= '' then
				return formatted
			end
			if prop2 then
				localOptions.property = prop2
				return formatStatements(localOptions, statedInItem)
			end
		end
		return nil
	end
	
	local args = {}
	local url_source = 'reference'
	args.url = reference.snaks['P854'] and formatSnak(reference.snaks['P854'][1])
	if reference.snaks.P1476 then
		for _, snak in ipairs(reference.snaks.P1476) do
			if not args.title or (snak.datavalue and snak.datavalue.value.language == 'hu') then
				args.title = snak.datavalue.value.text
			end
		end
	end
	statedInItem = reference.snaks.P248 and
		reference.snaks.P248[1].snaktype == 'value' and
		mw.wikibase.getEntity(reference.snaks.P248[1].datavalue.value.id) or
		nil
	if not args.title and statedInItem then
		args.title = formatStatements({
			property = 'P1476',  -- title (monolingual text)
			lang = 'all',
			first = true
		}, statedInItem)
	end
	if not args.title and statedInItem then
		args.title = formatSnaks(reference.snaks.P248, { link = 'nem' })
	end
	if not args.url and statedInItem then
		local id_property
		for _, statement in ipairs(mw.wikibase.getBestStatements(statedInItem.id, 'P1687')) do  -- Wikidata property
			if statement.mainsnak.snaktype == 'value' and (reference.snaks[statement.mainsnak.datavalue.value.id] or 
				next(mw.wikibase.getBestStatements(options.entityId, statement.mainsnak.datavalue.value.id))) then
				id_property = statement.mainsnak.datavalue.value.id
				break
			end
		end
		if not id_property then
			for _, statement in ipairs(mw.wikibase.getBestStatements(statedInItem.id, 'P527')) do
				if statement.mainsnak.snaktype == 'value' then
					id_property = formatStatements{
						entityId = statement.mainsnak.datavalue.value.id,
						property = 'P1687',
						first = true,
						excludespecial = true,
						format = 'raw'
					}
					if id_property then
						break
					end
				end
			end
		end
		if id_property then
			url_source = 'external_id'
			local fs_options = { property = id_property, first = true, excludespecial = true, format = 'url' }
			if reference.snaks[id_property] then
				args.url = formatSnak(reference.snaks[id_property][1], fs_options)
			else
				-- Get property from item instead of reference
				fs_options.entityId = options.entityId
				args.url = formatStatements(fs_options)
			end
		end
	end
	if not args.url and statedInItem then
		url_source = 'stated_in'
		args.url = formatStatements({
			property = 'P953',
			first = true,
			excludespecial = true
		}, statedInItem)
		if not args.url then
			args.url = formatStatements({
				property = 'P856',
				first = true,
				excludespecial = true
			}, statedInItem)
		end
	end
	if args.url and args.title and args.title ~= '' then  -- url and title
		local Time = {}
		if reference.snaks.P577 or reference.snaks.P813 or (reference.snaks.P1065 and reference.snaks.P2960) then
			Time = require('Modul:Time')
		end
		if url_source ~= 'external_id' then
			args.author = formatProperty('P50', 'P2093', true)
		end
		local time
		if reference.snaks.P577 and reference.snaks.P577[1].snaktype == 'value' then
			time = Time.newFromWikidataValue(reference.snaks.P577[1].datavalue.value)
		elseif statedInItem then
			local times = formatStatements({
				property = 'P577',
				first = true,
				excludespecial = true,
				format = 'date-object',
				['felsorolás'] = 'table'
			}, statedInItem)
			time = times[1]
		end
		if time then
			if time.precision >= 11 then
				args.date = time:toIso8601()
			else
				args.year = tostring(time.year)
			end
		end
		if url_source ~= 'external_id' then
			args.publisher = formatProperty('P123', nil, true)
		end
		if not args.publisher and statedInItem then
			args.publisher = formatStatements({ property = 'P123', first = true }, statedInItem)
		end
		args.language = formatProperty('P407', 'P364', false, {link = 'nem'})
		args.accessdate = reference.snaks['P813'] and Time.newFromWikidataValue(reference.snaks.P813[1].datavalue.value):toIso8601()

		if args.url and args.url:match('^https?://www.ksh.hu') then
			args.url = args.url:gsub('p_lang=EN', 'p_lang=HU')
		end
		if args.url and args.url:match('^https://nepszamlalas2022.ksh.hu') then
			args.url = args.url:gsub('en/database', 'adatbazis')
		end

		if reference.snaks.P1065 and reference.snaks.P2960 then
			args.archiveurl = formatSnak(reference.snaks.P1065[1])
			args.archivedate = Time.newFromWikidataValue(reference.snaks.P2960[1].datavalue.value):toIso8601()
		end

		return frame:expandTemplate{title = 'Cite web', args = args}
	else
		local result = {}
		local excluded = true
		for key, referenceSnaks in pairs(reference.snaks) do
			-- exclude "imported from Wikimedia project", "Wikimedia import URL" and "inferred from"
			if key ~= 'P143' and key ~= 'P4656' and key ~= 'P3452' then
				-- don't show references with nothing more than an access date
				if key ~= 'P813' then
					excluded = false
				end
				for _, snak in ipairs(referenceSnaks) do
					table.insert(result, formatSnak(snak, snak.datavalue and snak.datavalue.type == 'time' and { link = 'nem' } or { property=snak.property }))
				end
			end
		end
		if excluded then
			return ''
		else
			return table.concat(result, ', ')
		end
	end
end

local function formatReferences(references, options)
	if not references then
		return ''
	end
	local limit = tonumber(options.showReferences) or 5
	local formattedReferences = {}
	for _, reference in ipairs(references) do
		if limit == 0 or #formattedReferences < limit then
			local formattedReference = formatReference(reference, options)
			if formattedReference and formattedReference ~= '' then
				table.insert(formattedReferences, frame:extensionTag('ref', formattedReference, {name = reference.hash}))
			end
		end
	end
	return table.concat(formattedReferences)
end

local function populationWithPointInTime(statement, options, last)
	if statement.mainsnak.snaktype ~= 'value' then
		return nil
	end
	local population = tonumber(statement.mainsnak.datavalue.value.amount)
	local text = (population < 10000 and tostring(population) or mw.getContentLanguage():formatNum(population)) .. ' fő'

	if statement.qualifiers and statement.qualifiers.P585 and statement.qualifiers.P585[1].snaktype == 'value' then  -- dátum
		local time, fDate = require('Modul:Time').newFromWikidataValue(statement.qualifiers.P585[1].datavalue.value)
		if time.precision >= 11 then
			fDate = mw.getContentLanguage():formatDate(options.dateformat or 'Y. M. j.', time:toIso8601())
		else
			fDate = tostring(time.year)
		end
		text = text .. ' ' .. mw.text.tag('span', {style = 'font-size:90%; white-space:nowrap;'}, '(' .. fDate .. ')')
	end
	if last and options.punctuation then
		text = text .. options.punctuation
	end
	text = text .. formatReferences(statement.references, options)
	local qid = statement.id:match("^[Qq]%d+"):upper()
	if qid == mw.wikibase.getEntityIdForCurrentPage() then
		return text
	end
	if last then
		text = text .. string.format(' <small class="plainlinks noexcerpts">[https://www.wikidata.org/wiki/%s?uselang=hu#P1082 +/-]</small>', qid)
	end
	return text
end

local function sortAuxiliary(statements, propGenerator, sortDesc)
	local newlist, auxlist = {}, {}

	local function sort(a, b)
		if a.prop and b.prop and a.prop ~= b.prop then
			return a.prop < b.prop
		elseif a.prop and b.prop then
			return a.i < b.i
		elseif a.prop or b.prop then
			return not a.prop
		else
			return a.i < b.i
		end
	end

	for i, v in ipairs(statements) do
		auxlist[i] = {
			i = i,
			prop = propGenerator(v, i)
		}
	end
	
	if sortDesc then
		table.sort(auxlist, function (a, b)
				return not sort(a, b)
			end)
	else
		table.sort(auxlist, sort)
	end
	for _, v in ipairs(auxlist) do
		table.insert(newlist, statements[v.i])
	end
	return newlist
end

local function getSortableValue(snak)
	if not snak or snak.snaktype ~= 'value' then
		return nil
	end
	local datavalue = snak.datavalue
	if datavalue.type == 'wikibase-entityid' then
		local id = 'Q' .. datavalue.value['numeric-id']
		local key = mw.wikibase.label(id)
		if not key then
			key = mw.wikibase.sitelink(id)
		end
		if not key then
			return id
		end
		return mw.language.getContentLanguage():caseFold(key)
	elseif datavalue.type == 'string' then
		return datavalue.value
	elseif datavalue.type == 'time' then
		return require('Modul:Time').newFromWikidataValue(datavalue.value)
	elseif datavalue.type == 'quantity' then
		return tonumber(datavalue.value.amount)
	elseif datavalue.type == 'monolingualtext' then
		return datavalue.value.text
	end
	return nil
end

local function sortByValue(statements, sortDesc)
	local function propGenerator(v)
		return getSortableValue(v.mainsnak)
	end
	return sortAuxiliary(statements, propGenerator, sortDesc)
end

local function sortByQualifier(statements, qualifier, sortDesc)
	local function propGenerator(v)
		return v.qualifiers and v.qualifiers[qualifier] and getSortableValue(v.qualifiers[qualifier][1])
	end
	return sortAuxiliary(statements, propGenerator, sortDesc)
end

local function filterByQualifier(options, statements)
	if not options.qualProp then
		return statements
	end
	local result = {}
	options.qualProp = (options.qualProp):upper()
	for _, s in ipairs(statements) do
		if s.qualifiers and s.qualifiers[options.qualProp] then
			if not options.qualValue then
				table.insert(result, s)
			else
				for _, snak in ipairs(s.qualifiers[options.qualProp]) do
					if snak.snaktype == 'value' then
						if snak.datavalue.type == 'wikibase-entityid' and ((options.qualValue):upper() .. ','):find(snak.datavalue.value.id .. ',', 1, true) or
							snak.datavalue.type == 'string' and (options.qualValue .. ','):find(snak.datavalue.value .. ',', 1, true) then
							table.insert(result, s)
							break
						end
					end
				end
			end
		end
	end
	return result
end

--[[
	Local function with forward declaration
--]]
function formatStatements(options, item)
	if not options.property or options.property == '' then
		formatError('property-param-not-provided')
	end
	local property = mw.wikibase.resolvePropertyId(options.property)
	if not property then
		formatError('property-not-found', options.property)
	end
	--Get entity
	local entity = item
	local statements = {}
	if entity then
		statements = entity:getAllStatements(property)
	else
		if options.entityId and options.pageTitle then
			return formatError('excluding-arguments', 'entityId', 'pageTitle')
		end
		if options.pageTitle then
			options.entityId = mw.wikibase.getEntityIdForTitle(options.pageTitle)
		elseif not options.entityId then
			options.entityId = mw.wikibase.getEntityIdForCurrentPage()
		end
		if not options.entityId then
			return options['felsorolás'] == 'table' and {} or nil
		end
		if mw.wikibase.isValidEntityId(options.entityId) then
			statements = mw.wikibase.getAllStatements(options.entityId, property)
		end
	end
	if #statements == 0 then
		return options['felsorolás'] == 'table' and {} or nil
	end

	if options.atDate then
		statements = atDate(statements, options.atDate)
	end

	-- TODO Extract selection and filtering
	if options.rank ~= 'all' then
		if not options.rank then
			statements = p.withHighestRank(statements)
		elseif options.rank == 'valid' then
			statements = withRank(statements, {'normal', 'preferred'})
		else
			statements = withRank(statements, {options.rank})
		end
	end

	if options.typeId then
		statements = p.getUpperLevelOfType(property, options.typeId, options.entityId, entity)
	end

	if options.excludespecial then
		local newStatements = {}
		for _, s in ipairs(statements) do
			if s.mainsnak.snaktype == 'value' then
				table.insert(newStatements, s)
			end
		end
		statements = newStatements
	end
	
	statements = filterByQualifier(options, statements)

	if options.sort then
		local comp = options.sort
		if type(comp) == 'string' and comp:match('[Pp]%d+') then
			statements = sortByQualifier(statements, string.upper(comp), options.sortDesc)
		elseif comp == '' or comp == true then
			statements = sortByValue(statements, options.sortDesc)
		else
			table.sort(statements, comp)
		end
	end

	if options.first then
		statements = firstValue(statements)
	end

	--Format statement and concat them cleanly
	local formattedStatements = {}
	-- loop variable to know if we're processing the last statement
	local i = 0
	for _, statement in ipairs(statements) do
		i = i + 1
		local fs
		if property == 'P1082' and options.format == 'default' then  -- population
			fs = populationWithPointInTime(statement, options, (i == #statements))
		else
			fs = p.formatStatement(statement, options, (i == #statements))
		end
		if fs then
			if options['felsorolás'] == 'lista' then
				fs = '* ' .. fs
			elseif options['felsorolás'] == 'számozott lista' then
				fs = '# ' .. fs
			end
			table.insert(formattedStatements, fs)
		end
	end
	
	local function collapsibleList(frame, args, list, size)
		if args['lenyíló'] and size > tonumber(args['lenyíló']) then
			return frame:expandTemplate{ title = 'Show', args = { 'Lista', list } }
		end
		return list
	end

	local function plainlist(items)
		if #items == 0 then
			return nil
		end
		if #items == 1 then
			return items[1]
		end
		return collapsibleList(frame, options, frame:expandTemplate{ title = 'Plainlist', args = { '\n* ' .. table.concat(items, '\n* ') .. '\n' } }, #items)
	end

	if options['felsorolás'] == 'lista' or options['felsorolás'] == 'számozott lista' then
		return collapsibleList(frame, options, table.concat(formattedStatements, '\n'), #formattedStatements)
	elseif options['felsorolás'] == 'sorok' then
		return plainlist(formattedStatements)
	elseif options['felsorolás'] == 'szöveg' then
		return mw.text.listToText(formattedStatements)
	elseif options['felsorolás'] == 'table' then
		return formattedStatements
	elseif options.separator or options.conjunction then
		options.separator = options.separator and string.gsub(options.separator, '&#32;', ' ')
		options.conjunction = options.conjunction and string.gsub(options.conjunction, '&#32;', ' ')
		return mw.text.listToText(formattedStatements, options.separator, options.conjunction)
	else
		if options.firstAfter then
			return formattedStatements[1] or ''
		end
		return plainlist(formattedStatements)
	end
end

local function formatQualifiers(statement, options, qualifiers)
	local result, orderedResult, startDate, endDate = {}, {}
	local function getInterval()
		if startDate and startDate ~= '' or endDate and endDate ~= '' then
			local dash = '–'
			if (startDate and not startDate:match('^%d+$')) or (endDate and not endDate:match('^%d+$')) then
				dash = ' – '
			end
			return (startDate or '') .. dash .. (endDate or '')
		end
	end
	if type(qualifiers) == 'string' and qualifiers:find('[Pp]%d') then
		qualifiers = qualifiers:upper()
	else
		qualifiers = nil
	end
	for key, snaks in pairs(statement.qualifiers) do
		if not qualifiers or qualifiers:find(key, 1, true) then
			if key == 'P580' then
				startDate = formatSnak(snaks[1], {link = 'nem'})
			elseif key == 'P582' then
				endDate = formatSnak(snaks[1], {link = 'nem'})
			else
				for _, snak in ipairs(snaks) do
					local formattedSnak = formatSnak(snak, { link = 'nem', ['format'] = 'geohack', showUnit = true })
					if qualifiers then
						-- order determined by the order in parameter
						if orderedResult[key] then
							table.insert(orderedResult[key], formattedSnak)
						else
							orderedResult[key] = { formattedSnak }
						end
					else
						table.insert(result, formattedSnak)
					end
				end
			end
		end
	end
	if qualifiers then
		for qualifier in qualifiers:gmatch('P%d+') do
			if qualifier == 'P580' or qualifier == 'P582' then
				local interval = getInterval()
				if interval then
					table.insert(result, interval)
					-- prevent interval to be inserted twice (both at P580 and P582)
					startDate, endDate = nil, nil
				end
			elseif orderedResult[qualifier] then
				for _, v in ipairs(orderedResult[qualifier]) do
					table.insert(result, v)
				end
			end
		end
	else
		local interval = getInterval()
		if interval then
			table.insert(result, 1, interval)
		end
	end
	return table.concat(result, ', ')
end

function p.formatStatement(statement, options, last)
	if not statement.type or statement.type ~= 'statement' then
		formatError('unknown-claim-type')
	end
	local options = options or {}
	local result
	if statement.mainsnak.snaktype == 'somevalue' and statement.mainsnak.datatype == 'time' and statement.qualifiers and 
		(statement.qualifiers.P1319 or statement.qualifiers.P1326) then
		-- TODO Extract method
		if statement.qualifiers.P1319 then
			if statement.qualifiers.P1326 then
				result = formatSnak(statement.qualifiers.P1319[1]) .. ' és ' .. formatSnak(statement.qualifiers.P1326[1]) .. ' között'
			else
				result = formatSnak(statement.qualifiers.P1319[1]) .. ' után'
			end
		else
			result = formatSnak(statement.qualifiers.P1326[1]) .. ' előtt'
		end
	else
		result = formatSnak(statement.mainsnak, options, statement)
	end

	--TODO reference and qualifiers
	if result and result ~= '' then
		if options.showQualifiers and statement.qualifiers then
			local formattedQualifiers = formatQualifiers(statement, options, options.showQualifiers)
			if formattedQualifiers and formattedQualifiers ~= '' then
				result = result .. ' <small>(' .. formattedQualifiers .. ')</small>'
			end
		end
		if last and options.punctuation then
			result = result .. options.punctuation
		end
		if options.showReferences then
			result = result .. formatReferences(statement.references, options)
		end
	end
	return result
end

function p.formatStatements(frame, args, item)
	if not args then
		args = getArgs(frame, { removeBlanks = false })
	end

	--If a value if already set, use it
	if args.value and args.value ~= '' then
		return args.value ~= '-' and args.value or nil
	end
	if args.externalId == '-' then
		return nil
	end
	if args.externalId and args.externalId ~= '' then
		return formatExternalId(args, args.externalId)
	end
	args.entityId = args.entityId ~= '' and args.entityId or nil
	args.pageTitle = args.pageTitle ~= '' and args.pageTitle or nil
	args.qualProp = args.qualProp ~= '' and args.qualProp or nil
	args.qualValue = args.qualValue ~= '' and args.qualValue or nil
	return formatStatements(args, item)
end

--[[
	Returns string true if connected Wikibase item contains property specified 
	by property argument, empty string otherwise.
	Used by template Wikidata-f in conditional expressions.
--]]
function p.containsProperty(frame, args, item)
	if not args then
		args = getArgs(frame, { removeBlanks = false })
	end
	if not args.property then
		formatError('property-param-not-provided')
	end
	if args.value == '-' or args.externalId == '-' then
		return nil
	end
	if args.value then
		return true
	end
	if args.externalId and args.externalId ~= '' then
		return true
	end
	if not item then  -- default usage from Wikidata-f template
		args.entityId = args.entityId ~= '' and args.entityId or nil
		args.pageTitle = args.pageTitle ~= '' and args.pageTitle or nil
		local entityId = args.entityId
		if not entityId then
			if args.pageTitle then
				entityId = mw.wikibase.getEntityIdForTitle(args.pageTitle)
			else
				entityId = mw.wikibase.getEntityIdForCurrentPage()
			end
		end
		if not entityId then
			return nil
		end
		if next(mw.wikibase.getBestStatements(entityId, args.property:upper())) == nil then
			return nil
		end
		return true
	end
	if not item.claims or not item.claims[args.property:upper()] then
		return nil
	end
	if args.rank == 'all' then
		return true
	elseif args.rank == 'valid' then
		-- if we're just searching, it doesn't matter
		-- if we want all or only the better ones
		args.rank = nil
	end
	for _, claim in ipairs(item.claims[args.property:upper()]) do
		if
			(args.rank and claim.rank == args.rank or not args.rank and claim.rank ~= 'deprecated') and
			(not args.excludespecial or claim.mainsnak.snaktype == 'value')
		then
			return true
		end
	end
	return nil
end

function p.containsPropertyWithValue(item, property, value)
	if not property or not value then
		return false
	end
	if not item or not item.claims or not item.claims[property:upper()] then
		return false
	end
	for _, statement in ipairs(item.claims[property:upper()]) do
		if statement.rank ~= 'deprecated' and statement.mainsnak.snaktype == 'value' then
			local type = statement.mainsnak.datavalue.type
			if type == 'wikibase-entityid' then
				if 'Q' .. statement.mainsnak.datavalue.value['numeric-id'] == value then
					return true
				end
			end
		end
	end
	return false
end

function p.isOfType(item, class)
	if not item or not item.claims or not item.claims.P31 or not class then
		return false
	end
	local isSubclass, visited
	local level = 0

	local function checkProperty(item, property)
		-- mw.wikibase.getEntity is expensive when called with the ID of an entity not connected to the current page.
		if level > 15 then
			return false
		end
		level = level + 1
		for _, s in ipairs(item:getBestStatements(property)) do
			if s.mainsnak.snaktype == 'value' then
				local itemId = 'Q' .. s.mainsnak.datavalue.value['numeric-id']
				if itemId == class or isSubclass(itemId) then
					return true
				end
			end
		end
		return false
	end

	isSubclass = function (itemId)
		if visited[itemId] then
			return false
		end
		local item = mw.wikibase.getEntity(itemId)
		if not item then  -- deleted item
			return false
		end
		visited[itemId] = true
		visited[item.id] = true
		return checkProperty(item, 'P279')
	end

	visited = { [item.id] = true }
	return checkProperty(item, 'P31')
end

--[[
	Local functions available to other modules.
  ]]
p.formatters = {
	formatDatavalue = formatDatavalue,
	formatSnak = formatSnak,
	formatSnaks = formatSnaks
}

return p