Module:Wikidata art/sandbox

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
--[[  
  __  __           _       _      __        ___ _    _     _       _                     _   
 |  \/  | ___   __| |_   _| | ___ \ \      / (_) | _(_) __| | __ _| |_ __ _    __ _ _ __| |_ 
 | |\/| |/ _ \ / _` | | | | |/ _ (_) \ /\ / /| | |/ / |/ _` |/ _` | __/ _` |  / _` | '__| __|
 | |  | | (_) | (_| | |_| | |  __/_ \ V  V / | |   <| | (_| | (_| | || (_| | | (_| | |  | |_ 
 |_|  |_|\___/ \__,_|\__,_|_|\___(_) \_/\_/  |_|_|\_\_|\__,_|\__,_|\__\__,_|  \__,_|_|   \__|
                                                                                             
This module is intended to provide localized text for different infobox fields.
At the moment we have:
|====================|===========================|=====================|
| Infobox Field      | Property                  | Template            |
|====================|===========================|=====================|
| object history     | commissioned by (P88)     | {{ProvenanceEvent}} |
|                    | owned by (P127)           |                     |
|                    | significant event (P793)  |                     |
| exhibition history | exhibition history (P608) | none                |
| inscriptions       | inscription (P1684)       | {{inscription}}m    |
| medium             | material used (P186)      | {{Technique}}       |
| work location      | work location (P937)      | none                |
| creator            | creator(P170), author(P50)|                     |
|                    | architect (P84)           | {{Creator}}         |
| institution        | inventory number (P217)   | {{Institution}}     |
|                    | collection (P195)         |                     |
|                    | location (P276)           |                     |
| accession number   | inventory number (P217)   |  none               |
|====================|===========================|=====================|


Please do not modify this code without applying the changes first at 
"Module:Artwork/sandbox" and testing at "Module:Artwork/testcases".

Authors and maintainers:
* User:Jarekt - original version 
]]
local getLabel         = require("Module:Wikidata label")._getLabel            -- used for creation of name based on wikidata
local getDate          = require("Module:Wikidata date")._date                 -- used for processing of date properties
local qualifierDate    = require("Module:Wikidata date")._qualifierDate        -- used for processing of date qualifiers
local creator          = require("Module:Creator")._creator                    -- render creator templates
local institution      = require("Module:Institution")._institution            -- render institution templates
local material_LUT     = require('Module:Artwork/Technique LUT')   


-- ==================================================
-- === Internal functions ===========================
-- ==================================================

local function length(T)
  local count = 0
  for _ in pairs(T) do count = count + 1 end
  return count
end

local function langSwitch(list,lang)
	local langList = mw.language.getFallbacksFor(lang)
	table.insert(langList,1,lang)
	for i,language in ipairs(langList) do
		if list[language] then
			return list[language]
		end
	end
	return nil
end

local function getProperty(entity, prop, outputType)
	local Output = {}
	if entity.claims and entity.claims[prop] then
		for _, statement in pairs( entity:getBestStatements( prop )) do
			if (statement.mainsnak.snaktype == "value") then 
				local val = statement.mainsnak.datavalue.value
				if val.id then 
					val = val.id
				elseif val.text then
					val = val.text
				end
				table.insert(Output, val)
			end
		end
	end
	if #Output==0 then
		return nil
	elseif outputType=='one' then
		return Output[1]
	else
		return Output
	end
end

local function getPropertyQual(entity, prop, qualifiers, lang, offset)
	local Res = {}
	if entity.claims and entity.claims[prop] then
		for k, statement in ipairs( entity:getBestStatements( prop )) do
			if (statement.mainsnak.snaktype == "value") then 
				local res = {} -- table with fields: key, value, P... (qualifiers)
				local jdn = k + (offset or 0) -- "Julian day number" will be used as a key for sorting events; initialize
				local val = statement.mainsnak.datavalue.value
				if val.id then 
					res.value_id = val.id
					val = getLabel(val.id, lang)
				elseif val.text then
					res.value_lang = val.language
					val = val.text
				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 = getLabel(snak.datavalue.value.id, lang)
							res[qual ..'_id'] = snak.datavalue.value.id
						elseif (snak.snaktype == "value" and snak.datatype == 'string') then 
							val = snak.datavalue.value
						elseif (snak.snaktype == "value" and snak.datatype == 'monolingualtext') then 
							val = snak.datavalue.value.text
							res[qual.."_lang"] = snak.datavalue.value.language
						elseif (snak.snaktype == "value" and snak.datatype == 'time') then
							val = qualifierDate(snak, lang)
							if iQual==1 then -- first qualifier in the qualifiers list will be used as a sorting value
								jdn = val.jdn
							end
							val = val.str
						else
							val = nil
						end
						res[qual] = val
					end
				end
				res.key = jdn
				table.insert(Res, res)
			end
		end
	end
	local tableComp = function (rec1, rec2) return rec1.key<rec2.key end
	table.sort(Res, tableComp)
	return Res
end

-- ==================================================
-- === External functions ===========================
-- ==================================================
local p = {}

-- ===========================================================================
function p.get_object_history(entity, lang)
	-- Provenance look up table converting items IDs to template inputs
	local ProvenanceLUT = {
		Q22340494  = "acquisition", --acquisition
		Q707482    = "gift", --gift
		Q211557    = "bequest", --bequest
		Q194189    = "sale", --sales
		Q184303    = "gift", --gift
		Q177923    = "auction", --auction
		Q5260774   = "deposit", --deposit
		Q1124860   = "gift", --donation
		Q1362753   = "acquisition", --acquisition
		Q1756454   = "theft", --art theft
		Q17781833  = "destruction", --destruction
		Q217102    = "restored", --conservation
		Q2727213   = "theft", --theft
		Q6498684   = "in collection", --ownership
		Q753297    = "discovery", --discovery
		Q14903979  = "conveyance", --change of ownership
		Q189539    = "loan", --loan
		Q601401    = "exchange", --trade
		Q6160      = "damaged", --vandalism
		Q19880899  = "theft", --Isabella Stewart Gardner Museum theft
		Q1156800   = "restitution", --restitution
		Q1369832   = "purchase", --purchasing
		Q851304    = "theft", --Looted art
		Q3030513   = "missing", --disappearance
		Q21745157  = "destruction", --destroyed artwork
		Q53706     = "theft", --robbery
		Q328376    = "theft", --Nazi plunder
		Q420708    = "acquisition", --Acquisition
		Q760089    = "commission", --commission
		Q200303    = "inheritance", --inheritance
		Q3196      = "burnt", --fire
		Q192623    = "theft", --looting
	}
	--{{ProvenanceEvent|time=1950-03-01|type=discovery|newowner=Elias Cohen|place=The Hague}}
	local frame = mw.getCurrentFrame()
	local EventList = {}

	-- discovery statements
	local discoveror     =  getProperty(entity, 'P61',  'one')   -- discoverer or inventor (P61) 
	local discoveryPlace =  getProperty(entity, 'P189', 'one')   -- location of discovery (P189) 
	local d = getDate(entity, 'P575' , lang)              -- discovery date
	local discoveryTime = d.str
	local event = {}
	if discoveror or discoveryPlace or discoveryTime then
		event.str = frame:expandTemplate{ title = 'ProvenanceEvent', args = { type='discovery', time=discoveryTime, discoveror=event.value, place=discoveryPlace, lang=lang } } 
		event.key = 0;
		--event.str = '(P61)  ' .. event.str
		table.insert(EventList, event)
	end
	
	-- from   commissioned by (P88) / point in time (P585) (time property)
	local provEvents = getPropertyQual(entity, 'P88', {'P585'}, lang) -- 0 is where the numbering of undated events will start
	for _, event in ipairs( provEvents) do
		if event.P585 then
			event.str = frame:expandTemplate{ title = 'ProvenanceEvent', args = { type='commissioned', time=event.P585, newowner=event.value, lang=lang } } 
			--event.str = '(P88)  ' .. event.str
			table.insert(EventList, event)
		end
	end
	-- from  owned by (P127) / P580 (time property)
	local provEvents = getPropertyQual(entity, 'P127', {'P580'}, lang, 100) -- 100 is where the numbering of undated events will start
	for _, event in ipairs( provEvents) do
		if event.P580 then
			event.str = frame:expandTemplate{ title = 'ProvenanceEvent', args = { type='acquisition', time=event.P580, newowner=event.value, lang=lang } } 
			--event.str = '(P127)  ' .. event.str
			table.insert(EventList, event)
		end
	end
	-- from significant event P793 property with point in time (P585) qualifier
	local provEvents = getPropertyQual(entity, 'P793', {'P585'}, lang, 200) -- 200 is where the numbering of undated events will start
	for _, event in ipairs( provEvents) do
		local eventType = ProvenanceLUT[event.value_id] -- look up event type based on stored item ID
		if event.P585 and eventType then
			event.str = frame:expandTemplate{ title = 'ProvenanceEvent', args = { type=eventType, time=event.P585, lang=lang } } 
			--event.str = '(P793)  ' .. event.str
			table.insert(EventList, event)
		elseif event.P585  then
			event.str = event.P585 .. ": unknown event: "..event.value.."<br/>"
			--event.str = '(P793)  ' .. event.str
			table.insert(EventList, event)
		end
	end
	if #EventList>0 then -- if any events
		local tableComp = function (rec1, rec2) return rec1.key<rec2.key end
		table.sort(EventList, tableComp) -- sort them by the date using sort key
		local X, event = {}, {}
		for _, event in ipairs(EventList) do -- collect just text of the template
			table.insert(X, event.str)
		end
		return '* ' .. table.concat(X,"\n* ")
	end
	return nil
end

-- ===========================================================================
function p.get_exhibition_history(entity, lang)	
--  exhibition history (P608)  (item property) /  'P580', 'P582' (time properties) 
	local prop = getPropertyQual(entity, 'P608', {'P580', 'P582'}, lang)
	local X={}
	for _, p in ipairs(prop) do
		local str = p.value
		if p.P580 then
			str = mw.ustring.format("%s (%s - %s)", p.value, p.P580, p.P582 or '')
		end
		table.insert(X, str)
	end
	if length(X)>0 then
		return "\n*" .. table.concat(X,"\n*")
	end
	return nil
end

-- ===========================================================================
function p.get_medium(entity, lang)
	-- material used (P186) (item property) /  applies to part (P518) (item property) 
	local prop = getPropertyQual(entity, 'P186', {'P518'}, lang)
	if not prop then
		return nil -- if no P186 statements than exit
	end
	local temp_args = {} -- technique template arguments
	local And = {'1', 'and', 'and2', 'and3', 'and4'} -- field names to use
	temp_args.lang = lang
	local material = {}
	local n = 0;
	local ok = true;
	for _, p in ipairs( prop) do
		table.insert(material, p.value)
		local mat = material_LUT[p.value_id] -- use lookup table to convert item IDs to terms used by the template
		if not mat then
			ok = false;                      -- unrecognized material
		end
		if p.P518_id=='Q861259' then         -- applies to part: painting surface
			temp_args.on = mat
		elseif n<4 then
			n = n + 1;
			temp_args[And[n]] = mat
		end
	end
	if length(material)==2 and temp_args["1"]=='oil' and temp_args.on=='canvas' then
		local LUT = require("Module:i18n/oil on canvas")  -- oil on canvas can be done in LUA
		return langSwitch(LUT, lang)
	elseif ok then -- if 
		local frame = mw.getCurrentFrame()
		return frame:expandTemplate{ title = 'technique', args=temp_args }
	else 
		return table.concat(material,", ")
	end
	local id = getProperty(entity, 'P2079', 'one')
	if id then
		return getLabel(id, lang)
	end
	return nil
end

-- ===========================================================================
function p.get_inscription(entity, lang)
--[[
 Wikidata
	 inscription (P1684) - Monolingual text
	 applies to part (P518) - item property
	 instance of (P31) - item property
 Commons template:
	{{inscription |1= |full form= |type= |side= |position= |description= |comment= |ID= |language= |translation= |en= |de= |medium= }}
]]
	local LUT = {
	  -- positions
		Q15332388    = "bottom",
		Q17525439    = "bottom",
		Q16421635    = "bottom",
		Q23595       = "center",
		Q13196750    = "left",
		Q17525441    = "left",
		Q257418      = "obverse",
		Q82383       = "on base",
		Q11193       = "on base",
		Q860792      = "on frame",
		Q1542661     = "reverse",
		Q32198402    = "reverse",
		Q16938807    = "reverse",
		Q14565199    = "right",
		Q17525442    = "right",
		Q15332375    = "top",
		Q17525438    = "top",
		-- sides
		Q9305022     = "recto",
		Q9368452     = "verso",
		-- type
		Q188675      = "signature",
		Q205892      = "date",
		Q1898184     = "dedication",
		Q168346      = "monogram",
		Q2221906     = "place",
		Q783521      = "title",
		Q206287      = "quotation",
		Q644099      = "stamp",
		Q162919      = "seal",
		-- complex positions
		bottom_left   = "bottom left",
		bottom_right  = "bottom right",
		bottom_center = "bottom center",
		center_left   = "center left",
		center_right  = "center right",
		left_top      = "top left",
		right_top     = "top right",
		center_top    = "top center"
	}
	local frame = mw.getCurrentFrame()
	local X = {}
	for _, statement in ipairs( entity:getBestStatements( 'P1684' )) do
		if (statement.mainsnak.snaktype == "value") then 
			local val = statement.mainsnak.datavalue.value
			local temp_args, position, iType = {}, {}, {}
			temp_args['1']     = val.text -- text
			temp_args.language = val.language -- language of the text
			temp_args.lang     = lang -- language of the reader

			if statement.qualifiers then
				if statement.qualifiers.P31 then
					for _, snak in ipairs( statement.qualifiers.P31) do
						table.insert(iType, LUT[snak.datavalue.value.id])
					end
					temp_args.type = table.concat(iType, '/')
				end				
				if statement.qualifiers.P518 then
					for _, snak in ipairs( statement.qualifiers.P518) do
						local part = LUT[snak.datavalue.value.id]
						if (part=="recto" or part=="verso") then
							temp_args.side = part
						else
							table.insert(position, part)
						end
					end
				end
				if length(position)==1 then
					temp_args.position = position[1]
				elseif length(position)==2 then
					table.sort(position)
					temp_args.position = LUT[table.concat(position, '_')]
				end
			end
					
			val = frame:expandTemplate{ title = 'inscription', args=temp_args }
			table.insert(X, val)
		end
	end
	if length(X)==1 then
		return X[1]
	elseif length(X)>0 then
		return "\n*" .. table.concat(X,"\n*")
	end
	return nil
end

-- ===========================================================================
function p.get_work_location(entity, lang)
	-- work_location (P937) /  'P580', 'P582' (time properties) 
	local prop = getPropertyQual(entity, 'P937', {'P580', 'P582', 'P585'}, lang)
	local X={}
	for _, p in ipairs(prop) do
		local str = p.value
		if p.P580 or p.P582 then
			str = mw.ustring.format("%s (%s - %s)", p.value, p.P580 or '', p.P582 or '')
		elseif p.P585 then
			str = mw.ustring.format("%s (%s)", p.value, p.P585)
		else
			str = p.value
		end
		table.insert(X, str)
	end
	if length(X)>0 then
		return table.concat(X,"; ")
	end
	return nil
end

-- ===========================================================================
function p.get_depicted_people(entity, lang)

	local prop = getProperty(entity, 'P180', 'all') -- look up "instance of" property for "Institution" entity
	local X={}	
	for i, pid in ipairs(prop or {}) do
		if i<50 then
			local cEntity = mw.wikibase.getEntity(pid)
			local P31 = getProperty(cEntity, 'P31', 'all')
			for _, p31 in ipairs(P31 or {}) do
				if p31=='Q5' then -- instance of "human"
					table.insert(X, getLabel(cEntity, lang))
				end
			end
		end
	end
	if length(X)==1 then
		return X[1]
	elseif length(X)>0 then
		return '* ' .. table.concat(X,"\n* ")
	end
	return nil
end

-- ===========================================================================
function p.get_accession_number (entity, lang)
	local Res = {} -- initialize final output

	-- harvest data from inventory number (P217) property with qualifiers:  collection (P195) and end time (P582)
	local Y = {}  -- Y is a structure where we have a table of IDs for each collection
	local prop = getPropertyQual(entity, 'P217', {'P195', 'P582'}, lang)
	for k, p in ipairs(prop) do                      -- loop over all IDs found
		if not p.P582 then                             -- skip if there is an "end date"
			local key = p.P195_id or k
			if not Y[key] then Y[key]={} end -- initialize if it does not exist
			table.insert(Y[key], p.value)          -- group IDs by collection
			Res.id = p.value -- return one of the pure ID strings, to be used as category sortkey
		end
	end
	
	--assemble the wikitext of the accession_number field
	local strTable = {}                 -- table with wikitext strings for each "collection"
	for key, id in pairs(Y) do          -- loop over institutions
		local id=mw.text.listToText(id)   -- convert all the IDs into a single string (in most cases there will be only one)
		if type(key)=='string' then       -- if "collection" qualifier is used than add it to the ID
			table.insert(strTable, mw.ustring.format( "%s <small>(%s)</small>", id , getLabel(key, lang) ) )
		else
			table.insert(strTable, id )     -- if no "collection" is mentioned than just return ID
		end
	end
	
	-- assemble final output structure
	if #strTable==1 then     -- single ID case
		Res.str = strTable[1]  -- just return the string
	elseif #strTable>1 then  -- if more than one than return bulleted list
		Res.str = "* " .. table.concat(strTable, "\n* ")
	end
	return Res
end

-- ===========================================================================
local function renderInstitution(entity, lang)
-- local function to create wikitext for a single institution template or {{Private collection}} template
-- once we have entity check if Institution template exist and call it or assemble one based on Wikidata
	local frame = mw.getCurrentFrame()
	
	-- first check for few special cases which will result in {{Private collection}} template
	if entity.id == 'Q768717' then   -- render {{Private collection}} template
		return frame:expandTemplate{ title ='Private collection'} .. '<br/>\n'
	end
	local P31 = getProperty(entity, 'P31', 'all') -- look up "instance of" property for "Institution" entity
	for _, p in ipairs(P31 or {}) do
		if p=='Q5' then                             -- if "Institution" entity is a person than render {{Private collection}} template
			return frame:expandTemplate{ title ='Private collection', args={ owner = getLabel(entity, lang)}} .. '<br/>\n'
		elseif p=='Q768717' then                    -- if "Institution" is an instance of "Private collection" than render {{Private collection}} template
			return frame:expandTemplate{ title ='Private collection'} .. '<br/>\n'
		end
	end
	
	-- render Institution template
	local P1612 = getProperty(entity, 'P1612', 'one')                                  -- look up "Commons Institution page" property
	if P1612 then
		return frame:expandTemplate{ title ='Institution:' .. P1612, args={'collapse'} } -- use existing template
	else
		local inst,_ = institution({wikidata=entity.id, lang=lang, collapse=1})          -- create institution based on item id 
		return inst
	end
end

-- ===========================================================================
function p.get_institution(entity, lang)
  local collection, location = {}, {} -- relevant data is stored in collection (P195) and location (P276) properties

	--  harvest data from inventory number (P217) property with qualifiers:  collection (P195), and end time (P582)
	local prop = getPropertyQual(entity, 'P217', {'P580', 'P582', 'P195'}, lang) -- P580 if present is used for sorting
	for _, p in ipairs(prop) do
		if not p.P582 and p.P195_id then  -- skip if there is an "end date"
			collection[p.P195_id] = 1       -- store collection item ID
		end
	end
	
	-- harvest data from collection (P195) / start time (P580) + end time (P582)
	local prop = getPropertyQual(entity, 'P195', {'P580', 'P582'}, lang) -- P580 if present is used for sorting
	for _, p in ipairs(prop) do
		if  p.P582 then                -- skip if there is an "end date"
			collection[p.value_id] = nil -- and delete from Collection list
		else
			collection[p.value_id] = 1   -- otherwise  collection item ID to the list
		end
	end
	
	-- harvest data from location (P276) / start time (P580) + end time (P582)
	local prop = getPropertyQual(entity, 'P276', {'P580', 'P582'}, lang)
	for _, p in ipairs(prop) do 
		if not p.P582 and not collection[p.value_id] then -- skip if there is an "end date" or the value is in collection table
			location[p.value_id] = 1     -- store location item ID
		end
	end 
	
	-- initialize output structure
	local Res = {}
	Res.institution = nil
	Res.location    = nil
	Res.id          = nil
	
	-- first try usual cases of single collection item 
	if length(collection)==1  then                            -- only a single collection item
		local cId, _  = next(collection, nil)                   -- collection item ID
		local cEntity = mw.wikibase.getEntity(cId)              -- collection entity
		local cParent = getProperty(cEntity, 'P361', 'one')     -- collection parent object of which collection item is part of (P361) 
		if cParent == 'Q19675' or cParent == 'Q1075988' then    -- special case where collection is part of Louvre Museum
			local frame = mw.getCurrentFrame()
			Res.institution = frame:expandTemplate{ title ='Institution:Louvre', args={'collapse'} } -- render existing {{Institution:Louvre}} template
			Res.id          = 'Q1075988'
			Res.location    = getLabel(cEntity, lang)             -- use collection and location tables to populate location/department field  
			if length(location)>0 then
				local lId, _  = next(location, nil)                 -- Location item ID
				Res.location = Res.location .. '<br/>\n' .. getLabel(lId, lang)	
			end
			return Res
		end
		
		if cId=='Q812285' and length(location)>0 then           -- if collection is Bavarian State Painting Collections (Q812285) 
			collection = location                                 -- use location instead collection
		else
			Res.institution = renderInstitution(cEntity, lang)    -- use collection entity to render Institution template
			Res.id          = cEntity.id
			if length(location)>0 then                            -- single collection and at least one location
				local lId, _  = next(location  , nil)               -- location item ID
				local lEntity = mw.wikibase.getEntity(lId)          -- location entity
				local lParent = getProperty(lEntity, 'P361', 'one') -- location parent object of which location item is part of (P361) 
				if lParent == cId then                              -- location is part of the collection listed above
					Res.location    = getLabel(lEntity, lang)	        -- use location entity as location/department field                                            
				end                                                 -- if collection and locations are not related so ignore location(s)
			end
			return Res
		end
	end
	
	-- If the case is not usual try generic approach
	if length(collection)==0 and length(location)>0 then    -- no collections but we have some locations
		collection = location                                 -- use location instead collection
	end
	if length(collection)>0 then                            -- collections or locations only or locations same as collections
		local X = {}                                          -- table with wikitext of all the institution templates
		for cId, _ in pairs(collection) do                    -- render all collections
			table.insert(X, renderInstitution(mw.wikibase.getEntity(cId), lang) )
		end
		Res.institution = table.concat(X, '\n')
	end
	return Res
end

-- ===========================================================================
function p.get_creator(entity, prop, lang)

	-- harvest the data
	local IDs = {}
	local qualifiers = {P1773='attributed to', P1774='workshop of', P1775='follower of', P1776='circle of', P1777='manner of', P1779='possibly', P1780='school of', P1877='after'};
	local LUT = {Q18122778='presumably', Q30230067='possibly', Q56644435='probably', Q50137645='attributed to'}
	if entity.claims and entity.claims[prop] then
		for _, statement in ipairs( entity:getBestStatements( prop )) do
			local option, itemID1, itemID2, role
			if (statement.mainsnak.snaktype == "somevalue") then 
				table.insert(IDs, {itemID=nil, option=nil, role=nil});
			elseif (statement.mainsnak.snaktype == "value") then 
				itemID1 = statement.mainsnak.datavalue.value.id
				if statement.qualifiers and statement.qualifiers.P518 then       -- applies to part (P518) 
					role = statement.qualifiers.P518[1].datavalue.value.id         -- specify role of "creator" like: bookbinding, lithography, etc.
				end
				if statement.qualifiers and statement.qualifiers.P1480 then      -- sourcing circumstances (P1480) 
					option = LUT[statement.qualifiers.P1480[1].datavalue.value.id] -- add certainty qualifiers
				end				
				for qual, opt in pairs( qualifiers ) do
					if statement.qualifiers and statement.qualifiers[qual] then
						itemID2 = statement.qualifiers[qual][1].datavalue.value.id   -- those qualifiers provide new creator ID
						table.insert(IDs, {itemID=itemID2, option=opt, role=role});
						break
					end
				end
				if not (itemID1=='Q4233718' and itemID2) then -- add new creator, except for the case when they are anonymous and we already have one
					table.insert(IDs, {itemID=itemID1, option=option, role=role});
				end
			end
		end
	end

	--sort the table
	local tableComp = function (rec1, rec2) return (rec1.itemID or 'ZZZ')<(rec2.itemID or 'ZZZ') end
	table.sort(IDs, tableComp)
	
	-- IDs table cleanup
	-- "workshop of", "circle of", "school of", "studio of", "or follower", "or workshop", "and workshop", "attributed to", "after", "formerly attributed to", "follower of", "manner of", "namepiece", "possibly", "probably".
	for k = 2, #IDs do
		if IDs[k-1].itemID==IDs[k].itemID then
			local val = (IDs[k-1].option or '') .. (IDs[k].option or '')
			if val=='workshop of' then
				IDs[k  ].option = "and workshop"
				IDs[k-1].option = "delete"
			elseif val=="follower of" then
				IDs[k  ].option = "or follower"
				IDs[k-1].option = "delete"
			end	
		end
	end

	-- render the output template(s)
	local Creators = {}
	local frame = mw.getCurrentFrame()
	for k =1, #IDs do
		local itemID = IDs[k].itemID
		local option = IDs[k].option
		local role   = IDs[k].role
		if itemID==nil then  -- render {{Unknown|author}} template
			val = frame:expandTemplate{ title ='Unknown', args={'author'}}
			table.insert(Creators, val)
		elseif itemID=='Q4233718' then -- render anonymous label
			val = getLabel(itemID, lang)
			table.insert(Creators, val)
		elseif option ~= "delete" then
			local eEntity = mw.wikibase.getEntity(itemID)
			local P1472 = getProperty(eEntity, 'P1472', 'one') -- look up "Commons Creator page" property
			if P1472 then
				if option then option=option..'/collapse' else option='collapse' end
				val = frame:expandTemplate{ title ='Creator:' .. P1472, args = {option} } -- use existing template 
			else
				val, _ = creator({wikidata=itemID, lang=lang, option=option, collapse=1})-- create creator based on item id 
			end
			if role then
				val = "'''" .. getLabel(role, lang) .. "''': " .. val
			end
			table.insert(Creators, val)
		end
	end  -- for

	-- gather the output structure
	local Res = {}
	Res.str = nil
	Res.id  = nil                            -- if only one creator and no "option" modifier than return ID
	Res.IDs = IDs                            -- raw data used to render the template(s)
	if #Creators>0 then
		Res.str = table.concat(Creators, '\n') -- text of the template
	end
	if #IDs==1 and not IDs[1].option then
		Res.id = IDs[1].itemID
	end
	return Res
end

-- ===========================================================================
function p.get_references(entity, lang)
	local Res -- initialize final output
	
	local wordsep   = mw.message.new( "Word-separator" ):inLanguage(lang):plain()
	local colon     = mw.message.new( "Colon-separator" ):inLanguage(lang):plain() .. wordsep
	local semicolon = mw.message.new( "Semicolon-separator" ):inLanguage(lang):plain() .. wordsep

	-- harvest data from catalog code (P528) property with qualifiers:  catalog (P972)
	local strTable = {}                 -- table with wikitext strings for each "reference"
	local prop = getPropertyQual(entity, 'P528', {'P972'}, lang)
	local catalog, catalog_code, str
	for k, p in ipairs(prop) do                      -- loop over all IDs found
		if p.P972 then                                 -- skip if there is an "end date"
			if not catalog then
				catalog      = getLabel('Q2352616', lang); -- get translation of word "catalog"
				catalog_code = getLabel('P528', lang);     -- get translation of word "catalog code"
			end
			str = catalog .. colon ..  "''" .. p.P972 .. "''" .. semicolon .. catalog_code .. colon .. p.value
			table.insert(strTable, str)          -- group IDs by collection
		end
	end

	-- harvest data from "described at URL" (P973) property with qualifier: language (P407), title (P1476), publisher (P123) and author (P50) 
	local label
	prop = getPropertyQual(entity, 'P973', {'P407', 'P1476', 'P123', 'P50'}, lang)
	for k, p in ipairs(prop) do
		if not label then
			label  = getLabel('P973', lang) -- get translation of phrase "described at URL"
		end
		str = label .. colon .. p.value
		if p.P1476 then -- display title if available rather than raw URL
			str = string.format("%s%s [%s ''%s'']", label, colon, p.value, p.P1476)
		end
		if p.P50 then -- add author
			str = str .. ", " .. p.P50
		end
		if p.P123 then -- add publisher
			str = str .. ", " .. p.P123
		end
		if p.P407 then -- add language
			str = str .. " (" .. p.P407 .. ")"
		end
		table.insert(strTable, str)          -- group IDs by collection
	end
	
	-- Management of direct ID to museum databases: table of 
	-- a) Wikidata properties of the museum cataogue ID and
	-- b) names of the corresponding template on Wikimedia Commons
	-- e.g. the Louvre database, Atlas, has an "Atlas ID" (P1212) on Wikidata
	-- and Template:Louvre_online on Wikimedia Commons.
	local commons_templates_for_database = {
		["P347"]  = "Joconde",        -- Joconde database (French Republic)
		["P1212"] = "Louvre online",  -- Atlas database (Louvre)
		["P4659"] = "Orsay online",   -- Orsay database (Musée d'Orsay)
		["P4157"] = "MEG online",     -- MEG database (Musée d'Ethnographie de Genève)
		["P1679"] = "Art UK",         -- identifier for artworks (publicly owned oil paintings in the UK)
		["P2014"] = "Moma online",    -- identifier for a Museum of Modern Art artwork
		["P2092"] = "Bildindex",      -- Bildindex der Kunst und Architektur ID
		["P2108"] = "Kunstindeks",    -- Kunstindeks Danmark artwork ID
		["P4611"] = "LACMA online",   -- Los Angeles County Museum of Art website
		-- we can add everything from https://commons.wikimedia.org/wiki/Category:Museum_database_templates
		-- ["P2242"] = "",    -- Florentine musea catalogue ID -- Could not find Commons template :(
		-- ["P2282"] = "",    -- Groeningemuseum work PID -- Could not find Commons template :(
		-- ["P2344"] = "",    -- AGORHA work ID -- Could not find Commons template :(
		-- ["P2511"] = "",    -- MSK Gent work PID -- Could not find Commons template :(
		["P350"]  = "RKDimages"       -- RDK (Netherlands Institute for Art History)
	}
	for property, template_name in pairs(commons_templates_for_database) do
		database_id = getProperty(entity, property, 'one')
		if database_id then
			local frame = mw.getCurrentFrame()
			str = frame:expandTemplate{ title = template_name, args = { database_id } } 
			table.insert(strTable, str)
		end
	end
	
	-- harvest data from described by source (P1343) property with qualifiers:  pages (P304), publication date (P577), 
	--  section, verse, or paragraph (P958),  volume (P478),  reference URL (P854),  title (P1476),  statement is subject of (P805)  
	prop = getPropertyQual(entity, 'P1343', {'P304', 'P958', 'P478', 'P854', 'P1476', 'P805', 'P577'}, lang)
	label = nil
	for k, p in ipairs(prop) do 
		if not label then
			label  = getLabel('P1343', lang) -- get translation of word ""
		end
		local frame = mw.getCurrentFrame()
		local cite_arg = {} 
		cite_arg.title   = p.value	    -- described by source (P1343)
		cite_arg.url     = p.P854 or ''	-- reference URL (P854)
		cite_arg.volume  = p.P478 or ''	-- volume (P478)
		cite_arg.pages   = p.P304 or ''	-- pages (P304)
		cite_arg.chapter = p.P958 or ''	-- section, verse, or paragraph (P958)
		cite_arg.series  = p.P805 or ''	-- statement is subject of (P805)
		cite_arg.date    = p.P577 or ''	-- statement is subject of (P805)
		str = frame:expandTemplate{ title ='Cite_book', args = cite_arg }
		table.insert(strTable, label .. colon .. str) 
	end
	-- assemble final output structure
	if #strTable==1 then     -- single ID case
		Res = strTable[1]  -- just return the string
	elseif #strTable>1 then  -- if more than one than return bulleted list
		Res = "* " .. table.concat(strTable, "\n* ")
	end
	return Res
end

-- ===========================================================================
function p.debug(frame)
	local field  = frame.args.field
	local lang   = frame.args.lang
	local entity = mw.wikibase.getEntity(frame.args.wikidata)
	local str, X
	if field=='object_history' then
		return p.get_object_history(entity, lang)     -- object history
	elseif field=='exhibition_history' then
		return p.get_exhibition_history(entity, lang) -- exhibition history
	elseif field=='inscription' then
		return p.get_inscription(entity, lang)
	elseif field=='medium' then
		return p.get_medium(entity, lang)
	elseif field=='work_location' then
		return p.get_work_location(entity, lang)	
	elseif field=='institution' then
		X = p.get_institution(entity, lang)	
		return (X.institution or '') .. '\n' .. (X.location or '')
	elseif field=='accession_number' then
		local res = p.get_accession_number(entity, lang)
		return res.str or ''
	elseif field=='creator' then
		local res = p.get_creator(entity, 'P170', lang)
		return res.str or '';
	elseif field=='references' then
		return p.get_references(entity, lang) or ''
	elseif field=='depicted_people' then
		return p.get_depicted_people(entity, lang) or ''		
	end
	return ''
end

return p