Module:Institution
Documentation for this module may be created at Module:Institution/doc
--[[
__ __ _ _ ___ _ _ _ _ _
| \/ | ___ __| |_ _| | ___ _|_ _|_ __ ___| |_(_) |_ _ _| |_(_) ___ _ __
| |\/| |/ _ \ / _` | | | | |/ _ (_)| || '_ \/ __| __| | __| | | | __| |/ _ \| '_ \
| | | | (_) | (_| | |_| | | __/_ | || | | \__ \ |_| | |_| |_| | |_| | (_) | | | |
|_| |_|\___/ \__,_|\__,_|_|\___(_)___|_| |_|___/\__|_|\__|\__,_|\__|_|\___/|_| |_|
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
]]
local Wikidata2 = require("Module:Wikidata label") -- 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") -- 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") -- used for internationalization of dates
-- ==================================================
-- === Internal functions ===========================
-- ==================================================
local function langSwitch(list,lang)
local langList = mw.language.getFallbacksFor(lang)
table.insert(langList,1,lang)
for i,language in ipairs(langList) do
if list[language] then
return list[language]
end
end
return nil
end
local function info_box(text, lang, qCode)
return string.format('<table class="messagebox plainlinks layouttemplate" style="border-collapse:collapse; border-width:2px; border-style:solid; width:100%%; clear: both; '..
'border-color:#f28500; background:#ffe;direction:ltr; border-left-width: 8px; ">'..
'<tr>'..
'<td class="mbox-image" style="padding-left:.9em;">'..
' [[File:Commons-emblem-issue.svg|class=noviewer|45px]]</td>'..
'<td class="mbox-text" style="">%s</td>'..
'</tr></table>', string.format(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 <span class=...> 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 = Wikidata2._getLabel(tag, args.lang, "-", "ucfirst")
else
tag = langSwitch(tag, args.lang)
end
local cell1 = string.format('<td style="%s">%s</td>\n', args.style2, tag)
local cell2 = string.format('<td colspan="2" style="%s">'.. param.wrapper ..'</td>', args.style1, field or '')
return string.format('<tr valign="top">\n%s%s</tr>\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('<span class="fn" id="creator"><bdi>%s\n</bdi></span>', args.name or 'missing name') )
table.insert(top, string.format('[[File:Blue pencil.svg|15px|link=Institution:%s]]', args.linkback or '') )
if args.wikidata then -- Wikidata Link
table.insert(top, string.format('[[File:Wikidata-logo.svg|20px|wikidata:%s|link=wikidata:%s]]', 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 = "<span style='float:%s; font-size:80%%; margin-%s:20px;'> ([[%s|%s]])</span>"
table.insert(top, string.format(formatStr, odir, odir, args.inventory, langSwitch(labels.inventory, args.lang) ))
end
local line = string.format('<th colspan="4" style="%s">%s</th>', args.style2, table.concat(top, ' '))
local results = {}
table.insert(results, string.format('<tr valign="top">\n%s\n</tr>\n', line))
-- add other fields
local param = {
{field='native_name' , wrapper='%s'},
{field='parent' , wrapper='%s'},
{field='location' , wrapper='<span class="locality">%s</span>'},
{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('[[File:%s|200x140px|alt=%s|class=photo]]', args.image, args.name or '')
local n = #results -- number of rows below
line = string.format('<td rowspan="%i" style="width:120px" id="fileinfotpl_creator_image"><span class="wpImageAnnotatorControl wpImageAnnotatorOff">%s</span></td>', n, field)
table.insert(results, 2, string.format('<tr valign="top">\n%s\n</tr>\n', line) )
end
results = table.concat(results)
-- build table
local collapsed = ''
if args.namespace == 6 then
collapsed = 'collapsed'
end
local style = string.format('class="toccolours collapsible %s" cellpadding="2" cellspacing="0" style="direction:%s; text-align:%s; border-collapse:collapse; background:#f0f0ff; border:1px solid #aaa;" lang="%s"',
collapsed, dir, text_align, args.lang)
results = string.format('<table %s>\n%s\n</table>\n', style, results)
results = string.format('<div class="vcard">\n%s\n</div>\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
local args = { lat=lat, lon=lon, lang=lang, prec="50", mode="institution"}
if namespace == 6 then -- in files
str = Coordinates.lat_lon{args=args}
else
str = Coordinates.GeoHack_link{args=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('<span class="wpImageAnnotatorControl wpImageAnnotatorOff">[[File:Openstreetmap logo.svg|20px|Link to OpenStreetMap|link=%s]]</span>', 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('<span class="wpImageAnnotatorControl wpImageAnnotatorOff">[[File:Google favicon.svg|16px|Link to Google Maps|link=%s]]</span>', 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[[Category:Institution template home categories]]'
--cats = cats .. string.format('\n[[Category:namespace %i]]',args.namespace)
-- check for wikidata q-code
if not args.wikidata then
cats = cats .. '\n[[Category:Institution template home categories without Wikidata link]]'
end
end
-- ===============================================================
-- === automatic categorization of pages in Institution: namespace ===
-- ===============================================================
if args.namespace~=106 then
return cats
end
-- add [[Category:Institution templates]] category
cats = cats .. string.format('\n[[Category:Institution templates]]')
-- check for key information
if not args.linkback and not args.wikidata then
cats = cats .. '\n[[Category:Institution templates without linkback]]'
end
if not args.name then
cats = cats .. '\n[[Category:Institution templates without name]]'
end
-- add homecat category
if args.homecat then
cats = cats .. string.format('\n[[Category:%s]]',args.homecat)
end
-- check for image
if not args.image then
cats = cats .. '\n[[Category:Institution templates without images]]'
end
-- check for wikidata q-code
if not args.wikidata then
cats = cats .. '\n[[Category:Institution templates without Wikidata link]]'
end
-- check for homecat
if not args.homecat then
cats = cats .. '\n[[Category:Institution templates without home category]]'
else
local hc = mw.title.new('Category:'..args.homecat)
if not hc.exists then
cats = cats .. '\n[[Category:Institution templates without home category]]'
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
local today = '+' .. os.date('!%F') .. 'T00:00:00Z/11' -- today's date in QS format
-- two forms of QuickStatements command with and without quotes
local qsCommand = {'%s|%s|%s|S143|Q48552277|S813|' .. today, '%s|%s|"%s"|S143|Q48552277|S813|' .. today}
-- compare Linkback to the actual page name. Many "Linkbacks" are created with
-- tool which produces & and ' instead of "&" and "'"
if args0.linkback then
local linkback = args0.linkback
linkback = mw.ustring.gsub(linkback, ''', "'")
linkback = mw.ustring.gsub(linkback, '&', "&")
if linkback~=args0.pagename then
cats = cats .. '\n[[Category:Institution templates with mismatching linkback]]'
end
end
-- add [[Category:Institution templates with unknown parameter]] 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' }
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[[Category:Institution templates with unknown paramete|%s]]', cats, field)
end
end
-- skip the rest if no q-code
if not args0.wikidata then
return cats, args1
end
-- add [[Category:Wikidata based Institution templates]] and [[Category:Institution templates with Wikidata link: local linkback]]
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[[Category:Institution templates based only on Wikidata]]', 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'}
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, without checking values
local fields = {'website'}
for _, field in ipairs( fields ) do
if args0[field] and data[field] then
comp[field] = 'redundant'
end
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
-- to do {P1448='native_name', P1705='native_name1', P749='parent', P131='location', P276='location1', 'established' }
-- ==================================================
-- === website =====================================
-- ==================================================
args0.website_ = args0.website
if args0.website then
str = string.match(args0.website, "%[([^ %]]+)[ %]]")
if str then
args0.website_ = str
end
end
local a1 = args0.website_ -- creator template value
local d1 = data.website -- wikidata q-code
if a1 and 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[[Category:Institution templates with Wikidata link: %s %s]]', cats, outcome, field)
end
-- convert QS table to a string
local QS = '' -- quick_statements final string
if #qsTable>0 then
local qsHeader = 'https://tools.wmflabs.org/quickstatements/#v1='
local qsWrapper = ' [[File:Commons_to_Wikidata_QuickStatements.svg|15px|link=%s]]'
QS = table.concat( qsTable, '%0A')
QS = mw.ustring.gsub(QS, '|', "%%09")
QS = mw.ustring.gsub(QS, '"', "%%22")
QS = mw.ustring.gsub(QS, ' ', "%%20")
QS = string.format(qsWrapper, qsHeader .. QS)
cats = cats .. '\n[[Category:Institution templates with Wikidata link: quick statements]]'
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 = '[[Category:Institution templates with bad Wikidata link|invalid]]'
end
end
if not entity then
return data, cats
end
-- ===========================================================================
-- === Step 1: time properties
-- ===========================================================================
-- harvest time properties: translated date and year number
local d = getDate(entity, 'P1619' , lang) -- date of official opening
if not d.str or d.str=='' then
d = getDate(entity, 'P571' , lang) -- inception date
end
data.established, data.established_ = d.str, d.iso
-- ===========================================================================
-- === Step 2: simple string and Q-code properties
-- ===========================================================================
-- harvest string and Q-code properties
local property = {P18='image', P154='image1', P373='homecat', P1612='linkback', P1448='native_name', P1705='native_name1',
P1476='title', P131='location', P276='location1', P749='parent', P856='website'}
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 = Wikidata2._getLabel(v.id , lang, "wikipedia")
elseif v.text then
v = v.text
end
data[field] = v
end
end
end
end
data.native_name = data.native_name or data.native_name1
data.location = data.location or data.location1
data.image = data.image or data.image1
-- trim website name
if data.website then
local website = mw.ustring.gsub(data.website , '^https?\:\/\/', "") -- remove "http://" or "https://" at the beginning
website = mw.ustring.gsub(website , '\/$', "") -- "/" at the end
data.website = string.format("[%s %s]", data.website, website)
end
-- ===========================================================================
-- === Step 3: geographic coordinates
-- ===========================================================================
local P625 = entity:getBestStatements( 'P625' ) -- coordinate location
if P625[1] and P625[1].mainsnak.datavalue.value.latitude then
v = P625[1].mainsnak.datavalue.value
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 = Wikidata2._getLabel(entity, lang, "wikipedia") -- create name based on wikidata 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
-- ===========================================================================
-- === 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._ISOdate(args0.established, lang)
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 = {'name', 'native_name', 'inventory', 'parent', 'location', 'latitude', 'longitude', 'demo', 'image',
'homecat', 'established', 'website', 'authority', 'linkback', 'wikidata', 'lang', 'namespace' }
for _, field in ipairs( fields ) do
args[field] = args0[field] or data[field]
end
--args.name = data.name or args0.name
args.location = City._city(args.location, lang)
args.coordinates = coords(args.latitude, args.longitude, args0.osm, args0.geopoly, args0.namespace, lang)
-- 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 = {}
for name, value in pairs( frame:getParent().args ) do
if value ~= '' then -- nuke empty strings
local name1 = string.gsub( string.lower(name), ' ', '_')
args[name1] = value
end
end
for name, value in pairs( frame.args ) do
if value ~= '' then -- nuke empty strings
local name1 = string.gsub( string.lower(name), ' ', '_')
args[name1] = value
end
end
if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then
args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language
end
args.namespace = mw.title.getCurrentTitle().namespace -- get page namespace
args.pagename = mw.title.getCurrentTitle().text -- get {{PAGENAME}}
-- call the inner "core" function
local results, cats = p._institution(args)
return results .. cats
end
return p