Module:Wikidata/sandbox

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Lua
CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules

Documentation for this module may be created at Module:Wikidata/sandbox/doc

Code

--script that retrieves basic data stored in Wikidata, for the datamodel, see https://www.mediawiki.org/wiki/Extension:Wikibase_Client/Lua

local p = {}

local linguistic = require('Module:Linguistic')
local selectClaims = require('Module:Wikidata/GetClaims')
local tools = require('Module:Wikidata/Tools')
local entities = require('Module:Wikidata/FormatEntity')
local dates = require('Module:Wikidata/Dates')
local weblink = require('Module:Weblink')

local langSwitch = require('Module:LangSwitch')._langSwitch
local fb = require('Module:Fallback')
local i18nmessages = mw.loadData('Module:i18n/wikidata')

-- Wiki-specific parameters
local defaultlang = mw.getCurrentFrame():callParserFunction('Int', 'Lang')
local defaultlink = 'wikidata'

local function i18n(str, lang)
	local message = i18nmessages[str]
	if type(message) == 'string' then
		return message
	end
	return langSwitch(message, lang or defaultlang)
end

local function formatError(key, text)
	return error(i18n(key) .. ' ' .. (text or ''), 2)
end

p.getClaims = selectClaims.getClaims

local function removeBlanks(args)
	for i = #args, 1, -1 do
		if (args[i] == '') or (args[i] == '-') then
			table.remove(args, i)
		end
	end
	return args
end

local function formatTheUnknown() -- See if we can grant/adapt the usage of 'unknown'
	return i18n('somevalue')
end

local function getQualifiers(statement, qualifs, params)
	if not statement.qualifiers then
		return nil
	end
	if type(qualifs) == 'string' then
		qualifs = mw.text.split(qualifs, ',')
	end
	local vals = {}
	for i, j in pairs(qualifs) do
		j = string.upper(j)
		if statement.qualifiers[j] then
			local inserted = false
			if statement.qualifiers[j][1].datatype == 'monolingualtext' then
				local in_preferred_lang
				for _, language in pairs(fb.fblist(params.lang or defaultlang)) do
					for _, snak in pairs(statement.qualifiers[j]) do
						if isInLanguage(snak, language) then
							in_preferred_lang = snak
							break
						end
					end
					if in_preferred_lang then
						break
					end
				end
				if in_preferred_lang then
					table.insert(vals, in_preferred_lang)
					inserted = true
				end
			end
			if not inserted then
				for _, snak in pairs(statement.qualifiers[j]) do
					table.insert(vals, snak)
				end
			end
		end
	end
	if #vals == 0 then
		return nil
	end
	return vals
end

function p.formatSnak(snak, params)
	-- special values: 'novalue' and 'somevalue'
	local datatype = snak.datatype
	if datatype == 'somevalue' then
		return formatTheUnknown()
	elseif datatype == 'novalue' then
		return i18n('novalue') --todo
	end
	local value = snak.datavalue.value
	-- user-defined displayformat
	local displayformat = params.displayformat
	if type(displayformat) == 'function' then
		return displayformat(snak, params)
 	end
	if datatype == 'wikibase-item' or datatype == 'wikibase-property' then
		return entities.formatEntity(tools.getId(snak), params)
	elseif datatype == 'url' then
		return weblink.makelink(value, params.text, params.displayformat)
	elseif datatype == 'math' then
		return mw.getCurrentFrame():extensionTag('math', value)
	elseif datatype == 'string' or datatype == 'external-id' or datatype == 'commonsMedia' then
		if params.urlpattern then
			local urlpattern = params.urlpattern
			if type(urlpattern) == 'function' then
				urlpattern = urlpattern(value)
			end
			value = '[' .. mw.ustring.gsub(urlpattern, '$1', value) .. ' ' .. (params.text or value) .. ']'
		end
		return value
	elseif datatype == 'time' then -- format example: +00000001809-02-12T00:00:00Z
		return dates.formatTimeSnak(snak, params) 
	elseif datatype == 'globe-coordinate' then
		-- default return a table with latitude, longitude, precision and globe that can be used by another module
		if displayformat == 'latitude' then
			return value.latitude
		elseif displayformat == 'longitude' then
			return value.longitude
		elseif displayformat == 'qualifier' then
			local coord = require('Module:Coordinates')
			value.globe = require('Module:Wikidata/Globes')[value.globe]
			value.precision = nil
			return coord._coord(value)
		else
			value.globe = require('Module:Wikidata/Globes')[value.globe] -- get English name for geohack
			return value
		end
	elseif datatype == 'quantity' then
		-- TODO: handle precision parameters
		if displayformat == 'raw' then
			return tonumber(value.amount)
		else
			local formatNum = require('Module:Formatnum')
			local number = formatNum.formatNum(value.amount)
			local unit = mw.ustring.match(value.unit, '(Q%d+)')
			if unit then
				number = number .. ' ' .. entities.formatEntity(unit, params)
			end
			return number
		end
	elseif datatype == 'monolingualtext' then
		if displayformat == 'raw' then
			-- Don't use HTML
			local byte, char = string.byte, mw.ustring.char
			local lang = value.language:lower()
			local tag = {}
			table.insert(tag, char(0x2068)) -- U+2068: First Strong Isolate (FSI)
			table.insert(tag, char(0xE0001)) -- U+E0001: Language tag
			for i = 1, #lang do
				local b = byte(lang, i)
				if b >= 0x61 and b <= 0x7A or b == 0x2D or b == 0x5F then -- 'a'..'z', '-', '_'
					table.insert(tag, char(0xE0000 + b)) -- U+E0020..U+E007E: Tag characters (remaps ASCII only)
				end
			end
			table.insert(tag, value.text)
			table.insert(tag, char(0xE007F)) -- U+E007F: Cancel Tag
			table.insert(tag, char(0x2069)) -- U+2069: Pop Directional Isolate (PDI)
			return table.concat(tag)
		else
			return '<bdi lang="' .. value.language .. '">' .. value.text .. '</bdi>'
		end
	else
		return formatError('unknown-datavalue-type', datatype )
	end
end

function p.formatStatementQualifiers(statement, qualifs, params)
	if not params then params = {} end
	local qualiftable = getQualifiers(statement, qualifs, params)
	if not qualiftable then
		return nil
	end
	for i, j in pairs(qualiftable) do
		local params = params
		if j.datatype == 'globe-coordinate' then
			params.displayformat = 'qualifier'
		end
		qualiftable[i] = p.formatSnak(j, params)
	end
	return linguistic.conj(qualiftable, params.lang or defaultlang)
end

function p.formatStatement(statement, args)
	if not statement.type or statement.type ~= 'statement' then
		return formatError('unknown-claim-type', statement.type)
	end
	if not args then args = {} end
	local lang = args.lang or defaultlang
	local str = p.formatSnak(statement.mainsnak, args)

	local qualifs = args.showqualifiers
	if qualifs then
		local qualifStr = p.formatStatementQualifiers(statement, qualifs, args)
		if qualifStr then
			if args.delimiter then
				str = str .. args.delimiter .. qualifStr
			else
				-- str = str .. linguistic.inparentheses(qualifStr, lang)
				str = str .. ' (' .. qualifStr .. ')'
			end
		end
	end

	if args.showdate then -- when `showdate` and `p.chronosort` are both set, date retrieval is performed twice
		local params
		local date = dates.getFormattedDate(statement, params)
		if date then
			-- str = str .. ' <small>' .. linguistic.inparentheses(date, lang) .. '</small>'
			str = str .. ' <small>(' .. date ..')</small>'
		end
	end

	if args.showsource and statement.references then
		local cite = require('Module:Cite')
		local frame = mw.getCurrentFrame()
		local sourcestring = ''
		for i, ref in pairs(statement.references) do
			if ref.snaks.P248 then
				for j, source in pairs(ref.snaks.P248) do
					if not tools.isSpecial(source) then
						local page
						if ref.snaks.P304 and not tools.isSpecial(ref.snaks.P304[1]) then
							page = ref.snaks.P304[1].datavalue.value
						end
						local s = cite.citeitem('Q' .. source.datavalue.value['numeric-id'], lang, page)
						s = frame:extensionTag('ref', s)
						sourcestring = sourcestring .. s
					end
				end
			elseif ref.snaks.P854 and not tools.isSpecial(ref.snaks.P854[1]) then
				s = frame:extensionTag('ref', p.getDatavalue(ref.snaks.P854[1]))
				sourcestring = sourcestring .. s
			end
		end
		str = str .. sourcestring
	end
	return str
end

function p._getDescription(entity, lang)
	if not entity then
		return i18n('no description')
	end
	if type(entity) == 'string' and (not lang or lang == defaultlang) then
		return mw.wikibase.description(entity)
	end
	entity = mw.wikibase.getEntity(entity)
	local descriptions = entity.descriptions
	if not descriptions then
		return i18n('no description')
	end
	local langlist = fb.fblist(lang or defaultlang) -- list of fallback languages if no label in the desired language
	for i, lg in pairs(langlist) do
		if descriptions[lg] then
			return descriptions[lg].value
		end
	end
	return i18n('no description')
end

-- == FUNCTIONS HANDLING SEVERAL STATEMENTS ===

function p.stringTable(args) -- find the relevant claims, and formats them as a list of strings
	-- Get claims
	local claims = p.getClaims(args)
	if not claims then
		return nil
	end
	-- Define the formatter function
	local formatterFun = p.formatStatement
	if args.type == 'date' then
		formatterFun = dates.getFormattedDate
	elseif args.type == 'qualifiers' then
		formatterFun = formatStatementQualifiers
	end
	-- Format claims
	for i = #claims, 1, -1 do
		claims[i] = formatterFun(claims[i], args)
		if not claims[i] then
			table.remove(claims, i)
		end
	end
	return claims
end

function p.formatStatements(args)--Format statements and concat them cleanly
	-- If a value is already set, use it
	if args.value == '-' then
		return nil
	end
	if args.value then
		return args.value
	end
	-- Obsolete parameters 
	if args.item then
		args.entity = args.item
	end
	local values = p.stringTable(args) -- gets statements, and format each of them
	if not values then
		return nil
	end
	return linguistic.conj(values, args.lang or defaultlang, args.conjtype) -- concatenate them
end

-- == FRAME FUNCTIONS ==

function p.getDescription(frame) -- simple for simple templates like {{Q|}}}
	local entity = frame.args.entity
	local lang = frame.args.lang
	return p._getDescription(entity, lang)
end

function p.formatStatementsE(frame)
	local args = {}
	if frame == mw.getCurrentFrame() then
		args = frame:getParent().args -- paramètres du modèle appelant (est-ce vraiment une bonne idée ?)
		for k, v in pairs(frame.args) do
			args[k] = v
		end
	else
		args = frame
	end
	args = removeBlanks(args)
	return p.formatStatements(args)
end

function p._getLabel(entity, args) 
	return entities.formatEntity(entity, args)
end
function p.getLabel(frame) -- simple for simple templates like {{Q|}}}
	local args = removeBlanks(frame.args)
	local entity = args.entity or args[1]
	if string.sub(entity, 1, 10) == 'Property:P' then
		entity = string.sub(entity, 10)
	elseif string.sub(entity, 1, 1) ~= 'P' and string.sub(entity, 1, 1) ~= 'Q'
	or not tonumber(string.sub(entity, 2)) then
		return i18n('invalid-id')
	end
	return entities.formatEntity(entity, args)
end

function p._wikidataDate(prop, item, params)
	local claims = p.getClaims{entity = item, property = prop}
	if not claims then
		return nil
	end
	params = params or {}
	local vals = {}
	for i, j in pairs(claims) do
		local v = dates.getFormattedDate(j, params)
		if v then
			table.insert(vals, v)
		end
	end
	return linguistic.conj(vals, params.conjtype or 'or')
end

function p.wikidataDate(frame)
	 p._wikidataDate(frame.args[property], frame.args[params], frame.args)
end

function p._main_date(entity)	
	-- First try with P580/P582
	local startpoint = p._wikidataDate('P580', entity)
	local endpoint = p._wikidataDate('P582', entity)
	if startpoint or endpoint then
		return (startpoint or '') .. '&#x202F;–&#x2009;' .. (endpoint or '')
	end
	-- Else use P585
	return p._wikidataDate('P585', entity)
end

function p.main_date(frame)
	return p._main_date(frame.args[1])
end

-- returns the page id (Q...) of the current page or nothing of the page is not connected to Wikidata
function p.pageId(frame)
	local entity = mw.wikibase.getEntityObject()
	return entity and entity.id
end

return p