Modul:Wikidata

A Wikiszótárból, a nyitott szótárból

A modult a Modul:Wikidata/doc lapon tudod dokumentálni

require('Modul:No globals')

local p = {}

local getArgs = require('Modul:Arguments').getArgs
local frame = mw.getCurrentFrame()

local i18n = {
	['errors'] = {
		['property-param-not-provided'] = "Hiányzó ''property='' paraméter",
		['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',
	},
	['somevalue'] = "''nem ismert''",
	['novalue'] = "''nincs''"
}

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

local function 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 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 = p.formatStatements(nil, 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)
	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 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 = p.formatStatements(nil, options2)
		if label2 and label2 ~= '' then
			label = label2
		end
	end
	if options.chrProperty and options.chrDate then
		local dates = getChrDates(options.chrDate, options.entityId)
		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 p.formatStatements(nil, 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)
	--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)
		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.direction == 'latitude' then
			return datavalue.value.latitude
		elseif options.direction == 'longitude' then
			return datavalue.value.longitude
		elseif options.direction == 'both' then
			return require('Modul:Coordinate').coord{
				datavalue.value.latitude, datavalue.value.longitude,
				precision = 'wikidata', format = 'dms'
			}
		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
			result = formatNum(amount)
		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 then
					local unitItem = mw.wikibase.getEntity(unitId)
					if p.isOfType(unitItem, 'Q8142') then  -- currency
						local symbol = unitItem:getBestStatements('P558')[1]
						result = formatNum(amount) .. ' ' .. (symbol and p.formatStatement(symbol) or unitItem:getLabel() or unitId)
					else
						result = formatNum(amount) .. ' ' .. (unitItem:getLabel() or unitId)
					end
				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
		if not options.lang or options.lang == '~hu' and datavalue.value.language ~= 'hu' then
			return datavalue.value.text
		end
		for lang in mw.text.gsplit(options.lang, ',') 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 formatSnak(snak, options)
	local options = options or {}
	if snak.snaktype == 'somevalue' then
		return options.somevalue or i18n['somevalue']
	elseif snak.snaktype == 'novalue' then
		return i18n['novalue']
	elseif snak.snaktype == 'value' then
		if options['value-module'] or options['value-function'] then
			return formatDatavalue(snak.datavalue, options)
		end
		if snak.datatype == 'math' then
			return frame:extensionTag('math', snak.datavalue.value)
		elseif snak.datatype == 'external-id' then
			if options['formatExternal'] then
				local formatter = p.formatStatements{
					entityId = snak.property,
					property = 'P1630',
					first = true
				}
				if formatter and formatter ~= '' then
					local id = formatDatavalue(snak.datavalue, options)
					local url = string.gsub( formatter, '%$1', ( string.gsub( id, '%%', '%%%%' ) ) )
					if options['formatExternalPlainUrl'] then
						return url
					else
						return string.format( '[%s %s]', url, id )
					end
				end
				-- else fall back to plain version
			end
			return formatDatavalue(snak.datavalue, options)
		else
			return formatDatavalue(snak.datavalue, options)
		end
	else
		formatError('unknown-snak-type')
	end
end

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, entity)
	local stated_id
	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 stated_id then
			localOptions.entityId = stated_id
			localOptions.property = prop
			formatted = p.formatStatements(localOptions)
			if formatted and formatted ~= '' then
				return formatted
			end
			if prop2 then
				localOptions.property = prop2
				return p.formatStatements(localOptions)
			end
		end
		return nil
	end
	local args = {}
	local url_source = 'reference'
	args.url = reference.snaks['P854'] and formatSnak(reference.snaks['P854'][1], options)
	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
	if reference.snaks.P248 and type(entity) == 'table' then
		stated_id = formatSnak(reference.snaks.P248[1], {format = 'raw'})
		if not args.title then
			args.title = p.formatStatements{
				entityId = stated_id,
				property = 'P1476',
				first = true
			}
		end
		if not args.title then
			args.title = formatSnaks(reference.snaks.P248, {link = 'nem'})
		end
		if not args.url then
			local id_property = p.formatStatements{
				entityId = stated_id,
				property = 'P1687',
				first = true,
				format = 'raw'
			}
			if not id_property then
				local parts = p.formatStatements{
					entityId = stated_id,
					property = 'P527',
					['felsorolás'] = 'table',
					format = 'raw'
				}
				for _, v in ipairs(parts) do
					id_property = p.formatStatements{
						entityId = v,
						property = 'P1687',
						first = true,
						format = 'raw'
					}
					if id_property then
						break
					end
				end
			end
			if id_property then
				url_source = 'external_id'
				local id = reference.snaks[id_property]
				local fs_options = {first = true, formatExternal = true, formatExternalPlainUrl = true}
				if id then
					args.url = formatSnak(id[1], fs_options)
				else
					fs_options.property = id_property
					-- call p.formatStatements() as local formatStatements() is not defined yet
					args.url = p.formatStatements(nil, fs_options, entity)
				end
			end
		end
		if not args.url then
			url_source = 'stated_in'
			args.url = p.formatStatements{
				entityId = stated_id,
				property = 'P953',
				first = true
			}
		end
	end
	if args.url 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 then
			time = Time.newFromWikidataValue(reference.snaks.P577[1].datavalue.value)
		elseif stated_id then
			time = p.formatStatements{
				entityId = stated_id,
				property = 'P577',
				first = true,
				format = 'date-object'
			}
		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
		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 reference.snaks.P1065 and reference.snaks.P2960 then
			args.archiveurl = formatSnak(reference.snaks.P1065[1], options)
			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
		options.formatExternal = 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 options))
				end
			end
		end
		if excluded then
			return ''
		else
			return table.concat(result, ', ')
		end
	end
end

local function formatReferences(references, options, entity)
	if not references then
		return ''
	end
	if not entity and options.entityId then
		entity = mw.wikibase.getEntity(options.entityId)
	end
	local formattedReferences = {}
	for _, reference in ipairs(references) do
		local formattedReference = formatReference(reference, options, entity)
		if formattedReference and formattedReference ~= '' then
			table.insert(formattedReferences, frame:extensionTag('ref', formattedReference, {name = reference.hash}))
		end
	end
	return table.concat(formattedReferences)
end

local function populationWithPointInTime(statement, options, entity, 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 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, entity)
	if last then
		text = text .. string.format(' <small class="plainlinks noexcerpts">[https://www.wikidata.org/wiki/%s?uselang=hu#P1082 +/-]</small>', entity and entity:getId() or options.entityId)
	end
	return text
end

local function sortAuxiliary(statements, propGenerator)
	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
	table.sort(auxlist, sort)
	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)
	local function propGenerator(v)
		return getSortableValue(v.mainsnak)
	end
	return sortAuxiliary(statements, propGenerator)
end

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

local function formatStatements(options, item)
	if not options.property or options.property == '' then
		formatError('property-param-not-provided')
	end
	local property = options.property:upper()
	--Get entity
	local entity = item
	local statements
	if entity then
		statements = entity:getAllStatements(property)
	else
		if not options.entityId or options.entityId == '' then
			options.entityId = mw.wikibase.getEntityIdForCurrentPage()
		end
		if not options.entityId then
			return options['felsorolás'] == 'table' and {} or nil
		end
		statements = mw.wikibase.getAllStatements(options.entityId, property)
	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 = 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

	if options.sort then
		local comp = options.sort
		if type(comp) == 'string' and comp:match('[Pp]%d+') then
			statements = sortByQualifier(statements, string.upper(comp))
		elseif comp == '' or comp == true then
			statements = sortByValue(statements)
		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, entity, (i == #statements))
		else
			fs = p.formatStatement(statement, options, entity, (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 plainlist(items)
		if #items == 0 then
			return ''
		end
		if #items == 1 then
			return items[1]
		end
		return frame:expandTemplate{ title = 'Plainlist', args = { '\n* ' .. table.concat(items, '\n* ') .. '\n' } }
	end

	if options['felsorolás'] == 'lista' or options['felsorolás'] == 'számozott lista' then
		return table.concat(formattedStatements, '\n')
	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', direction = 'both', 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, entity, 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)
	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, entity)
		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
	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)
	end
	if args.value == '-' then
		return nil
	end
	if args.value then
		return true
	end
	if not args.property then
		formatError('property-param-not-provided')
	end
	local entity = item or mw.wikibase.getEntity(args.entityId ~= '' and args.entityId or nil)
	if not entity or not entity.claims or not entity.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 pairs(entity.claims[args.property:upper()]) do
		if args.rank and claim.rank == args.rank or not args.rank and claim.rank ~= 'deprecated' 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 function checkProperty(item, property)
		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

return p