Module:Wikidata art

--[[   __  __           _       _      __        ___ _    _     _       _                     _    |  \/  | ___   __| |_   _| | ___ \ \      / (_) | _(_) __| | __ _| |_ __ _    __ _ _ __| |_  | |\/| |/ _ \ / _` | | | | |/ _ (_) \ /\ / /| | |/ / |/ _` |/ _` | __/ _` |  / _` | '__| __| | |  | | (_) | (_| | |_| | |  __/_ \ 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)     |  |
 * | owned by (P127)          |                     |
 * | significant event (P793) |                     |
 * exhibition history | exhibition history (P608) | none               |
 * inscriptions      | inscription (P1684)       |      |
 * medium            | material used (P186)      |        |
 * work location     | work location (P937)      | none                |
 * creator           | creator(P170), author(P50)|                     |
 * | architect (P84)          |          |
 * institution       | inventory number (P217)   |      |
 * | collection (P195)        |                     |
 * | location (P276)          |                     |
 * accession number  | inventory number (P217)   |  none               |
 * | 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: ]] 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') local id_properties_LUT = require('Module:Artwork/Artwork ID properties LUT') local TagQS           = require('Module:TagQS') local core            = require('Module:Core')
 * User:Jarekt - original version

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

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

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

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

--- local function getAllProperties(entity, prop) return core.parseStatements(entity:getAllStatements( prop ), nil) end

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

--- local function getAllItemProperties(item, prop) return core.parseStatements(mw.wikibase.getAllStatements( item, prop ), nil) 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 = core.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 = core.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 Q105334701 = "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 }	--	local frame = mw.getCurrentFrame local EventList = {}

-- discovery statements local discoveror    =  getProperty(entity, 'P61')   -- discoverer or inventor (P61) local discoveryPlace = getProperty(entity, 'P189')   -- 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=discoveror, place=discoveryPlace, inline=1, lang=lang } } event.key = 0; -- keys are usually dates passed from getPropertyQual but place this one at the front of the queue table.insert(EventList, event) end -- from  commissioned by (P88) / point in time (P585) (time property) local eIcon = core.editAtWikidata(entity.id, 'P88', lang) 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, inline=1, lang=lang } } .. eIcon table.insert(EventList, event) end end -- from owned by (P127) / P580 (time property) eIcon = core.editAtWikidata(entity.id, 'P127', lang) 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, inline=1, lang=lang } } .. eIcon table.insert(EventList, event) end end -- from significant event P793 property with point in time (P585) qualifier eIcon = core.editAtWikidata(entity.id, 'P793', lang) 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, inline=1, lang=lang } } .. eIcon table.insert(EventList, event) elseif event.P585 then event.str = event.P585 .. ": unknown event: "..event.value .. eIcon.." " 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) local comma = mw.message.new( "comma-separator"):inLanguage(lang):plain

-- exhibition history (P608)  (item property) /  'P580', 'P582' (time properties) local prop = getPropertyQual(entity, 'P608', {'P580', 'P582', 'P585', 'P276'}, lang) local rows, cells = {}, {} for i, p in ipairs(prop) do		local places, dates_flag, row, cell if p.P580 or p.P585 then -- likely the item is for a GLAM institution or a city cells = { p.value } if p.P276 then table.insert(cells, p.P276 ) end if p.P580 then table.insert(cells, mw.ustring.format("%s - %s", p.P580, p.P582 or '') ) else table.insert(cells, p.P585 ) end row = table.concat(cells, comma) if i==1 then row = row .. core.editAtWikidata(p.value_id, 'P608', lang) end else -- likely the item is for the exhibit where dates and location will be a property of the exhibit cells = { "''" .. p.value .. "''" }			local exh_entity = mw.wikibase.getEntity(p.value_id) places = getPropertyQual(exh_entity, 'P276', {'P580', 'P582'}, lang) for _, pl in ipairs(places) do				dates_flag = pl.P580 or pl.P585 if pl.P580 then cell = mw.ustring.format("%s (%s - %s)", pl.value, pl.P580, pl.P582 or '') elseif pl.P585 then cell = mw.ustring.format("%s (%s)", pl.value, pl.P585) else cell = pl.value end end table.insert(cells, cell) local start = getDate(exh_entity, 'P580', lang).str local stop = getDate(exh_entity, 'P582', lang).str if start and not dates_flag then table.insert(cells, mw.ustring.format("%s - %s", start, stop or '') ) end row = table.concat(cells, comma) .. core.editAtWikidata(p.value_id, '', lang) end table.insert(rows, row) end if length(rows)>0 then return "\n*" .. table.concat(rows,"\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 or length(prop)==0 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 p.P518_id=='Q107105674' then -- applies to part: mount temp_args.mounted = mat elseif n<4 then n = n + 1; temp_args[And[n]] = mat end end local medium 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 medium = core.langSwitch(LUT, lang) elseif ok then -- if 		local frame = mw.getCurrentFrame medium = frame:expandTemplate{ title = 'technique', args=temp_args } else medium = table.concat(material,", ") end if not medium then local id = getProperty(entity, 'P2079') if id then medium = core.getLabel(id, lang) .. core.editAtWikidata(entity.id, 'P608', lang) end else medium = medium .. core.editAtWikidata(entity.id, 'P186', lang) end return medium 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: local LUT = { -- positions stored in "applies to part (P518)" qualifier Q15332388 = "bottom", Q17525439 = "bottom", Q11812678 = "bottom", Q15332375 = "top", Q17525438 = "top", Q23595   = "center", Q13196750 = "left", Q17525441 = "left", Q14565199 = "right", Q17525442 = "right", Q27956549 = "top left", Q27956533 = "top right", Q27956553 = "bottom left", Q27956561 = "bottom right", Q82383   = "on the base", Q11193   = "on the base", Q860792  = "on the frame", -- if 2 positions are provided they can be combined into a single entry -- supported by the template 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",

-- sides in "applies to part (P518)" qualifier Q9305022 = "recto", Q257418  = "recto", -- obverse Q9368452 = "verso", Q1542661 = "verso", -- reverse Q32198402 = "verso", -- reverse Q16938807 = "verso", -- reverse -- inscription type stored in "object has role (P3831)" qualifier Q188675  = "signature",   -- signature Q1373131 = "signature",   -- signature Q205892  = "date",        -- calendar date Q1898184 = "dedication",  -- dedication Q168346  = "monogram",    -- monogram Q2221906 = "place",       -- geographic location Q783521  = "title",       -- title Q206287  = "quotation",   -- quotation Q644099  = "stamp",       -- rubber stamp Q162919  = "seal",        -- seal Q1417099 = "accession number", -- accession number Q319608  = "artist's address", -- postal address Q14659   = "coat of arms",     -- coat of arms Q42470   = "motto",            -- motto Q1772    = "epitaph",          -- epitaph Q43065   = "watermark",        -- watermark Q827198  = "speech balloon",   -- speech balloon Q98877418 = "reign mark",      -- reign mark on Chinese ceramics Q18585177 = "caption",         -- caption Q2374398 = "cartouche",        -- cartouche Q112110  = "emblem",           -- emblem Q3509975 = "in memoriam",      -- in memoriam Q1272809 = "kalos inscription",-- kalos inscription Q15873403 = "publisher's mark", -- publisher's mark Q105844397 = "initials", -- if 2 inscription types are provided they can be combined into a 		-- single entry supported by the template date_signature = "signature and date", date_monogram = "monogram and date" }	local max_insc = 20 local frame = mw.getCurrentFrame local AllInsc = {} local addIconFlag = true for iInsc, statement in ipairs( entity:getBestStatements( 'P1684' )) do -- "inscription (P1684)" 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 temp_args.nocat   = '1'  -- no inscription categories (They are very slow)

if statement.qualifiers then if statement.qualifiers.P3831 then -- object has role (P3831) for _, snak in ipairs( statement.qualifiers.P3831) do						table.insert(iType, LUT[snak.datavalue.value.id]) end if length(iType)==2 then -- two iType values can be combined into a single value used by the template table.sort(iType) -- order them alphabetically val = LUT[table.concat(iType, '_')] if val ~= nil then iType = { [1]=val } -- example: signature and date end end temp_args.type = table.concat(iType, '/') end if statement.qualifiers.P518 then -- applies to part (P518) used for locattion 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 statement.qualifiers.P2441 then -- literal translation (P2441) local snak = statement.qualifiers.P2441[1] val = snak.datavalue.value temp_args[val.language] = val.text end if statement.qualifiers.P7008 then -- unabbreviated text (P7008) local snak = statement.qualifiers.P7008[1] val = snak.datavalue.value temp_args['full form'] = val.text end if length(position)==1 then temp_args.position = position[1] elseif length(position)==2 then -- two position values can be combined into a single value used by the template table.sort(position) -- order them alphabetically temp_args.position = LUT[table.concat(position, '_')] end end if 	addIconFlag and iInsc==1 then temp_args['1'] = temp_args['1'].. core.editAtWikidata(entity.id, 'P1684', lang) addIconFlag = false end if 	iInsc0 then return "\n*" .. table.concat(AllInsc,"\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,"; ") .. core.editAtWikidata(entity.id, 'P937', lang) end return nil end

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

local maxDepict = 50 -- maximum number of Depict statements to check, to prevent running out of memory for items with 100's of them local X, Y, Done = {{},{}}, {}, {} local ID_LUT = { Q5       = 1, -- human Q21070568 = 1, -- human who may be fictional Q95074   = 1, -- fictional character Q4271324 = 1, -- mythical character Q18563360 = 1, -- Quranic character Q20643955 = 1, -- human biblical figure Q22813672 = 1, -- two biblical humans Q22813674 = 1, -- group of biblical humans Q235113  = 1, -- angel Q178342  = 1, -- archangel Q581450  = 1, -- fallen angel Q178885  = 1, -- deity Q979507  = 1, -- Hindu deity Q22989102 = 1, -- Greek deity Q11688446 = 1, -- Roman deity Q16513881 = 1, -- Norse deity Q22704077 = 2, -- biblical episode Q1406161 = 2, -- artistic theme Q46999986 = 2 -- martyrdom }

local props = {P921='main subject', P180='depicts'} for prop, _ in pairs(props) do		local iCounter = {0, 0} -- two counters local items = getBestProperties(entity, prop) for iDepict, pid in ipairs(items or {}) do			if iDepict<maxDepict then local P31s = getAllItemProperties(pid, 'P31') for _, p31 in ipairs(P31s or {}) do					local dType = ID_LUT[p31] -- type of depicts: 1 or 2 if dType and (not Done[pid]) then -- instance of "human", etc. was found local text = core.getLabel(pid, lang) iCounter[dType] = iCounter[dType] + 1 -- how many of that type if iCounter[dType]==1 then text = text .. core.editAtWikidata(entity.id, prop, lang) end table.insert(X[dType], text) Done[pid] = true break end end end end end for i = 1,2 do		local n = #X[i] if n==0 then X[i] = nil elseif n==1 then X[i] = X[i][1] else X[i] = '* ' .. table.concat(X[i],"\n* ") end end return X end

-- =========================================================================== function p.get_depicted_people(entity, lang) return p.get_depicted(entity, lang)[1] 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 X = {}                -- 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(X, mw.ustring.format( "%s (%s) ", id, core.getLabel(key, lang) ) ) else table.insert(X, id)    -- if no "collection" is mentioned than just return ID		end end -- assemble final output structure if length(X)==1 then    -- single ID case Res.str = X[1] .. core.editAtWikidata(entity.id, 'P217', lang) -- just return the string elseif length(X)>1 then -- if more than one than return bulleted list X[1] = X[1] .. core.editAtWikidata(entity.id, 'P217', lang) Res.str = "* " .. table.concat(X, "\n* ") end return Res end

-- =========================================================================== local function renderInstitution(entity, lang) -- local function to create wikitext for a single institution template or template -- once we have entity check if Institution template exist and call it or assemble one based on Wikidata local frame = mw.getCurrentFrame local inst -- first check for few special cases which will result in template if entity.id == 'Q768717' then  -- render  template return frame:expandTemplate{ title ='Private collection'} .. ' '	end local P31 = getBestProperties(entity, 'P31') -- 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  template return frame:expandTemplate{ title ='Private collection', args={ owner = getLabel(entity, lang)}} .. ' '		elseif p=='Q768717' then                   -- if "Institution" is an instance of "Private collection" than render  template return frame:expandTemplate{ title ='Private collection'} .. ' '		end end -- render Institution template local P1612 = getProperty(entity, 'P1612')                                 -- look up "Commons Institution page" property -- make sure second argument is string, never nil (it returns nil for the empty string, but throws error for nil) local templateName = mw.title.makeTitle( 'Institution', P1612 or '' ) if templateName and templateName.exists 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

-- =========================================================================== local function isPrivateCollection(entity) -- test if collection is a private_collection -- see https://www.wikidata.org/wiki/Wikidata:WikiProject_sum_of_all_paintings/Private_collection local private_collection = 'Q768717' if entity.claims and entity.claims.P195 then for _, statement in ipairs( entity:getBestStatements( 'P195' )) do			if (statement.mainsnak.snaktype == "somevalue") then local quals = statement.qualifiers if quals and not quals.P582 and quals.P3831 then -- object has role (P3831) and no end time (P582) for _, qual in ipairs( quals.P3831 ) do						local role0 = qual.datavalue.value.id   -- specify the role of "collection" if role0 == private_collection then return true end end end end end end return false 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 if isPrivateCollection(entity) then collection.Q768717 = 1 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')    -- 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 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 .. ' \n' .. core.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 lParent = getItemProperty(lId, 'P361')         -- 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(cEntity, 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 local inst = renderInstitution(mw.wikibase.getEntity(cId), lang) table.insert(X, inst ) 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', Q230768='attributed to'} local anonymous = 'Q4233718' if entity.claims and entity.claims[prop] then for _, statement in ipairs( entity:getBestStatements( prop )) do			local option, itemID1, itemID2, role, quals if (statement.mainsnak.snaktype == "value") then itemID1 = statement.mainsnak.datavalue.value.id			else -- if (statement.mainsnak.snaktype == "somevalue") itemID1 = nil end -- look for role of "creator" like: bookbinding, lithography, etc.			quals = statement.qualifiers if quals and quals.P518 then                   -- applies to part (P518) role = quals.P518[1].datavalue.value.id    -- specify the role of "creator" end if quals and quals.P3831 then                  -- object has role (P3831) for _, qual in ipairs( quals.P3831 ) do					local role0 = qual.datavalue.value.id   -- specify the role of "creator" if itemID1 == nil and role0 == anonymous then itemID1 = anonymous                 -- user is anonymous else role = role0 end end end -- look for "options" related to certainty ('presumably', 'possibly', 'probably', etc.) if quals and (quals.P5102 or quals.P1480) then -- sourcing circumstances (P1480) and  nature of statement (P5102) local q = quals.P5102 or quals.P1480		-- values used by P1480 were moved to P5102 option = LUT[q[1].datavalue.value.id]      -- add certainty qualifiers end -- look for qualifiers thet provide new creator ID ('school of', 'after', etc.) for qual, opt in pairs( qualifiers ) do				if quals and quals[qual] then itemID2 = quals[qual][1].datavalue.value.id 					table.insert(IDs, {itemID=itemID2, option=opt, role=role}); break end end -- add new creator, except for the case when they are anonymous and we have secondary ID 			if not ((itemID1==anonymous or itemID1==nil) and itemID2) then table.insert(IDs, {itemID=itemID1, option=option, role=role}); 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 val, _ local itemID = IDs[k].itemID local option = IDs[k].option local role  = IDs[k].role if itemID == nil then -- render author template val = frame:expandTemplate{ title ='Unknown', args={'author'}} table.insert(Creators, val) elseif itemID == anonymous then -- render anonymous label val = core.getLabel(itemID, lang) table.insert(Creators, val) elseif option ~= "delete" then local P1472 = getItemProperty(itemID, 'P1472') -- look up "Commons Creator page" property local templateName = mw.title.makeTitle('Creator',P1472 or '' ) if P1472 and templateName.exists 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 local tag = TagQS.createTag('creator_role', nil, role) val = "'''" .. core.getLabel(role, lang) .. "''': " .. tag .. 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 = string.format(' ', 			table.concat(Creators, '\n'),			core.editAtWikidata(entity.id, prop, lang)) 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 comma    = mw.message.new( "Comma-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 str local addIconFlag = true for _, p in ipairs(prop) do                     -- loop over all IDs found if p.P972 then --[=[		-- commented out to not use label cf. https://commons.wikimedia.org/wiki/Module_talk:Wikidata_art#Catalogues local catalog = core.getLabel('Q2352616', lang); -- get translation of word "catalog" str = catalog .. colon .. "''" .. p.P972 .. "''" .. comma .. p.value --]=]			str = "''" .. p.P972 .. "''" .. comma .. p.value if addIconFlag then str = str .. core.editAtWikidata(entity.id, 'P528', lang) addIconFlag = false end 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 --[=[ -- commented out to not use label cf. https://commons.wikimedia.org/wiki/Module_talk:Wikidata_art#Catalogues if not label then label = getLabel('P973', lang) -- get translation of phrase "described at URL" end str = string.format("%s%s [%s %s]", label, colon, p.value, mw.uri.decode( p.value ) ) --]=]		str = string.format("[%s %s]", p.value, mw.uri.decode( p.value ) ) if p.P1476 then -- display title if available rather than raw URL local linkTitle = mw.ustring.gsub(p.P1476, '%]', '&#93;') -- escape closing square brackets that close links in MediaWiki --[=[			-- commented out to not use label cf. https://commons.wikimedia.org/wiki/Module_talk:Wikidata_art#Catalogues str = string.format("%s%s [%s %s]", label, colon, p.value, linkTitle) --]=]			str = string.format("[%s %s]", p.value, linkTitle) 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 if k==1 then str = str .. core.editAtWikidata(entity.id, 'P973', lang) end table.insert(strTable, str)         -- group IDs by collection end -- Support for direct IDs to museum databases: table of 	--    a) Wikidata properties of the museum catalog ID and	--     b) names of the corresponding template on Wikimedia Commons -- e.g. the Museum of Modern Art online collection has a	-- "Museum of Modern Art work ID" (P2014) on Wikidata -- and Template:Moma_online on Wikimedia Commons. -- we can add everything from https://commons.wikimedia.org/wiki/Category:Museum_database_templates local commons_templates_for_database = { --[=[		["P347"] = "Joconde",        -- Joconde database (French Republic) ["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 ["P350"] = "RKDimages"       -- RDK (Netherlands Institute for Art History) --]=]	} --[=[	for property, template_name in pairs(commons_templates_for_database) do		local database_id = getProperty(entity, property) if database_id then local frame = mw.getCurrentFrame local eIcon = core.editAtWikidata(entity.id, property, lang) str = frame:expandTemplate{ title = template_name, args = { database_id } } str = mw.text.trim(str) .. eIcon table.insert(strTable, str) end end

-- harvest more direct IDs to museum databases from Wikidata and display -- them independently of Commons templates --]=]	id_properties_LUT['P1212'] = nil -- remove retired Atlas ID	for prop, property_urls in pairs(id_properties_LUT) do		if not commons_templates_for_database[prop] then local formatter_url = core.langSwitch(property_urls, lang) if not formatter_url then for _,url in pairs(property_urls) do					-- use any URL if none found with langSwitch formatter_url = url end end local eIcon = core.editAtWikidata(entity.id, prop, lang) for i, database_id in ipairs(getBestProperties(entity, prop) or {}) do local str = (getLabel(prop, lang) .. ': ['					.. string.gsub(formatter_url, '$1', database_id) .. ' '					.. database_id .. ']' .. eIcon) table.insert(strTable, str) end 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 --[=[ -- commented out to not use label cf. https://commons.wikimedia.org/wiki/Module_talk:Wikidata_art#Catalogues 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) -- differentiate between "pages" and "page" if not string.find(cite_arg.pages, '%p') then cite_arg.page = cite_arg.pages cite_arg.pages = nil end 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 } if k==1 then str = str .. core.editAtWikidata(entity.id, 'P1343', lang) end --[=[		-- commented out to not use label cf. https://commons.wikimedia.org/wiki/Module_talk:Wikidata_art#Catalogues table.insert(strTable, label .. colon .. str) --]=]		table.insert(strTable, 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.get_other_versions(entity, pagename, lang) local property = {P10='video', P18='image', P996='scan', P3451='nighttime view', P4896='model3D', P7420='framed_image', P7417='image of backside', P7457="creator's signature", P10093='image with color chart' } 	local files = {} for prop, field in pairs( property ) do		for __, file in ipairs( getAllProperties(entity, prop) or {} ) do			if file ~= pagename then local label = getLabel(prop, lang, '-') local eIcon = core.editAtWikidata(entity.id, prop, lang) table.insert(files, file .. "|" .. label .. eIcon) end end end if #files<=1 then -- #files==1 means gallery with a single image (which is also displayed above) return '' end files = '\n' .. table.concat(files, '\n') local frame = mw.getCurrentFrame return frame:extensionTag{ name = 'gallery', content = files, args = {mode = 'packed-hover'}} 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(entity, lang)[1] or '' elseif field=='depicted_themes' then return p.get_depicted(entity, lang)[2] or '' elseif field=='other_versions' then return p.get_other_versions(entity, , lang) or  end return '' end

return p