Модуль:Artwork

    Матеріал з Релігія в огні

    Документацію для цього модуля можна створити у Модуль:Artwork/документація

    --[[  
      __  __           _       _           _         _                      _    
     |  \/  | ___   __| |_   _| | ___ _   / \   _ __| |___      _____  _ __| | __
     | |\/| |/ _ \ / _` | | | | |/ _ (_) / _ \ | '__| __\ \ /\ / / _ \| '__| |/ /
     | |  | | (_) | (_| | |_| | |  __/_ / ___ \| |  | |_ \ V  V / (_) | |  |   < 
     |_|  |_|\___/ \__,_|\__,_|_|\___(_)_/   \_\_|   \__| \_/\_/ \___/|_|  |_|\_\
                                                                                 
    This module is intended to be the engine behind "Template:Artwork", "Template:Art photo",
    "Template:Photograph" and "Template:Book".
    
    Please do not modify this code without applying the changes first at 
    "Module:Artwork/sandbox" and testing at "Template:Artwork/testcases".
    
    Authors and maintainers:
    * User:Jarekt - original version 
    ]]
    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 getSitelinks     = require("Module:Wikidata label")._sitelinks           -- 
    local getDate          = require("Module:Wikidata date")._date                 -- used for processing of date properties
    local authorityControl = require("Module:Authority control")._authorityControl -- used for formatting of Authority control row
    local ISOdate          = require('Module:ISOdate')                             -- used for simple date formating
    local Size             = require('Module:Size')._size                          -- Lua code behind {{Size}} template
    local TitleFromWD      = require('Module:Title').wikidata_title                -- Lua code behind {{Title}} template
    local Art              = require('Module:Wikidata art')                        --
    local alterName        = require("Module:Name")._name
    local TagQS            = require('Module:TagQS')
    local core             = require("Module:Core")
    local artcore          = require("Module:Artwork/core")
    
    -- Lazy loading functions: load them only if they are needed
    local function Information(args)
    	return require("Module:Information")._information(args)
    end
    
    local function ObjectTypeID(object_type)
    	local LUT = mw.loadData('Module:I18n/objects/data') -- lazy loading (only if needed
    	return LUT[string.lower(object_type)]
    end
    
    local function periodSpan(id, lang)
    	local PeriodSpan = require('Module:Period')._periodSpan
    	return PeriodSpan(id, lang)
    end
    
    -- ==================================================
    -- === Internal functions ===========================
    -- ==================================================
    
    -------------------------------------------------------------------------------
    local function empty2nil(str)
    	if str=='' then
    		return nil
    	else 
    		return str
    	end
    end
    
    -------------------------------------------------------------------------------
    local function nowiki(str) -- remove all the links
    	if not str then
    		return str
    	end
    	str = TagQS.removeTag(str, nil)
    	str = mw.ustring.gsub(str, '%<abbr %</abbr%>', '')
    	str = mw.ustring.gsub(str, "<[^>]*>", "")    -- remove all html tags from str
    	str = mw.ustring.gsub(str, "'''", "")        -- remove bold 
    	str = mw.ustring.gsub(str, "''", "")         -- remove italics
    	str = mw.ustring.gsub(str, "%[%[[Ff]ile:[^%]]+%]%]", "") -- remove file icons
    	str = mw.ustring.gsub(str, "%[%[[^|]*|", "") -- remove piped links, like "[[:en:test|"
    	str = mw.ustring.gsub(str, "%[[hH][tT][tT][pP][sS]?://[^ ]+ ([^%]]*)%]", "%1")   -- remove URL links, like "[https://www.wikidata.org/wiki/Q2706250 xxxx] -> xxxx"
    	str = mw.ustring.gsub(str, "%[%[", "" )      -- remove piped links, like "[["
    	str = mw.ustring.gsub(str, "%]%]", "" )      -- remove piped links, like "]]"
    	return str
    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 quote(str)
    	return '"' .. str .. '"'
    end
    
    -------------------------------------------------------------------------------
    local function if_else(Boolean, TrueStatement, FalseStatement)
    	if Boolean then
    		return TrueStatement
    	else
    		return FalseStatement
    	end
    end
    
    -------------------------------------------------------------------------------
    local function add_QS(qsTable, prop, value, qualifiers)
    	local qsStr =  prop .. '|' .. value
    	if qualifiers then
    		for qual, val in pairs(qualifiers) do
    			qsStr = qsStr .. '|' .. qual .. '|' .. val
    		end
    	end
    	table.insert( qsTable, qsStr)
    end
    
    -------------------------------------------------------------------------------
    local function render_QS_URL(qsTable, item, withIcon)
    	local today = '+' .. os.date('!%F') .. 'T00:00:00Z/11' -- today's date in QS format
    	local url = mw.title.getCurrentTitle():canonicalUrl()  -- URL to current page
    	local ref = '|S143|Q565|S813|' .. today .. '|S4656|"' .. mw.uri.decode(url) .. '"'
    	local icon = 'File:Commons_to_Wikidata_QuickStatements.svg'
    	for k, qs in ipairs(qsTable) do
    		if qs:sub(1,1)=='P' then
    			qsTable[k] = item .. '|' .. qs .. ref
    		else
    			qsTable[k] = item .. '|' .. qs -- for labels, etc.
    		end
    	end
    	local qsURL = table.concat(qsTable, '||')
    	local hoverMsg = 'Add properties to Wikidata item based on this file'
    	if string.upper(item)=='CREATE' then
    		qsURL = 'CREATE||' .. string.gsub(qsURL, item..'|', 'LAST|')
    		icon  = 'File:Plus Wikidata.svg'
    		hoverMsg = 'Create new Wikidata item based on this file'
    	end
    	qsURL = mw.uri.encode(qsURL, "PATH")
    	qsURL = 'https://quickstatements.toolforge.org/#/v1=' .. qsURL    -- create full URL link
    	withIcon = withIcon or true -- define default
    	if withIcon then -- use URL as a link accessed by clicking an icon
    		qsURL = '&nbsp;[[' .. icon .. '|15px|link=' .. qsURL .. '|' .. hoverMsg ..']]' 
    	end
    	return qsURL
    end
    
    -- ===========================================================================
    -- === This function is responsible for adding maintenance categories      ===
    -- === which are not related to Wikidata                                   ===
    -- === INPUTS:                                                             ===
    -- ===  * args0  - data from the local arguments                           ===
    -- ===  * args   - merged data from the local arguments and Wikidata       ===
    -- ===========================================================================
    local function add_maintenance_categories(args0, args)
    	local cats = '' -- categories 
    	mw.getCurrentFrame():expandTemplate{ title = 'Infobox template tag' } -- add the template tag
    
    	-- ====================================================
    	-- === automatic tagging of pages in all namespaces === 
    	-- ====================================================
    	if args.date or args.year then
    	    -- add an empty template which can be used as a tag in PetScan
    		local d    = os.date('!*t')                   -- current date table
    		local current_year  = tonumber(d.year)        -- current year
    		local creation_year = tonumber(ISOdate._ISOyear(args.year or args.date))
    		if creation_year and current_year and (current_year-creation_year)>200 then
    			mw.getCurrentFrame():expandTemplate{ title ='Works created more than 200 years ago' }
    		end
    	end 
    	
    	if args0.namespace==0 and mw.ustring.sub(args0.pagename,1,8) == "Artwork:" then
    		cats = cats .. '\n[[Category:Artwork templates]]'
    		if args.homecat then
    			cats = cats .. '\n[[Category:' .. args.homecat .. ']]'
    		end
    	end
    	
    	-- add categories related to accession number for artworks
    	if args0.id and args0.infobox=='artwork' then
    		local sortkey = nowiki(args0.id) -- strip any links that might be there
    		if #sortkey>30 then
    			sortkey = 'zzz'
    		end
    		cats = string.format('%s\n[[Category:Artworks with known accession number| %s]]', cats, sortkey)
    	end
    
    	-- add categories related to template:book transcluded into template for specific book
    	if args0.infobox=='book' and args0.namespace==10 then
    		cats = cats .. '\n[[Category:Book templates]]'
    		-- add homecat category
    		if args0.homecat~='~' then
    			local page = {}
    			if args0.homecat then
    				cats = string.format('%s\n[[Category:%s| ]]',cats, args0.homecat)
    				page = mw.title.new( args0.homecat, 'category' )
    			end	
    			if not page or not page.exists or not args0.homecat then
    				cats = cats .. '\n[[Category:Book templates without home category]]'
    			end
    		end
    	end
    	return cats
    end
    
    -- ===========================================================================
    -- === This function is responsible for creating QuickStatements           ===
    -- === to pages in creator namespace which are related to wikidata         ===
    -- === INPUTS:                                                             ===
    -- ===  * args0 - local inputs from the Artwork template                   ===
    -- ===  * data  - data pulled from Wikidata                                ===
    -- ===========================================================================
    local function create_QuickStatements(args0, data)
    	-- setup QuickStatements 
    	local qsTable = {}  -- table to store QuickStatements 
    	local item = args0.wikidata
    	if args0.no_qs then
    		return nil -- "no_qs" flag means we do not want QuickStatements based on this file
    	end
    
    	-- 
    	if not data.id and args0.id then
    		local id = nowiki(args0.id) -- strip any links that might be there
    		if args0.institution_id and #id<20 then
    			add_QS(qsTable, 'P217', quote(id), {P195=args0.institution_id})
    		end
    	end
    	
    	-- add QS based on "image" field
    	local lut = { jpg='P18', jpeg='P18', png='P18', pdf='P996', djvu='P996', stl='P4896'} 
    	--			  webm='P10', ogv='P10', mp3='P51', wav='P51' -- ignore other extensions as they are mostly wrong
    	if (args0.image) then -- QS code to help transfer content of "image" field to Wikidata
    		local ext = mw.ustring.lower(mw.ustring.match(args0.image, "^.+%.(.+)$") or '') -- file extension
    		local image = quote(mw.ustring.gsub(args0.image,'_',' '))
    		local prop = lut[ext]
    		if prop and not data[prop] then
    			add_QS(qsTable, prop, image)
    			data[prop] = image -- mark it so we don't add it twice
    		end
    	end
    		
    	-- add QS based on current filename
    	local image = quote(mw.ustring.gsub(args0.pagename,'_',' ')) -- get current filename
    	local multiPageFile = (args0.namespace==6 and args0.num_pages>1 and (args0.mimeType=='application/pdf' or args0.mimeType=='image/vnd.djvu'))
    	if (not data.P996 and multiPageFile) then -- QS code to help transfer image to Wikidata
    		local qual = if_else(args0.image_page, {P4714=tostring(args0.image_page)}, nil)
    		add_QS(qsTable, 'P996', image, qual)
    	end
    	
    	if (args0.namespace==6 and args0.num_pages==1 and args0.infobox=='artwork') then -- QS code to help transfer image to Wikidata
    		local ext = mw.ustring.lower(mw.ustring.match(args0.pagename, "^.+%.(.+)$")) -- file extension
    		local prop = lut[ext]
    		--prop = if_else(prop, prop, 'P18')
    		if prop and not data[prop] then
    			add_QS(qsTable, prop, image)
    		end
    	end
    	
    	-- add "Commons Category" (P373) if template at category page
    	if not data.homecat and args0.namespace==14 then 
    		add_QS(qsTable, 'P373', image)
    	end	
    	
    	-- look for hidden text in various templates so they can be passed to Wikidata if needed using QS
    	-- copy args0['data'] to args0['era'] if it contains an "era QS"
    	-- because they share one parameter/field at template level input,
    	-- but need separate handling here
    	-- TODO: splitting/sorting (at an earlier stage) would be better than simply copying,
    	-- in cases where there is one value for each they won't be processed without splitting
    	
    	if args0['date'] and TagQS.hasTag('era') then
    		args0['era'] = args0['date']
    	end
    	
    	-- different fields from different tables are allowed to create QS codes to allow data transfer to Wikidata
    	-- some fields are often not used correctly in some templates like institution in book template and are not allowed to create QS
    	local fields = {object_type='artwork', era='artwork', medium='artwork', dimensions='artwork', institution='artwork', artist='artwork', reference_wga='artwork', date='all' ,
    		language='book', author='book', translator='book', publisher='book', printer='book', illustrator='book', editor='book', publication_date='book'}
    	for field, infobox in pairs( fields ) do
    		if args0[field] and not data[field] and (args0.infobox==infobox or infobox=='all') then
    			local qs  = TagQS.readTag(args0[field], field)
    			if qs then 
    				for _, v in ipairs( mw.text.split( qs, ';', true ) ) do
    					table.insert( qsTable, v )
    				end
    			end
    		end
    	end
    	if args0.dimensions and not data.dimensions and (args0.infobox=='artwork') then
    		for _, v in ipairs(TagQS.readTags(args0.dimensions, 'dimensions')) do
    			table.insert( qsTable, v )
    		end
    	end
    	
    	-- Special case of QS codes for multilingual labels extracted from {{title}} and {{description}} templates, etc.
    	if args0.title then
    		local max_title_len = 100
    		-- strip titles from title field with {{title}} template: {{title|lang=...|1=...}}
    		local title = TagQS.readTag(args0.title, 'title') or ''	
    		local label, lang = mw.ustring.match(title, 'P1476%|((%w+):.+)')
    		if lang and label then
    			label = nowiki(label)
    			if not(data.title_ and data.title_[lang]) and #label<max_title_len then
    				add_QS(qsTable, 'P1476', label)
    			end
    		end
    		lang, label = mw.ustring.match(title, 'P1476%|(%w+):(.+)')
    		if lang and label then
    			label = nowiki(label)
    			if not(data.labels and data.labels[lang]) and #label<max_title_len then
    				add_QS(qsTable, 'L'..lang, label)
    			end
    		end
    
    		-- strip titles from title field with {{title}} templates, with fields like |en=, |es=, etc.
    		for _, title in ipairs(TagQS.readTags(args0.title, 'label')) do
    		    lang, label = mw.ustring.match(title, 'L(%w+)[%|,](.+)')
    			if lang and label then
    				label = nowiki(label)
    				if not(data.labels and data.labels[lang]) and #label<max_title_len then
    					add_QS(qsTable, 'L'..lang, label)
    				end
    			end
    		end	
    		-- strip titles from title field with {{description}} templates, like {{en}}, {{es}}, etc.
    		-- <span class="language pl" title="Polish"><b>Polski&#58; </b></span> abc</div>
    		local pat = '%<span class="language (%w+)" .-%</span%> *([^%<]+)%</div%>'
    		for lang, label in mw.ustring.gmatch(args0.title, pat) do
    			label = nowiki(label)
    			if not(data.labels and data.labels[lang]) and #label<max_title_len then
    				add_QS(qsTable, 'L'..lang, quote(label))
    			end
    		end	
    	end
    	
    	-- add "instance of" statement
    	local type_id = data.object_type_id
    	if not data.object_type_id then
    		if args0.infobox=='book' then
    			add_QS(qsTable, 'P31', 'Q3331189') -- version, edition, or translation
    		elseif args0.object_type then
    			type_id = ObjectTypeID(args0.object_type)
    			if type_id then
    				add_QS(qsTable, 'P31', type_id)   -- painting etc.
    				data.object_type_id = { type_id } -- object_type_id is an array
    			end
    		end
    	end
    	
    	-- add descriptions
    	local artist_id = data.artist_id or args0.artist_id
    	local typeList = {
    		Q3305213={de="Gemälde von %s", en="painting by %s", es="cuadro de %s", fr="tableau de %s", it="pittura di %s", nl="schilderij van %s", sl="slika avtorja %s"}
    	}
    	if type_id and artist_id and typeList[type_id] then
    		for lang, phrase in pairs(typeList[type_id]) do 
    			if not data.descriptions or not data.descriptions[lang] then 
    				local artist = mw.wikibase.getLabelByLang(artist_id, lang)
    				if artist then 
    					add_QS(qsTable, 'D'..lang, quote(string.format(phrase, artist)))
    				end
    			end
    		end
    	end
    	
    	-- ==================================================
    	-- === Create QuickStatement codes ================== 
    	-- ==================================================
    	if #qsTable==0 then
    		return nil
    	end	
    	local QS = render_QS_URL(qsTable, args0.wikidata)
    	return QS
    end
    
    -- ===========================================================================
    -- === Function for creating search icons                                  ===
    -- === INPUTS:                                                             ===
    -- ===  * args0 - local inputs from the Artwork template                   ===
    -- ===========================================================================
    local function add_search_link(args0)
    	local sTable, tTable = {}, {}
    	local creator_id, s, t, text, QS, title
    	
    	-- get English title
    	if args0.title then
    		local title = TagQS.readTag(args0.title, 'label') or ''
    
    		-- strip titles from title field with {{title}} templates, with fields like {{en}}, {{es}}, etc.
    		s = 'Len%|(.+)'
    		for label in mw.ustring.gmatch(args0.title, s) do
    			title = nowiki(label)
    		end	
    	end
    	if args0.title and not title then
    		-- strip titles from title field with {{description}} templates, like {{en}}, {{es}}, etc.
    		s = '%<span class="language en" [^%<]+%</span%> *([^%<]+)%</div%>'
    		for label in mw.ustring.gmatch(args0.title, s) do
    			title = nowiki(label)
    		end	
    	end
    	text = title or nowiki(args0.title) or ''
    	
    	-- get author info 
    	if args0.infobox=='artwork' then
    		creator_id = args0.artist_id or args0.author_id
    	elseif args0.infobox=='photograph'  then
    		creator_id = args0.photographer_id or args0.author_id
    	elseif args0.infobox=='book'  then
    		creator_id = args0.author_id
    	end
    	if creator_id then
    		local prop = if_else(args0.infobox=='book', 'P50', 'P170')
    		sTable[prop] = creator_id
    	else 
    		text = text  .. ' ' .. nowiki(args0.artist or args0.author or '')
    	end
    
    	-- set other properties
    	if args0.object_type=='painting' and args0.infobox=='artwork' then
    		sTable.P31 = 'Q3305213' -- painting
    	elseif args0.infobox=='book' then
    		sTable.P31 = 'Q3331189' -- version, edition, or translation
    	end
    	if args0.institution_id then
    		sTable.P195 = args0.institution_id
    	end
    	if args0.id then
    		text = text .. ' ' .. nowiki(args0.id) -- add accession_id as text not as P217 statement
    	end
    
    	-- If no usable info than abort 
    	local count = 0
    	for _ in pairs(sTable) do count = count + 1 end
    	if count==0 then
    		return nil
    	end 
    
    	-- create URL and clickable icon with either Cirrus  search
    	-- see https://www.mediawiki.org/wiki/Help:Extension:WikibaseCirrusSearch
    	table.insert(tTable, text)
    	for prop, field in pairs( sTable) do
    		table.insert(tTable, 'haswbstatement:' .. prop .. '=' .. field)
    	end
    	s = mw.uri.encode(table.concat(tTable, ' '), "QUERY")
    	s = {'search='..s, 'title=Special:Search', 'profile=advanced', 'fulltext=1', 'ns0=1'}
    	s = 'https://www.wikidata.org/w/index.php?' .. table.concat(s,'&')   -- create full URL link
    	s = '[[File:Wikidata CheckUser.svg|15px|link=' .. s .. '|Wikidata search (Cirrus search)]]'
    	
    	-- create URL and clickable icon with  SPARQL search
    	tTable = {}	
    	table.insert(tTable, 'SELECT ?item ?itemLabel ?image')
    	table.insert(tTable, 'WHERE {')
    	for prop, field in pairs( sTable) do
    		table.insert(tTable, ' ?item wdt:' .. prop .. ' wd:' .. field .. '.')
    	end
    	table.insert(tTable, ' OPTIONAL { ?item wdt:P18 ?image } .')
    	table.insert(tTable, ' SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }')
    	table.insert(tTable, '}')
    	t = mw.uri.encode(table.concat(tTable, '\n'), "PATH")
    	t = 'https://query.wikidata.org/#' .. t
    	t = '[[File:Wikidata Query Service Favicon.svg|15px|link=' .. t .. '|Wikidata query (SPARQL)]]' 
    
    	-- go to [[Data:Completely indexed painting collections.tab]] and look up if 
    	-- args0.institution_id is one of the items in column 1 
    	local collCmpl, cat = false, ''
    	if args0.object_type=='painting' and args0.infobox=='artwork' and args0.institution_id then
    		local tab = mw.ext.data.get('Completely indexed painting collections.tab', 'en')
    		for iRow, row in pairs(tab.data) do
    			if row[1]==args0.institution_id then
    				collCmpl = true -- no need for adding painting items to this collection
    			end
    		end
    	end
    	if collCmpl then
    		cat = '\n[[Category:Paintings from completely indexed collections]]'
    	end
    
    	-- go to [[Data:Completely indexed painters.tab]] and look up if 
    	-- args0.artist_id is one of the items in column 1 
    	local authorCmpl = false
    	if args0.object_type=='painting' and args0.infobox=='artwork' and (args0.artist_id or args0.author_id) then
    		local tab = mw.ext.data.get('Completely indexed painters.tab', 'en')
    		for iRow, row in pairs(tab.data) do
    			if row[1]==args0.artist_id or row[1]==args0.author_id then
    				authorCmpl = true -- no need for adding painting items by this painter
    			end
    		end
    	end
    	if authorCmpl then
    		cat = '\n[[Category:Paintings by completely indexed painters]]'
    	end	
    	if not collCmpl and not authorCmpl then
    		args0.wikidata = 'CREATE'
    		QS = create_QuickStatements(args0, {})
    	end
    	
    	local searchStr = '&nbsp;(' .. table.concat({s, t, QS}, ' ') ..')'
    	return searchStr, cat
    end
    
    -- ===========================================================================
    local function chect_instance_of(instance_of)
    	-- check object_type_id against a list of incorrect values for P31 property of associated item
    	-- Black and white list id is of wrong type if bwLUT returns "1", bwLUT = "2" means good type
    	-- bad  {Q5='human', Q11266439='template ', Q4167410='disambiguation', Q4167836='category', Q532='village', Q482994='album', Q16521='taxon' }
    	-- groups {Q15727816='painting series',  sculpture series (Q19479037),  artwork series (Q15709879),  group of sculptures (Q27031439),  
    	--         group of paintings (Q18573970) ,  polyptych (Q1278452),  diptych (Q475476),  triptych (Q79218)  }
    	-- good {Q199414='bog body', Q7881='skeleton'} -- one of those overwrites "bad" flag so 'human' & 'bog body' is OK (those are museum objects)
    	local groupItem = false
    	if instance_of then
    		local bad = false
    		local bwLUT = {Q5=1, Q11266439=1, Q4167410=1, Q4167836=1, Q532=1, Q482994=1, Q16521=1, Q15727816=2, Q19479037=2, 
    		               Q15709879=2, Q27031439=2, Q18573970=2, Q1278452=2, Q475476=2, Q79218 = 2, Q199414=3, Q7881=3}
    		for _, typeId in ipairs( instance_of ) do
    			local v = bwLUT[typeId]
    			if v==1 then
    				bad = true
    			elseif v==2 then
    				groupItem = true
    				return 'group'			
    			elseif v==3 then
    				bad = false
    				return 'good'
    			end 
    		end
    		return if_else(bad, 'bad', 'good')
    	end
    	return 'unknown'
    end
    
    -- ===========================================================================
    -- === This function is responsible for adding maintenance categories      ===
    -- === to pages in creator namespace which are related to Wikidata         ===
    -- === INPUTS:                                                             ===
    -- ===  * args0 - local inputs from the Artwork template                   ===
    -- ===  * args1 - merge of local and Wikidata metadata                     ===
    -- ===  * data  - data pulled from Wikidata                                ===
    -- ===========================================================================
    local function add_wikidata_maintenance_categories(args0, args1, data)
    	local cats = ''  -- categories 
    	local comp = {}  -- outcome of argument vs. Wikidata comparison
    	local OK = ((args0.infobox=='artwork' or args0.infobox=='photograph') and  (args0.namespace==6 or args0.namespace==14)) -- artworks and photographs can be in file or category namespace
    		or (args0.infobox=='book' and (args0.namespace==10 or args0.namespace==14) ) -- books can be in template or category namespace
    		or (args0.infobox=='book' and args0.namespace==6 and (args0.mimeType=='application/pdf' or args0.mimeType=='image/vnd.djvu')) -- books can also be in file namespace if it is PDF or DjVu
    
    	if ( not OK or (args0.wikidata_cat==false)) then -- continue only if the namespace is a Category or file
    		return cats, args1
    	end
    	local LUT = {artwork='Artworks', photograph='Photographs', book='Books'}
    
    	-- skip the rest if no item ID
    	if not args0.wikidata then
    		local cat2, TypeLUT, oType 
    		TypeLUT = {['grave']='Graves', ['tomb']='Graves', ['funeral chapel']='Graves', 
    			['funeral niche']='Graves', ['painting']='Paintings', ['aircraft']='SKIP' }
    		oType   = TypeLUT[string.lower(args0.object_type or '')] or LUT[args0.infobox]
    		if oType~='SKIP' then
    			cats = string.format('%s\n[[Category:%s without Wikidata item]]', cats, oType)
    		end
    		args1.QS, cat2 = add_search_link(args0)
    		return cats .. (cat2 or ''), args1
    	end
    	
    	--=======================================================================================================
    	--=== Categories and files with {{Artwork}} template linked to Wikidata item below
    	--=======================================================================================================
    	cats = string.format('%s\n[[Category:%s with Wikidata item|%s]]', cats, LUT[args0.infobox], args0.wikidata)
    
    	-- check object_type_id against a list of incorrect values for P31 property of associated item
    	local groupItem = false
    	if data.object_type_id then
    		local status = chect_instance_of(data.object_type_id )
    		groupItem = (status == 'group')
    		if status == 'bad' or status == 'group' then
    			cats = string.format('%s\n[[Category:%s with %s Wikidata item|%s]]', cats, LUT[args0.infobox], status, args0.wikidata)
    		end
    	end
    	
    	-- local fields which are missing on Wikidata
    	local fields = {'date', 'publication_date', 'medium', 'dimensions', 'image', 'institution', 'author', 'artist'}
    	for _, field in ipairs( fields ) do
    		if not data[field] and args0[field] and not string.match(args0[field], '%<span style="display: none;"%>Unknown '..field..'<%/span%>') then
    			comp[field] = 'missing'
    		end
    	end
    	if not data.id and args0.id then
    		comp['accession number'] = 'missing'
    	end
    
    	
    	if comp.artist=='missing' and string.match(args0.artist, '%<span style="display: none;"%>Unknown a%w+%<%/span%>') then 
    		comp.artist = nil -- ignore {{Unknown|artist}}
    	end
    
    	--  mark local fields redundant to Wikidata
    	local fields = {['date']='date', medium='medium', dimensions='dimensions', institution_id='institution', author_id='author', artist_id='artist'}
    	for field1, field2 in ipairs( fields ) do
    		if data[field1] and args0[field1] and data[field1]==args0[field1] then
    			comp[field2] = 'redundant'
    		end
    	end
    	
    	-- Redundant author and artist
    	if (data.author_id==args0.artist_id and data.author_id) then
    		comp.artist = 'redundant'
    	end	
    	if (data.artist_id==args0.author_id and data.artist_id) then
    		comp.author = 'redundant'
    	end
    	
    	-- handle case when creator template is a red-link but Wikidata has creator item ID
    	if (string.match(args0.artist or '', "%[%[:Creator:") and data.artist) then
    		args1.artist = data.artist
    		comp.artist  = 'redundant'
    	end
    	if (string.match(args0.author or '', "%[%[:Creator:") and data.author) then
    		args1.author = data.author
    		comp.author  = 'redundant'
    	end
    	
    	-- process "image" field
    	if (data.image and args0.image) then 
    		comp.image = 'redundant'
    	end
    	local simpleFile = (args0.namespace==6 and args0.num_pages==1 and args0.infobox=='artwork')
    	if not data.image and (args0.image or simpleFile) then
    		comp.image = 'missing'	
    	end	
    		
    	-- add categories related to accession number for artworks
    	if not args0.id and data.id and args0.infobox=='artwork' then
    		cats = string.format('%s\n[[Category:Artworks with accession number from Wikidata| %s]]', cats, args0.wikidata)
    		cats = string.format('%s\n[[Category:Artworks with known accession number| %s]]', cats, data.id_id or 'zzz')
    	end
    	
    	if not data.genre_id and args0.infobox=='artwork' and args0.namespace==6 then
    		cats = string.format('%s\n[[Category:Artworks with Wikidata item missing genre| %s]]', cats, args0.wikidata)
    	end
    
    	-- ==================================================
    	-- === Create categories based on comp structure ==== 
    	-- ==================================================
    	for field, outcome in pairs( comp ) do
    		cats = string.format('%s\n[[Category:%s with Wikidata item %s %s|%s]]', cats, LUT[args0.infobox], outcome, field, args0.wikidata)
    	end
    	
    	-- Add QuickStatements
    	if not groupItem and args0.wikidata then
    		args1.QS = create_QuickStatements(args0, data)
    	end
    	if args1.QS then
    		cats = string.format('%s\n[[Category:%s with Wikidata item: quick statements]]', cats, LUT[args0.infobox])
    	end
    	
    	return cats, args1
    end
    
    -- ===========================================================================
    -- === Harvest Structured data on Commons properties                       ===
    -- === INPUTS:                                                             ===
    -- ===  * mid - SDC ID                                                     ===
    -- ===  * lang  - language id of the desired language                      ===
    -- ===  * namespace - namespace number of the page calling the module      ===
    -- ===========================================================================
    local function harvest_SDC(args0, lang)
    
    	local bookFlag = (args0.infobox=='book' and (args0.mimeType=='application/pdf' or args0.mimeType=='image/vnd.djvu'))
    	local sdc = {} -- structure similar to "args" but filled with SDC data
    	local cats = ''
    	
    	-- Harvest the different artwork types
    	local tab = mw.ext.data.get('Artwork types.tab', 'en')
    	sdc.artwork_types = {}
    	for iRow, row in pairs(tab.data) do
    		sdc.artwork_types[row[1]] = row[3]
    	end
    	
    	local entity = mw.wikibase.getEntity()
    	if not entity then
    		if args0.wikidata and bookFlag then
    			cats = cats .. '[[Category:Books with SDC link missing‎]]\n'
    		elseif args0.wikidata and args0.infobox~='book' then
    			cats = cats .. '[[Category:Artworks with SDC link missing‎]]\n'
    		end
    		return sdc, cats
    	end
    	
    	local property = {P6243='digital_representation_of', P4714='image_page', P921='main_subject', P180='depicts' }
    	for prop, field in pairs( property ) do
    		sdc[field] = getProperty(entity, prop)
    	end
    
    	-- get Wikidata item ID from digital representation of (P6243) 
    	if sdc.digital_representation_of then
    		local wEntity = mw.wikibase.getEntity(sdc.digital_representation_of)
    		if not wEntity then
    			 cats = cats .. '[[Category:Artworks with structured data with deleted digital representation of property‎]]'
    		elseif wEntity.id~=sdc.digital_representation_of then
    			 cats = cats .. '[[Category:Artworks with structured data with redirected digital representation of property‎]]'
    		end
    		sdc.wikidata = sdc.digital_representation_of
    		
    		-- If digital representation of is set, main subject should be set too to the same 
    		if sdc.main_subject then
    			if sdc.digital_representation_of~=sdc.main_subject then
    				cats = cats .. '[[Category:Artworks with digital representation of different main subject‎]]'
    			end
    		else 
    			cats = cats .. '[[Category:Artworks with digital representation of missing same main subject‎]]'
    		end
    
    		-- If digital representation of is set, depicts should be set too to the same 
    		if sdc.depicts then
    			if sdc.digital_representation_of~=sdc.depicts then
    				cats = cats .. '[[Category:Artworks with digital representation of different depicts‎]]'
    			end
    		else 
    			cats = cats .. '[[Category:Artworks with digital representation of missing same depicts‎]]'
    		end
    	end
    	
    	-- get Wikidata item ID from main subject (P921). If multiple, this will get the first preferred one
    	if not sdc.wikidata and sdc.main_subject then
    		local msEntity = mw.wikibase.getEntity(sdc.main_subject)
    		if not msEntity then
    			 cats = cats .. '[[Category:Artworks with structured data with deleted main subject property‎]]'
    		elseif msEntity.id~=sdc.main_subject then
    			 cats = cats .. '[[Category:Artworks with structured data with redirected main subject property‎]]'
    		end
    		
    		-- Make sure it's an actual thing (instance) instead of something general (subclass)
    		local msProperty = {P31='instance_of', P279='subclass_of' }
    		local msProperties = {} 
    		for prop, field in pairs( msProperty ) do
    			msProperties[field] = getProperty(msEntity, prop)
    		end
    		if msProperties.instance_of and not msProperties.subclass_of then
    			local instances_of = getBestProperties(msEntity, 'P31')
    			local status = chect_instance_of(instances_of )
    			if status ~= 'bad' then 
    				sdc.wikidata = sdc.main_subject
    				sdc.main_subject_accepted = sdc.main_subject
    
    				-- If main subject is set, depicts should be set too to the same 
    				if sdc.main_subject_accepted and sdc.depicts then
    					if sdc.main_subject~=sdc.depicts then
    						-- Here we'll probably see some false positives
    						cats = cats .. '[[Category:Artworks with main subject different depicts‎]]'
    					end
    				else 
    					cats = cats .. '[[Category:Artworks with main subject missing same depicts‎]]'
    				end
    			end
    		end
    	end
    	
    	if not sdc.wikidata and args0.wikidata then
    		if bookFlag then
    			cats = cats .. '[[Category:Books with SDC link missing‎]]\n'
    		elseif args0.infobox~='book' then
    			cats = cats .. '[[Category:Artworks with SDC link missing‎]]\n'
    		end
    	elseif sdc.wikidata and args0.wikidata and sdc.wikidata~=args0.wikidata then
    		cats = cats .. '[[Category:Artworks with mismatching digital representation of‎]]\n'
    	end
    	
    	-- get source from P7482
    	if entity.statements and entity.statements.P7482 then
    		local statement = entity.statements.P7482[1]
    		if statement.mainsnak.datavalue.value.id=='Q74228490' and statement.qualifiers and statement.qualifiers.P973 then
    			local url = statement.qualifiers.P973[1].datavalue.value
    			if statement.qualifiers.P137 then
    				local id = statement.qualifiers.P137[1].datavalue.value.id
    				local label = getLabel(id, lang, '-')
    				sdc.source_  = '[' .. url ..' ' .. label ..']' .. core.editAtSDC(args0.pagename, 'P7482', lang)
    			else
    				sdc.source_  = url .. core.editAtSDC(args0.pagename, 'P7482', lang)
    			end
    		end
    	end
    	
    	return sdc, cats
    end
    
    -- ===========================================================================
    -- === Harvest Wikidata properties matching creator template fields        ===
    -- === INPUTS:                                                             ===
    -- ===  * itemID1 - item id or a q-code from the template                  ===
    -- ===  * itemID2 - item id or a q-code from SDC                           ===
    -- ===  * lang  - language id of the desired language                      ===
    -- ===  * namespace - namespace number of the page calling the module      ===
    -- ===========================================================================
    local function harvest_wikidata(itemID1, itemID2, lang, namespace, infobox, pagename)
    	local data = {} -- structure similar to "args" but filled with Wikidata data
    	local cats = ''
    	local frame = mw.getCurrentFrame()
    	local entity = nil
    	local itemID = itemID1 or itemID2
    	if mw.wikibase and itemID then
    		local LUT = {artwork='Artworks', photograph='Photographs', book='Books'}
    		entity = mw.wikibase.getEntity(itemID)
    		if not entity then
    			 cats = string.format('[[Category:%s with bad Wikidata link]]', LUT[infobox])
    		elseif entity.id~=itemID and itemID1 then
    			 cats = string.format('[[Category:%s with redirected Wikidata link]]', LUT[infobox])
    		end
    	end
    	if not entity then
    		return data, cats
    	end
    
    	-- inception date: translated date and year number
    	local d = getDate(entity, 'P571' , lang) -- inception date
    	if d.str then
    		data.date_, data.year = d.iso, d.year
    		data.date = d.str .. core.editAtWikidata(entity.id, 'P571', lang)
    	end
    
    	-- publication date: translated date and year number
    	local d = getDate(entity, 'P577' , lang) -- publication date
    	if d.str then
    		data.publication_date_, data.publication_year = d.iso, d.year
    		data.publication_date = d.str .. core.editAtWikidata(entity.id, 'P577', lang)
    	end
    	
    	-- harvest string properties
    	local Debug ={}
    	local property = {P10='video', P18='image', P996='scan', P4896='model3D', P373='homecat', P51='audio',
    		P2093='authorStr', P393='edition', P4714='image_page', P1957='wikisource_index',  P7420='framed_image' }
    	for prop, field in pairs( property ) do
    		data[field] = getProperty(entity, prop)
    		data[prop]  = data[field] -- keep tract which properties were present at Wikidata
    	end
    	data.image = data.image or data.scan or data.video or data.model3D or data.framed_image
    	data.image_page = tonumber(data.image_page)
    	if data.edition then
    		data.edition = data.edition .. core.editAtWikidata(entity.id, 'P393', lang)
    	end
    	
    	-- harvest Q-code properties which are than converted from Q-number to labels (pick one)
    	local property = { P189='place_of_discovery', P2079='technique',  P872='printer', 
    	                   P136='genre', P921='subject', P179='series_title', P361='part_of',  
    					   P629='edition_of', P1071='place_of_creation', P495='country_of_origin'}
    	for prop, field in pairs( property ) do
    		local id = getProperty(entity, prop)
    		if id then 
    			data[field] = getLabel(id, lang, "wikipedia") .. core.editAtWikidata(entity.id, prop, lang)
    		end
    	end
    	data.place_of_creation = data.place_of_creation or data.country_of_origin
    	data.genre_id = getProperty(entity, 'P136')
    	
    	-- harvest Q-code properties which are than converted from Q-number to labels (pick all)
    	local property = { P31='object_type', P407='language', P123='publisher', P291='place_of_publication'}
    	for prop, field in pairs( property ) do
    		local ids = getBestProperties(entity, prop)
    		if ids then 
    			local T = {}
    			for _, id in ipairs( ids ) do
    				table.insert(T, getLabel(id, lang))
    			end
    			data[field] = table.concat(T, " / ") .. core.editAtWikidata(entity.id, prop, lang)
    			data[field ..'_id'] = ids
    		end
    	end
    
    	-- get era
    	data.era_id = getBestProperties(entity, 'P2348')
    	if data.era_id then
    		local T = {}
    		for _, id in ipairs( data.era_id ) do
    			local eraText  = getLabel(id, lang)
    			local spanText = periodSpan(id, lang)
    			if spanText then
    				eraText = eraText .. ' ' .. spanText
    			end
    			table.insert(T, eraText)
    		end
    		data.era = table.concat(T, " / ") .. core.editAtWikidata(entity.id, 'P2348', lang)
    	end
    	if data.era and data.date then
    		data.date = data.date .. "<br/>" .. data.era
    	elseif data.era and not data.date then
    		data.date = data.era
    	end
    	
    	-- get author and/or author creator template
    	local property = { P170='creator', P50='author', P84='architect', P287='designer', P98='editor', 
    		P655='translator', P110='illustrator', P2679='author_of_foreword', P2680='author_of_afterword' }   
    	for prop, field in pairs( property ) do
    		local d = Art.get_creator(entity, prop, lang)
    		data[field] = d.str 
    		data[field.."_id"] = d.id
    	end
    	data.author = data.author or data.authorStr      -- P2093='author name string'
    
    	-- get title (from 3 properties and label)
    	--local property = { P1476 = 'title', P1448='official_name', P1705='native_label', P1680='subtitle'}
    	local property = { P1476 = 'title', P1680='subtitle'}
    	for prop, field in pairs( property ) do
    		local title = {}
    		for _, statement in pairs( entity:getBestStatements(field)) do 
    			if (statement.mainsnak.snaktype == "value") then 
    				local val = statement.mainsnak.datavalue.value
    				title[val.language] = val.text -- look for multiple values each with a language code
    			end
    		end
    		if #title>0 then
    			data[field] = core.langSwitch(title, lang)
    		end
    		if field=='title' then
    			data.title_ = title
    		end
    	end
    	--data.title = data.title or data.official_name or data.native_label
    	data.title = TitleFromWD(entity, lang)
    	data.label = getLabel(entity, lang)
    	
    	-- get labels in all the languages
    	data.labels = {}
    	if entity.labels then
    		for lang, val in pairs(entity.labels) do -- loop over all labels
    			data.labels[lang] = val.value;
    			data.label = data.label or data.labels[lang] -- no label in preferred language so grab any label
    		end
    	end
    	-- get labels in all the languages
    	data.descriptions = {}
    	if entity.descriptions then
    		for lang, val in pairs(entity.descriptions) do -- loop over all descriptions
    			data.descriptions[lang] = 1; -- just record if it is present or not
    		end
    	end
    	
    	-- get authority control (rarely used for artworks)
    	local AC_cats
    	data.authority, AC_cats = authorityControl(entity, {wikidata = itemID}, lang, 5) 
    	local _,nIdentifiers = string.gsub(data.authority, "*", "")
    	if nIdentifiers<=1 then
    		data.authority, AC_cats = nil, ''
    	end
    	if not (namespace == 2 or namespace == 828 or math.fmod(namespace,2)==1) then
    		cats = cats .. AC_cats -- lets not add authorityControl categories to user pages, modules or talk pages and concentrate on templates and categories instead
    	end
    	
    	-- get object location
    	if getProperty(entity, 'P625') or getProperty(entity, 'P9149') then
    		local coorFun = require('Module:Coordinates')._LocationTemplateCore
    		local coori18n = require('Module:i18n/coordinates')
    		data.coordinates = coorFun({wikidata=entity, lang=lang, globe='earth', mode='object', bare=true})
    	end
    	
    	-- prepare fallback list of languages
    	local langList = mw.language.getFallbacksFor(lang)
    	table.insert(langList, 1, lang)
    	
    	-- get wikisource or wikiquote
    	local projects = {s='wikisource', q='wikiquote'}
    	for code, project in pairs(projects) do
    		local sitelinks = getSitelinks(entity, project)
    		if sitelinks then
    			local lng, _ = next(sitelinks)    -- get language of the first sitelink
    			table.insert(langList, lng) -- and add it to the list	so there is at least one lang with sitelink on the list
    			for _, language in ipairs(langList) do 
    				local sitelink = sitelinks[language]
    				if sitelink then 
    					data[project] = string.format('%s:%s:%s', code, language, sitelink)
    					break 
    				end
    			end
    		end
    	end
    	-- if no wikisource sitelink than use P1957 'wikisource_index' property
    	-- wikisource_index is in full url format (like https://es.wikisource.org/wiki/%C3%8Dndice:Sonetos_-_Leopoldo_Diaz.pdf) 
    	-- instead of interlink format (like s:es:%C3%8Dndice:Sonetos_-_Leopoldo_Diaz.pdf) 
    	data.wikisource = data.wikisource or data.wikisource_index 
    
    	-- properties with functions
    	data.object_history     = Art.get_object_history(entity, lang)     -- object history
    	data.exhibition_history = Art.get_exhibition_history(entity, lang) -- exhibition.history
    	data.inscriptions       = Art.get_inscription(entity, lang)
    	data.medium             = Art.get_medium(entity, lang)
    	data.medium             = empty2nil(data.medium) or data.technique; 
    	data.references         = Art.get_references(entity, lang)
    	data.other_versions     = Art.get_other_versions(entity, pagename)
    	data.reference_wga      = data.references and mw.ustring.isutf8(data.references) and mw.ustring.find( data.references, 'www%.wga%.hu' ) -- is there a link to wga.hu?
    	local X                 = Art.get_depicted(entity, lang)
    	data.depicted_people    = X[1]
    	data.description        = X[2] -- depicted artistic or religious themes
    	X                       = Art.get_accession_number(entity, lang)
    	data.id                 = X.str -- wikitext version of the accession number 
    	data.id_id              = X.id  -- one of accession numbers, which will be used as a sortkey
    	X                       = Art.get_institution(entity, lang)	
    	data.institution        = X.institution
    	data.institution_id     = X.id
    	data.department         = X.location
    	data.dimensions         = Size({entity=entity}, nil, nil, lang)
    	data.dimensions         = empty2nil(data.dimensions); 
    
    	return data, cats
    end
    
    -- ===========================================================================
    -- === Adjust parameters related to books   ===
    -- === and resolve potential aliases                                       ===
    -- === INPUTS:                                                             ===
    -- ===  * frame - contains imput parameters passed from the template       ===
    -- === OUTPUTS:                                                            ===
    -- ===  * args - cleaned up inputs                                         ===
    -- ===========================================================================
    local function header_customization(args0, data, args1)
    
    	-- get author Wikidata ID based on Wikidata
    	local creator_label, creator_id, creator_str, title_label, creator_tag
    	if args0.wikidata then
    		creator_id  = data.creator_id or data.author_id or data.architect_id or data.designer_id or data.editor_id or data.translator_id or data.illustrator_id  
    		if creator_id then
    			creator_label = getLabel(creator_id, args0.lang)
    		end
    		title_label = data.label -- create name based on Wikidata label
    	end
    
    	-- get author Wikidata ID based on commons
    	if not creator_label then
    		if args0.infobox=='artwork' then
    			creator_id  = args0.artist_id or args0.author_id 
    			creator_str = args0.artist or args0.author
    		elseif args0.infobox=='photograph' then
    			creator_id  = args0.photographer_id or args0.author_id 
    			creator_str = args0.photographer or args0.author
    		elseif args0.infobox=='book' then
    			creator_id  = args0.author_id 
    			creator_str = args0.author 
    		end
    		creator_tag = TagQS.readTag(creator_str or '', 'creator')
    		if creator_tag then
    			-- if author was "{{Creator:Meister Theoderich von Prag|circle of}}" than creator_id will be "P170|Q4233718|P1776|Q446631 "
    			local res = {creator_tag:match("^P170%|Q4233718%|(P%d+)%|(Q%d+)$")}
    			if res and res[1] and res[2]then 
    				creator_label = getLabel(res[2], args0.lang)
    				local LUT = {P1773='attributed to', P1774='workshop of', P1775='follower of', P1776='circle of', 
    					P1777='manner of', P1779='possibly', P1780='school of', P1877='after'}			
    				creator_label = alterName(LUT[res[1]], creator_label, args0.lang) -- call [[module:Name]] with the task
    				if creator_label  == "name not supported" then 
    					creator_label = nil
    				end		
    			end	
    		elseif creator_id and creator_id:match("^Q%d+$") then
    			creator_label = getLabel(creator_id, args0.lang)
    		end
    	end
    	
    	-- scrape labels from {{title}} template
    	if not title_label and args0.title then
    		local labels = {}
    		for _, title in ipairs(TagQS.readTags(args0.title, 'label')) do
    			local lang, label = mw.ustring.match(title, 'L(%w+)%|"([^"]+)"') 
    			if lang and label then
    				labels[lang]=label
    			end
    		end
    		title_label = core.langSwitch(labels,args0.lang)
    	end
    	
    	-- get title based on commons
    	if not title_label and args0.title then
    		title_label = nowiki(args0.title) 
    	end
    	
    	-- if title too long than truncate it
    	if title_label and #title_label>250 then 
    		title_label = nil
    	end
    	if creator_label and #creator_label>150 then 
    		creator_label = nil
    	end
    	
    	-- combing author and title labels
    	if creator_label and title_label then 
    		local colon = mw.message.new( "Colon-separator" ):inLanguage(args0.lang):plain()
    		args1.name = creator_label .. colon .. title_label
    	else
    		args1.name = title_label
    	end 
    
    	-- if we have a {{book}} template in file namespace and file of in PDF or DjVu than use it as image
    	if args0.infobox=='book' and  args0.namespace==6 and args1.image==nil 
    		and (args0.noimage==false or args1.image_page)
    		and (args0.mimeType=='application/pdf' or args0.mimeType=='image/vnd.djvu')  then
    		args1.image = args0.pagename
    	end
    
    	return args1
    end
    
    
    -- ==================================================
    -- === External functions ===========================
    -- ==================================================
    local p = {}
    
    -- ===========================================================================
    -- === Version of the function to be called from other LUA codes
    -- ===========================================================================
    function p.create_infobox(args0)
    	local lang = args0.lang  -- user's language
    	local cats, cats1, cats2 = '', '', ''         -- categories 
    	local str, data, sdc
    	
    	-- ===========================================================================
    	-- === Step 1: clean up of template arguments "args0"
    	-- ===========================================================================
    	args0 = artcore.clean_input_parameters(args0)
    	if not args0.wikidata and args0.namespace==14 then
    		local entity = mw.wikibase.getEntity()
    		if entity then -- category page connected to Wikidata through a sitelink
    			args0.wikidata = entity.id
    			cats1 = '[[Category:Artwork categories with sitelinks to Wikidata]]'
    			local qid = getProperty(entity, 'P301') -- follow wikidata's item redirect-like properties
    			if qid then -- item had "category's main topic (P301)" -> use it
    				args0.wikidata = qid
    			end
    		end
    	end
    	
    	-- ===========================================================================
    	-- === Step 2: one by one merge Wikidata and creator data
    	-- ===========================================================================
    	local wikidata_temp = args0.wikidata -- Wikidata from template
    	sdc = {}
    	if not args0.no_sdc and args0.namespace==6 then -- if file namespace
    		sdc, cats2 = harvest_SDC(args0, lang) -- harvest Structured Data on Commons
    		if (args0.sdc_source==false) or (args0.source) then
    			-- get source from SDC only if this is NOT the artwork infobox of {{Art Photo}} template
    			-- also skip sdc.source if we have args0.source, otherwise we might and up with 2 source fields
    			sdc.source_ = nil
    			args0.sdc_source = nil -- not needed anymore
    		end
    		args0.source_    = args0.source_    or sdc.source_    -- if source not provided than get it from SDC
    		args0.wikidata   = args0.wikidata   or sdc.wikidata   -- if Wikidata not provided than get it from P6243 ("digital representation of") 
    		args0.image_page = args0.image_page or sdc.image_page -- title image page for multipage book files 
    	end
    	data, cats = harvest_wikidata(wikidata_temp, sdc.wikidata, lang, args0.namespace, args0.infobox, args0.pagename)
    	cats = (cats or '') .. (cats1 or '') .. (cats2 or '')
    	-- based on the template type determine the meaning of "creator" 
    	if args0.infobox=='photograph' then
    		data.photographer, data.photographer_id = data.creator, data.creator_id
    	elseif args0.infobox=='book' then
    		--data.author, data.author_id = data.creator, data.creator_id
    	else -- args0.infobox=='artwork'
    		data.artist, data.artist_id = data.creator, data.creator_id
    	end
    	
    	-- mass merge (prioritize local values)  
    	local args = {}
    	local fields = { 'artist', 'artist_id', 'author', 'author_id', 'architect', 'designer', 'illustrator', 
    		'publisher', 'editor', 'translator', 'printer', 'photographer', 'photographer_id', 'wikisource', 'wikiquote',
    		'title', 'object_type', 'authority',  'image', 'id', 'homecat', 'coordinates', 'genre', 'subject', 'image_page',
    		'date', 'medium', 'name', 'depicted_people', 'depicted_place', 'place_of_creation', 'place_of_discovery', 
    		'dimensions', 'institution', 'department', 'references', 'object_history', 	'exhibition_history',  'inscriptions', 	
            'place_of_publication', 'publication_date', 'language', 'subtitle', 'series_title', 'volume', 'edition', 'edition_of',
    		'author_of_foreword', 'author_of_afterword','description','other_versions', 'part_of'
    	}
    	for _, field in ipairs( fields ) do 
    		args[field] = args0[field] or data[field]
    	end
    	-- copy fields only defined locally
    	local fields = { 'wikidata', 'original_description_info', 'original_description', 'biased', 'camera_coord', 'depicted_part',
    		'source', 'source_', 'strict',  'permission', 'demo', 'lang', 'notes', 'credit_line', 'linkback', 'pageoverview', 
    		'other_fields', 'other_fields_1', 'other_fields_2', 'other_fields_3', 'wikidata_cat', 'namespace', 'infobox', 'demo_image',
    		'no_qs'
    	}
    	for _, field in ipairs( fields ) do 
    		args[field] = args0[field] 
    	end
    
    	if args.artist_id and #args.artist_id>1 and args.artist_id==args.author_id and data.artist_id~=data.author_id then
    		-- args.artist_id is string so #args.artist_id is string length. data.artist_id!=data.author_id is to make sure
    		-- both _ids are not coming from Wikidata. Might need to also skip if args0.author has content other than creator template
    		args.author, args.author_id = nil, nil; -- if artist and author are the same than drop one
    		cats = cats .. '[[Category:Artworks with the same artist and author]]\n'
    	end
    	if args0.infobox=='artwork' and args0.photo_date and args0.photographer then
    		cats = cats .. '[[Category:Artworks with photograph information]]\n' 
    	end	
    	if args0.infobox=='book' and args0.publication_date and data.date then
    		-- some magazines have local publication date for specific issue and inception date on Wikidata for the 
    		-- magazine (publication of the first issue) -> ignore the inception date
    		args.date = nil 
    	end
    	
    	-- look for cases where a field like "photographer" has a single creator template with a role
    	-- "photographer" stated in front of it -> strip the role.
    	local LUT = { Q483501='artist', Q482980='author', Q42973='architect', Q5322166='designer', 
    		Q1607826='editor', Q333634='translator', Q644687='illustrator', Q33231='photographer' }    
    	for item, field in pairs( LUT ) do
    		if args[field] and args[field .. '_id'] then 
    			local tag = TagQS.createTag('creator_role', nil, item)
    			local start, finish = mw.ustring.find( args[field], tag, 1, true )
    			if start then -- remove "<b>...</b>: " + tag
    				-- split the string in 2 using the tag as a divider
    				local str1 = mw.ustring.sub( args[field], 1, start-1 ) 
    				local str2 = mw.ustring.sub( args[field],   finish+1 )
    				-- remove bold text at the end of str1
    				local str1 = mw.ustring.gsub( str1, "'''.*''': $" , '' ) 
    				args[field] = str1 .. str2 -- splice 2 halfs
    			end
    		end
    	end
    
    	-- internationalize local object_type string
    	if args0.object_type and args.object_type==args0.object_type then
    		local objectType = require('Module:I18n/objects')._object
    		args.object_type = objectType(args.object_type, nil, lang)
    	end	
    
    	-- convert all empty strings to nils
    	for _, field in ipairs( fields ) do 
    		if args[field] == '' then 
    			args[field] = nil; 
    		end
    	end
    	
    	-- ===========================================================================
    	-- === Step 3: create maintenance categories and render html of the table
    	-- ===========================================================================
    	args = header_customization(args0, data, args)
    	cats = cats .. add_maintenance_categories(args0, args)
    	args.QS = nil;
    	if not args0.no_sdc then
    		cats2, args = add_wikidata_maintenance_categories(args0, args, data)
    		cats = cats .. cats2
    	end
    	if args0.wikidata and (args0.wikidata ~='CREATE') and (args.namespace==6) and not (sdc.digital_representation_of or sdc.main_subject) then
    		if data.object_type_id then
    			for _, typeId in ipairs( data.object_type_id ) do
    				local artwork_type = sdc.artwork_types[typeId]
    				if artwork_type == '2D' then
    					cats = string.format('%s\n[[Category:Artworks missing digital representation of for 2D work |%s]]', cats, args0.wikidata)
    				elseif artwork_type == '3D' then
    					cats = string.format('%s\n[[Category:Artworks missing main subject for 3D work|%s]]', cats, args0.wikidata)
    				-- Can add the other types here later
    				end
    			end
    		end
    	elseif sdc.digital_representation_of or sdc.main_subject_accepted then
    		if data.object_type_id then
    			for _, typeId in ipairs( data.object_type_id ) do
    				local artwork_type = sdc.artwork_types[typeId]
    				if artwork_type and sdc.digital_representation_of then
    					if artwork_type == '2D' then
    						cats = string.format('%s\n[[Category:Artworks digital representation of 2D work|%s]]', cats, sdc.wikidata)
    					elseif artwork_type == '3D' then
    						cats = string.format('%s\n[[Category:Artworks digital representation of 3D work|%s]]', cats, sdc.wikidata)
    					elseif artwork_type == 'both' then
    						cats = string.format('%s\n[[Category:Artworks digital representation of both 2D and 3D work|%s]]', cats, sdc.wikidata)
    					elseif artwork_type == 'unclassified' then
    						cats = string.format('%s\n[[Category:Artworks digital representation of unclassified work|%s]]', cats, sdc.wikidata)
    					elseif artwork_type == 'no' then
    						cats = string.format('%s\n[[Category:Artworks digital representation of not a work of art|%s]]', cats, sdc.wikidata)
    					else
    						cats = string.format('%s\n[[Category:Artworks digital representation of unknown type of work|%s]]', cats, sdc.wikidata)
    					end
    				elseif not artwork_type and sdc.digital_representation_of then
    					cats = string.format('%s\n[[Category:Artworks digital representation of unknown type of work|%s]]', cats, sdc.wikidata)
    				
    				elseif artwork_type and sdc.main_subject_accepted then
    					if artwork_type == '2D' then
    						cats = string.format('%s\n[[Category:Artworks main subject 2D work|%s]]', cats, sdc.wikidata)
    					elseif artwork_type == '3D' then
    						cats = string.format('%s\n[[Category:Artworks main subject 3D work|%s]]', cats, sdc.wikidata)
    					elseif artwork_type == 'both' then
    						cats = string.format('%s\n[[Category:Artworks main subject both 2D and 3D work|%s]]', cats, sdc.wikidata)
    					elseif artwork_type == 'unclassified' then
    						cats = string.format('%s\n[[Category:Artworks main subject unclassified work|%s]]', cats, sdc.wikidata)
    					elseif artwork_type == 'no' then
    						cats = string.format('%s\n[[Category:Artworks main subject not a work of art|%s]]', cats, sdc.wikidata)
    					else
    						cats = string.format('%s\n[[Category:Artworks main subject type of work|%s]]', cats, sdc.wikidata)
    					end
    				elseif not artwork_type and sdc.main_subject_accepted then
    					cats = string.format('%s\n[[Category:Artworks main subject type unknown type of work|%s]]', cats, sdc.wikidata)
    				end
    			end
    		end
    	end
    
    	local results = artcore.build_html(args)
    	return results, cats
    end	
    
    -- ===========================================================================
    -- === Versions of the function to be called from template namespace
    -- === Each template with it's own entry point
    -- ===========================================================================
    function p.artwork(frame)
    	local args  = artcore.read_input_parameters(frame)
    	args.infobox = 'artwork'
    	local cats0 = artcore.verify_input_parameters(args)
    	args.demo_image = 'Noun project - Mona Lisa - in frame.svg' 
    	local results, cats1 = p.create_infobox(args) -- call the inner "core" function	 
    	return results .. cats0 .. cats1
    end
    
    -- ===========================================================================
    function p.photograph(frame)
    	local args  = artcore.read_input_parameters(frame)
    	args.infobox = 'photograph'
    	local cats0 = artcore.verify_input_parameters(args)
    	args.source_ = args.source or args.source_
    	args.source  = nil
    	args.demo_image = 'Breezeicons-actions-22-view-preview.svg' 
    	local results, cats = p.create_infobox(args) -- call the inner "core" function
    	-- most photographs do not have Wikidata id so remove cat "Photographs without Wikidata item"
    	cats = mw.ustring.gsub(cats,'%\n%[%[Category:Photographs without Wikidata item%]%]','')
    	return results .. cats
    end
    
    -- ===========================================================================
    -- This is for when we have photograph template in addition to artwork and we don't want any Wikidata
    function p.photograph_of_object(frame)
    	local args  = artcore.read_input_parameters(frame)
    	args.infobox = 'photograph'
    	local cats0 = artcore.verify_input_parameters(args)
    	args.source_ = args.source or args.source_
    	args.source  = nil
    	args.no_sdc = true
    	local results, cats = p.create_infobox(args) -- call the inner "core" function
    	return results .. cats
    end
    
    -- ===========================================================================
    function p.art_photo(frame)
    	local args = artcore.read_input_parameters(frame)	-- clean up input parameters
    	args.permission  = args.permission or args.photo_license or args.photo_licence  -- additional aliases
    	args.artwork_license = args.artwork_license or args.artwork_licence
    	args.source = args.source or args.source_
    	args.photo_license, args.photo_licence, args.artwork_licence, args.source_ = nil, nil, nil, nil
    	local results = {}
    	
    	-- split input arguments
    	local args2 = {}	
    	local fields = {['date']='photo_date',  description='photo_description', author='photographer', 
    		source='source', other_versions='other_versions', other_fields ='other_fields', 
    		permission='photo_license', permission='permission'
    	}
    	for field1, field2 in pairs( fields ) do 
    		if args[field2] then 
    			args2[field1] = args[field2]
    			args [field2] = nil
    		end
    	end
    	for _, field in ipairs( {'lang', 'demo'} ) do 
    		args2[field] = args[field]
    	end
    	args.permission = args.artwork_license
    	args.artwork_license = nil
    
    	-- create object infobox
    	args.infobox = 'artwork'
    	local cats0 = artcore.verify_input_parameters(args)
    	local header = frame:expandTemplate{ title = 'Section header', args = { ["1"]=args.artwork_header or 'Object', lang=args.lang } }
    	table.insert(results, "===" .. header .. "===")
    	args.demo_image = 'Noun project - Mona Lisa - in frame.svg' 
    	args.strict  = false
    	args.sdc_source = false -- no picking sources from SDC for this infobox
    	local infobox, cats1 = p.create_infobox(args) -- call the inner "core" function	
    	table.insert(results, infobox)
    	
    	--create {{information}} infobox
    	header = frame:expandTemplate{ title = 'Section header', args = { ["1"]='Photograph', lang=args.lang } }
    	table.insert(results, "===" .. header .. "===")
    	args2.strict  = false
    	table.insert(results, Information(args2) )
    	table.insert(results, cats0 .. cats1)
     
    	return table.concat(results, '\n')
    end
    
    -- ===========================================================================
    function p.book(frame)
    	local args = artcore.read_input_parameters(frame)
    	args.infobox               = 'book'
    	args.demo_image            = 'Placeholder book.svg'
    	args.strict                = false
    	args.place_of_publication  = args.place_of_publication or args.city -- book specific aliases
    	args.publication_date      = args.publication_date or args.date
    	args.source_               = args.source or args.source_
    	if args.isbn or args.lccn or args.oclc or args.bnf then
    		local auth_args = { ISBN=args.isbn, LCCN=args.lccn, OCLC=args.oclc, BNF=args.bnf, bare=1 }
    		args.authority = frame:expandTemplate{ title = 'Book authority control', args=auth_args }
    		args.isbn, args.lccn, args.oclc, args.bnf = nil, nil, nil, nil
    	end	
    	args.source, args.city, args.date  = nil, nil, nil
    	local results, cats = p.create_infobox(args) -- call the inner "core" function	 
    	return results .. cats
    end
    
    return p