# Module:Authority control/sandbox

require('Module:No globals')  

local p = {}  
local title = mw.title.getCurrentTitle  
local namespace = title.namespace  
local testcases = (string.sub(title.subpageText,1,9) == 'testcases')  

--==========================================================================  
-- Category functions  
--==========================================================================  

function p.getCatForId( id )  
local catName = ''  
if namespace == 0 then  
catName = 'Wikipedia articles with '..id..' identifiers'  
elseif namespace == 2 and not title.isSubpage then  
catName = 'User pages with '..id..' identifiers'  
else  
catName = 'Miscellaneous pages with '..id..' identifiers'  
end  
return ''..p.redCatLink(catName)  
end  

function p.redCatLink( catName ) --catName == 'Blah', not 'Category:Blah', not ''  
if catName and catName ~= '' and  
testcases == false and  
mw.title.new(catName, 14).exists == false  
then  
return ''  
end  
return ''  
end  

--==========================================================================  
-- Property formatting functions  
--==========================================================================  

function p.aagLink( id )  
--P3372's format regex: \d+ (e.g. 1)  
if not id:match( '^%d+$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'AAG' )  
end  

function p.acmLink( id )  
--P864's format regex: \d{11} (e.g. 12345678901)  
if not id:match( '^%d%d%d%d%d%d%d%d%d%d%d$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'ACM-DL' )  
end  

function p.adbLink( id )  
--P1907's format regex: [a-z][-a-z]+-([1-2]\d|[1-9])\d{0,3} (e.g. barton-sir-edmund-toby-71)  
if not id:match( '^[a-z][-a-z]+-[1-2]%d%d?%d?%d?$' ) and  
not id:match( '^[a-z][-a-z]+-[1-9]%d?%d?%d?$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'ADB' )  
end  

function p.agsaLink( id )  
--P6804's format regex: [1-9]\d* (e.g. 3625)  
if not id:match( '^[1-9]%d*$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'AGSA' )  
end  

function p.autoresuyLink( id )  
--P2558's format regex: [1-9]\d{0,4} (e.g. 12345)  
if not id:match( '^[1-9]%d?%d?%d?%d?$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'autores.uy' )  
end  

function p.awrLink( id )  
--P4186's format regex: (([A-Z]{3}\d{4})|([A-Z]{2}\d{5}))[a-z] (e.g. PR00768b)  
if not id:match( '^[A-Z][A-Z][A-Z]%d%d%d%d[a-z]$' ) and  
not id:match( '^[A-Z][A-Z]%d%d%d%d%d[a-z]$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'AWR' )  
end  

function p.balatLink( id )  
--P3293's format regex: \d+ (e.g. 1)  
if not id:match( '^%d+$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'BALaT' ) --no https as of 9/2019  
end  

function p.bibsysLink( id )  
--P1015's format regex: [1-9]\d* or [1-9](\d{0,8}|\d{12}) (e.g. 1234567890123)  
--TODO: follow up @ d:Property talk:P1015#Discrepancy between the 2 regex constraints or escalate/investigate  
if not id:match( '^[1-9]%d?%d?%d?%d?%d?%d?%d?%d?$' ) and  
not id:match( '^[1-9]%d%d%d%d%d%d%d%d%d%d%d%d$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'BIBSYS' )  
end  

function p.bildLink( id )  
--P2092's format regex: \d+ (e.g. 1)  
if not id:match( '^%d+$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'Bildindex' )  
end  

function p.bncLink( id )  
--P1890's format regex: \d{9} (e.g. 123456789)  
if not id:match( '^%d%d%d%d%d%d%d%d%d$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'BNC' )  
end  

function p.bneLink( id )  
--P950's format regex: (XX|FF|a)\d{4,7}|(bima|bimo|bica|bis[eo]|bivi|Mise|Mimo|Mima)\d{10} (e.g. XX1234567)  
if not id:match( '^[XF][XF]%d%d%d%d%d?%d?%d?$' ) and  
not id:match( '^a%d%d%d%d%d?%d?%d?$' ) and  
not id:match( '^bi[mcsv][aoei]%d%d%d%d%d%d%d%d%d%d$' ) and  
not id:match( '^Mi[sm][eoa]%d%d%d%d%d%d%d%d%d%d$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'BNE' ) --no https as of 9/2019  
end  

function p.bnfLink( id )  
--P268's format regex: \d{8}[0-9bcdfghjkmnpqrstvwxz] (e.g. 123456789)  
if not id:match( '^c?b?%d%d%d%d%d%d%d%d[0-9bcdfghjkmnpqrstvwxz]$' ) then  
return false  
end  
--Add cb prefix if it has been removed  
if not id:match( '^cb.+$' ) then  
id = 'cb'..id  
end  
return ''..id..' (data)'..p.getCatForId( 'BNF' )  
end  

function p.botanistLink( id )  
--P428's format regex: ('t )?(d')?(de )?(la )?(van (der )?)?(Ma?c)?(De)?(Di)?\p{Lu}?C?['\p{Ll}]*([-'. ]*(van )?(y )?(d[ae][nr]?[- ])?(Ma?c)?[\p{Lu}bht]?C?['\p{Ll}]*)*\.? ?f?\.? (e.g. L.)  
--not easily/meaningfully implementable in Lua's regex since "(this)?" is not allowed...  
if not mw.ustring.match( id, "^[%u%l%d%. '-]+$" ) then --better than nothing  
return false  
end  
local id2 = id:gsub(' +', '%%20')  
return *..id..*..p.getCatForId( 'Botanist' )  
end  

function p.bpnLink( id )  
--P651's format regex: \d{6,8} (e.g. 00123456)  
if not id:match( '^%d%d%d%d%d%d%d%d$' ) and --original format regex, changed 8/2019 to  
not id:match( '^0?%d%d%d%d%d%d%d$' ) and --allow 1-2 leading 0s, allowed by the website  
not id:match( '^0?0?%d%d%d%d%d%d$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'BPN' ) --no https as of 9/2019  
end  

function p.canticLink( id )  
--P1273's format regex: a\d{7}[0-9x] (e.g. a10640745)  
if not id:match( '^a%d%d%d%d%d%d%d[%dx]$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'CANTIC' ) --no https as of 10/2019  
end  

function p.ciniiLink( id )  
--P271's format regex: DA\d{7}[\dX] (e.g. DA12345678)  
if not id:match( '^DA%d%d%d%d%d%d%d[%dX]$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'CINII' )  
end  

function p.daaoLink( id )  
--P1707's format regex: [a-z\-]+\d* (e.g. rolf-harris)  
if not id:match( '^[a-z%-]+%d*$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'DAAO' )  
end  

function p.dblpLink( id )  
--P2456's format regex: \d{2,3} /\d+(-\d+)?|[a-z] /[a-zA-Z][0-9A-Za-z]*(-\d+)? (e.g. 123/123)  
if not id:match( '^%d%d%d?/%d+$' ) and  
not id:match( '^%d%d%d?/%d+%-%d+$' ) and  
not id:match( '^[a-z]/[a-zA-Z][0-9A-Za-z]*$' ) and  
not id:match( '^[a-z]/[a-zA-Z][0-9A-Za-z]*%-%d+$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'DBLP' )  
end  

function p.dsiLink( id )  
--P2349's format regex: [1-9]\d* (e.g. 1538)  
if not id:match( '^[1-9]%d*$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'DSI' )  
end  

function p.fnzaLink( id )  
--P6792's format regex: [1-9]\d* (e.g. 9785)  
if not id:match( '^[1-9]%d*$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'FNZA' )  
end  

function p.gndLink( id )  
--P227's format regex: 1[012]?\d{7}[0-9X]|[47]\d{6}-\d|[1-9]\d{0,7}-[0-9X]|3\d{7}[0-9X] (e.g. 4079154-3)  
if not id:match( '^1[012]?%d%d%d%d%d%d%d[0-9X]$' ) and  
not id:match( '^[47]%d%d%d%d%d%d%-%d$' ) and  
not id:match( '^[1-9]%d?%d?%d?%d?%d?%d?%d?%-[0-9X]$' ) and  
not id:match( '^3%d%d%d%d%d%d%d[0-9X]$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'GND' )  
end  

function p.hdsLink( id )  
--P902's format regex: \d{6} (e.g. 050123)  
if not id:match( '^%d%d%d%d%d%d$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'HDS' )  
end  

function p.iaafLink( id )  
--P1146's format regex: [0-9][0-9]* (e.g. 012)  
if not id:match( '^%d+$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'IAAF' )  
end  

function p.iciaLink( id )  
--P1736's format regex: \d+ (e.g. 1)  
if not id:match( '^%d+$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'ICIA' )  
end  

function p.isniLink( id )  
id = p.validateIsni( id ) --e.g. 0000-0000-6653-4145  
if not id then  
return false  
end  
return *..id:sub( 1, 4 )..' '..id:sub( 5, 8 )..' '..id:sub( 9, 12 )..' '..id:sub( 13, 16 )..*..p.getCatForId( 'ISNI' ) --no https as of 9/2019  
end  

function p.jocondeLink( id )  
--P347's format regex: [\-0-9A-Za-z]{11} (e.g. 12345678901)  
local regex = '^'..string.rep('[%-0-9A-Za-z]', 11)..'$'  
if not id:match( regex ) then  
return false  
end  
return *..id..*..p.getCatForId( 'Joconde' )  
end  

function p.kulturnavLink( id )  
--P1248's format regex: [0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12} (e.g. 12345678-1234-1234-1234-1234567890AB)  
if not id:match( '^%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'KULTURNAV' ) --no https as of 9/2019  
end  

function p.lccnLink( id )  
local parts = p.splitLccn( id ) --e.g. n78039510  
if not parts then  
return false  
end  
local lccnType = parts[1] ~= 'sh' and 'names' or 'subjects'  
id = parts[1] .. parts[2] .. p.append( parts[3], '0', 6 )  
return *..id..*..p.getCatForId( 'LCCN' )  
end  

function p.lirLink( id )  
--P886's format regex: \d+ (e.g. 1)  
if not id:match( '^%d+$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'LIR' ) --no https as of 9/2019  
end  

function p.lnbLink( id )  
--P1368's format regex: \d{9} (e.g. 123456789)  
if not id:match( '^%d%d%d%d%d%d%d%d%d$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'LNB' )  
end  

function p.leonoreLink( id )  
--P640's format regex: LH/\d{1,4}/\d{1,3}|19800035/\d{1,4}/\d{1,5}(Bis)?|C/0/\d{1,2} (e.g. LH/2064/18)  
if not id:match( '^LH/%d%d?%d?%d?/%d%d?%d?$' ) and --IDs from LH/1/1 to LH/2794/54 (legionaries)  
not id:match( '^19800035/%d%d?%d?%d?/%d%d?%d?%d?%d?$' ) and --IDs from 19800035/1/1 to 19800035/385/51670 (legionnaires who died 1954-1977 & some who died < 1954)  
not id:match( '^C/0/%d%d?$' ) then --IDs from C/0/1 to C/0/84 (84 famous legionaries)  
return false  
end  
return *..id..*..p.getCatForId( 'Léonore' ) --no https as of 9/2019  
end  

function p.mbaLink( id )  
--P434's format regex: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} (e.g. 12345678-1234-1234-1234-1234567890AB)  
if not id:match( '^%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'MusicBrainz' ) --special category name  
end  

function p.mbareaLink( id )  
--P982's format regex: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} (e.g. 12345678-1234-1234-1234-1234567890AB)  
if not id:match( '^%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'MusicBrainz area' ) --special category name  
end  

function p.mbiLink( id )  
--P1330's format regex: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} (e.g. 12345678-1234-1234-1234-1234567890AB)  
if not id:match( '^%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'MusicBrainz instrument' ) --special category name  
end  

function p.mblLink( id )  
--P966's format regex: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} (e.g. 12345678-1234-1234-1234-1234567890AB)  
if not id:match( '^%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'MusicBrainz label' ) --special category name  
end  

function p.mbpLink( id )  
--P1004's format regex: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} (e.g. 12345678-1234-1234-1234-1234567890AB)  
if not id:match( '^%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'MusicBrainz place' ) --special category name  
end  

function p.mbrgLink( id )  
--P436's format regex: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} (e.g. 12345678-1234-1234-1234-1234567890AB)  
if not id:match( '^%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'MusicBrainz release group' ) --special category name  
end  

function p.mbsLink( id )  
--P1407's format regex: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} (e.g. 12345678-1234-1234-1234-1234567890AB)  
if not id:match( '^%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'MusicBrainz series' ) --special category name  
end  

function p.mbwLink( id )  
--P435's format regex: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} (e.g. 12345678-1234-1234-1234-1234567890AB)  
if not id:match( '^%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'MusicBrainz work' ) --special category name  
end  

function p.mgpLink( id )  
--P549's format regex: \d{1,6} (e.g. 123456)  
if not id:match( '^%d%d?%d?%d?%d?%d?$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'MGP' )  
end  

function p.naraLink( id )  
--P1225's format regex: ^([1-9]\d{0,8})$ (e.g. 123456789)  
if not id:match( '^[1-9]%d?%d?%d?%d?%d?%d?%d?%d?$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'NARA' )  
end  

function p.nclLink( id )  
--P1048's format regex: \d+ (e.g. 1081436)  
if not id:match( '^%d+$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'NCL' ) --no https as of 9/2019  
end  

function p.ndlLink( id )  
--P349's format regex: 0?\d{8} (e.g. 012345678)  
if not id:match( '^0?%d%d%d%d%d%d%d%d$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'NDL' )  
end  

function p.ngvLink( id )  
--P2041's format regex: \d+ (e.g. 12354)  
if not id:match( '^%d+$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'NGV' )  
end  

function p.nkcLink( id )  
--P691's format regex: [a-z]{2,4}[0-9]{2,14} (e.g. abcd12345678901234)  
if not id:match( '^[a-z][a-z][a-z]?[a-z]?%d%d%d?%d?%d?%d?%d?%d?%d?%d?%d?%d?%d?%d?$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'NKC' )  
end  

function p.nlaLink( id )  
--P409's format regex: [1-9][0-9]{0,11} (e.g. 123456789012)  
if not id:match( '^[1-9]%d?%d?%d?%d?%d?%d?%d?%d?%d?%d?%d?$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'NLA' )  
end  

function p.nlgLink( id )  
--P3348's format regex: [1-9]\d* (e.g. 1)  
if not id:match( '^[1-9]%d*$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'NLG' )  
end  

function p.nliLink( id )  
--P949's format regex: \d{9} (e.g. 123456789)  
if not id:match( '^%d%d%d%d%d%d%d%d%d$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'NLI' )  
end  

function p.nlkLink( id )  
--P5034's format regex: KA.(19|20).{7} (e.g. KAC201501465)  
if not id:match( '^KA.19.......$' ) and  
not id:match( '^KA.20.......$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'NLK' )  
end  

function p.nlpLink( id )  
--P1695's format regex: 9810[0-9]\d* or A[0-9]{7}[0-9X] (e.g. 9810123456789012345 or A10414836)  
if not id:match( '^9810%d+$' ) and  
not id:match( '^A%d%d%d%d%d%d%d[%dX]$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'NLP' )  
end  

function p.nlrLink( id )  
--P1003's format regex: \d{9} (e.g. 123456789)  
if not id:match( '^%d%d%d%d%d%d%d%d%d$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'NLR' )  
end  

function p.nskLink( id )  
--P1375's format regex: \d{9} (e.g. 123456789)  
if not id:match( '^%d%d%d%d%d%d%d%d%d$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'NSK' ) --no https as of 9/2019  
end  

function p.ntaLink( id )  
--P1006's format regex: \d{8}[\dX] (e.g. 12345678X)  
if not id:match( '^%d%d%d%d%d%d%d%d[%dX]$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'NTA' )  
end  

function p.orcidLink( id )  
id = p.validateIsni( id ) --e.g. 0000-0002-7398-5483  
if not id then  
return false  
end  
id = id:sub( 1, 4 )..'-'..id:sub( 5, 8 )..'-'..id:sub( 9, 12 )..'-'..id:sub( 13, 16 )  
return *..id..*..p.getCatForId( 'ORCID' )  
end  

function p.picLink( id )  
--P2750's format regex: [1-9]\d* (e.g. 1)  
if not id:match( '^[1-9]%d*$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'PIC' )  
end  

function p.ridLink( id )  
--P1053's format regex: [A-Z]{1,3}-\d{4}-20[0-2]\d (e.g. AAS-5150-2020)  
if not id:match( '^[A-Z][A-Z]?[A-Z]?%-%d%d%d%d%-20[0-2]%d$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'RID' )  
end  

function p.reroLink( id )  
--P3065's format regex: 0[1-2]-[A-Z0-9]{1,10} (e.g. 02-A012345678)  
if not id:match( '^0[1-2]%-[A-Z%d][A-Z%d]?[A-Z%d]?[A-Z%d]?[A-Z%d]?[A-Z%d]?[A-Z%d]?[A-Z%d]?[A-Z%d]?[A-Z%d]?$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'RERO' )  
end  

function p.rkdartistsLink( id )  
--P650's format regex: [1-9]\d{0,5} (e.g. 123456)  
if not id:match( '^[1-9]%d?%d?%d?%d?%d?$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'RKDartists' )  
end  

function p.rkdidLink( id )  
--P350's format regex: [1-9]\d{0,5} (e.g. 123456)  
if not id:match( '^[1-9]%d?%d?%d?%d?%d?$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'RKDID' )  
end  

function p.rslLink( id )  
--P947's format regex: \d{1,9} (e.g. 123456789)  
if not id:match( '^%d%d?%d?%d?%d?%d?%d?%d?%d?$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'RSL' ) --no https as of 9/2019  
end  

function p.iccuLink( id )  
--P396's format regex: IT\\ICCU\\(\d{10}|\D\D[\D\d]\D\\\d{6}) (e.g. IT\ICCU\CFIV\000163)  
if not id:match( '^IT\\ICCU\\%d%d%d%d%d%d%d%d%d%d$' ) and  
not id:match( '^IT\\ICCU\\%u%u[%u%d]%u\\%d%d%d%d%d%d$' ) then --legacy: %u used here instead of %D (but the faulty ID cat is empty, out of ~12k uses)  
return false  
end  
return *..id..*..p.getCatForId( 'ICCU' ) end  

function p.selibrLink( id )  
--P906's format regex: [1-9]\d{4,5} (e.g. 123456)  
if not id:match( '^[1-9]%d%d%d%d%d?$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'SELIBR' )  
end  

function p.sikartLink( id )  
--P781's format regex: \d{7,9} (e.g. 123456789)  
if not id:match( '^%d%d%d%d%d%d%d%d?%d?$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'SIKART' ) --no https as of 9/2019  
end  

function p.snacLink( id )  
--P3430's format regex: \d*[A-Za-z][0-9A-Za-z]* (e.g. A)  
if not id:match( '^%d*[A-Za-z][0-9A-Za-z]*$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'SNAC-ID' )  
end  

function p.sudocLink( id )  
--P269's format regex: (\d{8}[\dX]|) (e.g. 026927608)  
if not id:match( '^%d%d%d%d%d%d%d%d[%dxX]$' ) then --legacy: allow lowercase 'x'  
return false  
end  
return *..id..*..p.getCatForId( 'SUDOC' )  
end  

function p.s2authoridLink( id )  
--P4012's format regex: [1-9]\d* (e.g. 1796130)  
if not id:match( '^[1-9]%d*$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'Semantic Scholar author' ) --special category name  
end  

function p.ta98Link( id )  
--P1323's format regex: A\d{2}\.\d\.\d{2}\.\d{3}[FM]? (e.g. A12.3.45.678)  
if not id:match( '^A%d%d%.%d%.%d%d%.%d%d%d[FM]?$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'TA98' )  
end  

function p.tdviaLink( id )  
--P7314's format regex: [a-z/-]+] (e.g. barkan-omer-lutfi)  
if not id:match( '^[a-z/-]+$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'TDVİA' )  
end  

function p.teLink( id )  
--P1693's format regex: E[1-8]\.\d{1,2}\.\d{1,2}\.\d{1,2}\.\d{1}\.\d{1}\.\d{1,3} (e.g. E1.23.45.67.8.9.0)  
local e1, e2 = id:match( '^E([1-8])%.(%d%d?)%.%d%d?%.%d%d?%.%d%.%d%.%d%d?%d?$' )  
if not e1 then  
return false  
end  
local TEnum = 'TEe0'..e1 --no formatter URL in WD, probably due to this complexity  
if e1 == '5' or e1 == '7' then  
if #e2 == 1 then e2 = '0'..e2 end  
TEnum = TEnum..e2  
end  
return *..id..*..p.getCatForId( 'TE' )  
end  

function p.tepapaLink( id )  
--P3544's format regex: \d+ (e.g. 1)  
if not id:match( '^%d+$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'TePapa' )  
end  

function p.thLink( id )  
--P1694's format regex: H\d\.\d{2}\.\d{2}\.\d\.\d{5} (e.g. H1.23.45.6.78901)  
local h1, h2 = id:match( '^H(%d)%.(%d%d)%.%d%d%.%d%.%d%d%d%d%d$' )  
if not h1 then  
return false  
end  
local THnum = 'THh'..h1..h2 --no formatter URL in WD, probably due to this complexity  
return *..id..*..p.getCatForId( 'TH' )  
end  

function p.tlsLink( id )  
local id2 = id:gsub(' +', '_')  
--P1362's format regex: \p{Lu}[\p{L}\d_',\.\-\(\)\*/–]{3,59} (e.g. Abcd)  
local  
local regex = "^%u"..string.rep(class, 3)..string.rep(class.."?", 56).."$"  
if not mw.ustring.match( id2, regex ) then  
return false  
end  
return *..id..*..p.getCatForId( 'TLS' ) --no https as of 9/2019  
end  

function p.troveLink( id )  
--P1315's format regex: [1-9]\d{5,7} (e.g. 12345678)  
if not id:match( '^[1-9]%d%d%d%d%d%d?%d?$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'Trove' )  
end  

function p.ukparlLink( id )  
--P6213's format regex: [a-zA-Z\d]{8} (e.g. AQUupyiR)  
if not id:match( '^[a-zA-Z%d][a-zA-Z%d][a-zA-Z%d][a-zA-Z%d][a-zA-Z%d][a-zA-Z%d][a-zA-Z%d][a-zA-Z%d]$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'UKPARL' )  
end  

function p.ulanLink( id )  
--P245's format regex: 500\d{6} (e.g. 500123456)  
if not id:match( '^500%d%d%d%d%d%d$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'ULAN' )  
end  

function p.uscongressLink( id )  
--P1157's format regex: [A-Z]00[01]\d{3} (e.g. A000123)  
if not id:match( '^[A-Z]00[01]%d%d%d$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'USCongress' ) --no https as of 9/2019  
end  

function p.vcbaLink( id )  
--P8034's format regex: \d{3}\/[1-9]\d{0,5} (e.g. 494/9793)  
if not id:match( '^%d%d%d\/[1-9]%d?%d?%d?%d?%d?$' ) then  
return false  
end  
local id2 = id:gsub('\/', '_')  
return *..id..*..p.getCatForId( 'VcBA' )  
end  

function p.viafLink( id )  
--P214's format regex: [1-9]\d(\d{0,7}|\d{17,20}) (e.g. 123456789, 1234567890123456789012)  
if not id:match( '^[1-9]%d%d?%d?%d?%d?%d?%d?%d?$' ) and  
not id:match( '^[1-9]%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d?%d?%d?$' ) then  
return false  
end  
return *..id..*..p.getCatForId( 'VIAF' )  
end  

--=========================== Helper functions =============================  

function p.append(str, c, length)  
while str:len < length do  
str = c .. str  
end  
return str  
end  

--Returns the ISNI check digit isni must be a string where the 15 first elements are digits, e.g. 0000000066534145  
function p.getIsniCheckDigit( isni )  
local total = 0  
for i = 1, 15 do  
local digit = isni:byte( i ) - 48 --Get integer value  
total = (total + digit) * 2  
end  
local remainder = total % 11  
local result = (12 - remainder) % 11  
if result == 10 then  
return "X"  
end  
return tostring( result )  
end  

--Validate ISNI (and ORCID) and retuns it as a 16 characters string or returns false if it's invalid  
--See http://support.orcid.org/knowledgebase/articles/116780-structure-of-the-orcid-identifier  
function p.validateIsni( id )  
--P213 (ISNI) format regex: [0-9]{4} [0-9]{4} [0-9]{4} [0-9]{3}[0-9X] (e.g. 0000-0000-6653-4145)  
--P496 (ORCID) format regex: 0000-000(1-[5-9]|2-[0-9]|3-[0-4])\d{3}-\d{3}[\dX] (e.g. 0000-0002-7398-5483)  
id = id:gsub( '[ %-]', '' ):upper  
if not id:match( '^%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d[%dX]$' ) then  
return false  
end  
if p.getIsniCheckDigit( id ) ~= string.char( id:byte( 16 ) ) then  
return false  
end  
return id  
end  

function p.splitLccn( id )  
--P244's format regex: (n|nb|nr|no|ns|sh)([4-9][0-9]|00|20[0-1][0-9])[0-9]{6} (e.g. n78039510)  
if id:match( '^%l%l?%l?%d%d%d%d%d%d%d%d%d?%d?$' ) then  
id = id:gsub( '^(%l+)(%d+)(%d%d%d%d%d%d)$', '%1/%2/%3' )  
end  
if id:match( '^%l%l?%l?/%d%d%d?%d?/%d+$' ) then  
return mw.text.split( id, '/' )  
end  
return false  
end  

--==========================================================================  
-- Wikidata, navigation bar, and documentation functions  
--==========================================================================  

function p.getIdsFromWikidata( itemId, property )  
local ids = {}  
local statements = mw.wikibase.getBestStatements( itemId, property )  
if statements then  
for _, statement in ipairs( statements ) do  
if statement.mainsnak.datavalue then  
table.insert( ids, statement.mainsnak.datavalue.value )  
end  
end  
end  
return ids  
end  

function p.matchesWikidataRequirements( itemId, reqs )  
for _, group in ipairs( reqs ) do  
local property = 'P'..group[1]  
local qid = group[2]  
local statements = mw.wikibase.getBestStatements( itemId, property )  
if statements then  
for _, statement in ipairs( statements ) do  
if statement.mainsnak.datavalue then  
if statement.mainsnak.datavalue.value['numeric-id'] == qid then  
return true  
end end end end end  
return false  
end  

function p.createRow( id, label, rawValue, link, withUid, specialCat )  
if link then  
if withUid then  
return '*<span>'..label..' <span>'..link..'</span></span>\n'  
end  
return '*<span>'..label..' '..link..'</span>\n'  
end  

local catName = 'Wikipedia articles with faulty '..(specialCat or id)..' identifiers'  
return '* <span>The '..id..' id '..rawValue..' is not valid.</span>'..p.redCatLink(catName)..'\n'  
end  

-- Creates a human-readable standalone wikitable version of p.conf, and tracking categories with page counts, for use in the documentation  
function p.docConfTable  
local wikiTable = '{|\n'..  
'! rowspan=2 | Parameter\n'..  
'! rowspan=2 | Label\n'..  
'! rowspan=2; data-sort-type=number | Wikidata property\n'..  
'! colspan=4 | Tracking categories and page counts\n'..  
'|-\n'..  
'! '.. 'Articles\n'..  
'! '.. 'User pages\n'..  
'! '.. 'Misc. pages\n'..  
'! '..'Faulty IDs\n'..  
'|-\n'  

local lang = mw.getContentLanguage  
for _, conf in pairs( p.conf ) do  
local param, link, pid = conf[1], conf[2], conf[3]  
local category = conf.category or param  
local args = { id = 'f', pid }  
local wpl = :expandTemplate{ title = 'Wikidata property link', args = args }  
--cats  
local articleCat = 'Wikipedia articles with '..category..' identifiers'  
local userCat = 'User pages with '..category..' identifiers'  
local miscCat = 'Miscellaneous pages with '..category..' identifiers'  
local faultyCat = 'Wikipedia articles with faulty '..category..' identifiers'  
--counts  
local articleCount = lang:formatNum( mw.site.stats.pagesInCategory(articleCat, 'pages') )  
local userCount = lang:formatNum( mw.site.stats.pagesInCategory(userCat, 'pages') )  
local miscCount = lang:formatNum( mw.site.stats.pagesInCategory(miscCat, 'pages') )  
local faultyCount = lang:formatNum( mw.site.stats.pagesInCategory(faultyCat, 'pages') )  
--concat  
wikiTable = wikiTable..'\n'..  
'|-\n'..  
'||'..param..  
'||'..link..  
'||data-sort-value='..pid..'|'..wpl..  
'|||'..articleCount..''..  
'|||'.. userCount..''..  
'|||'.. miscCount..''..  
'|||'.. faultyCount..''  
end  
return wikiTable..'\n|}'  
end  

--==========================================================================  
-- Configuration  
--==========================================================================  

-- If a specific "(identifier) redirect" exists for an identifier, please route through this particular redirect rather than linking directly to the target page. This reduces clutter in "What links here" and improves reverse lookup of articles where a manifestation of this particular identifier is used.  

-- Check that the Wikidata item has this property-->value before adding it  
local reqs = {}  

-- Parameter format: { 'parameter name', 'label', propertyId # in Wikidata, formatting/validation function }  
p.conf = {  
{ 'AAG', 'AAG', 3372, p.aagLink },  
{ 'ACM-DL', 'ACM DL', 864, p.acmLink },  
{ 'ADB', 'ADB', 1907, p.adbLink },  
{ 'AGSA', 'AGSA', 6804, p.agsaLink },  
{ 'autores.uy', 'autores.uy', 2558, p.autoresuyLink },  
{ 'AWR', 'AWR', 4186, p.awrLink },  
{ 'BALaT', 'BALaT', 3293, p.balatLink },  
{ 'BIBSYS', 'BIBSYS', 1015, p.bibsysLink },  
{ 'Bildindex', 'Bildindex', 2092, p.bildLink },  
{ 'BNC', 'BNC', 1890, p.bncLink },  
{ 'BNE', 'BNE', 950, p.bneLink },  
{ 'BNF', 'BNF', 268, p.bnfLink },  
{ 'Botanist', 'Botanist', 428, p.botanistLink },  
{ 'BPN', 'BPN', 651, p.bpnLink },  
{ 'CANTIC', 'CANTIC', 1273, p.canticLink },  
{ 'CINII', 'CiNii', 271, p.ciniiLink },  
{ 'DAAO', 'DAAO', 1707, p.daaoLink },  
{ 'DBLP', 'DBLP', 2456, p.dblpLink },  
{ 'DSI', 'DSI', 2349, p.dsiLink },  
{ 'FNZA', 'FNZA', 6792, p.fnzaLink },  
{ 'GND', 'GND', 227, p.gndLink },  
{ 'HDS', 'HDS', 902, p.hdsLink },  
{ 'IAAF', 'World Athletics', 1146, p.iaafLink },  
{ 'ICCU', 'ICCU', 396, p.iccuLink }, --formerly SBN  
{ 'ICIA', 'ICIA', 1736, p.iciaLink },  
{ 'ISNI', 'ISNI', 213, p.isniLink },  
{ 'Joconde', 'Joconde' , 347, p.jocondeLink },  
{ 'KULTURNAV', 'KulturNav', 1248, p.kulturnavLink },  
{ 'LCCN', 'LCCN', 244, p.lccnLink },  
{ 'LIR', 'LIR', 886, p.lirLink },  
{ 'LNB', 'LNB', 1368, p.lnbLink },  
{ 'Léonore', 'Léonore', 640, p.leonoreLink },  
{ 'MBA', 'MusicBrainz', 434, p.mbaLink, category = 'MusicBrainz' }, --special category name  
{ 'MBAREA', 'MusicBrainz', 982, p.mbareaLink, category = 'MusicBrainz area' }, --special category name  
{ 'MBI', 'MusicBrainz', 1330, p.mbiLink, category = 'MusicBrainz instrument' }, --special category name  
{ 'MBL', 'MusicBrainz', 966, p.mblLink, category = 'MusicBrainz label' }, --special category name  
{ 'MBP', 'MusicBrainz', 1004, p.mbpLink, category = 'MusicBrainz place' }, --special category name  
{ 'MBRG', 'MusicBrainz release group', 436, p.mbrgLink, category = 'MusicBrainz release group' }, --special category name  
{ 'MBS', 'MusicBrainz', 1407, p.mbsLink, category = 'MusicBrainz series' }, --special category name  
{ 'MBW', 'MusicBrainz work', 435, p.mbwLink, category = 'MusicBrainz work' }, --special category name  
{ 'MGP', 'MGP', 549, p.mgpLink },  
{ 'NARA', 'NARA', 1225, p.naraLink },  
{ 'NCL', 'NCL', 1048, p.nclLink },  
{ 'NDL', 'NDL', 349, p.ndlLink },  
{ 'NGV', 'NGV', 2041, p.ngvLink },  
{ 'NKC', 'NKC', 691, p.nkcLink },  
{ 'NLA', 'NLA', 409, p.nlaLink },  
{ 'NLG', 'NLG', 3348, p.nlgLink },  
{ 'NLI', 'NLI', 949, p.nliLink },  
{ 'NLK', 'NLK', 5034, p.nlkLink },  
{ 'NLP', 'NLP', 1695, p.nlpLink },  
{ 'NLR', 'NLR', 1003, p.nlrLink },  
{ 'NSK', 'NSK', 1375, p.nskLink },  
{ 'NTA', 'NTA', 1006, p.ntaLink },  
{ 'ORCID', 'ORCID', 496, p.orcidLink },  
{ 'PIC', 'PIC', 2750, p.picLink },  
{ 'RID', 'ResearcherID', 1053, p.ridLink },  
{ 'RERO', 'RERO', 3065, p.reroLink },  
{ 'RKDartists', 'RKD', 650, p.rkdartistsLink },  
{ 'RKDID', 'RKDimages ID', 350, p.rkdidLink },  
{ 'RSL', 'RSL', 947, p.rslLink },  
{ 'SELIBR', 'SELIBR', 906, p.selibrLink },  
{ 'SIKART', 'SIKART', 781, p.sikartLink },  
{ 'SNAC-ID', 'SNAC', 3430, p.snacLink },  
{ 'SUDOC', 'SUDOC', 269, p.sudocLink },  
{ 'S2AuthorId', 'S2AuthorId', 4012, p.s2authoridLink, category = 'Semantic Scholar author' }, --special category name  
{ 'TA98', 'TA98', 1323, p.ta98Link },  
{ 'TDVİA', 'TDVİA', 7314, p.tdviaLink },  
{ 'TE', 'TE', 1693, p.teLink },  
{ 'TePapa', 'TePapa', 3544, p.tepapaLink },  
{ 'TH', 'TH', 1694, p.thLink },  
{ 'TLS', 'TLS', 1362, p.tlsLink },  
{ 'Trove', 'Trove', 1315, p.troveLink }, --formerly NLA-person  
{ 'UKPARL', 'UKPARL', 6213, p.ukparlLink },  
{ 'ULAN', 'ULAN', 245, p.ulanLink },  
{ 'USCongress', 'US Congress', 1157, p.uscongressLink },  
{ 'VcBA', 'VcBA', 8034, p.vcbaLink },  
{ 'VIAF', 'VIAF', 214, p.viafLink },  
{ 'WORLDCATID', 'WorldCat Identities', 7859, nil },  
}  

-- Legitimate aliases to p.conf, for convenience  
-- Format: { 'alias', 'parameter name in p.conf' }  
p.aliases = {  
{ 'MusicBrainz', 'MBA' },  
{ 'MusicBrainz artist', 'MBA' },  
{ 'MusicBrainz label', 'MBL' },  
{ 'MusicBrainz release group', 'MBRG' },  
{ 'MusicBrainz work', 'MBW' },  
{ 'Leonore', 'Léonore' }, --alias name without diacritics  
{ 'leonore', 'Léonore' }, --lowercase variant without diacritics  
{ 'SBN', 'ICCU' }, --SBN alias to be deprecated at a later stage  
{ 'TDVIA', 'TDVİA' }, --alias name without diacritics  
{ 'tdvia', 'TDVİA' }, --lowercase variant without diacritics  
}  

-- Deprecated aliases to p.conf; tracked in  
-- Format: { 'deprecated parameter name', 'replacement parameter name in p.conf' }  
p.deprecated = {  
{ 'GKD', 'GND' },  
{ 'PND', 'GND' },  
{ 'RLS', 'RSL' },  
{ 'SWD', 'GND' },  
{ 'NARA-organization', 'NARA' },  
{ 'NARA-person', 'NARA' },  
}  

--==========================================================================  
-- Main  
--==========================================================================  

function p.authorityControl  
local resolveEntity = require( "Module:ResolveEntityId" )  
local parentArgs = :getParent.args  
local elements = {} --create/insert rows later  
local worldcatCat = ''  
local suppressedIdCat = ''  
local deprecatedIdCat = ''  

--Redirect aliases to proper parameter names  
for _, a in pairs( p.aliases ) do  
local alias, param = a[1], a[2]  
if (parentArgs[param] == nil or parentArgs[param] == '') and parentArgs[alias] then  
parentArgs[param] = parentArgs[alias]  
end  
end  

--Redirect deprecated parameters to proper parameter names, and assign tracking cat  
for _, d in pairs( p.deprecated ) do  
local dep, param = d[1], d[2]  
if (parentArgs[param] == nil or parentArgs[param] == '') and parentArgs[dep] then  
parentArgs[param] = parentArgs[dep]  
if namespace == 0 then  
deprecatedIdCat = ''  
end  
end  
end  

--Use QID= parameter for testing/example purposes only  
local itemId = nil  
if namespace ~= 0 then  
local qid = parentArgs['qid'] or parentArgs['QID']  
if qid then  
itemId = 'Q'..mw.ustring.gsub(qid, '^[Qq]', '')  
itemId = resolveEntity._id(itemId) --nil if unresolvable  
end  
else  
itemId = mw.wikibase.getEntityIdForCurrentPage  
end  

--Wikidata fallback if requested  
if itemId then  
for _, params in ipairs( p.conf ) do  
if params[3] > 0 then  
local val = parentArgs[mw.ustring.lower(params[1])] or parentArgs[params[1]]  
if val == nil or val == '' then  
local canUseWikidata = nil  
if reqs[params[1]] then  
canUseWikidata = p.matchesWikidataRequirements( itemId, reqs[params[1]] )  
else  
canUseWikidata = true  
end  
if canUseWikidata then  
local wikidataIds = p.getIdsFromWikidata( itemId, 'P'..params[3] )  
if wikidataIds[1] then  
if val == '' and (namespace == 0 or testcases) then  
suppressedIdCat = '[[Category:Wikipedia articles with suppressed authority control identifiers|'..params[1]..']]'  
else  
parentArgs[params[1]] = wikidataIds[1]  
end end end end end end end  

--Configured rows  
local rct = 0  
for _, params in ipairs( p.conf ) do  
local val = parentArgs[mw.ustring.lower(params[1])] or parentArgs[params[1]]  
if val and val ~= '' and type(params[4]) == 'function' then  
table.insert( elements, p.createRow( params[1], params[2]..':', val, params[4]( val ), true, params.category ) )  
rct = rct + 1  
end  
end  

--WorldCat  
local worldcatId = parentArgs['worldcatid'] or parentArgs['WORLDCATID']  
if worldcatId and worldcatId ~= '' then --if WORLDCATID present & unsuppressed  
table.insert( elements, p.createRow( 'WORLDCATID', *, worldcatId, 'WorldCat Identities: 'PATH')..' '..worldcatId..*, false ) ) --Validation?  
worldcatCat = ''  
elseif worldcatId == nil then --if WORLDCATID absent but unsuppressed  
local viafId = parentArgs['viaf'] or parentArgs['VIAF']  
local lccnId = parentArgs['lccn'] or parentArgs['LCCN']  
if viafId and viafId ~= '' and p.viafLink( viafId ) then --VIAF must be present, unsuppressed, & validated  
table.insert( elements, p.createRow( 'VIAF', *, viafId, 'WorldCat Identities (via VIAF): '..viafId..*, false ) )  
if namespace == 0 then  
worldcatCat = ''  
end  
elseif lccnId and lccnId ~= '' and p.lccnLink( lccnId ) then --LCCN must be present, unsuppressed, & validated  
local lccnParts = p.splitLccn( lccnId )  
if lccnParts and lccnParts[1] ~= 'sh' then  
local lccnIdFmtd = lccnParts[1]..lccnParts[2]..'-'..lccnParts[3]  
table.insert( elements, p.createRow( 'LCCN', *, lccnId, 'WorldCat Identities (via LCCN): '..lccnIdFmtd..*, false ) )  
if namespace == 0 then  
worldcatCat = ''  
end  
end  
end  
elseif worldcatId == '' then --if WORLDCATID suppressed  
suppressedIdCat = ''  
end  

local Navbox = require('Module:Navbox')  
local elementsCat = ''  
if rct == 0 or rct >= 25 then  
local eCat = 'AC with '..rct..' elements'  
elementsCat = ''..p.redCatLink(eCat)  
end  

local outString = ''  
if #elements > 0 then  
local args = { pid = 'identifiers' } -- #target the list of identifiers  
if testcases and itemId then args = { pid = 'identifiers', qid = itemId } end --expensive  
local pencil = :expandTemplate{ title = 'EditAtWikidata', args = args}  
outString = Navbox._navbox( {  
name = 'Authority control',  
navbox,  
body,  
group1 = ''..pencil,  
list1 = table.concat( elements )  
} )  
end  

local auxCats = worldcatCat .. elementsCat .. suppressedIdCat .. deprecatedIdCat  
if testcases then  
auxCats = mw.ustring.gsub(auxCats, '(%[%[)(Category)', '%1:%2') --for easier checking  
end  
outString = outString .. auxCats  
if namespace ~= 0 then  
outString = mw.ustring.gsub(outString, '(%[%[)(Category:Wikipedia articles)', '%1:%2') --by definition  
end  

return outString  
end  

return p