Module:Institution

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

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

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

Authors and maintainers:
 * User:Jarekt - original version

]] require('Module:No globals') -- 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 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 City            = require("Module:City")._city                          -- used to add wikidata bases links to names of places local Coordinates     = require("Module:Coordinates") local labels          = require("Module:I18n/institution") local ISOdate         = require("Module:ISOdate")._ISOdate                    -- used for internationalization of dates local LanguageCodes   = require("Module:LanguageCodes") local core            = require("Module:core")

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

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: -- * param - structures for 2 fields containing fields: --   - field    - field name --   - wrapper  - some fields need a wrapper around the field content -- * args - table with all the parameters -- ==================================================================== local function Build_html_row(param, args) local field = args[param.field] if field=='' then field=nul; end if not (field or args.demo) then return nil end local tag = labels[param.field] if type(tag)=='string' and string.match(tag, "^Q%d+$") then tag = getLabel(tag, args.lang, "-", "ucfirst") else tag = core.langSwitch(tag, args.lang) end local cell1 = string.format('%s \n', args.style2, tag) local cell2 = string.format(''.. param.wrapper ..' ', args.style1, field or '') return string.format(' \n%s%s \n', cell1, cell2) 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 -- get text direction local dir, text_align, odir if mw.language.new( args.lang ):isRTL then dir, text_align, odir = 'rtl', 'right', 'left' else dir, text_align, odir = 'ltr', 'left', 'right' end -- Top line with Creator name, lifespan and link icons - local top = {} table.insert(top, string.format(' %s\n ', args.name or 'missing name') ) table.insert(top, string.format(, args.linkback or ) ) if args.wikidata then -- Wikidata Link table.insert(top, string.format('', args.wikidata, args.wikidata) ) end if args.QS then -- quick_statement link to upload missing info to wikidata table.insert(top, string.format('%s', args.QS) ) end if args.inventory then local formatStr = " (%s) " table.insert(top, string.format(formatStr, odir, odir, args.inventory, core.langSwitch(labels.inventory, args.lang) )) end local line = string.format('%s ', args.style2, table.concat(top, ' ')) local results = {} table.insert(results, string.format(' \n%s\n \n', line)) -- add other fields local param = { {field='native_name' , wrapper='%s'}, {field='parent'     , wrapper='%s'}, {field='location' 	, wrapper=' %s '}, {field='coordinates', wrapper='%s'}, {field='established' , wrapper='%s'}, {field='website'    , wrapper='%s'}, {field='authority'	, wrapper='%s'}, }	for i=1,#param do		table.insert(results, Build_html_row(param[i], args)) end

-- Image on the Left if not args.image and args.demo then args.image = 'MarksburgSilhouette.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 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==106 then local box ='' if args.wikidata and string.match(cats,'missing linkback') then box = info_box('missing_linkback', args.lang, args.wikidata) elseif args.wikidata and string.match(cats,'without home category') then box = info_box('missing_homecat', args.lang, args.wikidata) end local doc = mw.getCurrentFrame:expandTemplate{ title ='documentation', args = { 'Template:Institution/documentation' } } results = results .. box .. doc -- add documentation to pages in creator namespace end return results end

-- =========================================================================== -- === Create coordinate link                                             === -- === INPUTS:                                                            === -- === * lat - latitude of the institution                                === -- === * lon - longitude of the institution                               === -- === * osm - "waypoint" ID gives better www.openstreetmap.org link      === -- === * geopoly - not woring at the moment                               === -- === * lang  - language id of the desired language                      === -- === * namespace - namespace number of the page calling the module      === -- =========================================================================== local function coords(lat, lon, osm, geopoly, namespace, lang) if not lat or not lon then return nil end

-- add OSM polygon, title etc.	local str, prec if namespace == 6 then -- in files str = Coordinates._lat_lon(lat, lon, prec, lang) else local args = { lat=lat, lon=lon, lang=lang, prec="50", mode="institution"} str = Coordinates._GeoHack_link(args) end -- OSM link local osmlink = string.format('//www.openstreetmap.org/index.html?mlat=%s&mlon=%s&zoom=17', lat, lon) if osm then osmlink = string.format('//www.openstreetmap.org/?way=%s', osm) end osmlink = string.format(' ', osmlink) -- Google maps link local gmaplink = string.format('//maps.google.com/maps?hl=%s&q=%s,%s&tab=wl', lang, lat, lon) if geopoly then --gmaplink = string.format('//tools.wmflabs.org/dschwenbot/geo_poly/?t=unnamed&p=%s', mw.text.encode(geopoly)) -- not working at the moment end gmaplink = string.format(' ', gmaplink) return str .. ' ' .. osmlink .. ' ' .. gmaplink end

-- =========================================================================== -- === This function is responsible for adding maintenance categories     === -- === which are not related to wikidata                                  === -- === INPUTS:                                                            === -- === * args  - merged data from the local arguments and Wikidata        === -- =========================================================================== local function add_maintenance_categories(args) local cats = '' -- categories -- if home category than if args.namespace==14 and args.homecat and mw.title.new('Category:' .. args.homecat):localUrl == mw.title.getCurrentTitle:localUrl then cats = cats .. '\n' --cats = cats .. string.format('\n',args.namespace) -- check for wikidata q-code if not args.wikidata then cats = cats .. '\n' end end -- ===============================================================	-- === automatic categorization of pages in Institution: namespace === -- ===============================================================	if args.namespace~=106 then return cats end -- add category cats = cats .. string.format('\n') -- check for key information if not args.linkback and not args.wikidata 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 -- check for image if not args.image then cats = cats .. '\n' end -- check for wikidata q-code if not args.wikidata 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 or not hc.exists then cats = cats .. '\n' end end

return cats end

-- =========================================================================== -- === This function is responsible for adding maintenance categories     === -- === to pages in Institution namespace which are related to wikidata    === -- === INPUTS:                                                            === -- === * args0 - local inputs from the Institution template page          === -- === * args1 - merge of local and wikidata metadata                     === -- === * data  - data pulled from Wikidata                                === -- =========================================================================== local function add_categories_to_institution_namespace(args0, args1, data) local cats = ''    -- categories local qsTable = {} -- table to store QuickStatements local comp   = {}  -- outcome of argument vs. wikidata 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', 'native_name', 'inventory', 'parent', 'location', 'latitude', 'longitude', 'osm', 'geopoly', 'image', 'homecat', 'established', 'website', 'authority', 'stub', 'demo', 'namespace', 'linkback', 'wikidata', 'lang', 'pagename', 'option', 'collapse' } 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 -- skip the rest if no q-code if not args0.wikidata then return cats, args1 end -- add and local val = {wikidata=1, linkback=0, lang=0, namespace=0, pagename=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) end -- mark parameters as "local" if they are present in Institution template local fields = {'name', 'native_name', 'parent', 'location', 'image', 'homecat', 'established', 'website', 'authority', 'linkback'} for _, field in ipairs( fields ) do		if args0[field] then comp[field] = 'local' end end -- redundant if commons Institution template and wikidata have those fields and they are the same local fields = {'established', 'native name‎'} for _, field in ipairs( fields ) do		if args0[field] and data[field] and args0[field]==data[field] then comp[field] = 'redundant' end end

-- redundant name if wikidata has at least English label if args0.name and data.name_ and not string.match(data.name_, "^%[%[d:Q%d+%|.+%]%]") then comp.name = 'redundant' end -- redundant if commons Institution template and wikidata have those fields, without checking values if args0.location and data.location then --comp.location = 'redundant' end -- ==================================================	-- === coordinates ================================= -- ==================================================	-- calculate distance local lat1, lat2, lon1, lon2 = args0.latitude, data.latitude, args0.longitude, data.longitude if lat1 and lat2 then comp.coordinates = 'local' end if lat1 and lat2 and lon1 and lon2 then local dLat = math.rad(lat1-lat2) local dLon = math.rad(lon1-lon2) local d = math.pow(math.sin(dLat/2),2) + math.pow(math.sin(dLon/2),2) * math.cos(math.rad(lat1)) * math.cos(math.rad(lat2)) d = 2 * math.atan2(math.sqrt(d), math.sqrt(1-d)) -- angular distance in radians d = 6371000 * d      -- radians to meters conversion if d<100 then comp.coordinates = 'redundant' else comp.coordinates = 'mismatching' end elseif lat1 and not lat2 and lon1 and not lon2 then comp.coordinates = 'item missing' table.insert( qsTable, string.format(qsCommand[1], args0.wikidata, 'P625', string.format('@%09.5f/%09.5f', lat1, lon1)) ) end

-- ==================================================	-- === website ===================================== -- ==================================================		args0.website_ = args0.website if args0.website then local str = string.match(args0.website, "%[([^ %]]+)[ %]]") if str then -- strip off [] brackets if detected args0.website_ = str end end local a1 = args0.website_   -- creator template value local d1 = data.website     -- wikidata q-code if a1 and d1 and a1==d1 then comp.website = 'redundant' elseif a1 and not d1 then comp.website = 'item missing' table.insert( qsTable, string.format(qsCommand[2], args0.wikidata, 'P856', a1) ) 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='P18', linkback='P1612', homecat='P373'} for field, prop in pairs( fields ) do		a1 = args0[field..'_'] -- creator template value d1 = data[field]      -- wikidata 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.wikidata, prop, a1) ) end end if comp.linkback == 'redundant' and (hash~=1 or not args0.linkback) then comp.linkback = nil 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|Q24731821|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 wikidata properties matching creator template fields       === -- === INPUTS:                                                            === -- === * qCode - item id or a q-code                                      === -- === * lang  - language id of the desired language                      === -- === * namespace - namespace number of the page calling the module      === -- =========================================================================== local function harvest_wikidata(qCode, lang, namespace) -- INPUTS: -- * qCode - item id or a q-code -- * lang - language id of the desired language -- * namespace - namespace number of the page calling the module local str, d, v	local data = {} -- structure similar to "args" but filled with wikidata 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 = '' end end if not entity then return data, cats end

-- ===========================================================================	-- === Step 1: time properties -- ===========================================================================		-- harvest time properties: translated date and year number local prop = 'P1619' local d = getDate(entity, prop, lang) -- date of official opening if not d.str or d.str=='' then prop = 'P571' d = getDate(entity, prop, lang) -- inception date end if d.str then data.established_ = d.iso data.established = d.str .. core.editAtWikidata(entity.id, prop, lang) end -- ===========================================================================	-- === Step 1a: website -- ===========================================================================		-- look for multiple values each with a language code local website = {} local url for _, statement in pairs( entity:getBestStatements( 'P856' )) do		if (statement.mainsnak.snaktype == "value") then url = statement.mainsnak.datavalue.value local lng = nil if statement.qualifiers and statement.qualifiers.P407 then lng = statement.qualifiers.P407[1].datavalue.value.id				lng = LanguageCodes[lng] end website[lng or 'en'] = url end end data.website = core.langSwitch(website, lang) if data.website and url then local label  = mw.ustring.gsub(url, '^https?\:\/\/', "")   -- remove "http://" or "https://" at the beginning label        = mw.ustring.gsub(label, '\/$', "")         -- "/" at the end data.website = string.format("[%s %s]", data.website, label) end if data.website then data.website = data.website .. core.editAtWikidata(entity.id, 'P856', lang) end

-- ===========================================================================	-- === Step 2: simple string and Q-code properties -- ===========================================================================		-- harvest string and Q-code properties local property = {P18='image', P154='logo_image', P373='homecat', P1612='linkback', P1448='official_name', P1705='native_name', P131='city', P276='location', P159='HQ_location', P749='parent', P361='partOf', P17='country'} local addIcon = {P1448=1, P1705=1, P131=1, P276=1, P159=1, P749=1, P361=1} for prop, field in pairs( property ) do		if entity.claims and entity.claims[prop] then -- if we have wikidata item and item has the property -- capture single "best" Wikidata 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 = core.getLabel(v.id, lang) elseif v.text then v = v.text end if addIcon[prop] then v = v .. core.editAtWikidata(entity.id, prop, lang) end data[field] = v 				end end end end data.native_name = data.official_name or data.native_name data.image      = data.logo_image or data.image data.location   = data.city or data.HQ_location or data.location data.parent     = data.parent or data.partOf if data.location and data.country then data.location = mw.text.listToText( {data.location, data.country}, ', ', ', ') end -- ===========================================================================	-- === Step 3: geographic coordinates -- ===========================================================================		local P625 = entity:getBestStatements( 'P625' ) -- coordinate location v = nil if P625[1] and P625[1].mainsnak.datavalue.value.latitude then v = P625[1].mainsnak.datavalue.value end if not v then -- check for location of headquarters location (P159) local P159 = entity:getBestStatements( 'P159' ) if P159[1] and P159[1].qualifiers and P159[1].qualifiers.P625 then v = P159[1].qualifiers.P625[1].datavalue.value end end if v and v.globe == 'http://www.wikidata.org/entity/Q2' then data.latitude, data.longitude = v.latitude, v.longitude end

-- =================================================================================	-- === Step 4: name and authority control -- =================================================================================		-- get name field data.name = getLabel(entity, lang) -- create name based on wikidata label data.name_ = getLabel(entity, 'en') -- try english label label

-- get authority control template local AC_cats data.authority, AC_cats = authorityControl(entity, {wikidata = qCode}, lang, 5) 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._institution(args0) local lang = args0.lang -- user's language local cats = ''        -- categories local str, data -- look up title info args0.namespace = mw.title.getCurrentTitle.namespace   -- get page namespace args0.pagename  = mw.title.getCurrentTitle.text        -- get

-- ===========================================================================	-- === Step 1: clean up of template arguments "args0" -- ===========================================================================	if args0.linkback then args0.linkback = string.sub(args0.linkback,13) end if args0.established then args0.established = ISOdate(args0.established, lang) end if not tonumber(args0.latitude) or not tonumber(args0.longitude) then args0.longitude = nil args0.latitude = nil end -- ===========================================================================	-- === Step 2: one by one merge wikidata and creator data -- ===========================================================================	data, cats = harvest_wikidata(args0.wikidata, lang, args0.namespace) -- mass merge (prioritize local values) local args = {} local fields = {'native_name', 'inventory', 'parent', 'location', 'latitude', 'longitude', 'demo', 'image', 'homecat', 'established', 'website', 'authority', 'linkback', 'wikidata', 'lang', 'namespace', 'collapse' } for _, field in ipairs( fields ) do 		args[field] = args0[field] or data[field] end args.name = data.name if not args.name or string.match(args.name or '', "^%[%[d:Q%d+%|Q.+%]%]") then args.name = args0.name -- no name on Wikidata end --args.name = data.name or args0.name

args.location = City(args.location, lang) args.coordinates = coords(args.latitude, args.longitude, args0.osm, args0.geopoly, args0.namespace, lang) if not args0.latitude and data.latitude then args.coordinates = args.coordinates .. core.editAtWikidata(args0.wikidata, 'P625', args.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 -- ===========================================================================	cats = cats .. add_maintenance_categories(args) -- If institution namespace than add maintenance categories args.QS = nil; if args.namespace==106 then str, args = add_categories_to_institution_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.institution(frame) -- switch to lowercase parameters to make them case independent local args = core.getArgs(frame)

if args.option == 'collapse' then args.collapse = 1 -- some "options" are to modify the name and some are commands to do things args.option = nil end local QS = '' if args.wikidata and string.match(args.wikidata or '', "^Q%d+$") then -- invisible language independent marking QS = string.format(' institution QS:P195,%s \n', args.wikidata) end -- call the inner "core" function local results, cats = p._institution(args) return results .. QS .. cats end

return p