Modulo:Wikidata

El Vikifontaro

Dokumentado por ĉi tiu modulo povas esti kreata ĉe Modulo:Wikidata/dokumentado

local i18n = {
	errors = {
		["entity-not-found"] = "Ento ne trovita",
		["invalid-date"] = "Nevalida dato %s",
		["invalid-field"] = "Nevalida kampo %s",
		["param-not-provided"] = "Neprovizita parametro %s",
		["unknown-claim-type"] = "Nekonata speco de aserto: %s",
		["unknown-datavalue-type"] = "Nekonata speco de datumvaloro: %s",
		["unknown-entity-type"] = "Nekonata speco de ento: %s",
		["unknown-snak-type"] = "Nekonata speco de snako: %s",
		["unknown-value-module"] = "Vi devas agordi kaj la parametron value-module kaj value-function",
		["value-function-not-found"] = "Funkcio montrata de la parametro value-function ne trovita",
		["value-module-not-found"] = "Modulo montrata de la parametro value-module ne trovita"
	},
	lang = mw.language.getContentLanguage(),
	novalue = "''sen valoro''",
	somevalue = "''valoro ne konata''"
}

function wrapWithSpan(str, param, value)
	-- TODO mw.html ?
	return '<span ' .. param .. '="' .. value .. '">' .. str .. '</span>'
end

function formatError( key, ... )
	return wrapWithSpan(string.format( i18n.errors[key], ... ), 'class', 'error')
end

function getEntityFromId( id )
	return mw.wikibase.getEntityObject( id )
end

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

function findEntity(options)
	local entity
	if options.entity and type(options.entity) == "table" then
		entity = options.entity
	end
	if not entity then
		if options.id and options.id ~= '' then
			entity = getEntityFromId(options.id)
		else
			entity = getEntityFromId()
		end
	end
	if options.of and options.of ~= '' then
		local property = options.of:upper()
		if entity and entity.claims and entity.claims[property] then
			local Filtered = filterStatements(entity.claims[property], { limit = 1 })
			for _, statement in pairs(Filtered) do
				if statement.mainsnak.datatype == 'wikibase-item' or statement.mainsnak.datatype == 'wikibase-property' then
					local id = getEntityIdFromValue( statement.mainsnak.datavalue.value )
					entity = string.find(id, '^[PQ]%d+$') and getEntityFromId( id ) or nil
					return entity
				end
			end
		end
		return nil
	else
		return entity
	end
end

function getSitelink(options)
	local site = nil
	if options.site and options.site ~= '' then
		site = options.site
	elseif options[1] and options[1] ~= '' then
		site = mw.text.trim(options[1])
	end

	local entity = findEntity(options)

	if not entity or not entity.sitelinks then
		return ''
	end

	local sitelink = entity:getSitelink(site)

	if not sitelink then
		return ''
	elseif options.pattern and options.pattern ~= '' then
		return formatFromPattern(sitelink, options)
	else
		return sitelink
	end
end

function formatStatements( options )
	if not options.property or options.property == '' then
		return formatError( 'param-not-provided', 'property' )
	end

	--Get entity
	local entity = findEntity(options)

	if not entity then
		return '' --TODO error?
	else
		options.entity = entity
	end

	if not entity.claims or not entity.claims[options.property:upper()] then
		return '' --TODO error?
	end

	options.limit = returnLimit(options.limit, 100, 1)

	local Statements = filterStatements(entity.claims[options.property:upper()], options)

	-- Format statements and concat them cleanly
	local formattedStatements = {}
	local see_more = '…&nbsp;více na [[d:' .. entity.id .. '#' .. options.property:upper() .. '|Wikidatech]]'
	local add_more = false
	if #Statements == options.limit then
		Statements[#Statements] = nil
		if options.showmore and options.showmore == 'true' then
			add_more = true
		end
	end
	for _, statement in pairs(Statements) do
		table.insert( formattedStatements, formatStatement( statement, options ) )
	end
	if add_more then table.insert( formattedStatements, see_more ) end
	return mw.text.listToText( formattedStatements, options.separator, options.conjunction )
end

function filterStatements(claims, options)
	local Statements, oldStatements = claims, {}
	-- apply filter by rank
	if options.rank ~= "all" then
		oldStatements, Statements = Statements, {}
		local hasPref = false
		if not options.rank or options.rank == "normal" then
			for _, statement in pairs(oldStatements) do
				if statement.rank == "preferred" then
					hasPref = true
					break
				end
			end
		end
		for _, statement in pairs(oldStatements) do
			if options.rank == "deprecated" or options.rank == "preferred" then
				if statement.rank == options.rank then
					table.insert(Statements, statement)
				end
			elseif hasPref then
				if statement.rank == "preferred" then
					table.insert(Statements, statement)
				end
			elseif statement.rank == "preferred" or statement.rank == "normal" then
				table.insert(Statements, statement)
			end
		end
	end
	-- apply filter by snak type
	if not options.showspecial or options.showspecial ~= "true" then
		oldStatements, Statements = Statements, {}
		for _, statement in pairs(oldStatements) do
			if statement.mainsnak.snaktype == "value" then
				table.insert(Statements, statement)
			end
		end
	end
	-- apply filter by qualifier property
	if options.withqualifier and options.withqualifier ~= '' then
		oldStatements, Statements = Statements, {}
		for _, statement in pairs(oldStatements) do
			if hasQualifierValue(statement.qualifiers, options.withqualifier:upper()) then
				table.insert(Statements, statement)
			end
		end
	end
	if options.withlang and options.withlang ~= '' then
		local datatype = getEntityFromId(options.property).datatype
		if datatype == 'monolingualtext' then
			oldStatements, Statements = Statements, {}
			for _, statement in pairs(oldStatements) do
				if statement.mainsnak.snaktype == "value" then
					if statement.mainsnak.datavalue.value.language == options.withlang then
						table.insert(Statements, statement)
					end
				end
			end
		end
	end
	-- apply filter by time
	if options.date and options.date ~= '' then
		local lang = i18n.lang
		local date
		if options.date == '#now' then
			date = tonumber(lang:formatDate( 'Ymd' ))
		else
			date = timeToNumber(options.date)
		end
		if date then
			oldStatements, Statements = Statements, {}
			local temp_value
			local Time = require 'Module:Time'
			for _, statement in pairs(oldStatements) do
				if statement.qualifiers then
					local P580 = hasQualifierValue(statement.qualifiers, "P580")
					local P582 = hasQualifierValue(statement.qualifiers, "P582")
					local P585 = hasQualifierValue(statement.qualifiers, "P585")
					if P585 then
						local value = Time.newFromWikidataValue( statement.qualifiers.datavalue.value )
						value = timeToNumber(value)
						if value and value <= date then
							if not temp_value then temp_value = value end
							if temp_value > value then
								temp_value = value
								Statements = { statement }
							end
						end
					elseif P580 then
						local value = Time.newFromWikidataValue( statement.qualifiers.datavalue.value )
						value = timeToNumber(value)
						if value and value <= date then
							if not P582 then
								table.insert(Statements, statement)
							else
								local value = Time.newFromWikidataValue( statement.qualifiers.datavalue.value )
								value = timeToNumber(value)
								if value and date <= value then
									table.insert(Statements, statement)
								end
							end
						end
					elseif P582 then
						local value = Time.newFromWikidataValue( statement.qualifiers.datavalue.value )
						value = timeToNumber(value)
						if value and date <= value then
							table.insert(Statements, statement)
						end
					end
				end
			end
		else
			return {} --formatError( 'invalid-date', options.date )
		end
	end
	-- apply filter by limit
	if #Statements > options.limit then
		oldStatements, Statements = Statements, {}
		for i, statement in pairs(oldStatements) do
			if i <= options.limit then
				table.insert( Statements, statement )
			else break end
		end
	end
	return Statements
end

function timeToNumber(date)
	if not (
		mw.ustring.find(date, "^%d%d?%d?%d?$")
		or mw.ustring.find(date, "^%d%d?%d?%d?-%d%d$")
		or mw.ustring.find(date, "^%d%d?%d?%d?-%d%d-%d%d$")
	) then return nil end
	if mw.ustring.sub(date, -3, -3) == '-' then
		if mw.ustring.sub(date, -6, -6) == '-' then
			date = mw.ustring.gsub(date, '-', '')
		else
			date = mw.ustring.gsub(date, '-', '') .. '00'
		end
	else
		date = date .. '0000'
	end
	return tonumber(date)
end

function formatStatement( statement, options )
	if not statement.type or statement.type ~= 'statement' then
		return formatError( 'unknown-claim-type', statement.type )
	end

	local mainsnak = formatSnak( statement.mainsnak, options )
	local qualifiers, references
	if statement.qualifiers and options.showqualifier and options.showqualifier ~= '' then
		local QualList = {}
		for Pid in mw.text.gsplit( options.showqualifier, ",", true ) do
			local ValuesList = {}
			Pid = Pid:upper()
			local options = {
				autoformat = "true",
				entity = options.entity,
				property = Pid
			}
			if statement.qualifiers[Pid] then
				for _, qualData in pairs(statement.qualifiers[Pid]) do
					if qualData.snaktype == "value" then
						if qualData.datavalue.type == "time" then
							options["value-module"] = "Wikidata/datum"
							options["value-function"] = "formatDate"
						end
						table.insert( ValuesList, formatDatavalue(qualData.datavalue, options) )
					end
				end
			elseif Pid == "TIME" then
				options["value-module"] = "Wikidata/datum"
				options["value-function"] = "formatDate"
				local P580 = hasQualifierValue(statement.qualifiers, "P580")
				local P582 = hasQualifierValue(statement.qualifiers, "P582")
				local P585 = hasQualifierValue(statement.qualifiers, "P585")
				if P580 or P582 or P585 then
					if P585 then
						table.insert( ValuesList, formatDatavalue(statement.qualifiers.P585[P585].datavalue, options) )
					elseif P580 then
						if P582 then
							local connector = ' – '
							if statement.qualifiers.P580[P580].datavalue.value.precision == 9
							and statement.qualifiers.P582[P582].datavalue.value.precision == 9 then
								connector = '–'
							end
							table.insert( ValuesList, formatDatavalue(statement.qualifiers.P580[P580].datavalue, options) .. connector .. formatDatavalue(statement.qualifiers.P582[P582].datavalue, options) )
						else
							table.insert( ValuesList, 'od ' .. formatDatavalue(statement.qualifiers.P580[P580].datavalue, options) )
						end
					elseif P582 then
						table.insert( ValuesList, 'do ' .. formatDatavalue(statement.qualifiers.P582[P582].datavalue, options) )
					end
				end
			end
			if #ValuesList > 0 then
				table.insert( QualList, mw.text.listToText(ValuesList) )
			end
		end
		if #QualList > 0 then
 			qualifiers = table.concat( QualList, '; ' )
 		end
	end
	if statement.references and options.showsource and options.showsource == 'true' then
		local useModule = require 'Module:Wikidata/cite'
		if useModule then
			local useFunction = "formatSource"
			if useModule[useFunction] then
				references = useModule[useFunction](statement.references, options)
			end
		end
	end
	if qualifiers then
		mainsnak = mainsnak .. ' (' .. qualifiers .. ')'
	end
	if references then
		mainsnak = mainsnak .. references
	end
	return mainsnak
end

function formatSnak( snak, options )
	if snak.snaktype == 'value' then
		return formatDatavalue( snak.datavalue, options )
	elseif snak.snaktype == 'somevalue' or snak.snaktype == 'novalue' then
		return i18n[snak.snaktype]
	else
		return formatError( 'unknown-snak-type', snak.snaktype )
	end
end

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 not formatter then
			return formatError( 'value-module-not-found' )
		end
		local fun = formatter[options['value-function']]
		if not fun then
			return formatError( 'value-function-not-found' )
		end
		return fun( datavalue.value, options )
	end

	--Default formatters
	if datavalue.type == 'wikibase-entityid' then
		return formatEntityId( getEntityIdFromValue( datavalue.value ), options )
	elseif datavalue.type == 'string' then
		if options.pattern and options.pattern ~= '' then
			return formatFromPattern( datavalue.value, options )
		elseif options.autoformat and options.autoformat == 'true' then
			local pattern = findPattern(options.property)
			if pattern ~= '' then
				return formatFromPattern( datavalue.value, { pattern = '[' .. pattern .. ' $1]' } )
			end
		else
			return datavalue.value
		end
	elseif datavalue.type == 'time' then
		local Time = require 'Module:Time'
		return Time.newFromWikidataValue( datavalue.value ):toString()
	elseif datavalue.type == 'globecoordinate' then
		if not options.field or options.field == '' then
		--	return formatError( 'param-not-provided', 'field' )
			return formatCoordinateValue(datavalue, 'dms')
		elseif options.field == "latitude" or options.field == "longitude" then
			return formatCoordinateValue(datavalue, options.typeOfCoordinates, options.field)
		elseif options.field == "precision" or options.field == "globe" then
			return datavalue.value[options.field]
		else
			return formatError( 'invalid-field', options.field )
		end
	elseif datavalue.type == 'monolingualtext' then
		return wrapWithSpan(datavalue.value.text, 'lang', datavalue.value.language)
	elseif datavalue.type == 'quantity' then
		return tonumber( datavalue.value.amount )
	else
		return formatError( 'unknown-datavalue-type', datavalue.type )
	end
end

function hasQualifierValue( Qualifiers, Pid )
	if Qualifiers and Qualifiers[Pid] then
		for number, qualData in pairs(Qualifiers[Pid]) do
			if qualData.snaktype == "value" then return number end
		end
	end
	return false
end

function formatCoordinateValue(datavalue, typeOfValue, field)
	-- type bude nepovinny - tj. "nil"
	-- priklad pouze -- je tam asi X chyb (mimo jine N vs S a podobne)
	local value = ""
	local latdmsText, londmsText
	if typeOfValue == 'dms' then
		local latDms = fastConvertDdToDms(datavalue.value.latitude)
		local lonDms = fastConvertDdToDms(datavalue.value.longitude)
		latdmsText = "N " .. latDms["degrees"] .. "° " .. latDms["minutes"] .. "' " .. latDms["seconds"] .. '"'
		londmsText = "E " .. lonDms["degrees"] .. "° " .. lonDms["minutes"] .. "' " .. lonDms["seconds"] .. '"'
		if field then
			if field == 'latitude' then
				value = latdmsText
			elseif field == 'longitude' then
				value = londmsText
			end
		else
			value = latdmsText .. " " .. londmsText
		end
	elseif typeOfValue == 'dd' then
		latdmsText = tonumber(datavalue.value.latitude)
		londmsText = tonumber(datavalue.value.longitude)
		if field then
			if field == 'latitude' then
				value = latdmsText
			elseif field == 'longitude' then
				value = londmsText
			end
		else
			value = latdmsText .. " " .. londmsText
		end
	else
		value = datavalue.value.latitude .. ' / ' .. datavalue.value.longitude .. ' (přesnost: ' .. datavalue.value.precision .. ')'
	end

	return value
end

function fastConvertDdToDms(ddValue)
	local dmsArr = {
		degrees = 0,
		minutes = 0,
		seconds = 0.0
	}

	if ddValue then
		dmsArr["degrees"] = math.floor(tonumber(ddValue))
		dmsArr["minutes"] = math.floor((tonumber(ddValue) - dmsArr["degrees"]) * 60)
		dmsArr["seconds"] = (tonumber(ddValue) - dmsArr["degrees"] - dmsArr["minutes"]/60) * 3600
	end

	return dmsArr
end

function formatEntityId( entityId, options )
	local formatter = require 'Modul:Wikidata/item'
	return formatter.formatEntityId( entityId, options )
end

function findPattern(property)
	return property and formatStatements({
		entity = getEntityFromId(property:upper()),
		property = 'P1630',
		limit = 1
	}) or ''
end

function returnLimit(limit, default, add)
	if tonumber(limit) then
		return tonumber(add) and (tonumber(limit) + tonumber(add)) or tonumber(limit)
	end
	return tonumber(default) or 100
end

function formatFromPattern( str, options )
	return mw.ustring.gsub( options.pattern, '$1', str ) .. '' --Hack to get only the first result of the function
end

local p = {}

function p.dumpWikidataEntity( frame )
	local args = frame and frame.args or nil

	return mw.dumpObject( getEntityFromId( args and args.id or nil ) )
end

function p.getBadges( frame )
	local args = frame and frame.args or {}
	local site = args and args.site or nil
	local entity = findEntity( args )
	local Badges = {}
	if entity and entity.sitelinks and site and entity.sitelinks[site] then
		for _, badge in pairs( entity.sitelinks[site].badges ) do
			table.insert( Badges, formatEntityId( badge ) )
		end
	end
	return table.concat( Badges, ', ' ) or ''
end

function p.getLabel( frame )
	local args = frame and frame.args or {}
	local lang = args and args.lang or nil
	local entity = findEntity( args )
	if not lang or lang == '' then
		lang = i18n.lang.code
	end
	if not entity or not entity.labels or not entity.labels[lang] then
		return ''
	end
	return entity.labels[lang].value
end

function p.getDescription( frame )
	local args = frame and frame.args or {}
	local lang = args and args.lang or nil
	local entity = findEntity( args )
	if not lang or lang == '' then
		lang = i18n.lang.code
	end
	if not entity or not entity.descriptions or not entity.descriptions[lang] then
		return ''
	end
	return entity.descriptions[lang].value
end

function p.getAliases( frame )
	local args = frame and frame.args or {}
	local lang = args and args.lang or nil
	local entity = findEntity( args )
	if not lang or lang == '' then
		lang = i18n.lang.code
	end
	if not entity or not entity.aliases or not entity.aliases[lang] then
		return ''
	end
	
	local limit = returnLimit(args.limit)

	local Aliases = {}
	for i, alias in pairs( entity.aliases[lang] ) do
		if i <= limit then
			table.insert( Aliases, alias.value )
		else break end
	end
	return mw.text.listToText( Aliases, args.separator, args.conjunction ) or ''
end

function p.getCount( frame )
	local args = frame and frame.args or {}
	if not args.property or args.property == '' then
		return formatError( 'param-not-provided', 'property' )
	end
	
	local entity = findEntity( args )

	if not entity or not entity.claims or not entity.claims[args.property:upper()] then
		return 0
	end

	args.limit = returnLimit(args.limit, 100)

	local Statements = filterStatements(entity.claims[args.property:upper()], args)

	return #Statements or 0
end

function p.getSitelink( frame )
	return getSitelink( frame.args )
end

function p.getSitelinkFromLua( options )
	return getSitelink( options )
end

function p.filterStatementsFromLua( claims, options )
	options.limit = returnLimit(options.limit, 100)
	return filterStatements( claims, options )
end

function p.formatStatements( frame )
	local args = frame and frame.args or {}
	--If a value is already set, use it
	if args.value and args.value ~= '' then
		return args.value
	end
	return formatStatements( frame.args )
end

function p.formatStatementsFromLua( options )
	--If a value is already set, use it
	if options.value and options.value ~= '' then
		return options.value
	end
	return formatStatements( options )
end

return p