Module:Authority control

From Wikimedia Commons, the free media repository
Jump to: navigation, search
Lua
CodeDiscussionEditHistoryNo test API DocumentationSubpagesLinksTestsResultsSandbox All modules

This module contains the code of the {{Authority control}} template. See its documentation.

Examples

Code Render Comment
{{#invoke:Authority control|authorityControl|Wikidata=Q79822}}
Authority control
Wikidata only
{{#invoke:Authority control|authorityControl|ISNI=0000000121363458|VIAF=64009368|Wikidata=Q79822}}
Authority control
Wikidata and local parameters
{{#invoke:Authority control|authorityControl|LCCN=n/96/32654|PND=130620823|BNF=cb16537008x|VIAF=70042340|ORCID = 0000-0001-5882-6823}}
Authority control
local parameters only
{{#invoke:Authority control|authorityControl|VIAF=96378698|ULAN=500094964|BIBSYS=x02076790}}
Authority control
local parameters only: BIBSYS
{{#invoke:Authority control|authorityControl|GND=118910906|VIAF=77114225|LCCN=nr/93/52276|TSURL=Abraham_Bloemaert|BPN=82561875|ULAN=500007342|ISNI=0000000114754223|BNF=cb124188501}}
Authority control
local parameters only: BPN
{{#invoke:Authority control|authorityControl|VIAF=137632269|ULAN=500025607|KID=1877}}
Authority control
local parameters only: KID
{{#invoke:Authority control|authorityControl|VIAF=167714210 |LCCN=n/80/20283 |GND=1006837-5 |TYP=k |SELIBR=123719 |BNF=cb11865019j |SUDOC=026394677 |ULAN=500125189 |ISNI=000000012260177X |Museofile=M5031 |NLA=35912436}}
Authority control
local parameters only: Museofile, NLA, SUDOC, SELIBR
{{#invoke:Authority control|authorityControl|GND=11881303X|LCCN=n/79/56359|VIAF=61625857|NAID=1332556|ULAN=500026108|ISNI=0000000121357568|BNF=cb123681937|NLA=35000977}}
Authority control
local parameters only: NAID, NLA
{{#invoke:Authority control|authorityControl|VIAF=75121530|NLA=36582360|ULAN=500240971|SELIBR=184709|GND=118529579|LCCN=n/79/22889|ResearcherID=I-6013-2012|TSURL=Albert_Einstein|BNF=cb119016075}}
Authority control
local parameters only: ResearcherID
{{#invoke:Authority control|authorityControl|Wikidata=Q3008609|lang=pl}}
Kontrola autorytatywna
Display in Polish
{{#invoke:Authority control|authorityControl|Wikidata=Q558722|lang=be-tarask}}
Нарматыўны кантроль
Display in Belarusian and fallback on English
{{#invoke:Authority control|authorityControl|Wikidata=Q82925|bare=1 }} "bare" version
Authority control
Authority control

Code

--[[  
 
This module is intended to be the engine behind "Template:Authority control".

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

Authors and maintainers:
* User:Jarekt - original version 

TO Do:
* 2 alternative identifiers?
]]

-- ==================================================
-- === Internal functions ===========================
-- ==================================================
local function getSitelink(item, lang)
	local entity   = mw.wikibase.getEntity(item);
	local langList = mw.language.getFallbacksFor(lang)
	table.insert(langList, 1, lang)
	for _, language in ipairs(langList) do 
		local sitelink = entity:getSitelink(language .. 'wiki')
		if sitelink then 
			return 'w:'.. language ..':'.. sitelink
		end
	end
	return nil
end

local function getIdentifierNameLink( lang, item1, item2, label )
-- Identifier names, like VIAF, LCCN, ISNI, need to be linked to the articles about them if possible
-- Alternativly they can be linked to the articles for institutions that issue them
	local entity, language
	local id_name_URL = nil
	-- 1) try wikipedia sitelink for the identifier in users language and in English
	if item1 and item1 ~='' then
		id_name_URL = getSitelink(item1, lang)
	end
	-- 2) try wikipedia sitelink for the issuedBy property in users language and in English
	if id_name_URL==nil and item2 and item2 ~='' then -- if no link than
		id_name_URL = getSitelink(item2, lang)
	end
	-- 3) if still no links than link to wikidata
	if id_name_URL==nil then	    
		return string.format("[[d:%s|%s]]", item1, label) -- link to wikidata
	else
		return string.format("[[%s|%s]]", id_name_URL, label) -- link to wikipedia
	end
end

-- ==================================================
-- Create link to a single identifier
-- INPUTS:
--  * val - value of the identifier
--  * URL_format - string used to create URL
--  * params - additional parameters related to this type of identifiers. Single item from "conf"
--  * color - color of the link
local function getIdentifierValLink(val, URL_format, params, color)
	if not val or val=='' then
		return ''
	end
	-- check if identifier is in the right format
	local mismatchStr = ''
	local val_ = val:gsub( ' ', '' ) -- remove spaces
	if (params.regexp and not val:match( params.regexp )) then
		mismatchStr  = string.format("<span style=\"color:red\">[does not match %s pattern]</span>", params.regexp)
	elseif (params.verify) then -- check if special "Verify" function is present
		mismatchStr = params.verify(val_) -- add error message if any
	end
	-- identifier_value_URL
	local val_URL = URL_format:gsub('$1', val_)-- URL part of the link for the identifier value
	if color~="blue" then
		val = string.format("<span style=\"color:%s\">%s</span>", color, val)
	end
	return string.format("[%s %s]%s", val_URL, val, mismatchStr) -- link to the identifier's external website
end

-- ==================================================
-- Convert between 2 formats of LCCN: "n/79/63767" -> "n79063767"
-- "n/79/63767" format was used as input by {{Authority Control}} templates
-- "n79063767" format is used by wikidata
local function fixLCCN(id)
  if id then
		local a, b, c = string.match(id, "([%a%d]*)/([%a%d]*)/([%a%d]*)")
		if c then
			local pad = 6 - string.len(c)
			if pad > 0 then
				c = string.rep("0", pad)..c
			end
			id = a..b..c
		end
	end
	return id
end -- fixLCCN

-- ==================================================
-- Verify last "check" digit is correct. ISNI and several other 
-- identifiers use last digit as a verification digit
local function verifyLastDigit( id )
    local total = 0
    for i = 1, #id-1 do
        local digit = id:byte( i ) - 48 --Get integer value
        total = (total + digit) * 2
    end
	
    --local remainder = total % 11
    local lastDigit = tostring((12 - total % 11) % 11)
    if lastDigit == '10' then
        lastDigit = "X"
    end
	
    if (lastDigit == string.sub( id, -1)) then
		return ''
	else
		return "<span style=\"color:red\">[last digit should be " .. lastDigit .. "]</span>"
	end
end

-- ==================================================
-- === Settings =====================================
-- ==================================================
-- In order to add a new identifier associated with Wikidata property do the following 
-- 1) go to [[Template:Authority control/IdentifierList]] and verify that the property number is on the list, if not than edit the page to add it
-- 2) copy code generated at [[Template:Authority control/IdentifierList]] to protected [[Module:Authority control/conf]]
-- 3) add the property to the "conf" list below

-- load 'Module:Authority control/conf' which holds hardwired data derived from Wikidata's properties of 
-- properties
local properties = require('Module:Authority control/conf')

--conf  holds list of identifiers to be displayed
local conf = {
	{label='VIAF'        , property='P214' , lang=''  , regexp='^%d+$' },
	{label='ISNI'        , property='P213' , lang=''  , regexp='^%d%d%d%d %d%d%d%d %d%d%d%d %d%d%d[%dX]$', verify=verifyISNI }, 
	{label='ORCID'       , property='P496' , lang=''  , regexp='^0000%-000[1-3]%-%d%d%d%d%-%d%d%d[%dX]$' },
	{label='ULAN'        , property='P245' , lang=''  , regexp='^500%d%d%d%d%d%d$' }, -- 'Union List of Artist Names' by Getty Research Institute
	{label='ResearcherID', property='P1053', lang=''  , regexp='^[A-Z]%-%d%d%d%d%-[12][90]%d%d$' },
	{label='LCCN'        , property='P244' , lang='en', regexp='^[ns][broshj]?%d%d%d%d%d%d%d%d%d?%d?$' }, -- Library of Congress Authorities
	{label='GND'         , property='P227' , lang='de', regexp='^[%dX%-]+$'},
	{label='SELIBR'      , property='P906' , lang='se', regexp='^%d+$' }, -- National Library of Sweden
	{label='SUDOC'       , property='P269' , lang='fr', regexp='^%d%d%d%d%d%d%d%d[%dxX]$' },    
	{label='BNF'         , property='P268' , lang='fr', regexp='^%d+%w?$' }, -- Bibliothèque nationale de France
	{label='BPN'         , property='P651' , lang='nl', regexp='^%d%d%d%d%d%d%d%d$' }, -- Biografisch Portaal number
	{label='NAID'        , property='P1222', lang='en', regexp='^%d+$' }, -- NARA ID
	{label='Museofile'   , property='P539' , lang='fr', regexp='^M%d%d%d%d%-?%d?%d?$' }, --Ministry of Culture (France)
	{label='NDL'         , property='P349' , lang='ja', regexp='^0?%d%d%d%d%d%d%d%d$' }, -- National Diet Library (of Japan)
	{label='NLA'         , property='P409' , lang='en', regexp='^[1-9]%d*$' }, -- National Library of Australia
	{label='BIBSYS'      , property='P1015', lang='no', regexp='^%d+$' }, -- Norwegian information system BIBSYS
	
	{label='HDS'         , property='P902' , lang='de', regexp='^[1-9]%d%d?%d?%d?%d?$' },  -- Historical Dictionary of Switzerland
	{label='MusicBrainz' , property='P434' , lang='en', regexp='^[-%x]+$' }, 
	{label='MGP'         , property='P549' , lang='en', regexp='^%d%d?%d?%d?%d?%d?$' },  -- Mathematics Genealogy Project
	{label='NCL'         , property='P1048', lang='zh', regexp='^%d+$' },  --National Central Library (Taiwan)
	{label='NKC'         , property='P691' , lang='cs', regexp='^%l%l%l?%l?%d%d%d?%d?%d?%d?%d?%d?%d?%d?%d?%d?%d?%d?$' },  --National Library of the Czech Republic
	{label='Léonore'     , property='P640' , lang='fr', regexp='^[LHC%/%d]+$' }, 
	{label='SBN'         , property='P396' , lang='it'},  -- Istituto Centrale per il Catalogo Unico /  National Library Service (SBN) of Italy
	{label='RLS'         , property='P947' , lang='ru', regexp='^%d%d%d%d%d%d%d%d%d$' },  --Russian State Library
	{label='Botanist'    , property='P428' , lang='en' }, 
	{label='US Congress' , property='P1157', lang='en', regexp='^%u00[01]%d%d%d' }, 
	{label='BNE'         , property='P950' , lang='es', regexp='' }, --Biblioteca Nacional de España
	{label='CALIS'       , property='P270' , lang='zh'}, --China Academic Library and Information 
	{label='CiNii'       , property='P271' , lang='jp', regexp='^DA%d%d%d%d%d%d%d[%dX]$' }, 
	{label='TLS'         , property='P1362', lang='de', regexp='' }, -- Theaterlexikon der Schweiz
	{label='SIKART'      , property='P781' , lang='de', regexp='^%d%d%d%d%d%d%d%d?%d?%d?$' }, -- Swiss
	{label='NLP'         , property='P1695', lang='pl', regexp='' }, -- National Library of Poland
	{label='WGA'         , property='P1882', lang='en', regexp='' }, -- Web Gallery of Art
	{label='KulturNav'   , property='P1248', lang='no', regexp='' }, 
	{label='RKD'         , property='P650' , lang='nl', regexp='^[1-9]%d%d?%d?%d?%d?$' }, --Netherlands Institute for Art History#Online artist pages
	{label='autores.uy'  , property='P2558', lang='uy', regexp='^[1-9]%d?%d?%d?$' },       --autores.uy
	{label='NLI'         , property='P949' , lang='he', regexp='^%d%d%d%d%d%d%d%d%d$' },  --National Library of Israel ID 
}

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

function p.getAuthorityControlTag( lang )
-- get a localized interwiki link to article "Authority Control"
	local field_name = "[[w:en:Help:Authority control|Authority control]]" -- hardwire the default
	if lang~='en' then
		local entity   = mw.wikibase.getEntity("Q36524")         -- get "authority control" entity
		local langList = mw.language.getFallbacksFor(lang)
		table.remove(langList)                                   -- remove "en" from the last position
		table.insert(langList, 1, lang)                          -- add the actual language to the first position
		for _, language in ipairs(langList) do 
			local article = entity:getSitelink(language .. 'wiki') -- wikipadia article title
			local label   = entity:getLabel(lang)                  -- fetch label
			if article~=nil and label~=nil then
				label = mw.language.new(language):ucfirst(label)     -- capitalize first letter
				field_name = string.format('[[w:%s:%s|%s]]', language, article, label) -- interwiki link
			end
		end
	end
	return field_name
end

-- ==================================================
function p._authorityControl(args, lang, length)
-- INPUTS:
--  * args   - stricture with identifier fields: args.VIAF, args.LCCN, etc.
--  * lang   - language code
--  * length - maximum length of the identifier array, or number of identifiers to display
-- OUTPUTS:
--  * results - wikicode string equivalent to {{Authority control|...|bare=1 }} call
--  * cats    - wikicode with maintenance categories

  -- count custom parameters (not pulled from Wikidata)
	local nCustomParam = 0 
	for _,params in ipairs( conf ) do
		if (args[params.label]~=nil) then
			nCustomParam = nCustomParam + 1
		end
	end
	
	-- Get entity - record of wikidata related to a single item
	local cats = '' -- categories (mismatching and missing)
	local q = args.Wikidata or nil
	local entity=nil
	if mw.wikibase and q then
		entity = mw.wikibase.getEntity(q)
		-- Check if this is category item
		if entity and entity.claims and entity.claims.P31 then 
			for _, statement in pairs( entity.claims.P31) do
				if (statement.mainsnak.snaktype == "value") and (statement.mainsnak.datavalue.value['numeric-id'] == 4167836)  then -- P31 == Wikimedia category 
					cats = '[[Category:Wrong wikidata code in authority control data: category item]]'
				end
				if (statement.mainsnak.snaktype == "value") and (statement.mainsnak.datavalue.value['numeric-id'] == 4167410)  then -- P31 == Wikimedia disambiguation page 
					cats = '[[Category:Wrong wikidata code in authority control data: disambiguation item]]'
				end
			end
		end
	end

	--compare provided arguments with Wikidata identifiers
	local data = {} -- structure similar to "args" but filled with wikidata data
	for _,params in ipairs( conf ) do
		local label = params.label
		data[label] = nil
		if entity and entity.claims and params.property and entity.claims[params.property] then -- if we have wikidata item and item has the property
			-- capture all Wikidata values for the identifier
			--for _, statement in pairs( entity.claims[params.property]) do
			for _, statement in pairs( entity:getBestStatements( params.property )) do
				if (statement.mainsnak.snaktype == "value") then -- or if statement.mainsnak.datavalue then 
					local v = statement.mainsnak.datavalue.value
					if data[label]==nil then
						data[label] = v         -- save the first value
					end
					if args[label] == v then  -- match between template and wikidata identifiers
						data[label] = ''        -- ignore identifier from wikidata
						break  
					end
				end
			end
		end
	end

	--Create string with all the identifiers listed
	local results1 = {} -- high priority list
	local results2 = {} -- low  priority list
	properties.P214.item = 'Q54919';   -- hardwire link to VIAF
	local TransStr = "To copy to wikidata: Copy this string. Paste to text editor. Replace spaces with tabs. Click on the link and paste it into the window."
	for _,params in ipairs( conf ) do
		local label = params.label
		local val1 = args[label] -- identifier value provided to the template
		local val2 = data[label] -- identifier value pulled from wikidata
		if val1 or val2 then
			local P = properties[params.property] -- properties of wikidata identifier propertyc
			-- name_link - link for the identifier name
			local name_link = getIdentifierNameLink( lang, P.item, P.issuedBy, label )
			
			-- val_link - identifier value or values
			local transfer = ''
			local val_link
			if not val1 then
				val_link = getIdentifierValLink(val2, P.URL_format, params, 'blue') -- wikidata only no local identifier
			elseif val2=='' then
				val_link = getIdentifierValLink(val1, P.URL_format, params, 'magenta') -- match was found
			elseif val2 then
				val_link = getIdentifierValLink(val1, P.URL_format, params, 'darkgreen') .. "/"..getIdentifierValLink(val2, P.URL_format, params, 'blue')
				cats  = string.format("%s[[Category:Pages using authority control with identifiers mismatching Wikidata‎]]\n", cats)
				transfer  = string.format("(<small><span title=\"%s\">%s[http://tools.wmflabs.org/wikidata-todo/quick_statements.php \t%s\t\"%s\"\tS143\tQ565]</span></small>)", 
				            TransStr, q, params.property, val1)
			elseif not val2 and entity then
				val_link = getIdentifierValLink(val1, P.URL_format, params, 'darkgreen')
				cats  = string.format("%s[[Category:Pages using authority control with identifiers missing from Wikidata‎]]\n", cats)
				transfer  = string.format("(<small><span title=\"%s\">%s[http://tools.wmflabs.org/wikidata-todo/quick_statements.php \t%s\t\"%s\"\tS143\tQ565]</span></small>)", 
				            TransStr, q, params.property, val1)
			else
				val_link = getIdentifierValLink(val1, P.URL_format, params, 'blue') -- local identifier and no wikidata q-code
			end

			-- combine them all
			local lineStr = string.format("\n*%s:&thinsp;<span class=\"uid\">%s</span>%s", name_link, val_link, transfer)
			if (params.lang==lang) or (params.lang=='') then
				table.insert(results1, lineStr) -- add to high priority list
			else
				table.insert(results2, lineStr) -- add to low priority list
			end
		end
	end -- for all sources
	
	-- merge high and low priority lists, trim them if needed and convert to string 
	--table.insert(results1, "\n*End list 1") -- for debuging
	--table.insert(results2, "\n*End list 2")
	for _,v in pairs(results2) do table.insert(results1, v) end
	local results = table.concat(results1, "", 1, math.min(#results1, length or #results1)) 
	
	-- Add Link to wikidata 
	if q then
		local icon = string.format("\n*[[File:Wikidata-logo.svg|20px|wikidata:%s|link=wikidata:%s]]",q,q);
		if results=='' then -- no identifiers: show wikidata item number
			results = string.format("%s: [[d:%s|%s]]",icon,q,q)
		else      -- identifiers: do not show wikidata item number only icon
			results = icon .. results
		end
	end
	
	-- Add link to Worldcat
	if (args.WORLDCATID==nil and (args.LCCN or data.LCCN))  then
		args.WORLDCATID = 'lccn-' .. (args.LCCN or data.LCCN)
	end
	if args.WORLDCATID  then
		results = string.format("%s\n*<span class=\"uid\">[//www.worldcat.org/identities/%s WorldCat]</span>", results, args.WORLDCATID)
	end
	
	-- Add maintenance categories
	if q == nil then
		cats = string.format("%s[[Category:Pages using authority control without Wikidata link]]\n", cats)
	end
	if nCustomParam>0 then
		if cats=='' and entity ~= nil then
			cats = string.format("%s[[Category:Pages using authority control with all identifiers matching Wikidata]]\n", cats)
		end
		if string.find(results, "<span style=\"color:red\">") then 
			cats = string.format("%s[[Category:Pages using authority control with badly formated identifier]]\n", cats)
		end
	end

	-- return results
	if results~='' then -- if there are any results than wrap them in <div> tag
		results  = string.format('<div class="hlist">%s\n</div>', results)
	end
	return results, cats
end

function p.authorityControl(frame)
	-- prepare arguments
	local args = {}
	for name, value in pairs( frame.args ) do 
		if value ~= '' then -- nuke empty strings
			args[name] = value
		end
	end
	for name, value in pairs( frame:getParent().args ) do 
		if value ~= '' then -- nuke empty strings
			args[name] = 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
	local yesno = require('Module:Yesno')
	local bare  = yesno(args.bare,false)         
	
	-- Convert template arguments to the same format as used on wikidata
	if (args.GND == nil or args.GND == '') and args.PND ~= nil and args.PND ~= '' then
		args.GND = args.PND --redirect PND to GND
	end
	if (args.BNF and args.BNF ~= '') then
		args.BNF = string.sub(args.BNF, 3) -- trim first 2 characters
	end
	if (args.ISNI and args.ISNI ~= '') then -- group in sets of 4
		args.ISNI = string.sub(args.ISNI, 1, 4).." "..string.sub(args.ISNI,5,8).." "..string.sub(args.ISNI,9,12).." "..string.sub(args.ISNI,13,16)
	end
	args.LCCN = fixLCCN(args.LCCN)
	args.Wikidata = args.Wikidata or args.Q or args.q or nil
	
	-- call the inner "core" function
	local results, cats = p._authorityControl(args, args.lang, args.length)
	local namespace = mw.title.getCurrentTitle().namespace
	if (namespace == 2 or namespace == 6 or namespace == 828 or math.fmod(namespace,2)==1) then
		cats = '' -- lets not add categories to user pages, files, modules or talk pages and concentrate on templates and categories instead
	end
	
	--package results as a infobox if not "bare"
	if not bare then
		-- Get field name for authority control
		local field_name = p.getAuthorityControlTag(args.lang)

		-- build table
		results = string.format('<tr><td class="type fileinfo-paramfield">%s</td><td>\n%s\n</td></tr>', field_name, results)
		local style = frame:expandTemplate{ title="Infobar-Layout", args={ ["lang"] = args.lang, ["class"] = 'commons-file-information-table' } }
		results = string.format('<table %s>%s</table>\n', style, results)
	else
		results = string.format('\n%s\n', results)
	end
	return results..cats
end

return p