Module:Neuchâtel fossil cast

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Lua

CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules


Code behind {{Neuchâtel fossil cast}}

Usage

{{#invoke:Neuchâtel fossil cast|function_name}}


Code

--[[

= Module:Neuchâtel fossil cast =

Authors and maintainers:
* User:Jarekt
* User:Harej

]]
require('strict') -- used for debugging purposes as it detects cases of unintended global variables
local getLabel      = require("Module:Wikidata label")._getLabel            -- used for creation of name based on Wikidata
local labels        = require("Module:I18n/artwork")                        -- internationalization of labels
local core          = require('Module:Core')
local art           = require('Module:Wikidata art') 
local qualifierDate = require("Module:Wikidata date")._qualifierDate        -- used for processing of date qualifiers
local institution   = require("Module:Institution")._institution
local creator       = require("Module:Creator")._creator 
local information   = require("Module:Information")._information
local p = {}

-- ===========================================================================
-- === Assemble wikidata query tools used by other modules ===================
-- ===========================================================================

-- ===========================================================================
local function getEntityLabel(entity, userLang) 
	local label
	-- build language fallback list
	local langList = mw.language.getFallbacksFor(userLang)
	table.insert(langList, 1, userLang) 
	-- get label
	for _, lang in ipairs(langList) do  -- loop over language fallback list looking for label in the specific language
		label = entity:getLabel(lang)
		if label then break end                    -- label found and we are done
	end	
	label = label or entity.id -- fallback value
	return '[[:d:'.. entity.id ..'|'.. label ..']]'
end

-------------------------------------------------------------------------------
local function getBestProperties(entity, prop)
	return core.parseStatements(entity:getBestStatements( prop ), nil)
end

-------------------------------------------------------------------------------
local function getProperty(entity, prop)
	return (core.parseStatements(entity:getBestStatements( prop ), nil) or {nil})[1]
end

-------------------------------------------------------------------------------
local function getItemProperty(item, prop)
	return (core.parseStatements(mw.wikibase.getBestStatements( item, prop ), nil) or {nil})[1]
end

-------------------------------------------------------------------------------
local function getItemProperties(item, prop)
	return core.parseStatements(mw.wikibase.getBestStatements( item, prop ), nil)
end

-------------------------------------------------------------------------------
local function getPropertyByQual(entity, prop, qualID, qvalue, lang)
	local Res = {}
	if entity.claims and entity.claims[prop] then
		for k, statement in ipairs( entity:getBestStatements( prop )) do
			if (statement.mainsnak.snaktype == "value" and statement.qualifiers and statement.qualifiers[qualID]) then
				local snak = statement.qualifiers[qualID][1]
				if (snak.snaktype == "value" and snak.datatype == 'wikibase-item' and snak.datavalue.value.id == qvalue) then 
					return statement.mainsnak.datavalue.value
				end
			end
		end
	end
	return ''
end

-------------------------------------------------------------------------------
local function getPropertyWithQual(entity, prop, qualifiers, lang)
	local Res = {}
	if entity.claims and entity.claims[prop] then
		for k, statement in ipairs( entity:getBestStatements( prop )) do
			local res, val = {}, nil -- table with fields: key, value, P... (qualifiers)
			if (statement.mainsnak.snaktype == "value") then 
				val = statement.mainsnak.datavalue.value
				if val.id then 
					val = val.id
				elseif val.text then
					res.value_lang = val.language
					val = val.text
				end
			else
				val = statement.mainsnak.snaktype
			end
			res.value = val
			for iQual, qual in ipairs( qualifiers ) do
				if statement.qualifiers and statement.qualifiers[qual] then
					local snak = statement.qualifiers[qual][1]
					if (snak.snaktype == "value" and snak.datatype == 'wikibase-item') then 
						val = snak.datavalue.value.id
					elseif (snak.snaktype == "value" and snak.datatype == 'string') then 
						val = snak.datavalue.value
					elseif (snak.snaktype == "value" and snak.datatype == 'time') then
						val = qualifierDate(snak, lang).str
					else
						val = nil
					end
					res[qual] = val
				end
			end
			table.insert(Res, res)
		end
	end
	return Res
end

--------------------------------------------------------------------
local function scientific_name(item, lang)
	-- Extract P225 (scientific name) and associated qualifiers: Extract P405 (discoverer) and P574 (time of discovery)
	local taxon_info = {}
	local name
	local entity = mw.wikibase.getEntity(item)
	local edit_str = core.editAtWikidata(item, 'P225') -- taxon name (P225)
	local props = getPropertyWithQual(entity, 'P225', {'P405', 'P574'}, lang) -- P405 axon author, P574 year of taxon publication
	for _, prop in ipairs(props) do
		local val = '[[d:' .. item .. '|' .. prop.value .. ']]'
		val = val or core.getLabel(item, lang)
		if prop.P405 then
			name = getItemProperty(prop.P405, 'P835') or getItemProperty(prop.P405, 'P734') -- author citation (zoology) or last name
			if (name) then
				name = '[[d:' .. prop.P405 .. '|' .. name .. ']]'
			else
				name = core.getLabel(prop.P405, lang)
			end
			taxon_info.discoverer     = core.getLabel(prop.P405, lang)
		end
		if (name and prop.P574) then
			taxon_info.specimen = "''" .. val .. "'' (".. name .. ', ' .. prop.P574 .. ')' .. edit_str
		else
			taxon_info.specimen =  "''" .. val .. "''"
		end
		taxon_info.discovery_time = prop.P574
	end
	return taxon_info
end

--------------------------------------------------------------------
local function taxonChain(item, endRank)	
	local rank, visited_items = nil, {}
	for iter = 1,10 do -- at maximum only 10 iterations are allowed
		visited_items[item] = true -- early detection of loops
		item = getItemProperty(item, 'P171')
		if (not item) then
			break -- stopping criteria: item is missing "parent taxon" property
		end
		rank = getItemProperty(item, 'P105')
		if visited_items[item] or (rank==endRank) then		
			break -- stopping criteria: loop is detected or end-rank was found
		end
	end
	if item and rank==endRank then
		return item
	end
end

--------------------------------------------------------------------
local function get_classification(item, lang)
	local val  = core.getLabel(item, lang)
	local X = {}
	local taxons = {'Q35409', 'Q37517', 'Q36602'} -- family -> class -> order 
	for _,taxon in ipairs(taxons) do
		local item = taxonChain(item, taxon) -- find family
		if item then
			local id  = core.getLabel(taxon, lang)
			local lab = core.getLabel(item,  lang)
			table.insert(X, id .. ': ' .. lab )
		else
			break
		end
	end
	
	if #X>0 then
		return  val .. ' (' .. table.concat(X,', ') .. ')'
	else
		return  val
	end
end

--------------------------------------------------------------------
local function timeChain(item, endStage)
	local inst, item1
	local  ok = false
	for _, item in ipairs(getItemProperties(item, 'P361')) do
		if item ~= 'Q63463770' then -- not "ICS Standard Global Chronostratigraphic (Geochronologic) Scale"
			item1 = item
			ok = true
			break
		end 
	end
	if (not ok)  then
		return nil
	end
	return item1
	-- for _, inst in ipairs(getItemProperties(item, 'P31')) do
		-- if inst == endStage then 
		   -- return item
		-- end 
	-- end		
end

--------------------------------------------------------------------
local function timeChain2(item, endStage)
	local  ok = false
	for _, item in ipairs(getItemProperties(item, 'P361')) do
		if item ~= 'Q63463770' then -- not "ICS Standard Global Chronostratigraphic (Geochronologic) Scale"
			ok = true
			break
		end 
	end
	if (ok)  then
		return item
	end
end

--------------------------------------------------------------------
local function timeChain1(item, endStage)	
	local rank, visited_items, ok, item1
	visited_items = {}
	for iter = 1,10 do -- at maximum only 10 iterations are allowed
		ok = false
		visited_items[item] = true -- early detection of loops
		for _, item in ipairs(getItemProperties(item, 'P361')) do
			if item ~= 'Q63463770' then -- not "ICS Standard Global Chronostratigraphic (Geochronologic) Scale"
				ok = true
				break
			end 
		end
		if (not ok) or visited_items[item] then
			break -- stopping criteria: item is missing "part of" property, or a loop is detected
		end
		for _, item1 in ipairs(getItemProperties(item, 'P31')) do
			if item1 == endStage then 
				return item
			end 
		end	
	end
end

-- ===========================================================================
-- === Template specific code ================================================
-- ===========================================================================
local function read_cast_from_wikidata(cast_qid, data, lang)
	-- the photographs are of fossil cast of specimens of organisms
	local X, id
	if not cast_qid then
		return data
	end
	local entity = mw.wikibase.getEntity(cast_qid)
	data.name = getEntityLabel(entity, lang) -- copy labels of the item to the top line
	X         = art.get_accession_number(entity, lang)
	data.id   = X.str -- wikitext version of the accession number 
	id        = getProperty(entity, 'P195')
	if id then
		id = getItemProperty(id, 'P127')
		if id=='Q121092336' then
			id = 'Q3330885'
		end
		if id then
			data.institution = institution({wikidata=id, lang=lang, collapse = 'collapsed'})
		end
	end
	return data
end

-- ===========================================================================
local function read_specimen_from_wikidata(specimen_qid, data, lang)
	local X, id, id1, val
	if not specimen_qid then
		return data
	end
	local entity = mw.wikibase.getEntity(specimen_qid)
	
	X               = art.get_creator(entity, 'P61', lang)
	data.discoverer = X.str	
	data.references = art.get_references(entity, lang)
	
	-- object_type
	local props = getPropertyWithQual(entity, 'P31', {'P642'}) 
	local edit_str = core.editAtWikidata(entity.id, 'P31', lang)
	for _, prop in ipairs(props) do
		data.type_status = core.getLabel(prop.value, lang) .. edit_str 
		if prop.P642 then -- Get taxon information via specimen
			data.classification = get_classification(prop.P642, lang)
			local taxon_info    = scientific_name(prop.P642, lang)
			data.specimen       = taxon_info.specimen
			data.discoverer     = taxon_info.discoverer
			data.discovery_time = taxon_info.discovery_time
		end
	end	
	
	-- discovery_place
	id  = getProperty(entity, 'P189')
	if id then
		id1 = getItemProperty(id, 'P17') -- country of discovery_place
		val = core.getLabel(id, lang)
		edit_str = core.editAtWikidata(entity.id, 'P189', lang)
		if id1 then 
			data.discovery_place = val .. ' (' .. core.getLabel(id1, lang) .. ')' .. edit_str
		elseif id then 
			data.discovery_place = val .. edit_str
		end
	end

	-- time period (P2348) 
	-- TODO: output "Late Jurassic (period: Jurassic, epoch: Mesozoic)" seems to be in wrong order (pertiod then epoch). Why?
	X ={}
	local stages = {epoch='Q754897',  period='Q392928'}
	id  = getProperty(entity, 'P2348')
	if id then 
		val = core.getLabel(id, lang)
		edit_str = core.editAtWikidata(entity.id, 'P2348', lang)
		for _,stage in pairs(stages) do
			id = timeChain(id, stage)
			if id then
				table.insert(X, core.getLabel(stage, lang) .. ': ' .. core.getLabel(id, lang))
			else
				break
			end
		end
		if #X>0 then
			data.time_period = val .. ' (' .. table.concat(X,', ') .. ')' ..edit_str
		else
			data.time_period = val .. edit_str
		end
	end

	return data
end


-- ===========================================================================
local function header_insert(data, cast_qid, lang)
	local field, tag, cell1, cell2, id
	local top, results = {}, {}
	if data.name then
		table.insert(top, string.format('<span class="fn" id="artwork"><bdi>%s\n</bdi></span>', data.name ) )
		table.insert(top, string.format('[[File:Wikidata-logo.svg|20px|wikidata:%s|link=wikidata:%s]]', cast_qid, cast_qid) )
		table.insert(top, string.format('[[File:Wikidata-Reasonator_small_logo.svg|5px|reasonator:%s|link=https://reasonator.toolforge.org/test/?q=%s]]', cast_qid, cast_qid) )

		cell1 = mw.ustring.format('<th colspan="2" style="background-color:#ccf; font-weight:bold; border:1px solid #aaa" lang="%s" text-align="center">%s</th>\n', lang, table.concat(top, '&nbsp;'))
		field = mw.ustring.format('<tr>\n%s\n</tr>\n\n', cell1)
		table.insert(results, field)
	end
	return table.concat(results)
end

-- ===========================================================================
local function fossil_insert(data, cast_qid, lang)
	local field, tag, cell1, cell2, id
	-- field specific preferences
	local params = {
		{field='type_status'    , tag='status'},
		{field='specimen'       , tag='specimen'},
		{field='classification' , tag='Q11398'},
		{field='time_period'    , tag='P2348'},
		{field='age'            , tag='Q568683'},
		{field='epoch'          , tag='Q754897'},
		{field='period'         , tag='Q392928'},
		{field='discoverer'     , tag='wm-license-information-author', id='fileinfotpl_aut'},
		{field='discovery_time' , tag='time_of_description'},
		{field='discovery_place', tag='place_of_discovery',            id='fileinfotpl_art_discovery_place'},
		{field='institution'    , tag='Q2668072',                      id='fileinfotpl_art_gallery'},  
		{field='id'             , tag='wm-license-artwork-id',         id='wm-license-artwork-id'},
		{field='references'     , tag='wm-license-artwork-references', id='fileinfotpl_art_references'},
	}
	local results = {}, {}
	for _, param in ipairs(params) do
		field = data[param.field]
		tag   = param.tag
		if string.sub(tag,1,10) == 'wm-license' then
			tag = mw.message.new( tag ):inLanguage(lang):plain() -- label message in lang language
		elseif string.match(tag, "^[QP]%d+$") then
			tag = getLabel(tag, lang, "-", "ucfirst")
		elseif labels[tag] then
			tag = core.langSwitch(labels[tag], lang)
		else
			tag = '('..tag..'/'..lang..')'
		end
		--	id = mw.ustring.format('id="%s" ', param.id)
		if field then  -- skip if no field
			cell1 = mw.ustring.format('<td %sclass="fileinfo-paramfield" lang="%s">%s</td>\n', id or '', lang, tag)
			cell2 = mw.ustring.format('<td>\n%s</td>', field or '')
			field = mw.ustring.format('<tr>\n%s%s\n</tr>\n\n', cell1, cell2)
			table.insert(results, field)
		end

	end
	return table.concat(results)
end

-- ===========================================================================
function p.fossil(frame)
	local args = core.getArgs(frame)
	local lang = args.lang or 'en'
	local data = {}
	data       = read_cast_from_wikidata(args.cast_qid, data, lang)
	data       = read_specimen_from_wikidata(args.specimen_qid, data, lang)

	-- Build the fossil information output
	args.other_fields_0 = header_insert(data, args.cast_qid, lang)
	args.other_fields_1 = fossil_insert(data, args.cast_qid, lang)
	args.strict         = false
	args.cast_qid       = nil
	args.specimen_qid   = nil
	args.permission     = frame:expandTemplate{title="Muséum d'histoire naturelle de Neuchâtel"}
	local results       = information(args)
	return results
end

-- ===========================================================================
function p.debug(frame)
	local field  = frame.args.field
	local lang   = frame.args.lang
	local item   = frame.args.item
	if field=='scientific_name' then
		return p.scientific_name(item, lang) or item
	elseif field=='test' then
		return 'test'
	end
	return ''
end

return p