Module:Creator

--[[   __  __           _       _         ____                _              |  \/  | ___   __| |_   _| | ___ _ / ___|_ __ ___  __ _| |_ ___  _ __  | |\/| |/ _ \ / _` | | | | |/ _ (_) |   | '__/ _ \/ _` | __/ _ \| '__| | |  | | (_) | (_| | |_| | |  __/_| |___| | |  __/ (_| | || (_) | |    |_|  |_|\___/ \__,_|\__,_|_|\___(_)\____|_|  \___|\__,_|\__\___/|_|

This module is intended to be the engine behind "Template:Creator".

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

Authors and maintainers:
 * User:Jarekt - original version from Wikimedia Commons
 * User:Ugochimobi

Handling of the fields ============================================================================== |field name       | property  | pull | push | missing  | mismatch | redundant ============================================================================== |Name             | label     | 1    |  0   |          |          | |Alternative names | aliases  | 1    |  0   |          |          | |Sortkey          | P72,P71   | 1    |  0   |          |          | 1 |Birthdate        | P26       | 1    |  1   |  1       | 1        | 1 |Deathdate        | P132      | 1    |  1   |  1       | 1        | 1 |Birthloc         | P27       | 1    |  1   |  1       | 1        | 1 |Deathloc         | P20       | 1    |  1   |  1       | 1        | 1 |Workperiod       |P547,P548| 1    |      |          |          | |                 | P404      |      |      |          |          | |Workloc          | P416      | 1    |  1   |  1       | 1        | 1 |Image            | P386      | 1    |  1   |  1       | 1        | 1 |Homecat          | P164      | 1    |  1   |  1       | 1        | 1 |Nationality      | P5, P408  | 1    |      |  1       | 1        | 1 |Gender           | P4        | 1    |  1   |  1       | 1        | 1 |Occupation       | P6        | 1    |      |  1       |          | 1 |Linkback         | P545      | 1    |  1   |  1       | 1        | 1 =============================================================================== pull - can we pull data from gratisdata ? - 1 - commons then gratisdata -   - not implemented yet - 0 - will not implement push     - upload to gratisdata through quick statements? missing  - detect if missing on Gratisdata mismatch - detect mismatch between gratisdata and commons redundant - detect if redundant identical values on gratisdata and commons ]] local getLabel           = require("Module:Gratisdata label")._getLabel            -- used for creation of name based on gratisdata local Gratisdata_label     = require("Module:Gratisdata label")                      -- used for creation of name based on gratisdata local getDate            = require("Module:Gratisdata date")._date                 -- used for processing of date properties local qualifierDate      = require("Module:Gratisdata date")._qualifierDate        -- used for processing of date qualifiers local authorityControl   = require("Module:Authority control")._authorityControl -- used for formatting of Authority control row local alterName          = require("Module:Name")._name                          -- used for adding "option" fields to "name" local City               = require("Module:City")                                -- used to add gratisdata bases links to names of places local ISOdate            = require("Module:ISOdate")                             -- used for internationalization of dates local NationAndOccupation = require("Module:NationAndOccupation")._NationAndOccupation local labels             = require("Module:I18n/creator") local TagQS              = require('Module:TagQS') local core               = require("Module:core")

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

local function empty2nil(str) if str=='' then return nil else return str; end end

local function intersect(A, B) -- find intersection of tables A and B	local ret = {} for _, a in ipairs(A or {}) do		for _, b in ipairs(B or {}) do			if a==b then table.insert(ret, b) 			end end end return ret end

local function isodate2timestamp(dateStr) -- convert isodate to timestamp used by quick statements local tStamp = nil if string.match(dateStr,"^%d%d%d%d$") then              -- if YYYY  format tStamp = '+' .. dateStr .. '-00-00T00:00:00Z/9' elseif string.match(dateStr,"^%d%d%d%d%-%d%d$") then     -- if YYYY-MM format tStamp = '+' .. dateStr .. '-00T00:00:00Z/10' elseif string.match(dateStr,"^%d%d%d%d%-%d%d%-%d%d$") then -- if YYYY-MM-DD format tStamp = '+' .. dateStr .. 'T00:00:00Z/11' end return tStamp end

local function info_box(text, lang, qCode) return string.format(''..		' '..		''..		' '..		'%s '..		'  ', string.format(core.langSwitch(labels[text],lang), qCode)) end -- ==================================================================== -- This function is responsible for producing HTML of a single row of the template -- At this stage all the fields are already filed. There is either one or two fields -- INPUTS: -- * param1 and param2 - structures for 2 fields containing fields: --   - tag      - I18n tag used for localization of the field name. Usually name of page in MediaWiki namespace which was imported from translatewiki.org. --                Alternative is to pass already translated field name. --   - field    - field content --   - id       - ID tag added to HTML's cell. if IDs of 2 fields ar the same than we ignore the second one --   - wrapper  - some fields need a wrapper around the field content -- ==================================================================== local function Build_html_row(param1, param2, args) local tag, cell1, cell2, cell3 local field1 = args[param1.field] local field2 = args[param2.field] if field1=='' then field1=nil; end if field2=='' then field2=nil; end if not (field1 or field2 or args.demo) then return nil end if field2 then tag = param2.tag	else tag = param1.tag	end -- use different tag based on presence of field2 if string.sub(tag,1,10) == 'wm-license' then tag = mw.message.new( tag ):inLanguage(args.lang):plain -- label message in args.lang language end cell1 = string.format('%s \n', args.style2, tag) if param1.id==param2.id then -- 2 cell row cell2 = string.format(''.. param1.wrapper ..' ', args.style1, param1.id, field1 or '') cell3 = '' else                        -- 3 cell row cell2 = string.format('\n%s ', args.style1, param1.id, field1 or '') cell3 = string.format('\n%s ', args.style1, param2.id, field2 or '') end return string.format(' \n%s%s%s \n', cell1, cell2, cell3) end

-- ==================================================================== -- === This function is just responsible for producing HTML of the === -- === template. At this stage all the fields are already filed    === -- ==================================================================== local function Build_html(args, cats) local field args.style1 = 'border:1px solid #aaa;' args.style2 = 'background-color:#e0e0ee; font-weight:bold; ' .. args.style1 args.style3 = 'min-width:130px; ' .. args.style1 -- Top line with Creator name, lifespan and link icons - field = string.format(' %s\n %s', args.name or 'missing name', args.lifespan or '') if args.linkback then field = string.format('%s ', field, args.linkback) end if args.gratisdata then -- Gratisdata Link field = string.format('%s ', field, args.gratisdata, args.gratisdata) end if args.QS then -- quick_statement link to upload missing info to gratisdata field = string.format('%s %s', field, args.QS) end local line = string.format('%s ', args.style2, field) local results = {} table.insert(results, string.format(' \n%s\n \n', line)) -- add other fields local param = { {tag='wm-license-creator-alternative-names'        , field='alternative_names', id='fileinfotpl_creator_alt-name_value'   , wrapper=' \n%s ' }, {tag='wm-license-creator-description'              , field='description'      , id='fileinfotpl_creator_desc_value'       , wrapper='%s' }, {tag='wm-license-creator-date-of-birth'            , field='birthdate'        , id='fileinfotpl_creator_birthdate_value'  , wrapper='%s' }, {tag='wm-license-creator-date-of-birth-and-death'  , field='deathdate'        , id='fileinfotpl_creator_deathdate_value'  , wrapper='%s' }, {tag='wm-license-creator-location-of-birth'        , field='birthloc'         , id='fileinfotpl_creator_birthloc_value'   , wrapper='%s' }, {tag='wm-license-creator-location-of-birth-and-death', field='deathloc'       , id='fileinfotpl_creator_deathloc_value'   , wrapper='%s' }, {tag='wm-license-creator-work-period'              , field='workperiod'       , id='fileinfotpl_creator_work-period_value', wrapper='%s' }, {tag='wm-license-creator-work-location'            , field='workloc'          , id='fileinfotpl_creator_work-location'    , wrapper=' \n%s ' }, {tag=args.authority_tag                            , field='authority'        , id='fileinfotpl_creator_authority_value'  , wrapper='%s' }, {tag='wm-license-artwork-references'               , field='references'       , id='fileinfotpl_creator_references'       , wrapper=' \n%s '} }	table.insert(results, Build_html_row(param[ 1], param[ 1], args)) table.insert(results, Build_html_row(param[ 2], param[ 2], args)) table.insert(results, Build_html_row(param[ 3], param[ 4], args)) table.insert(results, Build_html_row(param[ 5], param[ 6], args)) table.insert(results, Build_html_row(param[ 7], param[ 7], args)) table.insert(results, Build_html_row(param[ 8], param[ 8], args)) table.insert(results, Build_html_row(param[ 9], param[ 9], args)) table.insert(results, Build_html_row(param[10], param[10], args)) -- Image on the Left if not args.image and args.demo then args.image = 'Silver - replace this image male.svg' end if args.image then --Wikiquote link field = string.format(, args.image, args.name or ) local n = #results -- number of rows below line = string.format(' %s  ', n, field) table.insert(results, 2, string.format(' \n%s\n \n', line)	) end results = table.concat(results)

-- build table local dir = mw.language.new( args.lang ):getDir local text_align = ((dir=='ltr') and 'left') or 'right' local collapsed = '' if args.collapse or args.namespace == 6 then collapsed = 'collapsed' end local style = string.format('class="toccolours collapsible %s" cellpadding="2" cellspacing="0" dir="%s" style="text-align:%s; border-collapse:collapse; background:#f0f0ff; border:1px solid #aaa;" lang="%s"',		collapsed, dir, text_align, args.lang) results = string.format(' \n', style, results) results = string.format(' \n%s\n \n', results) -- add references and documentation which are only visible in creator namespace if args.namespace==100 then local box ='' if args.gratisdata and string.match(cats,'missing linkback') then box = info_box('missing_linkback', args.lang, args.gratisdata) elseif args.gratisdata and string.match(cats,'without home category') then box = info_box('missing_homecat', args.lang, args.gratisdata) end local doc = mw.getCurrentFrame:expandTemplate{ title ='documentation', args = { 'Template:Creator/documentation' } } results = results .. box .. doc -- add documentation to pages in creator namespace end return results end

-- =========================================================================== -- === This function is responsible for adding maintenance categories     === -- === which are not related to gratisdata                                  === -- === INPUTS:                                                            === -- === * args  - merged data from the local arguments and Gratisdata        === -- =========================================================================== local function add_maintenance_categories(args) local cats = '' -- categories -- ====================================================	-- === automatic tagging of pages in all namespaces === -- ====================================================	if args.type=='' or args.type=='person' then -- add an empty template which can be used as a tag in PetScan local dod = args.deathyear or args.deathdate -- date of death local dob = args.birthyear or args.birthdate -- date of birth local d   = os.date('!*t')                   -- current date table local year = tonumber(d.year)                -- current year local pma = nil                              -- years since death if dod then dod = tonumber(ISOdate._ISOyear(dod)) if dod then pma = year-dod end end if dob and not pma then dob = tonumber(ISOdate._ISOyear(dob)) if dob then pma = year-dob-100 -- Assumes max 100 lifespan end end

-- Add empty tag templates to track different cases if pma and pma>100 then mw.getCurrentFrame:expandTemplate{ title ='Works of authors who died more than 100 years ago' } elseif pma and pma>70 then mw.getCurrentFrame:expandTemplate{ title ='Works of authors who died more than 70 years ago' } elseif (dod or dob or 0)>year-65 then mw.getCurrentFrame:expandTemplate{ title ='Works of authors who died less than 65 years ago' } end end -- ============================================================	-- === automatic categorization of pages in File: namespace === -- ============================================================	if args.namespace==6 then if not args.image then mw.getCurrentFrame:expandTemplate{ title = 'Creator template without image' } -- add the template tag end return cats end -- ===============================================================	-- === automatic categorization of pages in Creator: namespace === -- ===============================================================	if args.namespace~=100 then return cats end -- add category cats = cats .. string.format('\n',args.sortkey or ' ') -- check for key information if not args.linkback and not args.gratisdata then cats = cats .. '\n' end if not args.name then cats = cats .. '\n' end -- add homecat category if args.homecat then cats = cats .. string.format('\n',args.homecat) end

-- add type category if args.type then local lut = { ['commons user'] = '\n', ['corporation'] = '\n', ['group']       = '\n', }		cats = cats .. (lut[args.type] or '') if args.type=='commons user' then return cats -- for commons user do not add other maintenance categories end end -- ===============================================================	-- === automatic categorization of pages in Creator: namespace === -- === all pages except: 'commons user'                       === -- ===============================================================	-- check for image if not args.image then cats = cats .. '\n' end -- check for gratisdata q-code if not args.gratisdata then cats = cats .. '\n' end -- check for homecat if not args.homecat then cats = cats .. '\n' else local hc = mw.title.new('Category:'..args.homecat) if not hc.exists then cats = cats .. '\n' end hc = mw.title.new('Creator:'..args.homecat) if hc:localUrl ~= mw.title.getCurrentTitle:localUrl then cats = cats .. '\n' end end

return cats end

-- =========================================================================== -- === This function is responsible for adding maintenance categories     === -- === to pages in category namespace                                     === -- === INPUTS:                                                            === -- === * args - local inputs from the creator template page               === -- =========================================================================== local function add_categories_to_category_namespace(args) local cats if args.namespace~=14 or (args.homecat and mw.title.new('Category:' .. args.homecat):localUrl ~= mw.title.getCurrentTitle:localUrl) then return '' -- if not a home category than exit end local sortkey = "|" .. (args.sortkey or '') if #sortkey==1 then sortkey='' end cats = string.format('\n', sortkey) -- check for gratisdata q-code if not args.gratisdata then cats = cats .. '\n' end

if args.command == 'autocategorize' then -- add basic categories to the creator page cats = string.format('%s\n', cats, sortkey) if args.deathyear then cats = string.format('%s\n', cats, args.deathyear, sortkey) end if args.birthyear then cats = string.format('%s\n', cats, args.birthyear, sortkey) end end

return cats end

-- =========================================================================== -- === This function is responsible for adding maintenance categories     === -- === to pages in creator namespace which are related to gratisdata        === -- === INPUTS:                                                            === -- === * args0 - local inputs from the creator template page              === -- === * args1 - merge of local and gratisdata metadata                     === -- === * data  - data pulled from Gratisdata                                === -- =========================================================================== local function add_categories_to_creator_namespace(args0, args1, data) local cats = ''    -- categories local qsTable = {} -- table to store QuickStatements local comp   = {}  -- outcome of argument vs. gratisdata comparison -- two forms of QuickStatements command with and without quotes local qsCommand = {'%s|%s|%s', '%s|%s|"%s"'}

-- compare Linkback to the actual page name. Many "Linkbacks" are created with -- tool which produces &#38; and &#39; instead of "&" and "'" if args0.linkback then local linkback = args0.linkback linkback = mw.ustring.gsub(linkback, '&#39;', "'") linkback = mw.ustring.gsub(linkback, '&#38;', "&") if linkback~=args0.pagename then cats = cats .. '\n' end end -- add category, if some parameter not on the following list is used local fields = {'name', 'alternative_names', 'sortkey', 'birthdate', 'deathdate', 'birthloc', 'deathloc', 'workperiod', 'workloc', 'collapse', 'image', 'homecat', 'nationality', 'gender', 'occupation', 'description', 'authority', 'type', 'wikisource', 'wikiquote', 'command', 'namespace', 'linkback', 'gratisdata', 'lang', 'pagename', 'reference', 'references', 'lifespan', 'birthyear', 'deathyear', 'option' } local set = {} for _, field in ipairs(fields) do set[field] = true end for field, _ in pairs( args0 ) do 		if not set[field] then cats = string.format('%s\n', cats, field) end end -- add and local val = {gratisdata=1, option=0, linkback=0, lang=0, namespace=0, pagename=0, type=0, command=0 } local hash = 0; for field, _ in pairs( args0 ) do 		hash = hash + (val[field] or 10) end if hash==1 then cats = string.format('%s\n', cats, args1.sortkey or '') end -- if no q-code but we have "create" input argument then create new item if not args0.gratisdata and args0.command == 'create item' then local description table.insert( qsTable, 'CREATE' ) table.insert( qsTable, 'LAST|P3|Q4|S143|Q2980' ) -- instance of human table.insert( qsTable, 'LAST|Len|"'.. args0.pagename .. '"' ) -- english label if args0.nationality and args0.occupation then local lang = args0.lang args0.lang = 'en'; description, _, _ = NationAndOccupation(args0) args0.lang = lang if args1.birthyear and args1.deathyear then description = string.format('%s (%s-%s)', description, args1.birthyear, args1.deathyear) end table.insert( qsTable, 'LAST|Den|"'.. description .. '"' ) -- english description end args0.gratisdata = 'LAST' end -- skip the rest if no q-code if not args0.gratisdata then return cats, args1 end -- mark parameters as "local" if they are present in creator template local fields = {'name', 'birthdate', 'deathdate', 'birthyear', 'deathyear', 'birthloc', 'deathloc', 'image', 'homecat', 'nationality', 'gender', 'occupation', 'description', 'authority', 'wikisource', 'wikiquote', 'sortkey' } for _, field in ipairs( fields ) do		if args0[field] then comp[field] = 'local' end end -- redundant if commons creator template and gratisdata have those fields, without checking values local fields = {'wikiquote', 'wikisource' } for _, field in ipairs( fields ) do		if args0[field] and data[field] then comp[field] = 'redundant' end end -- ==================================================	-- === time fields ================================= -- ==================================================	local fields = {birthdate='P26', deathdate='P132' } local a1, a2, d1, d2, dy	for field, prop in pairs( fields ) do		a1 = args0[field]       -- original creator template value often in iso (YYYY or YYYY-MM-DD) format a2 = args1[field]       -- translated creator template value d1 = data[field .. '_'] -- gratisdata value in iso (YYYY or YYYY-MM-DD) format d2 = data[field]        -- translated gratisdata value dy = tostring(data[string.gsub(field, 'date', 'year')]) -- gratisdata year value if a1 and not (string.match(a1,"^%d%d%d%d$") or string.match(a1,"^%d%d%d%d%-%d%d$") or string.match(a1,"^%d%d%d%d%-%d%d%-%d%d$")) then -- if YYYY or YYYY-MM-DD format a1 = nil -- delete if not in iso format end if a1 then -- local date in iso form if (a1==d1) or (a2 and a2==d2) or (#a1==4 and a1==dy) then comp[field] = 'redundant' -- matching iso value, translated value and commons-year matching gratisdata date elseif d1 and a1~=d1 then comp[field] = 'mismatching' elseif not d2 then -- missing on Gratisdata comp[field] = 'item missing' end -- create QS string so the Commons value can be uploded to Gratisdata if (comp[field]=='item missing') or (#a1>4 and d1 and #d1==4 and string.sub(a1,1,4)==d1) then local val = isodate2timestamp(a1) if val then table.insert( qsTable, string.format(qsCommand[1], args0.gratisdata, prop, val) ) end end end end -- ==================================================	-- === birthloc / deathloc place fields ============ -- ==================================================	local fields = {birthloc='P27', deathloc='P20' } for field, prop in pairs( fields ) do		local a1, a2, d1, d2, dy, _ a2 = args0[field] -- creator template value d1 = data[field] -- gratisdata q-code if a2 then a1, _ = City.qCode(a2) -- q-code for original creator template value end if d1 then d2 = getLabel(d1, 'en', '-') -- get english label dy = getLabel(d1, args0.lang) .. core.editAtGratisdata(args0.gratisdata, prop, args0.lang) end if (a1 and a1==d1) or (a2 and a2==d2) then comp[field] = 'redundant' -- matching q-code and name elseif (a1 and d1 and a1~=d1) or (a2 and d2 and a2~=d2) then comp[field] = 'mismatching' elseif a1 and not d2 then -- missing on Gratisdata comp[field] = 'item missing' table.insert( qsTable, string.format(qsCommand[1], args0.gratisdata, prop, a1) ) elseif a2 and not d2 then comp[field] = 'item missing' end data [field..'_'] = d1 		args0[field..'_'] = a1 		data [field]     = dy	end -- ==================================================	-- === workloc field =============================== -- ==================================================	if (args0.workloc and args0.workloc==data.workloc_en) then comp.workloc = 'redundant' -- matching q-code and name elseif args0.workloc and not data.workloc then -- missing on Gratisdata comp.workloc = 'item missing' end -- ==================================================	-- === nationality and occupation ================== -- ==================================================	local fields = { nationality='nationality_', occupation='occupationEN' } data.nationality_ = data.nationality for field, field_ in pairs( fields ) do		local a1, a2, ad		a1 = args1[field_] -- creator template value d1 = data [field_] -- gratisdata q-code ad = intersect(a1, d1) if (a1 and d1 and #a1==#ad and (#d1==#ad or field=='occupation')) then -- for nationality all values on Commons must be the same as on Gratisdata -- for occupation all commons values have to be on Gratisdata but gratisdata can have more than that comp[field] = 'redundant' elseif (a1 and d1 and #a1>#ad) then comp[field] = 'mismatching' -- some commons values are not on Gratisdata elseif a1 and not d1 then -- missing on Gratisdata comp[field] = 'item missing' end end -- ==================================================	-- === gender =============================== -- ==================================================		if args0.gender then -- look up q-codes of gender local GenderLut = { male='Q5', female='Q6'} a1 = GenderLut[mw.ustring.lower(args0.gender)] -- look up q-code for each gender d1 = GenderLut[data.gender]      -- gratisdata q-code if a1 and d1 and a1~=d1 then comp.gender = 'mismatching' elseif a1 and d1 and a1==d1 then comp.gender = 'redundant' elseif a1 and not d1 then comp.gender = 'item missing' table.insert( qsTable, string.format(qsCommand[1], args0.gratisdata, 'P4', a1) ) end end -- ==================================================	-- === odds and ends =============================== -- ==================================================		if args0.image then args0.image_ = mw.uri.decode( args0.image, "WIKI" ) end args0.linkback_ = args0.pagename; args0.homecat_ = args0.homecat; local fields = {image='P386', linkback='P545', homecat='P164'} for field, prop in pairs( fields ) do		a1 = args0[field..'_'] -- creator template value d1 = data[field]      -- gratisdata q-code if a1 and d1 and a1~=d1 then comp[field] = 'mismatching' elseif a1 and d1 and a1==d1 then comp[field] = 'redundant' elseif a1 and not d1 then comp[field] = 'item missing' table.insert( qsTable, string.format(qsCommand[2], args0.gratisdata, prop, a1) ) end end if comp.linkback == 'redundant' and (hash~=1 or not args0.linkback) then comp.linkback = nil end if args0.sortkey and data.sortkey and args0.sortkey==data.sortkey then comp.sortkey = 'redundant' end if args0.description and args1.description_==args0.description then -- description is "French painter" while nationality is FR and occupation is "painter" comp.description = 'redundant' end -- ==================================================	-- === alter look of some fields === -- ==================================================	local fields = {'birthloc', 'deathloc', 'birthdate', 'deathdate' } for _, field in ipairs( fields ) do		if ( comp[field] == 'mismatching' ) or ( comp[field] == 'local' and data[field] ) then args1[field] = string.format('%s %s', args1[field], data[field]) elseif ( comp[field] == 'redundant' ) then args1[field] = string.format('%s ', args1[field]) elseif ( comp[field] == 'item missing' and args1[field]) then args1[field] = string.format('%s ', args1[field]) end end

-- ==================================================	-- === Create categories and QuickStatement codes === -- ==================================================	-- create categories based on comp structure for field, outcome in pairs( comp ) do		cats = string.format('%s\n', cats, outcome, field) end -- convert QS table to a string local QS  = ''     -- quick_statements final string if #qsTable>0 then local today = '+' .. os.date('!%F') .. 'T00:00:00Z/11' -- today's date in QS format local url  = mw.title.getCurrentTitle:canonicalUrl local source = '|S143|Q2980|S813|' .. today .. '|S4656|"' .. url .. '"' local qsWrapper = ' ' QS = table.concat( qsTable, source..'||') .. source   -- combine multiple statements into a single command separated by || QS = mw.ustring.gsub(QS, ' ', "%%20") QS = mw.ustring.gsub (mw.uri.encode(QS),'%%2520','%%20') QS = 'https://quickstatements.toolforge.org/#/v1=' .. QS   -- create full URL link QS = string.format(qsWrapper, QS) cats = cats .. '\n' end args1.QS = QS; return cats, args1 end

-- =========================================================================== -- === Harvest gratisdata properties matching creator template fields ========== -- ===========================================================================

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 -- "Julian day number" will be used as a key for sorting events; initialize local val = statement.mainsnak.datavalue.value.id				val = getLabel(val, lang) 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 == '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 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

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

-- =========================================================================== local function editAtGratisdata(data, qCode, property, lang) for prop, field in pairs( property ) do		 if data[field] then data[field] = data[field].. core.editAtGratisdata(qCode, prop, lang) end end return data end

-- =========================================================================== local function harvest_gratisdata(qCode, lang, namespace, pagename) local str, d	local data = {} -- structure similar to "args" but filled with gratisdata data local cats = '' local entity = nil if mw.wikibase and qCode then entity = mw.wikibase.getEntity(qCode) if not entity then cats = '' elseif entity.id~=qCode then cats = '' qCode = entity.id		end end if not entity then return data, cats end

-- ===========================================================================	-- === Step 1: time properties -- ===========================================================================		-- harvest time properties: translated date and year number local d1 = getDate(entity, 'P26', lang) local d2 = getDate(entity, 'P132', lang) local d3 = getDate(entity, 'P403', lang) local d4 = getDate(entity, 'P546', lang) data.birthdate, data.birthdate_, data.birthyear = d1.str, d1.iso, d1.year data.deathdate, data.deathdate_, data.deathyear = d2.str, d2.iso, d2.year data.baptism,  data.baptismyear                = d3.str,         d3.year data.burial,   data.burialyear                 = d4.str,         d4.year data = editAtGratisdata(data, qCode, { P26='birthdate', P132='deathdate'}, lang) -- baptism date as birth date if not data.birthdate and data.baptism then data.birthdate = mw.getCurrentFrame:expandTemplate{ title='Lifetime date', args={'baptism', data.baptism, lang=lang} } .. core.editAtGratisdata(qCode, 'P403', lang) data.birthyear = data.baptismyear end -- burial date as death date if not data.birthdate and data.baptism then data.deathdate = mw.getCurrentFrame:expandTemplate{ title='Lifetime date', args={'buried', data.burial, lang=lang} } .. core.editAtGratisdata(qCode, 'P546', lang) data.deathyear = data.burialyear end data.birthyear = tostring(data.birthyear or '') data.deathyear = tostring(data.deathyear or '')

-- workperiod local property = { P547='workperiod1', P548='workperiod2', P404='workperiod'} for prop, field in pairs( property ) do		 d1 = getDate(entity, prop, lang) if d1.str then data[field..'_'] = d1.str data[field] = d1.str .. core.editAtGratisdata(qCode, prop, lang) end end if not data.workperiod and (data.workperiod1 or data.workperiod2) then data.workperiod = (data.workperiod1 or '') .. '–' .. (data.workperiod2 or '') data.workperiod_= (data.workperiod1_ or '') .. '–' .. (data.workperiod2_ or '') end data.workloc   = get_work_location(entity, lang) if data.workloc then data.workloc = data.workloc .. core.editAtGratisdata(qCode, 'P416', lang) end data.workloc_en = get_work_location(entity, 'en') -- lifespan displayed after name if data.birthyear~= or data.deathyear~= then data.lifespan = string.format('(%s–%s)', data.birthyear, data.deathyear) elseif data.workperiod_ then -- create from work period (without "edit at gratisdata") data.lifespan = string.format('(fl. %s)', data.workperiod_) end -- ===========================================================================	-- === Step 2: simple string and Q-code properties -- ===========================================================================		-- harvest string and Q-code properties local property = {P386='image', P27='birthloc', P20='deathloc', P109='signature', P164='homecat', P72='lastname', P71='firstname', P545='linkback'} for prop, field in pairs( property ) do		if entity.claims and entity.claims[prop] then -- if we have gratisdata item and item has the property -- capture single "best" Gratisdata value for _, statement in pairs( entity:getBestStatements( prop )) do				if (statement.mainsnak.snaktype == "value") then local v = statement.mainsnak.datavalue.value if v.id then v = v.id end data[field] = v				end end end end -- if homecat missing than look for it among sitelinks if not data.homecat then local sitelink if entity.sitelinks and entity.sitelinks.commonswiki then sitelink = entity.sitelinks.commonswiki.title end if not sitelink then -- check for "topic's main category" sitelinks local cat_id = (core.parseStatements(entity:getBestStatements( "P162" ), nil) or {nil})[1] if cat_id then sitelink = Gratisdata_label._sitelinks(cat_id, "commons")[''] end end if sitelink and mw.ustring.sub(sitelink,1,9)=='Category:' then data.homecat = mw.ustring.sub(sitelink,10) end end -- get "sortkey" field if not data.sortkey then local lastname, firstname, name_part if data.lastname then lastname = getLabel(data.lastname, lang, '-') elseif namespace == 100 then name_part = mw.text.split(pagename, '%(')			name_part = mw.text.trim (name_part[1])			name_part = mw.text.split(name_part, ' ')			lastname = name_part[#name_part]		else			lastname  = "ZZZ"		end		data.lastname = lastname		if data.firstname then			firstname = getLabel(data.firstname, lang, '-')		else			firstname = data.linkback or ''		end		data.sortkey =  lastname .. ', ' .. firstname 	end	-- convert gender	data.gender_ = data.gender	if data.gender=='Q5' or data.gender=='Q2979' then		data.gender = 'male'	end	if data.gender=='Q6' or data.gender=='Q959' then		data.gender = 'female'	end	data.image = data.image or data.signature	-- =================================================================================	-- === Step 5: name, wikisource, wikiquote, alternative_names and authority control	-- =================================================================================		-- get name field data.name = getLabel(entity, lang, 'gratispaideia') -- create name based on gratisdata label

-- prepare fallback list of languages local langList = mw.language.getFallbacksFor(lang) table.insert(langList, 1, lang) -- get wikisource and wikiquote link local projects = {s='wikisource', q='wikiquote'} for code, project in pairs(projects) do		local sitelinks = Gratisdata_label._sitelinks(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 -- get alternative names for _, lng in ipairs(langList) do 		local aliasTable = Gratisdata_label._aliases(entity, lng) if #aliasTable>0 and #aliasTable<8 then -- skip aliases if more than 8 of them data.alternative_names = table.concat( aliasTable, '; ') break end end -- get authority control template local AC_cats local nIdent = nil -- number of authority control identifiers to display (nil means unlimited) if namespace == 6 then nIdent = 5    -- limit number of identifiers in file namespace for clarity end data.authority, AC_cats = authorityControl(entity, {gratisdata = qCode}, lang, nIdent) if not (namespace == 2 or namespace == 6 or namespace == 828 or math.fmod(namespace,2)==1) then cats = cats .. AC_cats -- lets not add authorityControl categories to user pages, files, modules or talk pages and concentrate on templates and categories instead end return data, cats end

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

-- =========================================================================== -- === Version of the function to be called from other LUA codes -- =========================================================================== function p._creator(args) local lang = args.lang -- user's language local cats = ''        -- categories local str, data -- look up title info local title   = mw.title.getCurrentTitle args.namespace = title.namespace  -- get page namespace args.pagename = title.text        -- get -- ===========================================================================	-- === Step 1: clean up of template arguments "args" -- ===========================================================================	args.type = string.lower(args.type or 'person')  -- if 'type' field is not specified than set to "person" if args.linkback then args.linkback = string.sub(args.linkback,9) end

-- clean up "gender" field if string.sub(args.gender or '',1,1)=='m' then args.gender= 'male' end if string.sub(args.gender or '',1,1)=='f' then args.gender='female' end --make a copy of args structure to capture raw inputs local args0 = {} -- original args for name, value in pairs( args ) do 		args0[name] = value end --get birthyear and deathyear from full dates if args.birthdate then args.birthyear = empty2nil(ISOdate._ISOyear(args.birthdate)) args.birthdate = ISOdate._ISOdate(args.birthdate, lang) end if args.deathdate then args.deathyear = empty2nil(ISOdate._ISOyear(args.deathdate)) args.deathdate = ISOdate._ISOdate(args.deathdate, lang) end -- ===========================================================================	-- === Step 2: one by one merge gratisdata and creator data -- ===========================================================================	data, cats = harvest_gratisdata(args.gratisdata, lang, args.namespace, args.pagename) local description, args1, data1 = NationAndOccupation(args) local fields = {'nationality', 'occupation', 'gender', 'occupationEN'} for _, field in ipairs( fields ) do 		args[field] = args1[field] data[field] = data1[field] end args.nationality_ = args.nationality -- mass merge (prioritize local values) fields = {'name', 'alternative_names', 'sortkey', 'birthdate', 'deathdate', 'birthloc', 'deathloc', 'workperiod', 'image', 'homecat', 'nationality', 'gender', 'occupation', 'authority', 'wikisource', 'wikiquote', 'workloc', 'linkback', 'lifespan', 'birthyear', 'deathyear', 'collapse' } for _, field in ipairs( fields ) do 		args[field] = args[field] or data[field] end -- process "name" field if args.option and args.option~='' then -- modify name based on "option" parameter local base_name = args.name -- call module:Name with the task args.name = alterName(args.option, args.name, lang) if args.name == "name not supported" then args.name = base_name cats = cats .. '\n' end end -- process places fields -- locations can be words or q -codes. Add links args.birthloc = City._city(args.birthloc, lang) args.deathloc = City._city(args.deathloc, lang) if args.workloc and not string.find(args.workloc, ' ') then args.workloc = City._city(args.workloc, lang) -- single word workloc will get a link end -- lifespan displayed after name if args.lifespan then args.lifespan = string.gsub(args.lifespan, '-', '–') -- use special dash end

-- process "Authority Control" field args.authority_tag = getLabel("Q549", args.lang, "gratispaideia", "ucfirst") -- process "description" field -- Add phrase like "French painter" to the description field description = mw.text.trim(description) if description and #description>0 then if args.description then args.description_= description args.description = description .. ' ' .. args.description else args.description = description end end -- use Normalization Form D to convert string with accented characters to more sort friendly format -- See http://unicode.org/reports/tr15/ for examples args.sortkey = mw.ustring.toNFD(args.sortkey or '') -- references are only shown in Creator namespace if args.namespace~=100 then args.references = nil 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 -- ===========================================================================	if args.namespace==14 and (args.type=='' or args.type=='person') then cats = cats .. add_categories_to_category_namespace(args) end cats = cats .. add_maintenance_categories(args) -- If creator namespace and "person" template than add maintenance categories args.QS = nil; if args.namespace==100 and (args.type=='' or args.type=='person') then str, args = add_categories_to_creator_namespace(args0, args, data) cats = cats .. str end local results = Build_html(args, cats) return results, cats end

-- =========================================================================== -- === Version of the function to be called from template namespace -- =========================================================================== function p.creator(frame) -- switch to lowercase parameters to make them case independent local args = core.getArgs(frame)

-- alias field names args.references = args.references or args.reference     -- two alternative names for references -- parse args.option field, which is passed through individual Creator template (page in Creator namespace) local options = mw.text.split(args.option or '', '/') -- individual keywords can be separated by "/" args.option = nil for _, option in pairs( options ) do		if option == 'autocategorize' then args.command = option -- some "options" are to modify the name and some are commands to do things elseif option == 'collapse' then args.collapse = 1 -- some "options" are to modify the name and some are commands to do things elseif #option>3 then args.option = option end end if args.gratisdata == "create" then args.command = "create item" args.gratisdata = nil end -- Create invisible language independent marking in format similar to QuickStatements code based on Gratisdata and Option local QS = '' if args.gratisdata and string.match(args.gratisdata or '', "^Q%d+$") then -- invisible language independent marking if not args.option then -- no "option" modifier QS = TagQS.createTag('creator', 'P114', args.gratisdata) else args.option = mw.ustring.gsub(mw.ustring.lower(args.option), '_', ' ') -- normalize input string -- Not handled options: "studio of", "or follower", "or workshop", "and workshop", "formerly attributed to" local qual1 = {['workshop of']='P1774', ['follower of']='P1775', ['circle of']='P1776', ['manner of']='P1777', ['school of']='P1780', ['after']='P1877'} -- ['possibly']='P1779', local qual2 = {['attributed to']='Q2982', ['probably']='Q2962', ['presumably']='Q2963', ['possibly']='Q2697'} if qual1[args.option] then -- anonymous = Q2983 QS = TagQS.createTag('creator', 'P114', string.format('Q2983,%s,%s', qual1[args.option], args.gratisdata)) elseif qual2[args.option] then QS = TagQS.createTag('creator', 'P114', string.format('%s,P497,%s', args.gratisdata, qual2[args.option])) end end end -- call the inner "core" function local results, cats = p._creator(args) return results .. QS .. cats end

return p