Módulo:Propriedade Wikidata

A documentação para este módulo pode ser criada na página Módulo:Propriedade Wikidata/doc

wd = {}
-- Carrega as propriedades da página no Wikidata, se existir e não tiverem já sido carregadas
if not wdEntity then
				wdEntity = mw.wikibase.getEntity()
end
wd.props = wdEntity and wdEntity['claims']
-- Tabela para dados temporários, mantidos durante o processamento de uma propriedade
wd.temp = {}
-- Tabela para adicionar a data de cada propriedade, quando existir
wd.data = {}
meses = {['01']='janeiro', ['02']='fevereiro', ['03']='março', ['04']='abril', ['05']='maio', ['06']='junho',
	['07']='julho', ['08']='agosto', ['09']='setembro', ['10']='outubro', ['11']='novembro', ['12']='dezembro' }
-- Processa a propriedade ou qulificador pedido
wd.dados = function (prop)
				local p = {}
				local lingua = params and params['língua']
				for i in string.gmatch(prop, '[^:]+') do
								table.insert(p, i)
				end
				if not p[1] or not string.match(p[1], '^P%d+$') then
								wd.temp.debug = 'não é uma propriedade'
								return nil
				end
				if ext and ext.prop and ext.prop[p[1]] then
								wd.temp.debug = 'processada pela extensão'
								return ext.prop[p[1]](prop)
				end
				prop = wd.props and wd.props[p[1]]
				if not prop then
								wd.temp.debug = 'item não tem essa propriedade'
								return nil
				end
				local snak
				local arg = 2
				if p[2] then
								if string.match(p[2], '^P%d+$') then
												local qprop = p[2]  -- qualificador da propriedade (Pq)
												for _, propn in ipairs(prop) do
																if propn['qualifiers'] and propn['qualifiers'][qprop] then
																				if p[3] and string.match(p[3], '^Q%d+$') then
																								-- P:Pq:Qq
																								local qvalue = p[3]  -- valor do qualificador (Qq)
																								for i, v in ipairs(propn['qualifiers'][qprop]) do
																												if v['datatype'] == 'wikibase-item' and v['datavalue']['value']['id'] == qvalue then
																																snak = propn['mainsnak']
																																arg = 4
																																break
																												end
																								end
																				else
																								-- P:Pq
																								snak = propn['qualifiers'][qprop][1]
																								arg = 3
																								break
																				end
																end
												end
								elseif string.match(p[2], '^Q%d+$') then
												local pvalue = p[2]  -- valor da propriedade (Qv)
												if p[3] and string.match(p[3], '^P%d+$') then
																-- P:Qv:Pq
																local qprop = p[3]  -- qualificador (Pq)
																for _, propn in ipairs(prop) do
																				if propn['mainsnak']['datatype'] == 'wikibase-item' and
																						propn['mainsnak']['datavalue']['value']['id'] == pvalue then
																								if propn['qualifiers'] and propn['qualifiers'][qprop] then
																												snak = propn['qualifiers'][qprop][1]
																												arg = 4
																								else
																												wd.temp.debug = 'esse valor não possui o qualificador ' .. qprop
																												return nil
																								end
																				end
																end
												else
																wd.temp.debug = 'faltou o qualificador (último P do formato P:Qv:Pq)'
																return nil
												end
								end
				end
				if arg == 2 and p[2] == 'lista' then
								-- Listar todos valores de uma propriedade
								local lista = {}
								for _, propn in ipairs(prop) do
												local datatype = propn['mainsnak']['datatype']
												wd.temp.tipo = datatype
												if datatype == 'string' or datatype == 'external-id' then
																table.insert(lista, propn['mainsnak']['datavalue']['value'])
												elseif datatype == 'wikibase-item' then
																if not propn['mainsnak']['datavalue'] then
																				wd.temp.debug = 'propriedade não possui valor'
																				return nil
																end
																local qid = propn['mainsnak']['datavalue']['value']['id']
																local labels = mw.wikibase.getEntity(qid)['labels']
																if lingua and labels[lingua] then
																				table.insert(lista, labels[lingua])
																else
																				local pt, en, outra
																				for lang, v in pairs(labels) do
																								if lang == 'pt' then
																												pt = v['value']
																												break
																								elseif lang == 'pt-br' then
																												pt = v['value']
																								elseif lang == 'en' then
																												en = v['value']
																								elseif lang == 'es' or lang == 'fr' or lang == 'it' then -- outras linguas latinas
																												outra = v['value']
																								end
																				end
																				if pt or en or outra then
																								table.insert(lista, pt or en or outra)
																				end
																end
												else
																wd.temp.debug = 'esse tipo de dados em pt, pt-br, en, es, fr, ou it'
																return nil
												end
								end
								if #lista > 1 then
												wd.temp.debug = 'lista com ' .. #lista .. ' valores'
												return table.concat(lista, ', ', 1, #lista - 1) .. ' e ' .. lista[#lista]
								elseif #lista == 1 then
												wd.temp.debug = 'lista só possui um valor'
												return lista[1]
								else
												wd.temp.debug = 'não possui nenhum valor que pode ser retornado'
												return nil
								end
				end
				-- Escolher só um valor para retornar
				if not snak then
								if prop[1]['mainsnak']['datatype'] == 'monolingualtext' then
												wd.temp.tipo = 'monolingualtext' -- A FAZER: escolher língua
												local i = 1
												for n, v in pairs(prop) do
																if v['mainsnak']['datavalue']['value']['language'] == 'pt' then
																				i = n
																				break
																elseif v['mainsnak']['datavalue']['value']['language'] == 'pt-br' then
																				i = n
																end
												end
												wd.temp.debug = 'valor obtido'
												return prop[i]['mainsnak']['datavalue']['value']['text']
								else
									local mostrecent = ""
												for _, propn in ipairs(prop) do
													local time
																if arg == 2 and propn['qualifiers'] and propn['qualifiers']['P585'] then
																				time = propn['qualifiers']['P585'][1]['datavalue']['value']['time']
																				if time > mostrecent then
																								mostrecent = time
																								wd.data[p[1]] = time
																								snak = propn['mainsnak']
																				end
																end
																if propn['rank'] == 'preferred' then
																				snak = propn['mainsnak']
																				wd.data[p[1]] = time
																				break
																elseif not snak and propn['rank'] == 'normal' then
																				snak = propn['mainsnak']
																end
												end
								end
				end
				local args = {}
				for n, a in ipairs(p) do
								if n >= arg then
												table.insert(args, p[n])
								end
				end
				if snak then
								return wd.valorsnak(snak, args)
				end
end
-- Retorna o valor de um snak de acordo com o tipo de dado
wd.valorsnak = function(snak, args)
				local datatype = snak['datatype']
				wd.temp.tipo = datatype
				if datatype == 'string' or datatype == 'url' or datatype == 'external-id' or datatype == 'math' or
						datatype == 'commonsMedia' then
								wd.temp.debug = #args == 0 and 'valor obtido' or 'esse tipo de dado não recebe modificações'
								return snak['datavalue']['value']
				elseif datatype == 'monolingualtext' then
								wd.temp.debug = 'obtido valor na língua ' .. snak['datavalue']['value']['language']
								return snak['datavalue']['value']['text']
				elseif datatype == 'wikibase-item' then
								if not snak['datavalue'] then
												wd.temp.debug = 'propriedade não possui valor'
												return nil
								end
								local qid = snak['datavalue']['value']['id']
								local item = mw.wikibase.getEntity(qid)
								local labels = item['labels']
								local pt, en, outra
								for lang, v in pairs(labels) do
												if lang == 'pt' then
																pt = v['value']
																break
												elseif lang == 'pt-br' then
																pt = v['value']
												elseif lang == 'en' then
																en = v['value']
												elseif lang == 'es' or lang == 'fr' or lang == 'it' then -- outras linguas latinas
																outra = v['value']
												end
								end
								local valor = pt or en or outra
								if not valor then
												wd.temp.debug = 'item não tem rótulo em pt, pt-br, en, es, fr ou it'
												return qid
								end
								if args[1] == 'link' and item['sitelinks'] and item['sitelinks']['ptwiki'] then
												wd.temp.debug = 'valor obtido e adicionado link'
												return '[[' .. item['sitelinks']['ptwiki']['title'] .. '|' .. valor .. ']]'
								end
								wd.temp.debug = args[1] == 'link' and 'valor obtido sem link pois não possui artigo' or 'valor obtido'
								return valor
				elseif datatype == 'time' then
								local ac, ano, mes, dia, hora, minuto, segundo = string.match(
										snak['datavalue']['value']['time'], '([+-])(%d+)%-(%d%d)%-0?(%d%d?)T(%d%d):(%d%d):(%d%d)Z')
								if hora == '00' and minuto == '00' and segundo == '00' then
												if mes == '00' then
																wd.temp.debug = 'data só possui o ano'
																return ano .. (ac == '-' and ' AC' or '')
												end
												wd.temp.debug = 'data obtida, não possui hora'
												return dia .. ' de ' .. meses[mes] .. ' de ' .. ano .. (ac == '-' and ' AC' or '')
								else
												wd.temp.debug = 'obtida data e hora'
												return string.format('%d/%d/%d %d:%d:%d', dia, mes, ano, hora, minuto, segundo)
								end
				elseif datatype == 'quantity' then
								local amount = tonumber(snak['datavalue']['value']['amount'])
								local unit = string.match(snak['datavalue']['value']['unit'], '//www.wikidata.org/entity/(Q%d+)')
								return wd.quantidade(amount, unit, args)
				elseif datatype == 'globe-coordinate' then
								local lat = tonumber(snak['datavalue']['value']['latitude'])
								local long = tonumber(snak['datavalue']['value']['longitude'])
								return wd.coordenadas(lat, long, args)
				else
								wd.temp.debug = 'esse tipo de dado não é suportado'
								return nil
				end
end
-- Retorna número com determinados algarimos significativos
local algSig = function(n, alg)
				local a = alg - math.ceil(math.log10(math.abs(n)))
				return math.floor(n * 10 ^ a + 0.5) / 10 ^ a
end
-- Formata um número usando espaço como separador de milhar e vírgula como separador decimal
local formatnum = function(n)
				n = tostring(n)
				local m = 1
				while m > 0 do
								n, m = string.gsub(n, '^(%-?%d+)(%d%d%d)', '%1 %2')
				end
				return string.gsub(n, '(%d)%.(%d)', '%1,%2')
end
-- Retorna valor de uma quantidade de acordo com argumentos
wd.quantidade = function(n, qid, args)
				if args[1] == 'dividido' or args[1] == 'vezes' or args[1] == 'mais' or args[1] == 'menos' then
								wd.temp.debug = 'valor com operação matemática'
								resp = wd.matematica(n, args)
								return resp and formatnum(resp)
				elseif qid then
								return wd.unidade(n, qid, args)
				else
								wd.temp.debug = 'obtido valor sem unidade'
								return n and formatnum(n)
				end
end
-- Retorna unidade relacionada a um item do Wikidata baseado nos dados do Módulo:Unidades
wd.unidade = function(n, qid, args, loop)
				if not unidades then
								unidades = mw.loadData('Módulo:Unidades')
				end
				local u = type(unidades[qid]) == 'string' and unidades[unidades[qid]] or unidades[qid]
				if not u then
								wd.temp.debug = 'a unidade ' .. qid .. 'não é conhecida, se ela existir adicione no [[Módilo:Unidades]]'
								return n .. ' (unidade ' .. qid .. ')'
				end
				local nome = u['nome']
				local arg = table.remove(args, 1)
				-- Mudar a unidade se for pedido --
				if arg == 'unidade' and args[1] then
								nome = table.remove(args, 1)
												arg = table.remove(args, 1)
								local u2 = type(unidades[nome]) == 'string' and unidades[unidades[nome]] or unidades[nome]
								if u2 and u['grandeza'] ~= u2['grandeza'] then
												wd.temp.debug = 'tentando converter grandezas diferentes: ' .. u['nome'] .. ' (' .. u['grandeza'] ..
														') para ' .. u2['nome'] .. ' (' .. u2['grandeza'] .. ')'
												return nil
								end
								if u2 and u ~= u2 then
												n = n * u['si'] / u2['si'] -- convertendo
												u = u2
								end
				end
				local unidade
				if u['unidade'] then
								unidade = u['unidade']
				elseif n ~= 1 and u['plural'] then
								unidade = u['plural']
				elseif n == 1 and u['nome'] then
								unidade = u['nome']
				else
								wd.temp.debug = 'unidade ' .. nome .. ' não possui nome da unidade' .. (n ~= 1 and ' nem plural' or '') ..
										' para se colocado após o número, corrija no [[Módulo:Unidades]]'
								return ' (unidade ' .. qid .. ')'
				end
				local alg = 4
				if arg == 'alg' and tonumber(args[1]) then
								alg = math.max(1, math.min(12, math.floor(table.remove(args, 1))))
								arg = table.remove(args, 1)
				end
				wd.temp.debug = alg .. ' algarísmos significativos'
				local resp = formatnum(algSig(n, alg)) .. ' '
				if arg == 'link' and u['artigo'] then
								arg = table.remove(args, 1)
								resp = resp .. '[[' .. u['artigo'] .. '|' .. unidade .. ']]'
								wd.temp.debug = wd.temp.debug .. ', adicionado link'
				else
								resp = resp .. unidade
				end
				-- Exibir a conversão quando for pedido --
				if arg == 'converter' and args[1] and unidades[args[1]] and not loop then
								table.insert(args, 1, 'unidade')
								resp = resp .. ' (' .. wd.unidade(n, nome or qid, args, true) .. ')'
								wd.temp.debug = wd.temp.debug .. ', adicionado conversão'
				end
				return resp
end
-- Realiza operações matemáticas com quantidades
wd.matematica = function(n, args)
				local op = table.remove(args, 1)
				local prop = table.remove(args, 1)
				local n2
				if string.match(prop, '^%-?%d+$') then
								n2 = tonumber(prop)
								wd.temp.debug = 'valor ' .. n .. ' ' .. op .. (op == 'dividido' and ' por' or '') .. ' ' .. n2
				elseif string.match(prop, '^P%d+$') then
								local p2 = wd.props and wd.props[prop] and wd.props[prop][1]
								if not (p2 and p2['mainsnak']['datatype'] == 'quantity') then
												wd.temp.debug = p2 and prop .. ' não é do tipo quantity' or prop .. ' não existe no item'
												return
								end
								n2 = p2['mainsnak']['datavalue']['value']['amount']
								wd.temp.debug = 'operação com ' .. prop .. ' = ' .. n2
				else
								wd.temp.debug = 'operação matemática sem informar propriedade ou número'
								return nil
				end
				if op == 'dividido' then
								local r = n / n2
								if r ~= math.floor(r) then
												return tostring(algSig(r, 4))
								else
												return tostring(r)
								end
				elseif op == 'vezes' then
								return tostring(n * n2)
				elseif op == 'mais' then
								return tostring(n + n2)
				elseif op == 'menos' then
								return tostring(n - n2)
				end
end
-- Retorna as coordenadas no formato G° M' S" [NS] G° M' S" [LO] e coloca link quando pedido
wd.coordenadas = function(lat, long, args)
				local abs = math.abs(lat)
				local grau = math.floor(abs)
				local min = math.floor((abs - grau) * 60)
				local seg = math.floor(((abs - grau) * 60 - min) * 60)
				local hemi = lat >= 0 and 'N' or 'S'
				lat = string.format('%02d° %02d\' %02d" %s', grau, min, seg, hemi)
				abs = math.abs(long)
				grau = math.floor(abs)
				min = math.floor((abs - grau) * 60)
				seg = math.floor(((abs - grau) * 60 - min) * 60)
				hemi = long >= 0 and 'L' or 'O'
				long = string.format('%02d° %02d\' %02d" %s', grau, min, seg, hemi)
				local coor = string.gsub(lat, ' ', ' ') .. ' ' .. string.gsub(long, ' ', ' ')
				if args[1] == 'link' then
								local link = '<span class="plainlinks" style="white-space:nowrap" title="Mapas, fotos aéreas e outros dados para este local">[//tools.wmflabs.org/geohack/geohack.php?language=pt&pagename='
								local pagename =  mw.uri.encode(mw.title.getCurrentTitle().fullText)
								local coorlink = string.gsub(string.gsub(string.gsub(coor, '[^0-9NSLO]+', '_'), 'O', 'W'), 'L', 'E')
								local i = 2
								while #args > i do
												if args[i] == '' or args[i + 1] == '' then
																break
												end
												coorlink = coorlink .. '_' .. args[i] .. ':' .. string.gsub(args[i + 1], ' ', '_')
												i = i + 2
								end
								wd.temp.debug = 'obtida coordenada e adicionado link'
								return link .. pagename .. '&params=' .. coorlink .. ' ' .. coor .. ']</span>'
				end
				wd.temp.debug = 'obtida coordenada'
				return coor
end
wd.parser = function(s)
				local i = 1
				local grupos = {}
				local props = {}
				while i < #s do
								local abre = string.find(s, '{', i, true)
								local fecha = string.find(s, '}', i, true)
								if abre and not (fecha and fecha < abre) then
												table.insert(grupos, {abre})
												i = abre + 1
								else
												if fecha and #grupos > 0 then
																local encontrou
																for n = 0, #grupos - 1 do
																				if not grupos[#grupos - n][2] then
																								grupos[#grupos - n][2] = fecha
																								encontrou = true
																				end
																end
																if encontrou then
																				i = fecha + 1
																end
												elseif fecha then
																i = fecha + 1
												else
																break
												end
								end
				end
				i = 1
				while i < #s do
								local pos, fim = mw.ustring.find(s, 'P%d[%w:%-/]*', i)
								if pos then
												props[string.sub(s, pos, fim)] = pos
												i = fim + 1
								else
												break
								end
				end
				return grupos, props
end
-- Processa expressões que pedem dados do Wikidata
wd.expandir = function(str)
				local grupos, props = wd.parser(str)
				local expandido = {}
				local debug = {}
				for prop, pos in pairs(props) do
								local valor = wd.dados(prop)
								if valor then
												table.insert(expandido, pos)
												props[prop] = valor
								else
												props[prop] = false
								end
								table.insert(debug, prop .. ' [' .. (wd.temp.tipo or '?') .. ']: ' .. (wd.temp.debug or ''))
								wd.temp = {}
				end
				if #expandido == 0 then
								wd.temp.debug = #debug > 0 and table.concat(debug, '\n') or 'nenhuma propriedade em "' .. str .. '"'
								return nil
				end
				wd.temp.debug = #debug > 0 and table.concat(debug, '\n') or 'houve algum problema ao espandir "' .. str .. '"'
				for _, g in ipairs(grupos) do
								if #g == 2 then
												local apagar = true
												for _, pos in ipairs(expandido) do
																if g[1] < pos and g[2] > pos then
																				apagar = false
																				break
																end
												end
												if apagar then
																-- os grupos sem propriedades expandidas é preenchido com caracteres { para ser apagado
																str = str:sub(1, g[1]) .. string.rep('{', g[2] - g[1] - 1) .. str:sub(g[2])
												end
								end
				end
				str = str:gsub('[{}]+', '')  -- apaga caracteres { e }
				local oprops = {}
				-- Ordenando as propriedades pelo tamanho da string para evitar problemas quando uma é substring da outra
				for prop, valor in pairs(props) do
								local i = #oprops + 1
								for n, s in ipairs(oprops) do
												if #s < #prop then
																i = n
																break
												end
								end
								table.insert(oprops, i, prop)
				end
				-- Substituindo as propriedades pelo seu valor expandido
				for n, prop in ipairs(oprops) do
								local escprop = prop:gsub('[%^%$%(%)%%%.%[%]%*%+%-%?]','%%%1')  -- escapando caracteres especiais
								str = str:gsub(escprop, type(props[prop]) == 'string' and props[prop] or '')
				end
				return str
end
wd.formatardata = function(t)
	local ano, mes, dia = string.match(t, '^%+(%d+)%-(%d%d)%-0?(%d%d?)T')
	if not ano then
		return nil
	elseif mes == '00' then
		return ano
	else
		return meses[mes] .. ' de ' .. ano
	end
end
-- Tabela para funções invocadas diretamente
invoke = {}
invoke.dados = function(frame)
				local arg = frame.args['1']
				if arg and arg ~= '' then
								local resp = (wd.dados(arg) or '')
								if frame.args['2'] == 'debug' then
												resp = resp .. (wd.temp.debug and ' (' .. wd.temp.debug .. ')' or '')
								end
								return resp
				else
					arg = frame:getParent().args['1']
					if arg and arg ~= '' then
												local resp = (wd.dados(arg) or '')
												return resp
								end
				end
				return '(erro: nenhuma propriedade fornecida)'
end
invoke.expandir = function(frame)
				local arg = frame.args['1']
				if arg and arg ~= '' then
								local resp = (wd.expandir(arg) or '')
								if frame.args['2'] == 'debug' then
												resp = resp .. (wd.temp.debug and ' (' .. wd.temp.debug .. ')' or '')
								end
								return resp
				else
					args = frame:getParent().args
					if args['1'] and args['1'] ~= '' then
												local resp = (wd.expandir(args['1']) or '')
												if resp == '' and args['local'] then
													return args['local']
												elseif args['data'] and wd.data[args['data']] then
													local data = wd.formatardata(wd.data[args['data']])
													if data then
																	return resp ..' (' .. data .. ')'
													end
												end
												return resp
								end
				end
				return '(erro: nenhuma propriedade fornecida)'
end
return invoke